Q. 인터페이스가 있는 경우에는 JDK 동적 프록시를 적용하고, 그렇지 않은 경우에는 CGLIB를 적용하려면 어떻게 해야할까?
ProxyFactory)라는 기능을 제공한다이 설정을 변경할 수도 있다.
Q. 두 기술을 함께 사용할 때 부가 기능을 적용하기 위해 JDK 동적 프록시가 제공하는
InvocationHandler와 CGLIB가 제공하는 MethodInterceptor를 각각 중복으로 따로 만들어야
할까?
Advice라는 새로운 개념을 도입InvocationHandler나 MethodInterceptor를 신경 쓰지 않고 Advice만 만들면 된다.InvocationHandler나 MethodInterceptor는 Advice를 호출하게 된다.프록시 팩토리를 사용하면 Advice를 호출하는 전용 InvocationHandler나 MethodInterceptor를 내부에서 사용한다.
Q. 특정 조건에 맞을 때 프록시 로직을 적용하는 기능도 공통으로 제공되었으면?
Pointcut이라는 개념으로 이 문제를 일관성 있게 해결한다.Advice는 프록시에 적용하는 부가 기능 로직이다. 이것은 JDK 동적 프록시가 제공하는
InvocationHandler와 CGLIB가 제공하는 MethodInterceptor의 개념과 유사하다. 둘을 개념적으로 추상화 한 것이다. 프록시 팩토리를 사용하면 둘 대신에 Advice를 사용하면 된다
package org.aopalliance.intercept;
@FunctionalInterface
public interface MethodInterceptor extends Interceptor {
@Nullable
Object invoke(@Nonnull MethodInvocation invocation) throws Throwable;
}
MethodInterceptor와 이름이 같으므로 패키지 이름에 주의하자MethodInterceptor는 Interceptor를 상속하고 Interceptor는 Advice 인터페이스를 상속MethodInvocation invocation
public class LogTraceAdvice implements MethodInterceptor {
private final LogTrace logTrace;
public LogTraceAdvice(LogTrace logTrace) {
this.logTrace = logTrace;
}
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
TraceStatus status = null;
try {
Method method = invocation.getMethod();
String message = method.getDeclaringClass().getSimpleName() + "." +
method.getName() + "()";
status = logTrace.begin(message);
//로직 호출
Object result = invocation.proceed();
logTrace.end(status);
return result;
} catch (Exception e) {
logTrace.exception(status, e);
throw e;
}
}
}
Object result = invocation.proceed()
MethodInvocation 안에 target 정보도 들어 있다.// 인터페이스는 JDK 동적 프록시 사용
ServiceInterface target = new ServiceImpl();
ProxyFactory proxyFactory = new ProxyFactory(target);
proxyFactory.addAdvice(new TimeAdvice());
ServiceInterface proxy = (ServiceInterface) proxyFactory.getProxy();
proxy.save();
// 구체 클래스는 CGLIB 프록시 사용
ConcreteService target = new ConcreteService();
ProxyFactory proxyFactory = new ProxyFactory(target);
proxyFactory.addAdvice(new TimeAdvice());
ConcreteService proxy = (ConcreteService) proxyFactory.getProxy();
proxy.call();
new ProxyFactory(target)
proxyFactory.addAdvice(new TimeAdvice())
proxyFactory.getProxy(): 프록시 객체를 생성하고 그 결과를 받는다.assertThat(AopUtils.isAopProxy(proxy)).isTrue();
assertThat(AopUtils.isJdkDynamicProxy(proxy)).isTrue();
assertThat(AopUtils.isCglibProxy(proxy)).isFalse();
ProxyFactory로 프록시를 만들었다면 AopUtils로 어떤 프록시인지 간단하게 확인할 수 있다.proxyFactory.setProxyTargetClass(true);
스프링 부트는 AOP를 적용할 때 기본적으로
proxyTargetClass=true로 설정해서 사용한다. 따라서 인터페이스가 있어도 항상 CGLIB를 사용해서 프록시를 생성한다.
프록시 팩토리의 서비스 추상화 덕분에 구체적인 CGLIB, JDK 동적 프록시 기술에 의존하지 않고, 매우 편리하게 동적 프록시를 생성할 수 있다.

프록시 팩토리를 통해 프록시를 생성할 때 어드바이저를 제공하면 어디에 어떤 기능을 제공할 지 알 수 있다.
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();
new DefaultPointcutAdvisor
Advisor 인터페이스의 가장 일반적인 구현체로 생성자를 통해 하나의 포인트컷과 하나의 어드바이스를 넣어 준다.TimeAdvice는 Advice의 구현체Pointcut.TRUE: 항상 true를 반환하는 포인트컷proxyFactory.addAdvisor(advisor): 프록시 팩토리에 적용할 어드바이저를 지정public interface Pointcut {
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
}
public interface ClassFilter {
boolean matches(Class<?> clazz);
}
public interface MethodMatcher {
boolean matches(Method method, Class<?> targetClass);
//..
}
ClassFilter와 MethodMatcher 둘로 이루어진다. 이름 그대로 하나는 클래스가 맞는지, 하나는 메서드가 맞는지 확인할 때 사용한다. 둘 다 true로 반환해야 어드바이스를 적용할 수 있다.NameMatchMethodPointcut: 메서드 이름 기반으로 매칭. 내부에서 PatternMatchUtils를 사용
ServiceInterface target = new ServiceImpl();
ProxyFactory proxyFactory = new ProxyFactory(target);
NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
pointcut.setMappedNames("save");
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, new TimeAdvice());
proxyFactory.addAdvisor(advisor);
ServiceInterface proxy = (ServiceInterface) proxyFactory.getProxy();
proxy.save();
proxy.find();
PatternMatchUtils.*simpleMatch**save*JdkRegexpMethodPointcut: JDK 정규 표현식 기반으로 매칭TruePointcut: 항상 참을 반환AnnotationMatchingPointcut: 애노테이션으로 매칭AspectJExpressionPointcut: aspectJ 표현식으로 매칭
AspectJExpressionPointcut을 사용하게 된다.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(); ```
addAdvisor()를 통해서 어드바이저를 등록하면 된다.
스프링은 AOP를 적용할 때, 최적화를 진행해서 지금처럼 프록시는 하나만 만들고, 하나의 프록시에 여러 어드바이저를 적용한다. 정리하면 하나의 target 에 여러 AOP가 동시에 적용되어도, 스프링의 AOP는 target 마다 하나의 프록시만 생성한다.
ProxyFactory를 통해 프록시를 빈으로 띄워야 하기 때문이다.