Hudson을 설치하고 Hudson관리화면으로 이동하면 Your container doesn't use UTF-8 to decode URLs  이라는 빨간 색 글자가 보이는 경우가 있다. 말 그대로 웹서버 컨테이너가 UTF-8로 지정되어 있지 않습니다. 라고 알려주는 것.

 

해결방법은 허드슨 톰캣관련 문서에서 확인할 수 있었다.( http://wiki.hudson-ci.org/display/HUDSON/Tomcat )

 

 

i18n
Some versions of Tomcat (such as 5.0.28) uses iso-8859-1 to decode URLs, which is in a clear violation of the relevant RFCs. To fix this problem, add the following URIEncoding attribute to the connector definition in $TOMCAT_HOME/conf/server.xml.
<Connector port="8080" URIEncoding="UTF-8"/>
Other people reported that a presence of RequestDumperValve would also cause a problem.
Related reads
  2. John O'Conner's blog entry about his experiment. See Elliotte's comment in particular (elharo.)
 
 

Tomcat의 경우 인코딩타입을 명백하게 지정하지 않은 경우 기본값으로 iso-8859-1 를 사용하게 되기 때문에 $TOMCAT_HOME/conf/server.xml 의 Connector에 URIEncoding="UTF-8" 이라고 지정해주어야만 한다. 

 

서버를 직접 구동하는 경우에는 상관이 없지만

이클립스 플러그인으로 서버를 구동시키다 보면 가끔 Timeout 에러가 나면서 실행이 되지 않는 경우가 있다.

실행시  로드되는 모듈들이 많거나(특히 EJB 개발시..) 특정모듈이 로딩 중 Delay되는 경우 종종 발생하는데

 

Timeout waiting for XXX to start. Server did not start after 50s

 

이와 비슷한 에러들을 발생시키며 서버가 실행되지 않는다.

 

해결방법은 간단하다. Timdout limit 값을 늘려주면 된다. (-_-;)

 

수정하는 방법은 여러가지가 있겠지만

내 경우에는 서버창에서 (Window > Show View > Servers) 서버를 더블클릭한후  Timeouts 탭의 start 값을 늘려주었다.

JBoss의 경우 Default로 50, Tomcat의 경우 Default로 45로 되어 있는 것 같다.

 

 

이제 다시 구동하면 된다.

 

이클립스에서 Export --> java>javadoc 을 함으로써 소스파일을 쉽게 html 형식의 javadoc 파일을 쉽게 만들수 있다.

소스 코드를 UTF-8로 작성을 한 경우 빌드된 javadoc 파일들의 한글은 깨져 보이게 된다.

물론 브라우저 상에서 인코딩타입을 UTF-8로 변경하면 깨지지 않은 한글을 볼수 있지만

매번 다른 화면을 이동할 때마다 인코딩타입을 변경해주거나 자동선택을  선택해지 하고 UTF-8로 고정시켜야만 한다.

javadoc파일을 만들때 당연히 옵션이 있을 거라 생각하고 살펴보니..

역시나 javadoc 생성 마지막 단계에서 VM options을 지정함으로써 javadoc 파일의  Charset을 UTF-8로 설정할 수 있었다.

 

 

위의 그림과 같이 VM options 텍스트필드에 다음과 같이 입력한다.

 

-locale ko_KR -encoding UTF-8 -charset UTF-8 -docencoding UTF-8

 

 

이전 포스트에서 이클립스에서 class파일을 디컴파일하는 방법을 소개한 적이 있다.

헌데 간혹 jd-eclipse로 디컴파일이 되지 않는다는 분이 계셔서 확인해보았다.

class 파일을 열면 열리지 않고 에러문구만 다음과 같이

....could not be initialized... 라면서 재설치를 하라고 나온다... 

 

이런 문의가 있어서 테스트를 해봐도 이상이 없었는데

새로이 XP를 설치한 경우에야 동일 증상을 발견하고

그 이유를 알게 되었다.

이클립스 플러그인 외에 OS가 윈도우즈인 경우 (다른 OS는 상관없는 듯 ...)

 "Microsoft Visual C++ 2008 SP1 Redistributable Package" 가 설치되어 있어야 한다.

 

커스텀XP를 사용시 위 패키지 설치를 제한하는 경우가 있는데 이  라이브러리가 설치되지 않아

jd-eclipse가 초기화되지 않아서 발생한 문제였다.

 

참고로 이클립스 3.5 버젼에서 Equinox/p2 plug-in 을 설치하는 방법이 이전(3.4)와 조금 달라..

함께 끄적여 본다..

 

 

이클립스 3.5 버젼에서 Install Software 화면에서 하단의 Group items by category 항목이

기본적으로 체크되어 있는데 이 부분을 체크해제하면 위와 같이 Equinox p2 Provisioning 항목을 확인할 수 있다.

 

 

JOTM을 이용한 분산Transaction 처리

 

개요

Spring 등의 오픈소스프레임워크를 이용한 개발 프레젠테이션을 할 때 질문을 받았던 내용 중에 하나가무거운 EJB보다 필요한 기능만을 갖춘 가벼운 Spring등의 프레임워크를 쓰자는 것은 이해가 간다. EJB를 쓰는 주된 이유중에 하나인 분산처리 기능을 Spring등은 어떻게 처리하고 있는가?”

교과서적인 답변이랄까? Spring JTA를 이용한 분산처리 트랜잭션API을 지원하고 있습니다. -_- a

틀린 말은 아니지만 그 분의 질문은 JTA를 어떻게 사용할수 있는가라는 의도였던 것 같다. WAS 를 쓰면서도 EJB 만의 기능(앞서 언급한 분산처리기능)을 구현하거나 필요로 했던 프로젝트는 그다지 많지 않았다. 그래서 Spring등과 톰캣을 이용한 프로젝트들이 많이 생겨난 것이지만 사실 그렇기 때문에 Spring을 통해서도 충분히 분산처리가 가능합니다.라고 고객께 말은 드리지만 정작 분산처리기능이 필요한 경우에는 특히 분산트랜잭션 서비스를 지원하지 않은 서블릿컨테이너(톰캣)을 사용하는 경우에 JTA 를 어떻게 사용해야 하는지 개발자는 조금 더 알아야 하지 않을까?

JOTM(Java Open Transaction Manager)

 

두 개 이상의 자원에 동시에 접근하는데 Transaction이 필요한 경우가 있다. 예를 들어 결재정보를 저장하는 데이터베이스와 구매내역을 저장하는 데이터베이스가 다르다고 할때 이 두 데이터베이스에 접근하기 위한 DataSource는 다르지만 두 데이터베이스에 접근하는 코드는 단일 트랜잭션으로 처리되어야 한다. 웹로직이나 JBoss 등의 WAS는 자체적으로 분산트랜잭션서비스를 지원하지만 톰캣과 같은 서블릿컨테이너는 분산 트랜잭션을 지원하지 않는다. 만약 톰캣과 같은 분산트랜잭션을 지원하지 않은 컨테이너에서 구현해야 한다면 JOTM을 사용하면된다.

 

JOTM(Java Open Transaction Manager) ObjectWeb에서 제공하는 트랜잭션 관리자로서 JTA, JTS 등의 API를 지원하고 있으며 JOTM을 사용하면 기존의 JDBC 기반의 코드에 JTA를 이용한 트랜잭션 처리 코드를 손쉽게 적용할 수 있게 된다..

아래의 항목은 Spring에서 JOTM을 어떻게 사용하는지에 대하여 설명하고 있으며 순서에 따라 진행하면 된다.

 

1. JOTM library download  Jar 파일 설치

  - spring2.5를 다운받았다면 :jotm,jotm_jrmp_stubs.jar,xapool.jar webapplication classpath에 추가

  - JOTM site에서 download받게되면 ow_carol-all.jar를 찾아 webapplication classpath에 추가

 

2.spring에서 제공하는 JotmFactoryBean bean 설정

 

3.각각 사용할 database dataSource org.enhydra.jdbc.pool.StandardXAPoolDataSource를 이용하여 설정한다

 

4.ibatis를 사용할 경우 slqMapClient에 각각 사용할 dataSource property로 가지도록 설정한다

 

spring설정 file(일반적으로 application.xml)

 

  <!-- jotm 설정 -->

    <bean id="jotm"

        class="org.springframework.transaction.jta.JotmFactoryBean" />

 

    <!-- jotm property로 가지는 JtaTransactionManager설정 -->

    <bean id="transactionManager"

        class="org.springframework.transaction.jta.JtaTransactionManager"  p:userTransaction-ref="jotm" />

    <!-- StandardXAPoolDataSource를 이용한 postgresSQL dataSource설정  -->

    <bean id="postgresDBDataSource"  class="org.enhydra.jdbc.pool.StandardXAPoolDataSource"

destroy-method="shutdown">

        <property name="dataSource">

            <bean class="org.enhydra.jdbc.standard.StandardXADataSource" destroy-method="shutdown">

                <property name="transactionManager" ref="jotm" />

                <property name="driverName" value="org.postgresql.Driver" />

                <property name="url" value="jdbc:postgresql://localhost:5432/testDb?charSet=UTF-8" />

            </bean>

        </property>

        <property name="user" value="test" />

        <property name="password" value="test" />

    </bean>

    

    <!-- StandardXAPoolDataSource를 이용한 oracle dataSource설정  -->

    <bean id="oracleDBDataSource"  class="org.enhydra.jdbc.pool.StandardXAPoolDataSource"

        destroy-method="shutdown">

        <property name="dataSource">

            <bean class="org.enhydra.jdbc.standard.StandardXADataSource" destroy-method="shutdown">

                <property name="transactionManager" ref="jotm" />

                <property name="driverName" value="oracle.jdbc.driver.OracleDriver" />

                <property name="url" value="jdbc:oracle:thin:@127.0.0.1:1526:TEST" />

            </bean>

        </property>

        <property name="user" value="test" />

        <property name="password" value="test" />

    </bean>

    

    <!-- postgresSQL dataSource property로 하는 slqMapClient설정 -->

    <bean id="sqlMapClient"  class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">

       <property name="configLocation" value="/WEB-INF/config/sqlmap-config.xml"/>

       <property name="dataSource" ref="postgresDBDataSource"/>

    </bean>

    

    <!-- oracle dataSource property로 하는 slqMapClient설정 -->

    <bean id="oracleDbSqlMapClient"  class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">

       <property name="configLocation" value="/WEB-INF/config/sqlmap-config.xml"/>

       <property name="dataSource" ref="oracleDBDataSource"/>

    </bean>

    

    <!-- postgresSQL dataSource property로 하는 slqMapClient를 가지는 dao -->

    <bean id="postgresDbDao" class="test.dao.PostgresDbDao">

        <property name="sqlMapClient" ref="sqlMapClient" />    

    </bean>

    

    <!-- oracle dataSource property로 하는 slqMapClient를 가지는 dao -->

    <bean id="oracleDbDao" class="test.dao.OracleDbDao">

        <property name="sqlMapClient" ref="oracleDbSqlMapClient" />    

    </bean>

   <!-- The business Logic are autodetected POJOs labeled with the @Service annotaion -->

   <context:component-scan base-package="test.service" scoped-proxy="targetClass"  />

    <context:annotation-config />

    <!-- annotation으로 transcation를 선언 이때 transactionManager jotm transactionManager로 한다  -->

    <tx:annotation-driven transaction-manager="transactionManager"/>

 

iBatis 설정(일반적으로 sqlMap.xml)

 

  <insert id="insertTestInOracleDb" parameterClass="java.util.Map">

        INSERT INTO test_imsi ( name,age) VALUES (#name#,#age#)

    </insert>

    

    <insert id="insertTestInPostgresDb" parameterClass="java.util.Map">

        INSERT INTO test2_imsi ( name,age) VALUES (#name#,#age#)

    </insert>

 

dao (postgresSQL 사용, 여기서는 PostgresDbDao.java)

 

 public class PostgresDbDao extends SqlMapClientDaoSupport

{

    public void insertTestTable(Map<String,String> paramMap)

    {

        getSqlMapClientTemplate().insert("test.insertTestInPostgresDb",paramMap);

    }

}

 

dao (Oracle 사용, 여기서는 OracleDbDao.java)

 

 public class OracleDbDao extends SqlMapClientDaoSupport

{

    public void insertTestTable(Map<String,String> paramMap)

    {

        getSqlMapClientTemplate().insert("test.insertTestInOracleDb",paramMap);

    }

}

 

Service (비즈니스 로직 구현부)

 

 @Service

 @Transactional

 public class TestService

 {

    @Autowired

    private PostgresDbDao postgresDbDao;

    @Autowired

    private OracleDbDao oracleDbDao;

    

    public void jotmTransaction(Map<String,String> paramMap)

    {

        postgresDbDao.insertTestTable(paramMap);

        oracleDbDao.insertTestTable(paramMap);

    }

 }

 

 

IoBuffer

IoBuffer는 MINA 어플리케이션에서 사용되는 Byte Buffer를 말하는데 NIOByteBuffer 를 대체하고 있으며 대체 이유로 다음 두가지를 들고 있습니다

  • fill, get/putString 그리고 get/putAsciiInt() 와 같은 유용한 getter/setter 메소드를 제공하지 않는다
  • ByteBuffer 의 허용크기가 가변길이 데이터를 저장하기에 충분하지 않다

IoBuffer는 앞서 말했지만 MINA에서 NIO ByteBuffer를 대체하는 클래스입니다. 그 이유는 버퍼의 확장을 쉽게 하기 위해서인데요.

좀더 자세히 말하자면 MINA 에서 IO를 최소화하기 위해 데이터를 한번만 읽어 모든 처리를 하자는 취지에서 만들어진 것 같습니다.

한마디로 소켓으로부터 받은  데이터를 버퍼에 담아 한번에 처리하겠다는 것인데요 .

문제는 고정길이가 아닌 가변길이의 데이타가 전송되는 경우 버퍼크기를 초과하는 데이터를 처리하기가 쉽지 않다는 것이었습니다.

그래서 버퍼의 확장이 쉽도록 구현된 대체 클래스인 셈이죠. 하지만 메모리 관리방법을 JVM내 객체가 저장되는 공간인 Heap  영역이 아닌 일반메모리상에서 직접 접근할 수 있는 버퍼영역인  Direct Buffer를 사용하는 경우에 문제가 발생하게 됩니다.

즉 Buffer의 데이터에 다른 프로그램으로 직접 접근할 수 있다는 보안상의 결함을 가지고 있다는 것이죠.

따라서 MINA3  버젼부터는 변경할 예정이라고 하네요.(원문 참조, Direct Buffer가 아닌 Heap 버퍼 사용을 권장함)

MINA에서 IoBuffer를 사용하는 방법

IoBuffer 는 추상클래스입니다, 따라서 직접 인스턴스화 할 수는 없습니다. IoBuffer를 할당하기 위해서 우리는 두개의 allocate() 메소드 중 하나를 사용해야 합니다.

  • public static IoBuffer allocate(int capacity, boolean direct)
  • public static IoBuffer allocate(int capacity)

위에서 처럼 allocate() 메소드는 하나 또는 두개의 인자를 가집니다. :

  • capacity버퍼 크기 
  • direct버퍼 유형. direct buffer를 얻기 위해서는 true, heap bufferfalse

기본적으로 버퍼의 할당은  SimpleBufferAllocator 에 의해 핸들링되어 처리됩니다. 아래와 같은 방법으로도 사용될 수 있습니다

  • IoBuffer.setUseDirectBuffer(false);
  • IoBuffer buf = IoBuffer.allocate(1024);

즉 기본값으로 Heap버퍼를 사용하는 것으로 설정한 후 새로운 Heap 버퍼로 반환하는 것입니다. 이 방법을 사용하기 전에 주의할 점은 기본 버퍼유형을 설정해야 한다는 것입니다. 그래야만 Heap 버퍼를 사용하게 됩니다.(앞서 말한 이유로...)

 

잠깐 위에서 언급했지만 MINA에서 버퍼를 할당 및 관리를 IoBufferAllocator 에서 처리하도록 되어 있는데요. 다음 두가지 방법을 지원하고 있습니다. setAllocator()를 통해 설정할 수 있구요. 직접 IoBufferAllocator를 구현할 수도 있습니다.

  • SimpleBufferAllocator(기본값) - 매번 새로운 버퍼를 생성한다 
  • CachedBufferAllocator - 확장시 재사용가능한 버퍼영역을 캐싱한다.

예제

 

먼저 자동으로 버퍼를 확장하는 방법에 대한 예제 입니다.  IoBuffer는  autoExpand라는 프로퍼티를 이용하여 버퍼크기와 한계치를 자동으로 확장할수 있습니다.


IoBuffer buffer = IoBuffer.allocate(8);

buffer.setAutoExpand(true);

 

buffer.putString("12345678",encoder);

 

//버퍼에 추가

buffer.put((byte)10);

버퍼를 증가시키는 것은 어렵지 않습니다. 마치 StringBuffer 를 통해서 문자열을 추가하듯이 버퍼에 put메소드를 사용하면 자동적으로 증가가 됩니다.

 

지속적으로 버퍼가 증가만 된다면 문제겠죠. 메모리자원을 보존하기 위해 추가할당된 byte 들을 삭제하도록 요청할 필요가 있습니다. 이러한 요구에 따라 autoShrink 프로퍼티를 지원하고 있는데요. autoExpand 와는 반대의 개념으로 compact() 메소드가 호출되면 버퍼의 크기를 절반 또는 현재 할당된 영역의 4분지1이하의 크기로 줄일 수 있습니다. 다음은 버퍼 크기를 수동으로 줄이는 예제입니다.

 


IoBuffer buffer = IoBuffer.allocate(8);

buffer.setAutoExpand(true);

 

buffer.put((byte)1);

System.out.println("기본 버퍼 크기 = "+buffer.capacity());

buffer.shrink();

System.out.println(" shrink  이후 기본 버퍼 크기 = "+buffer.capacity());

 

buffer.capacity(32);

System.out.println("버퍼 크기를 32로 증가시킨 결과 = "+buffer.capacity());

buffer.shrink();

System
.out.println("shrink  이후 버퍼 크기 = "+buffer.capacity());

결과

기본 버퍼 크기 = 16 // 버퍼 크기가 16인 버퍼 생성. 즉 버퍼의 최소용량을 16으로 설정

shrink 후 기본버퍼 크기 = 16 // shrink() 호출 . 하지만 최소영역 이하로 줄일수 없습니다. 16

버퍼크기를 32로 증가시킨 결과 = 32 // 버퍼의 최대크기를 32로 늘임. 버퍼용량 32

shrink 이후 버퍼크기 =16 // shrink() 호출. 기본최소용량인 16으로 버퍼크기를 줄임

버퍼크기를 수동으로 줄이고자할때 shrink()를 사용하고 있지만 버퍼의 크기를 줄이기 위해 이 예제에서처럼 명시적으로 요청할 필요는 없습니다.

앞서 말한바와 같이 autoShrink 프로퍼티를 설정하면 자동적으로 메모리자원을 확보하기 위해 추가된 버퍼를 삭제할 수 있습니다.

 

이 글은 http://mina.apache.org/iobuffer.html 를 참고하여 작성되었구요.

좀더 상세하게 설명되어 있는 번역문은 pdf파일로 첨부하였습니다만. 언제나 그렇듯이 번역상태는 ....-_-a

'개발 이야기 > MINA' 카테고리의 다른 글

[MINA] Logging Configuration  (0) 2009.11.30
MINA 기반의 어플리케이션 구조  (0) 2009.05.28
MINA 2.0 에코서버 만들기  (0) 2009.05.21
 

MINA 기반의 어플리케이션 구조

개발 이야기/MINA | 2009. 5. 28. 18:01
Posted by 시반

MINA 기반의 어플리케이션 구조

개요                                                                                                            

MINA 기반의 어플리케이션의 구조에 대하여 많은 분들이 질문해오셨습니다. 기사를 통해 MINA 기반 어플리케이션이 어떤 아키텍처를 가지고 있는지 살펴보도록 하겠습니다.MINA 관련된 프레젠테이션 자료에서 발췌했습니다.

조감도 :

 

좀더 상세하게 알아볼까요?

 

이미지는 Apache MINA 이용하여 Network Application 빨리 개발하기(JavaOne 2008) 라는 이희승님의 발표자료에서 가져왔습니다.

크게 보면, MINA 기반의 어플리케이션은 3개의 레이어로 구분되어 집니다

·         I/O Service - 실제 I/O 수행하는 부분

·         I/O Filter Chain - 필터/변환, 바이트를 원하는 데이터 구조로 변환(역으로도 가능)

·         I/O Handler실제 비즈니스 로직이 들어가게 되는 부분

그럼 이제 어떻게 MINA 기반의 어플리케이션을 개발하는지 살펴 볼까요

1.    Create I/O service기본으로 제공하는 서비스 (*Acceptor)  또는 스스로 만든 I/O  서비스 중에서 선택합니다

2.    Create Filter Chain요청/응답간의 변환을 처리하기 위해 필터를 지정합니다. 필터는 기본으로 제공하는 필터 사용자 필터 중에서 선택합니다

3.    Create I/O Handler각기 다른 메시지들을 핸들링 하기 위한 비즈니스 로직을 작성합니다.
MINA
어플리케이션을 만드는 것은쉽죠~? ##########2*

 

ps. 마찬가지로 미나 레퍼런스를 번역한 글입니다.

원문은 http://mina.apache.org/mina-based-application-architecture.html 에서 확인할 수 있습니다.

'개발 이야기 > MINA' 카테고리의 다른 글

[MINA] Logging Configuration  (0) 2009.11.30
MINA에서 버퍼 제어하기 - IoBuffer  (0) 2009.06.01
MINA 2.0 에코서버 만들기  (0) 2009.05.21
 

MINA 2.0 에코서버 만들기

개발 이야기/MINA | 2009. 5. 21. 11:17
Posted by 시반

이번에 소개하고자 하는것은 이희승씨가 커미터로 계셨다는 

MINA(A Multi-purpose Infrastructure for Network Applications)입니다.

간단하게 말하면 자바의 네트워크 애플리케이션을 위한 프레임워크입니다.
필터를 사용한 뛰어난 확장성과 프로토콜 코덱과 비즈니스 로직을 분리하여 유지보수와 재사용성을 높인것이 특징인데요.
한글레퍼런스나 튜토리얼을 기대했는데 역시 아파치 재단이랄까?

이희승씨를 믿고 있건만 영어의 압박이 저를 힘들게 하네요...-_- a.

하지만 사용법은 그다지 어렵지 않은 듯 보입니다.간단하게 예제 프로그램을 따라 만들어 보았습니다.

MINA v2.0 Quick Guild를 참고하였으며. 현재 버젼은 M5까지 나와있습니다 기존의 문제점을 개선한 MINA3을 준비중이라고 하네요.
일단 예제를 위해서는 MINA코어뿐만 아니라 SLF4J Log4J도 필요로 하고 있구요.
SLF4J의 경우에는 1.5.6 버젼까지 나와있는 상태인데 사용하는 Log4J버젼에 따라 선택하면 됩니다.

slf4j-api-1.5.6.jar파일과 slf4j-logj12-1.5.6.jar을 사용합니다. log4j가 1.2버젼이라서.....
기타 JDK6.0, Eclipse3.4 환경에서 테스트합니다.

 
MinaTimeServer

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;
 
import org.apache.mina.core.service.IoAcceptor;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
import org.apache.mina.filter.logging.LoggingFilter;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
 
public class MinaTimeServer
{
   
private static final int PORT = 9123;
 
   
public static void main(String[] args) throws IOException
   
{
       
IoAcceptor acceptor = new NioSocketAcceptor();

        acceptor
.getFilterChain().addLast( "logger", new LoggingFilter() );
        acceptor
.getFilterChain().addLast("codec", new ProtocolCodecFilter(
                                           
new TextLineCodecFactory(Charset.forName("UTF-8"))));
 
        acceptor
.setHandler(new TimeServerHandler());
 
        acceptor
.getSessionConfig().setReadBufferSize(2048);
        acceptor
.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10);
 
        acceptor
.bind(new InetSocketAddress(PORT));
   
}
}


Non-Blocking IO로 만듭니다. 클라이언트의 연결 및 실시간 요청을 처리할 리스너를 등록합니다.

IoAcceptor 인터페이스로 구현된 acceptor 객체에 하나이상의 필터들을 등록할 수 있는데 이것을 FilterChain이라 합니다.

이 필터체인이라는 것에 addLast를 통해 필터들을 등록합니다.

위와 같이 로깅을 하거나 인코딩 코덱을 만들어 사용할 수 있습니다.
핸들러를 추가하고 버퍼와 유휴시간을 정의한 후에 PORT를 설정하여 Bind합니다.

TimeServerHandler

import java.util.Date;
 
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
 
public class TimeServerHandler extends IoHandlerAdapter
{

   
@Override
   
public void exceptionCaught(IoSession session, Throwable cause)
       
throws Exception
   
{
        cause
.printStackTrace();
   
}

   
@Override
   
public void messageReceived(IoSession session, Object message)
       
throws Exception
   
{
       
String str = message.toString();
 
       
if(str.trim().equalsIgnoreCase("quit"))
       
{
            session
.close(true);
           
return;
       
}
 
       
Date date = new Date();
        session
.write(date.toString() + "\r");
       
System.out.println("Message written...");
   
}

   
@Override
   
public void sessionIdle(IoSession session, IdleStatus status)
       
throws Exception
   
{
       
System.out.println("IDLE " + session.getIdleCount(status));
   
}
}


이녀석이 실제로 통신에 사용되는 핸들러입니다. 메시지를 받을때 마다 messageReceived이 호출됩니다.
쓰레드를 이용하고 있으며, 콜백형식으로 작동하는 것을 알 수 있습니다.

sessionIdle을 사용하면 Main에서 정의한 유휴시간마다 호출됩니다.

앞서 10초로 설정해 두었으니 10초동안 유휴상태로 있다면 그때마다 이 함수가 호출됩니다.

getIdleCount를 이용해 얼마나 유휴상태에 있는지를 확인하도록 합니다. 서버를 완성했으니 실행해 볼까요.

c:>telnet localhost 9123

 


quit를 입력하면 종료되고 그 이외의 문자를 입력하면 시간이 출력되는 것을 알 수 있습니다.

한글을 입력시 MalformedInputException이 나면서 동작안하는 경우가 있는데

인코딩타입을 UTF-8로 바꾸어주면됩니다.

번역한 튜토리얼은 첨부파일로 첨부합니다. 최대한 매끄러게 번역을 하려고 했는데. 앞서 이야기했지만 영어가 딸려놔서리..

오역이나 이상한 것들은 기냥 구박마시고 조용히 알려주세요..*^^*

 

'개발 이야기 > MINA' 카테고리의 다른 글

[MINA] Logging Configuration  (0) 2009.11.30
MINA에서 버퍼 제어하기 - IoBuffer  (0) 2009.06.01
MINA 기반의 어플리케이션 구조  (0) 2009.05.28
 
프로젝트에서 자주 나오는 요구사항 중 하나가 엑셀저장이다.
이전 블로그내 포스트에서 언급한적이 있지만 내 경우엔 주로 POI를 통해 엑셀파일을 만들고 서블릿을 통해 다운로드 받을 수 있도록 하거나 스프링에서 제공하는 AbstractExcelView(POI 사용시) 또는 AbstractJExcelView(JExcel 사용시) 라는 View클래스를 상속받아 구현하면 되었다.
스트럿츠2에서도 다양한 ResultType 을 지원하고 있지만 excel이 기본 ResultType으로는 없어서 기냥 첫번째 경우로 다운받도록 했는데 마침 행복한 아빠 님의 블로그에서 jXLS를 이용하여 엑셀을 스트럿츠2의 result type으로 지정하는 방법을 설명하고 있는 것을 보고 잠시 소개해 볼까 한다.
 
프로젝트를 하다보면 데이터를 엑셀로 출력해 달라는 요구가 빈번히 발생한다.
간단하게 CSV 파일로 출력할 수도 있으며 Excel이 HTML 형태의 문서양식도 지원하기에
HTML로 작성하고 mine type만 살짝 바꿔주어 엑셀로 읽을 수 있도록 하는 방법이 있다.

물론 CSV로 요구사항을 충분히 만족시킬 수 있을 경우도 있으나 고품질의 엑셀 양식을 요구할 경우 위 두 방법은 웬지 부족함이 있다. 이럴 경우 엑셀 포맷으로 출력할 필요가 있는데 여러가지 솔루션이 있어 어렵지 않게
해결할 수 있는 부분이지만 매번 어떻게 처리할 지 고민을 한다. 여러가지 방법을 사용해 본 후 가장 나은 방법을 추천해 본다.

솔루션 찾기

Java로 엑셀을 다루기 위한 방법은 여러가지 있으며 대표적인 것 3가지만 살펴본다.
Java Excel API은 개발자가 엑셀 스프레드시트를 동적으로 읽고, 쓰고 수정할 수 있도록 하는 성숙한 오픈소스 java API이다. Java 개발자는 간단한 API를 이용하여 엑셀 스프레드시트를 읽고, 수정하고 쓸 수 있다.

장점은 이 패키지는 다른 패키지를 필요로 하지 않고 현재버전(2.6.9.1)의 jar 파일의 크기가 709KB로 부담없이 쓸 수 있다는 것이다. 비교적 간단한 AP를 제공하나 엑셀의 차트나 그래프 매크로 정보를 생성할 수는 없으며 시트에 PNG 이미지만 추가할 수 있다.

 
POI는 Microsoft의 OLE 2 컴포넌트 문서 포맷을 다루기 위한 프로젝트이다. 따라서 POI에는 엑셀 뿐만 아니라 워드문서를 다루는 API 도 제공하고 몇가지 모듈로 나누어져 있다.
이중 HSSF 가 엑셀 파일 포맷을 다루기 위한 자바구현체이다. 오래되었고 다른 것 보다 큰 이상을 가지고 출발한 프로젝트라 다양한 API를 제공한다.

apache 재단에서 진행되는 프로젝트이며 POI 패키지는 여러개의 다른 패키지(commons, log4j 같은..)를 필요로 한다. 풍부한 API를 제공하는 대신에 사용하기 번거로운 점이 있으며 많은 패키지를 필요로 한다는 부담이 있다.

jXLS은 엑셀파일 포맷의 템플릿을 이용하여 엑셀 파일을 손쉽게 생성하기 위한 패키지이다. 또한 XML 설정 파일을 통해 엑셀파일의 데이터를 Java 객체로 읽는 장치도 제공한다.

사실 jXLS은 Javarta POI 패키지를 기반으로 동작한다. 따라서 jXLS을 사용하기 위해서는 POI가 사용하는 많은 다른 패키지를 필요로 한다.

반면 jXLS 자체는 매우 작으며 복잡한 보고서 생성이나 일정한 양식의 엑셀 데이터를 규칙에 따라 읽게 한다는 뚜렷한 목적이 있어 범용성은 약간 떨어지더라도 대부분의 엑셀 관련 작업에 훌륭한 솔루션이 될 수 있다.

우리는 jXLS을 이용하여 위 문제를 풀어본다.

jXLS 맛보기

jXLS은 템플릿을 기반으로 최종 엑셀파일을 생성한다.
JSP나 Velocity 또는 Freemarker 같이 템플릿을 만들고 출력할 데이터를 템플릿을 이용하여 변환하면 템플릿 모양대로 최종결과물이 생성하는 구조다.
여기서 jXLS은 템플릿으로 엑셀파일을 그대로 쓰며 따라서 템플릿 작성이 매우 쉽다. 또한 엑셀 파일을 그대로 사용하므로 엑셀의 서식과 차트등 엑셀 파일의 대부분의 기능을 그대로 사용할 수 있다.
 


간단한 예제로 설명을 한다.
Java 객체
출력할 데이터를 만든다. 아래는 Customer 클래스를 예로 사용했지만 Map도 지원한다. 각 속성에 대한 getter, setter는 존재해야 한다.
public class Customer {
    private Long no;
    private String name;
    private String cellphone;
    private String email;
    public Long getNo() {
        return no;
    }
    ...
}


변환
출력한 데이터를 만든 후 엑셀템플릿과 출력할 데이터 (Java 빈)을 이용하여 변환한다.
        // 출력할 객체를 만든다.
        List<Customer> customers = new ArrayList<Customer>();
        Customer customer = new Customer();
        customer.setNo(1L);
        ...
        customers.add(customer);
        ...
        
        Map<String, Object> beans = new HashMap<String, Object>();
        beans.put("customers", customers);
        XLSTransformer transformer = new XLSTransformer();
        transformer.transformXLS("엑셀템플릿파일이름.xls", beans, "엑셀결과파일이름.xls");


엑셀템플릿
엑셀파일로 다음과 같이 작성한다. 중간에 ${..}로 들어갈 곳은 데이터가 치환되는 부분이다. 위의 경우 "엑셀템플릿파일이름.xls" 파일을 아래와 같이 생성한다.


이제 프로그램을 구동하면 자바객체를 이용하여 엑셀파일을 생성할 것이다.

태그 사용하기
좀 더 세밀한 제어를 위해 jXLS은 여러가지 태그를 제공한다. 위의 예제를 태그로 변경하면 다음과 같다.


jXLS의 자세한 사용법은 jXLS 홈페이지를 참조한다.


 
웹환경 실전에서

우리는 경우에 따라 고객목록을 HTML로 출력하거나 엑셀파일로 다운로드할 것이다. 즉 동일한 데이터가 경우에 따라 표현하는 방법 만 달리하는 경우에 해당한다.
이런 요구사항은 흔히 발생하고 이런 경우 MVC 모델을 응용한 아키텍처를 많이 사용한다.
MVC(Model View Controller) 모델은 많이 들어보았을 것이다. 여기서 우리는 Model과 View를 분리하는 작업을 할 것이다. 위의 경우 Model은 Customer 클래스에 해당하고 View는 엑셀파일에 해당할 것이다.



Struts2 Result 구현
위 구현을 위해 우리는 Struts2를 사용할 것이다. Struts2는 가장 많이 사용하는 프레임워크로 Struts 1에 비해 구조가 많이 개선되었다. 위의 목적을 달성하기 위해 우리는 데이터를 엑셀로 만드는 Result Type만 구현하여 Struts2 프레임워크에 붙여(플러그인)주기만 하면 된다.


Excel Result Type 만들기
Struts2에 새로운 Result Type을 만들기 위해서는 com.opensymphony.xwork2.Result를 구현하면 된다.

엑셀로 변환하기 위해 필요한 정보는 엑셀 템플릿, 엑셀에 출력할 객체들 그리고 다운로드받을 파일이름 정도일 것이다. 이것들을 이용하여 변환을 한다.

JXLSResult.java
아래는 본인이 사용하는 Struts2 엑셀 result tyup 클래스이다.
(아래 소스의 저작권은 넥스트리소프트에 있습니다. 참조하여 사용하는 것은 문제가 없으나 저작권은 반드시 명시하여 주시기 바랍니다.)

public class JXLSResult implements Result {
    /** 엑셀 템플릿 */
    private String template;
    /** 엑셀에 출력할 객체들 */
    private String beans;
    /** 파일이름을 얻어올 키값 */
    private String filenameKey = "filename";

    public void execute(ActionInvocation invocation) throws Exception {
        ActionContext actionContext = invocation.getInvocationContext();
        ServletContext context
            = (ServletContext) actionContext.get(StrutsStatics.SERVLET_CONTEXT);
        HttpServletResponse response
            = (HttpServletResponse) actionContext.get(StrutsStatics.HTTP_RESPONSE);
        // 출력할 bean들을 만든다.
        Map<String, Object> beanParams = new HashMap<String, Object>();
        String[] beanNames = splitBeans();
        for (String beanName : beanNames) {
            beanParams.put(beanName, invocation.getStack().findValue(beanName));
        }
        XLSTransformer transformer = new XLSTransformer();
        InputStream is = null;
        HSSFWorkbook workbook;
        String finalTemplate = TextParseUtil.translateVariables(this.template, invocation.getStack());
        try {
            is = readTemplate(finalTemplate, context);
            workbook = transformer.transformXLS(is, beanParams);
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    // 무시
                }
            }
        }
        String filename = invocation.getStack().findString(filenameKey);
        if (filename == null)
            filename = "기본파일이름";
        writeWorkbook(filename, response, workbook);
    }
    /** 엑셀에 출력할 객체이름(key)들을 분리한다. */
    private String[] splitBeans() {
        return this.beans.split(",");
    }
    /** 엑셀 결과를 출력한다. */
    private void writeWorkbook(
        String filename, HttpServletResponse response, HSSFWorkbook workbook)
        throws IOException {
        response.setHeader(
            "Content-disposition", "attachment;filename=" + encodeFileName(filename + ".xls"));
        response.setContentType(FileServerUtil.getContentTypeOfFile("xls"));
        workbook.write(response.getOutputStream());
    }
    /** 파일이름 인코딩 */
    private String encodeFileName(String filename) {
        try {
            return URLEncoder.encode(filename, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e.getMessage, e);
        }
    }
    /** 엑셀 템플릿을 읽는다. */
    private InputStream readTemplate(
        String finalTemplate, ServletContext context) throws FileNotFoundException {
        String templateFilePath = context.getRealPath(finalTemplate);
        return new FileInputStream(templateFilePath);
    }
    /**
     * @param template 엑셀 템플릿
     */
    public void setTemplate(String template) {
        this.template = template;
    }
    /**
     * @param beans 엑셀에 출력할 객체들
     */
    public void setBeans(String beans) {
        this.beans = beans;
    }
    /**
     * @param filenameKey 파일이름을 얻어올 키값
     */
    public void setFilenameKey(String filenameKey) {
        this.filenameKey = filenameKey;
    }
}



