AOP(Aspect Oriented Programming)
💡AOP(Aspect Oriented Programming): 관점 지향 프로그래밍이라는 의미로, 어떤 로직을 기준으로 핵심적인 관점, 공통적인 관점(부가적인 관점)으로 나누어서 보고 그 관점을 기준으로 모듈화하겠다는 의미이다.
애플리케이션 전반에 걸쳐 공통적으로 사용되는 기능들에 대한 관심사를 바로 공통 관심 사항(Cross-cutting concern)이라 부르며, 애플리케이션의 주목적을 달성하기 위한 핵심 로직에 대한 관심사를 핵심 관심 사항(Core concern)이라 부른다.
위 사진을 예로 들자면, 커피 주문 애플리케이션에서 커피 메뉴 등록, 커피 주문, 커피 주문 변경 등등 기능들이 핵심 관심 사항이고, 공통적으로 처리해야 할 부분인 로깅, 보안, 트랜잭션 같은 경우를 공통 관심 사항이라 할 수 있다.
AOP를 간단하게 말하자면, 공통된 기능을 재사용하는 기법이라 말할 수 있다.
AOP를 함으로써 우리는 이러한 이점을 얻을 수 있다.
- 코드의 간결성 유지
- 객체 지향 설계 원칙에 맞는 코드 구현
- 코드의 재사용
AOP 적용 방식
핵심 기능에 공통 기능을 삽입하는 방법에는 다음과 같이 세가지 방법이 있다.
- 컴파일 시점에 코드에 공통기능을 삽입하는 방법(AOP 전용 도구 사용 방식: AspectJ)
- AspectJ가 제공하는 특별한 컴파일러를 사용해야 하기 때문에 컴파일러가 필요하다는 단점이 존재한다.
- 클래스 로딩 시점에 바이트 코드에 공통 기능을 삽입하는 방법(AOP 전용 도구 사용 방식: AspectJ)
- 특별한 옵션과 클래스 로더 조작기를 지정해야하므로 운영하기가 어렵다.
- 런타임에 프록시 객체를 생성해서 공통 기능을 삽입하는 방법(스프링이 제공)⭐
- 스프링 AOP는 런타임 방법을 사용하고 있다.
- 프록시를 생성하는데에는 JDK DynamicProxy, CGLib Proxy 두가지 방법이 있다. 인터페이스 유무에 따라 스프링은 두가지 방법을 섞어 사용한다.
- 인터페이스가 존재하면 JDK 동적 프록시를, 인터페이스없이 클래스만 존재한다면 CGLib 프록시를 생성한다.
스프링 AOP는 AspectJ 문법을 차용하고 프록시 방식의 AOP를 제공한다. 따라서 스프링 AOP는 AspectJ를 직접 사용하는 것이 아니다.
Spring Boot2.0에서는 CGLib가 가지고 있던 단점들을 해결하여 CGLib 프록시를 생성한다.
자세한 내용은 https://ojt90902.tistory.com/721 참고
AOP 주요 용어
용어 | 의미 |
Advice(어드바이스) | - 언제 공통 관심 기능을 핵심 로직에 적용할 지를 정의한다. 즉, 무엇을 언제 적용할지에 대한 것 - 메서드를 호출하기 전(언제)에 트랜잭션 시작(공통기능)을 적용하다는 것을 정의한다. |
Joinpoint(조인포인트) | - 어드바이스가 적용될 수 있는 위치를 의미한다. - 메서드 호출, 필드 값 변경이 Joinpoint에 해당한다. - 스프링은 프록시를 이용해서 AOP를 구현하기 때문에 메서드 호출에 대한 조인포인트만 지원한다. |
Pointcut(포인트컷) | - 조인포인트의 부분 집합으로서 실제 어드바이스가 적용되는 조인포인트를 나타낸다. - 스프링 AOP의 조인 포인트는 메서드의 호출이므로, 스프링에서 포인트컷은 메서드를 선정하는 것과 관련돼있다. - 스프링에서는 정규 표현식이나 AspectJ의 문법을 이용하여 포인트컷을 정의할 수 있다. |
Weaving(위빙) | - 어드바이스를 핵심 로직 코드에 적용하는 것을 위빙이라고 한다. - 시점에 따라 컴파일 시, 클래스 로딩시, 런타임 시 위빙으로 구분가능 하며, 스프링에서는 런타임 위빙을 사용하고 있다. |
Aspect(에스펙트) | - 여러 객체에 공통으로 적용되는 기능을 에스펙트라고 한다. - 트랜잭션이나 보안 등이 에스펙트의 좋은 예이다. |
어드바이스의 종류
용어 | 의미 |
Befor Advice | 대상 객체의 메서드 호출 전에 공통 기능을 실행한다. |
After Returing Advice | 대상 객체의 메서드가 익셉션없이 실행된 이후에 공통 기능을 실행한다. |
After Throwing Advice | 대상 객체의 메서드를 실행하는 도중 익셉션이 발생한 경우에 공통 기능을 실행한다. |
After Advice | 익셉션 발생 여부에 상관없이 대상 객체의 메서드 실행 후 공통 기능을 실행한다.(try-catch finally의 finally 블록과 비슷하다) |
Around Advice | - 대상 객체의 메서드 실행 전, 후 또는 익셉션 발생 시점에 공통 기능을 실행하는데 사용된다. - 가장 빈번하게 사용된다. |
Aspect
implementation 'org.springframework.boot:spring-boot-starter-aop'
해당 의존성을 추가하게 되면 자동 프록시 생성기(AnnotationAwareAspectJAutoProxyCreator)를 사용할 수 있게 되고, 이것이 Advisor 기반으로 프록시를 생성한다.
이와 더불어, 자동 프록시 생성기는 @Aspect를 확인하여 Advisor로 변환해서 저장하는 작업을 수행한다.
자동 프록시 생성기에 의해 @Aspect에서 Advisor로 변환된 @Aspect Advisor 빌더 내부에 저장된다.
동작 과정
- 스프링 빈 대상이 되는 객체를 생성한다(컴포넌트 스캔 대상)
- 생성된 객체를 컨테이너에 등록하기 직전에 빈 후처리기에 전달한다.
- 모든 어드바이저 빈을 조회한다. @Aspect Advisor 빌더 내부에 저장된 모든 어드바이저를 조회한다.
- 조회한 어드바이저에 포함된 포인트 컷을 통해 클래스와 메서드 정보를 매칭하면서 프록시를 적용할 대상인지 아닌지를 판단한다.
- 여러 어드바이저의 하나라도 포인트컷의 조건을 만족한다면 프록시를 생성한다.
- 컨테이너는 객체를 받아 빈으로 등록한다.
@Aspect는 어드바이저를 쉽게 만들 수 있는 역할이기 때문에 컴포넌트 스캔 대상이 아니다. 따라서 스프링 빈으로 따로 등록해줘야 한다.
스프링 AOP 구현
/*
공통 관심 사항을 정의하는 Aspect 구현 클래스
*/
@Aspect
public class GugudanAspect {
// @Pointcut("execution(public void cal*(..))")
@Pointcut("execution(public void com..calculate(..))")
private void targetMethod() {}
@Around("targetMethod()")
public Object measureTime(ProceedingJoinPoint joinPoint) throws Throwable {
// 핵심 기능 로직 실행 전 호출
long start = System.nanoTime();
try {
Object result = joinPoint.proceed(); // 핵심 기능 호출
return result;
} finally {
// 핵심 기능 실행 후 호출
long end = System.nanoTime();
Signature signature = joinPoint.getSignature();
System.out.printf("%s.%s 메서드 호출!\n", joinPoint.getTarget().getClass().getSimpleName(), signature.getName());
System.out.printf("실행 시간: %d ns", (end-start));
}
}
}
@Aspeact
애스펙트는 위와 같이 애노테이션을 사용하여 구현할 수 있다.
애스펙트는 공통 기능과 그 적용 시점을 정의한 어드바이스, 그리고 이것을 적용할 지점을 의미하는 포인트컷을 포함한다.
@Pointcut
해당 애노테이션은 포인트컷을 구현하기 위한 애노테이션이다.
애스펙트를 적요할 위치를 지정할 때 사용하는 포인트컷의 설정을 보면 execution()으로 시작되는 명시자를 사용하여 어드바이스의 대상이 되는 메서드를 지정하고 있다.
@Around
타깃 객체의 메서드 실행 전과 후 또는 예외가 발생했을 때 사용한다.
proceed()
타깃 객체의 메서드를 호출
Signature getSignature()
호출한 메서드의 시그너처(메서드 이름과 파라미터를 뜻함), 정보를 구한다.
@Configuration
//@EnableAspectJAutoProxy(proxyTargetClass = true) // 인터페이스가 아닌 자바 클래스를 상속받아 프록시 생성
@EnableAspectJAutoProxy // @Aspect 애노테이션 붙인 클래스를 공통 기능으로 적용
public class GugudanConfig {
@Bean
public GugudanAspect gugudanAspect() {
return new GugudanAspect();
}
@Bean
public Gugudan gugudan() {
return new GugudanByForLoop();
}
}
@EnableAspectJAutoProxy
스프링으로 @Aspect 애노테이션이 붙은 빈 객체를 찾아 해당 객체의 포인트컷과 어드바이스 설정을 사용하도록 한다.
proxyTargetClass = true 옵션이 붙는다면 자바 클래스를 상속받아 프록시를 생성한다(CGLib Proxy)
여러개의 Advice
@Aspect
public class GugudanCacheAspect {
--- 생략 ---
// 어드바이스 정의
@Around("cacheTarget()")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
.. 생략
}
}
애스펙트가 더 추가되었다. 애스펙트의 적용 순서가 중요한 경우, @Order 애노테이션을 사용하여 순서를 정할 수 있다.
@Order
@Aspect
@Order(1) // 🛑 추가
public class GugudanAspect {
--- 생략 ---
}
@Aspect
@Order(2) // 🛑 추가
public class GugudanCacheAspect {
--- 생략 ---
}
Reference
https://velog.io/@backtony/Spring-AOP-%EC%B4%9D%EC%A0%95%EB%A6%AC
'Spring > 개념' 카테고리의 다른 글
[Web] 서블릿(servlet)이란? (0) | 2023.05.23 |
---|---|
[Spring] 스프링 PSA (0) | 2023.05.11 |
[Spring Security] 스프링 시큐리티의 동작 구조 (0) | 2023.05.11 |
[Spring Security] 스프링 시큐리티와 FilterChain (1) | 2023.05.11 |
[Spring] Spring 기초 및 모듈 구성, Sprig VS SpringBoot (0) | 2023.05.02 |