Whiteship's Note


[스프링 3.0] @Async 테스트

Spring/3.0 : 2010.03.04 17:55


@Async 애노테이션을 사용한 메서드의 반환 타입은 둘 중 하나여야 합니다. void 거나.. java.util.concurrent.Future 타입이어야 한다네요. 뭐.. 그러니까 사실상 하나나 마찬가지이죠. 그렇다고 해서 다른 리턴타입으로 설정하면 비동기로 동작하지 않는 건 아닙니다. 다만;; 해당 메서드의 클라이언트 입장에서 보면 리턴값이 전부 null 이기 때문에 황당한 경우가 발생할테지만 말이죠.

스프링에서 Future 인터페이스의 구현체로 AsyncResult를 제공해줍니다. 이걸 이용해서 간단하게 Thread를 반환하는 비동기 메서드를 만들었습니다.

    @Async
    public Future<Thread> more() {
        return new AsyncResult<Thread>(Thread.currentThread());
    }

@Async는 @Transaction과 비슷하게 타입에 선언할 수도 있습니다. 그러면 해당 클래스의 모든 메서드가 비동기 메서드로 처리되겠죠.

<task:executor id="myExecutor" pool-size="5"/>

쓰레드 풀 갯수를 5개로 설정해 놓고 다음과 같이 테스트를 해봤습니다.

    @Test
    public void async() throws Exception {
        assertThat(beanService, is(notNullValue()));
        Set<Thread> threads = new HashSet<Thread>();

        for(int i = 0 ; i < 200 ; i++){
            collectThreadInfo(beanService.more(), threads);
        }
        assertThat(threads.size(), is(5));
        assertThat(threads.contains(beanService.more().get()), is(true));
    }

    private void collectThreadInfo(Future<Thread> future, Set<Thread> threads) throws Exception {
        threads.add(future.get());
    }

200번까지 안돌려도 상관없지만. 그냥.. 충분히 돌려서 쓰레드 풀에 있는 모든 쓰레드를 컬렉션에 모아둔 다음에 쓰레드 풀에서 만든 쓰레드 갯수를 확인하고 마지막으로 한 번 더 호출해서 반환 받은 Thread가 현재까지 사용한 쓰레드 중 하나인지 확인합니다.


저작자 표시
신고
top

TAG @Async, Spring

그루비 사용자와 스프링

Spring/etc : 2009.01.07 10:55


스프링의 동적 언어 지원 기능은 2.0 버전부터 두각을 드러냈지만 '아.. 이런 기능이 있네..' '오.. 되는구나..' 정도로 간단한 예제를 몇 개 실험해봤을 뿐 어떻게 활용해야 할지는 전혀 몰랐는데 관련 기사가 developerWorks에 떴습니다. 두 개씩이나..

Groovier Spring, Part 1: Integration basics

Groovier Spring, Part 2: Change application behavior at run time

1부는 2월에 2부는 3월에 번역해서 한국 IBM developerWorks에 올릴 생각입니다.
신고
top


Grails(is Spring) 시작하기

Grails : 2008.12.05 13:08


0. 일단 http://www.grails.org/Home 여기로 가서 홈피 구경을 합니다.

1. 다운로드하러 갑니다. 이때 주의 할 건 맨 위에 보이는 1.0.4가 아니라 1.1.0 베타1을 받습니다.

2. 압축을 풀고 Grails 홉 디렉터리를 GRAILS_HOME으로 환경변수에 추가하고 PATH에는 GRAILS_HOME/bin을 추가해줍니다.

3. 콘솔에서 grails help를 입력해 봅니다.

=> 화면에 뭔가 잘 뜨면 Grilas 준비 끝 납니다. 이젠 이클립스를 조금 준비해 줍니다.

4. 이클립스를 켜고 Groovy 플러그인을 받아서 설치합니다. http://dist.codehaus.org/groovy/distributions/update/ 업데이트 사이트를 이용할 수 있습니다.

5. Preference -> Java -> Build Path -> Classpath Component인가(?) 에 new 를 클릭하고 GRAILS_HOME 환경 변수를 등록해 줍니다.

=> 이클립스 준비 끝. 이젠 Grails 퀵스타트를 보면서 프로젝트를 하나 만들어 봅니다.

6. grails create-app 프로젝트이름

7. 이클립스에서 import ->  Existing Project ... 로 프로젝트 로딩(Grails 프로젝트는 만들 때 기본으로 이클립스에서 인식할 수 있는 프로젝트로 만들어 줌)

8. grails create-domain-class 도메인이름

9. 이클립스에서 프로젝트 리로딩(F5)

10. 도메인 클래스에 필요한 속성 추가

11. grails generate-all

12. grails run-app

13. 브라우저에 http://localhost:8080/프로젝트이름/도메인이름/list 입력

14. 환호성.. 오오오~~~~!!!

코드가 Groovy라서 자바 코드 활용도 간단하고 코딩 스타일도 (자바 스타일이랑 거의 똑같이 해도 제대로 동작하지만) 좀 더 간결해지고 무엇보다.. 스크립트 언어 장점을 그대로 활용할 수 있다는거.. 이 말은 서버 재시작 없이 애플리케이션 변경하면 그 변경 사항이 그대로 적용된다는거... 아드리안 콜리어가 보여준 데모의 아주 일부를 해 봤습니다.

아드리안이 보여준 데모는 이 데모 전에 스프링 인티그레이션을 보여주고 위의 데모 뒤에 위 애플리케이션이랑 스프링 인티그레이션으로 만든 애플리케이션을 연동하고, 마지막으로 grails war를 사용해서 스프링 dm 서버에 설치하는 것 까지 보여줬습니다.

바램이 있다면, Grails에서 스프링 DM 개발을 빨리 지원해주면 좋겠습니다. grails bundle 같은 명령어로 쉽게 OSGi 번들로 패키징 해주면 좋겠습니다. 이런 플러긴을 만들 수 있는지 살펴보고 없으면 제가 만들어서 스프링소스에 제공해도 되겠네요. 흠~ 해볼까나~
신고

'Grails' 카테고리의 다른 글

Grails(is Spring) 시작하기  (2) 2008.12.05
top


스프링을 사용하는 애플리케이션의 성능 최적화 방안

Spring/etc : 2008.11.18 13:38


참조: Spring In Production White Paper
번역, 요약, 편역: 백기선

어떻게 하면 스프링을 사용한 애플리케이션의 성능을 향상 시킬 수 있을까?

처음으로 할 일은 성능을 측정하여 핫스팟을 발견하고 변경으로 인해 얻을 수 있는 이점을 정량화 한다. 최적화는 두 분류 효율적인 청사진 만들기(설정 튜닝하기)와 효율적인 런타임 기능 사용하기(애플리케이션 설계 최적화)로 나뉘어진다.

측정하기

튜닝은 측정부터.. 아파치 JMeter, Selenium 그리고 프로파일러를 사용한다. 스프링소스 컨설턴트들은 JAMon을 Spring AOP나 ApsectJ와 함께 사용해서 컴포넌트의 동작이나 요청 처리 경로를 프로파일링 할 때 좋은 성과를 봤다.

효율적인 청사진 만들기

효율적인 청사진 만들기의 비밀은 배포 플랫폼의 장점을 충분히 활용하는것에 있다. 스프링이 환경 종속적인 정보를 애플리케이션 코드 밖으로 빼내어 관리하기 때문에 이렇게 하는 것이 훨씬 쉽다.

데이터베이스 커넥션 풀의 경우, 애플리케이션 서버 위에서 실행하고 있다면 풀 설정을 관리자 콘솔에서 하고 스프링에는 JNDI를 통해 참조하도록 할 수 있다.

<jee:jndi-lookup id="dataSource"
    jndi-name="jdbc/MyDataSource"/>

이렇게 하면 두 가지 장점이 생긴다. 하나는 애플리케이션을 애플리케이션 서버 콘솔을 통해 운영팀이 관리하기 쉬워진다. 두 번째는 애플리케이션 서버 밴더가 커넥션 풀을 최적화 할 것이다.

같은 이유로 JMS ConnectionFactory 와 Destinationeh 애플리케이션 서버에 설정하고 JNDI로 얻어올 수 있다.

트랜잭션 관리의 경우도, 스프링은 커스텀 플랫폼 트랜잭션 매니저를 제공하여 여러분 배포 환경에 맞는 걸 사용할 수 있도록 해준다. ex) WebLogicJtaTransactionManager, Oc4jJtaTransactionManager,
스프링 2.5에 추가된 <tx:jta-transaction-manager/> 태그가 자동으로 기반으로 하고 있는 플랫폼을 찾아서 적절한 구현체를 선택할 것이다.

스프링 JMX를 사용하여 애플리케이션 자원을 노출할 때, 제품 플랫폼이 제공하는 MBeanServer와 연동하고 싶을 것이다. ex)  웹로직-JNDI (“java:comp/env/jmx/runtime”), WebSphereMBeanServerFactoryBean,
스프링 2.5에 추가된  <context:mbean-server ... /> 태그를 사용하면 자동으로 적절한 MBeanServer를 찾아준다.

이런 장점이 있지만, 통합 테스트를 애플리케이션 서버 밖에서 하고 싶을 것이다. 예를 들어, 기본적인 메시징 테스트는 ActiveMQ를 사요해서 하지만 실제 제품을 배포할 때는 IBM의 MQSeries를 사용할 수 있다. 스프링이 여러 설정 파일을 사용하는 기능이 있기 때문에 쉽게 처리할 수 있다. 우리는 모든 환경-독립적인 설정을 핵심 애플리케이션 설정에서 분리하는 것을 추천한다. 최선책은 애플리케이션 모듈 마다 하나의 설정 파일을 유지하는 것이다. 여기에 추가로 integration-test.xml 설정과 proeduction.xml 같은 설정을 정의할 수 있을 것이다. 통합 테스트를 할 때는 적절한 모듈 설정 파일과 integration-test.xml을 사용하여 application context를 만들면 된다. 배포할 때는 production.xml 파일을 사용하면 될 것이다.

이와 관련있는 설정으로 PropertyPlaceholderConfigurer는 설정 값을 외부화 하여 운영 팀에서 변경할 수 있도록 할 때 매우 유용하다. 스프링소스 컨설턴트가 성공적으로 사용하고 있는 방법은 다음과 같이 properties 파일을 연쇄적으로 사용하는 것이다.

1. classpath*:*.properties.local: 이에 해당하는 프로퍼티 파일들은 소스 코드 관리 시스템에 포함시키지 않느다. 개발자 마다 재정의해서 쓰도록 한다.

