Spring Boot 集成mybatis 源码解析

  Spring Boot项目中集成mybatis来开发项目,我相信每个用Spring boot 的小伙伴都使用过,感觉就是特别爽,在yml文件中配置一下,就能对数据库进行访问了,其实现原理是什么呢?带着疑问,我们走进代码。

  1. 在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>

  1. 在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
  1. 数据库脚本
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='项目用户';
  1. 创建实体,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不会被实例化容器中的结论就是错误的。
  下面我们来看一个例子。

  1. 在META-INF/spring.factories中配置
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=
    com.example.springbootstudy.service.impl.ConfigurationTestA,
    com.example.springbootstudy.service.impl.ConfigurationTestA.ConfigurationTestB

  2. 创建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的。我相信,只有自己学习以后再去实践,实践之后再来学习,这样的效果才会更好。

本文的github地址
https://github.com/quyixiao/spring-boot-study

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值