Home Spring Framework - 포인트컷
Post
Cancel

Spring Framework - 포인트컷

포인트컷 지시자

포인트컷 표현식은 AspectJ pointcut express 즉, AspectJ가 제공하는 포인트컷 표현식을 줄여서 말하는 것이다.

포인트컷 표현식은 execution과 같은 포인트컷 지시자(Pointcut Designator)로 시작한다.

  • execution
    • 메소드 실행 조인 포인트를 매칭
    • 스프링 AOP에서 가장 많이 사용하고, 기능도 복잡함
  • within
    • 특정 타입 내의 조인 포인트를 매칭
  • args
    • 인자가 주어진 타입의 인스턴스인 조인 포인트
  • this
    • 스프링 빈 객체(스프링 AOP 프록시)를 대상으로 하는 조인 포인트
  • target
    • Target 객체(스프링 AOP 프록시가 가르키는 실제 대상)를 대상으로 하는 조인 포인트
  • @target
    • 실행 객체의 클래스에 주어진 타입의 애노테이션이 있는 조인 포인트
  • @within
    • 주어진 애노테이션이 있는 타입 내 조인 포인트
  • @annotation
    • 메서드가 주어진 애노테이션을 가지고 있는 조인 포인트를 매칭
  • @args
    • 전달된 실제 인수의 런타임 타입이 주어진 타입의 애노테이션을 갖는 조인 포인트
  • bean
    • 빈의 이름으로 포인트컷을 지정
    • 스프링 전용 포인트컷 지시자

보통 execution을 가장 많이 사용하고 다른 지시자는 자주 사용되지 않는다.

execution 기본 사용

1
2
3
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)throws-pattern?)

execution(접근제어자? 반환타입 선언타입?메서드이름(파라미터) 예외?)
  • 메소드 실행 조인 포인트를 매칭
  • ?는 생략가능
  • *과 같은 패턴 지정 가능

가장 정확한 포인트컷

1
2
3
4
5
6
  @Test
  void exactMatch() {
    //public java.lang.String hello.aop.member.MemberServiceImpl.hello(java.lang.String)
    pointcut.setExpression("execution(public String hello.aop.member.MemberServiceImpl.hello(String))");
    Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
  }
  • MemberServiceImpl.hello(String) 메소드와 가장 정확하게 모든 내용이 매칭되는 표현식
  • 매칭 조건
    • 접근제어자? : public
    • 반환타입 : String
    • 선언타입? : hello.aop.member.MemberServiceImpl
    • 메소드이름 : hello
    • 파라미터 : (String)
    • 예외? : 생략

가장 많이 생략한 포인트컷

1
2
3
4
5
@Test
void allMatch() {
  pointcut.setExpression("execution(* *(..))");
  Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
  • ’*‘은 아무 값이 들어와도 된다라는 표현으로, 위 표현식은 모든 메소드에 대해 매칭이 성공함
  • 파라미터에서 ‘..’은 파라이터의 타입과 파라미터 수가 상관없타는 표현
  • 매칭 조건
    • 접근제어자? : 생략
    • 반환타입 : *
    • 선언타입? : 생략
    • 메소드이름 : *
    • 파라미터 : (..)
    • 예외? : 생략

기본 매칭 예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
@Test
void nameMatch() {
  //public java.lang.String hello.aop.member.MemberServiceImpl.hello(java.lang.String)
  pointcut.setExpression("execution(* hello(..))");
  Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}

@Test
void nameMatchStart1() {
  //public java.lang.String hello.aop.member.MemberServiceImpl.hello(java.lang.String)
  pointcut.setExpression("execution(* hel*(..))");
  Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}

@Test
void nameMatchStart2() {
  //public java.lang.String hello.aop.member.MemberServiceImpl.hello(java.lang.String)
  pointcut.setExpression("execution(* *el*(..))");
  Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}

@Test
void nameMatchFalse() {
  //public java.lang.String hello.aop.member.MemberServiceImpl.hello(java.lang.String)
  pointcut.setExpression("execution(* nono(..))");
  Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isFalse();
}

@Test
void packageExactMatch1() {
  //public java.lang.String hello.aop.member.MemberServiceImpl.hello(java.lang.String)
  pointcut.setExpression("execution(* hello.aop.member.MemberServiceImpl.hello(..))");
  Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}

@Test
void packageExactMatch2() {
  //public java.lang.String hello.aop.member.MemberServiceImpl.hello(java.lang.String)
  pointcut.setExpression("execution(* hello.aop.member.*.*(..))");
  Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}

@Test
void packageExactMatchFalse() {
  //public java.lang.String hello.aop.member.MemberServiceImpl.hello(java.lang.String)
  pointcut.setExpression("execution(* hello.aop.*.*(..))");
  Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isFalse();
}

@Test
void packageExactMatchSubPackage1() {
  //public java.lang.String hello.aop.member.MemberServiceImpl.hello(java.lang.String)
  pointcut.setExpression("execution(* hello.aop.member..*.*(..))");
  Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}

@Test
void packageExactMatchSubPackage2() {
  //public java.lang.String hello.aop.member.MemberServiceImpl.hello(java.lang.String)
  pointcut.setExpression("execution(* hello.aop..*.*(..))");
  Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
  • hello.aop.member.(1).(2)
    • (1) : 타입
    • (2) : 메소드 이름
  • 패키지에서 ‘.’ 과 ‘..’의 차이
    • ’.’ : 정학하게 해당 위치의 패키지
    • ’..’ : 해당 위치의 패키지와 그 하위 패키지도 포함

타입 매칭

1
2
3
4
5
6
7
8
@Test
void superTypeExactMatchInternalFalse() throws NoSuchMethodException, SecurityException {
  //public java.lang.String hello.aop.member.MemberServiceImpl.hello(java.lang.String)
  pointcut.setExpression("execution(* hello.aop.member.MemberService.*(..))");

  Method internalMethod = MemberServiceImpl.class.getMethod("internal", String.class);
  Assertions.assertThat(pointcut.matches(internalMethod, MemberServiceImpl.class)).isFalse();
}
  • 부모 타입에 있는 메서드만 허용
    • 부모 타입을 표현식에 선언한 경우 부모 타입에서 선언한 메서드가 자식 타입에 있어야 매칭에 성공

파라미터 매칭

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// String 타입의 파라미터 허용
// (String)
@Test
void argsMatch() {
  //public java.lang.String hello.aop.member.MemberServiceImpl.hello(java.lang.String)
  pointcut.setExpression("execution(* *(String))");
  Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}

// 파라미터가 없어야 함
// ()
@Test
void argsMatchNoArgs() {
  //public java.lang.String hello.aop.member.MemberServiceImpl.hello(java.lang.String)
  pointcut.setExpression("execution(* *())");
  Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isFalse();
}

// 정확히 하나의 모든 타입인 파라미터 허용
// (Xxx)
@Test
void argsMatchNoStar() {
  //public java.lang.String hello.aop.member.MemberServiceImpl.hello(java.lang.String)
  pointcut.setExpression("execution(* *(*))");
  Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}

// 숫자와 무관하게 모든 타입의 모든 파라미터 허용
// 파라미터가 없어도 허용
// (), (Xxx), (Xxx, Xxx), ...
@Test
void argsMatchNoAll() {
  //public java.lang.String hello.aop.member.MemberServiceImpl.hello(java.lang.String)
  pointcut.setExpression("execution(* *(..))");
  Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}

// String 타입으로 시작하고 이후 숫자와 무관하게 모든 타입의 모든 파라미터 허용
// (String), (String, Xxx), (String, Xxx, Xxx), ...
@Test
void argsMatchNoComplex() {
  //public java.lang.String hello.aop.member.MemberServiceImpl.hello(java.lang.String)
  pointcut.setExpression("execution(* *(String, ..))");
  Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
  • execution 파라미터 매칭 규칙
    • (String) : 정확하게 String 타입 파라미터
    • () : 파라미터가 없어야 함
    • (*) : 정확히 하나의 모든타입인 파라미터
    • (_, _) : 정확히 두개의 모든타입인 파라미터
    • (..) : 숫자와 무관하게 모든 타입인 모든 파라미터. 파라미터가 없어도 됨.
    • (String, ..) : String 타입으로 시작하고 이후 숫자와 무관하게 모든 타입인 모든 파라미터
      • (String), (String, Xxx), (String, Xxx, Xxx) 허용

within

특정 타입 내의 조인 포인트들로 매칭을 제한한다. 즉, 해당 타입이 매칭되면 그 안의 메서드(조인 포인트)들이 자동으로 매칭된다. 기본 문법은 execution에서 타입부분과 사용한것과 유사하다.

1
2
3
4
5
@Test
void withinExact() {
  pointcut.setExpression("within(hello.aop.member.MemberServiceImpl)");
  Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}

주의 사항으로는 execution과는 달리 표현식에 부모 타입을 지정하면 안되고 정확하게 타입이 맞아야 한다.

args

인자가 주어진 타입의 인스턴스인 조인 포인트로 매칭한다. 기본 문법은 execution의 args 부분과 같다.

execution과 args의 차이점

  • execution은 파라미터 타입이 정확하게 매칭되어야 함
    • execution은 클래스에 선언된 정보를 기반으로 판당(정적)
  • args는 부모 타입을 허용 함
    • args는 실제 넘어온 파라미터 객체 인스턴스를 보고 판단(동적)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
 * execution(* *(java.io.Serializable)): 메서드의 시그니처로 판단 (정적)
 * args(java.io.Serializable): 런타임에 전달된 인수로 판단 (동적)
 */
@Test
void argsVsExecution() {
  //public java.lang.String hello.aop.member.MemberServiceImpl.hello(java.lang.String)

  // args
  Assertions.assertThat(pointcut("args()")
    .matches(helloMethod, MemberServiceImpl.class)).isFalse();
  Assertions.assertThat(pointcut("args(..)")
    .matches(helloMethod, MemberServiceImpl.class)).isTrue();
  Assertions.assertThat(pointcut("args(*)")
    .matches(helloMethod, MemberServiceImpl.class)).isTrue();
  Assertions.assertThat(pointcut("args(String,..)")
    .matches(helloMethod, MemberServiceImpl.class)).isTrue();
  Assertions.assertThat(pointcut("args(String)")
    .matches(helloMethod, MemberServiceImpl.class)).isTrue();
  Assertions.assertThat(pointcut("args(java.io.Serializable)")
    .matches(helloMethod, MemberServiceImpl.class)).isTrue();
  Assertions.assertThat(pointcut("args(Object)")
    .matches(helloMethod, MemberServiceImpl.class)).isTrue();

  // execution
  Assertions.assertThat(pointcut("execution(* *(String))")
    .matches(helloMethod, MemberServiceImpl.class)).isTrue();
  Assertions.assertThat(pointcut("execution(* *(java.io.Serializable))") //매칭 실패
    .matches(helloMethod, MemberServiceImpl.class)).isFalse();
  Assertions.assertThat(pointcut("execution(* *(Object))")               //매칭 실패
    .matches(helloMethod, MemberServiceImpl.class)).isFalse();
}

agrs 지시자는 단독으로 사용되기 보다는 파라미터 바인딩에서 주로 사용된다.

@target, @within

@target, @within은 다음과 같이 타입에 있는 어노테이션으로 AOP 정용 여부를 판단한다.

  • @target
    • 실행 객체의 클래스에 주어진 타입의 어노테이션이 있는 조인 포인트
    • 인스턴스의 모든 메소드를 조인 포인트로 적용
      • 부모 클래스의 메소드 까지 어드바이스를 적용
    • @target(MyAopAnnotation)
      • 예시) @target(hello.aop.member.annotation.ClassAop)
  • @within
    • 주어진 어노테이션이 있는 타입 내 조인 포인트
    • 해당 타입 내에 있는 메소드만 조인 포인트로 적용
      • 자기 자신의 클래스에 정의된 메소드에만 어드바이스를 적용
    • @within(MyAopAnnotation)
      • 예시) @within(hello.aop.member.annotation.ClassAop)
1
2
3
4
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ClassAop {
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Slf4j
@SpringBootTest
public class AtTargetAtWithinTest {

  ...

  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();
    }
  }
}

@target, @within 지시자는 파라미터 바인딩에서 함꼐 사용된다.

주의 args, @args, @target은 보통 단독으로 사용하면 안된다. 위 예제를 보면 execution(_ hello.app.._(..))을 통해 적용 대상을 제한하였다. 그 이유는, args, @args, @target 지시자는 실제 객체 인스턴스가 생성되고 실행 될떄 어드바이스 적용 여부를 확인할 수 있다. 실행 시점에 일어나는 포인트컷 적용 여부도 결국 프록시가 있어야 실행 시점에 판단할 수 있따. 프록시가 없다면 판단 자체가 불가능 하다. 그런데 스프링 컨테이너가 프록시를 생성하는 시점은 스프링 컨테이너가 만들어지는 어플리케이션 로딩 시점에 적용 할 수 있다. 따라서 args, @args, @target 같은 포인트컷 지시자가 있다면 스프링은 모든 스프링 빈에 AOP를 적용하려고 시도한다. 이와 같이 모든 스프링 빈에 AOP 프록시를 적용하려고 하면 스프링이 내부에서 사용하는 빈 중에서는 final로 지정된 빈들도 있기 떄문에 오류가 발생할 수 있다. 그러므로 args, @args, @target와 같은 표현식은 최대한 프록시 적용 대상을 축소하는 표현식과 함꼐 사용해야 한다.

