大家好,這里是 盛碼筆記。
本篇我們來聊一聊 Spring Boot 的“魔法”是如何實現的。你可能已經用過 Spring Boot
快速搭建項目,但有沒有想過:為什么只需要引入幾個 starter
,Spring Boot
就能自動配置好整個應用環境?
這背后的核心機制就是:起步依賴(Starter Dependencies
) 和 自動裝配(Auto-Configuration
)。本文將從源碼角度帶你深入理解這兩個機制的底層原理,建議你具備一定的 Spring 基礎后再閱讀。
(一)起步依賴
依賴傳遞機制(Maven/Gradle)
依賴傳遞的話,在講maven的時候有提到過,下面是地址(這里就不展開了)
Maven詳細教程(包含安裝)-CSDN博客
-
核心作用:Starter 本身是一個空項目,僅包含一個
pom.xml
(或 Gradle 構建文件),通過 Maven 的依賴傳遞聚合了某個場景所需的全部依賴。 -
示例:
spring-boot-starter-web
的pom.xml
中聲明了:<dependencies><dependency>spring-boot-starter</dependency><dependency>spring-boot-starter-json</dependency><dependency>spring-webmvc</dependency><dependency>spring-boot-starter-tomcat</dependency> </dependencies>
-
效果:用戶只需引入
spring-boot-starter-web
,即可自動獲得 Web 開發所需的所有庫(Tomcat、Jackson、Spring MVC 等)。
(二)自動配置
- 原理: Spring Boot 在啟動時掃描 classpath,根據存在的類、Bean 定義以及屬性設置,自動配置 Spring 應用所需的各種組件(如 DataSource, JPA, MVC, Security 等)。
- 實現:
@EnableAutoConfiguration
注解(通常由@SpringBootApplication
包含)啟用自動配置。- 大量的
XxxAutoConfiguration
類(在spring-boot-autoconfigure
jar 中),這些類使用@Configuration
定義配置,并使用@ConditionalOnXxx
(條件注解)來決定配置是否生效(例如@ConditionalOnClass
,@ConditionalOnMissingBean
,@ConditionalOnProperty
)。
Bean的導入機制
1. @ComponentScan
作用:
自動掃描指定包及其子包下所有被 @Component
、@Service
、@Controller
、@Repository
等注解標記的類,并注冊為Bean。
特點:
- 基于包路徑掃描:通過
basePackages
或basePackageClasses
指定掃描范圍。 - 批量注冊:一次性注冊多個Bean。
- 依賴組件注解:目標類必須被Spring的組件注解標記(如
@Component
)。 - 過濾規則:支持通過
includeFilters
/excludeFilters
自定義掃描規則。
@Configuration
@ComponentScan(basePackages = "com.example.service",includeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*Service")
)
public class AppConfig {}
適用場景:
項目中自定義組件的批量注冊(如Service、Controller層)。
2. @Import
作用:
顯式導入一個或多個配置類(@Configuration
)、普通類(視為Bean)或 ImportSelector
/ImportBeanDefinitionRegistrar
的實現類。
特點:
- 精確導入:直接指定要導入的類(而非包掃描)。
- 無需組件注解:被導入的類不需要被
@Component
標記。 - 支持多種類型:
- 配置類:導入其他
@Configuration
類。 - 普通類:直接作為Bean注冊。
ImportSelector
:動態選擇導入類。ImportBeanDefinitionRegistrar
:編程式注冊Bean。
- 配置類:導入其他
@Configuration
@Import({ DataSourceConfig.class, // 導入配置類MyUtility.class, // 普通類(注冊為Bean)MyImportSelector.class // 動態選擇導入
})
public class AppConfig {}
適用場景:
- 導入第三方庫的配置類(如
DataSourceConfig
)。 - 注冊未被組件注解標記的類(如工具類)。
3. ImportSelector
接口
作用:
通過編程方式動態選擇要導入的配置類或Bean類,返回類的全限定名字符串數組。
特點:
- 動態決策:根據運行時條件(如配置參數、環境變量)決定導入哪些類。
- 解耦設計:將導入邏輯從
@Import
注解中分離。 - 無侵入性:目標類無需實現任何接口或注解。
實現步驟:
- 實現
ImportSelector
接口。 - 重寫
selectImports()
方法,返回要導入的類的全限定名。 - 通過
@Import
引入該ImportSelector
。
public class MyImportSelector implements ImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {// 根據當前的運行環境動態選擇配置文件(dev和prod環境)if (System.getenv("env").equals("prod")) {return new String[]{"com.example.ProdDataSourceConfig"};}else {return new String[] { "com.example.DevDataSourceConfig" };}}
}// 通過@Import引入
@Configuration
@Import(MyImportSelector.class)
public class AppConfig {}
適用場景:
需要根據條件動態注冊Bean(如環境區分、特性開關)。
4. @Bean
方法
在 @Configuration
類中使用 @Bean
注解的方法:
@Configuration
public class AppConfig {@Beanpublic DataSource dataSource() {return new HikariDataSource();}
}
特點:
- 適用于實例化過程復雜的 Bean(如連接池)
- 可顯式控制 Bean 的初始化邏輯
5. ImportBeanDefinitionRegistrar
(了解)
編程式注冊 BeanDefinition:
public class CustomRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {registry.registerBeanDefinition("customBean", new RootBeanDefinition(CustomBean.class));}
}
特點:
- 最底層的 Bean 注冊方式
- 可操作 BeanDefinition 元數據(作用域/Lazy/初始化方法等)
源碼剖析
springboot版本為3.4.7
-
通過
@SpringBootApplication
是入口,點擊進去。通過下面的源碼可以發現,知道了為什么
@SpringBootApplication
掃描、注冊當前包及子包下的Bean了,還可以當配置類使用。// 注解使用范圍(類、接口或枚舉類型) @Target({ElementType.TYPE}) // 保留策略:注解信息保留在運行時 @Retention(RetentionPolicy.RUNTIME) // 注解會被包含在 JavaDoc 中 @Documented // 繼承:一個類使用了該注解,其子類將自動繼承該注解 @Inherited// 這個注解相當于@Configuration的增強版(配置類) @SpringBootConfiguration // 自動配置的核心注解(啟用 Spring Boot 的自動配置機制) @EnableAutoConfiguration// 啟用 Spring 的組件掃描功能,并通過自定義過濾器排除某些類不被掃描到容器中 // 不指定包路徑,默認掃描、注冊當前包及子包下的Bean @ComponentScan(excludeFilters = {@Filter( // 排除過濾器type = FilterType.CUSTOM, // 表示使用開發者自定義的過濾器邏輯classes = {TypeExcludeFilter.class} // 指定具體的過濾器類 ), @Filter(type = FilterType.CUSTOM,classes = {AutoConfigurationExcludeFilter.class} // 排除那些已經被標記為“自動配置”的類 )} ) public @interface SpringBootApplication {// 這里省略了 }
再看一下
@SpringBootConfiguration
里面,下文可知,@SpringBootConfiguration
的功能相當于增強版的@Configuration
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented // 配置類注解的功能 @Configuration // 提升性能,支持編譯時索引,加快組件掃描 @Indexed public @interface SpringBootConfiguration {@AliasFor(annotation = Configuration.class)boolean proxyBeanMethods() default true; }
-
再點擊
@EnableAutoConfiguration
,進去繼續看Spring Boot 自動裝配的核心是
@EnableAutoConfiguration
→AutoConfigurationImportSelector
(實現ImportSelector
)@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited // 將主配置類(即標注了 @EnableAutoConfiguration 的類)所在的包作為自動掃描的根包。 @AutoConfigurationPackage @Import({AutoConfigurationImportSelector.class}) public @interface EnableAutoConfiguration {String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";Class<?>[] exclude() default {};String[] excludeName() default {}; }
然后是
@AutoConfigurationPackage
,它的作用是將主配置類所在的包作為自動掃描的根包。(至于后續邏輯就不展開了)在 Spring Boot 自動配置過程中,Spring 需要知道哪些包是“主應用包”,即:
- 包含主配置類的包;
- 需要被自動掃描和處理的包;
這些信息會被存儲在一個特殊的 Bean 中,例如通過
AutoConfigurationPackages.register(...)
注冊到容器中。其他自動配置類(如
DataSourceAutoConfiguration
)會依賴這些包路徑來判斷是否啟用某些自動配置邏輯。@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited // 從使用 @AutoConfigurationPackage 注解的類中讀取 basePackages 和 basePackageClasses 屬性; // 將這些包路徑注冊到 Spring 容器中; // 供后續 Spring Boot 自動配置機制使用。 @Import({AutoConfigurationPackages.Registrar.class}) public @interface AutoConfigurationPackage {String[] basePackages() default {};Class<?>[] basePackageClasses() default {}; }
-
接下來需要點進入
AutoConfigurationImportSelector
類里面- 該類實現了
DeferredImportSelector
接口,而DeferredImportSelector
接口繼承于ImportSelector
接口,所以實現了selectImports
方法。這里與上文第三種導入Bean的方法類似,需要一個字符串數組。 - 這里不清楚的就是
getAutoConfigurationEntry
,跳轉過去
// 代碼部分省略 /*** 根據注解元數據選擇需要導入的自動配置類。** 該方法通常用于 Spring Boot 的自動配置機制中,根據當前類上的注解信息* 決定應該導入哪些自動配置類(即返回對應的全限定類名數組)。** @param annotationMetadata 注解元數據,描述了當前類上使用的注解信息* @return 返回一個字符串數組,包含所有需要導入的自動配置類的全限定名*/ public String[] selectImports(AnnotationMetadata annotationMetadata) {// 如果自動配置未啟用,則直接返回空數組,不導入任何配置類if (!this.isEnabled(annotationMetadata)) {return NO_IMPORTS;} else {// 獲取自動配置條目(包含要導入的配置類集合)AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);// 將配置類集合轉換為字符串數組并返回return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());} }
點擊
getAutoConfigurationEntry
方法跳轉過去- 根據該方法的名稱和返回值可以初步推測該方法時返回一個自動配置的對象
- 該方法圍繞
configurations
這個字符串列表集合,需要知道它是什么就再點擊getCandidateConfigurations
過去
/*** 獲取自動配置條目(包含要導入的自動配置類和被排除的類)** 該方法主要完成以下工作:* 1. 檢查是否啟用自動配置* 2. 獲取注解屬性* 3. 加載所有候選的自動配置類* 4. 去重處理* 5. 處理排除類* 6. 過濾不符合條件的配置類* 7. 發布自動配置導入事件** @param annotationMetadata 注解元數據,描述當前類上的注解信息* @return 返回一個 AutoConfigurationEntry 對象,包含自動配置類和排除類*/ protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {// 1. 判斷是否啟用了自動配置,如果沒有啟用,則返回一個空的 AutoConfigurationEntryif (!this.isEnabled(annotationMetadata)) {return EMPTY_ENTRY;} else {// 獲取注解屬性(例如 @EnableAutoConfiguration 上的 exclude 或 excludeName 屬性)AnnotationAttributes attributes = this.getAttributes(annotationMetadata);// 獲取所有的候選自動配置類(從 META-INF/spring.factories 中讀取)List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);//去除重復的配置類configurations = this.removeDuplicates(configurations);//獲取需要排除的自動配置類(通過注解屬性 exclude / excludeName)Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);this.checkExcludedClasses(configurations, exclusions);// 從候選配置類中移除被排除的類configurations.removeAll(exclusions);configurations = this.getConfigurationClassFilter().filter(configurations);this.fireAutoConfigurationImportEvents(configurations, exclusions);// 將處理后的配置類和排除類封裝成 AutoConfigurationEntry 返回return new AutoConfigurationEntry(configurations, exclusions);} }
點擊
getCandidateConfigurations
過去在這個方法里面可以知道這是從某個路徑中加載配置類,并返回字符串集合。
/*** 加載所有候選的自動配置類*/ protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {// 從 META-INF/spring/...imports 文件中加載自動配置類ImportCandidates importCandidates = ImportCandidates.load(this.autoConfigurationAnnotation, this.getBeanClassLoader());// 獲取配置類列表List<String> configurations = importCandidates.getCandidates();// 校驗是否找到配置類,避免為空Assert.state(!CollectionUtils.isEmpty(configurations), "No auto configuration classes found in META-INF/spring/" + this.autoConfigurationAnnotation.getName() + ".imports. If you are using a custom packaging, make sure that file is correct.");return configurations; }
- 該類實現了
-
獲取加載配置類的全路徑(包名+類名)
采用debug斷點調試
- 在
getCandidateConfigurations
方法里面打一個斷點 - 然后運行主類即可
可以發現
this.autoConfigurationAnnotation.getName()
獲取的值是org.springframework.boot.autoconfigure.AutoConfiguration
結合上面代碼中長字符串的拼接加載配置類的完整路徑是
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
- 在
-
查看配置類
怎么知道這個路徑對不對呢?直接找出來看一下就知道了唄
項目左邊打開外部庫,根據這個路徑找對應的文件
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
打開文件可以發現全都是全類名(包名+類名),再結合上面講Bean導入機制時,實現ImportSelector
接口的配置類可以全類名字符串數組加載、注冊Bean。 -
隨便點擊進入一個配置類,以
GsonAutoConfiguration
為例這里有代碼創建了Gson的Bean實例,所以很多類不需要手動聲明便可以直接使用,然后有一些以
Conditional
開頭的注解作用是按條件加載對應的Bean,后面會詳細講解。
按條件裝配
-
@ConditionalOnClass
-
作用: 當且僅當項目的類路徑 (classpath) 上存在指定的類時,才會加載被注解標記的配置(配置類、
@Bean
方法、@Component
等)。 -
場景:
-
自動配置的核心: Spring Boot Starter 的自動配置類大量使用此注解。例如,
DataSourceAutoConfiguration
可能這樣@Configuration @ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class }) public class DataSourceAutoConfiguration {// ... 只有在類路徑上有 DataSource 和 EmbeddedDatabaseType 時才配置數據源 }
這確保了只有當你引入了數據庫驅動(如
HikariCP
,tomcat-jdbc
)等依賴(它們會提供DataSource
實現類)時,Spring Boot 才會嘗試自動配置數據源。避免了因為缺少依賴而拋出ClassNotFoundException
。 -
可選功能: 當你開發一個庫或 Starter,并且某些功能需要特定的第三方庫支持時,可以使用
@ConditionalOnClass
來確保只有用戶引入了該庫,你的功能才會被激活。
-
-
注意事項: 指定的類名必須是全限定名(
com.example.SomeClass
)。Spring Boot 通常使用name
屬性指定字符串形式的類名(避免在編譯時就需要該類存在),但也支持直接使用Class<?>[] value()
。
-
-
@ConditionalOnMissingClass
-
作用: 當且僅當項目的類路徑 (classpath) 上不存在指定的類時,才會加載被注解標記的配置。
-
場景:
-
提供默認實現或回退方案: 當某個功能有多個實現(如日志框架:Logback, Log4j2),你可以為其中一個實現(如 Logback)配置默認 Bean,但條件設置為類路徑上不存在另一個實現(如 Log4j2)的關鍵類。
@Configuration @ConditionalOnMissingClass("org.apache.logging.log4j.core.LoggerContext") // 假設 Log4j2 的核心類 public class DefaultLoggingConfiguration {@Beanpublic Logger myLogger() {// 配置默認的 Logback Logger} }
這樣,如果用戶引入了 Log4j2,這個默認的 Logback 配置就不會生效。
-
環境隔離: 在特定環境(如測試)下排除某些不需要的配置。
-
-
注意事項: 同樣需要指定全限定名。使用相對較少,因為
@ConditionalOnClass
和@ConditionalOnBean
/@ConditionalOnMissingBean
組合通常能覆蓋大部分需求。
-
-
@ConditionalOnBean
-
作用: 當且僅當 Spring 應用上下文 (IoC 容器) 中已經存在(已經定義或即將定義)指定類型、指定名稱或滿足指定條件的 Bean 時,才會加載被注解標記的配置。
-
場景:
-
依賴其他 Bean 的自動配置: 某個自動配置需要另一個 Bean 已經存在才能工作。例如,一個配置
JdbcTemplate
的自動配置類可能依賴于DataSource
Bean:@Configuration public class JdbcTemplateAutoConfiguration {@Bean@ConditionalOnBean(DataSource.class) // 只有容器中存在 DataSource Bean 時才創建 JdbcTemplatepublic JdbcTemplate jdbcTemplate(DataSource dataSource) {return new JdbcTemplate(dataSource);} }
確保
JdbcTemplate
只在有可用的DataSource
時才被創建。 -
按需配置擴展功能: 如果用戶配置了某個特定的 Bean(如自定義的
MySpecialService
),則自動激活與之相關的額外功能或配置。
-
-
注意事項:
- 匹配條件可以指定 Bean 的類型 (
Class<?>
)、名稱 (String name
) 或使用@ConditionalOnSingleCandidate
等注解進一步限定。 - Bean 定義順序很重要! 被依賴的 Bean(
DataSource
)必須在依賴它的 Bean(JdbcTemplate
)之前被定義(或至少在同一批處理中能被檢測到)。Spring Boot 的自動配置機制通過精心設計的加載順序來解決這個問題。在自定義配置時需要注意潛在的循環依賴或順序問題。
- 匹配條件可以指定 Bean 的類型 (
-
-
@ConditionalOnMissingBean
-
作用: 當且僅當 Spring 應用上下文 (IoC 容器) 中不存在(沒有定義,也沒有即將定義)指定類型、指定名稱或滿足指定條件的 Bean 時,才會加載被注解標記的配置。
-
場景:
-
提供默認 Bean 實現 (關鍵!): 這是 Spring Boot 自動配置的核心機制,也是用戶覆蓋默認配置的關鍵入口。自動配置類會使用此注解定義默認 Bean:
@Configuration public class DataSourceConfiguration {@Bean@ConditionalOnMissingBean // 關鍵注解!如果用戶沒有自定義 DataSource Bean...public DataSource dataSource() {// ...則 Spring Boot 自動創建一個默認的 DataSource (如 HikariCP)return new HikariDataSource();} }
-
用戶自定義覆蓋: 如果用戶在自己的配置類中定義了相同類型的 Bean:
@Configuration public class MyCustomConfig {@Beanpublic DataSource myCustomDataSource() { // 自定義 DataSourcereturn new MySpecialDataSource();} }
此時,容器中已經存在一個類型為
DataSource
的 Bean (myCustomDataSource
)。因此,自動配置類中帶有@ConditionalOnMissingBean
的dataSource()
方法將不會執行,用戶自定義的DataSource
生效。這就是用戶覆蓋 Spring Boot 默認配置的方式。 -
避免重復定義: 確保只有在用戶沒有提供特定 Bean 時才提供默認實現。
-
-
總結
- 啟動入口與
@SpringBootApplication
- 應用啟動始于標注了
@SpringBootApplication
的主類。 @SpringBootApplication
是一個復合注解,核心包含:@SpringBootConfiguration
: 標記該類為配置類(本質上是@Configuration
)。@ComponentScan
: 掃描主類所在包及其子包下的@Component
,@Service
,@Repository
,@Controller
等注解的類,將其注冊為 Bean。@EnableAutoConfiguration
: 這是觸發自動配置的關鍵注解。
- 應用啟動始于標注了
@EnableAutoConfiguration
的魔力- 這個注解利用 Spring Framework 的
@Import
機制導入了AutoConfigurationImportSelector
類。 AutoConfigurationImportSelector
實現了DeferredImportSelector
接口,它的核心方法是selectImports()
。
- 這個注解利用 Spring Framework 的
- 加載自動配置候選列表 (
spring.factories
/AutoConfiguration.imports
)AutoConfigurationImportSelector.selectImports()
方法的核心任務是從 classpath 下的特定位置加載所有可能應用的自動配置類的全限定名列表。- 加載位置 (優先級從上到下):
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
(Spring Boot 2.7+ 推薦方式,一行一個全類名)。META-INF/spring.factories
(舊方式,但廣泛支持,EnableAutoConfiguration
key 下列出全類名)。(spring 3.x版本后沒有了)
- Spring Boot 在
spring-boot-autoconfigure
jar 包的META-INF
目錄下提供了大量內置的自動配置類(如DataSourceAutoConfiguration
,WebMvcAutoConfiguration
,JacksonAutoConfiguration
等)。 - 第三方 starter 也會在自己的 jar 包中提供相應的
spring.factories
或AutoConfiguration.imports
文件來聲明其自動配置類。
- 自動配置類的過濾與篩選 (條件注解
@Conditional
)- 加載得到的候選自動配置類列表并不會全部生效。Spring Boot 的核心機制在于按需配置。
- 每個自動配置類上都標注了一個或多個
@Conditional
及其衍生注解(條件注解),用來判斷這個配置類是否應該被加載、其內部的 Bean 是否應該被創建。 - 關鍵條件注解舉例:
@ConditionalOnClass
: 當 classpath 下存在指定的類時才生效。@ConditionalOnMissingClass
: 當 classpath 下不存在指定的類時才生效。@ConditionalOnBean
: 當 Spring 容器中存在指定的 Bean 時才生效。@ConditionalOnMissingBean
: 當 Spring 容器中不存在指定的 Bean 時才生效(這是用戶覆蓋默認配置的關鍵)。@ConditionalOnProperty
: 當指定的配置屬性具有特定值時才生效。@ConditionalOnResource
: 當 classpath 下存在指定的資源文件時才生效。@ConditionalOnWebApplication
/@ConditionalOnNotWebApplication
: 根據應用是否是 Web 應用來決定。@ConditionalOnJava
: 根據運行時的 Java 版本決定。
- 條件評估過程:
- Spring Boot 在啟動時(具體在
ConfigurationClassPostProcessor
處理配置類期間)會創建一個ConditionEvaluator
。 - 對于每個候選的自動配置類,
ConditionEvaluator
會解析并評估其上的所有條件注解。 - 只有所有條件都滿足的自動配置類,才會被真正導入 Spring 應用上下文,其內部的
@Bean
方法才有機會被執行。
- Spring Boot 在啟動時(具體在
- 自動配置類的執行與 Bean 注冊
- 通過條件評估篩選出來的自動配置類,會被當作標準的 Spring
@Configuration
類處理。 - Spring 容器會解析這些配置類:
- 執行類內部的
@Bean
方法,將返回的對象注冊為 Spring 容器中的 Bean。 - 處理類上的其他注解(如
@ImportResource
,@PropertySource
等)。
- 執行類內部的
- 自動配置類的作用:
- 創建和配置應用運行所需的核心基礎設施 Bean(如
DataSource
,JdbcTemplate
,DispatcherServlet
,ObjectMapper
,RestTemplateBuilder
等)。 - 根據 classpath 存在的依賴和用戶配置的屬性,智能地配置這些 Bean 的行為和默認值。
- 提供默認的屬性綁定(通過
@EnableConfigurationProperties
+@ConfigurationProperties
)。
- 創建和配置應用運行所需的核心基礎設施 Bean(如
- 通過條件評估篩選出來的自動配置類,會被當作標準的 Spring
- 用戶配置優先
- 核心原則:自動配置是非侵入式的。用戶定義的 Bean 總是優先于自動配置提供的默認 Bean。
- 這是通過
@ConditionalOnMissingBean
注解實現的。自動配置類在定義其默認 Bean 時,通常會加上@ConditionalOnMissingBean
。 - 如果用戶在自己的
@Configuration
類中顯式定義了一個相同類型的 Bean,那么這個用戶定義的 Bean 會被注冊到容器中。 - 當自動配置類執行到定義該默認 Bean 的方法時,
@ConditionalOnMissingBean
條件檢測到容器中已存在該類型的 Bean(用戶定義的),條件不滿足,該方法不會執行,從而避免了注冊默認 Bean。用戶配置成功覆蓋了自動配置。
- 屬性配置 (
application.properties
/yml
)- 自動配置類大量使用
@ConfigurationProperties
來綁定外部配置(主要是application.properties
或application.yml
文件)。 - 這些屬性用于覆蓋自動配置提供的默認值。例如,配置
spring.datasource.url
,spring.datasource.username
,spring.datasource.password
來覆蓋DataSourceAutoConfiguration
創建數據源時使用的默認值。
- 自動配置類大量使用
(三)自定義starter
自定義Starter是一種封裝特定功能(如自動配置、依賴管理等)的便捷方式,讓其他項目能快速集成。
自定義JWT Starter實現
這里以JWT為例,講解如何實現jwt starter如何實現,通過實現自定義stater可以更好理解起步依賴和自動配置。我們需要實現一個jwt-spring-boot-starter
自定義starter,在其他模塊導入這個依賴就可以直接調用JwtUtils
工具類里面的方法。
jwt-starter/
├── jwt-spring-boot-autoconfigure/ // 自動配置模塊
│ ├── src/main/java
│ │ └── com/example/jwt
│ │ ├── JwtAutoConfiguration.java
│ │ ├── JwtProperties.java
│ │ └── JwtUtils.java
│ │
│ └── pom.xml
├── jwt-spring-boot-starter/ // Starter模塊
│ └── pom.xml
-
創建
jwt-spring-boot-autoconfigure
模塊,只留下pom.xml文件和scr文件,刪除主啟動類,跟上面結構一樣(需要留下resource目錄),并導入依賴<!-- jjwt 核心 --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api</artifactId><version>0.11.5</version></dependency><!-- 實現包 --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><version>0.11.5</version><scope>runtime</scope></dependency><!-- 使用 jackson 解析 JSON --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId><version>0.11.5</version><scope>runtime</scope></dependency>
-
創建
jwt-spring-boot-starter
模塊,只留下pom.xml文件,再導入jwt-spring-boot-autoconfigure
模塊依賴<dependency><groupId>com.example</groupId><artifactId>jwt-spring-boot-autoconfigure</artifactId><version>0.0.1-SNAPSHOT</version> </dependency>
-
在
jwt-spring-boot-autoconfigure
模塊創建JwtProperties
配置類package com.example.jwt;import org.springframework.boot.context.properties.ConfigurationProperties;@ConfigurationProperties(prefix = "jwt") public class JwtProperties {// 密鑰(Base64編碼)private String secret = "c2VjcmV0LWtleS1mb3Itand0LWF1dGgtc3RhcnRlci1leGFtcGxl";// 有效時間(秒),默認1小時private long expiration = 3600;// 簽發者private String issuer = "jwt-starter";// 是否開啟調試模式private boolean debug = false;// Getters and Setterspublic String getSecret() {return secret;}public void setSecret(String secret) {this.secret = secret;}public long getExpiration() {return expiration;}public void setExpiration(long expiration) {this.expiration = expiration;}public String getIssuer() {return issuer;}public void setIssuer(String issuer) {this.issuer = issuer;}public boolean isDebug() {return debug;}public void setDebug(boolean debug) {this.debug = debug;} }
-
然后創建
JwtUtils
工具類package com.example;import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.security.Keys;import java.security.Key; import java.util.Date; import java.util.HashMap; import java.util.Map;public class JwtUtils {private final JwtProperties jwtProperties;public JwtUtils(JwtProperties jwtProperties) {this.jwtProperties = jwtProperties;}// 生成JWT Tokenpublic String generateToken(String subject) {return generateToken(subject, new HashMap<>());}public String generateToken(String subject, Map<String, Object> claims) {long now = System.currentTimeMillis();return Jwts.builder().setClaims(claims).setSubject(subject).setIssuer(jwtProperties.getIssuer()).setIssuedAt(new Date(now)).setExpiration(new Date(now + jwtProperties.getExpiration() * 1000)).signWith(getSignKey(), SignatureAlgorithm.HS256).compact();}// 解析JWT Tokenpublic Claims parseToken(String token) {return Jwts.parserBuilder().setSigningKey(getSignKey()).build().parseClaimsJws(token).getBody();}// 驗證Token是否有效public boolean validateToken(String token) {try {parseToken(token);return true;} catch (Exception e) {if (jwtProperties.isDebug()) {e.printStackTrace();}return false;}}// 從配置的密鑰獲取簽名Keyprivate Key getSignKey() {byte[] keyBytes = Decoders.BASE64.decode(jwtProperties.getSecret());return Keys.hmacShaKeyFor(keyBytes);}// 獲取Token中的主題(通常是用戶ID)public String getSubjectFromToken(String token) {Claims claims = parseToken(token);return claims.getSubject();}// 獲取Token中的自定義聲明public Object getClaimFromToken(String token, String claimName) {Claims claims = parseToken(token);return claims.get(claimName);} }
-
創建
JwtAutoConfiguration
自動配置的配置類import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;@Configuration // 啟用并注冊配置屬性類 // application.yml 或 application.properties 中的配置項自動綁定到一個 Java Bean 類中。 @EnableConfigurationProperties(JwtProperties.class) public class JwtAutoConfiguration {// 當spring容器不存在這個bean時,才注入該Bean@ConditionalOnMissingBean@Beanpublic JwtUtils jwtUtils (JwtProperties jwtProperties){return new JwtUtils(jwtProperties);} }
-
創建目錄文件
src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
導入自己的JwtAutoConfiguration
全路徑com.example.JwtAutoConfiguration
-
然后是測試,另外新建一個模塊,導入我們剛才寫好的依賴
<dependency><groupId>com.example</groupId><artifactId>jwt-spring-boot-starter</artifactId><version>0.0.1-SNAPSHOT</version> </dependency>
@SpringBootTest public class TestLogin {@Autowiredprivate JwtUtils jwtUtils;@Testvoid JwtStarterTest (){HashMap<String, Object> map = new HashMap<>();map.put("username","tom");map.put("password","123123");String jwt = jwtUtils.generateToken("0001",map);System.out.println(jwt);} }
最后這里可以看見jwt-spring-boot-starter
生效了
通過本文的講解,相信你已經對 Spring Boot
的兩大核心機制 —— 起步依賴(Starter
) 和 自動裝配(Auto-Configuration
) 有了更深入的理解。
Spring Boot
的“開箱即用”并非魔法,而是建立在 Spring
強大的容器管理和條件化配置基礎上的工程智慧。理解這些底層原理,不僅能幫助你更好地使用 Spring Boot
,還能在遇到復雜問題時更快地定位和解決。
如果你覺得這篇文章對你有幫助,歡迎點贊、收藏并分享給更多需要的小伙伴。這里是 盛碼筆記,我們下期再見!