본문은 인프런의 [스프링 핵심 원리 - 고급편]를 수강하고 정리한 글입니다. 필요에 따라 생략/수정된 부분이 있을 수 있으며, 내용이 추후 변경될 수 있습니다.
스프링 핵심 원리 - 고급편
1. 쓰레드 로컬
2. 템플릿 메서드 패턴과 콜백 패턴
3. 프록시 패턴과 데코레이터 패턴
4. 동적 프록시 기술
5. 스프링이 지원하는 프록시
6. 빈 후처리기
7. @Aspect AOP
8. 스프링 AOP 개념
9. 스프링 AOP 구현
10. 포인트컷
11. 실무 주의사항
1. AOP 구현1
1) @Aspect를 사용한 AOP 구현
@Slf4j
@Aspect
public class AspectV1 {
@Around("execution(* hello.aop.order..*(..))") //hello.aop.order 패키지와 하위 패키지
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("[log] {}", joinPoint.getSignature()); //join point 시그니처
return joinPoint.proceed();
}
}
- @Aspect
- 스프링 AOP를 적용할 때 주로 사용되는 애너테이션, 이를 통해 프록시 방식의 AOP가 적용된다
- @Aspect를 포함한 org.aspectj 패키지 관련 기능은 aspectjweaver.jar 라이브러리가 제공한다
- @Aspect는 애스펙트라는 표식일 뿐 객체를 컴포넌트 스캔의 대상으로 만들어주진 않으므로 AOP 적용을 위해선 별도 빈으로 등록하는 과정이 필요하다
- @Bean을 이용한 수동 등록
- @Component를 이용한 자동 등록
- @Import를 사용한 클래스 참조 (주로 설정 파일을 추가할 때 사용)
- @Around
- 애노테이션의 값(execution(* hello.aop.order..*(..)))은 포인트컷이 된다
- 애노테이션의 메서드(doLog)는 어드바이스가 된다
2) 포인트컷 분리
@Slf4j
@Aspect
public class AspectV2 {
@Pointcut("execution(* hello.aop.order..*(..))") //hello.aop.order 패키지와 하위 패키지
private void allOrder(){} //pointcut signature
@Around("allOrder()")
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("[log] {}", joinPoint.getSignature()); //join point 시그니처
return joinPoint.proceed();
}
}
- @Pointcut
- 애노테이션 값에 포인트컷 표현식을 사용한다
- 메서드 이름과 파라미터를 합쳐서 포인트컷 시그니처(signature)라고 한다
- 메서드의 반환 타입은 void여야 한다
- 코드 내용은 비워둔다
- 여기서 포인트컷 시그니처는 allOrder()이다
- 포인트컷에 의미를 부여할 수 있다는 장점이 있다
- 접근제어자(private, public, ...)를 사용할 수 있다
- @Around 어드바이스에서 포인트컷을 직접 지정하는 것도 가능하지만, 위 예제처럼 포인트컷 시그니처를 사용해도 된다
- ex) @Around(allOrder())
3) 어드바이스 추가
@Slf4j
@Aspect
public class AspectV3 {
//나머지는 AspectV2와 동일..
@Pointcut("execution(* *..*Service.*(..))") //클래스 이름 패턴이 *Service
private void allService(){}
@Around("allOrder() && allService()") //hello.aop.order 패키지와 하위 패키지 이면서 클래스 이름 패턴이 *Service
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());
}
}
}
- 트랙잭션을 적용하는 코드가 추가되었다 (실제로는 적용 X, 로그만 출력)
- allService()
- 타입 이름 패턴 매칭 → 클래스 & 인터페이스 모두 적용
- 어드바이스가 적용되어 호출 순서가 바뀜
- AS-IS) 클라이언트 → orderService.orderItem() → orderRepository.save()
- TO-BE) 클라이언트 → 프록시 로직[doLog() → doTransaction()] → orderService.orderItem() → orderRepository.save() → 프록시 로직[doLog()] → orderRepository.save()
4) 포인트컷 참조
앞서 포인트 컷에 접근 제어자를 적용할 수 있다는 사실을 언급한 바 있다. 이를 적절히 사용하면 잘 모듈화된 코드를 작성할 수 있다. 위에서 서 작성한 포인트컷을 분리해보자.
Pointcuts - 분리된 포인트컷 모듈
public class Pointcuts {
@Pointcut("execution(* hello.aop.order..*(..))") //hello.aop.order 패키지와 하위 패키지
public void allOrder(){}
@Pointcut("execution(* *..*Service.*(..))") //클래스 이름 패턴이 *Service
public void allService(){}
@Pointcut("allOrder() && allService()") //allOrder && allService
public void orderAndService() {}
}
포인트컷 모듈을 참조하는 예제
@Slf4j
@Aspect
public class AspectV4Pointcut {
@Around("hello.aop.order.aop.Pointcuts.allOrder()") //참조 대상 지정
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
//...
}
@Around("hello.aop.order.aop.Pointcuts.orderAndService()")
public Object doTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
//...
}
}
- @Around의 값에 참조 대상을 지정한다
- 이때 패키지명, 클래스명, 메소드명이 모두 포함되어야 한다
5) 어드바이스 순서 변경
어드바이스는 기본적으로 순서를 보장하지 않는다. 순서를 보장하기 위해선 @Order 애너테이션을 사용해야 하는데, @Order는 클래스 단위로 적용된다. 따라서 애스펙트를 클래스별로 나눠줘야 한다.
@Slf4j
public class AspectV5Order {
@Aspect
@Order(2) //클래스 단위로 적용된다.
public static class LogAspect {
//...
}
@Aspect
@Order(1) //클래스 단위로 적용된다.
public static class TxAspect {
//...
}
}
- 위 코드에선 내부 클래스로 어드바이저가 선언되었지만, 밖으로 빼도 무관하다
2. AOP 구현2
1) 참고 정보 획득
@Aspect는 @Around 외에도 다른 어드바이스들을 지원한다. 모든 어드바이스는 JoinPoint를 첫번째 파라미터에 사용할 수 있으면 생략도 가능하다. 단 @Around는 ProceedingJointPoint를 사용해야 한다. ProceedingJoinPoint는 JoinPont의 하위 타입이다.
package org.aspectj.lang;
public interface JoinPoint {
String toString(); //조언되는 방법에 대한 유용한 설명을 출력
String toShortString();
String toLongString();
Object getThis(); //프록시 객체 반환
Object getTarget(); //대상 객체 반환
Object[] getArgs(); //메서드 인수 반환
Signature getSignature(); //조언되는 메서드에 대한 설명을 반환
//...
}
package org.aspectj.lang;
public interface ProceedingJoinPoint extends JoinPoint {
//...
Object proceed() throws Throwable; //다음 어드바이스나 타겟 호출
Object proceed(Object[] args) throws Throwable; //위와 동일, 인수를 전달받음
}
2) 어드바이스 종류
1) @Around
@Around("hello.aop.order.aop.Pointcuts.orderAndService()")
public Object doTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
try {
log.info("[트랜잭션 시작] {}", joinPoint.getSignature()); //@Before
Object result = joinPoint.proceed();
log.info("[트랜잭션 커밋] {}", joinPoint.getSignature()); //@AfterReturning
return result;
} catch (Exception e) {
log.info("[트랜잭션 롤백] {}", joinPoint.getSignature()); //@AfterThrowing
throw e;
} finally {
log.info("[리소스 릴리즈] {}", joinPoint.getSignature()); //@After
}
}
- 메서드 호출 전후에 실행
- 가장 강력한 어드바이스 → 나머지 어드바이스 기능을 모두 수행할 수 있는 완전체
- 조인 포인트 실행 여부 선택
- joinPoint.proceed()의 호출 여부를 선택할 수 있다
- 전달 값 변환
- joinPoint.proceed(args[])를 호출할 때 임의의 인수를 넘길 수 있다
- 반환 값 변환
- 예외 변환
- 트랜잭션처럼 try/catch/finally가 모두 들어가는 구문 처리 가능
- 조인 포인트 실행 여부 선택
- 제약
- 어드바이스의 첫 번째 파라미터로 반드시 ProccedingJoinPoint를 사용해야 한다
- proceed()를 통해 대상을 실행하며, 여러 번 실행도 가능하다
- proceed()를 호출하지 않으면 타겟이 호출되지 않으므로 주의해야 한다
- 하나의 타겟만 호출되지 않는 것이 아니라 그 뒤에 연쇄적인 타켓 호출이 모두 일어나지 않는다
- @Around를 제외한 나머지 어드바이스들은 @Around 기능의 일부만을 지원하므로, @Around 어드바이스만 사용해도 모든 기능을 구현할 수 있다
2) @Before
@Before("hello.aop.order.aop.Pointcuts.orderAndService()")
public void doBefore(JoinPoint joinPoint) {
log.info("[before] {}", joinPoint.getSignature());
}
- 조인 포인트 실행 이전에 수행되는 작업을 정의한다
- @Around와 다르게 작업 흐름을 변경할 수 없다
- ProceedingJoinPoint가 아닌 JoinPoint 객체를 넘겨 받기 때문에 proceed() 자체를 호출할 수 없다
- 예외가 발생하지 않는 한 메서드 종료 시 자동으로 다음 타겟이 호출된다
3) @AfterReturning
@AfterReturning(value = "hello.aop.order.aop.Pointcuts.orderAndService()", returning = "result")
public void doReturn(JoinPoint joinPoint, Object result) {
log.info("[return] {} return={}", joinPoint.getSignature(), result);
}
- 메서드 실행이 정상적으로 완료된 후 실행되는 작업을 정의한다
- returning 속성에 사용된 이름은 어드바이스 메서드의 매개변수 이름과 동일해야 한다
- returning절에 지정된 변수의 타입을 반환하는 메서드만 대상으로 실행한다
- 만약 타겟의 메서드가 String 타입을 반환하는데, Int 타입의 result를 받고자 하면 무시된다
- 부모 타입을 지정하면 모든 자식 타입은 인정된다
- result의 메서드를 호출해서 상태를 변경할 수는 있지만, 반환되는 객체 자체를 바꿀 수는 없다
- 이 점이 @Around와 주요한 차이점이다
4) @AfterThrowing
@AfterThrowing(value = "hello.aop.order.aop.Pointcuts.orderAndService()", throwing = "ex")
public void doThrowing(JoinPoint joinPoint, Exception ex) {
log.info("[ex] {} message={}", ex);
}
- 메서드가 예외를 던지는 경우 실행되는 작업을 정의한다
- throwing 속성에 사용된 이름은 어드바이스 메서드의 매개변수 이름과 동일해야 한다
- throwing절에 지정된 변수의 타입과 일치하는 예외를 대상으로 실행한다
- 부모 타입을 지정하면 모든 자식 타입은 인정된다
5) @After
@After(value = "hello.aop.order.aop.Pointcuts.orderAndService()")
public void doAfter(JoinPoint joinPoint) {
log.info("[after] {}", joinPoint.getSignature());
}
- 조인 포인트가 정상 완료되었거나 메서드가 예외를 던지거나 상관없이 실행되는 작업을 정의한다 (=finally)
- 정상 및 예외 반환 조건을 모두 처리한다
- 일반적으로 리소스를 해제하는 데 사용한다
3) 실행 흐름

