目錄
3、getAutoConfigurationEntry()方法
3.2、getCandidateConfigurations()方法
3.4、返回值AutoConfigurationEntry對象
SpringBoot版本:2.1.1
複習一下,在之前關於Spring Boot的第三篇博文中,使用EnvironmentPostProcessor接口可以加載外部配置文件,要在META-INF/spring.factories文件中註冊,key是EnvironmentPostProcessor接口全類名,value是實現類的全類名。
@EnableAutoConfiguration也可以加載第三方配置,同樣要在META-INF/spring.factories文件中註冊,key是@EnableAutoConfiguration的全路徑,value是需要加載的類或配置類的全路徑,可以是普通類或者配置類,如果是配置類,裏面的bean會被納入spring容器,多個用逗號隔開。
Demo
先來看一個@EnableAutoConfiguration導入第三方配置的例子,隨便建一個項目SpringBoot_OutsideConfig,然後建一個bean還有一個配置類。項目結構如下:
pom.xml如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.eastcom</groupId>
<artifactId>SpringBoot_OutsideConfig</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.3.RELEASE</version>
</dependency>
</dependencies>
</project>
OutSideBean:
public class OutSideBean {
public OutSideBean() {
System.out.println("===============>>>OutSideBean被創建");
}
}
OutsideConfiguration:
public class OutsideConfiguration {
@Bean
public Runnable outsideRunnable() {
return ()->{};
}
}
建好以後在項目上右鍵-->Run As-->Maven Install。這個項目就先放一邊。然後在前面的父工程下新建子項目SpringBoot_EnableAutoConfiguration。
pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.eastcom</groupId>
<artifactId>SpringBoot_Demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>SpringBoot_EnableAutoConfiguration</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- 導入剛剛創建的項目 -->
<dependency>
<groupId>com.eastcom</groupId>
<artifactId>SpringBoot_OutsideConfig</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
然後在resources下新建META-INF/spring.factories文件,文件中內容如下,key是@EnableAutoConfiguration註解的全路徑,value是你要加載的類的全路徑,多個用逗號分隔。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.eastcom.outside.bean.OutSideBean,com.eastcom.outside.config.OutsideConfiguration
然後新建主類。
@ComponentScan
@EnableAutoConfiguration
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
System.out.println(context.getBean(OutSideBean.class));
System.out.println(context.getBean(OutsideConfiguration.class));
System.out.println(context.getBeansOfType(Runnable.class));
}
}
運行。
可以看到OutSideBean和配置類裏面的runnable以及配置類本身都已經加載進來了。
源碼解析
這裏如果把整個類的源碼放進來肯定是不怎麼方便看的,我個人是不喜歡這樣把所有源碼一股腦全粘貼進來,所以我把單個方法抽出來具體講。這樣也方便瀏覽。
從Application類點到@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 specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
Class<?>[] exclude() default {};
//根據類全路徑排除
/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
String[] excludeName() default {};
}
看到一個@Import註解,參數是一個類,再點AutoConfigurationImportSelector進去看源碼。
@Import註解用來導入一個或多個類(表示被spring容器託管),如果是一個配置類(配置類裏的bean會被spring容器託管)
點進去看DeferredImportSelector。
點進去看ImportSelector。
ImportSelector接口,只有一個方法,返回值是一個String數組,方法上面的介紹是選擇並返回應導入的類的名稱,基於註解的原數據導入配置類。所以方法返回的是一個Class全路徑的String數組,返回的Class會被Spring容器管理。
現在回去看AutoConfigurationImportSelector的selectImports方法。裏面的兩個主要方法我用框出來了。
1、isEnabled()方法:
返回的是一個boolean值。其中EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY在@EnableAutoConfiguration註解中定義,String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";,在博客開頭也講了,獲取這個配置項的值,默認是true。也就是說默認是開啓自動配置。
2、loadMetadata()方法:
可以看到最後是將META-INF/spring-autoconfigure-metadata.properties文件的元數據放到了PropertiesAutoConfigurationMetadata對象的properties屬性,該類實現了AutoConfigurationMetadata接口。
spring-autoconfigure-metadata.properties文件在spring-boot-autoconfigure-2.1.1.RELEASE.jar/META-INF下。
3、getAutoConfigurationEntry()方法
同樣我把主要要看的幾個地方框出來了。
3.1、getAttributes()方法
返回AnnotationAttributes實例,該包含了@EnableAutoConfiguration註解的屬性值。
protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
//得到@EnableAutoConfiguration註解的類型,也就是org.springframework.boot.autoconfigure.EnableAutoConfiguration
String name = getAnnotationClass().getName();
//getAnnotationAttributes(String annotationName,Boolean classValuesAsString):檢索給定類型的註釋的屬性。
//annotationName:要查找的註釋類型的完全限定類名
//classValuesAsString:是否將類引用轉換爲String類名,以便在返回的Map中將值作爲值轉換,而不是可能必須首先加載的類引用。
//fromMap(Map<String,Object> map):根據給定的Map返回AnnotationAttributes實例。
AnnotationAttributes attributes = AnnotationAttributes
.fromMap(metadata.getAnnotationAttributes(name, true));
Assert.notNull(attributes,
() -> "No auto-configuration attributes found. Is "
+ metadata.getClassName() + " annotated with "
+ ClassUtils.getShortName(name) + "?");
return attributes;
}
/**
* 返回 @EnableAutoConfiguration註解的類型
* @return the annotation class
*/
protected Class<?> getAnnotationClass() {
return EnableAutoConfiguration.class;
}
3.2、getCandidateConfigurations()方法
在講這個方法前說一下SpringFactoriesLoader.loadFactoryNames()這個方法
該方法的作用是使用給定的類加載器從“META-INF / spring.factories”加載給定類型的工廠實現的完全限定類名。也就是前面講的在spring.factories文件中註冊的,使用@EnableAutoConfiguration的全路徑做key,需要加載的類或配置做value。返回一個List<String>。
現在再來看這個方法你就一下能看懂了吧。
3.3、getExclusions()方法
/*
* 獲取註解屬性
*/
protected Set<String> getExclusions(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
Set<String> excluded = new LinkedHashSet<>();
excluded.addAll(asList(attributes, "exclude"));
excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName")));
excluded.addAll(getExcludeAutoConfigurationsProperty());
return excluded;
}
private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";
// 獲取配置項spring.autoconfigure.exclude的值
private List<String> getExcludeAutoConfigurationsProperty() {
if (getEnvironment() instanceof ConfigurableEnvironment) {
Binder binder = Binder.get(getEnvironment());
return binder.bind(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class)
.map(Arrays::asList).orElse(Collections.emptyList());
}
String[] excludes = getEnvironment()
.getProperty(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class);
return (excludes != null) ? Arrays.asList(excludes) : Collections.emptyList();
}
// attributes.getStringArray():獲取存儲在指定attributeName的字符串數組下的值
protected final List<String> asList(AnnotationAttributes attributes, String name) {
String[] value = attributes.getStringArray(name);
return Arrays.asList((value != null) ? value : new String[0]);
}
3.4、返回值AutoConfigurationEntry對象
最後是將需要裝配和排除的類封裝成了AutoConfigurationEntry對象返回。
流程總結
手滑截圖文字還沒寫完不知道點哪了,就完成了,就這樣吧,我在下面接着寫。(無奈╮(╯--╰)╭)
6、把需要排除的類移除
7、進行過濾,AutoConfigurationImportFilter的實現類包括OnBeanCondition、OnClassCondition,前面有講Condition,這裏主要就是過濾一些條件不滿足的類。
8、就是廣播事件,得到AutoConfigurationImportListener所有實現類,然後生成事件進行廣播。
9、最後把需要裝配和排除的類完全限定名封裝成了AutoConfigurationEntry對象返回
項目源碼:https://github.com/AiHePing/SpringBoot_Demo