[MyBatis学习笔记] 四、Mybatis插件
一、Mybatis插件简介
Mybatis
的四大组件Executor
、StatementHandler
、ParameterHandler
、ResultSetHandler
均提供了插件扩展机制,供开发者对组件功能进行扩展。Mybatis
支持用插件对上述四大核心对象进行拦截,以增强核心对象的功能,其本质上是借助动态代理实现的。
默认情况下,MyBatis 允许使用插件来拦截的方法调用包括1:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
二、Mybatis插件原理
四大核心对象在创建时,创建的对象并不会直接返回,而是通过InterceptorChain#pluginAll(Object target)
方法进行了处理,在该方法中,又会调用每个拦截器(自定义插件)的plugin
方法(Interceptor#plugin(Object target)
)对该target对象进行逐一增强处理。
三、简单的源码分析
自定义插件的相关属性可在mybatis-config.xml
文件中配置:
插件类ExamplePlugin
如下:
由于plugins
标签在mybatis-config.xml
中配置,因此本文会以XMLConfigBuilder.java
作为入口来进行分析。
1、起点: XMLConfigBuilder
起步顺序为:XMLConfigBuilder#parse()
-> XMLConfigBuilder#parseConfiguration(XNode)
-> XMLConfigBuilder#pluginElement(XNode)
,这里以pluginElement
方法作为开始:
XMLConfigBuilder#pluginElement
:
2、Configuration
Configuration#addInterceptor
:
可见,创建出的拦截器对象放入了Configuration
类的interceptorChain
属性中。
进入InterceptorChain
的addInterceptor
方法:
InterceptorChain#addInterceptor
:
再次回到Configuration
类中,查看interceptorChain
属性的使用情况:
Configuration# newParameterHandler(MappedStatement, Object, BoundSql)
:
Configuration#newResultSetHandler()
:
Configuration#newStatementHandler()
:
Configuration#newExecutor
:
上述四个方法中,并未直接返回创建好的相应对象,而是首先调用了InterceptorChain
中的pluginAll
方法,再将其返回值作为最终返回值。
3、InterceptorChain
那么,在InterceptorChain#pluginAll(Object)
中,发生了什么呢:
InterceptorChain#pluginAll
:
4、Interceptor
Interceptor#plugin
:
使用jdk
动态代理创建对象:
Plugin#wrap
:
Plugin
类实现了InvocationHandler
接口,其invoke
方法如下:
最后,可以在Invocation
类中的proceed
方法执行之前或之后,来执行相关的增强逻辑:
四、简单示例
1、DemoPlugin
@Intercepts({
@Signature(
type = StatementHandler.class,
method = "prepare",
args = {Connection.class, Integer.class}
)
})
public class DemoPlugin implements Interceptor {
/**
* 拦截方法:只要被拦截的目标对象的目标方法被执行时,都会执行intercept方法
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("对方法进行了增强, 开始...");
Object proceed = invocation.proceed();
System.out.println("对方法进行了增强, 结束!");
return proceed;
}
/**
* 主要为了把当前的拦截器生成代理存到拦截器链中
*/
@Override
public Object plugin(Object target) {
System.out.println("生成了代理对象");
return Plugin.wrap(target, this);
}
/**
* 获取配置文件的参数
*/
@Override
public void setProperties(Properties properties) {
System.out.println("获取到的配置文件参数是: " + properties);
}
}
2、mybatis-config.xml
<plugins>
<plugin interceptor="com.gavin11.study.mybatis.plugin.DemoPlugin" >
<property name="name" value="gavin"/>
<property name="gender" value="male"/>
</plugin>
</plugins>
3、测试程序
public class Test {
public static void main(String[] args) throws Exception {
SqlSessionFactory sqlSessionFactory;
try (
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
InputStreamReader isr = new InputStreamReader(Objects.requireNonNull(in), StandardCharsets.UTF_8)
) {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(isr);
}
try (
SqlSession session = sqlSessionFactory.openSession()
) {
UserMapper userMapper = session.getMapper(UserMapper.class);
User user1 = userMapper.findByIdNameAnn(new User(1, "lucy", null, null));
System.out.println(user1);
}
}
}
4、运行结果
获取到的配置文件参数是: {name=gavin, gender=male}
生成了代理对象
生成了代理对象
生成了代理对象
生成了代理对象
对方法进行了增强, 开始...
对方法进行了增强, 结束!
User{id=1, username='lucy', password='123', birthday='2019-12-12', orders=null, roles=null}
参考: