1. 程式人生 > >SpringBoot系列之嵌入式servlet容器自動配置原理

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,呼叫定製器的定製方法