我的Spring Boot學習記錄(二):Tomcat Server以及Spring MVC的問題
- 2019 年 10 月 15 日
- 筆記
Spring Boot版本: 2.0.0.RELEASE
這裡需要引入依賴 spring-boot-starter-web
這裡有可能有個人的誤解,請抱著懷疑態度看。
建議: 感覺自己也會被繞暈,所以有興趣的使用IDE工具看下源碼
1、Tomcat在什麼時候被初始化了?
在ServletWebServerApplicationContext
中有段程式碼,如下:
org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext @Override protected void onRefresh() { super.onRefresh(); try { createWebServer(); } catch (Throwable ex) { throw new ApplicationContextException("Unable to start web server", ex); } } /** * 創建Web服務容器,如:Tomcat,Jetty等;具體創建的容器根據#getWebServerFactory得到 * 而WebServerFactory在BeanFactory獲取,也就是在載入Bean時確定的, * 這裡通過Spring Boot自動配置了Tomcat,如果想要深入可以追著#getWebServerFactory看 * 下面有對應的不太詳細的解釋 */ private void createWebServer() { WebServer webServer = this.webServer; ServletContext servletContext = getServletContext(); if (webServer == null && servletContext == null) { ServletWebServerFactory factory = getWebServerFactory(); this.webServer = factory.getWebServer(getSelfInitializer()); } // ..... }
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration.EmbeddedTomcat#tomcatServletWebServerFactory /** * 這裡就通過自動載入Bean時載入了關於Tomcat的WebServerFactory * 至於為什麼載入Tomcat而不是Jetty,就要多謝Bean載入時@ConditionalOnClass註解 * 因為我們引入依賴spring-boot-starter-web 其次,它又引入了 spring-boot-starter-tomcat依賴 * 因為存在{ Servlet.class, Tomcat.class, UpgradeProtocol.class }這些class,所以載入Tomcat */ @Configuration @ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class }) @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT) public static class EmbeddedTomcat { @Bean public TomcatServletWebServerFactory tomcatServletWebServerFactory() { return new TomcatServletWebServerFactory(); } } // .......
上述中說明了Tomcat
的創建,而什麼時候調用onRefresh
並創建Tomcat
呢?
其實onRefresh
是org.springframework.context.support.AbstractApplicationContext
一個未具體實現方法,交給子類實現,它在調用refresh()
方法中調用。
而org.springframework.boot.SpringApplication#run(java.lang.String...)
調用了refreshContext
,又間接調用上述refresh()
方法
在onRefresh
調用後在refresh()
中還調用了finishRefresh()
,而重寫了其方法finishRefresh
的ServletWebServerApplicationContext
就啟動了Web服務,程式碼如下:
org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#finishRefresh @Override protected void finishRefresh() { super.finishRefresh(); WebServer webServer = startWebServer(); if (webServer != null) { publishEvent(new ServletWebServerInitializedEvent(webServer, this)); } } private WebServer startWebServer() { WebServer webServer = this.webServer; if (webServer != null) { // 這裡如何啟動具體可看org.springframework.boot.web.embedded.tomcat.TomcatWebServer // 這裡之所以我們啟動了Spring Boot後程式還在運行是因為Tomcat啟動執行緒在後台運行 webServer.start(); } return webServer; }
org.springframework.boot.web.embedded.tomcat.TomcatWebServer public TomcatWebServer(Tomcat tomcat, boolean autoStart) { Assert.notNull(tomcat, "Tomcat Server must not be null"); this.tomcat = tomcat; this.autoStart = autoStart; initialize(); } private void initialize() throws WebServerException { //..... // Unlike Jetty, all Tomcat threads are daemon threads. We create a // blocking non-daemon to stop immediate shutdown // 大概意思是創建一個非守護執行緒來運行吧 startDaemonAwaitThread(); //.... } private void startDaemonAwaitThread() { Thread awaitThread = new Thread("container-" + (containerCounter.get())) { @Override public void run() { TomcatWebServer.this.tomcat.getServer().await(); } }; awaitThread.setContextClassLoader(getClass().getClassLoader()); awaitThread.setDaemon(false); awaitThread.start(); }
上面就大概說了Tomcat怎麼在Spring Boot啟動後載入創建的
2、Spring MVC在這裡怎麼工作的?
平時使用SSM開發時,使用Spring MVC 我們知道需要配置DispatcherServlet
,而這裡的是通過自動配置的,還對DispatcherServlet
通過ServletRegistrationBean
類進行封裝,這裡自動裝配的程式碼在org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration
中可見,程式碼如下:
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration.DispatcherServletConfiguration @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME) public DispatcherServlet dispatcherServlet() { DispatcherServlet dispatcherServlet = new DispatcherServlet(); dispatcherServlet.setDispatchOptionsRequest( this.webMvcProperties.isDispatchOptionsRequest()); dispatcherServlet.setDispatchTraceRequest( this.webMvcProperties.isDispatchTraceRequest()); dispatcherServlet.setThrowExceptionIfNoHandlerFound( this.webMvcProperties.isThrowExceptionIfNoHandlerFound()); return dispatcherServlet; } org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration.DispatcherServletRegistrationConfiguration /** * 這裡通過構造器獲取已經註冊的 DispatcherServlet Bean * 然後封裝在ServletRegistrationBean */ @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME) @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME) public ServletRegistrationBean<DispatcherServlet> dispatcherServletRegistration( DispatcherServlet dispatcherServlet) { ServletRegistrationBean<DispatcherServlet> registration = new ServletRegistrationBean<>( dispatcherServlet, this.serverProperties.getServlet().getServletMapping()); registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME); registration.setLoadOnStartup( this.webMvcProperties.getServlet().getLoadOnStartup()); if (this.multipartConfig != null) { registration.setMultipartConfig(this.multipartConfig); } return registration; }
封裝了Servlet的ServletRegistrationBean
各個Bean又會被ServletContextInitializerBeans
進行管理。
在上述第一點中Tomcat創建載入中有個方法沒有詳述,就是ServletWebServerApplicationContext
中的createWebServer
方法,裡面調用了一個getSelfInitializer
方法,使用返回值作為參數,程式碼如下:
org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext private void createWebServer() { //... this.webServer = factory.getWebServer(getSelfInitializer()); //... } /** * Returns the {@link ServletContextInitializer} that will be used to complete the * setup of this {@link WebApplicationContext}. * @return the self initializer * @see #prepareWebApplicationContext(ServletContext) */ private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() { // 又封裝成了ServletContextInitializer return this::selfInitialize; } private void selfInitialize(ServletContext servletContext) throws ServletException { //.... //getServletContextInitializerBeans這裡就能獲取到封裝了Servlet或Filter等的ServletContextInitializer for (ServletContextInitializer beans : getServletContextInitializerBeans()) { // 這裡通過回調Servlet或Filter能夠獲取到servletContext,並且將自己(Servlet或Filter)註冊到servletContext,這裡可能要去了解下Tomcat了 beans.onStartup(servletContext); } } /** * Returns {@link ServletContextInitializer}s that should be used with the embedded * web server. By default this method will first attempt to find * {@link ServletContextInitializer}, {@link Servlet}, {@link Filter} and certain * {@link EventListener} beans. * @return the servlet initializer beans */ protected Collection<ServletContextInitializer> getServletContextInitializerBeans() { // 這裡在new ServletContextInitializerBeans 時會將BeanFactory給它 // 它在構造器中將封裝了Servlet或Filter等的ServletContextInitializer子類獲取 // 並放入一個成員變數sortedList中 return new ServletContextInitializerBeans(getBeanFactory()); }
到此,大概就知道了Spring MVC中DispatcherServlet
是怎麼進入Tomcat的了,如果還想細究DispatcherServlet
是怎麼被一步步置入Tomcat容器中的,可以看下org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory#getWebServer
中的程式碼,這裡不展開了。
3、隨便嘮嗑
入職後好久沒運動,被同事拉去打羽毛球,結果,氣喘不上來,腦暈,感覺不好,第一天全身酸痛,第二天更痛。我認為,入職後第一條建議就是找時間運動。
這篇其實接著第一篇文章我的Spring Boot學習記錄(一):自動配置的大致調用過程 就想要寫的了,拖了好久。期間問自己,為什麼干這種無趣的東西。不論如何,還是抽了時間寫了,就這樣吧。
這裡有可能有個人的誤解,請抱著懷疑態度看。
建議: 感覺自己也會被繞暈,所以有興趣的使用IDE工具看下源碼