SpringBoot自動裝配原理解析
什么是Spring Boot自動裝配
Spring Boot自動裝配是指在Spring Boot應用啟動時,根據(jù)類路徑下的jar包依賴、Bean定義、各種配置文件等信息,自動配置Spring應用上下文的Bean。
這種機制極大地簡化了配置工作,使得開發(fā)者可以更加專注于業(yè)務邏輯的實現(xiàn)。
在深入自動裝配原理前,我們先看下 SPI 機制
。
SPI 機制
SPI(Service Provider Interface)是一種動態(tài)替換服務提供者的機制。它允許一個服務接口有多個服務提供者,并且在程序運行時動態(tài)選擇一個服務提供者。
SPI又分為 JDK SPI
和 Spring SPI
。
JDK SPI
在 Java 平臺上,SPI 通常是通過 java.util.ServiceLoader
類實現(xiàn)的,這種機制在Java標準庫中廣泛應用,如JDBC驅(qū)動的管理。
SPI可以很靈活的讓接口和實現(xiàn)分離,讓服務提供者只提供接口,第三方來實現(xiàn),然后可以使用配置文件的方式來實現(xiàn)替換或者擴展。
工作原理
Java SPI 的工作原理基于以下幾個步驟:
- 定義服務接口:首先定義一個服務接口(或抽象類),作為服務的規(guī)范。
- 提供服務實現(xiàn):編寫接口的具體實現(xiàn)類。
- 注冊服務實現(xiàn):在
META-INF/services
目錄下創(chuàng)建一個以接口全限定名為名的文件
,文件內(nèi)容為接口實現(xiàn)類的全限定名。 - 加載服務實現(xiàn):使用
java.util.ServiceLoader
來加載并使用這些實現(xiàn)。
舉例說明一下:
(1)創(chuàng)建一個 DemoDAO
的接口
public interface DemoDao {<!--{cke_protected}{C}%3C!%2D%2D%20%2D%2D%3E-->}
(2)分別創(chuàng)建兩個實現(xiàn)類
public class MysqlDao implements DemoDao {<!--{cke_protected}{C}%3C!%2D%2D%20%2D%2D%3E-->}
public class OracleDao implements DemoDao {<!--{cke_protected}{C}%3C!%2D%2D%20%2D%2D%3E-->}
(3)在resources
下新建META-INF/services/
目錄,在該目錄下新建接口全限定名的文件com.study.spring.z_spi.DemoDao
,文件內(nèi)容是上面那兩個實現(xiàn)類。
com.study.spring.z_spi.MysqlDao com.study.spring.z_spi.OracleDao
(4)使用 JDK 提供的ServiceLoader
來測試下
public class JdkSpiApplication { public static void main(String[] args) { ServiceLoader<DemoDao> demoDaos = ServiceLoader.load(DemoDao.class); demoDaos.iterator().forEachRemaining(t -> { System.out.println(t); }); } }
輸出結(jié)果 :
com.study.spring.z_spi.MysqlDao@6e8cf4c6
com.study.spring.z_spi.OracleDao@12edcd21
JDBC DriverManager
Java SPI 機制在JDBC驅(qū)動管理中的應用主要體現(xiàn)在JDBC 4.0及以上版本的驅(qū)動自動發(fā)現(xiàn)和加載上。
在JDBC4.0之前,連接數(shù)據(jù)庫的時候,通常會用Class.forName(“com.mysql.jdbc.Driver”)先加載數(shù)據(jù)庫相關的驅(qū)動,然后再進行獲取連接等的操作。
而JDBC4.0之后不需要用Class.forName(“com.mysql.jdbc.Driver”)來加載驅(qū)動,直接獲取連接就可以了,這種方式就是使用了Java的SPI擴展機制來實現(xiàn)。
定義服務接口
在JDBC中,服務接口是由Java平臺定義的java.sql.Driver
接口,所有的JDBC驅(qū)動都必須實現(xiàn)這個接口,以提供與數(shù)據(jù)庫建立連接的能力。
提供服務實現(xiàn)
數(shù)據(jù)庫廠商(如MySQL、Oracle等)為它們的數(shù)據(jù)庫提供JDBC驅(qū)動程序,這些驅(qū)動程序?qū)崿F(xiàn)了java.sql.Driver
接口。
注冊服務提供者
JDBC驅(qū)動的注冊是通過在驅(qū)動程序的JAR包中的META-INF/services
目錄下創(chuàng)建一個名為java.sql.Driver
的文件來完成的。這個文件包含了實現(xiàn)java.sql.Driver
接口的驅(qū)動類的全限定名。當JVM啟動時,它會查找這個文件,并加載其中指定的驅(qū)動類。
加載服務提供者
在JDBC 4.0及以上版本中,DriverManager
類在初始化時會使用Java的SPI機制自動加載所有在META-INF/services/java.sql.Driver
文件中指定的驅(qū)動類。
這是通過ServiceLoader類實現(xiàn)的,它會查找并加載所有可用的JDBC驅(qū)動實現(xiàn)。
連接管理
當應用程序嘗試通過DriverManager.getConnection()
方法連接數(shù)據(jù)庫時,DriverManager會遍歷所有已加載的驅(qū)動實例,嘗試建立連接。一旦某個驅(qū)動成功建立連接,它就會返回這個連接,并且不會繼續(xù)嘗試其他的驅(qū)動實例。
SpringBoot SPI 機制
Spring Boot 對 SPI 機制進行了擴展,以支持其自動配置和模塊化架構。
Spring Boot 利用 spring.factories
(從 SpringBoot 2.7 起自動配置不推薦使用 /META-INF/spring.factories
文件,而是在/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
)文件,這個文件列出了與自動配置相關的接口及其實現(xiàn)類,Spring Boot 啟動時會加載這些配置。
spring.factories
這個文件里面使用鍵值對的格式列出了多種服務類型及其對應的實現(xiàn)類,常見的服務類型包括:
- org.springframework.boot.autoconfigure.EnableAutoConfiguration:用于自動配置。
- org.springframework.context.ApplicationListener:用于應用事件監(jiān)聽器。
- org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider:用于模板引擎的可用性判斷。
# Application Listeners org.springframework.context.ApplicationListener=\ org.springframework.boot.autoconfigure.BackgroundPreinitializer # Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\ org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
不使用SPI
現(xiàn)在我們先來看下,不使用 SPI 機制,怎么實現(xiàn) bean 的配置。
新建一個項目,將新類MyAppDemo
注入 IOC 容器。
在另一個項目 demo 中,引入 myApp 的依賴,并測試調(diào)用 test 方法。
@SpringBootApplication public class StarterDemoApplication { public static void main(String[] args) { SpringApplication.run(StarterDemoApplication.class, args); } } @SpringBootTest class StarterDemoApplicationTests { @Autowired private ApplicationContext applicationContext; @Test void contextLoads() { MyAppDemo demo = applicationContext.getBean(MyAppDemo.class); demo.test(); } }
測試發(fā)現(xiàn)找不到該 bean 對象
為什么引入的第三方依賴包中的 bean 沒有生效呢?
- 原因是因為,在類上添加
@Component
注解來聲明bean對象時,還需要保證@Component
注解能被Spring的組件掃描到。 - SpringBoot項目中的
@SpringBootApplication
注解,具有包掃描的作用,但是它只會掃描啟動類所在的當前包以及子包。 - 當前包:com.starter.demo, 第三方依賴中提供的包:com.myapp.demo(掃描不到)
所以,有兩種方案可以解決
- @ComponentScan 組件掃描第三方依賴的包路徑;
- @Import 導入(使用@Import導入的類會被Spring加載到IOC容器中)。
@ComponentScan 組件掃描
@SpringBootApplication @ComponentScan(basePackages = {"com.starter","com.myapp"}) public class StarterDemoApplication { public static void main(String[] args) { SpringApplication.run(StarterDemoApplication.class, args); } }
缺點:當需要引入大量的第三方依賴,就需要在啟動類上配置大量要掃描的包,這種方式會很繁瑣。
Import 導入
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Import { /** * {@link Configuration @Configuration}, {@link ImportSelector}, * {@link ImportBeanDefinitionRegistrar}, or regular component classes to import. */ Class<?>[] value(); }
從源碼可以看到,導入形式有以下幾種
- 普通類
- 配置類
- ImportSelector的實現(xiàn)類
- ImportBeanDefinitionRegistrar的實現(xiàn)類
導入普通類
@SpringBootApplication @Import(MyAppDemo.class) public class StarterDemoApplication { public static void main(String[] args) { SpringApplication.run(StarterDemoApplication.class, args); } }
導入配置類
去掉MyAppDemo
類上的@Component注解,新建一個MyAppConfig
配置類。
@Configuration public class MyAppConfig { @Bean public MyAppDemo myAppDemo() { return new MyAppDemo(); } }
在啟動類上導入配置類
@SpringBootApplication @Import(MyAppConfig.class) public class StarterDemoApplication { public static void main(String[] args) { SpringApplication.run(StarterDemoApplication.class, args); } }
導入ImportSelector的實現(xiàn)類
新建 ImportSelector
的實現(xiàn)類
public class MyAppImportSelector implements ImportSelector { public String[] selectImports(AnnotationMetadata importingClassMetadata) { return new String[] {"com.myapp.demo.MyAppDemo"}; } }
在啟動類上導入MyAppImportSelector
@SpringBootApplication @Import(MyAppImportSelector.class) public class StarterDemoApplication { public static void main(String[] args) { SpringApplication.run(StarterDemoApplication.class, args); } }
導入ImportBeanDefinitionRegistrar的實現(xiàn)類
新建 ImportBeanDefinitionRegistrar
的實現(xiàn)類
public class MyAppImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(MyAppDemo.class); registry.registerBeanDefinition("myAppDemoTest", builder.getBeanDefinition()); } }
在啟動類上導入MyAppImportBeanDefinitionRegistrar
@SpringBootApplication @Import(MyAppImportBeanDefinitionRegistrar.class) public class StarterDemoApplication { public static void main(String[] args) { SpringApplication.run(StarterDemoApplication.class, args); } }
模塊裝配
通過@Import
注解,我們可以導入第三方依賴包中的配置類。
但是基于上面的方式,我們在引入第三方依賴時,還要知道第三方依賴中有哪些配置類和哪些Bean對象?相當麻煩!
而第三方依賴自己最清楚自己有哪些配置類、有那些 Bean 對象,它提供一個注解,通過這個注解,外部系統(tǒng)可以引入自己所需要的 Bean 對象。
這個注解一般都以@EnableXxx
開頭,注解中封裝的就是@Import
注解,外部系統(tǒng)在使用時只需要加上@EnableXxxxx
注解即可。
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Import(MyAppConfig.class) public @interface EnableMyApp {}
@SpringBootApplication @EnableMyApp public class StarterDemoApplication { public static void main(String[] args) { SpringApplication.run(StarterDemoApplication.class, args); } }
spring 中的模塊裝配,是在3.1 之后引入了大量的@EnableXXX 注解,來快速整合激活相對應的模塊。
如:
● EnableTransactionManagement :開啟注解事務驅(qū)動
● EnableWebMvc :激活 SpringWebMvc
● EnableAspectJAutoProxy :開啟注解 AOP 編程
● EnableScheduling :開啟調(diào)度功能(定時任務)
模塊裝配的核心原則:自定義注解+@Import 導入組件
使用SPI
即使是采用@EnableXXX
注解,還是覺得麻煩怎么辦?
我想引入第三方依賴后,直接就去使用它,而不是再單獨寫一個什么什么注解。
下面我們來看看基于 Spring 的 SPI 機制怎么去實現(xiàn)。
在在resources
目錄下創(chuàng)建META-INF 目錄,并新建 spring.factories
文件,文件內(nèi)容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.myapp.demo.MyAppConfig
@Configuration public class MyAppConfig { @Bean public MyAppDemo myAppDemo() { return new MyAppDemo(); } }
我們只是引入了第三方依賴包,并沒有手動配置,也沒有寫什么注解啊,就可以通過IOC容器或DI依賴拿到bean對象了,這就是SpringBoot自動配置的強大之處。
自動裝配源碼
從 SpringBoot 核心注解說起
@SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication {}
重點看@EnableAutoConfiguration
,核心中的核心,重點中的重點。
點進去 AutoConfigurationImportSelector
可以看到 AutoConfigurationImportSelector
實現(xiàn)了 DeferredImportSelector
,而DeferredImportSelector
又繼承了ImportSelector
。
AutoConfigurationImportSelector
類中重寫了ImportSelector
接口的selectImports()
方法:
再點進去
可以看到這個getCandidateConfigurations()
方法,就是去獲取META-INF/spring.factories
文件中配置類的集合。
再接著點點點
以上就是SpringBoot自動裝配原理解析的詳細內(nèi)容,更多關于SpringBoot自動裝配的資料請關注腳本之家其它相關文章!
相關文章
Nacos后臺頻繁打印get changedGroupKeys:[]的問題及解決
這篇文章主要介紹了Nacos后臺頻繁打印get changedGroupKeys:[]的問題及解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01詳解IntelliJ IDEA 中如何配置多個jdk版本即(1.7和1.8兩個jdk都可用)
這篇文章主要介紹了詳解IntelliJ IDEA 中如何配置多個jdk版本即(1.7和1.8兩個jdk都可用),非常具有實用價值,需要的朋友可以參考下2017-11-11java數(shù)據(jù)結(jié)構基礎:單,雙向鏈表
這篇文章主要介紹了Java的數(shù)據(jù)解構基礎,希望對廣大的程序愛好者有所幫助,同時祝大家有一個好成績,需要的朋友可以參考下,希望能給你帶來幫助2021-07-07SpringBoot+WebSocket實現(xiàn)IM及時通訊的代碼示例
項目中碰到需要及時通訊的場景,使用springboot集成websocket,即可實現(xiàn)簡單的及時通訊,本文介紹springboot如何集成websocket、IM及時通訊需要哪些模塊、開發(fā)和部署過程中遇到的問題、以及實現(xiàn)小型IM及時通訊的代碼,需要的朋友可以參考下2023-10-10Java實現(xiàn)文件壓縮與解壓的示例[zip格式,gzip格式]
本篇文章主要介紹了Java實現(xiàn)文件壓縮與解壓的示例[zip格式,gzip格式],具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-01-01