Struts2에 Result Type 추가
struts.xml 에 구현한 result typ을 추가한다. 상세한 result type 구현 및 추가방법은 struts 사이트를 참조한다.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
    "http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
 ...
 <package name="my-default" extends="struts-default">
  <result-types>
   <result-type name="excel" class="com.nextree.fw.commonweb.struts.result.JXLSResult" />
  </result-types>
 ...
 </package>
</struts>



Controller 작성
Struts2의 Action을 controller로 사용하겠다. 아래와 같이 Action을 만든다. 참고로 struts2는 POJO를 그대로 Action으로 사용할 수 있다. 자세한 Struts2 Action 작성방법은 Struts2 site를 참조한다.
public class CustomerListController {
    /** 반환할 값 */
    private List<Customer> customers;

    public String execute() {
        ....
        this.customers = ....;   // 여기서 출력해야 할 데이터를 조회한다.
        return Action.SUCCESS;
    }
    /* 출력할 값의 getter 메소드를 반드시 제공한다. */
    public List<Customer> getCustomers() {
        return new JSONResult(this.zipCodeList);
    }
    /* 위 Excel Result type의 경우 file이름을 controller에서 가져오게 되어 있다. */
    public String getFilename() {
        return "고객목록";
    }
}


Action 정의
Struts2 의 struts.xml에 action을 정의한다. 우리는 excel로 다운로드하기에 앞에 설치한 excel result type을 사용한다.
 
...
<package name="mypackage" namespace="/mypackage" extends="my-default">
  <action name="customers" class="test.CustomerListController" method="execute">
   <result type="excel">
    <param name="template">/customer/CustomerList.xls</param>
    <param name="beans">customers</param>
    <param name="filenameKey">filename</param>
   </result>
  </action>
...

이제 http://hostname:port/context/mypackage/customers.do 로 접속하면 Excel 파일을 다운로드 할 것이다.

결론
위의 방법을 사용하면 그냥 JSP 작성하듯이 엑셀양식을 작성하여 엑셀을 다운로드하는 기능을 구현하기가 매우 쉽다.
엑셀을 그대로 사용하기 때문에 엑셀에 화려한 서식뿐만 아니라 차트나 수식등을 넣을 수도 있어 고품질의 엑셀파일을 만들 수 있다
 
 
 
 

ThreadLocal의 사용법과 활용

개발 이야기/Java | 2009. 4. 3. 13:54
Posted by 시반

예전에 스트럿츠2의 아키텍쳐를 공부하던 중에 "액션 컨텍스트가 메소드 호출에 파라미터로서 전달되지 않도록 쓰레드로컬을 구현하고 있으며 -java.lang.ThreadLocal은 자바 1.2에서 추가된 기능이다- 액션 컨텍스트는 각 쓰레드가 객체 자신의 인스턴스를 소유하는 Thread-specific storage를 제공하며 쓰레드 로컬값을 얻기위한 Access 는 쓰레드 로컬값이 어느 곳에서도 다뤄질 수 있도록 하기 위한 정적메소드이다."라고 했던 문구에서 자바1.2 버전부터 지원했다는 쓰레드 로컬이란 이름이 왜 그리 낯설기만 한지 당시엔 기냥 이름 그대로 쓰레드 단위로 로컬변수를 할당하고 사용한다는 의미로만 이해했다.(나중에 알고보니 틀린말은 아니었지만..자바 개발한지 나름 꽤 오래되었다고 생각했는데 용어자체가 낯설게 느껴진다는게 정말 난 날코더인가 하는 생각이 들었던 순간이었다...).

