Spring MVC 核心原理全解析

本文基于个人学习过程中的高频提问,从 Spring MVC 整体流程入手,逐步拆解底层原理、核心组件、设计模式,再延伸到 Tomcat 与 Spring 容器的联动、父子容器设计等核心知识点,全程结合源码片段、实例代码和逻辑图表,兼顾专业性和易懂性,适合用于学习记录、面试复习。

一、Spring MVC 整体流程

Spring MVC 的核心是"请求分发-处理-响应"的闭环,整个流程共8步,是理解所有后续原理的基础,必须烂熟于心。

1.1 完整流程(带源码关联)

  1. 请求 → DispatcherServlet :客户端(浏览器/Postman)发送 HTTP 请求,所有请求统一被 Spring MVC 的前端控制器 DispatcherServlet 拦截(由 Tomcat 配置映射,后续详解)。

    源码关联:DispatcherServlet 本质是 HttpServlet,重写了 doGet()doPost() 方法,最终都会调用核心方法 doDispatch(request, response),这是整个 Spring MVC 流程的入口。

  2. HandlerMapping → 找到对应 @RequestMapping 方法DispatcherServlet 调用 HandlerMappinggetHandlerRequest()方法,根据请求 URL 匹配到对应的 Controller 方法。

    源码关联:默认使用 RequestMappingHandlerMapping,其内部会在 Spring 启动时扫描所有标注 @Controller@RequestMapping(含 @GetMapping/@PostMapping)的方法,将 URL 与方法封装为 HandlerMethod 并缓存。

  3. HandlerAdapter → 执行方法DispatcherServlet拿到 HandlerMethod 后,不会直接执行,而是交给HandlerAdapter 适配执行。

    源码关联:DispatcherServlet 会遍历所有 HandlerAdapter 实现类,通过 supports(handler) 方法判断哪个适配器支持当前 HandlerMethod,默认使用 RequestMappingHandlerAdapter

  4. 参数解析 → HttpMessageConverterHandlerAdapter 通过 HttpMessageConverter 解析请求参数(如表单、JSON、路径参数),完成类型转换、数据绑定和校验,将参数整理后传递给 Controller 方法。

    实例:客户端发送 JSON 格式请求体 {"name":"张三","age":20}MappingJackson2HttpMessageConverter 会将其解析为 Java 实体类 User

  5. 执行 ControllerHandlerAdapter 通过反射调用 Controller 对应的业务方法,执行核心业务逻辑(如调用 Service、Mapper 操作数据库)。

  6. 返回值处理 → HttpMessageConverter :Controller 方法执行完毕后,返回值(如 String、ModelAndView、Java 实体类)由 HandlerAdapter 通过 HttpMessageConverter 处理,例如将 Java 实体类转为 JSON 响应,或解析 String 为视图名称。

  7. 异常处理 → ExceptionResolver :执行过程中若出现异常(如业务异常、空指针),由 HandlerExceptionResolver 统一拦截处理,避免直接返回错误页面给客户端。

    源码关联:默认使用 DefaultHandlerExceptionResolver,也可自定义异常处理器(实现 HandlerExceptionResolver 接口)。

  8. 渲染页面 / 返回 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 容器中获取所有实现 HandlerMappingHandlerAdapter 接口的 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",原因如下:

  1. 匹配规则多样:支持精确匹配(/user/list)、通配符(/user/*)、后缀匹配(*.do)、Bean 名称匹配(BeanNameUrlHandlerMapping)、手动配置匹配(SimpleUrlHandlerMapping)等,每种规则的查找逻辑不同,无法用一个 Map 统一管理。

  2. 可扩展性:遵循开闭原则,若需自定义匹配规则,只需实现 HandlerMapping 接口并交给 Spring 管理,DispatcherServlet 会自动将其加入列表,无需修改源码。

  3. 职责分离:每个 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&gt; BeanNameUrlHandlerMapping &gt; 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件事)
  1. 统一调用入口:DispatcherServlet 只调用handlerAdapter.handle() 方法,无需关心 Handler 是注解方法还是接口实现。

  2. 参数解析与绑定:通过 HttpMessageConverter 解析请求参数(表单、JSON 等),完成类型转换、校验,传递给 Controller 方法。

  3. 返回值处理:将 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 核心流程(源码级)

  1. Tomcat 启动,解析项目的 web.xml 文件(或注解配置),发现配置了 ContextLoaderListener

  2. Tomcat 初始化 ContextLoaderListener 实例(因为它实现了 ServletContextListener,Tomcat 会自动扫描并初始化所有监听器)。

  3. Tomcat 启动完毕,发布 ServletContextEvent 事件(容器启动事件)。

  4. Tomcat 自动回调 ContextLoaderListenercontextInitialized(ServletContextEvent sce) 方法。

  5. 该方法内部,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 中需配置ContextLoaderListenerDispatcherServlet过滤器等,完整配置如下(带详细注释):

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点)

  1. 职责分离,分层干净

    Web 层归 Web 层,业务层归业务层,避免 Bean 混乱,便于维护。

    • 父容器(根容器):由 ContextLoaderListener 启动,管理业务层 Bean(Service、Mapper、DataSource、事务管理器、AOP),属于"通用层"。

    • 子容器(MVC 容器):由 DispatcherServlet 启动,管理 Web 层 Bean(Controller、HandlerMapping、HandlerAdapter、ViewResolver、拦截器),属于"Web 层"。

  2. 防止依赖混乱和循环依赖

    父子容器的核心规则:子容器能访问父容器的 Bean,父容器不能访问子容器的 Bean。这样可以避免:Service 注入 Controller(父容器不能访问子容器),只能是 Controller 注入 Service(子容器能访问父容器),天然防止层次混乱和循环依赖。

  3. 支持多 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 终极总结

  1. 父子容器设计初衷:职责分层、防止依赖混乱、支持多 DispatcherServlet 共用业务层,核心规则是"子能访问父,父不能访问子"。

    复制代码
     2. 关闭子容器:无 Web 能力,后台正常,接口 404;
    
     3. 关闭根容器:Web 能进,但业务 Bean 找不到,直接空指针。

五、总结

本文从 Spring MVC 整体流程入手,逐步拆解核心组件、设计模式、Tomcat 与 Spring 容器的联动、父子容器等核心知识点,所有内容均结合源码和实例,适合学习记录和面试复习,核心背诵点如下:

  1. Spring MVC 8步流程(请求→DispatcherServlet→HandlerMapping→HandlerAdapter→参数解析→Controller→返回值处理→响应)。

  2. 核心组件职责:DispatcherServlet(总指挥)、HandlerMapping(找方法)、HandlerAdapter(适配执行)、HandlerExecutionChain(方法+拦截器)。

  3. 设计模式:HandlerAdapter 用适配器模式+策略模式,Listener 用观察者模式,Filter/Interceptor 用责任链模式。

  4. 执行顺序:监听器→过滤器→DispatcherServlet→拦截器→Controller→拦截器→过滤器→响应。

  5. Spring 容器启动:Tomcat 通过 ContextLoaderListener 监听器回调,初始化根容器;DispatcherServlet 初始化子容器。

  6. 父子容器:职责分离,子能访问父,关闭子容器404,关闭根容器空指针。

相关推荐
Ulyanov2 小时前
《PySide6 GUI开发指南:QML核心与实践》 第二篇:QML语法精要——构建声明式UI的基础
java·开发语言·javascript·python·ui·gui·雷达电子对抗系统仿真
码界筑梦坊2 小时前
357-基于Java的大型商场应急预案管理系统
java·开发语言·毕业设计·知识分享
云烟成雨TD2 小时前
Spring AI Alibaba 1.x 系列【31】集成 Studio 模块实现可视化 Agent 调试
java·人工智能·spring
014-code2 小时前
Spring Data JPA 实战指南
java·spring
安小牛2 小时前
Android 开发汉字转带声调的拼音
android·java·学习·android studio
聚美智数2 小时前
企业实际控制人查询-公司实控人查询
android·java·javascript
zb200641202 小时前
SpringBoot详解
java·spring boot·后端
AI人工智能+电脑小能手3 小时前
【大白话说Java面试题】【Java基础篇】第7题:HashMap的get流程是什么
java·后端·面试·哈希算法·散列表·hash-index·hash
我头发多我先学3 小时前
C++ 模板全解:从泛型编程初阶到特化、分离编译进阶
java·开发语言·c++