프록시를 이용해서 원본 코드를 그대로 냅두고 로그 출력 로직을 도입할 수 있었다.
하지만 만약 적용해야할 곳이 100곳 이라면 100개의 프록시 클래스를 우리가 일일히 정의를 해주어야 한다는 단점이 있었다.
이를 해결하는 방법으로 JDK 동적 프록시와 CGLIB가 있다.
첫 번째로 JDK 동적 프록시다.
코드와 클래스 다이어그램을 보고 이해해 보자
package hello.proxy.jdkdynamic.code;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
@Slf4j
public class TimeInvocationHandler implements InvocationHandler {
private final Object target;
public TimeInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws
Throwable {
log.info("TimeProxy 실행");
long startTime = System.currentTimeMillis();
Object result = method.invoke(target, args);
long endTime = System.currentTimeMillis();
long resultTime = endTime - startTime;
log.info("TimeProxy 종료 resultTime={}", resultTime);
return result;
}
}
우선 원래 적었던 프록시 로직을 저 InvocationHandler 인터페이스를 구현하는 클래스에 적어 놓는다. 그리고 리플렉션으로 프록시가 호출되는 Method 정보 및 args 즉 인자들이 넘겨 지는데
이것을 이용해서 target을 호출하면 된다.
(코드로 분석결과 저 Method는 인터페이스 메서드 정보임 참고)
그리고 부가정보를 다른 곳에 적어 놓으면 된다.
package hello.proxy.jdkdynamic;
import hello.proxy.jdkdynamic.code.*;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Proxy;
@Slf4j
public class JdkDynamicProxyTest {
@Test
void dynamicA() {
AInterface target = new AImpl();
TimeInvocationHandler handler = new TimeInvocationHandler(target);
AInterface proxy = (AInterface)
Proxy.newProxyInstance(AInterface.class.getClassLoader(), new Class[]
{AInterface.class}, handler);
proxy.call();
log.info("targetClass={}", target.getClass());
log.info("proxyClass={}", proxy.getClass());
}
@Test
void dynamicB() {
BInterface target = new BImpl();
TimeInvocationHandler handler = new TimeInvocationHandler(target);
BInterface proxy = (BInterface)
Proxy.newProxyInstance(BInterface.class.getClassLoader(), new Class[]
{BInterface.class}, handler);
proxy.call();
log.info("targetClass={}", target.getClass());
log.info("proxyClass={}", proxy.getClass());
}
}