지난 포스팅에서는 AOP의 구현방법에 대해서 알아보았다.
가장 기본적인 형태의 @Aspect부터, 포인트컷 적용 방법, 포인트컷을 별도로 관리하는 방법, 다중 포인트컷과 다중 어드바이스, 어드바이스 순서를 지정하는 방법 등에 대해 알아보았다.
이번 포스팅에서는 @Around 등 여러 어드바이스 종류에 대해 알아보려고 한다.
@Around가 모든 시점에 적용이 가능하고,
나머지 어드바이스는 @Around의 기능을 작게 쪼개어 놓은거라고 생각하면 된다.
코드로 확인해보자.
@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());
}
}
가장 강력한 어드바이스로 메서드 전후에서 흐름을 제어할 수 있다.
코드를 보면 @Around이외의 어드바이스가 적용되는 시점을 확인할 수 있다.
ProceedingJoinPoint
JoinPoint의 하위타입으로 ProceedingJoinPoint을 이용해 타겟의 실행 여부를 제어할 수 있다.
@Around에서는 proceed()를 호출하지 않으면 다음 타겟의 로직을 수행하지 않으므로 주의하도록 하자.
// 조인포인트 실행 전의 로직만 작성할 때
// joinPoint.proceed 이전의 로직
@Before("hello.aop.order.aop.Pointcuts.orderAndService()")
public void doBefore(JoinPoint joinPoint) {
log.info("[before] {}", joinPoint.getSignature());
}
// 메서드 실행이 정상적으로 반환될 때 실행
// joinPoint.proceed 이후의 로직
@AfterReturning(value = "hello.aop.order.aop.Pointcuts.orderAndService()", returning = "result")
public void doReturn(JoinPoint joinPoint, Object result) {
log.info("[return] {} return={}", joinPoint.getSignature(), result);
}
// 메서드 실행이 예외를 반환할 때 실행
// catch와 같은 역할
@AfterThrowing(value = "hello.aop.order.aop.Pointcuts.orderAndService()", throwing = "ex")
public void doThrowing(JoinPoint joinPoint, Exception ex) {
log.info("[ex] {} message={}", ex);
}
// finally 부분의 로직과 같은 역할을 함
@After(value = "hello.aop.order.aop.Pointcuts.orderAndService()")
public void doAfter(JoinPoint joinPoint) {
log.info("[after] {}",joinPoint.getSignature());
}
모든 어드바이스에서는 첫번째 파라미터로 JoinPoint를 사용할 수 있다.(생략 가능)
다만, @Around에서는 ProceedingJoinPoint를 사용해야 한다.
앞서 어드바이스 중 @Around가 모든 기능을 수행할 수 있다는 것을 확인하였다.
그렇다면 @Around만 사용하면 되는게 아닌가? 하는 생각을 하게 된다. 일단 내가 그랬다.
그럼 @Around외의 어드바이스가 등장한 이유는 무엇일까?
아래의 코드로 확인해보자.
@Around("hello.aop.order.aop.Pointcuts.orderAndService()")
public void doBefore(ProceedingJoinPoint joinPoint) {
log.info("[before] {}", joinPoint.getSignature());
}
@Around를 사용헀음에도 불구하고 proceed()를 호출하지 않아 타겟이 호출되지 않는다.
다음은 코드를 보자.
@Before("hello.aop.order.aop.Pointcuts.orderAndService()")
public void doBefore(JoinPoint joinPoint) {
log.info("[before] {}", joinPoint.getSignature());
}
@Before를 사용하고 있기 때문에 타겟이 호출되지 않는 문제에 대해서 고민하지 않아도 된다.
덕분에 코드도 간결해지고 실수를 하게될 가능성도 낮아지며 그 영향도 줄어들게 된다.
김영한님이 좋은 설계는 제약이 있는것이라는 말씀을 하셨는데, 바로 앞서 봤던 예제만 하더라도 @Around 대신에 @Before를 사용했다면 발생하지 않았을 문제였다.
예전에 학원 다닐 때 다른 팀에서 @ResponseBody로 http메세지를 보내려하는데 자꾸 페이지가 이동하는 문제가 발생했던 적이 있다. 알고보니 http메서드에 따라 url을 구분하지 않고 @RequestMapping만을 사용해 또 다른 메서드로 매칭이 되어 페이지 이동이 발생했던 것이다. 이 문제 또한 RequestMapping만 사용할게 아니라 http메서드에 따라 구분을 해줬다면 발생하지 않았을 간단한 일이었다.
이처럼 개발을 할 때 그떄 당시에는 조금 더 귀찮은 과정이 생기더라도 적절한 제약에 따라 프로그래밍을 하는게 결과적으로 더 나은 선택이 될 수 있다는 것을 느끼게 되는 시간이었다.
이번 포스팅에서는 AOP어드바이스의 종류에 대해서 알아보았다.
어드바이스의 종류에는
등이 있었다.
그리고 @Around외의 어드바이스가 있는 이유에 대해서 알아보며, 제약 덕분에 의도가 명확해지고 이에 따라 고민해야할 범위가 줄어들고 의도를 파악하기도 수월해진다는 것을 알 수 있었다.
다음 포스팅에서는 포인트컷의 여러 지시자에 대해서 알아보도록 하자.
출처 : 김영한 - 스프링 핵심 원리 고급편