乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 SpringMVC概述与DispatcherServlet

    • 本章导读:Spring MVC概述与DispatcherServlet
    • Spring MVC 是什么
    • MVC 设计模式
    • 前端控制器模式
    • DispatcherServlet
    • 核心组件协作
  • 第2章 控制器与请求映射

    • 本章导读:控制器与请求映射
    • Controller
    • RestController
    • RequestMapping
    • GetMapping
    • PostMapping
    • PutMapping
    • DeleteMapping
    • PathVariable
    • RESTful
    • 请求映射原理
  • 第3章 请求参数获取与转换

    • 本章导读:请求参数获取与转换
    • RequestParam
    • RequestBody
    • RequestHeader
    • CookieValue
    • Model
    • ModelAttribute
    • 数据绑定原理
    • 数据校验
  • 第4章 响应数据与视图解析

    • 本章导读:响应数据与视图解析
    • ResponseBody
    • ResponseEntity
    • ModelAndView
    • ViewResolver
    • HttpMessageConverter
    • forward与redirect
  • 第5章 拦截器过滤器与跨域

    • 本章导读:拦截器、过滤器与跨域
    • HandlerInterceptor
    • WebMvcConfigurer
    • CrossOrigin
    • 登录验证实战
  • 第6章 文件上传与异常处理

    • 本章导读:文件上传与异常处理
    • MultipartFile
    • 文件下载
    • ExceptionHandler
    • ControllerAdvice
    • RestControllerAdvice
    • ResponseStatus
  • 第7章 高级特性与最佳实践

    • 本章导读:高级特性与最佳实践
    • SessionAttributes
    • SessionAttribute
    • RedirectAttributes
    • MockMvc测试
    • 国际化
    • 最佳实践
  • 第8章 扩展与异步机制

    • 本章导读:扩展与异步机制
    • 异步请求处理
    • 自定义参数解析器
    • 内容协商

前端控制器模式

本章是 Spring MVC 教程的架构思想层。上一章"Spring MVC 是什么"回答了"为什么需要 Spring MVC",本章深入回答"为什么 Spring MVC 选择 DispatcherServlet 作为唯一入口"。理解前端控制器模式这一设计思想,才能真正明白 DispatcherServlet 存在的合理性,以及它与传统 Servlet 开发的本质区别。


定义与作用

前端控制器模式(Front Controller Pattern)是一种经典的 Web 架构设计模式,其核心思想是:用一个中央控制器接收所有进入应用的请求,再由这个中央控制器根据请求特征分发给具体的业务处理器。这个中央控制器本身不处理业务逻辑,只负责"接收、分发、协调、收尾"。

在 Java Web 领域,前端控制器模式解决了传统 Servlet 开发中三个根深蒂固的问题:

问题传统 Servlet 的表现前端控制器模式的解决方式
重复代码泛滥每个 Servlet 都要写编码处理、认证检查、日志记录中央控制器统一处理,所有请求共享
配置无限膨胀每增加一个 URL 就要在 web.xml 加一对 servlet-mapping一个 Servlet 映射所有请求,路由逻辑内聚到代码中
横切逻辑分散登录校验、权限检查散落在各个 Servlet 中拦截器链在中央控制器处统一挂载,集中管理

与传统 Servlet 的架构对比

传统 Servlet 开发中,每个功能对应一个独立的 Servlet 类,客户端请求直接打到各个 Servlet 上:

上图的致命缺陷在于:编码处理、认证检查、日志记录这些通用逻辑被复制到了每一个 Servlet 中。当白歌架构师要求"所有接口增加请求耗时统计"时,小崔不得不修改 50 个 Servlet 类。

前端控制器模式彻底扭转了这一局面:

关键转变:通用逻辑从"分散在 N 个 Servlet"变成了"集中在 1 个 DispatcherServlet"。新增业务只需增加 Controller 方法,无需触碰中央控制器的任何代码。

生活类比:医院挂号台

想象你去医院看病:

  • 传统 Servlet 模式:医院有 20 个科室,每个科室门口都有一个独立的挂号窗口。你要去骨科就去骨科窗口排队,要去内科就去内科窗口排队。每个窗口都要重复问你"有没有医保卡""有没有过敏史""手机号是多少"——同样的信息采集重复 20 次。如果医院新增"体检中心",就要再开一个窗口、再配一套信息采集流程。

  • 前端控制器模式:医院只有一个总挂号台(DispatcherServlet)。你走到总挂号台,工作人员统一采集你的基本信息(编码处理、认证检查),然后查询科室分布表(HandlerMapping),给你一张导诊单(HandlerExecutionChain),指引你去对应科室(Controller)看病。看完病后,报告单交回总挂号台,总挂号台统一打印、盖章、交付(视图渲染)。如果医院新增科室,只需在科室分布表上增加一条记录,总挂号台本身不需要任何改造。

这个类比的关键在于:总挂号台不看病,它只负责"统一入口、统一预处理、统一分发、统一收尾"。

其他使用前端控制器模式的框架

前端控制器模式并非 Spring MVC 独创,它是 Java Web 框架的通用架构选择:

框架前端控制器类映射路径
Spring MVCDispatcherServlet/(默认)
Struts 1.xActionServlet*.do(常见配置)
JSFFacesServlet/faces/* 或 *.jsf
Spring WebFluxDispatcherHandler/(响应式版本)

这些框架的前端控制器职责高度一致:统一接收请求、统一预处理、统一路由分发、统一响应包装。差异只在于内部组件的名称和实现细节。


核心原理

请求流经前端控制器的完整时序

前端控制器模式的核心价值,在于它定义了一套标准化的请求处理流水线。所有请求必须经过相同的预处理、分发、后处理环节:

上图揭示了一个关键设计:前端控制器不是简单的"转发器",而是一个完整的请求处理框架。它定义了预处理、路由、拦截、执行、后处理、清理六个标准阶段,所有请求一视同仁地经过这条流水线。

为什么"一个入口"优于"N 个入口"

从软件工程角度看,前端控制器模式符合单一职责原则的反向应用——不是让每个组件职责单一,而是让同一类职责只在一个地方实现:

职责传统 Servlet(分散实现)前端控制器(集中实现)
请求编码每个 Servlet 手动设置DispatcherServlet 统一设置 CharacterEncodingFilter
登录校验每个 Servlet 手动检查 Session拦截器链统一挂载 AuthenticationInterceptor
请求日志每个 Servlet 手动打印DispatcherServlet 统一记录请求 URI、方法、耗时
异常处理每个 Servlet 写 try-catch@ControllerAdvice 全局捕获
跨域配置每个响应头手动设置CORS 过滤器统一处理

结论:当系统有 N 个接口时,传统模式把通用逻辑复制了 N 份,前端控制器模式只实现 1 份。N 越大,优势越明显。


完整示例

场景

飞翔科技要开发一个电商平台,预计有 80 多个 REST 接口。CTO 大翔召集技术会议,要求架构师白歌评估"传统 Servlet 方案"和"前端控制器方案"的架构差异。后端工程师小崔、前端工程师黄俪、运维工程师李眉参与讨论。

传统 Servlet 方案(被否决)

小崔先按传统思路写了一个原型:

// ProductServlet.java
public class ProductServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        // 每个 Servlet 重复:编码处理
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("application/json;charset=UTF-8");
        
        // 每个 Servlet 重复:登录校验
        HttpSession session = req.getSession();
        if (session.getAttribute("user") == null) {
            resp.setStatus(401);
            resp.getWriter().write("{\"error\":\"未登录\"}");
            return;
        }
        
        // 每个 Servlet 重复:请求日志
        System.out.println("[" + new Date() + "] " + req.getMethod() + " " + req.getRequestURI());
        
        // 业务逻辑(这才是真正该写的代码)
        String id = req.getParameter("id");
        Product product = productService.findById(Integer.parseInt(id));
        
        // 每个 Servlet 重复:JSON 序列化
        String json = "{\"id\":" + product.getId() + ",\"name\":\"" + product.getName() + "\"}";
        resp.getWriter().write(json);
    }
}
// OrderServlet.java
public class OrderServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        // 同样的编码处理、登录校验、请求日志...又复制了一遍
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("application/json;charset=UTF-8");
        // ... 重复代码省略
    }
}

web.xml 配置:

<!-- 80 个接口 = 80 对 servlet + servlet-mapping -->
<servlet><servlet-name>product</servlet-name><servlet-class>com.feixiang.ProductServlet</servlet-class></servlet>
<servlet-mapping><servlet-name>product</servlet-name><url-pattern>/product</url-pattern></servlet-mapping>

<servlet><servlet-name>order</servlet-name><servlet-class>com.feixiang.OrderServlet</servlet-class></servlet>
<servlet-mapping><servlet-name>order</servlet-name><url-pattern>/order</url-pattern></servlet-mapping>

<!-- 还有 78 个... -->

团队反馈:

  • 白歌(架构师):"通用逻辑复制了 80 份,一旦要改认证方式(比如从 Session 改 JWT),小崔你要改 80 个文件?"
  • 黄俪(前端):"我发现不同接口的错误格式不一样,有的返回 JSON,有的返回 HTML,有的直接 500 空白页。"
  • 李眉(运维):"我想统计每个接口的 QPS 和平均耗时,但日志格式不统一,根本没法做监控。"
  • 大翔(CTO):"这个方案不可接受。换一个思路。"

前端控制器方案(被采纳)

白歌提出了基于 Spring MVC 前端控制器模式的方案:

// 统一编码过滤器(由 DispatcherServlet 前置处理)
public class EncodingFilter implements Filter {
    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) 
            throws IOException, ServletException {
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("application/json;charset=UTF-8");
        chain.doFilter(req, resp);
    }
}
// 统一认证拦截器(挂载在 DispatcherServlet 的拦截器链上)
public class AuthInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) 
            throws Exception {
        if (req.getSession().getAttribute("user") == null) {
            resp.setStatus(401);
            resp.getWriter().write("{\"error\":\"未登录\"}");
            return false; // 拦截,不继续执行
        }
        return true; // 放行
    }
}
// 业务控制器(只写业务逻辑,不写任何通用代码)
@RestController
@RequestMapping("/api")
public class ProductController {

    @Autowired
    private ProductService productService;

    @GetMapping("/products/{id}")
    public Product getProduct(@PathVariable int id) {
        // 只关注业务:根据 ID 查询商品
        return productService.findById(id);
    }
}

@RestController
@RequestMapping("/api")
public class OrderController {

    @Autowired
    private OrderService orderService;

    @GetMapping("/orders/{id}")
    public Order getOrder(@PathVariable int id) {
        // 只关注业务:根据 ID 查询订单
        return orderService.findById(id);
    }
}

Spring MVC 配置:

@Configuration
@EnableWebMvc
@ComponentScan("com.feixiang")
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 拦截器统一挂载:所有 /api/** 请求都要经过认证检查
        registry.addInterceptor(new AuthInterceptor())
                .addPathPatterns("/api/**");
    }
}

团队反馈:

  • 白歌:"通用逻辑全部集中到 DispatcherServlet 和拦截器链上。改认证方式只需改 AuthInterceptor 一个类。"
  • 黄俪:"所有接口的错误格式统一了,401 由 AuthInterceptor 统一返回,500 由 @ControllerAdvice 统一处理。"
  • 李眉:"我在 DispatcherServlet 层加了一个耗时统计拦截器,所有接口的 QPS 和 RT 自动输出到日志,格式完全一致。"
  • 大翔:"这才是正确的架构方向。小崔,后续所有业务接口都按这个模式写。"

易错场景与面试考点

误区一:前端控制器模式"消灭"了 Servlet

错误认知:"用了 DispatcherServlet,就等于不用学 Servlet 了。"

纠正:DispatcherServlet 本身就是 Servlet,它继承自 HttpServlet,由 Servlet 容器(Tomcat)创建和调用。前端控制器模式不是"替代 Servlet",而是在 Servlet 之上建立了一层架构。不理解 Servlet 生命周期,就无法理解:

  • 为什么 DispatcherServlet 需要在 init() 中初始化 Spring 容器
  • 为什么 service() 方法是所有请求的入口
  • 为什么 Filter 在 DispatcherServlet 之前执行

误区二:前端控制器模式等于"单例瓶颈"

错误认知:"所有请求都走一个 DispatcherServlet,这不是单点瓶颈吗?"

纠正:DispatcherServlet 虽然是逻辑上的唯一入口,但 Servlet 容器会为它创建多线程并发处理机制。Tomcat 的线程池同时处理多个请求,每个请求在 DispatcherServlet 的 service() 方法中有独立的调用栈。DispatcherServlet 本身是无状态的,不存在线程安全问题。真正可能成为瓶颈的是后端业务逻辑或数据库,而不是 DispatcherServlet。

误区三:任何项目都应该用前端控制器模式

错误认知:"前端控制器模式是最佳实践,所以所有 Web 项目都应该用 Spring MVC。"

纠正:前端控制器模式适合请求类型多样、通用逻辑复杂、需要集中管控的项目。如果一个项目只有 2-3 个接口,且没有复杂的预处理需求,直接使用原生 Servlet 或 JAX-RS 可能更轻量。架构选择取决于问题复杂度,而非"哪个更流行"。

面试高频:前端控制器模式解决了什么问题?

标准回答:

  1. 消除重复代码:编码处理、认证检查、日志记录等通用逻辑从 N 个 Servlet 集中到 1 个前端控制器
  2. 统一请求入口:所有请求走同一条预处理流水线,便于集中管控和监控
  3. 解耦路由与业务:路由逻辑由前端控制器和 HandlerMapping 负责,业务处理器只关注业务
  4. 标准化横切逻辑:拦截器链机制让登录校验、权限检查、耗时统计等横切逻辑可插拔、可复用
  5. 降低维护成本:新增业务只需增加处理器,无需修改中央控制器的任何代码

面试高频:Struts 的 ActionServlet 和 Spring MVC 的 DispatcherServlet 有什么区别?

标准回答:两者都是前端控制器模式的具体实现,核心职责相同(接收请求、统一预处理、路由分发)。主要区别在于内部架构:

  • ActionServlet 的路由配置依赖 XML(struts-config.xml),处理器必须是 Action 类的 execute 方法
  • DispatcherServlet 的路由支持注解(@RequestMapping),处理器可以是任意方法,通过 HandlerAdapter 适配不同签名,扩展性更强

小结

前端控制器模式是 Spring MVC 的架构基石。它用一个中央控制器(DispatcherServlet)替代了传统开发中 N 个独立 Servlet 的分散入口模式,将编码处理、认证检查、日志记录等通用逻辑从业务代码中剥离出来,实现了集中管控、统一标准、解耦复用。

本章与全局的关系:本章从设计模式视角解释了"为什么需要 DispatcherServlet"。下一章"DispatcherServlet"将聚焦这个前端控制器的具体实现——它如何初始化、如何接收请求、如何协调内部组件。再下一章"核心组件协作"将深入讲解 DispatcherServlet 如何与 HandlerMapping、HandlerAdapter、ViewResolver 配合完成请求调度。

上一页
MVC 设计模式
下一页
DispatcherServlet