개요
스터디 중에 AOP에 대한 압박 질문을 받았는데 하나도 대답을 못해서 정리할려고 한다...ㅎㅅㅎ
1. 사용 목적
애플리케이션 로직은 크게 핵심 기능과 부가 기능으로 나눌 수 있다.
- 핵심 기능은 해당 객체가 제공하는 고유의 기능
- 부가 기능은 핵심 기능을 보조하기 위해 제공되는 기능이다.(로그 추적 기능, 트랜잭션 기능 등...)
보통 기존 프로젝트에 부가 기능을 추가하게 되면 하나의 클래스가 아닌 여러 클래스에 부가 기능을 추가하게 된다.
예를 들어서 프로젝트의 모든 클래스에 로그 기능을 추가한다면 하나의 부가 기능(로그 추적)을 여러 곳에 동일하게 사용하게 된다.
이러한 부가 기능을 횡단 관심사라고 한다.
1.1 단점
하지만 이러한 기존 프로젝트에 부가 기능을 추가하게 된다면 여러 문제점이 있다. 만약 부가 기능을 적용해야 할 클래스가 100개 라면 100개에 모두 똑같은 부가 기능 코드를 추가해야 하고 거기에 더해 단순 호출이 아닌 try- catch-finally 구문이 필요하다면 더욱 복잡해질 것이다.
이렇게 많은 클래스에 부가 기능을 힘들게 추가했다고 가정한 후 만약 수정이 필요하다면,,,? 또 100개의 클래스를 하나씩 뜯어 고쳐야 한다는 노가다 과정을 겪어야 한다.
2. AOP란?
이러한 부가 기능을 적용할 때 위와 같은 문제점이 발생한다. 그래서 많은 개발자들은 오랜 시간 고민해왔다. 고민 끝에 AOP라는 용어가 나오게 되었다.
AOP는 관점 지향 프로그래밍의 약어로 부가 기능을 핵심 기능에서 분리해 한 곳으로 관심하도로 하고 이 부가 기능을 어디에 적용할지 선택하는 기능을 합한 하나의 모듈이다.
3. AOP 용어 정리
AOP 프록시
AOP 기능을 구현하기 위해 만든 프록시 객체, 스프링에서 AOP 프록시는 JDK 동적 프록시 또는 CGLIB 프록시이다.
조인 포인트 ( Join Point )
어드바이스가 적용될 수 있는 위치로, AOP를 적용할 수 있는 모든 지점이라 생각하면 된다.
스프링 AOP는 프록시 방식을 사용하므로 조인 포인트는 항상 메소드 실행 지점으로 제한된다.
포인트컷 ( Pointcut )
조인 포인트 중에서 어드바이스(부가 기능)를 어디에 적용할 지, 적용하지 않을 지 위치를 판단하는 필터링하는 기능 ( 주로 AspectJ 표현식을 사용해서 지정 )
프록시를 사용하는 스프링 AOP는 메서드 실행 지점을 포인트 컷으로 필터링 한다.
타겟 ( Target )
어드바이스를 받는 객체, 포인트컷으로 결정
어드바이스 ( Advice )
부가 기능
특정 조인 포인트에서 Aspect에 의해 취해지는 조치
Around(주변), Before(전), After(후)와 같은 다양한 종류의 어드바이스가 있음
애스펙트( Aspect )
어드바이스 + 포인트컷을 모듈화 한 것
하나의 어드바이스만이 아닌 여러 어드바이스와 포인트 컷이 함께 존재할 수 있다.
어드바이저 ( Advisor )
하나의 어드바이스와 하나의 포인트 컷으로 구성
즉, 어드바이스 + 포인트 컷 = 어드바이저
4. AOP 적용 방식
AOP의 적용 방식은 크게 3가지가 있다.
- 컴파일 시점
- 클래스 로딩 시점
- 런타임 시점 ( 프록시 사용 )
컴파일 시점과 클래스 로딩 시점 적용 방식은 AspectJ 프레임워크를 직접 사용해야 하고, 이 AspectJ를 학습하기 위해선 엄청난 분량과 설정의 번거로움이 있다. 그래서 주로 런타임 시점 적용 방식을 사용하는 스프링 AOP를 사용 한다.
5. AOP 적용 가능 위치
주로 사용하는 런타임 시점 적용 방식의 스프링 AOP 관점에서의 방식이다.
- 스프링 AOP는 메서드 실행 지점에만 AOP를 적용할 수 있다.
- 프록시 방식을 사용하는 스프링 AOP는 스프링 컨테이너에 해당 @Aspect을 빈 등록을 해야 AOP를 적용할 수 있다.
6. 스프링에서 AOP 적용하기 (@Aspect)
스프링 AOP를 적용하기 위해서는 아래의 라이브러리를 build.gradle에 의존성을 추가 해줘야 한다.
implementation 'org.springframework.boot:spring-boot-starter-aop' // 스프링 aop 추가
위의 라이브러리를 추가하면 스프링 부트가 AOP 관련 클래스를 자동으로 스프링 빈에 등록해준다.
이러한 스프링 부트의 자동 설정은 "AnnotationAwareAspectJAutoProxyCreator"라는 빈 후처리기가 스프링 빈에 자동으로 등록해주는데, 이름 그대로 자동으로 프록시를 생성해주는 빈 후처리기이다.
6.1 AnnotationAwareAspectJAutoProxyCreator란?
1. @Aspect 어노테이션이 붙은 클래스를 Advisor(어드바이저)로 변환하여 저장
2. Advisor(어드바이저)를 자동으로 찾아와 프록시를 생성하고 Pointcut(프록시 적용 대상 필터링)을 보고 프록시가 필요한 곳에 Advice(부가 기능)을 적용
6.2 코드 - @Around
- 스프링은 @Aspect 애노테이션으로 매우 편리하게 포인트컷과 어드바이스로 구성되어 있는 어드바이저 생성 기능을 지원한다.
- 어드바이저로 사용할 클래스에 @Aspect 어노테이션을 붙여줌으로써 스프링 AOP를 적용할 수 있다.
@Aspect
public class LogTraceAspect {
@Around("execution(* hello.proxy.app..*(..))") // 포인트컷 (AspectJ 표현식)
public Object execute(ProceedingJoinPoint joinPoint) { // 어드바이스
// 어드바이스 로직
}
}
@Around 어드바이스를 사용할 경우 메서드의 파리미터로 "ProceedingJoinPoint"를 꼭 넣어줘야 한다.
ProceedingJoinPoint의 proceed()는 다음 어드바이스나 타켓을 호출하는 것으로, 어드바이스를 사용하기 위해서는 꼭 proceed() 메서드를 호출해줘야 한다.
이 외에도 호출되는 대상 객체에 대한 정보, 실행되는 메서드에 대한 정보 등이 필요할 때가 있는데 이 경우에는
ProceedingJoinPoint 인터페이스가 제공하는 아래의 메서드를 사용할 수 있다. (이 외에도 더 있지만 일부만 설명)
메서드 | 설 명 |
Signature getSignature() | 호출되는 메서드에 대한 정보를 반환 |
Object getTarget() | 대상 객체를 반환 |
String getName | 메서드의 이름을 반환 |
String toLongString() | 메서드를 완전하게 표현한 문장을 반환 (메서드의 리턴 타입, 파라미터 타입 모두 표시) |
이렇게 ProceedingJoinPoint로 호출되는 객체에 대한 정보나, 실행되는 메서드의 정보를 알 수 있는 이유는
스프링 부트 자동 설정으로 "AnnotationAwareAspectJAutoProxyCreator" 이라는 자동 프록시 생성기가 빈 등록되어 있는데,
이 자동 프록시 생성기가 @Aspect가 붙은 클래스를 보고 Advisor(어드바이저)로 변환해 저장해준다.
그리고 이 Advisor(어드바이저)를 보고 포인트컷의 대상이 되는 것들을 "ProxyFactory"에 인자로 넘겨 자동으로 프록시를 생성하고 적용해준다.
여기서 생성된 프록시 객체가 메서드를 호출할 때, ProceedingJoinPoint 객체를 생성하고 이를 advice에 전달한다.
즉, ProceedingJoinPoint는 프록시가 메서드를 호출하는 시점의 정보를 가져 어드바이스가 적용되는 대상을 이미 알고 있다.
6.3 @Pointcut
@Around 에 포인트컷 표현식을 직접 넣을 수 도 있지만, @Pointcut 애노테이션을 사용해서 별도로 분리해 재사용할 수 도 있다.
@Slf4j
@Aspect
@Component
public class AspectExample {
// hello.aop.test 패키지와 하위 패키지에 적용
@Pointcut("execution(* hello.aop.test..*(..))")
private void allTestLog() {} // 포인트컷 시그니쳐
@Around("allTestLog()") // 포인트컷을 메서드로 만들어 사용
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("[log] {}", joinPoint.getSignature()); // join point 시그니처
return joinPoint.proceed(); // 실제 타깃 호출
}
}
7. 어드바이스 종류
- @Around : 메서드 호출 전후에 수행, 가장 강력한 어드바이스, 조인 포인트 실행 여부 선택, 반환 값 변환, 예외 변환 등이 가능 (사실 이거 하나만 사용해도 무방)
- @Before : 조인 포인트 실행 이전에 실행
- @AfterReturning : 조인 포인트가 정상 완료후 실행
- @AfterThrowing : 메서드가 예외를 던지는 경우 실행
- @After : 조인 포인트가 정상 또는 예외에 관계없이 실행(finally)
@Around를 제외한 나머지 어드바이스들은 JoinPoint를 첫 번째 파라미터에 사용한다. (생략도 가능)
( JoinPoint는 ProceedingJoinPoint의 부모 타입)
'Spring > Spring Boot' 카테고리의 다른 글
Spring Batch 관련 예상 질문 (0) | 2024.09.19 |
---|---|
배치 트리거링(Batch Triggering)이란? (0) | 2024.09.19 |
스프링 배치(Spring Batch) 사용하기 - 2편 (0) | 2024.09.11 |
스프링 배치(Spring Batch) 사용하기 - 1편 (0) | 2024.09.10 |
Spring : N+1 문제 해결 2편 - 문제 원인 분석과 해결 방법 (0) | 2024.09.09 |