Loading...
Spring Framework Reference Documentation 7.0.2의 Using the ProxyFactoryBean to Create AOP Proxies의 한국어 번역본입니다.
아래의 경우에 피드백에서 신고해주신다면 반영하겠습니다.
감사합니다 :)
ProxyFactoryBean to Create AOP ProxiesSpring IoC 컨테이너(ApplicationContext 또는 BeanFactory)를 비즈니스 객체에 사용한다면(그리고 그래야만 합니다!), Spring의 AOP FactoryBean 구현체 중 하나를 사용해야 합니다. (factory bean은 간접 계층을 도입하여, 다른 타입의 객체를 생성할 수 있게 해준다는 점을 기억하십시오.)
Spring AOP support 또한 내부적으로 factory bean을 사용합니다.
Spring에서 AOP 프록시를 생성하는 기본적인 방법은
org.springframework.aop.framework.ProxyFactoryBean을 사용하는 것입니다. 이는 포인트컷, 적용되는 어드바이스, 그리고 그 순서에 대해 완전한 제어를 제공합니다. 그러나 그러한 제어가 필요하지 않다면 더 단순한 선택지가 있으며, 이들이 더 바람직합니다.
다른 Spring FactoryBean 구현체와 마찬가지로, ProxyFactoryBean은 간접 계층을 도입합니다. foo라는 이름의 ProxyFactoryBean을 정의하면, foo를 참조하는 객체는 ProxyFactoryBean 인스턴스 자체를 보는 것이 아니라 ProxyFactoryBean의 getObject() 메서드 구현에 의해 생성된 객체를 보게 됩니다. 이 메서드는 타깃 객체를 감싸는 AOP 프록시를 생성합니다.
ProxyFactoryBean 또는 다른 IoC-aware 클래스를 사용하여 AOP 프록시를 생성하는 가장 중요한 장점 중 하나는 어드바이스와 포인트컷 또한 IoC에 의해 관리될 수 있다는 점입니다. 이는 다른 AOP 프레임워크로는 달성하기 어려운 특정 접근 방식을 가능하게 하는 강력한 기능입니다. 예를 들어, 어드바이스 자체가 애플리케이션 객체를 참조할 수 있으며(타깃은 어떤 AOP 프레임워크에서도 사용 가능해야 합니다), Dependency Injection이 제공하는 모든 플러그어빌리티의 이점을 누릴 수 있습니다.
Spring에서 제공되는 대부분의 FactoryBean 구현체와 마찬가지로,
ProxyFactoryBean 클래스 자체는 JavaBean입니다. 그 프로퍼티는 다음과 같은 용도로 사용됩니다:
몇 가지 핵심 프로퍼티는 org.springframework.aop.framework.ProxyConfig(Spring에서 모든 AOP 프록시 팩토리의 슈퍼클래스)로부터 상속됩니다. 이러한 핵심 프로퍼티에는 다음이 포함됩니다:
proxyTargetClass: 타깃 클래스의 인터페이스가 아니라 타깃 클래스 자체를 프록시해야 한다면 true입니다. 이 프로퍼티 값이 true로 설정되면 CGLIB 프록시가 생성됩니다(그러나 JDK- and CGLIB-based proxies도 참고하십시오).optimize: CGLIB을 통해 생성된 프록시에 대해 aggressive optimization을 적용할지 여부를 제어합니다. 관련 AOP 프록시가 optimization을 어떻게 처리하는지 완전히 이해하지 못했다면 이 설정을 무턱대고 사용해서는 안 됩니다. 이는 현재 CGLIB 프록시에만 사용됩니다. JDK dynamic 프록시에는 아무런 영향이 없습니다.frozen: 프록시 설정이 frozen 상태이면 설정에 대한 변경이 더 이상 허용되지 않습니다. 이는 약간의 optimization으로도 유용하며, 프록시가 생성된 후 호출자가 Advised 인터페이스를 통해 프록시를 조작할 수 없게 하려는 경우에도 유용합니다. 이 프로퍼티의 기본값은 false이므로, 추가 어드바이스를 추가하는 등의 변경이 허용됩니다.exposeProxy: 현재 프록시를 타깃이 접근할 수 있도록 ThreadLocal에 노출할지 여부를 결정합니다. 타깃이 프록시를 얻을 필요가 있고 exposeProxy 프로퍼티가 true로 설정된 경우, 타깃은 AopContext.currentProxy() 메서드를 사용할 수 있습니다.ProxyFactoryBean에 특화된 다른 프로퍼티는 다음과 같습니다:
proxyInterfaces: String 인터페이스 이름의 배열입니다. 이것이 제공되지 않으면 타깃 클래스에 대해 CGLIB 프록시가 사용됩니다(그러나 JDK- and CGLIB-based proxies도 참고하십시오).interceptorNames: 적용할 Advisor, 인터셉터, 또는 기타 어드바이스 이름의 String 배열입니다. 순서는 first come-first served 기준으로 중요합니다. 즉, 리스트에서 첫 번째 인터셉터가 호출을 가장 먼저 가로챌 수 있습니다.이 이름들은 현재 팩토리 내의 bean 이름이며, ancestor 팩토리의 bean 이름도 포함됩니다. 여기서는 bean 레퍼런스를 언급할 수 없습니다. 그렇게 하면 ProxyFactoryBean이 어드바이스의 싱글톤 설정을 무시하게 되기 때문입니다.
인터셉터 이름에 asterisk(*)를 추가할 수 있습니다. 이렇게 하면 asterisk 앞 부분으로 시작하는 이름을 가진 모든 advisor bean이 적용됩니다. 이 기능을 사용하는 예제는 Using “Global” Advisors에서 찾을 수 있습니다.
getObject() 메서드가 얼마나 자주 호출되든 팩토리가 하나의 객체만을 반환해야 하는지 여부입니다. 여러 FactoryBean 구현체가 이러한 메서드를 제공합니다. 기본값은 true입니다. 예를 들어, stateful mixin과 같은 stateful 어드바이스를 사용하려면, prototype 어드바이스와 함께 singleton 값을 false로 사용하십시오.이 섹션은 ProxyFactoryBean이 특정 타깃 객체(이하 프록시 대상 객체)에 대해 JDK-based 프록시 또는 CGLIB-based 프록시 중 어느 것을 생성할지 선택하는 방법에 대한 결정적인 문서입니다.
JDK- 또는 CGLIB-based 프록시를 생성하는 것과 관련된
ProxyFactoryBean의 동작은 Spring 1.2.x와 2.0 사이에서 변경되었습니다. 이제ProxyFactoryBean은 인터페이스를 자동 감지하는 semantics 측면에서TransactionProxyFactoryBean클래스와 유사한 동작을 보입니다.
프록시 대상 객체의 클래스(이하 타깃 클래스)가 어떤 인터페이스도 구현하지 않는 경우, CGLIB-based 프록시가 생성됩니다. 이는 가장 쉬운 시나리오입니다. JDK 프록시는 인터페이스 기반이며, 인터페이스가 없으면 JDK proxying은 불가능하기 때문입니다. 타깃 bean을 연결하고 interceptorNames 프로퍼티를 설정하여 인터셉터 리스트를 지정할 수 있습니다. ProxyFactoryBean의 proxyTargetClass 프로퍼티가 false로 설정되었더라도 CGLIB-based 프록시가 생성된다는 점에 유의하십시오. (이렇게 하는 것은 의미가 없으며, bean definition에서 제거하는 것이 가장 좋습니다. 기껏해야 중복이고, 최악의 경우 혼란을 야기하기 때문입니다.)
타깃 클래스가 하나(또는 그 이상)의 인터페이스를 구현하는 경우, 생성되는 프록시의 타입은 ProxyFactoryBean의 설정에 따라 달라집니다.
ProxyFactoryBean의 proxyTargetClass 프로퍼티가 true로 설정된 경우, CGLIB-based 프록시가 생성됩니다. 이는 의미가 있으며, 최소 놀람의 원칙에 부합합니다. ProxyFactoryBean의 proxyInterfaces 프로퍼티가 하나 이상의 fully qualified 인터페이스 이름으로 설정된 경우에도, proxyTargetClass 프로퍼티가 true로 설정되어 있다는 사실 때문에 CGLIB-based proxying이 적용됩니다.
ProxyFactoryBean의 proxyInterfaces 프로퍼티가 하나 이상의 fully qualified 인터페이스 이름으로 설정된 경우, JDK-based 프록시가 생성됩니다. 생성된 프록시는 proxyInterfaces 프로퍼티에 지정된 모든 인터페이스를 구현합니다. 타깃 클래스가 proxyInterfaces 프로퍼티에 지정된 것보다 훨씬 더 많은 인터페이스를 구현하더라도 상관없지만, 이러한 추가 인터페이스는 반환된 프록시가 구현하지 않습니다.
ProxyFactoryBean의 proxyInterfaces 프로퍼티가 설정되지 않았지만 타깃 클래스가 하나(또는 그 이상)의 인터페이스를 구현하는 경우, ProxyFactoryBean은 타깃 클래스가 실제로 적어도 하나의 인터페이스를 구현한다는 사실을 자동으로 감지하고 JDK-based 프록시를 생성합니다. 실제로 프록시되는 인터페이스는 타깃 클래스가 구현하는 모든 인터페이스입니다. 사실상 이는 타깃 클래스가 구현하는 모든 인터페이스의 리스트를 proxyInterfaces 프로퍼티에 제공하는 것과 동일합니다. 그러나 훨씬 덜 번거롭고, 오타가 발생할 가능성도 훨씬 적습니다.
ProxyFactoryBean이 동작하는 간단한 예제를 살펴보겠습니다. 이 예제는 다음을 포함합니다:
personTarget bean definition입니다.Advisor 및 Interceptor.personTarget bean), 프록시할 인터페이스, 그리고 적용할 어드바이스를 지정하는 AOP 프록시 bean definition.다음 listing은 예제를 보여줍니다:
1<bean id="personTarget" class="com.mycompany.PersonImpl"> 2 <property name="name" value="Tony"/> 3 <property name="age" value="51"/> 4</bean> 5 6<bean id="myAdvisor" class="com.mycompany.MyAdvisor"> 7 <property name="someProperty" value="Custom string property value"/> 8</bean> 9 10<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor"> 11</bean> 12 13<bean id="person" 14 class="org.springframework.aop.framework.ProxyFactoryBean"> 15 <property name="proxyInterfaces" value="com.mycompany.Person"/> 16 17 <property name="target" ref="personTarget"/> 18 <property name="interceptorNames"> 19 <list> 20 <value>myAdvisor</value> 21 <value>debugInterceptor</value> 22 </list> 23 </property> 24</bean>
interceptorNames 프로퍼티는 현재 팩토리 내의 인터셉터 또는 advisor의 bean 이름을 담는 String 리스트를 받는다는 점에 유의하십시오. advisor, 인터셉터, before, after returning, 그리고 throws 어드바이스 객체를 사용할 수 있습니다. advisor의 순서는 중요합니다.
리스트가 bean 레퍼런스를 담지 않는 이유가 궁금할 수 있습니다. 그 이유는
ProxyFactoryBean의 singleton 프로퍼티가false로 설정된 경우, 독립적인 프록시 인스턴스를 반환할 수 있어야 하기 때문입니다. advisor 중 하나라도 prototype이라면, 독립적인 인스턴스를 반환해야 하므로 팩토리에서 prototype 인스턴스를 얻을 수 있어야 합니다. 레퍼런스를 보유하는 것만으로는 충분하지 않습니다.
앞서 보인 person bean definition은 다음과 같이 Person 구현체 대신 사용할 수 있습니다:
1Person person = (Person) factory.getBean("person");
1val person = factory.getBean("person") as Person
같은 IoC 컨텍스트 내의 다른 bean은 일반적인 Java 객체에서와 마찬가지로, 이에 대해 강하게 타입이 지정된 의존성을 표현할 수 있습니다. 다음 예제는 그 방법을 보여줍니다:
1<bean id="personUser" class="com.mycompany.PersonUser"> 2 <property name="person"><ref bean="person"/></property> 3</bean>
이 예제에서 PersonUser 클래스는 Person 타입의 프로퍼티를 노출합니다. 이 클래스 관점에서 AOP 프록시는 “real” person 구현체 대신 투명하게 사용할 수 있습니다. 그러나 그 클래스는 dynamic 프록시 클래스일 것입니다. 나중에 논의할 Advised 인터페이스로 캐스팅하는 것도 가능할 것입니다.
anonymous inner bean을 사용하여 타깃과 프록시 사이의 구분을 숨길 수 있습니다. ProxyFactoryBean definition만 다릅니다. 어드바이스는 완전성을 위해 포함되어 있습니다. 다음 예제는 anonymous inner bean을 사용하는 방법을 보여줍니다:
1<bean id="myAdvisor" class="com.mycompany.MyAdvisor"> 2 <property name="someProperty" value="Custom string property value"/> 3</bean> 4 5<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor"/> 6 7<bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean"> 8 <property name="proxyInterfaces" value="com.mycompany.Person"/> 9 <!-- Use inner bean, not local reference to target --> 10 <property name="target"> 11 <bean class="com.mycompany.PersonImpl"> 12 <property name="name" value="Tony"/> 13 <property name="age" value="51"/> 14 </bean> 15 </property> 16 <property name="interceptorNames"> 17 <list> 18 <value>myAdvisor</value> 19 <value>debugInterceptor</value> 20 </list> 21 </property> 22</bean>
anonymous inner bean을 사용하면 Person 타입의 객체가 하나만 존재한다는 장점이 있습니다. 이는 애플리케이션 컨텍스트의 사용자가 un-advised 객체에 대한 레퍼런스를 얻지 못하게 하거나, Spring IoC autowiring과 관련된 어떤 모호성도 피해야 할 때 유용합니다. 또한 ProxyFactoryBean definition이 self-contained라는 점에서, 논쟁의 여지는 있지만 장점이 있습니다. 그러나 팩토리에서 un-advised 타깃을 얻을 수 있는 것이 실제로 장점이 되는 경우도 있습니다(예를 들어, 특정 test 시나리오에서).
인터페이스 하나 이상이 아니라 클래스를 프록시해야 한다면 어떻게 해야 할까요?
앞선 예제에서 Person 인터페이스가 없다고 상상해 봅시다. 비즈니스 인터페이스를 전혀 구현하지 않는 Person이라는 클래스에 어드바이스를 적용해야 했습니다. 이 경우 Spring이 dynamic 프록시가 아니라 CGLIB proxying을 사용하도록 설정할 수 있습니다. 그렇게 하려면 앞서 보인 ProxyFactoryBean에서 proxyTargetClass 프로퍼티를 true로 설정하십시오. 클래스보다는 인터페이스에 대해 프로그래밍하는 것이 가장 좋지만, 인터페이스를 구현하지 않는 클래스에 어드바이스를 적용하는 기능은 레거시 코드를 다룰 때 유용할 수 있습니다. (일반적으로 Spring은 규범적이지 않습니다. 좋은 practice를 쉽게 적용할 수 있게 해주지만, 특정 접근 방식을 강제하지는 않습니다.)
원한다면 인터페이스가 있더라도, 어떤 경우든 CGLIB 사용을 강제할 수 있습니다.
CGLIB proxying은 런타임에 타깃 클래스의 서브클래스를 생성하는 방식으로 동작합니다. Spring은 이 생성된 서브클래스가 원래 타깃으로 메서드 호출을 위임하도록 설정합니다. 이 서브클래스는 Decorator 패턴을 구현하는 데 사용되며, 어드바이스를 위빙합니다.
CGLIB proxying은 일반적으로 사용자에게 투명해야 합니다. 그러나 고려해야 할 몇 가지 이슈가 있습니다:
final 클래스는 확장될 수 없으므로 프록시할 수 없습니다.final 메서드는 override될 수 없으므로 어드바이스를 적용할 수 없습니다.private 메서드는 override될 수 없으므로 어드바이스를 적용할 수 없습니다.classpath에 CGLIB을 추가할 필요가 없습니다. CGLIB은 재패키징되어
spring-coreJAR에 포함되어 있습니다. 즉, CGLIB-based AOP는 JDK dynamic 프록시와 마찬가지로 "out of the box"로 동작합니다.
CGLIB 프록시와 dynamic 프록시 사이에는 성능 차이가 거의 없습니다. 이 경우 성능은 결정적인 고려 사항이 되어서는 안 됩니다.
인터셉터 이름에 asterisk를 추가하면, asterisk 앞 부분과 일치하는 bean 이름을 가진 모든 advisor가 advisor 체인에 추가됩니다. 이는 표준 “global” advisor set을 추가해야 할 때 유용할 수 있습니다. 다음 예제는 두 개의 global advisor를 정의합니다:
1<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean"> 2 <property name="target" ref="service"/> 3 <property name="interceptorNames"> 4 <list> 5 <value>global*</value> 6 </list> 7 </property> 8</bean> 9 10<bean id="global_debug" class="org.springframework.aop.interceptor.DebugInterceptor"/> 11<bean id="global_performance" class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor"/>
The Advisor API in Spring
Concise Proxy Definitions