유용성
1. ProxyFactory
- ProxyFactory를 통해 인터페이스가 있는 경우 JDK 동적 프록시 사용, 그렇지 않으면 CGLIB 사용하도록 하여 일관성 있고 편리하게 프록시를 사용할 수 있게 함
- ProxyFactory 를 통해 프록시 기술을 선택할 수 있게 함
proxyTargetClass=true
로 설정하면, 인터페이스 여부와 상관 없이 구체 클래스 기반 프록시 CGLIB 를 사용함.
스프링부트 AOP에서는 기본적으로 proxyTargetClass=true
로 설정되어 있음
2. Advice, Pointcut
- Advice를 통해 부가 기능을 구현하기 편함
- 기존에는 JDK 동적 프록시 InvocationHandler 와 CGLIB가 제공하는 MethodInterceptor를 중복으로 따로 만들어야 했음
- 이 부가 기능 로직을 advice 로 위임하고, InvocationHandler와 MethodInterceptor는 advice 를 호출하도록 함
구성
- 포인트컷 Pointcut: 어디에 부가 기능을 적용할지. 어디에 부가 기능을 적용하지 말지 판단하는 필터링 역할. 주로 클래스와 메서드 이름으로 필터링함. Point 에 기능을 적용할지 말지 잘라서(Cut) 구분하는 것.
- 어드바이스 Advice: 프록시가 호출되는 부가 기능. 프록시 로직.
- 어드바이저 Advisor: 포인트컷과 어드바이스를 가지고 있는 것. 포인트컷+어드바이스1
스프링 AOP의 역할과 책임 설계
- 포인트컷은 대상 여부를 확인하는 필터 역할만 담당
- 어드바이스는 깔끔하게 부가 기능 로직만 담당
- 둘을 합치면 어드바이저가 완성.
사용
Advisor 사용 예시
@Test
void advisorTest1() {
ServiceInterface target = new ServiceImpl();
ProxyFactory proxyFactory = new ProxyFactory(target);
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(Pointcut.TRUE, new TimeAdvice());
proxyFactory.addAdvisor(advisor);
ServiceInterface proxy = (ServiceInterface) proxyFactory.getProxy();
proxy.save();
proxy.find();
}
@Slf4j
public class TimeAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
log.info("TimeProxy 실행");
long startTime = System.currentTimeMillis();
Object result = invocation.proceed();
long endTime = System.currentTimeMillis();
long resultTime = endTime - startTime;
log.info("TimeProxy 종료 resultTime={}", resultTime);
return result;
}
}
스프링이 제공하는 포인트컷 종류
NameMatchMethodPointcut
: 메서드 이름을 기반으로 매칭. 내부에서는 PatternMatchUtils
사용
JdkRegexMethodPointcut
: JDK 정규 표현식을 기반으로 포인트컷을 매칭
TruePointcut
: 항상 참을 반환
AnnotationMatchingPointcut
: 애노테이션을 매칭
AspectJExpressionPointcut
: aspectJ 표현식으로 매칭. 주로 실무에서는 이 방식 사용.
하나의 프록시, 여러 어드바이저
- 프록시를 중첩해서 사용할 수도 있지만, ProxyFactory를 또 생성해야하는 코드 중복이 발생한다.
- 스프링에서는 한 프록시에 여러 어드바이저를 사용할 수 있도록 한다.
- 스프링에서는 하나의 타겟에 하나의 프록시만 생성된다. 착각하는 경우가 많음.
@Test
@DisplayName("하나의 프록시, 여러 어드바이저")
void multiAdvisorTest2() {
//client -> proxy -> advisor2 -> advisor1 -> target
DefaultPointcutAdvisor advisor1 = new DefaultPointcutAdvisor(Pointcut.TRUE, new Advice1());
DefaultPointcutAdvisor advisor2 = new DefaultPointcutAdvisor(Pointcut.TRUE, new Advice2());
//프록시1 생성
ServiceInterface target = new ServiceImpl();
ProxyFactory proxyFactory1 = new ProxyFactory(target);
proxyFactory1.addAdvisor(advisor2);
proxyFactory1.addAdvisor(advisor1);
ServiceInterface proxy = (ServiceInterface) proxyFactory1.getProxy();
//실행
proxy.save();
}