2. classpath*:META-INF/*.properties.default: 이 속성 파일들은 빌드에 의해 생성되는 애플리케이션 요소에 포함되며 기본 설정 값들을 가지고 있다. 프로젝트의 요구사항에 따라 이 수준은 생략할 수도 있다.

3. classpath*:*.properties: 이 파일들은 애플리케이션 요소들 밖에 존재두고, 운영팀에서 쉽게 수정할 수 있게 한다.

<context:property-placeholder
  location="classpath*:META-INF/*.properties.default,
classpath*:*.properties,
            classpath*:*.properties.local"/>

효율적인 청사진 만들기에 추가적으로 생각해 봐야 할 것들
  • 최적의 JDBC 커넥션 풀 갯수 찾아보기. 테스트 할 때 실제 배포 시나리오대로 해볼 것
  • 쓰레드 풀 구현체를 사용하는 스프링의 TaskExecutor를 사용할 때 최적의 쓰레드 풀 크기를 찾아볼 것. 처음은 CPU 갯수와 동일한 쓰레드 갯수로 설정해두고, 로컬 파일 I/O를 쓰면 쓰레드 하나를 추가하고. 네트워크 기반 I/O를 할 떈 또 몇 개를 추가하는 식으로..
  • read-only 트랜잭션일 경우에는 read-only 속성을 선언할 것. 이렇게 하면 하이버네이트를 사용하여 많은 객체를 데이터베이스에서 읽은 다음 아무 일도 하지 않았을 때 정말로 성능이 향상된다. 이렇게 하면 FlushMode.NEVER로 설정되기 때문에 세션에서 불필요한 dirth checking을 하지 않는다.
  • 만약 2단 커밋이 필요 없다면, JTA 대신에 로컬 트랜잭션 매니저 사용을 고려해 보라. 스프링은 HibernateTransactionManager를 통해서 정말 쉽게 JDBC와 Hibernate 데이터 접근을 동일한 트랜잭션에서 처리하게 해준다. 둘은 다른 방법으로 DB에 접근하지만 동일한 트랜잭션을 사용한다.
  • acknowledge=”transacted" 설정을 통해 메시지 리스너 컨테이너에 네이티브 JMS 트랜잭션 사용을 고려하라.

런타임 최적화하기

대부분의 엔터프라이즈 애플리케이션 성능 문제는 영속 계층으로부터 기인한다. Good performance here is often a function of sound design choices. 몇 가지 팁을 살펴보자.
  • ORM 툴을 사용할 때, eager와 lazy 로딩 전략의 균형을 잘 맞춰야 한다. 기본으로 로딩 지연을 사용하고, 특정 경우에만 fetch-join으로 튜닝하여 이른 로딩 장점을 활용한다. 쿼리를 튜닝할 때는 데이터 셋을 제품이 지금부터 향후 1년 간을 기준으로 하라.
  • ORM 툴이나 데이터베이스를 사용하여 로그에 SQL문이 보이도록 하라. 너무 많은 쿼리가 발생하는 이규가 생길 때 이를 쉽게 찾을 수 있다.
  • 하이버네이트를 사용할 때, 하이버네이트 Statistics 객체를 사용하여 런타임에 무슨 일이 벌어지는지 알 수 있게 하라. 프로그래밍을 통해 statics에 접근하거나, 스프링을 사용하여 하이버네이트 Statics MBean을 여러분의 MBean 서버에 노출시킬 수 있다. 프로그래밍을 통한 statics 객체 사용을 JUnit 테스트에 활용하여 여러분이 예측 가능한 쿼리가 얼마나 많이 발생하는지 확인하거나, 허용하는 쿼리 수를 기술 할 수 있다. 그 수를 벗어나면 테스트가 실패하도록.
  • 배치 스타일의 기능, 벌크 업데이트 또는 추가, 스토어드 프로시저는 보통 ORM 보다는 JDBC를 사용하는 것이 최선책이다. 스프링은 이들을 혼용하기 쉽게 해준다. 예를 들어 하이버네이트와 JDBC 데이터 접근을 동일한 트랜잭션으로 할 수 있다. 이 때 동일한 테이블을 사용하는 JDBC가 제대로 동작하려면 하이버네이트 세션을 적절한 시기에 flush 해주어야 한다.
  • 데이터베이스가 제공하는 기능을 활용하라.
    • 엑셀 스프레드시트를 일겅야 하는 애플리케이션에서 간단하게 변환하고 각 행을 SQL 서버 테이블에 넣어야 한다면 3시간이 걸리는 일도 SQL 서버 lined 쿼리를 사용하면 17초 만에 뚝딱.
    • 하이버네이트로 데이터 트리를 특정 뎁쓰로 변환하는 작업을 아무리 튜닝해도 시간도 오래 걸리고 메모리로 쫑나는데, 이걸 오라클의 스토어드 프로시저로 오라클의 계층 쿼리 기능으로 하니까 5초 미만으로 해결 됨.
    • flat 파일을 오라클 데이터베이스로 읽을 필요가 있을 때, 오라클 SQL 로더를 사용하여 데이터를 staging 테이블로 읽어들인다음, 스토어드 프로시저로 변경하고 데이터를 복사하여 원하는 테이블에 넣을 수 있다.
  • 만약 (비즈니스 로직은 전혀 없고)완전한 영속 로직만 있는 메소드가 있다면 데이터베이스의 스토어드 프로시저로 옮기고 스프링 JDBC를 사용하여 그것을 호출하라.
  • 읽기 전용 참조 데이터는 메모리 내부의 캐시에 둘 수 있다.
배치 애플리케이션은 추가적으로 고려할 것이 있다. 메소드 사용이 중요하기 때문이다. 스트림-기반 알고리즘이 최선의 선택이다. 예를 들어 컬렉션 보다는 이터레이터를 사용하라. 파일을 가지고 작업할 때, 만약 줄을 나눠야 한다면 스프링-기반이 아니라 캐릭터-기반을 사용하라. 우리는 이런 접근 방법을 사용하여 2백 50만 줄을 읽어 들인 적이 있다. 파싱하고 처리하는데 4초 미만이 걸렸고 메모리는 102K만 사용했다.

XML을 사용하는 배치 애플리케이션도 스트리밍을 사용하라. 우리는 280mb 파일에 들어있는 100,000개의 복잡한 XML 이벤트를 처리해야 할 필요가 있었다. DOM 기반의 접근 방법으로 2.5 시간이 걸렸고, 가비지 컬렉팅으로 9분이 필요했다. XML pull-parshing 기반 접근 방법으로 바꾸었더니 3초 만에 처리가 끝났고 200k 메모리를 사용했다.

또 다른 팁으로 단위 테스트와 통합 테스트를 할 때 java.lang.management 패키지에 있는 JVM 통계정보를 사용하는 것이다. 그것을 사용하면 CPU와 가비지 컬렉션 시간들을 확인할 수 있다.

데이터 계층을 위한 마지막으로 조언으로  every team benefits from access to a good DBA.

이밖에 스프링소스 컨설턴트로부터 얻은 다른 최적화 방안은 다음과 같다.
  • 스프링 배치 프로젝트가 제공하는 retry 기능을 사용하여 실패시 재시도가 필요할 때 사용할 수 있다.(예를 들어, 오라클 RAC의 개별 노드에서 실패한 기능의 경우) 사용자가 에러를 만나는 부담을 덜어줄 수 있다.
  • 웹 요소 랜더링 비용을 과소평가 하지 말아라. 트랜잭션 밖에서 되길 원할 것이다.(이부분 때문에 OSIV 패턴 이야기가 나왔군..)
  • application context를 요청 마다 새로 생성하지 말아라.
  • 스프링의 비동기 task executer를 사용하여 백그라운드에서 실행해도 될 작업을 사용자가 기다리게 하지 말아라.
  • 적절한 리모팅 프로토콜을 선택하라. 만약 SOAP 호환이 필요없다면, 스프링의 HttpInvoker 같이 간단한 스키마를 사용하는 것이 더 간단하고 빠를 것이다.
  • 스프링 AOP를 애플리케이션의 굉장히 여러 부분에 적용하고 있다면 ApsectJ 사용을 고려하라
스프링소스 컨설턴트들이 최적화에 도움을 얻은 참고자료는 다음과 같다.
  • Thomas Kyte's “Runstats.sql” test harness
  • “Effective Oracle by Design” (Thomas Kyte)
  • “Java Performance Tuning” (Jack Shirazi)
  • Sun's Java Performance Guides


신고
top


AspectJ와의 연동을 고려한다면, 포인트컷을 최소화 해야합니다.

AOP : 2008.10.08 16:37


무슨 이야기냐면, 최소 권한 원칙인가... 그런거랑 비슷한겁니다.

바로 예제를 보면서 살펴보죠.

@Aspect
public class HibernateExceptionToDataAccessException {

    @Pointcut("@within(org.springframework.stereotype.Repository)")
    public void accountHibernateExceptionInDao(){}

    @AfterThrowing(pointcut="accountHibernateExceptionInDao()", throwing="e")
    public void translateHibernateException(HibernateException e){
        throw SessionFactoryUtils.convertHibernateAccessException(e);
    }

}

이 애스팩트는 하이버네이트 예외를 스프링의 DataAccessException으로 변환해주는 애스팩트입니다. 이 녀석은 하이버네이트로 DAO 만드는 여러 방법 중에 가장 깔끔한 방법을 사용할 때 쓰면 좋고 안 써도 별로 상관없는(하이버네이트 버전이 올라가면서 하이버네이트 예외도 uncatched exception으로 바꼈으며 계층 구조도 세밀하게 나눠뒀기 때문입니다.) 그런 애스팩트입니다. 그래도, 버전 올리기 힘든 하이버 예전 버전을 사용하는 라이브러리를 사용해야 한다면 유용하겠죠.

어쨋든, 본론으로 돌아가서..

저렇게 만들어둔 애스팩트에 문제점이 보이나요? 저도 방금전까진 몰랐습니다. 일단, 저 애스팩트의 포인트컷은 이해가 가시죠? @Repository 애노테이션을 가지고 있는 클래스의 모든 조인포인트를 나타낸 겁니다.

문제는 바로 이 조인포인트.. 이게 핵심입니다. 스프링에서는 메소드 실행 조인포인트만 사용하기 때문에, 저 애스팩트를 스프링 AOP에서 사용할 땐, 원하던 메소드에만 적용이 될 겁니다. 하지만, 만약 저 애스팩트를 AspectJ와 연동해서 사용한다면? 어떤 일이 벌어질까요?

사용자 삽입 이미지

캬오.. 43개??? 말도 안돼. 내가 테스트 할려고 만든 DAO가 몇개나 된다고.. 그 안에 메소드도 거의 한 두 개밖엔 안만들었는데.. 왠 43개.... 바로 크로스 레퍼런스 뷰를 열고 확인해봤습니다.

사용자 삽입 이미지

캬오~~~~ 마이 미스테이크... 처방이 필요합니다. 처방은 간단하기 때문에 비밀! 캬캬캬.(이번주 KSUG 세미나에서 공개하도록 하죠.) 처방후에는..

사용자 삽입 이미지

이렇게 AspectJ에서도 메소드 실행 조인포인트에만 걸 수 있습니다. 음하하하..
신고
top


개발에 필요한 AOP 뭐가 있을까?

AOP : 2008.10.06 17:22


1. 간단한 메소드 성능 검사

 개발 도중 특히 DB에 다량의 데이터를 넣고 빼는 등의 배치 작업에 대해서 시간을 측정해보고 쿼리를 개선하는 작업은 매우 의미가 있을 겁니다. 그럴 때 귀찮게 매번 해당 메소드 처음과 끝에 System.currentTimeMillis();를 쓰거나, 스프링이 제공하는 StopWatch 코드를 집어넣고 빼긴 뭐 합니다. 그런식으로 테스트 해보고 싶은 메소드가 여러 개일 때도 귀찮겠죠?

2. 트랜잭션 처리

트랜잭션 처리는 거의 필수라는 생각이 듭니다. 저야.. 여태까지 스프링의 도움으로 아무런 어려움없이 간단하게 트랜잭션 기능을 사용하고 있지만, 이걸 스프링 없이 사용하려고 해보니 코딩 하기가 싫어졌습니다. 보기 싫은 try-catch 코드는 계속 늘어나고, 핵심 로직은 정글 속에 숨어버리니 말이죠.

3. 예외 변환

스프링에는 DataAccessException이라는 매우 잘 정의되어 있는 예외 계층 구조가 있습니다. 예전 하이버네이트 예외들은 몇 개 없었고 그나마도 Uncatched Exception이 아니였습니다. 이렇게 구조가 별로 안 좋은 예외들이 발생했을 때, 그걸 잡아서 잘 정의되어 있는 예외 계층 구조로 변환해서 다시 던지는 애스팩트는 제 3의 프레임워크를 사용할 때, 본인의 프레임워크나 애플리케이션에서 별도의 예외 계층 구조로 변환하고 싶을 때 유용합니다.

4. 아키텍처 검증

이것에 관련된 내용과 애스팩트는 어젯 밤에 공개했습니다. 유용하겠죠?

5. 기타

- 하이버네이트와 JDBC를 같이 사용할 때, DB 동기화 문제 해결.
- 멀티쓰레드 Safety 관려하여 롹을 가지고 수행해야 하는 메소드들에 일괄적으로 메소드 수행 전에 롹을 가지게 하고, 메소드 실행 후에롹을 반환하는 애스팩트
- 데드롹 등으로 인해, PessimisticLockingFailureException 이런 예외를 만났을 떄 재시도를 하는 애스팩트.
- 로깅, 인증, 권환, ...

찾아보니, 유용한 것들이 많이 있었습니다. 물론 이밖에도 상당히 여러 경우에 AOP를 활용할 수 있을 겁니다. 단지, 익숙하지 않다보니.. 잘 안 쓰게 되는데, 이번 주 주말 KSUG 세미나 마지막 발표를 통해서 어느 정도 AOP와 가까워지는 시간이 되길 바랍니다. 이번 세미나에서 위에 나열 한 것 중에 절반 정도를 살펴보겠습니다.
신고
top


context:component-scan 엘리먼트는 annotation-config도 등록해줌.

Spring/Chapter 3 : 2008.09.22 18:43



이클립스에서 F2를 이용해서 읽어봤습니다.

Scans the classpath for annotated components that will be auto-registered as
 Spring beans. By default, the Spring-provided @Component, @Repository,
 @Service, and @Controller stereotypes will be detected. Note: This tag implies the
 effects of the 'annotation-config' tag, activating @Required, @Autowired,
 @PostConstruct, @PreDestroy, @Resource, @PersistenceContext and
 @PersistenceUnit annotations in the component classes, which is usually desired
 for autodetected components (without external configuration). Turn off the
 'annotation-config' attribute to deactivate this default behavior, for example in order
 to use custom BeanPostProcessor definitions for handling those annotations. Note:
 You may use placeholders in package paths, but only resolved against system
 properties (analogous to resource paths). A component scan results in new bean
 definition being registered; Spring's PropertyPlaceholderConfigurer will apply to
 those bean definitions just like to regular bean definitions, but it won't apply to the
 component scan settings themselves.

Content Model : (include-filter*, exclude-filter*)

수확이 있었군, 스프링 공부할 때 살펴볼 클래스가 하나 등장했다. PropertyPlaceholderConfigurer
설정 파일 읽어서 BeanDefinition 객체로 만든다는것 까진 알고 있었는데, 어디서 누가 하는진 몰랐는데 저 녀석이 하고 있었나 봅니다. 캬오~ JavaDoc 귿이야..

신고
top


static inner class를 Spring에 bean으로 등록하기

모하니?/Coding : 2008.08.21 14:49


public class Foo {
    static class Bar {
        static String say() {
            return "Bar-Bar-Bar";
        }
    }
}

요런 클래스가 있습니다. static inner class는 일반 inner class완 다르게 outter 객체가 없이도 접근할 수 있습니다. 사실상 outter 클래스를 통해서 접근할 뿐 보통 static 클래스랑 다를게 없습니다. inner class는 다르죠. outter 클래스의 인스턴스(객체)를 통해서만 접근할 수 있습니다.

따라서 일반 inner class를 bean으로 등록한다는 건 좀 이상합니다. outter 객체에 종속되어 있는 inner class만 독립적으로 사용할 수 없으니까요. 그걸 bean으로 등록하는 방법도 모르겠을 뿐더러, 등록한다고 해도.. 어떻게 쓸까요? 그 녀석들 만들려면 outter 객체를 항상 만들어서 inner class의 객체를 만들어서 반환하도록??.. 흠.. 좀 아닌거 같습니다. 그렇게 독립적으로 사용할 녀석이었다면 inner class가 아니였겠죠.

그렇다면, static inner class는 어떨까요? 얘는 bean으로 등록할 수 있더군요!! 왜냐면, 단순히 outter 클래스를 통해서만 접근할 뿐 일반 static 클래스와 다를것이 없거든요. 마치 outter 클래스 이름이 네임스페이스처럼 느껴질 뿐입니다.

등록하는 방법은 간단합니다. "$"를 사용해서
<bean id="bar" class="net.openseed.sandbox.innerclass.Foo$Bar" />

요런식으로 등록하면 됩니다. $를 사용해서 Bar 클래스가 Foo 클래스 안에 있다는 것을 알려줍니다. 간단하죠. 캬;; 스프링엔 참 별에 별게 많아요.

ps : 알려주신 사부님께 쌩큐.
신고
top


log4j 설정 파일 위치를 명시적으로 설정하고 싶을 때..

모하니?/Coding : 2008.07.23 13:51


Junit4 기반 테스트시 Log4 설정파일 위치 지정 방법이 있을까요?

질문에 답이 있었습니다.

질문의 내용은 웹 애플리케이션이 돌아갈 때 사용할 log4j 설정파일은 web.xml에서 설정할 수 있었는데, 테스트 할 때 사용할 설정 파일의 위치는 어떻게 설정할 수 있느냐 입니다. 저도 잘 모르겠어서 어떻게 해야 되지? 라는 고민을 하다가 가장 먼저 떠오른 방법은 문제를 우회하는 방법이었습니다. "왜 위치를 명시적으로 설정해야 되지?" 싶어서 말이죠.

그래서 생각한 방법이 소스 폴더에 설정 파일을 두는 방법입니다. Maven이 아닐 경우 보통 src와 test 라는 소스 폴더를 만들어서 사용할텐데요. 그 경우 둘 중 아무곳에나 설정 파일을 두기만 하면 output 폴더(이클립스 자바 프로젝트 기본 outpu 폴더는 bin폴더)로 이동하게 됩니다. 그럼 그 파일을 log4j가 찾아서 사용하죠. Maven일 경우네는 src/main/resouces에 두거나 src/test/resource에 두면 됩니다.

그러나 Maven일 때 주의 할 것이 있는데, 바로 해당 폴더 루트에 위치 시켜야 한다는 겁니다. src/main/recouces에 두지 않고 그 안에 새로운 폴더 하나를 만들어서 두면 그건 소스 폴더로 인식하지 않습니다. 따라서 log4j가 설정파일을 못찾게 되죠.

결론적으로 명시적으로 log4j 설정파일 위치를 설정해주고 싶습니다. 굳이 소스폴더가 아니여도 참조할 수 있게 말이죠. 그래서 살펴본 것이 web.xml에 등록되어 있는 org.springframework.web.util.Log4jConfigListener 이 클래스 입니다. 분명 저 클래스가 설정 파일 위치 정보를 가져가니까 어디선가는 그 파일을 읽어서 설정하겠죠.

그래서 Log4jConfigListener 이 클래스를 찾아갔더니 별로 코드가 없습니다.

public class Log4jConfigListener implements ServletContextListener {

    public void contextInitialized(ServletContextEvent event) {
        Log4jWebConfigurer.initLogging(event.getServletContext());
    }

    public void contextDestroyed(ServletContextEvent event) {
        Log4jWebConfigurer.shutdownLogging(event.getServletContext());
    }

}

이 코드에서 사용하고 있는 Log4jWebConfigurer를 찾아갔습니다. 그리고 그 안에 있는 initLogging 메소드 안에서 다음의 코드를 발견했습니다.

Log4jConfigurer.initLogging(location, refreshInterval);

빙고!! 게임은 끝났습니다.

유겐 휄러가 2003년에 만든 클래스더군요. Log4jConfigurer 클래스를 감상하면서.. 참 멋지다는 생각을 했습니다.

/*
 * Copyright 2002-2008 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.util;

import java.io.File;
import java.io.FileNotFoundException;
import java.net.URL;

import org.apache.log4j.LogManager;
import org.apache.log4j.PropertyConfigurator;
import org.apache.log4j.xml.DOMConfigurator;

/**
 * Convenience class that features simple methods for custom log4j configuration.
 *
 * <p>Only needed for non-default log4j initialization, for example with a custom
 * config location or a refresh interval. By default, log4j will simply read its
 * configuration from a "log4j.properties" or "log4j.xml" file in the root of
 * the classpath.
 *
 * <p>For web environments, the analogous Log4jWebConfigurer class can be found
 * in the web package, reading in its configuration from context-params in
 * <code>web.xml</code>. In a J2EE web application, log4j is usually set up
 * via Log4jConfigListener or Log4jConfigServlet, delegating to
 * Log4jWebConfigurer underneath.
 *
 * @author Juergen Hoeller
 * @since 13.03.2003
 * @see org.springframework.web.util.Log4jWebConfigurer
 * @see org.springframework.web.util.Log4jConfigListener
 * @see org.springframework.web.util.Log4jConfigServlet
 */

