Spring Boot不得不說的一個特點就是自動裝配迅诬,它是starter的基礎(chǔ)董饰,也是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進(jìn)行操作
@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容器中已經(jīng)存在RedisTemplate實例了透硝,這就是Spring Boot自動裝配機(jī)制。
自動裝配的實現(xiàn)
自動裝配在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版本就已經(jīng)開始支持@Enable注解了幔欧,它的主要作用是把相關(guān)組件的Bean裝配到IoC容器中罪治。@Enable注解對JavaConfig的進(jìn)一步完善,使開發(fā)者減少了配置代碼量礁蔗,降低了使用難度觉义,比較常見的Enable注解有@EnableWebMvc,@EnableScheduling等浴井。
如果我們要基于JavaConfig的形式來完成Bean的裝載晒骇,則必須要使用@注解及@Bean注解。@Enable注解本質(zhì)上就是對這兩種注解的封裝,在@Enable注解中洪囤,一般都會帶有一個@Import注解徒坡,比如@EnableScheduling注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import({SchedulingConfiguration.class})
@Documented
public @interface EnableScheduling {
}
所以,使用@Enable注解后瘤缩,Spring會解析到@Import導(dǎo)入的配置類喇完,并且根據(jù)這個配置類中的描述來實現(xiàn)Bean的裝配。
EnableAutoConfiguration注解
當(dāng)我們查看@EnableAutoConfiguration這個注解的時候剥啤,可以看到除了@Import注解之外锦溪,還有一個@AutoConfigurationPackage注解,而且@Import注解導(dǎo)入的并不是一個Configuration的配置類铐殃,而是AutoConfigurationImportSelector類海洼,那AutoConfigurationImportSelector里面包含什么東西呢?
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
}
AutoConfigurationImportSelector
AutoConfigurationImportSelector這個類實現(xiàn)了ImportSelector富腊,它只有一個selectImports抽象方法坏逢,并且返回一個String數(shù)組,這個數(shù)組中的值就是需要裝配到IoC容器中的類赘被,當(dāng)在@Import中導(dǎo)入一個ImportSelector的實現(xiàn)類后是整,會把該實現(xiàn)類中返回的class名稱都裝載到IoC容器中。
public interface ImportSelector {
String[] selectImports(AnnotationMetadata var1);
}
和@Confifguration不同的是民假,ImportSelector可以實現(xiàn)批量裝配浮入,而且可以通過邏輯處理來實現(xiàn)Bean的選擇性裝配,也就是可以根據(jù)上下文來決定哪些類可以被裝配到IoC容器中羊异,下面通過一個例子介紹下ImportSelector的使用:
- 首先創(chuàng)建兩個類事秀,我們要把這兩個類裝配到IoC容器中
public class FirstClass {
...
}
public class SecondClass {
...
}
- 創(chuàng)建ImportSelector的實現(xiàn)類,在實現(xiàn)類中把上面定義的兩個類加入到數(shù)組中
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[] {FirstClass.class.getName(), SecondClass.class.getName()}
}
}
- 創(chuàng)建一個類似EnableAutoConfiguration的注解野舶,通過@Import導(dǎo)入MyImportSelector
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({MyImportSelector.class})
public @interface MyAutoImport {
}
- 創(chuàng)建啟動類易迹,在啟動類上使用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)的好處在于裝配的靈活性平道,也可以實現(xiàn)批量裝配睹欲。在MyImportSelector的String數(shù)組中可以定義多個Configuration類,一個配置類代表的就是某一個技術(shù)組件中批量的Bean的聲明一屋,所以在自動裝配這個過程中只需要掃描到指定路徑下對應(yīng)的配置類即可窘疮。
自動裝配原理
自動裝配的核心是掃描約定目錄下的文件進(jìn)行解析,解析完成之后把得到的Configuration配置類通過ImportSelector進(jìn)行導(dǎo)入冀墨,進(jìn)而完成Bean的自動裝配過程闸衫。
我們看一下AutoConfigurationImportSelector中的selectImports方法,它是ImportSelector接口的實現(xiàn)诽嘉,該方法中主要做了兩件事:
- AutoConfigurationMetadataLoader.loadMetadata方法從META-INF/spring-autoconfigure-metadata.properties文件中加載自動裝配的條件元數(shù)據(jù)楚堤,也就是只有滿足條件的Bean才會被裝配
- autoConfigurationEntry.getConfigurations()方法收集所有符合條件的配置類疫蔓,進(jìn)行自動裝配
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這個方法含懊,這個方法會掃描指定路徑下的文件進(jìn)行解析身冬,從而得到所需要裝配的配置類,它主要做了下面幾件事:
- getAttributes方法獲得@EnableAutoConfiguration注解中的屬性exclude岔乔、excludeName等酥筝。
- getCandidateConfiguration方法獲得所有自動裝配的配置類。
- removeDuplicates方法去掉重復(fù)的配置項雏门。
- getExclusions方法根據(jù)@EnableAutoConfiguration注解中配置的exclude等屬性嘿歌,把不需要自動裝配的配置類移除。
- fireAutoConfigurationImportEvents廣播事件茁影。
- 最后返回經(jīng)過多層判斷和過濾之后的配置類集合
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排除等操作步脓,得到最終需要實現(xiàn)自動裝配的配置類。其中g(shù)etCandidateConfigurations方法是獲得配置類最核心的方法浩螺。
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內(nèi)部提供的一種約定俗成的加載方式,和Java的SPI類似要出。它會掃描classpath下的META-INF/spring.factories文件鸳君,spring.factories文件中的數(shù)據(jù)以key=value的形式存儲,SpringFactoriesLoader.loadFactoryNames()會根據(jù)key的到對應(yīng)的value值患蹂,因此或颊,在自動裝配這個場景中,key對應(yīng)為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注解,這個條件控制機(jī)制在這里的用途是判斷classpath下是否存在RabbitTemplate和Channel這兩個類盛末,如果有弹惦,則把當(dāng)前配置類注冊到IoC容器中。@EnableConfigurationProperties是屬性配置悄但,我們可以按照約定在application.properties中配置RabbitMQ的參數(shù)棠隐,這些配置會加載到RabbitProperties中。
到此處檐嚣,自動裝配的工作流程就結(jié)束了助泽,其實主要的核心過程是如下幾點:
- 通過@Import(AutoConfigurationImportSelector)實現(xiàn)配置類的導(dǎo)入
- AutoConfigurationImportSelector類實現(xiàn)了ImportSelector接口啰扛,重寫了方法selectImports,用于實現(xiàn)批量配置類的裝載嗡贺。
- 通過spring提供的SpringFactoriesLoader機(jī)制掃描classpath下META-INF/spring.factories文件隐解,讀取需要實現(xiàn)自動裝配的配置類。
- 通過條件篩選诫睬,把不符合條件的配置類移除煞茫,最終完成自動裝配。