SpringBoot源码阅读三:自动配置实现原理

2023-10-31

本文是SpringBoot源码阅读计划的第三篇文章,本文简单探究一下SpringBoot自动配置实现原理。

本文源码地址为:https://github.com/zouhuanli/MySpringBoot.git

如果说spring-boot-xxx-starter实现了某一个组件(功能)的自动依赖管理,通过gradle或maven依赖导入不仅仅指指定了该组件(功能)的依赖项,还指定了版本。 比如mybatis-spring-boot-starter组件

<dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-autoconfigure</artifactId>
    </dependency>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
    </dependency>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
    </dependency>
  </dependencies>

这里引入了mybatis、mybatis-spring依赖以及其版本号。

 <mybatis-spring.version>3.0.2</mybatis-spring.version>

而SpringBoot的自动配置是通过XXXAutoConfiguration类来实现的,比如DataSourceAutoConfiguration。而此类AutoConfiguration类是如何注册到容器,以及何处起作用的?
这是本文需要解答的。

一、@SpringBootApplication注解解读

回到MySpringBootApplication这个启动类,如下。

@SpringBootApplication
public class MySpringBootApplication {

    public static void main(String[] args) {
        SpringApplication.run(MySpringBootApplication.class, args);
    }

}

这里只有一个SpringBootApplication注解,我们进入这个注解。

/**
 * Indicates a {@link Configuration configuration} class that declares one or more
 * {@link Bean @Bean} methods and also triggers {@link EnableAutoConfiguration
 * auto-configuration} and {@link ComponentScan component scanning}. This is a convenience
 * annotation that is equivalent to declaring {@code @SpringBootConfiguration},
 * {@code @EnableAutoConfiguration} and {@code @ComponentScan}.
 *
 * @author Phillip Webb
 * @author Stephane Nicoll
 * @author Andy Wilkinson
 * @since 1.2.0
 */
@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 {

	/**
	 * Exclude specific auto-configuration classes such that they will never be applied.
	 * @return the classes to exclude
	 */
	@AliasFor(annotation = EnableAutoConfiguration.class)
	Class<?>[] exclude() default {};

	/**
	 * Exclude specific auto-configuration class names such that they will never be
	 * applied.
	 * @return the class names to exclude
	 * @since 1.3.0
	 */
	@AliasFor(annotation = EnableAutoConfiguration.class)
	String[] excludeName() default {};

	/**
	 * Base packages to scan for annotated components. Use {@link #scanBasePackageClasses}
	 * for a type-safe alternative to String-based package names.
	 * <p>
	 * <strong>Note:</strong> this setting is an alias for
	 * {@link ComponentScan @ComponentScan} only. It has no effect on {@code @Entity}
	 * scanning or Spring Data {@link Repository} scanning. For those you should add
	 * {@link org.springframework.boot.autoconfigure.domain.EntityScan @EntityScan} and
	 * {@code @Enable...Repositories} annotations.
	 * @return base packages to scan
	 * @since 1.3.0
	 */
	@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
	String[] scanBasePackages() default {};

	/**
	 * Type-safe alternative to {@link #scanBasePackages} for specifying the packages to
	 * scan for annotated components. The package of each class specified will be scanned.
	 * <p>
	 * Consider creating a special no-op marker class or interface in each package that
	 * serves no purpose other than being referenced by this attribute.
	 * <p>
	 * <strong>Note:</strong> this setting is an alias for
	 * {@link ComponentScan @ComponentScan} only. It has no effect on {@code @Entity}
	 * scanning or Spring Data {@link Repository} scanning. For those you should add
	 * {@link org.springframework.boot.autoconfigure.domain.EntityScan @EntityScan} and
	 * {@code @Enable...Repositories} annotations.
	 * @return base packages to scan
	 * @since 1.3.0
	 */
	@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
	Class<?>[] scanBasePackageClasses() default {};

	/**
	 * The {@link BeanNameGenerator} class to be used for naming detected components
	 * within the Spring container.
	 * <p>
	 * The default value of the {@link BeanNameGenerator} interface itself indicates that
	 * the scanner used to process this {@code @SpringBootApplication} annotation should
	 * use its inherited bean name generator, e.g. the default
	 * {@link AnnotationBeanNameGenerator} or any custom instance supplied to the
	 * application context at bootstrap time.
	 * @return {@link BeanNameGenerator} to use
	 * @see SpringApplication#setBeanNameGenerator(BeanNameGenerator)
	 * @since 2.3.0
	 */
	@AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")
	Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

	/**
	 * Specify whether {@link Bean @Bean} methods should get proxied in order to enforce
	 * bean lifecycle behavior, e.g. to return shared singleton bean instances even in
	 * case of direct {@code @Bean} method calls in user code. This feature requires
	 * method interception, implemented through a runtime-generated CGLIB subclass which
	 * comes with limitations such as the configuration class and its methods not being
	 * allowed to declare {@code final}.
	 * <p>
	 * The default is {@code true}, allowing for 'inter-bean references' within the
	 * configuration class as well as for external calls to this configuration's
	 * {@code @Bean} methods, e.g. from another configuration class. If this is not needed
	 * since each of this particular configuration's {@code @Bean} methods is
	 * self-contained and designed as a plain factory method for container use, switch
	 * this flag to {@code false} in order to avoid CGLIB subclass processing.
	 * <p>
	 * Turning off bean method interception effectively processes {@code @Bean} methods
	 * individually like when declared on non-{@code @Configuration} classes, a.k.a.
	 * "@Bean Lite Mode" (see {@link Bean @Bean's javadoc}). It is therefore behaviorally
	 * equivalent to removing the {@code @Configuration} stereotype.
	 * @since 2.2
	 * @return whether to proxy {@code @Bean} methods
	 */
	@AliasFor(annotation = Configuration.class)
	boolean proxyBeanMethods() default true;

}

