Spring AOP API
이전 장에서는 @AspectJ와 스키마 기반 측면 정의를 사용한 Spring의 AOP 지원에 대해 설명했습니다. 이 장에서는 하위 수준의 Spring AOP API에 대해 설명합니다. 일반적인 애플리케이션의 경우 이전 장에서 설명한 대로 AspectJ 포인트컷과 함께 Spring AOP를 사용하는 것이 좋습니다.
섹션 요약
Pointcut API in Spring
이 섹션에서는 Spring이 중요한 포인트컷 개념을 처리하는 방법을 설명합니다.
Concepts
Spring의 포인트컷 모델을 사용하면 조언 유형에 관계없이 포인트컷을 재사용할 수 있습니다. 동일한 포인트컷으로 서로 다른 조언을 타겟팅할 수 있습니다.
Org.springframework.aop.Pointcut 인터페이스는 특정 클래스 및 메서드에 대한 조언을 타겟팅하는 데 사용되는 중앙 인터페이스입니다. 전체 인터페이스는 다음과 같습니다:
public interface Pointcut {
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
}
Pointcut 인터페이스를 두 부분으로 분할하면 클래스 및 메서드 일치 부분을 재사용하고 세분화된 구성 작업(예: 다른 메서드 일치자와의 "결합" 수행)을 수행할 수 있습니다.
ClassFilter 인터페이스는 포인트컷을 지정된 대상 클래스 집합으로 제한하는 데 사용됩니다. 일치() 메서드가 항상 참을 반환하면 모든 대상 클래스가 일치합니다. 다음 목록은 ClassFilter 인터페이스 정의를 보여줍니다:
public interface ClassFilter {
boolean matches(Class clazz);
}
메서드매처 인터페이스가 일반적으로 더 중요합니다. 전체 인터페이스는 다음과 같습니다:
public interface MethodMatcher {
boolean matches(Method m, Class<?> targetClass);
boolean isRuntime();
boolean matches(Method m, Class<?> targetClass, Object... args);
}
Matches(Method, Class) 메서드는 이 포인트컷이 대상 클래스의 지정된 메서드와 일치하는지 여부를 테스트하는 데 사용됩니다. 이 평가는 모든 메서드 호출에 대해 테스트할 필요가 없도록 AOP 프록시를 생성할 때 수행할 수 있습니다. 주어진 메서드에 대해 두 개의 인자로 구성된 일치 메서드가 참을 반환하고 MethodMatcher의 isRuntime()메서드가 참을 반환하면 모든 메서드 호출에 대해 세 개의 인자로 구성된 일치 메서드가 호출됩니다. 이렇게 하면 대상 조언이 시작되기 직전에 메서드 호출에 전달된 인수를 포인트컷으로 살펴볼 수 있습니다.
대부분의 메서드매처 구현은 정적이므로 isRuntime() 메서드가 거짓을 반환합니다. 이 경우 3인자 일치 메서드는 절대로 호출되지 않습니다.
가능하면 포인트컷을 정적으로 만들어 AOP 프록시가 생성될 때 AOP 프레임워크가 포인트컷 평가 결과를 캐시할 수 있도록 하세요 |
Operations on Pointcuts
Spring은 포인트컷에 대한 연산(특히 유니온과 교집합)을 지원합니다.
유니온은 두 포인트컷이 일치하는 메서드를 의미합니다. 교차는 두 포인트컷이 일치하는 메서드를 의미합니다. 일반적으로 유니온이 더 유용합니다.org.springframework.aop.support.Pointcuts 클래스의 정적 메서드를 사용하거나 같은 패키지의ComposablePointcut 클래스를 사용하여 포인트컷을 구성할 수 있습니다. 그러나 일반적으로 AspectJ 포인트컷 표현식을 사용하는 것이 더 간단한 접근 방식입니다.
AspectJ Expression Pointcuts
2.0부터 Spring에서 사용하는 가장 중요한 포인트컷 유형은org.springframework.aop.aspectj.AspectJExpressionPointcut입니다. 이것은 AspectJ에서 제공하는 라이브러리를 사용하여 AspectJ 포인트컷 표현식 문자열을 구문 분석하는 포인트컷입니다.
지원되는 AspectJ 포인트컷 프리미티브에 대한 설명은 이전 장을 참조하십시오.
Convenience Pointcut Implementations
Spring은 몇 가지 편리한 포인트컷 구현을 제공합니다. 그 중 일부는 직접 사용할 수 있고, 다른 일부는 애플리케이션별 포인트컷에서 서브클래싱하도록 되어 있습니다.
정적 포인트컷
정적 포인트컷은 메서드와 대상 클래스를 기반으로 하며 메서드의 인수를 고려할 수 없습니다. 정적 포인트컷은 대부분의 용도에 충분하며 가장 좋습니다. Spring은 메서드가 처음 호출될 때 한 번만 정적 포인트컷을 평가할 수 있습니다. 그 이후에는 메서드가 호출될 때마다 포인트컷을 다시 평가할 필요가 없습니다.
이 섹션의 나머지 부분에서는 Spring에 포함된 몇 가지 정적 포인트컷 구현에 대해 설명합니다.
정규 표현식 포인트컷
정적 포인트컷을 지정하는 한 가지 확실한 방법은 정규 표현식입니다. Spring 외에도 여러 AOP 프레임워크에서 이를 가능하게 합니다.org.springframework.aop.support.JdkRegexpMethodPointcut은 JDK의 정규식 지원을 사용하는 일반 정규식 포인트컷입니다.
JdkRegexpMethodPointcut 클래스를 사용하면 패턴 문자열 목록을 제공할 수 있습니다. 이 중 하나라도 일치하는 것이 있으면 포인트컷이 true로 평가됩니다. 결과적으로 결과 포인트컷은 지정된 패턴의 결합이 됩니다
다음 예제는 JdkRegexpMethodPointcut을 사용하는 방법을 보여줍니다:
<bean id="settersAndAbsquatulatePointcut"
class="org.springframework.aop.support.JdkRegexpMethodPointcut">
<property name="patterns">
<list>
<value>.*set.*</value>
<value>.*absquatulate</value>
</list>
</property>
</bean>
Spring은 Advice를 참조할 수 있는 RegexpMethodPointcutAdvisor라는 편의 클래스를 제공합니다( Advice는 조언 이전에 인터셉터, 조언 던지기 등이 될 수 있음을 기억하세요). 내부적으로 Spring은 JdkRegexpMethodPointcut을 사용합니다. 다음 예제에서 볼 수 있듯이 하나의 빈이 포인트컷과 조언을 모두 캡슐화하므로 RegexpMethodPointcutAdvisor를 사용하면 배선이 간소화됩니다:
<bean id="settersAndAbsquatulateAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref bean="beanNameOfAopAllianceInterceptor"/>
</property>
<property name="patterns">
<list>
<value>.*set.*</value>
<value>.*absquatulate</value>
</list>
</property>
</bean>
모든 Advice 유형에 RegexpMethodPointcutAdvisor를 사용할 수 있습니다.
속성 기반 포인트컷
정적 포인트컷의 중요한 유형은 메타데이터 기반 포인트컷입니다. 이것은 메타데이터 속성(일반적으로 소스 수준 메타데이터)의 값을 사용합니다.
동적 포인트컷
동적 포인트컷은 정적 포인트컷보다 평가하는 데 비용이 더 많이 듭니다. 정적 정보뿐만 아니라 메서드 인자도 고려합니다. 즉, 메서드를 호출할 때마다 평가해야 하며 인수가 달라질 수 있으므로 결과를 캐시할 수 없습니다.
대표적인 예가 제어 흐름 포인트컷입니다.
제어 흐름 포인트컷
Spring 제어 흐름 포인트컷은 개념적으로 AspectJ의 흐름 포인트컷과 유사하지만 덜 강력합니다. (현재 포인트컷이 다른 포인트컷과 일치하는 조인 지점 아래에서 실행되도록 지정할 수 있는 방법은 없습니다.) 제어 흐름 포인트컷은 현재 호출 스택과 일치합니다. 예를 들어, 조인 지점이 com.mycompany.web 패키지의 메서드 또는 SomeCaller 클래스에 의해 호출된 경우 실행될 수 있습니다. 제어 흐름 포인트컷은 org.springframework.aop.support.ControlFlowPointcut 클래스를 사용하여 지정합니다.
제어 흐름 포인트컷은 다른 동적 포인트컷보다 런타임에 평가하는 데 훨씬 더 많은 비용이 듭니다. Java 1.4에서는 다른 동적 포인트컷의 약 5배에 달하는 비용이 듭니다 |
Pointcut Superclasses
Spring은 자체 포인트컷을 구현하는 데 도움이 되는 유용한 포인트컷 슈퍼클래스를 제공합니다.
정적 포인트컷이 가장 유용하므로StaticMethodMatcherPointcut을 서브클래싱해야 합니다. 이를 위해서는 하나의 추상 메서드만 구현해야 합니다(다른 메서드를 재정의하여 동작을 사용자 정의할 수 있지만). 다음 예제는 StaticMethodMatcherPointcut을 서브클래싱하는 방법을 보여줍니다:
class TestStaticPointcut extends StaticMethodMatcherPointcut {
public boolean matches(Method m, Class targetClass) {
// return true if custom criteria match
}
}
동적 포인트컷을 위한 슈퍼클래스도 있습니다. 모든 어드바이스 유형에 사용자 지정 포인트컷을 사용할 수 있습니다.
Custom Pointcuts
Spring AOP의 포인트컷은 언어 기능이 아닌 Java 클래스(AspectJ에서처럼)이기 때문에 정적이든 동적이든 사용자 정의 포인트컷을 선언할 수 있습니다. Spring의 사용자 정의 포인트컷은 임의로 복잡할 수 있습니다. 그러나 가능하면 AspectJ 포인트컷 표현식 언어를 사용하는 것이 좋습니다.
이후 버전의 Spring에서는 JAC에서 제공하는 "시맨틱 포인트컷"(예: "대상 객체의 인스턴스 변수를 변경하는 모든 메서드")을 지원할 수 있습니다 |
Advice API in Spring
이제 Spring AOP가 조언을 처리하는 방법을 살펴볼 수 있습니다.
Advice Lifecycles
각 조언은 스프링 빈입니다. 조언 인스턴스는 모든 조언 객체에서 공유되거나 각 조언 객체에 고유할 수 있습니다. 이는 클래스별 또는 인스턴스별 조언에 해당합니다.
클래스별 조언이 가장 자주 사용됩니다. 트랜잭션 어드바이저와 같은 일반적인 조언에 적합합니다. 이들은 프록시된 객체의 상태에 의존하거나 새로운 상태를 추가하지 않습니다. 단지 메서드와 인자에 대해서만 작동합니다.
인스턴스별 조언은 믹스인을 지원하기 위한 도입에 적합합니다. 이 경우 조언은 프록시된 객체에 상태를 추가합니다.
동일한 AOP 프록시에서 공유 및 인스턴스별 조언을 혼합하여 사용할 수 있습니다.
Advice Types in Spring
Spring은 여러 가지 조언 유형을 제공하며 임의의 조언 유형을 지원하도록 확장할 수 있습니다. 이 섹션에서는 기본 개념과 표준 조언 유형에 대해 설명합니다.
어드바이스 주변 인터셉션
Spring의 가장 기본적인 조언 유형은 조언을 둘러싼 인터셉션입니다.
Spring은 메서드 인터셉트를 사용하는 어라운드 어드바이스에 대한 AOP 얼라이언스 인터페이스를 준수합니다. 메서드 인터셉터를 구현하고 어라운드 어드바이스를 구현하는 클래스는 다음 인터페이스도 구현해야 합니다:
public interface MethodInterceptor extends Interceptor {
Object invoke(MethodInvocation invocation) throws Throwable;
}
Invoke() 메서드의 MethodInvocation 인수는 호출되는 메서드, 대상 조인 지점, AOP 프록시 및 메서드에 대한 인수를 노출합니다.Invoke() 메서드는 호출의 결과인 조인 지점의 반환 값을 반환해야 합니다.
다음 예제는 간단한 MethodInterceptor 구현을 보여줍니다:
public class DebugInterceptor implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("Before: invocation=[" + invocation + "]");
Object rval = invocation.proceed();
System.out.println("Invocation returned");
return rval;
}
}
MethodInvocation의 proceed() 메서드 호출에 주목하세요. 이것은 인터셉터 체인을 따라 조인 지점을 향해 진행합니다. 대부분의 인터셉터는 이 메서드를 호출하고 그 반환값을 반환합니다. 그러나 주변의 조언과 마찬가지로 MethodInterceptor는 진행 메서드를 호출하는 대신 다른 값을 반환하거나 예외를 던질 수 있습니다. 그러나 정당한 이유 없이 이렇게 하는 것은 바람직하지 않습니다.
메서드 인터셉터 구현은 다른 AOP 얼라이언스 호환 AOP 구현과 상호 운용성을 제공합니다. 이 섹션의 나머지 부분에서 설명하는 다른 조언 유형은 일반적인 AOP 개념을 구현하지만 Spring에 특화된 방식으로 구현합니다. 가장 구체적인 조언 유형을 사용하면 이점이 있지만, 다른 AOP 프레임워크에서 해당 측면을 실행하려는 경우 조언을 중심으로 메서드 인터셉터를 사용하는 것이 좋습니다. 포인트컷은 현재 프레임워크 간에 상호 운용되지 않으며, AOP 얼라이언스에서는 현재 포인트컷 인터페이스를 정의하고 있지 않다는 점에 유의하세요 |
비포 어드바이스
더 간단한 조언 유형은 비포 어드바이스입니다. 이것은 메서드를 입력하기 전에만 호출되므로 MethodInvocation객체가 필요하지 않습니다.
비포 어드바이스의 가장 큰 장점은 진행()메서드를 호출할 필요가 없으므로 실수로 인터셉터 체인에서 진행하지 못할 가능성이 없다는 것입니다.
다음 목록은 MethodBeforeAdvice 인터페이스를 보여줍니다:
public interface MethodBeforeAdvice extends BeforeAdvice {
void before(Method m, Object[] args, Object target) throws Throwable;
}
(Spring의 API 설계는 필드 인터셉션에 일반적인 객체가 적용되고 Spring이 이를 구현할 가능성은 낮지만 필드 비포 어드바이스를 허용합니다.)
반환 유형은 무효라는 점에 유의하세요. Before 조언은 조인 포인트가 실행되기 전에 사용자 지정 동작을 삽입할 수 있지만 반환 값을 변경할 수는 없습니다. Before 조언이 예외를 던지면 인터셉터 체인의 추가 실행을 중지합니다. 예외는 인터셉터 체인을 다시 전파합니다. 체크되지 않았거나 호출된 메서드의 시그니처에 있는 경우 클라이언트에 직접 전달됩니다. 그렇지 않으면 AOP 프록시에 의해 체크되지 않은 예외로 래핑됩니다.
다음 예제는 모든 메서드 호출을 계산하는 Spring의 before 조언을 보여줍니다:
public class CountingBeforeAdvice implements MethodBeforeAdvice {
private int count;
public void before(Method m, Object[] args, Object target) throws Throwable {
++count;
}
public int getCount() {
return count;
}
}
비포 어드바이스는 모든 포인트컷과 함께 사용할 수 있습니다 |
Throws 조언
Throws 조언은 조인 지점이 예외를 던진 경우 조인 지점이 반환된 후에 호출됩니다. Spring은 타이핑된 Throw 조언을 제공합니다. 이것은org.springframework.aop.ThrowsAdvice 인터페이스에 메서드가 포함되어 있지 않다는 것을 의미합니다. 이 인터페이스는 주어진 객체가 하나 이상의 타입이 지정된 Throw 어드바이스 메서드를 구현하는 것을 식별하는 태그 인터페이스입니다. 이러한 메서드는 다음과 같은 형식이어야 합니다:
afterThrowing([Method, args, target], subclassOfThrowable)
마지막 인자만 필요합니다. 메서드 시그니처는 조언 메서드가 메서드와 인자에 관심이 있는지 여부에 따라 하나 또는 네 개의 인자를 가질 수 있습니다. 다음 두 목록은 throw 조언의 예시인 클래스를 보여줍니다.
다음 조언은 RemoteException이 던져지면 호출됩니다(서브클래스 포함):
public class RemoteThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(RemoteException ex) throws Throwable {
// Do something with remote exception
}
}
앞의 조언과 달리 다음 예제에서는 4개의 인수를 선언하므로 호출된 메서드, 메서드 인자 및 대상 객체에 액세스할 수 있습니다. ServletException이 발생하면 다음 조언이 호출됩니다:
public class ServletThrowsAdviceWithArguments implements ThrowsAdvice {
public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
// Do something with all arguments
}
}
마지막 예제는 RemoteException과 ServletException을 모두 처리하는 단일 클래스에서 이 두 메서드를 어떻게 사용할 수 있는지 보여줍니다. 단일 클래스에서 여러 개의 throw 조언 메서드를 결합할 수 있습니다. 다음 목록은 마지막 예시를 보여줍니다:
public static class CombinedThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(RemoteException ex) throws Throwable {
// Do something with remote exception
}
public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
// Do something with all arguments
}
}
던지기 조언 메서드가 예외 자체를 던지면 원래 예외를 재정의합니다(즉, 사용자에게 던져진 예외를 변경합니다). 재정의 예외는 일반적으로 모든 메서드 시그니처와 호환되는 RuntimeException입니다. 그러나 throw-advice 메서드가 검사된 예외를 던지는 경우 대상 메서드의 선언된 예외와 일치해야 하므로 특정 대상 메서드 서명과 어느 정도 결합되어 있습니다. 대상 메서드의 서명과 호환되지 않는 선언되지 않은 검사 예외를 던지지 마세요! |
Throws 조언은 모든 포인트컷과 함께 사용할 수 있습니다 |
애프터 어드바이스 반환
Spring의 애프터 리턴어드바이스는 다음 목록에 나와 있는org.springframework.aop.AfterReturningAdvice 인터페이스를 구현해야 합니다:
public interface AfterReturningAdvice extends Advice {
void afterReturning(Object returnValue, Method m, Object[] args, Object target)
throws Throwable;
}
반환 후 조언은 반환 값(수정할 수 없음), 호출된 메서드, 메서드의 인수 및 대상에 액세스할 수 있습니다.
다음 반환 후 조언은 예외를 던지지 않은 모든 성공적인 메서드 호출을 계산합니다:
public class CountingAfterReturningAdvice implements AfterReturningAdvice {
private int count;
public void afterReturning(Object returnValue, Method m, Object[] args, Object target)
throws Throwable {
++count;
}
public int getCount() {
return count;
}
}
이 조언은 실행 경로를 변경하지 않습니다. 예외가 발생하면 반환값 대신 인터셉터 체인이 발생합니다.
반환 후 조언은 모든 포인트컷과 함께 사용할 수 있습니다 |
도입 조언
Spring은 도입 조언을 특별한 종류의 인터셉트 조언으로 취급합니다.
소개에는 다음 인터페이스를 구현하는 IntroductionAdvisor와 IntroductionInterceptor가 필요합니다:
public interface IntroductionInterceptor extends MethodInterceptor {
boolean implementsInterface(Class intf);
}
AOP Alliance MethodInterceptor 인터페이스에서 상속된 invoke() 메서드는 도입을 구현해야 합니다. 즉, 호출된 메서드가 도입 인터페이스에 있는 경우 도입 인터셉터는 메서드 호출을 처리할 책임이 있으며 진행()을 호출할 수 없습니다.
도입 조언은 메서드가 아닌 클래스 수준에서만 적용되므로 어떤 포인트컷과도 함께 사용할 수 없습니다. 소개 조언은 다음 메서드가 있는IntroductionAdvisor에서만 사용할 수 있습니다:
public interface IntroductionAdvisor extends Advisor, IntroductionInfo {
ClassFilter getClassFilter();
void validateInterfaces() throws IllegalArgumentException;
}
public interface IntroductionInfo {
Class<?>[] getInterfaces();
}
메서드매처가 없으므로 소개 조언과 관련된 포인트컷이 없습니다. 클래스 필터링만 논리적입니다.
GetInterfaces() 메서드는 이 어드바이저가 소개한 인터페이스를 반환합니다.
유효성 검사 인터페이스() 메서드는 내부적으로 도입된 인터페이스가 구성된 도입 어드바이저에 의해 구현될 수 있는지 여부를 확인하는 데 사용됩니다.
Spring 테스트 스위트의 예를 들어 하나 이상의 객체에 다음 인터페이스를 도입하고 싶다고 가정해 보겠습니다:
public interface Lockable {
void lock();
void unlock();
boolean locked();
}
이것은 믹스인을 보여줍니다. 유형에 관계없이 조언된 객체를 Lockable로 형변환하고 잠금 및 잠금 해제 메서드를 호출할 수 있기를 원합니다. Lock() 메서드를 호출하면 모든 설정자 메서드가 LockedException을 던지길 원합니다. 따라서 객체가 전혀 알지 못하는 상태에서 객체를 불변으로 만드는 기능을 제공하는 측면을 추가할 수 있는데, 이것이 바로 AOP의 좋은 예입니다.
먼저, 무거운 작업을 수행하는 IntroductionInterceptor가 필요합니다. 이 경우, 우리는 org.springframework.aop.support.DelegatingIntroductionInterceptor편의 클래스를 확장합니다. 소개 인터셉터를 직접 구현할 수도 있지만, 대부분의 경우DelegatingIntroductionInterceptor를 사용하는 것이 가장 좋습니다.
DelegatingIntroductionInterceptor는 도입된 인터페이스의 실제 구현에 도입을 위임하도록 설계되어 인터셉션의 사용을 숨깁니다. 생성자 인수를 사용하여 모든 객체에 델리게이트를 설정할 수 있습니다. 기본 델리게이트(인자 없는 생성자를 사용할 때)는 이것입니다. 따라서 다음 예제에서 델리게이트는 DelegatingIntroductionInterceptor의 LockMixin 서브클래스입니다. 델리게이트가 주어지면(기본적으로 그 자체) DelegatingIntroductionInterceptor 인스턴스는 델리게이트가 구현한 모든 인터페이스(IntroductionInterceptor 제외)를 찾고 그중 하나에 대한 도입을 지원합니다. LockMixin과 같은 서브클래스는 suppressInterface(클래스 intf)메서드를 호출하여 노출되지 않아야 하는 인터페이스를 억제할 수 있습니다. 그러나 도입 인터셉터가 얼마나 많은 인터페이스를 지원하도록 준비되었는지에 관계없이도입 어드바이저는 실제로 어떤 인터페이스를 노출할지 제어합니다. 도입된 인터페이스는 대상에 의해 동일한 인터페이스의 구현을 숨깁니다.
따라서 LockMixin은 DelegatingIntroductionInterceptor를 확장하고 Lockable자체를 구현합니다. 슈퍼클래스는 Lockable이 도입을 위해 지원될 수 있다는 것을 자동으로 선택하므로 이를 지정할 필요가 없습니다. 이런 식으로 인터페이스를 얼마든지 도입할 수 있습니다.
잠긴 인스턴스 변수를 사용하는 것에 주목하세요. 이렇게 하면 대상 객체에 유지되는 상태에 추가 상태를 효과적으로 추가할 수 있습니다.
다음 예시는 LockMixin 클래스 예시를 보여줍니다:
public class LockMixin extends DelegatingIntroductionInterceptor implements Lockable {
private boolean locked;
public void lock() {
this.locked = true;
}
public void unlock() {
this.locked = false;
}
public boolean locked() {
return this.locked;
}
public Object invoke(MethodInvocation invocation) throws Throwable {
if (locked() && invocation.getMethod().getName().indexOf("set") == 0) {
throw new LockedException();
}
return super.invoke(invocation);
}
}
종종 invoke() 메서드를 재정의할 필요가 없습니다. 일반적으로DelegatingIntroductionInterceptor 구현(메서드가 도입되면 델리게이트 메서드를 호출하고, 그렇지 않으면 조인 지점을 향해 진행)으로 충분합니다. 현재의 경우, 잠긴 모드에서는 세터 메서드를 호출할 수 없다는 확인을 추가해야 합니다.
필요한 도입은 고유한LockMixin 인스턴스를 보유하고 도입된 인터페이스(이 경우에는Lockable만)를 지정하기만 하면 됩니다. 더 복잡한 예로는 도입 인터셉터(프로토타입으로 정의됨)에 대한 참조를 사용할 수 있습니다. 이 경우 LockMixin과 관련된 구성이 없으므로 새로 만들기를 사용하여 생성합니다. 다음 예제는 LockMixinAdvisor 클래스를 보여줍니다:
public class LockMixinAdvisor extends DefaultIntroductionAdvisor {
public LockMixinAdvisor() {
super(new LockMixin(), Lockable.class);
}
}
이 어드바이저는 구성이 필요하지 않으므로 매우 간단하게 적용할 수 있습니다. (그러나IntroductionAdvisor 없이는 IntroductionInterceptor를 사용할 수 없습니다.) 인트로뷰와 마찬가지로 어드바이저는 상태 저장형이므로 인스턴스별로 사용해야 합니다. 각 어드바이저 객체마다 다른 LockMixinAdvisor 인스턴스, 즉LockMixin이 필요합니다. 어드바이저는 조언된 객체 상태의 일부로 구성됩니다.
이 어드바이저는 다른 어드바이저와 마찬가지로 Advised.addAdvisor() 메서드를 사용하거나 XML 구성에서 (권장되는 방법)을 사용하여 프로그래밍 방식으로 적용할 수 있습니다. '자동 프록시 생성기'를 포함하여 아래에 설명된 모든 프록시 생성 옵션은 인트로와 상태 저장 믹스인을 올바르게 처리합니다.
Spring의 어드바이저 API
Spring에서 어드바이저는 포인트컷 표현식과 연관된 단 하나의 어드바이스 객체만 포함하는 어스펙트럼입니다.
소개와 같은 특별한 경우를 제외하고는 모든 어드바이저를 모든 어드바이스와 함께 사용할 수 있습니다. 가장 일반적으로 사용되는 어드바이저 클래스는org.springframework.aop.support.DefaultPointcutAdvisor입니다. 메서드인터셉터, 비포어어드바이스,던스어드바이스와 함께 사용할 수 있습니다.
Spring에서는 동일한 AOP 프록시에서 어드바이저와 어드바이스 유형을 혼합할 수 있습니다. 예를 들어 하나의 프록시 구성에서 조언 주위 인터셉터, 조언 던지기, 조언 전 인터셉터를 사용할 수 있습니다. Spring은 필요한 인터셉터 체인을 자동으로 생성합니다.
Using the ProxyFactoryBean to Create AOP Proxies
비즈니스 객체에 Spring IoC 컨테이너( ApplicationContext 또는 BeanFactory)를 사용하는 경우(그리고 사용해야 합니다!), Spring의 AOPFactoryBean 구현 중 하나를 사용하고 싶을 것입니다. (팩토리 빈은 다른 유형의 객체를 생성할 수 있는 간접성 계층을 도입한다는 점을 기억하세요.)
Spring AOP 지원도 은밀하게 팩토리 빈을 사용합니다 |
Spring에서 AOP 프록시를 생성하는 기본 방법은org.springframework.aop.framework.ProxyFactoryBean을 사용하는 것입니다. 이를 통해 포인트컷, 적용되는 모든 조언 및 순서를 완벽하게 제어할 수 있습니다. 그러나 이러한 제어가 필요하지 않은 경우 더 간단한 옵션이 있습니다.
Basics
다른 Spring FactoryBean 구현과 마찬가지로 ProxyFactoryBean은 어느 정도의 간접성을 도입합니다. Foo라는 이름의 ProxyFactoryBean을 정의하는 경우, foo를 참조하는 객체는 ProxyFactoryBean 인스턴스 자체가 아니라 ProxyFactoryBean에서 getObject() 메서드의 구현에 의해 생성된 객체를 보게 됩니다. 이 메서드는 대상 객체를 래핑하는 AOP 프록시를 생성합니다.
ProxyFactoryBean 또는 다른 IoC 인식 클래스를 사용하여 AOP 프록시를 생성할 때 가장 중요한 이점 중 하나는 조언과 포인트컷도 IoC로 관리할 수 있다는 것입니다. 이는 다른 AOP 프레임워크에서는 달성하기 어려운 특정 접근 방식을 가능하게 하는 강력한 기능입니다. 예를 들어, 조언은 그 자체로 애플리케이션 객체(모든 AOP 프레임워크에서 사용할 수 있는 대상 외에)를 참조할 수 있으며, 의존성 주입이 제공하는 모든 플러그 기능의 이점을 누릴 수 있습니다.
JavaBean Properties
Spring과 함께 제공되는 대부분의 FactoryBean 구현과 공통적으로,ProxyFactoryBean 클래스는 그 자체로 JavaBean입니다. 그 속성은 익숙합니다:
- 프록시할 대상을 지정합니다.
- CGLIB 사용 여부를 지정합니다(나중에 설명하며 JDK 및 CGLIB 기반 프록시도 참조).
일부 주요 프로퍼티는 org.springframework.aop.framework.ProxyConfig(Spring의 모든 AOP 프록시 팩토리에 대한 수퍼클래스)에서 상속됩니다. 이러한 주요 속성은 다음과 같습니다:
- proxyTargetClass: 대상 클래스의 인터페이스가 아닌 대상 클래스를 프록시할 경우 true입니다. 이 속성 값을 true로 설정하면 CGLIB 프록시가 생성됩니다( JDK 및 CGLIB 기반 프록시도 참조).
- optimize: CGLIB를 통해 생성된 프록시에 적극적인 최적화를 적용할지 여부를 제어합니다. 관련 AOP 프록시가 최적화를 처리하는 방식을 완전히 이해하지 않는 한 이 설정을 함부로 사용해서는 안 됩니다. 이 설정은 현재 CGLIB 프록시에만 사용됩니다. JDK 동적 프록시에는 영향을 미치지 않습니다.
- frozen: 프록시 구성이 고정되면 더 이상 구성을 변경할 수 없습니다. 이는 약간의 최적화와 프록시가 생성된 후 호출자가 ( Advised인터페이스를 통해) 프록시를 조작할 수 없도록 하려는 경우에 유용합니다. 이 속성의 기본값은거짓이므로 변경(예: 추가 조언 추가)이 허용됩니다.
- exposeProxy: 현재 프록시를 대상에서 액세스할 수 있도록ThreadLocal에 노출할지 여부를 결정합니다. 타겟이 프록시를 가져와야 하고 exposeProxy 속성이 true로 설정된 경우, 타겟은AopContext.currentProxy() 메서드를 사용할 수 있습니다.
ProxyFactoryBean과 관련된 다른 프로퍼티는 다음과 같습니다:
- proxyInterfaces: 인터페이스 이름의 문자열 배열입니다. 이 값을 제공하지 않으면 대상 클래스에 대한 CGLIB 프록시가 사용됩니다( JDK 및 CGLIB 기반 프록시도 참조).
- 인터셉터 이름: 적용할 어드바이저, 인터셉터 또는 기타 조언 이름의 문자열 배열입니다. 순서는 중요하며 선착순으로 지정됩니다. 즉, 목록의 첫 번째 인터셉터가 호출을 가장 먼저 인터셉트할 수 있습니다.
인터셉터 이름에 별표(*)를 추가할 수 있습니다. 이렇게 하면 별표 앞 부분으로 시작하는 이름을 가진 모든 어드바이저 빈이 적용됩니다. 이 기능의 사용 예는 "글로벌" 어드바이저 사용하기에서 확인할 수 있습니다.
- 이름은 조상 공장의 원두 이름을 포함하여 현재 공장의 원두 이름입니다. 여기서 빈 참조를 언급할 수 없습니다. 그렇게 하면ProxyFactoryBean이 조언의 싱글톤 설정을 무시하기 때문입니다.
- 싱글톤: GetObject() 메서드의 호출 빈도에 관계없이 팩토리에서 단일 객체를 반환할지 여부입니다. 여러 FactoryBean 구현에서 이러한 메서드를 제공합니다. 기본값은 true입니다. 상태 저장 조언을 사용하려면(예: 상태 저장 믹스인) 싱글톤 값false와 함께 프로토타입 조언을 사용하세요.
JDK- and CGLIB-based proxies
이 섹션은 ( 프록시할) 특정 대상 객체에 대한 JDK 기반 프록시 또는 CGLIB 기반 프록시를 생성하는 방법에 대한 최종 문서로 사용됩니다.
JDK 또는 CGLIB 기반 프록시 생성과 관련된 ProxyFactoryBean의 동작은 Spring 1.2.x 버전과 2.0 버전 사이에서 변경되었습니다. 이제 ProxyFactoryBean은 자동 감지 인터페이스와 관련하여TransactionProxyFactoryBean 클래스와 유사한 시맨틱을 나타냅니다 |
프록시할 대상 객체의 클래스(이하 간단히 대상 클래스라고 함)가 인터페이스를 구현하지 않는 경우, CGLIB 기반 프록시가 생성됩니다. 이것은 가장 쉬운 시나리오인데, JDK 프록시는 인터페이스 기반이며 인터페이스가 없다는 것은 JDK 프록시가 불가능하다는 것을 의미하기 때문입니다. 대상 빈을 연결하고 인터셉터 이름 속성을 설정하여 인터셉터 목록을 지정할 수 있습니다.ProxyFactoryBean의 proxyTargetClass 속성이 false로 설정되어 있어도 CGLIB 기반 프록시가 생성됩니다. (그렇게 하는 것은 의미가 없으며, 기껏해야 중복되고 최악의 경우 혼란을 줄 수 있으므로 빈 정의에서 제거하는 것이 가장 좋습니다)
대상 클래스가 하나 이상의 인터페이스를 구현하는 경우 생성되는 프록시 유형은 ProxyFactoryBean의 구성에 따라 달라집니다.
ProxyFactoryBean의 proxyTargetClass 속성이 true로 설정된 경우, CGLIB 기반 프록시가 생성됩니다. 이는 합리적이며 최소한의 놀라움 원칙에 부합합니다.ProxyFactoryBean의 proxyInterfaces 속성이 하나 이상의 정규화된 인터페이스 이름으로 설정되어 있더라도 proxyTargetClass 속성이 true로 설정되어 있으면 CGLIB 기반 프록시가 유효하게 됩니다.
ProxyFactoryBean의 proxyInterfaces 속성이 하나 이상의 정규화된 인터페이스 이름으로 설정된 경우 JDK 기반 프록시가 생성됩니다. 생성된 프록시는 proxyInterfaces프로퍼티에 지정된 모든 인터페이스를 구현합니다. 대상 클래스가 proxyInterfaces 속성에 지정된 인터페이스보다 훨씬 더 많은 인터페이스를 구현하는 경우에도 괜찮지만, 이러한 추가 인터페이스는 반환된 프록시에서 구현되지 않습니다.
ProxyFactoryBean의 proxyInterfaces 속성이 설정되지 않았지만 대상 클래스가 하나 이상의 인터페이스를 구현하는 경우ProxyFactoryBean은 대상 클래스가 실제로 하나 이상의 인터페이스를 구현한다는 사실을 자동으로 감지하고 JDK 기반 프록시를 생성합니다. 실제로 프록시되는 인터페이스는 대상 클래스가 구현하는 모든 인터페이스입니다. 사실상 이것은 대상 클래스가 구현하는 모든 인터페이스의 목록을 proxyInterfaces 속성에 제공하는 것과 동일합니다. 그러나 작업량이 훨씬 적고 오타가 발생할 가능성이 적습니다.
Proxying Interfaces
ProxyFactoryBean의 간단한 작동 예시를 살펴보겠습니다. 이 예제에는 다음이 포함됩니다:
- 프록시되는 대상 빈. 이것이 이 예제에서 personTarget 빈 정의입니다.
- 조언을 제공하는 데 사용되는 어드바이저와 인터셉터.
- 대상 객체( personTarget 빈), 프록시할 인터페이스 및 적용할 조언을 지정하는 AOP 프록시 빈 정의.
다음 목록은 그 예를 보여줍니다:
<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>
인터셉터 이름 속성은 현재 팩토리에 있는 인터셉터 또는 어드바이저의 빈 이름이 들어 있는 문자열 목록을 받습니다. 어드바이저, 인터셉터, 반환 전, 반환 후에 어드바이저를 사용할 수 있으며 어드바이저 객체를 던질 수 있습니다. 어드바이저의 순서는 중요합니다.
목록에 빈 참조가 없는 이유가 궁금하실 것입니다. 그 이유는 ProxyFactoryBean의 싱글톤 속성이 false로 설정된 경우 독립적인 프록시 인스턴스를 반환할 수 있어야 하기 때문입니다. 어드바이저 중 하나가 프로토타입인 경우 독립적인 인스턴스를 반환해야 하므로 팩토리에서 프로토타입의 인스턴스를 가져올 수 있어야 합니다. 참조를 보유하는 것만으로는 충분하지 않습니다 |
앞서 표시된 사람 빈 정의는 다음과 같이 사람 구현 대신 사용할 수 있습니다:
Person person = (Person) factory.getBean("person");
동일한 IoC 컨텍스트에 있는 다른 빈은 일반 Java 객체와 마찬가지로 강력하게 유형화된 종속성을 표현할 수 있습니다. 다음 예제는 그 방법을 보여줍니다:
<bean id="personUser" class="com.mycompany.PersonUser">
<property name="person"><ref bean="person"/></property>
</bean>
이 예제의 PersonUser 클래스는 Person 유형의 프로퍼티를 노출합니다. 이 예제에서는 AOP 프록시를 '실제' 사람 구현 대신 투명하게 사용할 수 있습니다. 그러나 그 클래스는 동적 프록시 클래스가 됩니다. 이를 Advised 인터페이스(나중에 설명)로 캐스팅할 수 있습니다.
익명의 내부 빈을 사용하여 대상과 프록시의 구분을 숨길 수 있습니다. ProxyFactoryBean 정의만 다릅니다. 이 조언은 완전성을 위해서만 포함되어 있습니다. 다음 예제는 익명 내부 빈을 사용하는 방법을 보여줍니다:
<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>
익명 내부 빈을 사용하면 Person 유형의 객체가 하나만 있다는 이점이 있습니다. 이는 애플리케이션 컨텍스트의 사용자가 조언되지 않은 객체에 대한 참조를 얻지 못하도록 방지하거나 Spring IoC 자동 배선을 통해 모호성을 피해야 하는 경우에 유용합니다. ProxyFactoryBean 정의가 독립적이라는 점에서도 이점이 있습니다. 그러나 팩토리에서 조언되지 않은 대상을 얻을 수 있는 것이 실제로 이점이 될 수 있는 경우도 있습니다(예: 특정 테스트 시나리오에서).
Proxying Classes
하나 이상의 인터페이스가 아닌 클래스를 프록시해야 한다면 어떻게 해야 할까요?
앞선 예제에서 Person 인터페이스가 없었다고 가정해 봅시다. 비즈니스 인터페이스를 구현하지 않은 Person이라는 클래스를 알려야 했습니다. 이 경우 동적 프록시 대신 CGLIB 프록시를 사용하도록 Spring을 구성할 수 있습니다. 이렇게 하려면 앞서 표시된 ProxyFactoryBean의proxyTargetClass 속성을 true로 설정합니다. 클래스가 아닌 인터페이스로 프로그래밍하는 것이 가장 좋지만, 인터페이스를 구현하지 않는 클래스를 알려주는 기능은 레거시 코드로 작업할 때 유용할 수 있습니다. (일반적으로 Spring은 규범적이지 않습니다. 모범 사례를 쉽게 적용할 수 있지만 특정 접근 방식을 강요하는 것은 피합니다
원한다면 인터페이스가 있더라도 어떤 경우에도 CGLIB를 사용하도록 강제할 수 있습니다.
CGLIB 프록시는 런타임에 대상 클래스의 서브클래스를 생성하는 방식으로 작동합니다. Spring은 이렇게 생성된 서브클래스가 메서드 호출을 원래 대상에 위임하도록 구성합니다. 이 서브클래스는 데코레이터 패턴을 구현하는 데 사용되며, 조언을 엮어줍니다.
CGLIB 프록시는 일반적으로 사용자에게 투명해야 합니다. 그러나 고려해야 할 몇 가지 문제가 있습니다:
- 최종 클래스는 확장할 수 없기 때문에 프록시할 수 없습니다.
- 최종 메서드는 재정의할 수 없으므로 조언할 수 없습니다.
- 비공개 메서드는 재정의할 수 없으므로 조언할 수 없습니다.
- 보이지 않는 메서드, 일반적으로 다른 패키지의 부모 클래스에서 비공개 메서드를 패키징하는 메서드는 사실상 비공개이므로 조언할 수 없습니다.
클래스 경로에 CGLIB를 추가할 필요가 없습니다. CGLIB는 재패키징되어 스프링 코어 JAR에 포함됩니다. 다시 말해, CGLIB 기반 AOP는 JDK 동적 프록시와 마찬가지로 "즉시" 작동합니다 |
CGLIB 프록시와 동적 프록시 간에는 성능 차이가 거의 없습니다. 이 경우 성능이 결정적인 고려 사항이 되어서는 안 됩니다.
Using “Global” Advisors
인터셉터 이름에 별표를 추가하면 별표 앞 부분과 일치하는 빈 이름을 가진 모든 어드바이저가 어드바이저 체인에 추가됩니다. 이 기능은 표준 "글로벌" 어드바이저 세트를 추가해야 할 때 유용하게 사용할 수 있습니다. 다음 예에서는 두 개의 글로벌 어드바이저를 정의합니다:
<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"/>
간결한 프록시 정의
특히 트랜잭션 프록시를 정의할 때 유사한 프록시 정의가 많이 나올 수 있습니다. 부모 및 자식 빈 정의와 내부 빈 정의를 함께 사용하면 훨씬 더 깔끔하고 간결한 프록시 정의를 얻을 수 있습니다.
먼저 다음과 같이 프록시에 대한 부모, 템플릿, 빈 정의를 생성합니다:
<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>
이것은 그 자체로 인스턴스화되지 않으므로 실제로 불완전할 수 있습니다. 그런 다음, 생성해야 하는 각 프록시는 하위 빈 정의로, 대상은 어차피 자체적으로 사용되지 않으므로 프록시의 대상을 내부 빈 정의로 래핑합니다. 다음 예는 이러한 하위 빈을 보여줍니다:
<bean id="myService" parent="txProxyTemplate">
<property name="target">
<bean class="org.springframework.samples.MyServiceImpl">
</bean>
</property>
</bean>
부모 템플릿의 속성을 재정의할 수 있습니다. 다음 예제에서는 트랜잭션 전파 설정을 재정의합니다:
<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>
부모 빈 예제에서는앞서 설명한 대로 추상 속성을 true로 설정하여 부모 빈 정의를 추상적인 것으로 명시적으로 표시하여 실제로는 인스턴스화되지 않을 수 있습니다. 애플리케이션 컨텍스트(단순 빈 팩토리는 제외)는 기본적으로 모든 싱글톤을 사전 인스턴스화합니다. 따라서 (적어도 싱글톤 빈의 경우) 템플릿으로만 사용하려는 (부모) 빈 정의가 있고 이 정의가 클래스를 지정하는 경우 추상속성을 true로 설정하는 것이 중요합니다. 그렇지 않으면 애플리케이션 컨텍스트에서 실제로 사전 인스턴스화를 시도합니다.
ProxyFactory를 사용하여 프로그래밍 방식으로 AOP 프록시 생성하기
Spring을 사용하면 프로그래밍 방식으로 AOP 프록시를 쉽게 생성할 수 있습니다. 이를 통해 Spring IoC에 대한 의존성 없이 Spring AOP를 사용할 수 있습니다.
대상 객체가 구현하는 인터페이스는 자동으로 프록시됩니다. 다음 목록은 하나의 인터셉터와 하나의 어드바이저를 사용하여 대상 객체에 대한 프록시를 생성하는 방법을 보여줍니다:
ProxyFactory factory = new ProxyFactory(myBusinessInterfaceImpl);
factory.addAdvice(myMethodInterceptor);
factory.addAdvisor(myAdvisor);
MyBusinessInterface tb = (MyBusinessInterface) factory.getProxy();
첫 번째 단계는org.springframework.aop.framework.ProxyFactory 유형의 객체를 생성하는 것입니다. 앞의 예제에서와 같이 대상 객체를 사용하여 생성하거나 대체 생성자에서 프록시할 인터페이스를 지정할 수 있습니다.
조언(인터셉터를 특수한 종류의 조언으로 사용), 어드바이저 또는 둘 다를 추가하고 ProxyFactory의 수명 동안 이를 조작할 수 있습니다.도입 인터셉션 어라운드 어드바이저를 추가하면 프록시가 추가 인터페이스를 구현하도록 할 수 있습니다.
또한 ProxyFactory에는 ( AdvisedSupport에서 상속된) 편리한 메서드를 통해 이전 및 던지기 조언과 같은 다른 조언 유형을 추가할 수 있습니다.AdvisedSupport는 ProxyFactory와 ProxyFactoryBean의 수퍼클래스입니다.
대부분의 애플리케이션에서는 AOP 프록시 생성을 IoC 프레임워크와 통합하는 것이 모범 사례입니다. 일반적으로 AOP를 사용하여 Java 코드에서 구성을 외부화하는 것이 좋습니다 |
Advised 객체 조작하기
AOP 프록시를 어떻게 생성하든,org.springframework.aop.framework.Advised 인터페이스를 사용하여 프록시를 조작할 수 있습니다. 구현하는 다른 인터페이스에 관계없이 모든 AOP 프록시를 이 인터페이스로 캐스팅할 수 있습니다. 이 인터페이스에는 다음과 같은 메서드가 포함되어 있습니다:
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() 메서드는 팩토리에 추가된 모든 어드바이저, 인터셉터 또는 기타 어드바이저 유형에 대한 어드바이저를 반환합니다. 어드바이저를 추가한 경우 이 인덱스에서 반환되는 어드바이저는 추가한 객체입니다. 인터셉터나 다른 어드바이스 유형을 추가한 경우 Spring은 이를 항상 참을 반환하는 포인트컷을 사용하여 어드바이저로 래핑합니다. 따라서 메서드인터셉터를 추가한 경우 이 인덱스에 대해 반환되는 어드바이저는메서드인터셉터와 모든 클래스 및 메서드와 일치하는 포인트컷을 반환하는 DefaultPointcutAdvisor입니다.
추가 어드바이저() 메서드는 모든 어드바이저를 추가하는 데 사용할 수 있습니다. 일반적으로 포인트컷과 조언을 보유하는 어드바이저는 일반적인 DefaultPointcutAdvisor로, 모든 조언이나 포인트컷에 사용할 수 있습니다(단, 인트로에는 사용할 수 없음).
기본적으로 프록시가 생성된 후에도 어드바이저나 인터셉터를 추가하거나 제거할 수 있습니다. 유일한 제한 사항은 공장 출고 시 기존 프록시에는 인터페이스 변경 사항이 표시되지 않으므로 소개 어드바이저를 추가하거나 제거할 수 없다는 것입니다. (이 문제를 피하기 위해 공장에서 새 프록시를 얻을 수 있습니다.)
다음 예에서는 AOP 프록시를 Advised 인터페이스에 캐스팅하고 그 조언을 검토 및 조작하는 방법을 보여 줍니다:
Advised advised = (Advised) myObject;
Advisor[] advisors = advised.getAdvisors();
int oldAdvisorCount = advisors.length;
System.out.println(oldAdvisorCount + " advisors");
// Add an advice like an interceptor without a pointcut
// Will match all proxied methods
// Can use for interceptors, before, after returning or throws advice
advised.addAdvice(new DebugInterceptor());
// Add selective advice using a pointcut
advised.addAdvisor(new DefaultPointcutAdvisor(mySpecialPointcut, myAdvice));
assertEquals("Added two advisors", oldAdvisorCount + 2, advised.getAdvisors().length);
프로덕션 환경에서 비즈니스 개체에 대한 조언을 수정하는 것이 바람직한지(말장난 의도는 없음) 의문이지만, 의심할 여지없이 합법적인 사용 사례가 있습니다. 그러나 개발(예: 테스트 등)에서는 매우 유용할 수 있습니다. 인터셉터나 기타 조언의 형태로 테스트 코드를 추가하여 테스트하려는 메서드 호출 내부에 들어갈 수 있는 것이 매우 유용하다는 것을 알게 된 적이 있습니다. (예를 들어, 이 조언은 해당 메서드에 대해 생성된 트랜잭션 내부로 들어가서 트랜잭션을 롤백으로 표시하기 전에 SQL을 실행하여 데이터베이스가 올바르게 업데이트되었는지 확인할 수 있습니다 |
프록시를 만든 방법에 따라 일반적으로 고정 플래그를 설정할 수 있습니다. 이 경우 Advised isFrozen() 메서드는 참을 반환하고, 추가 또는 제거를 통해 조언을 수정하려고 시도하면 AopConfigException이 발생합니다. 조언된 객체의 상태를 고정하는 기능은 일부 경우에 유용합니다(예: 보안 인터셉터를 제거하는 호출 코드를 방지하는 경우).
Using the "auto-proxy" facility
지금까지 ProxyFactoryBean 또는 이와 유사한 팩토리 빈을 사용하여 AOP 프록시를 명시적으로 생성하는 것을 고려했습니다.
또한 Spring에서는 선택한 빈 정의를 자동으로 프록시할 수 있는 "자동 프록시" 빈 정의를 사용할 수 있습니다. 이는 컨테이너가 로드될 때 모든 빈 정의를 수정할 수 있는 Spring의 "빈 포스트 프로세서" 인프라를 기반으로 합니다.
이 모델에서는 XML 빈 정의 파일에 몇 가지 특수한 빈 정의를 설정하여 자동 프록시 인프라를 구성합니다. 이를 통해 자동 프록시를 사용할 수 있는 대상을 선언할 수 있습니다. ProxyFactoryBean을 사용할 필요는 없습니다.
이 작업에는 두 가지 방법이 있습니다:
- 현재 컨텍스트에서 특정 빈을 참조하는 자동 프록시 생성자를 사용하는 방법.
- 별도로 고려해야 할 자동 프록시 생성의 특수한 경우: 소스 수준 메타데이터 속성에 의해 구동되는 자동 프록시 생성.
Auto-proxy Bean Definitions
이 섹션에서는org.springframework.aop.framework.autoproxy 패키지에서 제공하는 자동 프록시 생성자에 대해 설명합니다.
BeanNameAutoProxyCreator
BeanNameAutoProxyCreator 클래스는 리터럴 값 또는 와일드카드와 일치하는 이름을 가진 빈에 대한 AOP 프록시를 자동으로 생성하는 BeanPostProcessor입니다. 다음 예제는 BeanNameAutoProxyCreator 빈을 생성하는 방법을 보여줍니다:
<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 속성이 있습니다. 이름이 지정된 "인터셉터"는 어드바이저 또는 모든 어드바이스 유형이 될 수 있습니다.
일반적으로 자동 프록시를 사용할 때와 마찬가지로 BeanNameAutoProxyCreator를 사용하는 주요 요점은 최소한의 구성으로 여러 개체에 동일한 구성을 일관되게 적용하는 것입니다. 선언적 트랜잭션을 여러 개체에 적용하는 데 널리 사용됩니다.
앞의 예제에서 jdkMyBean 및 onlyJdk와 같이 이름이 일치하는 빈 정의는 대상 클래스가 있는 일반적인 오래된 빈 정의입니다. AOP 프록시는 BeanNameAutoProxyCreator에 의해 자동으로 생성됩니다. 일치하는 모든 빈에 동일한 조언이 적용됩니다. 앞의 예제에서 인터셉터 대신 어드바이저를 사용하는 경우 포인트컷이 다른 빈에 다르게 적용될 수 있습니다.
디폴트 어드바이저 자동 프록시 생성자
보다 일반적이고 매우 강력한 자동 프록시 생성자는DefaultAdvisorAutoProxyCreator입니다. 이것은 자동 프록시 어드바이저의 빈 정의에 특정 빈 이름을 포함할 필요 없이 현재 컨텍스트에서 적격한 어드바이저를 자동으로 적용합니다. 이 메커니즘은 일관된 구성과 중복 방지라는 BeanNameAutoProxyCreator와 동일한 장점을 제공합니다.
이 메커니즘을 사용하려면 다음과 같이 하세요:
- DefaultAdvisorAutoProxyCreator 빈 정의를 지정합니다.
- 동일하거나 관련된 컨텍스트에서 어드바이저를 원하는 수만큼 지정합니다. 인터셉터나 다른 조언이 아닌 어드바이저여야 한다는 점에 유의하세요. 이는 후보 빈 정의에 대한 각 조언의 적격성을 확인하기 위해 평가할 포인트컷이 있어야 하기 때문에 필요합니다.
DefaultAdvisorAutoProxyCreator는 각 어드바이저에 포함된 포인트컷을 자동으로 평가하여 각 비즈니스 객체(예: 예제에서 businessObject1 및 businessObject2 )에 적용해야 하는 어드바이스가 있는지 확인합니다.
즉, 각 비즈니스 개체에 어드바이저를 몇 개든 자동으로 적용할 수 있습니다. 어드바이저의 어떤 포인트컷도 비즈니스 객체의 메서드와 일치하지 않으면 해당 객체는 프록시되지 않습니다. 새 비즈니스 객체에 대한 빈 정의가 추가되면 필요한 경우 자동으로 프록시됩니다.
일반적으로 자동 프록시는 호출자나 종속성이 어드바이저가 없는 객체를 가져올 수 없도록 하는 이점이 있습니다. 이ApplicationContext에서 getBean("businessObject1") 을 호출하면 대상 비즈니스 객체가 아닌 AOP 프록시가 반환됩니다. (앞서 표시된 "내부 빈" 관용구도 이러한 이점을 제공합니다.)
다음 예제에서는 이 섹션에서 설명한 다른 요소와 DefaultAdvisorAutoProxyCreator 빈을 생성합니다:
<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에는 구성 가능한 순서 값이 있습니다. 기본 설정은 정렬되지 않은 값입니다.
Using TargetSource Implementations
Spring은org.springframework.aop.TargetSource 인터페이스로 표현되는 TargetSource라는 개념을 제공합니다. 이 인터페이스는 조인 지점을 구현하는 "대상 객체"를 반환하는 역할을 담당합니다. TargetSource구현은 AOP 프록시가 메서드 호출을 처리할 때마다 대상 인스턴스를 요청받습니다.
Spring AOP를 사용하는 개발자는 일반적으로 TargetSource 구현으로 직접 작업할 필요는 없지만 풀링, 핫 스왑 가능 및 기타 정교한 타깃을 지원하는 강력한 수단을 제공합니다. 예를 들어 풀링 TargetSource는 풀을 사용하여 인스턴스를 관리함으로써 각 호출에 대해 다른 타깃 인스턴스를 반환할 수 있습니다.
TargetSource를 지정하지 않으면 기본 구현이 로컬 객체를 래핑하는 데 사용됩니다. 예상대로 각 호출에 대해 동일한 타겟이 반환됩니다.
이 섹션의 나머지 부분에서는 Spring과 함께 제공되는 표준 대상 소스와 이를 사용하는 방법에 대해 설명합니다.
사용자 정의 대상 소스를 사용하는 경우, 일반적으로 대상은 싱글톤 빈 정의가 아닌 프로토타입이어야 합니다. 이렇게 하면 Spring이 필요할 때 새 대상 인스턴스를 생성할 수 있습니다 |
Hot-swappable Target Sources
Org.springframework.aop.target.HotSwappableTargetSource는 호출자가 참조를 유지하면서 AOP 프록시의 대상을 전환할 수 있도록 하기 위해 존재합니다.
대상 소스의 대상을 변경하면 즉시 적용됩니다.핫스왑가능타겟소스는 스레드 안전합니다.
다음 예시와 같이 HotSwappableTargetSource의 swap() 메서드를 사용하여 타깃을 변경할 수 있습니다:
HotSwappableTargetSource swapper = (HotSwappableTargetSource) beanFactory.getBean("swapper");
Object oldTarget = swapper.swap(newTarget);
다음 예제는 필요한 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를 사용할 수 있습니다.
Pooling Target Sources
풀링 대상 소스를 사용하면 동일한 인스턴스 풀이 유지되고 메서드 호출이 풀의 자유 객체로 이동하는 상태 비저장 세션 EJB와 유사한 프로그래밍 모델을 제공합니다.
Spring 풀링과 SLSB 풀링의 중요한 차이점은 Spring 풀링은 모든 POJO에 적용될 수 있다는 것입니다. 일반적인 Spring과 마찬가지로 이 서비스는 비침습적인 방식으로 적용될 수 있습니다.
Spring은 상당히 효율적인 풀링 구현을 제공하는 커먼즈 풀 2.2를 지원합니다. 이 기능을 사용하려면 애플리케이션의 클래스 경로에 commons-pool Jar가 있어야 합니다. 다른 풀링 API를 지원하기 위해org.springframework.aop.target.AbstractPoolingTargetSource를 서브클래싱할 수도 있습니다.
Commons Pool 1.5+도 지원되지만 Spring Framework 4.2부터 더 이상 사용되지 않습니다 |
다음 목록은 구성 예시를 보여줍니다:
<bean id="businessObjectTarget" class="com.mycompany.MyBusinessObject"
scope="prototype">
... properties omitted
</bean>
<bean id="poolTargetSource" class="org.springframework.aop.target.CommonsPool2TargetSource">
<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의 자바독과 사용하려는 구체적인 서브클래스를 참조하세요. 최대 크기는 가장 기본적이며 항상 존재가 보장됩니다.
이 경우 myInterceptor는 동일한 IoC 컨텍스트에서 정의해야 하는 인터셉터의 이름입니다. 그러나 풀링을 사용하기 위해 인터셉터를 지정할 필요는 없습니다. 풀링만 원하고 다른 조언은 원하지 않는다면interceptorNames 속성을 전혀 설정하지 마세요.
풀링된 객체를org.springframework.aop.target.PoolingConfig 인터페이스에 캐스팅할 수 있도록 Spring을 구성할 수 있으며, 이 인터페이스는 소개를 통해 풀의 구성 및 현재 크기에 대한 정보를 노출합니다. 다음과 유사한 어드바이저를 정의해야 합니다:
<bean id="poolConfigAdvisor" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetObject" ref="poolTargetSource"/>
<property name="targetMethod" value="getPoolingConfigMixin"/>
</bean>
이 어드바이저는추상풀링타겟소스 클래스의 편의 메서드를 호출하여 얻으므로 메서드인보킹팩토리빈을 사용합니다. 이 어드바이저의 이름(여기서는poolConfigAdvisor)은 풀링된 객체를 노출하는 ProxyFactoryBean의 인터셉터 이름 목록에 있어야 합니다.
형변환은 다음과 같이 정의됩니다:
PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject");
System.out.println("Max pool size is " + conf.getMaxSize());
상태 비저장 서비스 객체를 풀링하는 것은 일반적으로 필요하지 않습니다. 대부분의 상태 비저장 객체는 기본적으로 스레드에 안전하며, 리소스가 캐시된 경우 인스턴스 풀링이 문제가 될 수 있으므로 기본 선택이 되어서는 안 된다고 생각합니다 |
자동 프록시를 사용하면 더 간단한 풀링을 사용할 수 있습니다. 자동 프록시 생성자가 사용하는 TargetSource 구현을 설정할 수 있습니다.
Prototype Target Sources
"프로토타입" 타겟 소스를 설정하는 것은 풀링 타겟 소스를 설정하는 것과 유사합니다. 이 경우 메서드를 호출할 때마다 대상의 새 인스턴스가 생성됩니다. 최신 JVM에서는 새 객체를 생성하는 비용이 높지 않지만, 새 객체를 배선하는 비용(IoC 종속성 충족)은 더 비쌀 수 있습니다. 따라서 특별한 이유 없이 이 접근 방식을 사용해서는 안 됩니다.
이를 위해 앞서 표시된 풀타겟소스 정의를 다음과 같이 수정할 수 있습니다(명확성을 위해 이름도 변경했습니다):
<bean id="prototypeTargetSource" class="org.springframework.aop.target.PrototypeTargetSource">
<property name="targetBeanName" ref="businessObjectTarget"/>
</bean>
유일한 속성은 대상 빈의 이름입니다. 일관된 이름을 지정하기 위해TargetSource 구현에서 상속이 사용됩니다. 풀링 대상 소스와 마찬가지로 대상 빈은 프로토타입 빈 정의여야 합니다.
ThreadLocal Target Sources
ThreadLocal 대상 소스는 들어오는 각 요청(즉, 스레드당)에 대해 객체를 생성해야 하는 경우에 유용합니다. ThreadLocal의 개념은 스레드와 함께 리소스를 투명하게 저장할 수 있는 JDK 차원의 기능을 제공합니다. 다음 예제에서 볼 수 있듯이ThreadLocalTargetSource를 설정하는 것은 다른 유형의 대상 소스에 대해 설명한 것과 거의 동일합니다:
<bean id="threadlocalTargetSource" class="org.springframework.aop.target.ThreadLocalTargetSource">
<property name="targetBeanName" value="businessObjectTarget"/>
</bean>
ThreadLocal 인스턴스는 멀티스레드 및 멀티클래스로더 환경에서 잘못 사용할 경우 심각한 문제(잠재적으로 메모리 누수)가 발생할 수 있습니다. 항상 ThreadLocal을 다른 클래스로 래핑하는 것을 고려해야 하며, ThreadLocal 자체를 직접 사용하지 마십시오(래퍼 클래스에서 제외). 또한 스레드에 로컬 리소스를 올바르게 설정하고 설정 해제(후자의 경우ThreadLocal.remove() 호출을 포함)하는 것을 항상 기억해야 합니다. 설정 해제하지 않으면 문제가 발생할 수 있으므로 어떤 경우에도 설정 해제를 수행해야 합니다. Spring의ThreadLocal 지원은 이 작업을 대신 수행하며, 다른 적절한 처리 코드 없이ThreadLocal 인스턴스를 사용하는 것을 항상 고려해야 합니다 |
새로운 어드바이스 타입 정의하기
Spring AOP는 확장 가능하도록 설계되었습니다. 현재 내부적으로 인터셉트 구현 전략이 사용되고 있지만, 조언에 대한 인터셉트, 조언 전, 조언 던지기, 조언 반환 후 외에도 임의의 조언 유형을 지원할 수 있습니다.
Org.springframework.aop.framework.adapter 패키지는 핵심 프레임워크를 변경하지 않고도 새로운 사용자 정의 조언 유형을 지원할 수 있는 SPI 패키지입니다. 사용자 정의 조언 유형에 대한 유일한 제약 조건은org.aopalliance.aop.Advice 마커 인터페이스를 구현해야 한다는 것입니다.
자세한 내용은 org.springframework.aop.framework.adapterjavadoc을 참조하세요.
'언어 > Spring Docs 번역' 카테고리의 다른 글
Testing (1) (0) | 2024.07.07 |
---|---|
Core Technologies / Null-safety, Data Buffers and Codecs, Logging, Ahead of Time Optimizations (0) | 2024.07.07 |
Core Technologies / Aspect Oriented Programming with Spring (0) | 2024.07.07 |
Core Technologies / Spring Expression Language (SpEL) (0) | 2024.07.07 |
Core Technologies / Validation, Data Binding, and Type Conversion (0) | 2024.07.07 |