설정 사항
- org.aspectj 패키지 관련 기능은 aspectjweaver.jar 라이브러리가 제공하는 기능이다. 따라서 해당 라이브러리 의존관계의 추가가 필요하며 spring-boot인 경우 spring-boot-starter-aop를 포함하면 스프링 AOP 기능 + asppecthweaver.jar도 함께 사용할 수 있도록 의존관계에 포함된다.
구성요소
- 포인트 컷을 별도의 클래스로 관리하여 public으로 사용할 수 있도록 구성할 것이다.
import org.aspectj.lang.annotation.Pointcut; public class Pointcuts { @Pointcut("execution(* hello.aop.example..*(..))") public void allExample(){} // point cut signature @Pointcut("execution(* *..*Service.*(..))") public void allService(){} // point cut signature @Pointcut(" allExample() && allService()") public void exampleAndService(){} }
- @Aspect가 붙은 클래스에서는 advice만 작성할 것이다.
외부 포인트 컷을 사용하기 위해선 포인트컷 클래스의 패키 지명과 클래스명까지 포함해야 한다.
포인트 컷 클래스에는 public으로 접근 지시자를 설정하며 반환 타입은 void 함수의 몸체는 비워야 한다.
@Slf4j @Aspect public class AspectExample { @Around("hello.aop.example.aop.Pointcuts.allExample()") public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable{ log.info("[log] {}", joinPoint.getSignature()); return joinPoint.proceed(); } @Around("hello.aop.example.aop.Pointcuts.exampleAndService()") public Object doTransaction(ProceedingJoinPoint joinPoint) throws Throwable{ try { log.info("[트랜잭션 시작] {}", joinPoint.getSignature()); Object result = joinPoint.proceed(); log.info("[트랜잭션 커밋] {}", joinPoint.getSignature()); return result; } catch (Exception e) { log.info("[트랜잭션 롤백]", joinPoint.getSignature()); throw e; } finally{ log.info("[리소스 릴리즈] {}", joinPoint.getSignature()); } } }
스프링 AOP를 활용하여 포인트 컷이 중복되는 메서드에서 advice가 적용되는 순서를 지정하려고 한다. 스프링이 제공해 주는 @Order를 통해 어드바이스 단위에 붙여 사용했지만 순서가 적용되지 않는다.
org.springframework.core.annotation.@Order 애노테이션은 어드바이스 단위로 적용할 수 없고 클래스 단위로 적용해야 한다는 점이다. 따라서 하나의 애스펙트에 여러 어드바이스가 있는 경우에 별도의 순서를 지정하기 위해선 애스팩트를 별도의 클래스로 분리해야 한다.
@Slf4j
public class AspectExample {
@Order(2)
@Aspect
public static class LogAspect{
@Around("hello.aop.example.aop.Pointcuts.allExample()")
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable{
log.info("[log] {}", joinPoint.getSignature());
return joinPoint.proceed();
}
}
@Order(1)
@Aspect
public static class TxAspect{
@Around("hello.aop.example.aop.Pointcuts.exampleAndService()")
public Object doTransaction(ProceedingJoinPoint joinPoint) throws Throwable{
try {
log.info("[트랜잭션 시작] {}", joinPoint.getSignature());
Object result = joinPoint.proceed();
log.info("[트랜잭션 커밋] {}", joinPoint.getSignature());
return result;
} catch (Exception e) {
log.info("[트랜잭션 롤백]", joinPoint.getSignature());
throw e;
} finally{
log.info("[리소스 릴리즈] {}", joinPoint.getSignature());
}
}
}
}
별도의 클래스로 분리해도 되지만 편의상 내부 클래스를 만들었다.
AspectExample 클래스는 껍데기로 활용하고, 내부에서 각 적용될 애스펙트를 생성해 @Order를 적용시켜주면 된다.
스프링 AOP 어드바이스 종류
스프링 AOP는 프락시를 사용하는 AOP를 차용했기 때문에 메서드에만 AOP를 적용 시킬 수 있다.
그래서 메서드에 적용시킬 수 있는 여러 어드바이스를 제공하는 여러가지 종류가 있다.
- @Around : 메소드 호출 전후에 수행
- @Before : 조인 포인트 실행 이전에 실행
- @After Returning : 조인 포인트가 정상 완료 후 실행
- @After Throwing : 메서드가 예외를 던지는 경우 실행
- @After : 조인 포인트가 정상 또는 예외에 관계없이 실행(finally)
@Around 애노테이션은 메서드의 모든 부분을 통제한다. 실제 타깃이 호출되기 전, 후, 예외 발생 시, finally처럼 전반적으로 모든 코드를 작성할 수 있다. 나머지 @Before, @After 등등은 이 @Around를 부분 부분 잘라낸 것이라고 생각하면 된다.
@Around("hello.aop.example.aop.Pointcuts.exampleAndService()")
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());
}
}
* Tip
모든 어드바이스는 org.aspectj.lang.JoinPoint를 첫 번째 파라미터에 사용할 수 있다.
단 @Around는 ProceedingJointPoint를 사용해야 한다.
JoinPoint 인터페이스의 주요 기능
- getArgs() : 메서드 인수를 반환합니다.
- getThis() : 프락시 객체를 반환합니다.
- getTarget() : 대상 객체를 반환합니다.
- getSignature() : advice가 적용되는 메서드에 대한 설명을 반환합니다.
- toString() : dvice가 적용되는 방법에 대한 유용한 설명을 인쇄합니다.
ProceedingJoinPoint 인터페이스의 주요 기능
- proceed() : 다음 어드바이스나 타깃을 호출한다.
@Before - 조인 포인트 실행 전
@Before("hello.aop.example.aop.Pointcuts.exampleAndService()")
public void doBefore(JoinPoint joinPoint) {
log.info("[before] {}", joinPoint.getSignature());
}
작업의 흐름을 변경할 수 없다. 메서드의 종료 시 자동으로 다음 타깃이 호출되며, 예외 발생 시에는 다음 코드가 호출되지 않는다.
@AfterReturning - 메서드 실행이 정상적으로 반환된 경우
@AfterReturning(value = "hello.aop.example.aop.Pointcuts.exampleAndService()",returning = "result")
public void doReturn(JoinPoint joinPoint, Object result) {
log.info("[return] {} return={}", joinPoint.getSignature(), result);
}
returing 속성에 사용된 이름은 어드바이스 메서드의 매개변수 이름과 일치해야 한다.
returning절에 지정된 타입의 값을 반환하는 메서드만 대상으로 실행되며 부모 타입 지정 시 모든 자식 타입은 인정된다.
* 반환되는 객체를 변경할 수 없다. 다만 조작은 가능하다.
@AfterThrowing - 메서드 실행이 예외를 던져서 종료될 경우
@AfterThrowing(value = "hello.aop.example.aop.Pointcuts.exampleAndService()",throwing = "ex")
public void doThrowing(JoinPoint joinPoint, Exception ex) {
log.info("[ex] {} message={}", joinPoint.getSignature(),ex.getMessage());
}
@After - 메서드 실행이 종료될 때 실행된다.
@After(value = "hello.aop.example.aop.Pointcuts.exampleAndService()")
public void doAfter(JoinPoint joinPoint) {
log.info("[after] {}", joinPoint.getSignature());
}
'Spring|Spring-boot > Spring AOP' 카테고리의 다른 글
Pointcut - within, args (0) | 2022.01.29 |
---|---|
Pointcut - execution (0) | 2022.01.29 |
Spring AOP (0) | 2022.01.27 |
[Spring boot] AOP 활용예제 (0) | 2021.06.09 |
[Spring] AOP(Aspect Oriented Programming) 방법론 (0) | 2020.06.22 |
댓글