WEB/Spring

스프링 핵심 원리 - 고급편 7) @Aspect AOP , 스프링 AOP 개념

Tony Lim 2022. 6. 12. 11:52
728x90

@Aspect AOP

AspectJ 에서 제공하는 에노테이션이다. annotation을 차용했을 뿐 실제 내부 구현은 스프링이 한것이다. 진짜 AspectJ를 사용하는것이 아니다. (컴파일 ,로드타임 위버 사용하는것 아님)

@Slf4j
@Aspect
public class LogTraceAspect {

    private final LogTrace logTrace;

    public LogTraceAspect(LogTrace logTrace) {
        this.logTrace = logTrace;
    }

    @Around("execution(* hello.proxy.app..*(..))")
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
        TraceStatus status = null;
        try {
            String message = joinPoint.getSignature().toShortString();
            status = logTrace.begin(message);

            //로직 호출
            Object result = joinPoint.proceed();

            logTrace.end(status);
            return result;
        } catch (Exception e) {
            logTrace.exception(status, e);
            throw e;
        }
    }
}

Invocation 에서 ProceedingJoinPoint 로 바뀌엇다. 동일하게 전달되는 클래스의 메타데이터를 지니고 있다.

@Configuration
@Import({AppV1Config.class, AppV2Config.class})
public class AopConfig {

    @Bean
    public LogTraceAspect logTraceAspect(LogTrace logTrace) {
        return new LogTraceAspect(logTrace);
    }
}

빈으로 등록된 LogTraceAspect에 @Aspect 가 붙은것을 확인 -> 빌더를 통해서 Advisor 를 생성한다.

만들어진 Advisor는 캐싱된다. 나중에 또 호출되면 빌더에서 만들지않고 캐시에 저장된것을 돌려준다.

근데 왜 빌더가 끼어들었지? 원래는 그냥 프록시에서 컨테이너에 advisor의 pointcut을 확인하고 적용할지 말지 결정하는 간단한 프로세스아니었나?

위에 컨테이너에서 Advisor를 조회하는것은 그대로 있고 @Aspect 를 기반으로 만들어진 것도 따로 저장소가 있어서 거기서도 조회를 하는것이다.

저장소만 다를뿐 그냥 둘다 Advisor이다.

 


 

AspectJ

크게 3가지 시점에 부가기능 로직이 추가된다.

컴파일 시점, 클래스 로딩 시점,런타임 시점(프록시)

ApsectJ 컴파일러가 .class를 만드는 시점에 부가기능 로직을 추가한다. 컴파일 시점에 aspect code가 들어가있는거다.

특별한 컴파일러를 요구하기에 잘 사용되지 않는다.

 

클래스로더가 JVM에 올리기 전에 조작한 후에 올리는 방법이다. Instrumentation 이라는 자바에서 제공하는 기능이다.

java -javaagent 라는 옵션을 통해 클래스 로더 조작기를 지정해야하는 번거로움이 존재한다. 잘 안쓰인다.

 

런타임 시점은 main 메서드가 이미 실행된 이후의 시점이다. 여태 학습한것이 프록시 방식의 AOP 적용 방식이다.

프록시 방식은 메소드 실행 지점에만 적용 가능하다. -> 메서드 오버라이딩 개념으로 동작한다.

 


용어 정리

Join Point = 어드바이스가 적용될수 있는 위치의 전체 집합이다. Spring AOP의 경우 조인포인트는 항상 메소드 실행 지점으로 제한된다.

PointCut = 어드바이스가 적용될 수 있는 위치다 JoinPoint의 부분 집합이다.

Target = 어드바이스가 적용되는 객체

Advice = 부가기능

Aspect == Advisor

@Aspect
public class AspectV6Advice {

    @Around("hello.aop.order.aop.Pointcuts.orderAndService()")
    public Object doTransaction(ProceedingJoinPoint joinPoint) throws Throwable {

        try {
            //@Before
            log.info("[트랜잭션 시작] {}", joinPoint.getSignature());
            Object result = joinPoint.proceed();
            //@AfterReturning
            log.info("[트랜잭션 커밋] {}", joinPoint.getSignature());
            return result;
        } catch (Exception e) {
            //@AfterThrowing
            log.info("[트랜잭션 롤백] {}", joinPoint.getSignature());
            throw e;
        } finally {
            //@After
            log.info("[리소스 릴리즈] {}", joinPoint.getSignature());
        }
    }

    @Before("hello.aop.order.aop.Pointcuts.orderAndService()")
    public void doBefore(JoinPoint joinPoint) {
        log.info("[before] {}", joinPoint.getSignature());
    }

    @AfterReturning(value = "hello.aop.order.aop.Pointcuts.orderAndService()", returning = "result")
    public void doReturn(JoinPoint joinPoint, Object result) {
        log.info("[return] {} return={}", joinPoint.getSignature(), result);
    }

    @AfterThrowing(value = "hello.aop.order.aop.Pointcuts.orderAndService()", throwing = "ex")
    public void doThrowing(JoinPoint joinPoint, Exception ex) {
        log.info("[ex] {} message={}", ex);
    }

    @After(value = "hello.aop.order.aop.Pointcuts.orderAndService()")
    public void doAfter(JoinPoint joinPoint) {
        log.info("[after] {}", joinPoint.getSignature());
    }

}

Exception , return 값 심지어 메소드에 넘어온 parameter 까지 중간에 잡아서 변경할 수 있다.

@Around의 경우 ProceedingJointPoint 를 파라미터로 써야한다. proceed를 호출해야하기 때문에 JointPoint보다 기능이 더 많은 것이다.

인자로 받을 수 있는 클래스를 명시 해줘야 받아올 수 있다. parameter에 Integer를 해놓고 target이 return 하는 String을 알아서 받아오기를 기대하면 안된다. 
이 경우에 해당 advice가 적용 안된다.

 

어드바이스가 적용되는 순서와 return 되는 순서는 반대다.

상식적으로 동작한다고 보면 된다.

@Around 만 쓰면 다 되긴한다. 하지만 애노태이션 만 보면 알 수 있게 여러가지 존재하는것이다.

좋은 설계는 제약이 있는 것이다. 실수를 미연에 방지하게 해준다.

 

 

 

728x90