在日常开发中,我们只需编写一个带@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个核心注解的作用(解决"为什么不用手动配置"的疑问):
-
@SpringBootConfiguration:本质是@Configuration(源码中直接继承@Configuration),标记当前主类是一个Spring配置类,允许在类中使用@Bean注解注册Bean。
-
@ComponentScan:自动扫描当前主类所在的包及其子包下的所有带@Component、@Service、@Controller、@Repository等注解的类,将其注册为Spring容器中的Bean(无需手动配置Bean扫描路径)。
-
@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));
}
}
关键细节解析(贴合启动流程,呼应前文):
-
环境创建:getOrCreateEnvironment() 会根据前面判定的webApplicationType,创建对应的环境对象------Web应用创建StandardServletEnvironment,该环境包含Web相关的属性源(如ServletContext属性),为后续Tomcat启动和DispatcherServlet配置提供基础。
-
配置整合:configureEnvironment() 是核心,会将命令行参数(如启动时传入的--server.port=8081)、系统环境变量、应用配置文件(application.properties)等整合到环境中,后续所有组件(Tomcat、DispatcherServlet)都会从该环境中获取配置。
-
监听器通知:listeners.environmentPrepared(environment) 会发布环境准备完成事件,触发相关监听器执行(如配置文件加载监听器),确保所有配置都已加载到位。
-
环境绑定: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注册:
-
onRefresh() 方法:Web应用的上下文(AnnotationConfigServletWebServerApplicationContext)会重写该方法,在该方法中启动内嵌Tomcat;
-
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的完整逻辑串联起来:
-
SpringApplication.run() 调用 refreshContext(),进而调用 onRefresh() 方法;
-
onRefresh() 方法中调用 createWebServer(),创建Tomcat实例,并将所有ServletContextInitializer(包括DispatcherServletRegistrationBean)注册到Tomcat;
-
Tomcat启动时(tomcat.start()),会遍历所有注册的ServletContextInitializer,调用其onStartup()方法;
-
DispatcherServletRegistrationBean的onStartup()方法被调用,通过servletContext.addServlet()将DispatcherServlet注册到Tomcat;
-
注册完成后,DispatcherServlet会被Tomcat初始化,后续就能接收并处理Web请求。
至此,DispatcherServlet 就是通过这一整条链路,被注册到 Tomcat 里的。
七、完整链路总结(梳理所有疑问,形成闭环)
结合前面的源码解析和逻辑拆解,我们将Spring Boot启动的完整链路,以及所有核心疑问的答案,总结如下:
-
启动入口:main方法执行SpringApplication.run(),传入主类;
-
注解解析:@SpringBootApplication整合@Configuration、@ComponentScan、@EnableAutoConfiguration,开启自动配置和Bean扫描;
-
初始化准备:创建SpringApplication实例,判定应用类型为Web应用,加载监听器和初始化器;
-
上下文准备:prepareContext() 关联环境、加载主类和自动配置类,为上下文刷新做准备;
-
上下文刷新:refresh() 方法启动Spring容器,其中onRefresh()启动内嵌Tomcat,finishBeanFactoryInitialization()实例化DispatcherServlet;
-
Tomcat启动:通过TomcatServletWebServerFactory创建Tomcat实例,调用tomcat.start()启动服务;
-
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的核心组件已就绪。