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 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容器启动原理》。