지난 포스팅에 이어, 이번 포스팅에서는 4) ~ 8)
까지의 내용을 정리한다.
👉 목차는 다음과 같다.
1) 프록시 팩토리 - 소개
2) 프록시 팩토리 - 예제 코드1
3) 프록시 팩토리 - 예제 코드2
4) 포인트컷, 어드바이스, 어드바이저 - 소개
5) 예제 코드1 - 어드바이저
6) 예제 코드2 - 직접 만든 포인트컷
7) 예제 코드3 - 스프링이 제공하는 포인트컷
8) 예제 코드4 - 여러 어드바이저 함께 적용
9) 프록시 팩토리 - 적용1
10) 프록시 팩토리 - 적용2
11) 정리
바로 하나씩 확인해보자.
스프링 AOP를 공부했다면 다음과 같은 단어를 들어보았을 것이다. 항상 잘 정리가 안되는 단어들인데, 단순하지만 중요하니 이번에 확실히 정리해보자.
포인트컷( Pointcut
): 어디에 부가 기능을 적용할지, 어디에 부가 기능을 적용하지 않을지 판단하는 필터링 로직이다. 주로 클래스와 메서드 이름으로 필터링 한다. 이름 그대로 어떤 포인트(Point)에 기능을 적용할지 하지 않을지 잘라서(cut) 구분하는 것이다.
어드바이스( Advice
): 이전에 본 것 처럼 프록시가 호출하는 부가 기능이다. 단순하게 프록시 로직이라 생각하면 된다. (=부가 기능 로직, =프록시 로직)
어드바이저( Advisor
): 단순하게 하나의 포인트컷과 하나의 어드바이스를 가지고 있는 것이다. 쉽게 이야기해서 포인트컷1 + 어드바이스1이다.
정리하면 부가 기능 로직을 적용해야 하는데, 포인트컷으로 어디에? 적용할지 선택하고, 어드바이스로 어떤 로직을 적용할지 선택하는 것이다. 그리고 어디에? 어떤 로직?을 모두 알고 있는 것이 어드바이저이다.
쉽게 기억하기
Advice
)을 어디( Pointcut
)에 할 것인가?Advisor
)는 어디( Pointcut
)에 조언( Advice
)을 해야할지 알고 있다.역할과 책임
이렇게 구분한 것은 역할과 책임을 명확하게 분리한 것이다.
✔️ 참고
어드바이저는 하나의 포인트컷과 하나의 어드바이스를 가지고 있다.
프록시 팩토리를 통해 프록시를 생성할 때 어드바이저를 제공하면 어디에 어떤 기능을 제공할 지 알 수 있다.
new DefaultPointcutAdvisor
: Advisor
인터페이스의 가장 일반적인 구현체이다. 생성자를 통해 하나의 포인트컷과 하나의 어드바이스를 넣어주면 된다. (어드바이저는 하나의 포인트컷과 하나의 어드바이스로 구성된다.)Pointcut.TRUE
: 항상 true
를 반환하는 포인트컷이다. 이후에 직접 포인트컷을 구현해볼 것이다.new TimeAdvice()
: 앞서 개발한 TimeAdvice
어드바이스를 제공한다.proxyFactory.addAdvisor(advisor)
: 프록시 팩토리에 적용할 어드바이저를 지정한다. 어드바이저는 내부에 포인트컷과 어드바이스를 모두 가지고 있다. 따라서 어디에 어떤 부가 기능을 적용해야 할지 어드바이저 하나로 알 수 있다. 프록시 팩토리를 사용할 때 어드바이저는 필수이다.proxyFactory.addAdvice(new TimeAdvice())
이렇게 어드바이저가 아니라 어드바이스를 바로 적용했다. 이것은 단순히 편의 메서드이고 결과적으로 해당 메서드 내부에서 지금 코드와 똑같은 다음 어드바이저가 생성된다. ( DefaultPointcutAdvisor(Pointcut.TRUE, new TimeAdvice())
)save()
, find()
각각 모두 어드바이스가 적용된 것을 확인할 수 있다.이번에는 save()
메서드에는 어드바이스 로직을 적용하지만, find()
메서드에는 어드바이스 로직을 적용하지 않도록 해보자.
물론 과거에 했던 코드와 유사하게 어드바이스에 로직을 추가해서 메서드 이름을 보고 코드를 실행할지 말지 분기를 타도 된다. 하지만 이런 기능에 특화되어서 제공되는 것이 바로 포인트컷이다.
이번에는 해당 요구사항을 만족하도록 포인트컷을 직접 구현해보자.
ClassFilter
와 MethodMatcher
둘로 이루어진다. 이름 그대로 하나는 클래스가 맞는지, 하나는 메서드가 맞는지 확인할 때 사용한다. 둘다 true
로 반환해야 어드바이스를 적용할 수 있다.
👉 일반적으로 스프링이 이미 만들어둔 구현체를 사용하지만 개념 학습 차원에서 간단히 직접 구현해보자.
new DefaultPointcutAdvisor(new MyPointcut(), new TimeAdvice())
: 어드바이저에 직접 구현한 포인트컷을 사용한다.save()
를 호출할 때는 어드바이스가 적용되지만, find()
를 호출할 때는 어드바이스가 적용되지 않는다.
✔️ 코드 설명
MyPointcut
Pointcut
인터페이스를 구현한다.MyMethodMatcher
를 사용한다.MyMethodMatcher
MethodMatcher
이다. MethodMatcher
인터페이스를 구현한다.matches()
: 이 메서드에 method
, targetClass
정보가 넘어온다. 이 정보로 어드바이스를 적용할지 적용하지 않을지 판단할 수 있다."save"
인 경우에 true
를 반환하도록 판단 로직을 적용했다.isRuntime()
, matches(... args)
: isRuntime()
이 값이 참이면 matches(... args)
메서드가 대신 호출된다. 동적으로 넘어오는 매개변수를 판단 로직으로 사용할 수 있다.isRuntime()
이 false
인 경우 클래스의 정적 정보만 사용하기 때문에 스프링이 내부에서 캐싱을 통해 성능 향상이 가능하지만, isRuntime()
이 true
인 경우 매개변수가 동적으로 변경된다고 가정하기 때문에 캐싱을 하지 않는다.그림으로 정리
save()
를 호출한다.Service
클래스의 save()
메서드에 어드바이스를 적용해도 될지 물어본다.true
를 반환한다. 따라서 어드바이스를 호출해서 부가 기능을 적용한다.save()
를 호출한다.find()
를 호출한다.Service
클래스의 find()
메서드에 어드바이스를 적용해도 될지 물어본다.false
를 반환한다. 따라서 어드바이스를 호출하지 않고, 부가 기능도 적용되지 않는다.스프링은 우리가 필요한 포인트컷을 이미 대부분 제공한다.
이번에는 스프링이 제공하는 NameMatchMethodPointcut
를 사용해서 구현해보자.
NameMatchMethodPointcut
을 생성하고 setMappedNames(...)
으로 메서드 이름을 지정하면 포인트컷이 완성된다.save()
를 호출할 때는 어드바이스가 적용되지만, find()
를 호출할 때는 어드바이스가 적용되지 않는다.
스프링이 제공하는 포인트컷
스프링은 무수히 많은 포인트컷을 제공한다. 대표적인 몇가지만 알아보자.
NameMatchMethodPointcut
: 메서드 이름을 기반으로 매칭한다. 내부에서는 PatternMatchUtils
를 사용한다. ( 예) *xxx*
허용 )JdkRegexpMethodPointcut
: JDK 정규 표현식을 기반으로 포인트컷을 매칭한다.TruePointcut
: 항상 참을 반환한다.AnnotationMatchingPointcut
: 애노테이션으로 매칭한다.AspectJExpressionPointcut
: aspectJ 표현식으로 매칭한다.가장 중요한 것은 aspectJ 표현식
AspectJExpressionPointcut
을 사용하게 된다.Pointcut
의 동작 방식과 전체 구조에 집중하자.어드바이저는 하나의 포인트컷과 하나의 어드바이스를 가지고 있다.
만약 여러 어드바이저를 하나의 target
에 적용하려면 어떻게 해야할까?
쉽게 이야기해서 하나의 target
에 여러 어드바이스를 적용하려면 어떻게 해야할까?
지금 떠오르는 방법은 프록시를 여러게 만들면 될 것 같다.
여러 프록시
advisor1
, advisor2
모두 항상 true
를 반환하도록 설정했다. 따라서 둘다 어드바이스가 적용된다. )
여러 프록시의 문제
하나의 프록시, 여러 어드바이저
addAdvisor()
를 통해서 어드바이저를 등록하면 된다.advisor
가 호출된다. 여기서는 advisor2
, advisor1
순서로 등록했다.advice2
, advice1
순서대로 호출된 것을 알 수 있다.
✔️ 중요
target
에 여러 AOP가 동시에 적용되어도, 스프링의 AOP는 target
마다 하나의 프록시만 생성한다. 이부분을 꼭 기억해두자.강의를 듣고 정리한 글입니다. 코드와 그림 등의 출처는 김영한 강사님께 있습니다.