Java實現(xiàn)JDK動態(tài)代理的原理詳解
概念
代理:為控制A對象,而創(chuàng)建出新B對象,由B對象代替執(zhí)行A對象所有操作,稱之為代理。一個代理體系建立涉及到3個參與角色:真實對象(A),代理對象(B),客戶端。
其中的代理對象(B)起到中介作用,連通真實對象(A)與客戶端,如果進一步拓展,代理對象可以實現(xiàn)更加復雜邏輯,比如對真實對象進行訪問控制。
案例
需求:員工業(yè)務層接口調(diào)用save需要admin權限,調(diào)用list不需要權限,沒權限調(diào)用時拋出異常提示。
靜態(tài)代理
/**
* 代理接口
*/
public interface IEmployeeService {
void save();
void list();
}/**
* 真實對象
*/
public class EmployeeServiceImpl implements IEmployeeService {
@Override
public void save() {
System.out.println("EmployeeServiceImpl-正常的save....");
}
@Override
public void list() {
System.out.println("EmployeeServiceImpl-正常的list....");
}
}/**
* 模擬當前登錄用戶對象
*/
public class SessionHolder {
private static String currentUser;
public static String getCurrentUser(){
return currentUser;
}
public static void setCurrentUser(String currentUser){
SessionHolder.currentUser = currentUser;
}
}/**
* 代理對象
*/
public class EmployeeProxy implements IEmployeeService {
//真實對象
private EmployeeServiceImpl employeeService;
public EmployeeProxy(EmployeeServiceImpl employeeService){
this.employeeService = employeeService;
}
@Override
public void save() {
//權限判斷
if("admin".equals(SessionHolder.getCurrentUser())){
employeeService.save();
}else{
throw new RuntimeException("當前非admin用戶,不能執(zhí)行save操作");
}
}
@Override
public void list() {
employeeService.list();
}
}public class App {
public static void main(String[] args) {
System.out.println("----------------真實對象--------------------");
EmployeeServiceImpl employeeService = new EmployeeServiceImpl();
employeeService.list();
employeeService.save();
System.out.println("----------------代理對象--------------------");
SessionHolder.setCurrentUser("dafei"); //設置權限(當前登錄用戶)
EmployeeProxy employeeProxy = new EmployeeProxy(employeeService);
employeeProxy.list();
employeeProxy.save();
}
}----------------真實對象-------------------- EmployeeServiceImpl-正常的list.... EmployeeServiceImpl-正常的save.... ----------------代理對象-------------------- EmployeeServiceImpl-正常的list.... Exception in thread "main" java.lang.RuntimeException: 當前非admin用戶,不能執(zhí)行save操作 at com.langfeiyes.pattern.proxy.demo.EmployeeProxy.save(EmployeeProxy.java:20) at com.langfeiyes.pattern.proxy.demo.App.main(App.java:16)
使用真實對象EmployeeServiceImpl 直接調(diào)用時,不管是list 還是save都能直接訪問,但不符合需求上的admin權限限制。如果使用代理對象EmployeeProxy,可以完成需求實現(xiàn)。
通過直接創(chuàng)建新類新類代理對象方式完成代理邏輯,這種方式稱之為靜態(tài)代理模式。
JDK動態(tài)代理模式
Java常用的動態(tài)代理模式有JDK動態(tài)代理,也有cglib動態(tài)代理,此處重點講解JDK的動態(tài)代理
還是原來的需求,前面的IEmployeeService EmployeeServiceImpl SessionHolder 都沒變,新加一個JDK代理控制器-EmployeeInvocationHandler
/**
* jdk動態(tài)代理控制類,由它牽頭代理類獲取,代理方法的執(zhí)行
*/
public class EmployeeInvocationHandler implements InvocationHandler {
//真實對象-EmployeeServiceImpl
private Object target;
public EmployeeInvocationHandler(Object target){
this.target = target;
}
//獲取jvm在內(nèi)存中生成代理對象
public Object getProxy(){
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this);
}
//代理對象控制執(zhí)行方法
//參數(shù)1:代理對象
//參數(shù)2:真實對象的方法(使用方式得到方法對象)
//參數(shù)3:真實對象方法參數(shù)列表
//此處是代理對象對外暴露的可編輯的方法處理場所,代理對象每調(diào)用一個次方法,就會執(zhí)行一次invoke
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String name = method.getName();
if("save".equals(name) && !"admin".equals(SessionHolder.getCurrentUser())){
throw new RuntimeException("當前非admin用戶,不能執(zhí)行save操作");
}
return method.invoke(target, args);
}
}測試App類稍微改動下:
public class App {
public static void main(String[] args) {
System.out.println("----------------真實對象--------------------");
EmployeeServiceImpl employeeService = new EmployeeServiceImpl();
employeeService.list();
employeeService.save();
System.out.println("----------------代理對象--------------------");
SessionHolder.setCurrentUser("dafei");
EmployeeInvocationHandler handler =
new EmployeeInvocationHandler(employeeService);
IEmployeeService proxy = (IEmployeeService) handler.getProxy();
proxy.list();
proxy.save();
}
}上面代碼一樣可以實現(xiàn)需求,跟靜態(tài)代理區(qū)別就在于少創(chuàng)建了代理對象。此時存在疑問點,沒有創(chuàng)建代理對象,為啥可以實現(xiàn)代理類調(diào)用呢??
原理分析
先拋出結論JDK動態(tài)代理底層實現(xiàn)原理:使用接口實現(xiàn)方式,運行時,在內(nèi)存中動態(tài)構建出一個類,然后編譯,執(zhí)行。這個類是一次性的,JVM停止,代理類就消失。
參與角色 要理解JDK動態(tài)代理原理,首先得了解JDK動態(tài)代理涉及到的類

