Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- SpringBoot
- kotlin
- 김영한
- 자바
- 스프링 핵심 원리
- Servlet
- Exception
- pointcut
- Spring Boot
- spring
- JPQL
- Thymeleaf
- db
- 그리디
- AOP
- http
- springdatajpa
- QueryDSL
- 알고리즘
- transaction
- Android
- 인프런
- Proxy
- JDBC
- 스프링 핵심 기능
- java
- 백준
- jpa
- 스프링
- Greedy
Archives
- Today
- Total
개발자되기 프로젝트
[스프링AOP] Advice 종류 본문
어드바이스는 앞서 살펴본 @Around 외에도 여러가지 종류가 있다.
1. Advice종류
- @Around : 메서드 호출 전후에 수행, 가장 강력한 어드바이스,
조인 포인트 실행 여부 선택, 반환 값 변환, 예외 변환 등이 가능 ->모든 것을 할 수 있음. - @Before : 조인 포인트 실행 이전에 실행
- @AfterReturning : 조인 포인트가 정상 완료후 실행
- @AfterThrowing : 메서드가 예외를 던지는 경우 실행
- @After : 조인 포인트가 정상 또는 예외에 관계없이 실행(finally)
2. AspectV6
@Slf4j
@Aspect
public class AspectV6Advice {
/*
//hello.aop.order 패키지와 하위 패키지이면서, 클래스 이름 패턴이 *Service
@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());
//@Before 끝나고 알아서 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);
//return 값인 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);
//호출 되고 throw e; 호출됨.
}
@After(value = "hello.aop.order.aop.Pointcuts.orderAndService()")
public void doAfter(JoinPoint joinPoint){
log.info("[after] {}", joinPoint.getSignature());
}
}
- doTransaction() 메서드에 남겨둔 주석을 보자.
- 복잡해 보이지만 사실 @Around 를 제외한 나머지 어드바이스들은 @Around 가 할 수 있는 일의 일부만
제공할 뿐이다. - 따라서 @Around 어드바이스만 사용해도 필요한 기능을 모두 수행할 수 있다.
3. 참고 정보 획득
모든 어드바이스는 org.aspectj.lang.JoinPoint 를 첫번째 파라미터에 사용할 수 있다. (생략해도 된다.)
단 @Around 는 ProceedingJoinPoint 을 사용해야 한다.
참고로 ProceedingJoinPoint 는 org.aspectj.lang.JoinPoint 의 하위 타입이다.
- ProceedingJoinPoint 인터페이스의 주요 기능
- proceed() : 다음 어드바이스나 타켓을 호출한다.
- @Around의 경우 개발자가 직접 proceed()를 호출해야 한다.
- 따라서 @Around를 사용하는 경우 ProceedingJointPoint를 사용해야 함.
- JoinPoint 인터페이스의 주요 기능
- getArgs() : 메서드 인수를 반환합니다.
- getThis() : 프록시 객체를 반환합니다.
- getTarget() : 대상 객체를 반환합니다.
- getSignature() : 조언되는 메서드에 대한 설명을 반환합니다.
- toString() : 조언되는 방법에 대한 유용한 설명을 인쇄합니다.
4. @Before: JoinPoint 실행 전, proceed()호출 전
@Before("hello.aop.order.aop.Pointcuts.orderAndService()")
public void doBefore(JoinPoint joinPoint){
log.info("[before] {}", joinPoint.getSignature());
//@Before 끝나고 알아서 proceed() 호출됨.
}
- @Around 와 다르게 작업 흐름을 변경할 수는 없다.
- @Around 는 ProceedingJoinPoint.proceed() 를 호출해야 다음 대상이 호출된다.
- 만약 호출하지 않으면 다음 대상이 호출되지 않는다.
- 반면에 @Before 는 ProceedingJoinPoint.proceed() 자체를 사용하지 않는다.
- 메서드 종료시 자동으로 다음 타켓이 호출된다. 물론 예외가 발생하면 다음 코드가 호출되지는 않는다.
5. @AfterReturning: 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);
//return 값인 result 변경이 불가능.
}
- returning 속성에 사용된 이름은 어드바이스 메서드의 매개변수 이름과 일치해야 한다.
- returning 절에 지정된 타입(Object)의 값을 반환하는 메서드만 대상으로 실행한다.
(부모 타입을 지정하면 모든 자식 타입은 인정된다.)
예를 들어 returning절에 String을 지정했다면, String을 반환하는 메서드만 대상으로 실행됨. - @Around 와 다르게 반환되는 객체를 변경할 수는 없다.
반환 객체를 변경하려면 @Around 를 사용해야 한다.
참고로 반환 객체를 조작할 수 는 있다.
6. @AfterThrowing: proceed() 실행이 예외를 던져서 종료될 때 실행
@AfterThrowing(value = "hello.aop.order.aop.Pointcuts.orderAndService()", throwing = "ex")
public void doThrowing(JoinPoint joinPoint, Exception ex){
log.info("[ex] {} message={}", joinPoint.getSignature(), ex);
//호출 되고 throw e; 호출됨.
}
- throwing 속성에 사용된 이름은 어드바이스 메서드의 매개변수 이름과 일치해야 한다.
- throwing 절에 지정된 타입과 맞은 예외를 대상으로 실행한다.
(부모 타입을 지정하면 모든 자식 타입은 인정된다.)
7. @After
@After(value = "hello.aop.order.aop.Pointcuts.orderAndService()")
public void doAfter(JoinPoint joinPoint){
log.info("[after] {}", joinPoint.getSignature());
}
- 메서드 실행이 종료되면 실행된다. (finally를 생각하면 된다.)
- 정상 및 예외 반환 조건을 모두 처리한다.
- 일반적으로 리소스를 해제하는 데 사용한다.
8. @Around
- 메서드의 실행의 주변에서 실행된다. 메서드 실행 전후에 작업을 수행한다.
- 가장 강력한 어드바이스
- 조인 포인트 실행 여부 선택 joinPoint.proceed() 호출 여부 선택
- 전달 값 변환: joinPoint.proceed(args[])
- 반환 값 변환
- 예외 변환
- 트랜잭션 처럼 try ~ catch~ finally 모두 들어가는 구문 처리 가능
- 어드바이스의 첫 번째 파라미터는 ProceedingJoinPoint 를 사용해야 한다.
- proceed() 를 통해 대상을 실행한다.
- proceed() 를 여러번 실행할 수도 있음(재시도)
9. Test 실행
@Slf4j
@SpringBootTest
@Import(AspectV6Advice.class) //스프링 빈으로 등록
public class AopTest {
@Autowired
OrderService orderService;
@Autowired
OrderRepository orderRepository;
@Test
void aopInfo(){
log.info("isAopProxy, orderService={}", AopUtils.isAopProxy(orderService));
log.info("isAopProxy, orderRepository={}", AopUtils.isAopProxy(orderRepository));
}
@Test
void success(){
orderService.orderItem("itemA");
}
@Test
void exception(){
assertThatThrownBy(() -> orderService.orderItem("ex")).isInstanceOf(IllegalStateException.class);
}
}
[before] void hello.aop.order.OrderService.orderItem(String)
[orderService] 실행
[orderRepository] 실행
[return] void hello.aop.order.OrderService.orderItem(String) return=null
[after] void hello.aop.order.OrderService.orderItem(String)
- 순서
- 스프링은 5.2.7 버전부터 동일한 @Aspect 안에서 동일한 조인포인트의 우선순위를 정했다.
- 실행 순서: @Around , @Before , @After , @AfterReturning , @AfterThrowing
- 어드바이스가 적용되는 순서는 이렇게 적용되지만, 호출 순서와 리턴 순서는 반대라는 점을 알아두자.
- 물론 @Aspect 안에 동일한 종류의 어드바이스가 2개 있으면 순서가 보장되지 않는다.
- 이 경우 앞서 배운 것 처럼 @Aspect 를 분리하고 @Order 를 적용하자.
10. @Around외에 다른 Advice가 있는 이유??
@Around("hello.aop.order.aop.Pointcuts.orderAndService()")
public void doBefore(ProceedingJoinPoint joinPoint) {
log.info("[before] {}", joinPoint.getSignature());
}
- 이 코드는 타켓을 호출하지 않는 문제가 있다.
- 이 코드를 개발한 의도는 타켓 실행 전에 로그를 출력하는 것이다.
- 그런데 @Around 는 항상 joinPoint.proceed() 를 호출해야 한다.
- 만약 실수로 호출하지 않으면 타켓이 호출되지 않는 치명적인 버그가 발생한다.
@Before("hello.aop.order.aop.Pointcuts.orderAndService()")
public void doBefore(JoinPoint joinPoint) {
log.info("[before] {}", joinPoint.getSignature());
}
- @Before 는 joinPoint.proceed() 를 호출하는 고민을 하지 않아도 된다.
- @Around 가 가장 넓은 기능을 제공하는 것은 맞지만, 실수할 가능성이 있다.
- 반면에 @Before , @After 같은 어드바이스는 기능은 적지만 실수할 가능성이 낮고, 코드도 단순하다.
- 그리고 가장 중요한 점이 있는데, 바로 이 코드를 작성한 의도가 명확하게 들어난다는 점이다.
- @Before 라는 애노테이션을 보는 순간 아~ 이 코드는 타켓 실행 전에 한정해서 어떤 일을 하는 코드구나~~
11. 좋은 설계는 제약이 있는 것이다
- 좋은 설계는 제약이 있는 것이다.
- @Around 만 있으면 되는데 왜? 이렇게 제약을 두는가? 제약은 실수를 미연에 방지한다.
- 일종에 가이드 역할을 한다.
- 만약 @Around 를 사용했는데, 중간에 다른 개발자가 해당
코드를 수정해서 호출하지 않았다면? 큰 장애가 발생했을 것이다. - 처음부터 @Before 를 사용했다면 이런 문제 자체가 발생하지 않는다.
- 제약 덕분에 역할이 명확해진다.
- 다른 개발자도 이 코드를 보고 고민해야 하는 범위가 줄어들고 코드의 의도도 파악하기 쉽다.
12. GitHub: 220105 SpringAop - Advice 종류
'인프런 > [인프런] 스프링 핵심 원리 - 고급' 카테고리의 다른 글
[스프링AOP 포인트컷] 예제코드 (0) | 2022.01.05 |
---|---|
[스프링AOP 포인트컷] Pointcut 지시자 (0) | 2022.01.05 |
[스프링AOP] 스프링AOP 구현 - Advice 순서 (0) | 2022.01.05 |
[스프링AOP] 스프링AOP 구현 - Pointcut 참조 (0) | 2022.01.05 |
[스프링AOP] 스프링AOP 구현 - 여러 Advice (0) | 2022.01.05 |
Comments