@annotation

메소드가 주어진 어노테이션을 가지고 있는 조인 포인트를 매칭

1
2
3
4
5
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodAop {
  String value();
}
1
2
3
4
5
6
public class MemberServiceImpl {
  @MethodAop("test value")
  public String hello(String param) {
    return "ok";
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Slf4j
@Import(AtAnnotationTest.AtAnnotationAspect.class)
@SpringBootTest
public class AtAnnotationTest {
  @Slf4j
  @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();
    }
  }

  ...
}

@args

전달된 실제 인수의 런타임이 주어진 타입의 어노테이션을 갖는 조인 포인트. 전달된 인수의 런타임 타입에 @Check 어노테이션이 있는 경우에 매칭한다.

  • @args(test.Check)

@bean

스프링 전용 포인트컷 지시자이며 빈의 이름으로 지정한다. 스프링 비의 이름으로 AOP 적용 여부를 지정한다. @bean은 스프링에서만 사용할 수 있는 특별한 지시자 이다.

  • bean(orderService) bean(*Repository)
    • 과 같은 패턴 사요 가능
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Slf4j
@Import(BeanTest.BeanAspect.class)
@SpringBootTest
public class BeanTest {
  @Autowired
  OrderService orderService;

  @Aspect
  static class BeanAspect {
    @Around("bean(orderService) || bean(*Repository)")
    public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
      log.info("[bean] {}", joinPoint.getSignature());
      return joinPoint.proceed();
    }
  }

  ...
}

매개변수 전달

포인트컷 표현식을 사용하여 어드바이스에 매개변수를 전달할 수 있다.

  • this
  • target
  • args
  • @target
  • @within
  • @annotation
  • @args

일반적으로 아래와 같이 사용한다.

1
2
3
4
@Before("allMember() && args(arg, ..)")
public void logArgs3(String arg) {
  log.info("[logArgs3] arg={}", arg);
}
  • 포인트컷의 이름과 매개변수의 이름을 맞추어야 함
    • 위 예시에선 arg로 맞춤
  • 타입이 메소드에 지정한 타입으로 제한됨
    • 위 예시에선 String으로 매개변수가 선언되었으므로 아와 같이 정의됨
      • args(arg, ..) -> args(String, ..)

다양한 매개변수 전달 예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
@Slf4j
@Import(ParameterTest.ParameterAspect.class)
@SpringBootTest
public class ParameterTest {

  @Autowired
  MemberService memberService;

  @Slf4j
  @Aspect
  static class ParameterAspect {

    @Pointcut("execution(* hello.aop.member..*.*(..))")
    private void allMember() {}

    @Around("allMember()")
    public Object logArgs1(ProceedingJoinPoint joinPoint) throws Throwable {
      Object arg1 = joinPoint.getArgs()[0];
      log.info("[logArgs1]{}, arg={}", joinPoint.getSignature(), arg1);
      return joinPoint.proceed();
    }

    @Around("allMember() && args(arg, ..)")
    public Object logArgs2(ProceedingJoinPoint joinPoint, Object arg) throws Throwable {
      log.info("[logArgs2]{}, arg={}", joinPoint.getSignature(), arg);
      return joinPoint.proceed();
    }

    @Before("allMember() && args(arg, ..)")
    public void logArgs3(String arg) {
      log.info("[logArgs3] arg={}", arg);
    }

    @Before("allMember() && this(obj)")
    public void thisArgs(JoinPoint joinpoint, MemberService obj) {
      log.info("[thisArgs]{}, obj={}", joinpoint.getSignature(), obj.getClass());
    }

    @Before("allMember() && target(obj)")
    public void targetArgs(JoinPoint joinpoint, MemberService obj) {
      log.info("[targetArgs]{}, obj={}", joinpoint.getSignature(), obj.getClass());
    }

