注意:本次自動配置原理基於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