Spring Boot - AOP!
AOP (Aspect Oriented Programming)
AOP: 관점 지향적 프로그래밍 기법(Aspect Oriented Programming)
스프링에서 모든 기능은 핵심 비지니스 로직과 공통 기능으로 나뉜다.
로그인 인증과정 같이 중복되고 공통적으로 사용되는 기능을 공통 기능이라 하며cross-cutting concern(공통 관심사항)이라 표현한다.
스프링 부트의 수많은 기본 Filter, Interceptor 등이 모두 AOP 기법으로 개발되었다 할 수 있다.
AOP 라이브러리
AOP 기법을 위한 대표적인 라이브러리는 아래 2개
Spring AOP
간단한 AOP 구현을 위해 Spring 컨테이너가 관리하는 빈에만 적용 가능한 라이브러리AspectJ
완전한 AOP 솔루션 제공, 모든 자바객체에 적용가능하지만 복잡한 구성을 가지는 라이브러리
Spring AOP 를 사용하게되면 자동으로 생성되는 코드로 인해 AspectJ 보다 훨씬 성능이 느리지만
간단한 사용방법과 설정으로 인해 Spring AOP 를 주로 사용한다.
두 라이브러리가 아예 분리되어있진 않고
Spring AOP코드에서AOP개념을 가져오기 위해AspectJ라이브러리에 의존중임.
AOP 주요 용어
| 용어 | 의미 |
|---|---|
Aspect |
여러 객체에 공통적으로 적용되는 기능을 Aspect라 한다. 인증, 트랜잭션, 보안 과 같은 공통적 기능을 의미한다. |
Advice |
공통적으로 사용되는 기능(Aspect)를 핵심 로직에 언제 적용하는지, 시점을 의미한다(핵심기능 시작 전, 후, 예외발생시 등). |
JoinPoint |
Advice를 적용 가능한 지점을 의미한다. 핵심 기능과 공통 기능이 어느지점에서 만나는지를 의미하며 메서드 호출, 필드값 변경 지점을 의미한다. |
Pointcut |
많은 JoinPoint중에 실제 핵심기능과 보조기능이 접하게 되는 지점을 Pointcut이라 한다. JoinPoint의 부분집합을 Pointcut이라 할 수 있다. |
Weaving |
프로그래머가 설정한 Advice대로 Aspect를 핵심로직 코드에 적용하는 것, 행위를 뜻한다. |
구현되는 Advice의 종류
| Advice명 | 설명 | 사용 클래스 |
|---|---|---|
Before Advice |
핵심기능 전에 수행하는 공통 기능 (로그인 체크 등) | MethodBeforeAdvice |
Around Advice |
핵심기능 수행 전 후에 수행하는 고통 기능 (시간 체크 등) | MethodInterceptor |
After Returning Advice |
예외 발생이 없을 경우 수행하는 기능 | AfterReturningAdvice |
After Throwing Advice |
예외 발생할 경우 수행하는 기능 | ThrowsAdvice |
After Advice |
예외 발생 상관없이 핵심기능 수행 후 수행하는 공통 기능 | AfterAdvice |
공통기능이 핵심기능 어느시점에 실행 되는지에 따라 위 5가지 Advice를 택해야 한다.
Weaving하는 시점
다음 3가지 시점에 Weaving(적용)한다.
- 컴파일시
- 클래스 로딩시
- 런타임시
Spring AOP에선 3번째 방법인 런타임시에Weaving할 수 있는데 Proxy기반의 AOP이다.
crossCuttingConcern: 공통 관심사항coreConcern: 핵심 기능
핵심 기능을 하는 클래스, 보조(공통)기능을 하는 클래스가 있으면 따로 2개의 클래스가 올라가고
가상으로 만들어진 프로그래머가 설정한 대로 위의 여러 클래스(핵심, 보조 클래스들)가 적용(Weaving)된 Proxy클래스(가짜) 가 만들어진다.
이러한 Proxy 클래스를 만드는 방법은 다음 3가지가 있다.
- XML스키마 기반의 POJO 클래스를 이용한 AOP구현
- AspectJ에서 정의한 @Aspect 어노테이션 기반의 AOP구현
- 스프링API를 이용한 AOP구현
이중 XML스키마를 이용한 1번과 어노테이션을 사용한 2번을 알아보자
Proxy 객체 만들어보기
위의 3가지 방법은 스프링에서 제공하는 기능을 이용해 Proxy클래스를 만드는 것 인데
어떻게 Proxy 클래스가 만들어지는지 알기 위해 라이브러리를 사용하지 않고 Proxy클래스를 만들어 보자.
- 핵심기능을 계산기 기능
- 공통기능을 핵심기능이 처리되는 시간을 로그로 기록
먼저 계산기 뼈대를 가진 Calculator 인터페이스 작성
1 | public interface Calculator { |
그리고 이를 구현한 CalcultorImpl 클래스 작성
1 |
|
로그출력같은 공통적인 보조업무를 빼서 AOP 기능수행을 하는 Proxy 클래스를 생성
java.lang.reflect.InvocationHandler 인터페이스를 상속한 LogPrintHandler클래스를 정의
공통 기능을 구현한 보조클래스와 핵심 기능이 합쳐진 proxy 클래스 이다.
1 | public class LogPrintHandler implements InvocationHandler { |
AOP과정에서 위와같은 LogPrintHandler와 같은 java.lang.reflect.Proxy 인스턴스가 생기게 된다 보면 된다.
1 | public static void main(String[] args) { |
Spring AOP
런타임 AOP 에서 사용하는 방법은 아래 2가지
JDK Dynamic Proxycglib(Code Generator Library) Proxy
JDK Dynamic Proxy 는 위에서 살펴보았던 reflect 패키지의 클래스를 사용하는 방법이다.
java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler
cglib 는 바이트코드 조작을 통해 프록시 객체를 생성하기때문에 reflect 패키지의 인터페이스 구현 등의 별도의 추가작업을 하지 않아도 Proxy 패턴 구현이 가능하다.
org.springframework.cglib.proxy.Enhancerorg.springframework.cglib.proxy.MethodInterceptor
Spring AOP 에선 cglib Proxy 를 사용한다.
런타임시에 각종 어노테이션이 설정된 클래스들을 classpath 에서 찾은 뒤 cglib 클래스를 사용하여 동적으로 프록시 관련 코드를 생성 후 로직 사이사이에 삽입하는 과정을 거친다.
cglib Proxy 객체 생성
Spring AOP 를 사용하면 지금부터 소개할 cglib 클래스들이 자동으로 런타임시 생성된다고 보면 된다.
하지만 여기선 Spring AOP 를 사용하기 전에 직접 cglib 를 클래스를 생성하고 Bean 으로 등록하는 과정을 수행한다.
cglib 클래스를 사용하기 위해 아래 dependency 를 삽입.
1 | implementation 'org.springframework.boot:spring-boot-starter-aop' |
add 라는 핵심 기능 전 후 에 아래 3가지 AOP 작업을 수행하도록 설정한다.
- 실행 전 로그기록
- 실행 후 로그기록
- 소요시간 로그 기록
공통 기능인 Around Advice 역할을 수행할 cglib Proxy 클래스로 생성한다.
Around Advice기능을 가진 Proxy클래스를 정의하려면 먼저 공통기능 클래스를 정의하기 위한 MethodInterceptor 인터페이스를 구현해야한다.
1 |
|
Around Advice을 통해 핵심기능 함수 실행문으로 들어온 후 시작 바로후, 끝나기 바로 전에 공통기능 삽입이 가능하다.
만약 핵심기능 실행 전, 혹은 후에 공통기능을 먼저 삽입하고 싶다면,
핵심기능 수행전 매개변수, 핵심기능 수행 후 반환값을 사용해 공통 기능을 수행해야 한다면
Before Advice 역할을 하는 MethodBeforeAdvice 인터페이스를 구현,After Returning Advice역할을 하는 AfterReturningAdvice 인터페이스를 구현한 공통 기능 클래스를 정의해야 한다.
그냥 Spring 프레임워크를 사용하게 될 경우
위와 같이 AOP 기법을 사용한 공통 기능 클래스를 모두 정의하였으면 핵심기능 클래스와 공통기능 클래스를 하나의 Proxy클래스로 만들어주면 된다.
Spring AOP 라이브러리
cglib 클래스를 사용하여 AOP 를 사용하는 방법을 알아보았는데 스프링에선 대부분 어노테이션을 사용해서 AOP 를 수행한다.
1 |
|
AOP Advice annotation
AOP Advice 를 설정할 수 있는 어노테이션은 아래 5가지
@Before
1 |
|
@After
1 |
|
@AfterReturning
1 |
|
@AfterThrowing
1 |
|
@Around
1 |
|
1 | // org.springframework.aop.AfterReturningAdvice 클래스 상속 |
직접 어노테이션 생성
1 | // 자식 클래스에도 상속 |
Spring AOP 의 문제점
- Proxy 객체의 외부 호출을 래핑하는 방식이기 때문에 내부 함수호출시에는 AOP 과정이 일어나지 않는다.
- 추가적인 클래스를 다량 작성해야한다.
컴파일 타임에선 어노테이션 사용의 문제가 있어도 알수 없어 런타임시에 확인이 필요하다.
빌드시 어노테이션 오류를 체크하기 위해 Annotation Processor 를 사용할 수 있다.
https://kouzie.github.io/cicd/gradle/gradle-Plugin,-buildSrc/#annotation-processor