Spring Boot 启动原理全解析(一)

在日常开发中,我们只需编写一个带@SpringBootApplication注解的主类,执行main方法就能启动一个Web服务,无需手动配置Tomcat、无需编写大量XML配置,这背后正是Spring Boot的自动配置和内嵌容器机制在发挥作用。本文将从「Spring Boot 的启动原理」这一核心疑问出发,一步步深入源码,结合实例,梳理从启动入口到DispatcherServlet注册进Tomcat的完整链路,把每一个关键节点、每一处源码细节讲清楚,解决你对启动流程的所有困惑。

一、核心疑问开篇:Spring Boot 启动到底做了什么?

我们最核心的疑问:Spring Boot为什么能通过main方法直接启动Web服务?启动过程中,Spring容器、内嵌Tomcat、DispatcherServlet是如何初始化、关联并最终完成Web环境搭建的?

带着这个疑问,我们从启动入口开始,一步步拆解源码,还原整个启动链路。

二、启动入口:main方法与@SpringBootApplication注解(源码+实例)

2.1 启动入口实例(日常开发真实写法)

Spring Boot的启动入口非常简洁,一个带@SpringBootApplication注解的主类,加上main方法中执行SpringApplication.run(),就是整个应用的启动起点:

java 复制代码
// 核心启动类(真实开发中最常见的写法)
@SpringBootApplication
public class SpringBootDemoApplication {
    public static void main(String[] args) {
        // 启动核心方法:传入主类字节码和命令行参数
        SpringApplication.run(SpringBootDemoApplication.class, args);
    }
}
    

2.2 @SpringBootApplication注解源码解析(核心复合注解)

很多人疑惑,为什么一个@SpringBootApplication就能搞定所有?其实它是一个复合注解,整合了3个核心注解,我们看源码(Spring Boot 2.7.x版本,最常用稳定版):

java 复制代码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 核心复合注解:3个关键注解的整合
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)
})
public @interface SpringBootApplication {
    // 省略属性(如exclude、excludeName等,用于排除指定自动配置类)
}
    

拆解3个核心注解的作用(解决"为什么不用手动配置"的疑问):

  1. @SpringBootConfiguration:本质是@Configuration(源码中直接继承@Configuration),标记当前主类是一个Spring配置类,允许在类中使用@Bean注解注册Bean。

  2. @ComponentScan:自动扫描当前主类所在的包及其子包下的所有带@Component、@Service、@Controller、@Repository等注解的类,将其注册为Spring容器中的Bean(无需手动配置Bean扫描路径)。

  3. @EnableAutoConfiguration:最核心的注解,开启Spring Boot的自动配置机制------根据当前类路径下的依赖(如引入spring-boot-starter-web,就会自动配置Web相关组件),自动装配所需的Bean和配置,无需手动编写XML或Java配置。

2.3 @EnableAutoConfiguration源码深挖(自动配置的核心)

@EnableAutoConfiguration的核心是导入AutoConfigurationImportSelector类,通过该类加载所有符合条件的自动配置类,源码关键部分:

java 复制代码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 导入自动配置选择器,核心逻辑在这里
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    Class<?>[] exclude() default {};
    String[] excludeName() default {};
}
    

AutoConfigurationImportSelector的核心方法是selectImports(),它会读取META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件(Spring Boot 2.7+版本,之前是spring.factories),该文件中定义了所有可自动配置的类,比如:

  • ServletWebServerFactoryAutoConfiguration:内嵌Web容器(Tomcat)的自动配置类;

  • DispatcherServletAutoConfiguration:DispatcherServlet的自动配置类;

  • WebMvcAutoConfiguration:Spring MVC的自动配置类。

这一步就解决了"Spring Boot如何知道要配置Tomcat和DispatcherServlet"的疑问------通过自动配置类,根据依赖动态加载所需组件。

三、核心启动方法:SpringApplication.run() 源码拆解(全流程核心)

main方法中执行的SpringApplication.run(),是整个启动流程的入口,我们拆解其源码(简化核心逻辑,去掉无关分支),看看它到底做了什么:

java 复制代码
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    // 调用重载方法,创建SpringApplication实例并执行run
    return run(new Class<?>[] { primarySource }, args);
}

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    // 1. 创建SpringApplication实例(初始化应用上下文、环境等)
    return new SpringApplication(primarySources).run(args);
}
    