InvocationHandler:真實對象方法調(diào)用處理器,內(nèi)置invoke方法,其功能:為真實對象定制代理邏輯
EmployeeInvocationHandler:員工服務真實對象方法調(diào)用處理器,此類有3個用途: 1>設置真實對象
//真實對象-EmployeeServiceImpl
private Object target;
public EmployeeInvocationHandler(Object target){
this.target = target;
}2>定制代理方法實現(xiàn)邏輯
為真實對象save方法添加了權限校驗邏輯
//代理對象控制執(zhí)行方法
//參數(shù)1:代理對象
//參數(shù)2:真實對象的方法(使用方式得到方法對象)
//參數(shù)3:真實對象方法參數(shù)列表
//此處是代理對象對外暴露的可編輯的方法處理場所,代理對象每調(diào)用一個次方法,就會執(zhí)行一次invoke
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String name = method.getName();
if("save".equals(name) && !"admin".equals(SessionHolder.getCurrentUser())){
throw new RuntimeException("當前非admin用戶,不能執(zhí)行save操作");
}
return method.invoke(target, args);
}3>返回代理對象
方法執(zhí)行完之后,返回一個名為:$ProxyX的代理類(其中的X是序號,一般默認為0),這代理類由JDK動態(tài)構建出來。
//獲取jvm在內(nèi)存中生成代理對象
public Object getProxy(){
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this);
}Proxy:動態(tài)代理控制類,是JDK動態(tài)生成的$ProxyX類的父類,它作用如下:
1>通過調(diào)用ProxyBuilder 類builder方法構建代理對象類
private static Constructor<?> getProxyConstructor(Class<?> caller,
ClassLoader loader,
Class<?>... interfaces){
return proxyCache.sub(intf).computeIfAbsent(
loader,
(ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
);
}
2>通過newProxyInstance方法返回$ProxyX類的實例
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) {
//...
}$Proxy0:App類運行時,JDK動態(tài)構建出來的代理類,繼承至Proxy類
public class App {
public static void main(String[] args) {
//System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
System.out.println("----------------真實對象--------------------");
EmployeeServiceImpl employeeService = new EmployeeServiceImpl();
employeeService.list();
employeeService.save();
System.out.println("----------------代理對象--------------------");
SessionHolder.setCurrentUser("dafei");
EmployeeInvocationHandler handler =
new EmployeeInvocationHandler(employeeService);
IEmployeeService proxy = (IEmployeeService) handler.getProxy();
proxy.list();
proxy.save();
}
}默認情況下JVM是不保存動態(tài)創(chuàng)建代理類字節(jié)碼對象的,可以在main方法中配置代理參數(shù)讓字節(jié)碼保留
//JDK8之前
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
//JDK8之后
System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");執(zhí)行完之后,會在項目根目錄生成代理類字節(jié)碼對象。

