相信工做中用mybatis的同窗大部分都使用過PageHelper分佈插件,最近也是想了解一下PageHelper的實現原理,PageHelper也是經過mybatis的插件來實現的。具體怎麼去實現一個mybatis插件下面作具體的介紹。sql
工做中遇到過一個場景,打印mybatis的執行sql日誌到公司日誌平臺。那麼就須要自定義mybatis插件來實現,在執行sql以前,但願可以攔截到mybatis的執行sql,而後使用公司的日誌框架打印日誌。
myba支持攔截的方法:設計模式
在自定義mybatis插件的時候,須要指定本身所須要的攔截方式,例如我上面工做中是須要使用StatementHandler。mybatis
mybatis插件中主要使用到了兩種設計模式:動態代理和責任鏈模式;app
責任鏈模式
在說mybatis中的責任鏈以前,咱們先回想一下責任鏈模式:
責任鏈模式中涉及兩個角色:框架
具體處理者角色(ConcretaHandler)角色:處理具體的請求或者將請求發送到下一個具體處理者
具體處理者擁有下一下處理者的引用,若是須要下一個處理者處理,那麼調用下一個處理者的處理方法便可。
mybatis插件中責任鏈模式的應用
mybatis中的插件實際上也能夠叫作攔截器,mybatis中使用責任鏈模式將臃腫的功能拆分紅單一的Handler處理類中,開發人員能夠根據業務需求將多個Handler對象組合成一條責任鏈,實現請求的處理。mybatis也是經過這種模式來提供mybatis的擴展性。ide
例如:如今有 HandlerA、HandlerB、HandlerC三個字段的業務邏輯
當業務只須要HandlerA和HandlerC時,只須要動態組合獲得HandlerA->HandlerC就能夠了。優化
mybatis中的Executor、ParameterHandler、ResultSetHandler、StatementHandler它們都是經過Configuration.new()方法來建立的,而Configuration.new()方法實際上調用的是InterceptorChain.pluginAll()方法來生成代理對象,因此經過Configuration.new*()系列方法獲得的對象實際是一個代理對象。
以Configuration.newExecutor()方法爲例介紹:
1.Configuration.newExecutor()this
public Executor newExecutor(Transaction transaction, ExecutorType executorType) { executorType = executorType == null ? this.defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Object 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 (this.cacheEnabled) { executor = new CachingExecutor((Executor)executor); } Executor executor = (Executor)this.interceptorChain.pluginAll(executor); return executor; }
2.interceptorChain.pluginAll(executor) 插件
public Object pluginAll(Object target) { Interceptor interceptor; for(Iterator var2 = this.interceptors.iterator(); var2.hasNext(); target = interceptor.plugin(target)) { interceptor = (Interceptor)var2.next(); } return target; }
3.target = interceptor.plugin(target) debug
interceptor.plugin()方法其實是咱們自定義插件的plugin方法。
通常咱們這個方法的實現是經過Plugin.wrap來生成代理
public Object plugin(Object target) { return Plugin.wrap(target, this); }
4. Plugin.wrap
public static Object wrap(Object target, Interceptor interceptor) { Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); Class<?> type = target.getClass(); Class<?>[] interfaces = getAllInterfaces(type, signatureMap); return interfaces.length > 0 ? Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)) : target; }
mybatis中使用的攔截器都須要實現Interceptor接口
public interface Interceptor { Object intercept(Invocation var1) throws Throwable; Object plugin(Object var1); void setProperties(Properties var1); }
用戶自定義的攔截器除了繼承Interceptor接口,還須要使用@Intercepts和@Signature兩個註解標識。
@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 }), @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class })})
例如上面的定義:
將咱們自定義的mybatis的插件配置到mybatis-config.xml中
<plugins> <plugin interceptor="com.xxx.XXXInterceptor"> <!-- 對攔截器中的屬性進行初始化 --> <property name="name" value="Bob"/> </plugin> </plugins>
例如,咱們比較經常使用的對sql進行監控,監控sql的執行時長及慢查詢等,那麼咱們就能夠經過編寫MonitorInterceptor攔截器
@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 }), @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class })}) @Slf4j public class MonitorInercepor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { try { Object[] args = invocation.getArgs(); MappedStatement ms = (MappedStatement) args[0]; Object parameter = args[1]; long start = System.currentTimeMillis(); Object result = invocation.proceed(); // 獲取代理 long end = System.currentTimeMillis(); long cost = end - start; log.debug("[TimerInterceptor] execute [{}] cost [{}] ms, parameter:{}", ms.getId(), cost, parameter); if (cost > 1000) { log.warn("Sql語句執行時間超過1秒鐘,請檢查優化,方法:{},耗時:{}ms,參數:{}", ms.getId(), cost, parameter); } return result; } catch (Throwable r) { log.error(r.getMessage(), r); } return invocation.proceed(); } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { } }