SpringBoot2.4.0中嵌入式servlet容器的自動配置以及啟動原理(自我理解)

舍其小夥伴發表於2020-12-14

1.前言

在 springboot1.x 版本中,通過EmbeddedServletContainerAutoConfiguration來定製嵌入式的servlet容器,如下所示。

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@Import(BeanPostProcessorsRegistrar.class)
//匯入BeanPostProcessorsRegistrar:給容器中匯入一些元件
//匯入了EmbeddedServletContainerCustomizerBeanPostProcessor
//後置處理器:bean初始化前後(建立完物件,還沒賦值賦值)執行初始化工作
public class EmbeddedServletContainerAutoConfiguration {

	@Configuration
	@ConditionalOnClass({ Servlet.class, Tomcat.class })//判斷當前是否引入了Tomcat依賴;
	@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
	//判斷當前容器沒有使用者自己定義EmbeddedServletContainerFactory:嵌入式的 Servlet容器工廠;作用:建立嵌入式的Servlet容器
	public static class EmbeddedTomcat {
		@Bean
		public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
			return new TomcatEmbeddedServletContainerFactory();
		}
	}
	... // undertow 和 jetty 的配置
}

在 springboot2.x 版本中,通過EmbeddedWebServerFactoryCustomizerAutoConfiguration自動建立對應的WebServerFactoryCustomizer來定製servlet容器,在此記錄一下在這個版本中的嵌入式Servlet容器自動配置原理以及啟動原理。

2.自動配置流程

2.1.分析部分原始碼
這裡記錄在 springboot2.x 版本中,嵌入式Servlet容器自動配置原理(以Tomcat為例)。

1.首先我們進入EmbeddedWebServerFactoryCustomizerAutoConfiguration的原始碼,發現 springboot2.4.0 支援四種servlet容器,從上到下分別為tomcat、jetty、undertow、netty。

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication
@EnableConfigurationProperties(ServerProperties.class)
public class EmbeddedWebServerFactoryCustomizerAutoConfiguration {

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass({ Tomcat.class, UpgradeProtocol.class })
	public static class TomcatWebServerFactoryCustomizerConfiguration {
		@Bean
		public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment,
				ServerProperties serverProperties) {
			return new TomcatWebServerFactoryCustomizer(environment, serverProperties);
		}
	}

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass({ Server.class, Loader.class, WebAppContext.class })
	public static class JettyWebServerFactoryCustomizerConfiguration {
		@Bean
		public JettyWebServerFactoryCustomizer jettyWebServerFactoryCustomizer(Environment environment,
				ServerProperties serverProperties) {
			return new JettyWebServerFactoryCustomizer(environment, serverProperties);
		}

	}


	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass({ Undertow.class, SslClientAuthMode.class })
	public static class UndertowWebServerFactoryCustomizerConfiguration {
		@Bean
		public UndertowWebServerFactoryCustomizer undertowWebServerFactoryCustomizer(Environment environment,
				ServerProperties serverProperties) {
			return new UndertowWebServerFactoryCustomizer(environment, serverProperties);
		}

	}

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(HttpServer.class)
	public static class NettyWebServerFactoryCustomizerConfiguration {
		@Bean
		public NettyWebServerFactoryCustomizer nettyWebServerFactoryCustomizer(Environment environment,
				ServerProperties serverProperties) {
			return new NettyWebServerFactoryCustomizer(environment, serverProperties);
		}

	}

}

2.本篇以tomcat為例,所以我們點進TomcatWebServerFactoryCustomizer(TomcatWeb伺服器工廠定製器)進行檢視。發現它應該是呼叫customize()定製介面,定製Servlet容器配置。

public class TomcatWebServerFactoryCustomizer implements WebServerFactoryCustomizer<ConfigurableTomcatWebServerFactory>, Ordered {
		
	private final ServerProperties serverProperties;

