作为 Spring Boot 的核心特性之一,内嵌 Web 容器的自动配置机制让开发者能够快速启动和运行 Web 应用。本文将深入剖析 ServletWebServerApplicationContext 的工作机制,带你理解 Spring Boot 如何实现"开箱即用"的 Web 开发体验。
Spring Boot 选择内嵌 Web 容器而非传统的外部部署方式,主要基于以下几个设计考量:
提示:虽然内嵌容器带来了诸多便利,但在高并发生产环境中,仍建议通过性能调优和适当的 JVM 参数配置来确保稳定性。
让我们先来看下内嵌容器实现的核心类结构:
code复制AbstractApplicationContext
└── GenericApplicationContext
└── AbstractRefreshableConfigApplicationContext
└── AbstractRefreshableWebApplicationContext
└── ServletWebServerApplicationContext
└── AnnotationConfigServletWebServerApplicationContext
在这个继承体系中,ServletWebServerApplicationContext 是关键实现类,它主要承担以下职责:
Spring Boot 应用的启动过程可以概括为以下几个关键阶段:
refresh() 方法是 Spring 容器初始化的核心入口,它定义了一套标准化的容器刷新流程:
java复制@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 准备刷新上下文
prepareRefresh();
// 初始化 BeanFactory
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 准备 BeanFactory
prepareBeanFactory(beanFactory);
try {
// 允许后置处理 BeanFactory
postProcessBeanFactory(beanFactory);
// 调用 BeanFactory 后置处理器
invokeBeanFactoryPostProcessors(beanFactory);
// 注册 Bean 后置处理器
registerBeanPostProcessors(beanFactory);
// 初始化消息源
initMessageSource();
// 初始化应用事件广播器
initApplicationEventMulticaster();
// 初始化特殊 Bean
onRefresh();
// 注册监听器
registerListeners();
// 完成 BeanFactory 初始化
finishBeanFactoryInitialization(beanFactory);
// 完成刷新
finishRefresh();
} catch (BeansException ex) {
// 处理异常...
}
}
}
在这 12 个标准步骤中,onRefresh() 和 finishRefresh() 是 Web 容器初始化的关键扩展点。
ServletWebServerApplicationContext 通过重写 onRefresh() 方法,将 Web 容器的创建逻辑嵌入到标准刷新流程中:
java复制@Override
protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
} catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
这种设计体现了模板方法模式的应用,父类定义算法骨架,子类实现特定步骤的扩展。
createWebServer() 是 Web 容器创建的核心方法,其主要逻辑包括:
java复制private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
// 1. 获取 Web 服务器工厂
ServletWebServerFactory factory = getWebServerFactory();
// 2. 创建 Web 服务器
this.webServer = factory.getWebServer(getSelfInitializer());
} else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
} catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context", ex);
}
}
initPropertySources();
}
getWebServerFactory() 方法从 Spring 容器中获取 ServletWebServerFactory 实例:
java复制protected ServletWebServerFactory getWebServerFactory() {
String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
if (beanNames.length == 0) {
throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing "
+ "ServletWebServerFactory bean.");
}
if (beanNames.length > 1) {
throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple "
+ "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
}
return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}
Spring Boot 通过自动配置机制提供了 Tomcat、Jetty 和 Undertow 三种 Web 服务器工厂的实现。开发者可以通过引入不同的 starter 依赖来选择使用的服务器类型:
以 Tomcat 为例,TomcatServletWebServerFactory 的 getWebServer() 方法实现了 Tomcat 服务器的创建和配置:
java复制@Override
public WebServer getWebServer(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);
connector.setThrowOnFailure(true);
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 getTomcatWebServer(tomcat);
}
这个方法完成了 Tomcat 实例的基本配置,包括:
getSelfInitializer() 方法返回一个 ServletContextInitializer,用于将 Spring 的 DispatcherServlet 和其他 Web 组件注册到 Servlet 容器中:
java复制private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
return this::selfInitialize;
}
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerWebApplicationScopes(getBeanFactory(), servletContext);
// 注册所有 ServletContextInitializer
ServletContextInitializerBeans initializers = new ServletContextInitializerBeans(getBeanFactory());
for (ServletContextInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
这种机制替代了传统的 web.xml 配置方式,实现了基于 Java 的 Servlet 组件注册。
在 finishRefresh() 阶段,Web 服务器被正式启动:
java复制@Override
protected void finishRefresh() {
super.finishRefresh();
WebServer webServer = startWebServer();
if (webServer != null) {
publishEvent(new ServletWebServerInitializedEvent(webServer, this));
}
}
startWebServer() 方法会调用 WebServer 的 start() 方法,使服务器开始监听配置的端口。同时,它会发布 ServletWebServerInitializedEvent 事件,通知监听器 Web 服务器已经就绪。
除了通过 SpringApplication 启动应用外,我们还可以直接使用 AnnotationConfigServletWebServerApplicationContext 以编程方式启动 Web 应用:
java复制public class ProgrammaticWebApp {
public static void main(String[] args) {
// 1. 创建上下文
AnnotationConfigServletWebServerApplicationContext context =
new AnnotationConfigServletWebServerApplicationContext();
// 2. 注册配置类
context.register(WebConfig.class);
// 3. 刷新容器(触发 WebServer 创建与启动)
context.refresh();
System.out.println("Server started on port: " +
context.getWebServer().getPort());
// 4. 优雅关闭
Runtime.getRuntime().addShutdownHook(new Thread(context::close));
}
}
@Configuration
@EnableAutoConfiguration
@ComponentScan
public class WebConfig {
@Bean
public RouterFunction<ServerResponse> routes() {
return route(GET("/hello"), req -> ok().bodyValue("Hello from embedded server"));
}
}
这种启动方式特别适合以下场景:
注意:编程式启动时,需要确保正确管理应用上下文的生命周期,避免资源泄漏。
问题现象:
code复制ApplicationContextException: Unable to start web server
Caused by: IllegalArgumentException: No servlet or filter mapping specified
排查步骤:
解决方案:
xml复制<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
问题现象:
code复制WebServerException: Unable to start embedded Tomcat
Caused by: java.net.BindException: Address already in use
解决方案:
yaml复制server:
port: 8081
java复制@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class MyWebTest { ... }
问题原因:
正确注册方式:
java复制@Bean
public FilterRegistrationBean<MyFilter> myFilter() {
FilterRegistrationBean<MyFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new MyFilter());
registration.addUrlPatterns("/api/*");
registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
return registration;
}
注意事项:
问题现象:
code复制WebApplicationContextUtils.getWebApplicationContext(servletContext) 返回 null
解决方案:
java复制context.setServletContext(servletContext);
java复制@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> tomcatCustomizer() {
return factory -> {
factory.setPort(9090);
factory.addConnectorCustomizers(connector -> {
connector.setProperty("relaxedQueryChars", "|{}[]");
});
};
}
java复制@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class MyIntegrationTest { ... }
java复制@Bean
public ServletWebServerFactory servletContainer() {
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
tomcat.addAdditionalTomcatConnectors(createSslConnector());
return tomcat;
}
private Connector createSslConnector() {
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
connector.setScheme("https");
connector.setSecure(true);
connector.setPort(8443);
protocol.setSSLEnabled(true);
// 配置 SSL 相关参数
return connector;
}
java复制@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> tomcatCustomizer() {
return factory -> factory.addConnectorCustomizers(connector -> {
ProtocolHandler handler = connector.getProtocolHandler();
if (handler instanceof AbstractProtocol) {
AbstractProtocol<?> protocol = (AbstractProtocol<?>) handler;
protocol.setMaxThreads(200);
protocol.setMinSpareThreads(20);
protocol.setConnectionTimeout(5000);
}
});
}
java复制@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> accessLogCustomizer() {
return factory -> factory.addContextCustomizers(context -> {
AccessLogValve accessLogValve = new AccessLogValve();
accessLogValve.setPattern("%h %l %u %t \"%r\" %s %b");
accessLogValve.setDirectory("logs");
accessLogValve.setPrefix("access_log");
accessLogValve.setSuffix(".log");
context.getPipeline().addValve(accessLogValve);
});
}
生命周期管理:
初始化顺序:
Servlet 栈限制:
要深入理解内嵌容器的启动过程,可以在以下关键位置设置断点:
ServletWebServerApplicationContext.onRefresh()ServletWebServerApplicationContext.createWebServer()TomcatServletWebServerFactory.getWebServer()ServletWebServerApplicationContext.finishRefresh()使用 --debug 参数启动应用,可以查看自动配置报告:
code复制java -jar your-application.jar --debug
在报告中搜索与 Web 服务器相关的配置类:
ServletWebServerFactoryAutoConfigurationDispatcherServletAutoConfigurationEmbeddedTomcat/EmbeddedJetty/EmbeddedUndertow要了解 Servlet、Filter 等组件如何被注册到容器中,可以追踪:
ServletContextInitializerBeans 的初始化过程DispatcherServletRegistrationBean 的创建和配置FilterRegistrationBean 的处理流程通过理解 ServletWebServerApplicationContext 的工作原理,开发者可以更灵活地定制 Spring Boot 的 Web 容器行为,也能更高效地排查启动和运行时的各种问题。这种深入的理解对于构建高性能、高可靠性的 Web 应用至关重要。