이 내용은 인프런 김영한 선생님의 스프링 핵심 원리 - 고급편을 바탕으로 정리한 것입니다.
1. ProxyFactory란?
이 글을 읽기 전 reflection에 대해 알면 좋다.
참고 : https://tech-monster.tistory.com/138
ProxyFactory는 인터페이스로 Proxy를 구성한 경우 혹은 클래스로 Proxy를 구성한 경우 두개의 경우에 일관성 있게 접근할 수 있고, 더욱 편리하게 사용할 수 있는 추상기능을 제공한다.
프록시 팩토리를 사용하면 Advice 를 호출하는 전용 InvocationHandler , MethodInterceptor 를
내부에서 사용한다.
*Advice : 프록시에 적용하는 부가 기능 로직
2. ProxyFactory 구현하기
ServiceInterface.java
package hello.common.service;
public interface ServiceInterface {
void save();
void find();
}
ServiceImpl.java
package hello.common.service;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ServiceImpl implements ServiceInterface{
@Override
public void save() {
log.info("save 호출");
}
@Override
public void find() {
log.info("find 호출");
}
}
TimeAdvice.java
package hello.common.advice;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
@Slf4j
public class TimeAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
log.info("TimeProxy 실행");
long startTime = System.currentTimeMillis();
// Object result = method.invoke(target, args); // call
Object result = invocation.proceed();
long endTime = System.currentTimeMillis();
long resultTime = endTime - startTime;
log.info("TimeProxy 종료 resultTim={}", resultTime);
return result;
}
}
Advice 를 만드는 기본적인 방법은 MethodInterceptor를 구현하면 된다
ProxyFactoryTest.java
package hello.proxyfactory;
import hello.common.advice.TimeAdvice;
import hello.common.service.ConcreteService;
import hello.common.service.ServiceImpl;
import hello.common.service.ServiceInterface;
import lombok.extern.slf4j.Slf4j;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.AopUtils;
import static org.assertj.core.api.Assertions.*;
@Slf4j
public class ProxyFactoryTest {
@Test
@DisplayName("인터페이스가 있으면 JDK 동적 프록시 사용")
void interfaceProxy() {
ServiceInterface target = new ServiceImpl();
ProxyFactory proxyFactory = new ProxyFactory(target);
proxyFactory.addAdvice(new TimeAdvice());
ServiceInterface proxy = (ServiceInterface) proxyFactory.getProxy();
log.info("targetClass={}", target.getClass());
log.info("proxyClass={}", proxy.getClass());
proxy.save();
assertThat(AopUtils.isAopProxy(proxy)).isTrue();
assertThat(AopUtils.isJdkDynamicProxy(proxy)).isTrue();
assertThat(AopUtils.isCglibProxy(proxy)).isFalse();
}
@Test
@DisplayName("구체 클래스만 있으면 CGLIB 사용")
void concreteProxy() {
ConcreteService target = new ConcreteService();
ProxyFactory proxyFactory = new ProxyFactory(target);
proxyFactory.addAdvice(new TimeAdvice());
ConcreteService proxy = (ConcreteService) proxyFactory.getProxy();
log.info("targetClass={}", target.getClass());
log.info("proxyClass={}", proxy.getClass());
proxy.call();
assertThat(AopUtils.isAopProxy(proxy)).isTrue();
assertThat(AopUtils.isJdkDynamicProxy(proxy)).isFalse();
assertThat(AopUtils.isCglibProxy(proxy)).isTrue();
}
@Test
@DisplayName("ProxyTargetClass 옵션을 사용하면 인터페이스가 있어도 CGLIB를 사용하고, 클래스 기반 프록시 사용")
void proxyTargetClass() {
ServiceInterface target = new ServiceImpl();
ProxyFactory proxyFactory = new ProxyFactory(target);
proxyFactory.setProxyTargetClass(true);
proxyFactory.addAdvice(new TimeAdvice());
ServiceInterface proxy = (ServiceInterface) proxyFactory.getProxy();
log.info("targetClass={}", target.getClass());
log.info("proxyClass={}", proxy.getClass());
proxy.save();
assertThat(AopUtils.isAopProxy(proxy)).isTrue();
assertThat(AopUtils.isJdkDynamicProxy(proxy)).isTrue();
assertThat(AopUtils.isCglibProxy(proxy)).isFalse();
}
}
'Spring > 스프링 기본' 카테고리의 다른 글
AOP란? / 스프링 AOP사용 (0) | 2022.04.02 |
---|---|
포인트 컷, 어드바이스, 어드바이저(PointCut, Advice, Advisor) (0) | 2022.03.28 |
오류 처리 / API 예외 처리 (0) | 2022.03.20 |
오류 처리 / Bean Validation (0) | 2022.03.18 |
오류 처리 / validation & bindingResult (0) | 2022.03.18 |