探究動態代理的祕密

1 代理模式

動態代理屬於代理模式,那究竟什麼是代理模式呢?

說白了,代理模式就是爲對象提供一個代理以控制對某個對象的訪問,原對象被稱爲委託類,這個代理的實現被稱爲代理類。代理類在爲委託類預處理消息之後會將消息轉發給委託類,之後還能進行消息的後置處理。代理類不實現具體服務,而是利用委託類來完成服務。

2 代理模式的優勢

爲啥我們要加一個代理類呢?直接使用委託類來完成業務需求不簡單嗎?事實上,使用代理模式是有大大的好處滴。

  1. 隱藏了委託類的實現
  2. 實現了客戶與委託類之間的解耦
  3. 我們可以在不修改委託類代碼的情況下做一些額外的處理

在 Java 的許多場景中我們都會運用到代理模式,舉個栗子,在 Spring 的 AOP 切面中我們爲切面生成了一個代理類。

代理主要分爲靜態代理、JDK 動態代理和 CGLIB 動態代理,它們都有各自的特點,接下來我會詳細介紹這幾種代理模式。

3 靜態代理

實現

先建立一個委託接口

//委託接口
public interface WorkInterface {

	void work();
}

實現委託類

//委託類
public class WorkService implements WorkInterface {

	@Override
	public void work() {
		System.out.println("在這裏實現相應的業務邏輯");
	}

}

再實現代理類

//代理類
public class WorkProxy implements WorkInterface {
	
	private WorkInterface workInterface = new WorkService();

	@Override
	public void work() {
		System.out.println("在執行業務邏輯之前記錄日誌");
		workInterface.work();
		System.out.println("在執行業務邏輯之後記錄日誌");
	}

}

測試一下

//測試類
public class WorkTest {

	public static void main(String[] args) {
		WorkInterface workProxy = new WorkProxy();
		workProxy.work();
	}
}

執行結果是沒問題的

在執行業務邏輯之前記錄日誌
在這裏實現相應的業務邏輯
在執行業務邏輯之後記錄日誌

優點

靜態代理實現過程比較簡單,其代理關係在編譯期間就已經確定。靜態代理適用於委託類中委託方法較少的情況下。

缺點

當委託類中委託方法很多時,我們需要在代理類中寫一大堆的代理方法,在效率上是難以接受的。

4 淺談動態代理

動態代理是指代理類在程序運行時創建(靜態代理是在編譯期創建)。

相信大家之前對 JVM 的類加載機制多多少少有些瞭解,其主要分爲五個階段,分別爲加載,驗證,準備,解析,初始化。在加載階段,JVM 會完成以下三個步驟:

  1. 通過一個類的全名或其它途徑來獲取這個類的二進制字節流
  2. 將這個字節流所代表的靜態存儲結構轉化爲方法區的運行時數據結構
  3. 在內存中生成一個代表這個類的 Class 對象,作爲方法區中對這個類訪問的入口

動態代理就發生在加載階段的第一個階段,類的二進制字節流來源有很多,包括但不限於 zip 包、運行時計算生成、數據庫獲取等,我們所說的動態代理技術便是運行時計算生成,根據接口或者目標對象計算出代理類的字節碼然後加載進 JVM 中。

一般使用動態代理有兩種常見的做法,一是通過使用接口的 JDK 動態代理,一是通過使用繼承類的 CGLIB 動態代理。

5 JDK 動態代理

實現

實現一個委託接口

//委託接口
public interface WorkInterface {

	void work1();
	
	void work2();
}

接着實現一個委託類

//委託類
public class WorkService implements WorkInterface {

	@Override
	public void work1() {
		System.out.println("在這裏實現相應的業務邏輯1");
	}
	
	@Override
	public void work2() {
		System.out.println("在這裏實現相應的業務邏輯2");
	}

}

生成一箇中間類

//中間類
public class WorkProxyInvocationHandler implements InvocationHandler{

	//持有委託類對象的引用
	private Object obj ;
	
	//傳入委託類的對象
	public WorkProxyInvocationHandler(Object obj){
        this.obj = obj;

    }
	
	/**
	 * @param proxy 代理對象
     * @param method 代理方法
     * @param args 方法的參數
	 */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("在執行業務邏輯之前記錄日誌");
		method.invoke(obj, args);
		System.out.println("在執行業務邏輯之後記錄日誌");
		return null;
	}
	
	//動態生成代理類對象
	public Object newProxyInstance() {
		return Proxy.newProxyInstance(
                //指定代理對象的類加載器
                obj.getClass().getClassLoader(),
                //代理對象需要實現的接口,可以同時指定多個接口
                obj.getClass().getInterfaces(),
                //方法調用的實際處理者,代理對象的方法調用都會轉發到這裏
                this);
	}
		
}

最後測試一下

//測試類
public class WorkTest {

	public static void main(String[] args) {
		WorkProxyInvocationHandler workProxyInvocationHandler = new WorkProxyInvocationHandler(new WorkService());
		WorkInterface workService = (WorkInterface) workProxyInvocationHandler.newProxyInstance();
		workService.work1();
		workService.work2();
	}
}

結果如下

在執行業務邏輯之前記錄日誌
在這裏實現相應的業務邏輯1
在執行業務邏輯之後記錄日誌
在執行業務邏輯之前記錄日誌
在這裏實現相應的業務邏輯2
在執行業務邏輯之後記錄日誌

原理

我們需要生成一個實現 InvocationHandler 接口的中間類。

