발생일: 2011.06.01

문제:
처음 디자인 패턴을 공부할 당시 프록시 패턴과 데코레이터 패턴을 꽤 헷갈려했던 기억이 난다.
얼마 전에 책을 읽다가 이 차이점을 명확하게 설명해놓은 부분이 있어 메모해둔다.
프록시 패턴, 데코레이터 패턴에서 사용하고 있는, 전반적인 '프록시'에 대한 개념부터 설명을 시작해 쏙쏙~ 이해된다.^^


해결책:
프록시:
자신이 클라이언트가 사용하려고 하는 실제 대상인 것처럼 위장해서 클라이언트의 요청을 받아준다.
대리자, 대리인과 같은 역할을 한다고 해서 프록시(proxy)라고 부른다.
프록시를 통해 최종적으로 요청을 위임받아 처리하는 실제 오브젝트를 타깃(target) 또는 실체(real subject)라고 한다.

프록시의 특징은 타깃과 같은 인터페이스를 구현했다는 것과, 프록시가 타깃을 제어할 수 있는 위치에 있다는 것이다.

프록시는 사용 목적에 따라 두 가지로 구분할 수 있다.
a) 클라이언트가 타깃에 접근하는 방법을 제어하기 위함. (--> 프록시 패턴)
b) 타깃에 부가적인 기능을 부여해주기 위함. (--> 데코레이터 패턴)


데코레이터 패턴:
타깃에 부가적인 기능을 런타임 시 다이내믹하게 부여해주기 위해 프록시를 사용하는 패턴


프록시 패턴:
프록시 패턴의 프록시는 타깃의 기능을 확장하거나 추가하지 않는다.
대신 클라이언트가 타깃에 접근하는 방식을 변경해준다.
타깃의 기능 자체에는 관여하지 않으면서 접근하는 방법을 제어해주는 프록시를 이용하는 것이다.
(따라서 프록시 패턴의 프록시는 코드에서 자신이 만들거나 접근할 타깃의 구체적인 클래스 정보를 알고 있는 경우가 많다.)


요약:
프록시: 클라이언트와 사용 대상 사이에 대리 역할을 맡은 오브젝트를 두는 방법을 총칭.
데코레이터 패턴: 타깃에 부가적인 기능을 부여해주기 위해 프록시를 사용하는 패턴.
프록시 패턴: 타깃에 대한 접근 방법을 제어하려는 목적을 가지고 프록시를 사용하는 패턴. 

저작자 표시 비영리 변경 금지
Posted by ohgyun
발생일: 2010.05.06

문제:
현업이 종종 특정 데이터를 엑셀로 뽑아달라는 요청을 합니다.
요청이 간헐적이고 같은 데이터를 시도 때도 없이 새로 뽑아달라는 요청을 해서,
좀 효율적으로 처리하고자 동적 쿼리를 수행하는 메뉴를 하나 만들었습니다.

해당 쿼리가 자주 변하고 보안 문제도 있었기 때문에,
관리를 편하게 하기 위해 DB 대신 파일 기반으로 설계했습니다.
서버에 올라가 있는 sql 파일을 읽어 목록을 구성하고,
해당 쿼리를 담은 sql 파일을 읽어 엑셀로 다운로드 받을 수 있는 기능입니다.
(우리는 요청 쿼리를 담은 sql 파일을 서버에 올리기만 하면 되도록 말이죠.)

iBATIS 를 사용하고 있어 파일에서 읽은 쿼리를 아래와 같이 $sql$ 형태로 전달하도록 했는데,

    <select id="dynamicQuery" resultClass="hashmap">
        $sql$
    </select>

첫 번째는 정상적으로 실행되고, 그 이후 다른 쿼리를 다운로드 받으려 시도했을 때
아래와 같은 에러가 발생합니다.


Caused by: com.ibatis.common.jdbc.exception.NestedSQLException:   
--- The error occurred while applying a result map.  
--- Check the 쿼리ID - AutoResultMap.
--- Check the result mapping for the 컬럼명 property.  
--- Cause: java.sql.SQLException: 부적합한 열 이름


문제가 뭘까요...


해결책:
iBATIS 는 기본적으로 해당 쿼리에 대한 메타데이터(필드, 타입 등)를 캐시한다고 합니다.
따라서 위 dynamicQuery 구문이 실행된 이후 그 결과의 컬럼 정보가 캐시되었고,
이후 다른 컬럼 정보를 포함한 쿼리가 dynamicQuery 로 실행되었을 때
캐시된 컬럼 정보로 값을 찾으려하다 오류가 난 거였던 거죠.

이 경우엔, 아래와 같이 해당 select 구문의 메타데이터를 캐시하지 않도록 설정하면 된다고 합니다.

    <select id="dynamicQuery" resultClass="hashmap" remapResults="true">
        $sql$
    </select>


아래 포스트에서 참고했습니다.

저작자 표시 비영리 변경 금지
Posted by ohgyun
문서를 정리하다가 예전에 헤드퍼스트 디자인 패턴 책을 읽고 정리해 놓은 파일이 있어 옮겨봅니다.

==============================================================

디자인 원칙
    - 애플리케이션에서 달라지는 부분을 찾아내고, 달라지지 않는 부분으로부터 분리한다.
    - 구현이 아닌 인터페이스에 맞춰서 프로그래밍한다.
    - 상속보다는 구성을 활용한다.
    - 서로 상호작용을 하는 객체 사이에서는 가능하면 느슨하게 결한하는 디자인을 사용해야 한다.
    - 클래스는 확장에 대해서는 열려 있어야 하지만 코드 변경에 대해서는 닫혀 있어야 한다.
       (OCP : Open-Closed Principle)
    - 추상화된 것에 의존하도록 만들어라. 구상클래스에 의존하도록 만들지 않도록 한다.
    - 최소 지식 원칙 - 정말 친한 친구하고만 얘기하라.
       (다음 네 종류의 객체의 메서드만 호출한다.
         1. 객체 자체 / 2. 메서드에 매개변수로 전달된 객체 / 3. 그 메서드에서 생성하거나 인스턴스를 만든 객체 / 4. 그 객체에 속하는 구성 요소)
    - 헐리우드 원칙 - 먼저 연락하지 마세요. 저희가 연락 드리겠습니다.
    - 클래스를 바꾸는 이유는 한 가지 뿐이어야 한다.
    + ... 나중에 어떻게 바뀔 것인가?


스트래티지 패턴(Strategy Pattern)
    - 알고리즘군을 정의하고 각각을 캐슐화하여 교환해서 사용할 수 있도록 만든다.
    - 알고리즘을 사용하는 클라이언트와는 독립적으로 알고리즘을 변경할 수 있다.
    - 구성을 사용한다.
    - 일반적으로 서브클래스를 만드는 방법을 대신하여 유연성을 극대화하기 위한 용도로 쓰인다.
    - 예: QuarkBehavior & FlyBehavior
    

옵저버 패턴(Observer Pattern)
    - 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들한테 연락이 가고
       자동으로 내용이 갱신되는 방식으로 일대다(one-to-many) 의존성을 정의한다.
    - 주제(Subject) & 옵저버(Observer)
    - Observable & Observer:
         Observable 에 register, remove, notify 가 있고, 
         Observer 에 update 가 있다. (notify 에서 update 를 호출)
    - 예: 신문 구독 서비스, 기상관측 시스템
    

데코레이터 패턴(Decorator Pattern)
    - 객체에 추가적인 요건을 동적으로 첨가한다.
    - 데코레이터는 서브클래스를 만드는 것을 통해서 기능을 유연하게 확장할 수 있는 방법을 제공한다.    
    - 예: 스타버즈 커피
    
    
팩토리 패턴(Factory Pattern)    
    - 팩토리 메서드 패턴 :
       객체를 생성하기 위한 인터페이스를 정의하는데, 어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정하게 만든다.
       클래스의 인스턴스를 만드는 일을 서브클래스에 맡긴다.
    - 제품을 생산하는 부분과 사용하는 부분을 분리시킬 수 있다.
    - 추상 팩토리 패턴 :
       인터페이스를 이용하여 서로 연관된, 또는 의존하는 객체를 구상 클래스를 지정하지 않고 생성한다.
       구상 클래스는 서브 클래스에 의해 만들어진다.


싱글턴 패턴(Singleton Pattern)
    - 해당 클래스의 인스턴스가 하나만 만들어지고,
       어디서든지 그 인스턴스에 접근할 수 있도록(전역 접근) 하기 위한 패턴
       
       
커맨드 패턴(Command Pattern)
    - 요구 사항을 객체로 캡슐화 할 수 잇으며, 매개변수를 써서 여러 가지 다른 요구 사항을 집어넣을 수 있다.
       또한 요청 내역을 큐에 저장하거나 로그로 기록할 수도 잇으며, 작업취소 기능도 지원 가능하다.
    - 예: 리모콘
    - 서블릿의 doGet(), doPost() 또는 스트럿츠의 Action() 메서드도 커맨드 패턴이지 않을까?
    

어댑터 패턴(Adapter Pattern)
    - 한 클래스의 인터페이스를 클라이언트에서 사용하고자 하는 다른 인터페이스로 변환한다.
       어댑터를 이용하면 인터페이스 호환성 문제 때문에 같이 쓸 수 없는 클래스들을 연결해서 쓸 수 있다.  
       

퍼사드 패턴(Facade Pattern)
    - 어떤 서브시스템의 일련의 인터페이스에 대한 통합된 인터페이스를 제공한다.
       퍼사드에서 고수준 인터페이스를 정의하기 때문에 서브시스템을 더 쉽게 사용할 수 있다.
    - 서브시스템의 호출을 퍼사드에서 처리해준다. (기본 명령 호출 정도랄까...)
    - 일련의 클래스들에 대한 인터페이스를 단순화 시킨다.
    
    - 각 패턴별 차이점:
       데코레이터 패턴 : 인터페이스는 바꾸지 않고 책임(기능)만 추가
       어댑터 패턴 : 한 인터페이스를 다른 인터페이스로 변환
       퍼사드 패턴 : 인터페이스를 간단하게 바꿈


템플릿 메서드 패턴(Template Method Pattern)
    - 메서드에서 알고리즘의 골격을 정의한다.
       알고리즘의 여러 단계 중 일부는 서브클래스에서 구현할 수 있다.
       템플릿 메서드를 이용하면 알고리즘의 구조는 그대로 유지하면서 서브클래스에서 특정 단계를 재정의할 수 있다.
    - 스트래티지 패턴과 다른 점:
       템플릿 메서드 패턴은 알고리즘의 개요를 정의한다. 실제 작업 중 일부는 서브클래스에서 처리.
       스트래티지 패턴은 객체 구성을 통해서 알고리즘을 캡슐화 및 구현
    - 예) Arrays.sort(배열); --- compareTo() 를 구현하도록 되어 있다.
            Applet , init(), start(), stop(), destory()
            그렇다면 서블릿에도 템플릿 메서드가 쓰이는 거구나. init() - service() - destory()

            
이터레이터 패턴(Iterator Pattern)
    - 컬렉션 구현 방법을 노출시키지 않으면서도
       그 잡합체 안에 들어있는 모든 항목에 접근할 수 있게 해주는 방법을 제공한다.
    - 컬렉션의 구현을 드러내지 않으면서 컬렉셔네 있는 모든 객체들에 대해 반복작업할 수 있다.
    

컴포지트 패턴(Composite Pattern)
    - 객체들을 트리 구조로 구성하여 부분과 전체를 나타내는 계층구조로 만들 수 있다.
       이 패턴을 이용하면 클라이언트에서 개별 객체와 다른 객체들로 구성된
       복합 객체(composite)를 똑같은 방법으로 다룰 수 있다.
    - 클라이언트에서 객체 컬렉션과 개별 객체를 똑같은 식으로 처리할 수 있다.
    - 예) 트리 구조의 패턴, 디렉토리 구조
    - 예) XMLObject 객체가 컴포지트 패턴을 구현한 게 아닐까
    

스테이트 패턴(State Pattern)
    - 객체의 내부 상태가 바뀜에 따라서 객체의 행동을 바꿀 수 있다.
       마치 객체의 클래스가 바뀌는 것과 같은 결과를 얻을 수 있다.
    - 상태 전환의 흐름을 결정하는 코드를 어느 쪽에 집어넣는지 잘 고려해야 한다.
       (상태 객체인지, Context 객체인지)
    - 각 상태를 클래스로 캡슐화함으로써 나중에 변경시켜야 하는 내용을 국지화시킬 수 있다.
    - 스트래티지 패턴:
          어떤 클래스의 인스턴스를 만들고 그 인스턴스에게 어떤 행동을 구현하는 전략 객체를 건내준다.
       스테이트 패턴:
          컨텍스트 객체를 생성할 때 초기 상태를 지정해주는 경우 이후로는 컨텍스트 객체가 알아서 상태를 변경.
          
          
프록시 패턴(Proxy Pattern)
    - 어떤 객체에 대한 접근을 제어하기 위한 용도로 대리인이나 대변인에 해당하는 객체를 제공하는 패턴
    - 다른 객체를 대변한느 객체를 만들어서 주 객체에 대한 접근을 제어할 수 있다.
    - 원격프록시(remote proxy): 원격 객체에 대한 접근 제어
                                                클라이언트와 원격 객체 사이에서 데이터 전달을 관리
       가상프록시(virtual proxy): 생성하기 힘든(인스턴스를 만드는 데 많은 비용이 드는) 자원에 대한 접근 제어
       보호프록시(protection proxy): 접근 권한이 필요한 자원에 대한 접근 제어
                                                     호출하는 쪽의 권한에 따라서 객체에 있는 메소드에 대한 접근 제어
       방화벽 프록시: 일련의 네트워크 자원에 대한 접근 제어
       스마트 레퍼런스 프록시: 주 객체가 참조될 때마나 추가 행동을 제공. 객체에 대한 레퍼런스 개수를 세는 등
       캐싱 프록시: 비용이 많이 드는 작업의 결과를 임시로 저장
                          웹 서버 프록시 또는 컨텐츠 관리 및 퍼블리싱 시스템 등에서 사용
       동기화 프록시: 여러 스레드에서 주 객체에 접근하는 경우 안전하게 작업을 처리할 목적(분산 환경 등에서 사용)
       복잡도 숨김 프록시: 복잡한 클래스들의 집합에 대한 접근을 제어하고 복잡도를 숨겨줌
                                     퍼사드 프록시라고도 함.
                                     프록시에서는 접근을 제어하지만 퍼사드 패턴에서는 대체 인터페이스만 제공
       지연 복사 프록시: 클라이언트에서 필요로 할 때까지 객체가 복사되는 것을 지연시킴으로써 객체의 복사 제어
       
    - 아래 객체들은 모두 클라이언트와 객체 사이에 끼여들어서 요청을 전달한다.
         데코레이터 패턴: 클래스에 새로운 행동을 추가하기 위한 용도
         어댑터 패턴: 다른 객체의 인터페이스를 바꿔주기 위한 용도
         프록시 패턴: 어떤 클래스에 대한 접근을 제어하기 위한 용도
    - java.reflect.Proxy 에 기능이 내장되어 있다.
    
    
디자인 패턴 정의
    - 패턴이란 특정 컨텍스트 내에서 주어진 문제에 대한 해결책이다.
    - 어떤 컨텍스트 내에서 일련의 제약조건에 의해 영향을 받을 수 있는 문제에 봉착했다면,
       그 제약조건 내에서 목적을 달성하기 위한 해결책을 찾아낼 수 있는 디자인을 적용하면 된다.    
      

주의점 및 추가 사항      
    - 디자인 패턴의 과다한 사용은 불필요하게 복잡한 코드를 초래할 수 있다.
       항상 가장 간단한 해결책으로 목적을 달성할 수 있도록 하고, 반드시 필요할 때만 디자인 패턴을 적용하자.      
    - 코딩할 때 어떤 패턴을 사용하고 있는지 주석으로 적어주자.
       클래스와 메서드 이름을 만들 때도 사용 중인 패턴이 분명하게 드러날 수 있도록 해보자.
       다른 개발자들이 그 코드를 볼 때 무엇을 어떻게 구현했는지 훨씬 빠르게 이해할 수 있다.


그 외 잘 정리된 참고 사이트 : http://vincehuston.org/dp/
저작자 표시 비영리 변경 금지
Posted by ohgyun
발생일: 2010.03.11

문제:
후배 P와 같이 시스템 운영 이관을 하다 문득 배치 파일 옵션에 빌드와 컴파일이 구분되어 있는 걸 보고,
궁금한 마음에 '빌드와 컴파일 차이가 뭐죠?' 라고 물었습니다.

뭘까요...?
같은 용어라 생각하고 다르단 생각을 못해봤는데 구분되어 있네요. 뭘까요...

해결책:

빌드와 컴파일의 차이
빌드와 컴파일의 차이에 대해 명확하고 쉽게 설명해 놓은 좋은 포스트가 있습니다.
컴파일은 빌드의 부분 집합이며,
단순한 컴파일에 비해 빌드는 실행할 수 있는 소프트웨어로 변환하는 과정이라고 합니다.

그러고 보니, 시스템 이관을 할 때에도, 개인 작업을 할 때에도,
빌드 버전을 따거나 '빌드'라는 것에 중점을 두고 수행한 적은 없었습니다.

같은 분이 작성하신 빌드의 중요성 글도 좋으니 참고해보세요.

저작자 표시 비영리 변경 금지
Posted by ohgyun
발생일: 2010.03.09

문제:
꽤 오랫동안 시스템을 운영해왔고 프로젝트도 여러 번 있었지만,
properties 파일들은 별 생각 없이 가이드 대로 사용하고 있었습니다.
만들어진 시스템을 받을 때 쯤이면, 이미 properties 파일들은 제 위치에 놓여져 있기 때문이기도 했고요.

이번에 작은 라이브러리를 만들어 배포해 보려고 하니,
사용자 별 설정을 읽어와야 해서 properties 에 대해 고민할 기회가 생겼습니다.


해결책:
날이 갈수록 좋은 프레임웍이 만들어지고 하다보니,
기본적인 것보다는 만들어 진 라이브러리의 사용법 학습에 더 치중하게 되는 경향이 생기는 것 같네요.

위 내용은 구글링 해보니 쉽게 찾을 수 있었습니다.

Smartly load your properties (영문)
꽤 오래된 (03년도) 포스트이긴 하지만, 내용이 아주 좋습니다.
자바에서 프로퍼티 파일을 로드하는 방법을 디테일하게 알려줍니다.
기존에는 단순히 (편하다는 이유로) ResourceBundle 을 이용했는데,
ClassLoader 를 사용하는 방법과 각 메서드간의 차이점도 잘 정리해 놓았네요.
저작자 표시 비영리 변경 금지
Posted by ohgyun
발생일: 2010.02.10

문제:
iBatis 에 parameterClass 로 List 를 넘긴 후,
iterate 태그 사용을 시도하였을 경우 아래와 같은 예외가 발생한다.

Error getting ordinal list from JavaBean. Cause java.lang.StringIndexOutOfBoundsException


이 때 사용된 iBatis 구문은 아래와 같다.

<select id="selectSomething" parameterClass="list">
    // select something and use iterate
    <iterate property="someList">
        #someList[]#
    </iterate>
</select>


잘못된 게 없는 것 같은데, 왜 이럴까.


해결책:
정확한 원인은 모르겠으나, 일단 패러미터를 Map 형태로 전달해주면 문제 없이 작동한다.

queryForList 등의 메서드를 호출하기 전,
패러미터로 전달할 list 객체를 Map 객체로 감싼 후 전달하도록 한다.

<select id="selectSomething" parameterClass="map">
    // select something and use iterate
    <iterate property="someList">
        #someList[]#
    </iterate>
</select>


BUT, 아래 더 올바른 해결 방법이 있다.
=================================================================
# 추가. 2010.02.11

list 를 패러미터로 전달 시 iterate 태그에서 property 를 설정하면,
전달된 list 에서 해당 property 를 찾으려고 시도하는 것 같다.
<iterate> 태그에서 property 속성을 제거하면 정상적으로 작동한다.

<select id="selectSomething" parameterClass="list">
    // select something and use iterate
    <iterate> // iterate 태그 내 property 속성을 제거하라
        #someList[]#
    </iterate>
</select>


참고: iBatis: Support for Array or List Parameter with SQL IN Keyword



저작자 표시 비영리 변경 금지
Posted by ohgyun
발생일: 2009.01.06

문제:
진행 중인 미니 프로젝트에서 웹 어플리케이션이 로딩될 때 설정해야 할 것들이 있다.
여기서는 contextRealRoot 를 메모리에 미리 올려둘 목적으로 쓰려고 하는데,
그 외에도 리소스나, 권한 등 일반적으로도 초기화 때 설정할 사항들이 많다.

스트럿츠 프레임웍의 경우, 웹 어플리케이션의 초기 환경 설정을 위해 PlugIn 기능을 제공한다.

스트럿츠 프레임웍의 PlugIn 기능 보기



이 기능을 프레임웍이 없는 일반 서블릿 환경에서 가볍게 한 번 구현해 볼까 한다.


해결책:
스트럿츠 프레임웍을 뜯어보지는 않아서 어떻게 구현되어 있는 지는 정확히 모르겠지만,
여기서는 서블릿 컨텍스트 리스너와 옵저버 패턴을 써서 구현했으며, 개략적인 클래스 다이어그램은 아래와 같다.







PlugIn 인터페이스는 옵저버 역할을 하며, init() 메서드를 구현하도록 한다.
init() 메서드에서는 실제 초기화 할 작업을 구현하면 된다.
또한 ConcreatePlugIn 은 PlugIn 인터페이스를 구현한 실제 PlugIn 클래스라고 보면 되겠다.

옵저버 패턴 Subject 역할을 하는 것이 PlugInRepository 이며,
여기서는 PlugIn 을 담아 두고, 알려주는 역할을 한다.
PlugInRepository 의 init() 메서드를 통해 각 PlugIn 의 초기화 작업을 수행할 수 있다.

