<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          SpringBoot原理及源碼剖析

          共 9649字,需瀏覽 20分鐘

           ·

          2021-01-11 22:41

          點擊上方藍(lán)色字體,選擇“標(biāo)星公眾號”

          優(yōu)質(zhì)文章,第一時間送達(dá)

          66套java從入門到精通實戰(zhàn)課程分享

          前言

          現(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)步驟如下:

          1. springboot啟動

          2. @SpringBootApplication起作用

          3. @EnableAutoConfiguration

          4. @AutoConfigurationPackage這個組合注解為
            @Import(AutoConfigurationPackages.Registrar.class)將Registrar類導(dǎo)入到容器中,Registrar將主程序類所在包和所有子包下的組件掃描到springboot容器中

          5. @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 秒


          感謝點贊支持下哈?

          瀏覽 53
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  91在线视频成人 | 中国操逼网址 | 欧美色撸 | 成人毛片18女人毛片 | 中文字幕日日夜夜 |