1. 程式人生 > >Java代理模式(3)一CGLib動態代理

Java代理模式(3)一CGLib動態代理

目錄

前言

Java代理模式(2)一動態代理中提到Java的動態代理只侷限於實現介面的實現類(RealSubject/RealSubject2都實現 ProblemInterface),儘管比起靜態代理優點有很多,但是實際業務中不是所有的類都會實現一個介面,在SpringHibernate這些框架更是很明顯,所以它們都會用到了CGLib對實現介面的類動態生成代理類

一、CGLib原理

1、什麼是CGLib?

CGLib是一個強大的,高效能,高質量的程式碼生成類庫(Code Generation Library),提供比反射更為強大方便的特性來實現比Java動態代理更為靈活的代理。它為沒有實現介面的類提供代理,是JDK動態代理的一種補充與擴充。

2、CGLib原理

(1)動態生成一個代理類的子類,子類重寫要代理的類的所有方法(不包括final修飾的,實際上是繼承了重寫了被代理類的所有方法,自然final不能被重寫代理)。在子類中採用方法攔截的技術攔截intercept所有父類方法的呼叫,同時織入橫切邏輯。
理解起來可能會有點晦澀,先進行CGLib相關的方法介紹或許能理解:

  • net.sf.cglib.proxy.Enhancer 位元組碼增強器,可以很方便的對類進行拓展,使用Enhancer.create()方法建立動態代理類返回。
  • net.sf.cglib.proxy.MethodInterceptor 主要的方法攔截類,被代理物件RealSubject
    的每個method()的呼叫都會轉向實現此介面的intercept()方法。具體請看下圖。
  • net.sf.cglib.proxy.MethodProxy JDKjava.lang.reflect.Method類的代理類,可以方便的實現對目標物件方法的呼叫。

這裡寫圖片描述

  • intercept()中我們可以對被代理物件方法進行呼叫,在呼叫的前後還可以加入其它業務,也就是織入橫切邏輯
@Override
    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 ; }
  • 當對Proxy代理中所有方法的呼叫時,都會轉向MethodInterceptor型別的攔截intercept()方法,在攔截方法中再呼叫真實業務RealSubject物件相應的方法。

(2)它是通過底層ASM位元組碼相關處理,轉為位元組碼生成新的代理類的子類,所以會比通過Java的反射機制建立動態代理更加有效率。關於ASM可以檢視此部落格Java ASM介紹ASM快速入門

二、完整例子程式碼實現

業務類RealSubject3

public class RealSubject3 {

    public void resolve() {
        System.out.println("真實業務類RealSubject3主要實現CRUD操作...");
    }

}

實現MethodInterceptor介面的MethodInterceptorImpl類:

public class MethodInterceptorImpl implements MethodInterceptor{

    private Object target;//業務類真實物件

    //類似於JDK動態代理中的繫結
    public Object getInstance(Object target) {  
        this.target = target; 
        Enhancer enhancer = new Enhancer();
        //設定代理類的父類,即RealSubject3為proxy的父類
        enhancer.setSuperclass(this.target.getClass());  
        //設定回撥:表示對代理物件proxy的方法的呼叫都會回撥intercept()函式進行處理
        enhancer.setCallback(this); 
       // 建立動態代理類物件並返回  
       return enhancer.create(); 
    }

    // 實現回撥方法 
    @Override
    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; 
    } 
}

CGLibClient測試類:

public class CGLibClient {

    public static void main(String[] args) {
        RealSubject3 realSubject3 = new RealSubject3();
        MethodInterceptorImpl methodInterceptor = new MethodInterceptorImpl();
        RealSubject3 proxy = (RealSubject3)methodInterceptor.getInstance(realSubject3);//生成RealSubject3的子類代理類proxy
        proxy.resolve();
    }

}

結果如下,成功實現了代理

呼叫前相關操作…
真實業務類RealSubject3主要實現CRUD操作…
呼叫後相關操作…