整个run()方法分为两大步骤:创建SpringApplication实例 + 执行run(args)方法,我们分别拆解。

3.1 第一步:创建SpringApplication实例(初始化准备)

SpringApplication的构造方法会完成一系列初始化,核心逻辑(源码简化):

java 复制代码
public SpringApplication(Class<?>... primarySources) {
    this(null, primarySources);
}

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    // 校验主类(确保传入的primarySources有效)
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    // 3. 判定应用类型:是Web应用(SERVLET)还是非Web应用(NONE)
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    // 4. 加载所有ApplicationContextInitializer(上下文初始化器,用于初始化Spring上下文)
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 5. 加载所有ApplicationListener(应用监听器,监听启动过程中的各种事件)
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    // 6. 推断主类(找到执行main方法的类)
    this.mainApplicationClass = deduceMainApplicationClass();
}
    

关键重点:webApplicationType的判定------Spring Boot会根据类路径下是否存在Servlet相关的类(如javax.servlet.Servlet),判定应用类型为SERVLET(Web应用),这是后续启动内嵌Tomcat的前提。

3.2 第二步:执行run(args)方法(核心流程,启动全链路)

这是整个启动流程的核心,源码简化后,核心步骤如下(每一步都对应一个关键操作,解决"启动过程中到底做了什么"的疑问):

java 复制代码
public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start(); // 计时,记录启动耗时
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    configureHeadlessProperty(); // 配置无头模式(无关核心,可忽略)
    
    // 1. 加载所有SpringApplicationRunListener(启动监听器,监听启动的各个阶段)
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting(); // 发布启动开始事件
    
    try {
        // 2. 处理命令行参数,创建并配置环境(Environment)
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        
        // 3、准备运行环境:将监听器、命令行参数融入环境配置,为上下文创建做准备
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
        configureIgnoreBeanInfo(environment);
        
        // 4. 打印Banner(启动时的Spring Boot图标,可自定义)
        Banner printedBanner = printBanner(environment);
        
        // 5. 创建Spring应用上下文(ApplicationContext)
        //  根据webApplicationType,创建对应的上下文:Web应用对应AnnotationConfigServletWebServerApplicationContext
        context = createApplicationContext();
        
        // 6. 加载异常报告器(用于启动异常时的报告)
        exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);
        
        // 7. 准备上下文(核心步骤:关联环境、注册Bean、初始化内嵌容器等)
        prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        
        // 8. 刷新上下文(最核心的一步:启动Spring容器、初始化内嵌Tomcat、注册DispatcherServlet)
        refreshContext(context);
        
        // 9. 刷新上下文后的操作(空方法,可自定义扩展)
        afterRefresh(context, applicationArguments);
        
        stopWatch.stop(); // 停止计时
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
        }
        
        listeners.started(context); // 发布启动完成事件
        
        // 10. 执行所有CommandLineRunner和ApplicationRunner(启动后执行的自定义逻辑)
        callRunners(context, applicationArguments);
    } catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }
    
    try {
        listeners.running(context); // 发布应用运行中事件
    } catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }
    
    // 返回初始化完成的Spring上下文
    return context;
}

其中,**prepareContext()(准备上下文)refreshContext()(刷新上下文)**是最核心的两个步骤,也是我们解开"Tomcat如何启动、DispatcherServlet如何注册"的关键。

四、关键步骤深挖:prepareEnvironment、prepareContext与refreshContext(源码级)

4.1 prepareEnvironment(准备运行环境,核心源码+解析)

该方法的核心作用:初始化应用运行环境(Environment),整合系统环境变量、命令行参数、配置文件(application.properties/yml)等配置,同时通知监听器环境已准备完成,源码关键逻辑如下:

java 复制代码
// SpringApplication的prepareEnvironment方法
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    // 1. 创建环境:根据应用类型(Web/SERVLET)创建对应的环境对象
    // Web应用对应StandardServletEnvironment,非Web应用对应StandardEnvironment
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    // 2. 配置环境:将命令行参数、系统变量、配置文件等融入环境
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    // 3. 绑定环境到SpringApplication:将环境配置关联到当前应用实例
    ConfigurationPropertySources.attach(environment);
    // 4. 发布环境准备完成事件,通知所有监听器(如配置文件加载监听器)
    listeners.environmentPrepared(environment);
    // 5. 将环境绑定到应用上下文(后续上下文会使用该环境)
    bindToSpringApplication(environment);
    // 6. 如果是Web应用,将环境转换为Web环境(确保包含Web相关配置)
    if (!this.isCustomEnvironment) {
        environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                deduceEnvironmentClass());
    }
    // 7. 再次绑定配置源,确保环境配置生效
    ConfigurationPropertySources.attach(environment);
    return environment;
}

// 配置环境的核心方法:整合各种配置源
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
    // 配置属性源(系统变量、环境变量、命令行参数等)
    configurePropertySources(environment, args);
    // 配置 Profiles(环境切换,如dev、test、prod)
    configureProfiles(environment, args);
}

// 配置属性源:将命令行参数、系统变量等添加到环境的属性源中
protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
    MutablePropertySources sources = environment.getPropertySources();
    // 1. 添加默认属性源(Spring Boot内置的默认配置)
    if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
        sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));
    }
    // 2. 添加命令行参数属性源(优先级高于默认配置)
    if (args != null) {
        sources.addFirst(new SimpleCommandLinePropertySource(args));
    }
}

关键细节解析(贴合启动流程,呼应前文):

  1. 环境创建:getOrCreateEnvironment() 会根据前面判定的webApplicationType,创建对应的环境对象------Web应用创建StandardServletEnvironment,该环境包含Web相关的属性源(如ServletContext属性),为后续Tomcat启动和DispatcherServlet配置提供基础。

  2. 配置整合:configureEnvironment() 是核心,会将命令行参数(如启动时传入的--server.port=8081)、系统环境变量、应用配置文件(application.properties)等整合到环境中,后续所有组件(Tomcat、DispatcherServlet)都会从该环境中获取配置。

  3. 监听器通知:listeners.environmentPrepared(environment) 会发布环境准备完成事件,触发相关监听器执行(如配置文件加载监听器),确保所有配置都已加载到位。

  4. 环境绑定:bindToSpringApplication(environment) 将环境绑定到SpringApplication实例,后续创建上下文(createApplicationContext())时,会将该环境传入上下文,确保上下文能获取到所有配置。

总结:prepareEnvironment 是"准备运行环境"的核心步骤,它整合了所有启动所需的配置,为后续上下文准备、Tomcat启动、DispatcherServlet注册提供了必要的环境支持,是连接启动入口和核心组件初始化的关键桥梁。

4.2 prepareContext(上下文准备)

该方法的核心作用是:将环境、主类、监听器等关联到Spring上下文,为后续的上下文刷新(初始化容器、注册Bean)做准备,源码关键逻辑:

java 复制代码
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
        SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
    // 1. 将环境设置到上下文
    context.setEnvironment(environment);
    // 2. 配置上下文的资源加载器、类加载器等
    postProcessApplicationContext(context);
    // 3. 执行所有ApplicationContextInitializer的初始化方法(初始化上下文)
    applyInitializers(context);
    // 4. 发布上下文准备完成事件,通知监听器
    listeners.contextPrepared(context);
    // 5. 记录启动日志
    if (this.logStartupInfo) {
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }
    
    // 6. 注册命令行参数Bean(可在项目中注入ApplicationArguments获取命令行参数)
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
    if (printedBanner != null) {
        beanFactory.registerSingleton("springBootBanner", printedBanner);
    }
    
    // 7. 加载主类作为配置类,注册到Spring容器
    load(context, primarySources.toArray(new Object[0]));
    
    // 8. 发布上下文加载完成事件
    listeners.contextLoaded(context);
}
    

重点:load(context, primarySources) 方法会将我们的主类(SpringBootDemoApplication)作为配置类加载到Spring容器中,后续的自动配置类(如Tomcat、DispatcherServlet的自动配置类)也会在这里被加载。

4.3 refreshContext(上下文刷新)------ 核心中的核心

refreshContext() 本质是调用Spring上下文的refresh()方法(继承自AbstractApplicationContext),这个方法是Spring容器初始化的核心,也是内嵌Tomcat启动、DispatcherServlet注册的关键。我们看源码(简化核心逻辑):

