- 本文參考的博客(基本是摘自博客中的內(nèi)容):
http://www.reibang.com/p/83693d3d0a65
http://www.reibang.com/p/10da93ef57d1 - springboot一大優(yōu)勢就是省去了很多的配置瑞你,也就是說當(dāng)springboot啟動的時(shí)候继找,springboot在內(nèi)部就已經(jīng)幫忙封裝好了齿穗,者其實(shí)就是springboot的自動裝配操作耀怜。
1告嘲、從@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 {
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
//根據(jù)包路徑掃描
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
//直接根據(jù)class類掃描
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
}
初看@SpringBootApplication有很多的注解組成撕彤,其實(shí)歸納就是一個(gè)"三體"結(jié)構(gòu)配椭,重要的只有三個(gè)Annotation:
@Configuration(@SpringBootConfiguration實(shí)質(zhì)就是一個(gè)@Configuration)
@EnableAutoConfiguration
@ComponentScan
- 也就是說我們在開發(fā)的時(shí)候匿乃,加上上面的上個(gè)注解會等同于加上@SpringBootApplication注解
(1)@Configuration注解
- 這個(gè)注解實(shí)際上就是代表了一個(gè)配置類,相當(dāng)于一個(gè)beans.xml文件初橘,可以看我的另一篇文章:
http://www.reibang.com/p/81880251a700
(2)@ComponentScan
- @ComponentScan的功能其實(shí)就是自動掃描并加載符合條件的組件或bean定義验游,最終將這些bean定義加載到容器中,也可以看我的另一篇文章:
http://www.reibang.com/p/a00d2b43e160
(3)@EnableAutoConfiguration
- 在spring中有關(guān)于@Enablexxx的注解是開啟某一項(xiàng)功能的注解保檐,比如@EnableScheduling表示開啟spring的定時(shí)任務(wù)耕蝉。其原理是借助@Import的幫助,將所有符合自動配置條件的bean定義加載到Ioc容器夜只。其有關(guān)@Import注解的原理可有看:http://www.reibang.com/p/a00d2b43e160
- EnableAutoConfiguration代表開啟springboot的自動裝配
@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 {};
}
從源碼中可以知道垒在,最關(guān)鍵的要屬@Import(EnableAutoConfigurationImportSelector.class),借助EnableAutoConfigurationImportSelector扔亥,@EnableAutoConfiguration可以幫助SpringBoot應(yīng)用將所有符合條件的@Configuration配置都加載到當(dāng)前SpringBoot創(chuàng)建并使用的IoC容器场躯。同時(shí)借助于Spring框架原有的一個(gè)工具類:SpringFactoriesLoader,@EnableAutoConfiguration就可以實(shí)現(xiàn)智能的自動配置旅挤。
//從這里可以看出該類實(shí)現(xiàn)很多的xxxAware和DeferredImportSelector踢关,所有的aware都優(yōu)先于selectImports
//方法執(zhí)行,也就是說selectImports方法最后執(zhí)行粘茄,那么在它執(zhí)行的時(shí)候所有需要的資源都已經(jīng)獲取到了
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
...
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
//1加載META-INF/spring-autoconfigure-metadata.properties文件
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
//2獲取注解的屬性及其值(PS:注解指的是@EnableAutoConfiguration注解)
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
//3.在classpath下所有的META-INF/spring.factories文件中查找org.springframework.boot.autoconfigure.EnableAutoConfiguration的值耘成,并將其封裝到一個(gè)List中返回
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
//4.對上一步返回的List中的元素去重、排序
configurations = this.removeDuplicates(configurations);
//5.依據(jù)第2步中獲取的屬性值排除一些特定的類
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
//6對上一步中所得到的List進(jìn)行過濾,過濾的依據(jù)是條件匹配瘪菌。這里用到的過濾器是
//org.springframework.boot.autoconfigure.condition.OnClassCondition最終返回的是一個(gè)ConditionOutcome[]
//數(shù)組撒会。(PS:很多類都是依賴于其它的類的,當(dāng)有某個(gè)類時(shí)才會裝配师妙,所以這次過濾的就是根據(jù)是否有某個(gè)
//class進(jìn)而決定是否裝配的诵肛。這些類所依賴的類都寫在META-INF/spring-autoconfigure-metadata.properties文件里)
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.filter(configurations, autoConfigurationMetadata);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return StringUtils.toStringArray(configurations);
}
}
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中加載配置,SpringFactoriesLoader屬于Spring框架私有的一種擴(kuò)展方案,其主要功能就是從指定的配置文件META-INF/spring.factories加載配置,即根據(jù)@EnableAutoConfiguration的完整類名org.springframework.boot.autoconfigure.EnableAutoConfiguration作為查找的Key,獲取對應(yīng)的一組@Configuration類
public abstract class SpringFactoriesLoader {
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null)
return result;
try {
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()) {
List<String> factoryClassNames = Arrays.asList(
StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
result.addAll((String) entry.getKey(), factoryClassNames);
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
...
總結(jié):@EnableAutoConfiguration作用就是從classpath中搜尋所有的META-INF/spring.factories配置文件默穴,并將其中org.springframework.boot.autoconfigure.EnableutoConfiguration對應(yīng)的配置項(xiàng)通過反射(Java Refletion)實(shí)例化為對應(yīng)的標(biāo)注了@Configuration的JavaConfig形式的IoC容器配置類怔檩,然后匯總為一個(gè)并加載到IoC容器。這些功能配置類要生效的話蓄诽,會去classpath中找是否有該類的依賴類(也就是pom.xml必須有對應(yīng)功能的jar包才行)并且配置類里面注入了默認(rèn)屬性值類薛训,功能類可以引用并賦默認(rèn)值。生成功能類的原則是自定義優(yōu)先仑氛,沒有自定義時(shí)才會使用自動裝配類乙埃。
- 所以功能類能生效需要的條件:(1)spring.factories里面有這個(gè)類的配置類(一個(gè)配置類可以創(chuàng)建多個(gè)圍繞該功能的依賴類)(2)pom.xml里面需要有對應(yīng)的jar包
二、自動裝配案例說明以Redis為例
1锯岖、從spring-boot-autoconfigure.jar/META-INF/spring.factories中獲取redis的相關(guān)配置類全限定名(有120多個(gè)的配置類)RedisAutoConfiguration介袜,一般一個(gè)功能配置類圍繞該功能,負(fù)責(zé)管理創(chuàng)建多個(gè)相關(guān)的功能類出吹,比如RedisAutoConfiguration負(fù)責(zé):JedisConnectionFactory遇伞、RedisTemplate、StringRedisTemplate這3個(gè)功能類的創(chuàng)建
2捶牢、RedisAutoConfiguration配置類生效的一個(gè)條件是在classpath路徑下有RedisOperations類存在鸠珠,因此springboot的自動裝配機(jī)制會會去classpath下去查找對應(yīng)的class文件。
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
...}
3.如果pom.xml有對應(yīng)的jar包,就能匹配到對應(yīng)依賴class秋麸,
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
4跳芳、匹配成功,這個(gè)功能配置類才會生效竹勉,同時(shí)會注入默認(rèn)的屬性配置類@EnableConfigurationProperties(RedisProperties.class)
@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {
private int database = 0;
private String url;
private String host = "localhost";
private String password;
private int port = 6379;
...
5.Redis功能配置里面會根據(jù)條件生成最終的JedisConnectionFactory、RedisTemplate,并提供了默認(rèn)的配置形式@ConditionalOnMissingBean(name = "redisTemplate")
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
//用戶沒定義就使用默認(rèn)的
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(
RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean(StringRedisTemplate.class)
public StringRedisTemplate stringRedisTemplate(
RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
6.最終創(chuàng)建好的默認(rèn)裝配類娄琉,會通過功能配置類里面的 @Bean注解次乓,注入到IOC當(dāng)中
7.用戶使用,當(dāng)用戶在配置文件中自定義時(shí)候就會覆蓋默認(rèn)的配置@ConditionalOnMissingBean(name = "redisTemplate")
三孽水、自動依賴過程總結(jié)
1.通過各種注解實(shí)現(xiàn)了類與類之間的依賴關(guān)系票腰,容器在啟動的時(shí)候Application.run,會調(diào)用EnableAutoConfigurationImportSelector.class的selectImports方法(其實(shí)是其父類的方法)--這里需要注意女气,調(diào)用這個(gè)方法之前發(fā)生了什么和是在哪里調(diào)用這個(gè)方法需要進(jìn)一步的探討
2.selectImports方法最終會調(diào)用SpringFactoriesLoader.loadFactoryNames方法來獲取一個(gè)全面的常用BeanConfiguration列表
3.loadFactoryNames方法會讀取FACTORIES_RESOURCE_LOCATION(也就是spring-boot-autoconfigure.jar 下面的spring.factories)杏慰,獲取到所有的Spring相關(guān)的Bean的全限定名ClassName,大概120多個(gè)
4.selectImports方法繼續(xù)調(diào)用filter(configurations, autoConfigurationMetadata);這個(gè)時(shí)候會根據(jù)這些BeanConfiguration里面的條件,來一一篩選缘滥,最關(guān)鍵的是
@ConditionalOnClass轰胁,這個(gè)條件注解會去classpath下查找,jar包里面是否有這個(gè)條件依賴類朝扼,所以必須有了相應(yīng)的jar包赃阀,才有這些依賴類,才會生成IOC環(huán)境需要的一些默認(rèn)配置Bean
5.最后把符合條件的BeanConfiguration注入默認(rèn)的EnableConfigurationPropertie類里面的屬性值擎颖,并且注入到IOC環(huán)境當(dāng)中