源码分析——Spring Boot是如何实现自动配置的

目录

  • 前言
  • 自动配置注解
  • 加载spring.factories
  • 加载配置文件
  • 总结

前言

一直以来都把Spring Boot看作一个黑盒子来使用,虽然也了解过一些原理,但还是朦朦胧胧的感觉。今天就来学习、记录下Spring Boot的自动配置原理。

自动配置注解

在新建好一个Spring Boot项目后,会有如下经典启动类:

@SpringBootApplication
public class BlogApplication {

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

}

自动配置的奥秘就隐藏在 @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 {

可以看到这是个复合型注解,其中就有想要的 @EnableAutoConfiguration(开启自动配置),继续查看这个注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

@EnableAutoConfiguration又使用@Import导入了AutoConfigurationImportSelector.class,从命名就能看出来,这个类会选择导入需要自动配置的类,进而查看:

	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
				.loadMetadata(this.beanClassLoader);

		// 这里获取需要自动配置的类
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
				annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}

这个类重写了顶级接口ImportSelectorselectImports方法来导入自动配置类,其中又调用了自身的getAutoConfigurationEntry方法,进一步查看:

	protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
			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 = filter(configurations, autoConfigurationMetadata);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

这个方法除了获取需要的配置类,还做了一些其他工作,比如去重、按照约定再排除一部分(比如有时候我们不需要加载某个功能,就可以在 @SpringBootApplication中设置exclude字段来排除)。这里着重看获取配置类,即查看getCandidateConfigurations方法:

	protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
				getBeanClassLoader());
		Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
				+ "are using a custom packaging, make sure that file is correct.");
		return configurations;
	}

	protected Class<?> getSpringFactoriesLoaderFactoryClass() {
		return EnableAutoConfiguration.class;
	}
	
	protected ClassLoader getBeanClassLoader() {
		return this.beanClassLoader;
	}

这里又调用了SpringFactoriesLoader.loadFactoryNames来加载配置,并传入两个参数,一个是自动配置类的Class类型:EnableAutoConfiguration.class,一个是本类的一个类加载器:this.beanClassLoader。注意当前类AutoConfigurationImportSelector是在org.springframework.boot.autoconfigure包下的。

查看SpringFactoriesLoader.loadFactoryNames方法:

	public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
	
	public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		// 获取Class文件的全限定名,这里对于自动配置,即EnableAutoConfiguration.class.getName()
		// 结果是:org.springframework.boot.autoconfigure.EnableAutoConfiguration
		String factoryTypeName = factoryType.getName();
		// 继续根据下边的私有方法,通过给定的类加载器加载
		return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
	}

	private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		// 先从缓存中取
		MultiValueMap<String, String> result = cache.get(classLoader);
		// 如果缓存已加载过,则直接返回结果
		if (result != null) {
			return result;
		}
		// 没加载过
		try {
			// 加载文件,获得URL集合
			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();
			// 遍历URL集合
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				// 读取配置文件
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryTypeName, factoryImplementationName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

这里首先可以看到的是缓存的应用,这也是很多源码中的老套路了,把加载到的东西存起来,下次再加载的时候直接取就行了,很值得学习的一点。

此外,可以看出来,首次加载的时候,也即缓存中不存在的时候,它是通过类加载器去加载类路径下的FACTORIES_RESOURCE_LOCATION指明的文件,这里它是一个 static final 的字符串,为"META-INF/spring.factories",也就是说,会去加载类路径下的META-INF文件夹下的spring.factories配置文件。下边具体解析过程等看完配置文件之后再看,先去找找配置文件。

加载spring.factories

因为当前是在追溯自动配置,所以就查看自动配置包,也即前边所说的org.springframework.boot.autoconfigure包。查看该包的内容:
源码分析——Spring Boot是如何实现自动配置的_第1张图片

果然,有个指定目录下的spring.factories文件,查看该文件,这里有很多内容,当然也有想要的自动配置相关内容:
源码分析——Spring Boot是如何实现自动配置的_第2张图片

这里是以键值对的形式给出了配置,key是某个类的全限定名,value是很多需要自动配置的类的全限定名(类名都是xxxAutoConfiguration格式)的长串,彼此之间用逗号,隔开(\只表示换行而已,无实际用处)。可以看到很多熟悉的面孔,比如切面AOP的自动配置(AopAutoConfiguration),缓存的自动配置(CacheAutoConfiguration)。

现在回到之前的解析过程:

	// 新建一个result,result是一个特殊的map,value是一个列表List。即Map>类型
	// 可以查看LinkedMultiValueMap的定义,底层就是依托一个Map>
	result = new LinkedMultiValueMap<>();
	while (urls.hasMoreElements()) {
		URL url = urls.nextElement();
		UrlResource resource = new UrlResource(url);
		Properties properties = PropertiesLoaderUtils.loadProperties(resource);
		// 获取键值对
		for (Map.Entry<?, ?> entry : properties.entrySet()) {
			// 获取key的名字,比如org.springframework.boot.autoconfigure.EnableAutoConfiguration
			String factoryTypeName = ((String) entry.getKey()).trim();
			// 这里是调用一个工具类方法来获取value,该工具类以 ,作为分隔符,把字符串分割为字符串数组
			for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
				result.add(factoryTypeName, factoryImplementationName.trim());
			}
		}
	}
	// 结果放入缓存,以类加载器为key,result为value
	cache.put(classLoader, result);
	return result;

