MyBatis 插件
MyBatis使用责任链的封装了Mapper的调用过程,在SqlSession四大对象调度的时候我们有机会插入
代码去执行一些特殊要求以满足场景需求,这便是Mybatis 插件技术
插件接口
package org.apache.ibatis.plugin; import java.util.Properties; /** * Mybatis 模板模式 * @author Clinton Begin */ public interface Interceptor { /** * 直接覆盖要拦截对象原有的方法,核心方法 * @param invocation 通过它可以反射调度原来对象的方法 * @return * @throws Throwable */ Object intercept(Invocation invocation) throws Throwable; /** * 给被拦截对象生成一个代理对象,并返回它 * @param target 被拦截对象 * @return */ default Object plugin(Object target) { return Plugin.wrap(target, this); } /** * 插件初始化时调用,然后插件对象会被存到配置中 * @param properties 允许在 plugin 元素中配置所需的参数 */ default void setProperties(Properties properties) { // NOP } }
插件的初始化
插件的初始化是在MyBatis初始化的时候完成的,在 XMLConfigBuilder 类中查看 pluginElement(XNode parent) 方法向上查找可知
在MyBatis上下文初始化过程中,就开始读入插件配置信息,同时使用反射技术生成插件实例,然后调用插件方法中的setProperties方法
设置我们配置的参数,然后将插件实例保存到配置中,以便读取和使用
插件的代理和反射设计
插件用的是责任链模式(一个对象在多个角色中传递,处在传递链上的任何角色都有处理它的机会)
MyBatis的责任链是由inteceptorChain去定义的,这个可以在Configuration类的 newExecutor方法中看到
executor = (Executor) interceptorChain.pluginAll(executor);
pluginAll方法是生成代理对象的方法, 有多少个拦截器就生成多少个代理对象,这样每个插件都可以拦截到真实的对象
MyBatis提供实现了InvocationHandler接口的Plugin类,方便编写代理类

wrap方法为我们生成这个对象的动态代理对象
如果使用这个类为插件生成代理对象,name代理对象调用方法的时候就会进入到invoke方法中,如果存在签名的拦截方法,插件的intercept方法就会被我们在这里调用,然后返回结果,
如果不存签名方法,那么将直接使用反射调度我们要执行的方法
工具类 - MetaObject
编写插件时常用到的工具类,可以读取或修改一些重要的属性
MyBatis中大量使用了这个类镜像包装,包括四大对象,可以通过MetaObject来操作被包装的对象
forObject ,用于包装对象,使用 SystemMetaObject.forObject(Object obj)
getValue, 用于获取对象的属性值,支持OGNL
setValue,设置对象的属性值,支持OGNL
插件开发过程
MyBatis插件可以拦截SqlSession中四大对象的任意一个,但是需要注册签名才能够运行插件
签名需要一些规则:
确定需要拦截的对象
- Executor ,是执行SQL的全过程,参数、组装结果集返回和执行SQL过程都可以拦截
- StatementHandler,执行SQL的过程,可以重写执行SQL的过程,
- ParameterHandler,主要拦截执行SQL的参数组装,可以重写组装参数规则
- ResultSetHandler,拦截执行结果的组装,可以重写组装结果的规则
拦截方法和参数
package com.test.plugin; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.plugin.Intercepts; import org.apache.ibatis.plugin.Invocation; import org.apache.ibatis.plugin.Signature; import java.sql.Connection; @Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", // 拦截 StatementHandler 中的 prepare 方法 args = {Connection.class}) // 拦截 prepare 方法的 Connection 参数 }) public class MyPlugin implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { return null; } }
实现完整拦截
package com.test.plugin; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.plugin.*; import java.sql.Connection; import java.util.Properties; @Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", // 拦截 StatementHandler 中的 prepare 方法 args = {Connection.class}) // 拦截 prepare 方法的 Connection 参数 }) public class MyPlugin implements Interceptor { /** * 代替拦截对象方法的内容 * @param invocation * @return * @throws Throwable */ @Override public Object intercept(Invocation invocation) throws Throwable { System.out.println("执行前..."); Object obj = invocation.proceed(); System.out.println("执行后..."); return obj; } /** * 生成代理对象,除非自定义,否则不用重写 * @param target * @return */ @Override public Object plugin(Object target) { return Plugin.wrap(target,this); } /** * 读取Plugin元素上配置的属性 * 例如: * <Plugin> * <property name="aname" value="unknown" /> * </Plugin> * @param properties */ @Override public void setProperties(Properties properties) { System.out.println(properties.getProperty("aname")); } }
配置插件
在配置文件中增加Plugin元素
<plugins> <plugin interceptor="com.test.plugin.MyPlugin"> <property name="aname" value="unknown"/> </plugin> </plugins>
总结
- 尽量少使用插件,因为这会修改MyBatis底层设计
- 插件底层是责任链模式,通过反射方法运行,性能不高,尽量减少代理处理的代码,从而提高性能
- 编写插件需要了解SqlSession四大对象的运行原理

浙公网安备 33010602011771号