Spring

[Spring Boot] 01. Spring Boot Core (3) - AOP(관점 지향 프로그래밍) *

Joo.v7 2024. 11. 8. 21:05

8. 관점 지향 프로그래밍 (AOP)

  • AOP
  • Spring AOP 활성화
  • AOP 주요 용어 정리
  • Spring AOP 문법 - Point Cut
  • Pointcut - Designator(지시자) 옵션
  • Pointcut - 표현식 예제
  • Spring AOP 문법 - Advice
  • Advice - 사용
  • Advice - JoinPoint 활용

8. 관점 지향 프로그래밍 (AOP)

AOP

  • 프로그램 구조를 다른 방식으로 생각하게 함으로써 OOP를 보완.
  • OOP에서 모듈화의 핵심 단위는 Class 이지만, AOP에서 모듈화의 핵심 단위는 Aspect.
  • 관점(Aspect)는 다양한 타입과 객체에 걸친 관심(Concern)을 모듈화 할 수 있게 한다.
  • 관심사를 분리하여, 관점별로 각각의 기능을 모듈화.
  • 설정을 추가하여 Weaving함.
    (Weaving: Advice를 Target Object에 적용하는 과정)

 

Spring AOP 활성화

  • pom.xml에 spring-boot-starter-app 의존성 추가.
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

 

Spring aop 예시)

@Aspect
@Component
public class GreetingAop {

    @Pointcut("execution(* org.example.greeting..sayHello())")
    public void cut() {
    }

    @Around("cut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {

        StopWatch stopWatch = new StopWatch();
        System.out.println("===== StopWatch start =====");
        stopWatch.start();

        Object object = joinPoint.proceed();

        stopWatch.stop();
        System.out.println("===== StopWatch stop =====");
        System.out.println(stopWatch.prettyPrint());

        return object;
    }
}

 

AOP 주요 용어 정리

더보기

Aspect

  • 관심사를 모듈화한 클래스.
  • AOP의 기본 구성 요소로서, 특정 조건에서 특정 작업을 수행하도록 구성된 모듈.
  • 하나 이상의 @Pointcut과 Advice(@Before, @After, @Around...)의 조합으로 만들어지는 AOP의 기본 모듈.
  • Spring에서는 @Aspect annotation을 포함하는 클래스.

Join Point (어디에)

  • Aspect가 적용될 수 있는 지점 (메소드 실행, Exception 처리 ... 등)
  • Pointcut의 후보.
  • Spring AOP에서는 메소드 실행만 대상이다.

Advice (무엇을)

  • 특정 Join Point에 Aspect에 의해 취해질 조치.
  • Before, After, Around, AfterReturning, AfterThrowing ... 등 다양한 타입이 있다.

Pointcut

  • Advice를 적용할 Join Point를 선별하는 작업.
  • Advice는 Pointcut 표현식과 연결되고, Pointcut이 매치한 Join Point에서 실행됨.

Target object

  • Advice가 적용되는 대상 객체

Weaving

  • Advice를 Target Object에 적용하는 과정.
  • 컴파일 타임, 로드 타임, 런타임 등에 수행될 수 있다.

AOP Proxy (Spring)

  • AOP Framework가 생성하는 객체.
  • Target Object를 감싸며, Advice 정의에 따라 동작.

 

Advisor (Spring)

  • Pointcut과 Advice를 하나씩 갖고 있는 객체.
  • 개발자가 Pointcut과 Advice를 설정하면, 이를 결합하여 Advisor 객체를 생성.

Spring AOP

  • AOP 개념을 Spring Bean에 적용하기 위한 것.
  • Spring Bean 대상이므로, ApplicationContext가 처리함.
  • Runtime weaving.
  • Spring에서는 AOP 구현을 위해 Proxy를 사용한다.
  • Spring에서는 Bean을 Proxy로 감싸서 앞 뒤에 로직을 넣는 기능을 제공한다.
  • Spring에서는 Aspect, Join point, Advice 등의 어노테이션으로 이를 제어한다.

 