public abstract class Log4jConfigurer {


    /** Pseudo URL prefix for loading from the class path: "classpath:" */
    public static final String CLASSPATH_URL_PREFIX = "classpath:";

    /** Extension that indicates a log4j XML config file: ".xml" */
    public static final String XML_FILE_EXTENSION = ".xml";


    /**
     * Initialize log4j from the given file location, with no config file refreshing.
     * Assumes an XML file in case of a ".xml" file extension, and a properties file
     * otherwise.
     * @param location the location of the config file: either a "classpath:" location
     * (e.g. "classpath:myLog4j.properties"), an absolute file URL
     * (e.g. "file:C:/log4j.properties), or a plain absolute path in the file system
     * (e.g. "C:/log4j.properties")
     * @throws FileNotFoundException if the location specifies an invalid file path
     */
    public static void initLogging(String location) throws FileNotFoundException {
        String resolvedLocation = SystemPropertyUtils.resolvePlaceholders(location);
        URL url = ResourceUtils.getURL(resolvedLocation);
        if (resolvedLocation.toLowerCase().endsWith(XML_FILE_EXTENSION)) {
            DOMConfigurator.configure(url);
        }
        else {
            PropertyConfigurator.configure(url);
        }
    }

    /**
     * Initialize log4j from the given location, with the given refresh interval
     * for the config file. Assumes an XML file in case of a ".xml" file extension,
     * and a properties file otherwise.
     * <p>Log4j's watchdog thread will asynchronously check whether the timestamp
     * of the config file has changed, using the given interval between checks.
     * A refresh interval of 1000 milliseconds (one second), which allows to
     * do on-demand log level changes with immediate effect, is not unfeasible.
     * <p><b>WARNING:</b> Log4j's watchdog thread does not terminate until VM shutdown;
     * in particular, it does not terminate on LogManager shutdown. Therefore, it is
     * recommended to <i>not</i> use config file refreshing in a production J2EE
     * environment; the watchdog thread would not stop on application shutdown there.
     * @param location the location of the config file: either a "classpath:" location
     * (e.g. "classpath:myLog4j.properties"), an absolute file URL
     * (e.g. "file:C:/log4j.properties), or a plain absolute path in the file system
     * (e.g. "C:/log4j.properties")
     * @param refreshInterval interval between config file refresh checks, in milliseconds
     * @throws FileNotFoundException if the location specifies an invalid file path
     */
    public static void initLogging(String location, long refreshInterval) throws FileNotFoundException {
        String resolvedLocation = SystemPropertyUtils.resolvePlaceholders(location);
        File file = ResourceUtils.getFile(resolvedLocation);
        if (!file.exists()) {
            throw new FileNotFoundException("Log4j config file [" + resolvedLocation + "] not found");
        }
        if (resolvedLocation.toLowerCase().endsWith(XML_FILE_EXTENSION)) {
            DOMConfigurator.configureAndWatch(file.getAbsolutePath(), refreshInterval);
        }
        else {
            PropertyConfigurator.configureAndWatch(file.getAbsolutePath(), refreshInterval);
        }
    }

    /**
     * Shut down log4j, properly releasing all file locks.
     * <p>This isn't strictly necessary, but recommended for shutting down
     * log4j in a scenario where the host VM stays alive (for example, when
     * shutting down an application in a J2EE environment).
     */
    public static void shutdownLogging() {
        LogManager.shutdown();
    }

    /**
     * Set the specified system property to the current working directory.
     * <p>This can be used e.g. for test environments, for applications that leverage
     * Log4jWebConfigurer's "webAppRootKey" support in a web environment.
     * @param key system property key to use, as expected in Log4j configuration
     * (for example: "demo.root", used as "${demo.root}/WEB-INF/demo.log")
     * @see org.springframework.web.util.Log4jWebConfigurer
     */
    public static void setWorkingDirSystemProperty(String key) {
        System.setProperty(key, new File("").getAbsolutePath());
    }

}


유틸 클래스인데, abstract로 해놨습니다. 인스턴스 만들지 말라는거죠. 멋지지 않나요. 캬캬. 유틸 클래스를 보통 무심하게 그냥 public class로 해놓기 일수인데, abstract라는 키워드 하나가 정말 멋지게 느껴지지 않나요. 그리고 저 충실한 JavaDoc 코멘트들.. 정말 유겐은 대단합니다.

그러니까... 저런 코드를 출처(스프링 프로젝트 홈피 링크 덜렁 남기는게 아니라, 저 클래스나 javadoc URL을 걸어놔야 출처를 명시했다고 생각합니다. 그게 보통 말하는 출처 아닌가요?? 장난이 아니고서야 누가 대체 출처를 www.google.com 이라고 달죠?)도 없이 원작자 이름도 빼고 베끼는 코드를 보면, 어떻게 흥분을 안 하겠습니까.. 에흄.. 유겐은 참 착하기도 하지.

얘기가 좀 샜는데, 어쨌거나 저 클래스를 잘 이용하면 이 글의 제목에 대한 답은 된 것 같습니다. 역시나 멋진 스프링은 아직도 공부할 것들이 무궁무진 합니다. 특히 전 소스코드는 자세히 들여본적이 거의 없는데, 저 코드를 보고나니까 좀 관심이 갑니다. 쉬운 클래스 부터 하나씩 갈펴봐야겠습니다. BeanFactory나 ApplicationContext는 넘 어려워서 원~
신고
top


스프링의 getBean() 타입 캐스팅 없애는 방법

모하니?/Coding : 2008.04.28 18:08


 Service myService = (Service) ctx.getBean("service");

대부분 위처럼 캐스팅을 해서 사용합니다. 저는 캐스팅이 정말 싫었습니다.

 Service myService = ctx.getBean(Service.class);

이렇게 캐스팅 없이 사용할 수 있는 방법이 있습니다.

정답은? JavaConfig, http://www.springframework.org/javaconfig, JavaConfigApplicationContext
신고
top


찾았다. WebBindingInitializer

Spring/Chapter 13 : 2008.04.22 18:52


이 녀석이 였구나.. 여러 컨트롤러에 PropertyEditor 적용할 때 필요한 녀석이..

레퍼런스를 저 키워드로 뒤지면 다음과 같은 코드가 나옵니다.

<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
    <property name="cacheSeconds" value="0" />
    <property name="webBindingInitializer">
        <bean class="org.springframework.samples.petclinic.web.ClinicBindingInitializer" />
    </property>
</bean>

헤헷 이것만 가지고는 뭐.. 어쩌라는 건지 알 수 없죠. 저 클래스를 찾아봐야 합니다. 저 클래스를 찾는 방법은 여러 방법이 있지만, 제가 올려드리죠.

public class ClinicBindingInitializer implements WebBindingInitializer {

    @Autowired
    private Clinic clinic;

    public void initBinder(WebDataBinder binder, WebRequest request) {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        dateFormat.setLenient(false);
        binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
        binder.registerCustomEditor(String.class, new StringTrimmerEditor(false));
        binder.registerCustomEditor(PetType.class, new PetTypeEditor(this.clinic));
    }

}

우왕~~ 귿이다~~ 이제 컨트롤러 마다 똑같은 프로퍼티 에디터 등록 안해도 되겠당.
신고
top


Spring 컨트롤러와 request scope bean

모하니?/Coding : 2008.04.16 19:07


스프링 컨트롤러와 request scope관한 이메일이 왔습니다.

...

제가 느끼고 있는 궁금증은 Controller 사용시 bean scope에 관한 부분입니다.

몇번의 프로젝트에서  SpringMVC를 이용하여 프로젝트를 했었는데요.
하나의 Controller에서 요청을 처리를 하기 위해 MultiActionController를 사용했습니다.
 
BaseController를 정의해서 BaseController가 MultiActionController를 상속받도록 만들었고,
모든 Controller는 BaseController를 상속 받아 쓰는 형식으로 구조를 잡았습니다.
 
BaseController에는 handleRequestInternal() 메소드를 오버라이드 해서 모든 request 값을
파싱하여 Map에 담도록 해놓았구요. (map에서 값을 꺼내 요청을 처리하도록 말이죠.)
 
테스트를 위해 Controller 내에서 sleep() 을 준 뒤, 몇개의 요청을 날려보면
가장 나중에 요청된 정보로 앞의 정보들이 변경되더군요.
아마도 scope이 singleton이라 그런것 같더군요.
그래서 빈 설정시에 Controller에 대한 scope을 모두 request로 바꿔줬습니다.
 
<bean id="memberController" class="MemberController" scope="request">
 
이렇게 바꾸어주니 이전과 같은 현상은 발생하지 않더군요.

과연 이렇게 하는 것이 맞는 것인지, 아니면 BaseController를 scope="request"로 만들면 그걸
상속받는 다른 빈도 request가 되는 것인지 정확한 판단이 서질 않더군요.

제가 알기로는 Spring에서 Controller 이용시 threadsafe 한 설계는 개발자의 몫이라고 들었습니다.
어디를 봐도 정확한 가이드가 나와있지 않아서 혼자 헤매다 이렇게 메일을 드리게 되었습니다.

...

그리고 다음과 같이 답변해드렸습니다.

more..


request scope 빈을 써본지가 까마득한데 이런 경우에 유용하게 쓸 수 있겠네요. 내용 공유를 허락해주셔서 감사합니다.
신고
top


Spring XML 설정 파일에서 import 동작 원리

Spring/Chapter 3 : 2008.03.10 01:30


잘못된 spring configuration문제로 StackOverflowException 황당한 예외 이 글을 읽다가 궁금해서 테스트를 해봤습니다.

package contextImport;

import static org.junit.Assert.*;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class ApplicationContextTest {

    @Test
    public void testname() throws Exception {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("contextImport/applicationContext.xml");
        assertNotNull(applicationContext);
       
        A a = (A) applicationContext.getBean("bean1");
        assertEquals("whiteship2000", a.getName());
    }
}


간단합니다. A라는 객체의 속성 값을 보고 어느 파일에서 세팅된 녀석을 가져온 건지 확인하기 위한 코드 입니다.

Application Context 설정 파일은 세 개입니다.

applicationContext.xml : bean1(A.class, name=keesun), bean2(B.class)

    <import resource="applicationContext2.xml" />
    <import resource="applicationContext3.xml" />
    <bean id="bean1" class="contextImport.A">
        <property name="name" value="keesun" />
    </bean>
    <bean id="bean2" class="contextImport.B" />

applicationContext2.xml : bean1(B.class)

    <bean id="bean1" class="contextImport.B"/>

applicationContext3.xml : bean1(A.class, name=whiteship2000)

    <bean id="bean1" class="contextImport.A">
        <property name="name" value="whiteship2000" />
    </bean>

