[Spring AOP] 스프링이 지원하는 프록시

2025. 7. 6. 16:52·Spring/AOP

스프링이 지원하는 프록시

이전 챕터에서 프록시 객체를 동적으로 만들어주는 동적 프록시 기술을 살펴보고, 그 한계점에 대해 살펴봤다.
문제점

  • 인터페이스가 있는 경우에는 JDK 동적 프록시를 적용하고, 그렇지 않은 경우에는 CGLIB를 적용하려면 어떻게 해야할까?
  • 두 기술을 함께 사용할 때 부가 기능을 제공하기 위해 JDK 동적 프록시가 제공하는 InvocationHandler 와 CGLIB가 제공하는 MethodInterceptor 를 각각 중복으로 만들어서 관리해야 할까?
  • 특정 조건에 맞을 때 프록시 로직을 적용하는 기능도 공통으로 제공되었으면?

위와 같은 문제점들을 해결할 수 있도록, 스프링은 ProxyFactory라는 기능을 제공하여 문제를 해결한다.

프록시 팩토리 - 소개

Q: 인터페이스가 있는 경우에는 JDK 동적 프록시를 적용하고, 그렇지 않은 경우에는 CGLIB를 적용하려면 어떻게 해야할까?
스프링은 유사한 구체적인 기술들이 있을 때, 그것들을 통합해서 일관성 있게 접근할 수 있고, 더욱 편리하게 사용할 수 있는 추상화된 기술을 제공한다.

스프링은 동적 프록시를 통합해서 편리하게 만들어주는 프록시 팩토리(ProxyFactory)라는 기능을 제공한다.
이전에는 상황에 따라서 JDK 동적 프록시를 사용하거나 CGLIB를 사용해야 했다면, 이제는 이 프록시 팩토리 하나로 편리하게 동적 프록시를 생성할 수 있다.

  • 인터페이스가 있으면 JDK 동적 프록시 사용
  • 구체 클래스만 있으면 CGLIB 사용
  • 혹은 개발자가 지정해줄 수도 있음!

Q: 두 기술을 함께 사용할 때 부가 기능을 적용하기 위해 JDK 동적 프록시가 제공하는 InvocationHandler와 CGLIB가 제공하는 MethodInterceptor를 각각 중복으로 따로 만들어야 할까?
스프링은 이 문제를 해결하기 위해 부가 기능을 적용할 때 Advice 라는 새로운 개념을 도입했다. 개발자는 InvocationHandler나 MethodInterceptor를 신경쓰지 않고, Advice 만 만들면 된다. 개발자가 Advice를 구현해두면, 이 Advice를 기반으로 ProxyFactory가 프록시 객체를 만든다. 이 프록시 객체는 InvocationHandler, MethodInterceptor 중 하나일 것이고, 이들은 무조건 Advice를 호출한다.

아까 봤던 프록시 팩토리 - 사용 흐름 클래스 의존관계 오른쪽에다가 위 의존관계를 붙이면 아래와 같다.

ProxyFactory가 둘 중 하나를 적절하게 만들어주고, 만들어진 객체는 무조건 Advice 로직을 수행한다.

Q: 특정 조건에 맞을 때 프록시 로직을 적용하는 기능도 공통으로 제공되었으면?
스프링은 Pointcut이라는 개념을 도입해서 프록시 로직 실행 조건을 지정하는 문제를 일관성 있게 해결한다.

프록시 팩토리 - 예제 코드

프록시 팩토리를 이용해서 프록시 객체를 만들려면, 먼저 Advice와 PointCut을 등록해야 한다.
(또는 Advice과 PointCut을 조합한 Advisor를 등록해야 한다. Advisor과 관련된 내용은 아래에서 다시 다룬다.)

Advice 를 만드는 방법은 여러가지가 있지만, 기본적인 방법은 다음 인터페이스를 구현하면 된다.

MethodInterceptor - 스프링이 제공하는 코드

package org.aopalliance.intercept; // 패키지 명에 주의하자. 

