스프링의 핵심, IoC/DI, AOP, PSA - AOP편
Spring AOP란?
- AOP(ASpect-Oriented Programming)는 횡단 관심사(e.g. 로깅, 트랜잭션, 보안)등 여러 모듈에서 공통적으로 사용하는 기능을 분리하여 관리하는 기법입니다.
- Spring에서는 Proxy Pattern을 이용하여 AOP를 지원합니다.
Proxy Pattern이란?
- Proxy Pattern은 특정 객체에 대해 접근을 제어하며. 특정 객체로 요청을 보낼 때 Proxy가 이를 가로채 특정 작업을 수행할 수 있도록 하는 기법입니다.(참고)
- 구조를 간단하게 설명드리자면, 실제 객체(이하 Service)의 인터페이스를 구현한 Proxy를 만들어, Proxy가 Service를 참조로 가지고 있어 Client가 operation을 호출하면 Proxy의 operation이 실행되고, Proxy가 작업을 수행한 후 Service의 operation을 호출합니다.
(참고로 Proxy Pattern과 유사한 구현 구조인 Decorator 패턴이 있는데, Proxy는 실제 객체로의 접근 제어, Decorator는 기능 확장에 조금 더 초점을 맞추었다고 알려져 있는데, 스프링에서는 두 패턴을 합쳐서 Proxy라고 부르는 것이 아닌가 합니다.)
Spring에서 Proxy를 만드는 방법
ProxyFactory
- Spring은 내부적으로 ProxyFactory라는 객체를 사용하여 Proxy 객체를 생성합니다.
public static void main(String[] args) {
ProxyFactory proxyFactory = new ProxyFactory();
MyObject myObject = new MyObject();
Pointcut pointcut = new MyPointcut();
Advice advice = new MyObjectAdvice();
proxyFactory.setTarget(myObject);
proxyFactory.addAdvisor(new DefaultPointcutAdvisor(pointcut, advice));
MyObject proxy = (MyObject) proxyFactory.getProxy();
proxy.operate();
}
Spring AOP에서 Proxy를 사용할 때는 Advice, Pointcut, Advisor라는 개념이 등장하는데요, 3가지의 개념은 아래와 같습니다.
1. Advice: 프록시에서 수행할 작업 그 자체입니다. MethodInterceptor 인터페이스를 구현해야 하며, 인자로 넘어오는 invocation.proceed()를 통해 실제 객체의 메서드를 호출할 수 있습니다.
public static class MyObjectAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("Proxy operated");
return invocation.proceed();
}
}
2. Pointcut: Advice가 실행되기 위한 조건입니다. Pointcut은 Class와 Method의 정보에 따라 필터링하는 것이 가능합니다.
public static class MyPointcut implements Pointcut{
@Override
public ClassFilter getClassFilter() {
return new MyClassFilter();
}
@Override
public MethodMatcher getMethodMatcher() {
return new MyMethodMatcher();
}
}
public static class MyClassFilter implements ClassFilter{
@Override
public boolean matches(Class<?> clazz) {
return clazz.getSimpleName().equals("MyObject");
}
}
public static class MyMethodMatcher implements MethodMatcher{
@Override
public boolean matches(Method method, Class<?> targetClass) {
return method.getName().equals("operate");
}
@Override
public boolean isRuntime() {
return false;
}
@Override
public boolean matches(Method method, Class<?> targetClass, Object... args) {
return matches(method, targetClass);
}
}
3. Advisor: Pointcut과 Advice를 합친 것입니다.
Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
ProxyFactory에서 객체를 만드는 원리
- ProxyFactory는 Pointcut을 통해 프록시 대상인지 확인하고, 맞다면 Advice를 적용해 프록시 객체를 만든다고 하는데요, 조금 더 자세한 과정을 보도록 하겠습니다.
- 스프링 객체 생성
- 생성된 객체를 빈 저장소에 등록하기 직전에 BeanPostProcessor에 전달한다.
- (스프링 부트) 스프링 부트의 자동설정에는 AnnotationAwareAspectJAutoProxyCreator라는 BeanPostProcessor이 있으며, 이 객체는 빈으로 등록된 모든 Advisor를 가져와 Pointcut이 하나 이상 만족하는지 확인하고, 만족한다면 프록시 객체를 만들어 반환한다.
- 이 때 해당되는 Pointcut의 갯수만큼 Proxy 객체가 생성되지는 않고, 하나만 생성된다고 합니다.
- 생성된 프록시(또는 실 객체)가 DI 컨테이너에 등록됩니다.
ProxyFactory에서 객체를 만드는 두가지 방법
- 그럼 ProxyFactory는 어떻게 객체를 만들까요? 총 두가지의 방법으로 Proxy를 만듭니다.
- 인터페이스가 있는 경우 : JDK 동적 프록시 기술을 활용하여 인터페이스를 구현한 프록시를 만듭니다.(인터페이스가 강제)
- 인터페이스가 없는 경우 : CGLIB 라이브러리(바이트코드 조작 라이브러리)를 활용하여 구현 객체를 상속한 프록시를 만듭니다.(final class에는 적용할 수 없음.)
- 다만 kotlin의 경우에는 kotlin-spring 에서 프록시로 만들어야하는 클래스를 자동으로 open으로 만들어준다고 합니다.(참고)
- 다만 ProxyFactory가 사용하는 리플렉션은 바이트 코드 조작에 비해 성능이 떨어지기 때문에, Spring Boot는 인터페이스 유무에 상관없이 CGLIB을 사용하도록 기본 설정되어 있다고 합니다.(참고)
@Aspect 프록시
- 위에서 보여드린 예시 코드, 즉 저희가 직접 Pointcut과 Advice 클래스를 만드는 것 보다 현재는 AspectJ Annotation 방식과 유사한 방식으로 구현을 많이 합니다.
@Aspect
@Component
public class MyAspect{
@Around("execution(* org.example..*(..))")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("do Proxy");
return joinPoint.proceed()
}
}
- 이렇게 @Aspect 를 사용하면, AnnotationAwareAspectJAutoProxyCreator가 @Around는 Pointcut으로, execute 메서드 내부는 advice로 바꾸어 Advisor로 만들고, 적용 대상이 되는 객체를 프록시로 만들어 줍니다.
정리
- AOP는 Aspect Oriented Programming의 줄임말로, AOP에서는 횡단 관심사를 일괄적으로 처리하기 위한 프로그래밍 기법이다.
- Spring은 Spring AOP 라이브러리를 통해 이를 지원해 주며, Spring AOP에서는 Pointcut(프록시 적용 조건), Advice(부가 로직)을 기반으로 Advisor를 만들며, 이 Advisor를 기반으로 프록시를 만든다.
- 등록된 Advisor는 AutoProxyCreator라는 BeanPostProcessor에 의해 프록시가 생성되어 빈으로 등록된다.
- Proxy는 JDK Dynamic Proxy, CGLIB을 사용하는 ProxyFactory에 의해 만들어지며, Spring Boot의 기본설정은 성능상의 문제로 CGLIB만을 사용한다.
- 실제 개발할 때는 AspectJ의 어노테이션 기반 방식으로 만들어주며 이는 AutoProxyCreator에 의해 Advisor로 변환된다.
참고
https://docs.spring.io/spring-framework/reference/core/aop/proxying.html
https://f-lab.kr/insight/understanding-spring-aop
https://www.inflearn.com/course/스프링-핵심-원리-고급편/dashboard
https://stackoverflow.com/questions/73231258/spring-boot-default-proxying-mechanism