Mybatis插件源码分析(含示例)
对于mybatis插件首先得有一个使用上的认知,然后我们再去研究原理和源码会更加容易理解。
插件使用
网上有很多不错的博客,我摘取了一个给大家看看。感谢原博主。http://www.mybatis.cn/archives/81.html
概述
Mybatis插件又称拦截器,Mybatis采用责任链模式,通过动态代理组织多个插件(拦截器),通过这些插件可以改变Mybatis的默认行为(诸如SQL重写之类的),MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
总体概括为:
拦截执行器的方法
拦截参数的处理
拦截结果集的处理
拦截Sql语法构建的处理
Mybatis四大接口
既然Mybatis是对四大接口进行拦截的,那我们先要知道Mybatis的四大接口是哪些: Executor,StatementHandler, ResultSetHandler, ParameterHandler。
- Executor是 Mybatis的内部执行器,它负责调用StatementHandler操作数据库,并把结果集通ResultSetHandler进行自动映射,另外,它还处理了二级缓存的操作。从这里可以看出,我们也是可以通过插件来实现自定义的二级缓存的。
- StatementHandler是Mybatis直接和数据库执行sql脚本的对象。另外它也实现了Mybatis的一级缓存。这里,我们可以使用插件来实现对一级缓存的操作(禁用等等)。
- ParameterHandler是Mybatis实现Sql入参设置的对象。插件可以改变我们Sql的参数默认设置。
- ResultSetHandler是Mybatis把ResultSet集合映射成POJO的接口对象。我们可以定义插件对Mybatis的结果集自动映射进行修改。
设计实现一个自定义插件
- 需求
把Mybatis所有执行的sql都记录下来。
- 代码实现
通过对org.apache.ibatis.executor.statement.StatementHandler 中的prepare 方法进行拦截即可。
prepare 方法签名如下:
Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException;
自定义一个类,实现 org.apache.ibatis.pluginInterceptor 接口,代码如下:
package cn.mybatis.mybatis3.interceptor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.util.Properties;
@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class, Integer.class}) })
public class SQLStatsInterceptor implements Interceptor {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
BoundSql boundSql = statementHandler.getBoundSql();
String sql = boundSql.getSql();
logger.info("mybatis intercept sql:{}", sql);
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
String dialect = properties.getProperty("dialect");
logger.info("mybatis intercept dialect:{}", dialect);
}
}
-
mybatis-config.xml 文件中增加plugins节点
这样一个插件就开发完成了,接下来需要在 mybatis-config.xml 文件中增加plugins节点,完整配置如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<plugins>
<plugin interceptor="com.bytebeats.mybatis3.interceptor.SQLStatsInterceptor">
<property name="dialect" value="mysql" />
</plugin>
</plugins>
</configuration>
源码分析
源码分析我将从插件的初始化和插件运行2个方向来展开。将涉及到的几个核心类Plugin、Interceptor、Intercepts、Signature。
插件的初始化
回顾一下我们之前源码分析第一章的内容,文章中提到了Configuration的初始化过程
org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream)
org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream, java.lang.String, java.util.Properties)
org.apache.ibatis.builder.xml.XMLConfigBuilder#parse()
org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration()
private void parseConfiguration(XNode root) {
try {
// issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
//插件初始化
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
这段代码肯定很熟悉,之前我们多次讲到过,“数据源、Mapper、缓存”的初始化都是在这里。这次我们主要看pluginElement(root.evalNode("plugins"));
,
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).getDeclaredConstructor().newInstance();
interceptorInstance.setProperties(properties);
configuration.addInterceptor(interceptorInstance);
}
}
}
<plugins>
<plugin interceptor="com.naiqing.mybatis.interceptor.SQLStatsInterceptor">
</plugin>
</plugins>
可以看出来java代码,循环所有的标签,解析其中的interceptor
类名通过反射方法Class.getDeclaredConstructor().newInstance()
实现对象的创建初始化。
所有的插件也被初始到Configration.interceptorChain
中了,InterceptorChain
我们来看下实现。
org.apache.ibatis.plugin.InterceptorChain
package org.apache.ibatis.plugin;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @author Clinton Begin
*/
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<>();
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}
InterceptorChain
的实现很简单,之所以把源码贴出来,是为了让你注意下里面的pluginAll(Object target)
方法。它是为了将target通过代理模式串联所有插件。这个方法后面会有4次调用,分别对应mybatis可扩展的4个类。
插件加载
我们以更新update操作为例,插件加载
- Executor加载所有插件
SqlSession session = sqlSessionFactory.openSession()
org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSession()
org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource()
org.apache.ibatis.session.Configuration#newExecutor()
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor 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 (cacheEnabled) {
executor = new CachingExecutor(executor);
}
//Executor加载所有插件
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
- StatementHandler加载所有插件
org.apache.ibatis.executor.BatchExecutor#doUpdate()
org.apache.ibatis.session.Configuration#newStatementHandler()
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 = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
- ResultSetHandler加载所有插件
org.apache.ibatis.executor.BatchExecutor#doUpdate()
org.apache.ibatis.session.Configuration#newStatementHandler()
org.apache.ibatis.executor.statement.RoutingStatementHandler#RoutingStatementHandler()
org.apache.ibatis.executor.statement.SimpleStatementHandler#SimpleStatementHandler()
org.apache.ibatis.executor.statement.BaseStatementHandler#BaseStatementHandler()
org.apache.ibatis.session.Configuration#newParameterHandler()
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 = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
- ParameterHandler加载所有插件
org.apache.ibatis.executor.BatchExecutor#doUpdate()
org.apache.ibatis.session.Configuration#newStatementHandler()
org.apache.ibatis.executor.statement.RoutingStatementHandler#RoutingStatementHandler()
org.apache.ibatis.executor.statement.SimpleStatementHandler#SimpleStatementHandler()
org.apache.ibatis.executor.statement.BaseStatementHandler#BaseStatementHandler()
org.apache.ibatis.session.Configuration#newResultSetHandler()
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 = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
通过上述的4段代码,我们看到了都是通过pluginAll()的方法将插件关联起来。之前我们看过InterceptorChain类的全部源码,其中pluginAll()的方法是遍历了所有的Interceptor,调用其plugin方法。我们来看下plugin方法的实现。其实这里就对应了我们自己扩展插件的实现了,但这个方法基本差不多,看下具体实现
com.naiqing.mybatis.interceptor.SQLStatsInterceptor
com.naiqing.mybatis.interceptor.SQLStatsInterceptor#plugin()
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
我们再来看下
org.apache.ibatis.plugin.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);
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
//代理类
new Plugin(target, interceptor, signatureMap));
}
return target;
}
到这里我们可以看到,就是使用的jdk动态代理方式实现了插件的扩展。
最终达到的效果简单模拟就是
Executor= ...<plugin3 <plugin2 <plugin1 <Executor>>>>
StatementHandler= ...<plugin3 <plugin2 <plugin1 <StatementHandler>>>>
ResultSetHandler= ...<plugin3 <plugin2 <plugin1 <ResultSetHandler>>>>
ParameterHandler= ...<plugin3 <plugin2 <plugin1 <ParameterHandler>>>>
插件的加载就像洋葱一层一层包裹着原类。
我们可以看出来,在插件加载的时候是不区分的一股脑的全部加载上来,也就是说一个插件定义出来以后会被Executor、StatementHandler、ResultSetHandler、ParameterHandler同时加载。但一般情况下,我们都只是扩展其中一个类的某个方法,他是在哪里实现的呢?我们继续向下看。
Plugin类
这个类是Mybatis插件的核心实现类,把他看明白了你就知道插件是实现原理了。
下面是plugin.java的源码(去掉了部分不关心的代码)
package org.apache.ibatis.plugin;
/**
* @author Clinton Begin
*/
public class Plugin implements InvocationHandler {
private final Object target;
private final Interceptor interceptor;
private final Map<Class<?>, Set<Method>> signatureMap;
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,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//根据当前类取到对应的自定义插件中的扩展类名
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
//判断当前执行的方法与自定义插件中需要扩展的方法相同。
if (methods != null && methods.contains(method)) {
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
/**
* 得到自定义插件类的需要扩展的类和其方法
**/
private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
//拿到自定义插件的Intercepts注解
Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
...
//得到签名类信息(即需要扩展的类,也就是我们之前所说的Executor、StatementHandler、ResultSetHandler、ParameterHandler)
Signature[] sigs = interceptsAnnotation.value();
Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
for (Signature sig : sigs)
//扩展类对应的方法名
Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
try {
Method method = sig.type().getMethod(sig.method(), sig.args());
methods.add(method);
} catch (NoSuchMethodException e) {
...
}
}
return signatureMap;
}
}
wrap()
首先我们来看下之前已经看过的方法wrap()
之所以要再看一遍这个方法,是因为上面我们只讲到了这个方法完成代理的作用,但没有介绍到另一个重要作用。
//得到自定义插件类的需要扩展的类和其方法
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
signatureMap
作为一个plugin类的重要成员属性,作用就是装有自定义插件(即上面示例的SQLStatsInterceptor类)需要扩展的类和方法。在这里完成了初始化。
getSignatureMap()
org.apache.ibatis.plugin.Plugin#getSignatureMap()
- 拿到自定义插件的Intercepts注解。
interceptor.getClass().getAnnotation(Intercepts.class);
- 得到签名类信息(即需要扩展的类,也就是我们之前所说的Executor、StatementHandler、ResultSetHandler、ParameterHandler)。
Signature[] sigs = interceptsAnnotation.value()
- 得到方法名。
signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
我们再来对照之前编写的示例代码SQLStatsInterceptor.java
@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class, Integer.class}) })
最终形成了一个需要扩展类方法的map,以扩展类Class(示例StatementHandler.class)为key,Set(示例prepare)为value。
invoke()
熟悉jdk动态代理的话,这个方法你应该不陌生。所有的被代理的类执行任意方法时都会先执行invoke方法。
根据我们上面的总结,在插件加载的时候,并不区分扩展类,但一般情况下,我们都只是扩展其中一个类的某个方法,他是在哪里实现的呢?
答案就在这个里。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//根据当前类取到对应的自定义插件中的扩展类名
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
//判断当前执行的方法与自定义插件中需要扩展的方法相同。
if (methods != null && methods.contains(method)) {
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
通过getSignatureMap()方法得到扩展类的方法map的signatureMap
,当每一次调用invoke方法的时候都会试图根据类名取到扩展方法(如果没取到,说明此方法不需要扩展),然后判断当前方法是否为需要扩展的方法,如果是则调用interceptor.intercept(),传入了target当前类,method方法,args参数。而intercept()是自定义的插件所具体的实现了。我们之前示例里面是这样的:
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
BoundSql boundSql = statementHandler.getBoundSql();
String sql = boundSql.getSql();
System.out.println("mybatis intercept sql:"+sql);
return invocation.proceed();
}
Invocation类
这个类就是用于传递代理类向我们自定义插件之间传递参数的,类本身很简单。只有3个属性,target当前被代理的类、method方法、args参数。这里我们主要是想说明下invocation.proceed()
。
public Object proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target, args);
}
这个方法的作用就是让我们的代理继续向下执行,比如说之前我们提到的代理关系
StatementHandler= ...<plugin3 <plugin2 <plugin1 <StatementHandler>>>>
当我们调用需要扩展的方法如示例中的StatementHandler.prepare()
方法时,首先是plugin3.invoke()
,执行完成以后调用invocation.proceed()
会传递到plugin2.invoke()
,一直向下传最终调用StatementHandler.prepare()
方法完成全部插件的调用。
插件流程图
插件初始化过程流程图
插件调用流程图
以Executor插件为例