java 复制代码
// SpringApplication的refreshContext方法
protected void refreshContext(ConfigurableApplicationContext context) {
    // 调用上下文的refresh方法
    refresh(context);
    if (this.registerShutdownHook) {
        try {
            context.registerShutdownHook(); // 注册关闭钩子,优雅关闭应用
        } catch (AccessControlException ex) {
            // 忽略权限异常
        }
    }
}

// AbstractApplicationContext的refresh方法(核心逻辑)
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // 1. 准备刷新上下文(初始化属性源、验证环境等)
        prepareRefresh();
        
        // 2. 获取BeanFactory(Spring容器的核心,用于管理Bean)
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
        
        // 3. 准备BeanFactory(配置类加载器、注册默认Bean等)
        prepareBeanFactory(beanFactory);
        
        try {
            // 4. 后置处理BeanFactory(允许自定义扩展)
            postProcessBeanFactory(beanFactory);
            
            // 5. 执行所有BeanFactoryPostProcessor(BeanFactory的后置处理器,修改Bean定义)
            invokeBeanFactoryPostProcessors(beanFactory);
            
            // 6. 注册所有BeanPostProcessor(Bean的后置处理器,干预Bean的创建过程)
            registerBeanPostProcessors(beanFactory);
            
            // 7. 初始化消息源(国际化相关,无关核心)
            initMessageSource();
            
            // 8. 初始化应用事件多播器(用于发布事件)
            initApplicationEventMulticaster();
            
            // 9. 子类重写:初始化特殊Bean(Web应用中,这里会启动内嵌Tomcat)
            onRefresh();
            
            // 10. 注册所有ApplicationListener(应用监听器)
            registerListeners();
            
            // 11. 实例化所有非懒加载的单例Bean(包括DispatcherServlet)
            finishBeanFactoryInitialization(beanFactory);
            
            // 12. 完成上下文刷新(发布刷新完成事件,启动Web服务)
            finishRefresh();
        } catch (BeansException ex) {
            // 异常处理:销毁已创建的Bean,关闭上下文
            destroyBeans();
            cancelRefresh(ex);
            throw ex;
        } finally {
            // 重置上下文的公共缓存
            resetCommonCaches();
        }
    }
}
    

这里有两个最关键的方法,直接关系到Tomcat启动和DispatcherServlet注册:

  1. onRefresh() 方法:Web应用的上下文(AnnotationConfigServletWebServerApplicationContext)会重写该方法,在该方法中启动内嵌Tomcat;

  2. finishBeanFactoryInitialization(beanFactory) 方法:实例化所有非懒加载的单例Bean,其中就包括DispatcherServlet,并且会完成DispatcherServlet的初始化和注册。

五、内嵌Tomcat启动(源码+逻辑)

我们先解决"Spring Boot如何启动Tomcat"的疑问------核心是onRefresh()方法,Web应用上下文重写了该方法,源码如下:

java 复制代码
// AnnotationConfigServletWebServerApplicationContext的onRefresh方法
@Override
protected void onRefresh() {
    super.onRefresh();
    try {
        // 核心:创建并启动内嵌Web容器(Tomcat)
        createWebServer();
    } catch (Throwable ex) {
        throw new ApplicationContextException("Unable to start web server", ex);
    }
}

// 创建Web容器的核心方法
private void createWebServer() {
    WebServer webServer = this.webServer;
    ServletContext servletContext = getServletContext();
    if (webServer == null && servletContext == null) {
        // 1. 获取Web容器工厂(根据自动配置,这里是TomcatServletWebServerFactory)
        ServletWebServerFactory factory = getWebServerFactory();
        // 2. 创建Web容器(Tomcat),并注册ServletContextInitializer
        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();
}
    

5.1 关键:ServletWebServerFactory(Web容器工厂)

getWebServerFactory() 方法会获取到自动配置的Web容器工厂,因为我们引入了spring-boot-starter-web依赖,自动配置类ServletWebServerFactoryAutoConfiguration会生效,默认创建TomcatServletWebServerFactory(Tomcat容器工厂)。

TomcatServletWebServerFactory的getWebServer()方法,会创建Tomcat实例、配置Tomcat(端口、协议等),并启动Tomcat,源码简化:

java 复制代码
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
    // 1. 创建Tomcat实例
    Tomcat tomcat = new Tomcat();
    // 2. 配置Tomcat的基础目录(临时目录)
    File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
    tomcat.setBaseDir(baseDir.getAbsolutePath());
    // 3. 创建Tomcat的连接器(默认8080端口,可通过application.properties配置)
    Connector connector = new Connector(this.protocol);
    tomcat.getService().addConnector(connector);
    customizeConnector(connector);
    tomcat.setConnector(connector);
    // 4. 配置Tomcat的引擎、主机等
    tomcat.getHost().setAutoDeploy(false);
    configureEngine(tomcat.getEngine());
    // 5. 注册ServletContextInitializer(用于后续注册DispatcherServlet)
    for (ServletContextInitializer initializer : initializers) {
        tomcat.addContextCustomizers(new ServletContextInitializerContextCustomizer(initializer));
    }
    // 6. 自定义Tomcat配置(可通过实现TomcatCustomizer扩展)
    customizeTomcat(tomcat);
    // 7. 创建TomcatWebServer实例,并启动Tomcat
    return new TomcatWebServer(tomcat, getPort() >= 0);
}
   

