SpringBoot Starter的用法以及原理小結(jié)
為了理解SpringBoot Starter的寫法,以hello-spring-boot-starter作為示例來(lái)講解
創(chuàng)建父模塊hello-spring-boot-starter-project
hello-spring-boot-starter-project將作為整個(gè)項(xiàng)目的父模塊,其pom.xml文件如下:
<!--添加子模塊-->
<modules>
<module>hello-spring-boot-starter</module>
<module>hello-spring-boot-starter-autoconfigure</module>
</modules>
<!--定義的參數(shù)-->
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-boot.version>3.0.7</spring-boot.version>
</properties>
<!--依賴的配置約定-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>3.5.11</version>
</dependency>
</dependencies>
</dependencyManagement>
創(chuàng)建子模塊hello-spring-boot-starter-autoconfigure
hello-spring-boot-starter-autoconfigure作為父模塊hello-spring-boot-starter-project的一個(gè)子模塊,其pom.xml關(guān)鍵配置如下:
<!--設(shè)置父模塊--> <parent> <groupId>edu.whut</groupId> <artifactId>hello-spring-boot-starter-project</artifactId> <version>1.0-SNAPSHOT</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> </dependency> </dependencies>
在其模塊內(nèi)部創(chuàng)建三個(gè)文件HelloService.java:
public class HelloService {
private final HelloProperties properties;
public HelloService(HelloProperties properties) {
this.properties = properties;
}
public void sayHello() {
System.out.println("Hello,"+properties.getObject());
}
}
HelloProperties.java:
@ConfigurationProperties(prefix = "hello")
public class HelloProperties {
private String object;
public String getObject() {
return object;
}
public void setObject(String object) {
this.object = object;
}
}
HelloAutoConfiguration.java:
//標(biāo)識(shí)自動(dòng)配置類
@AutoConfiguration
@EnableConfigurationProperties(HelloProperties.class)
public class HelloAutoConfiguration {
@Bean
//在application文件中配置了hello.object配置才會(huì)構(gòu)造這個(gè)bean
@ConditionalOnProperty(prefix = "hello", name = "object")
public HelloService helloService(HelloProperties properties) {
return new HelloService(properties);
}
}
在hello-spring-boot-starter-autoconfigure模塊中的resources下的META-INF下的spring目錄下創(chuàng)建一個(gè)名為org.springframework.boot.autoconfigure.AutoConfiguration.imports的文件,文件中寫入:
edu.whut.HelloAutoConfiguration
創(chuàng)建hello-spring-boot-starter子模塊
hello-spring-boot-starter作為父模塊hello-spring-boot-starter-project的一個(gè)子模塊,其pom.xml關(guān)鍵配置如下:
<!--指定父模塊--> <parent> <groupId>edu.whut</groupId> <artifactId>hello-spring-boot-starter-project</artifactId> <version>1.0-SNAPSHOT</version> </parent> <dependencies> <dependency> <groupId>edu.whut</groupId> <artifactId>hello-spring-boot-starter-autoconfigure</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies>
這樣這個(gè)模塊就完成了
使用hello-spring-boot-starter
隨意創(chuàng)建一個(gè)springboot項(xiàng)目,在main方法中查找對(duì)應(yīng)的名為helloService的bean
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(DemoApplication.class, args);
Object bean = applicationContext.getBean("helloService");
System.out.println(bean);
}
結(jié)果為:
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'helloService' available
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:978)
at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1381)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1296)
at com.example.demo.DemoApplication.main(DemoApplication.java:12)
這是正常的,因?yàn)楝F(xiàn)在還未設(shè)置配置,更改aplication.properties,寫入:
hello.object="world"
再次運(yùn)行程序,現(xiàn)在就可以獲取到starter中配置的bean:
edu.whut.HelloService@4463d9d3
為什么要使用springboot starter?
有人可能好奇,這么大費(fèi)周章,就是為了把一個(gè)對(duì)象注入到IOC容器中?那為什么不直接在項(xiàng)目里注入?
因?yàn)槊總€(gè)Starter是一個(gè)高內(nèi)聚的功能模塊,通過(guò)依賴傳遞和條件化配置(@ConditionalOnClass等)實(shí)現(xiàn)“智能裝配”,避免冗余代碼。
為什么不直接寫starter,反而需要一個(gè)autoconfigure?
這是為了遵循Spring Boot官方提倡的“關(guān)注點(diǎn)分離”原則,將自動(dòng)配置邏輯和依賴管理解耦,讓架構(gòu)更清晰、更易維護(hù)。
autoconfigure包含條件化配置類、@ConfigurationProperties、META-INF/spring.factories(或org.springframework.boot.autoconfigure.AutoConfiguration.imports)
starter僅一個(gè)pom.xml,聚合autoconfigure模塊? + 該功能所需的所有第三方依賴
springboot starter自動(dòng)裝配的原理
@SpringBootApplciation注解是一個(gè)組合注解,這個(gè)注解被一個(gè)@EnableAutoConfiguration所注解。
@EnableAutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class) // 核心關(guān)鍵
public @interface EnableAutoConfiguration {
// ...
}
ImportSelector是一個(gè)用于動(dòng)態(tài)、編程式地選擇要導(dǎo)入的配置類的核心接口,它是一個(gè)函數(shù)式接口,核心方法是:
String[] selectImports(AnnotationMetadata importingClassMetadata);
這個(gè)方法根據(jù)給定的注解元數(shù)據(jù)(被@Import注解的類的信息),返回一個(gè)由全限定類名組成的字符串?dāng)?shù)組。這些返回的類名會(huì)在運(yùn)行時(shí)被Spring容器處理,就像它們?cè)揪捅?code>@Import注解直接引用一樣,其內(nèi)部的@Configuration、@Bean等注解會(huì)被正常解析。
AutoConfigurationImportSelector
AutoConfigurationImportSelector實(shí)現(xiàn)了ImportSelector接口,實(shí)現(xiàn)了selectImports方法。
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 1. 檢查是否開啟了自動(dòng)裝配(默認(rèn)是開啟的)
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
因此核心的邏輯是getAutoConfigurationEntry(annotationMetadata)
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
//加載所有候選配置類
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes); //移除重復(fù)和顯式排除的類
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
//進(jìn)行條件注解的篩選
configurations = this.getConfigurationClassFilter().filter(configurations);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
}
在這個(gè)方法中,核心邏輯只有三步:
- 加載所有的候選配置類
- 移除重復(fù)和顯式排除的類
- 進(jìn)行“條件注解”篩選
加載候選配置類
調(diào)用 getCandidateConfigurations()。這個(gè)方法會(huì)去約定好的位置,讀取一個(gè)文件,這個(gè)文件里列出了所有可能被加載的自動(dòng)配置類。
這個(gè)約定好的位置,在springboot3是META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,這個(gè)文件的每一行是一個(gè)全類名,在springboot2.x中是META-INF/spring.factories,org.springframework.boot.autoconfigure.EnableAutoConfiguration為鍵的值就是一系列全類名。
無(wú)論哪種格式,此時(shí)得到的都是一個(gè)巨大的List,包含了Spring Boot所有內(nèi)置的(如DataSourceAutoConfiguration, WebMvcAutoConfiguration)以及第三方starter提供的自動(dòng)配置類。因此要進(jìn)行后續(xù)的排除和篩選。
總結(jié)
- 啟動(dòng):執(zhí)行SpringApplication.run(),啟動(dòng)Spring容器
- 觸發(fā):容器解析主類上的@SpringBootApplication->@EnableAutoConfiguration
- 決策:@EnableAutoConfiguration導(dǎo)入AutoConfigurationImportSelector
- 掃描:AutoConfigurationImportSelector從 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(Spring Boot 3)中讀取所有候選自動(dòng)配置類。
- 排除和篩選:對(duì)候選列表進(jìn)行層層篩選。
- 導(dǎo)入:將最終滿足所有條件的自動(dòng)配置類的全限定名數(shù)組返回給容器。
- 解析與注冊(cè):容器將這些自動(dòng)配置類當(dāng)作普通的 @Configuration類進(jìn)行解析,將其內(nèi)部符合條件的 @Bean方法注冊(cè)為Bean定義。
到此這篇關(guān)于SpringBoot Starter的用法以及原理小結(jié)的文章就介紹到這了,更多相關(guān)SpringBoot Starter用法內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java如何根據(jù)前端返回的字段名進(jìn)行查詢數(shù)據(jù)
這篇文章主要為大家詳細(xì)介紹了Java如何根據(jù)前端返回的字段名進(jìn)行查詢數(shù)據(jù),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-11-11
mybatis?plus配置自動(dòng)create_time和update_time方式
在處理數(shù)據(jù)時(shí),注意時(shí)間類型的轉(zhuǎn)換非常重要,不同編程環(huán)境和數(shù)據(jù)庫(kù)對(duì)時(shí)間數(shù)據(jù)的處理方式各異,因此在數(shù)據(jù)遷移或日常處理中需謹(jǐn)慎處理時(shí)間格式,個(gè)人經(jīng)驗(yàn)表明,了解常用的時(shí)間轉(zhuǎn)換函數(shù)和方法能有效避免錯(cuò)誤,提高工作效率,希望這些經(jīng)驗(yàn)?zāi)転榇蠹規(guī)?lái)幫助2024-09-09
Java網(wǎng)絡(luò)編程之UDP協(xié)議詳細(xì)解讀
這篇文章主要介紹了Java網(wǎng)絡(luò)編程之UDP協(xié)議詳細(xì)解讀,UDP協(xié)議全稱是用戶數(shù)據(jù)報(bào)協(xié)議,在網(wǎng)絡(luò)中它與TCP協(xié)議一樣用于處理數(shù)據(jù)包,是一種無(wú)連接的協(xié)議,在OSI模型中,在第四層——傳輸層,處于IP協(xié)議的上一層,需要的朋友可以參考下2023-12-12
詳解Java阻塞隊(duì)列(BlockingQueue)的實(shí)現(xiàn)原理
這篇文章主要介紹了詳解Java阻塞隊(duì)列(BlockingQueue)的實(shí)現(xiàn)原理,阻塞隊(duì)列是Java util.concurrent包下重要的數(shù)據(jù)結(jié)構(gòu),有興趣的可以了解一下2017-06-06
解決mybatis竟然報(bào)Invalid value for getInt()的問(wèn)題
使用mybatis遇到一個(gè)非常奇葩的問(wèn)題,總是報(bào)Invalid value for getInt()的問(wèn)題,怎么解決呢?下面小編通過(guò)場(chǎng)景分析給大家代來(lái)了mybatis報(bào)Invalid value for getInt()的解決方法,感興趣的朋友參考下吧2021-10-10

