1、是什么?
mybatis plugin 是一种拦截机制,它允许在特定对象的方法执行之前进行拦截处理,来实现特定的处理功能。
2、用途
-
性能监控和日志记录:你可以使用插件来记录SQL执行的时间,以及执行结果,这对于性能调优和问题调试都非常有帮助。MyBatis的插件API非常简单,可以轻松实现这种功能。
-
数据加密解密:MyBatis插件可以用来实现数据加密和解密,以保护敏感数据的安全性和完整性。通过插件,可以在数据存储、传输或使用过程中对数据进行加密和解密操作,从而实现数据的保护。
-
分页和字段统一赋值: 可以实现自己的分页逻辑以及特殊字段(创建时间、创建人)填充等。
3、使用
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;
}
}
<!-- mybatis-config.xml -->
<plugins>
<plugin interceptor="org.mybatis.example.ExamplePlugin">
<property name="someProperty" value="100"/>
</plugin>
</plugins>
4、拦截原理
通过配置可以知道,在plugins中去配置我们的拦截器,会被解析到configarution对象中去
解析元素到configarution中去,最终保存到interceptorChain拦截链中
InterceptorChain结构
但是,保存之后如何实现对四大对象的拦截呢?
从最基础的mybatis案列开始,获取mapper
经过代理之后执行到查询selectOne()
一直调用下去到doQuery,在这里对我们的statementhandler进行代理拦截
创建RoutingStatementHandler对象的构造函数中会去创建ParameterHandler,ResultSetHandler的代理拦截
调用其父类构造,其中会创建我们的parameterHandler、resultSetHandler
找到了创建的位置后,以其中一个statementhandler为例,看看如何代理,如何拦截。
主要步骤就是循环interceptors,每一个拦截器代理会经历plugin.wrap判断是否代理
1.获取signatureMap
2.获取代理的目标类,即四大对象
3.获取目标类的接口,判断是否在interceptors中声明,如果匹配到目标接口则进行代理。
回到我们定义的Interceptor实现,每一个@Signature代表我们要拦截的对象接口方法,比如第一个意思代表StatementHandler的query方法,参数是Statement.class、ResultHandler.class类型
调用interceptorChain.pluginAll(statementHandler) 循环嵌套代理,将代理后的类又赋值给target再次代理。
Plugins.wrap实现
获取签名集合,即定义的四个@Signature
最终解析出StatementHandler.class的(query、update、batch)和Executor.class的(query)方法与我们定义的相同
解析完成后,还需要判断当前的target的接口是否属于其中的一个接口
获取目标类接口
判断是否在signatureMap,如果在,则将接口添加进来,根据匹配的接口数量来判断是否代理。采用JDK动态代理,Plugin为处理器
后续对StatementHandler的所有方法都会执行我们Plugin的invoke方法
从 signatureMap中获取相应对象的方法,如果不为null并且匹配当前方法,则interceptor.intercept,
将目标对象和当前方法包装为 new Invocation(target, method, args)
最后来到了我们的拦截方法,最后如果是嵌套的代理一定要调用invocation.proceed();这样才会进入到我们的下一个Plugin的invoke()方法
补充:
Executor的代理在openSqlSession的时候