【springboot】自动整合Tomcat原理
通過前面我們會SpringBoot的自動配置機制、Starter機制、啟動過程的底層分析,我們拿一個實際的業務案例來串講一下,那就是SpringBoot和Tomcat的整合。
我們知道,只要我們的項目添加的starter為:spring-boot-starter-web,那么我們啟動項目時,SpringBoot就會自動啟動一個Tomcat。
那么這是怎么做到的呢?
自動配置類ServletWebServerFactoryAutoConfiguration
首先我們可以發現,在spring-boot-starter-web這個starter中,只是簡單的引入了spring-boot-starter-tomcat這個starter,這個spring-boot-starter-tomcat又引入了tomcat-embed-core依賴,所以只要我們項目中依賴了spring-boot-starter-web就相當于依賴了Tomcat。
然后在SpringBoot眾多的自動配置類中,有一個自動配置類叫做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 { // ... }首先看這個自動配置類所需要的條件:
在上面提到的spring-boot-starter-web中,其實還間接的引入了spring-web、spring-webmvc等依賴,這就使得第二個條件滿足,而對于第一個條件的ServletRequest類,雖然它是Servlet規范中的類,但是在我們所依賴的tomcat-embed-core這個jar包中是存在這個類的,這是因為Tomcat在自己的源碼中把Servlet規范中的一些代碼也包含進去了,比如:
這就使得ServletWebServerFactoryAutoConfiguration這個自動配置的兩個條件都符合,那么Spring就能去解析它,一解析它就發現這個自動配置類Import進來了三個類:
EmbeddedTomcat
很明顯,Import進來的這三個類應該是差不多,我們看EmbeddedTomcat這個類:
@Configuration(proxyBeanMethods = false) @ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class }) @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT) static class EmbeddedTomcat {@BeanTomcatServletWebServerFactory 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;}}可以發現這個類是一個配置類,所以Spring也會來解析它,不過它也有兩個條件:
所以,通常只要我們項目依賴中有Tomcat依賴,那就符合條件,那最終Spring容器中就會有TomcatServletWebServerFactory這個Bean。
對于另外的EmbeddedJetty和EmbeddedUndertow,也差不多,都是判斷項目依賴中是否有Jetty和Undertow的依賴,如果有,那么對應在Spring容器中就會存在JettyServletWebServerFactory類型的Bean、或者存在UndertowServletWebServerFactory類型的Bean。
總結一下:
那么SpringBoot給我們配置的這幾個Bean到底有什么用呢?
TomcatServletWebServerFactory
我們前面說到,TomcatServletWebServerFactory實現了ServletWebServerFactory這個接口,這個接口的定義為:
@FunctionalInterface public interface ServletWebServerFactory {WebServer getWebServer(ServletContextInitializer... initializers); }public interface WebServer {void start() throws WebServerException;void stop() throws WebServerException;int getPort(); }我們發現ServletWebServerFactory其實就是用來獲得WebServer對象的,而WebServer擁有啟動、停止、獲取端口等方法,那么很自然,我們就發現WebServer其實指的就是Tomcat、Jetty、Undertow,而TomcatServletWebServerFactory就是用來生成Tomcat所對應的WebServer對象,具體一點就是TomcatWebServer對象,并且在生成TomcatWebServer對象時會把Tomcat給啟動起來,在源碼中,調用TomcatServletWebServerFactory對象的getWebServer()方法時就會啟動Tomcat。
我們再來看TomcatServletWebServerFactory這個Bean的定義:
@Bean 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; }要構造這個Bean,Spring會從Spring容器中獲取到TomcatConnectorCustomizer、TomcatContextCustomizer、TomcatProtocolHandlerCustomizer這三個類型的Bean,然后把它們添加到TomcatServletWebServerFactory對象中去,很明顯這三種Bean是用來配置Tomcat的,比如:
也就是我們可以通過定義TomcatConnectorCustomizer類型的Bean,來對Tomcat進行配置,比如:
package com.morris.spring.boot;import org.apache.catalina.connector.Connector; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer; import org.springframework.context.annotation.Bean;@SpringBootApplication public class SpringBootDemoApplication {@Beanpublic TomcatConnectorCustomizer tomcatConnectorCustomizer(){return new TomcatConnectorCustomizer() {@Overridepublic void customize(Connector connector) {connector.setPort(8888);}};}public static void main(String[] args) {SpringApplication.run(SpringBootDemoApplication.class, args);}}這樣Tomcat就會綁定8888這個端口。
ServletWebServerFactory的調用
有了TomcatServletWebServerFactory這個Bean之后,在SpringBoot的啟動過程中,會執行ServletWebServerApplicationContext的onRefresh()方法,而這個方法會調用createWebServer()方法:
private void createWebServer() {WebServer webServer = this.webServer;ServletContext servletContext = getServletContext();if (webServer == null && servletContext == null) {StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");// 獲取TomcatServletWebServerFactoryServletWebServerFactory factory = getWebServerFactory();createWebServer.tag("factory", factory.getClass().toString());// 啟動tomcatthis.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(); }很明顯,getWebServerFactory()負責獲取具體的ServletWebServerFactory對象,要么是TomcatServletWebServerFactory對象,要么是JettyServletWebServerFactory對象,要么是UndertowServletWebServerFactory對象,注意只能獲取到一個,然后調用該對象的getWebServer方法,啟動對應的Tomcat、或者Jetty、或者Undertow。
getWebServerFactory方法中的邏輯比較簡單,獲取Spring容器中的ServletWebServerFactory類型的Bean對象,如果沒有獲取到則拋異常,如果找到多個也拋異常,也就是在Spring容器中只能有一個ServletWebServerFactory類型的Bean對象。
拿到TomcatServletWebServerFactory對象后,就調用它的getWebServer方法,而在這個方法中就會生成一個Tomcat對象,并且利用前面的TomcatConnectorCustomizer等等會Tomcat對象進行配置,最后啟動Tomcat。
這樣在啟動應用時就完成了Tomcat的啟動,到此我們通過這個案例也看到了具體的Starter機制、自動配置的具體使用。
將DispatherServlet和Filter注入到Web容器
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {return this::selfInitialize; }private void selfInitialize(ServletContext servletContext) throws ServletException {// 將servletContext與spring容器關聯prepareWebApplicationContext(servletContext);registerApplicationScope(servletContext);// 將servletContext的初始化參數注入到spring容器中WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);for (ServletContextInitializer beans : getServletContextInitializerBeans()) {/*** DispatcherServletRegistrationBean是在DispatcherServletAutoConfiguration中注入到spring中** DispatcherServletRegistrationBean負責將DispatcherServlet注入到tomcat* FilterRegistrationBean負責將Filter注入到tomcat** @see org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean* @see org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean* @see FilterRegistrationBean*/beans.onStartup(servletContext);} }ServletWebServerFactoryAutoConfiguration
我們前面提到了我們可以利用TomcatConnectorCustomizer對Tomca中的Connector組件進行配置,我們可能會想到默認情況下,SpringBoot是不是就是提供了一個TomcatConnectorCustomizer的Bean,然后給Connector配置了8080端口,或者從Environment對象中獲取server.port配置,并設置到Connector中去呢?
并不是,因為如果SpringBoot這么實現,那么默認就得提供三個ConnectorCustomizer的Bean,一個TomcatConnectorCustomizer、一個JettyConnectorCustomizer、一個UndertowConnectorCustomizer,這是比較不恰當的,我們知道默認情況下,不管我們是用Tomcat,還是Jetty,還是Undertow,它們啟動時綁定的都是8080端口,也就是說SpringBoot并不會根據不同的WebServer設置不同的端口,也就是說SpringBoot只會給WebServer設置端口,而不會區分WebServer的不同實現。
所以在自動配置類ServletWebServerFactoryAutoConfiguration中,會定義一個ServletWebServerFactoryCustomizer類型的Bean,定義為:
@Bean public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties,ObjectProvider<WebListenerRegistrar> webListenerRegistrars,ObjectProvider<CookieSameSiteSupplier> cookieSameSiteSuppliers) {return new ServletWebServerFactoryCustomizer(serverProperties,webListenerRegistrars.orderedStream().collect(Collectors.toList()),cookieSameSiteSuppliers.orderedStream().collect(Collectors.toList())); }這個Bean會接收一個ServerProperties的Bean,ServerProperties的Bean對應的就是properties文件中前綴為server的配置,我們可以利用ServerProperties對象的getPort方法獲取到我們所配置的server.port的值。
而ServletWebServerFactoryCustomizer是針對一個ServletWebServerFactory的自定義器,也就是用來配置TomcatServletWebServerFactory這個Bean的,到時候ServletWebServerFactoryCustomizer就會利用ServerProperties對象來對TomcatServletWebServerFactory對象進行設置。
在ServletWebServerFactoryAutoConfiguration這個自動配置上,除開Import了EmbeddedTomcat、EmbeddedJetty、EmbeddedUndertow這三個配置類,還Import了一個ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,這個BeanPostProcessorsRegistrar會向Spring容器中注冊一個WebServerFactoryCustomizerBeanPostProcessor類型的Bean。
WebServerFactoryCustomizerBeanPostProcessor是一個BeanPostProcessor,它專門用來處理類型為WebServerFactory的Bean對象,而我們的TomcatServletWebServerFactory、JettyServletWebServerFactory、UndertowServletWebServerFactory也都實現了這個接口,所以不管當前項目依賴的情況,只要在Spring在創建比如TomcatServletWebServerFactory這個Bean時,WebServerFactoryCustomizerBeanPostProcessor就會對它進行處理,處理的邏輯為:
org.springframework.boot.web.server.WebServerFactoryCustomizerBeanPostProcessor#postProcessBeforeInitialization(org.springframework.boot.web.server.WebServerFactory)
@SuppressWarnings("unchecked") private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {LambdaSafe.callbacks(WebServerFactoryCustomizer.class, getCustomizers(), webServerFactory).withLogger(WebServerFactoryCustomizerBeanPostProcessor.class).invoke((customizer) -> customizer.customize(webServerFactory)); }private Collection<WebServerFactoryCustomizer<?>> getCustomizers() {if (this.customizers == null) {// Look up does not include the parent contextthis.customizers = new ArrayList<>(getWebServerFactoryCustomizerBeans());this.customizers.sort(AnnotationAwareOrderComparator.INSTANCE);this.customizers = Collections.unmodifiableList(this.customizers);}return this.customizers; }調用ServletWebServerFactoryCustomizer對象的customize方法:
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryCustomizer#customize
@Override public void customize(ConfigurableServletWebServerFactory factory) {PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();map.from(this.serverProperties::getPort).to(factory::setPort);map.from(this.serverProperties::getAddress).to(factory::setAddress);map.from(this.serverProperties.getServlet()::getContextPath).to(factory::setContextPath);map.from(this.serverProperties.getServlet()::getApplicationDisplayName).to(factory::setDisplayName);map.from(this.serverProperties.getServlet()::isRegisterDefaultServlet).to(factory::setRegisterDefaultServlet);map.from(this.serverProperties.getServlet()::getSession).to(factory::setSession);map.from(this.serverProperties::getSsl).to(factory::setSsl);map.from(this.serverProperties.getServlet()::getJsp).to(factory::setJsp);map.from(this.serverProperties::getCompression).to(factory::setCompression);map.from(this.serverProperties::getHttp2).to(factory::setHttp2);map.from(this.serverProperties::getServerHeader).to(factory::setServerHeader);map.from(this.serverProperties.getServlet()::getContextParameters).to(factory::setInitParameters);map.from(this.serverProperties.getShutdown()).to(factory::setShutdown);for (WebListenerRegistrar registrar : this.webListenerRegistrars) {registrar.register(factory);}if (!CollectionUtils.isEmpty(this.cookieSameSiteSuppliers)) {factory.setCookieSameSiteSuppliers(this.cookieSameSiteSuppliers);} }這樣當TomcatServletWebServerFactory這個Bean對象創建完成后,它里面的很多屬性,比如port,就已經是程序員所配置的值了,后續執行getWebServer方法時,就直接獲取自己的屬性,比如port屬性,設置給Tomcat,然后再利用TomcatConnectorCustomizer等進行處理,最后啟動Tomcat。
到此,SpringBoot整合Tomcat的核心原理就分析完了,主要涉及的東西有:
總結
以上是生活随笔為你收集整理的【springboot】自动整合Tomcat原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: html5之web worker
- 下一篇: npm 下载第三方包