SpringBoot原理及源碼剖析
點擊上方藍(lán)色字體,選擇“標(biāo)星公眾號”
優(yōu)質(zhì)文章,第一時間送達(dá)
前言
現(xiàn)在spring boot已成為主流的開發(fā)框架之一,相信不少讀者項目都已使用了此框架,所以基于框架的使用就不再贅述,主要聊聊它的思想和原理。
基礎(chǔ)介紹
核心思想
約定由于配置就是springboot的核心思想,又稱按約定編程,本質(zhì)上就死如果大家遵守其制定的規(guī)范,就無需提供額外的配置。舉個栗子來講比如項目中創(chuàng)建了user類,默認(rèn)情況下數(shù)據(jù)庫中就應(yīng)該會存在一張user表,只有偏離這一約定,比如user表改成person表的時候,就需要寫有關(guān)表名的映射配置。
springboot 的優(yōu)點
再談springboot的優(yōu)點之前先談?wù)勚暗膕pring的優(yōu)缺點
spring的優(yōu)點:主要為ioc和aop兩塊
spring的缺點:1. 繁重的配置文件,最初的xml配置可算得上是重量級配置,2.5版本引入了基于注解的組件掃描,3.0的時候引入了基于java的配置,至今現(xiàn)在大多項目都已采用注解配置來代替xml配置。
2. 項目的依賴管理耗時耗力,各個引入的組件依賴還是版兼容性都容易引起很多問題。
而springboot就針對spring的局限性進行改良和優(yōu)化:
起步依賴
本質(zhì)上來講是一個maven對象模型(pom),定義了其他庫的傳遞依賴,這些東西加一起支持某項功能,簡單來講,起步依賴就是將某種功能的坐標(biāo)打包一起,并提供一些默認(rèn)功能。
自動配置
指的是springboot會自動將一些配置類的bean注入到容器里,我們需要使用的地方可以用@Autowired,@Resource注解來使用它。
“自動”的意思為我們想使用某個功能包,我們只需引用,相關(guān)的配置不必要管,springboot會自動注入這些配置bean,我們只需使用即可。
源碼剖析
依賴管理
思考項目中引入pom依賴為何不需要標(biāo)明版本號也可以正常運行。
無非是引入了spring-boot-starter-parent和spring-boot-starter-web核心依賴,所以接下來看下核心依賴
spring-boot-starter-parent
org.springframework.boot
spring-boot-starter-parent<11./artifactId>
2.2.2.RELEASE
?
點擊這個父依賴進去后,發(fā)了里面還有一個父依賴spring-boot-dependencies
org.springframework.boot
spring-boot-dependencies
2.2.2.RELEASE
../../spring-boot-dependencies
繼續(xù)查看spring-boot-dependencies底層源文件,核心代碼如下:
5.15.11
...
8.2.0
8.0.18
2.3.1
2.2.2.RELEASE
2.0.4.RELEASE
1.2.4.RELEASE
5.2.1.RELEASE
Corn-RELEASE
3.0.8.RELEASE
3.28.0
${jakarta-mail.version}
9.0.29
3.0.11.RELEASE
2.0.1attribute.version>
...
可以發(fā)現(xiàn)該文件都常用的依賴組件進行統(tǒng)一的版本號管理,如activemq,spring,tomcat,都與Spring Boot 2.2.2版本一致,這也是為啥引入一些依賴無需填寫版本號的原因。
spring-boot-starter-web
先查看pom源碼,
org.springframework.boot
spring-boot-starter
2.2.2.RELEASE
compile
org.springframework.boot
spring-boot-starter-json
2.2.2.RELEASE
compile
org.springframework.boot
spring-boot-starter-tomcat
2.2.2.RELEASE
compile
org.springframework.boot
spring-boot-starter-validation
2.2.2.RELEASE
compile
tomcat-embed-el
org.apache.tomcat.embed
org.springframework
spring-web
5.2.2.RELEASE
compile
org.springframework
spring-webmvc
5.2.2.RELEASE
compile
可以看到web依賴中已提供web開發(fā)所需的所有底層依賴,所以引入這一個依賴就無需引入其他相關(guān)web開發(fā)依賴。
自動配置
springboot如何做到自動配置的,先看下啟動類上的@SpringBootApplication注解
@SpringBootApplication
public?class?SpringbootDemoApplication?{
public?static?void?main(String[]?args)?{
SpringApplication.run(SpringbootDemoApplication.class,?args);
}
}
@Target({ElementType.TYPE})?//注解的適用范圍,Type表示注解可以在類,接口,枚舉或注解中
@Retention(RetentionPolicy.RUNTIME)?//表示注解的生命周期Runtime運行時
@Documented?//表示注解可以記錄在javadoc中
@Inherited?//表示注解可以被子類繼承
@SpringBootConfiguration?//?標(biāo)明該類為配置類
@EnableAutoConfiguration?//?啟動自動配置功能
@ComponentScan(?//?包掃描器
excludeFilters?=?{@Filter(
type?=?FilterType.CUSTOM,
classes?=?{TypeExcludeFilter.class}
),?@Filter(
type?=?FilterType.CUSTOM,
classes?=?{AutoConfigurationExcludeFilter.class}
)}
)
public?@interface?SpringBootApplication?{
...
}
可以看出,@SpringBootApplicatio是一個組合注解,我們主要關(guān)注里面的三個注解@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan
@SpringBootConfiguration注解
先看下注解源碼
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration?//配置IOC容器
public?@interface?SpringBootConfiguration?{
}
可以看到其中引入了@Configuration注解,即可以被Srping掃描到的配置注解,所以@SpringBootConfiguration與@Configuration作用相同,都是標(biāo)志一個可以被掃描到的配置類,只不過@SpringBootConfiguration被springboot重新封裝命名而已。
@EnableAutoConfiguration注解
@EnableAutoConfiguration注解表示開啟自動配置功能,這是一個核心注解,先看下源碼

