Spring Boot不得不說的一個特點就是自動裝配,它是starter的基礎,也是spring boot的核心,那到底什么是自動裝配呢?
簡單的說,就是自動將Bean裝配到IoC容器中。接下來,我們通過一個例子來了解下自動裝配。
- 添加starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- 在application.properties中添加Redis的配置
spring.redis.host=localhost
spring.redis.port=6379
- 在Controller中使用redisTemplate對Redis進行操作
@RestController
public class RedisController {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@GetMapping("/test")
public String test() {
redisTemplate.opsForValue().set("test", "test demo");
return "Test Demo";
}
}
在上面例子中,我們并沒有通過XML形式或者注解形式把RedisTemplate注入到IoC容器中,但是在RedisController中卻可以直接使用@Autowired來注入redisTemplate實例,這就表明IoC容器中已經存在RedisTemplate實例了,這就是Spring Boot自動裝配機制。
自動裝配的實現
自動裝配在Spring boot中是通過@EnableAutoConfiguration注解來開啟的,這個注解是在啟動類注解@SpringBootApplication中聲明的。
@SpringBootApplication
public class SpringBootDemoApplication {
public static void main(String[] args) {
SpringApplication,run(SpringBootDemoApplication.class, args);
}
}
查看@SpringBootApplication注解,可以看到@EnableAutoConfiguration注解的聲明
@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{
其實spring 3.1版本就已經開始支持@Enable注解了,它的主要作用是把相關組件的Bean裝配到IoC容器中。@Enable注解對JavaConfig的進一步完善,使開發者減少了配置代碼量,降低了使用難度,比較常見的Enable注解有@EnableWebMvc,@EnableScheduling等。
如果我們要基于JavaConfig的形式來完成Bean的裝載,則必須要使用@注解及@Bean注解。@Enable注解本質上就是對這兩種注解的封裝,在@Enable注解中,一般都會帶有一個@Import注解,比如@EnableScheduling注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import({SchedulingConfiguration.class})
@Documented
public @interface EnableScheduling {
}
所以,使用@Enable注解后,Spring會解析到@Import導入的配置類,并且根據這個配置類中的描述來實現Bean的裝配。
EnableAutoConfiguration注解
當我們查看@EnableAutoConfiguration這個注解的時候,可以看到除了@Import注解之外,還有一個@AutoConfigurationPackage注解,而且@Import注解導入的并不是一個Configuration的配置類,而是AutoConfigurationImportSelector類,那AutoConfigurationImportSelector里面包含什么東西呢?
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
}
AutoConfigurationImportSelector
AutoConfigurationImportSelector這個類實現了ImportSelector,它只有一個selectImports抽象方法,并且返回一個String數組,這個數組中的值就是需要裝配到IoC容器中的類,當在@Import中導入一個ImportSelector的實現類后,會把該實現類中返回的class名稱都裝載到IoC容器中。
public interface ImportSelector {
String[] selectImports(AnnotationMetadata var1);
}
和@Confifguration不同的是,ImportSelector可以實現批量裝配,而且可以通過邏輯處理來實現Bean的選擇性裝配,也就是可以根據上下文來決定哪些類可以被裝配到IoC容器中,下面通過一個例子介紹下ImportSelector的使用:
- 首先創建兩個類,我們要把這兩個類裝配到IoC容器中
public class FirstClass {
...
}
public class SecondClass {
...
}
- 創建ImportSelector的實現類,在實現類中把上面定義的兩個類加入到數組中
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[] {FirstClass.class.getName(), SecondClass.class.getName()}
}
}
- 創建一個類似EnableAutoConfiguration的注解,通過@Import導入MyImportSelector
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({MyImportSelector.class})
public @interface MyAutoImport {
}
- 創建啟動類,在啟動類上使用MyAutoImport注解,然后通過ConfigurableApplicationContext獲取FirstClass
@SpringBootApplication
@MyAutoImport
public class ImportSelectorDemo {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringBootApplication.run(ImportSelectorDemo.class);
FirstClass fc = context.getBean(FirstClass.class);
}
}
這種方式相比于@Import(*Configuration.class)的好處在于裝配的靈活性,也可以實現批量裝配。在MyImportSelector的String數組中可以定義多個Configuration類,一個配置類代表的就是某一個技術組件中批量的Bean的聲明,所以在自動裝配這個過程中只需要掃描到指定路徑下對應的配置類即可。
自動裝配原理
自動裝配的核心是掃描約定目錄下的文件進行解析,解析完成之后把得到的Configuration配置類通過ImportSelector進行導入,進而完成Bean的自動裝配過程。
我們看一下AutoConfigurationImportSelector中的selectImports方法,它是ImportSelector接口的實現,該方法中主要做了兩件事:
- AutoConfigurationMetadataLoader.loadMetadata方法從META-INF/spring-autoconfigure-metadata.properties文件中加載自動裝配的條件元數據,也就是只有滿足條件的Bean才會被裝配
- autoConfigurationEntry.getConfigurations()方法收集所有符合條件的配置類,進行自動裝配
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());
}
}
接下來我們了解下getAutoConfigurationEntry這個方法,這個方法會掃描指定路徑下的文件進行解析,從而得到所需要裝配的配置類,它主要做了下面幾件事:
- getAttributes方法獲得@EnableAutoConfiguration注解中的屬性exclude、excludeName等。
- getCandidateConfiguration方法獲得所有自動裝配的配置類。
- removeDuplicates方法去掉重復的配置項。
- getExclusions方法根據@EnableAutoConfiguration注解中配置的exclude等屬性,把不需要自動裝配的配置類移除。
- fireAutoConfigurationImportEvents廣播事件。
- 最后返回經過多層判斷和過濾之后的配置類集合
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);
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);
}
}
總的來說,它先獲得所有的配置類,通過去重、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;
}
這個方法中用到了SpringFactoriesLoader,它是Spring內部提供的一種約定俗成的加載方式,和Java的SPI類似。它會掃描classpath下的META-INF/spring.factories文件,spring.factories文件中的數據以key=value的形式存儲,SpringFactoriesLoader.loadFactoryNames()會根據key的到對應的value值,因此,在自動裝配這個場景中,key對應為EnableAutoConfiguration,value是多個配置類,也就是getCandidateConfigurations方法的返回值。
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
......
如果我們打開RabbitAutoConfiguration,可以看到它就是一個基于JavaConfig形式的配置類:
@Configuration
@ConditionalOnClass({RabbitTemplate.class, Channel.class})
@EnableConfigurationProperties({RabbitProperties.class})
@Import({RabbitAnnotationDrivenConfiguration.class})
public class RabbitAutoConfiguration {
public RabbitAutoConfiguration() {
}
@Configuration
@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);
}
......
}
除了@Configuration注解,還有一個@ConditionalOnClass注解,這個條件控制機制在這里的用途是判斷classpath下是否存在RabbitTemplate和Channel這兩個類,如果有,則把當前配置類注冊到IoC容器中。@EnableConfigurationProperties是屬性配置,我們可以按照約定在application.properties中配置RabbitMQ的參數,這些配置會加載到RabbitProperties中。
到此處,自動裝配的工作流程就結束了,其實主要的核心過程是如下幾點:
- 通過@Import(AutoConfigurationImportSelector)實現配置類的導入
- AutoConfigurationImportSelector類實現了ImportSelector接口,重寫了方法selectImports,用于實現批量配置類的裝載。
- 通過spring提供的SpringFactoriesLoader機制掃描classpath下META-INF/spring.factories文件,讀取需要實現自動裝配的配置類。
- 通過條件篩選,把不符合條件的配置類移除,最終完成自動裝配。