echo

任生命穿梭 时间的角落

0%

SpringBoot自动配置解析

使用Spring Initializer 生成一个最简单的 Web 应用,打开启动类。

1
2
3
4
5
6
7
@SpringBootApplication
public class Demo01Application {

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

点击 @SpringBootApplication ,我们可以看到如下注解信息:

1
2
3
4
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

@SpringBootConfiguration

@SpringBootConfiguration 表示一个类提供Spring Boot应用程序。它的作用等同于@Configuration,以便可以自动找到配置。应用程序应该只包含一个@SpringBootConfiguration ,通常的Spring Boot应用程序将从@SpringBootApplication自动继承它。

1
2
3
4
5
6
@Configuration
public @interface SpringBootConfiguration {
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;

}

proxyBeanMethods 方法指定是否应代理@Bean方法:默认为 Full模式,保证每个@Bean方法被调用多少次返回的Bean都是单实例的;也可为 Lite 模式(值为 false),每次调用 @Bean 方法都会返回新的实例。
如果配置类 Bean 之间有依赖关系,则强制使用 Full 模式。
新建 MyConfig 配置类来测试

1
2
3
4
5
6
7
8
@Configuration
public class MyConfig {

@Bean
public User getUser(){ //User 为一个POJO,这里省略定义
return new User("li", 18);
}
}

接下来在 Main 方法中添加如下代码:

1
2
3
4
MyConfig bean = run.getBean(MyConfig.class);
User u1 = bean.getUser();
User u2 = bean.getUser();
System.out.println("U1 == U2? " + (u1 == u2));

运行,可以看到结果为 true,在用户代码中直接调用@Bean方法的情况下也返回共享的单例bean实例。

接下来为 @Configuration 添加配置@Configuration(proxyBeanMethods = false),再次运行,可以看到结果为 false,返回了两个不同的实例。

接下来测试组建依赖,修改 MyConfig 为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Configuration(proxyBeanMethods = true)
public class MyConfig {

@Bean
public User getUser(){
User u = new User("li", 18);
u.setPet(getPet());
return u;
}

@Bean
public Pet getPet(){
return new Pet("tom");
}
}

如果将 proxyBeanMethods 改为 false,IDEA 将会提示“使用 Bean 标记的方法被 proxyBeanMethods 为 false 的配置类调用,将其改为 true 或者使用依赖注入。”

@ComponentScan

替换默认的 Filter,替换为AutoConfigurationExcludeFilter

@EnableAutoConfiguration

1
2
3
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}
  1. @AutoConfigurationPackage
    使用 @AutoConfigurationPackage 来注册包。如果没有指定 base packages 或 base package classes,则注册带有@AutoConfigurationPackage 的类所在的包。

  2. @Import(AutoConfigurationPackages.Registrar.class)
    AutoConfigurationImportSelector 中有如下函数:

    1
    2
    3
    4
    5
    6
    7
    8
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
    return NO_IMPORTS;
    }
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }

找到 getAutoConfigurationEntry 定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 ,用来获取所有的配置类,点击进入。

1
2
3
4
5
6
7
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 方法

1
2
3
4
5
6
7
8
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}

点击进入 loadSpringFactories方法

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
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) {
return result;
}

result = new HashMap<>();
try {
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
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();
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
for (String factoryImplementationName : factoryImplementationNames) {
result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
.add(factoryImplementationName.trim());
}
}
}

// Replace all lists with unmodifiable lists containing unique elements
result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
cache.put(classLoader, result);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
return result;
}

打断点运行到 classLoader.getResource位置,我们可以看到 FACTORIES_RESOURCE_LOCATION 此时为META-INF/spring.factories,这里扫描系统中所有 META-INF/spring.factories位置的文件,spring-boot-autoconfigure-2.4.3.jar 中的 spring.factories 文件写死了 springboot 启动就给容器中加载所有的配置类(xxxxx AutoConfiguration)。

在 spring boot 启动过程中,所有的自动配置都是条件配置,以org.springframework.boot.autoconfigure.web.servlet 为例:

1
2
3
4
5
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)

@ConditionalOnClass(DispatcherServlet.class) 注解指定 只有 DispatcherServlet.class 在类路径中存在才自动配置。

1
2
3
4
5
6
7
@Bean
@ConditionalOnBean(MultipartResolver.class)
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
public MultipartResolver multipartResolver(MultipartResolver resolver) {
// Detect if the user has created a MultipartResolver but named it incorrectly
return resolver;
}

还可以自己配置 MultipartResolver,如果自行定义了这个 Bean,那么 spring boot 就会使用用户自定义的。

总结:

  • SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration

  • 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。xxxxProperties里面拿。xxxProperties和配置文件进行了绑定

  • 生效的配置类就会给容器中装配很多组件

  • 只要容器中有这些组件,相当于这些功能就有了

  • 定制化配置

    • 用户直接自己@Bean替换底层的组件
    • 用户去看这个组件是获取的配置文件什么值就去修改。

xxxxxAutoConfiguration —> 组件 —> xxxxProperties里面拿值 —-> application.properties