重点:TomcatWebServer的构造方法中,会调用tomcat.start()方法,启动Tomcat服务------这就是为什么我们执行main方法后,Tomcat会自动启动,无需手动部署。

六、DispatcherServlet注册(源码级链路)

解决了Tomcat启动的疑问,接下来就是核心:DispatcherServlet如何被注册到Tomcat中?整个链路分为3步,全程源码可追溯。

6.1 第一步:DispatcherServlet的自动配置(DispatcherServletAutoConfiguration)

因为@EnableAutoConfiguration开启了自动配置,DispatcherServletAutoConfiguration会被加载,该类会自动创建DispatcherServlet实例,并配置相关属性,源码关键部分:

java 复制代码
// 自动配置类,仅在Web应用中生效
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {

    // 核心:注册DispatcherServlet Bean
    @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
    @ConditionalOnMissingBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
    public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
        DispatcherServlet dispatcherServlet = new DispatcherServlet();
        // 配置DispatcherServlet的属性(如是否抛出异常、上下文配置等)
        dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
        dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
        dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
        dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
        dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
        return dispatcherServlet;
    }

    // 第二步:注册DispatcherServletRegistrationBean(用于将DispatcherServlet注册到Tomcat)
    @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
    @ConditionalOnMissingBean(DispatcherServletRegistrationBean.class)
    public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
            WebMvcProperties webMvcProperties, ServletRegistrationBeanProperties servletRegistrationBeanProperties) {
        // 创建注册Bean,关联DispatcherServlet
        DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
                webMvcProperties.getServlet().getPath());
        registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
        registration.setLoadOnStartup(servletRegistrationBeanProperties.getLoadOnStartup());
        if (servletRegistrationBeanProperties.getEnabled() != null) {
            registration.setEnabled(servletRegistrationBeanProperties.getEnabled());
        }
        return registration;
    }
}
    

关键:这里创建了两个核心Bean:

  • DispatcherServlet:Spring MVC的核心前端控制器,负责接收请求、分发请求到对应的Controller;

  • DispatcherServletRegistrationBean:用于将DispatcherServlet注册到Tomcat的ServletContext中(Tomcat管理Servlet的核心上下文)。

6.2 第二步:ServletContextInitializer的作用(连接DispatcherServlet和Tomcat)

DispatcherServletRegistrationBean实现了ServletContextInitializer接口,该接口的onStartup(ServletContext servletContext)方法,会在Tomcat启动时被调用,用于注册Servlet、Filter等组件。

我们看ServletContextInitializer的核心方法(DispatcherServletRegistrationBean继承自ServletRegistrationBean,重写了onStartup方法):

java 复制代码
// ServletRegistrationBean的onStartup方法
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
    // 注册Servlet到Tomcat的ServletContext中
    registerServlet(servletContext);
}

