[Spring] 스프링 AOP(Aspect Oriented Programming)란? - @Aspect

AOP를 사용하지 않는다면 ?

AOP에 대해 설명하기 전에 AOP를 사용하는 이유에 대해 먼저 알아 봅시다.

 

애플리케이션 로직은 크게 핵심 기능과 부가 기능으로 나눌 수 있습니다.

  • 핵심 기능 : 해당 객체가 제공하는 고유의 기능
  • 부가 기능 : 핵심 기능을 보조하기 위해 제공되는 기능 ( ex: 로그 추적 기능, 트랜잭션 기능 )

보통 기존 프로젝트에 부가 기능을 추가하게 되면 하나의 클래스가 아닌 여러 클래스에 부가 기능을 추가하게 됩니다.

예를 들어서 프로젝트의 모든 클래스에 로그 기능을 추가 한다면 하나의 부가 기능(로그 추적)을 여러 곳에 동일하게 사용하게 됩니다.

이러한 부가 기능을 바로 횡단 관심사(cross-cutting concerns) 라고 합니다. 

 

부가 기능 적용시 문제점

그런데 이러한 기존 프로젝트에 부가 기능을 추가하게 된다면 여러 문제 점이 있습니다.

만약 부가 기능을 적용해야할 클래스가 100개라면 100개에 모두 똑같은 부가 기능 코드를 추가 해야하고, 거기에 더해 단순 호출이 아닌 try ~ catch ~ finally 구문이 필요하다면 더욱 복잡 해집니다.

이렇게 많은 클래스에 부가 기능을 힘들게 추가 했다고 가정해봅시다.

여기서 만약 수정이 필요하다면 어떨까요?

또 100개의 클래스를 모두 하나씩 뜯어 고쳐야 하는 노가다 과정을 겪어야 할 것 입니다.

 


AOP(Aspect Oriented Programming)란?

부가 기능을 적용할 때 위와 같은 문제점들로 인해 수 많은 개발자들이 오랜 시간 고민해왔을 것 입니다.

고민 끝에 AOP(Asepct Oriented Programming)이란 용어가 나왔습니다.

AOP는 부가 기능을 핵심 기능에서 분리해 한 곳으로 관리하도록 하고, 이 부가 기능을 어디에 적용할지 선택하는 기능을 합한 하나의 모듈입니다.

AOP의 A에 해당하는 Aspect는 우리말로 번역하면 "관점"이라는 뜻입니다.

즉, 이름 그대로 애플리케이션을 바라보는 관점을 하나하나의 기능에서 횡단 관심사(cross-cutting concerns) 관점으로 달리 보는 것 입니다.

이러한 프로그래밍 방식을 관점 지향 프로그래밍 즉, AOP라고 합니다.


AOP 용어 정리

 

AOP 프록시

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

 

조인 포인트 ( Join Point )

어드바이스가 적용될 수 있는 위치로, AOP를 적용할 수 있는 모든 지점이라 생각하면 된다.

스프링 AOP는 프록시 방식을 사용하므로 조인 포인트는 항상 메소드 실행 지점으로 제한된다.

 

포인트컷 ( Pointcut )

조인 포인트 중에서 어드바이스(부가 기능)를 어디에 적용할 지, 적용하지 않을 지 위치를 판단하는 필터링하는 기능 ( 주로 AspectJ 표현식을 사용해서 지정 )

프록시를 사용하는 스프링 AOP는 메서드 실행 지점을 포인트 컷으로 필터링 한다.

 

타겟 ( Target )

어드바이스를 받는 객체, 포인트컷으로 결정

 

어드바이스 ( Advice )

부가 기능

특정 조인 포인트에서 Aspect에 의해 취해지는 조치

Around(주변), Before(전), After(후)와 같은 다양한 종류의 어드바이스가 있음

 

애스펙트( Aspect )

어드바이스 + 포인트컷을 모듈화 한 것

하나의 어드바이스만이 아닌 여러 어드바이스와 포인트 컷이 함께 존재할 수 있다.

 

어드바이저 ( Advisor )

하나의 어드바이스와 하나의 포인트 컷으로 구성

즉, 어드바이스 + 포인트 컷 = 어드바이저

 


AOP 적용 방식

AOP의 적용 방식은 크게 3가지가 있습니다.

  • 컴파일 시점
  • 클래스 로딩 시점
  • 런타임 시점 ( 프록시 사용 )

컴파일 시점과 클래스 로딩 시점 적용 방식은 AspectJ 프레임워크를 직접 사용해야 하고, 이 AspectJ를 학습하기 위해선 엄청난 분량과 설정의 번거로움이 있어

주로 런타임 시점 적용 방식을 사용하는 스프링 AOP를 사용 합니다. (스프링은 AspectJ의 문법을 차용해 프록시 방식의 AOP를 적용. AspectJ를 직접 사용 X )

런타임 시점은 컴파일도 다 끝나고, 클래스 로더에 클래스도 다 올라가서 이미 자바가 실행되고 난 다음을 말합니다. ( 자바의 메인( Main ) 메서드가 실행된 다음)

프록시를 사용하기 때문에 AOP 기능에 일부 제약이 있지만, 특별한 컴파일러나, 자바를 실행할 때 복잡한 옵션과 클래스 로더 조작기를 설정하지 않아도 스프링이 알아서 자동으로 설정 해주기 때문에 훨씬 편리합니다.

 

AOP 적용 가능 위치

주로 사용하는 런타임 시점 적용 방식의 스프링 AOP 관점에서만 설명하겠습니다.

  • 스프링 AOP는 메서드 실행 지점에만 AOP를 적용할 수 있다.
  • 프록시 방식을 사용하는 스프링 AOP는 스프링 컨테이너에 해당 @Aspect을 빈 등록을 해야 AOP를 적용할 수 있다.

스프링 AOP 적용하기 ( @Aspect )

스프링 AOP를 적용하기 위해서는 아래의 라이브러리를 build.gradle에 의존성을 추가 해줘야합니다.

implementation 'org.springframework.boot:spring-boot-starter-aop' // 스프링 aop 추가

위의 라이브러리를 추가하면 aspectJ 관련 라이브러리를 등록하고, 스프링 부트가 AOP 관련 클래스를 자동으로 스프링 빈에 등록해줍니다.

이러한 스프링 부트의 자동 설정은 "AnnotationAwareAspectJAutoProxyCreator"라는 빈 후처리기가 스프링 빈에 자동으로 등록해주는데, 이름 그대로 자동으로 프록시를 생성해주는 빈 후처리기입니다.

 

AnnotationAwareAspectJAutoProxyCreator의 역할

1. @Aspect 어노테이션이 붙은 클래스를 Advisor(어드바이저)로 변환하여 저장

2. Advisor(어드바이저)를 자동으로 찾아와 프록시를 생성하고 Pointcut(프록시 적용 대상 필터링)을 보고 프록시가 필요한 곳에 Advice(부가 기능)을 적용해줍니다.

 

스프링 AOP 적용

스프링 애플리케이션에 프록시를 적용하려면 포인트컷과 어드바이스로 구성되어 있는 어드바이저 ( Advisor )를 만들어서 스프링 빈으로 등록하면

나머지는 "AnnotationAwareAspectJAutoProxyCreator" 자동 프록시 생성기가 모두 자동으로 처리해줍니다.

자동 프록시 생성기는 스프링 빈으로 등록된 어드바이저들을 찾고, 스프링 빈들에 자동으로 포인트컷이 매칭되는 경우에 프록시를 적용해줍니다.

 

스프링은 @Aspect 애노테이션으로 매우 편리하게 포인트컷과 어드바이스로 구성되어 있는 어드바이저 생성 기능을 지원하며

어드바이저로 사용할 클래스에 @Aspect 어노테이션을 붙여줌으로써 스프링 AOP를 적용할 수 있습니다.

@Aspect
public class LogTraceAspect {

    @Around("execution(* hello.proxy.app..*(..))")  // 포인트컷 (AspectJ 표현식)
    public Object execute(ProceedingJoinPoint joinPoint) { // 어드바이스
        // 어드바이스 로직
    }
}

 

주의할 점으로 스프링 AOP 적용 시에는 private, final 메소드는 AOP 적용 불가합니다.

 

ProceedingJoinPoint

@Around 어드바이스를 사용할 경우 메서드의 파리미터로 "ProceedingJoinPoint"를 꼭 넣어줘야 합니다.

ProceedingJoinPoint의 proceed()는 다음 어드바이스나 타켓을 호출하는 것으로, 어드바이스를 사용하기 위해서는 꼭 proceed() 메서드를 호출해줘야 합니다.

 

이 외에도  호출되는 대상 객체에 대한 정보, 실행되는 메서드에 대한 정보 등이 필요할 때가 있는데 이 경우에는

ProceedingJoinPoint 인터페이스가 제공하는 아래의 메서드를 사용할 수 있습니다. (이 외에도 더 있지만 일부만 설명)

메서드 설 명
Signature getSignature()  호출되는 메서드에 대한 정보를 반환.
Object getTarget()  대상 객체를 반환
String getName  메서드의 이름을 반환
String toLongString()  메서드를 완전하게 표현한 문장을 반환 (메서드의 리턴 타입, 파라미터 타입 모두 표시)

 

이렇게  ProceedingJoinPoint 호출되는 객체에 대한 정보나, 실행되는 메서드의 정보 알 수 있는 이유

스프링 부트 자동 설정으로 "AnnotationAwareAspectJAutoProxyCreator" 이라는 자동 프록시 생성기가 빈 등록되어 있는데,

이 자동 프록시 생성기가 @Aspect가 붙은 클래스를 보고 Advisor(어드바이저)로 변환해 저장해줍니다.

그리고 이 Advisor(어드바이저)를 보고 포인트컷의 대상이 되는 것들을 "ProxyFactory"에 인자로 넘겨 자동으로 프록시를 생성하고 적용해줍니다.

여기서 생성된 프록시 객체가 메서드를 호출할 때, ProceedingJoinPoint 객체를 생성하고 이를 advice에 전달합니다.

즉, ProceedingJoinPoint는 프록시가 메서드를 호출하는 시점의 정보를 가져 어드바이스가 적용되는 대상을 이미 알고 있습니다.


이번 포스팅으로 스프링 AOP가 무엇인지, 그리고 스프링 AOP 적용방법에 대해 알아보았습니다.

다음 포스팅에서는 스프링 AOP의 사용방법에 대해 정리해보겠습니다.

 

[Spring] 스프링 AOP (@Aspect) 사용법

 

hstory0208.tistory.com

 

 

[Spring] 스프링 AOP - Pointcut 표현식

AspectJ는 포인트컷을 편리하게 표현하기 위한 특별한 표현식을 제공합니다. 스프링에서는 포인트컷 표현식에서 사용하기 위해 AspectJ가 제공하는 포인트컷 지시자(Pointcut Designator) 줄여서 PCD를 지

hstory0208.tistory.com

 


참고자료 : 김영한의 스프링 핵심 원리 - 고급편