通过上一篇博客《深入 Mybatis 插件机制》了解了插件原理,并且 Mybatis 所有插件都保存在 Configuration
的 interceptorChain
属性中,在 Configuration
中提供了注册插件方法 addInterceptor(Interceptor interceptor)
将插件添加到 interceptorChain
,那用户怎么向 Configuration
注册插件呢?
结合源码,本文分享了四种注册 Mybatis 插件方式:
- 直接注册到 Spring IOC 容器;
- 通过定制化器
ConfigurationCustomizer
定制Configuration
; - 通过 Mybatis 配置文件注册;
- 曲线救国 —— 通过注入 SqlSessionFactory 获取 Configuration 注册;
一、直接注册到 Spring IOC 容器
要向 Configuration
注册插件,就需要了解 Configuration
保存在哪里,创建时机等。
在 mybatis-spring-boot-autoconfigure
Jar 包的 spring.factories
文件中配置了 Mybatis 自动配置类 MybatisAutoConfiguration
,在 MybatisAutoConfiguration
中,Mybatis 向 Spring IOC 注册了 SqlSessionFactory
, SqlSessionTemplate
等几个重要 Bean。 SqlSessionFactory
中就包含 Configuration
实例,并且在创建 SqlSessionTemplate
最主要的工作就是初始化 Configuration
。
MybatisAutoConfiguration
构造函数:
@Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnBean({DataSource.class})
@EnableConfigurationProperties({MybatisProperties.class})
@AutoConfigureAfter({DataSourceAutoConfiguration.class})
public class MybatisAutoConfiguration {
private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class);
private final MybatisProperties properties;
private final Interceptor[] interceptors;
private final ResourceLoader resourceLoader;
private final DatabaseIdProvider databaseIdProvider;
private final List<ConfigurationCustomizer> configurationCustomizers;
public MybatisAutoConfiguration(MybatisProperties properties, ObjectProvider<Interceptor[]> interceptorsProvider, ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider, ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) {
this.properties = properties;
this.interceptors = (Interceptor[])interceptorsProvider.getIfAvailable();
this.resourceLoader = resourceLoader;
this.databaseIdProvider = (DatabaseIdProvider)databaseIdProvider.getIfAvailable();
this.configurationCustomizers = (List)configurationCustomizersProvider.getIfAvailable();
}
// ...
}
在 MybatisAutoConfiguration
构造函数中,会把 Spring IOC 容器中的 Mybatis
插件注入到属性 interceptors
中,在创建 SqlSessionFactory
并初始化 Configuration
时会用到该属性。
在构造 SqlSessionFactory
方法 sqlSessionFactory(DataSource dataSource)
内部,会将构造函数注入的这些属性设置到 SqlSessionFactoryBean
中,其中包括将属性 interceptors
设置到SqlSessionFactoryBean
中, 最后再调用 SqlSessionFactoryBean
的 getObject()
生成 SqlSessionFactory
实例:
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
// ...
if (!ObjectUtils.isEmpty(this.interceptors)) {
factory.setPlugins(this.interceptors);
}
// ...
return factory.getObject();
}
SqlSessionFactoryBean
类 getObject()
函数内部会调用 afterPropertiesSet()
函数:
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
afterPropertiesSet()
函数会调用 buildSqlSessionFactory()
函数构建 SqlSessionFactory
:
@Override
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
"Property 'configuration' and 'configLocation' can not specified with together");
this.sqlSessionFactory = buildSqlSessionFactory();
}
在 afterPropertiesSet() 方法内,会将刚才设置到 plugins
属性的插件注册到 Configuration
中:
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
if (!isEmpty(this.plugins)) {
for (Interceptor plugin : this.plugins) {
configuration.addInterceptor(plugin);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registered plugin: '" + plugin + "'");
}
}
}
}
所以,这里提供了一种向 Configuration
注册插件方法 —— 直接将插件注册到 Spring IOC 容器中。
例如:
@Bean
public PageHelper pageHelper(){
Properties properties = new Properties();
properties.setProperty("helperDialect", "mysql");
PageHelper pageHelper = new PageHelper();
pageHelper.setProperties(properties);
return pageHelper;
}
这种方式注册插件大致流程如下图:
二、通过定制化器 ConfigurationCustomizer 定制 Configuration
在 Spring 中,比较重要类一般会提供定制化器 xxxCustomizer
供用户定制,比如 Jackson 提供 Jackson2ObjectMapperBuilderCustomizer
定制 Jackson 序列化等功能。同样 Configuration
也提供定制化接口 ConfigurationCustomizer
:
public interface ConfigurationCustomizer {
/**
* Customize the given a {@link Configuration} object.
* @param configuration the configuration object to customize
*/
void customize(Configuration configuration);
}
ConfigurationCustomizer
接口只包含方法 customize(Configuration configuration)
,用户可以拿到 Configuration
参数,以对其实现定制操作。
在 MybatisAutoConfiguration
类的 sqlSessionFactory(DataSource dataSource)
方法内会回调所有 ConfigurationCustomizer
接口的customize(Configuration configuration)
方法:
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
// ...
if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
customizer.customize(configuration);
}
}
// ...
}
例如,注册分页插件,也可以通过如下方式注册:
@Bean
ConfigurationCustomizer mybatisConfigurationCustomizer() {
return new ConfigurationCustomizer() {
@Override
public void customize(org.apache.ibatis.session.Configuration configuration) {
Properties properties = new Properties();
properties.setProperty("helperDialect", "mysql");
PageHelper pageHelper = new PageHelper();
pageHelper.setProperties(properties);
configuration.addInterceptor(pageHelper);
}
};
}
三、通过 Mybatis 配置文件注册
这种方式也是最常见的一种注册 Mybatis 插件方式,通过这种方式注册首先在 Mybatis 配置文件 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.github.pagehelper.PageHelper">
<property name="dialect" value="mysql"/>
</plugin>
</plugins>
</configuration>
然后在 Spring 配置文件 application.xml
文件中通过参数 mybatis.config-location
告诉 Spring Mybatis 配置文件地址:
mybatis:
mapper-locations: classpath:static/mybatis/*.xml
config-location: classpath:mybatis-config.xml
在 SqlSessionFactoryBean
类 buildSqlSessionFactory()
方法构建 SqlSessionFactory
时会解析配置文件 mybatis.config-location
,并将解析到的插件注册到 Configuration
中。
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
// ...
if (this.configuration != null) {
// ...
} else if (this.configLocation != null) {
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
configuration = xmlConfigBuilder.getConfiguration();
}
// ...
if (xmlConfigBuilder != null) {
try {
xmlConfigBuilder.parse();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Parsed configuration file: '" + this.configLocation + "'");
}
} catch (Exception ex) {
throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
} finally {
ErrorContext.instance().reset();
}
}
// ...
}
四、曲线救国 —— 通过注入 SqlSessionFactory 获取 Configuration 注册
要注册插件,首先要获取 Configuration
,而 Configuration
实例有存在于 SqlSessionFactory
,但是 SqlSessionFactory
是被 Spring IOC 容器管理的。那这不就好办了吗,可以在任何 Spring 管理的 Bean 中注入 SqlSessionFactory
实例,再获取其 configuration
,然后注册插件不就可以了吗,但是这种方式不便于管理 Mybatis 插件,因为很难知道某个插件在哪个 Bean 中注册的。
@Configuration
public class InterceptorConfig implements InitializingBean {
private final SqlSessionFactory sqlSessionFactory;
public InterceptorConfig(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
}
@Override
public void afterPropertiesSet() throws Exception {
org.apache.ibatis.session.Configuration configuration = sqlSessionFactory.getConfiguration();
Properties properties = new Properties();
properties.setProperty("helperDialect", "mysql");
PageHelper pageHelper = new PageHelper();
pageHelper.setProperties(properties);
configuration.addInterceptor(pageHelper);
}
}