本文基于个人学习过程中的高频提问,从 Spring MVC 整体流程入手,逐步拆解底层原理、核心组件、设计模式,再延伸到 Tomcat 与 Spring 容器的联动、父子容器设计等核心知识点,全程结合源码片段、实例代码和逻辑图表,兼顾专业性和易懂性,适合用于学习记录、面试复习。
一、Spring MVC 整体流程
Spring MVC 的核心是"请求分发-处理-响应"的闭环,整个流程共8步,是理解所有后续原理的基础,必须烂熟于心。
1.1 完整流程(带源码关联)
-
请求 → DispatcherServlet :客户端(浏览器/Postman)发送 HTTP 请求,所有请求统一被 Spring MVC 的前端控制器
DispatcherServlet拦截(由 Tomcat 配置映射,后续详解)。源码关联:
DispatcherServlet本质是HttpServlet,重写了doGet()、doPost()方法,最终都会调用核心方法doDispatch(request, response),这是整个 Spring MVC 流程的入口。 -
HandlerMapping → 找到对应 @RequestMapping 方法 :
DispatcherServlet调用HandlerMapping的getHandlerRequest()方法,根据请求 URL 匹配到对应的 Controller 方法。源码关联:默认使用
RequestMappingHandlerMapping,其内部会在 Spring 启动时扫描所有标注@Controller、@RequestMapping(含 @GetMapping/@PostMapping)的方法,将 URL 与方法封装为HandlerMethod并缓存。 -
HandlerAdapter → 执行方法 :
DispatcherServlet拿到HandlerMethod后,不会直接执行,而是交给HandlerAdapter适配执行。源码关联:
DispatcherServlet会遍历所有HandlerAdapter实现类,通过supports(handler)方法判断哪个适配器支持当前HandlerMethod,默认使用RequestMappingHandlerAdapter。 -
参数解析 → HttpMessageConverter :
HandlerAdapter通过HttpMessageConverter解析请求参数(如表单、JSON、路径参数),完成类型转换、数据绑定和校验,将参数整理后传递给 Controller 方法。实例:客户端发送 JSON 格式请求体
{"name":"张三","age":20},MappingJackson2HttpMessageConverter会将其解析为 Java 实体类User。 -
执行 Controller :
HandlerAdapter通过反射调用 Controller 对应的业务方法,执行核心业务逻辑(如调用 Service、Mapper 操作数据库)。 -
返回值处理 → HttpMessageConverter :Controller 方法执行完毕后,返回值(如 String、ModelAndView、Java 实体类)由
HandlerAdapter通过HttpMessageConverter处理,例如将 Java 实体类转为 JSON 响应,或解析 String 为视图名称。 -
异常处理 → ExceptionResolver :执行过程中若出现异常(如业务异常、空指针),由
HandlerExceptionResolver统一拦截处理,避免直接返回错误页面给客户端。源码关联:默认使用
DefaultHandlerExceptionResolver,也可自定义异常处理器(实现HandlerExceptionResolver接口)。 -
渲染页面 / 返回 JSON :若返回值是视图名称(如 "index"),则由
ViewResolver解析为具体视图(如 JSP、HTML)并渲染;若返回值是 JSON 格式(如 @ResponseBody 注解),则直接将处理后的 JSON 响应给客户端。
1.2 流程简化
一句话浓缩:请求→DispatcherServlet→HandlerMapping找方法→HandlerAdapter执行→参数/返回值转换器→Controller→异常解析器→响应页面/JSON
1.3 流程图表(直观理解)
是
否
客户端请求
DispatcherServlet(前端控制器)
HandlerMapping(找方法)
返回HandlerExecutionChain(方法+拦截器)
HandlerAdapter(适配执行)
HttpMessageConverter(参数解析)
Controller(执行业务)
HttpMessageConverter(返回值处理)
异常?
ExceptionResolver(异常处理)
渲染页面/返回JSON
响应客户端
二、核心组件详解(源码级)
Spring MVC 的核心组件围绕上述流程工作,每个组件的职责、源码实现和设计思路都需要重点掌握。
2.1 Coyote(Tomcat HTTP 连接器)
很多人会混淆 Coyote 与 Spring MVC 的关系,其实 Coyote 是 Tomcat 的组件,而非 Spring MVC,但它是请求进入的"第一道大门"。
-
核心职责:负责网络通信、HTTP 协议解析、接收客户端请求、返回响应数据(相当于 Tomcat 的"通信引擎")。
-
与 Tomcat 其他组件的关系:Tomcat 主要由 Coyote(连接器)、Catalina(容器核心,管理 Servlet)、Jasper(JSP 解析器)组成。
-
源码关联:Coyote 定义了
HttpConnector接口,负责监听端口、接收请求,将请求封装为HttpRequest后交给 Catalina 处理。
一句话记忆:Coyote 是 Tomcat 用来接收、处理 HTTP 请求的底层引擎,是 Spring MVC 接收请求的前提。
2.2 DispatcherServlet(前端控制器)
DispatcherServlet 是 Spring MVC 的"总指挥",所有请求都经过它,负责协调所有其他组件,核心源码如下:
java
// DispatcherServlet 核心方法:请求分发
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
try {
// 1. 找到当前请求对应的 HandlerExecutionChain(方法+拦截器)
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 2. 找到支持当前 Handler 的 HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 3. 执行拦截器 preHandle
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 4. 执行 Controller 方法,获取返回值
ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 5. 执行拦截器 postHandle
mappedHandler.applyPostHandle(processedRequest, response, mv);
// 6. 渲染视图/处理返回值
processDispatchResult(processedRequest, response, mappedHandler, mv, null);
} catch (Exception ex) {
// 7. 异常处理
processDispatchResult(processedRequest, response, mappedHandler, null, ex);
}
}
核心疑问:DispatcherServlet 如何持有 HandlerMapping 和 HandlerAdapter 列表?
源码解析:DispatcherServlet 在初始化方法 initStrategies(context) 中,会主动从 Spring 容器中获取所有实现 HandlerMapping 和 HandlerAdapter 接口的 Bean,封装为列表:
java
// 初始化 HandlerMapping
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
// 从 Spring 容器中获取所有 HandlerMapping 类型的 Bean
Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<>(matchingBeans.values());
// 排序(按优先级 order)
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
// 初始化 HandlerAdapter(逻辑与上面一致)
private void initHandlerAdapters(ApplicationContext context) {
// 同理,获取所有 HandlerAdapter 类型的 Bean 并排序
}
总结:DispatcherServlet 在初始化时,主动从 Spring 容器中抓取所有 HandlerMapping 和 HandlerAdapter,保存为列表,用于后续请求分发。
2.3 HandlerMapping(处理器映射器)
HandlerMapping 的核心作用是"根据请求 URL 找到对应的 Controller 方法",很多人误以为它是一个"大 Map",其实不然。
2.3.1 内部结构(源码简化)
以默认的 RequestMappingHandlerMapping 为例,其内部本质是"URL 与 HandlerMethod 的映射表 + 匹配规则":
java
public class RequestMappingHandlerMapping extends AbstractHandlerMethodMapping<RequestMappingInfo> {
// 核心:URL → HandlerMethod 的映射缓存
private final Map<String, HandlerMethod> urlLookupMap = new LinkedHashMap<>();
// Spring 启动时执行:扫描所有 @Controller 和 @RequestMapping
@Override
public void afterPropertiesSet() {
// 扫描全项目 @Controller 注解的类
detectHandlerMethods(this.getApplicationContext());
}
// 扫描方法,建立 URL 与 HandlerMethod 的映射
protected void detectHandlerMethods(Object handler) {
Class<?> handlerType = handler.getClass();
// 遍历类中的所有方法,解析 @RequestMapping 注解
for (Method method : handlerType.getMethods()) {
RequestMappingInfo mappingInfo = getMappingForMethod(method, handlerType);
if (mappingInfo != null) {
// 建立 URL 与 HandlerMethod 的映射,存入缓存
registerHandlerMethod(handler, method, mappingInfo);
urlLookupMap.put(mappingInfo.getPatternsCondition().getPatterns().iterator().next(),
new HandlerMethod(handler, method));
}
}
}
// 请求来时,根据 URL 查找 HandlerMethod
@Override
protected HandlerMethod getHandlerMethodForMappingPath(String mappingPath) {
return urlLookupMap.get(mappingPath);
}
}
2.3.2 为什么 DispatcherServlet 持有多个 HandlerMapping?
Spring MVC 启动时,会自动注册多个 HandlerMapping 实现类,每个对应一种 URL 匹配规则,而非一个"大 Map",原因如下:
-
匹配规则多样:支持精确匹配(/user/list)、通配符(/user/*)、后缀匹配(*.do)、Bean 名称匹配(BeanNameUrlHandlerMapping)、手动配置匹配(SimpleUrlHandlerMapping)等,每种规则的查找逻辑不同,无法用一个 Map 统一管理。
-
可扩展性:遵循开闭原则,若需自定义匹配规则,只需实现
HandlerMapping接口并交给 Spring 管理,DispatcherServlet 会自动将其加入列表,无需修改源码。 -
职责分离:每个 HandlerMapping 各司其职,例如
RequestMappingHandlerMapping管注解匹配,BeanNameUrlHandlerMapping管 Bean 名称匹配,互不干扰。
2.3.3 请求匹配流程(源码逻辑)
DispatcherServlet 遍历 HandlerMapping 列表,按优先级(order 越小,优先级越高)查找,第一个找到匹配 URL 的 HandlerMapping,返回HandlerExecutionChain(执行链),后续不再匹配:
java
// DispatcherServlet 查找 Handler 的方法
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
// 遍历所有 HandlerMapping
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
默认优先级:RequestMappingHandlerMapping> BeanNameUrlHandlerMapping > SimpleUrlHandlerMapping,因此注解匹配永远优先。
2.4 HandlerAdapter(处理器适配器)
核心疑问:DispatcherServlet 找到 HandlerMethod 后,为什么不直接执行,非要交给 HandlerAdapter?Adapter 到底适配什么?
2.4.1 设计模式:适配器模式 + 策略模式
Spring MVC 在这里使用了适配器模式,核心目的是"统一不同处理器的调用接口",同时结合策略模式,动态选择适配的适配器。
-
适配器模式:将不同调用方式的 Handler(如 @Controller 方法、Controller 接口、HttpRequestHandler),统一适配为
HandlerAdapter接口,让 DispatcherServlet 无需关心内部差异。 -
策略模式:多个 HandlerAdapter 对应多种适配策略,DispatcherServlet 根据 Handler 的类型,动态选择对应的适配器。
2.4.2 适配器模式示例(极简代码)
用生活中的"转接头"类比,清晰理解适配器的作用:
java
// 1. 目标接口(统一标准:HandlerAdapter)
interface HandlerAdapter {
boolean supports(Object handler); // 判断是否支持当前 Handler
void handle(HttpServletRequest request, HttpServletResponse response, Object handler); // 执行 Handler
}
// 2. 被适配者1:@Controller 方法(HandlerMethod)
class HandlerMethod {
private Object controller; // Controller 实例
private Method method; // 目标方法
// 省略构造器、getter/setter
}
// 3. 适配器1:RequestMappingHandlerAdapter(适配 HandlerMethod)
class RequestMappingHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
// 判断是否是 HandlerMethod(@Controller 方法)
return handler instanceof HandlerMethod;
}
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HandlerMethod handlerMethod = (HandlerMethod);
// 解析参数(HttpMessageConverter)
Object[] args = resolveArguments(request, handlerMethod);
// 反射执行 Controller 方法
handlerMethod.getMethod().invoke(handlerMethod.getController(), args);
}
// 参数解析(简化)
private Object[] resolveArguments(HttpServletRequest request, HandlerMethod handlerMethod) {
// 调用 HttpMessageConverter 解析请求参数,绑定到方法参数上
return new Object[0];
}
}
// 4. 被适配者2:老版 Controller 接口
interface Controller {
void handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
}
// 5. 适配器2:SimpleControllerHandlerAdapter(适配 Controller 接口)
class SimpleControllerHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
return handler instanceof Controller;
}
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 直接调用 Controller 接口的方法
((Controller) handler).handleRequest(request, response);
}
}
2.4.3 HandlerAdapter 的核心作用(3件事)
-
统一调用入口:DispatcherServlet 只调用
handlerAdapter.handle()方法,无需关心 Handler 是注解方法还是接口实现。 -
参数解析与绑定:通过
HttpMessageConverter解析请求参数(表单、JSON 等),完成类型转换、校验,传递给 Controller 方法。 -
返回值处理:将 Controller 方法的返回值(String、ModelAndView、实体类)统一处理,转为视图或 JSON 响应。
2.4.4 扩展自定义 HandlerAdapter
只需两步,即可自定义适配器并自动加入 DispatcherServlet 的适配器列表:
java
// 1. 实现 HandlerAdapter 接口
@Component // 2. 交给 Spring 管理,自动被 DispatcherServlet 抓取
public class MyHandlerAdapter implements HandlerAdapter {
// 判断是否支持自定义 Handler(这里假设自定义 MyHandler)
@Override
public boolean supports(Object handler) {
return handler instanceof MyHandler;
}
// 适配执行自定义 Handler
@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
MyHandler myHandler = (MyHandler);
myHandler.execute(); // 自定义执行逻辑
return null;
}
// 省略其他方法(getLastModified)
}
// 自定义 Handler
public class MyHandler {
public void execute() {
System.out.println("自定义 Handler 执行");
}
}
原理:DispatcherServlet 初始化时,会从 Spring 容器中获取所有 HandlerAdapter 类型的 Bean,自定义适配器会自动加入列表,请求来时,若匹配到 MyHandler,就会使用该适配器执行。
2.5 HandlerExecutionChain(执行链)
核心疑问:HandlerMapping 找到 HandlerMethod 后,为什么不直接返回 HandlerMethod,而是返回 HandlerExecutionChain?
2.5.1 执行链的结构(源码)
java
public class HandlerExecutionChain {
// 真正要执行的 Handler(Controller 方法/自定义 Handler)
private final Object handler;
// 该请求需要经过的所有拦截器
private List<HandlerInterceptor> interceptors;
// 执行拦截器 preHandle(请求进入 Controller 之前)
public boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
for (HandlerInterceptor interceptor : interceptors) {
if (!interceptor.preHandle(request, response, handler)) {
return false;
}
}
return true;
}
// 执行拦截器 postHandle(Controller 执行后,视图渲染前)
public void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
for (int i = interceptors.size() - 1; i >= 0; i--) {
interceptors.get(i).postHandle(request, response, handler, mv);
}
}
// 执行拦截器 afterCompletion(视图渲染后,响应返回前)
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex) throws Exception {
for (int i = interceptors.size() - 1; i >= 0; i--) {
interceptors.get(i).afterCompletion(request, response, handler, ex);
}
}
}
2.5.2 执行链的核心作用
请求的完整流程不仅是执行 Controller 方法,还需要在方法前后执行拦截器(如登录校验、权限控制、日志记录),执行链的作用就是"将 HandlerMethod 和拦截器打包,让 DispatcherServlet 能统一调度执行"。
完整执行顺序(结合拦截器):
java
// DispatcherServlet 中执行链的调用逻辑
// 1. 执行所有拦截器 preHandle
if (!mappedHandler.applyPreHandle(request, response)) {
return;
}
// 2. 执行 Controller 方法
ModelAndView mv = ha.handle(request, response, mappedHandler.getHandler());
// 3. 执行所有拦截器 postHandle
mappedHandler.applyPostHandle(request, response, mv);
// 4. 渲染视图
processDispatchResult(request, response, mappedHandler, mv, null);
// 5. 执行所有拦截器 afterCompletion
mappedHandler.afterCompletion(request, response, null);
2.6 拦截器、过滤器、监听器(区别与执行顺序)
三者常被混淆,核心区别在于"归属容器"和"执行时机",结合源码和流程,彻底分清:
2.6.1 三者归属与核心职责
| 组件 | 归属容器 | 核心接口 | 核心职责 | 设计模式 |
|---|---|---|---|---|
| 拦截器(HandlerInterceptor) | Spring MVC 容器 | HandlerInterceptor | 请求进入 Controller 前后、视图渲染后执行,可访问 Spring 容器 Bean,做精细控制(如权限校验) | 责任链模式 + AOP |
| 过滤器(Filter) | Tomcat(Servlet 容器) | javax.servlet.Filter | 请求进入 DispatcherServlet 之前执行,做全局过滤(如编码、跨域) | 责任链模式 |
| 监听器(Listener) | Tomcat(Servlet 容器) | 如 ServletContextListener、HttpSessionListener | 监听容器事件(项目启动/关闭、会话创建/销毁),不拦截请求,只做事件回调 | 观察者模式 |
2.6.2 执行顺序(必背)
Tomcat 启动
监听器(初始化)
请求进入 Tomcat
过滤器(doFilter)
DispatcherServlet
拦截器 preHandle
Controller 方法
拦截器 postHandle
视图渲染
拦截器 afterCompletion
回到过滤器
响应返回客户端
2.6.3 关键细节(源码关联)
-
过滤器:Spring 提供了多个现成 Filter,如
CharacterEncodingFilter(编码过滤)、CorsFilter(跨域),需在 web.xml 中配置或通过注解注册。 -
监听器:Spring 自身依赖监听器启动,最核心的是
ContextLoaderListener(后续详解),它实现了ServletContextListener,监听 Tomcat 启动事件,初始化 Spring 容器。 -
拦截器:自定义拦截器需实现
HandlerInterceptor,并通过WebMvcConfigurer配置加入 Spring MVC 容器。
三、Tomcat 与 Spring 容器的联动(核心:Spring 容器怎么启动?)
当 Spring Web 应用部署到 Tomcat 时,Tomcat 如何启动 Spring 容器?核心依赖 ContextLoaderListener 监听器。
3.1 核心流程(源码级)
-
Tomcat 启动,解析项目的 web.xml 文件(或注解配置),发现配置了
ContextLoaderListener。 -
Tomcat 初始化
ContextLoaderListener实例(因为它实现了ServletContextListener,Tomcat 会自动扫描并初始化所有监听器)。 -
Tomcat 启动完毕,发布
ServletContextEvent事件(容器启动事件)。 -
Tomcat 自动回调
ContextLoaderListener的contextInitialized(ServletContextEvent sce)方法。 -
该方法内部,Spring 初始化根容器(
WebApplicationContext),加载配置文件、扫描 Bean、初始化单例 Bean。
3.2 源码片段(ContextLoaderListener 核心方法)
java
public class ContextLoaderListener implements ServletContextListener {
private ContextLoader contextLoader;
@Override
public void contextInitialized(ServletContextEvent event) {
// 初始化 Spring 根容器
this.contextLoader = createContextLoader();
if (this.contextLoader == null) {
this.contextLoader = new ContextLoader();
}
// 核心:启动根容器
this.contextLoader.initWebApplicationContext(event.getServletContext());
}
@Override
public void contextDestroyed(ServletContextEvent event) {
// 关闭 Spring 根容器
if (this.contextLoader != null) {
this.contextLoader.closeWebApplicationContext(event.getServletContext());
}
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
3.3 完整 web.xml 配置(传统 SSM 项目)
web.xml 中需配置ContextLoaderListener、DispatcherServlet、过滤器等,完整配置如下(带详细注释):
xml
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!-- 1. 全局参数:指定 Spring 根容器配置文件路径(业务层 Bean:Service、Mapper、DataSource) -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-context.xml</param-value>
</context-param>
<!-- 2. 监听器:启动 Spring 根容器(Tomcat 启动时触发) -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 3. 编码过滤器(Spring 提供,解决中文乱码) -->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 4. 前端控制器:DispatcherServlet(启动 Spring MVC 子容器) -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 指定 Spring MVC 子容器配置文件(Web 层 Bean:Controller、HandlerMapping 等) -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<!-- 服务器启动时就初始化,而非第一次请求时 -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern> <!-- 拦截所有请求(除了 JSP) -->
</servlet-mapping>
</web-app>
3.4 配置文件的读取逻辑
-
根容器(ContextLoaderListener):读取
<context-param>中的contextConfigLocation,加载spring-context.xml,初始化业务层 Bean。 -
子容器(DispatcherServlet):读取自身
<init-param>中的contextConfigLocation,加载spring-mvc.xml,初始化 Web 层 Bean。 -
注意:两个
contextConfigLocation是独立的,互不干扰,对应两个容器。
四、Spring 父子容器(设计初衷 + 关闭影响)
Spring 为什么要分根容器(父容器)和 MVC 子容器?关闭其中一个会有什么后果?这是面试高频题,核心围绕"职责分离"和"依赖规则"。
4.1 为什么要分父子容器?(核心3点)
-
职责分离,分层干净 :
Web 层归 Web 层,业务层归业务层,避免 Bean 混乱,便于维护。
-
父容器(根容器):由 ContextLoaderListener 启动,管理业务层 Bean(Service、Mapper、DataSource、事务管理器、AOP),属于"通用层"。
-
子容器(MVC 容器):由 DispatcherServlet 启动,管理 Web 层 Bean(Controller、HandlerMapping、HandlerAdapter、ViewResolver、拦截器),属于"Web 层"。
-
-
防止依赖混乱和循环依赖 :
父子容器的核心规则:子容器能访问父容器的 Bean,父容器不能访问子容器的 Bean。这样可以避免:Service 注入 Controller(父容器不能访问子容器),只能是 Controller 注入 Service(子容器能访问父容器),天然防止层次混乱和循环依赖。
-
支持多 DispatcherServlet 场景 :
一个项目可以配置多个 DispatcherServlet(如 /api/* 对应一个子容器,/admin/* 对应另一个子容器),它们可以共用同一个父容器(业务层 Bean),减少重复配置,提高复用性。
4.2 父子容器结构图表
不可访问
不可访问
Tomcat 启动
ContextLoaderListener
根容器(父):可访问
DispatcherServlet1
DispatcherServlet2
子容器1:Controller1、HandlerMapping1
子容器2:Controller2、HandlerMapping2
4.3 关闭子容器(MVC 容器)会怎样?
关闭子容器 = 不配置 DispatcherServlet,或不配置其对应的配置文件,后果如下:
-
根容器正常启动:Service、Mapper、DataSource、事务等业务层 Bean 正常初始化,后台程序可正常运行(如定时任务)。
-
Web 层完全失效:没有 Controller、HandlerMapping、HandlerAdapter,无法接收和处理 HTTP 请求,浏览器访问任何接口都会返回 404。
一句话总结:项目活着,但不能处理请求,相当于一个纯后台程序。
4.4 关闭根容器会怎样?
关闭根容器 = 不配置 ContextLoaderListener,后果更严重:
-
子容器正常启动:DispatcherServlet 会创建 MVC 子容器,扫描并初始化 Controller,能接收 HTTP 请求(接口能访问)。
-
业务层完全失效:子容器中没有 Service、Mapper 等 Bean,Controller 中通过 @Autowired 注入 Service 时,会报
NoSuchBeanDefinitionException(找不到 Bean)或 NullPointerException(注入为 null),一调用业务逻辑就报错。
一句话总结:接口能访问,但业务逻辑无法执行,项目完全不可用。
4.5 终极总结
-
父子容器设计初衷:职责分层、防止依赖混乱、支持多 DispatcherServlet 共用业务层,核心规则是"子能访问父,父不能访问子"。
2. 关闭子容器:无 Web 能力,后台正常,接口 404; 3. 关闭根容器:Web 能进,但业务 Bean 找不到,直接空指针。
五、总结
本文从 Spring MVC 整体流程入手,逐步拆解核心组件、设计模式、Tomcat 与 Spring 容器的联动、父子容器等核心知识点,所有内容均结合源码和实例,适合学习记录和面试复习,核心背诵点如下:
-
Spring MVC 8步流程(请求→DispatcherServlet→HandlerMapping→HandlerAdapter→参数解析→Controller→返回值处理→响应)。
-
核心组件职责:DispatcherServlet(总指挥)、HandlerMapping(找方法)、HandlerAdapter(适配执行)、HandlerExecutionChain(方法+拦截器)。
-
设计模式:HandlerAdapter 用适配器模式+策略模式,Listener 用观察者模式,Filter/Interceptor 用责任链模式。
-
执行顺序:监听器→过滤器→DispatcherServlet→拦截器→Controller→拦截器→过滤器→响应。
-
Spring 容器启动:Tomcat 通过 ContextLoaderListener 监听器回调,初始化根容器;DispatcherServlet 初始化子容器。
-
父子容器:职责分离,子能访问父,关闭子容器404,关闭根容器空指针。