스프링이 지원하는 프록시
이전 챕터에서 프록시 객체를 동적으로 만들어주는 동적 프록시 기술을 살펴보고, 그 한계점에 대해 살펴봤다.
문제점
- 인터페이스가 있는 경우에는 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개의 동적 프록시 생성 코드를 만들어야 한다!
- 컴포넌트 스캔을 사용하는 경우 지금까지 학습한 방법으로는 프록시 적용이 불가능하다.
- 실제 객체를 컴포넌트 스캔으로 스프링 컨테이너에 스프링 빈으로 등록을 다 해버린 상태이기 때문이다.
컴포넌트 스캔을 마친 뒤에, 프록시를 적용하길 원하는 빈에 프록시를 적용할 수 있으면 좋지 않을까? 다음 챕터(빈 후처리기)
'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 |