一、Jvm加載物件
在說Java動態代理之前,還是要說一下Jvm加載物件的程序,這個依舊是理解動態代理的基礎性原理:

Java類即源代碼程式.java型別檔案,經過編譯器編譯之后就被轉換成位元組代碼.class型別檔案,類加載器負責讀取位元組代碼,并轉換成java.lang.Class物件,描述類在元資料空間的資料結構,類被實體化時,堆中存盤實體化的物件資訊,并且通過物件型別資料的指標找到類,
程序描述:原始碼->.java檔案->.class檔案->Class物件->實體物件
所以通過New創建物件,獨斷其背后很多實作細節,理解上述程序之后,再了解一個常用的設計模式,即代理模式,
二、代理模式
1、基本描述
代理模式給某一個(目標)物件提供一個代理物件,并由代理物件持有目標物件的參考,所謂代理,就是一個物件代表另一個物件執行相應的動作程式,而代理物件可以在客戶端和目標物件之間起到中介的作用,

代理模式在實際的生活中場景很多,例如中介、律師、代購等行業,都是簡單的代理邏輯,在這個模式下存在兩個關鍵角色:
目標物件角色:即代理物件所代表的物件,
代理物件角色:內部含有目標物件的參考,可以操作目標物件;AOP編程就是基于這個思想,
2、靜動態模式
-
靜態代理:在程式運行之前確定代理角色,并且明確代理類和目標類的關系,
-
動態代理:基于Java反射機制,在JVM運行時動態創建和生成代理物件,
三、靜態代理
基于上述靜態代理的概念,用一段代碼進行描述實作,基本邏輯如下:
- 明確目標物件即被代理的物件;
- 定義代理物件,通過構造器持有目標物件;
- 代理物件中定義前后置增強方法;
目標物件與前后置增強代碼就組成了代理物件,這樣就不用直接訪問目標物件,像極了電視劇中那句話:我是律師,我的當事人不方便和你對話,
public class Proxy01 {
public static void main(String[] args) {
TargetObj targetObj = new TargetObj() ;
ProxyObj proxyObj = new ProxyObj(targetObj) ;
proxyObj.invoke();
}
}
class TargetObj {
public void execute (){
System.out.println("目標類方法執行...");
}
}
class ProxyObj {
private TargetObj targetObj ;
/**
* 持有目標物件
*/
public ProxyObj (TargetObj targetObj){
this.targetObj = targetObj ;
}
/**
* 目標物件方法呼叫
*/
public void invoke (){
before () ;
targetObj.execute();
after () ;
}
/**
* 前后置處理
*/
public void before (){
System.out.println("代理物件前置處理...");
}
public void after (){
System.out.println("代理物件后置處理...");
}
}
靜態代理明確定義了代理物件,即有一個代理物件的.java檔案加載到JVM的程序,很顯然的一個問題,在實際的開發程序中,不可能為每個目標物件都定義一個代理類,同樣也不能讓一個代理物件去代理多個目標物件,這兩種方式的維護成本都極高,
代理模式的本質是在目標物件的方法前后置入增強操作,但是又不想修改目標類,通過前面反射機制可以知道,在運行的時候可以獲取物件的結構資訊,基于Class資訊去動態創建代理物件,這就是動態代理機制,
順便說一句:技術的底層實作邏輯不好理解是眾所周知,然而基礎知識點并不復雜,例如代理模式的基本原理,但是結合到實際的復雜應用中(AOP模式),很難活靈活現的理解到是基于反射和動態代理的方式實作的,
四、動態代理
1、場景描述
基于一個場景來描述動態代理和靜態代理的區別,即最近幾年很火的概念,海外代購:

在代購剛興起的初期,是一些常去海外出差的人,會接代購需求,即代理人固定;后來就興起海外代購平臺,海淘等一系列產品,即用戶代購需求(目標物件)由代購平臺去實作,但是具體誰來操作這個就看即時分配,這個場景與動態代理的原理類似,
2、基礎API案例
首先看兩個核心類,這里簡述下概念,看完基本程序再細聊:
-
Proxy-創建代理物件,核心引數:
- ClassLoader:(目標類)加載器;
- Interfaces:(目標類)介面陣列;
- InvocationHandler:代理呼叫機制;
-
InvocationHandler-代理類呼叫機制:
- invoke:這個上篇說的反射原理;
- method:反射類別庫中的核心API;
目標物件和介面
interface IUser {
Integer update (String name) ;
}
class UserService implements IUser {
@Override
public Integer update(String name) {
Integer userId = 99 ;
System.out.println("UserId="+userId+";updateName="+name);
return userId ;
}
}
代理物件執行機制
class UserHandler implements InvocationHandler {
private Object target ;
public UserHandler (Object target){
this.target = target ;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before()...");
Object result = method.invoke(target, args);
System.out.println("after()...");
return result;
}
}
具體組合方式
public class Proxy02 {
public static void main(String[] args) {
/*
* 生成$Proxy0的class檔案
*/
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
/*
* 目標物件資訊
*/
IUser userService = new UserService();
ClassLoader classLoader = userService.getClass().getClassLoader();
Class<?>[] interfaces = UserService.class.getInterfaces() ;
/*
* 創建代理物件
*/
InvocationHandler userHandler = new UserHandler(userService);
/*
* 代理類物件名
* proxyClassName=com.java.proxy.$Proxy0
*/
String proxyClassName = Proxy.newProxyInstance(classLoader,interfaces,userHandler).getClass().getName();
System.out.println("proxyClassName="+proxyClassName);
/*
* 具體業務實作模擬
*/
IUser proxyUser1 = (IUser) Proxy.newProxyInstance(classLoader,interfaces,userHandler);
IUser proxyUser2 = (IUser) Proxy.newProxyInstance(classLoader,interfaces,userHandler);
proxyUser1.update("cicada") ;
proxyUser2.update("smile") ;
}
}
這里之所以要生成代理類的結構資訊,因為從JVM加載的程序看不到相關內容,關鍵資訊再次被獨斷:
javap -v Proxy02.class

查看代理類名稱
/*
* proxyClassName=com.java.proxy.$Proxy0
*/
String proxyClassName = Proxy.newProxyInstance(classLoader,interfaces,userHandler).getClass().getName();
System.out.println("proxyClassName="+proxyClassName);
下意識輸出代理物件名稱,這里即對應JVM機制,找到Class物件名,然后分析結構,這樣就明白動態代理具體的執行原理了,
生成代理類.class檔案
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
通過上面JVM加載物件的機制可知,描述代理類的Class物件一定存在,只是在運行時并沒有生成顯式的.class檔案,通過上面生成代理類.class的語法,會在專案目錄的/com/java/proxy路徑下創建檔案,
順便說一句:作為一只程式員,復雜總是和我們環環相繞,說好的簡單點呢?
3、代理類結構
繼承與實作
class $Proxy0 extends Proxy implements IUser {}
從代理類的功能來思考,可以想到需要繼承Proxy與實作IUser介面,還有就是持有呼叫機制的具體實作類,用來做業務增強,
構造方法
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
通過構造方法,持有UserHandler具體的執行機制物件,
介面實作
final class $Proxy0 extends Proxy implements IUser {
private static Method m3;
public final Integer update(String var1) throws {
try {
return (Integer)super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
}
目標類的基本需求update()方法,通過代理類進行承接,并基于UserHandler實作具體的增強業務處理,
基礎方法
final class $Proxy0 extends Proxy implements IUser {
private static Method m0;
private static Method m1;
private static Method m2;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("com.java.proxy.IUser").getMethod("update", Class.forName("java.lang.String"));
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
}
基于Object類,定義Java中幾個常用方法equals()判斷,toString()方法,hashCode()值,這個在分析Map原始碼的時候有說過為什么這幾個方法通常都是一起出現,
4、JDK原始碼
上面是案例執行的程序和原理,還有一個關鍵點要明白,即JDK原始碼的邏輯:
IUser proxyUser = (IUser) Proxy.newProxyInstance(classLoader,interfaces,userHandler);
Proxy提供的靜態方法newProxyInstance(),通過各個引數的傳入,構建一個新的代理Class物件,即$Proxy0類的結構資訊,這里再回首看下三個核心引數:
-
ClassLoader:基于JVM運行程序,所以需要獲取目標類UserService的類加載器;
-
Interfaces:目標類UserService實作的介面,從面向物件來考慮,介面與實作分離,代理類通過實作IUser介面,模擬目標類的需求;
-
InvocationHandler:代理類提供的功能封裝即UserHandler,可以在目標方法呼叫前后做增強處理;
最后總結一下動態代理的實作的核心技術點:Jvm加載原理、反射機制、面向物件思想;每次閱讀JDK的原始碼都會驚嘆設計者的鬼斧神工,滴水穿石堅持才會有識訓,
JVM類加載機制 | 代理模式 | AOP切面編程 | 自定義日志記錄 | Map原始碼分析
五、源代碼地址
GitHub·地址
https://github.com/cicadasmile/java-base-parent
GitEE·地址
https://gitee.com/cicadasmile/java-base-parent

閱讀標簽
【Java基礎】【設計模式】【結構與演算法】【Linux系統】【資料庫】
【分布式架構】【微服務】【大資料組件】【SpringBoot進階】【Spring&Boot基礎】
【資料分析】【技術導圖】【 職場】
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/288595.html
標籤:Java
