Springboot Spring原理2深度解析
提示:文章寫(xiě)完后,目錄可以自動(dòng)生成,如何生成可參考右邊的幫助文檔
前言
3. Spring Boot?動(dòng)配置
自動(dòng)裝配是屬性的自動(dòng)裝配,比如剛剛說(shuō)的,set注入ben,構(gòu)造方法注入bean
自動(dòng)配置呢
SpringBoot的?動(dòng)配置就是當(dāng)Spring容器啟動(dòng)后, ?些配置類(lèi), bean對(duì)象等就自動(dòng)存入到了IoC容器中,不需要我們手動(dòng)去聲明, 從而簡(jiǎn)化了開(kāi)發(fā), 省去了繁瑣的配置操作.
SpringBoot?動(dòng)配置, 就是指SpringBoot是如何將依賴(lài)jar包中的配置類(lèi)以及Bean加載到Spring IoC容
器中的.
主要分以下兩個(gè)??:
- Spring 是如何把對(duì)象加載到SpringIoC容器中的
- SpringBoot 是如何實(shí)現(xiàn)的
3.1 Spring 加載Bean
為什么加入依賴(lài),一些bean,注解(@Mapper),就自動(dòng)配置了

比如我們現(xiàn)在加入了一個(gè)包autoconfig,假設(shè)它是第三方的包
如果對(duì)這個(gè)打包,通過(guò)pom引入,那么就會(huì)出現(xiàn)在外部庫(kù)中

所以這個(gè)我們自定義的第三方的包,放在這里和放在外部庫(kù)是一樣的效果
為什么放在這里效果和打包加依賴(lài)放在外部庫(kù)是一樣的效果呢
因?yàn)閱?dòng)類(lèi)只能加載它所在目錄下的所有bean,不能加載它所在目錄下的其他包下的bean的
我們的包autoconfig就是放在外面的
@Component
public class CkConfig {
public void use(){
System.out.println("CkConfig");
}
}
然后我們?cè)陧?xiàng)目中就想使用這個(gè)包
@SpringBootApplication
public class SpringPrinciple2Application {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringPrinciple2Application.class, args);
CkConfig bean = context.getBean(CkConfig.class);
bean.use();
}
}執(zhí)行

直接報(bào)錯(cuò)了,說(shuō)找不到這個(gè)bean
因?yàn)閟pring掃描的路徑,默認(rèn)是啟動(dòng)類(lèi)所在的目錄
但是這個(gè)包在外面,所有加了注解Component也沒(méi)有用
第一種方式是使用注解ComponentScan,這個(gè)就是指定要掃描的路徑了
@SpringBootApplication
@ComponentScan("com.ck.demo.autoconfig")
public class SpringPrinciple2Application {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringPrinciple2Application.class, args);
CkConfig bean = context.getBean(CkConfig.class);
bean.use();
}
}

這樣就成功了
就是手動(dòng)指定spring的掃描路徑,但是缺點(diǎn)就是所有的外部的bean都要手動(dòng)指定,比如redis的,mybatis的包都要手動(dòng)加入
而且加了ComponentScan的話(huà),原來(lái)的啟動(dòng)類(lèi)下的路徑就不會(huì)再掃描了,意思就是原來(lái)的config下的bean,就是package com.ck.demo.springprinciple2;包下的都不會(huì)再掃描了
ComponentScan下可以寫(xiě)數(shù)組的
@SpringBootApplication
@ComponentScan({"com.ck.demo.autoconfig","com.ck.demo.springprinciple2"})
public class SpringPrinciple2Application {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringPrinciple2Application.class, args);
CkConfig bean = context.getBean(CkConfig.class);
bean.use();
}
}所以這樣之前的,和新加入的都可以生效了
@Import(CkConfig.class)
@SpringBootApplication
public class SpringPrinciple2Application {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringPrinciple2Application.class, args);
CkConfig bean = context.getBean(CkConfig.class);
bean.use();
}
}還可以通過(guò)這個(gè)方式,使用注解Import,來(lái)導(dǎo)入class
但是spring的話(huà),這兩種方式都沒(méi)有采用

@Import({CkConfig.class, CkConfig2.class})
也是可以添加多個(gè)的
public class MyImport implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.ck.demo.autoconfig.CkConfig","com.ck.demo.autoconfig.CkConfig2"};
}
}
我們?cè)诘谌絘utoconfig下寫(xiě)一個(gè)這個(gè)類(lèi),表示Import要掃描的路徑
@Import(MyImport.class)
@SpringBootApplication
public class SpringPrinciple2Application {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringPrinciple2Application.class, args);
CkConfig bean = context.getBean(CkConfig.class);
bean.use();
}
}這樣就可以了,這個(gè)就是第三方提供給你bean的路徑
但是還是麻煩,繼續(xù)升級(jí),第三方提供注解就好了

@Target(ElementType.TYPE)//這個(gè)注解可以加在什么地方上
@Retention(RetentionPolicy.RUNTIME)//生命周期
@Import(MyImport.class)//注解的作用
public @interface EnableCkConfig {
}
這個(gè)注解定義在autoconfig下,也是第三方提供的
@EnableCkConfig
@SpringBootApplication
public class SpringPrinciple2Application {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringPrinciple2Application.class, args);
CkConfig bean = context.getBean(CkConfig.class);
bean.use();
CkConfig2 bean2 = context.getBean(CkConfig2.class);
bean2.use();
}
}這樣就可以了

所以第三方只需要提供注解就可以了
但是導(dǎo)入mybatis的依賴(lài)的時(shí)候,就那些bean也不用在啟動(dòng)類(lèi)上加入注解啊
所以還有其他方式,連注解也不需要加
就是在resource目錄下創(chuàng)建一個(gè)文件,放入"com.ck.demo.autoconfig.CkConfig","com.ck.demo.autoconfig.CkConfig2"這些東西,就會(huì)自動(dòng)創(chuàng)建和管理了
這個(gè)文件就是META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
放入
com.ck.demo.autoconfig.CkConfig com.ck.demo.autoconfig.CkConfig2

這個(gè)就是告訴spring,我要管理哪些對(duì)象
這樣的話(huà),自定義注解EnableCkConfig也不需要了,也可以獲取第三方的bean了

3.2 源碼分析
但是spring用的是什么方式呢
我們?nèi)タ磫?dòng)類(lèi)@SpringBootApplication
因?yàn)檫@個(gè)注解,所以它是啟動(dòng)類(lèi)
還有ApplicationContext context = SpringApplication.run(SpringPrinciple2Application.class, args);這個(gè)來(lái)啟動(dòng)
點(diǎn)進(jìn)去注解SpringBootApplication

@Target({ElementType.TYPE})表示主要要加在什么上
@Retention(RetentionPolicy.RUNTIME)生命周期
@Documented文檔相關(guān)
@Inherited繼承相關(guān),是源注解,不看了
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
這三個(gè)最重要
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)因?yàn)檫@個(gè),所以會(huì)掃碼啟動(dòng)類(lèi)下的路徑
加了這個(gè),默認(rèn)掃描ComponentScan注解所在的路徑,也就是SpringBootApplication注解所在的路徑
SpringBootConfiguration注解點(diǎn)進(jìn)去

Indexed注解是一個(gè)優(yōu)化的注解,不重要
重點(diǎn)是Configuration,五大注解之一,所以SpringBootConfiguration注解就是Configuration分裝了一下
所以注解SpringBootConfiguration表示這是一個(gè)配置類(lèi),這個(gè)會(huì)被spring給識(shí)別到
所以重點(diǎn)就是注解EnableAutoConfiguration了
3.3 注解@EnableAutoConfiguration

@Import({AutoConfigurationImportSelector.class})
AutoConfigurationImportSelector這個(gè)實(shí)現(xiàn)了DeferredImportSelector,DeferredImportSelector實(shí)現(xiàn)了ImportSelector

實(shí)現(xiàn)了方法selectImports
返回了要管理的bean
AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
主要是從這個(gè)getAutoConfigurationEntry來(lái)的
點(diǎn)進(jìn)去

AnnotationMetadata是注解的原信息
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
這個(gè)方法點(diǎn)進(jìn)去

這個(gè)就是加載配置了
Assert是斷言
String aa=null;
Assert.notNull(aa,"aa不能為空");
比如這個(gè)意思就是斷言aa不能為空
為空的話(huà),就報(bào)"aa不能為空"的錯(cuò)誤


這個(gè)斷言的就是沒(méi)有配置信息在META-INF/spring/。。。。。
所以
ImportCandidates importCandidates = ImportCandidates.load(this.autoConfigurationAnnotation, this.getBeanClassLoader());
這個(gè)就是讀取META-INF/spring/這個(gè)文件的配置信息的,沒(méi)有的話(huà),就會(huì)包一個(gè)異常

但是我們平時(shí)并沒(méi)有這個(gè)文件啊,為什么沒(méi)有報(bào)錯(cuò)
ctrl+N搜索org.springframework.boot.autoconfigure.AutoConfiguration.imports

點(diǎn)擊第一個(gè)

找到這里,發(fā)現(xiàn)這里就有那個(gè)META-INF/spring/的文件

這里面放的就是spring要加載的bean
說(shuō)明spring它自己就寫(xiě)了一個(gè)這種文件了
所以要管理我們第三方的bean的話(huà),也是寫(xiě)一個(gè)這個(gè)路徑就可以了,spring就會(huì)認(rèn)識(shí)了,spring是根據(jù)路徑來(lái)掃描的,都是這個(gè)路徑的就都可以?huà)呙璧搅?/p>

比如這個(gè)里面就有事務(wù)
spring就會(huì)管理DataSourceTransactionManagerAutoConfiguration這個(gè)bean了
ctrl+n搜索

然后DataSourceTransactionManagerAutoConfiguration這個(gè)里面還有bean
這樣就都生效了

比如redis的,搜索RedisAutoConfiguration這個(gè)bean

然后RedisAutoConfiguration這個(gè)bean里面就有RedisTemplate這個(gè)bean,所以RedisTemplate這個(gè)bean也是自動(dòng)注入的

除了@Bean之外,還有其他注解
比如注解ConditionalOnMissingBean
表示在不同的條件下才注入這個(gè)bean
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
return new StringRedisTemplate(redisConnectionFactory);
}
這個(gè)意思就是,如果你沒(méi)有聲明stringRedisTemplate這個(gè)Bean,我就聲明stringRedisTemplate這個(gè)bean
@ConditionalOnMissingBean(
name = {"redisTemplate"}
)
意思就是你要是聲明了redisTemplate這個(gè)bean,我就不聲明了,你沒(méi)有聲明,我就聲明
@ConditionalOnClass({RedisOperations.class})
看有沒(méi)有RedisOperations這個(gè)類(lèi),沒(méi)有的話(huà),這個(gè)類(lèi)下的所有bean都不,包括自己這個(gè)類(lèi)的bean都不生效

這個(gè)RedisOperations就在import org.springframework.data.redis.core.RedisOperations;上面擺著
這個(gè)RedisOperations類(lèi)就是我們要引入的依賴(lài)?yán)锩娴?,換個(gè)意思就是沒(méi)有引入依賴(lài),就沒(méi)有RedisOperations這個(gè)類(lèi),就不能加載Bean了
所以加了依賴(lài),外部庫(kù)就有org.springframework.data.redis.core,這個(gè)類(lèi)RedisOperations就有了,對(duì)應(yīng)的RedisAutoConfiguration和它下面的方法這些Bean就有了

不加依賴(lài)所以就不行了

繼續(xù)回到這里,就是獲取META-INF/spring/配置的這里
就是
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
這里configurations就是META-INF/spring/文件里面的配置,包括我們自己寫(xiě)在META-INF/spring/里面的
this.checkExcludedClasses(configurations, exclusions);
這個(gè)是檢查排除,根據(jù)依賴(lài)信息來(lái)檢查排除
configurations.removeAll(exclusions);
這個(gè)就是刪除沒(méi)有引入依賴(lài)的配置
最后返回
所以getAutoConfigurationEntry返回的就是要求spring給我們管理的配置,對(duì)象,bean

所以注解@Import({AutoConfigurationImportSelector.class}),返回的就是要求spring給我們管理的配置,對(duì)象,bean
現(xiàn)在我們看AutoConfigurationPackage這個(gè)注解

它又import了

import了Register,這又是一個(gè)我們注入包的方式
Register加載bean
public class MyRegister implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(CkConfig.class);
// beanDefinition.setScope("xxxx");//表示這個(gè)bean是單例的還是多例的
registry.registerBeanDefinition("ckConfig",beanDefinition);//注入bean,第一個(gè)參數(shù)是bean的名稱(chēng)
}
}
我們?cè)赼utoconfig下定義MyRegister
@SpringBootApplication
@Import(MyRegister.class)
public class SpringPrinciple2Application {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringPrinciple2Application.class, args);
CkConfig bean = context.getBean(CkConfig.class);
bean.use();
// CkConfig2 bean2 = context.getBean(CkConfig2.class);
// bean2.use();
}
}
這又是一種方式導(dǎo)入在這里插入代碼片
繼續(xù)分析

這里定義了一個(gè)Register的類(lèi)
定義了registerBeanDefinitions這個(gè)方法
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, (String[])(new PackageImports(metadata)).getPackageNames().toArray(new String[0]));
}
(String[])(new PackageImports(metadata)).getPackageNames().toArray(new String[0])這個(gè)得到的就是我們啟動(dòng)類(lèi)所在路徑com.ck.demo.springprinciple2
AutoConfigurationPackages.register這個(gè)就是注入第三方的注解,比如@Mapper
為什么spring能夠管理第三方的注解

第三方就會(huì)寫(xiě)那些register
然后給我們注入
然后BlogInfoMapper使用了注解Mapper
然后注解Mapper就會(huì)拿到BlogInfoMapper的路徑

比如我們搜索這個(gè)MybatisPlus的這個(gè)類(lèi)

找到這里,這里實(shí)現(xiàn)了Register

這里調(diào)用的registerBeanDefinitions,就是我們剛剛看到的啟動(dòng)類(lèi)原碼里面定義的方法

就是這個(gè)方法

這里會(huì)得到啟動(dòng)類(lèi)的路徑
然后在這個(gè)啟動(dòng)類(lèi)里面掃描得到Mapper注解,掃描誰(shuí)加了Mapper注解
然后調(diào)用方法registerBeanDefinitions加到spring就可以管理了
這個(gè)就是MybatisPlus第三方注解加入Bean的方式
就是調(diào)用第三方的注解,但是第三方的注解不是立馬加入Bean,而是,先獲取啟動(dòng)類(lèi)路徑,根據(jù)啟動(dòng)類(lèi)路徑掃描誰(shuí)
調(diào)用了Mapper注解,誰(shuí)調(diào)用個(gè),就調(diào)用方法registerBeanDefinitions加到spring就可以管理了
public class MyRegister implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(CkConfig.class);
// beanDefinition.setScope("xxxx");//表示這個(gè)bean是單例的還是多例的
registry.registerBeanDefinition("ckConfig",beanDefinition);//注入bean,第一個(gè)參數(shù)是bean的名稱(chēng)
}
}
比如這個(gè),xx使用了注解Mapper(會(huì)自動(dòng)調(diào)用相應(yīng)方法),—》把xx給registry.registerBeanDefinition,然后MyRegister 由于引入了依賴(lài),而且implements ImportBeanDefinitionRegistrar,就自動(dòng)會(huì)被spring給@Import(MyRegister.class)
3.4 總結(jié)

AutoConfigurationPackage用的是Register的方式來(lái)注入bean,主要是用來(lái)掃描第三方注解,比如@Mapper,
第三方實(shí)現(xiàn)ImportBeanDefinitionRegistrar,然后就可以注入了
當(dāng)SpringBoot程序啟動(dòng)時(shí), 會(huì)加載配置?件當(dāng)中所定義的配置類(lèi), 通過(guò) @Import 注解將這些配置類(lèi)全
部加載到Spring的IOC容器中, 交給IOC容器管理.
- Bean的作?域共分為6種: singleton, prototype, request, session, application和websocket.
- Bean的?命周期共分為5?部分: 實(shí)例化, 屬性復(fù)制, 初始化, 使?和銷(xiāo)毀
- SpringBoot的?動(dòng)配置原理源碼?是 @SpringBootApplication 注解, 這個(gè)注解封裝了3個(gè)注解?@SpringBootConfiguration 標(biāo)志當(dāng)前類(lèi)為配置類(lèi)
? @ComponentScan 進(jìn)?包掃描(默認(rèn)掃描的是啟動(dòng)類(lèi)所在的當(dāng)前包及其?包)
? @EnableAutoConfiguration
? @Import 注解 : 讀取當(dāng)前項(xiàng)?下所有依賴(lài)jar包中 META-INF/spring.factories ,
METAINF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 兩個(gè)?件??定義的配置類(lèi)(配置類(lèi)中定義了 @Bean 注解標(biāo)識(shí)的?法)
? @AutoConfigurationPackage : 把啟動(dòng)類(lèi)所在的包下?所有的組件都注?到 Spring容器中
總結(jié)
到此這篇關(guān)于Springboot Spring原理2的文章就介紹到這了,更多相關(guān)Springboot Spring原理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java Spring Security認(rèn)證與授權(quán)及注銷(xiāo)和權(quán)限控制篇綜合解析
Spring Security 是 Spring 家族中的一個(gè)安全管理框架,實(shí)際上,在 Spring Boot 出現(xiàn)之前,Spring Security 就已經(jīng)發(fā)展了多年了,但是使用的并不多,安全管理這個(gè)領(lǐng)域,一直是 Shiro 的天下2021-10-10
springboot 跨域配置類(lèi)及跨域請(qǐng)求配置
這篇文章主要介紹了springboot 跨域配置類(lèi)及跨域請(qǐng)求配置,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-01-01
在Spring框架下配置Quartz集群的詳細(xì)步驟(MySQL數(shù)據(jù)源)
Quartz 是一個(gè)功能強(qiáng)大的調(diào)度庫(kù),可以在 Java 應(yīng)用中用于執(zhí)行定時(shí)任務(wù),本文將介紹如何在 Spring 框架下配置 Quartz 集群,并使用 MySQL 作為數(shù)據(jù)源來(lái)存儲(chǔ)調(diào)度信息,文中有詳細(xì)的代碼供大家參考,需要的朋友可以參考下2025-01-01
Java截取PDF內(nèi)容為圖片的實(shí)現(xiàn)代碼
本文主要介紹了java實(shí)現(xiàn)截取PDF指定頁(yè)并進(jìn)行圖片格式轉(zhuǎn)換功能的技術(shù)要點(diǎn),通過(guò)實(shí)例代碼,文章詳細(xì)地介紹了如何使用java語(yǔ)言來(lái)實(shí)現(xiàn)PDF指定頁(yè)的截取和圖片格式轉(zhuǎn)換,需要的朋友可以參考下2025-10-10
Spring Cloud 專(zhuān)題之Sleuth 服務(wù)跟蹤實(shí)現(xiàn)方法
這篇文章主要介紹了Spring Cloud 專(zhuān)題之Sleuth 服務(wù)跟蹤,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-08-08
IDEA集成git和使用步驟的實(shí)現(xiàn)方法
這篇文章主要介紹了IDEA集成git和使用步驟的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03
Java 判斷一個(gè)時(shí)間是否在另一個(gè)時(shí)間段內(nèi)
這篇文章主要介紹了Java 判斷一個(gè)時(shí)間是否在另一個(gè)時(shí)間段內(nèi)的相關(guān)資料,需要的朋友可以參考下2016-10-10

