동적 프록시란 프록시 클래스를 일일이 만들지 않고 리플렉션을 이용해 해당 클래스가 가진 메서드를 실행시키는 방식으로 구현한다.
만약 위에 프록시패턴을 적용한 것처럼 구현한다면 ProxyA, ProxyB를 각각 만들어줬어야 했을 것이다.
JDK 동적 프록시는 java에서 제공하는 동적 프록시 방식으로 인터페이스를 필수로 요구한다. 사용 방법은 간단하다, package java.lang.reflect;
에서 InvocationHandler
인터페이스 구현체를 만들어서 사용하면 된다.
InvocationHanlder 구현체에 호출할 Target 대상을 지정해준다. 위 다이어그램에서는 ImplA, ImplB를 전달해주는 것이다.
로그를 통해 실제 객체가 호출된 것이 아니라 proxyClass=class com.sun.proxy.$Proxy9
프록시 객체가 호출된 것을 알 수 있다. $Proxy#num
은 JDK 동적 프록시가 클래스 이름을 정하는 패턴이다.
프록시 객체의 생성은 JDK 동적 프록시가 해주고 우리는 해당 기능만 만들어주면 된다. 최종 구현형태는 아래 그림처럼 된다.
하지만, JDK 동적 프록시는 인터페이스가 있는 객체만 구현 가능하다. 스프링에서는 구체클래스만 있는 경우에도 동적 프록시를 제공하기 위해서 CGLIB 라이브러리를 활용한다.
Code Generator Library
사용방법은 JDK 동적 프록시와 유사하다. InvocationHandler
를 만들었던 것처럼 MethodInterceptor
를 만든다.
package org.springframework.cglib.proxy;
public interface MethodInterceptor extends Callback {
Object intercept(Object obj, Method method, Object[] args, MethodProxy
proxy) throws Throwable;
}
CGLIB은 Enhancer를 사용해서 프록시를 생성한다.
CGLIB이 프록시 객체에 이름을 주는 방식은 아래와 같다.
대상클래스$$EnhancerByCGLIB$$임의코드
hello.proxy.common.service.ConcreteService$$EnhancerByCGLIB$$25d6b0e3
런타임 의존 관계
클래스 의존 관계
추가로 CGLIB을 적용하기 위해선 객체에 기본 생성자가 있어야 한다.
동적 프록시를 사용하면 프록시 객체를 일일이 만들지 않기 때문이 기본 프록시 패턴을 사용했을 때의 문제점을 개선했다.
하지만, 이제 상황에 따라 인터페이스가 있을 경우 JDK 동적 프록시, 없을 경우 CGLIB을 쓴다던지 전략이 있어야 한다.
두 기술을 함께 사용하려면 InvocationHandler와 MethodInterceptor를 각각 중복으로 만들어서 관리해야할까?
특정조건에 맞을 때 프록시 로직을 적용하는 기능도 같이 제공해주길 바래서 나온 것이 스프링의 프록시 팩토리다.