在使用mybatis的时候,我们可以自己指定plugin,在sql执行过程中,增加一些额外的逻辑处理,这篇笔记主要记录plugin的原理
使用
1、声明自定义的plugin
@Intercepts(@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class , RowBounds.class, ResultHandler.class}))
public class TestPlugin1 implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("测试插件1被执行");
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
在自定义的plugin插件中,我们需要通过注解,执行当前插件作用于哪个对象?
在mybatis底层,有四个比较重要的对象,分别是:executor、parameterHandler、ResultSetHandler、StatementHandler
2、在全局配置文件中,增加plugin节点的配置
<plugins>
<plugin interceptor="org.apache.ibatis.study.nativestu.TestPlugin1"></plugin>
<plugin interceptor="org.apache.ibatis.study.nativestu.TestPlugin2"></plugin>
</plugins>
这样即可,在sql被执行的时候,会被自定义的plugin拦截到,执行对应的intercept()方法
但是,在使用的时候,需要通过注解,指定当前插件对哪个类生效,在mybatis底层,有四个比较重要的类,分别是:executor、parameterHandler、ResultSetHandler、StatementHandler
需要声明当前插件是作用于哪个类的哪个方法,我上面指定的是executor的query方法,为什么是query方法,因为不管是selectList还是selectOne,底层都是调用的executor的query方法
源码
在mybatis中的plugin,其实就是拦截器,自然,也肯定是通过代理来实现的
解析plugin
我们先说解析的逻辑,在mybatis中,会解析全局配置文件,在解析全局配置文件的时候,会解析配置,将我们自定义的plugin,转换为interceptor对象存储
在前面博客中,mybatis源码:mybatis的sql解析 有提到过,原生的mybatis,是从SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 这里的build方法开始的,所有的解析逻辑,都会在这个方法中完成;
所以我们直接调到这个方法中
org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration
这个方法是mybatis在解析了节点之后,会依次解析其子节点的方法
在这个方法中,有一行和本篇博客有关的代码
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).newInstance();
interceptorInstance.setProperties(properties);
configuration.addInterceptor(interceptorInstance);
}
}
}
public void addInterceptor(Interceptor interceptor) {
interceptorChain.addInterceptor(interceptor);
}
org.apache.ibatis.plugin.InterceptorChain
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
private final List<Interceptor> interceptors = new ArrayList<>();
上面这个方法,入参的node节点,就是,可以看到,在方法中,会获取到期children节点,然后依次获取到对应的interceptor配置,也就是我们自定义的插件的全类目,然后根据全类名,通过newInstance()方法来初始化对象,然后把对象设置到了configuration的interceptor属性中,最终,会添加到一个list集合中
以上,就是mybatis在启动时,解析plugin的逻辑
使用
把所有的自定义插件,解析成interceptor,然后存入到list集合中,自然是为了使用
SqlSession sqlSession = sqlSessionFactory.openSession();
这是要讲的代码,在这里的openSession()方法中,会获取到一个sqlSession对象,在初始化对象的时候,会先初始化一个executor对象,
这里中间的代码,不是本篇博客要关心的,所以就跳过,调用链可以参考上面的视频,我们只关心初始化executor的这个方法
org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType)
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
// 默认的executor是SimpleExecutor
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);
}
//如果二级缓存开启,创建cachingExecutor,这里的executor会被包装在cachingExecutor中
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
/**
* 上面的逻辑是根据不同的executorType类型来初始化不同的执行器
* 下面是为executor生成动态代理对象
*/
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
这一段代码,就是根据当前指定的executorType初始化不同的对象,我们需要关心的,是倒数第二行代码
org.apache.ibatis.plugin.InterceptorChain
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
可以看到,这个方法就是从上面解析之后的list集合中,依次遍历,调用自定义插件的plugin()方法;
我们在自定义的plugin对应的plugin()方法中,调用的是Plugin.wrap()
所以,我们接着来看Plugin.wrap()的源码
public static Object wrap(Object target, Interceptor interceptor) {
//1.获取@Intercepts注解的配置信息
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
// 2.获取当前target的class(这里的target,是从前面入参进来的,就是mybatis的四大对象其中的一个)
Class<?> type = target.getClass();
// 3.判断注解配置的类中,是否包含当前入参的target
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
/**
* 4.如果包含,就生成代理对象
* 可以看到这里入参的invocation对象,就是plugin
*/
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
所以,这几段代码的逻辑,连起来看,是这样的:
- 在初始化executor对象之后,会判断是否需要给当前executor生成动态代理对象
- 判断的依据是什么?就是我们自定义的插件,是否有要作用于executor对象的
- 如果有,就依次通过jdk代理,生成代理对象
- 所以,如果我们自定义了插件,作用于executor对象,最后生成的executor对象是一个代理对象,而这个代理对象,是包了N层(这里的N层就是指有N对plugin要作用于executor对象)
在我们执行selectList查询的时候,依赖于executor对象的query方法,所以,会被代理对象所拦截到,就会执行我们自定义插件的interceptor()方法
结论
所以,对于自定义插件的原理,大致是这样的:
- 在解析全局配置文件的时候,会解析; 然后将自定义的插件,转换为interceptor对象,存到内存中,以list集合的形式存入
- 然后在初始化executor时,会对executor进行增强处理
- 根据自定义的插件相关配置,来判断是否要对executor进行增强,如果需要增强,会通过jdk动态代理生成代理对象
- 在执行sql的时候,会被代理对象拦截到,然后先执行对应的intercept方法