실제로 컨텍스트가 로드될 때 호출되는 것은 PlugInLoader 클래스이며 서블릿 컨텍스트 리스너이다.
web.xml 에서 <listener-class>로 등록되어 있어 컨텍스트 로드 시점에서 호출되며,
로더에서는 PlugInRepository 과 PlugIn 을 생성하여 등록하고 초기화 시킨다.


전체 코드는 아래와 같다.

전체 코드 보기



초기화 수행 시 필요한 작업들을 PlugIn 객체로 구현한 후,
코드의 PlugInLoader 의 init() 메서드에서처럼 PlugInRepository 에 등록한 후 작업을 호출해주면 된다.

PlugInRepository 의 생성자에 ServletContext 를 넘기도록 한 것은,
PlugIn 객체에서 컨텍스트에 접근할 수 있도록 하기 위해서이다.

실제로 작동시켜 보니 예상했던 대로 잘 작동한다.


몇 가지 기능을 추가로 구현하면, 가벼운 라이브러리로 쓸 수도 있을 것 같다.

#1.
여기서는 추가하려는 PlugIn 객체를 PlugInLoader 에서 직접 추가해줬다.
스트럿츠에서는 수행할 PlugIn 에 대한 정보를 struts-config.xml 에 추가해야 한다.

그렇다면 아예, 특정 인터페이스를 구현한 클래스를 찾는 ClassFinder 따위(검색해보니 좀 있다)를 구현해서,
구현 PlugIn 객체를 자동으로 찾아 호출하도록 하면 더 편리할 것 같다.

사용자 입장에서는 단지 PlugIn 인터페이스만 구현하게 말이다.

#2.
PlugInRepository 는 싱글턴으로 구현해도 괜찮을 것 같다.

#3.
컨텍스트가 종료될 때의 작업을 추가해도 괜찮겠다. (contextDestroyed 시)

#4.
그 외, 예외 처리가 필요하다. (플러그인의 초기화가 실패했을 경우, 로드 자체가 불가능 하게 할 것인가?)

#5.
여기서는 서블릿 컨텍스트 리스너를 이용해서 컨텍스트가 리로드 될 때에도 초기화가 수행된다.
만약, 서버를 올릴 때에만 초기화 하고 싶다면,
    1. 이미 로드되었는지 여부에 대한 flag 를 두어 처리하거나,
        (* 컨텍스트가 리로드 되면, 리스너 클래스 로딩를 새로 하는 모양이다.
            flag 를 static 으로 생성해도 그 값이 저장되지 않는다.)
    2. 컨텍스트 리스너 대신 servlet 의 load-on-startup 값을 1로 설정하면 되겠다.
        (PlugInLoader 를 서블릿으로 구현하고, 서블릿의 init() 메서드 부분을 구현한다.)

저작자 표시 비영리 변경 금지
Posted by ohgyun
발생일: 2010.01.05

문제:
시스템에서 Fasoo DRM 이라는 암호화 모듈을 사용하고 있다.
내부적으로는 문서 다운로드 요청이 들어왔을 때, 암호화 처리를 한 후 돌려주는 방식으로 사용하고 있는데,
얼마 전 서버를 옮긴 이후부터 클래스 패스를 찾을 수 없다는 메세지가 나온다.

javax.servlet.ServletException: no fasoopackager in java.library.path

서버는 JEUS 를 쓰는데, 업체에서 제공해준 jar 파일을
각 라이브러리 폴더(WEB-INF/lib, JEUS_HOME/lib/application 등)로 옮겨보며 테스트 해보아도 해결되지 않는다.

뭐가 문제일까...?


해결책:

javax.servlet.ServletException: no fasoopackager in java.library.path

이 메세지가 의미하는 것은 일반 자바 라이브러리의 클래스 패스를 찾지 못하겠다는 것이 아니라,
 "JVM 이 'fassopackager' 라는 네이티브 라이브러리를 찾을 수 없다" 는 것이다.
즉, 네이티브 어플리케이션을 실행하기 위한 라이브러리를 찾지 못한다는 얘기다.

위 암호화 모듈이 암호화 작업을 수행할 때, 모든 작업을 자바 라이브러리에서 수행하는 게 아니었던 것이다.
확인해보니 해당 라이브러리에서 암호화를 수행하기 위해 fasoopackager.dll 이라는 파일을 이용하고 있었다.

