질문/답변 페이지 많이 이용해 주세요.

Archive for the ‘Java’ Category.

Tomcat 고가용성 구성 테스트 노트

Tomcat 6.0을 가지고 고가용성, 즉 클러스터링을 따라해 보았습니다.

시나리오.
1. tomcat 인스턴스는 weblogic, oc4j와 비슷하게 제품 바이너리와 인스턴스를 분리해 본다.
2. cluster 구성으로 생성한다.
3. 애플리케이션 배포 시나리오를 생각해 본다.
4. 테스트 애플리케이션 준비
5. Apache mod_proxy_jk 세팅

Tomcat 바이너리와 인스턴스 분리

톰캣 바이너리는 app/apache/apache-tomcat-6.0.32 경로에 있습니다.
인스턴스 경로는 아래와 같은 구성을 시도해봅니다.

app/apache/user_projects - 톰캣 인스턴스묶음용? 경로
app/apache/user_projects/cluster_domain - 톰캣이 만들어질 도메인?
app/apache/user_projects/cluster_domain/bin - 도메인용 스크립트 디렉토리 ( startup.sh, shutdown.sh 존재 )
app/apache/user_projects/cluster_domain/servers - 톰캣 인스턴스용 디렉토리
app/apache/user_projects/cluster_domain/servers/tomcat1 - 'tomcat1'이란 이름의 톰캣 인스턴스
app/apache/user_projects/cluster_domain/servers/tomcat1/bin,conf,logs,temp,webapps,work - 'tomcat1'인스턴스 구성요소
app/apache/user_projects/cluster_domain/servers/tomcat2 - 'tomcat2'이란 이름의 톰캣 인스턴스
app/apache/user_projects/cluster_domain/servers/tomcat2/bin,conf,logs,temp,webapps,work - 'tomcat2'인스턴스 구성요소
app/apache/user_projects/cluster_domain/webapps - 이 도메인에서 사용할 애플리케이션 준비 디렉토리

인스턴스 구성. bin 경로와 conf 경로를 준비합니다.

# DOMAIN_HOME=~/app/apache/user_projects/cluster_domain
# cd $DOMAIN_HOME
# cd servers/tomcat1
# cd bin
# cp ../../../../apache-tomcat-6.0.32/bin/*.sh .
# cd ../conf
# cp -R ../../../../apache-tomcat-6.0.32/conf/* .

인스턴스 구성중 우선 catalina.sh 구성을 수정합니다.

JAVA_HOME=~/app/sun/java/jdk1.6.0_27
JAVA_OPTS="-Djava.security.egd=file:/dev/./urandom -Djava.awt.headless=true -XX:MaxPermSize=256m"
CATALINA_HOME=~/app/apache/apache-tomcat-6.0.32
CATALINA_BASE=~/app/apache/user_projects/cluster_domain/servers/tomcat1

위 구성을 통해 tomcat/lib의 바이너리를 사용하면서, tomcat1경로를 기준으로한 톰캣을 사용할 수 있습니다.

인스턴스 구성중 server.xml 구성을 변경합니다.
server.xml의 경우 포트, cluster 항목, 애플리케이션 경로를 구성합니다.
기본 포트에 tomcat1은 10000번을 더하고, tomcat2는 20000번을 더하여 포트를 정했습니다.(18080, 18443, 18009, 28080, 28443, 28009 )

<?xml version='1.0' encoding='utf-8'?>
<Server port="18005" shutdown="SHUTDOWN">
  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  <Listener className="org.apache.catalina.core.JasperListener" />
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <Listener className="org.apache.catalina.mbeans.ServerLifecycleListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />

  <GlobalNamingResources>
    <Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />
  </GlobalNamingResources>

  <Service name="Catalina">

    <Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
        maxThreads="150" minSpareThreads="4"/>

    <Connector executor="tomcatThreadPool"
               port="18080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="18443" />
    <Connector port="18443" maxThreads="200"
               scheme="https" secure="true" SSLEnabled="true"
               keystoreFile="./conf/.keystore" keystorePass="changeit"
               clientAuth="false" sslProtocol="TLS"/>

    <Connector port="18009" protocol="AJP/1.3" redirectPort="18443" />

    <Engine name="Catalina" defaultHost="localhost" jvmRoute="jvm1">

      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true"
            xmlValidation="false" xmlNamespaceAware="false">
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log." suffix=".log" pattern="common" resolveHosts="false"/>
      </Host>
    </Engine>
  </Service>