자.. 지금 bean1 이라는 id로 등록되어 있는 빈이 전부 세개 이고 그 중에서 두 개는 A 클래스 하나는 B 클래스 입니다.

결론부터 말씀드리자면, 저는 StackOverFlow를 목격하지 못했습니다. Casting Exception은 목격할 수 있지만 운좋게 피해 갈 수도 있습니다.

1. 위와 같이 설정 해 놓고 getBean("bean1")을 호출하면 applicationContext.xml에 정의되어 있는 bean1을 돌려줍니다.

놀랐습니다. 에러가 날 줄 알았는데, 에러가 안나서.. 그런데 생각해보니 참 잘 만들었다는 생각이 들었습니다.  오오~ import 한 건 무시하고 오버라이딩 했나봐~~ 근데 이건 착각이었습니다.

오버라이딩이라고 하기도 뭐한것이... import는 엄연히 자기가 가지고 있는 빈들과 동급으로 처리하는 거지 상속 구조 처럼 상위 하위와 같은 게층 구조가 아니기 때문입니다.

사실은 오버라이딩 보다 더 단순한 규칙이 적용됩니다. 이 글을 끝까지 보시면 알 수 있습니다.

2. applicationContext.xml에 있는 bean1 설정을 주석처리하거나 없애버리고 getBean("bean1")을 호출하면, applicationContext3.xml에 정의 되어 있는 bean2를 돌려줍니다.

오호.. 이거 뭐야!! 완전 똑똑하자나!! 어떻게 알았어!! 내가 bean1을 A로 캐스팅 할 줄 알고 applicationContext3.xml 에 있는 bean1을 준거야?? 그런거야?? 너 정말 그렇게 천재인거야???

아니죠. 그렇게 똑똑 할 수는... 없습니다. 불가능하죠. 빈을 만드는 시점(ApplicationContext 인데다가 싱글톤이니까 초기에 생성하겠죠)에서는 밖에서 누굴 부를지 알 수가 없습니다. 글쵸?

암튼, 그럼 어떻게 된 일이냐...

3. 2번 상황에서 import문의 위치를 바꿔서 applicationContext3.xml이 위로 가고 applicationContext2.xml이 아래로 오게 해놓고 테스트를 했습니다.

java.lang.ClassCastException: contextImport.B
    at contextImport.ApplicationContextTest.testname(ApplicationContextTest.java:16)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:585)
    at org.junit.internal.runners.TestMethod.invoke(TestMethod.java:59)
    at org.junit.internal.runners.MethodRoadie.runTestMethod(MethodRoadie.java:98)
    at org.junit.internal.runners.MethodRoadie$2.run(MethodRoadie.java:79)
    at org.junit.internal.runners.MethodRoadie.runBeforesThenTestThenAfters(MethodRoadie.java:87)
    at org.junit.internal.runners.MethodRoadie.runTest(MethodRoadie.java:77)
    at org.junit.internal.runners.MethodRoadie.run(MethodRoadie.java:42)
    at org.junit.internal.runners.JUnit4ClassRunner.invokeTestMethod(JUnit4ClassRunner.java:88)
    at org.junit.internal.runners.JUnit4ClassRunner.runMethods(JUnit4ClassRunner.java:51)
    at org.junit.internal.runners.JUnit4ClassRunner$1.run(JUnit4ClassRunner.java:44)
    at org.junit.internal.runners.ClassRoadie.runUnprotected(ClassRoadie.java:27)
    at org.junit.internal.runners.ClassRoadie.runProtected(ClassRoadie.java:37)
    at org.junit.internal.runners.JUnit4ClassRunner.run(JUnit4ClassRunner.java:42)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:38)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:460)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:673)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:386)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)

이야!!! 역시.. 뭔가 수상하다 했어!!
 
import는 import를 한 쪽에서 다른쪽에 정의 되어 있는 모든 bean 설정을 그대로 딱 import문을 사용한 고 위치에 XML 설정을 삽입하는 겁니다.

같은 이름의 bean이 있을 때 동작하는 방식도 무지 단순합니다. 같은 타입이건 아니건 간에 무조건 맨 아래 쪽에 정의 되어 있는 bean이 짱입니다.

스택오버플로우가 어떻게 발생한건지 더 궁금해져 갑니다 ㅠ.ㅠ
Ologist님 알려주세요~ 어떻게 된거지요???



퀴즈. 그렇다면, 다른 건 동일하고 applicationContext.xml만 다음과 같이 정의하고 위의 코드를 돌리면 어떤 결과가 발생할까요?

    <bean id="bean1" class="contextImport.A">
        <property name="name" value="keesun" />
    </bean>
      
    <bean id="bean2" class="contextImport.B" />

    <import resource="applicationContext3.xml" />
    <import resource="applicationContext2.xml" />

신고
top


Spring사용해서 POJO로 JMS하기 (ActiveMQ)



1. ActiveMQ 프로젝트를 다운로드 받습니다.(브로커를 실행해야 하기 때문에...)
2. Writer 작성하기
3. Reader 작성하기
4. bean 설정하기
    4-1. MessageListenerAdapter 로 Reader 등록하기.
    4-2. Writer 등록하기.
    4-3. ActiveMQConnectionFactory 등록하기.
    4-4. ActiveMQQueue 등록하기.
    4-5. Message Converter 등록하기.
    4-6. JMS Template 등록하기.
    4-7. Message Listener Container 등록하기.
5. 테스트.
    5-1. ActiveMQ 브로커 실행.
    5-2. 테스트 클래스 실행.

등록할 bean들이 조금 많지만, 다들 몇 줄 안 되고 필요한 이유를 알게 된다면 그리 어렵지 않습니다.

1. ActiveMQ 다운로드
http://activemq.apache.org/activemq-411-release.html

2. Writer 작성하기.
public class MessageWriter {

    private JmsTemplate jmsTemplate;

    public void setJmsTemplate(JmsTemplate jmsTemplate) {
        this.jmsTemplate = jmsTemplate;
    }

    public void write(DTO dto){
        jmsTemplate.convertAndSend(dto);
    }
}

3. Reader 작성하기.
public class MessageReader {

    public void readMessage(DTO dto){
        DTO recievedDto = (DTO)dto;
        System.out.println(recievedDto.getName());
    }
}

Writer의 경우 스프링 API(JmsTemplate)에 종속성이 생겨서 사알짝 POJO라고 할 수 있지만, Reader의 경우는 완전한 POJO입니다.

4. bean 설정하기.

4-1. MessageListenerAdapter 로 Reader 등록하기.
    <bean id="messageReader"
        class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
        <property name="delegate">
            <bean class="MessageReader" />
        </property>
        <property name="defaultListenerMethod" value="readMessage" />
        <property name="messageConverter" ref="dtoConverter" />
    </bean>

4-2. Writer 등록하기.
    <bean id="messageWriter" class="MessageWriter">
        <property name="jmsTemplate" ref="jmsTemplate" />
    </bean>

4-3. ActiveMQConnectionFactory 등록하기.
    <bean id="connectionFactory"
        class="org.apache.activemq.ActiveMQConnectionFactory">
        <property name="brokerURL" value="tcp://localhost:61616" />
    </bean>

4-4. ActiveMQQueue 등록하기.
    <bean id="queue"
        class="org.apache.activemq.command.ActiveMQQueue">
        <constructor-arg index="0" value="whitehsip.test.queue1" />
    </bean>

4-5. Message Converter 등록하기.
    <bean id="dtoConverter" class="DtoConverter" />

4-6. JMS Template 등록하기.
    <bean id="jmsTemplate"
        class="org.springframework.jms.core.JmsTemplate">
        <property name="connectionFactory" ref="connectionFactory" />
        <property name="defaultDestination" ref="queue" />
        <property name="messageConverter" ref="dtoConverter" />
    </bean>

4-7. Message Listener Container 등록하기.
    <bean
        class="org.springframework.jms.listener.SimpleMessageListenerContainer">
        <property name="connectionFactory" ref="connectionFactory" />
        <property name="destination" ref="queue" />
        <property name="messageListener" ref="messageReader" />
    </bean>

5. 테스트.

5-1. ActiveMQ 브로커 실행.
다운받은 ActiveMQ의 bin 폴더의 activemq.bat 파일을 실행합니다.

5-2. 테스트 클래스 실행.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"/springContext.xml"})
public class QueueTest {

    @Autowired
    private MessageWriter messageWriter;

    @Test
    public void testActiveMQJMS() throws Exception {
        DTO sendingDto = new DTO();
        sendingDto.setName("기선");
        messageWriter.write(sendingDto);
    }

}

콘솔창에 "기선" 이라고 찍히면 됩니다.


신고

'Spring In Action > 10. JMS' 카테고리의 다른 글

Spring사용해서 POJO로 JMS하기 (ActiveMQ)  (0) 2007.12.04
top


RMI + Spring



Remoting 기능을 지원해줍니다. 스프링을 사용하면 매우 간단하게 RMI를 사용할 수 있습니다.

서비스를 제공하는 쪽

1. POJO로 서비스 인터페이스 구현체 개발
2. RmiServiceExporter 등록하기

인터페이스는 더이상 Remote 인터페이스를 확장하지 않아도 됩니다.

package chapter8.client;

public interface EchoService {

    // word를 세 번 반복한 문자열을 반환합니다.
    String say(String word);
}

구현체의 메소드들은 더이상 RemoteException을 던지지 않아도 됩니다.

package chapter8.server;

import chapter8.client.EchoService;

public class EchoServiceImpl implements EchoService {

    public String say(String word) {
        StringBuilder builder = new StringBuilder(word);
        for (int i = 0; i < 2; i++) {
            builder.append(word);
        }
        return builder.toString();
    }

}

스프링 설정 파일에 서비스 구현체와 Exporter를 설정해줍니다.

    <bean id="echoService" class="chapter8.server.EchoServiceImpl" />

    <bean class="org.springframework.remoting.rmi.RmiServiceExporter">
        <property name="service" ref="echoService" />
        <property name="serviceName" value="EchoService" />
        <property name="serviceInterface"
            value="chapter8.client.EchoService" />
    </bean>

RmiServiceExporter가 기본으로 로컬에 1099 포트에 RMI 레지스트리가 있는지 확인하고 있으면 서비스를 등록하고, 없으면 RMI 레지스트리를 새로 만들어 실행한 다음 서비스를 추가합니다. 이 모든일을 알아서 해주기 때문에 개발자는 할 일이 없습니다.(하고 싶다면, 레지스트리 위치 설정과 포트 설정을 할 수 있겠죠. 그 때는 registryHost 속성과 registryPort 속성에 원하는 값을 설정해 주면 됩니다.)

서비스를 사용하는 쪽

1. 마치 서비스가 로컬에 존재하는 듯이 코딩합니다.
2. 스프링 설정 파일에서 RmiProxyFactoryBean을 사용하여 원하는 서비스를 bean으로 설정합니다.

