티스토리 뷰
Advice 종류
@Around : 메서드 호출 전후에 수행, 가장 강력한 어드바이스, 조인 포인트 실행 여부 선택, 반환 값 변환, 예외 변환 등이 가능
@Before : 조인 포인트 실행 이전에 실행된다.
@AfterReturning : 조인 포인트가 정상 완료후 실행된다.
@AfterThrowing : 메서드가 예외를 던지는 경우 실행된다.
@After : 조인 포인트가 정상 또는 예외에 관계없이 실행(finally)된다.
아래 예제를 살펴보면서 좀 더 깊이 학습해보자!
@Slf4j
@Aspect
public class AspectAdvice {
@Around("com.hyuuny.advanced.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("com.hyuuny.advanced.order.aop.Pointcuts.orderAndService()")
public void doBefore(JoinPoint joinPoint) {
log.info("[before] {}", joinPoint.getSignature());
}
@AfterReturning(value = "com.hyuuny.advanced.order.aop.Pointcuts.orderAndService()", returning = "result")
public void doReturn(JoinPoint joinPoint, Object result) {
log.info("[return] {}, return={}", joinPoint.getSignature(), result);
}
@AfterThrowing(value = "com.hyuuny.advanced.order.aop.Pointcuts.orderAndService()", throwing = "ex")
public void doThrowing(JoinPoint joinPoint, Exception ex) {
log.info("[ex] {}, message={}", joinPoint.getSignature(), ex.getMessage());
}
@After(value = "com.hyuuny.advanced.order.aop.Pointcuts.orderAndService()")
public void doAfter(JoinPoint joinPoint) {
log.info("[after] {}", joinPoint.getSignature());
}
}
doTransaction()
메서드에 남겨둔 주석을 보자.
복잡해 보이지만 사실 @Around
를 제외한 나머지 어드바이스들은 @Around
가 할 수 있는 일의 일부만 제공할 뿐이다. 따라서 @Around
어드바이스만 사용해도 필요한 기능을 모두 수행할 수 있다.
참고
모든 어드바이스는 org.aspectj.lang.JoinPoint
를 첫번째 파라미터에 사용할 수 있다. (생략해도 된다.)
단, @Around
는 ProceedingJoinPoint
을 사용해야 한다. ProceedingJoinPoint
는 org.aspectj.lang.JoinPoint
의 하위 타입이다.
@Around
메서드의 실행의 주변에서 실행. 메서드 실행 전후에 작업을 수행.
@Around("com.hyuuny.advanced.order.aop.Pointcuts.orderAndService()")
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());
}
}
가장 강력한 어드바이스. (거즘 @Around
로 처리한다고 보면 된다.)
조인 포인트 실행 여부 선택, 전달 값 변환, 반환 값 변환, 예외 변환, 트랜잭션 처럼 try ~ catch~ finally
모두 들어가는 구문 처리가 가능하다. 어드바이스의 첫 번째 파라미터는 ProceedingJoinPoint
를 사용해야 하고, proceed()
를 통해 대상을 실행한다. proceed()
를 여러번 실행할 수도 있다.
@Before
조인 포인트 실행 전
@Before("com.hyuuny.advanced.order.aop.Pointcuts.orderAndService()")
public void doBefore(JoinPoint joinPoint) {
log.info("[before] {}", joinPoint.getSignature());
}
@Around
와 다르게 작업 흐름을 변경할 수는 없다.@Before
는 ProceedingJoinPoint.proceed()
자체를 사용하지 않으며, 메서드 종료시 자동으로 다음 타켓이 호출된다. 물론 예외가 발생하면 다음 코드가 호출되지는 않는다.
@AfterReturning
메서드 실행이 정상적으로 반환될 때 실행
@AfterReturning(value = "com.hyuuny.advanced.order.aop.Pointcuts.orderAndService()", returning = "result")
public void doReturn(JoinPoint joinPoint, Object result) {
log.info("[return] {}, return={}", joinPoint.getSignature(), result);
}
returning
속성에 사용된 이름은 어드바이스 메서드의 매개변수 이름과 일치해야 한다.returning
절에 지정된 타입의 값을 반환하는 메서드만 대상으로 실행한다. (부모 타입을 지정하면 모든 자식 타입은 인정된다.) @Around
와 다르게 반환되는 객체를 변경할 수는 없지만, 조작할 수 는 있다.
@AfterThrowing
메서드 실행이 예외를 던져서 종료될 때 실행
@AfterThrowing(value = "com.hyuuny.advanced.order.aop.Pointcuts.orderAndService()", throwing = "ex")
public void doThrowing(JoinPoint joinPoint, Exception ex) {
log.info("[ex] {}, message={}", joinPoint.getSignature(), ex.getMessage());
}
throwing
속성에 사용된 이름은 어드바이스 메서드의 매개변수 이름과 일치해야 한다.throwing
절에 지정된 타입과 맞은 예외를 대상으로 실행한다. (부모 타입을 지정하면 모든 자식 타입은 인정된다.)
@After
메서드 실행이 종료되면 실행
@After(value = "com.hyuuny.advanced.order.aop.Pointcuts.orderAndService()")
public void doAfter(JoinPoint joinPoint) {
log.info("[after] {}", joinPoint.getSignature());
}
정상 및 예외 반환 조건을 모두 처리하며, 일반적으로 리소스를 해제할 때 사용한다.
Test code
@Import(AspectAdvice.class)
public class AopTest {
@Autowired
OrderService orderService;
@Autowired
OrderRepository orderRepository;
@Test
void success() {
orderService.orderItem("itemA");
}
@Test
void exception() {
Assertions.assertThatThrownBy(() -> orderService.orderItem("ex"))
.isInstanceOf(IllegalStateException.class);
}
}
success 결과
정상적으로 실행되어 @Before
, @AfterReturning
, @After
가 호출된 것을 확인할 수 있다.
exception 결과
@Before
실행 후, 예외가 발생하여 @AfterThrowing
, @After
가 호출된 것을 확인할 수 있다
Advice 호출 순서
- 스프링은 5.2.7 버전부터 동일한
@Aspect
안에서 동일한 조인포인트의 우선순위를 정했다. - 실행 순서:
@Around
->@Before
->@After
->@AfterReturning
->@AfterThrowing
- 어드바이스가 적용되는 순서는 이렇게 적용되지만, 호출 순서와 리턴 순서는 반대이다.
@Aspect
안에 동일한 종류의 어드바이스가 2개 있으면 순서가 보장되지 않는다.@Aspect
를 분리하고@Order
를 적용하자.
@Around 외에 다른 어드바이스가 존재하는 이유가 뭘까?
@Around("com.hyuuny.advanced.order.aop.Pointcuts.orderAndService()")
public void doBefore(JoinPoint joinPoint) {
log.info("[before] {}", joinPoint.getSignature());
}
이 코드는 타켓을 호출하지 않는 문제가 있다.
이 코드를 개발한 의도는 타켓 실행 전에 로그를 출력하는 것이다. @Around
는 항상 joinPoint.proceed()
를 호출해야 한다. 만약 실수로 호출하지 않으면 타켓이 호출되지 않는 치명적인 버그가 발생한다.
@Before("com.hyuuny.advanced.order.aop.Pointcuts.orderAndService()")
public void doBefore(JoinPoint joinPoint) {
log.info("[before] {}", joinPoint.getSignature());
}
반면에 @Before
는 joinPoint.proceed()
를 호출하는 고민을 하지 않아도 된다.
@Around
가 가장 넓은 기능을 제공하는 것은 맞지만, 실수할 가능성이 있다. 반면에 @Before
, @After
같은 어드바이스는 기능은 적지만 실수할 가능성이 낮고, 코드도 단순하다. 그리고 가장 중요한 점이 있는데, 바로 이 코드를 작성한 의도가 명확하게 드러난다는 점이다.
Reference
김영한. 스프링 핵심 원리 - 고급편. 인프런.
'Spring' 카테고리의 다른 글
[Spring] AOP @target, @within (0) | 2022.06.06 |
---|---|
[Spring] AOP Pointcut execution (0) | 2022.05.31 |
[Spring] AOP 용어 (0) | 2022.05.25 |
[Spring] AOP는 어떻게 적용되는걸까? (0) | 2022.05.24 |
[Spring] @Aspect AOP (0) | 2022.05.23 |
- Total
- Today
- Yesterday
- 데이터베이스
- mysql 8.0
- 노마드
- spring boot
- webflux
- 코테
- 파이썬
- Refactoring
- 스프링
- 문자열
- 리팩토링
- 그리디
- 릿코드
- 북클럽
- 구현
- 김영한
- Algorithm
- MySQL
- Real MySQL
- 노마드코더
- 백준
- Spring
- leetcode
- 자료구조
- 알고리즘
- 스프링 부트
- 코틀린
- 인프런
- 정렬
- kotlin
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |