Java 動態代理原理圖解 (附:2種實現方式詳細對比)

2022-10-31 12:02:17

 Java動態代理原理圖解(附2種實現方式詳細對比)-mikechen的網際網路架構

動態代理在 Java 中有著廣泛的應用,例如:Spring AOP 面向切面程式設計,Hibernate 資料查詢、以及 RPC Dubbo 遠端呼叫等,都有非常多的實際應用@mikechen

目錄

Java 動態代理原理

按照代理的建立時期,代理類可以分為兩種:

Java動態代理原理圖解(附2種實現方式詳細對比)-mikechen的網際網路架構

  • 靜態代理:由程式設計師建立或特定工具自動生成原始碼,再對其編譯,在程式執行前,代理類的 .class 檔案就已經存在了。
  • 動態代理:在程式執行時,可以運用反射機制動態建立代理類的 .class 檔案。

動態代理類與靜態代理類最主要的不同點是:代理類的位元組碼不是在程式執行前生成的,而是在程式執行時再虛擬機器器中程式自動建立的。

動態代理的實現方式很多。例如:JDK 自身提供的動態代理,就利用了上面提到的反射機制。除了反射,動態代理還可以通過 CGLib 來實現,而 CGLib 是基於 ASM(一個 Java 位元組碼操作框架)而非反射實現的。

簡單來說,動態代理是一種行為方式,而 反射ASM 只是它的一種實現手段而已。

本文我主要詳解 Java 動態代理的 2 種主流現方式:JDK 原生動態代理CGLib

Java動態代理原理圖解(附2種實現方式詳細對比)-mikechen的網際網路架構

JDK 原生動態代理

JDK Proxy 動態代理的實現無需參照第三方類,只需要實現 InvocationHandler 介面,重寫 invoke() 方法即可,整個實現程式碼如下所示:、

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
 
/**
* JDK Proxy 相關範例
*/
public class ProxyExample {
static interface Car {
void running();
}
 
static class Bus implements Car {
@Override
public void running() {
System.out.println("The bus is running.");
}
}
 
static class Taxi implements Car {
@Override
public void running() {
System.out.println("The taxi is running.");
}
}
 
/**
* JDK Proxy
*/
static class JDKProxy implements InvocationHandler {
private Object target; // 代理物件
 
// 獲取到代理物件
public Object getInstance(Object target) {
this.target = target;
// 取得代理物件
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
}
 
/**
* 執行代理方法
* @param proxy 代理物件
* @param method 代理方法
* @param args 方法的引數
* @return
* @throws InvocationTargetException
* @throws IllegalAccessException
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws InvocationTargetException, IllegalAccessException {
System.out.println("動態代理之前的業務處理.");
Object result = method.invoke(target, args); // 執行呼叫方法(此方法執行前後,可以進行相關業務處理)
return result;
}
}
 
public static void main(String[] args) {
// 執行 JDK Proxy
JDKProxy jdkProxy = new JDKProxy();
Car carInstance = (Car) jdkProxy.getInstance(new Taxi());
carInstance.running();

以上程式的執行結果是:

動態代理之前的業務處理。

 The taxi is running.

可以看出, JDK Proxy 實現動態代理的核心是實現 Invocation 介面,我們檢視 Invocation 的原始碼,會發現裡面其實只有一個 invoke() 方法,原始碼如下:

public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}

這是因為在動態代理中有一個重要的角色,也就是代理器,它用於統一管理被代理的物件,顯然 InvocationHandler 就是這個代理器。而 invoke() 方法,則是觸發代理的執行方法,我們通過實現 Invocation 介面來擁有動態代理的能力。

CGLib 動態代理實現

CGLIB (Code Generation Library) 是一個基於 ASM 的位元組碼生成庫,它允許我們在執行時對位元組碼進行修改、和動態生成 CGLIB 通過繼承方式實現代理。

Java動態代理原理圖解(附2種實現方式詳細對比)-mikechen的網際網路架構

在使用 CGLib 之前,我們要先在專案中引入 CGLib 框架,在 pom.xml 中新增如下設定

<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>

CGLib 的實現程式碼:

package com.mikechen.proxydemo;
 
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
 
import java.lang.reflect.Method;
 
public class CGLibExample {
 
static class Car {
public void running() {
System.out.println("The car is running.");
}
}
 
/**
* CGLib 代理類
*/
static class CGLibProxy implements MethodInterceptor {
private Object target; // 代理物件
 
public Object getInstance(Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();
// 設定父類別為範例類
enhancer.setSuperclass(this.target.getClass());
// 回撥方法
enhancer.setCallback(this);
// 建立代理物件
return enhancer.create();
}
 
@Override
public Object intercept(Object o, Method method,
Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("方法呼叫前業務處理.");
Object result = methodProxy.invokeSuper(o, objects); // 執行方法呼叫
return result;
}
}
 
// 執行 CGLib 的方法呼叫
public static void main(String[] args) {
// 建立 CGLib 代理類
CGLibProxy proxy = new CGLibProxy();
// 初始化代理物件
Car car = (Car) proxy.getInstance(new Car());
// 執行方法
car.running();

以上程式的執行結果是:

方法呼叫前業務處理。

The car is running.

可以看出:

CGLib 和 JDK Proxy 的實現程式碼比較類似,都是通過實現代理器的介面,再呼叫某一個方法完成動態代理的。

唯一不同的是,CGLib 在初始化被代理類時,是通過 Enhancer 物件把代理物件設定為被代理類的子類,來實現動態代理的。

因此,被代理類不能被關鍵字 final 修飾,如果被 final 修飾,再使用 Enhancer 設定父類別時會報錯,動態代理的構建會失敗。

 

JDK 動態代理與 CGLib 的區別

1.  JDK 動態代理具體實現原理

  • 通過實現 InvocationHandler 介面,建立自己的呼叫處理器;
  • 通過為 Proxy 類指定 ClassLoader 物件和一組 interface ,來建立動態代理;
  • 通過反射機制獲取動態代理類別建構函式,其唯一引數型別就是呼叫處理器介面型別;
  • 通過建構函式建立動態代理類範例,構造時呼叫處理器物件作為引數參入。

2.  CGLib 動態代理

CGLib 是一個強大、高效能的 Code 生產類庫,可以實現執行期動態擴充套件 java 類,Spring 在執行期間通過 CGlib 繼承要被動態代理的類,重寫父類別的方法,實現 AOP 面向切面程式設計。

3.  兩者對比

  • JDK 動態代理是面向介面的。
  • CGLib 動態代理是通過位元組碼底層繼承要代理類來實現(如果被代理類被 final 關鍵字所修飾,會失敗)。

4.  效能對比

  • CGLib 所建立的動態代理物件,在實際執行時候的效能要比 JDK 動態代理高不少,有研究表明,大概要高出10倍;
  • CGLib 在建立物件的時候所花費的時間,比 JDK 動態代理要多很多,有研究表明,大概要高出8倍。

因此,對於 singleton 的代理物件或者具有範例池的代理,因為無需頻繁的建立代理物件,更適合採用 CGLib 動態代理,反之,則比較適用 JDK 動態代理。

 

以上,是關於 Java 動態代理原理、以及動態代理2 種實現方式的解析。

希望有所幫助,謝謝【關注+點贊+轉發】支援。

作者簡介

陳睿 | mikechen , 10年+大廠架構經驗,「mikechen 的網際網路架構」系列文章作者,專注於網際網路架構技術。