動態代理和責任鏈設計模式適用范圍廣,在Spring和MyBatis有著重要的應用,比如SpringAOP、Mybatis的插件技術,想要搞懂當中的技術原理必須掌握上面兩個設計模式,
代理模式可以理解為您要操作一個物件,但是要經過這個物件的“代理”物件去操作,就好似你在一家軟體公司做開發,客戶發現程式有Bug,會找到商務對接人說,最后商務的同事再找到你去解決問題,“商務”是代理物件,“你”是真實物件,代理模式分為靜態代理和動態代理,其作用是可以在真實物件訪問之前或者之后加入自定義的邏輯,又或者根據自定義規則來控制是否使用真實物件,
靜態代理是真實物件與代理物件(Proxy)實作相同的介面,代理物件包含真實物件的參考,客戶端通過代理物件去訪問真實物件,代理物件可以在真實物件訪問之前或之后執行其他操作,假設要你設計一個對外開放的商品庫存資訊查詢介面,并且要限制呼叫方在一天時間內的呼叫次數,用代理模式代理庫存介面,首先在庫存查詢前檢查用戶是否有權限訪問,然后在查詢后要記錄用戶查詢日志,以便根據查詢次數,判斷呼叫上限,

動態代理是在程式運行時,通過反射機制創建代理物件,實作動態代理方法,動態代理相比于靜態代理的好處,是代理物件不用實作真實物件的介面,這樣能代理更多方法,因為靜態代理是一個介面對應一個型別,如果介面添加新方法,則所有代理類都要實作此方法,所以動態代理脫離了介面實作,一個代理類就能代理更多方法,一些公共代碼邏輯也就可以在多個代理方法里復用,例如:資料庫事務開啟、提交、回滾,這些公共代碼都分別是在真實方法呼叫的前后出現,而動態代理會幫我們把功能代碼織入到方法里,在Java中最常用動態代理有兩種,一種是JDK動態代理,這是JDK自帶的功能;另一種是CGLIB,由第三方提供的一個技術,Spring是用了JDK代理和CGLIB兩種,兩者的區別是,JDK代理要提供介面作為于代理引數才能使用,而CGLIB不需要提供介面,只要一個非抽象類就能代理,適用于一些不能提供介面的場景,
1. JDK動態代理
(1)定義真實物件介面,因為JDK動態代理要借助介面才能代理物件
public interface SayService { void sayHello(); } public class SayServiceImpl implements SayService { @override public void sayHello() { System.out.println("Hello Friend"); } }
(2)創建代理類,實作java.lang.reflect.InvocationHandler介面
public class JdkProxyExample implements InvocationHandler { // 真實物件 private Object target = null; public Object bind(Object target) { this.target = target; return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("代理前"); System.out.println("呼叫真實物件"); Object r = method.invoke(target, args); System.out.println("代理后"); return r; } }
Proxy.newProxyInstance方法的作用是創建代理物件,并建立代理物件與真實物件的關系,包含3個引數
-
第1個是類加載器,用于把Java位元組碼轉換成Class類實體物件,這里用了target物件所屬的類加載器
-
第2個是生成的動態代理物件需要掛載到哪些介面下,上面是取了真實物件的介面
-
第3個是實作InvocationHandler介面的代理類,this表示當前物件
InvocationHandler介面的invoke方法實作代理邏輯,invoke其中引數含義如下
-
proxy:代理物件,就是bind方法生成的物件
-
method:當前呼叫方法,method.invoke(target, args)呼叫真實物件方法
-
args:當前呼叫方法引數
(3)測驗JDK動態代理
public void testJdk() { JdkProxyExample jdkProxy = new JdkProxyExample(); SayService proxy = (SayService) jdkProxy.bind(new SayServiceImpl()); proxy.sayHello(); } /* 代理前 呼叫真實物件 Hello Friend 代理后 */
2. CGLIB動態代理
JDK動態代理要提供介面才能代理,但在一些不能提供介面的場景下,CGLIB動態代理技術不需要提供介面,只要一個非抽象類就能動態代理,新建類實作MethodInterceptor介面(spring框架的cglib包),使用Enhacer創建代理物件,代碼實作如下:
import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class CglibProxyExample implements MethodInterceptor { public Object bind(Class classz) { Enhancer enhancer = new Enhancer(); enhancer.setCallback(this); enhancer.setSuperclass(classz); return enhancer.create(); } @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println("呼叫真實物件前"); Object result = methodProxy.invokeSuper(proxy, args); System.out.println("呼叫真實物件后"); return result; } } @Test public void testCglib() { CglibProxyExample cglibProxy = new CglibProxyExample(); TestService proxy = (TestService)cglibProxy.bind(TestService.class); proxy.sayHello(); } /* 呼叫真實物件前 Hi hello 呼叫真實物件后 */
3. 攔截器
由于動態代理一般不好理解,通常會設計一個攔截器介面提供給開發者使用,這樣只需要知道攔截器介面方法、含義和作用即可,無須知道動態代理是怎么實作,攔截器介面設計如下:
public interface Interceptor { boolean before(Object target); Object around(Object target, Method method, Object[] args); void after(Object target); void afterThrowing(Object target); void afterReturning(Object target); }
假定攔截器使用規則是:在呼叫真實方法前,先訪問before方法,如果回傳true,執行真實物件方法,否則執行around方法代替真實方法的呼叫,after方法在真實方法或around呼叫后執行,如果真實方法或around呼叫例外,執行afterThrowing方法,否則執行afterReturning方法,
定義了以上規則,開發者只要知道攔截器怎樣使用,不需要知道動態代理怎么實作,
下面代碼演示如何使用上面定義的攔截器
public class SayServiceInterceptor implements Interceptor { @Override public boolean before(Object target) { System.out.println("代理前執行before方法"); // 不執行真實物件的方法 return false; } @Override public Object around(Object target, Method method, Object[] args) { System.out.println("真實物件方法被替換,執行around"); return null; } @Override public void after(Object target) { System.out.println("代理后執行after方法"); } @Override public void afterThrowing(Object target) { System.out.println("真實物件方法呼叫例外,執行afterThrowing方法"); } @Override public void afterReturning(Object target) { System.out.println("最后執行afterReturning方法"); } } public void testJdk() { SayService say = new SayServiceImpl(); SayServiceInterceptor interceptor = new SayServiceInterceptor(); SayService proxy = (SayService) JdkProxyExample.bind(say, interceptor); proxy.sayHello(); } /** 代理前執行before方法 真實物件方法被替換,執行around 代理后執行after方法 最后執行afterReturning方法 */
上面規則用動態代理實作代碼如下:
public class JdkProxyExample implements InvocationHandler { // 真實物件 private Object target = null; // 攔截器 private Interceptor interceptor; public JdkProxyExample(Object target, Interceptor interceptor) { this.target = target; this.interceptor = interceptor; } public static Object bind(Object target, Interceptor interceptor) { return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new JdkProxyExample(target, interceptor)); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if(interceptor == null) { return method.invoke(target, args); } Object result = null; boolean exceptionFlag = false; try { if (interceptor.before(target)) { result = method.invoke(target, args); }else { result = interceptor.around(target, method, args); } }catch (Exception e) { exceptionFlag = true; } finally { interceptor.after(target); } if(exceptionFlag) { interceptor.afterThrowing(target); }else { interceptor.afterReturning(target); } return result; } }
設計攔截器能簡化了動態代理的使用方法,使程式更簡單,在實際使用場景中攔截器實作后,要在開發者的程式進行xml配置,或者在實作類添加注解標識等方式,來查找到攔截器實作類,然后反射創建并加載到程式里,SpringAOP也是通過@Aspect注解創建切面(攔截器),@execution定義連接點攔截方法,
4. 責任鏈模式
設計攔截器去代替動態代理,然后將攔截器的介面提供給開發者用,從而簡化開發者的開發難度,但是攔截器可能會有多個,舉個例子,您要請假一周,然后在OA上提交請假申請單,要經過專案經理、部門經理、人事等多個角色的審批,每個角色都會對申請單攔截、修改、審批等,如果把請假申請單看做一個物件,則它會經過三個攔截器的攔截處理,當一個物件在一條鏈上被多個攔截器攔截處理時,我們把這樣的設計模式稱為責任鏈模式,前一個攔截器的回傳結果會作用于后一個攔截器,代碼上的實作是用后一個攔截器去代理了前一個攔截器的方法,以此類推,層層代理,最終結果如下圖:
代碼實作如下:
@Test public void testInterceptor() { SayService target = new SayServiceImpl(); SayService proxy1 = (SayService)JdkProxyExample.bind(target, new SayServiceInterceptor("proxy1")); SayService proxy2 = (SayService)JdkProxyExample.bind(proxy1, new SayServiceInterceptor("proxy2")); SayService proxy3 = (SayService)JdkProxyExample.bind(proxy2, new SayServiceInterceptor("proxy3")); proxy1.sayHello(); } /** proxy3:代理前執行before方法 proxy2:代理前執行before方法 proxy1:代理前執行before方法 Hello Friends proxy1:代理后執行after方法 proxy2:代理后執行after方法 proxy3:代理后執行after方法 */
before方法執行順序是從最后一個攔截器到第一個攔截器,而after方法是從第一個攔截器到最后一個,責任鏈模式的優點在于我們可以在傳遞鏈中加上新的攔截器,增加攔截邏輯,但缺點是每增加一層代理反射,程式性能越差,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/539153.html
標籤:其他
上一篇:讓Visual Leak Detector使用最新10.0版本的dbghelp.dll
下一篇:Leetcode刷題第五周
