AOP

박윤택·2022년 6월 21일
1

Spring

목록 보기
5/18

AOP

지난 포스팅에서 언급한 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가 등장하게 되었다.


AOP 용어 및 개념들

1. Aspect

  • 여러 객체에 공통으로 적용되는 기능
  • 어드바이스(부가기능 정의 코드) + 포인트컷(어드바이스 적용 범위 결정)을 모듈화하여 애플리케이션에 포함되는 횡단 기능

2. 조인 포인트


S : 메서드 실행전의 포인트
E : 메서드 실행 후의 포인트
이러한 포인트를 어드바이스가 적용될 조인 포인트라고 한다.

  • 애플리케이션 실행 흐름에서의 특정 포인트를 의미
  • 조인 포인트에 관심 코드(Aspect Code)를 추가할 수 있다.
  • 횡단 관심은 조인포인트 전/후에 AOP에 의해 자동으로 추가
  • 스프링 AOP는 프록시 방식을 사용하여 조인 포인트는 항상 메서드 실행 시점으로 제한

3. 어드바이스(Advice)

  • 조인포인트에서 수행되는 코드를 의미
  • 부가 기능에 해당

4. 위빙(Weaving)

  • Adivce를 핵심 코드에 적용하는 것
  • 핵심 코드 기능에 영향을 주지 않고 부가 기능을 추가할 수 있다.

5. AOP 프록시(Proxy)

  • AOP 기능을 구현하기 위해 만든 프록시 객체
  • 스프링에서 AOP 프록시는 JDK 동적 프록시 또는 CGLIB 프록시다.

6. 타겟(Target)

  • 핵심 기능을 담고 있는 모듈로 타겟은 부가기능을 부여할 대상
  • Advice를 받는 객체이고 포인트 컷으로 결정된다.

7. 어드바이저(Advisor)

  • 하나의 어드바이스와 하나의 포인트 컷으로 구성된다.
  • 스프링 AOP에서만 사용되는 특별한 용어

타입별 Advice

Advice 순서

어드바이스는 기본적으로 순서를 보장하지 않는다. 순서를 지정하려면 @Aspect 적용 단위로 org.springframework.core.annotation.@Order 어노테이션을 적용해야한다. 어드바이스 단위가 아니라 클래스 단위로 적용할 수 있고 Aspect를 별도의 클래스로 분리해야한다.


Advice 종류

1. Before

조인 포인트 이전에 실행되고 Before Advice를 구현한 메서드는 일반적으로 리턴 타입이 void이다. 리턴 값을 갖더라도 Advice 적용과정에 아무 영향이 없다. 주의할 점으로 메서드에서 예외를 발생시키면 대상 객체의 메서드가 호출되지 않게 된다.

@Before("hello.aop.order.aop.Pointcuts.orderAndService()")
public void doBefore(JoinPoint joinPoint) {
    log.info("[before] {}", joinPoint.getSignature());
}

2. After returning

조인 포인트가 정상 완료 된 후 실행된다.

@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 절에 지정된 타입의 값을 반환하는 메서드만 대상을 실행한다.


3. After throwing

메서드가 예외를 던지는 경우에 실행된다.

@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 속성에 사용된 이름은 어드바이스 메서드의 매개변수 이름과 동일해야하며 지정된 타입과 맞는 예외를 대상으로 실행한다.


4. After (finally)

조인 포인트의 동작(정상 또는 예외)과는 상관없이 실행되고 메서드 실행 후 공통 기능을 실행한다. 일반적으로 리소스를 해제하는데 사용된다.

5. Around

메서드 호출 전후에 수행하며 가장 강력한 어드바이스이다. 메서드 실행 전, 후, 예외 발생 시점에 공통 기능을 실행한다. 어드바이스의 첫번째 파라미터는 ProceedingJointPoint를 사용해야 한다.
proceed()를 통해 대상을 실행하며 여러번 실행할 수 있다. @Around만 있어도 모든 기능 수행이 가능하지만 역할을 명확히 하기 위해 타입별 다른 Advice를 사용하는 것이 좋다.


Pointcut 표현식

포인트컷은 관심 조인 포인트를 결정하므로 어드바이스가 실행되는 시기를 제어할 수 있다. AspectJ는 포인트컷을 편리하게 표현하기 위한 특별한 표현식을 제공한다.

ex) @Pointcut("execution( hello.aop.order..(..))")


포인트컷 지시자 종류

종류설명
execution메서드 실행 조인트 포인트를 매칭한다. 스프링 AOP에서 가장 많이 사용하며, 기능도 복잡하다.
within특정 타입 내의 조인 포인트를 매칭한다.
args인자가 주어진 타입의 인스턴스인 조인 포인트
this스프링 빈 객체(스프링 AOP 프록시)를 대상으로 하는 조인 포인트
targetTarget 객체(스프링 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)


0개의 댓글