四、嵌入式Servlet容器自動配置原理

注意:本次自動配置原理基於SpringBoot 1.X版本,其中部分類在2.X版本有所變化,但是具體的流程和原理都是相似的,重要的是觀察原理

(一)Servlet容器啓動過程

org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration

@Configuration
	@ConditionalOnClass({ Servlet.class, Tomcat.class })  
	@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
	public static class EmbeddedTomcat {

		@Bean
		public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
			return new TomcatEmbeddedServletContainerFactory();
		}

	}

其中@Configuration表示如下是一個JAVA配置
@ConditionalOnClass({ Servlet.class, Tomcat.class }) 判斷是否引入了有Servlet依賴Tomcat依賴
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT) 判斷當前容器中沒有用戶自己定義的嵌入式容器工廠(創建嵌入式的Servlet),如果沒有就啓用下面的配置

嵌入式的容器工廠有如下幾個(1.X版本):
進入EmbeddedServletContainerFactory,按下CTRL+H觀察右側可以發現共有三個不同的容器工廠

嵌入式的容器工廠有如下幾個(1.X版本):
進入EmbeddedServletContainerFactory,按下CTRL+H觀察右側可以發現共有三個不同的容器工廠

在這裏插入圖片描述
上面幾個不同的工廠也對應了其容器,EmbeddedServletContainer

public interface EmbeddedServletContainer {
/**
	 * Starts the embedded servlet container. Calling this method on an already started
	 * container has no effect.
	 * @throws EmbeddedServletContainerException if the container cannot be started
	 */
	void start() throws EmbeddedServletContainerException;

	/**
	 * Stops the embedded servlet container. Calling this method on an already stopped
	 * container has no effect.
	 * @throws EmbeddedServletContainerException if the container cannot be stopped
	 */
	void stop() throws EmbeddedServletContainerException;

	/**
	 * Return the port this server is listening on.
	 * @return the port (or -1 if none)
	 */
	int getPort();}

在這裏插入圖片描述
根據上面的觀察,我們就能夠明白,SpringBoot就是根據我們依賴文件的不同去啓動不同的工廠和容器,默認情況下依賴tomcat,因此我們啓動的時候使用的是tomcat的factory,啓動的容器也就是tomcat容器

接下來主要看一下tomcat容器的啓動過程:

public class EmbeddedServletContainerAutoConfiguration {

	/**
	 * Nested configuration if Tomcat is being used.
	 */
	@Configuration
	@ConditionalOnClass({ Servlet.class, Tomcat.class })
	@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
	public static class EmbeddedTomcat {

		@Bean
		public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
			return new TomcatEmbeddedServletContainerFactory();
		}

	}

這裏初始化了TomcatEmbeddedServletContainerFactory()類,進入這個類後我們看下下面這個方法

@Override
	public EmbeddedServletContainer getEmbeddedServletContainer(
			ServletContextInitializer... initializers) {
		Tomcat tomcat = new Tomcat();
		File baseDir = (this.baseDirectory != null ? this.baseDirectory
				: createTempDir("tomcat"));
		tomcat.setBaseDir(baseDir.getAbsolutePath());
		Connector connector = new Connector(this.protocol);
		tomcat.getService().addConnector(connector);
		customizeConnector(connector);
		tomcat.setConnector(connector);
		tomcat.getHost().setAutoDeploy(false);
		configureEngine(tomcat.getEngine());
		for (Connector additionalConnector : this.additionalTomcatConnectors) {
			tomcat.getService().addConnector(additionalConnector);
		}
		prepareContext(tomcat.getHost(), initializers);
		return getTomcatEmbeddedServletContainer(tomcat);
	}

這裏面首先 new Tomcat();創建了一個tomcat實例,設置了項目路徑,設置了連接器,配置了引擎,完成上述工作後調用了getTomcatEmbeddedServletContainer

protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
			Tomcat tomcat) {
		return new TomcatEmbeddedServletContainer(tomcat, getPort() >= 0);
	}

將前面的tomcat對象傳遞過來後,這裏面只做了一個端口是否大於等於0的判斷,接下來就是調用了initialize方法,在這個方法中this.tomcat.start();啓動了傳入的tomcat容器

private void initialize() throws EmbeddedServletContainerException {
		TomcatEmbeddedServletContainer.logger
				.info("Tomcat initialized with port(s): " + getPortsDescription(false));
		synchronized (this.monitor) {
			try {
				addInstanceIdToEngineName();
				try {
					// Remove service connectors to that protocol binding doesn't happen
					// yet
					removeServiceConnectors();

					// Start the server to trigger initialization listeners
					this.tomcat.start();

					// We can re-throw failure exception directly in the main thread
					rethrowDeferredStartupExceptions();

					Context context = findContext();
					try {
						ContextBindings.bindClassLoader(context, getNamingToken(context),
								getClass().getClassLoader());
					}
					catch (NamingException ex) {
						// Naming is not enabled. Continue
					}

					// Unlike Jetty, all Tomcat threads are daemon threads. We create a
					// blocking non-daemon to stop immediate shutdown
					startDaemonAwaitThread();
				}
				catch (Exception ex) {
					containerCounter.decrementAndGet();
					throw ex;
				}
			}
			catch (Exception ex) {
				throw new EmbeddedServletContainerException(
						"Unable to start embedded Tomcat", ex);
			}
		}
	}
(二)嵌入式容器的配置修改如何生效

對於嵌入式的Servlet容器修改主要有倆種方式:
1:配置文件修改,對應於ServerProperties
2:自定義的定製器:EmbeddedServletContainerCustomizer

具體流程:

1:SpringBoot根據導入的依賴情況,給容器中添加相應的嵌入式Servlet工廠,EmbeddedServletContainerFactory

2:容器中某個組件要創建對象就會觸發後置處理器
EmbeddedServletContainerCustomizerBeanPostProcessor,只要是嵌入式的Servlet容器工廠,後置處理器都會處理

3:後置處理器,從容器中獲取所有的EmbeddedServletContainerCustomizer,調用定製器的定製方法customize

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章