public interface MethodInterceptor extends Interceptor {
    Object invoke(MethodInvocation invocation) throws Throwable;
}
  • MethodInvocation invocation
    • 내부에는 다음 메서드를 호출하는 방법, 현재 프록시 객체 인스턴스, args, 메서드 정보 등이 포함되어 있다. 기존에 파라미터로 제공되는 부분들이 이 안으로 모두 들어갔다고 생각하면 된다.
  • CGLIB의 MethodInterceptor 와 이름이 같으므로 패키지 이름에 주의하자
    • 참고로 여기서 사용하는 org.aopalliance.intercept 패키지는 스프링 AOP 모듈(spring-aop) 안에 들어있다.
  • MethodInterceptor는 Interceptor 를 상속하고 Interceptor 는 Advice 인터페이스를 상속한다.

TimeAdvice 예제

@Slf4j
public class TimeAdvice implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        log.info("TimeProxy 실행");
        long startTime = System.currentTimeMillis();

        Object result = invocation.proceed();

        long endTime = System.currentTimeMillis();
        long resultTime = endTime - startTime;
        log.info("TimeProxy 종료 resultTime={}ms", resultTime);
        return result;
    }
}
  • MethodInterceptor 인터페이스를 구현한다.
  • Object result = invocation.proceed()
    • invocation.proceed()를 호출하면 target 클래스를 호출하고 그 결과를 받는다.
    • 그런데 기존에 보았던 코드들과 다르게 target 클래스의 정보가 보이지 않는다. target 클래스의 정보는 MethodInvocation invocation 안에 모두 포함되어 있다.
    • 그 이유는 바로 다음에 확인할 수 있는데, 프록시 팩토리로 프록시를 생성하는 단계에서 이미 target 정보를 파라미터로 전달받기 때문이다.

프록시 팩토리 사용 예제

@Slf4j
public class ProxyFactoryTest {

    @Test
    @DisplayName("인터페이스가 있으면 JDK 동적 프록시 사용")
    void interfaceProxy() {
        ServiceInterface target = new ServiceImpl();
        ProxyFactory proxyFactory = new ProxyFactory(target);
        proxyFactory.addAdvice(new TimeAdvice());
        ServiceInterface proxy = (ServiceInterface) proxyFactory.getProxy();
        log.info("targetClass={}", target.getClass());
        log.info("proxyClass={}", proxy.getClass());

        proxy.save();

        assertThat(AopUtils.isAopProxy(proxy)).isTrue();
        assertThat(AopUtils.isJdkDynamicProxy(proxy)).isTrue();
        assertThat(AopUtils.isCglibProxy(proxy)).isFalse();
    }
}

    @Test
    @DisplayName("구체 클래스만 있으면 CGLIB 사용")
    void concreteProxy() {
        ConcreteService target = new ConcreteService();
        ProxyFactory proxyFactory = new ProxyFactory(target);
        proxyFactory.addAdvice(new TimeAdvice());
        ConcreteService proxy = (ConcreteService) proxyFactory.getProxy();
        log.info("targetClass={}", target.getClass());
        log.info("proxyClass={}", proxy.getClass());

        proxy.call();

        assertThat(AopUtils.isAopProxy(proxy)).isTrue();
        assertThat(AopUtils.isJdkDynamicProxy(proxy)).isFalse();
        assertThat(AopUtils.isCglibProxy(proxy)).isTrue();
    }
}
  • new ProxyFactory(target) : 프록시 팩토리를 생성할 때, 생성자에 프록시의 호출 대상을 함께 넘겨준다. 프록시 팩토리는 이 인스턴스 정보를 기반으로 프록시를 만들어낸다.
    • 스프링 빈은 싱글톤으로 관리된다. 스프링 빈 등록할 때 ProxyFactory의 target으로 지정하고, 프록시 팩토리를 생성해서 프록시 객체를 빈으로 등록하면 야무질 것 같다.
  • proxyFactory.addAdvice(new TimeAdvice()): 프록시 팩토리를 통해서 만든 프록시가 사용할 부가 기능 로직을 설정한다. JDK 동적 프록시가 제공하는 InvocationHandler 와 CGLIB가 제공하는 MethodInterceptor 의 개념과 유사하다. 이렇게 프록시가 제공하는 부가 기능 로직을 어드바이스(Advice, 한글 의미: 조언)라 한다.
  • proxyFactory.getProxy(): 프록시 객체를 생성하고 그 결과를 받는다.