Spring AOP 문법 - Point Cut

  • Target의 여러 Join point 중에서 Advice를 적용할 대상을 재정하는 키워드.
  • Spring AOP는 Spring Bean의 '메소드 실행' join point만 지원한다.
  • Point Cut 선언은 expression(조건)과 signature(이름)로 구성된다.
@Pointcut(
          "execution("           // Pointcut Designator(지시자): 어떤 타입에 적용할지를 결정
           + "[접근제한자 패턴] "   // public: 생략 가능
           + "리턴타입 패턴"       // *: 와일드 타입 사용 가능
           + "[패키지명, 클래스 경로 패턴]"          // '..', '*', '+' 와일드 카드 사용 가능
           + "메소드명 패턴(파라미터 타입 패턴|..)"  // .greet(User, String)
           + "[throws 예외 타입 패턴]"          // 생략 가능  
           +")"   
          )
 @Pointcut("execution(* org.example.greeting.Greeting.sayHello())") // the pointcut expression
public void cut() { } // the pointcut signature

 

와일드 카드

더보기

1. * (별표)

하나의 클래스, 메소드, 패키지 이름 등을 대신하는 와일드 카드.
ex) execution(* get*(..)): get으로 시작하는 모든 메소드에 매칭.

 

2. .. (점 두 개)

메소드 파라미터 인자의 수나, 패키지 경로에서 하나 이상의 하위 패키지를 포함할 수 있음을 의미.

ex) execution(* com.example..*(..)): com.example 패키지와 모든 하위 패키지의 모든 메소드를 매칭.


*() vs *(..)

첫 번째는 hello()와 같은 메소드 파라미터가 없는 메소드들을 의미.

두 번째는 hello(), hello(String a, int b)처럼 메소드 파라미터에 상관없이 모든 메소드를 의미.

 

3. + (플러스)

클래스 이름 뒤에 사용되며, 지정된 클래스 또는 인터페이스를 구현하거나 확장하는 모든 클래스와 매칭.

ex) within(com.example.service.Service+): Service interface를 구현하는 모든 클래스에 매칭.

 

Pointcut - Designator(지시자) 옵션

  • execution
    • 가장 일반적으로 사용되는 지시자로, 메소드 실행을 가로채는 데 사용
  • within
    • 특정 타입(클래스나 인터페이스) 내의 모든 조인 포인트를 매칭
    • 주로 특정 패키지나 클래스 내의 메소드를 대상으로 할 때 사용
  • this
    • 주어진 타입을 구현한 스프링 AOP Proxy 객체에 매칭
  • target
    • this와 유사하지만, 주어진 타입을 구현한 타겟 객체에 매칭
  • args
    • 메소드가 받는 인자의 타입을 기반으로 조인 포인트를 매칭
  • @taget
    • 인자가 아닌, 객체의 클래스에 붙은 어노테이션을 기준으로 조인 포인트를 매칭
  • @args
    • 메소드의 인자에 적용된 어노테이션을 기준으로 조인 포인트를 매칭
  • @within
    • 클래스 레벨에 적용된 어노테이션을 기준으로 조인 포인트를 매칭
  • @annotation
    • 메소드 레벨에 적용된 어노테이션을 기준으로 조인 포인트를 매칭
  • bean
    • 특정 빈 이름에 해당하는 빈에 대해서만 조인 포인트를 매칭

 

Pointcut - 조합

  • 포인트컷 표현식은 &&, ||, ! 으로 조합할 수 있다.
// anyPublicOperation 포인트컷은 모든 public 메소드 실행에 매칭 된다.
@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {} 

// inTrading 포인트컷은 com.xyz.myapp.trading 패키지 내의 메소드 실행에 매칭
@Pointcut("within(com.xyz.myapp.trading..*)")
private void inTrading() {} 

// tradingOperation 포인트컷은 com.xyz.myapp.trading 패키지 내의 퍼블릭 메소드 실행에 매칭
@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {} 

 