</Server>

이 구성을 tomcat1, tomcat2에 맞게 각각 작성하면, 두개의 인스턴스를 구동하고 cluster를 확인할 수 있습니다.

Tomcat 클러스터 구성

톰캣에서 기본으로 제공하고 유일한 멀티캐스트 기반 TCP 클러스터를 사용해 봅니다.

server.xml에서 Engine항목에 구성을 추가합니다.


    <Engine name="Catalina" defaultHost="localhost" jvmRoute="jvm1">

      <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>

    </Engine>

애플리케이션 배포 시나리오

지금까지의 구성을 통해서는 tomcat1, tomcat2 인스턴스에 각각 webapps 경로에 애플리케이션을 배포해야 합니다.

만약 두개의 애플리케이션이 동일한 애플리케이션을 사용하고 같이 배포되어야 한다면, 앞서 준비한 cluster_domain/webapps 에 애플리케이션을 넣어주고,
server.xml의 Host항목에 appBase를 “../../webapps” 형태로 해주면 됩니다.

단, tomcat의 manager같은 경우 앞단의 load balancer를 타고 들어갈 때, 구성이 좀 까다로워지는 관계로 저는 인스턴스별로 애플리케이션이 배포되도록 했습니다.

단, 형태를 domain/webapps에 원본을 두고, 인스턴스는 symbolic link를 사용하는 꼼수를 좀 사용했구요.

테스트 애플리케이션 준비

클러스터링을 활용하는 애플리케이션은 아래 두 파일을 가지는 간단한 예제로 확인할 수 있습니다.

index.jsp

<%@page contentType="text/html; charset=UTF-8" %>
<%
if ( request.getParameter("name") != null ) {
    session.setAttribute("name", request.getParameter("name"));
}
%>
<%=session.getAttribute("name") %>
<form method="POST">
    <input type="text" name="name" />
    <input type="submit" />
</form>

web.xml

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
         version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee">
    <distributable />
</web-app>

distributable 항목을 넣어야 cluster환경에서 쓸 수 있습니다. 그리고 물론 session에 주입하는 객체는 serializable을 구현하도록해야 하고.

이 앱을 준비한 후, http://localhost:18080/test/index.jsp http://localhost:28080/test/index.jsp 형태로 호출하여 세션이 공유되는 것을 확인할 수 있습니다.

Apache mod_proxy_jk 세팅

클러스터된 애플리케이션에 아파치가 밸런싱하여 가는 구성과 각각의 서버로 가능 구성은 각각 아래와 같습니다.

# 로드 밸런싱 구성
<Proxy balancer://cluster>
BalancerMember ajp://localhost:18009 loadfactor=1
BalancerMember ajp://localhost:28009 loadfactor=1
ProxySet lbmethod=bytraffic
</Proxy>

# 로드 밸런서로 보내기
ProxyPass /clusteredapp balancer://cluster/clusteredapp
ProxyPassReverse /clusteredapp balancer://cluster/clusteredapp

# 개별 서버로 보내기
ProxyPass /tomcat1-manager ajp://localhost:18009/tomcat1-manager
ProxyPassReverse /tomcat1-manager ajp://localhost:18009/tomcat1-manager
ProxyPass /tomcat2-manager ajp://localhost:28009/tomcat2-manager
ProxyPassReverse /tomcat2-manager ajp://localhost:28009/tomcat2-manager

weblogic session persistence 설정하기

웹로직에서 웹애플리케이션을 개발하고 배포를 하는 과정에서 재배포 혹은 서버 점검에 따라 인스턴스를 재기동할 경우 사용자 세션이 끊기는 현상이 발생합니다.

이에 대하여 웹로직 배치 기술자 조정을 통해 세션을 유지하는 방법을 찾아보고 글 남깁니다.

보통 세션 퍼시스턴스의 설정은 클러스터 환경에서 서로 다른 서버간 세션 공유에 활용되는 것이 많으리라 생각됩니다.

아래 설정은 단일 서버에서 파일에 세션 상태를 기록하여 세션을 유지하는 예제입니다.

<?xml version='1.0' encoding='UTF-8'?>
<weblogic-web-app xmlns="http://www.bea.com/ns/weblogic/90" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

  <session-descriptor>
    <persistent-store-type>file</persistent-store-type>
  </session-descriptor>

