이전 글 링크: https://velog.io/@dailylifecoding/spring-and-java-proxy-usage-1
이전 글에서는 ProxyFactory 를 사용해서 프록시를 편하게 생성하는 방법을 알아봤다.
그런데 여태 하면서 AOP 와 관련된 어떤 용어를 쓴 건 본적이 없을 것이다.
이번에는 AOP 에서 자주 쓰는 용어들과 그 용어와 관련된 구현체들을 사용해서
ProxyFactory 를 사용해보자.
AOP 에는 다양한 용어가 쓰이지만, 이번 실습에서 필요한 3가지만 알고 넘어가겠다.
포인트컷(Pointcut)는 부가기능을 어디에 적용할지를 판단하는 기준이다.
어드바이스(Advice)는 프록시가 사용하는 부가기능(로직)이다.
어드바이저(Advisor)는 1개의 Pointcut + 1개의 Advice 을 하나로 묶은 것이라 생각하면 된다.
지금부터 프록시 부가기능(Advice)
과 프록시 부가기능 적용지점(Pointcut)
을 모두 담고 있는
Advisor
를 ProxyFactory 에 적용해 볼 것이다.
용어는 대충 알았으니 이제 저 용어와 관련된 구현체들을 ProxyFactory 에 적용해보자.
스프링에서는 DefaultPointcutAdvisor 라는 것을 제공해서 편하게
Advisor 를 생성할 수 있다. 한번 ProxyFactory 에 적용해보자.
// 일부 import 생략...
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.Pointcut;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
class AdvisorTest {
@Test
void proxyFactoryWithAdvisorTest() {
User target = new UserImpl();
ProxyFactory proxyFactory = new ProxyFactory(target);
// 1번째 인자. Pointcut.TRUE : 프록시 부가기능이 모든 곳(여기서는 타겟 객체의
// 모든 메소드)에 동작한다.
// 2번째 인자. 부가기능을 의미하는 Advice 인터페이스 구현체를 넣는다.
DefaultPointcutAdvisor advisor
= new DefaultPointcutAdvisor(Pointcut.TRUE, new MyAdvice());
proxyFactory.addAdvisor(advisor);
User proxy = (User) proxyFactory.getProxy();
proxy.say("i am advisor Test");
}
// MethodInterceptor 는 Advice 를 상속하는 인터페이스다.
static class MyAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("MyAdvice intercept [START]");
Object result = invocation.proceed();
System.out.println("MyAdvice intercept [END]");
return result;
}
}
}
출력 결과
MyAdvice intercept [START]
word = i am advisor Test
MyAdvice intercept [END]
위에서는 Pointcut.TRUE
를 사용해서 모든 지점(여기서는 타겟 객체의 모든 메소드)에 프록시 부가기능이 동작하도록 했다. 하지만 Pointcut 을 직접 만들어서 특정 메소드에만
부가기능이 동작하도록 할 수 있다.
Pointcut 인터페이스는 아래와 같은 메소드 시그니처를 갖고 있다.
보면 알겠지만 Class, Method 단위로 필터링 할 수 된다는 것을 알 수 있다.
지금부터 직접 이 Pointcut을 구현하고 적용해보자.
일반적으로는 스프링이 이미 구현한 Pointcut 클래스를 쓰지만,
배워가는 과정이니 직접 만들어보겠다.
static class CustomMethodMatcher implements MethodMatcher {
private String matchingMethodName = "say";
@Override
public boolean matches(Method method, Class<?> targetClass) {
return method.getName().equals(matchingMethodName);
}
@Override
public boolean isRuntime() {
return false;
}
@Override
public boolean matches(Method method, Class<?> targetClass, Object... args) {
throw new UnsupportedOperationException();
}
}
static class MyPointcut implements Pointcut {
/**
* 타겟 객체가 어떤 클래스든 프록시 대상 후보!
*/
@Override
public ClassFilter getClassFilter() {
return ClassFilter.TRUE;
}
/**
* 타겟 객체의 특정 메소드만 프록시의 대상이 되도록 설정
*/
@Override
public MethodMatcher getMethodMatcher() {
return new CustomMethodMatcher();
}
}
@Test
void pointcutTest() {
User target = new UserImpl(1L, "dailyCode", "dailyCode");
ProxyFactory proxyFactory = new ProxyFactory(target);
// NameMatchMethodPointcut 라는 스프링이 이미 구현한 포인트 컷 사용도 가능
DefaultPointcutAdvisor advisor
= new DefaultPointcutAdvisor(new MyPointcut(), new MyAdvice());
proxyFactory.addAdvisor(advisor);
User proxy = (User) proxyFactory.getProxy();
// 프록시 적용
proxy.say("i am advisor Test");
// 프록시 미적용
proxy.whoAmI();
}
참고:
MethodMatcher 인터페이스의 아래 메소드는 주제에서 벗어남으로 설명을 생략하겠다.
isRuntime()
메소드matches(Method method, Class<?> targetClass, Object... args)
메소드다만
isRuntime
의 반환값에 따라 다른matches
가 동작한다는 점만 알자.
- isRuntime 이 true 일 경우 :
matches(Method method, Class<?> targetClass, Object... args)
실행- isRuntime 이 false 일 경우:
matches(Method method, Class<?> targetClass)
실행
출력 결과
MyAdvice intercept [START]
word = i am advisor Test
MyAdvice intercept [END]
i am dailyCode // whoAmI 메소드는 프록시 미적용!!
잠시 프록시 객체의 요청 처리 흐름을 정리하고 넘어가자.
말로 풀자면 이렇다.
위에서 작업한 내용은 사실 아래처럼 간단하게 구현할 수 있다.
이래서 스프링이 이미 구현한 클래스를 사용하는 게 편리하다.
// import org.springframework.aop.support.NameMatchMethodPointcut
NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
pointcut.setMappedName("say");
DefaultPointcutAdvisor advisor
= new DefaultPointcutAdvisor(pointcut, new MyAdvice());
스프링은 이외에도 아래와 같은 포인트 컷을 제공한다.
JdkRegexpMethodPointcut
TruePointcut
AnnotationMatchingPointcut
AspectJExpressionPointcut
==>
(가장 실무에서 많이 보는 녀석)그런데 위처럼 하나의 프록시에 하나의 Advisor 만 적용했다.
이번에는 하나의 proxy 에 1개 이상의 Advisor 를 적용해보자.
ProxyFactory 를 사용하면 이게 가능하다!
가볍게 코드를 짜서 동작하는 것을 확인하고 넘어가자.
// 적용할 부가기능 1
static class MyAdvice1 implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("START of MyAdvice1 Intercept!!!");
Object result = invocation.proceed();
System.out.println("END of MyAdvice1 Intercept!!!");
return result;
}
}
// 적용할 부가기능 2
static class MyAdvice2 implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("START of MyAdvice2 Intercept!!!");
Object result = invocation.proceed();
System.out.println("END of MyAdvice2 Intercept!!!");
return result;
}
}
@Test
void multiAdvisorTest() {
// 포인트컷 하나 생성, say 메소드에만 프록시 적용하도록 하기 위함.
NameMatchMethodPointcut myPointcut = new NameMatchMethodPointcut();
myPointcut.setMappedName("say");
// Advisor 생성 -
DefaultPointcutAdvisor advisor1
= new DefaultPointcutAdvisor(myPointcut, new MyAdvice1());
DefaultPointcutAdvisor advisor2
= new DefaultPointcutAdvisor(Pointcut.TRUE, new MyAdvice2());
// 타겟 객체 생성
UserImpl target = new UserImpl(1L, "dailycode", "dailycode");
// 타겟 객체 정보를 갖는 ProxyFactory 생성
ProxyFactory proxyFactory = new ProxyFactory(target);
// Multi Advisor 적용
proxyFactory.addAdvisor(advisor1);
proxyFactory.addAdvisor(advisor2);
// 프록시 객체 생성
User proxy = (User) proxyFactory.getProxy();
// advisor1, advisor2 모두 적용
proxy.say("wow");
// advisor2 만 적용
proxy.whoAmI();
}
출력 결과
START of MyAdvice1 Intercept!!!
START of MyAdvice2 Intercept!!!
word = wow
END of MyAdvice2 Intercept!!!
END of MyAdvice1 Intercept!!!
START of MyAdvice2 Intercept!!!
i am dailycode
END of MyAdvice2 Intercept!!!
왜 이렇게 동작하는지는 이제 다 알거라고 생각하고 넘어가겠다.
내가 생각하는 Proxy 생성법은 이게 다인 거 같다.
그런데 사실 여태 쓴 코드를 우리가 직접 작성할 일은 거의 없을 것이다.
왜냐하면 스프링 프레임워크가 제공하는 @Aspect
와 자동 프록시 생성기
를
사용할 것이기 때문이다.
하지만 이 자동 프록시 생성기
도 결국은 내부적으로 ProxyFactory
를 사용한다.
그러니 여태 배운 게 헛된 것은 절대로 아니다.
기회가 된다면 다음에는 AOP
뭔지 가볍게 알아보고,
스프링 프레임워크에서 제공하는 @Aspect
와 자동 프록시 생성기
를 사용하는 방법도
테스트 코드와 설명으로 게시물을 작성해보겠다.
아무튼 오늘은 여기까지 😋
에구 힘들다...