可以看到主要设置了排除项和成员扫描路径。其使用的SpringBootConfiguration标识是一个SpringBoot的配置类,也是使用的@Configuration注解。
@ComponentScan设置了扫描路径以及排除项。
我们重点看一下EnableAutoConfiguration这个注解,见名知其义,这里是开启自动配置的注解。

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

    /**
     * Environment property that can be used to override when auto-configuration is
     * enabled.
     */
                        //是否启用自动配置的参数,可以设置值来覆盖是否启动自动配置
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    /**
     * Exclude specific auto-configuration classes such that they will never be applied.
     * @return the classes to exclude
     */
                        //排除项
    Class<?>[] exclude() default {};

    /**
     * Exclude specific auto-configuration class names such that they will never be
     * applied.
     * @return the class names to exclude
     * @since 1.3.0
     */
                        //排除项
    String[] excludeName() default {};

}

这里有两个注解。@AutoConfigurationPackage,应该是与扫描的包路径相关。AutoConfigurationImportSelector主要处理@EnableAutoConfiguration注解,去引入XXXAutoConfiguration等组件Bean。 通过@AutoConfigurationPackage注解进入AutoConfigurationPackages这个类。

	@Override
		public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
			register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
		}

PackageImports(AnnotationMetadata metadata) {
			AnnotationAttributes attributes = AnnotationAttributes
				.fromMap(metadata.getAnnotationAttributes(AutoConfigurationPackage.class.getName(), false));
			List<String> packageNames = new ArrayList<>(Arrays.asList(attributes.getStringArray("basePackages")));
			for (Class<?> basePackageClass : attributes.getClassArray("basePackageClasses")) {
				packageNames.add(basePackageClass.getPackage().getName());
			}
			if (packageNames.isEmpty()) {
				packageNames.add(ClassUtils.getPackageName(metadata.getClassName()));
			}
			this.packageNames = Collections.unmodifiableList(packageNames);
		}

这里取得了默认的扫描组件的基本路径,示例工程是”com.homura.myspringboot’,就是MySpringBootApplication的包路径。所以一般会把启动类放置到工程的顶层包里面。

下面重点解读一下AutoConfigurationImportSelector。

二、AutoConfigurationImportSelector解读

AutoConfigurationImportSelector是决定那些AutoConfiguration类型需要导入的核心管理类。
先看下类关系图:

AutoConfigurationImportSelector

可以看到其顶层接口是Aware和ImportSelector,ImportSelector主要是选择哪些@Configuration配置类需要引入。
我们进入AutoConfigurationImportSelector#selectImports方法,

@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}

继续来到getAutoConfigurationEntry方法。


	/**
	 * Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata}
	 * of the importing {@link Configuration @Configuration} class.
	 * @param annotationMetadata the annotation metadata of the configuration class
	 * @return the auto-configurations that should be imported
	 */
	protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
                                //注意这行代码,获得自动配置类的候选集合
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
                                //去重
		configurations = removeDuplicates(configurations);
                                //排除项
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
                                //过滤器
		configurations = getConfigurationClassFilter().filter(configurations);
                                //发布事件
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

我们继续进入getCandidateConfigurations方法。

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		List<String> configurations = ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader())
			.getCandidates();
		Assert.notEmpty(configurations,
				"No auto configuration classes found in "
						+ "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
						+ "are using a custom packaging, make sure that file is correct.");
		return configurations;
	}

再来到这个load方法:

public static ImportCandidates load(Class<?> annotation, ClassLoader classLoader) {
		Assert.notNull(annotation, "'annotation' must not be null");
		ClassLoader classLoaderToUse = decideClassloader(classLoader);
                        //自动配置类的导入路径,这里是AutoConfiguration.class的全名+.imports后缀
                        //也就是org.springframework.boot.autoconfigure.AutoConfiguration.imports这个文件
		String location = String.format(LOCATION, annotation.getName());
		Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse, location);
		List<String> importCandidates = new ArrayList<>();
		while (urls.hasMoreElements()) {
			URL url = urls.nextElement();
			importCandidates.addAll(readCandidateConfigurations(url));
		}
		return new ImportCandidates(importCandidates);
	}

这里导入了AopAutoConfiguration、PropertyPlaceholderAutoConfiguration等诸多AutoConfiguration类的类名。目前版本的Boot里面有148个自动配置类。 经过一些排除和过滤,示例工程AutoConfiguration类共有34个就全部导入了,如下。

configurations

那么AutoConfiguration类是在哪里注册到容器,和SpringFramework相结合呢? 我们从ConfigurationClassPostProcessor#processConfigBeanDefinitions方法一路追踪,来到ConfigurationClassParser#parse方法,再进入ConfigurationClassParser#doProcessConfigurationClass方法, 最后来到processImports方法。

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
			Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
			boolean checkForCircularImports) {

		if (importCandidates.isEmpty()) {
			return;
		}

		if (checkForCircularImports && isChainedImportOnStack(configClass)) {
			this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
		}
		else {
			this.importStack.push(configClass);
			try {
				for (SourceClass candidate : importCandidates) {
                    //注意看这个分支
					if (candidate.isAssignable(ImportSelector.class)) {
						// Candidate class is an ImportSelector -> delegate to it to determine imports
						Class<?> candidateClass = candidate.loadClass();
						ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
								this.environment, this.resourceLoader, this.registry);
						Predicate<String> selectorFilter = selector.getExclusionFilter();
						if (selectorFilter != null) {
							exclusionFilter = exclusionFilter.or(selectorFilter);
						}
						if (selector instanceof DeferredImportSelector deferredImportSelector) {
							this.deferredImportSelectorHandler.handle(configClass, deferredImportSelector);
						}
						else {
							String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
							Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
							processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
						}
					}
					else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
						// Candidate class is an ImportBeanDefinitionRegistrar ->
						// delegate to it to register additional bean definitions
						Class<?> candidateClass = candidate.loadClass();
						ImportBeanDefinitionRegistrar registrar =
								ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
										this.environment, this.resourceLoader, this.registry);
						configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
					}
					else {
						// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
						// process it as an @Configuration class
						this.importStack.registerImport(
								currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
						processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
					}
				}
			}
			catch (BeanDefinitionStoreException ex) {
				throw ex;
			}
			catch (Throwable ex) {
				throw new BeanDefinitionStoreException(
						"Failed to process import candidates for configuration class [" +
						configClass.getMetadata().getClassName() + "]: " + ex.getMessage(), ex);
			}
			finally {
				this.importStack.pop();
			}
		}
	}

跟踪执行流程,如下:

importCandidates

这里创建AutoConfigurationImportSelector对象,并加入deferredImportSelectorHandler。 然后来到:

this.deferredImportSelectorHandler.process();

进入process方法:

public void process() {
			List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
			this.deferredImportSelectors = null;
			try {
				if (deferredImports != null) {
					DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
					deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
					deferredImports.forEach(handler::register);
					handler.processGroupImports();
				}
			}
			finally {
				this.deferredImportSelectors = new ArrayList<>();
			}
		}

再来到processGroupImports这个方法,按照组导入,这里的默认组是AutoConfigurationGroup。

public void processGroupImports() {
			for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
				Predicate<String> exclusionFilter = grouping.getCandidateFilter();
				grouping.getImports().forEach(entry -> {
					ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
					try {
                                    //这里导入和注册AutoConfiguration类
						processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter),
								Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)),
								exclusionFilter, false);
					}
					catch (BeanDefinitionStoreException ex) {
						throw ex;
					}
					catch (Throwable ex) {
						throw new BeanDefinitionStoreException(
								"Failed to process import candidates for configuration class [" +
										configurationClass.getMetadata().getClassName() + "]", ex);
					}
				});
			}
		}

这里”grouping.getImports()“并不是上面.imports里面全部的,和上面解读的34个自动配置类一样。

grouping_imports

这里processImports方法就是处理一个具体的XXXAutoConfiguration类的相关注册信息。

processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter),
								Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)),
								exclusionFilter, false)

这里第三个参数importCandidates就是一个具体的AutoConfiguration类了,如org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration。

