SpringBoot(五)@EnableAutoConfiguration註解的工作原理

目錄

Demo

源碼解析

1、isEnabled()方法:

2、loadMetadata()方法:

3、getAutoConfigurationEntry()方法

3.1、getAttributes()方法

3.2、getCandidateConfigurations()方法

3.3、getExclusions()方法

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

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章