본문 바로가기
Spring|Spring-boot/Spring AOP

Spring AOP @target, @within

by oncerun 2022. 1. 29.
반응형

 

@target : 실행 객체의 클래스에 주어진 타입의 애노테이션이 있는 조인 포인트

@within : 주어진 애노테이션이 있는 타입 내 조인 포인트

 

이 둘은 사용자 정의 애노테이션으로 AOP 적용 여부를 판단한다.

 

Class, Method에 붙일 애노테이션을 준비하자.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ClassAOP {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ClassAOP {
}

 

@target과 @within의 차이

 - @target은 인스턴스의 모든 메서드를 조인 포인트로 적용한다.

 - @within은 해당 타입 내에 있는 메서드만 조인 포인트로 적용한다.

 

즉 @target은 부모 타입의 메서드까지 AOP가 적용되지만, @within은 해당 타입의 메서드에만 적용한다고 생각하면 된다.

 

 

기존처럼 포인트 컷으로 실제 인스턴스를 만들지 않고 클래스에서 메서드 정보를 가져와 테스트를 진행하려고 하였는데, 

@target, @within은 실제 인스턴스를 통해 판단하기 때문에 springbootTest를 진행할 것이다.

 

1. 부모와 자식 객체를 만든다.

    @Autowired
    Tesla tesla;

    @Autowired
    Car car;
    
static class Car{
    public void drive(){};
}

@ClassAOP
static class Tesla extends Car{
    public void autoDrive(){};
}

 

2. Aspect를 만든다.

@Aspect
static class TargetWithinAspect {

    @Around("execution(* hello.aop..*(..)) && @target(hello.aop.example.annotation.ClassAOP) ")
    public Object atTarget(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("@target {}", joinPoint.getSignature());
        return joinPoint.proceed();
    }

    @Around("execution(* hello.aop..*(..)) && @within(hello.aop.example.annotation.ClassAOP) ")
    public Object atWithin(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("@within {}", joinPoint.getSignature());
        return joinPoint.proceed();
    }
}

 

3. 스프링 빈으로 등록한다.

static class ExampleConfig {
    @Bean
    public Car car() {
        return new Car();
    }

    @Bean
    public Tesla tesla() {
        return new Tesla();
    }

    @Bean
    public TargetWithinAspect targetWithinAspect() {
        return new TargetWithinAspect();
    }

}

 

[ 기댓값 ]

@target 애노테이션으로 처리한 어드바이저는 부모의 메서드인 drive()와 자식 메서드인 autoDrive()까지 적용되길 기대

@within 애노테이션은 오직 자식 메서드인 autoDrive()에만 적용되길 기대

AOP가 적용되는 Spring Bean인 Tesla는 Proxy가 적용되어야 하는데 인터페이스가 없으니 CGLIB 프록시가 적용되어야 한다.

또한 @target은 부모의 메서드도 호출한다 했으니 Car객체도 Proxy적용대상이 된다. 따라서 CGLIB 프록시가 적용되어야 한다.

 

@Test
void atTest() {

    log.info(" tesla Proxy {}", tesla.getClass());
    log.info(" car Proxy {}", car.getClass());

    tesla.autoDrive();
    tesla.drive();
}
hello.aop.pointcut.TargetAndWithinTest   :  tesla Proxy class hello.aop.pointcut.TargetAndWithinTest$Tesla$$EnhancerBySpringCGLIB$$7a879001
hello.aop.pointcut.TargetAndWithinTest   :  car Proxy class hello.aop.pointcut.TargetAndWithinTest$Car$$EnhancerBySpringCGLIB$$dd700b17
hello.aop.pointcut.TargetAndWithinTest   : @target void hello.aop.pointcut.TargetAndWithinTest$Tesla.autoDrive()
hello.aop.pointcut.TargetAndWithinTest   : @within void hello.aop.pointcut.TargetAndWithinTest$Tesla.autoDrive()
hello.aop.pointcut.TargetAndWithinTest   : @target void hello.aop.pointcut.TargetAndWithinTest$Car.drive()

 

 

 

주의

@Around("@target(hello.aop.example.annotation.ClassAOP) ")

 

만약 @target을 단독으로 사용했다고 가정해보자.  @target은 실제 인스턴스가 생성되고 해당 인스턴스의 메서드가  실행될 때 어드바이스 적용 여부를 판단할 수 있는데, 이는 전제가 해당 객체가 프록시임을 뜻한다. 

 

이는 의도치 않게 Car 클래스가 프록시로 만들어 진것처럼 최적화가 불가능하고, final로 선언된 클래스에는 프록시 객체를 생성하지 못하기 때문에 런타임 시점에 컴파일 오류가 발생한다. 이는 스프링이 모든 스프링 빈에 AOP를 적용하려는 시도 때문이다. 

기억해야 할 것은 프록시가 없으면 런타임에 해당 메서드의 어드바이스 적용 여부를 판단하지 못하기 때문에 프록시 객체가 필요한데, 단독으로 사용될 경우 모든 스프링 빈을 AOP만들려고 하니( 스프링 컨테이너가 프록시 생성하는 시점을 생각해보자) exectution.. 등으로 조건을 더 붙여서 범위를 줄여서 함께 사용해야 한다.

반응형

'Spring|Spring-boot > Spring AOP' 카테고리의 다른 글

Spring AOP 한계  (0) 2022.01.31
Spring AOP 매개변수 활용  (0) 2022.01.30
Pointcut - within, args  (0) 2022.01.29
Pointcut - execution  (0) 2022.01.29
Spring AOP (2)  (0) 2022.01.29

댓글