第一節 從零開始手寫 mybatis(一)MVP 版本 中咱們實現了一個最基本的能夠運行的 mybatis。html
常言道,萬事開頭難,而後中間難。java
mybatis 的插件機制是 mybatis 除卻動態代理以外的第二大靈魂。git
下面咱們一塊兒來體驗一下這有趣的靈魂帶來的痛苦與快樂~github
在實際開發過程當中,咱們常用的Mybaits插件就是分頁插件了,經過分頁插件咱們能夠在不用寫count語句和limit的狀況下就能夠獲取分頁後的數據,給咱們開發帶來很大sql
的便利。除了分頁,插件使用場景主要還有更新數據庫的通用字段,分庫分表,加解密等的處理。數據庫
這篇博客主要講Mybatis插件原理,下一篇博客會設計一個Mybatis插件實現的功能就是每當新增數據的時候不用數據庫自增ID而是經過該插件生成雪花ID,做爲每條數據的主鍵。設計模式
Mybatis的插件其實就是個攔截器功能。它利用JDK動態代理和責任鏈設計模式的綜合運用。採用責任鏈模式,經過動態代理組織多個攔截器,經過這些攔截器你能夠作一些你想作的事。mybatis
因此在講Mybatis攔截器以前咱們先說說JDK動態代理+責任鏈設計模式。app
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("sayHello......"); } } /** * 自定義代理類須要實現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("------插入前置通知代碼-------------"); //執行相應的目標方法 Object rs = method.invoke(target,args); System.out.println("------插入後置處理代碼-------------"); 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("------插入前置通知代碼-------------"); } }
和
public class TransactionInterceptor implements Interceptor{ @Override public void intercept() { System.out.println("------插入後置處理代碼-------------"); } }
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("------插入前置通知代碼-------------"); Object result = invocation.process(); System.out.println("------插入後置處理代碼-------------"); 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("------插入前置通知代碼-------------"); Object result = invocation.process(); System.out.println("------插入後置處理代碼-------------"); 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("------tx start-------------"); Object result = invocation.process(); System.out.println("------tx end-------------"); 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="com.github.houbb.mybatis.plugin.SimpleLogInterceptor"/> </plugins>
咱們就是簡單的輸出一下入參和出參。
public class SimpleLogInterceptor implements Interceptor{ @Override public void before(Invocation invocation) { System.out.println("----param: " + Arrays.toString(invocation.getArgs())); } @Override public void after(Invocation invocation, Object result) { System.out.println("----result: " + result); } }
輸出日誌以下。
----param: [com.github.houbb.mybatis.config.impl.XmlConfig@3b76982e, MapperMethod{type='select', sql='select * from user where id = ?', methodName='selectById', resultType=class com.github.houbb.mybatis.domain.User, paramType=class java.lang.Long}, [Ljava.lang.Object;@67011281] ----result: User{id=1, name='luna', password='123456'} User{id=1, name='luna', password='123456'}
是否是灰常的簡單,那麼是怎麼實現的呢?
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 總體對於插件的設計理念,技術層面仍是動態代理,結合了責任鏈的設計模式。
這種套路學會以後,其實不少相似的框架,咱們本身在實現的時候均可以借鑑這種思想。