이후로 기냥저냥 코드를 보고 그냥 저냥 그렇게 쓰이는구나라구 넘어가고 그럭저럭 다른 사람에게 이야기할정도로 자주 사용하게 된 기능이지만 자바캔 블로그 에서 로컬쓰레드에 대해서 설명을 한 글이 있기에 올려본다.

(솔직히 능력이 된다면야 ThreadLocal 에 대해 직접 글을 올렸겠지만 올렸다 하더라도 madVirus님처럼 설명할 자신은 정말 없다. -_- .그래서 더더욱 슬프당.흐미~)

 

쓰레드 단위로 로컬 변수를 할당하는 기능은 ThreadLocal 클래스를 통해서 제공되는데

다음 글에서는 ThreadLocal 클래스의 기본적인 사용방법과 활용 방법을 살펴보도록 한다.

ThreadLocal이란?

일반 변수의 수명은 특정 코드 블록(예, 메서드 범위, for 블록 범위 등) 범위 내에서만 유효하다.

{
    int a = 10;
    ...
   // 블록 내에서 a 변수 사용 가능
}
// 변수 a는 위 코드 블록이 끝나면 더 이상 유효하지 않다. (즉, 수명을 다한다.)

반면에 ThreadLocal을 이용하면 쓰레드 영역에 변수를 설정할 수 있기 때문에, 특정 쓰레드가 실행하는 모든 코드에서 그 쓰레드에 설정된 변수 값을 사용할 수 있게 된다. 아래 그림은 쓰레드 로컬 변수가 어떻게 동작하는 지를 간단하게 보여주고 있다.