테스트 코드입니다.

package chapter8;

import org.springframework.test.AbstractDependencyInjectionSpringContextTests;
import chapter8.client.Keesun;

public class KeesunTest extends AbstractDependencyInjectionSpringContextTests {

    @Override
    protected String[] getConfigLocations() {
        return new String[] { "chapter8/client/springContext.xml" };
    }

    private Keesun keesun;

    public void setKeesun(Keesun keesun) {
        this.keesun = keesun;
    }

    public void testDI() throws Exception {
        assertNotNull(keesun);
    }

    public void testEcho() throws Exception {
        String echoResult = keesun.yaahoo("Spring");
        assertEquals("SpringSpringSpring", echoResult);
    }
}

Keesun 이란 클래스가 EchoService를 사용하고 있습니다.

package chapter8.client;

public class Keesun {

    private EchoService echoService;

    public void setEchoService(EchoService echoService) {
        this.echoService = echoService;
    }

    public String yaahoo(String word){
        return echoService.say(word);
    }
}

마지막으로 스프링 설정 파일에 Keesun과 EchoService를 bean으로 등록해 줍니다.

    <bean id="keesun" class="chapter8.client.Keesun" />

    <bean id="echoService"
        class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
        <property name="serviceUrl"
            value="rmi://localhost/EchoService" />
        <property name="serviceInterface"
            value="chapter8.client.EchoService" />
    </bean>

확인하기

1. Server 실행하기
2. Test 코드(Client) 실행하기.

레지스트리 서버를 동작시키려면 서비스를 제공하는 측 스프링 설정 파일을 읽어들이면서 Exporter bean을 생성하면되겠죠.

package chapter8;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class EchoServiceServer {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("chapter8/server/springContext.xml");
    }
}

다음은 위에서 작성했던 KeesunTest를 실행해 줍니다.

사용자 삽입 이미지


신고

'Spring In Action > 8. Remoting' 카테고리의 다른 글

스프링 인 액션 8장 발표자료  (0) 2007.12.16
RMI + Spring  (0) 2007.11.30
top

TAG rmi, Spring

LocaleChangeInterceptor(국제화 지원 인터셉터)가 있었군요.

모하니?/Coding : 2007.10.25 10:18


이 클래스 역시 Spring에서 제공하고 있는 기본 Interceptor입니다. 다음과 같이 bean으로 등록한 다음에 국제화를 지원하고 싶은 요청들의 인터셉터로 등록하면 되겠습니다.
사용자 삽입 이미지

위 설정 파일은 스프링 프레임웤에 포함되어 있는 샘플 애플리케이션 일부 입니다. LocaleChangeInterceptor 인터셉터의 핵심 코드는 다음과 같이 구현되어 있습니다.

사용자 삽입 이미지
request에서 "locale"이라는 파라미터 값을 가져와서 Locale을 적용해 줍니다.
신고
top


Guice와 Spring JavaConfig의 DI 스타일 비교

Spring/Mission : 2007.03.21 09:14


원문은 Guice vs. Spring JavaConfig: A comparison of DI styles 이런 제목으로 올라왔습니다. 다소 기다란 글로 다 읽진 못했습니다. 대강 훝기만 했죠.

Spring In Action 책 초반에 나오는 Knight 예제를 구글 Guice와 Spring의 Configurationd을 XML이 아닌 Java로 어노테이션을 사용하여 할 수 있도록 하는 JavaConfig를 사용하여 비교한 글입니다.

비교된 항목은
1. String Identifier : both Guice and Spring JavaConfig register a win in this category, as neither relies on String identifiers in their configuration and both are easily refactorable through modern IDEs. 둘 다 비슷!

2. Speed/Performance : bean 생성할 때 여러 life cycle을 제공하는 spring이 당연히 단순하게 묶기만 하는 guice보다 느릴 수 밖에... Guice 승!

3. JAR file footprint : 예제(aop와 di사용한 예제)를 돌릴 때 필요한 jar 파일 갯수로..Guice는 딱 두 개 필요하고 spring은 spring-javaconfig.jar, spring-beans.jar, spring-aop.jar, spring-core.jar, spring-context.jar, commons-logging.jar, aopalliance.jar, asm-2.2.2jar, cglib-nodep-2.1_3.jar, and aspectjweaver.jar 좀.. 많이 필요하군요. Guice가 Spring보다 훨씬 가볍다고 할 수 있겠네요. Guice 승!

4. Non-intrusive : AOP에서 injection 되는 클래스가 Guice에 종속된다는 군요. 따라서 재사용 할 수도 없고 Guice.jar파일 없으면 컴파일도 안된다는군요. Spring 승!

5. Third-party beans : Third-party 빈에 속성을 DI 할 때 SpringConfig는 전혀 불편함을 못느꼈으나.. Guice로는 어려웠다. Spring 승!

6. Constant/literal injection : Injection할 때 Guice의 어노테이션 사용이 다소 불편한 것이 있다봅니다. Spring 승!

7. AOP : Spring이나 Guice나 둘 다 AspectJ에는 비교할 수 없지만 Guice는 공식 문서에서도 AOP가 Spring에 비해 좀 딸린다고 알렸고 그게 오히려 단순함을 추구하는 추세라고 했지만...어노테이션 붙이는게 복잡한다 봅니다. Spring 승!

결국 Spring이 DI와 AOP 기능만 Guice와 비교 해봤을 때 4승 1무 2패로 이겼네요.

이글의 밑에는 미친밥이 직접와서 리플도 달아놨군요. 흠.. 거기까지 읽기가 귀찮네요.
신고
top

TAG AOP, DI, Guice, Spring

Spring + Hibernate 프로젝트 베이스

모하니?/Coding : 2007.03.16 21:26


사용자 삽입 이미지
Spring 과 Hibernate 로 모델 부분을 구현하여 jar로 묶어서 나중에 war에 넣을 부분을 코딩할 프로젝트 베이스 입니다.

어노테이션 기반으로 퍼시스턴스를 표현할 것입니다.
public class MemberTest {
    @Test public void persistence(){
        ApplicationContext ac = new ClassPathXmlApplicationContext(
                new String[] { "applicationContext-dao.xml",
                        "applicationContext-jdbc-datasource.xml" });
        SessionFactory sf = (SessionFactory) ac.getBean("sessionFactory");

        Session s = sf.openSession();
        Transaction tx = s.beginTransaction();

        Member keesun = new Member();
        keesun.setName("기선");
        keesun.setAccount("whiteship");
        keesun.setPassword("pass");

        s.save(keesun);
        tx.commit();
        s.close();

        assertNotNull(keesun.getId());
    }
}

위와 같은 예제 코드가 하나 들어있으며 DB 설정은 database.properties 에서 적절하게 바꿔주시면 됩니다. hibernate.dialect 에 넣어야 할 값은 여기를 참조해서 사용하시는 DB에 맞게 바꿔주시면 됩니다.


나중에 war 프로젝트 베이스도 만들면 올려둬야쥐~
신고

'모하니? > Coding' 카테고리의 다른 글

까다로운 녀석..ㅠ.ㅠ  (0) 2007.03.27
Report Validator v0.9  (2) 2007.03.26
Report Validator v0.5  (0) 2007.03.26
아 답답해;;  (2) 2007.03.26
테스트를 꼭 실행 해야 하는 시점  (0) 2007.03.23
Spring + Hibernate 프로젝트 베이스  (0) 2007.03.16
JDBC Connection Properties  (0) 2007.03.16
만들고 싶은게 너무 많아요.  (6) 2007.03.09
레포트 검사기  (2) 2007.03.09
랜덤 리스트 축출기 v1.0  (0) 2007.03.08
Tdd Helper 사용 중  (0) 2007.03.07
top


주소록 개발 카탈로그



프로젝트 시작
* 요구 사항 인터뷰
개발 준비 작업
* 프로젝트 생성 + jar 파일 추가
* MySQL 설치 + 사용
* MySQL Connector Down + testAdd()
신나게 개발

more..

개발 도중 공부

more..

중간 점검
* 수정해야 할 것들
* 중간점검
에피소드
* Alt + Shift + X, R 주의
* DB 인코딩 문제
* MySQL 설치 시 주의 할 점



신고

'Spring > 주소록 만들기' 카테고리의 다른 글

SimpleFormController 에피소드2  (0) 2007.02.02
SimpleFormController 에피소드1  (2) 2007.02.02
Spring's form tag  (0) 2007.02.01
DisplayTag과 SpringMVC  (0) 2007.01.31
DisplayTag 배끼기  (2) 2007.01.31
주소록 개발 카탈로그  (4) 2006.12.30
JSP 화면 작성  (0) 2006.12.29
Spring MVC configuration 파일들 설정 하기  (0) 2006.12.26
MVC 컨트롤러 작성  (0) 2006.12.26
화면 작성  (0) 2006.12.26
Alt + Shift + X, R 주의  (2) 2006.12.25
top


log4j 설정하기(in spring)



log4j.properties 파일을 작성합니다.

more..


rootLogger에서 첫번째 DEBUG는 로깅 레벨을 지정합니다. 각 logging 툴 마다 지원하는 레벨이 다른데 예전 영회형 블러그에 log4j와 logging을 비교해둔 표가 있어서 퍼왔습니다.


log4j 는 다섯 단계 logging은 7 단계가 있는 아래로 갈 수록 많은 메시지를 출력해 줍니다. 다른 부분은 log4j를 공부해 볼 시간이 없기 때문에 직관적으로 콘솔 창에 뿌려주겠다고 설정하는 부분과 출력하는 포맷을 지정하는 것 같습니다.

이렇게 파일을 작성하는 데에는 역시 구글 신 님과 영회 형 블러그의 힘이 컸습니다. 감사합니다.

파일 위치 시키기

하지만 이 파일을 어디에 위치 시켜야 돌아가는지는 모르겠더군요. 그래서 이곳 저곳 열심히 복사해서 놓고 삭제하고 해보다 보니.. 소스코드폴더에 위치 시키면 된다는 것을 삽질로 발견했습니다. ㅎㅎㅎ;;
소스 코드 폴더가 두 개이기 때문에 두 곳에 모두 위치 시켜 놓지 않으면 log4j 설정파일을 못찾는 다면서 로그 메시지를 뿌려주지 않습니다.

문제는 두 개의 설정 파일 중에서 낮은 레벨이 우선시 되어 처리 됩니다. 한쪽은 FATAL이고 한쪽은 DEBUG면 FATAL에 맞춰서 출력됩니다.(로그 메시지가 왠만한 상황에선 출력되지 않는것 같습니다.) 이렇게 같은 내용의 파일이 중복되다 보니 이것도 리팩토링이 필요할 것 같은데.. 현재로썬 어떻게 해야될지 모르겠네요. :)

