从源码角度探索 Mybatis 插件注册方式

通过上一篇博客《深入 Mybatis 插件机制》了解了插件原理,并且 Mybatis 所有插件都保存在 ConfigurationinterceptorChain 属性中,在 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 注册了 SqlSessionFactorySqlSessionTemplate 等几个重要 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 中, 最后再调用 SqlSessionFactoryBeangetObject() 生成 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();
}

SqlSessionFactoryBeangetObject() 函数内部会调用 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.xmlplugins 标签中指定插件,例如:

<?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

SqlSessionFactoryBeanbuildSqlSessionFactory() 方法构建 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);
    }
}
  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值