캠핑과 개발


EHCache를 이용한 기본적인 캐시 구현 방법 및 분산 캐시 구현 방법을 살펴본다.

EHCache의 주요 특징 및 기본 사용법

게시판이나 블로그 등 웹 기반의 어플리케이션은 최근에 사용된 데이터가 또 다시 사용되는 경향을 갖고 있다. 80:20 법칙에 따라 20%의 데이터가 전체 조회 건수의 80%를 차지할 경우 캐시를 사용함으로써 성능을 대폭적으로 향상시킬 수 있을 것이다.

본 글에서는 캐시 엔진 중의 하나인 EHCache의 사용방법을 살펴보고, Gaia 시스템에서 EHCache를 어떻게 사용했는 지 살펴보도록 하겠다.

EHCache의 주요 특징

EHCache의 주요 특징은 다음과 같다.

  • 경량의 빠른 캐시 엔진
  • 확장(scable) - 메모리 & 디스크 저장 지원, 멀티 CPU의 동시 접근에 튜닝
  • 분산 지원 - 동기/비동기 복사, 피어(peer) 자동 발견
  • 높은 품질 - Hibernate, Confluence, Spring 등에서 사용되고 있으며, Gaia 컴포넌트에서도 EHCache를 사용하여 캐시를 구현하였다.
기본 사용법

EHCache를 사용하기 위해서는 다음과 같은 작업이 필요하다.

  1. EHCache 설치
  2. 캐시 설정 파일 작성
  3. CacheManager 생성
  4. CacheManager로부터 구한 Cache를 이용한 CRUD 작업 수행
  5. CacheManager의 종료
EHCache 설치

EHCache 배포판은 http://ehcache.sourceforge.net/ 사이트에 다운로드 받을 수 있다. 배포판의 압축을 푼 뒤, ehcache-1.2.x.jar 파일이 생성되는 데, 이 파일을 클래스패스에 추가해준다. 또한, EHCache는 자카르타의 commons-logging API를 사용하므로, commons-logging과 관련된 jar 파일을 클래스패스에 추가해주어야 한다.

ehcache.xml 파일

EHCache는 기본적으로 클래스패스에 존재하는 ehcache.xml 파일로부터 설정 파일을 로딩한다. 가장 간단한 ehcache.xml 파일은 다음과 같이 작성할 수 있다.

<ehcache>
    <diskStore path="java.io.tmpdir"/>

    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="true"
            maxElementsOnDisk="10000000"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU"
            />
    
    <cache name="simpleBeanCache"
            maxElementsInMemory="10"
            eternal="false"
            overflowToDisk="false"
            timeToIdleSeconds="300"
            timeToLiveSeconds="600"
            memoryStoreEvictionPolicy="LRU" />

</ehcache>

위 코드에서 <defaultCache> 태그는 반드시 존재해야 하는 태그로서, 코드에서 캐시를 직접 생성할 때 사용되는 캐시의 기본 설정값을 저장한다. <cache> 태그는 하나의 캐시를 지정할 때 사용된다. name 속성은 캐시의 이름을 지정하며, 코드에서는 이 캐시의 이름을 사용하여 사용할 Cache 인스턴스를 구한다.

설정 파일에 대한 자세한 내용은 뒤에서 살펴보기로 하자.

CacheManager 생성

ehcache.xml 파일을 작성했다면 그 다음으로 할 작업은 net.sf.ehcache.CacheManager 객체를 생성하는 것이다. CacheManager 객체는 다음의 두 가지 방법 중 한가지 방식을 사용하여 생성할 수 있다.

  • CacheManager.create() : 싱글톤 인스턴스 사용
  • new CacheManager() : 새로운 CacheManager 인스턴스 생성
CacheManager.create() 메소드는 싱글톤 인스턴스를 생성하기 때문에 최초에 한번 호출될 때에만 CacheManager의 초기화 작업이 수행되며, 이후에는 동일한 CacheManager 인스턴스를 리턴하게 된다. 아래는 CacheManager.create() 메소드의 사용 예이다.

CacheManager cacheManager = CacheManager.create();

싱글톤 인스턴스가 아닌 직접 CacheManager 객체를 조작하려면 다음과 같이 new를 사용하여 CacheManager 인스턴스를 생성해주면 된다.

CacheManager cacheManager = new CacheManager();

두 방식 모두 클래스패스에 위치한 ehcache.xml 파일로부터 캐시 설정 정보를 로딩한다.

만약 클래스패스에 위치한 ehcache.xml 파일이 아닌 다른 설정 파일을 사용하고 싶다면 다음과 같이 URL, InputStream, 또는 String(경로) 객체를 사용하여 설정 파일의 위치를 지정할 수 있다.

URL configFile = this.getClass().getResource("/ehcache_config_replicate.xml")
CacheManager cacheManager = new CacheManager(configFile);

Cache에 CRUD 수행

CacheManager 인스턴스를 생성한 다음에는 CacheManager 인스턴스로부터 Cache 인스턴스를 구하고, Cache 인스턴스를 사용하여 객체에 대한 캐시 작업을 수행할 수 있게 된다.

Cache 구하기
net.sf.ehcache.Cache 인스턴스는 CacheManager.getCache() 메소드를 사용하여 구할 수 있다.

CacheManager cacheManager = new CacheManager(configFileURL);
Cache cache = cacheManager.getCache("simpleBeanCache");

CacheManager.getCache() 메소드에 전달되는 파라미터는 ehcache.xml 설정 파일에서 <cache> 태그의 name 속성에 명시한 캐시의 이름을 의미한다. 지정한 이름의 Cache 인스턴스가 존재하지 않을 경우 CacheManager.getCache() 메소드는 null을 리턴한다.

Create/Update 작업 수행
Cache 인스턴스를 구한 다음에는 Cache.put() 메소드를 사용하여 캐시에 객체를 저장할 수 있다. 아래 코드는 Cache.put() 메소드의 사용예이다.

Cache cache = cacheManager.getCache("simpleBeanCache");

SimpleBean newBean = new SimpleBean(id, name);
Element newElement = new Element(newBean.getId(), newBean);
cache.put(newElement);

Cache.put() 메소드는 net.sf.ehcache.Element 객체를 전달받는다. Element 클래스는 캐시에 저장될 원소를 나타내며, 키와 값을 사용하여 원소를 표현한다. Element 객체를 생성할 때 첫번째 파라미터는 원소의 키를 의미하며, 두번째 파라미터는 원소의 값을 의미한다.

EHCache는 캐시에 저장될 각각의 객체들을 키를 사용하여 구분하기 때문에, Element 객체를 생성할 때 (의미상) 서로 다른 객체는 서로 다른 키를 사용해야 한다.

Map과 마찬가지로 EHCache가 제공하는 Cache는 삽입을 하거나 기존의 값을 수정할 때 모두 Cache.put() 메소드를 사용한다. 기존에 캐시에 저장된 객체를 수정하길 원한다면 다음과 같이 동일한 키를 사용하는 Element 객체를 Cache.put() 메소드에 전달해주면 된다.

Element newElement = new Element(id, someBean);
cache.put(newElement);
...
Element updatedElement = new Element(id, updatedBean);
cache.put(updatedElement);

Read 작업 수행
Cache에 보관된 객체를 사용하려면 Cache.get() 메소드를 사용하면 된다. Cache.get() 메소드는 키를 파라미터로 전달받으며, 키에 해당하는 Element 객체를 리턴하며 관련 Element과 존재하지 않을 경우 null을 리턴한다. 아래 코드는 Cache.get() 메소드의 사용예이다.

Element element = cache.get(key);
SimpleBean bean = (SimpleBean) element.getValue();

Element.getValue() 메소드는 캐시에 저장된 객체를 리턴한다. 만약 Serializable 하지 않은 객체를 값으로 저장했다면 다음과 같이 Element.getObejectValue() 메소드를 사용하여 값을 구해야 한다.

Element element = cache.get(key);
NonSerializableBean bean = (NonSerializableBean) element.getObjectValue();

Delete 작업 수행
Cache에 보관된 객체를 삭제하려면 Cache.remove() 메소드를 사용하면 된다. 아래 코드는 Cache.remove() 메소드의 사용예이다.

boolean deleted = cache.remove(key);

Cache.remove() 메소드는 키에 해당하는 객체가 존재하여 삭제한 경우 true를 리턴하고, 존재하지 않은 경우 false를 리턴한다.

CacheManager의 종료

사용이 종료된 CacheManager는 다음과 같이 shutdown() 메소드를 호출하여 CacheManager를 종료해야 한다.

cacheManager.shutdown();

Cache 값 객체 사용시 주의사항

캐시에 저장되는 객체는 레퍼런스가 저장된다. 따라서, 동일한 키에 대해 Cache.put()에 전달한 Element의 값과Cache.get()으로 구한 Element의 값은 동일한 객체를 참조하게 된다.

SimpleBean bean = ...;
Element element = new Element(key, bean);
cache.put(element);

Element elementFromCache = cache.get(key);
SimpleBean beanFromCache = (SimpleBean)elementFromCache.getValue();

(bean == beanFromCache); // true
(element == elementFromCache); // false

위 코드에서 Cache.put()에 전달된 element 객체와 Cache.get()으로 구한 elementFromCache 객체는 서로 다른 객체이다. 하지만, 두 Element 객체가 갖고 있는 값은 동일한 객체를 참조하고 있다. 따라서, 캐시에 값으로 저장된 객체를 변경하게 되면 캐시에 저장된 내용도 변경되므로, 캐시 사용시 이 점에 유의해야 한다.

캐시 설정

캐시 설정 파일에 <cache> 태그를 이용하여 캐시를 설정했었다. 캐시 설정과 관련하여 <cache> 태그는 다양한 속성을 제공하고 있는데, 이들 속성에는 다음과 같은 것들이 존재한다.

name 캐시의 이름 필수
maxElementsInMemory 메모리에 저장될 수 있는 객체의 최대 개수 필수
eternal 이 값이 true이면 timeout 관련 설정은 무시되고, Element가 캐시에서 삭제되지 않는다. 필수
overflowToDisk 메모리에 저장된 객체 개수가 maxElementsInMemory에서 지정한 값에 다다를 경우 디스크에 오버플로우 되는 객체는 저장할 지의 여부를 지정한다. 필수
timeToIdleSeconds Element가 지정한 시간 동안 사용(조회)되지 않으면 캐시에서 제거된다. 이 값이 0인 경우 조회 관련 만료 시간을 지정하지 않는다. 기본값은 0이다. 선택
timeToLiveSeconds Element가 존재하는 시간. 이 시간이 지나면 캐시에서 제거된다. 이 시간이 0이면 만료 시간을 지정하지 않는다. 기본값은 0이다. 선택
diskPersistent VM이 재 가동할 때 디스크 저장소에 캐싱된 객체를 저장할지의 여부를 지정한다. 기본값은 false이다. 선택
diskExpiryThreadIntervalSeconds Disk Expiry 쓰레드의 수행 시간 간격을 초 단위로 지정한다. 기본값은 120 이다. 선택
memoryStoreEvictionPolicy 객체의 개수가 maxElementsInMemory에 도달했을 때,모메리에서 객체를 어떻게 제거할 지에 대한 정책을 지정한다. 기본값은 LRU이다. FIFO와 LFU도 지정할 수 있다. 선택

아래 코드는 몇 가지 설정 예이다.

<!--
sampleCache1 캐시. 최대 10000개의 객체를 저장할 수 있으며, 
5분 이상 사용되지 않거나 또는 10분 이상 캐시에 저장되어 있을 경우 
캐시에서 제거된다. 저장되는 객체가 10000개를 넘길 경우, 
디스크 캐시에 저장한다.
-->
<cache name="sampleCache1"
       maxElementsInMemory="10000"
       maxElementsOnDisk="1000"
       eternal="false"
       overflowToDisk="true"
       timeToIdleSeconds="300"
       timeToLiveSeconds="600"
       memoryStoreEvictionPolicy="LFU"
       />

<!--
sampleCache2 캐시. 최대 1000개의 객체를 저장한다. 
오버플로우 된 객체를 디스크에 저장하지 않기 때문에 
캐시에 최대 개수는 1000개이다. eternal이 true 이므로, 
timeToLiveSeconds와 timeToIdleSeconds 값은 무시된다.
-->
<cache name="sampleCache2"
       maxElementsInMemory="1000"
       eternal="true"
       overflowToDisk="false"
       memoryStoreEvictionPolicy="FIFO"
       />

<!--
sampleCache3 캐시. 오버플로우 되는 객체를 디스크에 저장한다.
디스크에 저장된 객체는 VM이 재가동할 때 다시 캐시로 로딩된다.
디스크 유효성 검사 쓰레드는 10분 간격으로 수행된다.
-->
<cache name="sampleCache3"
       maxElementsInMemory="500"
       eternal="false"
       overflowToDisk="true"
       timeToIdleSeconds="300"
       timeToLiveSeconds="600"
       diskPersistent="true"
       diskExpiryThreadIntervalSeconds="600"
       memoryStoreEvictionPolicy="LFU"
       />

분산 캐시

EHCache는 분산 캐시를 지원한다. EHCache는 피어(peer) 자동 발견 및 RMI를 이용한 클러스터간 데이터 전송의 신뢰성 등 분산 캐시를 위한 완전한 기능을 제공하고 있다. 또한, 다양한 옵션을 통해 분산 상황에 맞게 설정할 수 있도록 하고 있다.

참고로, EHCache는 RMI를 이용하여 분산 캐시를 구현하고 있기 때문에, Serializable 한 객체만 분산 캐시에서 사용 가능하다. 키 역시 Serializable 해야 한다.

분산 캐시 구현 방식

EHCache는 한 노드의 캐시에 변화가 생기면 나머지 노드에 그 변경 내용을 전달하는 방식을 사용한다. 즉, 클러스터에 있는 캐시 인스턴스가 n개인 경우, 한번의 변경에 대해 n-1개의 변경 통지가 발생한다.

각 노드의 캐시간 데이터 전송은 RMI를 통해서 이루어진다. EHCache가 데이터 전송 기술로서 RMI를 사용하는 이유는 다음과 같다.

  • 자바에서 기본적으로 제공하는 원격 메커니즘
  • 안정화된 기술
  • TCP 소켓 옵션을 튜닝할 수 있음
  • Serializable 한 객체를 지원하기 때문에, 데이터 전송을 위해 XML과 같은 별도의 포맷으로 변경할 필요가 없음
노드 발견

EHCache는 클러스터에 새로운 노드가 추가돌 경우 해당 노드를 자동적으로 발견하는 방식과, 지정된 노드 목록에 대해서만 클러스터의 노드로 사용하는 방식을 지원하고 있다.

멀티캐스트 방식

멀티캐스트 모드를 사용한 경우, 지정한 멀티캐스트 IP(224.0.0.1~239.255.255.255)와 포트에 참여하는 노드를 자동으로 발견하게 된다. 지정한 IP와 포트에 참여한 노드는 자기 자신을 다른 노드에 통지한다. 이 방식을 사용하면 클러스터에 동적으로 노드를 추가하거나 제거할 수 있다.

노드 목록 지정 방식

클러스터에 포함되는 노드 목록을 지정한다. 동적으로 새로운 노드를 추가하거나 기존 노드를 제거할 수 없다.

분산 캐시 설정

분산 캐시를 사용하기 위해서는 다음과 같은 세 개의 정보를 지정해주어야 한다.

  • CacheManagerPeerProvider - 피어 발견 관련 설정
  • CacheManagerPeerListener - 메시지 수신 관련 설정
  • 캐시별 CacheReplicator - 메시지 생성 규칙 설정
CacheManagerPeerProvider 설정

CacheManagerPeerProvider는 새롭게 추가된 노드를 발견하는 방식을 지정한다.

노드를 자동으로 발견하는 멀티캐스트 방식을 사용하려면 다음과 같이 설정한다.

<cacheManagerPeerProviderFactory
    class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"
    properties="peerDiscovery=automatic, 
                    multicastGroupAddress=230.0.0.100, multicastGroupPort=1234" />

위 코드에서 properties 속성의 값에 사용된 프로퍼티는 다음과 같다.

peerDiscovery automatic으로 지정하면 멀티캐스트 방식을 사용한다.
multicaseGroupAddress 멀티캐스트 IP
multicaseGroupPort 포트 번호

하나의 클러스터에 포함될 노드들은 동일한 멀티캐스트 IP와 포트 번호를 사용해야 한다.

클러스터에 참여할 노드 목록을 지정하는 IP 방식을 사용하려면 다음과 같이 설정한다.

<cacheManagerPeerProviderFactory
    class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"
    properties="peerDiscovery=manual, 
                    rmiUrls=//server2:12345/cache1|//server2:12345/cache2" />

위 코드에서 properties 속성의 값에 사용된 프로퍼티는 다음과 같다.

peerDiscovery manual로 지정한 IP 지정 방식이다.
rmiUrls 분산 노드에 참여할 서버 및 캐시 목록을 지정한다. 현재 노드의 정보는 포함시켜서는 안 된다.

이 경우, rmiUrls에 명시된 포트 번호는 뒤에 살펴볼 CacheManagerPeerListener가 사용할 포트 번호를 지정해주어야 한다.

CacheManagerPeerListener 설정

노드를 발견하는 방식을 지정했다면, 다음으로 할 작업은 클러스터에 있는 다른 노드에서 발생한 변경 정보를 수신할 때 사용할 포트 번호를 지정하는 것이다. 다음과 같은 코드를 이용하여 수신과 관련된 포트 번호를 설정할 수 있다.

<cacheManagerPeerListenerFactory
    class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory"
    properties="port=12345, socketTimeoutMillis=120000" />

위 코드에서 properties 속성의 값에 사용된 프로퍼티는 다음과 같다.

port 메시지를 수신할 때 사용되는 포트
socketTimeoutMillis 이 노드에 메시지를 보냈을 때 메시지 전송을 기다리는 시간. 기본값은 2000ms.

캐시별 CacheReplicator 설정

분산 환경에 적용되어야 하는 캐시는 캐시의 내용이 변경되었을 때 다른 노드에 있는 캐시에 변경 내역을 알려주어야 한다. <cacheEventListenerFactory> 태그를 사용하면, 언제 어떻게 캐시의 변경 내역을 통지할지의 여부를 지정할 수 있다. 아래 코드는 설정의 예이다.

<cache name="simpleBean"
      maxElementsInMemory="100"
      eternal="false"
      overflowToDisk="false"
      timeToIdleSeconds="300"
      timeToLiveSeconds="600"
      memoryStoreEvictionPolicy="LRU">
       <cacheEventListenerFactory 
           class="net.sf.ehcache.distribution.RMICacheReplicatorFactory" 
           properties="replicateUpdatesViaCopy=true,replicateUpdates=true" />
</cache>

위 코드와 같이 <cacheEventListenerFactory>의 구현 클래스로 RMICacheReplicatorFactory를 지정하면 캐시에 변경이 생길 때 마다 해당 변경 내역을 클러스터에 참여하고 있는 노드의 캐시에 통지하게 된다. properties 속성에 프로퍼티를 지정하면, 캐시 요소의 추가, 변경, 삭제 등에 대해 통지 방식을 적용할 수 있다. 설정할 수 있는 프로퍼티는 다음과 같다.

replicatePuts 캐시에 새로운 요소가 추가됐을 때 다른 노드에 복사할지의 여부
replicateUpdates 캐시 요소의 값이 변경되었을 때 다른 노드에 값을 복사할지의 여부
replicateRemovals 캐시 요소가 삭제되었을 때 다른 노드에 반영할지의 여부
replicateAsynchronously 비동기로 값을 복사할지의 여부
replicateUpdatesViaCopy 새로운 요소를 다른 노드에 복사할 지 아니면 삭제 메시지를 보낼지의 여부
asynchronousReplicationIntervalMillis 비동기 방식을 사용할 때 변경 내역을 다른 노드에 통지하는 주기. 기본값은 1000.

위 속성의 기본값은 모두 true이다. 따라서, 기본 설정값을 사용하려면 다음과 같이 properties 속성을 사용하지 않아도 된다.

<cache name="simpleBean" ...
      memoryStoreEvictionPolicy="LRU">
       <cacheEventListenerFactory 
           class="net.sf.ehcache.distribution.RMICacheReplicatorFactory" />
</cache>

어플리케이션 구동시 캐시 데이터 로딩하기

CacheManager가 초기화 될 때, 클러스터에 있는 다른 캐시로부터 데이터를 로딩할 수 있다. 이는 초기 구동이 완료된 후 곧 바로 서비스를 제공할 수 있음을 의미한다. 초기 구동시 다른 노드로부터 캐시 데이터를 로딩하려면 다음과 같이 <bootstrapCacheLoaderFactory> 태그의 구현 클래스를 RMIBootstrapCacheLoaderFactory로 지정해주면 된다.

<cache name="simpleBean" ...
      memoryStoreEvictionPolicy="LRU">
       <bootstrapCacheLoaderFactory
           class="net.sf.ehcache.distribution.RMIBootstrapCacheLoaderFactory"
           properties="bootstrapAsynchronously=true,
                       maximumChunkSizeBytes=5000000" />

       <cacheEventListenerFactory 
           class="net.sf.ehcache.distribution.RMICacheReplicatorFactory" />
</cache>

RMIBootstrapCacheLoaderFactory에 전달 가능한 프로퍼티 목록은 다음과 같다.

bootstrapAsynchronously 비동기적으로 수행할지의 여부를 지정
maximumChunkSizeBytes 클러스터의 다른 노드로부터 로딩 가능한 데이터의 최대 크기

RMIBoostrapCacheLoaderFactory를 설정하면 캐시를 초기화 할 때, 원격지 노드의 캐시에 저장된 데이터를 로딩하여 로컬 캐시에 저장한다.

분산 캐시 고려사항

분산 캐시를 사용할 때에는 다음과 같은 내용을 고려해야 한다.

  • 노드 증가에 따라 네트워크 트래픽 증가:
    많은 양의 네트워크 트래픽이 발생할 수 있다. 특히 동기 모드인 경우 성능에 영향을 받을 수 있다. 비동기 모드인 경우 버퍼에 변경 내역을 저장하였다가 일정한 주기로 버퍼에 쌓인 내역을 다른 노드에 통지하기 때문에 이 문제를 다소 완하시킬 수 있다.
  • 데이터 불일치 발생 가능성:
    두 노드에서 동시에 동일한 캐시의 동일한 데이터에 대한 변경을 수행할 경우, 두 노드 사이에 데이터 불일치가 발생할 수 있다. 캐시 데이터의 불일치가 매우 심각한 문제가 될 경우, 동기 모드(replicateAsynchronously=false)와 복사 메시지 대신 삭제 메시지를 전송(replicateUpdatesViaCopy=false)함으로써 이 문제를 해결할 수 있다.
관련링크:

참조 : http://javacan.tistory.com/123


annotation으로 javaBean의 중북되고 반복되는 코드를 한결 편리하게 사용할 수 있는 유틸이 있네요.
getter, setter, toString, hasCode등과 같이 bean을 만들때 매번 불편하고 가독성 없이 추가만 해주었는데
이 유틸리티를 사용하면 이런 불편함이 많이 줄어들것으로 생각이 됩니다.
딱히 javaBean이 아니라 다른곳에서도 유용하게 사용하실 수 있습니다.
사용법및 설치법은 다른 여러가지 방법으로도 사용을 할 수 있다고 하나 이클립스에서 설정하는 법을 설명하겠습니다.

다운로드 : http://projectlombok.org/
설치방법 : 해당 사이트에서 lombok.jar 파일을 다운받은 후 console에서 다운로드 경로로 이동한 다음 
java -jar lombok.jar 명령을 입력하게 되면 GUI 창이 노출이 됩니다. 해당 창이 노출이 되면 eclipse 파일의 경로를 지정하고
install/update 버튼을 선택하기면 하면 설치가 끝납니다.

사용방법 : http://www.ibm.com/developerworks/kr/library/os-lombok/index.html


ㅁ Pattern 클래스 : 정규식 패턴을 지정 정의
ㅁ Matcher 클래스 : 정규식 패턴을 데이터와 비교 

1단계 : 정규식에 해당하는 Pattern 인스턴스를 얻는다.
Pattern p = Pattern.compile("c[a-z]*");

2단계 : 정규식을 적용하여 비교할 Matcher 인스턴스를 얻는다.
Matcher m = p.matcher(data[i]);

3단계 : 정규식 여부를 확인한다.
if문 사용시 : if(m.matches()) {}

 

ㅁ 정규식 Rule
- 리터럴([]) 범위 지정 : [0-9] - 숫자, [a-z] - 소문자, [a-zA-Z]알파벳
- 리터럴(.) 모든범위문자
- 리터럴* : 해당 리터럴이 0번 이상 반복
- 리터럴+ : 해당 리터럴이 1번 이상 반복(반드시 1번은 나옴)
- ^리터럴 : 리터럴 조건을 만족하지 않는~
- 리터릴|리터럴 : 택1
- 리터럴{숫자} : 숫자만큼 반복
- (리터럴) : 리터럴 그룹화, 그룹화된 리터럴은 Matcher클래스의 인스턴스 메소드 group(int i) 호출을 통해 그룹단위로 얻을 수 있다.

 

ex1) [b|c].*{7} : b 또는 c로 시작하는 8자리 문자열
ex2) c.*d : c로 시작하고 d로 끝나는 문자열
ex3) 전화번호 정규식 : (0\d{1,2})-(\d{3,4})-(\d{4})

>>
 String ptn = "(0\\d{1,2})-(\\d{3,4})-(\\d{4});
 Pattern p = Pattern.compile(ptn);
 Matcher m = p.matcher("012-1234-4567");

>>
String regex = ".*he5|.*h5";
  String str = "he5chhh.h5hh.he85";
  Pattern p = Pattern.compile(regex);
  Matcher m = p.matcher(str.toLowerCase());
  System.out.println(m.matches());

 if(m.find())
  System.out.println( m.group() + " has " + m.group(1) + ", " + m.group(2) + ", " + m.group(3) );

[출처] http://blog.naver.com/junz0279?Redirect=Log&logNo=70046805130


UPDATE: The new version of acegi has itself’s annotation implement. So you don’t need this any more.

annotation is so good that i can’t wait to use it in acegi

code sample   

import java.util.List;
import org.gotblog.common.acegi.SecurityConfig;
import org.springframework.transaction.annotation.Transactional;


public interface BookManager {
@Transactional
@SecurityConfig( { "ROLE_USER", "ROLE_ANONYMOUS" })
public List listBook();
}
here’s my implementation for it
    
package org.gotblog.common.acegi;
 
    import static java.lang.annotation.ElementType.METHOD;
    import static java.lang.annotation.ElementType.TYPE;
    import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
    import java.lang.annotation.Retention;
    import java.lang.annotation.Target;
 
    @Target( { TYPE, METHOD })
    @Retention(RUNTIME)
    public @interface SecurityConfig {
	String[] value();
    }
    package org.gotblog.common.acegi;
 
    import java.lang.annotation.Annotation;
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    import java.util.Collection;
    import java.util.HashSet;
    import java.util.Set;
 
    import org.springframework.metadata.Attributes;
 
    public class AcegiAnnotationAttributes implements Attributes {
	// TODO need to add cache
 
	public Collection getAttributes(Class targetClass) {
		Set<net.sf.acegisecurity.SecurityConfig> configs =
			new HashSet<net.sf.acegisecurity.SecurityConfig>();
		for (Annotation annotation : targetClass.getAnnotations()) {
			if (annotation instanceof SecurityConfig) {
				SecurityConfig config = (SecurityConfig) annotation;
				for (String value : config.value()) {
					configs.add(
new net.sf.acegisecurity.
                                                          SecurityConfig(value));
				}
				break;
			}
		}
		return configs;
	}
 
	public Collection getAttributes(Class targetClass, Class filter) {
		throw new IllegalArgumentException("Not support filter");
	}
 
	public Collection getAttributes(Method targetMethod) {
		Set<net.sf.acegisecurity.SecurityConfig> configs =
			new HashSet<net.sf.acegisecurity.SecurityConfig>();
		for (Annotation annotation : targetMethod.getAnnotations()) {
			if (annotation instanceof SecurityConfig) {
				SecurityConfig config = (SecurityConfig) annotation;
				for (String value : config.value()) {
					configs.add(
new net.sf.acegisecurity.
                                                         SecurityConfig(value));
				}
				break;
			}
		}
		return configs;
	}
 
	public Collection getAttributes(Method targetMethod, Class filter) {
		throw new IllegalArgumentException("Not support filter");
	}
 
	public Collection getAttributes(Field targetField) {
		throw new IllegalArgumentException("Not support field annotation");
	}
 
	public Collection getAttributes(Field targetField, Class filter) {
		throw new IllegalArgumentException("Not support field annotation");
	}
 
    }

    
<bean id="acegiAnnotationAttributes"
class="org.gotblog.common.acegi.AcegiAnnotationAttributes"/>
<bean id="objectDefinitionSource"
class="net.sf.acegisecurity.intercept.method.MethodDefinitionAttributes">
<property name="attributes">
<ref local="acegiAnnotationAttributes"/>
</property> </bean>
it works fine and i haven’t optimized it for performance
 
출처 : http://weavesky.com/2005/04/19/annotation-for-acegi/

JAVA를 처음 배우는 이들에게 가장 중요한건 무엇일까?

또 실무에서 JAVA를 통한 개발이 3년 이상이 지났는데도 스킬 향상 없이 항상 그 자리에 있는 개발자들의 문제는 무엇일까?

그 해답은 기본이다.

 

기초를 탄탄히 하면 경력이 올라갈수록 더욱 설계가 탄탄하고 제대로 된 개발을 할 수 있는 반면에 기초가 부족하면 경력이 올라갈수록 높은 스킬을 요구하는 프로그램을 개발해야 한다.

하지만 기초 없이 개발된 프로그램은 개발할 때는 전혀 아무런 문제가 발생되지 않지만 배포를 하게 되거나 서비스 오픈을 하고 얼마 지나지 않으면 조금씩 문제들이 발생을 하게 된다.

작게는 작은 오류 메시지나 일시적인 오동작으로, 크게는 서비스 중인 프로그램이 아예 동작을 안하게 되는 경우까지 발생한다.

이런 소스들을 파헤쳐 보면 정말이지 간단하고 사소한 문제로 인하여 문제가 발생을 했는데 원리나 기초만 제대로 파악하고 있으면 발생되지 않을 문제들이 다수가 있다.

이런 문제들은 이제 막 실무에 들어간 개발자들만이 다년간의 경력을 가진 개발자들에게도 흔하게 일어나는 현실이다.

 

'난 정말 JAVA를 공부한 적이 없다구요.'

이 책에는 이러한 기초나 원리에 대한 내용을 위주로 기술을 하고 있다. 초보자들이 꼭 알아야 할 기초와 그 기초가 이루어지게 되는 원리들을 이해 하기 쉽도록 설명을 하고 있다.

그리고 온라인 학습 사이트에 가입만 하면 동영상 강의를 무료로 제공해주니 책으로도 이해가 안되는 부분이 있을 경우 활용하면 좋을 듯 하다.

강의를 제공하는 URL은 다음과 같다.

http://www.orentec.co.kr/teachlist/JAVA_BASIC_1/teach_sub1.php

 

일반적으로 다른 JAVA관련 책들과 비교했을 때 두께는 결코 얇은 편이 아니다.

평균적으로 봤을때는 두꺼운 편에 속하는 듯 한다. 하지만 목차는 조금 줄은 듯 한데 네트워킹에 관련된 내용이 없기 때문인듯 하다. 이 부분은 개인적으로는 다소 아쉬운 부분이긴 하지만 네트워크 프로그래밍 부분은 서점에 가도 책 한권으로 따로 만들어질 만큼 분량이 크기 때문에 페이지 조금 늘려서 목차를 남긴다고 해도 크게 활용 되지 못할 듯 하니 냉정하게 이 부분이 뺀 선택은 저자의 많은 고민거리였을 듯 하다.

 

개인적으로 이 책에서 다소 아쉬운 부분이 있는데 그것은 전체적인 소스에 대해서 JAVA의 기본적인 Convention에서 조금 벗어난다는 것이다.

 

이 책에서는 예제 소스에서 대해서 다음과 같이 보여준다.

class Method2Param

{

       public static void main(String[] args)

       {

             System.out.println("Hello java");

       }

}

 

하지만 기본 Convention은 다음과 같다.

public class Method2Param {

       public static void main(String[] args) {

             System.out.println("Hello java");

       }

}

 

이는 성능에 영향을 미치거나 문제가 되지는 않는 부분이지만 추후에는 조금 헷갈려 하는 부분일 듯 하다. 이런식의 코딩은 C#이나 actionscript 코딩이기 때문이다. 이는 저자가 많은 언어를 습득했다고 생각이 든다. 하지만 JAVA를 배우는데는 전혀 관계가 없지만 개인적으로는 다소 아쉽긴 하다.

 

이 책의 장점은 위에서 설명했듯이 전체적으로 목차는 좀 작은 편이긴 하나 그 책의 두께만큼은 다른 책보다 오히려 두껍다. 이는 하나의 목차에 대해서는 더욱 알차게 세부목차가 채워져 있고 그 내용 또한 상세하게 기술되어져 있다는 것을 말해준다. 저자가 과감하게 일반 서적과의 비교를 꾀하고 가장 필요한 항목이 무엇인가를 고민한 끝에 내린 결정이 아닌가 생각이 든다.

 

 

그리고 이 책은 꾸밈이 없다.

이 책을 만든 의도는 JAVA를 모르는 이를 대상으로 기본을 탄탄히 하기 위한 의도이지 소스를 좀 더 보기 좋게 꾸며서 기본서도 아닌 활용서도 아닌 그런 어중간한 서적이 아니다.

기초를 대상으로 무엇이 필요한지를 기술하고 그 내용 중에 헷갈려 하는 부분은 그림으로 상세하게 설명을 한다. 소스는 이번 내용에 배운 소스만으로 코드를 구성한다. 그리고 마지막으로 배운 내용을 퀴즈로 정리하여 복습 하도록 하고 있다.

저자는 지필의도대로 기초의 중요성을 강조하고 그 내용을 단계적으로 암기 할 수 있도록 하고 있다. 기초가 탄탄하면 그 활용도는 더욱 배가 되기 때문이다.

 

아래 그림은 이 책에 삽입된 이미지 들이다. 내용으로 한번 설명을 하고 이해를 돕기 위한 이미지를 제공한다.

 

 

 

 

 

 



사실 어떤 좋은 도서가 있던지 개인이 노력을 하지 않으면 아무 소용이 없는 것이다.

노력 없이 좋은 결과를 얻기는 힘들지만 또 노력만 가지고는 좋은 결과를 내기 또한 어렵다.

요즘 세상은 노력하고 열심히 하는 사람보다는 잘하는 사람을 원하기 때문이다.

누구든지 1~2년을 잘 할 수 있다. 몇 년은 노력으로 그 흠을 매울 수 있기 때문이다. 하지만 시간이 흘러 갈수록 기초가 부족한 사람은 활용도 못하고 스스로 무너지게 되어있다. 그러니 점점 더 남들과의 거리도 멀어진다. 이는 JAVA과 아니라 어떤 언어든지 똑같다.

 

