jdk代理和cglib代理的聯系與區別


https://mp.weixin.qq.com/s/YeNXq8VkP-IlqZ8rBv486w

 導讀:

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對象的數組,表示的是我將要給我需要代理的對象提供一組什么接口,如果我提供了一組接口給它,那么這個代理對象就宣稱實現了該接口(多態),這樣我就能調用這組接口中的方法了。

  • Handler:InvocationHandler對象,表示的是當我這個動態代理對象在調用方法的時候,會關聯到哪一個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中繼承的toString和hashCode方法)。Enhancer不能夠攔截final方法,例如Object.getClass()方法,這是由於Java final方法語義決定的。基於同樣的道理,Enhancer也不能對fianl類進行代理操作。這也是Hibernate為什么不能持久化final class的原因。

2、net.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.MethodProxy:java 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 性能區別:

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

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

 

5.3 各自局限:

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

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

 

6. 總結

 


免責聲明!

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



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