프록시 팩토리를 통한 프록시 적용 확인
프록시 팩토리로 프록시가 잘 적용되었는지 확인하기 위해 유틸리티를 사용할 수 있다.

  • AopUtils.isAopProxy(proxy) : 프록시 팩토리를 통해서 프록시가 생성되면 JDK 동적 프록시나, CGLIB 모두 참이다.
  • AopUtils.isJdkDynamicProxy(proxy): 프록시 팩토리를 통해서 프록시가 생성되고, JDK 동적 프록시인 경우 참
  • AopUtils.isCglibProxy(proxy): 프록시 팩토리를 통해서 프록시가 생성되고, CGLIB 동적 프록시인 경우 참

proxyTargetClass 옵션

@Test
@DisplayName("ProxyTargetClass 옵션을 사용하면 인터페이스가 있어도 CGLIB를 사용하고, 클래스 기반 프록시 사용")
void proxyTargetClass() {
    ServiceInterface target = new ServiceImpl();
    ProxyFactory proxyFactory = new ProxyFactory(target);
    proxyFactory.setProxyTargetClass(true); //중요
    proxyFactory.addAdvice(new TimeAdvice());
    ServiceInterface proxy = (ServiceInterface) proxyFactory.getProxy();
    log.info("targetClass={}", target.getClass());
    log.info("proxyClass={}", proxy.getClass());

    proxy.save();

    assertThat(AopUtils.isAopProxy(proxy)).isTrue();
    assertThat(AopUtils.isJdkDynamicProxy(proxy)).isFalse();
    assertThat(AopUtils.isCglibProxy(proxy)).isTrue();
}

프록시 팩토리는 proxyTargetClass 라는 옵션을 제공하는데, 이 옵션에 true 값을 넣으면 인터페이스가 있어도 강제로 CGLIB를 사용한다. 그리고 인터페이스가 아닌 클래스 기반의 프록시를 만들어준다.

프록시 팩토리의 기술 선택 방법

  • 대상에 인터페이스가 있으면: JDK 동적 프록시, 인터페이스 기반 프록시
  • 대상에 인터페이스가 없으면: CGLIB, 구체 클래스 기반 프록시
  • proxyTargetClass=true: CGLIB, 구체 클래스 기반 프록시, 인터페이스 여부와 상관없음

참고

스프링 부트는 AOP를 적용할 때 기본적으로 proxyTargetClass=true로 설정해서 사용한다.

따라서 인터페이스가 있어도 항상 CGLIB를 사용해서 구체 클래스를 기반으로 프록시를 생성한다.

자세한 이유는 뒷 챕터에서 설명!

포인트컷, 어드바이스, 어드바이저 - 소개

  • 포인트컷(Pointcut): 어디에 부가 기능을 적용할지, 어디에 부가 기능을 적용하지 않을지 판단하는 필터링 로직이다.
    • 주로 클래스와 메서드 이름으로 필터링한다.
    • 이름 그대로 어떤 포인트(Point)에 기능을 적용할지 하지 않을지 잘라서(cut) 구분하는 것이다.
  • 어드바이스(Advice): 이전에 본 것 처럼 프록시가 호출하는 부가 기능이다. 단순하게 프록시 로직이라 생각하면 된다.
  • 어드바이저(Advisor): 단순하게 하나의 포인트컷과 하나의 어드바이스를 가지고 있는 것이다. 쉽게 이야기해서 포인트컷1 + 어드바이스1이다.

역할과 책임

  • 포인트컷은 대상 여부를 확인하는 필터 역할만 담당한다.
  • 어드바이스는 깔끔하게 부가 기능 로직만 담당한다.
  • 둘을 합치면 어드바이저가 된다. 스프링의 어드바이저는 하나의 포인트컷 + 하나의 어드바이스로 구성된다.

예제 코드1 - 어드바이저

직접 Advisor를 만들고, 프록시 팩토리에 Advisor 정보를 넘겨보자.

@Slf4j
public class AdvisorTest {
    @Test
    void advisorTest1() {
        ServiceInterface target = new ServiceImpl();
        ProxyFactory proxyFactory = new ProxyFactory(target);
        DefaultPointcutAdvisor advisor = new
            DefaultPointcutAdvisor(Pointcut.TRUE, new TimeAdvice());
        proxyFactory.addAdvisor(advisor);
        ServiceInterface proxy = (ServiceInterface) proxyFactory.getProxy();
        proxy.save();
        proxy.find();
    }
}
  • new DefaultPointcutAdvisor: Advisor 인터페이스의 가장 일반적인 구현체이다. 생성자를 통해 하나의 포인트컷과 하나의 어드바이스를 넣어주면 된다. 어드바이저는 하나의 포인트컷과 하나의 어드바이스로 구성된다.
    • Pointcut.TRUE: 항상 true 를 반환하는 포인트컷이다. 이후에 직접 포인트컷을 구현해볼 것이다.
    • new TimeAdvice(): 앞서 개발한 TimeAdvice 어드바이스를 제공한다.
  • proxyFactory.addAdvisor(advisor): 프록시 팩토리에 적용할 어드바이저를 지정한다.
    • 프록시 팩토리를 사용할 때 어드바이저는 필수이다.
    • 그런데 생각해보면 이전에 분명히 proxyFactory.addAdvice(new TimeAdvice()) 이렇게 어드바이저가 아니라 어드바이스를 바로 적용했다. 이것은 단순히 편의 메서드이고 결과적으로 해당 메서드 내부에서 지금 코드와 똑같은 다음 어드바이저가 생성된다.
    • DefaultPointcutAdvisor(Pointcut.TRUE, new TimeAdvice())

예제 코드2 - 직접 만든 포인트컷

포인트컷을 직접 만들어서, 어드바이스 로직이 적용되는 기준을 지정해보자.

Pointcut 관련 인터페이스 - 스프링 제공

public interface Pointcut {
    ClassFilter getClassFilter();
    MethodMatcher getMethodMatcher();
}

public interface ClassFilter {
    boolean matches(Class<?> clazz);
}

public interface MethodMatcher {
    boolean matches(Method method, Class<?> targetClass);
    //..
}

포인트컷은 크게 ClassFilter와 MethodMatcher 둘로 이루어진다. 이름 그대로 하나는 클래스가 맞는지, 하나는 메서드가 맞는지 확인할 때 사용한다. 둘다 true로 반환해야 어드바이스를 적용할 수 있다.

@Test
@DisplayName("직접 만든 포인트컷")
void advisorTest2() {
    ServiceImpl target = new ServiceImpl();
    ProxyFactory proxyFactory = new ProxyFactory(target);
    DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(new MyPointcut(), new TimeAdvice());
    proxyFactory.addAdvisor(advisor);
    ServiceInterface proxy = (ServiceInterface) proxyFactory.getProxy();
    proxy.save();
    proxy.find();
}

static class MyPointcut implements Pointcut {
    @Override
    public ClassFilter getClassFilter() {
        return ClassFilter.TRUE;
    }

    @Override
    public MethodMatcher getMethodMatcher() {
        return new MyMethodMatcher();
    }
}

static class MyMethodMatcher implements MethodMatcher {
    private String matchName = "save";
    @Override
    public boolean matches(Method method, Class<?> targetClass) {
        boolean result = method.getName().equals(matchName);
        log.info("포인트컷 호출 method={} targetClass={}", method.getName(), targetClass);
        log.info("포인트컷 결과 result={}", result);
        return result;
    }

    @Override
    public boolean isRuntime() {
        return false;
    }

    @Override
    public boolean matches(Method method, Class<?> targetClass, Object... args){
        throw new UnsupportedOperationException();
    }
}

MyPointcut

  • 클래스 필터는 항상 true를 반환하도록 했고
  • 메서드 비교 기능은 MyMethodMatcher 를 사용한다.
    • matches(): 이 메서드에 method, targetClass 정보가 넘어온다. 이 정보로 어드바이스를 적용할지 적용하지 않을지 판단할 수 있다.
      • 여기서는 메서드 이름이 "save" 인 경우에 true를 반환하도록 판단 로직을 적용했다.
    • isRuntime(), matches(... args): isRuntime()이 값이 참이면 matches(... args) 메서드가 대신 호출된다. 동적으로 넘어오는 매개변수를 판단 로직으로 사용할 수 있다.
      • isRuntime()이 false 인 경우 클래스의 정적 정보만 사용하기 때문에 스프링이 내부에서 캐싱을 통해 성능 향상이 가능하지만, isRuntime()이 true 인 경우 매개변수가 동적으로 변경된다고 가정하기 때문에 캐싱을 하지 않는다.
      • 크게 중요한 부분은 아니니 참고만 하고 넘어가자

예제 코드3 - 스프링이 제공하는 포인트컷

스프링은 우리가 필요한 포인트컷을 이미 대부분 제공한다.

@Test
@DisplayName("스프링이 제공하는 포인트컷")
void advisorTest3() {
    ServiceImpl target = new ServiceImpl();
    ProxyFactory proxyFactory = new ProxyFactory(target);
    NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
    pointcut.setMappedNames("save");
    DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, new TimeAdvice());
    proxyFactory.addAdvisor(advisor);
    ServiceInterface proxy = (ServiceInterface) proxyFactory.getProxy();
    proxy.save();
    proxy.find();
}
  • 스프링이 제공하는 NameMatchMethodPointcut 를 사용했다
    • setMappedNames(...)으로 메서드 이름을 지정하면 포인트컷이 완성된다.

이 외에도 스프링은 무수히 많은 포인트 컷을 제공한다.

  • NameMatchMethodPointcut : 메서드 이름을 기반으로 매칭한다. 내부에서는 PatternMatchUtils 를 사용한다.
    • 예) *xxx* 허용
  • JdkRegexpMethodPointcut : JDK 정규 표현식을 기반으로 포인트컷을 매칭한다.
  • TruePointcut : 항상 참을 반환한다.
  • AnnotationMatchingPointcut: 애노테이션으로 매칭한다.
  • AspectJExpressionPointcut: aspectJ 표현식으로 매칭한다. (중요!)
    • 가장 중요한 내용이다. 뒤에서 자세히 다룰 예정

예제 코드4 - 여러 어드바이저 함께 적용

만약 여러 어드바이저를 하나의 target 에 적용하려면 어떻게 해야할까?
쉽게 이야기해서 하나의 target에 여러 어드바이스를 적용하려면 어떻게 해야할까?

앞에서 했던 방식을 떠올려보면 프록시 체인을 형성할 수도 있을 것이다. 프록시 팩토리 여러개를 만들면 된다.

public class MultiAdvisorTest {
    @Test
    @DisplayName("여러 프록시")
    void multiAdvisorTest1() {
        //client -> proxy2(advisor2) -> proxy1(advisor1) -> target

        //프록시1 생성
        ServiceInterface target = new ServiceImpl();
        ProxyFactory proxyFactory1 = new ProxyFactory(target);
        DefaultPointcutAdvisor advisor1 = new DefaultPointcutAdvisor(Pointcut.TRUE, new Advice1());
        proxyFactory1.addAdvisor(advisor1);
        ServiceInterface proxy1 = (ServiceInterface) proxyFactory1.getProxy();

        //프록시2 생성, target -> proxy1 입력
        ProxyFactory proxyFactory2 = new ProxyFactory(proxy1);
        DefaultPointcutAdvisor advisor2 = new DefaultPointcutAdvisor(Pointcut.TRUE, new Advice2());
        proxyFactory2.addAdvisor(advisor2);
        ServiceInterface proxy2 = (ServiceInterface) proxyFactory2.getProxy();

        //실행
        proxy2.save();
    }
    // 생략
  • proxyFactory2의 target이 proxy1인 부분에 주의하자.

문제점
만약 적용해야 하는 어드바이저가 10개라면 10개의 프록시를 생성해야한다.

스프링의 문제 해결
스프링은 이 문제를 해결하기 위해 하나의 프록시에 여러 어드바이저를 적용할 수 있게 만들어두었다.

@Test
@DisplayName("하나의 프록시, 여러 어드바이저")
void multiAdvisorTest2() {
    //proxy -> advisor2 -> advisor1 -> target

    DefaultPointcutAdvisor advisor2 = new DefaultPointcutAdvisor(Pointcut.TRUE, new Advice2());
    DefaultPointcutAdvisor advisor1 = new DefaultPointcutAdvisor(Pointcut.TRUE, new Advice1());

    ServiceInterface target = new ServiceImpl();
    ProxyFactory proxyFactory1 = new ProxyFactory(target);

    proxyFactory1.addAdvisor(advisor2);
    proxyFactory1.addAdvisor(advisor1);
    ServiceInterface proxy = (ServiceInterface) proxyFactory1.getProxy();

    //실행
    proxy.save();
}
  • 프록시 팩토리에 원하는 만큼 addAdvisor()를 통해서 어드바이저를 등록하면 된다.
  • 등록하는 순서대로 advisor 가 호출된다. 여기서는 advisor2, advisor1 순서로 등록했다.

Proxy | advisor2의 포인트컷 체크 advisor2의 advice 로직 수행 | | advisor1의 포인트컷 체크 advisor1의 advice 로직 수행 |

뒤에서 다룰 내용이지만, 위에서 적용된 원리로 인해 하나의 target 에 여러 AOP가 동시에 적용되어도, 스프링의 AOP는 target 마다 하나의 프록시만 생성한다. 이부분을 꼭 기억해두자.

프록시 팩토리 - 적용1

로그 추적기 예제에 ProxyFactory를 적용해보자. 먼저 인터페이스 기반의 V1에 적용해볼 것이다.
Advisor를 등록해야하고, Advisor를 등록하기 위해서는 Advice와 PointCut을 만들어야 한다.

LogTraceAdvice


@Slf4j
public class LogTraceAdvice implements MethodInterceptor {
    private final LogTrace logTrace;

