본문 바로가기

Spring

스프링의 핵심, IoC/DI, AOP, PSA - AOP편

반응형

Spring AOP란?

  • AOP(ASpect-Oriented Programming)는 횡단 관심사(e.g. 로깅, 트랜잭션, 보안)등 여러 모듈에서 공통적으로 사용하는 기능을 분리하여 관리하는 기법입니다.
  • Spring에서는 Proxy Pattern을 이용하여 AOP를 지원합니다.

 

Proxy Pattern이란?

  • Proxy Pattern은 특정 객체에 대해 접근을 제어하며. 특정 객체로 요청을 보낼 때 Proxy가 이를 가로채 특정 작업을 수행할 수 있도록 하는 기법입니다.(참고)

https://refactoring.guru/ko/design-patterns/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를 적용해 프록시 객체를 만든다고 하는데요, 조금 더 자세한 과정을 보도록 하겠습니다.
    1. 스프링 객체 생성
    2. 생성된 객체를 빈 저장소에 등록하기 직전에 BeanPostProcessor에 전달한다.
    3. (스프링 부트) 스프링 부트의 자동설정에는 AnnotationAwareAspectJAutoProxyCreator라는 BeanPostProcessor이 있으며, 이 객체는 빈으로 등록된 모든 Advisor를 가져와 Pointcut이 하나 이상 만족하는지 확인하고, 만족한다면 프록시 객체를 만들어 반환한다.
      1. 이 때 해당되는 Pointcut의 갯수만큼 Proxy 객체가 생성되지는 않고, 하나만 생성된다고 합니다.
    4. 생성된 프록시(또는 실 객체)가 DI 컨테이너에 등록됩니다.

ProxyFactory에서 객체를 만드는 두가지 방법

  • 그럼 ProxyFactory는 어떻게 객체를 만들까요? 총 두가지의 방법으로 Proxy를 만듭니다.
    1. 인터페이스가 있는 경우 : JDK 동적 프록시 기술을 활용하여 인터페이스를 구현한 프록시를 만듭니다.(인터페이스가 강제)
    2. 인터페이스가 없는 경우 : CGLIB 라이브러리(바이트코드 조작 라이브러리)를 활용하여 구현 객체를 상속한 프록시를 만듭니다.(final class에는 적용할 수 없음.)
      1. 다만 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

반응형