</weblogic-web-app>

세션에 사용자 정의 클래스를 넣을 경우, 해당 클래스를 serializable을 구현하도록 해야 합니다. 그렇지 않을 경우, 세션은 유지되나 해당 세션 애트리뷰트를 참조할 수 없습니다.

그리고 해당 출처는 아래와 같습니다.

http://download.oracle.com/docs/cd/E13222_01/wls/docs81/webapp/sessions.html
위 문서에 설명이 나오며, weblogic.xml 파일 실제 구조는 아래 URL에서 참조하였습니다.

http://cheese.springnote.com/pages/4542851

웹로직의 보편적인 웹개발상의 디버그

웹로직을 사용하면서, 에러의 원인을 쉽게 찾을 수 없을 때, 원인을 추적하는 방법을 나열해 봅니다.

dispatcher forward 사용시 해당 리소스가 없을 경우 추적하기.( 알 수 없는 404 에러 )

웹개발 프레임워크를 사용할 때 주로 개발하는 형태가, controller를 작성하여 비즈니스 로직을 수행한 후, 사용자에게 보여질 페이지(JSP)로 RequestDispatcher.forward 하는 형태이리라 생각됩니다. ( 물론 다른 형태도 많겠지만, RequestDispatcher.forward 하는 형태를 가정한 상황으로 글을 씁니다. )

아래는 그렇게 사용한 java servlet 예제입니다.

RequestDispatcher rd = null;
rd = request.getRequestDispatcher("/main.jsp");
rd.forward(request, response);

이 경우 만약 main.jsp 파일이 존재하지 않는다면, 웹로직은 404 에러 페이지만 출력할 뿐입니다. 그러면 사용자는 호출한 URL이 존재하지 않는 것인지 forward 대상 페이지가 없는 것인지 알 수 없습니다.

tomcat 이나 다른 WAS의 경우는 주로 위 처리에 대한 에러가 로그파일 혹은 웹브라우저에 추적이 가능한 정보를 제공하는데, 웹로직은 404 에러코드만 줄 뿐 추가정보를 제공하지 않았습니다. ( 제 경험 )

이에 추적정보를 찾는 방법을 알았기에 공유합니다.

  1. 해당 서버를 선택합니다.
  2. 디버그 탭을 선택합니다.
  3. weblogic 항목을 펼칩니다.
  4. servlet 항목을 펼칩니다.
  5. DebugURLResolution 항목에 체크표합니다.
  6. 사용 버튼을 눌러 활성화 합니다.

위 과정을 거치면 아래와 같은 로그를 발견할 수 있습니다. 서버의 logs/서버명.log 파일에 기록됩니다.

####<2011. 6. 17 오후 4시 41분 10초 KST> <Debug> <URLResolution> <whity> <AdminServer> <[ACTIVE] ExecuteThread: '1' for queue: 'weblogic.kernel.Default (self-tuning)'> <<anonymous>> <> <> <1308296470912> <BEA-000000> <ServletContext@465470[app:test module:test path:/test spec-version:null]: resolving request with relUri: /main.jsp>
####<2011. 6. 17 오후 4시 41분 10초 KST> <Debug> <URLResolution> <whity> <AdminServer> <[ACTIVE] ExecuteThread: '1' for queue: 'weblogic.kernel.Default (self-tuning)'> <<anonymous>> <> <> <1308296470912> <BEA-000000> <ServletContext@465470[app:test module:test path:/test spec-version:null]: getResourceAsSource() couldn't find source for : /main.jsp>

위 로그 마지막 줄을 통해서 main.jsp 파일을 찾을 수 없다는 내용을 확인할 수 있습니다.

JDeveloper + WebLogic 환경에서 Remote Debugging 사용하기.

JDeveloper와 WebLogic을 사용하는 개발환경에서 Integrated Server를 쓰지 않고, 테스트 혹은 운용서버에 배포후 Remote Debugging을 하는 방법을 간략히 소개합니다.

1. 대상 서버의 Remote Debugging JVM 옵션 세팅.

먼저 JVM에서 Remote Debugging을 받아들이기 위한 설정을 추가합니다.
아래 세팅을 서버의 구동 스크립트에 포함되도록 합니다.
( 저는 편의상 개발서버의 .profile을 사용하고 있습니다. nodemanager 등을 사용중인 경우에는 콘솔에서 세팅 가능하겠습니다. )

# JDK 1.4 이하 기본 형태
# -Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=[port]
# JDK 1.5 이상 기본 형태
# -agentlib:jdwp=transport=dt_socket,server=y,address=[port]
JAVA_OPTIONS=$JAVA_OPTIONS\
"-Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,address=4000,server=y,suspend=n"

2. 대상 서버의 Tunneling 설정.

weblogic 콘솔에서 대상 서버의 Protocols탭에 Enable Tunneling.을 체크합니다.

3. JDeveloper에서 대상 프로젝트에 Remote Debugging 설정.

개발중인 프로젝트에 Remote Debugging을 세팅하는 과정입니다.

  1. ‘Project Properties’에서 ‘Run/Debug/Profile’ 선택.
  2. ‘Run Configurations’에서 복제 혹은 기존 것을 편집.
  3. ‘Launch Settings’에서 ‘Remote Debugging’ 체크.
  4. ‘Tool Settings’ – ‘Debugger’ – ‘Remote’에 아래 설정 추가.
    • Protocol : Attach to JPDA
    • Host : 해당서버 주소
    • Port : 4000 1항에서 설정.

4. Remote Debugging 시작.

프로젝트에서 ‘Start Remote Debugger’를 선택하면, 브레이크포인트 등 원하는 디버깅을 수행할 수 있습니다.

PKCS12 인증서를 Java keystore에 넣기.

PKCS12 인증서를 Java keystore에 넣기.

JDK1.5까지는 제공되지 않았던 기능으로 보이는데, JDK1.6에서는 keytool 명령으로 pkcs12인증서(*.p12)를 java keystore에 넣는 것이 가능하도록 바뀌었습니다.

keytool \
-importkeystore \
-srckeystore cert.p12 \
-destkeystore keystore.jks \
-srcstoretype pkcs12 \
-deststoretype jks \
-srcalias mycert \
-destalias mycert

weblogic nodemanager 명령 요약

weblogic nodemanager 에 접속하여 명령을 하는 것에 대한 간단한 요약.

telnet 접속의 경우.

telnet wls.dgkim.net 5555
DOMAIN #도메인명#
USER weblogic
PASS #패스워드#
SERVER #서버#
KILL # 종료시.
START # 시작시.

ssl 접속의 경우.

openssl s_client -connect wls.dgkim.net:5556
DOMAIN #도메인명#
USER weblogic
PASS #패스워드#
SERVER #서버#
KILL # 종료시.
START # 시작시.

WLST로 DataSource 생성하기.

이번에 웹로직으로 웹프로젝트를 하면서, JDBC 정보를 프로그램에 녹여볼 생각을 했습니다.

예전에 Oracle WAS의 경우 Oracle WAS Deployment Descriptor중 data-sources.xml을 활용하여 애플리케이션 배포에 JDBC정보를 넣어 배포를 했었는데, 웹로직은 약간 달랐습니다.( 그리고 웹로직의 버저닝을 사용하니 뭔가 좀 더 달라지는 듯 했습니다. )

그래서, 애플리케이션 소스에는 넣지 못하더라도, 배포 가능한 소스로 해보고자 방법을 찾던 중 WLST를 사용하는 방법의 예제를 찾았습니다.

그래서 그 방법으로 하기로 하고 아래와 같은 스크립트로 한번에 DataSource를 생성하였습니다.

import sys
from java.lang import System

# function definition BEGIN

# defining addJDBC function
def addJDBC(obj, servers):
    print("")
    print("*** Creating JDBC  ")
    
    # Create the Connection Pool.  The system resource will have
    # generated name of 'PoolName'+"-jdbc"
    
    myResourceName = obj["PoolName"]
    print("Here is the Resource Name: " + myResourceName)
    
    jdbcSystemResource = create(myResourceName,"JDBCSystemResource")
    myFile = jdbcSystemResource.getDescriptorFileName()
    print ("HERE IS THE JDBC FILE NAME: " + myFile)
    
    jdbcResource = jdbcSystemResource.getJDBCResource()
    jdbcResource.setName(obj["PoolName"])
    
    # Create the DataSource Params
    dpBean = jdbcResource.getJDBCDataSourceParams()
    myName=obj["JNDIName"]
    dpBean.setJNDINames([myName])
    
    # Create the Driver Params
    drBean = jdbcResource.getJDBCDriverParams()
    drBean.setPassword(obj["Password"])
    drBean.setUrl(obj["URLName"])
    drBean.setDriverName(obj["DriverName"])
    
    propBean = drBean.getProperties()
    driverProps = Properties()
    driverProps.setProperty("user",obj["UserName"])
    
    e = driverProps.propertyNames()
    while e.hasMoreElements() :
        propName = e.nextElement()
        myBean = propBean.createProperty(propName)
        myBean.setValue(driverProps.getProperty(propName))
        print myBean
        
        # Create the ConnectionPool Params
        ppBean = jdbcResource.getJDBCConnectionPoolParams()
        
        ppBean.setInitialCapacity(int(obj["InitialCapacity"]))
        ppBean.setMaxCapacity(int(obj["MaxCapacity"]))
        ppBean.setCapacityIncrement(int(obj["CapacityIncrement"]))
        
        if not obj["ShrinkPeriodMinutes"] == None:
            ppBean.setShrinkFrequencySeconds(int(obj["ShrinkPeriodMinutes"]))
        if not obj["TestTableName"] == None:
            ppBean.setTestConnectionsOnReserve(1)
            ppBean.setTestTableName(obj["TestTableName"])
        if not obj["LoginDelaySeconds"] == None:
            ppBean.setLoginDelaySeconds(int(obj["LoginDelaySeconds"]))

        # Adding KeepXaConnTillTxComplete to help with in-doubt transactions.
        xaParams = jdbcResource.getJDBCXAParams()
        xaParams.setKeepXaConnTillTxComplete(1)

        # Add Target
        
        for servername in servers:
            jdbcSystemResource.addTarget(getMBean("/Servers/" + servername))

# function definition END


print "@@@ Starting the script ..."


url = 't3://localhost:7001'
usr = 'weblogic'
password = 'welcome'
servernames = ['AdminServer', 'SSO1', 'SSO2']

connect(usr,password, url)

for servername in servernames:
    servermb=getMBean("Servers/" + servername)
    if servermb is None:
       print '@@@ No server MBean found'
       exit()

edit()
startEdit()

DB1 = {
    'PoolName' : 'DataSource for DGKIM',
    'JNDIName' : 'jdbc/dgkim',
    'Password' : 'password',
    'URLName' : 'jdbc:oracle:thin:@db.dgkim.net:1521:DGKIM',
    'DriverName' : 'oracle.jdbc.OracleDriver',
    'UserName' : 'dgkim',
    'InitialCapacity' : '20',
    'MaxCapacity' : '100',
    'CapacityIncrement' : '10',
    'ShrinkPeriodMinutes' : None,
    'TestTableName' : 'DUAL',
    'LoginDelaySeconds' : None
}

addJDBC(DB1, servernames)

save()
activate(block="true")

exit()

저는 프로젝트 특성상 하나의 DB가 아닌 여러개의 DB를 사용하게 됩니다. 위 예제에서는 DB1이란 것을 사용했지만, 실제로는 8개의 DB에 접속하는 프로그램을 작성했습니다.

그래서 위 코드를 만들고, 개발 서버냐 운용 서버냐에 따라 url과 servernames를 수정하여 한번에 데이터 소스를 작성해서 편리했습니다.

OAS 10g R2 에서 한글 파일명 413 에러 발생관련

Oracle Application Server 10g R2 ( 10.1.2 )에서 OC4J 컨택스트내에 파일명을 한글로 둘 경우, 처리가 정상적으로 되지 않는 문제가 있습니다.

물론 웹모듈에 한글 파일명을 두지 않는 것이 올바른 방법이겠으나, 어쩔수 없이 한글 파일명을 써야 되는 경우 아래 옵션을 OC4J 구동옵션에 추가하여 해결할 수 있습니다.

opmn.xml 에서 해당 OC4J인스턴스의 start-parameters 항목에 ajp.use.unparsed.uri 옵션을 추가해줍니다.

<category id="start-parameters">
   <data id="java-options" value="-server -XX:MaxPermSize=128M -Xms512M -Xmx1024M -Djava.security.policy=$ORACLE_HOME/j2ee/home/config/java2.policy -Djava.awt.headless=true -Dhttp.webdir.enable=false -Dajp.use.unparsed.uri=false"/>
