포인트컷 지시자
- 지금부터 포인트컷 표현식을 포함한 포인트컷에 대해서 자세히 알아보자.
aspectJ는 포인트컷을 편리하게 표현하기 위한 특별한 표현식을 제공한다.
ex) @Pointcut("execution(* hello.aop.order..*(..))")
포인트컷 표현식은 execution 같은 포인트컷 지시자(Pointcut Designator)로 시작한다. 줄여서 PCD라 한다.
포인트 컷 지시자의 종류:
• execution : 메소드 실행 조인 포인트를 매칭한다. 스프링 AOP에서 가장 많이 사용하고, 기능도 복잡하다.
• within : 특정 타입 내의 조인 포인트를 매칭한다
• args : 인자가 주어진 타입의 인스턴스인 조인 포인트
• this : 스프링 빈 객체(스프링 AOP 프록시)를 대상으로 하는 조인 포인트
• target : Target 객체(스프링 AOP 프록시가 가르키는 실제 대상)를 대상으로 하는 조인 포인트
• @target : 실행 객체의 클래스에 주어진 타입의 애노테이션이 있는 조인 포인트
• @within : 주어진 애노테이션이 있는 타입 내 조인 포인트
• @annotation : 메서드가 주어진 애노테이션을 가지고 있는 조인 포인트를 매칭
• @args : 전달된 실제 인수의 런타임 타입이 주어진 타입의 애노테이션을 갖는 조인 포인트
• bean : 스프링 전용 포인트컷 지시자, 빈의 이름으로 포인트컷을 지정한다.
포인트컷을 이해하기 위해 예제를 만들어보자.
먼저 아래와 같이 ClassAop, MethodAop라는 두개의 어노테이션을 만든다.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ClassAop {
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodAop {
String value();
}
그리고 아래와 같이 아주 간단한 MemberService인터페이스와 MemberService인터페이스의 구현체인 MemberServiceImpl을 만든다.
public interface MemberService {
String hello(String param);
}
@ClassAop
@Component
public class MemberServiceImpl implements MemberService{
@Override
@MethodAop("test value")
public String hello(String param) {
return "ok";
}
public String internal(String param){
return "ok";
}
}
아래와 같은 테스트를 실행시켜보면
@Slf4j
public class ExecutionTest {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
Method helloMethod;
@BeforeEach
public void init() throws NoSuchMethodException {
// MemberServiceImpl 클래스에서 메서드명이 "hello", 반환타입이 String인 메서드의
// 메타정보를 helloMethod가 참조하도록 한다.
helloMethod = MemberServiceImpl.class.getMethod("hello", String.class);
}
@Test
void printMethod(){
log.info("helloMethod = {}", helloMethod);
}
}
실행결과:
helloMethod = public java.lang.String hello.aop.member.MemberServiceImpl.hello(java.lang.String)
이제 포인트컷 학습을 위한 준비가 되었다.
execution
execution은 아래와 같이 생겼다.
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name- pattern(param-pattern) throws-pattern?)
execution(접근제어자? 반환타입 선언타입?메서드이름(파라미터) 예외?)
• 메소드 실행 조인 포인트를 매칭한다.
• ?는 생략할 수 있다.
• * 같은 패턴을 지정할 수 있다.
코드를 보며 execution을 이해해보자.
♦️ 가장 정확한 포인트컷
MemberServiceImpl.hello(String) 메서드와 가장 정확하게 모든 내용이 매칭되는 표현식은 아래와 같다.
@Slf4j
public class ExecutionTest {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
Method helloMethod;
@BeforeEach
public void init() throws NoSuchMethodException {
helloMethod = MemberServiceImpl.class.getMethod("hello", String.class);
}
@Test
void exactMatch() {
//execution사용. - 가장 정확한 표현식
pointcut.setExpression("execution(public String hello.aop.member.MemberServiceImpl.hello(String))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
// -> 테스트 성공
}
• AspectJExpressionPointcut에 pointcut.setExpression을 통해서 포인트컷 표현식을 적용할 수 있다.
• pointcut.matches(메서드,대상 클래스)를 실행하면 지정한 포인트컷 표현식의 매칭 여부를 true, false로 반환한다.
매칭 조건 :
• 접근제어자? : public
• 반환타입 : String
• 선언타입? : hello.aop.member.MemberServiceImpl
• 메서드이름: hello
• 파라미터 : (String) <- 원래는 java.lang.String처럼 모든 패키지명을 붙여줘야 하지만 String은 자바에서 제공하므로 생략 가능
• 예외? : 생략
♦️ 가장 많이 생략한 포인트컷
@Slf4j
public class ExecutionTest {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
Method helloMethod;
@BeforeEach
public void init() throws NoSuchMethodException {
helloMethod = MemberServiceImpl.class.getMethod("hello", String.class);
}
@Test
void allMatch() {
pointcut.setExpression("execution(* *(..))");// 사실상 모든 메서드에 적용되는 포인트컷이다.
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
// --> 테스트 성공
}
매칭 조건 :
• 접근제어자? : 생략
• 반환타입 : *
• 선언타입? : 생략
• 메서드이름 : *
• 파라미터 : (..)
• 예외? : 없음
• * 은 아무 값이 들어와도 된다는 뜻이다.
• 파라미터에서 .. 은 파라미터의 타입과 파라미터 수가 상관없다는 뜻이다. ( ( 0..* ) 파라미터는 뒤에 자세히 정리하겠다. )
♦️ 메서드 이름 매칭 관련 포인트컷
@Slf4j
public class ExecutionTest {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
Method helloMethod;
@BeforeEach
public void init() throws NoSuchMethodException {
helloMethod = MemberServiceImpl.class.getMethod("hello", String.class);
}
@Test
void nameMatch1() {
pointcut.setExpression("execution(* hello(..))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
@Test
void nameMatchStar1() {
pointcut.setExpression("execution(* hel*(..))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
@Test
void nameMatchStar2() {
pointcut.setExpression("execution(* *el*(..))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
@Test
void nameMatchFalse() {//얘는 매칭 실패
pointcut.setExpression("execution(* nono(..))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isFalse();
}
}
메서드 이름 앞뒤에 *을 사용해서 매칭할 수 있다.
♦️ 패키지 매칭 관련 포인트컷
@Slf4j
public class ExecutionTest {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
Method helloMethod;
@BeforeEach
public void init() throws NoSuchMethodException {
helloMethod = MemberServiceImpl.class.getMethod("hello", String.class);
}
@Test
void packageExactMatch1() {
pointcut.setExpression("execution(* hello.aop.member.MemberServiceImpl.hello(..))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
// --> 테스트 성공
@Test
void packageExactMatch2() {
pointcut.setExpression("execution(* hello.aop.member.*.*(..))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
// --> 테스트 성공
}
hello.aop.member.*(1).*(2)
(1): 타입
(2): 메서드 이름
@Test
void packageExactMatchFalse() {
pointcut.setExpression("execution(* hello.aop.*.*.*(..))"); // 정확히 hello.aop패키지 이어야함
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isFalse();
}
@Test
void packageMatchSubPackage1() {
pointcut.setExpression("execution(* hello.aop.member..*.*(..))"); // hello.aop.member 패키지 이하
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
@Test
void packageMatchSubPackage2() {
pointcut.setExpression("execution(* hello.aop..*.*(..))"); // hello.aop패키지 이하
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
패키지에서 . , .. 의 차이를 이해해야 한다.
. : 정확하게 해당 위치의 패키지
.. : 해당 위치의 패키지와 그 하위 패키지도 포함
♦️ 타입 매칭 - 부모 타입 허용
@Test
void typeExactMatch() {
pointcut.setExpression("execution(* hello.aop.member.MemberServiceImpl.*(..))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
위의 테스트는 당연히 MemberServiceImpl타입으로 포인트 컥으로 지정했으므로 테스트 결과는 성공일 것이다.
하지만 아래는 부모타입을 포인트컷으로 지정하였다.
@Test
void typeMatchSuperType() {
pointcut.setExpression("execution(* hello.aop.member.MemberService.*(..))"); // 부모 인터페이스 타입임에 주목
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
그래도 위의 테스트는 성공한다.
execution에서는 MemberService처럼 부모 타입을 선언해도 그 자식 타입은 매칭된다. 다형성에서 '부모타입 = 자식타입'이 할당 가능하다는 점을 떠올려보면 된다.
하지만 부모타입이라고 해서 자식의 모든 것을 품을 수 있는 것은 아니다. 아래의 MemberServiceImpl을 보면 오버라이드 메서드가 아닌 자신만의 메서드인 internal메서드를 가지고 있는 것을 알 수 있다.
@ClassAop
@Component
public class MemberServiceImpl implements MemberService{
@Override
@MethodAop("test value")
public String hello(String param) {
return "ok";
}
public String internal(String param){
return "ok";
}
}
하여 internal메서드로 테스트해보면 매칭하지 못한다.
@Test
void typeMatchSuperTypeInternal() throws NoSuchMethodException {
pointcut.setExpression("execution(* hello.aop.member.MemberService.*(..))");
Method internalMethod = MemberServiceImpl.class.getMethod("internal", String.class);
assertThat(pointcut.matches(internalMethod, MemberServiceImpl.class)).isFalse();
}
부모 타입을 표현식에 선언한 경우 부모 타입에서 선언한 메서드가 자식 타입에 있어야 매칭에 성공한다. 그래서 부모 타입에 있는 hello(String) 메서드는 매칭에 성공하지만, 부모 타입에 없는 internal(String) 는 매칭에 실패한다.
♦️ 파라미터 매칭
//String 타입의 파라미터 허용
//(String)
@Test
void argsMatch(){
pointcut.setExpression("execution(* *(String))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
// 파라미터 없는 경우
// ()
@Test
void noArgsMatch(){
pointcut.setExpression("execution(* *())");
//MemberServiceImpl안에는 파라미터가 없는 메서드 없음
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isFalse();
}
// 정확히 하나의 파라미터 허용, 모든 타입 허용
// (Xxx)
@Test
void argsMatchStar(){
pointcut.setExpression("execution(* *(*))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
//숫자와 무관하게 모든 파라미터, 모든 타입 허용
//(), (Xxx), (Xxx, Xxx)
@Test
void argsMatchAll(){
pointcut.setExpression("execution(* *(*))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
//String 타입으로 시작, 숫자와 무관하게 모근 파라미터, 모든 타입 허용
//(String), (String, Xxx), (String, Xxx, Xxx)
@Test
void argsMatchComplex(){
pointcut.setExpression("execution(* *(String, ..))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
execution 파라미터 매칭 규칙은 다음과 같다.
• (String) : 정확하게 String 타입 파라미터
• () : 파라미터가 없어야 한다.
• (*) : 정확히 하나의 파라미터, 단 모든 타입을 허용한다.
• (*, *) : 정확히 두 개의 파라미터, 단 모든 타입을 허용한다.
• (..) : 숫자와 무관하게 모든 파라미터, 모든 타입을 허용한다. 참고로 파라미터가 없어도 된다. 0..* 로 이해하면 된다.
• (String, ..) : String 타입으로 시작해야 한다. 숫자와 무관하게 모든 파라미터, 모든 타입을 허용한다. 예) (String) , (String, Xxx) , (String, Xxx, Xxx) 허용
within
within 지시자는 특정 타입 내의 조인 포인트에 대한 매칭을 제한한다. 즉 해당 타입이 매칭되면 그 안의 메서드(조인 포인트)들이 자동으로 매칭된다. 문법은 단순한데 execution 에서 타입 부분만 사용한다고 보면 된다.
코드로 보자.
@Slf4j
public class WithinTest {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
Method helloMethod;
@BeforeEach
public void init() throws NoSuchMethodException {
helloMethod = MemberServiceImpl.class.getMethod("hello", String.class);
}
@Test
void argsMatchComplex(){
pointcut.setExpression("execution(* *(String, ..))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
@Test
void withinExact() {
pointcut.setExpression("within(hello.aop.member.MemberServiceImpl)");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
@Test
void withinStar() {
pointcut.setExpression("within(hello.aop.member.*Service*)");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
@Test
void withinSubPackage() {
pointcut.setExpression("within(hello.aop..*)");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
}
하지만 within은 부모타입으로 지정이 불가능하다. (execution은 위에서 학습한 것처럼 가능)
@Test
@DisplayName("타켓의 타입에만 직접 적용, 인터페이스를 선정하면 안된다.")
void withinSuperTypeFalse() {
pointcut.setExpression("within(hello.aop.member.MemberService)");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isFalse();
}
@Test
@DisplayName("execution은 타입 기반, 인터페이스를 선정 가능.")
void executionSuperTypeTrue() {
pointcut.setExpression("execution(* hello.aop.member.MemberService.*(..))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
args
인자가 주어진 타입의 인스턴스인 조인 포인트로 매칭
기본 문법은 execution의 args 부분과 같다. 하지만 아래와 같은 차이점이 있다.
• execution은 파라미터 타입이 정확하게 매칭되어야 한다. execution은 클래스에 선언된 정보를 기반으로 판단한다.
• args 는 부모 타입을 허용한다. args는 실제 넘어온 파라미터 객체 인스턴스를 보고 판단한다.
아래 테스트를 보자.
@Slf4j
public class ArgsTest {
Method helloMethod;
@BeforeEach
public void init() throws NoSuchMethodException {
helloMethod = MemberServiceImpl.class.getMethod("hello", String.class);
}
private AspectJExpressionPointcut pointcut(String expression) {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(expression);
return pointcut;
}
@Test
void args() {
//hello(String)과 매칭
assertThat(pointcut("args(String)")
.matches(helloMethod, MemberServiceImpl.class)).isTrue();
assertThat(pointcut("args(Object)")
.matches(helloMethod, MemberServiceImpl.class)).isTrue();
assertThat(pointcut("args()")
.matches(helloMethod, MemberServiceImpl.class)).isFalse();
assertThat(pointcut("args(..)")
.matches(helloMethod, MemberServiceImpl.class)).isTrue();
assertThat(pointcut("args(*)")
.matches(helloMethod, MemberServiceImpl.class)).isTrue();
assertThat(pointcut("args(String,..)")
.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
/**
* execution(* *(java.io.Serializable)): 메서드의 시그니처로 판단 (정적) * args(java.io.Serializable): 런타임에 전달된 인수로 판단 (동적)
*/
@Test
void argsVsExecution() {
//Args
assertThat(pointcut("args(String)")
.matches(helloMethod, MemberServiceImpl.class)).isTrue();
assertThat(pointcut("args(java.io.Serializable)")//String implements Serializable
.matches(helloMethod, MemberServiceImpl.class)).isTrue();
assertThat(pointcut("args(Object)")//String extends Serializable
.matches(helloMethod, MemberServiceImpl.class)).isTrue();
//--Execution--
assertThat(pointcut("execution(* *(String))")
.matches(helloMethod, MemberServiceImpl.class)).isTrue();
//매칭 실패
assertThat(pointcut("execution(* *(java.io.Serializable))")//execution에서는 ㄴㄴ
.matches(helloMethod, MemberServiceImpl.class)).isFalse();
//매칭 실패
assertThat(pointcut("execution(* *(Object))")//execution에서는 ㄴㄴ
.matches(helloMethod, MemberServiceImpl.class)).isFalse();
}
}
• pointcut() : AspectJExpressionPointcut 에 포인트컷은 한번만 지정할 수 있다. 이번 테스트에서는 테스트를 편리하게 진행하기 위 해 포인트컷을 여러번 지정하기 위해 포인트컷 자체를 생성하는 메서드를 만들었다.
• 자바가 기본으로 제공하는 String 은 Object , java.io.Serializable 의 하위 타입이다.
• 정적으로 클래스에 선언된 정보만 보고 판단하는 execution(* *(Object)) 는 매칭에 실패한다.
• 동적으로 실제 파라미터로 넘어온 객체 인스턴스로 판단하는 args(Object) 는 매칭에 성공한다. (부모 타입 허용)
@target, @within
@target : 실행객체의 클래스에 주어진 타입의 어노테이션이 있는 조인 포인트
@within : 주어진 어노테이션이 있는 타입 내 조인 포인트
@target, @within 은 아래와 같이 타입이 있는 어노테이션으로 AOP적용 여부를 판단한다.
@target(hello.aop.member.annotation.ClassAop)
@within(hello.aop.member.annotation.ClassAop)
@target, @within의 차이 :
@target은 인스턴스의 모든 메서드를 조인 포인트로 적용한다.
@within은 해당 타입 내에 있는 메서드만 조인 포인트로 적용한다.
즉 @target은 부모 클래스의 메서드까지 어드바이스를 다 적용하고, @within은 자기 자신의 클래스에 정의된 메서드에만 어드바이스를 적용한다.
아래는 테스트.
@Slf4j
@Import({AtTargetAtWithinTest.Config.class})
@SpringBootTest
public class AtTargetAtWithinTest {
@Autowired
Child child;
@Test
void success() {
log.info("child Proxy={}", child.getClass());
child.childMethod(); //부모, 자식 모두 있는 메서드 child.parentMethod(); //부모 클래스만 있는 메서드
}
static class Config { //Child, AtTargetAtWithinAspect을 빈으로 등록
@Bean
public Child child() {
return new Child();
}
@Bean
public AtTargetAtWithinAspect atTargetAtWithinAspect() {
return new AtTargetAtWithinAspect();
}
}
static class Parent {
public void parentMethod() {} //부모에만 있는 메서드
}
@ClassAop
static class Child extends Parent {
public void childMethod() {}
}
@Slf4j
@Aspect
static class AtTargetAtWithinAspect {
//@target: 인스턴스 기준으로 모든 메서드의 조인 포인트를 선정, 부모 타입의 메서드도 적용
@Around("execution(* hello.aop..*(..)) && @target(hello.aop.member.annotation.ClassAop)")
public Object atTarget(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("[@target] {}", joinPoint.getSignature());
return joinPoint.proceed();
}
//@within: 선택된 클래스 내부에 있는 메서드만 조인 포인트로 선정, 부모 타입의 메서드는 적용되지 않음
@Around("execution(* hello.aop..*(..)) && @within(hello.aop.member.annotation.ClassAop)")
public Object atWithin(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("[@within] {}", joinPoint.getSignature());
return joinPoint.proceed();
}
}
}
실행결과:
child Proxy=class hello.aop.pointcut.AtTargetAtWithinTest$Child$$EnhancerBySpringCGLIB$$ca36027e
[@target] void hello.aop.pointcut.AtTargetAtWithinTest$Child.childMethod()
[@within] void hello.aop.pointcut.AtTargetAtWithinTest$Child.childMethod()
[@target] void hello.aop.pointcut.AtTargetAtWithinTest$Parent.parentMethod()
parentMethod() 는 Parent 클래스에만 정의되어 있고, Child 클래스에 정의되어 있지 않기 때문에 @within 에서 AOP 적용 대상이 되지 않는다.
실행결과를 보면 child.parentMethod() 를 호출 했을 때 [@within] 이 호출되지 않은 것을 확인할 수 있다.
참고
@target , @within 지시자는 뒤에서 설명할 파라미터 바인딩에서 함께 사용된다.
주의
다음 포인트컷 지시자는 단독으로 사용하면 안된다. args, @args, @target .
이번 예제를 보면 execution(* hello.aop..*(..)) 를 통해 적용 대상을 줄여준 것을 확인할 수 있다. args , @args , @target 은 실제 객체 인스턴스가 생성되고 실행될 때 어드바이스 적용 여부를 확인할 수 있다. 실행 시점에 일어나는 포인트컷 적용 여부도 결국 프록시가 있어야 실행 시점에 판단할 수 있다. 프록시가 없다면 판단 자체가 불가능하다. 그런데 스프링 컨테이너가 프록시를 생성하는 시점은 스프링 컨테이너가 만들어지는 애플리케이션 로딩 시점에 적용할 수 있다. 따라서 args , @args , @target 같은 포인트컷 지시자가 있으면 스프링은 모든 스프링 빈에 AOP를 적용하려고 시도한다. 앞서 설명한 것 처럼 프록시가 없으면 실행 시점에 판단 자체가 불가능하다. 문제는 이렇게 모든 스프링 빈에 AOP 프록시를 적용하려고 하면 스프링이 내부에서 사용하는 빈 중에는 final 로 지정된 빈들도 있기 때문에 오류가 발생할 수 있다. 따라서 이러한 표현식은 최대한 프록시 적용 대상을 축소하는 표현식과 함께 사용해야 한다.
@annotation, @args
@annotation : 메서드가 주어진 어노테이션을 가지고 있는 조인 포인트를 매칭
ex) @annotation(hello.aop.member.annotation.MethodAop)
다음과 같이 메서드(조인 포인트)에 어노테이션이 있으면 매칭한다. (해당 포스트 맨 위에 직접 만든 어노테이션과 MemberServiceImpl참고)
public class MemberServiceImpl {
@MethodAop("test value")
public String hello(String param) {
return "ok";
}
}
이를 테스트해보자.
@Slf4j
@SpringBootTest
public class AnnotationTest {
@Autowired
MemberService memberService;
@Test
void success(){
log.info("memberService Proxy = {}", memberService.getClass());
memberService.hello("helloA");
}
@Slf4j
@Component
@Aspect
static class AtAnnotationAspect{
@Around("@annotation(hello.aop.member.annotation.MethodAop)")
public Object doAtAnnotation(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("[@annotation] {}", joinPoint.getSignature());
return joinPoint.proceed();
}
}
}
실행결과:
memberService Proxy = class hello.aop.member.MemberServiceImpl$$EnhancerBySpringCGLIB$$f95ff287
[@annotation] String hello.aop.member.MemberServiceImpl.hello(String)
@args
전달된 실제 인수의 런타임 타입이 주어진 타입의 어노테이션을 갖는 조인 포인트
-> 전달된 인수의 런타임 타입에 @Check 어노테이션이 있는 경우에 매칭한다.
ex) @args(test.Check)
bean
스프링 전용 포인트컷 지시자, 빈의 이름으로 지정한다.
• 스프링 빈의 이름으로 AOP 적용 여부를 지정한다. 이것은 스프링에서만 사용할 수 있는 특별한 지시자이다.
• bean(orderService) || bean(*Repository)
• *과 같은 패턴을 사용할 수 있다.
이를 테스트해보자.
@Slf4j
@Import(BeanTest.BeanAspect.class)
@SpringBootTest
public class BeanTest {
@Autowired OrderService orderService;
@Test
void success(){
orderService.orderItem("ItemA");
}
@Slf4j
@Aspect
static class BeanAspect{
@Around("bean(orderService) || bean(*Repository)")
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("[bean] {}", joinPoint.getSignature());
return joinPoint.proceed();
}
}
}
실행결과:
[bean] String hello.aop.order.OrderService.orderItem(String)
[orderService] 실행
[bean] String hello.aop.order.OrderRepository.save(String)
김영한님의 인프런 강의와 PDF를 바탕으로 정리하였습니다.
'Spring > spring AOP' 카테고리의 다른 글
스프링 코어 2 - 스프링 AOP 실전 예제 (0) | 2022.01.16 |
---|---|
스프링 코어 2 - 포인트컷-매개변수 전달, this와 target (0) | 2022.01.16 |
스프링 코어 2 - 어드바이스 종류 (0) | 2022.01.14 |
스프링 코어 2 - AOP구현(포인트컷 분리, 어드바이스 추가 등...) (0) | 2022.01.14 |
스프링 코어 2 - AOP 개념 (0) | 2022.01.12 |