[AOP] 스프링 프록시와 프록시 팩토리
- 프록시 패턴을 사용할 때 프록시 클래스를 직접 생성한다면 엄청나게 많은 클래스를 개발자가 직접 생성해야 할 것이다
- 동적 프록시 기술을 사용한다면 개발자가 직접 프록시 클래스를 생성할 필요 없이 런타임시 알아서 생성해서 사용할 수 있다
1. JDK Dynamic 프록시
- 인터페이스 기반 프록시
-
리플렉션
으로 프록시 객체를 생성
public interface AInterface {
String call();
}
@Slf4j
public class AImpl implements AInterface {
@Override
public String call() {
log.info("A 호출");
return "a";
}
}
-
InvocationHandler
인터페이스 : 프록시에 적용할 공통 로직을 구현
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
-
Object proxy
: 프록시 자신 -
Method method
: 호출한 메서드 -
Object[] args
: 메서드 호출시 전달되는 인수
@Slf4j
public class TimeInvocationHandler implements InvocationHandler {
private final Object target;
public TimeInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = method.invoke(target, args);
long endTime = System.currentTimeMillis();
long resultTime = endTime - startTime;
log.info("TimeProxy 종료 resultTime={}", resultTime);
return result;
}
}
-
Object target
: 프록시가 호출할 대상 -
invoke(target, args)
: target 인스턴스의 메서드 실행 -
TimeInvocationHandler(Object target)
: 생성자로 타겟을 넘겨서 프록시 생성 - 프록시 사용
- Proxy.newProxyInstance() : 동적 프록시 생성
-
클래스로더
,인터페이스
,핸들러
를 파라미터로 입력
public class Main {
public static void main(String[] args) {
Ainterface target = new AImpl();
TimeInvocationHandler handler = new TimeInvocationHandler(target);
AInterface proxy = (AInterface) Proxy.newProxyInstance(AInterface.class.getClassLoader(),
new Class[] {AInterface.class}, handler);
proxy.call();
}
}
- 동적으로 생성된 프록시를 통해 입력된 핸들러를 실행
- 핸들러는 공통로직을 수행하고 핵심로직을
invoke()
로 실행
2. CGLIB 프록시 (기본)
- 구체 클래스 기반 프록시 (인터페이스가 없어도 프록시를 생성할 수 있다)
- CGLIB (Code Generator LIBrary) : 바이트코드를 조작해서 동적으로 클래스(프록시)를 생성하는 라이브러리
- 스프링부트 2.0부터 CGLIB가 default
@Slf4j
public class AService {
public String call() {
log.info("A 호출");
return "a";
}
}
-
MethodInterceptor
인터페이스 : 프록시에 적용할 공통 로직을 구현org.springframework.cglib.proxy.MethodInterceptor
public interface MethodInterceptor {
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable;
}
-
Object ojb
: 프록시가 적용될 객체 -
Method method
: 호출한 메서드 -
Object[] args
: 메서드 호출시 전달되는 인수 -
MethodProxy proxy
: 메서드 호출에 사용
import org.springframework.cglib.proxy.MethodInterceptor;
@Slf4j
public class TimeInvocationHandler implements MethodInterceptor {
private final Object target;
public TimeInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = proxy.invoke(target, args);
long endTime = System.currentTimeMillis();
long resultTime = endTime - startTime;
log.info("TimeProxy 종료 resultTime={}", resultTime);
return result;
}
}
-
Object target
: 프록시가 호출할 대상 -
invoke(target, args)
: target 인스턴스의 메서드 실행 -
TimeInvocationHandler(Object target)
: 생성자로 타겟을 넘겨서 프록시 생성 - 프록시 사용
-
Enhancer
: CGLIB에서 프록시를 생성하기 위한 클래스 -
create()
: 동적프록시 생성
-
public class Main {
public static void main(String[] args) {
AService target = new AService();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(ConcreteService.class);
enhancer.setCallback(new TimeMethodInterceptor(target));
AService proxy = (AService) enhancer.create();
proxy.call();
}
}
프록시 팩토리 (ProxyFactory)
- 스프링에서는 특정 조건에 맞을 시 해당 조건에 맞는 프록시 로직을 적용할 수 있도록 추상화 되어있다
-
ProxyFactory
를 사용해 동적 프록시를 통합해서 편리하게 만들 수 있다- JDK 동적 프록시 :
InvocationHandler
사용 - CGLIB 프록시 :
MethodInterceptor
사용
- JDK 동적 프록시 :
Advice
- 공통 기능이 들어있는 로직
-
Advice로 공통 로직을 구현하면
InvocationHandler
이나MethodInterceptor
가 호출되어 공통 로직을 수행한다 - 공통로직 구현
-
MethodInterceptor
구현org.aopalliance.intercept.MethodInterceptor
-
MethodInvocation
: target의 정보가 모두 포함되어 있는 클래스 -
proceed()
: target 인스턴스의 메서드 실행
-
import org.aopalliance.intercept.MethodInterceptor;
@Slf4j
public class TimeAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = invocation.proceed();
long endTime = System.currentTimeMillis();
long resultTime = endTime - startTime;
log.info("TimeProxy 종료 resultTime={}", resultTime);
return result;
}
}
- 프록시 사용
-
ProxyFactory(target)
: 생성자로 타겟을 넘겨 프록시팩토리 생성 -
addAdvice()
: 구현한 공통로직 설정 -
getProxy()
: 팩토리에서 생성된 프록시객체 반환
-
public class Main {
public static void main(String[] args) {
AService target = new AService();
ProxyFactory proxyFactory = new ProxyFactory(target);
// Advice만 적용
// proxyFactory.addAdvice(new TimeAdvice());
// Advisor 적용 (Advice + Pointcut)
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* hello.proxy.aop..*(..))");
proxyFactory.addAdvisor(new DefaultPointcutAdvisor(pointcut, new TimeAdvice()));
AService proxy = (AService) proxyFactory.getProxy();
proxy.call();
}
}
스프링에서 일반적으로 프록시를 사용할 때는 @Aspect
를 사용한다
Spring AOP 링크