到这里AutoConfiguration自动配置类及其引入的Bean配置就注册到容器,下面具体解读一个AutoConfiguration自动配置类解读自动配置的执行流程。

三、AutoConfiguration解读

这里以DataSourceAutoConfiguration这个自动配置类作为示例。
解读DataSourceAutoConfiguration之前,考虑一个及其简单的数据源自动配置类,全部使用Java代码+注解实现,可以简单的这样做。
先配置一个PropertySourcesPlaceholderConfigurer实例。

@Configuration
public class PropertySourcesPlaceholderConfigurerConfig {

    @Bean
    public PropertySourcesPlaceholderConfigurer propertyConfigurer() {
        PropertySourcesPlaceholderConfigurer propertyConfigurer = new PropertySourcesPlaceholderConfigurer();
        propertyConfigurer.setIgnoreUnresolvablePlaceholders(true);
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        Resource classPathResource = resolver.getResource("classpath:springmvc-db.properties");
        propertyConfigurer.setLocation(classPathResource);
        return propertyConfigurer;
    }

再配置数据库连接配置的PropertiesBean。

@Data
@Configuration
public class DataSourcePropertiesBean {
    /**
     * <property name="driverClassName" value="${db.driver}"/>
     * <property name="jdbcUrl" value="${db.url}"/>
     * <property name="username" value="${db.username}"/>
     * <property name="password" value="${db.password}"/>
     * <property name="connectionTestQuery" value="${db.connectionTestQuery}"/>
     * <!-- 生效超时 -->
     * <property name="validationTimeout" value="${db.validationTimeout}"/>
     * <!-- 连接只读数据库时配置为true, 保证安全 -->
     * <property name="readOnly" value="${db.readOnly}"/>
     * <!-- 等待连接池分配连接的最大时长(毫秒),超过这个时长还没可用的连接则发生SQLException, 缺省:30秒 -->
     * <property name="connectionTimeout" value="${db.connectionTimeout}"/>
     * <!-- 一个连接idle状态的最大时长(毫秒),超时则被释放(retired),缺省:10分钟 -->
     * <property name="idleTimeout" value="${db.idleTimeout}"/>
     * <!-- 一个连接的生命时长(毫秒),超时而且没被使用则被释放(retired),缺省:30分钟,建议设置比数据库超时时长少30秒,参考MySQL
     * wait_timeout参数(show variables like '%timeout%';) -->
     * <property name="maxLifetime" value="${db.maxLifetime}"/>
     * <!-- 连接池中允许的最大连接数。缺省值:10;推荐的公式:((core_count * 2) + effective_spindle_count) -->
     * <property name="maximumPoolSize" value="${db.maximumPoolSize}"/>
     **/
    @Value("${db.driver}")
    private String driverClassName;
    @Value("${db.url}")
    private String jdbcUrl;
    @Value("${db.username}")
    private String username;
    @Value("${db.password}")
    private String password;
    @Value("${db.connectionTestQuery}")
    private String connectionTestQuery;
    @Value("${db.validationTimeout}")
    private long validationTimeout;
    @Value("${db.readOnly}")
    private boolean readOnly;
    @Value("${db.connectionTimeout}")
    private long connectionTimeout;
    @Value("${db.idleTimeout}")
    private long idleTimeout;
    @Value("${db.maxLifetime}")
    private long maxLifetime;
    @Value("${db.maximumPoolSize}")
    private int maximumPoolSize;
    
}

再创建数据源Datasource。

@Configuration
public class DataSourceConfiguration {
    @Bean(name = "dataSource")
    public HikariDataSource dataSource(@Autowired @Qualifier("dataSourcePropertiesBean") DataSourcePropertiesBean dataSourceConfig) {
        return buildDataSource(dataSourceConfig);
    }
    private HikariDataSource buildDataSource(DataSourcePropertiesBean dataSourceConfig) {
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setPoolName("HikariDataSource-Pool-1");
        dataSource.setDriverClassName(dataSourceConfig.getDriverClassName());
        dataSource.setJdbcUrl(dataSourceConfig.getJdbcUrl());
        dataSource.setUsername(dataSourceConfig.getUsername());
        dataSource.setPassword(dataSourceConfig.getPassword());
        dataSource.setConnectionTestQuery(dataSourceConfig.getConnectionTestQuery());
        dataSource.setValidationTimeout(dataSourceConfig.getValidationTimeout());
        dataSource.setReadOnly(dataSourceConfig.isReadOnly());
        dataSource.setConnectionTimeout(dataSourceConfig.getConnectionTimeout());
        dataSource.setIdleTimeout(dataSourceConfig.getIdleTimeout());
        dataSource.setMaxLifetime(dataSourceConfig.getMaxLifetime());
        dataSource.setMaximumPoolSize(dataSourceConfig.getMaximumPoolSize());

        return dataSource;
    }

这样name为dataSource,类型为HikariDataSource的数据源对象就创建好了。这里使用到了PropertySourcesPlaceholderConfigurer这个对象来解析@value注入的配置项。
这只是非常简陋的实现方法,不过也可以帮助我们理解DataSourceAutoConfiguration这个自动配置类的实现原理。
因此,可以作一下初步的假设DataSourceAutoConfiguration这个配置类首先要解析数据库配置,生产XXXPropertiesBean这样一个对象来保存配置,然后创建Datasource的Bean对象。
我们开始看下这个类的源码。

1.DataSourceAutoConfiguration

DataSourceAutoConfiguration源码如下:

                                //AutoConfiguration注解标识是SpringBoot的自动配置类,注解里面使用了@Configuration
@AutoConfiguration(before = SqlInitializationAutoConfiguration.class)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
                                 //EnableConfigurationProperties注解,导入解析和存储配置项的Bean,这个注解引入了EnableConfigurationPropertiesRegistrar这个注册器
@EnableConfigurationProperties(DataSourceProperties.class)
                                //引入DataSourcePoolMetadataProvidersConfiguration,数据库连接池配置类
@Import(DataSourcePoolMetadataProvidersConfiguration.class)
public class DataSourceAutoConfiguration {
                                 //引入内嵌的数据源Datasource
	@Configuration(proxyBeanMethods = false)
	@Conditional(EmbeddedDatabaseCondition.class)
	@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
	@Import(EmbeddedDataSourceConfiguration.class)
	protected static class EmbeddedDatabaseConfiguration {

	}
                                 //创建连接池类型的数据源,有Hikari(默认)、Tomcat、Dbcp2、OracleUcp、Generic等类型
	@Configuration(proxyBeanMethods = false)
	@Conditional(PooledDataSourceCondition.class)
	@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
	@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
			DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class,
			DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class })
	protected static class PooledDataSourceConfiguration {
                                //数据库连接信息,看源码内部包含DataSourceProperties数据源配置信息这个对象。
		@Bean
		@ConditionalOnMissingBean(JdbcConnectionDetails.class)
		PropertiesJdbcConnectionDetails jdbcConnectionDetails(DataSourceProperties properties) {
			return new PropertiesJdbcConnectionDetails(properties);
		}

	}
}

我们进入这个类DataSourceConfiguration.Hikari。

   @Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(HikariDataSource.class)
	@ConditionalOnMissingBean(DataSource.class)
	@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "com.zaxxer.hikari.HikariDataSource",
			matchIfMissing = true)
	static class Hikari {

		@Bean
		static HikariJdbcConnectionDetailsBeanPostProcessor jdbcConnectionDetailsHikariBeanPostProcessor(
				ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
			return new HikariJdbcConnectionDetailsBeanPostProcessor(connectionDetailsProvider);
		}
                                     //重点代码,创建数据源对象
                                     //这里创建Datasource数据源对象依赖DataSourceProperties和JdbcConnectionDetails对象。
                                        //PropertiesJdbcConnectionDetails是DataSourceProperties的适配对象,不作过多解读。
                                        //重点解读一下DataSourceProperties这个类
		@Bean
		@ConfigurationProperties(prefix = "spring.datasource.hikari")
		HikariDataSource dataSource(DataSourceProperties properties, JdbcConnectionDetails connectionDetails) {
			HikariDataSource dataSource = createDataSource(connectionDetails, HikariDataSource.class,
					properties.getClassLoader());
			if (StringUtils.hasText(properties.getName())) {
				dataSource.setPoolName(properties.getName());
			}
			return dataSource;
		}

	}

接下来重点解读一下DataSourceProperties这个类。

2.DataSourceProperties

2.1 DataSourceProperties的注册和实例化

DataSourceProperties是解析和存储数据库配置的Bean,看属性和功能应和上面示例的DataSourcePropertiesBean一样。接下来要重点关注:
1.DataSourceProperties如何创建和何处创建?—注解Bean和创建Bean。
2.DataSourceProperties内部的各项属性(字段)如何获取到配置项文件的配置值?—-属性绑定。

相对于PropertiesJdbcConnectionDetails由应用显式创建(如下),DataSourceProperties这个对象并未找到显式创建的代码。
因此这里可以推断这个Bean应该首先在invokeBeanFactoryPostProcessors这里通过某一个XXXRegistrar完成BeanDefinition的注册,注册Bean信息到容器的beanDefinitionMap里面。然后finishBeanFactoryInitialization完成实例化真正创建这个对象。

                        //显式创建Bean
        @Bean
		@ConditionalOnMissingBean(JdbcConnectionDetails.class)
		PropertiesJdbcConnectionDetails jdbcConnectionDetails(DataSourceProperties properties) {
			return new PropertiesJdbcConnectionDetails(properties);
		}

我们看下DataSourceProperties这个类。

@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {

    private ClassLoader classLoader;

    /**
     * Whether to generate a random datasource name.
     */
    private boolean generateUniqueName = true;

    /**
     * Datasource name to use if "generate-unique-name" is false. Defaults to "testdb"
     * when using an embedded database, otherwise null.
     */
    private String name;

    /**
     * Fully qualified name of the connection pool implementation to use. By default, it
     * is auto-detected from the classpath.
     */
    private Class<? extends DataSource> type;
    //......
}

这里定义了各项配置项如name、driver-class-name、url、username、password,这些配置项需要使用’spring.datasource’前缀。
然后重点看一下@ConfigurationProperties这个注解:

/**
 * Annotation for externalized configuration. Add this to a class definition or a
 * {@code @Bean} method in a {@code @Configuration} class if you want to bind and validate
 * some external Properties (e.g. from a .properties file).
 * <p>
 * Binding is either performed by calling setters on the annotated class or, if
 * {@link ConstructorBinding @ConstructorBinding} is in use, by binding to the constructor
 * parameters.
 * <p>
 * Note that contrary to {@code @Value}, SpEL expressions are not evaluated since property
 * values are externalized.
 *
 * @author Dave Syer
 * @since 1.0.0
 * @see ConfigurationPropertiesScan
 * @see ConstructorBinding
 * @see ConfigurationPropertiesBindingPostProcessor
 * @see EnableConfigurationProperties
 */
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface ConfigurationProperties {

	/**
	 * The prefix of the properties that are valid to bind to this object. Synonym for
	 * {@link #prefix()}. A valid prefix is defined by one or more words separated with
	 * dots (e.g. {@code "acme.system.feature"}).
	 * @return the prefix of the properties to bind
	 */
	@AliasFor("prefix")
	String value() default "";

	/**
	 * The prefix of the properties that are valid to bind to this object. Synonym for
	 * {@link #value()}. A valid prefix is defined by one or more words separated with
	 * dots (e.g. {@code "acme.system.feature"}).
	 * @return the prefix of the properties to bind
	 */
	@AliasFor("value")
	String prefix() default "";

	/**
	 * Flag to indicate that when binding to this object invalid fields should be ignored.
	 * Invalid means invalid according to the binder that is used, and usually this means
	 * fields of the wrong type (or that cannot be coerced into the correct type).
	 * @return the flag value (default false)
	 */
	boolean ignoreInvalidFields() default false;

	/**
	 * Flag to indicate that when binding to this object unknown fields should be ignored.
	 * An unknown field could be a sign of a mistake in the Properties.
	 * @return the flag value (default true)
	 */
	boolean ignoreUnknownFields() default true;

}

这个注解标识一个类型是自动配置类的读取配置项属性的PropertiesBean。
进入EnableConfigurationProperties注解,来到EnableConfigurationPropertiesRegistrar这个类。
EnableConfigurationPropertiesRegistrar父类是ImportBeanDefinitionRegistrar,用来引入注册Bean的信息,就是额外添加BeanDefinition信息到容器中。类似的有AspectJAutoProxyRegistrar,MapperScannerRegistrar(来自mybatis)等。

class EnableConfigurationPropertiesRegistrar implements ImportBeanDefinitionRegistrar {

	private static final String METHOD_VALIDATION_EXCLUDE_FILTER_BEAN_NAME = Conventions
		.getQualifiedAttributeName(EnableConfigurationPropertiesRegistrar.class, "methodValidationExcludeFilter");

	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
		registerInfrastructureBeans(registry);
		registerMethodValidationExcludeFilter(registry);
		ConfigurationPropertiesBeanRegistrar beanRegistrar = new ConfigurationPropertiesBeanRegistrar(registry);
		getTypes(metadata).forEach(beanRegistrar::register);
	}

	private Set<Class<?>> getTypes(AnnotationMetadata metadata) {
		return metadata.getAnnotations()
			.stream(EnableConfigurationProperties.class)
			.flatMap((annotation) -> Arrays.stream(annotation.getClassArray(MergedAnnotation.VALUE)))
			.filter((type) -> void.class != type)
			.collect(Collectors.toSet());
	}

	static void registerInfrastructureBeans(BeanDefinitionRegistry registry) {
                                    //注册ConfigurationPropertiesBindingPostProcessor和ConfigurationPropertiesBinderFactory,只会注册一次
		ConfigurationPropertiesBindingPostProcessor.register(registry);
                                    //BoundConfigurationProperties,只会注册一次
		BoundConfigurationProperties.register(registry);
	}

	static void registerMethodValidationExcludeFilter(BeanDefinitionRegistry registry) {
		if (!registry.containsBeanDefinition(METHOD_VALIDATION_EXCLUDE_FILTER_BEAN_NAME)) {
			BeanDefinition definition = BeanDefinitionBuilder
				.rootBeanDefinition(MethodValidationExcludeFilter.class, "byAnnotation")
				.addConstructorArgValue(ConfigurationProperties.class)
				.setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
				.getBeanDefinition();
			registry.registerBeanDefinition(METHOD_VALIDATION_EXCLUDE_FILTER_BEAN_NAME, definition);
		}
	}

}