- 스프링 5.2.7 버전부터 하나의 @Aspect 안에서 동일한 조인포인트의 우선순위가 정해졌다
- @Around, @Before, @After, @AfterReturning, @AfterThrowing 순이다
- 실행순서도 이와 동일하다
- 호출 순서와 리턴 순서는 반대이다
- @Aspect 안에 동일한 종류의 어드바이스가 2개 이상 있으면 순서가 보장되지 않으므로 이 경우엔 @Aspect를 분리하고 @Order를 적용함으로써 순서를 지정할 수 있다 (1-5 참고)
4) @Around 외에 다른 어드바이스가 존재하는 이유?
@Around만 있어도 모든 기능을 수행할 수 있지만, 그럼에도 불구하고 다른 어드바이저들이 존재하는 이유가 있다.
1) @Around를 잘못 사용하면 위험하다
- @Around는 그 기능이 막강한 만큼 위험이 큰 어드바이저다
- 만약 개발자의 실수로 joinPoint.proceed()를 호출하지 않으면 큰 장애가 일어날 수 있다
- 타겟이 호출되지 않고, 그 뒤의 연쇄적인 로직이 모두 실행되지 않을 수 있다
2) 역할이 명확하다
- @Around는 그 기능이 다양하기 때문에 해당 어드바이저가 무슨 일을 하는지 파악하기 어렵다
- 다른 어드바이저들은 기능이 한정된 만큼 그 역할이 명확하다
- 예컨대 @Before은 조인 포인트 실행 전에, @After는 조인 포인트 실행 후에 수행된다는 것이 이름에서부터 명확히 드러난다
'Spring > 스프링 핵심 원리 - 고급편' 카테고리의 다른 글
[스프링 핵심 원리 - 고급편] 실무 주의사항 (0) | 2023.03.22 |
---|---|
[스프링 핵심 원리 - 고급편] 포인트컷 (0) | 2023.03.16 |
[스프링 핵심 원리 - 고급편] 스프링 AOP 개념 (0) | 2023.03.08 |
[스프링 핵심 원리 - 고급편] @Aspect AOP (0) | 2023.03.07 |
[스프링 핵심 원리 - 고급편] 빈 후처리기 (0) | 2023.03.06 |
본문은 인프런의 [스프링 핵심 원리 - 고급편]를 수강하고 정리한 글입니다. 필요에 따라 생략/수정된 부분이 있을 수 있으며, 내용이 추후 변경될 수 있습니다.
스프링 핵심 원리 - 고급편
1. 쓰레드 로컬
2. 템플릿 메서드 패턴과 콜백 패턴
3. 프록시 패턴과 데코레이터 패턴
4. 동적 프록시 기술
5. 스프링이 지원하는 프록시
6. 빈 후처리기
7. @Aspect AOP
8. 스프링 AOP 개념
9. 스프링 AOP 구현
10. 포인트컷
11. 실무 주의사항
1. AOP 구현1
1) @Aspect를 사용한 AOP 구현
@Slf4j
@Aspect
public class AspectV1 {
@Around("execution(* hello.aop.order..*(..))") //hello.aop.order 패키지와 하위 패키지
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("[log] {}", joinPoint.getSignature()); //join point 시그니처
return joinPoint.proceed();
}
}
- @Aspect
- 스프링 AOP를 적용할 때 주로 사용되는 애너테이션, 이를 통해 프록시 방식의 AOP가 적용된다
- @Aspect를 포함한 org.aspectj 패키지 관련 기능은 aspectjweaver.jar 라이브러리가 제공한다
- @Aspect는 애스펙트라는 표식일 뿐 객체를 컴포넌트 스캔의 대상으로 만들어주진 않으므로 AOP 적용을 위해선 별도 빈으로 등록하는 과정이 필요하다
- @Bean을 이용한 수동 등록
- @Component를 이용한 자동 등록
- @Import를 사용한 클래스 참조 (주로 설정 파일을 추가할 때 사용)
- @Around
- 애노테이션의 값(execution(* hello.aop.order..*(..)))은 포인트컷이 된다
- 애노테이션의 메서드(doLog)는 어드바이스가 된다
2) 포인트컷 분리
@Slf4j
@Aspect
public class AspectV2 {
@Pointcut("execution(* hello.aop.order..*(..))") //hello.aop.order 패키지와 하위 패키지
private void allOrder(){} //pointcut signature
@Around("allOrder()")
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("[log] {}", joinPoint.getSignature()); //join point 시그니처
return joinPoint.proceed();
}
}
- @Pointcut
- 애노테이션 값에 포인트컷 표현식을 사용한다
- 메서드 이름과 파라미터를 합쳐서 포인트컷 시그니처(signature)라고 한다
- 메서드의 반환 타입은 void여야 한다
- 코드 내용은 비워둔다
- 여기서 포인트컷 시그니처는 allOrder()이다
- 포인트컷에 의미를 부여할 수 있다는 장점이 있다
- 접근제어자(private, public, ...)를 사용할 수 있다
- @Around 어드바이스에서 포인트컷을 직접 지정하는 것도 가능하지만, 위 예제처럼 포인트컷 시그니처를 사용해도 된다
- ex) @Around(allOrder())
3) 어드바이스 추가
@Slf4j
@Aspect
public class AspectV3 {
//나머지는 AspectV2와 동일..
@Pointcut("execution(* *..*Service.*(..))") //클래스 이름 패턴이 *Service
private void allService(){}
@Around("allOrder() && allService()") //hello.aop.order 패키지와 하위 패키지 이면서 클래스 이름 패턴이 *Service
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());
}
}
}
- 트랙잭션을 적용하는 코드가 추가되었다 (실제로는 적용 X, 로그만 출력)
- allService()
- 타입 이름 패턴 매칭 → 클래스 & 인터페이스 모두 적용
- 어드바이스가 적용되어 호출 순서가 바뀜
- AS-IS) 클라이언트 → orderService.orderItem() → orderRepository.save()
- TO-BE) 클라이언트 → 프록시 로직[doLog() → doTransaction()] → orderService.orderItem() → orderRepository.save() → 프록시 로직[doLog()] → orderRepository.save()
4) 포인트컷 참조
앞서 포인트 컷에 접근 제어자를 적용할 수 있다는 사실을 언급한 바 있다. 이를 적절히 사용하면 잘 모듈화된 코드를 작성할 수 있다. 위에서 서 작성한 포인트컷을 분리해보자.
Pointcuts - 분리된 포인트컷 모듈
public class Pointcuts {
@Pointcut("execution(* hello.aop.order..*(..))") //hello.aop.order 패키지와 하위 패키지
public void allOrder(){}
@Pointcut("execution(* *..*Service.*(..))") //클래스 이름 패턴이 *Service
public void allService(){}
@Pointcut("allOrder() && allService()") //allOrder && allService
public void orderAndService() {}
}
포인트컷 모듈을 참조하는 예제
@Slf4j
@Aspect
public class AspectV4Pointcut {
@Around("hello.aop.order.aop.Pointcuts.allOrder()") //참조 대상 지정
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
//...
}
@Around("hello.aop.order.aop.Pointcuts.orderAndService()")
public Object doTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
//...
}
}
- @Around의 값에 참조 대상을 지정한다
- 이때 패키지명, 클래스명, 메소드명이 모두 포함되어야 한다
5) 어드바이스 순서 변경
어드바이스는 기본적으로 순서를 보장하지 않는다. 순서를 보장하기 위해선 @Order 애너테이션을 사용해야 하는데, @Order는 클래스 단위로 적용된다. 따라서 애스펙트를 클래스별로 나눠줘야 한다.
@Slf4j
public class AspectV5Order {
@Aspect
@Order(2) //클래스 단위로 적용된다.
public static class LogAspect {
//...
}
@Aspect
@Order(1) //클래스 단위로 적용된다.
public static class TxAspect {
//...
}
}
- 위 코드에선 내부 클래스로 어드바이저가 선언되었지만, 밖으로 빼도 무관하다
2. AOP 구현2
1) 참고 정보 획득
@Aspect는 @Around 외에도 다른 어드바이스들을 지원한다. 모든 어드바이스는 JoinPoint를 첫번째 파라미터에 사용할 수 있으면 생략도 가능하다. 단 @Around는 ProceedingJointPoint를 사용해야 한다. ProceedingJoinPoint는 JoinPont의 하위 타입이다.
package org.aspectj.lang;
public interface JoinPoint {
String toString(); //조언되는 방법에 대한 유용한 설명을 출력
String toShortString();
String toLongString();
Object getThis(); //프록시 객체 반환
Object getTarget(); //대상 객체 반환
Object[] getArgs(); //메서드 인수 반환
Signature getSignature(); //조언되는 메서드에 대한 설명을 반환
//...
}
package org.aspectj.lang;
public interface ProceedingJoinPoint extends JoinPoint {
//...
Object proceed() throws Throwable; //다음 어드바이스나 타겟 호출
Object proceed(Object[] args) throws Throwable; //위와 동일, 인수를 전달받음
}
2) 어드바이스 종류
1) @Around
@Around("hello.aop.order.aop.Pointcuts.orderAndService()")
public Object doTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
try {
log.info("[트랜잭션 시작] {}", joinPoint.getSignature()); //@Before
Object result = joinPoint.proceed();
log.info("[트랜잭션 커밋] {}", joinPoint.getSignature()); //@AfterReturning
return result;
} catch (Exception e) {
log.info("[트랜잭션 롤백] {}", joinPoint.getSignature()); //@AfterThrowing
throw e;
} finally {
log.info("[리소스 릴리즈] {}", joinPoint.getSignature()); //@After
}
}
- 메서드 호출 전후에 실행
- 가장 강력한 어드바이스 → 나머지 어드바이스 기능을 모두 수행할 수 있는 완전체
- 조인 포인트 실행 여부 선택
- joinPoint.proceed()의 호출 여부를 선택할 수 있다
- 전달 값 변환
- joinPoint.proceed(args[])를 호출할 때 임의의 인수를 넘길 수 있다
- 반환 값 변환
- 예외 변환
- 트랜잭션처럼 try/catch/finally가 모두 들어가는 구문 처리 가능
- 조인 포인트 실행 여부 선택
- 제약
- 어드바이스의 첫 번째 파라미터로 반드시 ProccedingJoinPoint를 사용해야 한다
- proceed()를 통해 대상을 실행하며, 여러 번 실행도 가능하다
- proceed()를 호출하지 않으면 타겟이 호출되지 않으므로 주의해야 한다
- 하나의 타겟만 호출되지 않는 것이 아니라 그 뒤에 연쇄적인 타켓 호출이 모두 일어나지 않는다
- @Around를 제외한 나머지 어드바이스들은 @Around 기능의 일부만을 지원하므로, @Around 어드바이스만 사용해도 모든 기능을 구현할 수 있다
2) @Before
@Before("hello.aop.order.aop.Pointcuts.orderAndService()")
public void doBefore(JoinPoint joinPoint) {
log.info("[before] {}", joinPoint.getSignature());
}
- 조인 포인트 실행 이전에 수행되는 작업을 정의한다
- @Around와 다르게 작업 흐름을 변경할 수 없다
- ProceedingJoinPoint가 아닌 JoinPoint 객체를 넘겨 받기 때문에 proceed() 자체를 호출할 수 없다
- 예외가 발생하지 않는 한 메서드 종료 시 자동으로 다음 타겟이 호출된다
3) @AfterReturning
@AfterReturning(value = "hello.aop.order.aop.Pointcuts.orderAndService()", returning = "result")
public void doReturn(JoinPoint joinPoint, Object result) {
log.info("[return] {} return={}", joinPoint.getSignature(), result);
}
- 메서드 실행이 정상적으로 완료된 후 실행되는 작업을 정의한다
- returning 속성에 사용된 이름은 어드바이스 메서드의 매개변수 이름과 동일해야 한다
- returning절에 지정된 변수의 타입을 반환하는 메서드만 대상으로 실행한다
- 만약 타겟의 메서드가 String 타입을 반환하는데, Int 타입의 result를 받고자 하면 무시된다
- 부모 타입을 지정하면 모든 자식 타입은 인정된다
- result의 메서드를 호출해서 상태를 변경할 수는 있지만, 반환되는 객체 자체를 바꿀 수는 없다
- 이 점이 @Around와 주요한 차이점이다
4) @AfterThrowing
@AfterThrowing(value = "hello.aop.order.aop.Pointcuts.orderAndService()", throwing = "ex")
public void doThrowing(JoinPoint joinPoint, Exception ex) {
log.info("[ex] {} message={}", ex);
}
- 메서드가 예외를 던지는 경우 실행되는 작업을 정의한다
- throwing 속성에 사용된 이름은 어드바이스 메서드의 매개변수 이름과 동일해야 한다
- throwing절에 지정된 변수의 타입과 일치하는 예외를 대상으로 실행한다
- 부모 타입을 지정하면 모든 자식 타입은 인정된다
5) @After
@After(value = "hello.aop.order.aop.Pointcuts.orderAndService()")
public void doAfter(JoinPoint joinPoint) {
log.info("[after] {}", joinPoint.getSignature());
}
- 조인 포인트가 정상 완료되었거나 메서드가 예외를 던지거나 상관없이 실행되는 작업을 정의한다 (=finally)
- 정상 및 예외 반환 조건을 모두 처리한다
- 일반적으로 리소스를 해제하는 데 사용한다
3) 실행 흐름

- 스프링 5.2.7 버전부터 하나의 @Aspect 안에서 동일한 조인포인트의 우선순위가 정해졌다
- @Around, @Before, @After, @AfterReturning, @AfterThrowing 순이다
- 실행순서도 이와 동일하다
- 호출 순서와 리턴 순서는 반대이다
- @Aspect 안에 동일한 종류의 어드바이스가 2개 이상 있으면 순서가 보장되지 않으므로 이 경우엔 @Aspect를 분리하고 @Order를 적용함으로써 순서를 지정할 수 있다 (1-5 참고)
4) @Around 외에 다른 어드바이스가 존재하는 이유?
@Around만 있어도 모든 기능을 수행할 수 있지만, 그럼에도 불구하고 다른 어드바이저들이 존재하는 이유가 있다.
1) @Around를 잘못 사용하면 위험하다
- @Around는 그 기능이 막강한 만큼 위험이 큰 어드바이저다
- 만약 개발자의 실수로 joinPoint.proceed()를 호출하지 않으면 큰 장애가 일어날 수 있다
- 타겟이 호출되지 않고, 그 뒤의 연쇄적인 로직이 모두 실행되지 않을 수 있다
2) 역할이 명확하다
- @Around는 그 기능이 다양하기 때문에 해당 어드바이저가 무슨 일을 하는지 파악하기 어렵다
- 다른 어드바이저들은 기능이 한정된 만큼 그 역할이 명확하다
- 예컨대 @Before은 조인 포인트 실행 전에, @After는 조인 포인트 실행 후에 수행된다는 것이 이름에서부터 명확히 드러난다
'Spring > 스프링 핵심 원리 - 고급편' 카테고리의 다른 글
[스프링 핵심 원리 - 고급편] 실무 주의사항 (0) | 2023.03.22 |
---|---|
[스프링 핵심 원리 - 고급편] 포인트컷 (0) | 2023.03.16 |
[스프링 핵심 원리 - 고급편] 스프링 AOP 개념 (0) | 2023.03.08 |
[스프링 핵심 원리 - 고급편] @Aspect AOP (0) | 2023.03.07 |
[스프링 핵심 원리 - 고급편] 빈 후처리기 (0) | 2023.03.06 |