首頁 > 軟體

SpringBoot詳細分析自動裝配原理並實現starter

2022-07-12 14:01:53

約定優於設定

SpringBoot的預定優於設定主要體現在以下幾個方面:

maven的目錄結構:

  • 組態檔預設存放在resources目錄下
  • 專案編譯後的檔案存放在target目錄下
  • 專案預設打包成jar格式

組態檔預設為application.ymlapplication.yamlapplication.properties

預設通過 spring.profiles.active 屬性來決定執行環境時的組態檔。

自動裝配

相對於傳統的Spring專案的繁瑣設定,SpringBoot專案只需要使用一個@SpringBootApplication註解就可以成功執行,哪有什麼歲月靜好,只不過是有人在替我們負重前行。讓我們來看一看在@SpringBootApplication註解的背後SpringBoot為我們做了哪些事情。

可以看到@SpringBootApplication是一個組合註解,上面四個不用看,因為是定義一個註解必須的,關鍵在於下面的@SpringBootConfiguration``@EnableAutoConfiguration``@ComponentScan三個註解。也就是說我們不用@SpringBootApplication,使用下這三個註解也可以成功執行一個SpringBoot應用。

@SpringBootConfiguration註解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}

點進原始碼可以發現,它實際上就是一個@Configuration註解,@Configuration大家應該都很熟悉了,加上這個註解後當前類就會被Spring所管理 。

@ComponentScan註解

這個註解用於定義Spring的掃描路徑,等價於<context:component-scan>,如果沒有設定掃描路徑,那麼SpringBoot會預設掃描當前類的包及其子包中所有標註了需要被管理的類。

@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 {};
}

@AutoConfigurationPackage其實也是一個@Import註解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}

@Import註解

@Configuration標註的Class上可以使用@Import引入其它的設定類,其實它還可以引入org.springframework.context.annotation.ImportSelector實現類。ImportSelector介面只定義了一個selectImports(),用於指定需要註冊為bean的Class名稱。當在@Configuration標註的Class上使用@Import引入了一個ImportSelector實現類後,會把實現類中返回的Class名稱都定義為bean。

@EnableAutoConfiguration@Import主要就是為了匯入一個AutoConfigurationImportSelector,下面我們分析一下這個類:

AutoConfigurationImportSelector類

AutoConfigurationImportSelect類實現了ImportSelector介面,所以我們清楚只需關注selectImports()方法的返回結果即可:

	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
				.loadMetadata(this.beanClassLoader);
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
				annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}

該方法返回的是要註冊到IOC容器中的物件的型別的全路徑名稱的字串陣列,所以我們要分析一下這個陣列是從哪裡來的?

進入到getAutoConfigurationEntry方法中:

	protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
			AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
        // 獲取註解的屬性資訊
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
        // 獲取候選設定
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
		configurations = removeDuplicates(configurations);
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
		configurations = filter(configurations, autoConfigurationMetadata);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

通過DEBUG可以看到,在getCandidateConfigurations方法中獲取到了很多java類全路徑

進入getCandidateConfigurations方法,可以看到有一個斷言:如果configurations為空的話,會提示No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct(在META-INF/spring.factories中找不到自動設定類。如果您使用的是自定義打包,請確保該檔案正確無誤)

由此我們可以推測:自動設定的類名陣列在META-INF/spring.factories檔案中,並且我們可以通過在正確的路徑下面新增這個檔案來自定義包來適配SpringBoot

	protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
				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;
	}

進入這個檔案可以看到,這裡設定了大量的需要自動裝配的類,當我們啟動Springboot專案的時候,SpringBoot會掃描所有jar包下面的META-INF/spring.factories檔案,並根據key進行讀取,在經過一系列的操作來完成自動裝配。

需要注意的是:上圖中的 spring.factories 檔案是在 spring-boot-autoconfigure 包下面,這個包記錄了官方提供的 stater 中幾乎所有需要的自動裝配類,所以並不是每一個官方的 starter 下都會有 spring.factories 檔案。

@AutoConfigurationPackage註解

@AutoConfigurationPackage註解的主要作用就是將主程式類所在包及所有子包下的元件到掃描到spring容器中。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}

這個註解實際上是匯入了AutoConfigurationPackages的一個內部類Registrar,這個類的作用就是讀取到最外層@SpringBootApplication註解中設定的掃描路徑(沒有設定預設當前所在包),將該路徑下所有檔案掃描,並分析註冊bean。

	static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
		@Override
		public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
			register(registry, new PackageImport(metadata).getPackageName());
		}
		@Override
		public Set<Object> determineImports(AnnotationMetadata metadata) {
			return Collections.singleton(new PackageImport(metadata));
		}
	}

手寫一個starter元件

上面提到在自己的包中新增META-INF/spring.factories檔案就可以適配SpringBoot實現自動設定,這其實是一種SPI的思想。

SPI,Service Provider Interface。即:介面服務的提供者。就是說我們應該面向介面(抽象)程式設計,而不是面向具體的實現來程式設計,這樣一旦我們需要切換到當前介面的其他實現就無需修改程式碼。

starter的命名規範:

官方的starter命名格式為spring-boot-starter-{xxx},例如:spring-boot-starter-web

自定義starter命名格式一般為{xxx}-spring-boot-starter,例如mybatis的mybatis-spring-boot-starter

1) 新建一個springboot專案myself-spring-boot-starter

2) 新建自己的業務類

public class MyselfService {
    private String myself;
    // ……省略 getter setter
    public String doBusiness(Object obj){
        return myself+obj.toString();
    }
}

3)業務需要的一些屬性值

@ConfigurationProperties("myself")
public class MysefProperties {
    private String myself;
    // ……省略 getter setter
}

4)新建自動裝配類,把自己的業務交給Spring管理

@Configuration
@EnableConfigurationProperties(MysefProperties.class)
public class MyselfAutoConfiguration {
    @Autowired
    MysefProperties mysefProperties;
    @Bean
    @ConditionalOnMissingBean(MyselfService.class)
    public MyselfService myselfService(){
        MyselfService myselfService = new MyselfService();
        myselfService.setMyself(mysefProperties.getMyself());
        return myselfService;
    }
}

5)在resources/META-INF下新建spring.factories,設定starter中設定類的位置

org.springframework.boot.autoconfigure.EnableAutoConfiguration=
com.yy.autoconfigure.MyselfAutoConfiguration

6)mvn install將自己的包打到自己倉庫中,在另外的專案直接參照即可

7)測試

mybatis-plus-boot-starter

我們可以學習一下其他第三方的成熟的starter,會發現其實套路是很相似的

到此這篇關於SpringBoot詳細分析自動裝配原理並實現starter的文章就介紹到這了,更多相關SpringBoot自動裝配原理內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com