跟踪这个类处理DatasourceAutoConfiguration的过程。

EnableConfigurationPropertiesRegistrar

继续来到ConfigurationPropertiesBeanRegistrar这个类,这里注册了DataSourceProperties到容器中,后续容器会自动创建这个Bean对象。

void register(Class<?> type) {
		MergedAnnotation<ConfigurationProperties> annotation = MergedAnnotations
			.from(type, SearchStrategy.TYPE_HIERARCHY)
			.get(ConfigurationProperties.class);
		register(type, annotation);
	}

	void register(Class<?> type, MergedAnnotation<ConfigurationProperties> annotation) {
		String name = getName(type, annotation);
		if (!containsBeanDefinition(name)) {
			registerBeanDefinition(name, type, annotation);
		}
	}

执行流程如图: ConfigurationPropertiesBeanRegistrar

到这里,我们知道了:被@ConfigurationProperties注解修饰的DataSourceProperties已经注册到容器了,并由容器去实例化。其他@ConfigurationProperties注解修饰的XXXProperties也是同理。

既然DataSourceProperties的注册和实例化(finishBeanFactoryInitialization方法内)已经完成了,接下来要去解答配置文件的配置项如何注入到DataSourceProperties字段中,也就是DataSourceProperties的每个属性如何绑定配置项文件的配置。

2.2 配置文件属性解析和绑定

属性绑定简单说就对DataSourceProperties的字段赋值。

@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {

    private ClassLoader classLoader;

    /**
     * Whether to generate a random datasource name.
     */
    private boolean generateUniqueName = true;

    /**
     * Datasource name to use if "generate-unique-name" is false. Defaults to "testdb"
     * when using an embedded database, otherwise null.
     */
    private String name;

    /**
     * Fully qualified name of the connection pool implementation to use. By default, it
     * is auto-detected from the classpath.
     */
    private Class<? extends DataSource> type;

    /**
     * Fully qualified name of the JDBC driver. Auto-detected based on the URL by default.
     */
    private String driverClassName;

    /**
     * JDBC URL of the database.
     */
    private String url;
                    //......
}

比如说如何设置url的值。 EnableConfigurationPropertiesRegistrar中注册了ConfigurationPropertiesBindingPostProcessor和ConfigurationPropertiesBinder这两个组件,看名字是与属性绑定相关的?
首先看下ConfigurationPropertiesBindingPostProcessor:

@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		if (!hasBoundValueObject(beanName)) {
			bind(ConfigurationPropertiesBean.get(this.applicationContext, bean, beanName));
		}
		return bean;
	}

之前的文件解读过BeanPostProcessor#postProcessBeforeInitialization在Bean已经实例化,尚未初始化的时候执行。参考这篇文章Bean的创建过程和Bean的生命周期。 其在Spring源码中调用的位置是AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInitialization方法。
因此创建DataSourceProperties这个Bean已经实例化,尚未初始化的时候,会执行下面代码:

@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		if (!hasBoundValueObject(beanName)) {
			bind(ConfigurationPropertiesBean.get(this.applicationContext, bean, beanName));
		}
		return bean;
	}
    
	private void bind(ConfigurationPropertiesBean bean) {
		if (bean == null) {
			return;
		}
		Assert.state(bean.asBindTarget().getBindMethod() != BindMethod.VALUE_OBJECT,
				"Cannot bind @ConfigurationProperties for bean '" + bean.getName()
						+ "'. Ensure that @ConstructorBinding has not been applied to regular bean");
		try {
			this.binder.bind(bean);
		}
		catch (Exception ex) {
			throw new ConfigurationPropertiesBindException(bean, ex);
		}
	}

然后我们来到ConfigurationPropertiesBinder。

BindResult<?> bind(ConfigurationPropertiesBean propertiesBean) {
		Bindable<?> target = propertiesBean.asBindTarget();
		ConfigurationProperties annotation = propertiesBean.getAnnotation();
		BindHandler bindHandler = getBindHandler(target, annotation);
		return getBinder().bind(annotation.prefix(), target, bindHandler);
	}

这里是实现PropertiesBean的属性绑定的功能的代码了。

ConfigurationPropertiesBinder

我们跟踪BindHandler#bind方法来到这个方法。

private Object bindDataObject(ConfigurationPropertyName name, Bindable<?> target, BindHandler handler,
			Context context, boolean allowRecursiveBinding) {
		if (isUnbindableBean(name, target, context)) {
			return null;
		}
		Class<?> type = target.getType().resolve(Object.class);
		BindMethod bindMethod = target.getBindMethod();
		if (!allowRecursiveBinding && context.isBindingDataObject(type)) {
			return null;
		}
		DataObjectPropertyBinder propertyBinder = (propertyName, propertyTarget) -> bind(name.append(propertyName),
				propertyTarget, handler, context, false, false);
		return context.withDataObject(type, () -> {
			for (DataObjectBinder dataObjectBinder : this.dataObjectBinders.get(bindMethod)) {
                                        //属性绑定
				Object instance = dataObjectBinder.bind(name, target, context, propertyBinder);
				if (instance != null) {
					return instance;
				}
			}
			return null;
		});
	}

继续来到JavaBeanBinder#bind方法。

private <T> boolean bind(DataObjectPropertyBinder propertyBinder, Bean<T> bean, BeanSupplier<T> beanSupplier,
			Context context) {
		boolean bound = false;
                                    //这里遍历属性集合,处理每一个属性
                                     //这里就是[generate-unique-name, class-loader, driver-class-name, embedded-database-connection, jndi-name, name, password, type, url, username, xa, bean-class-loader]
                                    //对应DatasourceProperties的每个字段
		for (BeanProperty beanProperty : bean.getProperties().values()) {
			bound |= bind(beanSupplier, propertyBinder, beanProperty);
			context.clearConfigurationProperty();
		}
		return bound;

这里就是具体绑定一项属性了,如spring.datasource.password。

private <T> Object bindObject(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler,
			Context context, boolean allowRecursiveBinding) {
                        //查找配置的值
		ConfigurationProperty property = findProperty(name, target, context);
		if (property == null && context.depth != 0 && containsNoDescendantOf(context.getSources(), name)) {
			return null;
		}
		AggregateBinder<?> aggregateBinder = getAggregateBinder(target, context);
		if (aggregateBinder != null) {
			return bindAggregate(name, target, handler, context, aggregateBinder);
		}
		if (property != null) {
			try {
                        //绑定和注入
				return bindProperty(target, context, property);
			}
			catch (ConverterNotFoundException ex) {
				// We might still be able to bind it using the recursive binders
				Object instance = bindDataObject(name, target, handler, context, allowRecursiveBinding);
				if (instance != null) {
					return instance;
				}
				throw ex;
			}
		}
		return bindDataObject(name, target, handler, context, allowRecursiveBinding);
	}

bindObject

最后在:

if (property.isSettable()) {
			property.setValue(beanSupplier, bound);
		}

使用反射调用setter来设置DatasourceProperties的对应字段的值。

到这里DatasourceProperties已经创建成功,并且读取了对应的配置的值。

2.3 创建Datasource

回到DataSourceConfiguration$Hikari这个配置类。

@Bean
		@ConfigurationProperties(prefix = "spring.datasource.hikari")
		HikariDataSource dataSource(DataSourceProperties properties, JdbcConnectionDetails connectionDetails) {
			HikariDataSource dataSource = createDataSource(connectionDetails, HikariDataSource.class,
					properties.getClassLoader());
			if (StringUtils.hasText(properties.getName())) {
				dataSource.setPoolName(properties.getName());
			}
			return dataSource;
		}

上面的解读过程中DataSourceProperties、JdbcConnectionDetails已经创建完成了,现在可以成功创建HikariDataSource了。

要重点理解DataSourceProperties的注册和实例化(创建对象)、属性绑定(解析和存储配置文件的配置值)这两个流程,整体流程建议按照文章的解读内容结合实际调试boot源码来加深理解。
本文从@SpringBootApplication注解开始,解读了自动配置的相关组件类,和某一个自动配置类的实现原理,由表到里,由浅入深,文章内容较多,还有一些和SpringFramework源码解读相结合的内容,希望读者能够按照流程去实际的调试SpringBoot代码,加深对自动配置实现原理的理解。

四、参考材料

1.《Spring Boot Reference Documentation》(version 3.1.5)
2.SpringBoot源码(版本3.1.3)