1. 程式人生 > >代理模式詳解:靜態代理、JDK動態代理與Cglib動態代理

代理模式詳解:靜態代理、JDK動態代理與Cglib動態代理

1. 代理模式簡介分類 - 概念 ​ 代理,是為了在不修改目標物件的基礎上,增強目標方法的業務邏輯。 ​ 客戶類需要執行的是目標物件的目標方法,但是真正執行的是代理物件的代理方法,客戶類對目標物件的訪問是通過代理物件來實現的。當然,代理類與目標類需要實現同一個介面。 - 舉例 生活中遇到了官司,我們平常老百姓對法律的瞭解不全面,所以一般都會請律師處理。 目標物件:法庭上我們一般稱為當事人即目標物件 代理類:律師稱為代理律師即代理類 共同介面:都為了一個共同的目標努力贏得官司即為共同介面 目標方法:我們所做的提供證據各種努力成為目標方法 代理方法:再此過程中我們可能做不全面,律師對證據材料等進行整理收集,結合法律法規進行辯證等等,此過程稱為代理方法 - 代理分類 代理模式一般分為靜態代理與動態代理,動態代理又分為JDK動態代理與CGLIB動態代理 2. 靜態代理 - 概念 靜態代理是指,代理類在程式執行前就已經定義好,其與目標類等關係在程式執行前就已經確立。 靜態代理類似於富翁於私人律師的代理關係,並不是在發生官司之後才去請律師,而是在此之前已經確立好的代理關係。 - 實現與解析 a、定義業務介面 ```java package com.rangers.proxy.staticProxy; /** * @Author Rangers * @Description * @Date 2021-03-09 **/ public interface IAccountService { // 轉賬業務介面 void transfer(); } ``` b、定義目標類與目標方法 ```java package com.rangers.proxy.staticProxy; /** * @Author Rangers * @Description * @Date 2021-03-09 **/ public class AccountServiceImpl implements IAccountService { // 轉賬業務實現即目標方法 @Override public void transfer() { System.out.println("進行轉賬操作"); } } ``` c、定義代理類AccountServiceImplProxy,實現IAccountService介面。在有參構造方法中傳入目標物件,將目標物件引入代理類,以便代理類呼叫目標方法,進行增強 ```java package com.rangers.proxy.staticProxy; /** * @Author Rangers * @Description * @Date 2021-03-09 **/ public class AccountServiceImplProxy implements IAccountService { // 宣告目標介面物件 private IAccountService target; public AccountServiceImplProxy() { } // 業務介面物件作為構造器,用於接收目標物件 public AccountServiceImplProxy(IAccountService target) { this.target = target; } @Override public void transfer() { // 此處對目標方法進行增強 System.out.println("對轉賬人身份校驗。。"); target.transfer(); System.out.println("進行日誌記錄。。"); } } ``` d、編寫測試類TransferServiceTest ```java package com.rangers.proxy.staticProxy; /** * @Author Rangers * @Description * @Date 2021-03-09 **/ public class TransferServiceTest { public static void main(String[] args) { // 建立目標物件 IAccountService target = new AccountServiceImpl(); // 建立代理物件,傳入目標物件進行初始化 IAccountService proxy = new AccountServiceImplProxy(target); // 執行代理物件的方法 proxy.transfer(); } } ``` 3. 動態代理 ​ 動態代理,程式在整個執行過程中根本就不存在目標類的代理類,目標物件的代理物件只是由代理工具在程式執行時由JVM根據反射等機制動態生成。代理物件與目標物件的代理關係在程式執行時才確立。 ​ 動態代理類似於普通人在有官司之後,再聘請律師的,即代理關係是在官司發生後確立的。 ​ 動態代理的實現方式有兩種:JDK的動態代理、CGLIB動態代理 a、JDK動態代理 - 概念 ​ JDK動態代理是通過JDK提供的 java.lang.reflect.Proxy類實現動態大力,使用其靜態方法newProxyInstance(),對目標物件、業務介面及業務增強邏輯,自動生成一個動態代理物件。 ```java public static newProxyInstance(ClassLoader classLoader,Class interfaces,InvocationHandler handler) classLoader:傳入目標類的類載入器,通過目標物件的反射獲取 interfaces:目標物件實現的介面陣列,通過目標物件的反射獲取 handler:業務增強邏輯,需要具體實現 ``` ​ InvocationHandler是個介面,實現InvocationHandler介面的類用於增加目標類的業務邏輯。需要實現invoke()方法,具體的增強邏輯就是在此方法中進行實現,程式呼叫住業務邏輯時會自動呼叫invoke()方法 ```java public Object invoke(Object proxy,Method method,Object[] args) proxy:生成的代理物件 method:目標方法 args:目標方法的引數 ``` ​ Method類物件,invoke()方法進行執行目標物件的目標方法 ```java public Object invoke(Object obj,Object args) method.invoke(Object target,Object...args)執行目標方法 target:目標物件 args:目標方法的執行引數 ``` - 實現與解析 1. 定義業務介面與實現類 ```java package com.rangers.proxy.jdkProxy; /** * @Author Rangers * @Description * @Date 2021-03-09 **/ public interface IAccountService { // 轉賬業務介面 void transfer(); } ``` ```java package com.rangers.proxy.jdkProxy; /** * @Author Rangers * @Description * @Date 2021-03-09 **/ public class AccountServiceImpl implements IAccountService { // 轉賬業務實現即目標方法 @Override public void transfer() { System.out.println("進行轉賬操作"); } } ``` 2. 定義JdkProxy類實現InvocationHandler介面,實現invoke()方法,並對業務邏輯進行增強 ```java package com.rangers.proxy.jdkProxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; /** * @Author Rangers * @Description * @Date 2021-03-09 **/ public class JdkProxy implements InvocationHandler { private Object target; public JdkProxy() { } public JdkProxy(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 此處對目標方法進行增強 System.out.println("對轉賬人身份校驗。。"); Object result = method.invoke(target, args); System.out.println("進行日誌記錄。。"); return result; } } ``` 3. 新建測試類JDKProxyTest ```java package com.rangers.proxy.jdkProxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * @Author Rangers * @Description * @Date 2021-03-09 **/ public class JDKProxyTest { public static void main(String[] args) { // 建立目標物件 IAccountService target = new AccountServiceImpl(); // 建立代理物件,傳入目標物件進行初始化 IAccountService proxyService = (IAccountService) Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new JdkProxy(target) ); // 亦可使用匿名類進行實現 /*(IAccountService) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 此處對目標方法進行增強 System.out.println("對轉賬人身份校驗。。"); Object result = method.invoke(target, args); System.out.println("進行日誌記錄。。"); return result; } });*/ // 此處執行的業務方法就是代理物件的增強過的邏輯 proxyService.transfer(); } } ``` 注:使用JDK動態代理時需要目標類目標方法必須在實現的介面中,否則不能使用此方式進行動態打擊。對於無介面的類需要實現動態代理,就要使用CGLIB方式來進行實現 b、CGLIB動態代理 - 概念 ​ CGLIB是一個開源的第三方程式碼生成類庫,對於無介面的類,要為其建立動態代理,就要使用CGLIB進行實現。CGLIB代理的生成原理是生成目標類的子類,子類是增強過的,就是目標類的代理類。所以,使用CGLIB生成動態代理,要求目標類必須能夠被繼承,即不能是final修飾的類。 ​ CGLIB包的底層是通過使用一個小兒快的位元組碼處理框架ASM(java位元組碼操控框架),來轉換位元組碼並生成新的類,通過對位元組碼進行增強來生成代理類。 ​ 我們靜態代理理解為私人律師,JDK動態代理成為代理律師,CGLIB動態代理可以理解為老父親的兒子。老父親是被需要增強對目標類,兒子則是用於增強父親對代理類,事先不需要約定。父親需要兒子增強什麼,兒子就增強什麼,即他們之間的關係不要介面來進行約束。 - 注意要點 使用CGLIB動態代理時,生成代理類的類需要實現MethodInterceptor介面及intercept()方法 ```java public Object intercept(Object proxy,Method method,Objectp[] args,MethodProxy methodProxy) proxy:代理物件 method:代理物件的方法,即增強後的方法 args:方法引數 methodProxy:代理方法的物件 ``` 建立代理物件時使用Enhancer類 ```java // 建立增強器 Enhancer enhancer = new Enhancer(); // 初始化增強器:將目標類指定為父類 enhancer.setSuperclass(target.class); // 初始化增強器:設定回撥至本類中的intercept()方法 enhancer.setCallback(this); // 使用增強器建立代理物件進行返回 enhancer.create(); ``` - 實現與解析 1. 引入CGLIB依賴 ```xml cglib
cglib-full 2.0.2
``` 2. 建立目標類AccountService ```java package com.rangers.proxy.cglibProxy; /** * @Author Rangers * @Description * @Date 2021-03-09 **/ public class AccountService { // 轉賬業務 即目標方法 public void transfer() { System.out.println("進行轉賬操作"); } // 查詢餘額 即目標方法 public void getBalance() { System.out.println("查詢餘額操作"); } } ``` 3. 建立代理類AccountServiceCglibProxy實現MethodInterceptor介面,完善intercept()方法進行增強,建立生成代理物件createProxy()方法 ```java package com.rangers.proxy.cglibProxy; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; /** * @Author Rangers * @Description * @Date 2021-03-09 **/ public class AccountServiceCglibProxy implements MethodInterceptor { // 宣告目標類的成員變數,並建立以目標類為引數的構造器,用於接收目標物件 private AccountService target; public AccountServiceCglibProxy(AccountService accountService) { this.target = accountService; } @Override public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { // 此處對目標方法進行增強 Object result = new Object(); if ("transfer".equals(method.getName())){ System.out.println("對轉賬人身份校驗。。"); result = method.invoke(target, args); System.out.println("進行日誌記錄。。"); }else{ // 直接執行目標物件的目標方法 result = methodProxy.invokeSuper(target,args); } return result; } // 建立代理物件 public AccountService createProxy(){ // 建立增強器 Enhancer enhancer = new Enhancer(); // 初始化增強器:將目標類指定為父類 enhancer.setSuperclass(AccountService.class); // 初始化增強器:設定回撥至本類中的intercept()方法 enhancer.setCallback(this); // 使用增強器建立代理物件 return (AccountService) enhancer.create(); } } ``` 4. 建立測試類CglibProxyTest ```java package com.rangers.proxy.cglibProxy; /** * @Author Rangers * @Description * @Date 2021-03-09 **/ public class CglibProxyTest { public static void main(String[] args) { // 目標物件 AccountService target = new AccountService(); // 建立代理物件,傳入目標物件進行初始化 AccountService accountService = new AccountServiceCglibProxy(target).createProxy(); accountService.transfer(); accountService.getBalance(); } }