為了方便解讀,將一些不需要的方法剔除之后
$Proxy0類
public class $Proxy0 extends Proxy implements IEmployeeService {
private static Method m4;
private static Method m3;
static {
try {
m4 = Class.forName("com.langfeiyes.proxy.demo.IEmployeeService")
.getMethod("save");
m3 = Class.forName("com.langfeiyes.proxy.demo.IEmployeeService")
.getMethod("list");
} catch (Exception e) {
e.printStackTrace();
}
}
public $Proxy0(InvocationHandler var1) throws Throwable {
super(var1);
}
public final void save() throws Throwable {
super.h.invoke(this, m4, (Object[])null);
}
public final void list() throws Throwable{
super.h.invoke(this, m3, (Object[])null);
}
}從源碼上看,$Proxy0的特點:
- 1>繼承了Proxy類,實現(xiàn)了IEmployeeService 接口
- 2>通過靜態(tài)塊的方式反射IEmployeeService接口save與list方法,得到他們的方法對象Method
- 3>調(diào)用父類構造器,需要傳入InvocationHandler 參數(shù)
- 4>重寫IEmployeeService接口的save list方法靠的是父類Proxy的h屬性.invoke方法
真相大白
下圖所有參與動態(tài)代理的類:

下圖是上圖的操作時序圖,跟著走就對了

到這,JDK動態(tài)代理就ok了。
相關文章
Mybatis中#{}和${}傳參的區(qū)別及#和$的區(qū)別小結
這篇文章主要介紹了Mybatis中#{}和${}傳參的區(qū)別及#和$的區(qū)別小結 的相關資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2016-07-07
解決Spring session(redis存儲方式)監(jiān)聽導致創(chuàng)建大量redisMessageListenerConta
這篇文章主要介紹了解決Spring session(redis存儲方式)監(jiān)聽導致創(chuàng)建大量redisMessageListenerContailner-X線程問題,需要的朋友可以參考下2018-08-08
Spring Data JPA+kkpager實現(xiàn)分頁功能實例
本篇文章主要介紹了Spring Data JPA+kkpager實現(xiàn)分頁功能實例,具有一定的參考價值,有興趣的可以了解一下2017-06-06
Mybatis之Select Count(*)的獲取返回int的值操作
這篇文章主要介紹了Mybatis之Select Count(*)的獲取返回int的值操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-11-11
SpringBoot配置Ollama實現(xiàn)本地部署DeepSeek
本文主要介紹了在本地環(huán)境中使用?Ollama?配置?DeepSeek?模型,并在?IntelliJ?IDEA?中創(chuàng)建一個?Spring?Boot?項目來調(diào)用該模型,文中通過圖文示例介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2025-03-03
在SpringBoot中配置MySQL數(shù)據(jù)庫的詳細指南
在 Spring Boot 中配置數(shù)據(jù)庫是一個相對簡單的過程,通常涉及到以下幾個步驟:添加數(shù)據(jù)庫驅(qū)動依賴、配置數(shù)據(jù)源屬性、以及可選的配置 JPA(如果使用),下面是小編給大家編寫的一個詳細的指南,以MySQL 數(shù)據(jù)庫為例,需要的朋友可以參考下2024-12-12
SpringBoot實現(xiàn)ImportBeanDefinitionRegistrar動態(tài)注入
在閱讀Spring Boot源碼時,看到Spring Boot中大量使用ImportBeanDefinitionRegistrar來實現(xiàn)Bean的動態(tài)注入,它是Spring中一個強大的擴展接口,本文就來詳細的介紹一下如何使用,感興趣的可以了解一下2024-02-02
SpringBoot整合Dubbo框架,實現(xiàn)RPC服務遠程調(diào)用
Dubbo是一款高性能、輕量級的開源Java RPC框架,它提供了三大核心能力:面向接口的遠程方法調(diào)用,智能容錯和負載均衡,以及服務自動注冊和發(fā)現(xiàn)。今天就來看下SpringBoot整合Dubbo框架的步驟2021-06-06

