InvocationHandler
를 구현하는 클래스를 생성하고 프록시가 수행해야 하는 로직을 정의한다./**
* 동적으로 생성된 프록시가 실행할 로직 (실행시간 측정)
*/
@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 {
log.info("TimeInvocation start");
long start = System.currentTimeMillis();
Object result = method.invoke(target, args); // 실제객체 호출!!
long end = System.currentTimeMillis();
log.info("TimeInvocation end, time = {}", end-start);
return result;
}
}
InvocationHandler
로 프록시의 동작을 구현했으니 이제 생성하고자 하는 인터페이스의 타입으로 프록시를 생성할 수 있다.// 프록시 생성
Proxy.newProxyInstance(실제객체.getClass().getClassLoader(), new Class[]{인터페이스.class}, invocationHandler);
ConcreteClass concreteClass = new ConcreteClass(); // 실제객체 (구현체만 있는 실제객체)
Enhancer enhancer = new Enhancer();
enhancer.setSupplerClass(ConcreteClass.class); // 프록시를 생성할 구체 클래스를 부모클래스로 지정 (상속을 통해 프록시를 생성)
enhancer.setCallback(new TestMethodInterceptor(concreteClass)); // 프록시가 수행할 로직 (MethodInterceptor 를 구현하는 클래스)
ConcreteClass concreteClass = (ConcreteClass) enhancer.create(); // 프록시 생성
concreteService.call(); // 프록시 호출
두 가지 동적프록시 기술로 프록시를 적용하고자 하는 클래스마다 프록시를 위한 클래스를 정의하는 문제를 해결했다.
- JDK 동적프록시 => 인터페이스가 있는 클래스에만 적용 가능하다.
- CGLIB 동적프록시 => 구체 클래스에 적용 가능하다.
InvocationHandler
, CGLIB => MethodInterceptor
Advice
에 공통으로 두어 위 문제를 해결한다. (Advice 도입)Advice는 프록시가 수행해야 할 로직이다. (로그추적, 실행시간 측정 ...)
org.aopalliance.intercept.MethodInterceptor
구현)public TestAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable{
// ==== 프록시 로직 ==== //
Object result = invocation.proceed();
// ==== 프록시 로직 ==== //
return result;
}
}
// 타겟인스턴스를 기반으로 프록시를 생성한다. (인터페이스가 있는 경우 JDK동적프록시를, 구체클래스만 있는 경우 CGLIB를 사용)
ProxyFactory proxyFactory = new ProxyFactory(타겟인스턴스);
proxyFactory.addAdvice(new TestAdvice()); // Advice 지정 (프록시가 수행할 로직)
Object proxy = proxyFactory.getProxy(); // 프록시 생성
BeanPostProcessor
를 이용해서 컴포넌트 스캔에 의해 등록되는 빈에도 프록시를 적용한다.BeanPostProcessor
인터페이스를 구현하면 스프링 컨테이너에 등록될 빈 객체와 빈의 이름을 받는 메서드를 오버라이딩 할 수 있다.@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
bean
이 프록시 적용 대상이라면 ProxyFactory
를 이용해서 프록시를 생성하고 생성된 프록시를 리턴한다.public class PackageLogTracePostProcessor implements BeanPostProcessor {
private final Advisor advisor;
public PackageLogTracePostProcessor(String basePackage, Advisor advisor) {
this.advisor = advisor;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
ProxyFactory proxyFactory = new ProxyFactory(bean);
proxyFactory.addAdvisor(advisor);
return proxyFactory.getProxy();
}
}
BeanPostProcessor
를 스프링 컨테이너에 올려주면 등록되는 모든 빈에 대해서 적용 가능하다.++ 이전에는 프록시를 적용하고자 하는 Bean마다 설정 클래스에서 실제객체 대신 프록시를 반환하도록 설정해줘야 했다. 이제는 프록시를 생성하는 부분을 설정클래스가 아닌 빈후처리기에 맡기므로 설정클래스에 프록시 생성을 위한 반복되는 코드가 사라졌다.
Advisor
를 생성하는데 여기에는 Pointcut
과 Advice
가 포함된다. 사실 이 두 객체만 있다면 이 빈 후처리기가 어디에 적용되어 대신 프록시를 생성하고 반환하며 어떤 기능을 수행할 지 알 수 있다.Advisor
에 해당하는 프록시를 자동으로 만들어준다. 의존성 추가
implementation 'org.springframework.boot:spring-boot-starter-aop'
Advisor 등록
/**
* Advisor의 Pointcut을 기반으로 프록시 생성 여부를 판단하고 프록시를 생성해서 등록한다.
*/
@Bean
public Advisor advisor1(LogTrace logTrace) {
NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
pointcut.setMappedNames("request*", "order*", "orderItem*");
LogTraceAdvice advice = new LogTraceAdvice(logTrace);
return new DefaultPointcutAdvisor(pointcut, advice);
}
Pointcut
과 Advice
로 Advisor
를 만들어서 Bean으로 등록하는 것으로 프록시 생성을 처리했다.@Aspect
)@Aspect
를 클래스 레벨에 붙여준다.@Aspect
를 찾고 해당 클래스의 포인트컷, 어드바이스 정보로 어드바이저를 생성하고 Bean으로 등록한다.@Aspect
가 붙은 클래스에서 포인트컷과 어드바이스에 대한 정의를 한다.@Aspect // 프록시 자동 생성기(빈 후처리기)는 @Aspect 를 찾고 이를 토대로 Advisor를 생성 및 빈 등록 기능을 한다.
public class LogTraceAspect {
@Around("execution(* hello.proxy.app..*(..))") // 포인트컷
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
// ==== Advice 로직 ==== //
// .... 프록시 부가기능 .... //
Object result = joinPoint.proceed(); // 실제객체 호출
// .... 프록시 부가기능 .... //
return result;
}
}
아래 강의를 100% 참고하여 정리한 내용입니다.
강의자료를 그대로 가져온 것은 아니니 보다 정확한 정보를 원한다면 강의를 들어주세요. (강추!)
인프런 - 스프링 핵심 원리 고급편 (김영한 님)