스프링 AOP
스프링 AOP 구현
@Slf4j
@Aspect
public class AspectV1 {
@Around("execution(* hello.aop.order..*(..))")
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("[log] {}", joinPoint.getSignature()); //join point 시그니처
return joinPoint.proceed();
}
}
- @Aspect를 사용해 AOP 구현
- @Around 어노테이션 값은 포인트컷이 된다.
- AspectJ 포인트컷 표현식 사용
- "execution ~ " : hello.aop.order 패키지 및 하위 패키지의 파라미터 상관없이 모든 메소드를 포인트컷 대상으로 지정한다는 의미
- @Around 어노테이션의 메소드인 doLog()는 어드바이스가 된다.
- @Aspect를 사용하더라도 컴포넌트 스캔이 되는 것이 아니기에 AOP를 사용하려면 별도의 스프링 빈으로 등록해줘야 한다.
- @Bean으로 직접 등록
- @Component로 컴포넌트 스캔을 사용해서 자동 등록
- 주로 설정 파일(@Configuration) 추가할 때 사용하는 @Import로 직접 등록
- joinPoint.getSigniture() : 조인포인트의 시그니처로 메소드의 모든 정보를 가져온다.
- 반환 타입, 패키지, 메소드명, 메소드 파라미터 타입 등
- @Around 어노테이션 값은 포인트컷이 된다.
- 스프링 AOP는 AspectJ 문법을 차용하고 프록시 방식의 AOP를 제공하는 것일 뿐 AspectJ를 직접 사용하는 것이 아니다.
- AspectJ가 제공하는 어노테이션이나 관련 인터페이스만 사용하고 실제 AspectJ가 제공하는 컴파일, 로드타임 위버등은 사용하지 않는다.
- @Aspect를 포함한 aspectj 패키지 관련 기능은 aspectjweaver.jar 라이브러리가 제공하는 기능이다.
- build.gradle에 spring-boot-starter-aop를 추가하면 aspectjweaver.ja도 함께 사용 가능하다.
스프링 AOP 구현 - 포인트컷 분리
@Slf4j
@Aspect
public class AspectV2 {
@Pointcut("execution(* hello.aop.order..*(..))")
private void allOrder() {} //pointcut signature
@Around("allOrder()")
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("[log] {}", joinPoint.getSignature());
return joinPoint.proceed();
}
}
- @Aspect 사용 시 포인트컷 분리 예제
- @Pointcut
- @Pointcut 어노테이션으로 별도의 포인트컷 표현식을 사용해서 포인트컷을 만들 수 있다.
- 메소드 이름과 파라미터를 합쳐서 포인트컷 시그니처라고 한다.
- 예제에서 포인트컷 시그니처는 allOrder()이다.
- 메소드의 반환 타입은 무조건 void에 코드 내용은 비워둬야 한다.
- @Around 어드바이스에 포인트컷을 직접 지정해도 되지만 포인트컷 시그니처를 사용해도 된다
- @Pointcut
- @Pointcut으로 분리함으로 하나의 포인트컷 표현식을 여러 어드바이스에서 함께 사용할 수 있다.
스프링 AOP 구현 - 어드바이스 추가
@Slf4j
@Aspect
public class AspectV3 {
//hello.aop.order 패키지와 하위 패키지의 모든 메소드
@Pointcut("execution(* hello.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();
}
//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());
}
}
}
- 기존 예제에 트랜잭션 기능 추가하기 위한 Aspect 예제
- allOrder() 포인트컷은 hello.aop.order 포함한 하위 패키지를 대상으로 한다.
- allService() 포인트컷은 타입 이름 패턴이 *Service를 대상으로 한다.
- ex) OrderService, MemberService ...
- 타입 이름 패턴의 경우 클래스, 인터페이스에 모두 적용이 된다.
- @Around("allOrder() && allService()")
- 포인트컷을 &&, ||, ! 3가지로 조합할 수 있다.
- doTransaction() 어드바이스는 hello.aop.order 포함한 하위 패키지면서 타입 이름 패턴이 *Service를 대상으로 모두 적용된다.
- doLog() 어드바이스는 hello.aop.order 포함한 하위 패키지를 대상으로 모두 적용된다.
- 예제에서는 Service, Repository 하나씩만 있기에 Service는 doTransaction(), doLog() 어드바이스가 모두 적용되고 Repository에는 doLog() 어드바이스만 적용된다.
스프링 AOP 구현 - 포인트컷 참조
public class Pointcuts {
//hello.aop.order 패키지와 하위 패키지의 모든 메소드
@Pointcut("execution(* hello.aop.order..*(..))")
public void allOrder() {
} //pointcut signature
//클래스 이름 패턴이 *Service
@Pointcut("execution(* *..*Service.*(..))")
public void allService() {
}
@Pointcut("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 {...}
}
- 포인트컷을 외부 클래스에 모아두고 참조해서 사용할 수 있다.
- 외부 클래스의 포인트컷을 참조할 때는 전체 패키지 경로 및 포인트컷 시그니처를 적어줘야 한다.
스프링 AOP 구현 - 어드바이스 순서
- 어드바이스는 기본적으로 순서를 보장하지 않는다.
@Slf4j
@Aspect
public class AspectV5Order {
@Aspect
@Order(2)
public static class LogAspect {
@Around("hello.aop.order.aop.Pointcuts.allOrder()")
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("[log] {}", joinPoint.getSignature()); //join point 시그니처
return joinPoint.proceed();
}
}
@Aspect
@Order(1)
public static class TxAspect {
@Around("hello.aop.order.aop.Pointcuts.orderAndService()")
public Object doTransaction(ProceedingJoinPoint joinPoint) throws Throwable {...}
}
}
- 어드바이스 순서 지정 예제
- 순서를 지정하고 싶으면 @Aspect 적용 단위로 @Order 어노테이션을 적용해야 한다.
- 어드바이스 단위가 아니고 클래스 단위로만 적용할 수 있다.
- 하나의 Aspect에 여러 Advice가 있으면 순서를 보장받을 수 없기에 Aspect를 별도의 클래스로 분리해야 한다.
- 내부 클래스로 Aspect를 분리하더라도 내부 클래스 모두 스프링 빈으로 각각 등록해줘야 한다.
- @Order는 숫자가 작을수록 실행 우선순위가 높은 것을 의미한다.
- 기존 예제에서는 한 애스팩트에 두 개의 어드바이스가 있다.
- @Order 어노테이션은 클래스 단위로만 적용되고 하나의 애스팩트에 여러 어드바이스가 있으면 순서를 보장할 수 없다
=> 각각 내부 클래스 LogAspect, TxAspect를 만들고 각 애스팩트마다 하나의 어드바이스만 넣음으로 @Order로 순서 지정을 했다.
- @Order 어노테이션은 클래스 단위로만 적용되고 하나의 애스팩트에 여러 어드바이스가 있으면 순서를 보장할 수 없다
- 순서를 지정하고 싶으면 @Aspect 적용 단위로 @Order 어노테이션을 적용해야 한다.
스프링 AOP 구현 - 어드바이스 종류
- 어드바이스 종류
- @Around : 메소드 호출 전후에 수행, 가장 강력한 어드바이스, 조인 포인트 실행 여부 선택, 반환 값 변환, 예외 변환 등 가능
- @Before : 조인 포인트 실행 이전에 실행
- @AfterReturning : 조인 포인트가 정상 완료 후 실행
- @AfterThrowing : 메소드가 예외를 던지는 경우 실행
- @After : 조인 포인트가 정상 또는 예외에 관계없이 실행(finally)
- @Around의 큰 하나의 기능을 @Before, @After ... 등 작은 기능으로 쪼개놓은 것과 같다.
@Aspect
@Slf4j
public class AspectV6Advice {
@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());
}
}
@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 doReturn(JoinPoint joinPoint, Exception ex) {
log.info("[ex] {} message={}", joinPoint.getSignature(), ex);
}
@After(value = "hello.aop.order.aop.Pointcuts.orderAndService()")
public void doAfter(JoinPoint joinPoint) {
log.info("[after] {}", joinPoint.getSignature());
}
}
- 여러 종류의 어드바이스 적용 예제
- @Around를 제외한 나머지 어드바이스들은 @Around의 기능의 일부만 제공한다.
- @Around 어드바이스만 사용해도 필요한 기능을 모두 수행할 수 있다.
- 모든 어드바이스는 JoinPoint를 첫번째 인수로 전달할 수 있다. (생략 가능)
- @Around는 ProceedJoinPoint를 사용해야 한다.
- @Before : 조인 포인트 실행 전에 실행
- @Around와 달리 작업 흐름을 변경할 수 없다.
- @Around는 proceed()로 다음 대상을 호출해야하지만 @Before는 proceed() 자체를 사용하지 않으며 메소드 종료 시 자동으로 다음 타겟이 호출된다.
- 예외 발생 시 다음 코드가 호출되지 않는다.
- @AfterReturning : 메소드 실행이 정상적으로 반환될 때 실행
- 파라미터 변수명과 returning 속성에 사용된 이름과 일치해야 하며 서로 매칭해준다.
- value 속성에는 포인트컷을 지정해준다.
- returning 절에 지정된 타입의 값을 반환하는 메소드만 대상으로 실행한다. (하위 타입도 인정)
- 예제에서 반환 타입을 Object로 했기에 모든 메소드를 대상으로 실행할 수 있다.
- ex) 반환 타입을 Integer로 지정하면 반환 타입이 Integer가 아닌 String, void 등의 메소드 대상으로는 @AfterReturning 자체가 실행되지 않는다.
- @Around와 달리 반환되는 객체를 조작할 수는 있으나 변경할 수는 없다.
- 반환 객체를 변경하려면 @Around를 사용해야 한다.
- ex) @AfterReturning은 setter를 통해 값을 조작할 수는 있으나 객체 자체를 다른 객체로 변경할 수는 없다.
- @AfterThrowing : 메소드 실행이 예외를 던져서 종료될 때 실행
- value 속성에는 포인트컷을 지정해준다.
- throwing 속성에 사용된 이름과 파라미터 변수명과 일치해야 하며 서로 매칭해준다.
- throwing 절에 지정된 타입과 맞는 예외를 대상으로 실행한다. (하위 타입도 인정)
- throw를 통해 직접 예외를 밖으로 던져주지 않아도 자동으로 예외를 밖으로 던져준다.
- @After : 메소드 실행이 종료되면 실행
- 정상 및 예외 반환 조건을 모두 처리한다.(finally와 유사)
- 주로 리소스를 해제하는데 사용한다.
- @Around : 메소드의 실행의 주변에서 실행
- 가장 강력한 어드바이스
- ProceedJoinPoint.proceed()로 조인 포인트 실행 여부 선택
- ProceedJoinPoint.proceed(args[])로 전달 값 변환
- 반환 값 변환
- 예외 변환
- 트랜잭션 처럼 try ~ catch ~ finally 모두 들어가는 구문 처리가 가능하다.
- 첫 파라미터는 ProceedJoinPoint를 사용해야 한다.
- proceed()를 통해 대상을 실행하며 여러번 실행할 수도 있다.
- 가장 강력한 어드바이스
- @Around를 제외한 나머지 어드바이스들은 @Around의 기능의 일부만 제공한다.
- JoinPoint 인터페이스 주요 기능
- getArgs() : 메소드 인수를 반환
- getThis() : 프록시 객체를 반환
- getTarget() : 대상 객체를 반환
- getSignature() : 어드바이스되는 메소드에 대한 설명을 반환
- toString() : 어드바이스되는 방법에 대한 유용한 설명을 인쇄
- JoinPoint의 하위 타입인 ProceedJoinPoint 주요 기능
- proceed() : 다음 어드바이스나 타겟을 호출
- @Around는 다음 어드바이스나 타겟을 직접 호출해야하기에 proceed()를 호출할 수 있는 ProceedJoinPoint를 사용해야 한다.
- 동일한 @Aspect 안에서 어드바이스 종류별 실행 우선순위
- 실행 순서 : @Around -> @Before -> @After -> @AfterReturning -> @AfterThrowing
리턴 순서는 실행 순서와 반대이며 @Before는 리턴 시 호출되지 않는다. - @Aspect안에 동일한 종류의 어드바이스가 2개 이상 있으면 순서는 보장되지 않는다.
- 이 경우 @Aspect를 분리하고 @Order를 사용해야 한다.
- 실행 순서 : @Around -> @Before -> @After -> @AfterReturning -> @AfterThrowing
- @Around 외에 다른 어드바이스가 존재하는 이유
- @Around는 가장 넓은 기능을 제공하지만 실수(다음 대상 호출을 안하는 경우 등..)를 할 가능성이 존재
- @Before, @After 등은 기능은 적지만 실수할 가능성이 적고 코드도 단순하며 코드를 작성한 의도가 명확하게 드러난다.
정리
- @Aspect : 부가 기능을 제공하는 Aspect 클래스를 생성하는 어노테이션
- 별도로 스프링 빈으로 등록해줘야 한다.(@Component, @Bean, @Import)
- @Around : 포인트컷을 지정하는 어노테이션
- 값으로 포인트컷을 직접 지정하거나 포인트컷 시그니처를 지정할 수 있다.
- 포인트 컷을 직접 지정하는 경우 AspectJ 포인트컷 표현식을 사용하며 형태는 "execution ~ " 이다.
- joinPoint.getSigniture() : 조인포인트의 시그니처로 메소드의 모든 정보를 가져온다.
- @Pointcut : 별도의 포인트컷 표현식을 사용해서 포인트컷을 만드는 어노테이션
- 메소드 이름과 파라미터를 합쳐서 포인트컷 시그니처라고 한다.
- 메소드의 반환 타입은 무조건 void에 코드 내용은 비워둬야 한다.
- 외부 클래스의 포인트컷을 참조하는 경우 전체 패키지 경로와 포인트컷 시그니처를 모두 명시해줘야한다.
- 어드바이스 실행 순서 지정
- 기본적으로 어드바이스는 실행 순서를 보장하지 않지만 순서를 지정하고 싶으면 @Aspect 적용 단위로 @Order 어노테이션을 적용해야 한다.
- 어드바이스 단위가 아니고 클래스 단위로만 적용할 수 있다.
- 하나의 Aspect에 여러 Advice가 있으면 순서를 보장받을 수 없기에 Aspect를 별도의 클래스로 분리해야 한다.
- 내부 클래스로 Aspect를 분리하더라도 내부 클래스 모두 스프링 빈으로 각각 등록해줘야 한다.
- @Order는 숫자가 작을수록 실행 우선순위가 높은 것을 의미한다.
- 어드바이스 종류
- @Before : 조인 포인트 실행 전에 실행
- @AfterReturning : 메소드 실행이 정상적으로 반환될 때 실행
- @AfterThrowing : 메소드 실행이 예외를 던져서 종료될 때 실행
- @After : 메소드 실행이 종료되면 실행
- @Around : 메소드의 실행의 주변에서 실행
동일한 @Aspect 안에서 어드바이스 종류별 실행 우선순위
- 실행 순서 : @Around -> @Before -> @After -> @AfterReturning -> @AfterThrowing
- 모든 어드바이스는 JoinPoint를 첫번째 인수로 전달할 수 있다. (생략 가능)
- @Around는 ProceedJoinPoint를 사용해야 한다.
출처 : [인프런 김영한 스프링 핵심 원리 - 고급편]
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B3%A0%EA%B8%89%ED%8E%B8/dashboard
스프링 핵심 원리 - 고급편 강의 | 김영한 - 인프런
김영한 | 스프링의 핵심 원리와 고급 기술들을 깊이있게 학습하고, 스프링을 자신있게 사용할 수 있습니다., 핵심 디자인 패턴, 쓰레드 로컬, 스프링 AOP스프링의 3가지 핵심 고급 개념 이해하기
www.inflearn.com
'Spring > [인프런 김영한 스프링 핵심 원리 - 고급편]' 카테고리의 다른 글
[인프런 김영한 스프링 핵심 원리 - 고급편] 스프링 AOP - 실전 예제 (0) | 2024.12.27 |
---|---|
[인프런 김영한 스프링 핵심 원리 - 고급편] 스프링 AOP - 포인트컷 (0) | 2024.12.27 |
[인프런 김영한 스프링 핵심 원리 - 고급편] 스프링 AOP 개념 (0) | 2024.12.16 |
[인프런 김영한 스프링 핵심 원리 - 고급편] @Aspect AOP (0) | 2024.12.15 |
[인프런 김영한 스프링 핵심 원리 - 고급편] 빈 후처리기 (1) | 2024.12.15 |