Spring Boot 集成mybatis 源码解析
Spring Boot项目中集成mybatis来开发项目,我相信每个用Spring boot 的小伙伴都使用过,感觉就是特别爽,在yml文件中配置一下,就能对数据库进行访问了,其实现原理是什么呢?带着疑问,我们走进代码。在pom.xml文件中新增配置<dependency><groupId>org.mybatis.spring.boot</groupId>&
Spring Boot项目中集成mybatis来开发项目,我相信每个用Spring boot 的小伙伴都使用过,感觉就是特别爽,在yml文件中配置一下,就能对数据库进行访问了,其实现原理是什么呢?带着疑问,我们走进代码。
- 在pom.xml文件中新增配置
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
- 在yml中添加数据库配置和mybatis配置
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/lz_test?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: ldd_biz
password: Hello1234
initial-size: 10
max-active: 100
min-idle: 10
max-wait: 60000
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
validation-query: SELECT 1 FROM DUAL
test-while-idle: true
test-on-borrow: false
test-on-return: false
stat-view-servlet:
enabled: true
url-pattern: /druid/*
filter:
stat:
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: true
wall:
config:
multi-statement-allow: true
mybatis:
#实体扫描,多个package用逗号或者分号分隔
type-aliases-package: com.example.springbootstudy.entity
mapper-locations: classpath:mapper/*Mapper.xml
- 数据库脚本
CREATE TABLE `lz_test_user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键id', `is_delete` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否删除', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '生成时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', `type` int(11) DEFAULT '0' COMMENT '0', `branch_id` int(11) DEFAULT NULL COMMENT '版本号', `real_name` varchar(256) DEFAULT NULL COMMENT '真实名称', `mobile` varchar(256) DEFAULT NULL COMMENT '手机号码', `username` varchar(256) DEFAULT NULL COMMENT '用户名', `task_id` int(11) DEFAULT NULL COMMENT '任务 id', `staff_id` int(11) DEFAULT '0' COMMENT '员工 id', `encrypt_flag` int(11) DEFAULT '0' COMMENT '是否加密,0 未加密,1 己加密,2其他加密算法', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=34 DEFAULT CHARSET=utf8mb4 COMMENT='项目用户';
- 创建实体,Mapper,Mapper.xml 测试方法
@Data
@Mapper
public class TestUser implements java.io.Serializable {
// 主键id
private Long id;
//是否删除
private Integer isDelete;
//生成时间
private Date gmtCreate;
//修改时间
private Date gmtModified;
//0
private Integer type;
//版本号
private Long branchId;
//真实名称
private String realName;
//手机号码
private String mobile;
//用户名
private String username;
//任务id
private Long taskId;
//员工 id
private Long staffId;
// 是否加密,0 未加密,1 己经加密 ,2 ,3 其他加密算法,在密钥泄漏时需要
private int encryptFlag;
}
@Mapper
public interface TestUserMapper {
// 所有的查询条件,默认是 AND 和 = 关系,如果想在其他的关系,可以写相关的注解@OR ,或@Like
TestUser selectTestUserById(Long id);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.springbootstudy.mapper.TestUserMapper">
<select id="selectTestUserById" resultType="com.example.springbootstudy.entity.TestUser">
select * from lz_test_user where id = #{id}
</select>
</mapper>
@RequestMapping("select")
public String select() {
TestUser testUser = testUserMapper.selectTestUserById(14l);
System.out.println(JSON.toJSONString(testUser));
return "SUCESS";
}
【测试结果】
因为Mybatis这一块也牵涉到太多的内容,而本文着重讲Spring Boot如何整合Mybatis的,如果有兴趣去研究MyBatis源码这一块,可以去看我的这一篇博客,https://blog.csdn.net/quyixiao/article/details/110295148,如果对本文中的一些注解的功能及源码不太理解的话,可以去看https://blog.csdn.net/quyixiao/article/details/117777543这一篇博客,因为在研究Spring Boot 整合Mybatis时,发现研究不下去,才去写Spring源码深度解析(郝佳)-Spring 常用注解使用及源码解析这一篇博客的,Spring Boot 整个体系,在了Spring源码深度解析(郝佳)-学习-Spring Boot体系原理,这一篇博客做了分析,目前万事具备了,只欠如何分析Spring Boot 整合MyBatis源码了。话不多说,直接进入主题吧。
这基本上是一个最简单的Spring 整个mybatis的例子了,虽然例子小,但是对于小项目来说,也是可以用于生产了,那Spring Boot是如何让开发变得如此简单的呢?带着疑问,我们来看看源码如何实现。
先从MybatisAutoConfiguration这个类看起。顾名思义,这个类就是mybatis自动配置类。
@org.springframework.context.annotation.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 = 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)");
}
}
@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());
}
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 (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
}
if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
factory.setMapperLocations(this.properties.resolveMapperLocations());
}
return factory.getObject();
}
@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);
}
}
public static class AutoConfiguredMapperScannerRegistrar
implements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware {
private BeanFactory beanFactory;
private ResourceLoader resourceLoader;
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
logger.debug("Searching for mappers annotated with @Mapper");
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
try {
if (this.resourceLoader != null) {
scanner.setResourceLoader(this.resourceLoader);
}
List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
if (logger.isDebugEnabled()) {
for (String pkg : packages) {
logger.debug("Using auto-configuration base package '{}'", pkg);
}
}
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);
}
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
}
@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());
}
}
}
MybatisAutoConfiguration这个类实在是太重要了,今天,我们就围绕着这个类来分析,首先,我们看这个类配置了@org.springframework.context.annotation.Configuration注解,显然会被SpringBootApplication注解扫描到。来看一下SpringBootApplication注解代码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
...
}
默认SpringBootApplication的ComponentScan注解扫描当前项目下所有的**.*.class的,但是其加了excludeFilters属性,配置了TypeExcludeFilter和AutoConfigurationExcludeFilter过滤器。而在Spring源码深度解析(郝佳)-Spring 常用注解使用及源码解析这篇博客也对SpringBootApplication的AutoConfigurationExcludeFilter属性做了详细分析,在AutoConfigurationExcludeFilter中会排除掉所有META-INF/spring.factories下org.springframework.boot.autoconfigure.EnableAutoConfiguration属性配置的configuration类,遗憾的是MybatisAutoConfiguration类刚好配置在mybatis-spring-boot-autoconfigure类下的META-INF/spring.factories文件中的org.springframework.boot.autoconfigure.EnableAutoConfiguration属性中,如下图所示。
那么MybatisAutoConfiguration的BeanDefinition是何时加入到容器中的呢?在Spring源码深度解析(郝佳)-Spring 常用注解使用及源码解析这篇博客中也分析了,在processDeferredImportSelectors方法中,获取META-INF/spring.factories下的所有org.springframework.boot.autoconfigure.EnableAutoConfiguration属性值bean,并调用processImports方法,最终将bean的BeanDefinition注入到容器中。
接下来,我们来看MybatisAutoConfiguration的ConditionalOnClass注解的第一个属性SqlSessionFactory,显然,我们知道SqlSessionFactory是sqlSessionFactory方法配置了Bean注解,在扫描MybatisAutoConfiguration的Bean方法时,创建的BeanDefinition。那我们来看一下这和段代码的实现。
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
throws IOException {
// Recursively process any member (nested) classes first
processMemberClasses(configClass, sourceClass);
// Process any @PropertySource annotations
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)) {
if (this.environment instanceof ConfigurableEnvironment) {
processPropertySource(propertySource);
}
else {
logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
"]. Reason: Environment must implement ConfigurableEnvironment");
}
}
// Process any @ComponentScan annotations
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
for (AnnotationAttributes componentScan : componentScans) {
// The config class is annotated with @ComponentScan -> perform the scan immediately
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// Check the set of scanned definitions for any further config classes and parse recursively if needed
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
if (ConfigurationClassUtils.checkConfigurationClassCandidate(
holder.getBeanDefinition(), this.metadataReaderFactory)) {
parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName());
}
}
}
}
// Process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), true);
// Process any @ImportResource annotations
if (sourceClass.getMetadata().isAnnotated(ImportResource.class.getName())) {
AnnotationAttributes importResource =
AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
String[] resources = importResource.getStringArray("locations");
Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
for (String resource : resources) {
String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
configClass.addImportedResource(resolvedResource, readerClass);
}
}
// Process individual @Bean methods
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
// Process default methods on interfaces
processInterfaces(configClass, sourceClass);
// Process superclass, if any
if (sourceClass.getMetadata().hasSuperClass()) {
String superclass = sourceClass.getMetadata().getSuperClassName();
if (!superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) {
this.knownSuperclasses.put(superclass, configClass);
// Superclass found, return its annotation metadata and recurse
return sourceClass.getSuperClass();
}
}
// No superclass -> processing is complete
return null;
}
上述方法中,其他的方法也在https://blog.csdn.net/quyixiao/article/details/117777543这篇博客做了详细分析,但是 retrieveBeanMethodMetadata方法,是扫描@Configuration注解类中的有@Bean注解的方法,并获取其方法元数据,并返回。接下来,我们来看retrieveBeanMethodMetadata方法的具体实现。
private Set<MethodMetadata> retrieveBeanMethodMetadata(SourceClass sourceClass) { AnnotationMetadata original = sourceClass.getMetadata(); Set<MethodMetadata> beanMethods = original.getAnnotatedMethods(Bean.class.getName()); if (beanMethods.size() > 1 && original instanceof StandardAnnotationMetadata) { try { // 尝试通过 ASM 读取类文件以获得确定性声明顺序... // 不幸的是,JVM 的标准反射以任意顺序 // 返回方法,即使在同一 JVM 上同一应用程序的不同运行之间也是如此。 AnnotationMetadata asm = this.metadataReaderFactory.getMetadataReader(original.getClassName()).getAnnotationMetadata(); Set<MethodMetadata> asmMethods = asm.getAnnotatedMethods(Bean.class.getName()); if (asmMethods.size() >= beanMethods.size()) { Set<MethodMetadata> selectedMethods = new LinkedHashSet<MethodMetadata>(asmMethods.size()); for (MethodMetadata asmMethod : asmMethods) { for (MethodMetadata beanMethod : beanMethods) { if (beanMethod.getMethodName().equals(asmMethod.getMethodName())) { selectedMethods.add(beanMethod); break; } } } if (selectedMethods.size() == beanMethods.size()) { // All reflection-detected methods found in ASM method set -> proceed beanMethods = selectedMethods; } } } catch (IOException ex) { logger.debug("Failed to read class file via ASM for determining @Bean method order", ex); // No worries, let's continue with the reflection metadata we started with... } } return beanMethods; }
关于StandardAnnotationMetadata的使用,也是非常简单
public static void main(String[] args) throws IOException {
AnnotationMetadata reflectReader = new StandardAnnotationMetadata(BeanConfig.class);
System.out.println(reflectReader.getAnnotationTypes());
}
网上有关StandardAnnotationMetadata的解释是
SimpleAnnotationMetadataReadingVisitor与StandardAnnotationMetadata的主要区别在于,SimpleAnnotationMetadataReadingVisitor是基于asm的实现,StandardAnnotationMetadata是基于反射的实现,那我们在使用时,应该要怎么选呢?
由于基于反射是要先加类加载到jvm中的,因此我的判断是,如果当前类没有加载到jvm中,就使用SimpleAnnotationMetadataReadingVisitor,如果类已经加载到jvm中了,两者皆可使用。
事实上,在spring包扫描阶段,读取类上的注解时,使用的都是SimpleAnnotationMetadataReadingVisitor,因为此时类并没有加载到jvm,如果使用StandardAnnotationMetadata读取,就会导致类提前加载。类提前加载有什么问题呢?java类是按需加载的,有的类可能在整个jvm生命周期内都没用到,如果全都加载了,就白白浪费内存了。
【总结】
本文介绍了 AnnotationMetadata两种实现方案,yyyyyyyyyyyyy一种基于 Java 反射,另一种基于ASM 框架。
因此上述方法主要是通过ASM技术,访问class文件,将配置了Bean注解的方法元数据返回。
接下来,我们来看看addBeanMethod方法。
public void addBeanMethod(BeanMethod method) {
this.beanMethods.add(method);
}
这个方法没有什么特别我地方,只将封装好的BeanMethod对象,加入到beanMethods集合中,而beanMethods又何时使用呢?
通过idea很快发现,只有一个地方使用了。
public Set<BeanMethod> getBeanMethods() { return this.beanMethods; } private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) { if (trackedConditionEvaluator.shouldSkip(configClass)) { String beanName = configClass.getBeanName(); if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) { this.registry.removeBeanDefinition(beanName); } this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName()); return; } if (configClass.isImported()) { registerBeanDefinitionForImportedConfigurationClass(configClass); } //遍历当前configuration类的所有的配置了@Bean注解的方法 for (BeanMethod beanMethod : configClass.getBeanMethods()) { loadBeanDefinitionsForBeanMethod(beanMethod); } loadBeanDefinitionsFromImportedResources(configClass.getImportedResources()); loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars()); }
接下来,我们继续看是如何通过BeanMethod创建bean的BeanDefinition的呢?
private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) { ConfigurationClass configClass = beanMethod.getConfigurationClass(); //获取Bean注解方法的元数据 MethodMetadata metadata = beanMethod.getMetadata(); //获得方法名 String methodName = metadata.getMethodName(); //判断方法是是否配置了Conditional注解, //如ConditionalOnBean,ConditionalOnClass,ConditionalOnMissingBean,ConditionalOnMissingClass //注解等,如果配置了,根据不同注解的条件,看当前BeanMethod是否跳过创建Bean if (this.conditionEvaluator.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN)) { configClass.skippedBeanMethods.add(methodName); return; } if (configClass.skippedBeanMethods.contains(methodName)) { return; } AnnotationAttributes bean = AnnotationConfigUtils.attributesFor(metadata, Bean.class); //@Bean注解上是否配置名称 List<String> names = new ArrayList<String>(Arrays.asList(bean.getStringArray("name"))); //如果@Bean注解中配置了名称,则使用@Bean注解上的第一个名称作为bean在容器中的名称, //否则以方法名作为Bean在容器中的名称 String beanName = (!names.isEmpty() ? names.remove(0) : methodName); //为Bean注册别名 for (String alias : names) { this.registry.registerAlias(beanName, alias); } //当前容器中是否存在相同beanName的beanDefinition //如果存在,则抛出异常 if (isOverriddenByExistingDefinition(beanMethod, beanName)) { if (beanName.equals(beanMethod.getConfigurationClass().getBeanName())) { throw new BeanDefinitionStoreException(beanMethod.getConfigurationClass().getResource().getDescription(), beanName, "Bean name derived from @Bean method '" + beanMethod.getMetadata().getMethodName() + "' clashes with bean name for containing configuration class; please make those names unique!"); } return; } ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata); beanDef.setResource(configClass.getResource()); beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource())); if (metadata.isStatic()) { //如果是static方法,则设置BeanClassName为configClass的类名 beanDef.setBeanClassName(configClass.getMetadata().getClassName()); beanDef.setFactoryMethodName(methodName); } else { //如果不是静态方法,则设置当前configuration bean名称 beanDef.setFactoryBeanName(configClass.getBeanName()); beanDef.setUniqueFactoryMethodName(methodName); } beanDef.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR); // 设置RequiredAnnotationBeanPostProcessor的skipRequiredCheck为true beanDef.setAttribute("org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor.skipRequiredCheck", Boolean.TRUE); //处理普通注解Lazy,Primary,DependsOn,Role,Description等注解 AnnotationConfigUtils.processCommonDefinitionAnnotations(beanDef, metadata); // @Bean(autowire = Autowire.BY_NAME) Autowire autowire = bean.getEnum("autowire"); if (autowire.isAutowire()) { beanDef.setAutowireMode(autowire.value()); } /* initMethod和destroyMethod如下用法 //@Bean(initMethod="initMethod",destroyMethod = "destroyMethod") public ImportByBCC importByBB(){ return new ImportByBCC(); } class ImportByBCC{ private void initMethod() { } private void destroyMethod(){ } }*/ String initMethodName = bean.getString("initMethod"); if (StringUtils.hasText(initMethodName)) { beanDef.setInitMethodName(initMethodName); } String destroyMethodName = bean.getString("destroyMethod"); if (destroyMethodName != null) { beanDef.setDestroyMethodName(destroyMethodName); } //@Scope注解的使用 ScopedProxyMode proxyMode = ScopedProxyMode.NO; AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(metadata, Scope.class); if (attributes != null) { beanDef.setScope(attributes.getString("value")); proxyMode = attributes.getEnum("proxyMode"); if (proxyMode == ScopedProxyMode.DEFAULT) { proxyMode = ScopedProxyMode.NO; } } //如果Scope的proxyMode模式配置了ScopedProxyMode.TARGET_CLASS或ScopedProxyMode.INTERFACES BeanDefinition beanDefToRegister = beanDef; if (proxyMode != ScopedProxyMode.NO) { BeanDefinitionHolder proxyDef = ScopedProxyCreator.createScopedProxy( new BeanDefinitionHolder(beanDef, beanName), this.registry, proxyMode == ScopedProxyMode.TARGET_CLASS); beanDefToRegister = new ConfigurationClassBeanDefinition( (RootBeanDefinition) proxyDef.getBeanDefinition(), configClass, metadata); } if (logger.isDebugEnabled()) { logger.debug(String.format("Registering bean definition for @Bean method %s.%s()", configClass.getMetadata().getClassName(), beanName)); } this.registry.registerBeanDefinition(beanName, beanDefToRegister); }
其实这个方法的重点是讲BeanDefinition设置setFactoryBeanName为当前ConfigureClass 和FactoryMethodName,Bean注解修饰的方法为FactoryMethod方法,方法所在的Class为FactoryBean。而关于Scope注解的使用,在另外一篇博客Spring @Bean @Scope注解 proxyMode的组合使用及源码解析。
下面,我们来看看sqlSessionFactory Bean的实例化。
@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnBean(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisAutoConfiguration {
private final MybatisProperties properties;
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();
}
@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());
}
//设置插件拦截器
if (!ObjectUtils.isEmpty(this.interceptors)) {
factory.setPlugins(this.interceptors);
}
//数据库id,如MYSQL,oracle等
if (this.databaseIdProvider != null) {
factory.setDatabaseIdProvider(this.databaseIdProvider);
}
//设置xml中使用到的别名类,所在包
if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
}
//设置类型处理器所在的包位置
if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
}
//设置Mapper所在位置
if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
factory.setMapperLocations(this.properties.resolveMapperLocations());
}
return factory.getObject();
}
从sqlSessionFactory方法中可以看到,整个方法都是围绕着属性文件properties来赋值的,在Spring源码深度解析(郝佳)-Spring 常用注解使用及源码解析在篇博客中,我们就己经详细的解析了EnableConfigurationProperties注解源码,只要配置了EnableConfigurationProperties注解,Spring会自动通过getProperty方法从环境中获取参数,并设置到MybatisProperties属性中,而yml中刚好设置了MybatisProperties对象的属性值。
mybatis: type-aliases-package: com.example.springbootstudy.entity mapper-locations: classpath:mapper/*Mapper.xml
而命名规则,则去掉-,以驼峰命名映射,如type-aliases-package对应的是MybatisProperties的typeAliasesPackage属性,而mapper-locations对应的是mapperLocations属性。
而细心的读者有没有发现,假如先实例化sqlSessionFactory的bean,后面实例化MybatisAutoConfiguration对象,properties属性值不就为空了嘛。这种情况会不会发生呢?我们在MybatisAutoConfiguration构造函数中打一个断点。

追踪方法调用栈,发现在bean实例化时调用了instantiateUsingFactoryMethod方法
而在实例化bean时,从BeanDefinition中,发现有FactoryBean,则先实例化FactoryBean,再调用factoryBean的factoryMethod来实例化当前bean。显然,无论如何MybatisAutoConfiguration总比SqlSessionFactory先实例化。因此properties属性不可能为空。而loadBeanDefinitionsForBeanMethod方法里面有一个重要的设置,就是为当前BeanMethod设置FactoryBeanName和FactoryMethodName。
接来下,我们继续看SqlSessionFactoryBean的getObject方法的内部实现。
public SqlSessionFactory getObject() throws Exception { if (this.sqlSessionFactory == null) { afterPropertiesSet(); } return this.sqlSessionFactory; } 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(); } protected SqlSessionFactory buildSqlSessionFactory() throws IOException { Configuration configuration; XMLConfigBuilder xmlConfigBuilder = null; if (this.configuration != null) { configuration = this.configuration; if (configuration.getVariables() == null) { configuration.setVariables(this.configurationProperties); } else if (this.configurationProperties != null) { configuration.getVariables().putAll(this.configurationProperties); } } else if (this.configLocation != null) { xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties); configuration = xmlConfigBuilder.getConfiguration(); } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration"); } configuration = new Configuration(); if (this.configurationProperties != null) { configuration.setVariables(this.configurationProperties); } } if (this.objectFactory != null) { configuration.setObjectFactory(this.objectFactory); } if (this.objectWrapperFactory != null) { configuration.setObjectWrapperFactory(this.objectWrapperFactory); } if (this.vfs != null) { configuration.setVfsImpl(this.vfs); } if (hasLength(this.typeAliasesPackage)) { String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); for (String packageToScan : typeAliasPackageArray) { configuration.getTypeAliasRegistry().registerAliases(packageToScan, typeAliasesSuperType == null ? Object.class : typeAliasesSuperType); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases"); } } } if (!isEmpty(this.typeAliases)) { for (Class<?> typeAlias : this.typeAliases) { configuration.getTypeAliasRegistry().registerAlias(typeAlias); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Registered type alias: '" + typeAlias + "'"); } } } if (!isEmpty(this.plugins)) { for (Interceptor plugin : this.plugins) { configuration.addInterceptor(plugin); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Registered plugin: '" + plugin + "'"); } } } if (hasLength(this.typeHandlersPackage)) { String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); for (String packageToScan : typeHandlersPackageArray) { configuration.getTypeHandlerRegistry().register(packageToScan); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Scanned package: '" + packageToScan + "' for type handlers"); } } } if (!isEmpty(this.typeHandlers)) { for (TypeHandler<?> typeHandler : this.typeHandlers) { configuration.getTypeHandlerRegistry().register(typeHandler); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Registered type handler: '" + typeHandler + "'"); } } } if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls try { configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource)); } catch (SQLException e) { throw new NestedIOException("Failed getting a databaseId", e); } } if (this.cache != null) { configuration.addCache(this.cache); } 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(); } } if (this.transactionFactory == null) { this.transactionFactory = new SpringManagedTransactionFactory(); } configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource)); if (!isEmpty(this.mapperLocations)) { for (Resource mapperLocation : this.mapperLocations) { if (mapperLocation == null) { continue; } try { XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments()); xmlMapperBuilder.parse(); } catch (Exception e) { throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e); } finally { ErrorContext.instance().reset(); } if (LOGGER.isDebugEnabled()) { LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'"); } } } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found"); } } return this.sqlSessionFactoryBuilder.build(configuration); } public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }
这个方法,实际是属性mybatis源码这一块了,前面主要是将Spring boot配置文件中的配置信息设置到configuration中,前部分根据settings,typeAliases,typeHandlers,plugins,objectFactory,objectWrapperFactory等配置信息设置到configuration中,后部分主要是对Mapper.xml进行parse操作,关于parse操作,在我的MyBatis源码解析系列也做了详细的解析,这里就不再赘述,而Mapper.xml中的一个个元素最终被解析成mapperStatement存储到configuration的属性中。而configuration对于mybatis而言,相当于一个配置的内存数据库。所有与mybatis相关的配置都存储到了Configuration中,而最终容器sqlSessionFactory是DefaultSqlSessionFactory对象,configuration就在创建对象时设置到DefaultSqlSessionFactory的configuration属性中。
而sqlSessionFactory实例化,主要是将yml或properties配置文件的内容设置到SqlSessionFactoryBean中,可能大家对这一块有点陌生了,我们来看看用mybatis-config.xml是如何来配置这些参数的。
<?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>
<properties resource="spring_101_200/config_121_130/spring125_mybatis_properties/db.properties"></properties>
<settings>
<setting name="cacheEnabled" value="false"/>
<setting name="useGeneratedKeys" value="true"/>
<setting name="defaultExecutorType" value="REUSE"/>
<setting name="mapUnderscoreToCamelCase" value="true" />
<setting name="autoMappingBehavior" value="FULL"/>
<setting name="localCacheScope" value="STATEMENT" />
<!--打开延迟加载的开关,全局性设置懒加载。如果设为‘false’,则所有相关联的都会被初始化加载。默认值 false -->
<setting name="lazyLoadingEnabled" value="true"/>
<!--将积极加载改为消极加载及按需加载,当设置为‘true’的时候,懒加载的对象可能被任何懒属性全部加载。否则,每个属性都按需加载,默认值 true -->
<setting name="aggressiveLazyLoading" value="false"/>
<!--当逻辑触发lazyLoadTriggerMethods 对应的方法(equals,clone,hashCode,toString)则执行延迟加载 -->
<setting name="lazyLoadTriggerMethods" value="hashCode"/>
<setting name="safeResultHandlerEnabled" value="true"/>
</settings>
<typeAliases>
<typeAlias type="com.spring_101_200.test_121_130.test_125_mybatis_properties.User" alias="User"></typeAlias>
<package name="com.spring_101_200.test_131_140.test_134_mybatis_usecolumnlabel"/>
<package name="com.spring_101_200.test_131_140.test_135_mybatis_executor"/>
</typeAliases>
<typeHandlers>
<package name="com.spring_101_200.test_131_140.test_132_mybatis_typehandlers"/>
</typeHandlers>
<plugins>
<plugin interceptor="com.spring_101_200.test_121_130.test_127_mybatis_plugins.DataScopeInterceptor">
<property name="someProperty" value="100"/>
</plugin>
<plugin interceptor="com.spring_101_200.test_121_130.test_127_mybatis_plugins.QueryScopeInterceptor">
<property name="someProperty" value="100"/>
</plugin>
</plugins>
<objectFactory type="com.spring_101_200.test_121_130.test_128_mybatis_objectfactory.UserObjectFactory">
<property name="email" value="哈哈"/>
</objectFactory>
<objectWrapperFactory type="com.spring_101_200.test_121_130.test_129_mybatis_objectwrapper.MyMapWrapperFactory"></objectWrapperFactory>
<databaseIdProvider type="DB_VENDOR">
<property name="MySQL" value="mysql" />
<property name="Oracle" value="oracle" />
</databaseIdProvider>
<environments default="online">
<environment id="development">
<transactionManager type="jdbc"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${db.driver}"/>
<property name="url" value="${db.url}"></property>
<property name="username" value="${db.username}"></property>
<property name="password" value="${db.pwd}"></property>
</dataSource>
</environment>
<environment id="online">
<transactionManager type="jdbc"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${db.driver}"/>
<property name="url" value="${db.url}"></property>
<property name="username" value="${db.username}"></property>
<property name="password" value="${db.pwd}"></property>
</dataSource>
</environment>
</environments>
<mappers>
<mapper class="com.spring_101_200.test_121_130.test_125_mybatis_properties.UserMapper"></mapper>
</mappers>
</configuration>
我们看了mybatis原生配置文件后,你会觉得,无非是将mybatis-config.xml配置文件中的内容移植到了Spring boot的yml或properties文件中而已。
接下来,我们继续来看sqlSessionFactory方法上的ConditionalOnMissingBean注解,这个注解的意思 ,就是说当前容器中存在SqlSessionFactory的bean,容器将不会调用sqlSessionFactory()方法实例化SqlSessionFactory,那什么时候会出现SqlSessionFactory被实例化了呢?我们先来看一个例子。
先注释掉yml配置文件中的mybatis配置信息
#mybatis:
# 实体扫描,多个package用逗号或者分号分隔
# type-aliases-package: com.example.springbootstudy.entity
# mapper-locations: classpath:mapper/*Mapper.xml
自定义SqlSessionFactoryBean。
@Configuration
public class SqlSessionFactoryBeanConfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
sqlSessionFactoryBean.setVfs(SpringBootVFS.class);
sqlSessionFactoryBean.setTypeAliasesPackage("com.example.springbootstudy.entity");
sqlSessionFactoryBean.setMapperLocations(resolveMapperLocations(new String [] {"classpath:mapper/*Mapper.xml"}));
return sqlSessionFactoryBean;
}
public Resource[] resolveMapperLocations(String [] mapperLocations) {
ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
List<Resource> resources = new ArrayList<Resource>();
if (mapperLocations != null) {
for (String mapperLocation : mapperLocations) {
try {
Resource[] mappers = resourceResolver.getResources(mapperLocation);
resources.addAll(Arrays.asList(mappers));
} catch (IOException e) {
// ignore
}
}
}
return resources.toArray(new Resource[resources.size()]);
}
}
而此时MybatisAutoConfiguration的sqlSessionFactory方法将不再执行,为什么呢?聪明的读者肯定会想到是我们自己创建了SqlSessionFactoryBean导致的,从名字上可以看出。SqlSessionFactoryBean不就是SqlSessionFactory的Bean工厂嘛。先看一下SqlSessionFactoryBean的getObject方法,getObject方法返回的是SqlSessionFactory,确定无疑了。但是sqlSessionFactory什么时候初始化呢?从getObject方法得知,如果sqlSessionFactory为空,则调用afterPropertiesSet方法初始化sqlSessionFactory。
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
确定是调用getObject方法时实例化的吗?先来看一下SqlSessionFactoryBean类结构 。

其中实现了InitializingBean接口非常重要,而这个接口中的afterPropertiesSet方法,是bean生命周期中必走的方法。下面来看一下Bean的生命周期流程图。
也就是说,在SqlSessionFactoryBean实例化过程中,肯定会实例化sqlSessionFactory。因此。getObject方法中调用afterPropertiesSet,只是为了保险起见而己,一般在调用getObject方法时,sqlSessionFactory不可能为空。
因为SqlSessionFactoryBean是sqlSessionFactory的工厂bean,而在实例化SqlSessionFactoryBean过程中,会将sqlSessionFactory注册到容器中,而由于ConditionalOnMissingBean的作用,因此MybatisAutoConfiguration的sqlSessionFactory()方法不会再执行。
接下来,我们来看MybatisAutoConfiguration中的另外一个方法。
@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());
}
}
这个方法非常重要,又非常有意思,为什么非常重要呢?因为其中的Import注解,只要配置了Import注解,AutoConfiguredMapperScannerRegistrar类就会被注入到容器中,即使AutoConfiguredMapperScannerRegistrar是一个普通类,没有任何注解,而为什么会有意思呢?如果容器中有MapperFactoryBean,则MapperScannerRegistrarNotFoundConfiguration不会被实例化,当容器中没有MapperFactoryBean 的bean时,会实例化MapperScannerRegistrarNotFoundConfiguration,并且在实例化地过程中,只打印一条日志说并没有发现MapperFactoryBean 的bean。
接下来,我们来看非常重要的部分,AutoConfiguredMapperScannerRegistrar。
public static class AutoConfiguredMapperScannerRegistrar
implements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware {
private BeanFactory beanFactory;
private ResourceLoader resourceLoader;
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
logger.debug("Searching for mappers annotated with @Mapper");
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
try {
if (this.resourceLoader != null) {
scanner.setResourceLoader(this.resourceLoader);
}
//获取默认的Spring Boot 启动类所在的包位置
List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
if (logger.isDebugEnabled()) {
for (String pkg : packages) {
logger.debug("Using auto-configuration base package '{}'", pkg);
}
}
//设置扫描的类中,需要配置Mapper注解
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);
}
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
}
private static final String BEAN = AutoConfigurationPackages.class.getName();
public static List<String> get(BeanFactory beanFactory) {
try {
return beanFactory.getBean(BEAN, BasePackages.class).get();
}
catch (NoSuchBeanDefinitionException ex) {
throw new IllegalStateException(
"Unable to retrieve @EnableAutoConfiguration base packages");
}
}
大家可能比较困惑,为什么通过AutoConfigurationPackages.get(this.beanFactory)方法,就能获取到当前Spring Boot 启动类所在的包呢?我们再来看SpringBootStudyApplication启动类上的SpringBootApplication注解。这不就是一个普通的注解嘛,有什么玄机呢?请听我慢慢道来。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
...
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
...
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
...
}
我们在Spring源码深度解析(郝佳)-Spring 常用注解使用及源码解析这篇博客就己经分析过。在Spring启动时,会通过processImports方法,递归扫描Bean的注解中是否在Import注解。接来下,我们看其对Import注解中的内容如何处理。
如果Import的内容是ImportBeanDefinitionRegistrar或其实现类,则加入到configClass的importBeanDefinitionRegistrars的属性中,而此时ConfigurationClass就是我们的启动类SpringBootStudyApplication。接着继续来看。
在加载configClasses中有一个重要的方法loadBeanDefinitionsForConfigurationClass。如下
private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass,
TrackedConditionEvaluator trackedConditionEvaluator) {
if (trackedConditionEvaluator.shouldSkip(configClass)) {
String beanName = configClass.getBeanName();
if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
this.registry.removeBeanDefinition(beanName);
}
this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
return;
}
if (configClass.isImported()) {
registerBeanDefinitionForImportedConfigurationClass(configClass);
}
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
}
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
功夫不负有心人,终于看到了importBeanDefinitionRegistrars被使用了。我们继续跟进代码。
private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
for (Map.Entry<ImportBeanDefinitionRegistrar, AnnotationMetadata> entry : registrars.entrySet()) {
entry.getKey().registerBeanDefinitions(entry.getValue(), this.registry);
}
}
@Order(Ordered.HIGHEST_PRECEDENCE)
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
register(registry, new PackageImport(metadata).getPackageName());
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.<Object>singleton(new PackageImport(metadata));
}
}
在addImportBeanDefinitionRegistrar方法调用得知,importBeanDefinitionRegistrars的key为registrar,而value为configClass的元数据信息。而最终调用register,使用的metadata其实是启动类SpringBootStudyApplication的类元数据 。而接来下,我们来看看new PackageImport(metadata).getPackageName()这行代码的整体实现。
private final static class PackageImport {
private final String packageName;
PackageImport(AnnotationMetadata metadata) {
this.packageName = ClassUtils.getPackageName(metadata.getClassName());
}
public String getPackageName() {
return this.packageName;
}
}
上述代码实现很简单,就是获取元数据类所在的包名。
接下来,我们继续来看register方法的内部实现。
public static void register(BeanDefinitionRegistry registry, String... packageNames) { if (registry.containsBeanDefinition(BEAN)) { BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN); ConstructorArgumentValues constructorArguments = beanDefinition .getConstructorArgumentValues(); constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames)); } else { GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClass(BasePackages.class); beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames); beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); registry.registerBeanDefinition( BEAN, beanDefinition); } }
上述方法实现也非常简单,无非就是注册BasePackages的BeanDefinition到容器中,但是需要注意的一点就是,将启动类的包名作为BasePackages的构造函数参数注入,接下来,我们来看看BasePackages的内部实现。
static final class BasePackages {
private final List<String> packages;
private boolean loggedBasePackageInfo;
BasePackages(String... names) {
List<String> packages = new ArrayList<String>();
for (String name : names) {
if (StringUtils.hasText(name)) {
packages.add(name);
}
}
this.packages = packages;
}
public List<String> get() {
if (!this.loggedBasePackageInfo) {
if (this.packages.isEmpty()) {
if (logger.isWarnEnabled()) {
logger.warn("@EnableAutoConfiguration was declared on a class "
+ "in the default package. Automatic @Repository and "
+ "@Entity scanning is not enabled.");
}
}
else {
if (logger.isDebugEnabled()) {
String packageNames = StringUtils
.collectionToCommaDelimitedString(this.packages);
logger.debug("@EnableAutoConfiguration was declared on a class "
+ "in the package '" + packageNames
+ "'. Automatic @Repository and @Entity scanning is "
+ "enabled.");
}
}
this.loggedBasePackageInfo = true;
}
return this.packages;
}
}
除去日志不看,上面的方法也非常简单,因为在创建Bean的时候,设置了构造函数参数值为启动类所在的包名,所以调用get方法,实际上返回就是启动类的包名。
按这么说,只要修改启动类所在位置,Mapper注解的类将不会被注入到容器中。来测试一把。
显然,换了启动类的所在包的位置,出现了各种问题。
可能项目访问都访问不了。
接下来,我们继续来看registerFilters方法。
public void registerFilters() { boolean acceptAllInterfaces = true; if (this.annotationClass != null) { //设置Mapper注解 addIncludeFilter(new AnnotationTypeFilter(this.annotationClass)); acceptAllInterfaces = false; } //如果指定了接口实现 if (this.markerInterface != null) { addIncludeFilter(new AssignableTypeFilter(this.markerInterface) { @Override protected boolean matchClassName(String className) { return false; } }); acceptAllInterfaces = false; } //既不指定注解,也不指定接口 if (acceptAllInterfaces) { addIncludeFilter(new TypeFilter() { @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { return true; } }); } //排除容器下所有以package-info类名结尾的类 addExcludeFilter(new TypeFilter() { @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { String className = metadataReader.getClassMetadata().getClassName(); return className.endsWith("package-info"); } }); }
对于默认的Spring Boot 加载Mapper类而言,就是扫描启动类所在的包下配置了Mapper注解类,并为Mapper类创建BeanDefinition注册到容器中。
public Set<BeanDefinitionHolder> doScan(String... basePackages) { //扫描包下的所有符合条件的类,并获取BeanDefinition Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages); if (beanDefinitions.isEmpty()) { logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration."); } else { processBeanDefinitions(beanDefinitions); } return beanDefinitions; } private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) { GenericBeanDefinition definition; for (BeanDefinitionHolder holder : beanDefinitions) { definition = (GenericBeanDefinition) holder.getBeanDefinition(); if (logger.isDebugEnabled()) { logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + definition.getBeanClassName() + "' mapperInterface"); } definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59 definition.setBeanClass(this.mapperFactoryBean.getClass()); definition.getPropertyValues().add("addToConfig", this.addToConfig); boolean explicitFactoryUsed = false; if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) { definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName)); explicitFactoryUsed = true; } else if (this.sqlSessionFactory != null) { definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory); explicitFactoryUsed = true; } if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) { if (explicitFactoryUsed) { logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored."); } definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName)); explicitFactoryUsed = true; } else if (this.sqlSessionTemplate != null) { if (explicitFactoryUsed) { logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored."); } definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate); explicitFactoryUsed = true; } if (!explicitFactoryUsed) { if (logger.isDebugEnabled()) { logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'."); } //bean通过类型注入 definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); } } }
如果当前ClassPathMapperScanner中有sqlSessionFactoryBeanName或sqlSessionTemplateBeanName以及addToConfig参数,则为当前Mapper的BeanDefinition设置这些参数,如果没有sqlSessionFactory和sqlSessionTemplate相关参数,则按类型注入。关于这些参数该如何配置呢?
我们在生产环境中,不可能每个Mapper都去配置@Mapper注解,而我们使用统一的@MapperScan注解来指定Mapper所在的包。会将MapperScan所指定包下的所有Bean的相关BeanDefinition都注册到容器中,接下来,我们来看看@MapperScan注解及使用。
注释掉TestUserMapper上的注解Mapper
在启动类SpringBootStudyApplication上添加@MapperScan(basePackages = { “com.example.springbootstudy” })注解。
在之前,我们先来看一下MapperScan注解源码。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
//指定包名
String[] value() default {};
//指定包名
String[] basePackages() default {};
//指定类数组
Class<?>[] basePackageClasses() default {};
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
//指定注解类型
Class<? extends Annotation> annotationClass() default Annotation.class;
//指定实现的接口
Class<?> markerInterface() default Class.class;
//指定sqlSessionTeamplate
String sqlSessionTemplateRef() default "";
//指定sqlSessionFactory
String sqlSessionFactoryRef() default "";
//指定 Mapper工厂Bean
Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
}
我们一般情况下,都是使用指定包名称,扫描包下所有的Bean,而其他属性,我们用得少之又少,下面我们来看一个其他属性的使用。先来看看markerInterface属性使用。
public interface BaseMapper {
}
public interface TestUserMapper extends BaseMapper{
TestUser selectTestUserById(Long id);
}
@SpringBootApplication
@MapperScan(value = "com.example.springbootstudy",markerInterface = BaseMapper.class)
public class SpringBootStudyApplication {
... 省略
}
@RequestMapping("select")
public String select() {
helloService.sayHello();
TestUser testUser = testUserMapper.selectTestUserById(14l);
System.out.println(JSON.toJSONString(testUser));
return "SUCESS";
}
执行结果
可能有人会想,你不加markerInterface属性,执行结果也一样嘛。
我们去掉markerInterface属性看看。
显然执行结果异常,当将包配置成com.example.springbootstudy.mapper时,执行结果如下。
显然是因为包的扫描范围导致的异常,Spring区分不出HelloService和TestUserMapper接口,哪个是业务类,哪个是Mybatis Mapper类,因此,Spring 一股脑的将其的FactoryBean设置成了MapperFactoryBean,因此导致了以上异常。看下图。
因为设置了HelloService的FactoryBean为MapperFactoryBean,导致在实例化时,创建的Bean是MapperProxy的代理。
调用HelloService的sayHello()方法当然就会出现异常。
通过例子,我们终于知道了markerInterface使用场景,假如Mapper扫描的包无法精确配置时,而MyBatis的Mapper都实现了一个统一的接口BaseMapper,因此,此时就可以使用markerInterface参数,来排除掉和MyBatis无关的类。
接下来,我们来看看annotationClass属性使用
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER })
public @interface MyMapper {
}
@MyMapper
public interface TestUserMapper {
TestUser selectTestUserById(Long id);
}
@SpringBootApplication
@MapperScan(value = "com.example.springbootstudy",annotationClass = MyMapper.class)
public class SpringBootStudyApplication {
...
}

使用场景和markerInterface一样,只是根据具体的业务需求来做具体实现,MapperScan注解使用这一块,就到这里了,我们接下来分析源码又是如何实现的呢?
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
... 省略
}
MapperScan上配置了@Import(MapperScannerRegistrar.class),在Spring启动时,会调用Registrar的registerBeanDefinitions方法,对这一块的逻辑,我们之前不知道分析过多少遍了,这里就不再赘述了,直接进入registerBeanDefinitions方法。
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
if (resourceLoader != null) {
scanner.setResourceLoader(resourceLoader);
}
Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
scanner.setAnnotationClass(annotationClass);
}
Class<?> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
scanner.setMarkerInterface(markerInterface);
}
Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
}
Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
}
scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
List<String> basePackages = new ArrayList<String>();
for (String pkg : annoAttrs.getStringArray("value")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (String pkg : annoAttrs.getStringArray("basePackages")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
//注册过滤器
scanner.registerFilters();
//扫描BeanDefinition
scanner.doScan(StringUtils.toStringArray(basePackages));
}
registerBeanDefinitions方法的内部实现也非常简单,无非就是将MapperScan注解中配置的内容设置到ClassPathMapperScanner中,再调用其doScan方法而已。
在processBeanDefinitions方法中,有一行非常重要的代码,就是
definition.setBeanClass(this.mapperFactoryBean.getClass());
mapperFactoryBean默认就是MapperFactoryBean类,这就意味着每个Mapper的FactoryBean就是MapperFactoryBean。接下来,我们来看看MapperFactoryBean的内部实现。在看内部实现之前,先来看一个类关系。
接下来,我们来看源码。
public abstract class DaoSupport implements InitializingBean {
protected final Log logger = LogFactory.getLog(getClass());
@Override
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
checkDaoConfig();
try {
initDao();
}
catch (Exception ex) {
throw new BeanInitializationException("Initialization of DAO failed", ex);
}
}
protected abstract void checkDaoConfig() throws IllegalArgumentException;
protected void initDao() throws Exception {
}
}
public abstract class SqlSessionDaoSupport extends DaoSupport {
private SqlSession sqlSession;
private boolean externalSqlSession;
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (!this.externalSqlSession) {
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
}
}
public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
this.sqlSession = sqlSessionTemplate;
this.externalSqlSession = true;
}
public SqlSession getSqlSession() {
return this.sqlSession;
}
@Override
protected void checkDaoConfig() {
notNull(this.sqlSession, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
}
}
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
private Class<T> mapperInterface;
private boolean addToConfig = true;
public MapperFactoryBean() {
}
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
@Override
protected void checkDaoConfig() {
super.checkDaoConfig();
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
Configuration configuration = getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
configuration.addMapper(this.mapperInterface);
} catch (Exception e) {
logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
throw new IllegalArgumentException(e);
} finally {
ErrorContext.instance().reset();
}
}
}
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
省略...
}
这三个类,我们先从属性入手,因为有setSqlSessionFactory和setSqlSessionTemplate方法,因此,在Bean的初始化时会填充,我们之前分析过创建好的DefaultSqlSessionFactory。再来看MapperFactoryBean有两个构造方法,会调用哪一个呢?再回头来看processBeanDefinitions方法,在这个方法中有一行:
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
将解析到的BeanClassName作为mapperFactoryBean的构造函数参数,因此在实例化时,肯定是调用带参数的构造方法,同时设置了mapperInterface为Mapper的类名称。因为MapperFactoryBean实现了InitializingBean接口,因此在Bean的生命周期中会调用afterPropertiesSet方法,再次回头来看Spring Bean的生命周期图。

而在afterPropertiesSet方法中,做一两件事情,第一件整改,对sqlSessionFactory较验,如果为空,抛出异常,较验通过后,将当前接口加入到configuration的Mapper中,容器中存储的Mapper是最终调用getObject方法返回值,接下来,我们来看看getObject方法内部又是如何实现的。
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
从最终结果来看,返回的是一个MapperProxy的JDK代理。而最终所有的Mapper方法的调用逻辑都在MapperProxy的invoke方法中。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
当我们配置@MapperScan扫描路径不正确时,此时会调用HelloService的 mapperMethod 的cachedMapperMethod 方法,肯定会报错,接下来,我们来看看HelloService出错原因。

简单的来讲,也就是说HelloService没有对应的MappedStatement,更直白的来讲,就是说HelloService接口没有对应的Mapper.xml,可能有读者又会问了,那接口和Mapper.xml又是如何关联起来的呢?
细心的读者肯定会发现,Mapper.xml和Mapper接口,就是通过mapper标签的namespace关联起来的,在之前的Mybatis系列博客中对这一块讲得很透彻了,这里也就再赘述。关于Spring Boot整合MyBatis这一块,好像己经解析完了,好像还漏了什么东西,那就是DataSource这一块。Datasource又是如何注入的呢?
同样是看MybatisAutoConfiguration类。
@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnBean(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisAutoConfiguration {
... 省略
}
因为MybatisAutoConfiguration配置了AutoConfigureAfter注解。证明DataSourceAutoConfiguration一定配置在classpath下的META-INF/spring.factories文件内,而DataSourceAutoConfiguration还在MybatisAutoConfiguration先实例化,之前我们也分析过,MybatisAutoConfiguration肯定比SqlSessionFactory先实例化,因此,sqlSessionFactory(DataSource dataSource)方法的dataSource参数,可能在DataSourceAutoConfiguration中,这只是我们的猜测,下面,来分析我们的猜测。
所以,Spring Boot在启动时,会默认扫描DataSourceAutoConfiguration下的Bean。
@Configuration
@Conditional(PooledDataSourceCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import({ DataSourceConfiguration.Tomcat.class, DataSourceConfiguration.Hikari.class,
DataSourceConfiguration.Dbcp.class, DataSourceConfiguration.Dbcp2.class,
DataSourceConfiguration.Generic.class })
@SuppressWarnings("deprecation")
protected static class PooledDataSourceConfiguration {
}
为了证实猜想,我们在DataSourceConfiguration.Tomcat的dataSource方法中打一个断点。
显然,程序进入断点,而关键代码是createDataSource方法,我们进入这个方法看看。
protected <T> T createDataSource(DataSourceProperties properties,
Class<? extends DataSource> type) {
return (T) properties.initializeDataSourceBuilder().type(type).build();
}
public DataSource build() {
Class<? extends DataSource> type = getType();
DataSource result = BeanUtils.instantiate(type);
//如果yml中没有配置没有driverClassName属性,从url中获取driverClassName属性
maybeGetDriverClassName();
bind(result);
return result;
}
private void bind(DataSource result) {
MutablePropertyValues properties = new MutablePropertyValues(this.properties);
//为属性命别名
new RelaxedDataBinder(result).withAlias("url", "jdbcUrl")
.withAlias("username", "user").bind(properties);
}
public void bind(PropertyValues pvs) {
MutablePropertyValues mpvs = (pvs instanceof MutablePropertyValues) ?
(MutablePropertyValues) pvs : new MutablePropertyValues(pvs);
doBind(mpvs);
}
protected void doBind(MutablePropertyValues mpvs) {
checkAllowedFields(mpvs);
checkRequiredFields(mpvs);
applyPropertyValues(mpvs);
}
上述代码,始终围绕着如何将yml配置文件中的配置,设置到Datasource的属性中去。而比较复杂的代码就是applyPropertyValues方法了,但是最终都是将yml中配置的数据库相关的属性注入到DataSource的poolProperties属性中。当Datasource创建成功后,会调用getValidationQuery方法,获取验证sql(/* ping */ SELECT 1),并最终调用setTestOnBorrow()方法判断Datasource是否创建成功。
大家可能又会想,你这个解析不太有用,因为,我们一般不用org.apache.tomcat.jdbc.pool.DataSource,用是用阿里的com.alibaba.druid.pool.DruidDataSource。接下来,我们来分析一下阿里的DruidDataSource在Spring Boot时,是如何初始化的,导入druid-spring-boot-starter的pom.xml在原来的配置文件中添加type配置,如下。
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.13</version>
</dependency>
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
druid:
url: jdbc:mysql://172.16.157.238:3306/lz_test?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8
username: ldd_biz
password: Hello1234
initial-size: 10
max-active: 10
min-idle: 5
max-wait: 60000
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
#Oracle需要打开注释
validation-query: SELECT 1
test-while-idle: true
test-on-borrow: false
test-on-return: false
stat-view-servlet:
enabled: true
url-pattern: /druid/*
login-username: admin
login-password: admin
filter:
stat:
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: false
wall:
config:
multi-statement-allow: true
其实理解了org.apache.tomcat.jdbc.pool.DataSource的创建过程,再来看DruidDataSource的创建,那就非常简单了。
先来看META-INF
我们先来看看DruidDataSourceAutoConfigure 类
@Configuration
@ConditionalOnClass(DruidDataSource.class)
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
@EnableConfigurationProperties({DruidStatProperties.class, DataSourceProperties.class})
@Import({DruidSpringAopConfiguration.class,
DruidStatViewServletConfiguration.class,
DruidWebStatFilterConfiguration.class,
DruidFilterConfiguration.class})
public class DruidDataSourceAutoConfigure {
private static final Logger LOGGER = LoggerFactory.getLogger(DruidDataSourceAutoConfigure.class);
@Bean(initMethod = "init")
@ConditionalOnMissingBean
public DataSource dataSource() {
LOGGER.info("Init DruidDataSource");
return new DruidDataSourceWrapper();
}
}
先来看看AutoConfigureBefore注解,DruidDataSourceAutoConfigure的AutoConfigureBefore注解中配置了DataSourceAutoConfiguration类,Spring Boot这么做的意图是什么呢?我们在之前的博客中知道。系统默认的Datasource是在DataSourceAutoConfiguration的PooledDataSourceConfiguration静态内部类的Import注解中DataSourceConfiguration.Tomcat bean的dataSource方法注册org.apache.tomcat.jdbc.pool.DataSource的。因此只要DruidDataSourceAutoConfigure比DataSourceAutoConfiguration先实例化。DataSourceConfiguration.Tomcat就不会被实例化。
要得出上面的结论,可能有小伙伴觉得有点牵强,其实我也觉得牵强。
但是上面有一点,无须置疑的一点是,DruidDataSourceAutoConfigure比DataSourceAutoConfiguration先实例化,那么在实例化DataSourceAutoConfiguration的内部类PooledDataSourceConfiguration时,肯定己经存在dataSource了,再来看PooledDataSourceConfiguration的配置。
@Configuration
@Conditional(PooledDataSourceCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import({ DataSourceConfiguration.Tomcat.class,
DataSourceConfiguration.Hikari.class,
DataSourceConfiguration.Dbcp.class, DataSourceConfiguration.Dbcp2.class,
DataSourceConfiguration.Generic.class })
@SuppressWarnings("deprecation")
protected static class PooledDataSourceConfiguration {
}
我们知道,因为配置了ConditionalOnMissingBean注解,注解中有DataSource,ConditionalOnMissingBean注解的意思是,只要存在DataSource和XADataSource任意一个,PooledDataSourceConfiguration就不会被注入到容器中,但是疑问在于PooledDataSourceConfiguration不会被注入,那他Import中的Bean会被注入吗?如果会被注入,那我们之前说的DataSourceConfiguration.Tomcat不会被实例化容器中的结论就是错误的。
下面我们来看一个例子。
-
在META-INF/spring.factories中配置
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
com.example.springbootstudy.service.impl.ConfigurationTestA,
com.example.springbootstudy.service.impl.ConfigurationTestA.ConfigurationTestB -
创建config配置文件
public class ImportTestA {
public ImportTestA() {
System.out.println("ImportTestA实例化");
}
}
@Configuration
public class ConfigurationTestA {
public ConfigurationTestA() {
System.out.println("ConfigurationTestA实例化");
}
@Configuration
@Import(ImportTestA.class)
@ConditionalOnMissingBean({ DataSource.class })
class ConfigurationTestB {
public ConfigurationTestB() {
System.out.println("ConfigurationTestB 实例化");
}
}
}
启动项目
3. 去掉ConfigurationTestB上的@ConditionalOnMissingBean({ DataSource.class })注解
从测试结果来看,显然己经实例化ImportTestA,为什么呢?
其实我们之前也分析过,在扫描Spring Boot启动类所在的包下所有class文件时,会先通过当前Class上的ConditionalOnMissingBean注解上的Conditional注解配置的Class【OnBeanCondition】,作为当前Class是否注入容器的判断条件,调用OnBeanCondition上的matches方法,如果返回false,则忽略掉当前Class,如果matches方法返回true,才会调用processImports方法,将当前Class所有Import的类的BeanDefinition注册到容器中。因而当前Class都不能被容器注册,那更不会调用processImports方法去扫描当前类的Import注解,去注册Bean了。
这一点明白以后,我们再来看看另外一个疑惑点。
@Configuration
public class ConfigurationTestA {
public ConfigurationTestA() {
System.out.println("ConfigurationTestA实例化");
}
@Configuration
class ConfigurationTestB {
public ConfigurationTestB() {
System.out.println("ConfigurationTestB 实例化");
}
}
}
class ConfigurationTestC {
public ConfigurationTestC() {
System.out.println("ConfigurationTestB 实例化");
}
}
@Bean
public ConfigurationTestC configurationTestC(){
return new ConfigurationTestC();
}
也就是说ConfigurationTestA一定比ConfigurationTestB先实例化吗?我们之前分析过Bean注解,如果ConfigurationTestC上配置了@Bean注解,那么ConfigurationTestA一定比ConfigurationTestC先实例化,因为在创建ConfigurationTestC的BeanDefinition时,将ConfigurationTestA的configurationTestC()方法作为ConfigurationTestC的uniqueFactoryMethod,ConfigurationTestA也作为ConfigurationTestC的FactoryBean,在实例化Bean时,发现有FactoryBean,则需要先实例化FactoryBean。
ConfigurationTestA和ConfigurationTestB都被@Configuration注解修饰,而且ConfigurationTestB作为ConfigurationTestA的内部类。那么ConfigurationTestA一定比ConfigurationTestB先实例化吗?带着疑问,我们还是来先看一个源码。
configurationClasses我们之前在Spring源码深度解析(郝佳)-Spring 常用注解使用及源码解析博客中分析过,在Spring启动时,越在configurationClasses集合前面的BeanDefinition,越先被实例化。而我们看到内部类ConfigurationTestA.ConfigurationTestB排在ConfigurationTestA的BeanDefinition前面,那么肯定ConfigurationTestA.ConfigurationTestB比ConfigurationTestA先实例化。一定是这样吗?带着疑问,我们继续追踪代码,我们在ConfigurationTestA的构造函数中打一个断点,来看看。
根据方法调用栈,我们发现在ConfigurationTestA.ConfigurationTestB的实例化过程中,在处理构造函数参数ConstructorResolver的createArgumentArray方法中,出现了ConfigurationTestA类。
也就是ConfigurationTestA.ConfigurationTestB实例化时,根据构造函数参数注入时,需要先创建ConfigurationTestA类。是否如此呢?我们先来看一个例子。
public static void main(String[] args) {
Constructor<?>[] rawCandidates = ConfigurationTestA.ConfigurationTestB.class.getDeclaredConstructors();
for (Constructor constructor : rawCandidates) {
Class<?>[] classes = constructor.getParameterTypes();
for (Class c : classes) {
System.out.println(c.getName());
}
}
}
结果打印
从结果中得知,内部类的无参构造方法中会注入其所在类对象。因此,即使ConfigurationTestA.ConfigurationTestB和ConfigurationTestA都配置了@Configuration注解,即使内部类ConfigurationTestA.ConfigurationTestB的BeanDefinition在ConfigurationTestA前面,在实例化ConfigurationTestB时,会发现需要其所在类ConfigurationTestA的实例,因此会先调用ConfigurationTestA的getBean方法,获取ConfigurationTestA的实例,因此ConfigurationTestA比ConfigurationTestA.ConfigurationTestB先实例化。带着疑问,我们来看看Spring中获取构造函数参数的代码在哪里。
经过两个例子的分析,我们知道因为AutoConfigureBefore注解DruidDataSourceAutoConfigure肯定比DataSourceAutoConfiguration先实例化。而DruidDataSourceAutoConfigure的实例化过程中会创建DataSource,因此PooledDataSourceConfiguration就不会被实例化。PooledDataSourceConfiguration上的Import注解内容DataSourceConfiguration.Tomcat就更加不会被实例化了,也就org.apache.tomcat.jdbc.pool.DataSource也不会被实例化了,当我们导入druid-spring-boot-starter包时,DruidDataSourceWrapper就代替了org.apache.tomcat.jdbc.pool.DataSource。
接下来,我们继续来看DruidDataSourceWrapper的实例化过程,同样。DruidDataSourceAutoConfigure 的Configuration中配置了@EnableConfigurationProperties({DruidStatProperties.class, DataSourceProperties.class})注解,容器中会注入DruidStatProperties和DataSourceProperties的bean,并使用环境变量中的值填充DruidStatProperties和DataSourceProperties的属性值,一般环境变量属性值来自于Spring Boot的yml或properties文件。
@ConfigurationProperties("spring.datasource.druid")
class DruidDataSourceWrapper extends DruidDataSource implements InitializingBean {
@Autowired
private DataSourceProperties basicProperties;
@Override
public void afterPropertiesSet() throws Exception {
//if not found prefix 'spring.datasource.druid' jdbc properties ,'spring.datasource' prefix jdbc properties will be used.
if (super.getUsername() == null) {
super.setUsername(basicProperties.determineUsername());
}
if (super.getPassword() == null) {
super.setPassword(basicProperties.determinePassword());
}
if (super.getUrl() == null) {
super.setUrl(basicProperties.determineUrl());
}
if(super.getDriverClassName() == null){
super.setDriverClassName(basicProperties.getDriverClassName());
}
}
}
DruidDataSourceWrapper的实例化过程中,肯定会执行afterPropertiesSet,设置username,password,url,driverClassName,而接下来就是Datasource的初始化这一块逻辑了,而DataSource初始化逻辑也不是一篇两篇博客能讲清楚的,将来有机会,再来写关于DataSource这一系列的博客,再来分析其内部实现了,这里就不再深入。
总结 :
关于Spring Boot 整合MyBatis这一块的博客就到里了,如果发现有问题或者有疑问,请在博客下方留言。在读这篇博客时,尽量先去看Spring源码深度解析(郝佳)-Spring 常用注解使用及源码解析 和Spring源码深度解析(郝佳)-学习-Spring Boot体系原理 这两篇博客,不然有些东西,可能还是不明白。如果你能从我的博客中学习到知识或者解读源码的方法,我也是比较高兴的,如果能发现问题,那我就更加高兴,如果对Spring Boot 整合MyBatis这一块有了深入理解的小伙伴,可以去研究一下Spring Boot 是如何整合Redis 和RabbitMQ的。我相信,只有自己学习以后再去实践,实践之后再来学习,这样的效果才会更好。
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐

所有评论(0)