≣ 목차
포인트컷( Pointcut )
어디에 어떤 부가 기능(Advice)을 적용할지 결정하는 필터링입니다.
/**
* Core Spring pointcut abstraction.
*
* <p>A pointcut is composed of a {@link ClassFilter} and a {@link MethodMatcher}.
* Both these basic terms and a Pointcut itself can be combined to build up combinations
* (e.g. through {@link org.springframework.aop.support.ComposablePointcut}).
*
* @author Rod Johnson
* @see ClassFilter
* @see MethodMatcher
* @see org.springframework.aop.support.Pointcuts
* @see org.springframework.aop.support.ClassFilters
* @see org.springframework.aop.support.MethodMatchers
*/
public interface Pointcut {
/**
* Return the ClassFilter for this pointcut.
* @return the ClassFilter (never {@code null})
*/
ClassFilter getClassFilter();
/**
* Return the MethodMatcher for this pointcut.
* @return the MethodMatcher (never {@code null})
*/
MethodMatcher getMethodMatcher();
/**
* Canonical Pointcut instance that always matches.
*/
Pointcut TRUE = TruePointcut.INSTANCE;
}
포인트 컷은 두 가지 주요 용도로 사용됩니다. 첫째, 어떤 빈에 프록시를 적용할지 판단하고, 둘째, 어드바이스가 적용될 메서드를 결정하는 것입니다. 이 두 가지 용도는 각각 ClassFilter와 MethodMatcher로 구분됩니다.
ClassFilter
자동 프록시 생성기(AnnotationAwareAspectJAutoProxyCreator)는 포인트 컷을 활용하여 특정 빈이 프록시를 생성해야 하는지 여부를 체크합니다. 이 과정에서 클래스 + 메서드 조건을 모두 비교하며, 모든 메서드에 대해 포인트 컷 조건과 하나하나 매칭해 봅니다. 만약 조건에 맞는 메서드가 하나라도 있으면 해당 빈에 프록시를 생성합니다. 그리고 조건에 맞는 메소드가 하나도 없으면 프록시를 생성하지 않습니다.
MethodMatcher
프록시가 호출될 때는 포인트 컷을 참고하여 부가 기능인 어드바이스를 적용할지 결정합니다. 모든 곳에 프록시를 생성하는 것은 비용이 많이 들기 때문에, 자동 프록시 생성기는 포인트 컷을 통해 필터링하여 어드바이스가 필요할 가능성이 있는 곳에만 프록시를 생성합니다.
어드바이스( Advice )
프록시가 호출하는 부가 기능(접근제어, 캐싱, 로깅)입니다.
어드바이저( Advisor )
하나의 포인트컷과 하나의 어드바이스로 구성되어 있습니다.
쉽게 기억하는 방법
조언자(Advisor)는 어디( Pointcut )에 조언( Advice )을 해야할지 알고 있습니다.
어드바이저 예제 코드 - 어드바이저 직접 적용
public class AdvisorTest {
@Test
void advisorTest1() {
ServiceImpl target = new ServiceImpl(); // 실제 객체 생성
ProxyFactory proxyFactory = new ProxyFactory(target); // 실제 객체로 프록시 객체 생성
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(Pointcut.TRUE, new TimeAdvice());
// Advisor 인터페이스의 일반적인 구현체인 DefaultPointcutAdvisor를 생성
// Pointcut.TRUE는 항상 true를 반환하는 포인트컷으로, 모든 메서드에 어드바이스가 적용
proxyFactory.addAdvisor(advisor);
// 프록시 팩토리에 어드바이저를 추가하여, 해당 어드바이저가 정의한 포인트컷과 어드바이스를 적용한다.
// 어드바이저는 포인트컷과 어드바이스를 모두 포함하고 있어, 어떤 부가 기능을 어디에 적용할지 결정한다.
ServiceInterface proxy = (ServiceInterface) proxyFactory.getProxy();
// 프록시 팩토리로 생성된 프록시 객체 호출
proxy.save(); // 프록시 객체를 통해 실제 메소드 호출
proxy.find();
}
}
@Slf4j
public class ServiceImpl implements ServiceInterface {
@Override
public void save() {
log.info("ServiceImpl.save 호출");
}
@Override
public void find() {
log.info("ServiceImpl.find 호출");
}
}
@Slf4j
public class TimeAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
log.info("TimeAdvice.invoke 실행");
long startTime = System.currentTimeMillis();
Object result = invocation.proceed();
long endTime = System.currentTimeMillis();
long resultTime = endTime - startTime;
log.info("TimeAdvice.invoke 종료 resultTime={}ms", resultTime);
return result;
}
}
스프링이 제공하는 포인트 컷 코드 예제 - NameMatchMethodPointcut
1 포인트 컷 구현체 생성
static class MyPointcut implements Pointcut {
@Override
public ClassFilter getClassFilter() {
return ClassFilter.TRUE;
}
@Override
public MethodMatcher getMethodMatcher() {
return new MyMethodMatcher();
}
}
2 메소드 매칭 구현체 생성
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) {
throw new UnsupportedOperationException();
}
}
3 테스트 코드 작성
@Test
@DisplayName("스프링이 제공하는 포인트컷")
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();
}
NameMatchMethodPointcut이외에 스프링이 제공하는 포인트 컷 클래스를 정리한 내용입니다.
- NameMatchMethodPointcut : 메서드 이름을 기반으로 매칭합니다. 내부에서는 PatternMatchUtils 를 사용합니다.
- JdkRegexpMethodPointcut : JDK 정규 표현식을 기반으로 포인트컷을 매칭합니다.
- TruePointcut : 항상 참을 반환합니다.
- AnnotationMatchingPointcut : 애노테이션으로 매칭합니다.
- AspectJExpressionPointcut : aspectJ 표현식으로 매칭합니다.
가장 사용하기 편리하고, 기능이 많은 AspectJExpressionPointcut을 대표적으로 사용합니다.
어드바이저를 여러 개 등록
여러 개의 어드바이저를 등록하면 어드바이저의 수만큼 프록시가 생성된다고 생각할 수 있지만, 실제로는 그렇지 않습니다. 스프링이 이미 이 부분을 최적화 했으며, 여러 개의 어드바이저를 적용하더라도 프록시는 어드바이저의 수와 관계없이 단 하나만 생성됩니다.
정리하자면 스프링이 제공하는 프록시 팩토리를 사용하면, 하나의 메서드(target)에 여러 AOP(어드바이저)가 적용되더라도 각 메서드마다 하나의 프록시만 생성됩니다.
출처
[1] - https://ttl-blog.tistory.com/863#포인트컷
[2] - 스프링 핵심 원리 고급편 - 김영한
'Spring' 카테고리의 다른 글
[Spring] @Aspect 정리 (0) | 2024.09.26 |
---|---|
[Spring] 빈 후처리기 (0) | 2024.09.24 |
[Spring] 스프링 프록시 팩토리 간단 정리 (0) | 2024.09.19 |
[Spring] Spring Security 6.x버전 달라진 사항 (0) | 2024.09.15 |
[Spring] 트랜잭션 전파 속성 정리 - propagation (0) | 2024.09.02 |