JDK 동적 프록시 대 CGLIB 동적 프록시
- JDK 동적 프록시는 인터페이스를 구현하는 클래스만 프록시할 수 있지만, CGLIB는 인터페이스를 구현하지 않는 클래스도 프록시할 수 있습니다. 또한 CGLIB 동적 프록시는 프록시된 클래스의 서브클래스를 생성하여 프록시된 클래스의 메서드 호출을 가로채므로 최종으로 선언된 클래스 및 메서드는 프록시할 수 없습니다.
- 둘 사이의 효율성 측면에서는 대부분의 경우 JDK 동적 에이전트가 더 우수하며, JDK 버전이 업그레이드될수록 이러한 이점은 더욱 분명해집니다.
- JDK 동적 프록시는 인터셉터, 리플렉션 메커니즘을 사용하여 프록시 인터페이스 익명 클래스를 생성한 후, 처리할 특정 메서드를 호출하기 전에 InvokeHandler를 호출합니다; CGLIB 동적 프록시는 ASM 프레임워크를 사용하고, 프록시 객체 클래스의 클래스 파일은 바이트코드 수정을 통해 로드되어 처리할 하위 클래스를 생성합니다.
JDK 동적 프록시 기본 원칙:
JDK 동적 프록시에서 생성된 프록시 클래스 $Proxy1은 Proxy에서 상속되어 HelloService 인터페이스를 구현하고, 프록시 클래스의 메서드를 호출할 때 인터셉터 MyInvocationHandler의 호출 메서드를 입력하게 되며, 다음은 프록시 클래스 생성 코드입니다:
// 프록시 객체 생성
HelloService helloService = (HelloService) Proxy.newProxyInstance(MyInvocationHandler.class.getClassLoader(), new Class[]{HelloService.class}, new MyInvocationHandler());
helloService.say();
위의 코드를 통해 얻은 헬로서비스 오브젝트는 실제로 JDK 동적 프록시 오브젝트이며, 다음과 같이 VM 옵션을 추가하여 동적 프록시 오브젝트를 저장하고 VM 옵션을 추가할 수 있습니다:
-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
그 후 다음과 같이 동적 프록시 객체가 생성되는데, say()는 실제로 개선이 필요한 HelloService에 정의된 메서드이므로, helloService.say()를 호출하면 실제로는 $Proxy1.say() 메서드가 호출되고, 여기서 h는 직접 정의한 MyInvocationHandler 인터셉터이며, 이후 인터셉터의 invoke 메서드로 이동하게 됩니다. MyInvocationHandler 인터셉터로 이동한 다음 인터셉터의 호출 메서드로 이동합니다.
import com.example.nettystudy.JdkProxyTest.HelloService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy1 extends Proxy implements HelloService {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
...
public final void say() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
...
}
다음으로 인터셉터의 호출 메서드를 살펴보겠습니다. 이 메서드에는 세 개의 매개 변수가 있는데, 첫 번째 매개 변수인 proxy는 위의 프록시 클래스 객체이고, method는 인터페이스의 say 메서드이므로 인터셉터가 추가하는 향상 작업을 수행하게 됩니다.
public class MyInvocationHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("메서드가 실행되기 전에");
// 여기서 HelloServiceImpl은 프록시 객체이고, 프록시 객체는 메서드를 실행합니다.
Object result = method.invoke(new HelloServiceImpl(), args);
System.out.println("메서드가 실행된 후");
return result;
}
}
cglib 동적 프록시의 기본 원리
cglib는 기본 바이트코드 기술을 사용하여 클래스의 서브클래스를 생성하고 서브클래스의 메서드를 사용하여 모든 부모 클래스 호출을 가로채고 교차 로직을 짜는 데 사용합니다.
cglib은 다음과 같이 사용됩니다:
// Human.java
public class Human {
public void info() {
System.out.println("Human invoke info");
}
public void fly() {
System.out.println("Human invoke fly");
}
}
// CGLibProxy.java
class CGLibProxy implements MethodInterceptor {
// CGLib프록시할 대상 객체
private Object targetObject;
public Object getProxyInstance(Object obj) {
this.targetObject = obj;
//1. 도구 클래스 생성
Enhancer enhancer = new Enhancer();
// 2.부모 클래스 - 클래스 또는 인터페이스 설정하기
enhancer.setSuperclass(obj.getClass());
//3. 콜백 함수 설정
enhancer.setCallback(this);
//4. 서브 클래스 객체, 즉 프록시 객체를 생성합니다.
Object proxyObj = enhancer.create();
// 프록시 객체를 반환합니다.
return proxyObj;
}
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
System.out.println("메서드 실행 전 처리 향상");
// 대상 객체 메서드 실행
Object obj = method.invoke(targetObject, args);
System.out.println("메서드 실행 후 향상된 처리");
return obj;
}
}
// TestCglibProxy.java
public class TestCglibProxy {
public static void main(String[] args) {
// 프록시 객체 생성
Human man = new Human();
// 다음 코드를 추가하여 프록시 클래스 소스 파일을 가져옵니다.
String path = CGLibProxy.class.getResource(".").getPath();
System.out.println(path);
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, path);
CGLibProxy cgLibProxy = new CGLibProxy();
Object obj = cgLibProxy.getProxyInstance(man);
System.out.println(obj.getClass());
Human hu = (Human)obj;
hu.info();
hu.fly();
}
}
위 프로그램의 출력은 다음과 같습니다:
생성된 프록시 클래스의 클래스 파일은 빨간색 경로를 따라가면 찾을 수 있습니다.
증강 대상 클래스인 Human 클래스에는 정보() 및 플라이() 메서드 두 개가 정의되어 있고, cglib에서 생성된 서브클래스는 Human 클래스를 상속받아 이 두 메서드를 재정의하게 되며, 생성된 프록시 클래스는 다음과 같습니다:
프록시 클래스에서 인터셉터는 먼저 var00100에 할당되고, 이후에는 직접 정의한 인터셉터의 인터셉트 메서드인 var10000.intercept 메서드가 호출됩니다
CGLibProxy#intercept().
public class Human$$EnhancerByCGLIB$$a1812f09 extends Human implements Factory {
// ... 나머지 코드는 생략하세요.
public final void info() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
var10000.intercept(this, CGLIB$info$0$Method, CGLIB$emptyArgs, CGLIB$info$0$Proxy);
} else {
super.info();
}
}
// ...
}





