云计算百科
云计算领域专业知识百科平台

Spring Boot Starter Web 原理分析:从依赖到内嵌服务器的完整启动流程

Spring Boot 的 spring-boot-starter-web 是绝大多数 Java Web 开发者接触的第一个 Starter。一个注解、一个 main 方法,一个完整的 Web 服务器就跑起来了——DispatcherServlet 自动注册好了,Tomcat 内嵌启动好了,连 JSON 转换器都准备就绪。

这篇文章将从源码层面剖析这一切是如何发生的。


一、整体架构:starter-web 的核心四层

spring-boot-starter-web 的设计遵循"开箱即用"的理念,其核心能力分布在四个层次:

层次载体职责
依赖管理层 spring-boot-starter-web pom 聚合所有必需依赖,统一版本
自动配置层 spring-boot-autoconfigure 提供条件化的配置类
容器抽象层 spring-boot 中的 WebServer 接口 抽象 Tomcat/Jetty/Undertow
运行时层 实际创建的内嵌容器实例 监听端口、处理请求

下面从最外层的依赖开始,逐层深入。


二、第一层:起步依赖——Starter 如何"聚合"一切

2.1 一个依赖,全家桶到位

在 pom.xml 中只需添加:

xml

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

这个依赖本身没有一行 Java 代码,它是一个纯粹的依赖聚合器。打开它的 pom 文件,会发现它引入了:

  • spring-boot-starter——核心场景启动器(包含自动配置基础)

  • spring-boot-starter-tomcat——默认内嵌容器

  • spring-web 和 spring-webmvc——Spring MVC 框架

  • spring-boot-starter-json——Jackson JSON 处理

这种设计带来的好处是:版本由 Spring Boot 父项目统一仲裁,开发者无需关心版本兼容性。

2.2 核心链路的依赖传递

text

spring-boot-starter-web
├── spring-boot-starter
│ └── spring-boot-autoconfigure ← 自动配置核心包
├── spring-boot-starter-tomcat
│ └── tomcat-embed-core ← 内嵌 Tomcat
├── spring-webmvc ← Spring MVC
└── spring-boot-starter-json
└── jackson-databind ← JSON 支持

关键在于 spring-boot-autoconfigure——它包含了 Spring Boot 官方提供的所有自动配置类(约 100+ 个),其中就包括 Web MVC 和嵌入式容器的配置。


三、第二层:自动配置——条件化装配的核心机制

3.1 @SpringBootApplication 的"三合一"身份

主程序类的 @SpringBootApplication 是一个组合注解:

java

@SpringBootConfiguration // 本质是 @Configuration
@EnableAutoConfiguration // 开启自动配置 ← 核心
@ComponentScan // 组件扫描
public @interface SpringBootApplication {}

其中最关键的是 @EnableAutoConfiguration。它通过 @Import(AutoConfigurationImportSelector.class),在启动时会批量导入自动配置类。

3.2 spring.factories 与 AutoConfiguration.imports

Spring Boot 2.7 之后,自动配置类的声明从 META-INF/spring.factories 迁移到了 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports。

这个文件里列出了所有候选配置类的全限定名:

text

org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration

Spring Boot 启动时会读取这个文件,将列出的类全部加载——注意是"加载到内存",而非"直接生效"。是否真正生效,取决于类上的条件注解。

3.3 条件注解:按需生效的"开关"

以 WebMvcAutoConfiguration 为例:

java

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
@ConditionalOnWebApplication(type = Type.SERVLET)
@AutoConfigureAfter(DispatcherServletAutoConfiguration.class)
public class WebMvcAutoConfiguration {
// 配置 RequestMappingHandlerMapping、RequestMappingHandlerAdapter 等
}

这些条件注解的含义:

  • @ConditionalOnClass(Servlet.class):类路径下必须有 Servlet.class(web 项目天然满足)

  • @ConditionalOnWebApplication:必须是 Servlet 类型的 Web 应用

  • @AutoConfigureAfter:在 DispatcherServletAutoConfiguration 之后执行,保证执行顺序

再看 DispatcherServletAutoConfiguration 内部:

java

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
@EnableConfigurationProperties(WebMvcProperties.class)
public class DispatcherServletAutoConfiguration {

@Configuration
@Conditional(DefaultDispatcherServletCondition.class)
@EnableConfigurationProperties(WebMvcProperties.class)
protected static class DispatcherServletConfiguration {
@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
}
}

只要条件满足,Spring Boot 就会自动将 DispatcherServlet 注册到容器中,开发者无需再写 web.xml 或 @Bean 配置。

3.4 属性绑定:配置文件如何注入

自动配置类通常配合 @EnableConfigurationProperties 使用,将配置文件中的属性绑定到 POJO:

java

@ConfigurationProperties(prefix = "server")
public class ServerProperties {
private Integer port = 8080; // 默认端口
// getter/setter
}

这样,application.yml 中的 server.port=9090 就能被自动读取并应用。


四、第三层:内嵌容器的装配与启动

4.1 容器工厂的自动配置

嵌入式 Web 容器的装配由 ServletWebServerFactoryAutoConfiguration 负责。它会根据 classpath 中的依赖,创建对应的工厂 bean:

java

@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@EnableConfigurationProperties(ServerProperties.class)
public class ServletWebServerFactoryAutoConfiguration {
// 导入具体的容器配置
}

这个配置类通过内部 @Import 导入了三种容器的配置类:

text

@Import({
ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class, // Tomcat
ServletWebServerFactoryConfiguration.EmbeddedJetty.class, // Jetty
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class // Undertow
})

Tomcat 是默认的,因为 spring-boot-starter-web 中引入了 spring-boot-starter-tomcat。如果希望切换到 Jetty,只需排除 Tomcat 并引入 Jetty 依赖即可:

xml

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

4.2 容器的创建时机:onRefresh()

内嵌容器的创建发生在 Spring 容器的 refresh() 过程中。AnnotationConfigServletWebServerApplicationContext(实际使用的上下文类)继承自 ServletWebServerApplicationContext,后者重写了 onRefresh() 方法:

java

// ServletWebServerApplicationContext.java
@Override
protected void onRefresh() {
super.onRefresh();
try {
createWebServer(); // 关键:创建 Web 服务器
} catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}

private void createWebServer() {
// 从容器中获取 ServletWebServerFactory(即 TomcatServletWebServerFactory)
ServletWebServerFactory factory = getWebServerFactory();
// 创建 WebServer 实例
this.webServer = factory.getWebServer(getSelfInitializer());
// 触发 ServletContext 初始化事件
getSelfInitializer().onStartup(this.webServer.getServletContext());
}

factory.getWebServer() 方法的执行流程:

  • 创建 Tomcat 实例

  • 创建 Connector(默认端口 8080,可通ServerProperties配置覆盖)

  • 配置 Engine、Host、Context

  • 返回 TomcatWebServer 对象(此时容器尚未启动,只完成了初始化)

  • 4.3 容器的启动时机:finishRefresh()

    容器在 onRefresh() 中创建完成,但真正启动是在 finishRefresh() 阶段:

    java

    // ServletWebServerApplicationContext.java
    @Override
    protected void finishRefresh() {
    super.finishRefresh();
    startWebServer(); // 启动 Web 服务器
    publishEvent(new ServletWebServerInitializedEvent(this.webServer, this));
    }

    private void startWebServer() {
    WebServer webServer = getWebServer();
    if (webServer != null) {
    webServer.start(); // 启动 Tomcat
    }
    }

    进入 TomcatWebServer.start() 内部:

    java

    // TomcatWebServer.java
    public void start() throws WebServerException {
    synchronized (this.monitor) {
    if (this.started) {
    return;
    }
    try {
    addPreviouslyRemovedConnectors();
    // 启动 Tomcat 的核心方法
    Tomcat tomcat = getTomcat();
    tomcat.start();
    // 检查 Connector 是否启动成功
    rethrowDeferredStartupExceptions();
    // 启动一个后台线程等待 Connector 初始化完成
    startDaemonAwaitThread();
    this.started = true;
    } catch (Exception ex) {
    // 异常处理
    }
    }
    }

    此时控制台会输出经典的日志:

    text

    Tomcat started on port(s): 8080 (http) with context path ''
    Started Application in 2.345 seconds (JVM running for 2.678)

    至此,完整的 Web 服务器启动完毕。


    五、完整时序图

    为了更直观地理解整个过程,以下是关键步骤的时序:

    说明:该图展示的是 Spring Boot 启动过程中,从 main 方法开始,到自动配置类加载、容器创建、Tomcat 启动的完整调用链路。


    六、切换到 Jetty/Undertow 的原理

    理解了默认流程后,切换容器的原理就很清晰了:

  • ServletWebServerFactoryAutoConfiguration 导入了三种容器的配置类

  • 每种容器的配置类上都带有 @ConditionalOnClass 注解

  • 当排除了 spring-boot-starter-tomcat 后,Tomcat 相关的类(如 Tomcat、TomcatServletWebServerFactory)不再存在于 classpath 中

  • 因此 EmbeddedTomcat 配置类不生效,而 Jetty 的配置类因引入了 spring-boot-starter-jetty 而生效

  • 本质是基于 classpath 的条件化配置在发挥作用。


    七、总结

    spring-boot-starter-web 的设计体现了 Spring Boot 的核心哲学:

  • 依赖聚合:一个 Starter 聚合了 Web 开发所需的所有依赖,版本由父项目统一仲裁

  • 条件装配:自动配置类通过 @ConditionalOnXxx 实现按需生效,避免过度加载

  • 生命周期钩子:利用 Spring 容器的 onRefresh() 和 finishRefresh() 模板方法,在恰当的时机创建和启动内嵌容器

  • 工厂抽象:通过 ServletWebServerFactory 接口统一不同容器的创建逻辑,切换只需改依赖

  • 理解这四层设计,不仅能用好 Web Starter,也能为自定义 Starter 提供清晰的参考模式——这正是 Spring Boot 能够成为 Java 生态基石的底层逻辑。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » Spring Boot Starter Web 原理分析:从依赖到内嵌服务器的完整启动流程
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!