概念
代理:為控制A對象,而建立出新B對象,由B對象取代執行A對象所有操作,稱為代理。一個代理體系建立涉及3個參與角色:真實物件(A),代理物件(B),客戶端。
其中的代理物件(B)起到中介作用,連通真實物件(A)與客戶端,如果進一步拓展,代理物件可以實現更加複雜邏輯,例如對真實物件進行存取控制。
案例
需求:員工業務層介面呼叫save需要admin權限,呼叫list不需要權限,沒權限呼叫時拋出例外提示。
靜態代理
/** * 代理接口 */ 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用户,不能执行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用户,不能执行save操作 at com.langfeiyes.pattern.proxy.demo.EmployeeProxy.save(EmployeeProxy.java:20) at com.langfeiyes.pattern.proxy.demo.App.main(App.java:16)
使用真實物件EmployeeServiceImpl 直接呼叫時,不管是list 還是save都能直接訪問,但不符合需求上的admin權限限制。如果使用代理對象EmployeeProxy,可以完成需求實作。
透過直接建立新類別新類別代理物件方式完成代理邏輯,這種方式稱之為靜態代理模式。
JDK動態代理模式
Java常用的動態代理模式有JDK動態代理,也有cglib動態代理,此處重點講解JDK的動態代理
還是原來的需求,前面的IEmployeeService EmployeeServiceImpl SessionHolder 都沒變,新加一個JDK代理控制器-EmployeeInvocationHandler
/** * jdk动态代理控制类,由它牵头代理类获取,代理方法的执行 */ public class EmployeeInvocationHandler implements InvocationHandler { //真实对象-EmployeeServiceImpl private Object target; public EmployeeInvocationHandler(Object target){ this.target = target; } //获取jvm在内存中生成代理对象 public Object getProxy(){ return Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } //代理对象控制执行方法 //参数1:代理对象 //参数2:真实对象的方法(使用方式得到方法对象) //参数3:真实对象方法参数列表 //此处是代理对象对外暴露的可编辑的方法处理场所,代理对象每调用一个次方法,就会执行一次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用户,不能执行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(); } }
上面程式碼一樣可以實現需求,跟靜態代理區別就在於少創建了代理物件。此時存在疑問點,沒有創建代理對象,為啥可以實現代理類別呼叫呢? ?
原理分析
先拋出結論JDK動態代理底層實作原理:使用介面實作方式,運行時,在記憶體中動態建構出一個類,然後編譯,執行。這個類別是一次性的,JVM停止,代理類別就消失。
參與角色 要理解JDK動態代理原理,首先得了解JDK動態代理所涉及的類別
InvocationHandler :真實物件方法呼叫處理器,內建invoke方法,其功能:為真實物件自訂代理邏輯
EmployeeInvocationHandler:員工服務真實物件方法呼叫處理器,此類有3個用途: 1>設定真實物件
//真实对象-EmployeeServiceImpl private Object target; public EmployeeInvocationHandler(Object target){ this.target = target; }
2>自訂代理方法實作邏輯
為真實物件save方法新增了權限校驗邏輯
//代理对象控制执行方法 //参数1:代理对象 //参数2:真实对象的方法(使用方式得到方法对象) //参数3:真实对象方法参数列表 //此处是代理对象对外暴露的可编辑的方法处理场所,代理对象每调用一个次方法,就会执行一次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用户,不能执行save操作"); } return method.invoke(target, args); }
#3>傳回代理物件
方法執行完之後,傳回一個名為:$ProxyX的代理類別(其中的X是序號,一般預設為0),而這個代理類別由JDK動態建構出來。
//获取jvm在内存中生成代理对象 public Object getProxy(){ return Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); }
Proxy:動態代理控制類,是JDK動態產生的$ProxyX類的父類,它作用如下:
1>透過呼叫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動態建構出來的代理類,繼承至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是不保存動態建立代理類別字節碼物件的,可以在main方法中配置代理參數讓字節碼保留
//JDK8之前 System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); //JDK8之后 System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
執行完之後,會在專案根目錄產生代理類別字節碼物件。
為了方便解讀,將一些不需要的方法剔除之後
#$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類,實作了IEmployeeService 介面
2>透過靜態區塊的方式反射IEmployeeService介面save與list方法,得到他們的方法物件Method
3>呼叫父類別建構器,需要傳入InvocationHandler 參數
4>重寫IEmployeeService介面的save list方法靠的是父類別Proxy的h屬性.invoke方法
真相大白
下圖所有參與動態代理的類別:
下圖是上圖的操作時序圖,跟著走就對了
以上是Java如何實作JDK動態代理的詳細內容。更多資訊請關注PHP中文網其他相關文章!

javaispopularforcross-platformdesktopapplicationsduetoits“ writeonce,runany where”哲學。 1)itusesbytiesebyTecodeThatrunsonAnyJvm-備用Platform.2)librarieslikeslikeslikeswingingandjavafxhelpcreatenative-lookingenative-lookinguisis.3)

在Java中編寫平台特定代碼的原因包括訪問特定操作系統功能、與特定硬件交互和優化性能。 1)使用JNA或JNI訪問Windows註冊表;2)通過JNI與Linux特定硬件驅動程序交互;3)通過JNI使用Metal優化macOS上的遊戲性能。儘管如此,編寫平台特定代碼會影響代碼的可移植性、增加複雜性、可能帶來性能開銷和安全風險。

Java將通過雲原生應用、多平台部署和跨語言互操作進一步提昇平台獨立性。 1)雲原生應用將使用GraalVM和Quarkus提升啟動速度。 2)Java將擴展到嵌入式設備、移動設備和量子計算機。 3)通過GraalVM,Java將與Python、JavaScript等語言無縫集成,增強跨語言互操作性。

Java的強類型系統通過類型安全、統一的類型轉換和多態性確保了平台獨立性。 1)類型安全在編譯時進行類型檢查,避免運行時錯誤;2)統一的類型轉換規則在所有平台上一致;3)多態性和接口機制使代碼在不同平台上行為一致。

JNI會破壞Java的平台獨立性。 1)JNI需要特定平台的本地庫,2)本地代碼需在目標平台編譯和鏈接,3)不同版本的操作系統或JVM可能需要不同的本地庫版本,4)本地代碼可能引入安全漏洞或導致程序崩潰。

新興技術對Java的平台獨立性既有威脅也有增強。 1)雲計算和容器化技術如Docker增強了Java的平台獨立性,但需要優化以適應不同雲環境。 2)WebAssembly通過GraalVM編譯Java代碼,擴展了其平台獨立性,但需與其他語言競爭性能。

不同JVM實現都能提供平台獨立性,但表現略有不同。 1.OracleHotSpot和OpenJDKJVM在平台獨立性上表現相似,但OpenJDK可能需額外配置。 2.IBMJ9JVM在特定操作系統上表現優化。 3.GraalVM支持多語言,需額外配置。 4.AzulZingJVM需特定平台調整。

平台獨立性通過在多種操作系統上運行同一套代碼,降低開發成本和縮短開發時間。具體表現為:1.減少開發時間,只需維護一套代碼;2.降低維護成本,統一測試流程;3.快速迭代和團隊協作,簡化部署過程。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

Dreamweaver Mac版
視覺化網頁開發工具

VSCode Windows 64位元 下載
微軟推出的免費、功能強大的一款IDE編輯器

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

Safe Exam Browser
Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。

Dreamweaver CS6
視覺化網頁開發工具