신고

'Spring > 주소록 만들기' 카테고리의 다른 글

단위 테스트 모르겠슴 ㅠ.ㅠ  (0) 2006.12.05
Eclipse에서 Rename Method 리팩토링  (0) 2006.12.04
세련된 SQL map (iBATIS)  (0) 2006.12.04
메소드 추상화  (2) 2006.12.04
log4j.properties 파일 위치 시키기  (2) 2006.12.04
log4j 설정하기(in spring)  (0) 2006.12.02
수정해야 할 것들  (2) 2006.11.28
iBATIS에서 selectKey 사용하기  (2) 2006.11.23
MySQL 설치 시 주의 할 점  (6) 2006.11.23
DB 인코딩 문제  (0) 2006.11.20
SqlMapClientDaoSupport  (0) 2006.11.18
top

TAG Spring

Spring Framework 소스코드 CVS로 다운받기

Spring/Mission : 2006.11.24 10:28


Spring 소스 SVN으로 다운받기를 시도해본 적이 있는데 오늘 갑자기 그 때 일이 다시 생각납니다. Spring 소스 코드를 CVS로 밖에 제공하지 않더군요. Eclipse에 SVN으로 checkout이나 commit은 종종 해봤지만 CVS는 해본적이 없어서 일단은 SVN repository와 같아 보이는 CVS repository를 열었습니다.

CVS+ 라는 아이콘을 클릭하고 소스코드가 있는 곳의 주소를 넣어줘야 될 것 같습니다.
위와 같이 입력하고 finish 를 하면 CVS repository에 다음과 같이 저장소가 하나 잡힌 것을 확인 할 수 있습니다. 아마도 Spring이겠죠. Spring을 체크아웃 받습니다.

이 안에 sample 코드도 많이 들어있고 spring 소스파일들은 src 폴더 안에 들어있습니다.

참고 : http://sourceforge.net/cvs/?group_id=73357

신고
top

TAG CVS, Spring

SqlMapClientDaoSupport




MySQL에 만들어둔 Member 테이블에 iBatis를 사용해서 SQL을 날리는 Member.xml에 있는 sql의 ID를 호출하는 SqlmapMemberDao가 상속받고 있는 SqlMapClientDaoSupport에 대해서 조사해 봅니다.

스프링 레퍼런스 12.5
를 참고하면 다음과 같은 표를 볼 수 있습니다.

저는 iBATIS 2,0을 사용하기에 DAO 클래인 SqlmapMemberDao가 SqlMapClientDaoSupport 클래스를 상위 클래스로 상속받도록 했습니다.

그리고 SqlMapClientDaoSupport에서 사용되는 SqlMapClient 객체를 DI(dependency injection)하기 위해 daocontext-member.xml에 다음과 같이 코딩합니다.
sqlMapClient 객체는 사용할 SqlMap을 다음과 같이 sql-map-config.xml 에 코딩해 둡니다.
여기서 사용하기로 한 Member.xml 파일을 보면 다음과 같습니다.

more..


이렇게 설정파일에 해당하는 iBatis Sql Map 2.0 과 JDBC인 datasource를 SqlMapClientFactoryBean 타입의 객체인 sqlMapClient의 속성으로 DI 시킵니다.

more..


글로 정리하다 보니까 이제 눈에 조금 보이네요.

SqlMapClientDaoSupport라는 클래스에서 SqlMapClient 객체를 사용하기 때문에 이것을 와이어링 해주는데.. SqlMapClieint는 org.springframework.orm.ibatis.SqlMapClientFactoryBean 이 타입이며 SqlMapClientFactoryBean을 정의 할때는 configLocation 이것과 dataSource 를 와이어링 해줘야 한다.

configLocation은 이곳에서 사용할 iBATIS sql map에 대한 정보를 가지고 있는 sql-map-config.xml 파일로 지정해주고 sql-map-config.xml에는 member.xml파일이 지정되어 있고 member.xml은 iBATIS를 사용하고 있다.

datasource는 db connection객체를 생성하기 위한 네 가지 정보에 값을 setting한다.
신고

'Spring > 주소록 만들기' 카테고리의 다른 글

log4j 설정하기(in spring)  (0) 2006.12.02
수정해야 할 것들  (2) 2006.11.28
iBATIS에서 selectKey 사용하기  (2) 2006.11.23
MySQL 설치 시 주의 할 점  (6) 2006.11.23
DB 인코딩 문제  (0) 2006.11.20
SqlMapClientDaoSupport  (0) 2006.11.18
AbstractTransactionalDataSourceSpringContextTests  (4) 2006.11.13
회원 목록 추가  (0) 2006.11.13
XML configuration  (0) 2006.11.10
MySQL Connector Down + testAdd()  (0) 2006.11.10
MySQL 설치 + 사용  (1) 2006.11.09
top


BeanFactory 인터페이스 살펴보기

Spring/Mission : 2006.10.25 14:03


먼저 org.springframework.beans.factory.BeanFactory가 책임지는 것은 무엇들이 있는지 살펴 보겠습니다.

BeanFactoryAPI를 살펴보겠습니다. 일반적으로BeanFactory XML 같은configuration data 안에 저장된 Bean에 대한 정의들을 읽어 들이고, org.springframwork.beans 패키지를 사용하여 Bean을설정합니다.

BeanFactory 인터페이스가 가지고 있는 필드 입니다.

Field Summary

static String

FACTORY_BEAN_PREFIX
         Used to dereference a FactoryBean and distinguish it from beans created by the FactoryBean.


BeanFactory 인터페이스 안에 들어있는 메소드들은 다음과 같습니다.

Method Summary

boolean

containsBean(String name)
         Does this bean factory contain a bean definition with the given name?

String[]

getAliases(String name)
         Return the aliases for the given bean name, if defined.

Object

getBean(String name)
         Return an instance, which may be shared or independent, of the given bean name.

Object

getBean(String name, Class requiredType)
         Return an instance (possibly shared or independent) of the given bean name.

Class

getType(String name)
         Determine the type of the bean with the given name.

boolean

isSingleton(String name)
         Is this bean a singleton?


메소드들은 대강 어떤 일을 책임을 지고 있는지 알겠는데 맨 위에 있는 필드 하나가 잘 모르겠네요.

org.springframework.beans.factory.BeanFactory
public static final StringFACTORY_BEAN_PREFIX"&"

Used to dereference a FactoryBean and distinguish it from beans created by the FactoryBean. For example, if the bean named myEjb is a FactoryBean, getting &myEjb will return the factory, not the instance returned by the factory.

dereference라는 단어는 처음보네요 ^^;; reference는 수도없이 봐왔는데. FactoryBean과 FactoryBean에 의해 생성된 bean을 구분하기 위해 사용한다고 하는군요. 예를 든 것을 보면 빈 이름이 myEjb이면 FactoryBean이고 &myEjb는 factory에 의해 반환되는 객체가 아니라 factory를 반환한다.

지쟈스... 무슨 말인지... Someboby PLZ HELP~
신고
top


Spring ApplicationContexts



Spring ApplicationContexts

Spring 에플리케이션의 심장과 정신에 해당하는 ApplicationContext 안에서 실제 DI를 수행합니다.

ApplicationContext는 BeanFactory를 특화시킨 것입니다.(ApplcationContext가 BeanFacotory를 상속받았죠.) BeanFactory는 Spring에서 사용되는 객체들의 레지스트리입니다. BeanFactory는 bean의 생성과 그들 간의 종속성 주입, 그리고 bean lookup(찾아 주기)를 담당합니다.

ApplicationContext는 이러한 BeanFactory의 기능에 부가적인 기능을 추가시킨 것입니다. 보통 BeanFactory대신에  이것을 사용한답니다. 웹 에플리케이션의 경우 웹에 특화된 WebApplicationContext를 사용합니다. ApplicationContext는 BeanFactoryPostProcessors를 가지고 초기화를 거친 후에 BeanFactory를 자동으로 처리할 수 있다고 합니다.(뭐가 어떻게 돌아간다는 건지는 잘 모르겠네요--;) 이밖에도 메시지를 internationalication(국제화)하는 기능, loosely coupled(약하게 결합된) 생산자와 소비자를 위한 event-routing mechanism, ApplicationContextAware같은 생명주기 인터페이스를 지원합니다.

ApplicationContext는 주로 XML 파일을 사용하여 bean들을 설정합니다. 매우 간단한 applicationContext.xml파일을 보겠습니다.



위 설정 파일에서 두 개의 bean을 등록한 것을 볼 수 있습니다. 이 bean들을 생성하고 관리하는 것이 ApplicationContext가 할일 입니다. 저 위의 <property> 속성을 보시면 Dependency Injection을 정의해 둔 것을 볼 수 있습니다. cashRegister bean에서 priceMatrix 레퍼런스가 priceMatrixBean을 사용하도록 정의한 것입니다.

위 설정 파일 없이도 그러한 것은 다음 순수 java code로도 가능합니다.

The Return of the POJO


이전 글과 이번 글에 걸쳐 DI와 ApplicationContext에 대해 배웠습니다. 이 두 개념은 Spring의 핵심 개념입니다. 이 것들을 에플리케이션(심지어 웹 에플리케이션 마저)을 Plain Old Java Object(POJO)로 개발 하기 위한 도구 입니다. 이것이 가능하기 때문에 강력한 Object Oriented 개념을 웹 에플리케이션 개발에 사용할 수 있습니다. 프레임웤에 특화된 코드를 작성하기 보다 비즈니스 로직에만 집중하여 개발하기 때문입니다. 이 책에서는 앞으로 도메인 객체를 먼저 작성하고 그런 다음 프레임웤을 사용하여 그것들을 웹 에플리케이션으로 향상시도록 할 것입니다.

여기까지 1장을 모두 요약하자면
  • Dependency Injection은 Inversion Of Control의 한 종류이다. 이것은 코드에 종속성을 주입하는 원리이다. 이것을 사용하면 객체 생성 또는 그것을 위치시키는 일을 프레임웍에게 전가 시키기 때문에 코드 자체의 테스트가 용이해진다.
  • ApplicationContext는 Spring의 주요 객체 레지스트리와 통합 포인트다. 주로 XML파일을 통해서 bean과 그들의 종속성을 설정한다. 많은 기능이 있지만 그 중에 객체 생성과 DI가 핵심 기능이다.
  • 마지막으로 POJO 스타일로 개발이 가능하다는 것이다. 따라서 객체 지향 개발 기술의 사용이 가능하며 POJO로 개발 된 시스템은 테스트가 용이하고 유연하다. 또한 개발자들이 프레임웤을 어떻게 다루는지 보다는 문제 영역과 비즈니스 로직에 집중하여 개발이 가능해진다.
신고

'Spring MVC > 2장 Spring 기본요소' 카테고리의 다른 글

