<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源碼后,整個人都精神了!

          共 15319字,需瀏覽 31分鐘

           ·

          2022-04-23 22:29

          作者:i聽風逝夜?

          鏈接:https://juejin.cn/post/7067714190424670239

          前言

          當讀完SpringBoot源碼后,被Spring的設計者們折服,Spring系列中沒有幾行代碼是我們看不懂的,而是難在理解設計思路,閱讀Spring、SpringMVC、SpringBoot需要花時間,由于沒開學,在家前前后后花了17天天才懂了個大概,這期間也加上寫文章時間,并對每一個知識點進行代碼驗證,也花了不少,如果除去這些的話,我覺得10天左右就能熟悉他們。

          光Spring核心就有3000多個java文件,Web模塊有1368個,而SpringBoot比較少,只有1880多個,看到這些數(shù)字讓我們有個輕重,知道該從哪讀起。

          還有推薦一本老書,2006年的,叫《Spring框架高級編程》,Spring原班人馬寫的,別看他老,他很少講代碼,而是說Spring設計思想。

          正文

          我們在前幾章已經(jīng)說完了Spring、SpringMVC,這章說最后一章SpringBoot,很多人不知道他們區(qū)別,在這里簡單說下,首先他們三個是依次誕生的,后一個依賴前一個,Spring最為一個底層支撐尤為重要,他的核心就是我們常說的IOC容器和AOP,容器里面存放的就是實例化后的對象,AOP依靠兩個技術來實現(xiàn),CGLIB和JDK的Proxy,如果單獨使用Spring開發(fā)程序,我們可以做桌面程序,也可以做命令行程序,但如果要做后端接口,那這就需要我們自己實現(xiàn)Servlet了,并且必須非常熟悉Spring。

          但后來有了SpringMVC,他的出現(xiàn)導致再也不用寫Servlet了,有了一種更方便的寫法,也就是在一個類上標記@Controller注解,再配合@GetMapping,就能實現(xiàn)一個接口,SpringMVC還提供了參數(shù)轉換器,能讓我們更加方便的從請求中獲取參數(shù),SpringMVC的核心是DispatcherServlet,可以在上一章了解。

          后來SpringBoot誕生了,SpringBoot并沒有什么新技術,他更多的是組裝能力,他是在SpringMVC的基礎上,把Servlet容器又隱藏掉了,因為在搭建SpringMVC的時候,還需要配置如Tomcat,這也是麻煩的一步,索性SpringBoot把配置他的過程也封裝掉,那么這就導致一個新手可以在幾分鐘內,實現(xiàn)一個后端API。

          但也不僅僅是隱藏掉Servlet容器那么簡單。

          那么下面我們深入分析一下SpringBoot,主要由以下幾點:

          1. 自動配置原理
          2. 內嵌Tomcat原理
          3. @EnableWebMvc做了什么
          4. WebMvcConfigurationSupport和WebMvcConfigurer區(qū)別
          5. jar啟動原理
          6. war啟動原理

          自動配置原理

          我們常說的自動配置也并不是什么厲害的東西,他是在解決Spring掃描不到的問題,我們在前幾章說過,對象能進入容器中的前提,是標有@Component注解并且能讓Spring掃描到,或者手動注冊。

          那么想這樣一個問題,你的Spring應用開發(fā)完了,打包成jar后上線部署,但有一天需要增加功能,并且這個功能需要在獨立的jar中,而你的主工程要依賴這個jar,但是麻煩的是需要要重新修改主工程中scan的路徑,或者加入@ComponentScan,這樣主工程中才能管理這個jar中的對象。

          而有一天這個主工程還要依賴其他小組開發(fā)的jar,并且包名也不一樣,那么還要在主工程中加入@ComponentScan。

          這是不是有點麻煩?

          那么有人就提出來了,我們能不能制定一個約定,外部的jar遵守這個約定,主項目中按照這個約定進行加載?

          這里的約定就是一個文件,文件中放入想被Spring管理的class類名,而主項目中掃描到這個jar中有這個文件后,就把文件內容中的class類名拿出來注冊到容器。

          那么這個文件就是常說的spring.factories,開啟按照約定加載就是通過@EnableAutoConfiguration。

          @EnableAutoConfiguration可能都聽過,他標注在@SpringBootApplication上,

          @EnableAutoConfiguration注解上又有一個@Import注解,@Import用來向容器導入一些bean,注解本身沒有什么用,想了解他是什么原理,做了什么,需要找到調用他的地方。

          對于一個下面這種方法啟動的SpringBoot程序來說,我們要了解上面注解的工作原理,第一個關鍵信息在ConfigurationClassPostProcessor下。

          @SpringBootApplication
          public?class?SampleWebUiApplication?{
          ???public?static?void?main(String[]?args)?throws?IOException?{
          ???????SpringApplication.run(SampleWebUiApplication.class,?args);
          ???}
          }
          復制代碼

          ConfigurationClassPostProcessor類會在Spring進行refresh()的時候執(zhí)行,詳細信息可以看以往文章的Spring第二章。

          ConfigurationClassPostProcessor會對SampleWebUiApplication進行解析,我們把標注在SampleWebUiApplication類上的的注解放在一起看,首先這個類有@ComponentScan注解,那么在ConfigurationClassPostProcessor執(zhí)行的時候就會提取出@ComponentScan上的信息,進行bean掃描,Spring規(guī)定,如果@ComponentScan上的basePackages為空,那么默認的basePackages會成為他所在的包,這也就是為什么普遍要把"啟動類"放在最外層,這樣他的所有子包才能得到掃描。

          下一步是判斷有沒有@Import注解,當然有了,就是下面這個。

          @Import(AutoConfigurationImportSelector.class)
          復制代碼

          如果存在@Import,就實例化他的值,這里的值就是AutoConfigurationImportSelector,并且有三種情況,其中一種情況是這個值繼承ImportSelector,那么Spring會認定這個類用于向容器導入新的bean,則會調用他的selectImports。

          而這個類就是我們自動配置的關鍵類。

          他會獲取所有jar中位于路徑META-INF/spring.factories下所有org.springframework.boot.autoconfigure.EnableAutoConfiguration的值,如果你隨便打開一個spring.factories文件,就會看到或多或少有下面這樣的配置。

          org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
          org.springframework.boot.devtools.autoconfigure.DevToolsDataSourceAutoConfiguration,\
          org.springframework.boot.devtools.autoconfigure.DevToolsR2dbcAutoConfiguration,\
          org.springframework.boot.devtools.autoconfigure.LocalDevToolsAutoConfiguration,\
          org.springframework.boot.devtools.autoconfigure.RemoteDevToolsAutoConfiguration
          復制代碼

          他是通過SpringFactoriesLoader加載的,這個類是Spring中的類,但是很少在Spring中看到他的使用。

          這就是上面我們所說的約定,這樣任何一個jar中只有遵循這個約定,就可以向Spring中注冊對象,下面簡單看下這段源碼,如果要詳細研究,重點對象還是SpringFactoriesLoader。

          @Override
          public?String[]?selectImports(AnnotationMetadata?annotationMetadata)?{
          ???if?(!isEnabled(annotationMetadata))?{
          ??????return?NO_IMPORTS;
          ???}
          ???/**
          ????*?獲取所有jar中位于路徑META-INF/spring.factories下所有`org.springframework.boot.autoconfigure.EnableAutoConfiguration`的值
          ????*/

          ???AutoConfigurationEntry?autoConfigurationEntry?=?getAutoConfigurationEntry(annotationMetadata);
          ???/**
          ????*?轉換成數(shù)組返回
          ????*/

          ???return?StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
          }
          復制代碼

          這就是自動配置,非常簡單,但是給我們帶來的好處卻有很多。

          內嵌Tomcat原理

          這個首先得Tomcat本身支持,如果Tomcat不支持內嵌,SpringBoot估計也沒辦法,或者可能會另找出路。

          Tomcat本身有一個Tomcat類,沒錯就叫Tomcat,全路徑是org.apache.catalina.startup.Tomcat,我們想啟動一個Tomcat,直接new Tomcat(),之后調用start()就可以了。

          并且他提供了添加Servlet、配置連接器這些基本操作。

          下面看一個例子。

          public?class?Main?{
          ????public?static?void?main(String[]?args)?{
          ????????try?{
          ????????????Tomcat?tomcat?=new?Tomcat();
          ????????????tomcat.getConnector();
          ????????????tomcat.getHost();
          ????????????Context?context?=?tomcat.addContext("/",?null);
          ????????????tomcat.addServlet("/","index",new?HttpServlet(){
          ????????????????@Override
          ????????????????protected?void?doGet(HttpServletRequest?req,?HttpServletResponse?resp)?throws?ServletException,?IOException?{
          ????????????????????resp.getWriter().append("hello");
          ????????????????}
          ????????????});
          ????????????context.addServletMappingDecoded("/","index");
          ????????????tomcat.init();
          ????????????tomcat.start();
          ????????}catch?(Exception?e){}
          ????}
          }
          復制代碼

          啟動后訪問http://localhost:8080/就可以看到響應hello。

          那么接下來就是分析SpringBoot在什么地方創(chuàng)建的Tomcat,以及在什么地方添加的DispatcherServlet。

          首先需要了解Spring本身提供的擴展函數(shù),因為SpringBoot都是在Spring的基礎上做的。

          這需要關注AbstractApplicationContext的refresh()方法,Spring的啟動流程離不開這個方法,其中調用了一個模板方法onRefresh(),在他的注釋上也已經(jīng)說了,用來留下做擴展,調用這個方法的時候所有bean已經(jīng)被收集完成了,但是還沒進行實例化。

          我們開發(fā)Web應用的時候,他的實現(xiàn)類都是AnnotationConfigServletWebServerApplicationContext,而創(chuàng)建Tomcat就是在他重寫的onRefresh()下,如下。

          @Override
          protected?void?onRefresh()?{
          ???super.onRefresh();
          ???try?{
          ??????createWebServer();
          ???}
          ???catch?(Throwable?ex)?{
          ??????throw?new?ApplicationContextException("Unable?to?start?web?server",?ex);
          ???}
          }
          復制代碼
          private?void?createWebServer()?{
          ???WebServer?webServer?=?this.webServer;
          ???/**
          ????*?ServletContext是Tomcat啟動后通過ServletContainerInitializer回調過來的
          ????*/

          ???ServletContext?servletContext?=?getServletContext();
          ???if?(webServer?==?null?&&?servletContext?==?null)?{
          ??????StartupStep?createWebServer?=?this.getApplicationStartup().start("spring.boot.webserver.create");
          ??????/**
          ???????*?服務創(chuàng)建工廠,
          ???????*/

          ??????ServletWebServerFactory?factory?=?getWebServerFactory();
          ??????createWebServer.tag("factory",?factory.getClass().toString());
          ??????/**
          ???????*?Tomcat啟動后會回調到?{@link?ServletWebServerApplicationContext#selfInitialize(ServletContext)}
          ???????*/

          ??????this.webServer?=?factory.getWebServer(getSelfInitializer());
          ??????createWebServer.end();
          ??????getBeanFactory().registerSingleton("webServerGracefulShutdown",
          ????????????new?WebServerGracefulShutdownLifecycle(this.webServer));
          ??????getBeanFactory().registerSingleton("webServerStartStop",
          ????????????new?WebServerStartStopLifecycle(this,?this.webServer));
          ???}
          ???else?if?(servletContext?!=?null)?{
          ??????try?{
          ?????????getSelfInitializer().onStartup(servletContext);
          ??????}
          ??????catch?(ServletException?ex)?{
          ?????????throw?new?ApplicationContextException("Cannot?initialize?servlet?context",?ex);
          ??????}
          ???}
          ???initPropertySources();
          }
          復制代碼

          這里有兩個關鍵,一個是Web服務創(chuàng)建工廠,即ServletWebServerFactory,因為有很多Web服務器類型,除了Tomcat,還有Jetty、Undertow,每個工廠創(chuàng)建的服務器都會被封裝在WebServer對象中并返回,WebServer提供了基本的啟動、停止服務的方法,而實例化對應的ServletWebServerFactory,并不是直接new,他要走Spring的創(chuàng)建流程,因為這里設計到一個擴展,有一個WebServerFactoryCustomizerBeanPostProcessor的后置處理器負責調用所有WebServerFactoryCustomizer的customize()方法,用來自定義服務配置,在這里可以修改監(jiān)聽的端口等信息。

          那么一定會有一個地方向Spring添加WebServerFactoryCustomizerBeanPostProcessor,其實就是在spring.factories下,負責導入的類是ServletWebServerFactoryAutoConfiguration。

          二是ServletContainerInitializer,這是Servlet規(guī)范中定義的,由各個Servlet容器去調用,用來做一些初始化工作,通常把他的實現(xiàn)類放入jar中的META-INF/services/javax.servlet.ServletContainerInitializer下,Tomcat會自己掃描,如果有的話就會調用,還有就是通過Context的addServletContainerInitializer,這個Context表示Tomcat中的一個Web應用實例。

          回調的時候主要會傳入ServletContext。

          而SpringBoot使用第二種方法,在Tomcat創(chuàng)建成功后,生成TomcatStarter對象,把這個對象給Tomcat送過去,將來在Tomcat啟動成功后,會回調他的onStartup。

          但是難在TomcatStarter中的onStartup會調用所有他保存的ServletContextInitializer對象,注意了,這是兩個不同的對象。

          ServletContainerInitializer是Servlet規(guī)范中的。

          ServletContextInitializer是SpringBoot中的。
          復制代碼

          而他所保存的實例中有一個是方法引用傳遞的。

          private?org.springframework.boot.web.servlet.ServletContextInitializer?getSelfInitializer()?{
          ???return?this::selfInitialize;
          }
          復制代碼

          所以最終會回調到這里,注意這里是被Tomcat所調用的。

          private?void?selfInitialize(ServletContext?servletContext)?throws?ServletException?{
          ???/**
          ????*?通過Lambda在Tomcat?OnStart階段時候會回調到這里
          ????*/

          ???/**
          ????*?設置ServletContext下文
          ????*/

          ???prepareWebApplicationContext(servletContext);
          ???registerApplicationScope(servletContext);
          ???WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(),?servletContext);
          ???/**
          ????*?從容器中獲取目前所有ServletContextInitializer,并調用
          ????*
          ????*?最關鍵的是DispatcherServletRegistrationBean
          ????*/

          ???for?(ServletContextInitializer?beans?:?getServletContextInitializerBeans())?{
          ??????beans.onStartup(servletContext);
          ???}
          }
          復制代碼

          到這里就有意思了,TomcatStarter中默認會有3個實例,干不同的事,注意這3個實例都不在Spring容器中。這3個其中一個就是上面那個selfInitialize方法,此方法具體做的事就是拿到Spring容器中所有ServletContextInitializer的實例,并依次調用其onStartup()方法,而其中一個實例是DispatcherServletRegistrationBean,看名字就知道這個對象肯定有點東西。

          我們進入DispatcherServletRegistrationBean的onStartup看做了什么,這里就不跟蹤源碼了,他做得事就是向ServletContext上下文中添加DispatcherServlet。

          protected?ServletRegistration.Dynamic?addRegistration(String?description,?ServletContext?servletContext)?{
          ???String?name?=?getServletName();
          ???return?servletContext.addServlet(name,?this.servlet);
          }
          復制代碼

          但是添加了Servlet,還沒給他作映射,那么接下configure()就是干這個的,我們幾乎沒有用過ServletRegistration.Dynamic,這也是Servlet規(guī)范中的,不是SpringBoot,他的其中作用就是添加映射,讓指定的url能到達此Servlet,獲取他就是通過上面addServlet,而DispatcherServlet的url路徑就是/

          剛才我們說了,他會拿到Spring容器中所有ServletContextInitializer的實例,并依次調用,那么我們就可以自己實現(xiàn)一個ServletContextInitializer,標記上@Component,也可以達到添加Servlet的效果。

          但是SpringBoot提供了ServletRegistrationBean,這個是專門向Web容器中添加Servlet的,那么我們借助他可以這樣做。

          @Configuration
          public?class?Test?{
          ???static?class?MyServlet?extends?HttpServlet?{
          ??????@Override
          ??????protected?void?doGet(HttpServletRequest?req,?HttpServletResponse?resp)?throws?ServletException,?IOException?{
          ?????????resp.getWriter().append("hello");
          ??????}
          ???}
          ???@Bean
          ???public?ServletRegistrationBean?servletRegistrationBean()?{
          ??????return?new?ServletRegistrationBean(new?MyServlet(),?"/test");
          ???}
          }
          復制代碼

          到這里就結束了,DispatcherServlet都已經(jīng)添加到容器中了,那還有兩個小問題,DispatcherServlet在什么地方生成、DispatcherServletRegistrationBean在什么地方生成,根據(jù)SpringBoot的命名規(guī)則,肯定是DispatcherServletAutoConfiguration,所有AutoConfiguration結尾的類幾乎都會在spring.factories下。

          @EnableWebMvc做了什么

          SpringBoot中沒有使用過@EnableWebMvc,至少我發(fā)現(xiàn),因為@EnableWebMvc的作用就是向容器導如DelegatingWebMvcConfiguration,而這個類已經(jīng)由WebMvcAutoConfiguration中導入了。

          具體DelegatingWebMvcConfiguration做了什么,我們下面說。

          WebMvcConfigurationSupport和WebMvcConfigurer區(qū)別

          我們先說WebMvcConfigurationSupport,DelegatingWebMvcConfiguration是繼承他的。

          他的作用是向容器導入HandlerMapping、ViewResolver等DispatcherServlet中需要的組件,不導入行不行,也行,因為在DispatcherServlet中首先會從容器中查找,如果沒有,則會生成默認的,默認創(chuàng)建后也不會加入到Spring容器中。

          我們通常繼承WebMvcConfigurationSupport添加一些資源或者參數(shù)轉換器,會不會覺得很神奇,SpringBoot自己就調用了如addResourceHandlers方法,其實這些方法都是在創(chuàng)建DispatcherServlet中需要的組件時候調用的,比如下面,這個返回的是處理靜態(tài)資源的對象。

          @Bean
          @Nullable
          public?HandlerMapping?resourceHandlerMapping(
          ??????@Qualifier("mvcContentNegotiationManager")
          ?ContentNegotiationManager?contentNegotiationManager,
          ??????@Qualifier("mvcConversionService")?FormattingConversionService?conversionService,
          ??????@Qualifier("mvcResourceUrlProvider")?ResourceUrlProvider?resourceUrlProvider)?
          {


          ???ResourceHandlerRegistry?registry?=?new?ResourceHandlerRegistry(this.applicationContext,
          ?????????this.servletContext,?contentNegotiationManager,?pathConfig.getUrlPathHelper());
          ???//調用此方法添加自定義資源路徑??????
          ???addResourceHandlers(registry);
          ......
          ???return?handlerMapping;
          }
          復制代碼

          SpringBoot導入的是EnableWebMvcConfiguration,他們三個是繼承關系。

          EnableWebMvcConfiguration》DelegatingWebMvcConfiguration》WebMvcConfigurationSupport

          復制代碼

          EnableWebMvcConfiguration中同樣會導入一些bean,EnableWebMvcConfiguration位于源碼WebMvcAutoConfiguration中,屬于內部類,但是在WebMvcAutoConfiguration上SpringBoot通過下面注解表示,如果已經(jīng)有了WebMvcConfigurationSupport實例,那么我就不會再向容器導入了,也就是WebMvcConfigurationSupport在SpringBoot中只會保留一個。

          @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
          復制代碼

          但并不是說你有好幾個類繼承了WebMvcConfigurationSupport,最終只有一個會被Spring管理,而是Spring在解析類的時候,會循環(huán)解析他的父類了,并且這個父類這能被解析一次,他里面標有@Bean的方法也只會調用一次。

          這也就是如果A、B兩個類都繼承WebMvcConfigurationSupport,你所有重寫的方法,在A、B中只有一個會被調用。

          那么如果你想通過好幾個類配置怎么辦,那就需要WebMvcConfigurer了,如果你看DelegatingWebMvcConfiguration源碼,他重寫的方法都很簡單,內部會有一個List保存所有WebMvcConfigurer實現(xiàn)類,重寫的方法都會遍歷這個集合,調用其相同的方法。

          那么這個集合是哪來的?

          可以看DelegatingWebMvcConfiguration中下面這個方法,標有@Autowired,說明Spring會自動調用這個方法,并且把容器中所有實現(xiàn)WebMvcConfigurer接口的類實例化后傳遞到這里。

          @Autowired(required?=?false)
          public?void?setConfigurers(List?configurers)?{
          ???if?(!CollectionUtils.isEmpty(configurers))?{
          ??????this.configurers.addWebMvcConfigurers(configurers);
          ???}
          }
          復制代碼

          那么我們如果想在多個類中配置,實現(xiàn)WebMvcConfigurer接口既可,而在SpringBoot中也會通過這個接口默認增加一些配置,比如下面這些資源通過url就可以直接訪問。

          classpath:/META-INF/resources/
          classpath:/resources/
          classpath:/static/
          classpath:/public/"
          復制代碼

          但是這就有個問題,如果我繼承了WebMvcConfigurationSupport,那么DelegatingWebMvcConfiguration就沒辦法實例化,所有實現(xiàn)了WebMvcConfigurer的接口就得不到執(zhí)行,是不是這樣呢?

          確實是的。

          但還有個問題,只要繼承了WebMvcConfigurationSupport,那么上面SpringBoot為你配置的默認資源訪問路徑,也會失效,就是因為SpringBoot的自動配置,發(fā)現(xiàn)如果有了WebMvcConfigurationSupport,那么自己就不會添加了。

          那不是說添加默認資源訪問路徑是在WebMvcConfigurer接口中的嗎?

          沒錯,但這個接口實現(xiàn)類在WebMvcAutoConfiguration下,也屬于內部類。

          解決辦法就是不要繼承WebMvcConfigurationSupport,我們應該實現(xiàn)WebMvcConfigurer接口,這是最好的辦法。

          jar啟動原理

          SpringBoot有兩種線上部署方法,jar和war,先說jar原理。

          要運行一個jar,首先需要在META-INF/MANIFEST.MF配置Main-Class,隨便打開一個SpringBoot項目,都會發(fā)現(xiàn)Main-Class都是org.springframework.boot.loader.JarLauncher。

          Manifest-Version:?1.0
          Spring-Boot-Classpath-Index:?BOOT-INF/classpath.idx
          Spring-Boot-Layers-Index:?BOOT-INF/layers.idx
          Start-Class:?com.example.springdemo.SpringDemoApplicationKt
          Spring-Boot-Classes:?BOOT-INF/classes/
          Spring-Boot-Lib:?BOOT-INF/lib/
          Spring-Boot-Version:?2.5.6
          Main-Class:?org.springframework.boot.loader.JarLauncher
          復制代碼

          那么我們就順藤摸瓜進入這個類的main方法。

          但看來看去,簡單的不得了,就是接著提取上面屬性中的Start-Class,通過反射調用他的main方法,這個類就是我們開發(fā)的時候的"主類"了。

          那為什么不直接把Main-Class設置成我們的主類?

          其實也可以,但是這就麻煩多了,主要原因是jar文件中,不能包含其他第三方jar,很多文章說的jar禁止嵌套jar也是說的這個意思,但是我專門去官網(wǎng)找jar格式說明,沒有找到關于禁止這一類的詞,更多的是關于第三方lib的都需要配置在Class-Path下,這個路徑是相對主jar路徑搜索的。

          一般普通java項目打包成jar后,如果第三方jar也要被打入,那么打包插件通常都會把第三方jar解壓后連同主項目編譯后的目錄一同放入jar中,不會把整個jar放在主jar文件中,這樣會報找不到類,

          如果SpringBoot使用這種方式把Spring所有依賴和其他依賴都解壓后放入主jar中,那么會導致混亂不堪。

          最終SpringBoot的這種方式純屬是被迫。

          他的實現(xiàn)思想是既然JVM不能加載jar中的jar,那么SpringBoot自己實現(xiàn)一個ClassLoader來加載,然后賦值給當前線程的ClassLoader,最終通過反射加載我們的主類時,指定一個ClassLoader。

          public?void?run()?throws?Exception?{
          ???Class?mainClass?=?Class.forName(this.mainClassName,?false,?Thread.currentThread().getContextClassLoader());
          ???Method?mainMethod?=?mainClass.getDeclaredMethod("main",?String[].class);
          ???mainMethod.setAccessible(true);
          ???mainMethod.invoke(null,?new?Object[]?{?this.args?});
          }
          復制代碼

          這也就是打包運行后,輸出某個類的ClassLoader是LaunchedURLClassLoader的原因。

          而這個LaunchedURLClassLoader就負責加載BOOT-INF下的class和jar。

          war啟動原理

          war就是由Tomcat來加載了,啟動原理也比較簡單,依靠ServletContainerInitializer來完成,上面說過,這是由Tomcat來回調,但還有一個注解沒有說@HandlesTypes,這個注解用來標注在ServletContainerInitializer的實現(xiàn)類上,參數(shù)是個Class類型,表示告訴Tomcat,對我進行回調的時候,把所有繼承或者和這個Class相等的Class給我傳過來。

          而ServletContainerInitializer的實現(xiàn)類,需要放到META-INF/services/javax.servlet.ServletContainerInitializer下,但是SpringBoot并沒有實現(xiàn)這個約定,實現(xiàn)了約定的是spring-web模塊,他的定義如下。

          @HandlesTypes(WebApplicationInitializer.class)
          public?class?SpringServletContainerInitializer?implements?ServletContainerInitializer?
          {}
          復制代碼

          可以看到,他要求Tomcat回傳所有WebApplicationInitializer的實現(xiàn)類給他,那么細心的你可能發(fā)現(xiàn),如果要部署在Tomcat中,必須要繼承SpringBootServletInitializer,重寫其中configure方法并配置我們的主類,而這個類正好又實現(xiàn)了WebApplicationInitializer接口。

          而SpringServletContainerInitializer中做的只是實例化傳遞過來的所有WebApplicationInitializer實現(xiàn)類,調用他們的onStartup()。

          那么這就到了上面說的SpringBootServletInitializer.onStartup下,最終會根據(jù)我們所配置的主類,同樣調用SpringApplication.run啟動。

          protected?WebApplicationContext?run(SpringApplication?application)?{
          ???return?(WebApplicationContext)?application.run();
          }


          瀏覽 79
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  婷婷亚洲视频 | 黄色大奶视频中国 | 国产视频黄色精频大全 | 中文字幕在线免费播放 | 影音先锋AV啪啪资源 |