지난 포스팅에서 언급한 Spring Framework의 핵심 프로그래밍 모델 중 하나인 관점 지향 프로그래밍(Aspect-Oriented Programming, AOP)에 대해서 좀 더 자세히 살펴본다.
먼저 Aspect라는 단어에 대해서 알아봐야 한다. Aspect는 측면, 관점이라는 뜻으로 AOP는 애플리케이션을 바라보는 관점을 하나 하나의 기능들을 횡단 관심사의 관점으로 보는 것이다.
- 핵심 기능 : 업무 로직을 포함하는 기능
- 부가 기능 : 핵심 기능을 도와주는 부가 기능, 횡단 관심
(ex. 로깅, 보안, 트랜잭션 등)- Aspect : 부가 기능을 정의한 코드인 어드바이스(Advice)와 어드바이스를 어디에 적용할지 결정하는 포인트컷(PointCut)을 합친 개념
(Advice + PointCut ⇒ Aspect)
OOP에서는 관심사 분리의 디자인 원칙을 준수해야한다. Spring MVC 구조는 @Controller
, @Service
, @Repository
와 같이 계층을 나누어 객체를 관리하게 되는데 이는 관심사 분리가 적용되었다.
OOP만 사용해선 횡단 관심사 코드를 깔끔하게 분리하고 비즈니스 코드에 적용하기 어려웠다. 예를 들어 비즈니스 코드마다 로깅 기능(부가 기능)이 들어간다면 비즈니스 코드마다 공통적으로 로깅 기능을 반복해서 수행해야한다. 여러 곳에 적용하게 되면 코드 중복과 수정이 번거로운 문제가 발생했다. 이러한 OOP의 관심사 분리에 대한 한계적인 부분을 해결하고자 AOP가 등장하게 되었다.
1. Aspect
2. 조인 포인트
S : 메서드 실행전의 포인트
E : 메서드 실행 후의 포인트
이러한 포인트를 어드바이스가 적용될 조인 포인트라고 한다.
3. 어드바이스(Advice)
4. 위빙(Weaving)
5. AOP 프록시(Proxy)
6. 타겟(Target)
7. 어드바이저(Advisor)
어드바이스는 기본적으로 순서를 보장하지 않는다. 순서를 지정하려면 @Aspect
적용 단위로 org.springframework.core.annotation.@Order
어노테이션을 적용해야한다. 어드바이스 단위가 아니라 클래스 단위로 적용할 수 있고 Aspect를 별도의 클래스로 분리해야한다.
조인 포인트 이전에 실행되고 Before Advice를 구현한 메서드는 일반적으로 리턴 타입이 void이다. 리턴 값을 갖더라도 Advice 적용과정에 아무 영향이 없다. 주의할 점으로 메서드에서 예외를 발생시키면 대상 객체의 메서드가 호출되지 않게 된다.
@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);
}
메서드 실행이 정상적으로 반환될 때 실행되고 returnning 속성에 사용된 이름은 어드바이스 메서드의 매개변수 이름과 일치해야 한다. 또한 returnning 절에 지정된 타입의 값을 반환하는 메서드만 대상을 실행한다.
메서드가 예외를 던지는 경우에 실행된다.
@AfterThrowing(value = "hello.aop.order.aop.Pointcuts.orderAndService()", throwing = "ex")
public void doThrowing(JoinPoint joinPoint, Exception ex) {
log.info("[ex] {} message={}", joinPoint.getSignature(), ex.getMessage());
}
메서드 실행이 예외를 던져서 종료될 때 실행하며 throwing 속성에 사용된 이름은 어드바이스 메서드의 매개변수 이름과 동일해야하며 지정된 타입과 맞는 예외를 대상으로 실행한다.
조인 포인트의 동작(정상 또는 예외)과는 상관없이 실행되고 메서드 실행 후 공통 기능을 실행한다. 일반적으로 리소스를 해제하는데 사용된다.
메서드 호출 전후에 수행하며 가장 강력한 어드바이스이다. 메서드 실행 전, 후, 예외 발생 시점에 공통 기능을 실행한다. 어드바이스의 첫번째 파라미터는 ProceedingJointPoint
를 사용해야 한다.
proceed()를 통해 대상을 실행하며 여러번 실행할 수 있다. @Around
만 있어도 모든 기능 수행이 가능하지만 역할을 명확히 하기 위해 타입별 다른 Advice를 사용하는 것이 좋다.
포인트컷은 관심 조인 포인트를 결정하므로 어드바이스가 실행되는 시기를 제어할 수 있다. AspectJ
는 포인트컷을 편리하게 표현하기 위한 특별한 표현식을 제공한다.
ex) @Pointcut("execution( hello.aop.order..(..))")
종류 | 설명 |
---|---|
execution | 메서드 실행 조인트 포인트를 매칭한다. 스프링 AOP에서 가장 많이 사용하며, 기능도 복잡하다. |
within | 특정 타입 내의 조인 포인트를 매칭한다. |
args | 인자가 주어진 타입의 인스턴스인 조인 포인트 |
this | 스프링 빈 객체(스프링 AOP 프록시)를 대상으로 하는 조인 포인트 |
target | Target 객체(스프링 AOP 프록시가 가르키는 실제 대상)를 대상으로 하는 조인 포인트 |
@target | 실행 객체의 클래스에 주어진 타입의 애너테이션이 있는 조인 포인트 |
@within | 주어진 애너테이션이 있는 타입 내 조인 포인트 |
@annotation | 메서드가 주어진 애너테이션을 가지고 있는 조인 포인트를 매칭 |
@args | 전달된 실제 인수의 런타임 타입이 주어진 타입의 애너테이션을 갖는 조인 포인트 |
bean | 스프링 전용 포인트컷 지시자이고 빈의 이름으로 포인트컷을 지정한다. |
포인트컷 표현식은 &&, ||, !를 사용하여 결합할 수 있다.
@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {}
@Pointcut("within(com.xyz.myapp.trading..*)")
private void inTrading() {}
@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {}
1. 모든 공개 메서드 실행
execution(public * *(..))
2. set 다음 이름으로 시작하는 모든 메서드 실행
execution( set(..))
3. AccountService 인터페이스에 의해 정의된 모든 메소드의 실행
execution( com.xyz.service.AccountService.(..))
4. service 패키지에 정의된 메서드 실행
execution( com.xyz.service..*(..))
5. 서비스 패키지 또는 해당 하위 패키지 중 하나에 정의된 메서드 실행
execution( com.xyz.service...*(..))
6. 서비스 패키지 내의 모든 조인 포인트 (Spring AOP에서만 메서드 실행)
within(com.xyz.service.*)
7. 서비스 패키지 또는 하위 패키지 중 하나 내의 모든 조인 포인트 (Spring AOP에서만 메서드 실행)
within(com.xyz.service..*)
8. AccountService 프록시가 인터페이스를 구현하는 모든 조인 포인트 (Spring AOP에서만 메서드 실행)
this(com.xyz.service.AccountService)
9. AccountService 대상 객체가 인터페이스를 구현하는 모든 조인 포인트 (Spring AOP에서만 메서드 실행)
target(com.xyz.service.AccountService)
10. 단일 매개변수를 사용하고 런타임에 전달된 인수가 Serializable과 같은 모든 조인 포인트 (Spring AOP에서만 메소드 실행)
args(java.io.Serializable)
11. 대상 객체에 @Transactional 애너테이션이 있는 모든 조인 포인트 (Spring AOP에서만 메서드 실행)
@target(org.springframework.transaction.annotation.Transactional)
12. 실행 메서드에 @Transactional 애너테이션이 있는 조인 포인트 (Spring AOP에서만 메서드 실행)
@annotation(org.springframework.transaction.annotation.Transactional)
13. 단일 매개 변수를 사용하고 전달된 인수의 런타임 유형이 @Classified 애너테이션을 갖는 조인 포인트(Spring AOP에서만 메서드 실행)
@args(com.xyz.security.Classified)
14. tradeService 라는 이름을 가진 스프링 빈의 모든 조인 포인트 (Spring AOP에서만 메서드 실행)
bean(tradeService)
15. 와일드 표현식 *Service 라는 이름을 가진 스프링 빈의 모든 조인 포인트
bean(*Service)