前文:在Mybatis源码解析(一)(二)(三)中,我们对mybatis如何解析配置信息,以及内部源码进行了解析,和如何利用spring框架的扩展点进行整合。那么,SpringBoot的自动配置的优势,简化了我们开发人员在整套系统上的配置。那么,本篇解析我们就来介绍下Mybatis整合Springboot的流程。还是按照往常的案例,我们先来介绍几个类和注解的使用,这里只做简单介绍,详细介绍会在Spring框架底层专题中进行讲解。
一、介绍类和注解的作用。
1、MybatisAutoConfiguration 。Mybatis利用SpringBoot的通过spring.factories注册一个配置类。这个类配置了Mybatis的一些配置信息。
2、@ConditionalOnClass({ xxxx.class })。这个是springboot自动配置类的注解信息,意思是当项目中后后面这个class时,spring才会加载下面的这个类。
3、@ConditionalOnBean(xxxx.class)。这个是springboot自动配置类的注解信息,意思是当项目IOC容器中有后面的类的bean时,spring才会加载下面的这个类。
4、@EnableConfigurationProperties(MybatisProperties.class)。这个是springboot自动配置类的注解信息,意思是让MybatisProperties这个类的配置文件在spring环境下生效。
5、@AutoConfigureAfter({ DataSourceAutoConfiguration.class})。这个注解是说在后面的类加载完之后在进行对标志这个注解的类进行加载进spring容器中。
6、SqlSessionFactory和SqlSessionFactoryBean和SqlSessionTemplate就不做将解了,前三篇都有解析。
7、AutoConfiguredMapperScannerRegistrar。这个类时如何我们的项目没有标注@MapperScan(),那么我们的项目就会利用@Import()注解注入这个类,对标记了@Mapper这个注解的类进行扫描加载,生成MapperFactoryBean代理对象。
二、MyBatis和SpringBoot集成的流程。
1、我们首先对starter进行分析。
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
2、mybatis-spring-boot-starter本身没有提供Jar包和Class文件,只有引入的依赖,我们重点说几个。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
</dependency>
</dependencies>
- 这个包主要会引入mybatis、mybatis-spring以及mybatis-spring-boot-autoconfigure(其他的不是关键),因此MyBatis和SpringBoor集成的关键是mybatis-spring-boot-autoconfigure 这个包;
- 从下面的截图看出,mybatis-spring-boot-autoconfigure本身比较简单,它通过spring.factories注册一个配置类,本身也就两三个类文件,关键还是在 MybatisAutoConfiguration 这个配置类。只要SpringBoot项目开启了
@EnableAutoConfiguration ,那么该配置文件就会自动装配,而SpringBoot的启动注解
@SpringBootApplication 就默认启用了 @EnableAutoConfiguration;
3、那我们就来讲一下这个Mybatis的自动配置类。MybatisAutoConfiguration 是MyBatis和SpringBoot集成的关键。
/**
* MyBatis的自动配置,提供 SqlSessionFactory 和 SqlSessionTemplate
*
* {@link EnableAutoConfiguration Auto-Configuration} for Mybatis. Contributes a
* {@link SqlSessionFactory} and a {@link SqlSessionTemplate}.
*
* 如果使用了MapperScan或者配置文件配置了接口的包,就会使用这些,如果没有,那么就会在自动配置的包下去扫描
* If {@link org.mybatis.spring.annotation.MapperScan} is used, or a
* configuration file is specified as a property, those will be considered,
* otherwise this auto-configuration will attempt to register mappers based on
* the interface definitions in or under the root auto-configuration package.
*
*/
//有DataSource这个Bean,并且classpath下有SqlSessionFactory和SqlSessionFactoryBean时这个配置类才会生效,
//并且会在DataSourceAutoConfiguration之后装配
@Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnBean(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisAutoConfiguration {
//配置
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 = interceptorsProvider.getIfAvailable();
this.resourceLoader = resourceLoader;
this.databaseIdProvider = databaseIdProvider.getIfAvailable();
this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
}
//初始化之前,检查配置文件是否存在
@PostConstruct
public 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)");
}
}
}
4、在上述这个类中,我们还有两个条件装载Bean。配置SqlSessionFactory
如果没有SqlSessionFactory对应的Bean,则装配一个,也是通过SqlSessionFactoryBean,和spring集成mybatis几乎一样。这里也不进行特殊讲述。详情请看myabtis源码解析的前几个章节。
// 条件装载bean。当下面的bean不存在时将进行加载进IOC容器里面。
@Bean
@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()));
}
Configuration configuration = this.properties.getConfiguration();
if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
configuration = new Configuration();
}
if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
customizer.customize(configuration);
}
}
factory.setConfiguration(configuration);
if (this.properties.getConfigurationProperties() != null) {
factory.setConfigurationProperties(this.properties.getConfigurationProperties());
}
// 。。。。。。。省略一部分代码。
return factory.getObject();
}
5、配置SqlSessionTemplate。如果没有 SqlSessionTemplate 对应的Bean,则装配一个
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
ExecutorType executorType = this.properties.getExecutorType();
if (executorType != null) {
return new SqlSessionTemplate(sqlSessionFactory, executorType);
} else {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
6、配置MapperFactoryBean。
如果我们在项目中使用了@MapperScan这个注解。我们点进去会发现@Import(MapperScannerRegistrar.class)。这里会把@MapperScan后面的包名,把mapper接口用jdk动态代理成一个对象。就是MapperFactoryBean。但是如果系统中没有MapperFactoryBean。那么就会执行下面的代码逻辑。就会导入AutoConfiguredMapperScannerRegistrar,在AutoConfiguredMapperScannerRegistrar里面完成 @Mapper 接口的扫描.
// 当MapperFactoryBean这个bean不存在时,导入AutoConfiguredMapperScannerRegistrar。当@ConditionalOnMissingBean后面的类不存在时,才会导入加载@Import后面的类,这个类也是实现spring注册bean的接口。
@org.springframework.context.annotation.Configuration
@Import({ AutoConfiguredMapperScannerRegistrar.class })
@ConditionalOnMissingBean(MapperFactoryBean.class)
public static class MapperScannerRegistrarNotFoundConfiguration {
@PostConstruct
public void afterPropertiesSet() {
logger.debug("No {} found.", MapperFactoryBean.class.getName());
}
}
AutoConfiguredMapperScannerRegistrar的逻辑。
/**
* 扫描标注了@Mapper的接口
*/
public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware {
private BeanFactory beanFactory;
private ResourceLoader resourceLoader;
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
try {
if (this.resourceLoader != null) {
scanner.setResourceLoader(this.resourceLoader);
}
List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
scanner.setAnnotationClass(Mapper.class);
scanner.registerFilters();
scanner.doScan(StringUtils.toStringArray(packages));
} catch (IllegalStateException ex) {
logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.", ex);
}
}
}
三、总结。
Mybatis和SpringBoot集成原理本身没有太多新东西,主要是利用了spring.factories机制 和 @ConditionalOnMissingBean 来实现,前者来导入一个配置类,后者当容器不存在某个Bean的时候则自动装配一个Bean ,至于@Import和ImportBeanDefinitionRegistrar等都是Spring本身的功能机制了。我们将在后续的专题中,讲解如何自定义实现一个starter包。和回顾整个myabtis的源码。
下一篇-我们回顾整个Mybatis的框架,运用了哪些设计模式,如何在我们日常的开发中去使用借鉴这些设计模式。