📌 Spring AOP(Aspect Oriented Programming)에 대한 자세한 개념들은 아래 포스팅을 참고해주세요.
<Spring AOP(Aspect Oriented Programming)란?>
애플리케이션의 실행 흐름에서의 AOP를 적용할 수 있는 모든 특정 지점
스프링 컨테이너가 관리할 수 있는 스프링 빈에만 AOP 적용 가능
항상 메서드 레벨에만 AOP 적용 가능
( 스프링 AOP가 프록시 방식을 사용하기 때문 )
✔️ 프록시 방식 ( Proxy )
➜ 런타임 중일 때, 스프링 컨테이너가 빈을 생성하면서
요청을 프록시 객체가 대신 가로채서 찐 객체에 넣어주는 것
애플리케이션에 새로운 동작을 추가하기 위해, 이 조인 포인트에 관심코드(Aspect Code) 추가함
➜ 횡단 관심사(공통 기능)이 조인포인트 전/후에 AOP에 의해 자동으로 추가됨
어드바이스 적용이 필요한 곳은 애플리케이션 내에 메서드를 가짐
JointPoint 메소드는 기본적으로 어드바이스 메소드에 매개변수로 선언만 하면 됨
🔥💬 한마디로 아니 두마디로
조인포인트 == 메서드 레벨 중에 AOP 적용할 수 있는 지점인데,
여기에 Aspect Code 추가하면 공통기능이 해당 지점 전/후에 자동으로 추가되어 실행이 된다.
✔️ JointPoint 인터페이스 주요 기능
JoinPoint.getArgs()
➜ JoinPoint에 전달된 인자를 배열로 반환
⠀⠀JoinPoint.getThis()
➜ AOP 프록시 객체를 반환
⠀⠀JoinPoint.getTarget()
➜ AOP가 적용된 대상 객체를 반환
( 클라이언트가 호출한 비즈니스 메소드를 포함하는 비즈니스 객체를 반환 )
⠀⠀JoinPoint.getSignature()
➜ 조언되는 메서드에 대한 설명을 반환
( 클라이언트가 호출한 메소드의 시그니처(리턴타입, 이름, 매개변수) 정보가 저장된 Signature 객체를 반환 )✔️
Signature
가 제공하는 메서드
( 객체가 선언하는 모든 연산에서, 연산의 이름, 매개변수로 받아들이는 객체들을 시그니처라고 함 )
String getName()
➜ 클라이언트가 호출한 메소드 이름을 반환
⠀⠀String toLongString()
➜ 클라이언트가 호출한 메소드의 리턴타입, 이름, 매개변수를 패키지 경로까지 포함해서 반환
⠀⠀String toShortString()
➜ 클라이언트가 호출한 메소드 시그니처를 축약한 문자열로 반환
⠀⠀JoinPoint.toString()
➜ 조언되는 방법에 대한 유용한 설명을 인쇄
✔ ProceedingJoinPoint 인터페이스 주요 기능
proceed()
➜ 다음 어드바이스나 타켓을 호출
핵심 기능을 담고있는 모듈
( 부가기능(Advice)이 적용될 객체 )
포인트컷(Pointcut)으로 어느 Target에 들어갈지 결정됨
( 위치 선별 기능으로 결정 )
특정 조인포인트(Join Point)에서 수행될 부가기능을 정리한 코드
핵심 기능(Target)에 Aspect를 언제 적용할지( Aspect 적용 시점 ) 정의
Ex. 메서드 실행 전 / 후
시스템 전체 Aspect에 API 호출 제공
순서를 보장하지 않음
@Aspect
적용 단위로 org.springframework.core.annotation.@Order
애너테이션을 적용해야함✔️ @Before
조인포인트 이전에 실행
Target 메서드(핵심 기능)가 실행되기 전에 처리해야할 필요가 있는 부가 기능을 메서드 호출 전에 실행
Before
Advice로 구현한 메서드는 일반적으로 리턴 타입이 void
( But, 리턴 값을 갖더라도 실제 Advice 적용 과정에는 영향 X )
작업 흐름 변경 불가
메서드 종료 시, 자동으로 다음 타겟 호출
( 주의점 : 메서드에서 예외를 발생시킬 경우, 대상 객체의 메서드 호출 안됨 )
✔️ @After returning
조인포인트가 정상 완료된 후 실행
( 메서드 실행이 예외없이 정상적으로 반환된 이후 공통 기능을 실행 )
< returning 속성에 사용된 이름 == 어드바이스 메서드의 매개변수 이름 > 이어야함
returning 절에 지정된 타입의 값을 반환하는 메서드만 대상을 실행
✔️ @After throwing
메서드 실행이 예외를 던져 종료될 경우에 실행
( 메서드를 실행하는 도중 예외가 발생한 경우 공통 기능을 실행 )
< throwing 속성에 사용된 이름 == 어드바이스 메서드의 매개변수 이름 > 이어야함
throwing 절에 지정된 타입과 맞은 예외를 대상으로 실행
✔️ @After (finally)
조인 포인트의 동작과는 상관없이 메서드 실행 후 공통 기능 실행
( 실행 결과가 정상, 예외와는 상관 X )
( 예외 동작의 finally를 생각하면 됨 )
보통 리소스 해제하는데 사용
✔️ @Around
메서드 호출 전/후, 예외 발생 시점에 공통 기능 실행
Ex. 조인 포인트 실행 여부 선택, 반환 값 변환, 예외 변환 등
가장 강력한 어드바이스
어드바이스의 첫 번째 파라미터는 ProceedingJoinPoint
사용해야함
proceed()
를 통해 대상 실행하고 여러번 실행 가능
💡 사실
@Around
만 있어도 모든 기능 수행 가능
But, 고려해야할 사항이 있을 때, 정상적으로 작동이 되지 않는 경우 있음
⠀
@Before
/@After
과 같은 어드바이스는 기능은 적지만 코드도 단순하고, 원하는 대로 작동하며 각각이 애너테이션만 봐도 어떤 일을 하는지 명확하게 파악 가능
⠀
➜ 좋은 설계는@Around
만 사용하는 것보다
제약을 가지더라도 문제 자체가 발생하지 않도록 역할을 명확하게 하여 실수를 사전에 방지하는 것임
조인포인트(Join Point)에서 어드바이스(Advice)가 적용될 위치를 선별하는 기능
( 애플리케이션 실행 흐름에서 AOP를 적용할 수 있는 모든 포인트에서 수행될 부가기능 코드의 위치를 선별하는 기능 )
관심 조인포인트를 결정 ➜ 어드바이스가 실행되는 시기 제어 가능
메서드 실행 지점만 포인트컷으로 선별 가능
AspectJ 표현식을 사용해서 지정
( && / || / ! 를 사용하여 결합 가능 )
💡
build.gradle
에 아래 의존성을 추가해야만 AspectJ 라이브러리 사용 가능 !dependencies { implementation 'org.springframework.boot:spring-boot-starter-aop }
execution
➜ 메서드 실행 조인트 포인트 매칭
➜ 스프링 AOP에서 가장 많이 사용, 기능도 복잡
within
➜ 특정 타입 내의 조인 포인트 매칭
args
➜ 인자가 주어진 타입의 인스턴스인 조인 포인트
this
➜ 스프링 빈 객체(스프링 AOP 프록시)를 대상으로 하는 조인 포인트
target
➜ Target 객체(스프링 AOP 프록시가 가르키는 실제 대상)를 대상으로 하는 조인 포인트
@target
➜ 실행 객체의 클래스에 주어진 타입의 애너테이션이 있는 조인 포인트
@within
➜ 주어진 애너테이션이 있는 타입 내 조인 포인트
@annotation
➜ 메서드가 주어진 애너테이션을 가지고 있는 조인 포인트를 매칭
@args
➜ 전달된 실제 인수의 런타임 타입이 주어진 타입의 애너테이션을 갖는 조인 포인트
bean
➜ 스프링 전용 포인트컷 지시자
➜ 빈의 이름으로 포인트컷을 지정
⠀
❗ execution을 가장 많이 사용하고 나머지는 자주 사용하지 않음 !
아래와 같이 사용 !
@Pointcut(execution([접근제어자] 반환 타입 [패키지/클래스.] 메서드명 (파라미터 타입|"..",...) [throws 예외]
[ ]
)로 표시되어 있는 부분은 생략 가능*
기호를 사용하여 모든 값을 표현하는 것이 가능..
)를 사용하여 0개 이상의 수 표현 가능Ex.
(..)
➜ 모든 매개변수*(..)
➜ 모든 메서드 선택(*)
➜ 반드시 1개의 매개변수를 가지는 메소드만 선택
- 모든 공개 메서드 실행
➜execution(public * *(..))
⠀- set 다음 이름으로 시작하는 모든 메서드 실행
( 메서드명이 set~~으로 시작하는 모든 메서드 )
➜execution(* set*(..))
⠀- AccountService 인터페이스에 의해 정의된 모든 메소드의 실행
➜execution(* com.xyz.service.AccountService.*(..))
( 파일 위치가 com.xyz.service 패키지의 AccountService 인터페이스인 것 )
⠀- service 패키지에 정의된 메서드 실행
➜execution(* com.xyz.service.*.*(..))
( 파일 위치가 com.xyz.service 패키지 )
⠀- service 패키지 또는 해당 하위 패키지 중 하나에 정의된 메서드 실행
➜execution(* com.xyz.service..*.*(..))
⠀- service 패키지 내의 모든 조인 포인트
(Spring AOP에서만 메서드 실행)
➜within(com.xyz.service.*)
⠀- service 패키지 또는 하위 패키지 중 하나 내의 모든 조인 포인트
(Spring AOP에서만 메서드 실행)
➜within(com.xyz.service..*)
⠀- AccountService 프록시가 인터페이스를 구현하는 모든 조인 포인트
(Spring AOP에서만 메서드 실행)
➜this(com.xyz.service.AccountService)
⠀- AccountService 대상 객체가 인터페이스를 구현하는 모든 조인 포인트
(Spring AOP에서만 메서드 실행)
➜target(com.xyz.service.AccountService)
⠀- 단일 매개변수를 사용하고 런타임에 전달된 인수가 Serializable과 같은 모든 조인 포인트
(Spring AOP에서만 메소드 실행)
➜args(java.io.Serializable)
⠀- 대상 객체에 @Transactional 애너테이션이 있는 모든 조인 포인트
(Spring AOP에서만 메서드 실행)
➜@target(org.springframework.transaction.annotation.Transactional)
⠀- 실행 메서드에 @Transactional 애너테이션이 있는 조인 포인트
(Spring AOP에서만 메서드 실행)
➜@annotation(org.springframework.transaction.annotation.Transactional)
⠀- 단일 매개 변수를 사용하고 전달된 인수의 런타임 유형이 @Classified 애너테이션을 갖는 조인 포인트
(Spring AOP에서만 메서드 실행)
➜@args(com.xyz.security.Classified)
⠀- memberService 라는 이름의 스프링 빈의 모든 조인 포인트
(Spring AOP에서만 메서드 실행)
➜bean(memberService)
⠀- 와일드 표현식
*Service
라는 이름을 가진 스프링 빈의 모든 조인 포인트
( ~~Service로 끝나는 이름의 빈들 )
➜bean(*Service)
🔥💬 한마디로,
위치 선별 기능으로 결정한 핵심 기능의 AOP 적용 가능 포인트에 부가기능 코드를 적용하는 것
➜ 프록시 방식을 통해서 AOP가 구현되는 과정
- 컴파일 타임
➜.java
파일 ->.class
파일로 컴파일되는 시점에 Aspect가 삽입됨
➜ Spring AOP에서는 사용 불가 / AspectJ 사용할 때 사용
⠀- 클래스 로드 타임
➜ 컴파일 이후에 메모리에 올라가는 시점에 AOP가 적용
➜ 스프링 AOP에서는 사용 불가 / AspectJ 사용할 때 사용
⠀- 런타임(실행중일 때) => Spring AOP (프록시 방식)
➜ 스프링 컨테이너가 객체를 생성할 때 프록시 객체를 자동으로 생성하고 원본 객체 대신 프록시 빈으로 등록
(IoC를 통해서)
스프링 컨테이너가 빈을 생성할 때 이 요청을 프록시 객체가 가로채서@Aspect
가 되어있는 객체들을 싹 모아서 찐 객체에 넣어줌
하나의 어드바이스(Advice) + 하나의 포인트컷(Pointcut) 으로 구성
( 수행 코드(부가기능) + 그 코드의 위치 선별 기능 )
스프링 AOP에서만 사용되는 특별한 용어
📌 다음 포스팅은 Custom Annotaiton을 사용하여 Spring AOP 프로젝트에 적용에 대한 내용입니다.
⠀
👉 [Project] Custom Annotaiton을 이용하여 Spring AOP 프로젝트에 적용하기 !