Spring Boot @EnableAutoConfiguration解析

刚做后端开发的时候,最早接触的是基础的spring,为了引用二方包提供bean,还需要在xml中增加对应的包<context:component-scan base-package="xxx" /> 或者增加注解@ComponentScan({ "xxx"})。当时觉得挺urgly的,但也没有去研究有没有更好的方式。

直到接触Spring Boot 后,发现其可以自动引入二方包的bean。不过一直没有看这块的实现原理。直到最近面试的时候被问到。所以就看了下实现逻辑。

更多文章见个人博客:https://github.com/farmerjohngit/myblog

使用姿势

讲原理前先说下使用姿势。

在project A中定义一个bean。

1
2
3
4
5
6
7
package com.wangzhi;
 
import org.springframework.stereotype.Service;
 
@Service
public class Dog {
}

import org.springframework.stereotype.Service;

@Service
public class Dog {
}

并在该project的resources/META-INF/下创建一个叫spring.factories的文件,该文件内容如下

1
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.wangzhi.Dog

然后在project B中引用project A的jar包。

projectA代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.wangzhi.springbootdemo;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
 
@EnableAutoConfiguration
public class SpringBootDemoApplication {
 
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringBootDemoApplication.class, args);
        System.out.println(context.getBean(com.wangzhi.Dog.class));
    }
 
}

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;

@EnableAutoConfiguration
public class SpringBootDemoApplication {

public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringBootDemoApplication.class, args);
System.out.println(context.getBean(com.wangzhi.Dog.class));
}

}

打印结果:

1
2
com.wangzhi.Dog@3148f668<code>
</code>

原理解析

总体分为两个部分:一是收集所有spring.factoriesEnableAutoConfiguration相关bean的类,二是将得到的类注册到spring容器中。

收集bean定义类

在spring容器启动时,会调用到AutoConfigurationImportSelector#getAutoConfigurationEntry

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
protected AutoConfigurationEntry getAutoConfigurationEntry(
        AutoConfigurationMetadata autoConfigurationMetadata,
        AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    // EnableAutoConfiguration注解的属性:exclude,excludeName等
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // 得到所有的Configurations
    List<String> configurations = getCandidateConfigurations(annotationMetadata,
            attributes);
    // 去重
    configurations = removeDuplicates(configurations);
    // 删除掉exclude中指定的类
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    configurations = filter(configurations, autoConfigurationMetadata);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

getCandidateConfigurations会调用到方法loadFactoryNames

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
        // factoryClassName为org.springframework.boot.autoconfigure.EnableAutoConfiguration
		String factoryClassName = factoryClass.getName();
        // 该方法返回的是所有spring.factories文件中key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的类路径
		return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
	}
 
 
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
 
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}
 
		try {
            // 找到所有的"META-INF/spring.factories"
			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
                // 读取文件内容,properties类似于HashMap,包含了属性的key和value
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryClassName = ((String) entry.getKey()).trim();
                    // 属性文件中可以用','分割多个value
					for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryClassName, factoryName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}

try {
// 找到所有的"META-INF/spring.factories"
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
// 读取文件内容,properties类似于HashMap,包含了属性的key和value
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryClassName = ((String) entry.getKey()).trim();
// 属性文件中可以用','分割多个value
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}

注册到容器

在上面的流程中得到了所有在spring.factories中指定的bean的类路径,在processGroupImports方法中会以处理@import注解一样的逻辑将其导入进容器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public void processGroupImports() {
    for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
        // getImports即上面得到的所有类路径的封装
        grouping.getImports().forEach(entry -> {
            ConfigurationClass configurationClass = this.configurationClasses.get(
                    entry.getMetadata());
            try {
                // 和处理@Import注解一样
                processImports(configurationClass, asSourceClass(configurationClass),
                        asSourceClasses(entry.getImportClassName()), false);
            }
            catch (BeanDefinitionStoreException ex) {
                throw ex;
            }
            catch (Throwable ex) {
                throw new BeanDefinitionStoreException(
                        "Failed to process import candidates for configuration class [" +
                                configurationClass.getMetadata().getClassName() + "]", ex);
            }
        });
    }
}
 
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
			Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
	...
    // 遍历收集到的类路径
    for (SourceClass candidate : importCandidates) {
       ...
        //如果candidate是ImportSelector或ImportBeanDefinitionRegistrar类型其处理逻辑会不一样,这里不关注
     	// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
						// process it as an @Configuration class
						this.importStack.registerImport(
								currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
		// 当作 @Configuration 处理			
        processConfigurationClass(candidate.asConfigClass(configClass));
   ...
}
 
    ...
}

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
...
// 遍历收集到的类路径
for (SourceClass candidate : importCandidates) {
...
//如果candidate是ImportSelector或ImportBeanDefinitionRegistrar类型其处理逻辑会不一样,这里不关注
// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
// process it as an @Configuration class
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
// 当作 @Configuration 处理
processConfigurationClass(candidate.asConfigClass(configClass));
...
}

...
}

可以看到,在第一步收集的bean类定义,最终会被以Configuration一样的处理方式注册到容器中。

End

@EnableAutoConfiguration注解简化了导入了二方包bean的成本。提供一个二方包给其他应用使用,只需要在二方包里将对外暴露的bean定义在spring.factories中就好了。对于不需要的bean,可以在使用方用@EnableAutoConfigurationexclude属性进行排除。

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注

关注我们