위와 같이, Java 에서 외부 프로그램에 접근하기 API를 JNI (Java Native Interface) 라고 한다.
해당 플랫폼에서만 실행 가능한 네이티브 코드(native code)에 접근하기 위해 만들어진 응용 프로프로그램 인터페이스로,
주로 자바만으로는 구현할 수 없거나, 다른 언어로 쓰여진 어플리케이션을 접근하려고 하는 경우에 쓰인다.
동작 환경이 해당 플랫폼에 제한된다는 단점이 있다.

JNI 를 사용하기 위해서는 사용하고자 하는 네이티브 라이브러리를 JVM 이 읽어올 수 있게 설정해줘야 한다.
native libary path 가 정상적으러 설정되어 있지 않아서 실행하고자 하는 파일을 찾을 수 없을 때,
보통 아래와 같은 예외가 발생한다.

java.lang.UnsatisfiedLinkError: no 파일명 in library path

native library path 를 설정해주는 방법은 운영체제마다 다른데,
Solaris 시스템 (Unix) 의 경우, LD_LIBRARY_PATH 에 해당 라이브러리의 경로를 설정해주면 된다.

윈도우 환경에서는 PATH 환경 변수에 해당 라이브러리 경로를 추가해주거나,
아래와 같이 자바 실행 옵션에 추가해주면 된다.(SDK 1.2 부터)

java -Djava.library.path=라이브러리 경로 클래스명


위 문제점이 발생한 환경에서는 사정 상 서버의 환경 변수를 추가할 수는 없었다.
다행히 제우스 실행 배치 파일을 확인해보니,
제우스 서버를 올릴 때 java.libarary.path 로 제우스 시스템 라이브러리(JEUS_HOME/lib/system) 폴더를 참조하고 있었다.

여기서는 해당 dll 파일을 제우스 시스템 라이브러리 폴더로 복사해주는 방법으로 해결했다~~


# 참고:

What is java.library.path?
문제 해결의 실마리를 얻은 JavaRanch 의 포럼
JNI 에 대한 위키백과 정보 (영문)

JNI 에 대한 SUN 의 튜토리얼.(영문)
HTML 버전으로 읽어보니 간편하고 좋다.
Introduction and Tutorial 부분만 읽어봤는데, 유래부터 예제까지 쉽게 잘 설명해뒀다.

저작자 표시 비영리 변경 금지
Posted by ohgyun
발생일: 2009.12.15

문제:
친구 홍이 문제점에 봉착했다며 묻는다.

Abstract Class 를 어떻게 Singleton 으로 만들 수 있을까?


Abstract Class 를 상속한 클래스들은 다 싱글턴으로 구현되도록 해야 한다고 한다.

음...
그럼 Abstrac class 안에 싱글턴 패턴인 getInstance  를 구현하되,
그 안에 팩토리 메서드 패턴으로 동적 타입으로 만들 수 있게 하면 안될까...?

private static AbstractSingleton uniqueInstance;
public static AbstractSingleton getInstance(Class cls) {
    // uniqueInstance 가 존재하는지 확인
    // 없을 경우, 동기화해서 class 에 따라 객체 생성해서 리턴
    // 있을 경우, uniqueInstance 리턴
}

이렇게 말이야...

음.... 가만,..
근데 이렇게 하면 uniqueInstance 에 단 하나의 인스턴스가 들어가버린다.
(즉, 한 번에 다형성을 무시하고 한 객체 밖에 생성할 수가 없다.)
게다가 Abstract Class 를 상속받는 클래스의 생성자가 private 일 테니 컴파일 에러도 나겠군...-_-;;
이렇게 하면 안되겠다.

흠.... 어떻게 해야할까...


해결책:
추상 클래스를 상속받은 아이를 위와 같은 방법으로 싱글턴으로 구현하는 건 불가능하다.

다만, 편법이 있으니, 아래와 같이 스태틱으로 인스턴스의 레지스트리를 만든 후에,
getInstance 에서 인스턴스에 저장된 값을 돌려주는 방법이 있다.

public abstract class AbstractSingleton {

    private static final Map<Class<? extends AbstractSingleton>, AbstractSingleton> instances
            = new HashMap<Class<? extends AbstractSingleton>, AbstractSingleton>();

    public static AbstractSingleton getInstance(Class<? extends AbstractSingleton> cls)
            throws InstantiationException, IllegalAccessException {
        if (instances.get(cls) == null) {
            synchronized (instances) {
                if (instances.get(cls) == null) {
                    instances.put(cls, cls.newInstance());
                }
            }
        }
        return instances.get(cls);
    }

    public abstract void setName(String name);

    public abstract String getName();
}


이 클래스를 상속받아 구현하는 서브 클래스는 아래와 같은 모습이다.

public class ConcreteSingleton extends AbstractSingleton {
    public String name;
   
    ConcreteSingleton() {} // private으로 구현하면 안된다. 기본 생성자로 만든다.
   
    public void setName(String name) {
        this.name = name;
    }
   
    public String getName() {
        return name;
    }
}


객체 생성은 아래와 같이 사용한다.

public class AbstractSingletonTest {
    public static void main(String[] args)
            throws InstantiationException, IllegalAccessException {
        // 객체를 생성한다.
        AbstractSingleton ac = AbstractSingleton.getInstance(ConcreteSingleton.class);
       
        System.out.println(ac.getName()); // null
        ac.setName("instance one"); // 객체의 이름을 설정한다
        System.out.println(ac.getName()); // instance one 출력
       
        // 두번 째 객체를 생성한다. 저장된 객체를 리턴한다.
        AbstractSingleton acOther = AbstractSingleton.getInstance(ConcreteSingleton.class);
        System.out.println(acOther.getName()); // instance one 출력
    }
}


안타깝게도, AbstractSingleton 클래스를 상속받는 서브 클래스의 생성자를 private 으로 설정할 수 없다.
다만 AbstractSingleton 클래스와 그 서브 클래스를 같은 패키지에 두고,
default 접근자로 작성하여 다른 패키지에서 해당 객체를 생성할 수 없도록 제한하는 정도에 만족해야겠다.



더 자세한 내용은 아래 페이지를 참고하자.
저작자 표시 비영리 변경 금지
Posted by ohgyun
발생일: 2009.12.08

문제:
xper 그룹스 메일링을 받아보고 있는데,
오늘 누군가가 TDD(Test Driven Development) 진행 중 생긴 문제점에 대해 질문을 한 내용이 있다.

유닛 테스트를 위해 모든 메서드를 public 으로 선언하다보니,
"public 메서드가 너무 남발되는 게 아닌가, 객체지향의 원칙을 깨버리는 게 아닌가." 하는 생각이 들었다고 한다.

나도 예전에 같은 문제로 질문한 적이 있었는데,
그 당시에 멤버 변수는 private 으로, 메서드는 모두 public 으로 설정하는 쪽으로 결론을 냈었다.

그에 따라 (테스트를 위해) private 으로 작성된 메서드의 접근자를 단순히 모두 public 으로 바꾸는 식으로 구현했었다.
이 때는 마틴파울러의 Refactoring 책을 읽고 있어서 Replace Temp With Query 기법을 쓴답시고
마침 private 메서드가 굉장히 많을 때이기도 했다.
(결국 한 클래스에 잘게 쪼개진 public 메서드가 꽤 많이 생기게 됐다.)

헌데 요새 디자인 패턴에 대해 공부하다 보니,
요 놈들을 클래스로 빼내고 프록시 패턴이나 퍼사드 패턴을 써서 정리할 수도 있을 것 같다.


여튼, 그건 그렇고, 나도 아직 궁금하다.
과연 단지 유닛 테스트를 하기 위해서 모든 메서드를 public 으로 선언해야 할까?

해결책:
이에 대한 토론이 활발하게 있었다고 한다.

일단 토론은 'private 메서드를 어떻게 테스트 할 것인가? (Testing Private Interfaces)' 부터 시작됐겠다.

그러다 모든 메서드는 public 이어야 한다. 라는 주장이 나오게 된 듯 하다.



"모든 메서드를 Public 으로 하자"는 토론에 대해 정리해보면 아래와 같다.
(각자의 의견을 주거니 받거니 하고 있어 재밌다.^^)


토론 정리 내용 보기



어느 쪽의 의견이 맞다고는 할 수 없겠다.^^;

나는 개인적으로 private 메서드를 쓰는 걸 좋아하긴 하지만,
이 참에 한 번 모든 메서드를 public 으로 쓰는 쪽으로 시도해보려고 한다.

단순히 private 제한자를 public 으로 replace 하는 것 말고,
디자인 자체를 변경하면서 말이다.


그렇다면 어떻게 적용해야 할까....?
위 토론 중에도 있던 내용인데, 참고가 될 것 같아 내용을 덧붙여 적어본다.


[모든 메서드를 public 으로 리팩토링] 보기






저작자 표시 비영리 변경 금지
Posted by ohgyun