이 문서는 개인적인 목적이나 배포하기 위해서 복사할 수 있다. 출력물이든 디지털 문서든 각 복사본에 어떤 비용도 청구할 수 없고 모든 복사본에는 이 카피라이트 문구가 있어야 한다.
9.5 AOP 프록시 생성에 ProxyFactoryBean 사용하기
비 즈니스 객체에 스프링 IoC 컨테이너(ApplicationContext나 BeanFactory)를 사용한다면(꼭 사용해야 한다!) 스프링의 AOP FactoryBean중의 하나를 사용하고자 할 것이다. (팩토리빈은 간접계층(layer of indirection)을 도입해서 다른 타입의 객체들을 생성할 수 있도록 한다는 점을 기억해라.)
Note
스프링 2.0 AOP 지원도 팩토리빈을 사용한다.
스프링 2.0 AOP 지원도 팩토리빈을 사용한다.
스 프링에서 AOP 프록시를 생성하는 기본적인 방법은 org.springframework.aop.framework.ProxyFactoryBean를 사용하는 것이다. 이 방법으로 적용할 포인트컷과 어드바이스와 이들의 순서를 완전히 제어할 수 있다. 하지만 이러한 제어가 필요하지 않다면 적절한 더 간단한 옵션이 있다.
9.5.1 기초
다른 스프링 FactoryBean 구현체처럼 ProxyFactoryBean은 간접 수준(level of indirection)을 도입한다. foo라는 이름으로 ProxyFactoryBean를 정의했다면 foo를 참조하는 객체들이 보는 것은 ProxyFactoryBean 인스턴스 자체가 아니라 getObject() 메서드의 ProxyFactoryBean 구현체가 생성한 객체다. 이 메서드는 대상 객체를 감싸는 AOP 프록시를 생성할 것이다.
AOP 프록시를 생성하는데 ProxyFactoryBean을 사용하거나 다른 IoC와 관련된 클래스를 사용할 때 IoC가 어드바이스와 포인트컷도 관리할 수 있다는 것이 가장 큰 장점이다. 다른 AOP 프레임워크와 함께 구성하는 여러운 접근을 가능하게 하는 것은 강력한 기능이다. 예를 들어 의존성 주입이 제공하는 플러거블(pluggability) 기능에서 얻은 이점으로 어드바이스 자체가 어플리케이션 객체를 참조할 것이다.(모든 AOP 프레임워크에서 사용가능해야 하는 대상객체이외에)
9.5.2 JavaBean 프로퍼티
스프링이 제공하는 대부분의 FactoryBean 구현체와 마찬가지로 ProxyFactoryBean 클래스 자체가 JavaBean이다. 이 클래스의 프로퍼티들은 다음을 할 수 있다.
- 프록시할 대상을 지정한다.
- CGLIB을 사용할 지를 지정한다.(아래의 내용과 Section 9.5.3, “JDK기반과 CGLIB기반의 프록시”를 참고해라.)
몇몇 핵심 프로퍼티는 org.springframework.aop.framework.ProxyConfig에서 상속받는다. (스프링에서 모든 AOP 프록시 팩토리들의 수퍼클래스다.) 이 핵심 프로퍼티들은 다음과 같다.
- proxyTargetClass: 대상 클래스의 인터페이스가 아니라 대상 클래스가 프록시되었다면 true이다. 이 프로퍼티 값이 true이라면 CGLIB 프록시가 생성될 것이다.(하지만 Section 9.5.3, “JDK기반과 CGLIB기반의 프록시”도 참고해라.)
- optimize: CGLIB으로 생성된 프록시에 적극적인 최적화를 적용할 것인지를 제어한다. 적절한 AOP 프록시가 어떻게 최적화를 다루는 지를 완전히 이해하지 못한채로 이 설정을 그냥 사용하지는 말아야 한다. 이 설정은 현재 CGLIB 프록시에만 사용되고 JDK 동적 프록시에서는 아무런 영향이 없다.
- frozen: 프록시설정이 frozen되면 더이상 설정을 변경할 수 없다. 이 설정은 사소한 최적화와 프록시가 생성된 후에 콜러(caller)가 프록시(Advised 인터페이스를 통해서)를 조작할 수 없게 하는 경우에 유용한다. 이 프로퍼티의 기본값은 false이므로 부가적인 어드바이스를 추가하는 것같은 변경작업이 가능하다.
- exposeProxy: 대상객체가 현재의 프록시에 접근할 수 있도록 현재 프록시를 ThreadLocal에 노출해야 하는지를 결정한다. 대상객체가 프록시를 획득해야 하고 exposeProxy 프로퍼티가 true로 설정되었다면 대상객체는 AopContext.currentProxy() 메서드를 사용할 수 있다.
- proxyInterfaces: 문자열 인터페이스 이름의 배열. 이 프로퍼티가 제공되지 않는다면 대상 클래스에 대한 CGLIB 프록시가 사용될 것이다.(하지만 Section 9.5.3, “JDK기반과 CGLIB기반의 프록시”도 참고해라.)
- interceptorNames: 적용할 Advisor나 인터셉터, 다른 어드바이스 이름의 문자열 배열. 순서가 중요하므로 처음 나온 것이 먼저 적용된다. 즉, 리스트의 첫 인터셉터가 가장 먼저 호출을 가로챌 수 있다.
이름들은 조상 팩토리의 빈 이름을 포함해서 현재 팩토리의 빈 이름들이다. 여기서 빈 참조를 없는데 그렇게 할 경우 어드바이스의 싱글톤 설정을 무시하는 ProxyFactoryBean가 되기 때문이다.
인 터셉터 이름을 별표(*)로도 추가할 수 있다. 이는 어플리케이션에서 별표 앞부분으로 시작하는 이름을 가진 모든 어드바이저 빈을 적용하게 된다. 이 기능을 사용하는 예제를 Section 9.5.6, “'전역' 어드바이저 사용하기”에서 볼 수 있다. - singleton: getObject() 메서드를 얼마나 많이 호출했는가에 상관없이 팩토리가 하나의 객체를 반환해야 하는지를 결정한다. 다수의 FactoryBean 구현체가 이러한 메서드를 제공한다. 기본값은 true다. 상태를 가진(stateful) 어드바이스를 사용하고자 한다면(예를 들면 상태를 가진 믹스인) singleton 값을 false로 하고 프로토타입 어드바이스를 사용해라.
9.5.3 JDK기반과 CGLIB기반의 프록시
이번 섹션에서는 특정 대상객체(프록시 되어야 하는 객체)에 대해서 JDK 기반의 프록시와 CGLIB 기반의 프록시 중 어느 프록시를 생성할 지 ProxyFactoryBean가 어떻게 선택하는지에 대한 완전한 문서를 제공한다.
Note
JDK 기반과 CGLIB기반 프록시를 생성하는 것과 관련된 ProxyFactoryBean의 동작은 스프링 버전 1.2.x와 2.0에서 달라졌다. ProxyFactoryBean는 이제 TransactionProxyFactoryBean 클래스등처럼 자동탐지되는 인터페이스과 관련된 의미와 유사하다.
JDK 기반과 CGLIB기반 프록시를 생성하는 것과 관련된 ProxyFactoryBean의 동작은 스프링 버전 1.2.x와 2.0에서 달라졌다. ProxyFactoryBean는 이제 TransactionProxyFactoryBean 클래스등처럼 자동탐지되는 인터페이스과 관련된 의미와 유사하다.
프록시될 대상 객체의 클래스(이후에는 대상 클래스라고 부른다.)가 어떤 인터페이스도 구현하지 않았다면 CGLIB 기반의 프록시가 생성될 것이다. 이는 JDK 프록시가 인터페이스 기반이라서 인터페이스가 없다는 것은 JDK 프록시를 적용할 수 없다는 의미이므로 가장 쉬운 시나리오다. 다른 시나리오는 대상 객체에 연결하고(plug) interceptorNames 프로퍼티로 인터셉터 리스트를 지정하는 것이다. ProxyFactoryBean의 proxyTargetClass 프로퍼티가 false로 설정되었을 때도 CGLIB 기반 프록시가 생성될 것이다. (분명히 이는 말이 안되지만 이것이 중복을 가장 줄여주로 혼란이 적기 때문에 빈 정의에서 제거하는 것이 가장 좋다.)
대상 클래스가 하나이상의 인터페이스를 구현했다면 생성되는 프록시의 타입은 ProxyFactoryBean 설정에 달려있다.
ProxyFactoryBean 의 proxyTargetClass 프로퍼티가 true이면 CGLIB기반 프록시가 생성될 것이다. 이는 논리적이고 최소놀람의 원칙(principle of least surprise)을 유지한다. ProxyFactoryBean의 proxyInterfaces 프로퍼티가 하나이상의 정규화된 인터페이스 이름으로 설정되고 proxyTargetClass가 true로 설정되었을 때 조차도 CGLIB 기반의 프록시가 적용될 것이다.
ProxyFactoryBean의 proxyInterfaces 프로퍼티가 하나 이상의 정규화된 인터페이스 이름으로 지정되면 JDK기반 프록시가 생성될 것이다. 생성된 프록시는 proxyInterfaces 프로퍼티에 지정된 모든 인터페이스를 구현할 것이다. 대상 클래스가 proxyInterfaces에 지정된 것보다 훨씬 더 많은 인터페이스를 구현했다면 문제는 없지만 이러한 부가적인 인터페이스들은 반환되는 프록시는 구현하지 않을 것이다.
ProxyFactoryBean 의 proxyInterfaces 프로퍼티를 설정하지 않았지만 대상 클래스가 하나 이상의 인터페이스를 구현했다면 ProxyFactoryBean는 대상 클래스가 실제로 최소 하나의 인터페이스를 구현했는지를 자동탐지해서 JDK기반 프록시를 생성할 것이다. 실제로 프록시되는 인터페이스는 대상 클래스가 구현한 모든 인터페이스이다. 이는 사실상 대상 클래스가 구현하는 각각의 인터페이스 리스트를 proxyInterfaces 프로퍼티에 제공한 것과 같다. 하지만 이는 현지히 적게 동작하고 오타가 날 확률이 적다.
9.5.4 프록시 인터페이스
동작하는 ProxyFactoryBean의 간단한 예제를 보자. 이 예제는 다음을 포함하고 있다.
- 프록시될 대상 빈. 이 빈은 아래 예제에서 "personTarget" 빈 정의이다.
- 어드바이스를 제공하는데 사용한 어드바이저와 인터셉터
- 대상 객체(personTarget 빈)을 지정하는 AOP 프록시 빈 정의와 프록시하는 인터페이스와 적용할 어드바이스.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | Xml <bean id="personTarget" class="com.mycompany.PersonImpl"> <property name="name" value="Tony"/> <property name="age" value="51"/> </bean> <bean id="myAdvisor" class="com.mycompany.MyAdvisor"> <property name="someProperty" value="Custom string property value"/> </bean> <bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor"> </bean> <bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="proxyInterfaces" value="com.mycompany.Person"/> <property name="target" ref="personTarget"/> <property name="interceptorNames"> <list> <value>myAdvisor</value> <value>debugInterceptor</value> </list> </property> </bean> |
interceptorNames 프로퍼티는 문자열의 리스트를 받는다. 이 리스트는 현재 팩토리의 인터셉터와 어드바이저의 빈 이름들이다. 어드바이저, 인터셉터, before advice, after returning advice, throws advice 객체를 사용할 수 있다. 어드바이저의 순서는 중요하다.
Note
아 마도 왜 리스트가 빈 참조를 가지지 않는지 궁금할 것이다. 그 이유는 ProxyFactoryBean의 싱글톤 프로퍼티가 false로 설정되어있드면 개별적인 프록시 인스턴스를 리턴할 수 있어야 하기 때문이다. 어드바이저 중에 그 프로토타입이 있다면 개별적인 인스턴스는 리턴되어야 하므로 팩토리에서 프로토타입의 인스턴스를 반드시 획득할 수 있어야 한다. 레퍼런스를 가지고 있다면 이를 만족시키지 못한다.
아 마도 왜 리스트가 빈 참조를 가지지 않는지 궁금할 것이다. 그 이유는 ProxyFactoryBean의 싱글톤 프로퍼티가 false로 설정되어있드면 개별적인 프록시 인스턴스를 리턴할 수 있어야 하기 때문이다. 어드바이저 중에 그 프로토타입이 있다면 개별적인 인스턴스는 리턴되어야 하므로 팩토리에서 프로토타입의 인스턴스를 반드시 획득할 수 있어야 한다. 레퍼런스를 가지고 있다면 이를 만족시키지 못한다.
위의 "person" 빈 정의를 다음과 같이 Person 구현체 대신에 사용할 수 있다.
Java
Person person = (Person) factory.getBean("person");
같은 IoC 컨텍스트내의 다른 빈들은 보통의 자바 객체처럼 강타입의 의존성을 빈에 나타낼 수 있다.
Xml
<bean id="personUser" class="com.mycompany.PersonUser">
<property name="person"><ref local="person"/></property>
</bean>
이 예제에서 PersonUser 클래스는 Person 타입의 프로퍼티를 노출할 것이다. 이에 관한한 AOP 프록시를 "실제" person 구현체대신에 투명하게 사용할 수 있다. 하지만 AOP 프록시의 클래스는 동적 프록시 클래스가 될 것이다. 이 클래스는 Advised 인터페이스(아래에서 설명한다)로 캐스팅 될 수 있다.
다음과 같은 익명 내부 빈(inner bean)을 사용해서 대상객체와 프록시사이의 구별을 감출 수 있다. ProxyFactoryBean 정의만이 다르다. 완전함을 위해서 어드바이스만이 포함된다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | Xml <bean id="myAdvisor" class="com.mycompany.MyAdvisor"> <property name="someProperty" value="Custom string property value"/> </bean> <bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor"/> <bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="proxyInterfaces" value="com.mycompany.Person"/> <!-- 대상에 대한 로컬 참조말고 내부 빈을 사용해라 --> <property name="target"> <bean class="com.mycompany.PersonImpl"> <property name="name" value="Tony"/> <property name="age" value="51"/> </bean> </property> <property name="interceptorNames"> <list> <value>myAdvisor</value> <value>debugInterceptor</value> </list> </property> </bean> |
여기에는 Person 타입의 객체가 딱 하나만 존재한다는 장점이 있다. 어플리케이션 컨텍스트의 사용자가 어드바이즈되지 않은 객체에 대한 참조를 획득하는 것을 막거나 스프링 IoC의 자동연결(autowiring)의 애매모호함을 피해야할 필요가 있을 경우 유용하다. ProxyFactoryBean 정의가 독립적인데서 오는 확실한 장점도 있다. 하지만 팩토리에서 어드바이즈되지 않은 대상을 획득할 수 있는 것이 실제로 장점이 될 수 있는 경우가 많이 있다. 예를 들면 몇몇 테스트 시나리오가 이러한 경우이다.
9.5.5 프록시 클래스
하나 이상의 인터페이스가 아니라 클래스를 프록시해야 한다면?
위 의 예제를 생각해 보자. 이 예제에는 Person 인터페이스가 없다. 그래서 어떤 비즈니스 인터페이스도 구현하지 않은 Person 클래스를 어드바이즈해야 한다. 이 경우에 스프링이 동적 프록시 대신 CGLIB 프록시를 사용하도록 설정할 수 있다. 위의 ProxyFactoryBean에서 proxyTargetClass 프로퍼티를 true로 설정해라. 클래스보다는 인터페이스를 사용하는 것이 좋지만 인터페이스를 구현하지 않은 클래스를 어드바이스할 수 있는 능력은 레가시코드를 사용해야 하는 경우에 유용할 수 있다. (보통 스프링은 규범적이지 않다. 스프링은 좋은 사례를 적용하기 쉽게 만들면서 특정 접근을 강제하지 않는다.)
인터페이스를 가진 경우에도 필요하다면 모든 경우에 CGLIB을 사용하도록 강제할 수 있다.
CGLIB 프록시는 런타임시에 대상 클래스의 하위클래스를 생성해서 동작한다. 스프링은 원래 대상에 메서드 호출을 위임하려고 생성한 이 하위클래스를 설정한다. 하위클래스는 어드바이스에서 위빙을 하는 데코레이터 패턴을 구현하는데 사용한다.
CGLIB 프록시는 보통 사용자에게 투명해야 한다. 하지만 고려해야할 이슈가 몇가지 있다.
- Final 메서드는 오버라이드할 수 없는 것처럼 어드바이즈할 수도 없다.
- 클래스패스에 CGLIB 2 바이너리가 필요할 것이다. 동적 프록시는 JDK에서 사용할 수 있다.
9.5.6 '전역' 어드바이저 사용하기
인터셉터 이름에 별표를 추가하면 별표앞의 부분과 일치하는 빈 이름을 가진 모든 어드바이저를 어드바이저 체인에 추가할 것이다. 이는 '전역' 어드바이저의 표준 세트에 추가해야하는 경우 쓸모가 있을 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 | Xml <bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target" ref="service"/> <property name="interceptorNames"> <list> <value>global*</value> </list> </property> </bean> <bean id="global_debug" class="org.springframework.aop.interceptor.DebugInterceptor"/> <bean id="global_performance" class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor"/> |
9.6 간결한 프록시 정의
특히 트랜잭션 프록시를 정의하는 경우에 유사한 프록시 정의를 많이 사용할 것이다. 내부 빈 정의와 함께 부모 빈 정의와 자식 빈 정의를 사용하면 프록시 정의를 더 깨끗하고 간결하게 만들 수 있다.
먼저 부모, 템플릿, 빈 정의를 프록시에 대해서 생성한다.
1 2 3 4 5 6 7 8 9 10 11 | Xml <bean id="txProxyTemplate" abstract="true" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="transactionManager" ref="transactionManager"/> <property name="transactionAttributes"> <props> <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property> </bean> |
이는 스스로 인스턴스화 되지 않을 것이므로 사실 불완전하다. 대상은 어떻게든 고유하게 사용되지는 않을 것이므로 그 다음 생성되어야 하는 각 프록시는 내부 빈 정의로 프록시의 대상을 감싸는 자식 빈 정의이다.
1 2 3 4 5 6 7 8 | Xml <bean id="myService" parent="txProxyTemplate"> <property name="target"> <bean class="org.springframework.samples.MyServiceImpl"> </bean> </property> </bean> |
물론 이번 경우처럼 부모 템플릿의 프로퍼티인 트랜잭션 전파(transaction propagation) 설정을 오버라이드할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | Xml <bean id="mySpecialService" parent="txProxyTemplate"> <property name="target"> <bean class="org.springframework.samples.MySpecialServiceImpl"> </bean> </property> <property name="transactionAttributes"> <props> <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop> <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop> <prop key="load*">PROPAGATION_REQUIRED,readOnly</prop> <prop key="store*">PROPAGATION_REQUIRED</prop> </props> </property> </bean> |
위 의 예제에서 앞에서 설명했던 abstract 속성을 사용해서 부모 빈 정의를 명시적으로 abstract로 표시했다. 그러므로 이 빈 정의는 실제로는 인스턴스화 되지 않는다. 어플리케이션 컨텍스트(하지만 단순한 빈 팩토리는 아니다)는 기본적으로 미리 인스턴스화되어서 모두 싱글톤이다. 그러므로 탬플릿으로만 사용하려는 (부모) 빈 정의가 있다면(그리고 이 정의는 클래스를 지정한다.) abstract 속성을 true로 설정해야하고 그렇지 않으면 어플리케이션 컨텍스트가 실제로 이 빈을 미리 인스턴스화 하려고 시도한다는 점은 중요하다.(최소한 싱글톤 빈에 대해서는)
9.7 ProxyFactory로 AOP 프록시를 프로그래밍적으로 생성하기
스프링에서 AOP 프록시를 프로그래밍적으로 생성하는 것은 쉽다. 이는 스프링 IoC에 대한 의존성없이 스프링 AOP를 사용할 수 있게 한다.
다음의 리스트는 하나의 인터셉터와 하나의 어드바이저를 가진 대상 객체에 대한 프록시 생성을 보여준다. 대상객체가 구현한 인터페이스는 자동적으로 프록시될 것이다.
1 2 3 4 5 6 | Java ProxyFactory factory = new ProxyFactory(myBusinessInterfaceImpl); factory.addAdvice(myMethodInterceptor); factory.addAdvisor(myAdvisor); MyBusinessInterface tb = (MyBusinessInterface) factory.getProxy(); |
첫 단계는 org.springframework.aop.framework.ProxyFactory 타입의 객체를 생성하는 것이다. 위의 예제처럼 대상객체와 함께 이를 생성하거나 대체 생성자(alternate constructor)에서 프록시되는 인터페이스를 지정할 수 있다.
어드바이스(어드바이스의 특수한 종류인 인터셉터와 함께)와 어드바이저를 추가할 수 있고 ProxyFactory가 살아있는 동안 이들을 조작할 수 있다. IntroductionInterceptionAroundAdvisor를 추가했다면 프록시가 부가적인 인터페이스를 구현하도록 할 수 있다.
ProxyFactory(AdvisedSupport에서 상속받은)에는 before advice와 throws advice같은 다른 어드바이스 타입을 추가하도록 하는 편리한 메서드도 존재한다. AdvisedSupport는 ProxyFactory와 ProxyFactoryBean의 수퍼클래스이다.
Tip
AOP 프록시 생성과 IoC 프레임워크를 통합하는 것은 대부분의 어플리케이션에서 좋은 사용사례이다. 일반적으로 AOP를 가진 자바코드에서 설정을 분리해내는 것을 권장한다.
AOP 프록시 생성과 IoC 프레임워크를 통합하는 것은 대부분의 어플리케이션에서 좋은 사용사례이다. 일반적으로 AOP를 가진 자바코드에서 설정을 분리해내는 것을 권장한다.
9.8 어드바이즈된 객체 조작하기
AOP 프록시를 생성했더라도 org.springframework.aop.framework.Advised 인터페이스를 사용해서 프록시를 조작할 수 있다. 다른 인터페이스가 이 인터페이스를 구현했더라도 모든 AOP프록시는 이 인터페이스로 캐스트할 수 있다. 이 인터페이스는 다음 메서드를 포함하고 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | Java Advisor[] getAdvisors(); void addAdvice(Advice advice) throws AopConfigException; void addAdvice(int pos, Advice advice) throws AopConfigException; void addAdvisor(Advisor advisor) throws AopConfigException; void addAdvisor(int pos, Advisor advisor) throws AopConfigException; int indexOf(Advisor advisor); boolean removeAdvisor(Advisor advisor) throws AopConfigException; void removeAdvisor(int index) throws AopConfigException; boolean replaceAdvisor(Advisor a, Advisor b) throws AopConfigException; boolean isFrozen(); |
getAdvisors() 메서드는 팩토리에 추가된 모든 어드바이저, 인터셉터, 다른 어드바이스 타입에 대해서 Advisor를 반환할 것이다. Advisor를 추가했다면 이 위치에서 반환된 어드바이저는 추가한 객체가 될 것이다. 인터셉터나 다른 어드바이스 타입을 추가했다면 스프링은 항상 true를 반환하는 포인트컷을 가진 어드바이저에서 이를 감쌀것이다. 그러므로 MethodInterceptor를 추가했다면 이 위치에서 반환된 어드바이저는 MethodInterceptor와 모든 클래스와 메서드와 일치하는 포인트컷을 반환하는 DefaultPointcutAdvisor가 될 것이다.
addAdvisor() 메서드는 어떤 Advisor라도 추가하는데 사용할 수 있다. 보통은 포인트컷과 어드바이스를 가진 어드바이저는 어떤 어드바이스나 포인트컷(하지만 인트로덕션과는 아니다)과도 사용할 수 있는 일반적인 DefaultPointcutAdvisor가 될 것이다.
기본적으로 일단 프록시가 생성된 후에도 어드바이저나 인터셉터를 추가하거나 제거할 수 있다. 유일한 제약사항은 팩토리에 존재하는 프록시가 인터페이스 변경을 보지 못하는 것처럼 인트로덕션 어드바이저를 추가하거나 제거할 수 없다. (이 문제를 피하기 위해서 팩토리에서 새로운 프록시를 획득할 수 있다.)
다음은 AOP 프록시를 Advised 인터페이스로 캐스팅하고 그 어드바이스를 검사하고 조작하는 간단한 예제이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | Java Advised advised = (Advised) myObject; Advisor[] advisors = advised.getAdvisors(); int oldAdvisorCount = advisors.length; System.out.println(oldAdvisorCount + " advisors"); // 포인트컷없이 인터셉터처럼 어드바이스를 추가해라 // 프록시된 모든 메서드를 매칭할 것이다 // 인터셉터, before advice, after returning advice, throws advice에 대해 사용할 수 있다 advised.addAdvice(new DebugInterceptor()); // 포인트컷을 사용해서 선택적으로 어드바이스를 추가한다 advised.addAdvisor(new DefaultPointcutAdvisor(mySpecialPointcut, myAdvice)); assertEquals("Added two advisors", oldAdvisorCount + 2, advised.getAdvisors().length); |
Note
분 명히 정당한 사용이라고 하더라도 프로덕션단계에서 비즈니스 객체의 어드바이스를 수정하는 것이 합당한가에 대해서 의문이 있을 수 있다. 하지만 개발단계에서는 아주 유용할 수 있다. 예를 들면 테스트의 경우이다. 테스트하려는 메서드 호출내부에서 얻은 인터셉터나 다른 어드바이스의 형식으로 테스트 코드를 추가할 수 있다는 것이 아주 유용하다는 것을 종종 발견한다. (예를 들어 어드바이스가 해당 메서드에 대해 생성된 트랜잭션안으로 들어갈 수 있다. 또는 트랜잭션을 롤백하기 이전에 데이터베이스가 제대로 갱신되었는지 검사하는 SQL을 실행할 수 있다.)
분 명히 정당한 사용이라고 하더라도 프로덕션단계에서 비즈니스 객체의 어드바이스를 수정하는 것이 합당한가에 대해서 의문이 있을 수 있다. 하지만 개발단계에서는 아주 유용할 수 있다. 예를 들면 테스트의 경우이다. 테스트하려는 메서드 호출내부에서 얻은 인터셉터나 다른 어드바이스의 형식으로 테스트 코드를 추가할 수 있다는 것이 아주 유용하다는 것을 종종 발견한다. (예를 들어 어드바이스가 해당 메서드에 대해 생성된 트랜잭션안으로 들어갈 수 있다. 또는 트랜잭션을 롤백하기 이전에 데이터베이스가 제대로 갱신되었는지 검사하는 SQL을 실행할 수 있다.)
어떻게 프록시를 생성했는가에 따라 frozen 플래그를 보통 설정할 수 있다. frozen 플래그를 설정한 경우 Advised isFrozen() 메서드는 항상 true를 반환할 것이다. 추가나 제거로 어드바이스를 수정하려는 어떤 시도도 AopConfigException가 발생할 것이다. 어드바이즈된 객체의 상태를 고정시키는 이 기능은 몇몇 경우에 유용하다. 예를 들면 보안 인터셉터를 제거하는 코드의 호출을 차단할 수 있다. 알려진 런타입 어드바이스 수정이 필요하지 하지 않은 경우 적극적인 최적화를 위해서 스프링 1.1에서도 사용할 수 있다.
9.9 "autoproxy" 기능 사용하기
지금까지 ProxyFactoryBean나 유사한 팩토리빈을 사용해서 명시적으로 프록시를 생성하는 방법을 살펴보았다.
스 프링도 선택한 빈 정의를 자동으로 프록시할 수 있는 "autoproxy" 빈정의를 사용할 수 있게 한다. 이 기능은 컨테이너 로딩처럼 모든 빈 정의를 수정할 수 있는 스프링의 "bean post processor" 인프라에 기반을 두고 있다.
이 모델에서 자동 프록시 인프라스트럭처를 설정하려고 XML 빈 정의 파일에 몇가지 특수한 빈 정의를 설정했다. 이는 자동프록시에 알맞는 대상을 선언할 수 있게 한다. ProxyFactoryBean를 사용할 필요가 없다.
이것을 하는 두가지 방법이 있다.
- 현재 컨텍스트에서 특정 빈을 참조하는 autoproxy creator의 사용.
- 별도로 생각할 수 있는 자동프록시 생성의 특수한 경우. 자동프록시 생성은 소스수준의 메타데이터 속성으로 만들어진다.
9.9.1 자동프록시 빈 정의
org.springframework.aop.framework.autoproxy 패키지는 다음의 표준 자동프록시 생성자(creator)를 제공한다.
9.9.1.1 BeanNameAutoProxyCreator
BeanNameAutoProxyCreator 클래스는 리터럴 값이나 와일드카드와 일치하는 이름을 가진 빈에 대한 AOP 프록시를 자동으로 생성하는 BeanPostProcessor이다.
1 2 3 4 5 6 7 8 9 10 | Xml <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="beanNames" value="jdk*,onlyJdk"/> <property name="interceptorNames"> <list> <value>myInterceptor</value> </list> </property> </bean> |
ProxyFactoryBean 가 그렇듯이 프로토타입 어드바이저에 올바른 행위를 하도록 하는 인터셉터의 리스트 대신 interceptorNames 프로퍼티가 있다. 지명된 "interceptors"는 어드바이저나 어떤 어드바이스 타입이라도 될 수 있다.
보통 자동 프록시가 그렇듯이 BeanNameAutoProxyCreator를 사용하는 핵심관점은 최소한의 설정으로 여러 객체에 하나의 설정을 일관성있게 적용하려는 것이다. 이는 여러 객체에 선언적 트랜잭선을 적용할 때 선호하는 선택이다.
위의 예제에서 "jdkMyBean"와 "onlyJdk"처럼 이름이 일치한 빈 정의는 대상클래스의 평범한 빈 정의(plain old bean definition)이다. BeanNameAutoProxyCreator가 자동적으로 AOP 프록시를 생성할 것이다. 같은 어드바이스가 매칭된 모든 빈에 적용될 것이다. 어드바이저를 사용했다면(위의 예제에서 인터셉터 대신에) 포인트컷은 다른 빈에 다르게 적용될 것이다.
9.9.1.2 DefaultAdvisorAutoProxyCreator
DefaultAdvisorAutoProxyCreator 는 더 일반적이고 아주 강력한 자동 프록시 생성자이다. 이 생성자는 자동프록시 어드바이저의 빈 정의에서 특정 빈 이름을 포함하지 않고도 현재 컨텍스트에서 적합한 어드바이저를 마법처럼(automagically) 적용할 것이다. 이는 일관성있는 설정과 BeanNameAutoProxyCreator같은 중복제거의 이점과 같은 이점을 제공한다.
이 메카니즘을 사용하는 것은 다음을 포함한다.
- DefaultAdvisorAutoProxyCreator 빈 정의를 지정함.
- 같 은 컨텍스트 혹은 관계된 컨텍스트에 다수의 Advisor를 지정함. 이들은 인터셉터나 다른 어드바이스가 아닌 반드시 Advisor여야 한다. 후보 빈 정의에 대한 각 어드바이스의 자격을 검사하기 위해 평가할 포인트컷이 있어야 하기 때문에 이 어드바이저는 필수적이다.
이는 자동으로 각 비즈니스 객체에 다수의 어드바이저를 적용할 수 있다는 말이다. 어느 어드바이저의 포인트컷도 비즈니스 객체의 매서드와 일치하는 것이 없다면 객체는 프록시되지 않을 것이다. 새로운 비즈니스 객체에 빈 정의를 추가했을 때 필요하다면 자동으로 프록시 될 것이다.
보통 자동프록시는 호출자(caller)나 의존성이 어드바이즈되지 않은 객체를 획득할 수 없게 하는 장점이 있다. 이 ApplicationContext에서 getBean("businessObject1")을 호출하면 대상 비즈니스 객체가 아니라 AOP 프록시를 반환할 것이다.(앞에서 나온 "내부 빈(inner bean)"도 이 장점을 제공한다.)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | Xml <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/> <bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor"> <property name="transactionInterceptor" ref="transactionInterceptor"/> </bean> <bean id="customAdvisor" class="com.mycompany.MyAdvisor"/> <bean id="businessObject1" class="com.mycompany.BusinessObject1"> <!-- Properties omitted --> </bean> <bean id="businessObject2" class="com.mycompany.BusinessObject2"/> |
많은 비즈니스 객체에 같은 어드바이스를 일관성있게 적용하려는 경우 DefaultAdvisorAutoProxyCreator가 아주 유용하다. 일단 인프라스트럭처 정의가 제 위치에 있다면 구체적인 프록시 설정을 포함하지 않고도 새로운 비즈니스 객체를 쉽게 추가할 수 있다. 부가적인 관점도 설정을 최소한으로 변경하면서 아주 쉽게 추가할 수 있다.(예를 들면 추적하거나 성능을 모니터링하느 관점)
DefaultAdvisorAutoProxyCreator는 필터링과 정렬을 지원한다. (같은 팩토리의 AdvisorAutoProxyCreator의 특정 어드바이저들만 평가하고 여러 용도로 사용하고 다르게 설정하기 위해 작명 관례를 사용해서) 이슈가 있는 경우에 정렬을 제대로 하기 위해 어드바이저가 org.springframework.core.Ordered를 구현할 수 있다. 위의 예제에서 사용한 TransactionAttributeSourceAdvisor에는 설정할 수 있는 order값이 있다. 기본 설정은 정렬하지 않는 것이다.
9.9.1.3 AbstractAdvisorAutoProxyCreator
DefaultAdvisorAutoProxyCreator 의 수퍼클래스다. 어드바이저 정의가 프레임워크의 동작을 충분하게 커스터마이징하지 못할 때 이 DefaultAdvisorAutoProxyCreator 클래스의 하위클래스를 만들어서 자신만의 오토프록시 생성자(creator)를 만들 수 있다.
9.9.2 메타데이터 주도 오토프록시 사용하기
오 토프록시의 특히 중요한 타입은 메타데이터에 주도된다. 이는 .NET ServicedComponents과 유사한 프로그래밍 모델을 만든다. EJB에서 처럼 XML 배포 디스크립터를 사용하는 대신에 소스수준의 속성으로 트랜잭션 관리와 다른 엔터프라이즈 서비스에 대한 설정을 유지한다.
이 경우에 메타데이터 속성을 이해하는 어드바이저와 결합해서 DefaultAdvisorAutoProxyCreator를 사용한다. 메타데이터를 오토프록시 생성 클래스 자체가 가지는 대신 후보 어드바이저들의 포인트컷 부분이 메타데이터를 가진다.
이는 실제로 DefaultAdvisorAutoProxyCreator의 특수한 경우이지만 독립적으로 고려할 만 하다.(메타데이터 친화적인 코드는 AOP 프레임워크 자체가 아니라 어드바이저들의 포인트컷이 담고 있다.)
JPetStore 샘플 어플리케이션의 /attributes 디렉토리는 속성주도 오토프록시의 사용방법을 보여준다. 이 경우에는 TransactionProxyFactoryBean를 사용할 필요가 없다. 메타데이터 친화적인 포인트컷을 사용하기 때문에 비즈니스 객체에서 transactional 속성을 정의하는 것만으로도 충분하다. /WEB-INF/declarativeServices.xml의 빈 정의는 다음의 코드를 포함하고 있다. 이는 일반적이고 JPetStore 외부에서도 사용할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | Xml <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/> <bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor"> <property name="transactionInterceptor" ref="transactionInterceptor"/> </bean> <bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor"> <property name="transactionManager" ref="transactionManager"/> <property name="transactionAttributeSource"> <bean class="org.springframework.transaction.interceptor.AttributesTransactionAttributeSource"> <property name="attributes" ref="attributes"/> </bean> </property> </bean> <bean id="attributes" class="org.springframework.metadata.commons.CommonsAttributes"/> |
DefaultAdvisorAutoProxyCreator 빈 정의(이름은 중요하지 않으므로 생략할 수도 있다)는 현재 어플리케이션 컨텍스트에서 적절한 포인트컷을 모두 선택할 것이다. 이 경우에 TransactionAttributeSourceAdvisor 타입의 "transactionAdvisor" 빈 정의를 transaction 속성을 가진 클래스나 메서드에 적용할 것이다. TransactionAttributeSourceAdvisor는 생성자 의존성을 통해서 TransactionInterceptor에 의존한다. 예제는 자동연결(autowiring)로 이 문제를 해결한다. AttributesTransactionAttributeSource는 org.springframework.metadata.Attributes 인터페이스의 구현체에 의존한다. 이 부분에서 "attributes" 빈은 속성 정보를 얻기 위해 Jakarta Commons Attributes API를 사용해서 이를 만족시킨다. (어플리케이션 코드는 반드시 Commons Attributes 컴파일 작업을 사용해서 컴파일되어야 한다.)
JPetStore 샘플 어플리케이션의 /annotation 디렉토리에는 JDK 1.5+ 어노테이션을 사용한 오토프록시와 유사한 예제가 있다. 다음 설정은 스프링의 Transactional 어노테이션을 자동으로 탐지할 수 있게 해서 해당 어노테이션이 붙은 빈을 암묵적으로 프록시하게 된다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | Xml <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/> <bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor"> <property name="transactionInterceptor" ref="transactionInterceptor"/> </bean> <bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor"> <property name="transactionManager" ref="transactionManager"/> <property name="transactionAttributeSource"> <bean class="org.springframework.transaction.annotation.AnnotationTransactionAttributeSource"/> </property> </bean> |
여기서 정의한 TransactionInterceptor는 PlatformTransactionManager 정의에 의존하고 있다. PlatformTransactionManager는 어플리케이션의 트랜잭션 요구사항(일반적으로는 이 예제처럼 JTA이고 그 외 Hibernate, JDO, JDBC이다.)에 지정될 것이므로 이 일반적인 파일에는 포함되어 있지 않다.(가능하기는 하지만)
1 2 3 4 | Xml <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"/> |
Tip
선 언적인 트랜잭션 관리만 필요한 경우 이러한 일반적인 XML 정의를 사용하면 스프링이 transaction 속성이 있는 모든 클래스와 메서드를 자동으로 프록시할 것이다. AOP로 직접 작업할 필요가 없고 프로그래밍 모델은 .NET ServicedComponents의 프로그래밍 모델과 유사하다.
선 언적인 트랜잭션 관리만 필요한 경우 이러한 일반적인 XML 정의를 사용하면 스프링이 transaction 속성이 있는 모든 클래스와 메서드를 자동으로 프록시할 것이다. AOP로 직접 작업할 필요가 없고 프로그래밍 모델은 .NET ServicedComponents의 프로그래밍 모델과 유사하다.
이 메카니즘은 확장할 수 있다. 커스텀 속성에 기반해서 오토프록시를 할 수 있다. 이렇게 하려면 다음의 과정이 필요한다.
- 커스텀 속성을 정의해라.
- 클래스나 메서드에 커스텀 속성이 있어서 발생하는 포임트컷을 포함해서 필수 어드바이스를 가진 Advisor를 지정해라. 커스텀 속성을 선택하는 정적 포인트컷을 구현한 이미 존재하는 어드바이스를 사용할 수도 있다.
1 2 3 4 5 6 7 8 9 10 11 12 | Xml <bean id="lockMixin" class="org.springframework.aop.LockMixin" scope="prototype"/> <bean id="lockableAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor" scope="prototype"> <property name="pointcut" ref="myAttributeAwarePointcut"/> <property name="advice" ref="lockMixin"/> </bean> <bean id="anyBean" class="anyclass" ... |
속성을 이해하는 포인트컷이 anyBean이나 다른 빈 정의의 어떤 메서드와 일치한다면 믹스인이 적용될 것이다. lockMixin와 lockableAdvisor는 둘다 프로토타입이다. myAttributeAwarePointcut 포인트컷은 어드바이즈된 개별 객체가 상태를 유지하지 않기 때문에 싱글톤 정의가 될 수 있다.
9.10 TargetSources 사용하기
스 프링은 org.springframework.aop.TargetSource에 나타난 TargetSource의 개념을 제공한다. 이 인터페이스는 조인포인트를 구현한 "대상 객체"를 반환하는 역할을 한다. TargetSource 구현체는 AOP 프록시가 메서드 호출을 다룰 때마다 대상 인스턴스를 요청한다.
스프링 AOP를 사용하는 개발자들이 TargetSource를 직접 다룰 필요는 보통 없지만 풀링, 핫 스왓, 다른 세련된 대상등을 지원하는 강력한 기능을 제공한다. 예를 들어 풀링 TargetSource는 인스턴스를 관리하는 풀(pool)을 사용해서 호출시마다 다른 대상 인스턴스를 반환할 수 있다.
TargetSource를 지정하지 않으면 지역(local) 객체를 감싸는 기본 구현체를 사용한다. 각 호출시마다(기대하는 대로) 같은 객체를 반환한다.
스프링이 제공하는 표준 대상 소스(target source)를 살펴보고 어떻게 사용할 수 있는지 알아보자.
Tip
커스텀 대상 소스를 사용하는 경우 대상은 보통 싱글톤 빈 정의가 아니라 프로토타입 빈 정의가 되어야 할 것이다. 이는 요청했을 때 스프링이 새로운 대상 인스턴스를 생성할 수 있게 한다.
커스텀 대상 소스를 사용하는 경우 대상은 보통 싱글톤 빈 정의가 아니라 프로토타입 빈 정의가 되어야 할 것이다. 이는 요청했을 때 스프링이 새로운 대상 인스턴스를 생성할 수 있게 한다.
9.10.1 핫 스왑이 가능한 대상 소스
호출자(caller)가 AOP 프록시의 대상에 대한 참조를 유지하는 동안 AOP 프록시의 대상을 교체할 수 있도록 org.springframework.aop.target.HotSwappableTargetSource가 존재한다.
대상 소스의 대상을 변경한 효과는 즉각적으로 일어난다. HotSwappableTargetSource는 쓰레드세이프하다.
다음과 같이 HotSwappableTargetSource의 swap() 메서드로 대상을 변경할 수 있다.
1 2 3 4 | Java HotSwappableTargetSource swapper = (HotSwappableTargetSource) beanFactory.getBean("swapper"); Object oldTarget = swapper.swap(newTarget); |
필요한 XML 정의는 다음과 같다.
1 2 3 4 5 6 7 8 9 10 11 | Xml <bean id="initialTarget" class="mycompany.OldTarget"/> <bean id="swapper" class="org.springframework.aop.target.HotSwappableTargetSource"> <constructor-arg ref="initialTarget"/> </bean> <bean id="swappable" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="targetSource" ref="swapper"/> </bean> |
위의 swap() 호출은 스왑가능한 빈의 대상을 변경한다. 이 빈에 대한 참조를 가진 클라이언트들은 변경을 알아차리지 못하지만 즉시 새로운 대상을 만날 것이다.
이 예제에서는 어떤 어드바이스도 추가하지 않았지만(TargetSource를 사용하는데 어드바이스를 추가해야 할 필요는 없다.) 당연히 모든 TargetSource는 임의의 어드바이스와 결합해서 사용할 수 있다.
9.10.2 대상 소스 풀링
대상 소스의 풀링을 사용하면 상태가 없는 세션 EJB와 유사한 프로그래밍 모델을 제공한다. 이 프로그래밍 모델은 동일한 인스턴스의 풀을 유지하고 메서드 호출로 풀에서 객체를 가져온다.
스프링 풀링과 SLSB 풀링사이의 결정적인 차이점은 스프링 풀링은 어떤 POJO라도 적용할 수 있다는 것이다. 보통 스프링이 하듯이 이는 비 침략적인 방법을 적용할 수 있다.
스 프링은 상당히 능률적인 풀링 구현체를 제공하는 Jakarta Commons Pool 1.3을 지원한다. 이 기능을 사용하려면 어플리케이션의 클래스패스에 commons-pool Jar가 필요하다. 다른 풀링 API를 지원하기 위해서 org.springframework.aop.target.AbstractPoolingTargetSource의 하위클래스를 만드는 것도 가능하다.
다음은 설정 예시이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | Xml <bean id="businessObjectTarget" class="com.mycompany.MyBusinessObject" scope="prototype"> ... properties omitted </bean> <bean id="poolTargetSource" class="org.springframework.aop.target.CommonsPoolTargetSource"> <property name="targetBeanName" value="businessObjectTarget"/> <property name="maxSize" value="25"/> </bean> <bean id="businessObject" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="targetSource" ref="poolTargetSource"/> <property name="interceptorNames" value="myInterceptor"/> </bean> |
대상 객체(이 예제에서는 "businessObjectTarget")는 반드시 프로토타입이어야 한다. 이는 PoolingTargetSource 구현체가 필요에 따라 풀의 크기를 조절하기 위해서 대상의 새로운 인스턴스를 생성할 수 있도록 한다. AbstractPoolingTargetSource의 프로퍼티에 대한 정보는 AbstractPoolingTargetSource javadoc과 구현된 하위클래스를 참고해라. 프로피티 중 "maxSize"가 가장 기본적인 프로퍼티이고 항상 존재한다는 것을 보장한다.
이 경우에 "myInterceptor"는 같은 IoC 컨텍스트에서 정의되어야 하는 인터셉터의 이름이다. 하지만 풀링을 사용하려고 인터셉터를 지정할 필요는 없다. 다른 어드바이스는 필요없고 풀링만을 사용하고자 한다면 interceptorNames 프로퍼티를 아예 설정하지 말아라.
풀에 있는 어떤 객체라도 org.springframework.aop.target.PoolingConfig 인터페이스로 캐스팅할 수 있도록 스프링을 설정할 수 있다. 이 인터페이스는 인트로덕션을 통해서 설정과 현재 풀의 크기에 대한 정보를 노출한다. 다음과 같이 어드바이저를 정의해야 한다.
1 2 3 4 5 6 | Xml <bean id="poolConfigAdvisor" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> <property name="targetObject" ref="poolTargetSource"/> <property name="targetMethod" value="getPoolingConfigMixin"/> </bean> |
AbstractPoolingTargetSource 클래스의 편리한 메서드를 호출해서 즉, MethodInvokingFactoryBean를 사용해서 이 어드바이저를 얻어올 수 있다. 이 어드바이저의 이름은(여기서는 "poolConfigAdvisor") 풀에 있는 객체를 노출하는 ProxyFactoryBean의 인터셉터 이름 목록에 있어야 한다.
캐스팅은 다음과 같을 것이다.
1 2 3 4 | Java PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject"); System.out.println("Max pool size is " + conf.getMaxSize()); |
Note
보 통 상태가 없는 서비스 객체들을 풀링할 필요는 없다. 상태를 갖지 않는 대부분의 객체들은 자연히 쓰레드세이프하기 때문에 풀링하는 것이 기본적인 선택이어야 한다고 생각하지 않고 리소스가 캐싱되는 경우 인스턴스 풀링은 문제의 소지가 있다.
보 통 상태가 없는 서비스 객체들을 풀링할 필요는 없다. 상태를 갖지 않는 대부분의 객체들은 자연히 쓰레드세이프하기 때문에 풀링하는 것이 기본적인 선택이어야 한다고 생각하지 않고 리소스가 캐싱되는 경우 인스턴스 풀링은 문제의 소지가 있다.
자동프록시를 사용해서 더 간단한 풀링을 사용할 수 있다. 자동프록시 생성자(autoproxy creator)가 사용하는 TargetSources를 설정할 수 있다.
9.10.3 프로토타입 대상 소스
"prototype" 대상소스를 설정하는 것은 TargetSource를 풀링하는 것과 비슷하다. 이 경우에 메서드 호출시마다 대상의 새로운 인스턴스를 생성할 것이다. 최신(modern) JVM에서는 새로운 객체를 생성하는 비용이 크지 않지만 새로운 객체를 연결하는 비용은(객체의 IoC 의존성을 만족시키는) 아마 좀 더 비쌀 것이다. 그러므로 타당한 이유가 없다면 이 접근을 사용하지 말아야 한다.
프로토타입 대상 소스를 설정하기 위해서 위에 나온 poolTargetSource 정의를 다음과 같이 수정할 수 있다.(명확함을 위해서 이름도 변경했다.)
1 2 3 4 5 | Xml <bean id="prototypeTargetSource" class="org.springframework.aop.target.PrototypeTargetSource"> <property name="targetBeanName" ref="businessObjectTarget"/> </bean> |
여기에는 대상 빈의 이름을 위한 딱 하나의 프로퍼티만 존재한다. 일관성 있는 작명을 보장하기 위해서 TargetSource 구현체는 상속을 사용한다. 대상 소스를 풀링하는 것처럼 대상 빈은 프로토타입 빈 정의여야 한다.
9.10.4 ThreadLocal 대상 소스
요 청이 들어올때마다 객체를 생성해야할 때(쓰레드마다) ThreadLocal 대상 소스가 유용하다. ThreadLocal의 개념은 쓰레드에 투명하게 리소스를 저장하는 JDK 범위의 기능을 제공한다. ThreadLocalTargetSource을 설정하는 방법은 다른 타입의 대상소스에서 설명한 것과 거의 같다.
1 2 3 4 5 | Xml <bean id="threadlocalTargetSource" class="org.springframework.aop.target.ThreadLocalTargetSource"> <property name="targetBeanName" value="businessObjectTarget"/> </bean> |
Note
멀티 쓰레드 환경이나 멀티-클래스로더 환경에서 ThreadLocal을 잘못 사용할 때 심각한 이슈가 있다.(잠재적으로 메모리 누수가 있을 수 있다.) 항상 다른 클래스에 쓰레드로컬을 감싸고 ThreadLocal 자체를 직접 사용하는 않는 것을 고려해야 한다. (물론 랩퍼(wrapper) 클래스는 제외하고) 그리고 쓰레드에 리소스로컬을 제대로 설정하고 설정해제해야 한다.(설정해제는 ThreadLocal.set(null)를 호출하는 것을 말한다) 설정해제를 사지 않으면 문제의 소지가 있는 동작이 일어날 수 있으므로 설정해제는 항상 해주어야 한다. 스프링의 ThreadLocal 지원은 개발자를 위해서 이것을 해주고 달리 코드를 적절하게 다루지 않도로 ThreadLocal을 사용하는 것은 항상 생각해야한다.
멀티 쓰레드 환경이나 멀티-클래스로더 환경에서 ThreadLocal을 잘못 사용할 때 심각한 이슈가 있다.(잠재적으로 메모리 누수가 있을 수 있다.) 항상 다른 클래스에 쓰레드로컬을 감싸고 ThreadLocal 자체를 직접 사용하는 않는 것을 고려해야 한다. (물론 랩퍼(wrapper) 클래스는 제외하고) 그리고 쓰레드에 리소스로컬을 제대로 설정하고 설정해제해야 한다.(설정해제는 ThreadLocal.set(null)를 호출하는 것을 말한다) 설정해제를 사지 않으면 문제의 소지가 있는 동작이 일어날 수 있으므로 설정해제는 항상 해주어야 한다. 스프링의 ThreadLocal 지원은 개발자를 위해서 이것을 해주고 달리 코드를 적절하게 다루지 않도로 ThreadLocal을 사용하는 것은 항상 생각해야한다.
9.11 새로운 Advice 타입 정의하기
스 프링 AOP는 확장할 수 있도록 설계되었다. 내부적으로 인터셉션 구현체 전략(interception implementation strategy)을 사용하고 있기 때문에 interception around advice, before advice, throws advice, after returning advice에 추가로 임의의 어드바이스 타입을 지원하는 것이 가능하다.
org.springframework.aop.framework.adapter 패키지는 코어 프레임워크를 수정하지 않고 새로운 커스텀 어드바이스 타입을 추가할 수 있도록 하는 SPI 패키지이다. 커스텀 Advice 타입의 유일한 제약사항은 org.aopalliance.aop.Advice 태그 인터페이스를 반드시 구현해야 한다는 것 뿐이다.
더 자세한 정보는 org.springframework.aop.framework.adapter 패키지의 javadoc을 참고해라.
9.12 추가적인 리소스
스프링 AOP의 더 많은 예제를 보려면 스프링 샘플 어플리케이션을 참고해라.
- JPetStore의 기본 설정은 선언적인 트랜잭션 관리를 위한 TransactionProxyFactoryBean의 사용방법을 설명한다.
- JPetStore의 /attributes 디렉토리는 속성주도 선언적인 트랜잭션 관리의 사용방법을 설명한다.
댓글 없음:
댓글 쓰기