SpringBoot系列之嵌入式servlet容器自動配置原理
嵌入式servlet容器自動配置原理
springboot中存在大量的自動配置類,瞭解它的工作原理有助於加深對程式碼的理解,實現自定義配置的修改,同時也方便以後借鑑其設計模式;本文通過研究EmbeddedServletContainerAutoConfiguration:嵌入式的Servlet容器自動配置類,並分析其中一種常用的Servlet容器,來理解嵌入式servlet容器自動配置的原理。
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) @Configuration @ConditionalOnWebApplication // 在web應用下才會生效 @Import(BeanPostProcessorsRegistrar.class) // 匯入後置處理器元件 public class EmbeddedServletContainerAutoConfiguration { /** * Nested configuration if Tomcat is being used. */ @Configuration // 判斷當前是否引入tomcat依賴 // 按照此原理我們可以通過排除tomcat依賴,並引入jetty或undertow依賴,達到切換嵌入式容器的目的 @ConditionalOnClass({ Servlet.class, Tomcat.class }) // 判斷當前容器中沒有使用者自己定義的嵌入式servlet容器工廠:EmbeddedServletContainerFactory;作用:建立嵌入式的servlet容器 @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT) public static class EmbeddedTomcat { @Bean public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() { return new TomcatEmbeddedServletContainerFactory(); } }
1. 嵌入式servlet容器工廠(EmbeddedServletContainerFactory)
檢視繼承關係發現springboot已經為我們配置好了三個容器工廠,按照此原理我們可以通過排除tomcat依賴,並引入jetty或undertow依賴,就可以達到切換嵌入式容器的目的了。
public interface EmbeddedServletContainerFactory { // 獲取嵌入式的servlet容器 EmbeddedServletContainer getEmbeddedServletContainer( ServletContextInitializer... initializers); }
2. 嵌入式servlet容器(EmbeddedServletContainer)
3. 以TomcatEmbeddedServletContainerFactory為例
@Override public EmbeddedServletContainer getEmbeddedServletContainer( ServletContextInitializer... initializers) { // 建立一個Tomcat Tomcat tomcat = new Tomcat(); // 配置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); // 傳入配置好的Tomcat ,返回EmbeddedServletContainer,並且啟動tomcat return getTomcatEmbeddedServletContainer(tomcat); } // 以下程式碼為輔助說明,無需仔細閱讀 protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer( Tomcat tomcat) { // port大於0就設定為自動啟動,即配置的server.port或EmbeddedServletContainerCustomizer中定義的埠號 return new TomcatEmbeddedServletContainer(tomcat, getPort() >= 0); } public TomcatEmbeddedServletContainer(Tomcat tomcat, boolean autoStart) { Assert.notNull(tomcat, "Tomcat Server must not be null"); this.tomcat = tomcat; this.autoStart = autoStart; // 完成以下初始化動作並啟動 initialize(); } 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(); // 關鍵點,啟動tomcat伺服器;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 i s 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); } } }
4. 嵌入式容器的配置
4.1 配置修改方式(ServerProperties、EmbeddedServletContainerCustomizer)
// 1)通過修改以server為字首的配置,可以實現嵌入式容器配置的修改
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties
implements EmbeddedServletContainerCustomizer, EnvironmentAware, Ordered {
// 2)通過實現EmbeddedServletContainerCustomizer介面來定製servlet容器引數
@Configuration
public class ServerConfigure {
/**
* 嵌入式serverlet容器規則訂製
* ServerProperties implements EmbeddedServletContainerCustomizer
* 其實寫配置修改servlet容器引數的原理和自定義EmbeddedServletContainerCustomizer一樣
*/
@Bean
public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer() {
return (ConfigurableEmbeddedServletContainer container) -> {
container.setPort(80);
};
}
ServerProperties也實現了EmbeddedServletContainerCustomizer介面,使我們可以通過修改配置來簡單的實現引數修改。由此可見,嵌入式servlet容器的引數配置,最終是通過EmbeddedServletContainerCustomizer定製器實現的。
4.2 配置如何生效
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
// 匯入後置處理器的註冊器BeanPostProcessorsRegistrar:給容器中倒入元件
// 匯入了EmbeddedServletContainerCustomizerBeanPostProcessor
// 後置處理器功能:bean初始化前後(建立完物件,尚未初始化)執行初始化工作
@Import(BeanPostProcessorsRegistrar.class)
public class EmbeddedServletContainerAutoConfiguration {
匯入了EmbeddedServletContainerCustomizerBeanPostProcessor
// 初始化之前
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// 如果當前初始化的是ConfigurableEmbeddedServletContainer型別的元件
if (bean instanceof ConfigurableEmbeddedServletContainer) {
postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean);
}
return bean;
}
// 獲取所有的定製器,呼叫每一個定製器的customize方法給servlet容器賦值
private void postProcessBeforeInitialization(ConfigurableEmbeddedServletContainer bean) {
for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) {
customizer.customize(bean);
}
}
private Collection<EmbeddedServletContainerCustomizer> getCustomizers() {
if (this.customizers == null) {
// Look up does not include the parent context
this.customizers = new ArrayList<EmbeddedServletContainerCustomizer>(this.beanFactory
// 從容器中獲取所有EmbeddedServletContainerCustomizer型別的元件
// 所以如果想定製servlet容器,給IOC容器中新增一個EmbeddedServletContainerCustomizer元件即可
// 值得一提的是ServerProperties也是定製器
.getBeansOfType(EmbeddedServletContainerCustomizer.class,false, false).values());
Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE);
this.customizers = Collections.unmodifiableList(this.customizers);
}
return this.customizers;
}
綜上可見嵌入式servlet容器自動配置原理步驟:
1)、SpringBoot根據匯入的依賴情況,給容器中新增相應的
EmbeddedServletContainerFactory【TomcatEmbeddedServletContainerFactory】
2)、容器中某個元件要建立物件就會驚動後置處理器;
EmbeddedServletContainerCustomizerBeanPostProcessor;
只要是嵌入式的Servlet容器工廠,後置處理器就工作;
3)、後置處理器,從容器中獲取所有的EmbeddedServletContainerCustomizer,呼叫定製器的定製方法