Aspect
@Slf4j
@Aspect
public class AspectV1 {
//hello.aop.order 패키지와 하위 패키지
@Around("execution(* hello.aop.order..*(..))")
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("[log] {}", joinPoint.getSignature()); //join point 시그니처
return joinPoint.proceed();
}
}
※ Pointcut : execution(* hello.aop.order..*(..))
→ hello.aop.order 패키지와 그 하위 패키지( .. )를 지정하는 AspectJ 포인트컷 표현식
※ Advice : doLog() 메서드
◎ 참고 : 스프링 빈으로 등록하는 방법
- @Bean 을 사용해서 직접 등록
- @Component 컴포넌트 스캔을 사용해서 자동 등록
- @Import 주로 설정 파일을 추가할 때 사용( @Configuration )
→ @Test 에서는 간단하게 @Import 기능을 사용

포인트컷 분리
Aspect
@Slf4j
@Aspect
public class AspectV2 {
//hello.aop.order 패키지와 하위 패키지
@Pointcut("execution(* hello.aop.order..*(..))") //pointcut expression
private void allOrder(){} //pointcut signature
@Around("allOrder()")
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("[log] {}", joinPoint.getSignature());
return joinPoint.proceed();
}
}
@Pointcut - 포인트컷 표현식
포인트컷 시그니처(signature) : 메서드 이름 + 파라미터 → allOrder()
코드 내용은 비워둔다.
어드바이스 추가
트랜잭션 기능 동작 방법
- 핵심 로직 실행 직전에 트랜잭션을 시작
- 핵심 로직 실행
- 핵심 로직 실행에 문제가 없으면 커밋
- 핵심 로직 실행에 예외가 발생하면 롤백
Aspect
@Slf4j
@Aspect
public class AspectV3 {
//hello.aop.order 패키지와 하위 패키지
@Pointcut("execution(* hello.aop.order..*(..))")
public 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();
}
//hello.aop.order 패키지와 하위 패키지 이면서 클래스 이름 패턴이 *Service
@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());
}
}
}
- allOrder() 포인트컷은 hello.aop.order 패키지와 하위 패키지를 대상으로 한다.
- allService() 포인트컷은 XxxService 처럼 Service 로 끝나는 것을 대상으로 한다.
- @Around("allOrder() && allService()") : 포인트 컷 조합
포인트컷이 적용된 AOP 결과
- orderService : doLog() , doTransaction() 어드바이스 적용
- orderRepository : doLog() 어드바이스 적용

포인트컷 참조
→ 포인트컷을 공용으로 사용하기 위해 별도의 외부 클래스로 분리
Pointcuts
public class Pointcuts {
//hello.springaop.app 패키지와 하위 패키지
@Pointcut("execution(* hello.aop.order..*(..))")
public void allOrder(){}
//타입 패턴이 *Service
@Pointcut("execution(* *..*Service.*(..))")
public void allService(){}
//allOrder && allService
@Pointcut("allOrder() && allService()")
public void orderAndService(){}
}
AspectV4Pointcut
@Slf4j
@Aspect
public class AspectV4Pointcut {
@Around("hello.aop.order.aop.Pointcuts.allOrder()")
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("[log] {}", joinPoint.getSignature());
return joinPoint.proceed();
}
@Around("hello.aop.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());
}
}
}
어드바이스 순서
하나의 애스팩트에 여러 어드바이스가 있으면 순서를 보장 받을 수 없다.
→ 애스펙트를 별도의 클래스로 분리
AspectV5Order
@Slf4j
public class AspectV5Order {
@Aspect
@Order(2)
public static class LogAspect {
@Around("hello.aop.order.aop.Pointcuts.allOrder()")
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
turlog.info("[log] {}", joinPoint.getSignature());
ren joinPoint.proceed();
}
}
@Aspect
@Order(1)
public static class TxAspect {
@Around("hello.aop.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());
}
}
}
}
- LogAspect , TxAspect 애스펙트로 각각 분리
- 각 애스펙트에 @Order 애노테이션을 통해 실행 순서를 적용

어드바이스 종류
※ 어드바이스 종류
- @Around : 메서드 호출 전후에 수행, 가장 강력한 어드바이스, 조인 포인트 실행 여부 선택, 반환 값 변환, 예외 변환 등이 가능
- @Before : 조인 포인트 실행 이전에 실행
- @AfterReturning : 조인 포인트가 정상 완료후 실행
- @AfterThrowing : 메서드가 예외를 던지는 경우 실행
- @After : 조인 포인트가 정상 또는 예외에 관계없이 실행(finally)
AspectV6Advice
@Slf4j
@Aspect
public class AspectV6Advice {
@Around("hello.aop.order.aop.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("hello.aop.order.aop.Pointcuts.orderAndService()")
public void doBefore(JoinPoint joinPoint) {
log.info("[before] {}", joinPoint.getSignature());
}
@AfterReturning(value = "hello.aop.order.aop.Pointcuts.orderAndService()", returning = "result")
public void doReturn(JoinPoint joinPoint, Object result) {
log.info("[return] {} return={}", joinPoint.getSignature(), result);
}
@AfterThrowing(value = "hello.aop.order.aop.Pointcuts.orderAndService()", throwing = "ex")
public void doThrowing(JoinPoint joinPoint, Exception ex) {
log.info("[ex] {} message={}", joinPoint.getSignature(), ex.getMessage());
}
@After(value = "hello.aop.order.aop.Pointcuts.orderAndService()")
public void doAfter(JoinPoint joinPoint) {
log.info("[after] {}", joinPoint.getSignature());
}
}
@Before : ProceedingJoinPoint.proceed() 사용하지 않는다. 메서드 종료시 자동으로 다음 타켓 호출. 예외 발생시 다음 코드 호출 X.
@AfterReturning : returning 속성에 사용된 이름은 어드바이스 메서드의 매개변수 이름과 일치. 반환 객체 변경 불가. 조작은 가능.
@AfterThrowing : throwing 속성에 사용된 이름은 어드바이스 메서드의 매개변수 이름과 일치.
@After : 정상 및 예외 반환 조건을 모두 처리. 리소스 해제할 때 사용.
@Around : 가장 강력한 Advice. proceed() 를 통해 대상을 실행. 여러번 가능.
- 조인 포인트 실행 여부 선택 joinPoint.proceed() 호출 여부 선택
- 전달 값 변환: joinPoint.proceed(args[])
- 반환 값 변환
- 예외 변환
- 트랜잭션 처럼 try ~ catch~ finally 모두 들어가는 구문 처리 가능

좋은 설계는 제약이 있는 것이다
- @Around 가 가장 넓은 기능을 제공하는 것은 맞지만, joinPoint.proceed() 호출해야만 한다.
- 반면에 @Before , @After 는 작성한 의도가 명확하게 드러나고 코드도 단순하다.
'Spring > Advanced' 카테고리의 다른 글
스프링 AOP - 예제 (0) | 2023.08.22 |
---|---|
스프링 AOP - 포인트컷 (0) | 2023.08.21 |
스프링 AOP 개념 (0) | 2023.08.21 |
@Aspect AOP (0) | 2023.08.21 |
빈 후처리기 (0) | 2023.08.17 |