    @Before("allMember() && @target(annotation)")
    public void atTarget(JoinPoint joinpoint, ClassAop annotation) {
      log.info("[atTarget]{}, obj={}", joinpoint.getSignature(), annotation);
    }

    @Before("allMember() && @within(annotation)")
    public void atWithin(JoinPoint joinpoint, ClassAop annotation) {
      log.info("[atWithin]{}, obj={}", joinpoint.getSignature(), annotation);
    }

    @Before("allMember() && @annotation(annotation)")
    public void atAnnotation(JoinPoint joinpoint, MethodAop annotation) {
      log.info("[atAnnotation]{}, atAnnotation.value={}", joinpoint.getSignature(), annotation.value());
    }
  }

  @Test
  void success() {
    log.info("memberService proxy={}", memberService.getClass());
    memberService.hello("hello A");
  }
}
memberService Proxy=class hello.aop.member.MemberServiceImpl$$EnhancerBySpringCGLIB$$82
[logArgs1]String hello.aop.member.MemberServiceImpl.hello(String), arg=helloA
[logArgs2]String hello.aop.member.MemberServiceImpl.hello(String), arg=helloA
[logArgs3] arg=helloA
[this]String hello.aop.member.MemberServiceImpl.hello(String), obj=class hello.aop.member.MemberServiceImpl$$EnhancerBySpringCGLIB$$8
[target]String hello.aop.member.MemberServiceImpl.hello(String), obj=class hello.aop.member.MemberServiceImpl

[@target]String hello.aop.member.MemberServiceImpl.hello(String), obj=@hello.aop.member.annotation.ClassAop()
[@within]String hello.aop.member.MemberServiceImpl.hello(String), obj=@hello.aop.member.annotation.ClassAop()
[@annotation]String hello.aop.member.MemberServiceImpl.hello(String),
annotationValue=test value
  • this : 프록시 객체를 전달 받음
  • target : 실제 대상 객체를 전달 받음
  • @target, @within : 타입의 어노테이션을 전달 받음
  • @annotation : 메소드의 어노테이션을 전달 받음

@this, @target

1
2
3
4
5
@Around("this(hello.aop.member.MemberService)")
...

@Around("target(hello.aop.member.MemberService)")
...
  • this
    • 스프링 빈 객체(스프링 AOP 프록시)를 대상으로 하는 조인 포인트
  • target
    • Target 객체(스프링 AOP 프록시가 가르키는 실제 대상)를 대상으로 하는 조인 포인트
  • 두가지 모두 * 과 같은 패턴을 사용할 수 없음
  • 두가지 모두 부모 타입을 허용

this vs target

스프링에서 AOP를 적용하면 실제 target 객체 대신에 프록시 객체가 스프링 빈으로 등록된다.

  • this는 스프링 빈으로 등록되어 있는 프록시 객체를 대상으로 포인트컷을 매칭
  • target은 실제 target 객체를 대상으로 포인트컷을 매칭
프록시 생성 방식에 따른 차이

스프링은 프록시를 생성할떄 JDK 동적 프록시와 CGLIB을 선택할 수 있다. 서로 프록시를 생성하는 방식이 다르기 떄문에 차이가 발생 한다.

  • JDK 동적 프록시는 인터페이스가 필수이고, 인터페이스를 구현한 프록시 객체를 생성
  • CGLIB은 인터페이스가 있더라도 구체 클래스를 상속 받아서 프록시 객체를 생성

JDK 동적 프록시 일떄 this와 target 차이 Alt text

  • MemberService 인터페이스 지정시
    • this(hello.aop.member.MemberService)
      • proxy 객체를 보고 판단
      • this는 부모타입을 허용하기 떄문에 AOP가 적용
    • target(hello.aop.member.MemberService)
      • proxy 객체를 보고 판단
      • target은 부모타입을 허용하기 떄문에 AOP가 적용
  • MemberServiceImpl 구체 클래스 지정시
    • this(hello.aop.member.MemberServiceImpl)
      • proxy 객체를 보고 판단
      • JDK 동적 프록시로 만들어진 proxy 객체는 MemberService 인터페이스를 기반으로 구현된 새로운 클래스 이므로, MemberServiceImpl를 전혀 알지 못하여 AOP 적용 대상이 아님
    • target(hello.aop.member.MemberServiceImpl)
      • proxy 객체를 보고 판단
      • target 객체가 MemberServiceImpl 타입이므로 AOP 적용 대상

