https://sundaland.tistory.com/76
[ ▶ The Advisor API in Spring ]
스프링에서 어드바이저는 포인트컷 표현식과 연관된 단일 어드바이스 객체만을 포함하는 액스펙트 (관심사)이다.
도입 (introductions)의 특별한 경우를 제외하고, 모든 어드바이저는 모든 어드바이스와 함께 사용할 수 있다. org.springframework.aop.support.DefaultPointcutAdvisor는 가장 일반적으로 사용되는 어드바이저 클래스이다. 이 클래스는 MethodInterceptor, BeforeAdvice 또는 ThrowsAdvice와 함께 사용할 수 있다.
스프링에서는 동일한 AOP 프록시에서 어드바이저와 어드바이스 타입을 혼합할 수 있다. 예를 들어, 하나의 프록시 구성에서 인터셉션 around 어드바이스, throws 어드바이스, 그리고 before 어드바이스를 사용할 수 있다. 스프링은 자동으로 필요한 인터셉터 체인을 생성한다.
[ ▷ Using the ProxyFactoryBean to Create AOP Proxies ]
스프링 IoC 컨테이너 (ApplicationContext 또는 BeanFactory)를 비즈니스 객체에 사용하는 경우 (스프링에서는 이를 권장한다), 스프링의 AOP FactroyBean 구현 중 하나를 사용하는 것이 좋다. FactroyBean은 간접적인 레이어를 도입하여 다른 유형의 객체를 생성할 수 있게 해준다.
스프링 AOP는 지원도 내부적으로 FactroyBean을 사용한다. 스프링에서 AOP 프록시를 생성하는 기본적인 방법은 org.springframework.aop.framework.ProxyFactoryBean을 사용하는 것이다. 이를 통해 포인트컷, 적용할 어드바이스, 어드바이스의 순서에 대해 완전한 제어를 할 수 있다. 그러나 이러한 제어가 필요하지 않다면 더 간단한 옵션을 사용하는 것이 좋다.
[ ▷ Basics ]
ProxyFactoryBean은 다른 스프링 FactoryBean 구현과 마찬가지로 간접적인 레벨을 도입한다. foo라는 이름의 ProxyFactoryBean을 정의하면, foo를 참조하는 객체는 ProxyFactoryBean 인스턴스 자체를 보는 것이 아니라, ProxyFactoryBean의 getObject() 메서드 구현에서 생성된 객체를 보게된다. 이 메서드는 타겟 객체를 감싸는 AOP 프록시를 생성한다.
ProxyFactoryBean 또는 다른 IoC-aware 클래스를 사용하여 AOP 프록시를 생성하는 가장 중요한 이점 중 하나는 어드바이스와 포인트컷도 IoC에 의해 관리될 수 있다는 점이다. 이는 다른 AOP 프레임워크로는 달성하기 어려운 접근 방식을 가능하게 하는 강력한 기능이다. 예를 들어, 어드바이스 자체가 애플리케이션 객체 (타겟 객체 이외의)를 참조할 수 있으며, 이는 의존성 주입이 제공하는 모든 플러그 가능성을 활용할 수 있게 한다.
[ ▷ JavaBean Properties ]
스프링에서 제공하는 대부분은 FactoryBean 구현과 마찬가지로 ProxyFactoryBean 클래스 자체도 자바 빈이다. 이 클래스 속성을 다음을 위해 사용된다.
- 프록시할 타겟을 지정한다.
- CGLIB를 사용할지 여부를 지정한다. (JDK 및 CGLIB 기반 프록시 참고)
일부 중요한 속성은 org.springframework.aop.framework.ProxyConfig (스프링의 모든 AOP 프록시 팩토리의 슈퍼클래스)에서 상속된다.
- proxyTargetClass : ture로 설정되면, 타겟 클래스의 인터페이스가 아닌 타겟 클래스 자체가 프록시된다. 이 속성 값이 true로 설정되면 CGLIB 프록시가 생성된다.
- optimize : CGLIB을 통해 생성된 프록시에 공격적인 최적화를 적용할지 여부를 제어한다. 이 설정을 무심코 사용하지 말아야 하며, 관련 AOP 프록시가 최적화를 처리하는 방법을 충분히 이해한 경우에만 사용해야 한다. 이 설정은 현재 CGLIB 프록시에만 사용되며, JDK 동적 프록시에는 영향을 미치지 않는다.
- frozen : 프록시 구성이 frozen으로 설정된 경우, 구성 변경이 더 이상 허용되지 않는다. 이는 약간의 최적화로 유용하며, 프록시 생성 후 호출자가 프록시를 조작 (Advised 인터페이스를 통해)할 수 없도록 하려는 경우에도 유용하다. 이 속성의 기본값은 false이므로, 추가 어드바이스 추가와 같은 변경이 허용된다.
- exposeProxy : 현재 프록시를 ThreadLocal에 노출해야 타겟에 액세서흘 수 있도록 할지 여부를 결정한다, 타겟이 프록시를 얻어야하고 exposeProxy 속성이 true로 설정된 경우, 대상은 AopContext.currentProxy() 메서드를 사용할 수 있다.
ProxyFactoryBean에 고유한 다른 속성을 아래와 같다.
- proxyInterfaces : 스트링 타입의 인터페이스 이름들의 배열이다. 이 속성이 제공되지 않으면 타겟 클래스에 대한 CGLIB 프록시가 사용된다.
- interceptorNames : 적용할 어드바이저, 인터셉터 또는 기탄 어드바이스 이름의 String 배열이다. 순서는 중요하며, 먼저 나열된 인터셉터가 호출을 가로챌 수 있는 첫 번째 인터셉터이다.
이름은 현재 팩토리의 빈 이름이며, 상위 팩토리의 빈 이름도 포함된다. 여기서 빈 참조를 언급해서는 안된다. 그렇게 하면
ProxyFactoryBean이 어드바이스 싱글톤 설정을 무시하게 되기 때문이다.
인터셉터 이름에 별표 (*)을 추가할 수 있다. 이렇게 하면 별표 앞 부분과 일치하는 이름을 가진 모든 어드바이저 빈이 어드바이저 체인에 추가된다.
- singleton: getObject() : 메서드가 호출될 때마다 동일한 객체를 반환할지 여부를 설정한다. 여러 FactoryBean 구현에서 이 메서드를 제공한다. 기본값은 true이다. 상태 저장 어드바이스를 사용하려는 경우 (상태 저장 믹스인에 대해) singleton 값을 false로 설정하고 프로토타입 어드바이스를 함께 사용한다.
[ ▷ JDK- and CGLIB-based proxies ]
이 섹션은 특정 타겟 객체에 대해 ProxyFactoryBean이 JDK 기반 프록시 또는 CGLIB 기반 프록시를 생성하는 방법에 대한 결정적 문서이다.
스프링 버전 2.0 부터는 ProxyFactoryBean의 JDK 또는 CGLIB 기반 프록시 생성에 관한 동작이 변경되었다. 이제 ProxyFactoryBean은 인터페이스 자동감지와 관련하여 TransactionProxyFactoryBean 클래스와 유사한 동작을 한다.
프록시할 타겟 객체의 클래스가 인터페이스를 구현하지 않는 경우, CGLIB 기반 프록시가 생성된다. 이는 JDK 프록시가 인터페이스 기반으므로 인터페이스가 없으면 JDK 프록시를 생성할 수 없기 때문에 가장 쉬운 시나리오이다.
타겟 빈을 플러그인하고 interceptorNames 속성을 설정하여 인터셉터 목록을 지정할 수 있다. ProxyFactoryBean의 proxyTargetClass 속성을 false로 설정해도 CGLIB 기반 프록시가 생성된다. (혼동 초래 방지를 위하여 이런 설정은 빈 정의에서 제거하는 것이 좋다). 타겟 클래스가 하나 이상의 인터페이스를 구현하는 경우, 생성된 프록시 유형은 ProxyFactoryBean의 구성에 따라 달라진다.
- ProxyFactoryBean의 proxyTargetClass 속성을 true로 설정한 경우, CGLIB 기반 프록시가 생성된다. 이는 놀라지 않게 하는 원칙에 부합한다. ProxyFactoryBean의 proxyInterfaces 속성이 하나 이상의 인터페이스 이름으로 설정된 경우에도 proxyTargetClass 속성이 true로 설정되면 CGLIB 기반 프록시가 생성된다.
- ProxyFactoryBean의 proxyInterfaces 속성이 하나 이상의 인터페이스 이름으로 설정된 경우, JDK 프록시가 생성된다. 생성된 프록시는 proxyInterfaces 속성에 지정된 모든 인터페이스를 구현한다. 타겟 클래스가 이 속성에 지정된 것보다 훨씬 더 많은 인터페이스를 구현하는 경우에도 반환된 프록시는 추가 인터페이스를 구현하지 않는다.
- ProxyFactoryBean의 proxyInterfaces 속성이 설정되지는 않았지만 타겟 클래스가 실제로 하나 이상의 인터페이스를 구현했다는 사실을 자동 감지하고 JDK 기반 프록시를 생성한다. 실제로 프록시된 인터페이스는 타겟 클래스가 구현하는 모든 인터페이스이다. 이는 타겟 클래스가 구현하는 모든 인터페이스 목록을 proxyInterfaces 속성에 제공하는 것과 동일한 효과가 있다. 그러나 이는 훨씬 덜 번거롭고 오타 오류의 가능성이 적다.
[ ▷ Proxying Interfaces ]
아래의 예제는 ProxyFactoryBean의 간단한 사용 예로 아래의 내용이 포함되어 있다.
- 프록시된 대상 빈, 아래의 예제에선 personTarget 빈 정의이다.
- 어드바이스를 제공하는 어드바이저와 인터셉터
- 대상 객체 (personTarget 빈), 프록시할 인터페이스 및 적용할 어드바이스를 지정하는 AOP 프록시 빈 정의
▼ 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>
▼ 자바 기반 구성
@Configuration
public class AppConfig {
@Bean
public PersonImpl personTarget() {
PersonImpl person = new PersonImpl();
person.setName("Tony");
person.setAge(51);
return person;
}
@Bean
public MyAdvisor myAdvisor() {
MyAdvisor advisor = new MyAdvisor();
advisor.setSomeProperty("Custom string property value");
return advisor;
}
@Bean
public DebugInterceptor debugInterceptor() {
return new DebugInterceptor();
}
@Bean
public ProxyFactoryBean person() {
ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
proxyFactoryBean.setTarget(personTarget());
proxyFactoryBean.setInterceptorNames("myAdvisor", "debugInterceptor");
proxyFactoryBean.setProxyInterfaces(new Class<?>[]{Person.class});
return proxyFactoryBean;
}
}
interceptorNames 속성은 현재 팩토리에서 인터셉터 또는 어드바이저의 빈 이름을 포함하는 String 리스트를 받는다. 어드바이저, 인터셉터, before, after returning, throws 어드바이스 객체를 사용할 수 있다. 어드바이저의 순서는 중요하다.
리스트가 빈 참조를 가지지 않는 이유는 ProxyFactoryBean의 singleton 속성이 false로 설정된 경우 독립된 프록시 인스턴스를 반환할 수 있어야 하기 때문이다. 어드바이저 중 하나가 프로토타입인 경우 독립된 인스턴스를 반환해야 하므로 팩토리에서 프로토타입 인스턴스를 얻을 수 있어야 한다.
▼ person 빈 정의를 Person 구현 대신 사용하는 방법
Person person = (Person) factory.getBean("person");
동일한 IoC 컨텍스트에 있는 다른 빈들은 일반 자바 객체처럼 강력하게 형식화된 의존성을 표현할 수 있다.
<bean id="personUser" class="com.mycompany.PersonUser">
<property name="person"><ref bean="person"/></property>
</bean>
이 예제에서 PersonUser 클래스는 Person 타입의 속성을 노출한다. 이 클래스는 "실제" Person 구현 대신 AOP 프록시를 투명하게 사용할 수 있다. 그러나 이 클래스는 동적 프록시 클래스가 된다. 그러나 이 클래스는 동적 프록시 클래스가 된다.
Advised 인터페이스로 캐스팅할 수도 있다.
익명 내부 빈을 사용하여 대상과 프록시 간의 구분을 숨길 수 있다. 오직 ProxyFactoryBean 정의만 다르다. 어드바이스는 완정성을 위해 포함된다.
▼ 익명 내부 빈을 사하는 방법 (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"/>
<!-- Use inner bean, not local reference to target -->
<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>
▼ 익명 내부 빈을 사하는 방법 (자바 기반)
@Configuration
public class AppConfig {
@Bean
public MyAdvisor myAdvisor() {
MyAdvisor advisor = new MyAdvisor();
advisor.setSomeProperty("Custom string property value");
return advisor;
}
@Bean
public DebugInterceptor debugInterceptor() {
return new DebugInterceptor();
}
@Bean
public ProxyFactoryBean person() {
ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
// 익명 내부 빈 대신 자바 객체 직접 생성
PersonImpl personTarget = new PersonImpl();
personTarget.setName("Tony");
personTarget.setAge(51);
proxyFactoryBean.setTarget(personTarget);
proxyFactoryBean.setInterceptorNames("myAdvisor", "debugInterceptor");
proxyFactoryBean.setProxyInterfaces(new Class<?>[]{Person.class});
return proxyFactoryBean;
}
}
익명 내부 빈을 사용하는 장점은 Person 타입의 객체가 하나만 있다는 점이다. 이는 애플리케이션 컨텍스트 사용자가 비어드바이스된 객체에 대한 참조는 얻지 못하게 하거나 스프링 IoC 자동 연결에서 모호성을 방지하려는 경우 유용하다. 또한 ProxyFactoryBean 정의가 자체 포함되어 있다는 점에서도 이점이 있을 수 있다. 그러나 팩토리에서 비어드바이스된 대상을 얻을 수 있다는 것이 실제로 장점일 수 있는 경우도 있다.
[ ▷ Proxying Classes ]
만약 여러 인터페이스가 아닌 클래스를 프록시해야 하는 경우에는 스프링에서 동적 프록시 대신 CGLIB 프록시를 사용하도록 구성할 수 있다. 이를 위해 ProxyFactoryBean의 proxyTargetClass 속성을 true로 설정하면 된다. 인터페이스보다 클래스에 프로그램하는 것이 더 좋지만, 인터페이스를 구현하지 않는 클래스를 어드바이스할 수 있는 능력은 레거시 코드 작업 시 유용할 수 있다. (일반적으로 스프링은 규범적이지 않다, 스프링은 좋은 관행을 쉽게 적용할 수 있게 하지만 특정 접근 방식을 강요하지 않는다.)
인터페이스가 있는 경우에도 CGLIB 사용을 강제할 수 있다.
CGLIB 프록시는 타겟 클래스의 서브캘릇를 런타임에 생성함으로써 작동한다. 스프링은 이 생성된 서브클래스를 구성하여, 메서드 호출을 원래 타겟에 위임한다. 서브 클래스는 Decorator 패턴을 구현하여 어드바이스를 가미한다.
CGLIB 프록시는 타겟 클래싀 서브 클래스를 런타임에 생성함으로써 작동한다. 스프링은 이 생성된 서브클래스를 구성하여 메서드 호출을 원래 타겟에 위임한다. 서브클래스는 Decorator 패턴을 구현하여 어드바이스를 가미한다. CGLIB 프록시는 일반적으로 사용자에게 투명해야 한다. 그러나 고려해야할 몇 가지 문제가 있다.
- final 클래스는 상속할 수 없으므로 프록시할 수 없다.
- final과 private 메서드는 오버라이드할 수 없으므로 어드바이스할 수 없다.
- package=-private 메서드와 같이 부모 클래스에서 다른 패키지 메서드는 효과적으로 private이므로 어드바이스할 수 없다.
CGLIB를 클래스 경로에 추가할 필요는 없다. CGLIB은 리패키징되어 spring-core JAR에 포함된다. 즉, CGLIB 기반 AOP는 JDK 동적 프록시처럼 "바로 사용할 수 있다" CGLIB 프록시와 동적 프록시 간의 성능 차이는 거의 없다. 이 경우 성능은 결정적인 고려사항이 되지 않아야 한다.
[ ▷ Using “Global” Advisors ]
인터셉터 이름에 별표를 추가하면 별표 앞 부분과 일치하는 이름을 가진 모든 어드바이저가 어드바이저 체인에 추가된다. 이는 표준 "Global" 어드바이저 세트를 추가해야 할 때 유용할 수 있다.
▼ 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"/>
▼ 자바 기반
@Configuration
public class AppConfig {
@Bean
public Service service() {
return new Service();
}
@Bean
public DebugInterceptor globalDebug() {
return new DebugInterceptor();
}
@Bean
public PerformanceMonitorInterceptor globalPerformance() {
return new PerformanceMonitorInterceptor();
}
@Bean
public ProxyFactoryBean proxy() {
ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
proxyFactoryBean.setTarget(service());
proxyFactoryBean.setInterceptorNames("globalDebug", "globalPerformance");
return proxyFactoryBean;
}
}