可以發(fā)現(xiàn)它也是一個組合注解,借助@Import來收集并注冊特定場景的相關(guān)bean并加載到ioc容器中。
繼續(xù)看下@AutoConfigurationPackage源碼
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})?//?導(dǎo)入Registrar中注冊的組件
public?@interface?AutoConfigurationPackage?{
}
可以看到借助@Import注解引入Registrar類到容器中,查看下Registrar這個類的源碼。
在Registrar類下的registerBeanDefinitions()方法,當(dāng)調(diào)式的時候可以看到這里獲取到注解的包名,說明@AutoConfigurationPackage作用就是將主程序類所在包和所有子包下的組件掃描到spring容器中。
這也是為啥要講啟動類放在最外層的包下的原因。
繼續(xù)看下@Import({AutoConfigurationImportSelector.class})導(dǎo)入的這個AutoConfigurationImportSelector類。
看下里面的loadMetadata方法
繼續(xù)深入到里面的getCandidateConfigurations下的loadFactoryNames方法
繼續(xù)觀察loadFactory方法
??public?static?List?loadFactoryNames(Class>?factoryClass,?@Nullable
????????????ClassLoader?classLoader)?{
??//獲取出入的鍵
????????String?factoryClassName?=?factoryClass.getName();
????????return
????????????????(List)loadSpringFactories(classLoader).getOrDefault(factoryClassName,
????????????????????????Collections.emptyList());
????}
????private?static?Map>?loadSpringFactories(@Nullable
?????????????????????????????????????????????????????????????????????????ClassLoader?classLoader)?{
????????MultiValueMap?result?=
????????????????(MultiValueMap)cache.get(classLoader);
????????if?(result?!=?null)?{
????????????return?result;
????????}?else?{
????????????try?{
//如果類加載器不為null則加載類路徑下的spring.factories,將其中設(shè)置的配置類全路徑信息封裝為類Enumeration對象
????????????????Enumeration?urls?=?classLoader?!=?null??
????????????????????????classLoader.getResources("META-INF/spring.factories")?:
????????????????????????ClassLoader.getSystemResources("META-INF/spring.factories");
????????????????LinkedMultiValueMap?result?=?new?LinkedMultiValueMap();
????????????????//循環(huán)Enumeration類對象。根據(jù)相應(yīng)的節(jié)點信息生成Properties對象,通過傳入的鍵獲取值,在將值切分一個個字符串轉(zhuǎn)為array,方法result集合中
????????????????while(urls.hasMoreElements())?{
????????????????????URL?url?=?(URL)urls.nextElement();
????????????????????UrlResource?resource?=?new?UrlResource(url);
????????????????????Properties?properties?=
????????????????????????????PropertiesLoaderUtils.loadProperties(resource);
????????????????????Iterator?var6?=?properties.entrySet().iterator();
????????????????????while(var6.hasNext())?{
????????????????????????Entry,??>?entry?=?(Entry)var6.next();
????????????????????????String?factoryClassName?=
????????????????????????????????((String)entry.getKey()).trim();
????????????????????????String[]?var9?=
????????????????????????????????StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
????????????????????????int?var10?=?var9.length;
????????????????????????for(int?var11?=?0;?var11?????????????????????????????String?factoryName?=?var9[var11];
????????????????????????????result.add(factoryClassName,?factoryName.trim());
????????????????????????}
????????????????????}
????????????????}
????????????????cache.put(classLoader,?result);
????????????????return?result;
最終發(fā)現(xiàn)會去讀取spring.factories這個文件
public?final?class?SpringFactoriesLoader?{
public?static?final?String?FACTORIES_RESOURCE_LOCATION?=?"META-
INF/spring.factories";
}
而這個文件其實在

@EnableAutoConfiguration就是從classpath中搜索META-INF/spring.factories配置文件,并將其中的org.springframework.boot.autoconfigure.EnableutoConfiguration對應(yīng)的配置項通過反射(Java
Refletion)實例化為對應(yīng)的標(biāo)注@Configuration的JavaConfig形式的配置類,并加載到IOC容器中。
所以總結(jié)一下,springboot底層自動配置實現(xiàn)步驟如下:
springboot啟動
@SpringBootApplication起作用
@EnableAutoConfiguration
@AutoConfigurationPackage這個組合注解為
@Import(AutoConfigurationPackages.Registrar.class)將Registrar類導(dǎo)入到容器中,Registrar將主程序類所在包和所有子包下的組件掃描到springboot容器中@Import(AutoConfigurationImportSelector.class)它通過將AutoConfigurationImportSelector類導(dǎo)入到容器中,AutoConfigurationImportSelector作用是通過selectImports方法的執(zhí)行過程中,使用內(nèi)部類SpringFactoriesLoader,查找classpath上所有jar包中的META-INF/spring.factories進行加載,實現(xiàn)將配置信息交給SpringFactory進行一系列容器創(chuàng)建的過程中。
@ComponentScan注解
@ComponentScan注解具體掃描的包路徑是由主程序文件所在的位置決定的,在掃描過程中通過上面介紹的@AutoConfigurationPackage進行解析,從而獲取到項目主程序啟動類所在的包的具體位置。
總的來說@SpringBootApplication的功能就是三個注解的組合使用。
|-?@SpringBootConfiguration
|-?@Configuration?//通過javaConfig來添加組件到IOC中
|-?@EnableAutoConfiguration
|-?@AutoConfigurationPackage?//自動配置包,與@ComponentScan掃描到的添加到IOC
|-?@Import(AutoConfigurationImportSelector.class)?//到META-INF/spring.factories中定義的bean添加到IOC容器中
|-?@ComponentScan?//包掃描
啟動流程
先觀察下啟動類的main方法
@SpringBootApplication
public?class?SpringbootDemoApplication?{
public?static?void?main(String[]?args)?{
SpringApplication.run(SpringbootDemoApplication.class,?args);
}
}
追進去
public?static?ConfigurableApplicationContext?run(Class>?primarySource,
String...?args)?{
return?run(new?Class[]{primarySource},?args);
}
public?static?ConfigurableApplicationContext?run(Class>[]?primarySources,
String[]?args)?{
return?(new?SpringApplication(primarySources)).run(args);
}
發(fā)現(xiàn)SpringApplication.run()方法主要分兩步,SpringApplication實例的初始化和調(diào)用run啟動項目。
查看SpringApplication的實例化
??public?SpringApplication(ResourceLoader?resourceLoader,?Class...
????????????primarySources)?{
????????this.sources?=?new?LinkedHashSet();
????????this.bannerMode?=?Mode.CONSOLE;
????????this.logStartupInfo?=?true;
????????this.addCommandLineProperties?=?true;
????????this.addConversionService?=?true;
????????this.headless?=?true;
????????this.registerShutdownHook?=?true;
????????this.additionalProfiles?=?new?HashSet();
????????this.isCustomEnvironment?=?false;
????????this.resourceLoader?=?resourceLoader;
????????Assert.notNull(primarySources,?"PrimarySources?must?not?be?null");
??//把項目起到呢類.class設(shè)置為屬性存儲起來
????????this.primarySources?=?new?LinkedHashSet(Arrays.asList(primarySources));
??//判斷當(dāng)前webApplicationType應(yīng)用的類型
????????this.webApplicationType?=?WebApplicationType.deduceFromClasspath();
??//?設(shè)置初始化器(Initializer),最后調(diào)用這些初始化器
????????this.setInitializers(this.getSpringFactoriesInstances(
????????????????ApplicationContextInitializer.class));
??//?設(shè)置監(jiān)聽器(Listener)
????????this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class))
????????;
??//?用于推斷并設(shè)置項目的main()方法啟動的主程序啟動類
????????this.mainApplicationClass?=?this.deduceMainApplicationClass();

查看run方法
????public?ConfigurableApplicationContext?run(String...?args)?{
????????StopWatch?stopWatch?=?new?StopWatch();
????????stopWatch.start();
????????ConfigurableApplicationContext?context?=?null;
????????Collection?exceptionReporters?=?new
????????????????ArrayList();
????????this.configureHeadlessProperty();
??//?1.獲取并監(jiān)聽容器
????????SpringApplicationRunListeners?listeners?=?this.getRunListeners(args);
????????listeners.starting();
????????Collection?exceptionReporters;
????????try?{
????????????ApplicationArguments?applicationArguments?=
????????????????????new?DefaultApplicationArguments(args);
??//?2.根據(jù)SpringApplicationRunListeners以及參數(shù)來準(zhǔn)備環(huán)境
????????????ConfigurableEnvironment?environment?=
????????????????????this.prepareEnvironment(listeners,?applicationArguments);
????????????this.configureIgnoreBeanInfo(environment);
??//?準(zhǔn)備Banner打印器?-?就是啟動Spring?Boot時打印在console上的ASCII字體
????????????Banner?printedBanner?=?this.printBanner(environment);
??//?3.?創(chuàng)建Spring容器
????????????context?=?this.createApplicationContext();
????????????exceptionReporters?=
????????????????????this.getSpringFactoriesInstances(SpringBootExceptionReporter.class,
????????????????????????????new?Class[]{ConfigurableApplicationContext.class},?new?Object[]{context});
??//?4.?Spring容器前置處理
????????????this.prepareContext(context,?environment,?listeners,
????????????????????applicationArguments,?printedBanner);
??//?5?刷新容器
????????????this.refreshContext(context);
??//?6.Spring容器后置處理
????????????this.afterRefresh(context,?applicationArguments);
????????????stopWatch.stop();
????????????if(this.logStartupInfo)?{
????????????????(new?StartupInfoLogger(this.mainApplicationClass))
????????????????????????.logStarted(this.getApplicationLog(),?stopWatch);
????????????}
??//?7.發(fā)出結(jié)束執(zhí)行的事件
????????????listeners.started(context);
??//?返回容器
????????????this.callRunners(context,?applicationArguments);
????????}?catch?(Throwable?var10)?{
????????????this.handleRunFailure(context,?var10,?exceptionReporters,?listeners);
????????????throw?new?IllegalStateException(var10);
????????}
????????try?{
????????????listeners.running(context);
????????????return?context;
????????}?catch?(Throwable?var9)?{
????????????this.handleRunFailure(context,?var9,?exceptionReporters,
????????????????????(SpringApplicationRunListeners)null);
????????????throw?new?IllegalStateException(var9);
????????}
????}


下面有一個詳細(xì)的啟動流程圖
總結(jié)
本文介紹了springboot容器的優(yōu)點以及版本控制和自動裝配的思想和原理。
版權(quán)聲明:本文為博主原創(chuàng)文章,遵循 CC 4.0 BY-SA 版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接和本聲明。
本文鏈接:
https://blog.csdn.net/z591045/article/details/112190401
粉絲福利:Java從入門到入土學(xué)習(xí)路線圖
??????

??長按上方微信二維碼?2 秒
感謝點贊支持下哈?