Spring ApplicationContexts  (0) 2006.10.06
Inversion of Control  (4) 2006.10.06
top


Inversion of Control



Inversion of Control

Dependency Injection(의존성 주입)과 혼용되어 사용되는 것을 종종 보았는데 이 글을 읽어보니 어느정도 명확해 지네요. IOC가 보다 광범위한 의미이고 이 것을 표현하는 여러가지 방법이 있습니다. DI도 그중에 하나라고 하네요. 이번 장에서 말하고 있기로는 AOP와 DI가 IOC의 일부라고 합니다.

먼저 간략히 정의를 살펴보면 IOC는 braod range of techniques that allow an object to become a passive participant in the system. IOC를 사용하면 객체의 몇몇 특성이나 시점(aspect)에 대한 제어권을 프레임웍이나 환경에게 넘기게 됩니다. 그러한 제어권에는 객체의 생성이나 의존하는 객체들의 임명 등이 있습니다.

AOP

먼저 AOP를 사용하게 되는 경우를 살펴보겠습니다.

여기 보시면 주황색 부분의 코드(인증 관련 코드)가 매번 중복되어 나타나는 것을 볼 수 있습니다.
사실 인증은 메소드 본래의 작업이 아닌 부가적인 작업에 불과함에도 본래 코드보다 많은 부분을 차지 할 뿐만 아니라 계속해서 반복되어 나타나고 있습니다.

이 코드를 시스템 뒤딴으로 빼려면 Spring의 AOP(Aspect-Oriented Programming)를 사용하서 간단히 해결할 수 있습니다. Aspects are concerns of the application that apply themselves across the entire system. Aspect란 전체 시스템에서 통용되어 사용되는 에플리케이션과의 관계라고 할 수 있나요...흠; 어렵네요. 위 예제에서는 SecurityManager가 시스템에서 통용되는 aspect의 하나의 예라고 할 수 있겠네요. 그리고 통용된다는 증거로 모든 BankAccount의 모든 메소드에서 hasPermission 메소드를 호출하고 있습니다. 이런 비슷한 aspect로는 logging, auditing, transaction management가 있습니다. 이 중에 auditing은 뭔지 잘 모르겠네요.


Spring AoP는 이러한 aspect들을 runtime 때 또는 compile 때 도메인 모델(여기선 BankAccount)에 끼워넣습니다.(보통 weaving 한다고 합니다.) 이 말은 BankAccount에서 위와 같이 주황색 부분의 코드를 전부 지워도 AOP 프레임웤이 이 전과 똑같이 동작하도록 도와준다는 것입니다.

Spring은 기본적으로 proxy-based AOP(프록시 기반 AOP)를 사용합니다. 이 말은 원래 목표가 되는 객체(targer object)를 - 여기서는 BankAccount가 되겠죠- Aspect를 적용하기 위해서 Proxy로 감싸서 목표 객체 대신에 사용한다는 것입니다.  다음의 그림을 보시면 이해가 될 것입니다.

여기서 잠깐 Spring은 프록시가 아닌 컴파일 할 때 끼워넣는 AspectJ를 지원한다는 점. 그리고 이게 단순한 프록시를 사용하는 솔루션 보다 더 많은 기능을 가지고 있다고 합니다.


BankTeller가 colseOut을 호출 하면 이것은 BankAccount의 메소드를 호출하는 것이 아니라 BankAccount의 프록시를 호출하게 되고 이 프록시는 weaving된 aspect에 따라 3번 작업을 하고 그리고 본래의 작업인 4번을 BankAccount를 이용하여 처리합니다.
여기까지 정리
  • IOC는 제어권을 프레임웤에 넘기는 포괄적인 개념이다.
  • 여기서 제워권이란 새로운 객체의 생성, 트랜잭션, 보안에 대한 제어들을 이야기 한다.
  • AOP는 IOC를 실현하는 하나의 기술이다.
  • DI또한 IOC를 실현하는 하나의 기술이다.
Dependency Injection

DI는 Spring 프레임웤의 핵심 기술입니다. DI is a technique that framework use to wire together an application. 프레임웤은 에플리케이션 간의 종속성을 연결하는 작업을 하여 에플리케이션에 있는 객체 생성을 하는 코드를 제거하는 일을 합니다.

DI와 Service Locator pattern을 비교할 수 있는데.. 그 둘을 비교하기 위해 간단한 예제를 보겠습니다. 이 예제는 쇼핑카트에 있는 물품들을 계산을 하는데 DB로 부터 그 물품들의 가격을 가져와서 합산하는 프로그램입니다. 따라서 먼저 간단한 인터페이스를 하나 작성을 합니다. 이 인터페이스는 CashRegister로써 쇼핑 카트를 매개변수로 받아서 그 안에 있는 물품을 계산하는 calculateTotalPrice 메소드가 있습니다. 그리고 PriceMatrix 인터페이스에는 lookup 메소드가 있어서 원하는 물품의 가격을 반환해주는 일을 합니다.


그리고 CashRegister를 구현한 CashRegisterImpl을 봅시다.


어쩌면 이렇게 한 것이 매우 자연스럽지만 다음과 같은 세가지 중요한 문제가 있습니다.
먼저 가장 중요한 것으로 인터페이스가 아닌 특정 구현에 의존한다는 것입니다. 이 말은 H.F. Degisn Pattern 1장에 나오는 디자인 원칙이며 위 코드는 그것을 어겼습니다. 특정 구현에 의존하게 되면 왜 안좋은지에 대한 것은 위에서 확인하시기 바랍니다.
둘째 PriceMatrixImpl 객체가 CashRegisterImpl을 생성 할 때 마다 매번 생성된다는 것입니다. 이것은 문제가 있지요. 가격표는 하나만 있으면 되는데 매번 계산할 때 마다 새로운 가격표를 생성한다는 것은 자원을 낭비하는 일입니다.
마지막으로 첫번째 발생한 일에 딸려 오는 문제로써 테스트하기가 힘들다는 것입니다. CashRegister를 테스트하기 위해서는 지금 PriceMatrixImpl까지 제대로 구현으르 해야 테스트가 가능합니다. 그리고 단위 테스트는 아예 할 수가 없는 상황이네요.

그럼 PriceMatrix없이 테스트가 가능하기나 하냔 말인가가 궁금한데요. 저도 잘 모르는 부분인데 Mock이라는 기술을 사용하면 단위 테스트가 가능하다고 합니다.

Service Locator를 사용해 봅시다.

Service Locator Pattern은 객체를 생성하여 그 레퍼런스를 얻어내는 과정을 숨기는 것을 말합니다. 클라이언트를 객체가 언제, 어디서 어떻게 생성되는지 모르도록 하는 패턴입니다. 클라이언트를 보호하고 코드 중복을 줄이기 위해서 이 패턴이 만들어졌다고 합니다. 보통 static 메소드를 사용하여 요구된 객체에 대한 하나의 객체를 반환해 줍니다. (싱글턴 패턴하고 다른게 뭐죠? 첨에는 static 팩토리인가.. 생각했다가 오직 하나의 클래스에 대한 하나의 객체만 리턴 해주면 이거 싱글턴 아닌가.. 하는 생각이 들었습니다.)


이렇게 함으로써 첫번째 문제(특정 구현에 의존하던 문제)와 두번째 문제(매번 새로운 객체를 생성하던 문제)가 해결되었습니다. 하지만 세번째 단위 테스트를 하려면 Mock 객체를 사용해야 하는데 Mock객체를 끼워넣을 방법이 없습니다. 왜내면 저 위의 주황색 부분이 test할 때는 Mock 객체를 넘겨주고 실제 사용할 때는 PriceMatrix객체를 주도록 바꾸기가 어렵기 때문입니다. 이런것을 효율적으로 하려면 클라이언트 코드에서 자원을 가져오거나 생성하는 활동에 전혀 참여하면 안됩니다. 그냥 자원이 클라이언트에 주어져야 합니다.

Dependency Injection을 사용해 봅시다.

Service Locator를 사용하는 대신에 프레임웤이 PriceMatrix type 객체의 레퍼런스를 CashRegisterImpl에 제공해 줍니다.  객체 생성과 객체 연결(location)에 대한 책임이 클래스에서 프레임웤으로 뒤집어졌습니다. 이러한 것을 DI라고 하며 두 가지 방법이 흔히 사용됩니다.

Spring AOP의 도움을 받는 method-based injection이라고 불리는 세번째 방법도 있으나 복잡하고 잘 쓰이질 않아서 여기서 다루진 않는답니다.

처음으로 볼 DI의 한 종류로는 constructor-based injection이 있습니다. 이 것은 객체가 생성될 때 종속성을 주입하는 방법입니다.


이렇게 하면 된거죠. 하지만 이 클래스는 Hollywood Principle을 따르고 있는데요. 할리우드 원칙이란 "내가 전화할테니 전화하지 마!"라는 원칙입니다. 다시 이 경우에 대입해 본다면 "내가 줄테니 자원을 달라고 요청하지마!" 라고 할 수 있겠습니다.

다음은 Setter-based injection입니다.


PriceMatrix type의 객체 생성 시기가 좀더 유연해 졌습니다. 생성자 기반과 세터 기반 중에 어떤 것을 사용할 지는 사용자의 선택입니다. 물론 어떤 경우를 선택하느냐에 따라 실제 종속성을 주입할 때 따르게 되는 지침에 해당하는 XML 파일의 내용이 약간 바뀔 것입니다.
이 것은 생성자 기반 종속성 주입 시에 사용할 XML 설정 파일의 일부이며
이 것은 세터 기반 종속성 주입 시에 사용할 XML 설정 파일의 일부입니다.

첫번째 문제-특정 구현에 의존했었던-는 해결된 듯 합니다.
두번째 문제-매번 새로운 객체를 생성했던-도 역시 bean이 기본적으로 싱클턴 객체로 생성되기 때문에 해결 된 듯합니다.
그럼 남은것은 세번째 문제-단위 테스트를 할 수 없었던-것인데요.
이렇게 하면 Service Locator에서 해결 못했던 Mock을 사용한 단위 test를 어떻게 할 수 있을까요?

처음 본거라 저도 잘 모르겠지만 다음과 같이 Mock을 사용한다고 합니다.

여기까지 정리하면..
  • DI는 종속성을 필요로 하는 코드 없이 여러 클래스들을 묶는데 사용하는 기술입니다.
  • 클라이언트는 프레임웤에게 종속성의 생명주기 관리를 넘겼습니다.
  • 이렇게 함으로써 클라이언트는 테스트하기 용이해 집니다.
신고

'Spring MVC > 2장 Spring 기본요소' 카테고리의 다른 글

Spring ApplicationContexts  (0) 2006.10.06
Inversion of Control  (4) 2006.10.06
top







티스토리 툴바