public interface InvocationHandler {

    /**
     * @param proxy 代理類對象
     * @param method 標識具體調用的是代理類的哪個方法
     * @param args 代理類方法的參數
     */
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

然後,我們在代理類中的所有方法的調用都會變成對 invoke 方法的調用,因此我們可以在 invoke 方法中添加統一的處理邏輯。

實現了 InvocationHandler 的類被稱爲中間類,它具有一個委託類對象引用,並在 invoke 方法中調用了委託類對象的相應方法。

我們看一下這行代碼

WorkInterface workService = (WorkInterface) workProxyInvocationHandler.newProxyInstance();

在這裏我們通過 newProxyInstance 方法獲取了一個代理類實例。通過這個代理類的實例,我們可以調用代理類的方法,而對代理類的方法調用都會調用中間類的 invoke 方法,在 invoke 方法中我們會調用委託類的對應方法,同時加上自己的處理邏輯。

中間類與委託類之間構成了靜態代理關係,中間類是代理類,委託類是委託類。然後代理類與中間類也構成一個靜態代理關係,在這個關係中,中間類是委託類,代理類是代理類。

從此,我們可以發現,動態代理其實是由由兩組靜態代理關係組成的。

特點

動態生成的代理類和委託類實現同一個接口,其內部是通過反射機制實現的。

與靜態代理相比,當委託類中委託方法很多且代理邏輯相同的情況下只需要實現一個 invoke 方法即可,效率大大提高。

JDK 動態代理爲什麼只能代理接口?

代理類本身已經繼承了 Proxy 這個類,而 java 是不允許多重繼承的,但是允許實現多個接口。

6 CGLIB 動態代理

JDK 動態代理的侷限性

JDK 動態代理依賴接口實現,如果我們只有類沒有接口,就無法使用 JDK 動態代理。這時,我們需要使用另一種動態代理技術 – CGLIB 動態代理。

實現

生成一個委託類

//委託類
public class WorkService {

	public void work1() {
		System.out.println("在這裏實現相應的業務邏輯1");
	}
	
	public void work2() {
		System.out.println("在這裏實現相應的業務邏輯2");
	}

}

實現 MethodInterceptor 接口

public class WorkInterceptor implements MethodInterceptor {

	//CGLIB增強類對象,代理類對象是由Enhancer類創建的
	private Enhancer enhancer = new Enhancer();
	
	/**
	 * @param obj  被代理的對象
     * @param method 代理的方法
     * @param args 方法的參數
     * @param proxy CGLIB方法代理對象
     * 
	 */
	@Override
	public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
		System.out.println("在執行業務邏輯之前記錄日誌");
		proxy.invokeSuper(obj, args);
		System.out.println("在執行業務邏輯之後記錄日誌");
		return null;
	}

	//使用動態代理創建一個代理對象
	public Object newProxyInstance(Class<?> c) {
        //設置產生的代理對象的父類,增強類型
        enhancer.setSuperclass(c);
        //定義代理邏輯對象爲當前對象,要求當前對象實現MethodInterceptor接口
        enhancer.setCallback(this);
        //使用默認無參數的構造函數創建目標對象,這是一個前提,被代理的類要提供無參構造方法
        return enhancer.create();
    }

}

測試一下

//測試類
public class WorkTest {

	public static void main(String[] args) {
		WorkInterceptor workInterceptor = new WorkInterceptor();
		WorkService workService = (WorkService) workInterceptor.newProxyInstance(WorkService.class);
		workService.work1();
		workService.work2();
	}
}

結果:

在執行業務邏輯之前記錄日誌
在這裏實現相應的業務邏輯1
在執行業務邏輯之後記錄日誌
在執行業務邏輯之前記錄日誌
在這裏實現相應的業務邏輯2
在執行業務邏輯之後記錄日誌

原理

CGLIB 代理對指定的委託類生成一個子類並重寫其中業務方法來實現代理。代理類對象是由 Enhancer 類創建的。CGLIB 創建動態代理類的模式是:

  1. 查找目標類上的所有非 final 的 public 類型的方法
  2. 將這些方法的定義轉成字節碼
  3. 將組成的字節碼轉換成相應的代理的 Class 對象然後通過反射獲得代理類的實例對象
  4. 實現 MethodInterceptor 接口,用來處理對代理類上所有方法的請求

7 總結

靜態代理與動態代理的區別

  1. 靜態代理的代理關係在編譯期就已經確定,動態代理的代理關係是在運行期確定的
  2. 靜態代理實現比較簡單,適合於委託類較少且確定的情況,而動態代理的優勢在於其靈活性

CGLIB 動態代理與 JDK 動態代理的區別

  1. JDK 動態代理基於接口實現,CGLIB 動態代理基於類實現
  2. JDK 動態代理基於 Java 反射機制實現,CGLIB 動態代理基於 ASM 框架通過生成業務類的子類實現

CGLIB 動態代理與 JDK 動態代理的原理

  1. JDK 動態代理利用反射生成代理類字節碼,並生成對象
  2. CGLIB 動態代理採用了非常底層的字節碼技術,通過字節碼爲一個類創建子類,並在子類中採用方法攔截,攔截所有父類方法的調用,順勢織入橫切邏輯,來完成動態代理的實現。使用 CGLIB 動態代理需要實現 MethodInterceptor 接口,重寫 intercept 方法,通過 Enhancer 類的回調方法來實現

參考:Java 靜態代理、Java動態代理、CGLIB動態代理

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章