[스프링 핵심 원리 - 고급편] #11. 스프링 AOP 구현 #756
Develop-KIM
started this conversation in
동환
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
스프링 AOP 구현1 - 시작
스프링 AOP를 구현하는 일반적인 방법은 앞서 학습한
@Aspect를 사용하는 방법이다.AspectV1
@Around애노테이션의 값인execution(* hello.aop.order..*(..))는 포인트컷이 된다.@Around애노테이션의 메서드인doLog는 **어드바이스(Advice)**가 된다.execution(* hello.aop.order..*(..))는hello.aop.order패키지와 그 하위 패키지(..)를 지정하는 AspectJ 포인트컷 표현식이다.OrderService,OrderRepository의 모든 메서드는 AOP 적용의 대상이 된다.AopTest - 추가
@Aspect는 애스펙트라는 표식이지 컴포넌트 스캔이 되는 것은 아니다. 따라서AspectV1를 AOP로 사용하려면 스프링 빈으로 등록해야 한다.스프링 빈으로 등록하는 방법은 다음과 같다.
@Bean을 사용해서 직접 등록@Component컴포넌트 스캔을 사용해서 자동 등록@Import주로 설정 파일을 추가할 때 사용(@Configuration)@Import는 주로 설정 파일을 추가할 때 사용하지만, 이 기능으로 스프링 빈도 등록할 수 있다.테스트에서는 버전을 올려가면서 변경할 예정이어서 간단하게
@Import기능을 사용한다.AopTest에@Import(AspectV1.class)로 스프링 빈을 추가했다.AopUtils.isAopProxy(...)도 프록시가 적용되었으므로true를 반환한다.실행 - success()
스프링 AOP 구현2 - 포인트컷 분리
@Around에 포인트컷 표현식을 직접 넣을 수 도 있지만,@Pointcut애노테이션을 사용해서 별도로 분리할 수 도 있다.AspectV2
@pointcut
@Pointcut에 포인트컷 표현식을 사용한다.void여야 한다.allOrder()이다. 이름 그대로 주문과 관련된 모든 기능을 대상으로 하는 포인트컷이다.@Around어드바이스에서는 포인트컷을 직접 지정해도 되지만, 포인트컷 시그니처를 사용해도 된다.여기서는
@Around("allOrder()")를 사용한다.private,public같은 접근 제어자는 내부에서만 사용하면private을 사용해도 되지만다른 애스팩트에서 참고하려면
public을 사용해야 한다.결과적으로
AspectV1과 같은 기능을 수행한다. 이렇게 분리하면 하나의 포인트컷 표현식을 여러 어드바이스에서 함께 사용할 수 있다.AspectV2 - 적용
실행 - success()
테스트를 실행해보면 다음과 같이 로그가 잘 출력되는 것을 확인할 수 있다.
스프링 AOP 구현3 - 어드바이스 추가
로그를 출력하는 기능에 추가로 트랜잭션을 적용하는 코드도 추가 (실제론 트랜잭션을 실행하는 것이 아니라 동작하는 것처럼 로그만 남김)
트랜잭션 기능은 보통 다음과 같이 동작한다.
AspectV3
allOrder()포인트컷은hello.aop.order패키지와 하위 패키지를 대상으로 한다.allService()포인트컷은 타입 이름 패턴이*Service를 대상으로 하는데쉽게 이야기해서
XxxService처럼Service로 끝나는 것을 대상으로 한다.*Servi*과 같은 패턴도 가능하다.@Around("allOrder() && allService()")&&(AND),||(OR),!(NOT) 3가지 조합이 가능하다.hello.aop.order패키지와 하위 패키지 이면서 타입 이름 패턴이*Service인 것을 대상으로 한다.doTransaction()어드바이스는OrderService에만 적용된다.doLog()어드바이스는OrderService,OrderRepository에 모두 적용된다.포인트컷이 적용된 AOP 결과는 다음과 같다.
orderService:doLog(),doTransaction()어드바이스 적용orderRepository:doLog()어드바이스 적용AspectV3 - 적용
실행 - success()
전체 실행 순서 분석
AOP 적용 전
클라이언트
orderService.orderItem()orderRepository.save()AOP 적용 후
doLog()doTransaction()]orderService.orderItem()doLog()]orderRepository.save()orderService에는doLog(),doTransaction()두가지 어드바이스가 적용되어 있고,orderRepository에는doLog()하나의 어드바이스만 적용된 것을 확인할 수 있다.실행 - exception()
예외 상황에서는 트랜잭션 커밋 대신에 트랜잭션 롤백이 호출되는 것을 확인할 수 있다.
그런데 여기에서 로그를 남기는 순서가 [
doLog()doTransaction()] 순서로 작동한다.만약 어드바이스가 적용되는 순서를 변경하고 싶으면 어떻게 하면 될까?
스프링 AOP 구현4 - 포인트컷 참조
다음과 같이 포인트컷을 공용으로 사용하기 위해 별도의 외부 클래스에 모아두어도 된다.
Pointcuts
orderAndService():allOrder()포인트컷와allService()포인트컷을 조합해서 새로운 포인트컷을 만들었다.AspectV4Pointcut
사용하는 방법은 패키지명을 포함한 클래스 이름과 포인트컷 시그니처를 모두 지정하면 된다.
포인트컷을 여러 어드바이스에서 함께 사용할 때 이 방법을 사용하면 효과적이다.
스프링 AOP 구현5 - 어드바이스 순서
어드바이스는 기본적으로 순서를 보장하지 않는다.
순서를 지정하고 싶으면
@Aspect적용 단위로org.springframework.core.annotation.@Order애노테이션을 적용해야 한다.문제는 이것을 어드바이스 단위가 아니라 클래스 단위로 적용할 수 있다는 점이다.
그래서 지금처럼 하나의 애스펙트에 여러 어드바이스가 있으면 순서를 보장 받을 수 없다. 따라서 애스펙트를 별도의 클래스로 분리해야 한다.
현재 로그를 남기는 순서가 아마도 [
doLog()doTransaction()] 이 순서로 남을 것이다.로그를 남기는 순서를 바꾸어서 [
doTransaction()doLog()] 트랜잭션이 먼저 처리되고, 이후에 로그가 남도록 변경해보자.AspectV5Order
하나의 애스펙트 안에 있던 어드바이스를
LogAspect,TxAspect애스펙트로 각각 분리했다.그리고 각 애스펙트에
@Order애노테이션을 통해 실행 순서를 적용했다.실행
실행 결과를 보면 트랜잭션 어드바이스가 먼저 실행되는 것을 확인할 수 있다.
스프링 AOP 구현6 - 어드바이스 종류
어드바이스 종류
@Around: 메서드 호출 전후에 수행, 가장 강력한 어드바이스, 조인 포인트 실행 여부 선택, 반환 값 변환, 예외 변환 등이 가능@Before: 조인 포인트 실행 이전에 실행@AfterReturning: 조인 포인트가 정상 완료후 실행@AfterThrowing: 메서드가 예외를 던지는 경우 실행@After: 조인 포인트가 정상 또는 예외에 관계없이 실행(finally)AspectV6Advice
doTransaction()메서드에 남겨둔 주석을 보자.복잡해 보이지만 사실
@Around를 제외한 나머지 어드바이스들은@Around가 할 수 있는 일의 일부만 제공할 뿐이다.따라서
@Around어드바이스만 사용해도 필요한 기능을 모두 수행할 수 있다.참고 정보 획득
모든 어드바이스는
org.aspectj.lang.JoinPoint를 첫번째 파라미터에 사용할 수 있다. (생략해도 된다.)단
@Around는ProceedingJoinPoint을 사용해야 한다.JoinPoint 인터페이스의 주요 기능
getArgs(): 메서드 인수를 반환합니다.getThis(): 프록시 객체를 반환합니다.getTarget(): 대상 객체를 반환합니다.getSignature(): 조언되는 메서드에 대한 설명을 반환합니다.toString(): 조언되는 방법에 대한 유용한 설명을 인쇄합니다.ProceedingJoinPoint 인터페이스의 주요 기능
proceed(): 다음 어드바이스나 타켓을 호출한다.어드바이스 종류
@Before조인 포인트 실행 전
@Around와 다르게 작업 흐름을 변경할 수는 없다.@Around는ProceedingJoinPoint.proceed()를 호출해야 다음 대상이 호출된다. 만약 호출하지 않으면 다음 대상이 호출되지 않는다.반면에
@Before는ProceedingJoinPoint.proceed()자체를 사용하지 않는다.메서드 종료시 자동으로 다음 타켓이 호출된다. 물론 예외가 발생하면 다음 코드가 호출되지는 않는다.
@AfterReturning
메서드 실행이 정상적으로 반환될 때 실행
returning속성에 사용된 이름은 어드바이스 메서드의 매개변수 이름과 일치해야 한다.returning절에 지정된 타입의 값을 반환하는 메서드만 대상으로 실행한다. (부모 타입을 지정하면 모든 자식 타입은 인정된다.)@Around와 다르게 반환되는 객체를 변경할 수는 없다. 반환 객체를 변경하려면@Around를 사용해야 한다. 참고로 반환 객체를 조작할 수 는 있다.@AfterThrowing
메서드 실행이 예외를 던져서 종료될 때 실행
throwing속성에 사용된 이름은 어드바이스 메서드의 매개변수 이름과 일치해야 한다.throwing절에 지정된 타입과 맞는 예외를 대상으로 실행한다. (부모 타입을 지정하면 모든 자식 타입은 인정된다.)@after
@around
joinPoint.proceed() 호출 여부 선택joinPoint.proceed(args[])try ~ catch~ finally모두 들어가는 구문 처리 가능ProceedingJoinPoint를 사용해야 한다.proceed()를 통해 대상을 실행한다.proceed()를 여러번 실행할 수도 있음(재시도)실행
순서
@Aspect안에서 동일한 조인포인트의 우선순위를 정했다.@Around,@Before,@After,@AfterReturning,@AfterThrowing@Aspect안에 동일한 종류의 어드바이스가 2개 있으면 순서가 보장되지 않는다.이 경우 앞서 배운 것 처럼
@Aspect를 분리하고@Order를 적용하자.@around 외에 다른 어드바이스가 존재하는 이유
@Around하나만 있어도 모든 기능을 수행할 수 있다. 그런데 다른 어드바이스들이 존재하는 이유는 무엇일까?@Before는joinPoint.proceed()를 호출하는 고민을 하지 않아도 된다.@Around가 가장 넓은 기능을 제공하는 것은 맞지만, 실수할 가능성이 있다.반면에
@Before,@After같은 어드바이스는 기능은 적지만 실수할 가능성이 낮고, 코드도 단순하다.그리고 가장 중요한 점이 있는데, 바로 이 코드를 작성한 의도가 명확하게 드러난다는 점이다.
@Before라는 애노테이션을 보는 순간 이 코드는 타켓 실행 전에 한정해서 어떤 일을 하는 코드구나 라는 것이 드러난다.좋은 설계는 제약이 있는 것이다
좋은 설계는 제약이 있는 것이다.
@Around만 있으면 되는데 왜? 이렇게 제약을 두는가? 제약은 실수를 미연에 방지한다.일종의 가이드 역할을 한다. 만약
@Around를 사용했는데, 중간에 다른 개발자가 해당 코드를 수정해서 호출하지 않았다면? 큰 장애가 발생했을 것이다.처음부터
@Before를 사용했다면 이런 문제 자체가 발생하지 않는다. 제약 덕분에 역할이 명확해진다.다른 개발자도 이 코드를 보고 고민해야 하는 범위가 줄어들고 코드의 의도도 파악하기 쉽다.
All reactions