源起
上篇我们讲到了MyBatis与SpringBoot的集成篇(一)—demo案例,在案例最后我们留下了一个疑问:mybatis-spring-boot-starter
是何方神圣,功能是什么?为什么我们依赖了这个starter
之后,开发变得简化了许多?
初识mybatis-spring-boot-starter
- 该章节内容总结自官网
- 官网传送门
- The MyBatis-Spring-Boot-Starter help you build quickly MyBatis applications on top of the Spring Boot.
By using this module you will achieve:
- Build standalone applications
- Reduce the boilerplate to almost zero
- Less XML configuration
- 上面的意思就是说:
MyBatis-Spring-Boot-Starter
能够帮助你快速的在SpringBoot的基础上构建一个MyBatis应用,简化你的xml配置等等; - MyBatis-Spring-Boot-Starter will:
- Autodetect an existing
DataSource
- Will create and register an instance of a
SqlSessionFactory
passing thatDataSource
as an input using theSqlSessionFactoryBean
- Will create and register an instance of a
SqlSessionTemplate
got out of theSqlSessionFactory
- Auto-scan your mappers, link them to the
SqlSessionTemplate
and register them to Spring context so they can be injected into your beans
- MyBatis-Spring-Boot-Starter将:
- 自动检测现有的
数据源
- 将创建并注册一个实例
SqlSessionFactory
是通过数据源
作为输入使用SqlSessionFactoryBean
- 将创建并注册从
SqlSessionFactory
中获取的SqlSessionTemplate的实例
- 自动扫描映射器,将它们链接到
SqlSessionTemplate
并将它们注册到Spring上下文,以便可以将它们注入到您的bean中
- 通过上面的简介描述,我们不禁好奇,MyBatis-Spring-Boot-Starter是如何实现自动检测数据源,并构建
SqlSessionFactory
的,进而获取到SqlSession操作数据的;带着这个疑问,我们来看一下MyBatis-Spring-Boot-Starter的源码 - 接下来我们从从github上将MyBatis-Spring-Boot-Starter的源码down下来。
源码探究
-
mybatis-spring-boot-starter
既然能够无缝连接SpringBoot
,那么我们猜想,他肯定是将自己融入与Spring的环境之中,一个是有一个入口,或者说是某一种加载机制,能让Spring去管理mybatis-spring-boot-starter
,对此,我们可以联想到SPI
。
Java SPI
机制
- 首先我们需要简单了解一下
SPI
机制,SPI(Service Provider Interface)
是Java语言中的一种服务发现机制;JDK
提供的服务发现类java.util.ServiceLoader
,里面约定了服务发现规则:
public final class ServiceLoader<S> implements Iterable<S>
{
private static final String PREFIX = "META-INF/services/";
// The class or interface representing the service being loaded
private final Class<S> service;
// The class loader used to locate, load, and instantiate providers
private final ClassLoader loader;
// The access control context taken when the ServiceLoader is created
private final AccessControlContext acc;
// Cached providers, in instantiation order
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// The current lazy-lookup iterator
private LazyIterator lookupIterator;
//****以下省略****
}
- 基于以上约定,当供应厂商提供了一个服务实现的时候,则需要在jar包的
META-INF/services/
目录里同时创建一个以服务接口命名的文件,文件内容即为实现类。比如:java.sql.Driver
的Mysql
服务实现,其中Driver
是JDK
中提供的数据库驱动接口规范;
Spring SPI
机制
- 基于
java
的这种SPI思想,Spring制定了自己的SPI规范,org.springframework.core.io.support.SpringFactoriesLoader
public final class SpringFactoriesLoader {
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap();
private SpringFactoriesLoader() {
}
//****以下省略****
}
- 这种自定义的SPI机制是Spring Boot Starter实现的基础。
mybatis-spring-boot-starter
- 通过源码,我们看到
mybatis-spring-boot-starter
中是没有任何实现代码的,它是依赖了mybatis-spring-boot-autoconfigure
的,所以这里我们直接看mybatis-spring-boot-autoconfigure
中的META-INF/spring.factories
,如图:
- 这里我们着重看
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
这个类,当SpringBoot启动的时候会自动扫描加载spring.factories
文件,进而加载MybatisAutoConfiguration
;
MybatisAutoConfiguration
- 接下来我们看一下
MyBatisAutoConfiguration
的源码
- 首先该类实现了
org.springframework.beans.factory.InitializingBean
接口, - InitializingBean接口为bean提供了初始化方法,凡是实现该接口的类,在初始化bean的时候会自动执行
afterPropertiesSet
方法。如下是MyBatisAutoConfiguration
中实现的方法,执行了properties
的校验,判断配置信息是否存在;
@Override
public void afterPropertiesSet() {
checkConfigFileExists();
}
private void checkConfigFileExists() {
if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) {
Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation());
Assert.state(resource.exists(),
"Cannot find config location: " + resource + " (please add config file or check your Mybatis configuration)");
}
}
- 此前我们分析过MyBatis初始化及会话工厂类、Sql执行过程等的源码,未看过的童鞋可查看下博主之前的博文;
- 通过源码分析和使用的了解,我们知道每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的;所以
MyBatisAutoConfiguration
中必然有构建SqlSessionFactory
的操作,我们翻看源码,果不其然:
@Bean
//如果不存在时创建SqlSessionFactory
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setVfs(SpringBootVFS.class);
if (StringUtils.hasText(this.properties.getConfigLocation())) {
factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
}
applyConfiguration(factory);
if (this.properties.getConfigurationProperties() != null) {
factory.setConfigurationProperties(this.properties.getConfigurationProperties());
}
if (!ObjectUtils.isEmpty(this.interceptors)) {
factory.setPlugins(this.interceptors);
}
if (this.databaseIdProvider != null) {
factory.setDatabaseIdProvider(this.databaseIdProvider);
}
if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
}
if (this.properties.getTypeAliasesSuperType() != null) {
factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());
}
if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
}
if (!ObjectUtils.isEmpty(this.typeHandlers)) {
factory.setTypeHandlers(this.typeHandlers);
}
if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
factory.setMapperLocations(this.properties.resolveMapperLocations());
}
Set<String> factoryPropertyNames = Stream
.of(new BeanWrapperImpl(SqlSessionFactoryBean.class).getPropertyDescriptors()).map(PropertyDescriptor::getName)
.collect(Collectors.toSet());
Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
if (factoryPropertyNames.contains("scriptingLanguageDrivers") && !ObjectUtils.isEmpty(this.languageDrivers)) {
// Need to mybatis-spring 2.0.2+
factory.setScriptingLanguageDrivers(this.languageDrivers);
if (defaultLanguageDriver == null && this.languageDrivers.length == 1) {
defaultLanguageDriver = this.languageDrivers[0].getClass();
}
}
if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) {
// Need to mybatis-spring 2.0.2+
factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver);
}
return factory.getObject();
}
- 因为有
@Bean
注解的存在,所以Spring IoC
容器在初始化的时候会加载执行该方法,从而构建出会话工厂SqlSessionFactory
,既然有了SqlSessionFactory
,顾名思义,我们可以从中获得SqlSession
的实例。SqlSession
提供了在数据库执行SQL
命令所需的所有方法。我们可以通过SqlSession
实例来直接执行已映射的SQL
语句; - 在
Spring
中对于SqlSession
封装了一层:SqlSessionTemplate
,SqlSessionTemplate
是线程安全的,所以一个实例可以被所有dao共享;
总结
- 阅读到此,相信你对
mybatis-spring-boot-starter
已经有了一定的了解了,他是一个使用Spring
提供的扩展机制,封装了一些通用配置及操作,与SpringBoot
结合,可以实现“即插即用”,降低了耦合度,简化开发,让程序员更专注与业务; -
Spring
号称粘合剂,能够很好的与各种插件进行集成开发,对此,很有很多东西值得我们去学习; - 此处我只是对
mybatis-spring-boot-starter
简单的做了剖析,不足之处,还望指正。