노력은 하지만 제자리 걸음을 걷고 있거나 이제 자바를 시작 한다면 이 책으로 통하여 시작해 보기 바란다. 아마도 이 책을 다 덮을 때 쯤 아니 쓰레드와 동기화(700P) 챕터까지만 공부해도(그 외에는 그때 필요할 때 보면 되기 때문인다) 자바의 대한 기본 문법, 사용법과 객체지향 개념을 정확이 이해를 하게 되지 않을까 생각한다. 열정과 노력을 가진 당신에게 목적지 까지 태워줄 가장 빠른 운송 수단이 될 것이다.

하지만 내용을 모두 이해 했다고 해도 소스를 모두 다 타이핑 해보기 바란다.

열 번 이해하는 것 보다 한번 타이핑 하는게 오히려 큰 도움이 된다. 개발 언어를 공부한다면 당연히 그렇게 해야만 한다.

'일상 > ' 카테고리의 다른 글

[서평] 난 정말 JAVA를 공부한 적이 없다구요.  (0) 2010.02.23
사랑하기 때문에  (0) 2009.09.18
Effective Java  (0) 2009.09.18
Adobe FLEX 3 실전 트레이닝 북  (0) 2009.09.18
퍼레이드  (0) 2009.09.09



자바를 통해서 실무에서 개발을 하고 있거나 현재 자바를 공부하고 있는 분이라면 자바 기본서 하나 쯤은 모두 가지고 있을 것이다. 하지만 모두 가지고 있는 기본서를 고르기란 쉽지 않은 것 또 한 사실이다.

 

그냥 서점가서 구매만 하면 되는데 머가 그렇게 어렵지?” 하고 말 할 수도 있지만 책을 고르고 구입하기란 쉬운데 그렇게 구매한 책을 한 번만 보고 책꽂이에 먼지가 쌓이도록 항상 꽂아 두느냐, 아니면 필요할 때 마다 필요한 부분을 찾아보고 활용 할 수 있느냐가 중요한 것이다.

 

자바 기본서는 모두 다 똑같다? 한마디로 말하면 아니다.

모든 기본서가 말하고자 하는 자바의 기본 문법은 비슷 하다고 할 수 있지만 그 하나의 문법을 어떻게 이해하기 쉽도록 풀어 쓰느냐, 어떤 상황에서 사용 할 수 있느냐를 예를 들어서 설명하는 것이 중요하다. 이는 이론만 가지고 집필한 책과 이론과 경험을 바탕으로 집필된 책과는 전혀 다르다

 

 

난 정말 JAVA를 공부한 적이 없다구요.’

 

서점에 가면 정말 수 많은 JAVA 관련 책들이 존재한다. 하지만 그 책을 펼쳐 내용을 보지 않으면 모두 다 같은 JAVA 서적 같아서 어떤 서적을 선뜻 골라야 할지도 난감하다.

난 정말 JAVA를 공부 한적이 없다구요’.

제목은 상당히 호기심을 유발하는 제목이다. 누구나 한번쯤 수많은 자바 관련 서적 중에 한번씩 뽑아서 볼 듯한 제목이다. 물론 서적이 제목만으로 말하는건 아니지만 내용이 충실하고 자신이 있으면 일단 독자들에게 선택할 수 있는 기회를 줘야 한다. 이 책은 흥미 있는 제목으로 독자들에게 선택할 수 있는 권리는 줬다고 생각한다.

이제 선택은 독자들의 몫인 것이다.

 

어떤 서적을 구입하던지 처음 보게 되는 부분은 머리말을 우선 보게 된다. 머리말을 읽어 보면 집필자의 성격과 어떤 전하고자 하는 집필자의 의도를 알 수 있어 서적을 읽을 때에도 그 집필자 말하는 의도를 제대로 짚을 수 있기 때문이다. 머리말을 보면 저자는 결코 이 서적을 칭찬하지도 않고 자신 있게 추천하지도 않는다. 다만 이 서적을 소신 있게 집필하였다는 말만 하고 있다. 하지만 이 서적은 그렇게 겸손할 필요는 없을 듯 하다.

내용을 보면 자바를 배우는 독자들이 정말 어떤 것들을 알아야 하는지 어떤 부분을 궁금해 할지를 미리 알고 기술 하고 있기 때문이다. 이는 풍부한 경험과 오랫동안 계획되어진 노력에서 의해서만 나올 수 있는 이 서적만의 가장 큰 장점이라고 할 수 있겠다. 또 하나는 일반 서적에는 흔히 볼 수 없는 다수의 그림들이 있다.

단순히 글로만 기술하고 결과 출력만을 보여준 많은 다른 서적들과는 다르게 많은 그림들을 삽입하여 독자들이 이해하기 난해한 부분들은 그림으로 설명함으로 독자들이 쉽게 자바의 원리를 이해 할 수 있게 하였다.

이게 무슨 장점이지? 라고 생각 할 수도 있겠지만 이런 부분은 이제 막 자바를 공부하는 개발자들에게는 정말 큰 선물이 될 것이다. 아직도 이런 소중한 내용을 얻기 위해서 수많은 발 품을 팔아 얻는 사람이 적지 않기 때문이다.  

 

이제 막 자바 공부 시작하기 위해 기본서를 고르고 있고 몇몇 개의 좋은 서적들을 리스트에 올리고 있다면  난 정말 JAVA를 공부한 적이 없다구요도 함께 추가하여 비교해보기 바란다. 이 보다 더 좋은 관련 서적도 많이 있을 것이고 독자들의 취향에 따라서 이 책이 맞지 않을 수도 있겠지만 일단 비교해보고 판단하기를 권한다. 현재 필자는 여러 권의 JAVA 관련 책을 가지고 있지만 남들에게 추천 해줄 만한 책은 몇 권 되지 않는다. 그렇게 때문에 JAVA 공부를 시작하는 독자들에게는 좋은 기본서 선택이 중요하다고 말하고 있고 어떤 책을 강력하게 추천 하는 것은 더욱 더 부담스럽고 조심스럽다. 하지만 난 정말 JAVA를 공부한 적이 없다구요는 JAVA 공부를 처음 시작하는 사람에게나 기초가 부족한 개발자들에게는 몇 권 되지 않는 좋은 기본서가 될 것이라고는 확신하고 개발을 할 때 항상 옆에 두고 펼쳐 볼 수 있는 좋은 바이블이 되지 않을까 생각한다.  좋은 서적들은 나름대로 장점이 있고 독자들에게도 맞는 도서가 있기 때문에 판단은 독자들이 하겠지만 개인 적으로는 이 도서에 후한 점수를 주고 싶다.


'일상 > ' 카테고리의 다른 글

[서평] 난 정말 JAVA를 공부한 적이 없다구요  (0) 2010.03.03
사랑하기 때문에  (0) 2009.09.18
Effective Java  (0) 2009.09.18
Adobe FLEX 3 실전 트레이닝 북  (0) 2009.09.18
퍼레이드  (0) 2009.09.09

메일 발송이나 초대장 발송등의 경우 내용은 동일하지만 몇가지의 정보만 변경되는 경우가 많다.
이런 경우에 MessageFormat 을 사용하면 괜찮겠다.

