相信工做中用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) { } }