0%

Spring Boot 自动装配

spring boot 源码版本 2.0.9.RELEASE。spring boot 高版本,没有走AutoConfigurationImportSelector#selectImports方法。

Spring Boot 自动装配

先看下Spring Boot 启动类注释 @SpringBootApplication。在这些注解中,重要的就是两个 @SpringBootConfiguration@EnableAutoConfiguration。从下图可以看到该注解是个组合注解,本文关于自动装配,用了到里面的 @EnableAutoConfiguration 里面的 @Import(AutoConfigurationImportSelector.class)

@Import({AutoConfigurationImportSelector.class}) 主要功能就是自动配置导入选择器。

AutoConfigurationImportSelector#selectImports

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 所以的配置存放在configuration中,获取所有依赖的配置
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 StringUtils.toStringArray(configurations);
}

参数含义:

  • AnnotationMetadata:SpringBoot启动类上的注释的全限定名,比如 @com.springboot.sample.starter.annotation.EnableSampleServer()
  • AutoConfigurationMetadata:依赖的AutoConfiguration类的元数据

AutoConfigurationImportSelector#getCandidateConfigurations

1
2
3
4
5
6
7
8
9
10
11
12
13
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;
}

从代码中可以看到,SpringFactoriesLoader.loadFactoryNames() 传入的参数有个 getSpringFactoriesLoaderFactoryClass,其实技术我们熟悉的 EnableAutoConfiguration 注释。

这里的逻辑就是通过 SpringFactoriesLoader.loadFactoryNames() 查询 configurations,如果没有查到,就打印日志。从日志中可以看出,实际需要需要加载的 Configuration 就是从 META-INF/spring.factories 中去加载的。

SpringFactoriesLoader#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
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, 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 {
// 从 META-INF/spring.factories 中加载 Conguration
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 factoryClassName = ((String) entry.getKey()).trim();
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);
}
}

从断点出可以看出,实际加载的就是 EnableAutoConfiguration的值。然后通过 Properties properties = PropertiesLoaderUtils.loadProperties(resource); 将文件内的内容封装成 Properties 对象。下图所加载的类就是一个自制的 spring boot starter 启动类。

总结

Spring Boot 启动会加载大量的启动类,会去搜索 META-INF/spring.factories 中的 EnableAutoConfiguration 的启动配置。

如何自己自作 Spring Boot Starter 依赖

先看下项目结构:

MonitorProperties

1
2
3
4
5
6
7
8
9
@ConfigurationProperties(prefix = "demo.monitor")
public class MonitorProperties {

private String loginUrl;
private String username;
private String password;
private String serverUrl;
// ...
}

这个类就是我们在使用这个starter类,需要在配置文件中配置的参数

MonitorAutoConfiguration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configurable
@ConditionalOnBean(SampleMarkerConfiguration.Marker.class)
@EnableConfigurationProperties(MonitorProperties.class)
public class MonitorAutoConfiguration {

@Autowired
private MonitorProperties monitorProperties;

@Bean
@ConditionalOnMissingBean
public MonitorService monitorService() {
return new MonitorService(monitorProperties);
}
}

自己制作 Configuration。@ConditionalOnBean 的含义是只有当 SampleMarkerConfiguration.Marker 这个 Bean 存在的时候,才会实现这个 Configuration,不然是不会初始化的。 @ConditionalOnMissingBean 注解的含义是当不存在 MonitorService 这个Bean 的时候,进行创建Bean。

SampleMarkerConfiguration

1
2
3
4
5
6
7
8
9
10
11
12
@Configurable
public class SampleMarkerConfiguration {

@Bean
public Marker sampleServerMarkerBean() {
return new Marker();
}

class Marker {

}
}

这个类其实就是一个激活 MonitorAutoConfiguration 的类。

EnableSampleServer

1
2
3
4
5
6
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(SampleMarkerConfiguration.class)
public @interface EnableSampleServer {
}

这个注释就是放在SpringBoot上,作为启动的激活的一个标识。

META-INF/spring.factories

使用

在需要的项目中引入starter依赖,在 Spring Boot 上加上 @EnableSampleServer 初始化类,这样就可以使用了。

starter中的具体业务逻辑或者功能,可以自己根据实际需求自己添加。

Spring Boot Starter Demo

Reference

客官,赏一杯coffee嘛~~~~