/**
 *
 */
package kr.pe.anaconda.test;

import java.io.File;
import java.text.MessageFormat;
import java.text.ParseException;
import java.util.Scanner;

/**
 * @author diem
 *
 */
public class MessageFormatTest {
 
 /**
  * msg 문자열에  {숫자}로 지정된 영역을 arguments 배열에 담긴 값으로 채워준다. 
  */
 private static void exam1(){
  String msg = "Name: {0} \nTel: {1} \nAge: {2} \nBirthday: {3}. ";
  Object[] arguments = {"홍길동", "123-1234-1234", "31", "06-22"};
  String result = MessageFormat.format(msg, arguments);
  System.out.println(result);
 }
 
 /**
  * 일정한 형식의 문자열에 패턴에 지정된 문자열을 입력한다.
  */
 private static void exam2(){
  String tableName = "USER";
  String msg = "INSERT INTO " +
    tableName +
    " VALUES (''{0}'', ''{1}'', ''{2}'', ''{3}'');";
  
  Object[][] arguments = {
    {"홍길동", "02-123-1234", "19", "10-11"},
    {"전우치", "02-123-1235", "520", "10-15"}    
  };
  
  for(int i = 0; i < arguments.length; i++){
   String result = MessageFormat.format(msg, arguments[i]);
   System.out.println(result);
  }
 }
 
 /**
  * 문자열에서 지정된 패턴으로 데이터를 추출한다.
  * @throws ParseException
  */
 private static void exam3() throws ParseException{
  String[] data = {
   "INSERT INTO USER VALUES ('홍길동', '02-123-1234', '19', '10-11');",
   "INSERT INTO USER VALUES ('전우치', '02-123-1235', '520', '10-15');"
  };
  
  String pattern = "INSERT INTO USER VALUES ({0}, {1}, {2}, {3});";
  MessageFormat mf = new MessageFormat(pattern);
  
  for(int i = 0; i < data.length; i++){
   Object[] objs = mf.parse(data[i]);
   for(int j = 0; j < objs.length; j++ ){
    System.out.println(objs[j]);
   }
  }
 }
 
 
 /**
  * 지정된 문자열에 정해진 패턴에 맞게 파일에서 정보를 읽어와서 데이타를 채운다.
  * user.text
  * '홍길동', '02-123-1234', '19', '10-11'
  * '전우치', '02-123-1235', '520', '10-15'  
  * @throws Exception
  */
 private static void exam4() throws Exception{
  String tableName = "USER";
  String fileName = "c:\\Noname2.txt";
  String msg = "INSERT INTO " +
   tableName +
   " VALUES (''{0}'', ''{1}'', ''{2}'', ''{3}'');";
  File file = new File(fileName);
  if(file.exists()){
   Scanner s = new Scanner(file);
   String pattern = "{0},{1},{2},{3}";
   MessageFormat mf = new MessageFormat(pattern);
   while(s.hasNextLine()){
    String line = s.nextLine();
    Object[] objs = mf.parse(line);
    System.out.println(MessageFormat.format(msg, objs));
   } 
   
   //작업이 완료 되면 파일을 닫아준다.
   s.close();
  }  
 }

 /**
  * @param args
  */
 public static void main(String[] args) throws Exception {
  // TODO Auto-generated method stub
  //exam1();
  //exam2();
  //exam3();
  exam4();
 }

}


출처 : http://cafe.naver.com/flexcomponent/12264

아시는 분들도 계시겠으나...
DataService 를 사용하지 않고 XML 응답만으로 Flex 를 개발할때
java 서버측 구현에 쉽게 적용 가능한 Caster 라는 XML 변환 프레임웍을 소개해 드립니다.

아래의 링크에서 Caster 를 다운로드 받습니다. 샘플도 많습니다. 다운받으세요~
http://www.castor.org/download.html

아래의 링크는 맵핑에 관련된 설명입니다.
http://www.castor.org/xml-mapping.html


간단히 소개해 드리자면
------------------------------------------------
Java결과객체 → Caster프레임웍 → 변환된 XML
                                ↑
                       XML맵핑룰.xml
------------------------------------------------
와 같이 동작하는 매우 일반적 형태의 프레임웍입니다.

 

바로 사용할 수도 있으나 사용의 편이성을 위해 아래와 같은 클래스를 맹길었습니다.

 

public class CasterMapper {

    /** XML 맵핑 객체 */
    Mapping mapping;

    // .. 스트림이나 파일을 받는 생성자 등등등...

    /**
     * XML 파일을 객체로 변환한다
     * @param xmlIn
     * @return
     * @throws MappingException
     * @throws ValidationException
     * @throws MarshalException
     */
    public Object xmlToObject(Reader xmlReader)
            throws MappingException, MarshalException, ValidationException {
        Unmarshaller unmarshaller = new Unmarshaller(this.mapping);
        return unmarshaller.unmarshal(new InputSource(xmlReader));
    }

 

    /**
     * 객체를 XML 로 변환한다.
     * @param obj
     * @param xmlOut
     * @throws IOException
     * @throws MappingException
     * @throws ValidationException
     * @throws MarshalException
     */
    public void objectToXml(Object obj, Writer xmlWriter)
            throws IOException, MappingException, MarshalException, ValidationException {
        Marshaller marshaller = new Marshaller(xmlWriter);
        marshaller.setMapping(this.mapping);
        marshaller.marshal(obj);
    }
}

 

그리고 Flex의 요청에 대한 응답을 처리하는 Servlet 내에서 아래처럼 결과객체를 변환하여
응답을 보냅니다.

 

    // #. 결과객체 (변환하고자 하는 객체)
    Object resultObject = new Object(); // 개인마다 DB의 결과를 담은 다른 클래스형의 객체가 들어갈겁니다...

    // #. 맵핑룰 XML 얻기
    InputStream xmlInputStream = getClass().getResourceAsStream("test_ed_mapping.xml");
    CasterMapper mapper = new CasterMapper(xmlInputStream);

    // #. print response
    mapper.objectToXml(resultObject, response.getWriter());

 

 

아래는 test_ed_mapping.xml 의 예로서 전체 XML응답전문을 Header 와 Body 로 구분하고
사용자 목록을 출력하도록 지정했습니다.

맵핑전문은 아래와 같은 특징이 있으니 잘 살펴보시기 바랍니다.
- Element(태그)는 클래스와 맵핑된다.
- 값은 Attribute 나 Element 로 표현될 수 있다.
- 위쪽에서 사용된 클래스에 대한 정의로 바로 아래에서 표현한다.

 

test 로 시작되는 패키지 내의 Class 는 개인적으로 만드셔야 합니다.
전문을 눈여겨 보면 어렵지 않게 클래스를 만들수 있으시라라 생각됩니다.
참고로 PagingList 란 넘은 java.util.ArrayList 형의 list 어트리뷰트를 가지고 있습니다.

 

<?xml version="1.0"?>
<!DOCTYPE mapping PUBLIC "-//EXOLAB/Castor Mapping DTD Version 1.0//EN" "http://castor.org/mapping.dtd">