위 그림에서 주목할 점은 동일한 코드를 실행하는 데, 쓰레드1에서 실행할 경우 관련 값이 쓰레드1에 저장되고 쓰레드2에서 실행할 경우 쓰레드2에 저장된다는 점이다.


ThreadLocal의 기본 사용법

ThreadLocal의 사용방법은 너무 쉽다. 단지 다음의 네 가지만 해 주면 된다.
  1. ThreadLocal 객체를 생성한다.
  2. ThreadLocal.set() 메서드를 이용해서 현재 쓰레드의 로컬 변수에 값을 저장한다.
  3. ThreadLocal.get() 메서드를 이용해서 현재 쓰레드의 로컬 변수 값을 읽어온다.
  4. ThreadLocal.remove() 메서드를 이용해서 현재 쓰레드의 로컬 변수 값을 삭제한다.
아래 코드는 ThreadLocal의 기본적인 사용방법을 보여주고 있다.

// 현재 쓰레드와 관련된 로컬 변수를 하나 생성한다.
ThreadLocal<UserInfo> local = new ThreadLocal<UserInfo>();

// 로컬 변수에 값 할당
local.set(currentUser);

// 이후 실행되는 코드는 쓰레드 로컬 변수 값을 사용
UserInfo userInfo = local.get();

위 코드만으로는 ThreadLocal이 어떻게 동작하는 지 잘 이해가 되지 않을테니, 구체적인 예제를 이용해서 ThreadLocal의 동작 방식을 살펴보도록 하겠다. 먼저 ThreadLocal 타입의 static 필드를 갖는 클래스를 하나 작성해보자.

public class Context {
    public static ThreadLocal<Date> local = new ThreadLocal<Date>();
}

이제 Context 클래스를 사용해서 쓰레드 로컬 변수를 설정하고 사용하는 코드를 작성할 차례이다. 아래는 코드의 예이다.

class A {
    public void a() {
        Context.local.set(new Date());
       
        B b = new B();
        b.b();

        Context.local.remove();
    }
}

class B {
    public void b() {
        Date date = Context.local.get();

        C c = new C();
        c.c();
    }
}

class C {
    public void c() {
        Date date = Context.local.get();
    }
}

위 코드를 보면 A, B, C 세 개의 클래스가 존재하는데, A.a() 메서드를 호출하면 다음 그림과 같은 순서로 메서드가 실행된다.


위 그림에서 1~10은 모두 하나의 쓰레드에서 실행된다. ThreadLocal과 관련된 부분을 정리하면 다음과 같다.
  • 2 - A.a() 메서드에서 현재 쓰레드의 로컬 변수에 Date 객체를 저장한다.
  • 4 - B.b() 메서드에서 현재 쓰레드의 로컬 변수에 저장된 Date 객체를 읽어와 사용한다.
  • 6 - C.c() 메서드에서 현재 쓰레드의 로컬 변수에 저장된 Date 객체를 읽어와 사용한다.
  • 9 - A.a() 메서드에서 현재 쓰레드의 로컬 변수를 삭제한다.
