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);
}

SpringBoot系列之嵌入式servlet容器自动配置原理_第1张图片

2. 嵌入式servlet容器(EmbeddedServletContainer)

SpringBoot系列之嵌入式servlet容器自动配置原理_第2张图片

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 getCustomizers() {
	if (this.customizers == null) {
		// Look up does not include the parent context
		this.customizers = new ArrayList(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,调用定制器的定制方法

限于篇幅关于嵌入式容器的启动原理请参考下一篇文章《SpringBoot系列之嵌入式servlet容器启动原理》。

 

 

你可能感兴趣的:(技术学习记录)