</category>

Apache와 OC4J 연동하기.

Oracle Application Server는 크게 Oracle HTTP Server(Apache MOD)와 OC4J(Oracle Container for J2EE)로 구성되어 있습니다.

그런데, OC4J의 경우 단독으로도 사용할 수 있도록 Standalone 버전으로 Oracle에서 제공하고 있습니다.

만약, 서버를 저와 같이 PHP나 다른 Apache의 기능을 사용하고, OAS를 사용할 수 없는 환경에서 Apache와 OC4J를 연동하여 사용하는 방법을 알려드립니다.(물론 이미 알고 있는 분도 계시겠지만.)

먼저, Apache와 OC4J의 연동은 OAS 에서는 mod_oc4j 라는 모듈을 통해 처리합니다.
그러나, mod_oc4j는 OAS에 맞도록 작성되어 단독 Apache에 쓰기에는 무리가 있습니다.

다음으로, AJP를 통해 OC4J와 연동하는 것이 가능합니다.
그러나, 톰캣에서 제공하는 AJP 모듈과 OC4J모듈의 연동에서는 알 수 없는 문제가 발생했었습니다.

다음은, proxy 모듈을 사용하는 방법입니다.(이 방법을 소개합니다.)

저의 경우 Apache 2.2 버전에서 제공되는 proxy 모듈로 사용하고 있습니다.

LoadModule proxy_module /usr/lib/apache2/modules/mod_proxy.so
LoadModule proxy_http_module /usr/lib/apache2/modules/mod_proxy_http.so

위와 같이 모듈이 활성화 된 상태에서 아래와 같이 설정하여 OC4J와 연동이 가능합니다.

ProxyPass /ical2bi http://localhost:8888/ical2bi

위 설정은 Apache 웹서버에 /ical2bi 형태로 접근할 경우, OC4J의 /ical2bi 로 요청을 넘겨줍니다.

이 방법의 경우, 일반적으로 잘 돌아가지만, redirect 등과 같이 서버 주소를 참조하는 경우에는 정상 동작하지 않는 경우도 발생할 수 있습니다.

proxy 모듈을 통해서 저는 OC4J의 예제만 보여드렸습니다만, OC4J 뿐 아닌 다른 WAS나 심지어 다른 웹서버까지 요청을 전달할 수 있습니다.

저는 하나의 서버에 여러개의 애플리케이션을 돌리고, 각각 접근하는 URL에 따라 애플리케이션을 구분하여 서비스하고 있습니다.

ps. 제 서버에는 개발에 사용된 언어가 PHP, Python, Java, CGI(Perl) 입니다. 이중 Java부분을 OC4J로 처리하고 있습니다.

Java properties 에서 문자 치환하기.

저는 아래와 같은 properties 파일을 만들고,

UNKNOWNERROR=알 수 없는 오류입니다.(ERRORCODE)

아래와 같은 코드로 ERRORCODE를 대체해 왔습니다.

package com.idatabank.sso.exception;

import java.util.ResourceBundle;

public class SSOException extends Exception {
    public SSOException() {
        super();
    }
    
    public SSOException(String errorType) {
        super(errorType);
        
        userMessage = rb.getString(errorType);
        
        if ( userMessage == null ) {
            userMessage = rb.getString("UNKNOWNERROR").replaceAll("ERRORCODE", errorType);
        }
    }
    
    /**
     * 사용자 에러 메시지를 세팅한다.
     * @param userMessage
     */
    public void setUserMessage(String userMessage) {
        this.userMessage = userMessage;
    }
    
    /**
     * 사용자 에러 메시지를 반환한다.
     * @return
     */
    public String getUserMessage() {
        return userMessage;
    }
    
    private String userMessage = null;
    private static final ResourceBundle rb = ResourceBundle.getBundle("com.idatabank.sso.exception.Messages");
}

그런데, 오늘 문자열 치환의 새로운 방법을 찾았습니다.

http.method_not_implemented=Method {0} is not defined in RFC 2068 and is not supported by the Servlet API 
      String errMsg = rb.getString("http.method_not_implemented");
      Object[] errArgs = new Object[1];
      errArgs[0] = method;
      errMsg = java.text.MessageFormat.format(errMsg, errArgs);

이 방법이 좀더 우아해 보입니다.