// 注册Servlet的核心逻辑
protected void registerServlet(ServletContext servletContext) {
    String servletName = getServletName();
    // 1. 创建ServletRegistration.Dynamic对象(Tomcat管理Servlet的核心对象)
    ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, getServlet());
    if (registration == null) {
        throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'.");
    }
    // 2. 配置Servlet的属性(如启动顺序、URL映射等)
    registration.setLoadOnStartup(getLoadOnStartup());
    if (isAsyncSupported()) {
        registration.setAsyncSupported(true);
    }
    // 3. 配置URL映射(默认是"/",可通过spring.mvc.servlet.path配置)
    registration.addMapping(getUrlMappings());
    registration.setInitParameters(getInitParameters());
    // 4. 配置Servlet的启动状态(是否启用)
    registration.setEnabled(isEnabled());
    // 5. 注册Servlet的MIME类型(可选)
    for (String key : this.servletMappings.keySet()) {
        registration.addMapping(key, this.servletMappings.get(key));
    }
    // 6. 注册Servlet的Security约束(可选)
    configureSecurity(registration);
}
    

重点:servletContext.addServlet(servletName, getServlet()) 方法,会将DispatcherServlet实例注册到Tomcat的ServletContext中,Tomcat会管理该Servlet的生命周期(初始化、销毁等)。

6.3 第三步:Tomcat启动时触发注册(完整闭环)

我们回顾整个链路,将DispatcherServlet注册到Tomcat的完整逻辑串联起来:

  1. SpringApplication.run() 调用 refreshContext(),进而调用 onRefresh() 方法;

  2. onRefresh() 方法中调用 createWebServer(),创建Tomcat实例,并将所有ServletContextInitializer(包括DispatcherServletRegistrationBean)注册到Tomcat;

  3. Tomcat启动时(tomcat.start()),会遍历所有注册的ServletContextInitializer,调用其onStartup()方法;

  4. DispatcherServletRegistrationBean的onStartup()方法被调用,通过servletContext.addServlet()将DispatcherServlet注册到Tomcat;

  5. 注册完成后,DispatcherServlet会被Tomcat初始化,后续就能接收并处理Web请求。

至此,DispatcherServlet 就是通过这一整条链路,被注册到 Tomcat 里的。

七、完整链路总结(梳理所有疑问,形成闭环)

结合前面的源码解析和逻辑拆解,我们将Spring Boot启动的完整链路,以及所有核心疑问的答案,总结如下:

  1. 启动入口:main方法执行SpringApplication.run(),传入主类;

  2. 注解解析:@SpringBootApplication整合@Configuration、@ComponentScan、@EnableAutoConfiguration,开启自动配置和Bean扫描;

  3. 初始化准备:创建SpringApplication实例,判定应用类型为Web应用,加载监听器和初始化器;

  4. 上下文准备:prepareContext() 关联环境、加载主类和自动配置类,为上下文刷新做准备;

  5. 上下文刷新:refresh() 方法启动Spring容器,其中onRefresh()启动内嵌Tomcat,finishBeanFactoryInitialization()实例化DispatcherServlet;

  6. Tomcat启动:通过TomcatServletWebServerFactory创建Tomcat实例,调用tomcat.start()启动服务;

  7. DispatcherServlet注册:通过DispatcherServletAutoConfiguration创建DispatcherServlet和其注册Bean,Tomcat启动时触发注册Bean的onStartup()方法,将DispatcherServlet注册到Tomcat。

整个过程无需手动配置Tomcat、无需手动注册DispatcherServlet,全靠Spring Boot的自动配置和内嵌容器机制完成,这也是Spring Boot"简化配置、快速开发"的核心所在。

八、补充:常见疑问解答(结合源码)

  • 疑问1:为什么引入spring-boot-starter-web就能启动Tomcat?------ starter-web依赖中包含了Tomcat的相关依赖(tomcat-embed-core等),同时触发ServletWebServerFactoryAutoConfiguration自动配置,创建Tomcat容器工厂,进而启动Tomcat。

  • 疑问2:DispatcherServlet的URL映射为什么默认是"/"?------ 由WebMvcProperties配置,默认值为"/",可通过application.properties中的spring.mvc.servlet.path配置修改。

  • 疑问3:如何替换内嵌容器(比如用Jetty替代Tomcat)?------ 排除starter-web中的tomcat依赖,引入jetty的starter依赖即可,自动配置会切换为JettyServletWebServerFactory。

  • 疑问4:Spring容器和Tomcat的启动顺序是什么?------ 先启动Spring容器(refresh()方法),在Spring容器刷新过程中(onRefresh())启动Tomcat,最后将DispatcherServlet注册到Tomcat,确保Tomcat启动后,Spring MVC的核心组件已就绪。