개요
- 나는 Spring AOP를 사용해서 어려가지 기능을 구현했다.
- 하지만, Spring AOP의 정확한 동작원리에 대해서 알지는 못했다.
- 그래서 이번기회에 Spring AOP의 꽃이라고 불리는 Spring Transaction(@Transactional) 코드를 예시로 분석해보면서 Spring AOP 에 대해서 공부해 보려고 한다.
Spring AOP
- Spring Transaction 코드를 보기전에 먼저, Spring AOP 에 대해서 개념을 정리해보자.
- 위의 그림을 보면, 프록시가 여러개의 어드바이저를 가지고 있고, 어드바이저는 1개의 포인트컷과 하나의 어드바이스를 가지고 있다.
- 또한, 프록시는 Target도 가지고 있으며, Target의 메소드 하나하나가 조인포인트가 될 수 있다.
- 포인트컷은 여러개의 조인포인트들을 필터링해가며, 실제로 어드바이스가 적용될 구간을 찾는다.
- 위의 내용을 이해하기 위해서 프록시(Proxy), 어드바이스(Advice), 포인트컷(Pointcut), 어드바이저(Advisor) 에 대해서 개념을 정리해 보자.
프록시(Proxy)
- SampleService라는 클래스가 있다고 가정하자.
- 또한, SampleService에는 아래와 같이 save() 메소드가 있고, 메소드 위에는 @Transactional이라는 애노테이션이 붙어있다.
@Slf4j
public class SampleService {
@Transactional
public void save() {
log.info("sample service save!");
}
}
- 아래의 코드는 프록시 패턴을 사용해서 만들어진 프록시 객체이다.
- @Transactional 에노테이션이 붙은 클래스는 아래와 같은 형태로 프록시 객체가 생성될 것이다. (사실은 더 추상화되어 복잡하다.)
public class TransactionProxy {
private SampleService target;
public void logic() {
//트랜잭션 시작
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
//SampleService 의 로직 수행
target.save();
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
throw new IllegalStateException(e);
}
}
}
- 코드에서 보면 실제 로직 target.save() 가 실행 되기 전후, 트랜잭션 관련 로직이 수행된 것을 확인할 수 있다.
- 이렇게 프록시 객체를 사용해서 실제 타겟 메소드가 호출되기 전후로 원하는 기능을 넣을 수 있다.
- 뒤에서 자세히 설명하겠지만, 위의 코드는 예시일 뿐이고 실제로는 어드바이저만 만들어주면, 자동으로 위와같은 프록시객체를 만들어준다.
어드바이스(Advice)
- Advice는 프록시에 적용하는 부가기능 로직이다.
- 위와 같이 MethodInterceptor 는 interceptor를 상속하고 있고, interceptor는 Advice를 상속하고있다.
- 따라서 Advice를 구현하려면, MethodInterceptor 를 구현하면 된다.
- invoke(MethodInvacation invocation) 메소드의 파라미터인 MethodInvacation에는 다음과 같은 것들이 포함되어있다.
- 다음 메소드를 호출하는 방법
- 현재 프록시 객체 인스턴스, 인수(args), 메소드 정보 등등...
- 뒤에서 자세히 설명하겠지만, @Transactional의 부가기능을 담당하는 Advice는 TransactionInterceptor이며, 이 클래스 또한 MethodInterceptor를 구현하고 있다.
- Advice도 간단한 예시를 들어보자.
@Slf4j
public class SampleAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// 타겟 메소드 실행 전 로직
log.info(invocation.getMethod().getName() + " before!");
// 실제 타겟 메소드 실행
Object result = invocation.proceed();
// 타겟 메소드 실행 후 로직
log.info(invocation.getMethod().getName() + " after!");
return result;
}
}
- 위와 같이 MethodInterceptor를 상속받아서 Advice를 구현할 수 있다.
포인트컷(Pointcut)
- 부가기능을 적용할 수 있는 구간 -> 이것을 조인포인트라고 한다.
참고로, 스프링 AOP의 조인 포인트는 항상 메소드 실행 지점으로 제한된다. -> 프록시 기반이기 때문 -> 프록시는 메소드가 실행되어야 실제 타겟을 호출하기 때문이다.
- 그리고 이런 조인포인트들 중에서 실제로 부가기능이 적용될 구간을 결정(필터링)하는 구분자가 바로 포인트컷이다.
- 주로 클래스와 메소드 이름으로 필터링한다.
- 정리해보면, 포인트컷이 조인 포인트 중에서 어드바이스가 적용될 위치를 선별한다고 생각하면 된다.
- 스프링이 기본적으로 다양한 포인트컷을 이미 제공해 주고 있기 때문에, 포인트컷을 직접 구현할 일은 많이 없다.
- 가장 많이 사용되는 포인트 컷인 AspectJExpressionPointcut에 대해서 알아보자.
- 포인트컷은 크게 ClassFilter 와 MethodMatcher 둘로 이루어진다.
- 따라서 AspectJExpressionPointcut도 ClassFilter, IntroductionAwareMethodMatcher를 상속받고 있는 것을 확인할 수 있다.
- ClassFilter
- 특정 클래스가 포인트컷 표현식에 의해 나타낸 관심 영역에 속하는지 여부를 결정하는 역할을한다.
- matches(Class) 메서드를 정의하며, 이 메서드는 주어진 클래스가 포인트컷 조건에 맞으면 true를 반환하고, 그렇지 않으면 false를 반환한다.
- IntroductionAwareMethodMatcher
- isCandidateForIntroduction(Class) 메서드에서 인터페이스에 대한 메서드 호출을 처리할 수 있는지 여부를 결정한다.
- AspectJExpressionPointcut 외에도 아래와 같은 포인트컷이 주로 사용된다.
- NameMatchMethodPointcut : 메서드 이름을 기반으로 매칭한다. 내부에서는 PatternMatchUtils를 사용한다.
- JdkRegexpMethodPointcut : JDK 정규 표현식을 기반으로 포인트컷을 매칭한다.
- TruePointcut : 항상 참을 반환한다.
- AnnotationMatchingPointcut : 애노테이션으로 매칭한다.
- 포인트 컷은 크게 2가지로 사용된다.
- 애플리케이션 구동단계에서 프록시 적용여부를 판단하는데 사용된다.
- 클래스, 메소드를 조건에 맞는지 체크한 다음 조건에 맞는다면, 프록시 객체를 생성한다. -> AOP를 적용하려면 프록시를 빈으로 등록해야되기 때문이다.
- 뒤에서 언급하겠지만, AnnotationAwareAspectJAutoProxyCreator(빈 후처리기)가 포인트컷을 보고 프록시 객체를 생성할지말지 판단한다.
- 런타임에 어드바이스 적용여부를 판단하는데 사용된다.
- 프록시가 호출 되었을 때 어드바이스를 적용할지 말지 포인트컷을 보고 판단한다.
- 애플리케이션 구동단계에서 프록시 적용여부를 판단하는데 사용된다.
어드바이저(Advisor)
- 하나의 포인트컷 + 하나의 어드바이스 = 어드바이저
- 따라서 어드바이저를 안다는 것은 부가기능(어드바이스)을 어디(포인트컷)에 적용할지 안다는 것 과 같다.
Spring AOP의 프록시, 어드바이스, 포인트컷, 어드바이저에 대해서 알아보았으니, Spring AOP를 구현하는 방법에 대해서 알아보자.
'Spring' 카테고리의 다른 글
Spring AOP 3편 (With Spring 트랜잭션) (0) | 2023.04.20 |
---|---|
Spring AOP 2편 (With Spring Transaction) (0) | 2023.04.20 |
SpringMVC를 이용해서 요청 Body값 Trim처리하기 (0) | 2023.03.06 |
AbstractRoutingDataSource를 통한 Multi-DataSource 구현 (0) | 2023.02.04 |
JDK 동적 프록시 (0) | 2022.08.06 |