ProxyFactory
인터페이스가 있을때 -> JDK동적프록시 (InvocationHandler)
인터페이스가 없을때 -> CGLIB(MethodInterceptor)
둘다 구현하기는귀찮다. 특정 조건이 맞을 때 프록시 로직을 적용하는 기능도 공통으로 제공되었으면한다.
추상화 레이어인 ProxyFacotry가 생겼고 InvocationHandler, MethodInterceptor 를 추상화한 Advice가 제공된다.
Advice = 조언 , 프록시가 제공하는 부가 기능 로직
Factory안에서 우리가 주입해준 Advice 를 CGLIB,JDK Dynamic을 알아서 구분하여 호출 하게 된다.
ProxyFactory + Advice 예제
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;
}
}
MethodInterceptor는 Advice를 구현하고 있다.
MethodInvocation에 target 클래스의 정보가 모두 포함되어있어 proceed()를 호출하면 target을 호출하는것과 같다.
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)).isFalse();
assertThat(AopUtils.isCglibProxy(proxy)).isTrue();
}
new ProxyFactory에 넘겨준 인스턴스를 보고 인터페이스의 유무에 따라 jdk, CGLIB를 사용하여 프록시를 생성하게 된다.
setProxyTargetClass를 true로 하면 인터페이스가 있어도 CGLIB 를 사용하여 프록시를 생성해준다.
ProxyFactory를 쓰면 AopUtils 스프링이 제공해주는 기능을 쓸 수 있다.
AOP 단어정리
Pointcut = 어떤 포인트(Point)에 기능을 적용할지 하지 않을지 잘라서(cut) 구분하는것
Advice = 프록시가 호출하는 부가기능 로직, 조언!
Advisor = Pointcut + Advice 1세트를 의미함 == 조언자 (어디에 어떤 조언을 해야하는지 알고 있음)
프록시가 일종의 필터역할을 하게 된다. 부가 기능을 적용할지 말지~
예제
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();
}
위에서 proxyFactory.addAdvice(new TimeAdvice()) 를 보면 어드바이스를 바로 적용한것 같지만 내부적으로 들어가면 Pointcut.True를 기본으로 가지고 있다.
즉 내부적으로 advisor 만들고 getProxy 때 proxy를 준것이다.
Pointcut - ClassFilter , MethodMatcher
static class MyPointcut implements Pointcut {
@Override
public ClassFilter getClassFilter() {
return ClassFilter.TRUE;
}
@Override
public MethodMatcher getMethodMatcher() {
return new MyMethodMatcher();
}
}
static class MyMethodMatcher implements MethodMatcher {
private String matchName = "save";
@Override
public boolean matches(Method method, Class<?> targetClass) {
boolean result = method.getName().equals(matchName);
log.info("포인트컷 호출 method={} targetClass={}", method.getName(), targetClass);
log.info("포인트컷 결과 result={}", result);
return result;
}
@Override
public boolean isRuntime() {
return false;
}
@Override
public boolean matches(Method method, Class<?> targetClass, Object... args) {
return false;
}
}
isRuntime이 false 면 위에 메소드가 호출된다. 정적인 정보이기에 캐싱을 할수 있다. 성능상의 장점이 잇다.
true이면 아래 메소드가 호출된다. 매개변수가 동적으로 변경(args) 되기 떄문에 캐싱이 힘들어 성능을 올리기 힘들다.
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(new MyPointcut(), new TimeAdvice());
해당 구현체를 기본 Pointcut대신 넣어주면 적용이된다.
NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
pointcut.setMappedNames("save");
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, new TimeAdvice());
스프링에서 제공해주는 기본 Pointcut 중에 NameMatchMethodPointCut이 존재한다. AspectJ를 쓰면 훨씬 다양하고 정교한 포인트컷을 사용할 수 있다.
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();
}
하나의 target에 하나의 프록시를 생성하고 여러개의 Advisor를 적용하는게 정석이다.
AOP적용수 만큼 프록시가 생성되는 것이 아니다.
실제 앱에서 적용할시에 일일이 @Configuration 파일을 만들어서 ProxyFactory를 통해 proxy를 return 하게 만들어줘야한다.
'WEB > Spring' 카테고리의 다른 글
스프링 핵심 원리 - 고급편 7) @Aspect AOP , 스프링 AOP 개념 (0) | 2022.06.12 |
---|---|
스프링 핵심 원리 - 고급편 6) 빈 후처리기 (0) | 2022.06.09 |
스프링 핵심 원리 - 고급편 4) 동적 프록시 기술 (0) | 2022.06.07 |
스프링 핵심 원리 - 고급편 4) 프록시 패턴과 데코레이터 패턴 (0) | 2022.06.06 |
스프링 핵심원리 고급편 3) 템플릿 메서드 패턴과 콜백 패턴 (0) | 2022.06.06 |