본문 바로가기
Spring

[Spring AOP] 포인트컷, 어드바이스, 어드바이저

by 감자b 2024. 12. 28.

스프링 AOP에서 빠질 수 없는 개념으로 포인트컷, 어드바이스, 어드바이저가 있다.

 

포인트컷(Pointcut): 어디에 부가 기능을 적용할지, 적용하지 않을 것인지 판단하는 필터링 로직

  • 주로 클래스와 메서드 이름으로 필터링한다.
public interface Pointcut {
     ClassFilter getClassFilter();
     MethodMatcher getMethodMatcher();
}

포인트컷은 크게 둘로 이루어진다.

  • ClassFilter : 적용하려는 클래스가 맞는지 확인
  • MethodMatcher : 적용하려는 메서드가 맞는지 확인일반적으로는 스프링이 제공하는 구현체를 사용한다.
  • 둘 다 true 로 반환해야 어드바이스를 적용할 수 있다.
  • 일반적으로는 스프링이 제공하는 구현체를 사용한다.

 

어드바이스(Advice): 프록시에 적용하는 부가 기능(프록시 로직)

public interface MethodInterceptor extends Interceptor {
     Object invoke(MethodInvocation invocation) throws Throwable;
}

MethodInterceptor는 Interceptor를 상속하고 Interceptor는 Advice 인터페이스를 상속하므로 위 인터페이스를 구현하면 Advice를 만들 수 있다.

invoke() 메서드 내부에는 MethodInvocation invocation를 매개변수로 받는데 내부에는 다음 메서드를 호출하는 방법이나 현재 프록시 객체 인스턴스, args, 메서드 정보 등 많은 정보가 포함되어 있다.

즉 JDK 동적 프록시의 핸들러나, CGLIB의 인터셉터에서 매개변수로 제공되는 부분들이 모두 포함된 객체라고 생각하면 된다.

@Slf4j
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={}ms", resultTime); 
		return result;
	}
}

 

어드바이저(Advisor): 하나의 포인트컷과 하나의 어드바이스를 가진 오브젝트

@Test
void advisorTest() {
     ServiceImpl target = new ServiceImpl();
     ProxyFactory proxyFactory = new ProxyFactory(target);
     NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
     pointcut.setMappedNames("save");
     DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, new TimeAdvice());
     proxyFactory.addAdvisor(advisor);
     ServiceInterface proxy = (ServiceInterface) proxyFactory.getProxy();
     proxy.save();
     proxy.find();
 }

위 코드의 흐름은 다음과 같다.

  1. 프록시 팩토리를 생성
  2. NameMatchMethodPointcut는 스프링이 제공하는 Pointcut의 구현체로 메서드 이름을 가지고 어드바이스 적용 여부를 확인한다.
    • 위 코드에서는 setMappedNames("save")를 통해 save만 어드바이스가 적용되도록 하였다.
    • 따라서 save 메서드는 어드바이스가 적용되지만 find는 적용되지 않는다.
  3. DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, new TimeAdvice())
    • 하나의 포인트컷과 하나의 어드바이스를 가진 어드바이저를 생성한다.
  4. proxyFactory.addAdvisor(advisor)
    • 프록시 팩토리에 적용할 어드바이저를 지정한다.
    • 어드바이저는 내부에 포인트컷과 어드바이스를 모두 가지고 있으므로 따라서 어디에 어떤 부가 기능을 적용해야 할 지 알 수 있다.
    • 프록시 팩토리를 사용할 때 어드바이저는 필수
  5. 프록시 팩토리를 통해 프록시 생성
  6. 프록시의 save, find 메서드 호출
    • save는 포인트컷이 true이므로 어드바이스 적용, find는 포인트컷이 false이므로 어드바이스가 적용되지 않음
  7. 실제 타겟의 메서드 호출

 

어드바이저는 하나의 포인트컷과 하나의 어드바이스를 가진다고 했는데, 만약 대상 객체(target)에 여러 어드바이스를 적용하려면 어떻게 해야할까?

스프링은 하나의 프록시 팩토리에 여러 어드바이저를 적용할 수 있도록 하였다.

@Test
void multiAdvisorTest() {
     DefaultPointcutAdvisor advisor2 = new DefaultPointcutAdvisor(Pointcut.TRUE, new Advice2());
     DefaultPointcutAdvisor advisor1 = new DefaultPointcutAdvisor(Pointcut.TRUE, new Advice1());
     ServiceInterface target = new ServiceImpl();
     ProxyFactory proxyFactory1 = new ProxyFactory(target);
     proxyFactory1.addAdvisor(advisor2);
     proxyFactory1.addAdvisor(advisor1);

     ServiceInterface proxy = (ServiceInterface) proxyFactory1.getProxy();
     //proxy -> advisor2 -> advisor1 -> target
     proxy.save();
}

여러 어드바이저를 적용하고 싶다면 프록시 팩토리에 addAdvisor()를 통해서 여러 어드바이저를 등록하면 된다. 등록하는 순서대로 advisor가 호출된다.

→ AOP 적용 수 만큼 프록시가 생성되는 것이 아니라 프록시는 하나만 만들고, 하나의 프록시에 여러 어드바이저를 적용한다.

 

 


스프링이 제공하는 포인트컷

  • NameMatchMethodPointcut : 메서드 이름을 기반으로 매칭
    • 내부에서는 PatternMatchUtils를 사용 → xxx 허용
  • JdkRegexpMethodPointcut : JDK 정규 표현식을 기반으로 포인트컷 매칭
  • TruePointcut : 항상 참 반환
  • AnnotationMatchingPointcut : 애노테이션으로 매칭
  • AspectJExpressionPointcut : aspectJ 표현식으로 매칭

참고

 

스프링 핵심 원리 - 고급편 강의 | 김영한 - 인프런

김영한 | 스프링의 핵심 원리와 고급 기술들을 깊이있게 학습하고, 스프링을 자신있게 사용할 수 있습니다., 핵심 디자인 패턴, 쓰레드 로컬, 스프링 AOP스프링의 3가지 핵심 고급 개념 이해하기

www.inflearn.com

 

'Spring' 카테고리의 다른 글

[Spring AOP] 스프링 AOP와 용어  (0) 2024.12.28
[Spring AOP] 빈 후처리기  (0) 2024.12.28
[Spring AOP] 프록시 팩토리  (0) 2024.12.28
[Spring AOP] 동적 프록시  (0) 2024.12.28
[Spring] ThreadLocal  (0) 2024.12.28