# MyBatis源码-插件解析
这一节我们来看看 MyBatis 中的插件是如何配置及源码的实现
# 如何配置一个自定义插件
配置一个 mybatis 自定义插件,一共需要2步,实现 Interceptor 接口 + 在xml 文件把插件配置进去,下边看下具体配置代码:
//实现Interceptor 接口
//配置注解 表明需要拦截的类及拦截的方法
@Intercepts({
@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
public class LogInterceptor implements Interceptor {
private String info;
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("LogInterceptor");
// 执行原有方法
Object result = invocation.proceed();
// 打印原方法输出结果的数目
System.out.println(info + ":" + ((List) result).size());
// 返回原有结果
return result;
}
@Override
public void setProperties(Properties properties) {
// 为拦截器设置属性
info = properties.get("preInfo").toString();
}
}
xml配置:
<plugins>
<plugin interceptor="com.github.yeecode.mybatisdemo.plugin.LogInterceptor1">
<property name="preInfo" value="LogInterceptor1 本次查询记录数目"/>
</plugin>
<plugin interceptor="com.github.yeecode.mybatisdemo.plugin.LogInterceptor2">
<property name="preInfo" value="LogInterceptor2 本次查询记录数目 : "/>
</plugin>
</plugins>
通过上面2 步就简单的配置了一个插件,可以拦截 mybatis 方法执行前后,然后加上自己的业务代码就可以了。
# mybatis 插件的拦截方法
不是所有 mybatis 的类都支持配置插件形式进行拦截,mybatis 有4大接口分别为:
1.Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) MyBatis的执行器,用于执行增删改查操作;
2.ParameterHandler (getParameterObject, setParameters) 处理SQL的参数对象;
3.ResultSetHandler (handleResultSets, handleOutputParameters) 处理SQL的返回结果集;
4.StatementHandler (prepare, parameterize, batch, update, query) 拦截Sql语法构建的处理
//type 就是插件拦截的类
//method 就是插件拦截的类的方法
//args 就是方法的参数
@Intercepts({
@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
# 插件的原理
系统在启动的时候,mybatis 会解析 xml 文件里边的 插件节点,然后把插件对应的类添加到拦截器链中
//XMLConfigBuilder 类
private void parseConfiguration(XNode root) {
try {
...
pluginElement(root.evalNode("plugins"));
...
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
String interceptor = child.getStringAttribute("interceptor");
Properties properties = child.getChildrenAsProperties();
//反射创建对象
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
//获取配置的插件参数
interceptorInstance.setProperties(properties);
//添加到拦截器链中 自然而然肯定可以添加多个插件
configuration.addInterceptor(interceptorInstance);
}
}
}
好了,上面做的一切就是把插件初始化到系统中,完事具备只待某个节点来触发插件了,下面继续
时光流转,让我们来到 mybatis 的核心配置类来看看
//Configuration 类
//看了下面代码,应该似曾相识,没错就是前面说到过的 插件的拦截方法
//主要是4个类,每个基本上实现都大同小异,只标注这一个方法,其它方法也一样
//参数处理
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
//拿到参数处理器,通过 pluginAll 获取到一个代理对象,注意这里返回的是代理对象!!!
//如果是多个拦截器,会层层调用形式一个链条
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
//结果集处理
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
//sql语句处理
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
//增删改查处理
public Executor newExecutor(Transaction transaction) {
return newExecutor(transaction, defaultExecutorType);
}
//InterceptorChain类
public Object pluginAll(Object target) {
//多个插件依次遍历, target最终就是类似这样的结果,形成了一个链条如:插件1->插件2->插件3
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
//Interceptor 类
default Object plugin(Object target) {
//返回一个代理
return Plugin.wrap(target, this);
}
//Plugin 代理类
//这个就是生成插件代理对象的最终代码!!!
public static Object wrap(Object target, Interceptor interceptor) {
//查找对象上面是否有插件的注解
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
//有注解才会生成代理对象
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
//target 上一个插件对象 interceptor 当前插件的对象
new Plugin(target, interceptor, signatureMap));
}
return target;
}
上面代码主要做了:
- 解析配置文件插件节点,然后反射生成对象;
- 在 mybatis 操作数据库的前后,可以插入我们定义的插件代理对象;
- 如果是多个插件,会形式一个代理链条
就拿上面我们配置的插件来说,那是一个对查询结果处理的插件,触发时机在这里:
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
//通过上面代码,sql语句在数据库已经执行完成,返回了相应的结果集。resultSetHandler其实是一个代理,执行handleResultSets的时候调用的是代理 invoke 方法
return resultSetHandler.handleResultSets(ps);
}
上面配置的插件,最后执行结果为:
LogInterceptor2
LogInterceptor1
LogInterceptor1 本次查询记录数目:4
LogInterceptor2 本次查询记录数目:4
先执行到 LogInterceptor2
插件,然后调用 invocation.proceed()
会调用 LogInterceptor1
,然后 LogInterceptor1
执行完返回结果,LogInterceptor2
拿到结果,执行结束。
mybatis 的插件,其实就是一个责任链设计模式,通过对方法拦截然后做层层处理,前提是在 mybats 暴露的4大类中,才能实现插件效果,同理如果设置的插件越多,链条执行就越长,执行时间也就越久。
参考文章:
1.https://www.cnblogs.com/qdhxhz/p/11390778.html
2.https://www.jianshu.com/p/b82d0a95b2f3
3.https://www.rewind.show/2022/12/06/%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB/MyBatis%E6%BA%90%E7%A0%81/MyBatis%E6%BA%90%E7%A0%81%EF%BC%884%EF%BC%89-%E6%8F%92%E4%BB%B6%E6%9C%BA%E5%88%B6/