注意:在編寫過程中可能會遇到以下的錯誤:

Exception in thread "main" java.lang.NoClassDefFoundError: org/objectweb/asm/Type
    at net.sf.cglib.core.TypeUtils.parseType(TypeUtils.java:184)
    at net.sf.cglib.core.KeyFactory.<clinit>(KeyFactory.java:72)
    at net.sf.cglib.proxy.Enhancer.<clinit>(Enhancer.java:72)
    ...

這要麼是因為jar包衝突,要麼就是沒引對jar包,我們通常使用帶ASMcglib-nodep-3.2.6.jar包,如果同時存在cglib-3.2.6.jar會衝突。

<dependency>
   <groupId>cglib</groupId>
    <artifactId>cglib-nodep</artifactId>
    <version>3.2.6</version>
</dependency>

現在我們再增加一個方法:add()方法:

public class RealSubject3 {

    public void resolve() {
        System.out.println("真實業務類RealSubject3主要實現CRUD操作...");
    }
    public void add() {
        System.out.println("add操作...");
    }
}

然後我們可以進行在回撥處理函式intercept()中進行對add()方法的許可權控制:

public class MethodInterceptorImpl implements MethodInterceptor{

    private Object target;//業務類真實物件
    private String name;
    //傳入username
    public MethodInterceptorImpl(String name) {
        this.name = name;
    }
    //類似於JDK動態代理中的繫結
    public Object getInstance(Object target) {  
        this.target = target; 
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(this.target.getClass());  //設定代理類的父類,即RealSubject3為proxy的父類
        //設定回撥:表示對代理物件proxy的方法的呼叫都會回撥intercept()函式進行處理
        enhancer.setCallback(this); 
       // 建立動態代理類物件並返回  
       return enhancer.create(); 
    }

    // 實現回撥方法 
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { 
        //System.out.println("呼叫前相關操作...");
        if (!"小李".equals(name)) { //對add()方法進行許可權控制
            System.out.println(name+"您不是管理員,只有管理員才有許可權");
            return null;  
        }
       System.out.println("管理員"+name+",歡迎您...");
        Object result = proxy.invokeSuper(obj, args); //呼叫真實業務類的方法
        //System.out.println("呼叫後相關操作...");
        return result; 
    } 
}

客戶端CGLibClient

public class CGLibClient {

    public static void main(String[] args) {
        RealSubject3 realSubject3 = new RealSubject3();
        String username = "小張";
        MethodInterceptorImpl methodInterceptor = new MethodInterceptorImpl(username);
        RealSubject3 proxy = (RealSubject3)methodInterceptor.getInstance(realSubject3);//生成RealSubject3的子類代理類proxy
        proxy.add();
        String username2 = "小李";
        MethodInterceptorImpl methodInterceptor2 = new MethodInterceptorImpl(username2);
        RealSubject3 proxy2 = (RealSubject3)methodInterceptor2.getInstance(realSubject3);//生成RealSubject3的子類代理類proxy
        proxy2.add();
    }
}

結果:

小張您不是管理員,只有管理員才有許可權
管理員小李,歡迎您…
add操作…

總結與補充

1、在代理實現了介面的類時候,預設的情況下Spring等框架都是使用JDK動態代理,當然也可以強制使用CGLib動態代理實現介面的被代理類,但是要代理沒有實現介面的被代理類時,Spring都會使用CGLib實現動態代理。

2、其實我們是可以檢視動態生成代理類.class檔案,之後反編譯檢視結果,首先我們CGLibClient程式中中加入如下程式碼,表示CGLib動態生成的.class輸出到指定的路徑下。

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "E:\\xxx\\xxx");

之後會生成.class檔案,利用反編譯工具進行檢視:

(1)生成的代理類extends RealSubject3
這裡寫圖片描述
(2)還有重寫了父類RealSubject3所有的方法(不包括final
這裡寫圖片描述
(3)還有必備的toStringequals等方法
這裡寫圖片描述