27-Spring Boot 注解开关原理

Spring 注解开关原理

1. 如果完成自动化配置

  1. 定义一个Annotation,让使用了这个Annotaion的应用程序自动化地注入一些类或者做一些底层的事情。
  2. 我们使用@Import注解配合一个配置类来完成上面的事情。
  3. 最简单的注解:EnableContentService,使用这个注解的程序会自动注入ContentService这个Bean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(ContentConfiguration.class)
public @interface EnableContentService {}

public interface ContentService {
void doSomething();
}

public class SimpleContentService implements ContentService {
@Override
public void doSomething() {
System.out.println("do some simple things");
}
}
  1. 之后在应用程序入口使用@EnableContentService注解,Spring Boot使用了ImportSelector来完成

2. ImportSelector的使用

  1. 修改@EnableContentService注解,添加属性policy,并且import一个Selector
1
2
3
4
5
6
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(ContentImportSelector.class)
public @interface EnableContentService {
String policy() default "simple";
}
  1. 这个ContentImportSelector根据EnableContentService注解里的policy加载不同的bean:也就是policy如果是core,则会加载CoreContentService,否则会加载SimpleContentService
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ContentImportSelector implements ImportSelector {

@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
Class<?> annotationType = EnableContentService.class;
AnnotationAttributes attributes = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(
annotationType.getName(), false));
String policy = attributes.getString("policy");
if ("core".equals(policy)) {
return new String[] { CoreContentConfiguration.class.getName() };
} else {
return new String[] { SimpleContentConfiguration.class.getName() };
}
}

}

2.1. CoreContentService

1
2
3
4
5
6
public class CoreContentService implements ContentService {
@Override
public void doSomething() {
System.out.println("do some import things");
}
}

2.2. CoreContentConfiguration

1
2
3
4
5
6
public class CoreContentConfiguration {
@Bean
public ContentService contentService() {
return new CoreContentService();
}
}

3. ImportSelector在Spring Boot中使用

  1. SpringBoot里的ImportSelector是通过SpringBoot提供的@EnableAutoConfiguration这个注解里完成的。
  2. 这个@EnableAutoConfiguration注解可以显式地调用,否则它会在@SpringBootApplication注解中隐式地被调用。
  3. @EnableAutoConfiguration注解中使用了EnableAutoConfigurationImportSelector作为ImportSelector。下面这段代码就是EnableAutoConfigurationImportSelector中进行选择的具体代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
public String[] selectImports(AnnotationMetadata metadata) {
try {
AnnotationAttributes attributes = getAttributes(metadata);
List<String> configurations = getCandidateConfigurations(metadata,
attributes);
configurations = removeDuplicates(configurations); // 删除重复的配置
Set<String> exclusions = getExclusions(metadata, attributes); // 去掉需要exclude的配置
configurations.removeAll(exclusions);
configurations = sort(configurations); // 排序
recordWithConditionEvaluationReport(configurations, exclusions);
return configurations.toArray(new String[configurations.size()]);
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
  1. 其中getCandidateConfigurations方法将获取配置类:
1
2
3
4
5
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
return SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
}
  1. SpringFactoriesLoader.loadFactoryNames方法会根据FACTORIES_RESOURCE_LOCATION这个静态变量从所有的jar包中读取META-INF/spring.factories文件信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
try {
Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
List<String> result = new ArrayList<String>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName); // 只会过滤出key为factoryClassNames的值
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
"] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
  1. getCandidateConfigurations方法中的getSpringFactoriesLoaderFactoryClass方法返回的是EnableAutoConfiguration.class,所以会过滤出key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的值。
  2. 之后根据文件内容进行配置初始化

4. 参考

  1. SpringBoot自动化配置的注解开关原理

27-Spring Boot 注解开关原理
https://spricoder.github.io/2022/04/13/Spring-Boot/27-Spring-Boot-%E6%B3%A8%E8%A7%A3%E5%BC%80%E5%85%B3%E5%8E%9F%E7%90%86/
作者
SpriCoder
发布于
2022年4月13日
许可协议