위 코드에서 중요한 건 A.a()에서 생성한 Date 객체를 B.b() 메서드나 C.c() 메서드에 파라미터로 전달하지 않는다는 것이다. 즉, 파라미터로 객체를 전달하지 않아도 한 쓰레드로 실행되는 코드가 동일한 객체를 참조할 수 있게 된다.

ThreadLocal의 활용

ThreadLocal은 한 쓰레드에서 실행되는 코드가 동일한 객체를 사용할 수 있도록 해 주기 때문에 쓰레드와 관련된 코드에서 파라미터를 사용하지 않고 객체를 전파하기 위한 용도로 주로 사용되며, 주요 용도는 다음과 같다.

  • 사용자 인증정보 전파 - Spring Security에서는 ThreadLocal을 이용해서 사용자 인증 정보를 전파한다.
  • 트랜잭션 컨텍스트 전파 - 트랜잭션 매니저는 트랜잭션 컨텍스트를 전파하는 데 ThreadLocal을 사용한다.
  • 쓰레드에 안전해야 하는 데이터 보관

이 외에도 쓰레드 기준으로 동작해야 하는 기능을 구현할 때 ThreadLocal을 유용하게 사용할 수 있다.

ThreadLocal 사용시 주의 사항

쓰레드 풀 환경에서 ThreadLocal을 사용하는 경우 ThreadLocal 변수에 보관된 데이터의 사용이 끝나면 반드시 해당 데이터를 삭제해 주어야 한다. 그렇지 않을 경우 재사용되는 쓰레드가 올바르지 않은 데이터를 참조할 수 있다.

 
블로그 이미지

시반

시반(詩伴)이란 함께 시를 짓는 벗이란 뜻을 가지고 있습니다. 함께 나눌수 있는 그런 공간이길 바라며...

카테고리

분류 전체보기 (233)
개발 이야기 (73)
WEB2.0 (57)
DB2 (24)
MySQL (6)
오라클 (26)
기타 (44)
취미 (0)
잡담 (2)