	@Override
	public void customize(ConfigurableTomcatWebServerFactory factory) {
		ServerProperties properties = this.serverProperties;
		ServerProperties.Tomcat tomcatProperties = properties.getTomcat();
		PropertyMapper propertyMapper = PropertyMapper.get();
		propertyMapper.from(tomcatProperties::getBasedir).whenNonNull().to(factory::setBaseDirectory);
		propertyMapper.from(tomcatProperties::getBackgroundProcessorDelay).whenNonNull().as(Duration::getSeconds)
				.as(Long::intValue).to(factory::setBackgroundProcessorDelay);
		customizeRemoteIpValve(factory);
		ServerProperties.Tomcat.Threads threadProperties = tomcatProperties.getThreads();
		propertyMapper.from(threadProperties::getMax).when(this::isPositive).to((maxThreads) -> customizeMaxThreads(factory, threadProperties.getMax()));
		...
		customizeStaticResources(factory);
		customizeErrorReportValve(properties.getError(), factory);
	}
}

此時我們大致瞭解,那我們就從入口開始分析。

2.2.執行程式時的流程

1.首先執行程式的main()方法,然後會呼叫run()方法。
在這裡插入圖片描述

2.執行run方法時,裡面會建立並初始化我們的IOC容器物件
在這裡插入圖片描述
3.在建立ApplicationContext的IOC容器物件時,在ServletWebServerApplicationContext有一個onRefresh()的方法,通過呼叫createWebServer()方法,建立WebServer。
在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述
4.來到createWebServer()方法,該方法通過裡面的getWebServerFactory()方法,通過名稱匹配最終能夠獲取到一個與當前應用(根據我們匯入的依賴servlet容器來獲取)所匯入的Servlet型別相匹配的web服務工廠,通過工廠就可以獲取到相應的 WebServerFactoryCustomizer (Web服務工廠定製器)

注:createWebServer()執行後,我們就到了 EmbeddedWebServerFactoryCustomizerAutoConfiguration,然後根據條件(配置的依賴)配置哪一個Web伺服器
在這裡插入圖片描述
在這裡插入圖片描述
5.我們點進factory.getWebServer()方法,找到了ServletWebServerFactory介面,因為我們前面繫結了tomcat,所以這裡會建立它的實現類TomcatServletWebServerFactory
在這裡插入圖片描述
到這裡位置,TomcatWebServerFactoryCustomizer元件建立完成,對應的服務配置類也已新增到IOC容器。
在這裡插入圖片描述
6.因為容器中某個元件要建立物件就會驚動後置處理器 然後就到WebServerFactoryCustomizerBeanPostProcessor(web服務工廠定製器元件的後置處理器),該類負責在bean元件初始化之前執行初始化工作。它先從IOC容器中獲取所有型別為WebServerFactoryCustomizerBeans(web服務工廠定製器的元件)
在這裡插入圖片描述
再通過後置處理器獲取到的TomcatWebServerFactoryCustomizer呼叫**customize()**定製方法,獲取到Servlet容器相關配置類ServerProperties,進行自動配置

	@Override
	public void customize(ConfigurableTomcatWebServerFactory factory) {
		ServerProperties properties = this.serverProperties;
		ServerProperties.Tomcat tomcatProperties = properties.getTomcat();
		PropertyMapper propertyMapper = PropertyMapper.get();
		propertyMapper.from(tomcatProperties::getBasedir).whenNonNull().to(factory::setBaseDirectory);
		propertyMapper.from(tomcatProperties::getBackgroundProcessorDelay).whenNonNull().as(Duration::getSeconds)
				.as(Long::intValue).to(factory::setBackgroundProcessorDelay);
		customizeRemoteIpValve(factory);
		ServerProperties.Tomcat.Threads threadProperties = tomcatProperties.getThreads();
		propertyMapper.from(threadProperties::getMax).when(this::isPositive)
				.to((maxThreads) -> customizeMaxThreads(factory, threadProperties.getMax()));
		propertyMapper.from(threadProperties::getMinSpare).when(this::isPositive)
				.to((minSpareThreads) -> customizeMinThreads(factory, minSpareThreads));
		propertyMapper.from(this.serverProperties.getMaxHttpHeaderSize()).whenNonNull().asInt(DataSize::toBytes)
				.when(this::isPositive)
				.to((maxHttpHeaderSize) -> customizeMaxHttpHeaderSize(factory, maxHttpHeaderSize));
		propertyMapper.from(tomcatProperties::getMaxSwallowSize).whenNonNull().asInt(DataSize::toBytes)
				.to((maxSwallowSize) -> customizeMaxSwallowSize(factory, maxSwallowSize));
		propertyMapper.from(tomcatProperties::getMaxHttpFormPostSize).asInt(DataSize::toBytes)
				.when((maxHttpFormPostSize) -> maxHttpFormPostSize != 0)
				.to((maxHttpFormPostSize) -> customizeMaxHttpFormPostSize(factory, maxHttpFormPostSize));
		propertyMapper.from(tomcatProperties::getAccesslog).when(ServerProperties.Tomcat.Accesslog::isEnabled)
				.to((enabled) -> customizeAccessLog(factory));
		propertyMapper.from(tomcatProperties::getUriEncoding).whenNonNull().to(factory::setUriEncoding);
		propertyMapper.from(tomcatProperties::getConnectionTimeout).whenNonNull()
				.to((connectionTimeout) -> customizeConnectionTimeout(factory, connectionTimeout));
		propertyMapper.from(tomcatProperties::getMaxConnections).when(this::isPositive)
				.to((maxConnections) -> customizeMaxConnections(factory, maxConnections));
		propertyMapper.from(tomcatProperties::getAcceptCount).when(this::isPositive)
				.to((acceptCount) -> customizeAcceptCount(factory, acceptCount));
		propertyMapper.from(tomcatProperties::getProcessorCache)
				.to((processorCache) -> customizeProcessorCache(factory, processorCache));
		propertyMapper.from(tomcatProperties::getRelaxedPathChars).as(this::joinCharacters).whenHasText()
				.to((relaxedChars) -> customizeRelaxedPathChars(factory, relaxedChars));
		propertyMapper.from(tomcatProperties::getRelaxedQueryChars).as(this::joinCharacters).whenHasText()
				.to((relaxedChars) -> customizeRelaxedQueryChars(factory, relaxedChars));
		customizeStaticResources(factory);
		customizeErrorReportValve(properties.getError(), factory);
	}

到這裡為止,嵌入式Servlet容器的自動配置完成。

綜上所述,我們有兩種解決方案用於配置嵌入式Servlet容器。
1.在全域性配置檔案application.properties中修改與server有關的配置

server.port=8090
server.tomcat.uri-encoding=UTF-8
server.tomcat.xxx…

2、實現WebServerFactoryCustomizer介面,重寫它的customize()方法,對容器進行定製配置:

@FunctionalInterface
public interface WebServerFactoryCustomizer<T extends WebServerFactory> {
    void customize(T factory);
}

3.嵌入式servlet容器啟動原理

1、應用啟動後,根據匯入的依賴資訊,建立了相應的Servlet容器工廠,建立了TomcatServletWebServerFactory,呼叫重寫的getWebServer()方法建立Tomcat容器。

public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware {

	...

	public WebServer getWebServer(ServletContextInitializer... initializers) {
        if (this.disableMBeanRegistry) {
            Registry.disableRegistry();
        }

        Tomcat tomcat = new Tomcat();
        File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
        tomcat.setBaseDir(baseDir.getAbsolutePath());
        Connector connector = new Connector(this.protocol);
        connector.setThrowOnFailure(true);
        tomcat.getService().addConnector(connector);
        this.customizeConnector(connector);
        tomcat.setConnector(connector);
        tomcat.getHost().setAutoDeploy(false);
        this.configureEngine(tomcat.getEngine());
        Iterator var5 = this.additionalTomcatConnectors.iterator();

        while(var5.hasNext()) {
            Connector additionalConnector = (Connector)var5.next();
            tomcat.getService().addConnector(additionalConnector);
        }

        this.prepareContext(tomcat.getHost(), initializers);
        return this.getTomcatWebServer(tomcat);
    }
    
...

}

2.找到這個方法的返回值中this.getTomcatWebServer(tomcat),繼續向下尋找該方法
在這裡插入圖片描述
3.進入TomcatWebServer檢視它的有參構造器,發現其中執行了一個initialize() 初始化方法,跟進檢視。
在這裡插入圖片描述
4.通過 this.tomcat.start(); tomcat啟動了。
在這裡插入圖片描述

相關文章