<mapping>

    <class name="test.common.xml.ed.XmlResponse">
        <map-to xml="response"/>
        <field name="header" type="test.common.xml.ed.XmlHeader">
            <bind-xml name="header" node="element"/>
        </field>
        <field name="body" type="test.common.xml.ed.XmlBody">
            <bind-xml name="body" node="element"/>
        </field>
    </class>

    <!-- HEADER -->
    <class name="test.common.xml.ed.XmlHeader">
        <map-to xml="header"/>
        <field name="id" type="java.lang.String">
            <bind-xml name="id" node="attribute"/>
        </field>
        <field name="group" type="java.lang.String">
            <bind-xml name="group" node="attribute"/>
        </field>
        <field name="code" type="java.lang.String">
            <bind-xml name="code" node="attribute"/>
        </field>
        <field name="message" type="java.lang.String">
            <bind-xml name="message" node="attribute"/>
        </field>
    </class>

    <!-- BODY -->
    <class name="test.common.xml.ed.XmlBody">
        <map-to xml="body"/>
        <field name="obj" type="test.common.xml.ed.PagingList">
            <bind-xml name="users" node="element"/>
        </field>
    </class>

    <!-- Users -->
    <class name="test.common.xml.ed.PagingList">
        <map-to xml="users"/>
        <field name="list" type="test.beans.User" collection="arraylist">
            <bind-xml name="user" node="element"/>
        </field>
    </class>

    <!-- USER -->
    <class name="test.beans.User">
        <map-to xml="user"/>

        <field name="id" type="java.lang.String">
            <bind-xml name="id" node="attribute"/>
        </field>

        <field name="name" type="java.lang.String">
            <bind-xml name="name" node="attribute"/>
        </field>

        <field name="email" type="java.lang.String">
            <bind-xml name="email" node="attribute"/>
        </field>
    </class>
</mapping>

 

 

이리하여 결과는 아래와 같습니다.

DB의 사용자 테이블에 질의한 쿼리의 결과가 User 객체의 List 로 반환이 되었다고 가정하고

이것에 대한 서블릿의 결과는 다음과 같습니다.

 

<?xml version="1.0" encoding="UTF-8"?>
<response>
    <header>
        <id>test.ViewMembers</id>
        <group>admin</group>
        <code>0000</code>
        <message>SUCCESS</message>
    </header>
    <body>
        <users>
            <user id="honggildong1" name="길동이" email="honggildongika@gildong.com"/>
            <user id="honggildong2" name="길동이" email="honggildongika@gildong.com"/>
            <user id="honggildong3" name="길동이" email="honggildongika@gildong.com"/>
            <user id="honggildong4" name="길동이" email="honggildongika@gildong.com"/>
            <user id="honggildong5" name="길동이" email="honggildongika@gildong.com"/>
            <user id="honggildong6" name="길동이" email="honggildongika@gildong.com"/>
            <user id="honggildong7" name="길동이" email="honggildongika@gildong.com"/>
            <user id="honggildong8" name="길동이" email="honggildongika@gildong.com"/>
            <user id="honggildong9" name="길동이" email="honggildongika@gildong.com"/>
            <user id="honggildong10" name="길동이" email="honggildongika@gildong.com"/>
        </users>
    </body>
</response>

 

편리하겠죠?

기존 JSP 로 작성된 시스템에서 Flex Client로 전환시 Layer 가 잘 나눠져 있다면
아주 쉽게 커스터마이징 가능하리라 생각됩니다.


간단한 내용이나 걍 참고하시라고 적어봤습니다.

 

[참고링크]
이곳에 뉴스그룹이 있으니 기타 트러블슈트 참고하시고요~
http://www.mail-archive.com/castor-dev@exolab.org/


'DEVELOPMENT > FLEX & AIR' 카테고리의 다른 글

FLEX - Style Exploer  (0) 2010.01.19
Flex 흐르는 텍스트 효과  (0) 2010.01.12
Flex 컴포넌트의 비율 유지  (0) 2009.12.21
Flex와 Java간 소켓 통신  (1) 2009.12.21
XML과 E4X 다루기  (0) 2009.12.20


Java와 Flex 간의 소켓을 이용한 통신 샘플


보안관련 인증 파일 첨부

'DEVELOPMENT > FLEX & AIR' 카테고리의 다른 글

[Caster] XML만을 사용시 서버측 XML 객체 상호변환  (0) 2009.12.24
Flex 컴포넌트의 비율 유지  (0) 2009.12.21
XML과 E4X 다루기  (0) 2009.12.20
[Flex] PieChart  (0) 2009.11.24
[Flex] LineChart  (0) 2009.11.24


java.util.Properties 파일 사용 예제

package kr.pe.anaconda.test.io.file;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
 * @author diem
 * 
 */
public class PropertiesSample {

	private static String defaultPropertiesPath = "c:\\example.properties";

	public static String getDefaultPropertiesPath() {
		return defaultPropertiesPath;
	}

	public static void setDefaultPropertiesPath(String defaultPropertiesPath) {
		PropertiesSample.defaultPropertiesPath = defaultPropertiesPath;
	}

	public static String getKey(String key) throws Exception {

		// ClassLoader.getResourceAsStream("some/pkg/resource.properties");
		// Class.getResourceAsStream("/some/pkg/resource.properties");
		// ResourceBundle.getBundle("some.pkg.resource");
		String value = null;
		InputStream is = new FileInputStream(defaultPropertiesPath);
		Properties p = null;
		try {
			p = new Properties();
			p.load(is);
			value = p.getProperty(key);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {is.close();} catch (IOException e) {}
		}
		return value;
	}

	/**
	 * 프로퍼티 파일에 사용자 값을 넣는다.
	 */
	public static void putPropertie(Map paramMap)
			throws FileNotFoundException, IOException {
		// 프로퍼티 파일 경로 key
		String propertiesKey = "properties.file.path";
		Properties proper = null;
		FileOutputStream output = null;
		try {
			String comment = paramMap.get("properties.comment");
			// 사용자가 프로퍼티 파일 경로를 넘기지 않을 경우 default properties로 셋팅하다.
			if (!paramMap.containsKey(propertiesKey)) {
				paramMap.put(propertiesKey, defaultPropertiesPath);
			}
			output = new FileOutputStream(paramMap.get(propertiesKey));
			// paramMap.remove(propertiesKey);
			proper = new Properties();
			proper.putAll(paramMap);
			proper.store(output, comment);
		} catch (FileNotFoundException fnfe) {
			throw new FileNotFoundException("properties 파일을 찾을수 없습니다.");
		} catch (IOException ioe) {
			throw new IOException("putPropertie Exception!", ioe);
		} finally {
			try {
				output.close();
			} catch (IOException e) {
			}
		}
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) throws Exception {
		// TODO Auto-generated method stub
		Map paramMap = new HashMap();
		paramMap.put("properties.file.path", "c:\\example12.properties");
		paramMap.put("name", "홍길동");
		paramMap.put("age", "31");
		paramMap.put("phone", "0111234567");
		PropertiesSample.putPropertie(paramMap);

		PropertiesSample.setDefaultPropertiesPath(paramMap
				.get("properties.file.path"));

		System.out.println(PropertiesSample.getDefaultPropertiesPath());
		System.out.println(PropertiesSample.getKey("name"));
	}
}