<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啟動流程源碼解析

          共 22651字,需瀏覽 46分鐘

           ·

          2021-05-14 12:27

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

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

            作者 |  Lucky帥小武

          來源 |  urlify.cn/A7rErq

          前言

          SpringBoot本質(zhì)上沒有技術(shù)革新,而是在Spring框架的基礎(chǔ)之上簡化了系統(tǒng)的配置,核心功能是自動裝配和starter組件

          一、SpringBoot核心功能

          1、獨(dú)立運(yùn)行程序(SpringBoot程序可以不依賴其他容器獨(dú)立運(yùn)行)

          2、內(nèi)嵌Servlet容器(內(nèi)嵌Servlet容器,不依賴其他Web容器)

          3、提供starter組件簡化maven配置(只需要依賴一個(gè)starter組件就可以自動依賴相關(guān)jar包,簡化maven配置)

          4、自動裝配配置(為大多數(shù)應(yīng)用場景提供了默認(rèn)配置,大幅度減少了應(yīng)用配置)

          二、SpringBoot常用注解

          注解修飾用途案例
          @ComponentScan自動掃描,修飾某個(gè)類則Spring會自動掃描當(dāng)前類所在包路徑下所以被@Component修飾的類,也可以通過basePackages自定義掃描包的根目錄@ComponentScan(basePackages="com.test.lucky")
          @Component聲明當(dāng)前類表示當(dāng)前類為一個(gè)組件,Spring容器會很自動掃描被@Component修飾的類加載bean到Spring容器中@Component(value="beanName")
          @Controller本質(zhì)是一個(gè)Component,聲明當(dāng)前類表示當(dāng)前類是一個(gè)控制器類,分發(fā)處理器會掃描被@Controller注解修飾的類的方法@Controller(value="testController")
          @RequestMapping方法聲明方法表示請求URL映射的方法@RequestMapping(value="/test")
          @ResponseBody方法/類將controller的方法返回的對象通過適當(dāng)?shù)霓D(zhuǎn)換器轉(zhuǎn)換為指定的格式之后,寫入到response對象的body區(qū),通常用來返回JSON數(shù)據(jù)或者是XML數(shù)據(jù)@ResponseBody
          @RestController等同于@Controller + @ResponseBody組合,表示當(dāng)前類的所有方法都自動被@ResponseBody注解修飾@RestController(value="testController")
          @Service本質(zhì)是一個(gè)Component, 聲明當(dāng)前類是Service層的bean@Service(value="testService")
          @Repository本質(zhì)是一個(gè)Component, 聲明當(dāng)前類是Dao層的bean@Repository(value="testDao")
          @Configuration本質(zhì)是一個(gè)Component, 表面當(dāng)前類是一個(gè)配置類,會生成一個(gè)代理對象,表示當(dāng)前類是@Bean定義的源類@Configuration
          @Bean方法相當(dāng)于<bean>標(biāo)簽,被@Bean修飾的方法返回對象會被加載到Spring容器中@Bean(name="beanName")
          @Import用于導(dǎo)入第三方包中的bean,導(dǎo)入的bean自動加載到Spring容器中@Import(value=Test.class)
          @Value方法/屬性將常量、配置中的值、其他bean注入到變量中@Value("${properteis.test}")

          三、SpringBoot啟動流程解析

          SpringBoot的功能是基于Spring框架,所以啟動時(shí)的核心步驟自然少不了Spring容器的初始化以及Spring容器的啟動過程,另外SpringBoot還提供了自動裝配配置功能。

          1、SpringBootApplication.run()方法

          SpringBoot程序啟動類案例如下:

          @SpringBootApplication
          public class BootStrap {

              public static void main(String[] args){
                  ApplicationContext applicationContext = SpringApplication.run(BootStrap.class);
              }
          }

          SpringBoot程序啟動入口,代碼比較簡潔,除了被@SpringBootApplication注解修飾之外,main豐富直接調(diào)用SpringBootApplication類的靜態(tài)方法run方法即可,最終會是調(diào)用了SpringBootApplication的實(shí)例方法run方法,核心邏輯如下:

          public ConfigurableApplicationContext run(String... args) {
                  ConfigurableApplicationContext context = null;
                  /** 1.通過反射創(chuàng)建ApplicationContext對象 */
                  context = createApplicationContext();
                  /** 2.準(zhǔn)備ApplicationContext上下文環(huán)境 */
                  prepareContext(context, environment, listeners, applicationArguments, printedBanner);
                  /** 3.刷新ApplicationContext */
                  refreshContext(context);
                  /** 4.刷新ApplicationContext后置處理*/
                  afterRefresh(context, applicationArguments);
                  return context;
              }

          可以看出SpringBoot程序啟動的核心邏輯實(shí)際就兩步,1、創(chuàng)建IOC容器ApplicationContext實(shí)例;2、啟動IOC容器。

          而SpringBoot的其他核心功能很顯然就是通過@SpringBootApplication注解來實(shí)現(xiàn),這個(gè)注解才是SpringBoot的核心實(shí)現(xiàn)

          2、@SpringBootApplication注解

          @SpringBootApplication注解定義如下:

          @Target(ElementType.TYPE)
          @Retention(RetentionPolicy.RUNTIME)
          @Documented
          @Inherited
          @SpringBootConfiguration
          @EnableAutoConfiguration
          @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
                  @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
          public @interface SpringBootApplication

          從定義可以看出@SpringBootApplication是一個(gè)組合注解,內(nèi)部包含了@SpringBootConfiguration、@EnableAutoConfiguration和@ComponentScan三個(gè)注解

          2.1、@SpringBootConfiguration

          @SpringBootConfiguration注解定義如下:

          1 @Target(ElementType.TYPE)
          2 @Retention(RetentionPolicy.RUNTIME)
          3 @Documented
          4 @Configuration
          5 public @interface SpringBootConfiguration

          可以看出@SpringBootConfiguration本質(zhì)上就是一個(gè)@Configuration注解,標(biāo)注當(dāng)前類是一個(gè)配置類

          2.2、@ComponentScan

          @ComponentScan注解定義如下:

          @Retention(RetentionPolicy.RUNTIME)
          @Target(ElementType.TYPE)
          @Documented
          @Repeatable(ComponentScans.class)
          public @interface ComponentScan  

          @ComponentScan注解表示自動掃描,會掃描當(dāng)前類包路徑下所有被@Component注解修飾的bean

          2.3、@EnableAutoConfiguration

          @EnableAutoConfiguration注解定義如下:

          @Target(ElementType.TYPE)
          @Retention(RetentionPolicy.RUNTIME)
          @Documented
          @Inherited
          @AutoConfigurationPackage
          @Import(AutoConfigurationImportSelector.class)
          public @interface EnableAutoConfiguration

          可以看出@EnableAutoConfiguration注解也是一個(gè)組合注解,包含了@AutoConfigurationPackage和@Import注解,再看@AutoConfigurationPackage注解的定義:

          1 @Target(ElementType.TYPE)
          2 @Retention(RetentionPolicy.RUNTIME)
          3 @Documented
          4 @Inherited
          5 @Import(AutoConfigurationPackages.Registrar.class)
          6 public @interface AutoConfigurationPackage

          該注解內(nèi)部包含了一個(gè)@Import注解,所以可以得出結(jié)論@EnableAutoConfiguration實(shí)際上就是由兩個(gè)@Import注解組成,分別導(dǎo)入了AutoConfiguartionImportSelector和AutoConfigurationPackages.Registrar類的實(shí)例。

          所以總結(jié)可以得出這兩個(gè)類是實(shí)現(xiàn)SpringBoot自動裝載配置的核心實(shí)現(xiàn)。

          關(guān)于@Import注解的詳細(xì)用法這里不多介紹,先簡單介紹下,@Import注解有三種用法,分別如下:

          1.顯示引入需要加載的bean,如:@Import(value=TestService.class)

          2.引入實(shí)現(xiàn)ImportSelector接口的類,如自定義MyImportSelector類實(shí)現(xiàn)ImportSelector并重寫selectImports方法,返回需要導(dǎo)入的bean的數(shù)組,案例如下:

          1 public class MyImportSelector implements ImportSelector {
          2     @Override
          3     public String[] selectImports(AnnotationMetadata importingClassMetadata) {
          4         return new String[]{UserService.class.getName(), GoodsService.class.getName()};
          5     }
          6 }

          3.引入實(shí)現(xiàn)ImportBeanDefinitionRegistrar接口的類,如自定義MyImportBeanDefinitionRegistrar并重寫registBeanDefinitions方法,手動注冊需要加載的bean,案例如下:

          public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

              @Override
              public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
                                                   BeanNameGenerator importBeanNameGenerator) {
                  /** 手動注冊需要加載的bean*/
                  registerBeanDefinitions(importingClassMetadata, registry);
              }
          }

          回過頭再看@EnableAutoConfiguration注解,分別導(dǎo)入了ImportSelector接口的實(shí)現(xiàn)類AutoConfigurationImportSelector和ImportBeanDefinitionRegistrar的實(shí)現(xiàn)類AutoConfigurationPackages.Registrar,接下來依次分析。

          2.4、AutoConfigurationImportSelector

          AutoConfigurationImportSelector實(shí)現(xiàn)了ImportSelector接口,所以會加載selectImports方法返回的String數(shù)組中的beanName,所以重點(diǎn)是selectImports方法,源碼如下:

          public String[] selectImports(AnnotationMetadata annotationMetadata) {
                  if (!isEnabled(annotationMetadata)) {
                      return NO_IMPORTS;
                  }
                  /** 加載元數(shù)據(jù)*/
                  AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                          .loadMetadata(this.beanClassLoader);
                  AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
                          annotationMetadata);
                  return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
              }

          核心步驟就兩步,第一步是執(zhí)行l(wèi)oadMetadata方法,源碼如下:

          protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";

              static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
                  /** 加載指定路徑下的元數(shù)據(jù)*/
                  return loadMetadata(classLoader, PATH);
              }

              static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
                  try {
                      Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
                              : ClassLoader.getSystemResources(path);
                      Properties properties = new Properties();
                      /** 遍歷Spring-autoconfigure-metadata.properties配置文件,讀取數(shù)據(jù)封裝為AutoConfigurationMetadata對象*/
                      while (urls.hasMoreElements()) {
                          properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
                      }
                      return loadMetadata(properties);
                  }
                  catch (IOException ex) {
                      throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
                  }
              }

          邏輯是從加載路徑下找到spring-autoconfigure-metadata.properties配置文件,然后遍歷讀取配置文件內(nèi)容加入到Properties對象中,并封裝成AutoConfigurationMetadata對象

          spring-autoconfigure-metadata.properties配置文件位于spring-boot-autoconfigure包中的META-INF目錄下,暫時(shí)可以不關(guān)心作用是什么,目前可以得出的結(jié)論是會先把這個(gè)配置中的內(nèi)容全部加載出來。

          再看第二步getAutoConfigurationEntry方法的實(shí)現(xiàn),源碼如下:

          protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
                                                                         AnnotationMetadata annotationMetadata) {
                  if (!isEnabled(annotationMetadata)) {
                      return EMPTY_ENTRY;
                  }
                  /** 封裝加載的配置元數(shù)據(jù)為AnnotationAttributes對象*/
                  AnnotationAttributes attributes = getAttributes(annotationMetadata);
                  /** 獲取所有候選的配置信息集合 (IOC容器需要加載的bean)
                   *  實(shí)際是加載META-INF/spring.factories配置文件中的bean
                   * */
                  List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
                  /** 去除重復(fù)的*/
                  configurations = removeDuplicates(configurations);
                  /** 去除排除的*/
                  Set<String> exclusions = getExclusions(annotationMetadata, attributes);
                  checkExcludedClasses(configurations, exclusions);
                  configurations.removeAll(exclusions);
                  /** 去除過濾的*/
                  configurations = filter(configurations, autoConfigurationMetadata);
                  fireAutoConfigurationImportEvents(configurations, exclusions);
                  /** 封裝成AutoConfigurationEntry對象*/
                  return new AutoConfigurationEntry(configurations, exclusions);
              }

          目的是加載META-INF/spring.factories配置文件,得到所有需要加載的類,然后通過去除、排除的方式篩選需要加載的類

          而spring.factories中有一個(gè)key為EnableAutoConfiguration,value是一個(gè)集合,包含了幾乎常用的所有各種中間件的自動配置類,這些類都在spring-boot-autoconfigure包中,包括常用的有:

          MongoDataAutoConfiguration、RedisAutoConfiguration、DataSourceAutoConfiguration、RabbitAutoConfiguration、ElasticsearchAutoConfiguration等等,每個(gè)類都封裝了各自的自動配置類

          tips:那么問題來了,SpringBoot默認(rèn)提供了各種各有的配置類,如果我們的程序中并沒有用到相關(guān)的組件,會不會講這些無用的配置類也加載到容器中呢?

          答案很顯然是否定的,spring-autoconfigure-metadata.properties中定義了各種組件的ConditionalOnClass的key,值為對應(yīng)的Class全路徑,比如:

          org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration.ConditionalOnClass=org.springframework.data.redis.core.RedisOperations

          org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration.ConditionalOnClass=com.rabbitmq.client.Channel,org.springframework.amqp.rabbit.core.RabbitTemplate

          org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration.ConditionalOnClass=javax.sql.DataSource,org.springframework.jdbc.core.JdbcTemplate

          ConditionalOnClass代表如果要加載對應(yīng)的屬性,必要要加載對應(yīng)的類,比如程序中用到了RabbitMQ,那么肯定依賴了RabbitMQ對應(yīng)的jar包,所以肯定會加載RabbitTemplate類,那么此時(shí)才會加載RabbitAutoConfiguration類,同理適用RedisAutoConfiguration類的前提是需要先加載RedisOperations類,只有先加載了對應(yīng)的類才會加載對應(yīng)的AutoConfiiguration類

          總結(jié)

          1、SpringBoot啟動時(shí)會通過@EnableAutoConfiguration注解導(dǎo)入AutoConfigurationImportSelector對象,會從掃描包路徑下META-INF/spring.factories中的配置,該配置文件中包含了各種組件的自動配置類對應(yīng)的AutoConfiguration類,然后通過加載各種組件的AutoConfiguration類從而可以加載對應(yīng)的自動配置類,并且通過ConditionalOnClass來過濾需要的自動配置類,去除掉程序中沒有使用的自動配置類

          2、@SpringBootApplication是個(gè)組合注解,分別是@SpringBootConfiguration、@ComponentScan和@EnableAutoConfiguration三個(gè)組件,其中@SpringBootConfiguration和@ComponentScan兩個(gè)組件保證可以掃描用戶自定義的bean,@EnableAutoConfiguration用于掃描加載默認(rèn)的第三方的bean

          3、SpringBoot程序啟動的流程實(shí)際就是IOC容器創(chuàng)建和啟動的過程,然后通過掃描用戶自定義的bean和第三方的bean完成bean的加載。

          四、SpringBoot內(nèi)置Tomcat源碼解析

          SpringBoot既然內(nèi)置了web容器,那么就不會只內(nèi)置Tomcat一個(gè)容器,還有Jetty、Undertow,所以從設(shè)計(jì)者的角度就會在這三個(gè)具體容器之上進(jìn)行抽象,定義了一個(gè)WebServer接口,WebServer接口定義如下:

          public interface WebServer {

              /** 啟動web容器*/
              void start() throws WebServerException;

              /** 關(guān)閉web容器*/
              void stop() throws WebServerException;

              /** 獲取web容器監(jiān)聽的端口 */
              int getPort();

          }

           

          而Tomcat、Jetty、Undertow等容器就會有各自的實(shí)現(xiàn)類實(shí)現(xiàn)了WebServer接口,分別是TomcatWebServer、JettyWebServer、UndertowWebServer,而SpringBoot采用了工廠模式來決定使用哪一個(gè)容器模式,所以定義了工廠類ServertWebServerFactory。在spring.factories配置文件中就定義了ServletWebServerFactoryAutoConfiguration,該類的定義如下:

          @Configuration(proxyBeanMethods = false)
          @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
          @ConditionalOnClass(ServletRequest.class)
          @ConditionalOnWebApplication(type = Type.SERVLET)
          @EnableConfigurationProperties(ServerProperties.class)
          @Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
                  ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
                  ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
                  ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
          public class ServletWebServerFactoryAutoConfiguration

          可以看出該類通過@Import注解分別導(dǎo)入了EmbeddedTomcat、EmbeddedJetty、EmbeddedUndertow的bean,以EmbeddedTomcat為例,定義如下:

          @Configuration(proxyBeanMethods = false)
              @ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
              @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
              public static class EmbeddedTomcat {

                  @Bean
                  public TomcatServletWebServerFactory tomcatServletWebServerFactory(
                          ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
                          ObjectProvider<TomcatContextCustomizer> contextCustomizers,
                          ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
                      TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
                      factory.getTomcatConnectorCustomizers()
                              .addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
                      factory.getTomcatContextCustomizers()
                              .addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
                      factory.getTomcatProtocolHandlerCustomizers()
                              .addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
                      return factory;
                  }

              }

          該類內(nèi)部通過@Bean注解定義了一個(gè)TomcatServletWebServerFactory對象,該對象就是TomcatWebServer的工廠類,TomcatServletWebServerFactory有一個(gè)getWebServer方法,定義如下:

          @Override
              public WebServer getWebServer(ServletContextInitializer... initializers) {
                  if (this.disableMBeanRegistry) {
                      Registry.disableRegistry();
                  }
                  Tomcat tomcat = new Tomcat();
                  File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
                  tomcat.setBaseDir(baseDir.getAbsolutePath());
                  Connector connector = new Connector(this.protocol);
                  connector.setThrowOnFailure(true);
                  tomcat.getService().addConnector(connector);
                  customizeConnector(connector);
                  tomcat.setConnector(connector);
                  tomcat.getHost().setAutoDeploy(false);
                  configureEngine(tomcat.getEngine());
                  for (Connector additionalConnector : this.additionalTomcatConnectors) {
                      tomcat.getService().addConnector(additionalConnector);
                  }
                  prepareContext(tomcat.getHost(), initializers);
                  return getTomcatWebServer(tomcat);
              }
          1 protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
          2         return new TomcatWebServer(tomcat, getPort() >= 0);
          3     }

          源碼不復(fù)雜,創(chuàng)建了一個(gè)Tomcat對象,然后初始化參數(shù),再傳給TomcatWebServer的構(gòu)造函數(shù)創(chuàng)建TomcatWebServer對象,TomcatWebServer構(gòu)造函數(shù)中會調(diào)用Tomcat的start方法啟動Tomcat服務(wù)器

          tips:那么問題來了,getWebServer方法是何時(shí)調(diào)用的呢?

          在Spring容器創(chuàng)建之后會執(zhí)行刷新操作,ApplicationContext的子類ServletWebServerApplicationContext實(shí)現(xiàn)了ApplicationContext的onRefresh方法,源碼如下:

          protected void onRefresh() {
                  super.onRefresh();
                  try {
                      createWebServer();
                  }
                  catch (Throwable ex) {
                      throw new ApplicationContextException("Unable to start web server", ex);
                  }
              }

          首先是調(diào)用父類的onRefresh方法,然后執(zhí)行createWebServer方法創(chuàng)建WebServer對象,邏輯就是從IOC容器中找到ServletWebServerFactory對象,執(zhí)行g(shù)etWebServer方法獲取WebServer對象。

          apache的Tomcat實(shí)現(xiàn)原理解析可參考:

          五、如何實(shí)現(xiàn)自定義starter

          1、新建項(xiàng)目spring-boot-mytest-starter(官方提供的starter命名為spring-boot-starter-xxx,自定義的通常命名為spring-boot-xxx-starter,用于和官方提供的區(qū)分開)






          粉絲福利:Java從入門到入土學(xué)習(xí)路線圖

          ??????

          ??長按上方微信二維碼 2 秒


          感謝點(diǎn)贊支持下哈 

          瀏覽 44
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  一区二区欧美精品 | 围内精品久久久久久久久久98 | 97色婷婷| 全国男人天堂网 | 亚欧洲精品在线视频 |