📚 스프링 AOP 구현
@Slf4j
@Aspect
public class AspectV1 {
// 포인트컷 표현식을 직접 기재
@Around("execution(* com.jwj.aop..*(..))")
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("[log] {}", joinPoint.getSignature());
return joinPoint.proceed();
}
}
@Slf4j
@Aspect
public class AspectV2 {
// @Pointcut 어노테이션을 사용해서 포인트컷 표현식을 별도로 분리
@Pointcut("execution(* com.jwj.aop.order..*(..))")
private void allOrder() {}
// 분리
@Around("allOrder()")
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("[log] {}", joinPoint.getSignature());
return joinPoint.proceed();
}
}
@Slf4j
@Aspect
public class AspectV3 {
// 패키지 경로를 Pointcut으로 분리
@Pointcut("execution(* com.jwj.aop.order..*(..))")
private void allOrder() {}
// 클래스 이름 패턴이 Service로 끝난다.
@Pointcut("execution(* *..*Service.*(..))")
private void allService() {}
@Around("allOrder()")
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("[log] {}", joinPoint.getSignature());
return joinPoint.proceed();
}
// 위에서 개발한 2개의 Pointcut을 조합
@Around("allOrder() && allService()")
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());
}
}
}
// 포인트컷을 공용으로 사용하기 위해 별도의 외부 클래스에 모아둔다.
public class Pointcuts {
@Pointcut("execution(* com.jwj.aop.order..*(..))")
public void allOrder(){}
@Pointcut("execution(* *..*Service.*(..))")
public void allService(){}
@Pointcut("allOrder() && allService()")
public void orderAndService() {}
}
@Slf4j
@Aspect
public class AspectV4 {
@Around("com.jwj.aop.order.Pointcuts.allOrder()") // 패키지 전체 경로 명시
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("[log] {}", joinPoint.getSignature());
return joinPoint.proceed();
}
@Around("com.jwj.aop.order.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());
}
}
}
@Slf4j
public class AspectV5 {
@Aspect
@Order(2) // 실행 순서를 지정
public static class LogAspect {
@Around("com.jwj.aop.order.Pointcuts.allOrder()")
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("[log] {}", joinPoint.getSignature());
return joinPoint.proceed();
}
}
@Aspect
@Order(1)
public static class TxAspect {
@Around("com.jwj.aop.order.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
: 메서드 호출 전후 수행
@Before
: 조인 포인트 실행 이전에 수행
@AfterReturning
: 조인 포인트가 정상 완료된 후 실행
@AfterThrowing
: 메서드가 예외를 던지는 경우 실행
@After
: 조인 포인트가 정상 또는 예외와 관계없이 실행
@Slf4j
@Aspect
public class AspectV6 {
@Around("com.jwj.aop.order.Pointcuts.orderAndService()")
public Object doTransaction(ProceedingJoinPoint joinPoint) throws Throwable
{
try {
//@Before : 조인 포인트 실행 이전
log.info("[around][트랜잭션 시작] {}", joinPoint.getSignature());
Object result = joinPoint.proceed();
//@AfterReturning : 조인 포인트 정상 완료된 후 실행
log.info("[around][트랜잭션 커밋] {}", joinPoint.getSignature());
return result;
} catch (Exception e) {
//@AfterThrowing : 메서드가 예외를 던진 경우 실행
log.info("[around][트랜잭션 롤백] {}", joinPoint.getSignature());
throw e;
} finally {
//@After : 정상 또는 예외와 관계없이 실행
log.info("[around][리소스 릴리즈] {}", joinPoint.getSignature());
}
}
@Before("com.jwj.aop.order.Pointcuts.orderAndService()")
public void doBefore(JoinPoint joinPoint) {
log.info("[before] {}", joinPoint.getSignature());
}
@AfterReturning(value = "com.jwj.aop.order.Pointcuts.orderAndService()", returning = "result")
public void doReturn(JoinPoint joinPoint, Object result) {
log.info("[return] {} return={}", joinPoint.getSignature(), result);
}
@AfterThrowing(value = "com.jwj.aop.order.Pointcuts.orderAndService()", throwing = "ex")
public void doThrowing(JoinPoint joinPoint, Exception ex) {
log.info("[ex] {} message={}", joinPoint.getSignature(), ex.getMessage());
}
@After("com.jwj.aop.order.Pointcuts.orderAndService()")
public void doAfter(JoinPoint joinPoint) {
log.info("[after] {}", joinPoint.getSignature());
}
}
@Around
어노테이션 하나만 있어도 되지만 범위를 줄여가며 제약을 두는 명확한 코드가 더 좋기에 각 상황에 맞는 어노테이션을 활용하는 것을 추천한다.