15.0 自定义插件
MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用.默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的类,方法,参数(由于有多态的情况)即可
// ExamplePlugin.java @Intercepts({ @Signature( type= Executor.class, method = "update", args = {MappedStatement.class,Object.class})}) public class ExamplePlugin implements Interceptor { private Properties properties = new Properties(); public Object intercept(Invocation invocation) throws Throwable { // implement pre processing if need Object returnObject = invocation.proceed(); // implement post processing if need return returnObject; } public void setProperties(Properties properties) { this.properties = properties; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
上面的插件将会拦截在 Executor 实例中所有的 “update” 方法调用, 这里的 Executor 是负责执行底层映射语句的内部对象。
注意:
这四大类中方法具体可以通过查看每个方法的签名来发现,或者直接查看 MyBatis 发行包中的源代码。 如果你想做的不仅仅是监控方法的调用,那么你最好相当了解要重写的方法的行为。 因为在试图修改或重写已有方法的行为时,很可能会破坏 MyBatis 的核心模块。 这些都是更底层的类和方法,所以使用插件的时候要特别当心。
MyBatis官方文档中也有这样的说明:
MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
在MyBatis的四大对象创建的过程中,每个对象的创建后都不是直接返回的,而是要经过一个拦截器链进行包装后返回:
executor = (Executor) interceptorChain.pluginAll(executor);
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
下面查看InterceptorChain类中的pluginAll方法:public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
形参Object target,这个是Executor、ParameterHandler、ResultSetHandler、StatementHandler接口的实现类,换句话说,plugin方法是要为Executor、ParameterHandler、ResultSetHandler、StatementHandler的实现类生成代理,从而在调用这几个类的方法的时候,其实调用的是InvocationHandler的invoke方法。
插件用的是责任链模式,责任链模式是一种对象行为模式。在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链,请求在这个链上传递,直到链上的某一个对象决定处理此请求。
责任链模式讲解
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SejyTzvj-1669338504017)(https://gimg2.baidu.com/image_search/src=http%3A%2F%2Ftva1.sinaimg.cn%2Flarge%2F00831rSTly1gdaind2f9sj31eg0mk0uv.jpg&refer=http%3A%2F%2Ftva1.sinaimg.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1671860091&t=e27fa2f65afedbfc80224d5ab7e66fe8)]
MyBatis接口
/**
- @author Clinton Begin
*/
public interface Interceptor {Object intercept(Invocation invocation) throws Throwable;
Object plugin(Object target);
void setProperties(Properties properties);
}
intercept:它将直接覆盖你所拦截的对象,有个参数Invocation对象,通过该对象,可以反射调度原来对象的方法;
plugin:target是被拦截的对象,它的作用是给被拦截对象生成一个代理对象;
setProperties:允许在plugin元素中配置所需参数,该方法在插件初始化的时候会被调用一次;简单的说,mybatis插件就是对ParameterHandler、ResultSetHandler、StatementHandler、Executor这四个接口上的方法进行拦截,利用JDK动态代理机制,为这些接口的实现类创建代理对象,在执行方法时,先去执行代理对象的方法,从而执行自己编写的拦截逻辑,所以真正要用好mybatis插件,主要还是要熟悉这四个接口的方法以及这些方法上的参数的含义;
另外,如果配置了多个拦截器的话,会出现层层代理的情况,即代理对象代理了另外一个代理对象,形成一个代理链条,执行的时候,也是层层执行;
关于mybatis插件涉及到的设计模式和软件思想如下:
设计模式:代理模式、责任链模式;
软件思想:AOP编程思想,降低模块间的耦合度,使业务模块更加独立;
一些注意事项:
不要定义过多的插件,代理嵌套过多,执行方法的时候,比较耗性能;
拦截器实现类的intercept方法里最后不要忘了执行invocation.proceed()方法,否则多个拦截器情况下,执行链条会断掉;