分析SpringBoot的啟動原理
背景
1> 大家都知道SpringBoot是通過main函式啟動的,這裡面跟蹤程式碼到處都沒有找到while(true),為什麼啟動後可以一直跑?
2> SpringBoot預設使用tomcat作為web容器。大家也可以通過在pom檔案中exclusion掉tomcat,denpendency jetty 的方法來使用jetty。那SpringBoot是怎麼做到在不同web容器之間切換的呢?
3> 傳統的web容器比如jetty本質上是直接通過java start.jar 來啟動,之後來載入spring上下文的,SpringBoot通過main函式是怎麼來啟動web容器的呢?
本文就這三個問題展開論述。
問題1分析
問題1很簡單,啟動後一直跑是因為啟動了執行緒池。原理就是有非deamon的執行緒在跑。Java虛擬機器規範定義要等所有使用者執行緒都執行完才會退出。
所以這個原理就和下面啟動執行緒池一樣
程式設計師修煉之道教我們:不要假定,要證明。雖然jetty使用執行緒池是常識,我們也來跟蹤下原始碼,看看執行緒池是在哪裡初始化的:
org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory類裡,建立Server的使用使用執行緒池作為初始化引數。然後建立了socket連線來監聽埠。(對於socket連線有之前沒接觸過的,可以自己查一下。建議動手實踐。《Java異常處理總結》這篇文章裡有不錯的簡單小例子可以實操下。)
到這裡,大家應該都明白了為什麼啟動後一直不停。但是又有疑問了:JettyServletWebServerFactory是個什麼東東?
問題2分析
關於問題2,我們寫個最簡單的類來debug一下:
進入SpringAppication.run的原始碼可以看到,裡面建立了一個context,預設是AnnotationConfigServletWebServerApplicationContext。一初始化,在Bean定義裡就載入了spring開天闢地的5個Bean。
繼續向下執行走到AbstractApplicationContext的refresh方法,執行到onRefresh時,你進入方法裡發現實際上執行的是
ServletWebServerApplicationContext的onFresh
這裡面實際只做了一件事:建立web服務。
進入這個方法,debug到getWebServerFactory
來看一下:
獲取的正式JettyServletWebServerFactory。為啥不是TomcatServlet呢?ServletWebServerFactoryAutoConfiguration的原始碼很好的說明了這個問題。原始碼的大意是當tomcat依賴存在就用tomcat,不然就按順序找jetty存不存在,不存在再找Undertow存不存在。找到了就返回這個bean作為Servlet的工廠類。
@Configuration @AutoConfigureOrder(-2147483648) @ConditionalOnClass({ServletRequest.class}) @ConditionalOnWebApplication( type = Type.SERVLET ) @EnableConfigurationProperties({ServerProperties.class}) @Import({ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,EmbeddedTomcat.class,EmbeddedJetty.class,EmbeddedUndertow.class}) public class ServletWebServerFactoryAutoConfiguration { public ServletWebServerFactoryAutoConfiguration() { } @Bean public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties) { return new ServletWebServerFactoryCustomizer(serverProperties); } @Bean @ConditionalOnClass( name = {"org.apache.catalina.startup.Tomcat"} ) public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(ServerProperties serverProperties) { return new TomcatServletWebServerFactoryCustomizer(serverProperties); } public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar,BeanFactoryAware { private ConfigurableListableBeanFactory beanFactory; public BeanPostProcessorsRegistrar() { } public void setBeanFactory(BeanFactory beanFactory) throws BeansException { if (beanFactory instanceof ConfigurableListableBeanFactory) { this.beanFactory = (ConfigurableListableBeanFactory)beanFactory; } } public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,BeanDefinitionRegistry registry) { if (this.beanFactory != null) { this.registerSyntheticBeanIfMissing(registry,"webServerFactoryCustomizerBeanPostProcessor",WebServerFactoryCustomizerBeanPostProcessor.class); this.registerSyntheticBeanIfMissing(registry,"errorPageRegistrarBeanPostProcessor",ErrorPageRegistrarBeanPostProcessor.class); } } private void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry,String name,Class<?> beanClass) { if (ObjectUtils.isEmpty(this.beanFactory.getBeanNamesForType(beanClass,true,false))) { RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass); beanDefinition.setSynthetic(true); registry.registerBeanDefinition(name,beanDefinition); } } } }
至此第二個問題也真相大白。
問題3分析
第三個問題是傳統的web容器比如jetty本質上是直接通過java start.jar 來啟動,之後來載入spring上下文的,SpringBoot通過main函式是怎麼來啟動web容器。
這個問題在前面問題分析過程中也給了很多線索。我們來回顧下:SpringApplication.run裡會建立Spring的應用上下文,預設是AnnotationConfigServletWebServerApplicationContext。首先會載入Spring開天闢地的5個Bean。然後它初始化各種Bean工廠。
SpringBoot在ServletWebServerApplicationContext中過載了onRefresh方法,除了以前Spring預設的onRefresh方法外還增加了createWebServer方法,在這個方法中對Web容器進行了初始化工作。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>${spring.boot.version}</version> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> <exclusion> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jetty</artifactId> <version>${spring.boot.version}</version> <exclusions> <exclusion> <groupId>org.eclipse.jetty.aggregate</groupId> <artifactId>jetty-all</artifactId> </exclusion> </exclusions> </dependency>
因為選擇servlet容器是類似於使用基於條件的註解方式。因為當exclusion掉tomcat後,只有jetty滿足條件,所以會載入JettyServletWebServerFactory。
通過getWebServer方法會new一個WebServer物件,new物件的方法會呼叫initialize方法,在這個方法中會對容器進行初始化並啟動。
而容器啟動的基本原理就是建立個執行緒池和網路套接字。用執行緒去處理套接字讀寫的內容。
總結
文字用帶有少許說明的三個問題開場展開論述,實際是使用了麥肯錫大法中的SCQA架構。
SCQA架構是金字塔模型裡面突出的一個論述方法,即“情境(Situation)、衝突(Complication)、問題(Question)、答案(Answer)”。可以幫助我們在陳述事實時條理更為清晰、有效。
SCQA其實只是麥肯錫做了總結。這個方法李清照都在用:
昨夜雨疏風驟,濃睡不消殘酒 (情境)
試問卷簾人,渠道海棠依舊(衝突)
知否,知否(問題)
應是綠肥紅瘦(答案)
文章正文看似一步步回答問題,實際上在講述怎樣去看spring原始碼,瞭解spring原理的一個過程。即:帶著問題去看,debug跟蹤原始碼驗證 的方法。
以上就是分析SpringBoot的啟動原理的詳細內容,更多關於SpringBoot 啟動原理的資料請關注我們其它相關文章!