Pointcut - 표현식 예제

더보기

Spring AOP는 주로 execution Pointcut 지정자를 사용한다.

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
 
 
모든 public 메소드
execution(public * *(..))
 
 
get~ 으로 시작하는 모든 메소드
execution(* get*(..))
 
 
org.example 패키지에 있는 모든 메소드
execution(* org.example..*(..))
 
 
org.example.greeting.Greeting 인터페이스에 정의된 모든 메소드
execution(* org.example.greeting.Greeting.*(..))
 
 
org.example.greeting 패키지의 모든 메소드 실행
within(org.example.greeting.*)
 
 
SpecialGreeting Proxy 구현체의 메소드 실행
this(org.example.greeting.SpecialGreeting)
 
 
SpecialGreeting 인터페이스의 구현 객체의 메소드 실행
target(org.example.greeting.SpecialGreeting)
 
 
런타임에 SpecialHelloTarget 타입의 단일 파라미터가 전달되는 메소드 실행
args(org.example.greeting.SpecialHelloTarget)
 
 
@Transactional 어노테이션을 가진 모든 타겟 객체의 메소드 실행
@target(org.springframework.transaction.annotation.Transactional)

 

Spring AOP 문법 - Advice

  • Advice는 Pointcut과 관련하여 메소드 실행 전, 후, 전/후 를 결정하기 위해 사용.
Advice 형태설명
Before Join Point 앞에서 실행할 Advice
After Join Point 뒤에서 실행할 Advice
AfterReturning Join Point가 완전히 정상 종료한다음 실행하는 Advice
Around Join Point 앞과 뒤에서 실행되는 Advice
AfterThrowing Join Point에서 예외가 발생했을때 실행되는 Advice

 

Advice - 사용

@Aspect
@Component
public class GreetingAop {

    //TODO-1 실행 순서를 확인한다.
    // around 시작 -> before -> afterReturn(afterThrowing) -> after -> around 종료
    @Pointcut("execution(* org.example.greeting..sayHello())")
    public void cut() {
    }

    // Advice들
    @Before("cut()")
    public void before() throws Throwable {
        System.out.println("before");
    }

    @AfterReturning("cut()")
    public void afterReturning() throws Throwable {
        System.out.println("afterReturning");
    }

    @AfterThrowing("cut()")
    public void afterThrowing() throws Throwable {
        System.out.println("afterThrowing");
    }

    @After("cut()")
    public void after() {
        System.out.println("after");
    }


    @Around("cut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {

        System.out.println("around1");

        Object object = joinPoint.proceed();

        System.out.println("around2");

        return object;

    }
}

 

Advice - JoinPoint 활용

  • 모든 Advice 메소드는 첫 번째 인자JoinPoint를 받을 수 있다.
  • Around Advice는 JoinPoint의 subClass인 ProceedingJoinPoint를 반드시 사용해야 한다.

JoinPoint 의 메소드

  • getArgs() : 타겟 메소드의 인자
  • getThis() : 프록시 객체
  • getTarget() : 타겟 객체
  • getSignature() : 타겟 객체의 메소드 시그니쳐
  • toString() : 타겟 객체의 메소드 정보
@Slf4j
@Component
@Aspect
public class AccountAop {

    @Pointcut("execution(* com.example.demo.account.service.AuthenticationService.login(..)) " +
            "|| execution(* com.example.demo.account.service.AuthenticationService.logout(..))")
    public void accountCut() {}

    @Around("accountCut()")
    public Object beforeLogin(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("{}({})", joinPoint.getSignature().getName(),Arrays.toString(joinPoint.getArgs()));
        return joinPoint.proceed();
    }

}

 

* 주의: Spring aop는 Proxy 기반으로 동작하기 때문에, 외부에서 받은 호출만 aop로 감쌀 수 있다.

 


 

출처: https://nhnacademy.dooray.com/share/pages/ABFgz8KgRv62A85x9KClUw

 

Spring Boot Core

 

nhnacademy.dooray.com