现在看这个解析过程就比较清晰了,它是把原始文件中的键值对做了修改,原始文件中的value是以,分割的字符串,这里通过构造一个result,将value改为一个List

加载配置文件

再进一步,看看把这些自动配置类加载进去后会做什么,以典型的ServletWebServerFactoryAutoConfiguration类为例,查看它的定义:

// 表明当前是个配置类
@Configuration(proxyBeanMethods = false)
// 指明自动配置类顺序
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
// 条件判断,只有在指定类加载后,才加载这个配置
@ConditionalOnClass(ServletRequest.class)
// web环境下才加载这个配置
@ConditionalOnWebApplication(type = Type.SERVLET)
// 绑定配置文件
@EnableConfigurationProperties(ServerProperties.class)
// 导入其它配置
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
		ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
		ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
		ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
	// 添加ServletWebServerFactoryCustomizer组件
	@Bean
	public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties) {
		return new ServletWebServerFactoryCustomizer(serverProperties);
	}

	@Bean
	@ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
	public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(
			ServerProperties serverProperties) {
		return new TomcatServletWebServerFactoryCustomizer(serverProperties);
	}
	...

这里可以看到它对配置做了一些要求,比如加载顺序、加载条件等,并且还有几个标记了@Bean注解的方法。在配置类中,这些方法会给spring容器中添加对应的组件,添加进去以后我们就能获取、使用这些组件了。

此外,这个配置类还绑定一个对应的配置文件 @EnableConfigurationProperties(ServerProperties.class),它可以根据这个绑定的配置文件做个性化的设置。查看这个配置文件类定义:

@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {

	/**
	 * Server HTTP port.
	 */
	private Integer port;

	/**
	 * Network address to which the server should bind.
	 */
	private InetAddress address;

这里就和我们日常使用的比较近了。@ConfigurationProperties标注当前类是个配置文件,它绑定前缀为“server”的配置(prefix = "server"),下边的私有字段就是可配置的属性。比如port,这就对应着我们经常在配置文件中写的server.port=8080,指明绑定的端口号。上边的配置类中:

	@Bean
	@ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
	public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(
			ServerProperties serverProperties) {
		return new TomcatServletWebServerFactoryCustomizer(serverProperties);
	}

就把这个配置文件的内容(serverProperties)传递给了具体的组件(可见默认是Tomcat),我们配置的端口号就会通过这条路径最终传递给Tomcat,并最终让它在指定端口监听。

总结

简单总结下Spring Boot自动配置的流程:

  1. 通过@EnableAutoConfiguration注解开启自动配置(@SpringBootApplication注解默认已包含)
  2. 自动加载类路径下META-INF/spring.factories文件,读取以EnableAutoConfiguration的全限定类名对应的值,作为候选配置类。这里默认给出了很多组件的自动配置类。
  3. 自动配置类可能会再导入一些依赖(比如@Import),或者给出一些配置条件,并且会通过@Bean注解把该组件所包含的组件注入到spring容器中以供使用。
  4. 自动配置类还可能会绑定xxxProperties配置文件类,该类又会和应用程序中的application.properties中的指定前缀绑定。第3步注入组件的时候,组件可能还会获取配置文件类中的内容,所以用户可以在application.properties修改指定配置,来制定自己的个性化服务。

你可能感兴趣的:(Spring,Boot)