JDK 동적 프록시
프록시는 타겟 코드의 수정없이 접근제어, 부가기능을 추가하기 위해 주로 사용됩니다. 하지만, 프록시를 사용하기 위해서는 대상 클래스 만큼 프록시 클래스를 만들어줘야하기 때문에 코드 중복이 발생한다는 단점이 있습니다. 이를 해결할 수 있는 기술이 동적 프록시 기술입니다.
동적 프록시 기술은 개발자가 직접 프록시 클래스를 만들지 않아도 되고, 런타임에 개발자 대신 동적으로 객체를 생성해줍니다. 또한 동적 프록시에 원하는 로직을 지정할 수도 있습니다.
JDK 동적 프록시 구현
JdkDynamicProxyInterface
public interface JdkDynamicProxyInterface {
String call();
}
JdkDynamicProxyImpl
@Slf4j
public class JdkDynamicProxyImpl implements JdkDynamicProxyInterface {
@Override
public String call() {
log.info("call 메서드 호출 됌");
return "Call Data";
}
}
ExecuteTimeInvocationHandler
import java.lang.reflect.InvocationHandler;
@Slf4j
public class ExecuteTimeInvocationHandler implements InvocationHandler {
//프록시가 호출할 대상
private final Object target;
public ExecuteTimeInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log.info("TimeProxy Execute");
long startTime = System.currentTimeMillis();
// 로직 실행
// 메서드 호출하는 부분이 동적으로 처리됩니다.
// Reflection을 사용하여 인스턴스의 메서드를 실행합니다.
Object result = method.invoke(target, args);
// 로직 종료
long endTime = System.currentTimeMillis();
long executeTime = endTime - startTime;
log.info("TimeProxy Terminate ExecuteTime:{}", executeTime);
return result;
}
}
JdkDynamicProxyEX
import java.lang.reflect.Proxy;
@Slf4j
public class JdkDynamicProxyEX {
@Test
void jdkDynamicProxy() {
JdkDynamicProxyImpl target = new JdkDynamicProxyImpl();
ExecuteTimeInvocationHandler handler = new ExecuteTimeInvocationHandler(target);
// JDK에서 제공하는 자바 프록시 객체 생성 기술
JdkDynamicProxyInterface proxy = (JdkDynamicProxyInterface) Proxy.newProxyInstance(
JdkDynamicProxyInterface.class.getClassLoader(), //프록시가 어디에 생성될 지 선언
new Class[]{JdkDynamicProxyInterface.class}, //프록시에 들어가는 인터페이스 선언
handler); //프록시가 호출할 로직을 선정
proxy.call();
log.info("targetClass:{}", target.getClass());
log.info("proxyClass:{}", proxy.getClass());
}
}
코드 진행 흐름
- JdkDynamicProxyEX 내에 proxy.call() 호출합니다.
- 프록시가 호출할 로직인 handler 즉, ExecuteTimeInvocationHandler안에 있는 로직 수행합니다.
- ExecuteTimeInvocationHandler 내에 invoke() 호출합니다.
- ExecuteTimeInvocationHandler 가 갖고있는 target 즉, JdkDynamicProxyImpl의 call() 을 수행합니다.
출력 결과
10:35:22.446 [main] INFO hello.proxy.jdkdynamic.code.ExecuteTimeInvocationHandler - TimeProxy Execute
10:35:22.451 [main] INFO hello.proxy.jdkdynamic.code.JdkDynamicProxyImpl - call 메서드 호출 됌
10:35:22.452 [main] INFO hello.proxy.jdkdynamic.code.ExecuteTimeInvocationHandler - TimeProxy Terminate ExecuteTime:1
10:35:22.455 [main] INFO hello.proxy.jdkdynamic.code.JdkDynamicProxyEX - targetClass:class hello.proxy.jdkdynamic.code.JdkDynamicProxyImpl
10:35:22.455 [main] INFO hello.proxy.jdkdynamic.code.JdkDynamicProxyEX - proxyClass:class jdk.proxy2.$Proxy9 [동적으로 생성된 프록시 객체]
결론
기존에는 인터페이스, 구현체, 프록시 객체까지 3개를 모두 코딩을 했다면
JDK 동적 프록시를 적용했을 때 인터페이스와 구현체를 구현하고 프록시 객체는 JDK 동적 프록시 기술을 사용해서
만들 수 있습니다. 그리고 다른 로직을 수행해야한다고 할 때 로직을 수행하는 InvocationHandler를 구현할 수 있습니다.
그래서 현재는 하나의 인터페이스와 구현체만을 구현하여 동적 프록시 기술을 적용했지만,
실무에서는 실행시간을 측정하기 위해서 많은 클래스에 동적 프록시 기술을 적용한다면
쉽게 실행시간을 측정할 수 있을 것 같습니다.
[참고]
1. Java 동적 프록시
'JAVA' 카테고리의 다른 글
[Java] Collection - Set (0) | 2022.09.19 |
---|---|
[Java] Collection - List (0) | 2022.09.19 |
[Java] Reflection (0) | 2022.08.24 |
[Java] Facade Pattern (0) | 2022.08.01 |
[Java] Observer Pattern (0) | 2022.08.01 |