CGLIB 일떄 this와 target 차이 Alt text

  • MemberService 인터페이스 지정시
    • this(hello.aop.member.MemberService)
      • proxy 객체를 보고 판단
      • this는 부모타입을 허용하기 떄문에 AOP가 적용
    • target(hello.aop.member.MemberService)
      • proxy 객체를 보고 판단
      • target은 부모타입을 허용하기 떄문에 AOP가 적용
  • MemberServiceImpl 구체 클래스 지정시
    • this(hello.aop.member.MemberServiceImpl)
      • proxy 객체를 보고 판단
      • CGLIB로 만들어진 proxy 객체는 MemberServiceImpl을 상속하여 구현된 클래스 이므로 AOP 적용 대상
        • this가 부모타입을 허용하기 떄문에 포인트컷의 대상이 됨
    • target(hello.aop.member.MemberServiceImpl)
      • proxy 객체를 보고 판단
      • target 객체가 MemberServiceImpl 타입이므로 AOP 적용 대상


즉, 프록시를 대상으로 하는 this의 경우 구체 클래스를 지정하면 프록시 생성 전략에 따라 다른 결과가 나올 수 있음.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
 * application.properties
 * spring.aop.proxy-target-class=true CGLIB
 * spring.aop.proxy-target-class=false JDK 동적 프록시
 */
@Slf4j
@Import(ThisTargetTest.ThisTargetAspect.class)
@SpringBootTest(properties = "spring.aop.proxy-target-class=false") //JDK 동적 프록시
//@SpringBootTest(properties = "spring.aop.proxy-target-class=true") //CGLIB
public class ThisTargetTest {
  @Autowired
  MemberService memberService;

  @Slf4j
  @Aspect
  static class ThisTargetAspect {
  //부모 타입 허용
  @Around("this(hello.aop.member.MemberService)")
  public Object doThisInterface(ProceedingJoinPoint joinPoint) throws Throwable {
    log.info("[this-interface] {}", joinPoint.getSignature());
    return joinPoint.proceed();
  }

  //부모 타입 허용
  @Around("target(hello.aop.member.MemberService)")
  public Object doTargetInterface(ProceedingJoinPoint joinPoint) throws Throwable {
    log.info("[target-interface] {}", joinPoint.getSignature());
    return joinPoint.proceed();
  }

  //this: 스프링 AOP 프록시 객체 대상
  //JDK 동적 프록시는 인터페이스를 기반으로 생성되므로 구현 클래스를 알 수 없음
  //CGLIB 프록시는 구현 클래스를 기반으로 생성되므로 구현 클래스를 알 수 있음
  @Around("this(hello.aop.member.MemberServiceImpl)")
  public Object doThis(ProceedingJoinPoint joinPoint) throws Throwable {
    log.info("[this-impl] {}", joinPoint.getSignature());
    return joinPoint.proceed();
  }

  //target: 실제 target 객체 대상
  @Around("target(hello.aop.member.MemberServiceImpl)")
  public Object doTarget(ProceedingJoinPoint joinPoint) throws Throwable {
    log.info("[target-impl] {}", joinPoint.getSignature());
    return joinPoint.proceed();
  }
  }
}
# JDK 동적 프록시 시용시
memberService Proxy=class com.sun.proxy.$Proxy53
[target-impl] String hello.aop.member.MemberService.hello(String)
[target-interface] String hello.aop.member.MemberService.hello(String)
[this-interface] String hello.aop.member.MemberService.hello(String)
# CGLIB 사용시
memberService Proxy=class hello.aop.member.MemberServiceImpl$$EnhancerBySpringCGLIB$$7df96bd3
[target-impl] String hello.aop.member.MemberServiceImpl.hello(String)
[target-interface] String hello.aop.member.MemberServiceImpl.hello(String)
[this-impl] String hello.aop.member.MemberServiceImpl.hello(String)
[this-interface] String hello.aop.member.MemberServiceImpl.hello(String)

참고 this, target 지시자는 단독으로 사용되기 보다는 파라미터 바인딩에서 주로 사용됨


참고

  • 스프링 핵심 원리 - 고급편(김영한)
This post is licensed under CC BY 4.0 by the author.

Spring Framework - 스프링 AOP 구현

Spring Framework - AOP 구현 예시