『SpringBoot 源码分析』自动装配
- 基于 2.2.9.RELEASE
- 问题:Spring Boot 到底是如何进行自动配置的,都把哪些组件进行了自动配置?
- 首先创建测试主程序
package com.lagou;
@SpringBootApplication
public class SpringBootMytestApplication{
public static void main(String[] args) {
SpringApplication.run(SpringBootMytestApplication.class, args);
}
}
- 创建测试 Controller
package com.lagou.controller;
@RestController
public class TestController {
@RequestMapping("/test")
public String test(){
System.out.println("源码环境构建成功...");
return "源码环境构建成功";
}
}
@SpringBootConfiguration
- 首先在主程序当中,我们通常会在主程序标记一个 @SpringBootApplication 注解
@SpringBootApplication
public class SpringBootMytestApplication{
public static void main(String[] args) {
SpringApplication.run(SpringBootMytestApplication.class, args);
}
}
- 当点击进去注解之后,会发现注解是个组合注解,其中 @SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan 是 Spring 定义的
@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 {
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
- @SpringBootConfiguration:当我们再点击进去之后,会发现其实是个包装了 @Configuration 的注解,也就是说标注了 @SpringBootApplication 的类,其实是主配置类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
@EnableAutoConfiguration
- 上面只是阐述了 @SpringBootConfiguration 是标注主配置类的一个关键配置,但是并没有引入自动装配的组件,还有一个注解 @EnableAutoConfiguration,用于启动自动配置功能
- 重点:Spring 很多以 Enable 开头的注解,其作用是借助 @Import 来收集并注册特定场景的 bean,并加载到 IOC 容器中
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
- 其中 @AutoConfigurationPackage 其实也是使用 @Import 导入组件 AutoConfigurationPackages.Registrar.class
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
- 当导入 AutoConfigurationPackages.Registrar.class 时,会调用 registerBeanDefinitions() 将信息注入到注册中心
public abstract class AutoConfigurationPackages {
...
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImport(metadata).getPackageName());
}
...
}
}
- 当调用 register() 方法,首先判断注册中心,是否已经包含名为 org.springframework.boot.autoconfigure.AutoConfigurationPackages 的 BasePackages.class 的 beanDefinition,没有的话,则创建,并且把包名 com.lagou 注册进去,供之后使用,例如给 JPA entity 的扫描器用来扫描开发人员通过注解 @Entity 定义的 entity 类
public abstract class AutoConfigurationPackages {
private static final String BEAN = AutoConfigurationPackages.class.getName();
...
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);
}
}
...
static final class BasePackages {
...
}
}
@Import(AutoConfigurationImportSelector.class)
- AutoConfigurationImportSelector 可以帮助 Springboot 应用将所有符合条件的 @Configuration 配置都加载到当前 IOC 容器中。从继承的关系就可以看到,AutoConfigurationImportSelector 实现了各种 Aware,可以获取到 IOC 容器的关键配置。同时实现了 DeferredImportSelector 接口,而关键的自动配置相关逻辑,就在 DeferredImportSelectorGrouping.getImports() 方法里
- 我们先直接来看调用,首先 DeferredImportSelectorGrouping.getImports() 会遍历 deferredImports 属性,然后通过 DeferredImportSelector.Group 来处理自动装配
class ConfigurationClassParser {
...
private static class DeferredImportSelectorGrouping {
private final DeferredImportSelector.Group group;
private final List<DeferredImportSelectorHolder> deferredImports = new ArrayList<>();
...
public Iterable<Group.Entry> getImports() {
for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
this.group.process(deferredImport.getConfigurationClass().getMetadata(),
deferredImport.getImportSelector());
}
return this.group.selectImports();
}
}
}
- 当调用 process() 方法时,会先将 deferredImportSelector 转换为 AutoConfigurationImportSelector,然后继续调用 getAutoConfigurationEntry() 方法
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
...
private static class AutoConfigurationGroup
implements DeferredImportSelector.Group, BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware {
@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
...
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
...
}
}
}
- 接下来会调用 getCandidateConfigurations() 方法,去扫描所有的配置类
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
...
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
...
}
}
- 接下来继续会去调用 SpringFactoriesLoader.loadFactoryNames() 方法,主要是去获取所有 spring.factories 中,key 为 org.springframework.boot.autoconfigure.EnableAutoConfiguration 的所有装配类
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
...
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
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;
}
}
- 当调用 loadFactoryNames() 时,会先扫描所有的 META-INF/spring.factories 下的内容,然后拿到键为 org.springframework.boot.autoconfigure.EnableAutoConfiguration 的所有值,并返回
public final class SpringFactoriesLoader {
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
...
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
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 {
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 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);
}
}
}
- META-INF/spring.factories 所在的位置
- 示例内容如下
- 当拿到自动装配类以后,回到 getAutoConfigurationEntry() 方法,继续执行一些过滤步骤,包括移除重新的配置类,排除 exclude 指定的类等等
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
...
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);
}
}
- 我们简单看看 filter() 流程,核心点在于过滤所有 @ConditionOnxxx 的条件,例如 CacheAutoConfiguration,它得存在相应的 bean 时,才会进行装配
- 完成过滤以后,返回到 process() 中,还会把返回的 AutoConfigurationEntry 装进 autoConfigurationEntries 属性集合中以及把符合条件的自动装配的类作为 key,存入 entries 属性集合中
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
...
private static class AutoConfigurationGroup
implements DeferredImportSelector.Group, BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware {
@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
...
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
this.autoConfigurationEntries.add(autoConfigurationEntry);
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
}
}
- 当处理完类的读取以后,DeferredImportSelectorGrouping 还需要执行一下 selectImports() 方法
class ConfigurationClassParser {
...
private static class DeferredImportSelectorGrouping {
private final DeferredImportSelector.Group group;
private final List<DeferredImportSelectorHolder> deferredImports = new ArrayList<>();
...
public Iterable<Group.Entry> getImports() {
for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
this.group.process(deferredImport.getConfigurationClass().getMetadata(),
deferredImport.getImportSelector());
}
return this.group.selectImports();
}
}
}
- 主要是对自动装配类再进行一次过滤,然后根据 @Order 进行排序
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
...
private static class AutoConfigurationGroup
implements DeferredImportSelector.Group, BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware {
private final Map<String, AnnotationMetadata> entries = new LinkedHashMap<>();
private final List<AutoConfigurationEntry> autoConfigurationEntries = new ArrayList<>();
...
@Override
public Iterable<Entry> selectImports() {
if (this.autoConfigurationEntries.isEmpty()) {
return Collections.emptyList();
}
Set<String> allExclusions = this.autoConfigurationEntries.stream()
.map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
.map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
.collect(Collectors.toCollection(LinkedHashSet::new));
processedConfigurations.removeAll(allExclusions);
return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
.map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
.collect(Collectors.toList());
}
}
}
- 总结
- 源码流程
- 自动配置类总结
HttpEncodingAutoConfiguration
- 以 HttpEncodingAutoConfiguration 装配为例来简单再回归一下装配过程,首先肯定是去先扫描 META-INF/spring.factories 下所有的文件 key 为 org.springframework.boot.autoconfigure.EnableAutoConfiguration 的值,经过一定条件过滤以后,再装入到容器当中。其中就包含了 org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration
- 那 HttpEncodingAutoConfiguration 为什么没被过滤掉呢?我们简单看看它上面条件
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(HttpProperties.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(CharacterEncodingFilter.class)
@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
private final HttpProperties.Encoding properties;
public HttpEncodingAutoConfiguration(HttpProperties properties) {
this.properties = properties.getEncoding();
}
...
}
- @EnableConfigurationProperties:该注解起到的作用是说把配置文件(如 application.yml)中的
spring.http
开头的属性,都装到 HttpProperties 类中。例如我们想设置 spring.http.encoding.charset=utf-8
,它就会自动装到对应属性中
@ConfigurationProperties(prefix = "spring.http")
public class HttpProperties {
private final Encoding encoding = new Encoding();
...
public static class Encoding {
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
private Charset charset = DEFAULT_CHARSET;
...
}
}
- @ConditionalOnWebApplication:这个是条件之一,要求的是启动的时候,容器类型是 ConditionalOnWebApplication.Type.SERVLET,只要在 pom.xml 配置了 spring-boot-starter-web,这一定会设置成这个类型
- @ConditionalOnClass 会判断 classpath 是否存在 CharacterEncodingFilter.class 这个类,只要在 pom.xml 配置了 spring-boot-starter-web,肯定会有这个类
- @ConditionalOnProperty 要求必须在 application.yml 中,配置了
spring.http.encoding.enabled
属性,但是属性上 matchIfMissing 允许缺失,所以这个条件也是通过的
- 满足了上面的所有条件后,自动装配类就没被过滤掉,加到容器中来
@ComponentScan
- 对于注解 @SpringBootApplication 里面还有一个关键注解为 @ComponentScan
...
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
...
}
- 对于 @ComponentScan 里面并没有配置像 value 属性用于指定要扫描的路径。而且现在只配置了 Filter 的过滤,但是为什么 springboot 能够扫描到 @RestController 标注的类并注入容器当中呢?原来是因为 @ComponentScan 如果没配置 value 属性的话,默认会扫当前类所在目录以及子目录下的 Component 标记,所以对象就可以扫到