    public LogTraceAdvice(LogTrace logTrace) {
        this.logTrace = logTrace;
    }

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        TraceStatus status = null;

        try {
            Method method = invocation.getMethod();
            String message = method.getDeclaringClass().getSimpleName() + "."
                + method.getName() + "()";

            status = logTrace.begin(message);

            //로직 호출
            Object result = invocation.proceed();

            logTrace.end(status);
            return result;
        } catch (Exception e) {
            logTrace.exception(status, e);
            throw e;
        }
    }
}

프록시 팩토리가 만들어낸 프록시 객체를 빈으로 등록

@Slf4j
@Configuration
public class ProxyFactoryConfigV1 {
    @Bean
    public OrderControllerV1 orderControllerV1(LogTrace logTrace) {
        OrderControllerV1 orderController = new OrderControllerV1Impl(orderServiceV1(logTrace));

        ProxyFactory factory = new ProxyFactory(orderController);
        factory.addAdvisor(getAdvisor(logTrace));
        OrderControllerV1 proxy = (OrderControllerV1) factory.getProxy();
        log.info("ProxyFactory proxy={}, target={}", proxy.getClass(), orderController.getClass());
        return proxy;
    }

    @Bean
    public OrderServiceV1 orderServiceV1(LogTrace logTrace) {
        OrderServiceV1 orderService = new OrderServiceV1Impl(orderRepositoryV1(logTrace));

        ProxyFactory factory = new ProxyFactory(orderService);
        factory.addAdvisor(getAdvisor(logTrace));
        OrderServiceV1 proxy = (OrderServiceV1) factory.getProxy();
        log.info("ProxyFactory proxy={}, target={}", proxy.getClass(), orderService.getClass());
        return proxy;
    }

    @Bean
    public OrderRepositoryV1 orderRepositoryV1(LogTrace logTrace) {
        OrderRepositoryV1 orderRepository = new OrderRepositoryV1Impl();

        ProxyFactory factory = new ProxyFactory(orderRepository);
        factory.addAdvisor(getAdvisor(logTrace));
        OrderRepositoryV1 proxy = (OrderRepositoryV1) factory.getProxy();
        log.info("ProxyFactory proxy={}, target={}", proxy.getClass(), orderRepository.getClass());
        return proxy;
    }

    private Advisor getAdvisor(LogTrace logTrace) {
        //pointcut
        NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
        pointcut.setMappedNames("request*", "order*", "save*");
        //advice
        LogTraceAdvice advice = new LogTraceAdvice(logTrace);
        //advisor = pointcut + advice
        return new DefaultPointcutAdvisor(pointcut, advice);
    }
}
  • 포인트컷은 NameMatchMethodPointcut 을 사용한다. 여기에는 심플 매칭 기능이 있어서 *을 매칭할 수 있다.
    • request*, order*, save*: request 로 시작하는 메서드에 포인트컷은 true 를 반환한다. 나머지도 같다.

프록시 팩토리 - 적용2

이제 구체 클래스 기반의 V2에 적용해볼 것이다. 우리가 학습했듯이, ProxyFactory가 JDK 동적 프록시와 CGLIB중 적절한 것을 선택해주므로, 우리는 그 부분에 대해서는 신경쓰지 않아도 된다.
즉, V1의 빈 생성 코드 구조와 V2의 빈 생성 코드 구조는 동일하다.

