基于 Spring Boot 开发的应用中,经常需要引用别的 starter 组件或者自定义公司内部使用的 starter。而 starter 的基础是 Spring Boot的自动装配。
什么是自动装配
自动装配,简单说就是自动将 Bean 装配到 IoC 容器中。下面通过 Spring Boot 整合 Redis 的案例感受一下自动装配的用法。
- 项目中引入 starter 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.1.7.RELEASE</version>
</dependency>
- 添加配置:在 application.properties 中添加:
spring.redis.host=localhost
spring.redis.port=6379
- 使用:新建测试类 RedisTestController 中使用 RedisTemplate
@RestController
public class RedisTestController {
@Autowired
RedisTemplate<String, String> redisTemplate;
@GetMapping("/hello")
public String hello() {
redisTemplate.opsForValue().set("key", "value");
return "Hello World";
}
}
这是我们一般的使用方式。在这个案例中我们并没有通过 xml 或者注解的方式将 RedisTemplate 注入 IoC 容器中,为什么在 Controller 中可以直接通过 @Autowired 注解注入 RedisTemplate 实例呢?这说明,我们使用该实例之前,容器中已经存在 RedisTemplate 了。这就是 Spring Boot 自动装配的结果。
自动装配的实现
@EnableAutoConfiguration
自动装配在 Spring Boot 中是通过 @EnableAutoConfiguration 注解实现的,这个注解的声明是在启动类注解 @SpringBootApplication 内。
@SpringBootApplication
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
点进 @SpringBootApplication 中:
@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 {
可以看到这个是复合注解,点进 @EnableAutoConfiguration 注解
@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 {};
}
@EnableAutoConfiguration 中 @AutoConfigurationPackage 的作用是把使用了该注解的类所在的包和子包下所有组件扫描到 Spring IoC 容器中。同时,@Import 导入了 @AutoConfigurationImportSelector,这个类会导入需要自动配置的类。
@AutoConfigurationImportSelector
点进 @AutoConfigurationImportSelector 中:
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
// 获取需要自动配置的类
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
该注解中重写了顶级接口 ImportSelector 中的 selectImports 方法来导入自动配置类,其中又调用了自身的 getAutoConfigurationEntry 方法:
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
// 获取所有需要自动配置的候选类
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
// 移除重复的配置项
configurations = this.removeDuplicates(configurations);
// 排除:根据exclude属性把不需要自动装配的配置类排除
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.filter(configurations, autoConfigurationMetadata);
// 广播事件
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
ImportSelector 可以实现批量装配,并且还可以通过逻辑处理来实现 Bean 的选择性装配,也就是可以根据上下文来决定哪些类能够被 IoC 容器初始化。
总的来说,getAutoConfigurationEntry 方法先获取所有配置类,再通过去重,exclude 排除等操作,得到最终需要自动配置的配置类。这里着重看如何获取所有的配置类,也就是 getCandidateConfigurations 方法:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
}
protected ClassLoader getBeanClassLoader() {
return this.beanClassLoader;
}
这里调用 SpringFactoriesLoader.loadFactoryNames 来加载配置,两个参数,一个是自动配置类的 Class 类型,
EnableAutoConfiguration.class ,另一个是本类的一个类加载器 this.beanClassLoader。
查看 SpringFactoriesLoader.loadFactoryNames 方法:
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
// 获取Class文件的全限定名,这里对于自动配置,即EnableAutoConfiguration.class.getName()
String factoryTypeName = factoryType.getName();
// 根据下面的方法,通过给定的类加载器加载
return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
// 先从缓存中取
if (result != null) {
// 如果缓存已经加载过,直接返回结果
return result;
} else {
try {
// 加载META-INF/spring.factories文件,获取URL集合
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();
// 遍历URL集合
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
// 读取配置
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();
String factoryTypeName = ((String)entry.getKey()).trim();
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String factoryImplementationName = var9[var11];
// 结果转成集合
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}
这里用到了 SpringFactoriesLoader ,它是 Spring 内部提供的一种约定俗成的加载方式,类似于Java 中的 SPI。简单说,它会扫描 classpath 下的 META-INF/spring.factories 文件,spring.factories 文件中的数据以 key = value 形式存储,而 SpringFactoriesLoader.loadFactoryNames 会根据 key 得到对应的 value 值。因此,这个场景中key 对应为 EnableAutoConfiguration ,value 是多个配置类,也就是 getCandidateConfigurations 方法返回值。
spring.factories
找到依赖中的 spring.factories
该配置文件以键值对给出需要自动配置的配置类,通过上述的代码获取 配置类的 String 数组。
随便找个值如 org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration 作为示例:
// 配置类
@Configuration(proxyBeanMethods = false)
// 条件判断,只有指定类加载后,才加载找个配置
@ConditionalOnClass({RabbitTemplate.class, Channel.class})
// 绑定的配置文件
@EnableConfigurationProperties({RabbitProperties.class})
// 导入其他配置
@Import({RabbitAnnotationDrivenConfiguration.class})
public class RabbitAutoConfiguration {
public RabbitAutoConfiguration() {
}
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({RabbitMessagingTemplate.class})
@ConditionalOnMissingBean({RabbitMessagingTemplate.class})
@Import({RabbitAutoConfiguration.RabbitTemplateConfiguration.class})
protected static class MessagingTemplateConfiguration {
protected MessagingTemplateConfiguration() {
}
@Bean
@ConditionalOnSingleCandidate(RabbitTemplate.class)
public RabbitMessagingTemplate rabbitMessagingTemplate(RabbitTemplate rabbitTemplate) {
return new RabbitMessagingTemplate(rabbitTemplate);
}
}
可以看到这里对配置做了一定的约束,比如加载顺序、加载条件、额外的组件等。
@ConditionalOnClass :该系列注解用于条件判断,之前写过关于该注解的笔记。
另外,该配置类还绑定了一个配置文件 @EnableConfigurationProperties({RabbitProperties.class}) ,可以根据绑定的这个配置文件做个性化修改:
@ConfigurationProperties(
prefix = "spring.rabbitmq"
)
public class RabbitProperties {
private String host = "localhost";
private int port = 5672;
private String username = "guest";
private String password = "guest";
private final RabbitProperties.Ssl ssl = new RabbitProperties.Ssl();
private String virtualHost;
private String addresses;
@DurationUnit(ChronoUnit.SECONDS)
private Duration requestedHeartbeat;
private boolean publisherReturns;
private ConfirmType publisherConfirmType;
private Duration connectionTimeout;
private final RabbitProperties.Cache cache = new RabbitProperties.Cache();
private final RabbitProperties.Listener listener = new RabbitProperties.Listener();
private final RabbitProperties.Template template = new Rab
@ConfigurationProperties 标注当前类是配置文件,它绑定前缀为 ”spring.rabbitmq“ 的配置,我们添加到配置会被传递到具体的组件覆盖默认配置。
总结
简单总结 Spring Boot 的自动装配流程:
- 通过 @EnableAutoConfiguration 注解开启自动配置。
- 自动加载类路径下 META-INF/spring.factories 文件,读取以 EnableAutoConfiguration 的全限定类名对应的值,作为候选配置类。这里默认给出了很多组件的自动配置类。
- 自动配置类可能会再导入一些依赖(比如 @Import),或者给出一些配置条件,并且会通过 @Bean 注解把该组件所包含的组件注入到 spring 容器中以供使用。
- 自动配置类还可能会绑定 xxxProperties 配置文件类,该类又会和应用程序中的
application.properties
中的指定前缀绑定。第 3 步注入组件的时候,组件可能还会获取配置文件类中的内容,所以用户可以在 application.properties 修改指定配置,来制定自己的个性化服务。