第一節 從零開始手寫 mybatis(一)MVP 版本 中我們實現了一個最基本的可以運行的 mybatis。
常言道,萬事開頭難,然後中間難。
mybatis 的插件機制是 mybatis 除卻動態代理之外的第二大靈魂。
下面我們一起來體驗一下這有趣的靈魂帶來的痛苦與快樂~
在實際開發過程中,我們經常使用的Mybaits插件就是分頁插件了,通過分頁插件我們可以在不用寫count語句和limit的情況下就可以獲取分頁後的數據,給我們開發帶來很大
的便利。除了分頁,插件使用場景主要還有更新資料庫的通用欄位,分庫分表,加解密等的處理。
這篇博客主要講Mybatis插件原理,下一篇博客會設計一個Mybatis插件實現的功能就是每當新增數據的時候不用資料庫自增ID而是通過該插件生成雪花ID,作為每條數據的主鍵。
Mybatis的插件其實就是個攔截器功能。它利用JDK動態代理和責任鏈設計模式的綜合運用。採用責任鏈模式,通過動態代理組織多個攔截器,通過這些攔截器你可以做一些你想做的事。
所以在講Mybatis攔截器之前我們先說說JDK動態代理+責任鏈設計模式。
package com.github.houbb.mybatis.plugin;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;public class JdkDynamicProxy { /** * 一個接口 */ public interface HelloService{ void sayHello(); } /** * 目標類實現接口 */ static class HelloServiceImpl implements HelloService{ @Override public void sayHello() { System.out.println(&34;); } } /** * 自定義代理類需要實現InvocationHandler接口 */ static class HelloInvocationHandler implements InvocationHandler { private Object target; public HelloInvocationHandler(Object target){ this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(&34;); //執行相應的目標方法 Object rs = method.invoke(target,args); System.out.println(&34;); return rs; } public static Object wrap(Object target) { return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),new HelloInvocationHandler(target)); } } public static void main(String[] args) { HelloService proxyService = (HelloService) HelloInvocationHandler.wrap(new HelloServiceImpl()); proxyService.sayHello(); }}
------插入前置通知代碼-------------sayHello......------插入後置處理代碼-------------
上面代理的功能是實現了,但是有個很明顯的缺陷,就是 HelloInvocationHandler 是動態代理類,也可以理解成是個工具類,我們不可能會把業務代碼寫到寫到到invoke方法裡,
不符合面向對象的思想,可以抽象一下處理。
可以設計一個Interceptor接口,需要做什麼攔截處理實現接口就行了。
public interface Interceptor { /** * 具體攔截處理 */ void intercept();}
public class LogInterceptor implements Interceptor{ @Override public void intercept() { System.out.println(&34;); }}
和
public class TransactionInterceptor implements Interceptor{ @Override public void intercept() { System.out.println(&34;); }}
public class InterfaceProxy implements InvocationHandler { private Object target; private List<Interceptor> interceptorList = new ArrayList<>(); public InterfaceProxy(Object target, List<Interceptor> interceptorList) { this.target = target; this.interceptorList = interceptorList; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //處理多個攔截器 for (Interceptor interceptor : interceptorList) { interceptor.intercept(); } return method.invoke(target, args); } public static Object wrap(Object target, List<Interceptor> interceptorList) { InterfaceProxy targetProxy = new InterfaceProxy(target, interceptorList); return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), targetProxy); }}
public static void main(String[] args) { List<Interceptor> interceptorList = new ArrayList<>(); interceptorList.add(new LogInterceptor()); interceptorList.add(new TransactionInterceptor()); HelloService target = new HelloServiceImpl(); HelloService targetProxy = (HelloService) InterfaceProxy.wrap(target, interceptorList); targetProxy.sayHello();}
------插入前置通知代碼-------------------插入後置處理代碼-------------sayHello......
這裡有一個很明顯的問題,所有的攔截都在方法執行前被處理了。
上面的動態代理確實可以把代理類中的業務邏輯抽離出來,但是我們注意到,只有前置代理,無法做到前後代理,所以還需要在優化下。
所以需要做更一步的抽象,
把攔截對象信息進行封裝,作為攔截器攔截方法的參數,把攔截目標對象真正的執行方法放到Interceptor中完成,這樣就可以實現前後攔截,並且還能對攔截對象的參數等做修改。
設計一個 Invocation 對象。
public class Invocation { /** * 目標對象 */ private Object target; /** * 執行的方法 */ private Method method; /** * 方法的參數 */ private Object[] args; public Invocation(Object target, Method method, Object[] args) { this.target = target; this.method = method; this.args = args; } /** * 執行目標對象的方法 */ public Object process() throws Exception{ return method.invoke(target,args); } // 省略 Getter/Setter}
public interface Interceptor { /** * 具體攔截處理 */ Object intercept(Invocation invocation) throws Exception;}
public class MyLogInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Exception { System.out.println(&34;); Object result = invocation.process(); System.out.println(&34;); return result; }}
public class MyInvocationHandler implements InvocationHandler { private Object target; private Interceptor interceptor; public MyInvocationHandler(Object target, Interceptor interceptor) { this.target = target; this.interceptor = interceptor; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Invocation invocation = new Invocation(target, method, args); // 返回的依然是代理類的結果 return interceptor.intercept(invocation); } public static Object wrap(Object target, Interceptor interceptor) { MyInvocationHandler targetProxy = new MyInvocationHandler(target, interceptor); return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), targetProxy); }}
最核心的就在於構建了 invocation,然後執行對應的方法。
public static void main(String[] args) { HelloService target = new HelloServiceImpl(); Interceptor interceptor = new MyLogInterceptor(); HelloService targetProxy = (HelloService) MyInvocationHandler.wrap(target, interceptor); targetProxy.sayHello();}
------插入前置通知代碼-------------sayHello......------插入後置處理代碼-------------
上面這樣就能實現前後攔截,並且攔截器能獲取攔截對象信息。
但是測試代碼的這樣調用看著很彆扭,對應目標類來說,只需要了解對他插入了什麼攔截就好。
再修改一下,在攔截器增加一個插入目標類的方法。
public interface Interceptor { /** * 具體攔截處理 * * @return 方法執行的結果 * @since 0.0.2 */ Object intercept(Invocation invocation) throws Exception; /** * 插入目標類 * * @return 代理 * @since 0.0.2 */ Object plugin(Object target);}
可以理解為把靜態方法調整為對象方法。
public class MyLogInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Exception { System.out.println(&34;); Object result = invocation.process(); System.out.println(&34;); return result; } @Override public Object plugin(Object target) { return MyInvocationHandler.wrap(target, this); }}
public static void main(String[] args) { HelloService target = new HelloServiceImpl(); Interceptor interceptor = new MyLogInterceptor(); HelloService targetProxy = (HelloService) interceptor.plugin(target); targetProxy.sayHello();}
------插入前置通知代碼-------------sayHello......------插入後置處理代碼-------------
public static void main(String[] args) { HelloService target = new HelloServiceImpl(); //1. 攔截器1 Interceptor interceptor = new MyLogInterceptor(); target = (HelloService) interceptor.plugin(target); //2. 攔截器 2 Interceptor interceptor2 = new MyTransactionInterceptor(); target = (HelloService) interceptor2.plugin(target); // 調用 target.sayHello();}
其中 MyTransactionInterceptor 實現如下:
public class MyTransactionInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Exception { System.out.println(&34;); Object result = invocation.process(); System.out.println(&34;); return result; } @Override public Object plugin(Object target) { return MyInvocationHandler.wrap(target, this); }}
日誌如下:
------tx start-------------------插入前置通知代碼-------------sayHello......------插入後置處理代碼-------------------tx end-------------
當然很多小夥伴看到這裡其實已經想到使用責任鏈模式,下面我們一起來看一下責任鏈模式。
public class InterceptorChain { private List<Interceptor> interceptorList = new ArrayList<>(); /** * 插入所有攔截器 */ public Object pluginAll(Object target) { for (Interceptor interceptor : interceptorList) { target = interceptor.plugin(target); } return target; } public void addInterceptor(Interceptor interceptor) { interceptorList.add(interceptor); } /** * 返回一個不可修改集合,只能通過addInterceptor方法添加 * 這樣控制權就在自己手裡 */ public List<Interceptor> getInterceptorList() { return Collections.unmodifiableList(interceptorList); }}
public static void main(String[] args) { HelloService target = new HelloServiceImpl(); Interceptor interceptor = new MyLogInterceptor(); Interceptor interceptor2 = new MyTransactionInterceptor(); InterceptorChain chain = new InterceptorChain(); chain.addInterceptor(interceptor); chain.addInterceptor(interceptor2); target = (HelloService) chain.pluginAll(target); // 調用 target.sayHello();}
------tx start-------------------插入前置通知代碼-------------sayHello......------插入後置處理代碼-------------------tx end-------------
實際上個人感覺這裡可以換一種角度,比如定義攔截器接口時,改為:
這樣可以代碼中可以不用寫執行的部分,實現起來更加簡單,也不會忘記。
public interface Interceptor { /** * 具體攔截處理 */ void before(Invocation invacation); /** * 具體攔截處理 */ void after(Invocation invacation);}
不過這樣也有一個缺點,那就是對於 process 執行的部分不可見,喪失了一部分靈活性。
對於 plugin() 這個方法,實際上實現非常固定。
應該對於接口不可見,直接放在 chain 中統一處理即可。
說了這麼多,如果你理解之後,那麼接下來的插件實現部分就是小菜一碟。
只是將上面的思想做一個簡單的實現而已。
引入插件,其他部分省略。
<plugins> <plugin interceptor=&34;/></plugins>
我們就是簡單的輸出一下入參和出參。
public class SimpleLogInterceptor implements Interceptor{ @Override public void before(Invocation invocation) { System.out.println(&34; + Arrays.toString(invocation.getArgs())); } @Override public void after(Invocation invocation, Object result) { System.out.println(&34; + result); }}
輸出日誌如下。
----param: [com.github.houbb.mybatis.config.impl.XmlConfig@3b76982e, MapperMethod{type=&39;, sql=&39;, methodName=&39;, resultType=class com.github.houbb.mybatis.domain.User, paramType=class java.lang.Long}, [Ljava.lang.Object;@67011281]----result: User{id=1, name=&39;, password=&39;}User{id=1, name=&39;, password=&39;}
是不是灰常的簡單,那麼是怎麼實現的呢?
public interface Interceptor { /** * 前置攔截 * @param invocation 上下文 * @since 0.0.2 */ void before(Invocation invocation); /** * 後置攔截 * @param invocation 上下文 * @param result 執行結果 * @since 0.0.2 */ void after(Invocation invocation, Object result);}
在 openSession() 的時候,我們啟動插件:
public SqlSession openSession() { Executor executor = new SimpleExecutor(); //1. 插件 InterceptorChain interceptorChain = new InterceptorChain(); List<Interceptor> interceptors = config.getInterceptorList(); interceptorChain.add(interceptors); executor = (Executor) interceptorChain.pluginAll(executor); //2. 創建 return new DefaultSqlSession(config, executor);}
這裡我們就看到了一個責任鏈,實現如下。
public class InterceptorChain { /** * 攔截器列表 * @since 0.0.2 */ private final List<Interceptor> interceptorList = new ArrayList<>(); /** * 添加攔截器 * @param interceptor 攔截器 * @return this * @since 0.0.2 */ public synchronized InterceptorChain add(Interceptor interceptor) { interceptorList.add(interceptor); return this; } /** * 添加攔截器 * @param interceptorList 攔截器列表 * @return this * @since 0.0.2 */ public synchronized InterceptorChain add(List<Interceptor> interceptorList) { for(Interceptor interceptor : interceptorList) { this.add(interceptor); } return this; } /** * 代理所有 * @param target 目標類 * @return 結果 * @since 0.0.2 */ public Object pluginAll(Object target) { for(Interceptor interceptor : interceptorList) { target = DefaultInvocationHandler.proxy(target, interceptor); } return target; }}
其中的 DefaultInvocationHandler 實現如下:
/** * 默認的代理實現 * @since 0.0.2 */public class DefaultInvocationHandler implements InvocationHandler { /** * 代理類 * @since 0.0.2 */ private final Object target; /** * 攔截器 * @since 0.0.2 */ private final Interceptor interceptor; public DefaultInvocationHandler(Object target, Interceptor interceptor) { this.target = target; this.interceptor = interceptor; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Invocation invocation = new Invocation(target, method, args); interceptor.before(invocation); // invoke Object result = method.invoke(target, args); interceptor.after(invocation, result); return result; } /** * 構建代理 * @param target 目標對象 * @param interceptor 攔截器 * @return 代理 * @since 0.0.2 */ public static Object proxy(Object target, Interceptor interceptor) { DefaultInvocationHandler targetProxy = new DefaultInvocationHandler(target, interceptor); return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), targetProxy); }}
本節的實現並不難,難在要理解 mybatis 整體對於插件的設計理念,技術層面還是動態代理,結合了責任鏈的設計模式。
這種套路學會之後,其實很多類似的框架,我們自己在實現的時候都可以借鑑這種思想。