JDK動態代理與CGLib動態代理相關問題


 導讀:

1、JDK動態代理原理是什么?為什么不支持類的代理?

2、JDK動態代理實例

3、CGLib代理原理是什么?

4、CGLib代理實例

5、JDK動態代理與CGLib代理的區別是什么?

6、總結

 

 

注:閱讀本文之前可以先閱讀:什么是代理模式? 

 

1. JDK動態代理原理是什么?為什么不支持類的代理?

 jdk動態代理圖:

 

利用攔截器(攔截器必須實現InvocationHanlder)加上反射機制生成一個實現代理接口的匿名類,在調用具體方法前調用InvokeHandler來處理。是在程序運行的過程中,根據被代理的接口來動態生成代理類的class文件,並加載運行的過程。

之所以只支持實現了接口的類的代理。從原理上講是因為JVM動態生成的代理類有如下特性:

繼承了Proxy類,實現了代理的接口,最終形式如下(HelloInterface為被代理類實現的接口):

 

public final class $Proxy0 extends Proxy implements HelloInterface{ ....... }

然而由於java不能多繼承,這里已經繼承了Proxy類了,不能再繼承其他的類,所以JDK的動態代理不支持對實現類的代理,只支持接口的代理。

 從使用上講,創建代理類時必須傳入被代理類實現的接口。

 

1.1 詳細介紹:

java的動態代理機制中,有兩個重要的類或接口,一個是 InvocationHandler(Interface)、另一個則是 Proxy(Class),這一個類和接口是實現我們動態代理所必須用到的。

1.1.1 InvocationHandler

每一個動態代理類都必須要實現InvocationHandler這個接口,並且每個代理類的實例都關聯了一個handler,當我們通過代理對象調用一個方法的時候,這個方法的調用就會被轉發為由InvocationHandler這個接口的 invoke 方法來進行調用。

InvocationHandler這個接口的唯一一個方法 invoke 方法:

Object invoke(Object proxy, Method method, Object[] args) throws Throwable

這個方法一共接受三個參數,那么這三個參數分別代表如下: 

  • proxy:  指代JDK動態生成的最終代理對象
  • method: 指代的是我們所要調用真實對象的某個方法的Method對象
  • args:   指代的是調用真實對象某個方法時接受的參數

1.1.2 Proxy

Proxy這個類的作用就是用來動態創建一個代理對象的類,它提供了許多的方法,但是我們用的最多的就是 newProxyInstance 這個方法:

 

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,  InvocationHandler handler)  throws IllegalArgumentException

 

這個方法的作用就是得到一個動態的代理對象,其接收三個參數,我們來看看這三個參數所代表的含義: 

  • loader:  ClassLoader對象,定義了由哪個ClassLoader來對生成的代理對象進行加載。
  • interfaces:  Interface對象的數組,表示的是我將要給我需要代理的對象提供一組什么接口,如果我提供了一組接口給它,那么這個代理對象就宣稱實現了該接口(多態),這樣我就能調用這組接口中的方法了。
  • HandlerInvocationHandler對象,表示的是當我這個動態代理對象在調用方法的時候,會關聯到哪一個InvocationHandler對象上。

所以我們所說的DynamicProxy(動態代理類)是這樣一種class:它是在運行時生成的class,在生成它時你必須提供一組interface給它,然后該class就宣稱它實現了這些 interface。這個DynamicProxy其實就是一個Proxy,它不會做實質性的工作,在生成它的實例時你必須提供一個handler,由它接管實際的工作。

 

2. JDK動態代理實例

2.1 創建接口類

public interface HelloInterface {

void sayHello();

}

 

2.2 創建被代理類,實現接口 

/**

 * 被代理類

 */

public class HelloImpl implements HelloInterface{

@Override

public void sayHello() {

System.out.println("hello");

}

}


2.3
創建InvocationHandler實現類 

 

/**

 * 每次生成動態代理類對象時都需要指定一個實現了InvocationHandler接口的調用處理器對象

 */

public class ProxyHandler implements InvocationHandler{

    private Object subject; // 這個就是我們要代理的真實對象,也就是真正執行業務邏輯的類

    public ProxyHandler(Object subject) {// 通過構造方法傳入這個被代理對象

        this.subject = subject;

    }

    /**

     *當代理對象調用真實對象的方法時,其會自動的跳轉到代理對象關聯的handler對象的invoke方法來進行調用

     */

@Override

public Object invoke(Object obj, Method method, Object[] objs)

throws Throwable {

Object result = null;

        System.out.println("可以在調用實際方法前做一些事情");

        System.out.println("當前調用的方法是" + method.getName());

        result = method.invoke(subject, objs);// 需要指定被代理對象和傳入參數

        System.out.println(method.getName() + "方法的返回值是" + result);

        System.out.println("可以在調用實際方法后做一些事情");

        System.out.println("------------------------");

        return result;// 返回method方法執行后的返回值

}

}

 

 

 

2.4 測試 

public class Mytest {

 

public static void main(String[] args) {

//第一步:創建被代理對象

HelloImpl hello = new HelloImpl();

//第二步:創建handler,傳入真實對象

ProxyHandler handler = new ProxyHandler(hello);

//第三步:創建代理對象,傳入類加載器、接口、handler

HelloInterface helloProxy = (HelloInterface) Proxy.newProxyInstance(

HelloInterface.class.getClassLoader(),

new Class[]{HelloInterface.class}, handler);

//第四步:調用方法

helloProxy.sayHello();

}

}


2.5 結果
 

可以在調用實際方法前做一些事情

當前調用的方法是sayHello

hello

sayHello方法的返回值是null

可以在調用實際方法后做一些事情

------------------------

 

3. CGLib代理原理是什么?

 

CGLib采用了非常底層的字節碼技術,其原理是通過字節碼技術為一個類創建子類,並在子類中采用方法攔截的技術攔截所有父類方法的調用,順勢織入橫切邏輯。(利用ASM開源包,對代理對象類的class文件加載進來,通過修改其字節碼生成子類來處理)

3.1 CGLib核心類:

1、 net.sf.cglib.proxy.Enhancer主要增強類,通過字節碼技術動態創建委托類的子類實例;

Enhancer可能是CGLIB中最常用的一個類,和Java1.3動態代理中引入的Proxy類差不多。和Proxy不同的是,Enhancer既能夠代理普通的class,也能夠代理接口。Enhancer創建一個被代理對象的子類並且攔截所有的方法調用(包括從Object中繼承的toStringhashCode方法)。Enhancer不能夠攔截final方法,例如Object.getClass()方法,這是由於Java final方法語義決定的。基於同樣的道理,Enhancer也不能對fianl類進行代理操作。這也是Hibernate為什么不能持久化final class的原因。

2net.sf.cglib.proxy.MethodInterceptor常用的方法攔截器接口,需要實現intercept方法,實現具體攔截處理;

 public java.lang.Object intercept(java.lang.Object obj,

                                  java.lang.reflect.Method method,

                                  java.lang.Object[] args,

                                  MethodProxy proxy)

                           throws java.lang.Throwable{}
  • obj:動態生成的代理對象 
  • method : 實際調用的方法
  • args:調用方法入參
  • proxy
  • net.sf.cglib.proxy.MethodProxyjava Method類的代理類,可以實現委托類對象的方法的調用;常用方法:methodProxy.invokeSuper(proxy, args);在攔截方法內可以調用多次

 

4. CGLib代理實例

4.1 創建被代理類

public class SayHello {

public void say(){

System.out.println("hello");

}

}

 

4.2 創建代理類 

/**

 *代理類

 */

public class ProxyCglib implements MethodInterceptor{

 private Enhancer enhancer = new Enhancer();  

 public Object getProxy(Class clazz){  

  //設置需要創建子類的類  

  enhancer.setSuperclass(clazz);  

  enhancer.setCallback(this);  

  //通過字節碼技術動態創建子類實例  

  return enhancer.create();  

 }  

 

 //實現MethodInterceptor接口方法  

 public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {  

  System.out.println("可以在調用實際方法前做一些事情");  

  //通過代理類調用父類中的方法  

  Object result = proxy.invokeSuper(obj, args);  

  System.out.println("可以在調用實際方法后做一些事情");  

  return result;  

 }

}

 

4.3 測試 

public class Mytest {

 

public static void main(String[] args) {

  ProxyCglib proxy = new ProxyCglib();  

  //通過生成子類的方式創建代理類  

  SayHello proxyImp = (SayHello)proxy.getProxy(SayHello.class);  

  proxyImp.say();  

}

}

 

4.4 結果 

可以在調用實際方法前做一些事情

hello

可以在調用實際方法后做一些事情

 

5. JDK動態代理與CGLib代理的區別是什么?

5.1 原理區別:

java動態代理是利用反射機制生成一個實現代理接口的匿名類,在調用具體方法前調用InvokeHandler來處理。核心是實現InvocationHandler接口,使用invoke()方法進行面向切面的處理,調用相應的通知。

cglib動態代理是利用asm開源包,對代理對象類的class文件加載進來,通過修改其字節碼生成子類來處理。核心是實現MethodInterceptor接口,使用intercept()方法進行面向切面的處理,調用相應的通知。

1、如果目標對象實現了接口,默認情況下會采用JDK的動態代理實現AOP

2、如果目標對象實現了接口,可以強制使用CGLIB實現AOP

3、如果目標對象沒有實現了接口,必須采用CGLIB庫,spring會自動在JDK動態代理和CGLIB之間轉換

 

5.2 性能區別:

1CGLib底層采用ASM字節碼生成框架,使用字節碼技術生成代理類,在jdk6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能對聲明為final的方法進行代理,因為CGLib原理是動態生成被代理類的子類。

2、在jdk6jdk7jdk8逐步對JDK動態代理優化之后,在調用次數較少的情況下,JDK代理效率高於CGLIB代理效率,只有當進行大量調用的時候,jdk6jdk7CGLIB代理效率低一點,但是到jdk8的時候,jdk代理效率高於CGLIB代理。

 

5.3 各自局限:

1JDK的動態代理機制只能代理實現了接口的類,而不能實現接口的類就不能實現JDK的動態代理。

2cglib是針對類來實現代理的,他的原理是對指定的目標類生成一個子類,並覆蓋其中方法實現增強,但因為采用的是繼承,所以不能對final修飾的類進行代理。

 

6. 總結

 

 

擴展閱讀:動態代理是基於什么原理?

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM