스프링 AOP 는 런타임에서 프록시 기반의 AOP 를 수행한다고 하였다. 프록시 자동 생성기가 있기에 Advisor 만 만들어 빈으로 등록하면 되는데, @Aspect 로 편리하게 Advisor 를 생성한다.
이번 시간에는 @Aspect 를 활용하여 스프링 AOP 를 구현해보자. 그리고 Pointcut 과 Advice 를 다루는 @Aspect 의 기능에 대해 알아보자.
핵심기능에 @Aspect 를 이용하여 AOP 를 적용해보자.
우선 핵심기능은 위와 같이 구성하였다.
@Around 로 Pointcut 을 설정하고, doLog 라는 메서드로 Advice 를 설정하였다. Pointcut 은 hello.aop.order 패키지의 모든 클래스들에 적용되도록 하였고, Advice 는 타겟 메서드 호출전에 시그니처를 출력하도록 하였다.
@Around 는 반드시 ProceedingJoinpoint 를 파라미터로 가져야한다. joinPoint 가 갖고있는 proceed() 메서드를 호출해야 타겟의 메서드가 실행되기 때문이다.
실행해보면 위와같은 결과가 실행된다. 타겟의 메서드들 호출전에 시그니처가 출력된다. joinPoint 의 시그니처는 메서드에 대한 반환형, 위치, 파라미터 등 다양한 정보를 갖고 있는 것을 확인할 수 있다.
@Aspect 내의 각 Aspect 에서 Pointcut 을 분리하여 작성할 수 있다.
위 그림과 같이 @Pointcut 을 통해 포인트컷을 별도의 메서드로 분리할 수 있다. 메서드의 반환 타입은 void 여야하고, 코드내용은 비워둔다.
Advice 에서는 직접 포인트컷 표현식을 사용해도 되지만 그림과 같이 포인트컷 시그니처를 사용해도 된다. allOrder() 처럼 사용하였다.
포인트컷 시그니처를 사용하면 포인트컷에 이름을 부여할 수 있는 장점이 있다. 자주 사용하는 포인트컷은 시그니처로 만들어 사용하자.
위와 같이 포인트컷 시그니처를 한 클래스에 몰아넣고 외부 클래스의 어드바이스에서 이를 참조할 수도 있다. 외부 클래스에서 사용하게 할 경우 접근제어자를 public 으로 설정해야한다.
이때 외부 포인트컷을 사용하는 어드바이스는 "패키지명.클래스명.포인트컷 시그니처 명" 으로 포인트컷을 사용할 수 있다.
하나의 타겟에 여러 Pointcut 이 매칭되어, 여러개의 Advice 가 적용될 수 있다.
하나의 타겟이 여러 Pointcut 에 매칭되게하여 여러 Advice를 적용할 수 있다.
상단의 Pointcut 는 allOrder 에 매칭되고, 하단의 Pointcut 는 allOrder 중 allService 에 매칭된다. 따라서 Service 클래스는 doLog 와 doTransaciton 두 Advice 가 모두 적용된다는 것이다.
실행결과이다. Service 를 기준으로보면 doLog 가 먼저 적용되고 그 뒤에 doTransaction 이 적용된다.
프록시객체의 코드로 보면 위처럼 존재할 것이다.
위의 Advice 에서 doTransaction 을 먼저 적용하고 싶다. 어떻게하면 순서를 변경할 수 있을까?
기본적으로 하나의 @Aspect 내에서 Advice 메서드들은 순서가 보장되지 않고, 클래스 단위로만 순서를 정할 수 있다. 즉 @Aspect 단위로만 순서를 정할 수 있다. 그래서 번거롭지만 doLog 와 doTranscation 을 별도의 클래스로 분리하여야 한다. @Order 로 Aspect 의 순서를 정할 수 있으며 낮을수록 먼저실행된다.
변경결과 TxAspect 가 먼저 실행되는 모습이다.
@Before("hello.aop.order.aop.Pointcuts.orderAndService()")
public void doBefore(JoinPoint joinPoint) {
log.info("{}" joinPoint.getSignature());
}
@Before 어드바이스는 타겟 메서드 호출전에 수행한다. 어드바이스가 종료되면 자동으로 다음 타겟을 호출한다.
@AfterReturning(value = "hello.aop.order.aop.Pointcuts.orderAndService()", returning = "result")
public void doReturn(JoinPoint joinPoint, Object result) {
log.info("{}, {}" joinPoint.getSignature(), result);
}
@AterReturning 어드바이스는 타겟 메서드가 반환된 뒤에 수행된다. returning 속성명과 파라미터명이 일치해야한다. 파라미터의 타입을 반환하는 메서드를 대상으로만 실행된다.
@AfterThrowing("hello.aop.order.aop.Pointcuts.orderAndService()" , throwing = "ex")
public void doThrowing(JoinPoint joinPoint, Exception ex) {
log.info("{}, {}" joinPoint.getSignature(), ex);
}
@AfterThrowing 은 타겟메서드 실행 중 예외가 발생하면 실행된다. throwing 속성명과 파라미터 이름이 일치해야한다. 파라미터의 타입의 예외를 던지는 메서드를 대상으로만 실행된다.
@After 는 메서드실행이 종료되면 실행된다. finally 와 유사하다. 일반적으로 리소스를 해제할 때 사용한다.
@Around("hello.aop.order.aop.Pointcuts.orderAndService()")
public void doAround(ProceedingJoinPoint joinPoint) {
log.info("{}, {}" joinPoint.getSignature());
joinPoint.proceed();
}
@Around 는 타겟 메서드 실행 전후에 작업을 수행한다. 타겟 메서드의 실행 여부를 결정하거나, 타겟 메서드를 여러번 실행할 수도 있는 막강한 어드바이스이다.
포인트컷에 매칭되는 메서드를 갖는 객체는 프록시객체로 변환되어 등록된다. 그리고 프록시객체는 실제객체와 Advice 를 알고 있다. 이 때 프록시객체의 메서드가 호출되면, 실제객체+실제객체메서드 정보를 파라미터로 넘기며 Advice 를 실행한다. Advice 에서는 이 정보를 JoinPoint 라는 파라미터로 조회하여 사용한다.
JoinPoint
ProceedingJoinPoint
@Around 가 가장 막강하여 모든 기능을 수행할 수 있다고 하였다. 그런데 @Around 는 joinPoint.proceed() 로 타겟메서드를 호출하지 않으면 치명적인 버그가 발생할 수 있다. 그래서 실제 타겟 메서드를 건들 수 없는 제약이 존재하는 다른 어드바이스를 사용하는 경우가 있다.
스프링에서 AOP 를 적용하려면 @Aspect 클래스 안에서 포인트컷+어드바이스를 메서드로 생성한다.
포인트컷은 @Pointcut 으로 별도 메서드로 분리하여 참조로 사용할 수 있다.
하나의 실제객체 메서드에 여러 어드바이스가 적용되면, @Aspect 단위로만 순서를 정할 수 있다.
@Around, @After 등 여러 어드바이스들이 존재한다.