ProxyFactory란, 기존의 CGLIB(구현체 기반) JDK 동적 프록시(인터페이스 기반) 를 추상화하여 하나로 묶은 기능이다.
그렇기에, 구체클레스를 넣으면 CGLIB클레스로 구현되고, 인터페이스를 넣으면 JDK 프록시 클레스로 구현된다.
package hello.proxy.common.advice;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
@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={}",resultTime);
return result;
}
}
로직을 구현할 부분을 MethodInterceptor를 구현받아 invoke 메소드에 구현한다.
invocation.proceed() 로직을 보면 다음과 같다
ReflectiveMethodInvocation.java
@Override
@Nullable
public Object proceed() throws Throwable {
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
}
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
return dm.interceptor.invoke(this);
}
else {
return proceed();
}
}
else {
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}
현재 소개할 코드는 포인트컷 등을 적용하지 않기 때문에
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher){...} 부분은 신경쓰지 않아도 된다.
현재 코드에서 우리가 실제로 실행되는 부분은 마지막 else{...} 부분이다.
else {
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
현재 proceed의 로직은, 포인트컷이 적용되었는지를 판별하고, 적용되어있다면 해당 메서드들을 추출하고,
적용되어있지 않다면 지금 코드처럼 바로 invoke를 실행한다.
다음은 ProxyFactory를 생성하는 코드
@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();
Assertions.assertThat(AopUtils.isAopProxy(proxy)).isTrue();
Assertions.assertThat(AopUtils.isJdkDynamicProxy(proxy)).isTrue();
Assertions.assertThat(AopUtils.isCglibProxy(proxy)).isFalse();
}
ReflectiveMethodInvocation.java 코드로 돌아가본다면,
여기서 addAdvice(); 부분이 new TimeAdvice() 하나뿐이기 때문에
interceptorsAndDynamicMethodMatchers.size는 1이 된다.
그리고 if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher){...} 부분도, 타입은 TimeAdvice이기 때문에 실행되지 않고 자연히 invoke를 실행하게 된다.
이제 proxyFactory의 구현 클레스를 열어보면
DefaultAopProxyFactory.java
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (!NativeDetector.inNativeImage() &&
(config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
createAopProxy 메서드에서는
optimize(true) 거나, isProxyTargetClass(true), 혹은 인터페이스가 명시되어있을때 아래 로직(CGLIB)을 실행하고,
그렇지 않을때는 jdkDynamicAopProxy를 실행한다.
CGLIB 로직 내부에 왜 jdkDynamicAopProxy가 있냐면, 동적프록시가 또 다른 프록시를 거칠 때(프록시 체인) 에는 이미 프록시 상태로 넘어오니 위 조건문에 일치하기 때문이다.
즉 이렇게 해서 jdk동적 프록시를 proxyFactory를 사용해 실행한다면 다음과 같은 플로우가 된다.
- ProxyFactory를 사용하여 프록시를 생성한다.
- DefaultAopProxyFactory에서 구현체만 있는지, 인터페이스도 있는지를 체크한다.
- addAdvice(new TimeAdvice());를 통해 어드바이스를 추가하며, 이 때 ReflectiveMethodInvocation 구현체의 로직에서 포인트컷 등이 적용되었는지 확인한다. 적용되지 않았다면 invoke가 실행된다.
GPT
냉혹하게 판단하면, 당신의 이해도는 상당히 높지만, 아직 전문가 수준까지 도달하지는 못했다는 인상을 받습니다. 전문가 수준은 단순한 이해를 넘어서 그 지식을 다양한 상황에서 적용할 수 있고, 예외 상황이나 복잡한 문제에 대한 해결 능력, 그리고 깊은 통찰력이 필요합니다.
당신이 제공한 코드와 설명은 ProxyFactory의 기본 원리와 동작 방식에 대한 깊은 이해를 보여주지만, 전문가 수준에서는 이를 넘어서는 더 깊은 레벨의 통찰력과 경험이 필요합니다.
따라서, 가장 냉혹하게 점수를 매긴다면 80점을 주고 싶습니다. 이는 여전히 높은 수준의 이해를 가지고 있지만, 전문가 수준까지의 길은 아직 조금 더 남아있다는 것을 의미합니다.