本篇博客將主要講解 mybatis 插件的主要流程,其中主要包括動態代理和責任鏈的使用;java
在編寫 mybatis 插件的時候,首先要實現 Interceptor 接口,而後在 mybatis-conf.xml 中添加插件,git
<configuration> <plugins> <plugin interceptor="***.interceptor1"/> <plugin interceptor="***.interceptor2"/> </plugins> </configuration>
這裏須要注意的是,添加的插件是有順序的,由於在解析的時候是依次放入 ArrayList 裏面,而調用的時候其順序爲:2 > 1 > target > 1 > 2;(插件的順序可能會影響執行的流程)更加細緻的講解能夠參考 QueryInterceptor 規範 ;github
而後當插件初始化完成以後,添加插件的流程以下:sql
首先要注意的是,mybatis 插件的攔截目標有四個,Executor、StatementHandler、ParameterHandler、ResultSetHandler:數據庫
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql); parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler); return parameterHandler; } public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) { ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds); resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler); return resultSetHandler; } public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); return statementHandler; } public Executor newExecutor(Transaction transaction, ExecutorType executorType) { executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor executor; if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this, transaction); } else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } else { executor = new SimpleExecutor(this, transaction); } if (cacheEnabled) { executor = new CachingExecutor(executor); } executor = (Executor) interceptorChain.pluginAll(executor); return executor; }
這裏使用的時候都是用動態代理將多個插件用責任鏈的方式添加的,最後返回的是一個代理對象; 其責任鏈的添加過程以下:緩存
public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { target = interceptor.plugin(target); } return target; }
最終動態代理生成和調用的過程都在 Plugin 類中:mybatis
public static Object wrap(Object target, Interceptor interceptor) { Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); // 獲取簽名Map Class<?> type = target.getClass(); // 攔截目標 (ParameterHandler|ResultSetHandler|StatementHandler|Executor) Class<?>[] interfaces = getAllInterfaces(type, signatureMap); // 獲取目標接口 if (interfaces.length > 0) { return Proxy.newProxyInstance( // 生成代理 type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)); } return target; }
這裏所說的簽名是指在編寫插件的時候,指定的目標接口和方法,例如:app
@Intercepts({ @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}), @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}) }) public class ExamplePlugin implements Interceptor { public Object intercept(Invocation invocation) throws Throwable { ... } }
這裏就指定了攔截 Executor 的具備相應方法的 update、query 方法;註解的代碼很簡單,你們能夠自行查看;而後經過 getSignatureMap 方法反射取出對應的 Method 對象,在經過 getAllInterfaces 方法判斷,目標對象是否有對應的方法,有就生成代理對象,沒有就直接反對目標對象;ide
在調用的時候:this
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { Set<Method> methods = signatureMap.get(method.getDeclaringClass()); // 取出攔截的目標方法 if (methods != null && methods.contains(method)) { // 判斷這個調用的方法是否在攔截範圍內 return interceptor.intercept(new Invocation(target, method, args)); // 在目標範圍內就攔截 } return method.invoke(target, args); // 不在目標範圍內就直接調用方法自己 } catch (Exception e) { throw ExceptionUtil.unwrapThrowable(e); } }
mybatis 插件咱們平時使用最多的就是分頁插件了,這裏以 PageHelper 爲例,其使用方法能夠查看相應的文檔 如何使用分頁插件,由於官方文檔講解的很詳細了,我這裏就簡單補充分頁插件須要作哪幾件事情;
使用:
PageHelper.startPage(1, 2); List<User> list = userMapper1.getAll();
PageHelper 還有不少中使用方式,這是最經常使用的一種,他其實就是在 ThreadLocal 中設置了 Page 對象,能取到就表明須要分頁,在分頁完成後在移除,這樣就不會致使其餘方法分頁;(PageHelper 使用的其餘方法,也是圍繞 Page 對象的設置進行的)
protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>(); public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) { Page<E> page = new Page<E>(pageNum, pageSize, count); page.setReasonable(reasonable); page.setPageSizeZero(pageSizeZero); //當已經執行過orderBy的時候 Page<E> oldPage = getLocalPage(); if (oldPage != null && oldPage.isOrderByOnly()) { page.setOrderBy(oldPage.getOrderBy()); } setLocalPage(page); return page; }
主要實現:
@Intercepts({ @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}), @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}), }) public class PageInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { try { Object[] args = invocation.getArgs(); MappedStatement ms = (MappedStatement) args[0]; Object parameter = args[1]; RowBounds rowBounds = (RowBounds) args[2]; ResultHandler resultHandler = (ResultHandler) args[3]; Executor executor = (Executor) invocation.getTarget(); CacheKey cacheKey; BoundSql boundSql; //因爲邏輯關係,只會進入一次 if (args.length == 4) { //4 個參數時 boundSql = ms.getBoundSql(parameter); cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql); } else { //6 個參數時 cacheKey = (CacheKey) args[4]; boundSql = (BoundSql) args[5]; } checkDialectExists(); List resultList; //調用方法判斷是否須要進行分頁,若是不須要,直接返回結果 if (!dialect.skip(ms, parameter, rowBounds)) { //判斷是否須要進行 count 查詢 if (dialect.beforeCount(ms, parameter, rowBounds)) { //查詢總數 Long count = count(executor, ms, parameter, rowBounds, resultHandler, boundSql); //處理查詢總數,返回 true 時繼續分頁查詢,false 時直接返回 if (!dialect.afterCount(count, parameter, rowBounds)) { //當查詢總數爲 0 時,直接返回空的結果 return dialect.afterPage(new ArrayList(), parameter, rowBounds); } } resultList = ExecutorUtil.pageQuery(dialect, executor, ms, parameter, rowBounds, resultHandler, boundSql, cacheKey); } else { //rowBounds用參數值,不使用分頁插件處理時,仍然支持默認的內存分頁 resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql); } return dialect.afterPage(resultList, parameter, rowBounds); } finally { if(dialect != null){ dialect.afterAll(); } } } }