靜態代理
又是一年畢業季,很多小伙伴開始去大城市打拼。來大城市第一件事就是租房,免不了和中介打交道,因為很多房東很忙,你根本找不到他。從這個場景中就可以抽象出來代理模式。
ISubject:被訪問者資源的抽象SubjectImpl:被訪問者具體實現類(房東)SubjectProxy:被訪問者的代理實現類(中介)
UML圖如下
舉個例子來理解一下這個設計模式老板讓記錄一下用戶服務的響應時間,用代理模式來實現這個功能。
public interface IUserService { public void request();}
public class UserServiceImpl implements IUserService { @Override public void request() { System.out.println(“this is userService”); }}
public class UserServiceProxy implements IUserService { private IUserService userService; public UserServiceProxy(IUserService userService) { this.userService = userService; } @Override public void request() { long startTime = System.currentTimeMillis(); userService.request(); System.out.println(“reques cost :” + (System.currentTimeMillis() - startTime)); } public static void main(String[] args) { IUserService userService = new UserServiceImpl(); UserServiceProxy userServiceProxy = new UserServiceProxy(userService); // this is userService // reques cost :0 userServiceProxy.request(); }}
一切看起來都非常的美好,老板又發話了,把產品服務的響應時間也記錄一下吧。又得寫如下3個類
IProductService ProductServiceImpl ProductServiceProxy
UserServiceProxy和ProductServiceProxy這兩個代理類的邏輯都差不多,卻還得寫2次。其實這個還好,如果老板說,把現有系統的幾十個服務的響應時間都記錄一下吧,你是不是要瘋了?這得寫多少代理類啊?
動態代理
黑暗總是暫時的,終究會迎來黎明,在JDK1.3之后引入了一種稱之為動態代理(Dynamic Proxy)的機制。使用該機制,我們可以為指定的接口在系統運行期間動態地生成代理對象,從而幫助我們走出最初使用靜態代理實現AOP的窘境
動態代理的實現主要由一個類和一個接口組成,即java.lang.reflect.Proxy類和java.lang.reflect.InvocationHandler接口。
讓我們用動態代理來改造一下上面記錄系統響應時間的功能。雖然要為IUserService和IProductService兩種服務提供代理對象,但因為代理對象中要添加的橫切邏輯是一樣的。所以我們只需要實現一個InvocationHandler就可以了。代碼如下
public class RequestCostInvocationHandler implements InvocationHandler { private Object target; public RequestCostInvocationHandler(Object target) { this.target = target; } /** 被代理對象的任何方法被執行時,都會先進入這個方法 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals(“request”)) { long startTime = System.currentTimeMillis(); // 執行目標對象的方法 method.invoke(target, args); System.out.println(“reques cost :” + (System.currentTimeMillis() - startTime)); } return null; } public static void main(String[] args) { // 3個參數解釋如下 // classloader,生成代理類 // 代理類應該實現的接口 // 實現InvocationHandler的切面類 IUserService userService = (IUserService) Proxy.newProxyInstance(IUserService.class.getClassLoader(), new Class[]{IUserService.class}, new RequestCostInvocationHandler(new UserServiceImpl())); IProductService productService = (IProductService) Proxy.newProxyInstance(IProductService.class.getClassLoader(), new Class[]{IProductService.class}, new RequestCostInvocationHandler(new ProductServiceImpl())); // this is userService // reques cost :0 userService.request(); // this is productService // reques cost :0 productService.request(); }}
UML圖如下。恭喜你,你現在已經理解了Spring AOP是怎么回事了,就是這么簡單,今天先不展開談Spring
先簡單談談動態代理在Mybatis中是如何被大佬玩的出神入化的
Mybatis核心設計思路
相信用過mybatis的小伙伴都能理解下面這段代碼,通過roleMapper這個接口直接從數據庫中拿到一個對象
Role role = roleMapper.getRole(3L);
直覺告訴我,一個接口是不能運行的啊,一定有接口的實現類,可是這個實現類我自己沒寫啊,難道mybatis幫我們生成了?你猜的沒錯,mybatis利用動態代理幫我們生成了接口的實現類,這個類就是org.apache.ibatis.binding.MapperProxy,我先畫一下UML圖,MapperProxy就是下圖中的SubjectProxy類
和上面的UML類圖對比一下,發現不就少了一個SubjectImpl類嗎?那應該就是SubjectProxy類把SubjectImple類要做的事情做了唄,猜對了。SubjectProxy通過SubjectImple和SubjectImple.xml之間的映射關系知道自己應該執行什么SQL。所以mybatis最核心的思路就是這么個意思,細節之類的可以看源碼,理清最主要的思路,看源碼就能把握住重點。
Mybatis插件原理
mybatis的插件也用到了動態代理,還用到了責任鏈模式,我就不從源碼角度分析了。說一下大概實現,我們用插件肯定是為了在原先的基礎上增加新功能。增加一個插件,mybatis就在原先類的基礎上用動態代理生成一個代理對象,如果有多個插件,就在代理對象的基礎上再生成代理對象,形式和如下函數差不多
plugin2( plugin1( start() ) )
我再給你寫個例子,你再看看相關的源碼分析文章(也許我以后會寫),很快就能理解了。
在mybatis中要想用插件,有如下2個步驟1.在mybatis-config.xml中配置插件,如下所示
《plugins》 《plugin interceptor=“org.xrq.mybatis.plugin.FirstInterceptor” /》 《plugin interceptor=“org.xrq.mybatis.plugin.SecondInterceptor” /》《/plugins》
2.插件類還得實現Interceptor接口
我現在給一個需求,一個應用返回字符串0,我加一個插件在字符串的左右兩邊加plugin1,再加一個插件在字符串的左右兩邊加plugin2,開寫
返回字符串的接口
public interface IGetStr { public String getStrZero(); public String getStrOne();}
返回字符串的實現類
public class GetStrImpl implements IGetStr { @Override public String getStrZero() { return “0”; } @Override public String getStrOne() { return “1”; }}
定義攔截器接口
public interface Interceptor { /** 執行攔截邏輯的方法 */ Object intercept(Invocation invocation); /** * target是被攔截的對象,它的作用是給被攔截對象生成一個代理對象,并返回它。 * 為了方便,可以直接使用Mybatis中org.apache.ibatis.plugin類的wrap方法生成代理對象 * 我這里也寫了一個Plugin方法 */ Object plugin(Object target);}
看到一個不認識的類Invocation,定義如下
public class Invocation { private final Object target; private final Method method; private final Object[] args; public Invocation(Object target, Method method, Object[] args) { this.target = target; this.method = method; this.args = args; } public Object proceed() throws InvocationTargetException, IllegalAccessException { return method.invoke(target, args); }}
就是簡單的封裝了一下目標對象,目標方法和目標方法的參數。proceed方法就是執行目標對象的目標方法
Plugin算是一個工具類,生成代理對象
public class Plugin implements InvocationHandler { /** 目標對象 */ private final Object target; /** Interceptor對象 */ private final Interceptor interceptor; public Plugin(Object target, Interceptor interceptor) { this.target = target; this.interceptor = interceptor; } /** 生成代理對象 */ public static Object wrap(Object target, Interceptor interceptor) { return Proxy.newProxyInstance(target.getClass().getClassLoader(), new Class[]{IGetStr.class}, new Plugin(target, interceptor)); } /** 被代理對象的方法執行時,這個方法會被執行 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 只為方法getStrZero生成代理對象 if (method.getName().equals(“getStrZero”)) { return interceptor.intercept(new Invocation(target, method, args)); } return method.invoke(target, args); }}
寫第一個插件
public class FirstInterceptor implements Interceptor { /** 執行攔截邏輯的方法 */ @Override public Object intercept(Invocation invocation) { try { return “plugin1 ” + invocation.proceed() + “ plugin1”; } catch (Exception e) { return null; } } /** 為原先的類生成代理對象 */ @Override public Object plugin(Object target) { return Plugin.wrap(target, this); }}
有2個方法
plugin是為插件生成代理對象,用了我自己寫的Plugin工具類intercept是增加攔截邏輯,invocation.proceed()是執行目標對象的目標方法,前文說過了哈,這里我們只對輸出做了改變
第二個插件和第一個插件類似
public class SecondInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) { try { return “plugin2 ” + invocation.proceed() + “ plugin2”; } catch (Exception e) { return null; } } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); }}
用一個容器保存插件,這里用到了責任鏈模式
public class InterceptorChain { /** 放攔截器 */ private final List《Interceptor》 interceptors = new ArrayList《Interceptor》(); public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { target = interceptor.plugin(target); } return target; } public void addInterceptor(Interceptor interceptor) { interceptors.add(interceptor); }}
pluginAll方法是精髓,為每個插件一層一層的生成代理對象,就像套娃娃一樣。
驗證一下
public class Main { public static void main(String[] args) { // 配置插件 InterceptorChain interceptorChain = new InterceptorChain(); interceptorChain.addInterceptor(new FirstInterceptor()); interceptorChain.addInterceptor(new SecondInterceptor()); // 獲得代理對象 IGetStr getStr = new GetStrImpl(); getStr = (IGetStr) interceptorChain.pluginAll(getStr); String result = getStr.getStrZero(); // plugin2 plugin1 0 plugin1 plugin2 System.out.println(result); result = getStr.getStrOne(); // 1 System.out.println(result); }}
大功告成,可以看到先定義的插件先執行。
類有點多,如果看的有點暈,多看幾次,你就很容易理解了,我這里還是精簡了很多。
一個InvocationHandler接口被大佬玩出了新境界,果然編程這件事還得靠想象力。
-
編程
+關注
關注
88文章
3619瀏覽量
93777
原文標題:Mybatis框架和插件將動態代理玩出了新境界
文章出處:【微信號:VOSDeveloper,微信公眾號:麻辣軟硬件】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論