JDK 動態代理和 CGLIB 代理

在 Spring 中 AOP 代理使用 JDK 動態代理和 CGLIB 代理來實現,默認如果目標對象是接口,則使用 JDK 動態代理,否則使用 CGLIB 來生成代理類。

1.JDK 動態代理

那麼接口(UserServiceBo)、目標對象(被代理對象 UserServiceImpl)、代理對象($Proxy0)三者具體關係可以使用下圖表示:

JDK 動態代理和 CGLIB 代理

正如上圖可知 JDK 動態代理是對接口進行的代理;代理類實現了接口,並繼承了 Proxy 類;目標對象與代理對象沒有什麼直接關係,只是它們都實現了接口,並且代理對象執行方法時候內部最終是委託目標對象執行具體的方法。

示例如下:

JDK 代理是對接口進行代理,所以首先寫一個接口類:

<code>public interface UserService {

public int add();
}/<code>

然後實現該接口如下:

JDK 動態代理和 CGLIB 代理

<code>public class UserServiceImpl implements UserService {

@Override
public int add() {
System.out.println("執行add方法");
return 0;
}
}/<code>
JDK 動態代理和 CGLIB 代理

JDK 動態代理是需要實現 InvocationHandler 接口,因此創建一個 InvocationHandler 的實現類:

JDK 動態代理和 CGLIB 代理

<code>public class MyInvocationHandler implements InvocationHandler {

private Object target;

public MyInvocationHandler(Object target) {
super();
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//1
System.out.println("********begin "+method.getName()+"********");
//2
Object result = method.invoke(target, args);
//3
System.out.println("********end "+method.getName()+"*********");
return result;
}

public Object getProxy(){
//4
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), target.getClass().getInterfaces(), this);
}

}/<code>
JDK 動態代理和 CGLIB 代理

接著進行測試,代碼如下:

JDK 動態代理和 CGLIB 代理

<code>public static void main(String[] args) {

//5打開這個開關,可以把生成的代理類保存到磁盤
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
//6創建目標對象(被代理對象)
UserService service = new UserServiceImpl();
//7創建一個InvocationHandler實例,並傳遞被代理對象
MyInvocationHandler handler = new MyInvocationHandler(service);
//8生成代理類

UserService proxy = (UserService) handler.getProxy();
proxy.add();
}/<code>
JDK 動態代理和 CGLIB 代理

其中代碼6 創建了一個 UserServiceImpl 的實例對象,這個對象就是要被代理的目標對象。

代碼7 創建了一個 InvocationHandler 實例,並傳遞被代理目標對象 service 給內部變量 target。

代碼8 調用 MyInvocationHandler 的 getProxy 方法使用 JDK 生成一個代理對象。

代碼5 設置系統屬性變量 sun.misc.ProxyGenerator.saveGeneratedFiles 為 true,這是為了讓代碼(8)生成的代理對象的字節碼文件保存到磁盤。

接著我們進行反編譯看核心的代碼,運行後會在項目的 com.sun.proxy 下面會生成 $Proxy0.class 類,經反編譯後核心 Java 代碼如下:

JDK 動態代理和 CGLIB 代理

<code>package com.sun.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy.JDK.UserServiceBo;

public final class $Proxy0
extends Proxy
implements UserService
{
private static Method m1;
private static Method m3;
private static Method m0;
private static Method m2;

public $Proxy0(InvocationHandler paramInvocationHandler)
{
super(paramInvocationHandler);
}

public final int add()
{
try
{
//9第一個參數是代理類本身,第二個是實現類的方法,第三個是參數
return h.invoke(this, m3, null);
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}

...省略

static
{
try
{

m3 = Class.forName("proxy.JDK.UserService").getMethod("add", new Class[0]);
...省略
return;
}
catch (NoSuchMethodException localNoSuchMethodException)

...省略
}
}/<code>
JDK 動態代理和 CGLIB 代理

可以看到代理類 $Proxy0 中 代碼9 中的 h 就是 main 函數里面創建的 MyInvocationHandler 的實例,h.invoke(this, m3, null) 實際就是調用的 MyInvocationHandler 的 invoke 方法,

而後者則是委託給被代理對象進行執行,這裡可以對目標對象方法進行攔截,然後對其功能進行增強。另外 代碼8 生成的 proxy 對象實際就是 $Proxy0 的一個實例,當調用 proxy.add() 時候,

實際是調用的代理類 $Proxy0 的 add 方法,後者內部則委託給 MyInvocationHandler 的 invoke 方法,invoke 方法內部有調用了目標對象 service 的 add 方法。

因此,JDK 動態代理機制總結如下:

JDK 動態代理機制只能對接口進行代理,其原理是動態生成一個代理類,這個代理類實現了目標對象的接口,目標對象和代理類都實現了接口,但是目標對象和代理類的 Class 對象是不一樣的,所以兩者是沒法相互賦值的。


CGLIB 動態代理

相比 JDK 動態代理對接口進行代理,CGLIB 則是對實現類進行代理,這意味著無論目標對象是否有接口,都可以使用 CGLIB 進行代理。

那麼接口(UserServiceBo)、目標對象(被代理對象 UserServiceImpl),代理對象(UserServiceImpl$$EnhancerByCGLIB$$d0bce05a)三者具體關係可以使用下圖表示:

JDK 動態代理和 CGLIB 代理

可知接口和代理對象沒有啥關係,代理對象是繼承了目標對象和實現了 Factory 接口。

例子如下:

使用 CGLIB 進行代理需要實現 MethodInterceptor,創建一個方法攔截器 CglibProxy 類:

JDK 動態代理和 CGLIB 代理

<code>public class CglibProxy implements MethodInterceptor {
//10
private Enhancer enhancer = new Enhancer();

//11
public Object getProxy(Class clazz) {
//12 設置被代理類的Class對象
enhancer.setSuperclass(clazz);
//13 設置攔截器回調
enhancer.setCallback( this);
return enhancer.create();
}


@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println(obj.getClass().getName()+"."+method.getName());
Object result = proxy.invokeSuper(obj, args);

return result;
}
}/<code>
JDK 動態代理和 CGLIB 代理

測試類如下:

JDK 動態代理和 CGLIB 代理

<code>public void testCglibProxy() {

//14 生成代理類到本地
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/zhuizhumengxiang/Downloads");
//15 生成目標對象
UserServiceImpl service = new UserServiceImpl();
//16 創建CglibProxy對象
CglibProxy cp = new CglibProxy();
//17 生成代理類
UserServiceBo proxy = (UserServiceBo) cp.getProxy(service.getClass());
proxy.add();
}/<code>
JDK 動態代理和 CGLIB 代理

執行上面代碼會在 /Users/huangjuncong/Downloads 目錄生成代理類 UserServiceImpl$$EnhancerByCGLIB$$d0bce05a.class 文件,反編譯後部分代碼如下:

JDK 動態代理和 CGLIB 代理

<code>public class UserServiceImpl$$EnhancerByCGLIB$$d0bce05a extends UserServiceImpl
implements Factory
{
static void CGLIB$STATICHOOK1()
{
//18 空參數
CGLIB$emptyArgs = new Object[0];

//19 獲取UserServiceImpl的add方法列表

Method[] tmp191_188 = ReflectUtils.findMethods(new String[] { "add", "()I" }, (localClass2 = Class.forName("zlx.cglib.zlx.UserServiceImpl")).getDeclaredMethods());
CGLIB$add$0$Method = tmp191_188[0];

//20 創建CGLIB$add$0,根據創建一個MethodProxy
CGLIB$add$0$Proxy = MethodProxy.create(localClass2, localClass1, "()I", "add", "CGLIB$add$0");

}
static
{
CGLIB$STATICHOOK1();
}
//(21)
final int CGLIB$add$0()
{
return super.add();
}
//(22)
public final int add()
{
...省略
//23 獲取攔截器,這裡為CglibProxy的實例
MethodInterceptor tmp17_14 = this.CGLIB$CALLBACK_0;
if (tmp17_14 != null)
{     //24 調用攔截器的intercept方法
Object tmp36_31 = tmp17_14.intercept(this, CGLIB$add$0$Method, CGLIB$emptyArgs, CGLIB$add$0$Proxy);
tmp36_31;
return tmp36_31 == null ? 0 : ((Number)tmp36_31).intValue();
}
return super.add();
}
}/<code>
JDK 動態代理和 CGLIB 代理

代碼18 創建了一個 CGLIB$emptyArgs,因為 add 方法是無入參的,所以這裡創建的 Object 對象個數為0,這對象是 CglibProxy 攔截器的 intercept 的第三個參數。

代碼19 獲取 UserServiceImpl 的 add 方法列表,並把唯一方法賦值給 CGLIB$add$0$Method,這個對象是 CglibProxy 攔截器的 intercept 第二個參數。

代碼20 創建了一個 MethodProxy 對象,當調用 MethodProxy 對象的 invokeSuper 方法時候會直接調用代理對象的 CGLIB$add$0 方法,也就是直接調用父類 UserServiceImpl 的 add 方法,這避免了一次反射調用,創建的 MethodProxy 對象是 CglibProxy 攔截器的 intercept 的第四個參數。

代碼22 是代理類的 add 方法,代碼(17)生成的代理對象 proxy 就是 UserServiceImpl$$EnhancerByCGLIB$$d0bce05a 的一個實例,當調用 proxy.add() 方法時候就是調用的代碼(22),從代碼(22)可知內部調用了攔截器 CglibProxy 的 intercept 方法,可知 intercept 的第一個參數就是代理對象本身。

  因此CGLIB代理的總結如下:

    CGLIB 是對目標對象本身進行代理,所以無論目標對象是否有接口,都可以對目標對象進行代理,其原理是使用字節碼生成工具在內存生成一個繼承目標對象的代理類,然後創建代理對象實例。

    由於代理類的父類是目標對象,所以代理類是可以賦值給目標對象的,自然如果目標對象有接口,代理對象也是可以賦值給接口的。

    CGLIB 動態代理中生成的代理類的字節碼相比 JDK 來說更加複雜。

總的來說:

  JDK 使用反射機制調用目標類的方法,CGLIB 則使用類似索引的方式直接調用目標類方法,所以 JDK 動態代理生成代理類的速度相比 CGLIB 要快一些,但是運行速度比 CGLIB 低一些,並且 JDK 代理只能對有接口的目標對象進行代理。


原文來源:https://www.cnblogs.com/huangjuncong/p/8667278.html


分享到:


相關文章: