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