정리

프록시 팩토리 덕분에 개발자는 매우 편리하게 프록시를 생성할 수 있게 되었고, 어떤 부가 기능을 어디에 적용할 지 명확하게 구분지어 적용할 수 있게 되었다.

남은 문제

  • 설정 파일이 지나치게 많아지게 된다.
    • 애플리케이션에 스프링 빈이 100개가 있다면 여기에 프록시를 통해 부가 기능을 적용하려면 100개의 동적 프록시 생성 코드를 만들어야 한다!
  • 컴포넌트 스캔을 사용하는 경우 지금까지 학습한 방법으로는 프록시 적용이 불가능하다.
    • 실제 객체를 컴포넌트 스캔으로 스프링 컨테이너에 스프링 빈으로 등록을 다 해버린 상태이기 때문이다.

컴포넌트 스캔을 마친 뒤에, 프록시를 적용하길 원하는 빈에 프록시를 적용할 수 있으면 좋지 않을까? 다음 챕터(빈 후처리기)

Ref) 스프링 핵심 원리 - 고급편 강의 | 김영한 - 인프런

'Spring > AOP' 카테고리의 다른 글

[Spring AOP] @Aspect AOP  (0) 2025.07.06
[Spring AOP] 빈 후처리기  (0) 2025.07.06
[Spring AOP] 동적 프록시 기술  (0) 2025.07.06
[Spring AOP] 프록시 패턴과 데코레이터 패턴  (0) 2025.07.06
[Spring AOP] 템플릿 메서드 패턴과 콜백 패턴  (0) 2025.07.06
'Spring/AOP' 카테고리의 다른 글
  • [Spring AOP] @Aspect AOP
  • [Spring AOP] 빈 후처리기
  • [Spring AOP] 동적 프록시 기술
  • [Spring AOP] 프록시 패턴과 데코레이터 패턴
lumana
lumana
배움을 나누는 공간 https://github.com/bebeis
  • lumana
    Brute force Study
    lumana
  • 전체
    오늘
    어제
    • 분류 전체보기 (463)
      • 개발 일지 (0)
        • Performance (0)
        • TroubleShooting (0)
        • Refactoring (0)
        • Code Style, Convetion (0)
        • Architecture (0)
      • Software Engineering (36)
        • Test (8)
        • 이론 (18)
        • Clean Code (10)
      • Java (72)
        • Basic (5)
        • Core (21)
        • Collection (7)
        • 멀티스레드&동시성 (13)
        • IO, Network (8)
        • Reflection, Annotation (3)
        • Modern Java(8~) (13)
        • JVM (2)
      • Spring (53)
        • Framework (12)
        • MVC (23)
        • Transaction (3)
        • AOP (11)
        • Boot (0)
        • AI (0)
      • DB Access (16)
        • Jdbc (1)
        • JdbcTemplate (0)
        • JPA (14)
        • Spring Data JPA (0)
        • QueryDSL (0)
      • Computer Science (130)
        • Data Structure (27)
        • OS (14)
        • Database (10)
        • Network (21)
        • 컴퓨터구조 (6)
        • 시스템 프로그래밍 (23)
        • Algorithm (29)
      • HTTP (8)
      • Infra (1)
        • Docker (1)
      • 프로그래밍언어론 (15)
      • Programming Language(Sub) (77)
        • Kotlin (1)
        • Python (25)
        • C++ (51)
        • JavaScript (0)
      • FE (11)
        • HTML (1)
        • CSS (9)
        • React (0)
        • Application (1)
      • Unix_Linux (0)
        • Common (0)
      • PS (13)
        • BOJ (7)
        • Tip (3)
        • 프로그래머스 (0)
        • CodeForce (0)
      • Book Review (4)
      • Math (3)
        • Linear Algebra (3)
      • AI (7)
        • DL (0)
        • ML (0)
        • DA (0)
        • Concepts (7)
      • 프리코스 (4)
      • Project Review (6)
      • LegacyPosts (11)
      • 모니터 (0)
      • Diary (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
lumana
[Spring AOP] 스프링이 지원하는 프록시
상단으로

티스토리툴바