스프링 핵심 원리 - 고급편 - (17) 동적 프록시 기술 - JDK 동적 프록시
인프런 스프링 핵심 원리 - 고급편을 학습하고 정리한 내용 입니다.
JDK 동적 프록시 - 소개
적용 대상이 100개면 프록시를 100개 만들지 않기 위해 해결 방법이 동적 프록시 기술이다.
주의
JDK 동적 프록시는 인터페이스를 기반으로 프록시를 동적으로 만들어준다. 따라서 인터페이스가 필수이다.
기본 예제 코드

해당 위치에 만들었다.
AInterface
1
2
3
public interface AInterface {
String call();
}
AImpl
1
2
3
4
5
6
7
8
@Slf4j
public class AImpl implements AInterface {
@Override
public String call() {
log.info("A 호출");
return "A";
}
}
BInterface
1
2
3
public interface BInterface {
String call();
}
BImpl
1
2
3
4
5
6
7
8
@Slf4j
public class BImpl implements BInterface {
@Override
public String call() {
log.info("B 호출");
return "B";
}
}
정말 간단한 예제이다.
A 인터페이스 - 구현체, B 인터페이스 - 구현체 이런 식으로 만든 것이다.
코드는 설명할 부분이 없고, 여기다 프록시를 적용하려면 지금 프록시를 2개 만들어야 하는 상황이다.
이걸 동적 프록시로 1개만 만들어서 해결해 보자.
JDK 동적 프록시 - 예제 코드
JDK 동적 프록시 InvocationHandler
JDK 동적 프록시에 적용할 로직은 InvocationHandler인터페이스를 구현해서 작성하면 된다.
1
2
3
4
5
6
7
8
9
10
public interface InvocationHandler {
Object invoke(Object var1, Method var2, Object[] var3) throws Throwable;
@CallerSensitive
static Object invokeDefault(Object proxy, Method method, Object... args) throws Throwable {
Objects.requireNonNull(proxy);
Objects.requireNonNull(method);
return Proxy.invokeDefault(proxy, method, args, Reflection.getCallerClass());
}
}
제공되는 파라미터는 다음과 같다.
Object proxy: 프록시 자신Method method: 호출한 메서드Object[] args: 메서드를 호출할 때 전달한 인수
이제 시간 측정 프록시를 구현해보자.
hello.proxy.jdkdynamic.code.TimeInvocationHandler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@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;
}
}
TimeInvocationHandler은InvocationHandler인터페이스를 구현한다. 이렇게 해서 JDK 동적 프록 시에 적용할 공통 로직을 개발할 수 있다.Object target: 동적 프록시가 호출할 대상method.invoke(target, args): 리플렉션을 사용해서target인스턴스의 메서드를 실행한다.args는 메서드 호출 시 넘겨줄 인수이다.
이제 테스트 코드를 클라이언트라 가정하고 어떻게 사용하는지 알아보자.
hello.proxy.jdkdynamic.JdkDynamicProxyTest
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@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());
}
}
new TimeInvocationHandler(target): 동적 프록시에 적용할 핸들러 로직이다.
1
AInterface proxy = (AInterface) Proxy.newProxyInstance(AInterface.class.getClassLoader(), new Class[]{AInterface.class}, handler);
- 동적 프록시는
java.lang.reflect.Proxy를 통해서 생성할 수 있다 - 클래스 로더 정보, 인터페이스, 그리고 핸들러 로직을 넣어주면 된다. 그러면 해당 인터페이스를 기반으로 동적 프록시를 생성하고 그 결과를 반환한다. (인터페이스로 캐스팅 가능하다.)
proxy.call()로 인터페이스를 동작 시킨다.

출력 결과를 보면 프록시가 A, B 둘 다 한번에 정상 수행된 것을 확인할 수 있다.
생성된 JDK 동적 프록시
위 사진에서 proxyClass=class com.sun.proxy.$Proxy8 이 부분이 동적으로 생성된 프록시 클래스 정보이다.
이것은 우리가 만든 클래스가 아니라 JDK 동적 프록시가 이름 그대로 동적으로 만들어준 프록시이다.
이 프록시는 TimeInvocationHandler로직을 실행한다.
실행 순서
- 클라이언트는 JDK 동적 프록시의
call()을 실행한다. - JDK 동적 프록시는
InvocationHandler.invoke()를 호출한다.TimeInvocationHandler가 구현체로 있으므로TimeInvocationHandler.invoke()가 호출된다. TimeInvocationHandler가 내부 로직(시간 측정)을 수행하고,method.invoke(target, args)를 호출해서target인 실제 객체(AImpl)를 호출한다.AImpl인스턴스의call()이 호출된다.AImpl인스턴스의call()의 실행이 끝나면TimeInvocationHandler로 응답이 돌아온다. 시간 로그를 출력하고 결과를 반환한다.

동적 프록시 클래스 정보 및 정리
dynamicA() 와 dynamicB() 둘을 동시에 함께 실행하면 JDK 동적 프록시가 각각 다른 동적 프록시 클래스를 만들어주는 것을 확인할 수 있다.

예제를 보면 AImpl , BImpl각각 프록시를 만들지 않았다.
프록시는 JDK 동적 프록시를 사용해서 동적으로 만들고 TimeInvocationHandler는 공통으로 사용했다.
JDK 동적 프록시 기술 덕분에 적용 대상 만큼 프록시 객체를 만들지 않아도 된다. 그리고 같은 부가 기능 로직을 한번만 개발해서 공통으로 적용할 수 있다.
만약 적용 대상이 100개여도 동적 프록시를 통해서 생성하고, 각각 필요한 InvocationHandler만 만들어서 넣어주면 된다.
결과적으로 프록시 클래스를 수 없이 만들어야 하는 문제도 해결하고, 부가 기능 로직도 하나의 클래스에 모아서 단일 책임 원칙(SRP)도 지킬 수 있게 되었다.
JDK 동적 프록시 없이 직접 프록시를 만들어서 사용할 때와 JDK 동적 프록시를 사용할 때의 차이를 그림으로 비교해 보자.



댓글남기기