当前位置: 首页 > news >正文

MVC问题记录

SpringMVC 和 Struts2 框架的对比

一、先行结论

  1. Spring MVC:当前 Java Web 领域的绝对主流与事实标准。设计理念现代、灵活性高,与 Spring 生态无缝集成,且安全性极强,是新项目的首选。
  2. Struts2:传统经典框架,曾广泛应用,但因严重安全漏洞(如 S2-045、S2-057 等远程代码执行漏洞) 、设计理念落后及维护更新停滞(Apache 已将其归入 attic 状态,即项目退休),已完全不推荐用于新项目

二、核心特性对比表

特性

SPRING MVC

STRUTS2

核心架构与设计

基于方法(Method :1 个 URL 映射到 1 个控制器类的 1 个方法

基于类(Class :1 个 URL 映射到 1 个 Action 类,执行其 execute 方法

拦截机制

处理器拦截器(HandlerInterceptor):细粒度控制(preHandle/postHandle/afterCompletion)

拦截器栈:功能强但设计复杂,基于责任链模式,需通过配置管理

控制器实现

@Controller注解,普通 POJO 类,方法参数 / 返回值灵活

需继承ActionSupport类或实现Action接口,与框架 API 强耦合

数据绑定

支持@RequestParam/@PathVariable/@RequestBody,与 Spring 转换器无缝集成

基于 OGNL 表达式,曾是安全漏洞主要来源

视图集成

高度解耦,支持 JSP/Thymeleaf/Freemarker 等,通过 ViewResolver 配置

与 JSP+OGNL 深度绑定,支持其他视图但灵活性差

性能

更高:控制器默认单例,无需频繁创建对象,减少 GC 压力

较低:Action 默认多例(每个请求创建新实例),GC 压力大

配置方式

推崇注解驱动 + Java Config,配置简洁易维护,XML 配置已淘汰

重度依赖 XML+“约定优于配置”,配置繁琐

与 Spring 生态集成

无缝集成(Spring 核心组件),可直接使用 IoC/AOP/ 事务 / Spring Security

集成困难,需额外插件 + 复杂配置才能对接 Spring IoC

安全性

极高:设计简洁 + 社区支持强,历史严重漏洞极少,与 Spring Security 是黄金组合

极差:历史大量高危 RCE 漏洞,是其衰落的核心原因

RESTful 支持

原生支持:通过@RestController/@GetMapping等注解轻松构建 REST API

支持差:需插件或手动配置,非 RESTful 优先设计

学习曲线

中等:有 Spring 基础则极易上手,设计直观

中等偏上:需理解拦截器栈、OGNL 等独特概念

当前状态与社区

极其活跃:Spring 生态核心,持续更新,社区庞大,行业标准

基本停滞:Apache 归入 attic 状态,不再维护

三、核心差异详解

3.1 架构设计:基于方法 vs 基于类

  1. Spring MVC 的优势
    1. 灵活性极致:1 个控制器类可包含多个方法,每个方法独立设计参数 / 返回值,便于测试与复用。
    2. 低耦合:控制器是普通 POJO,无需依赖框架 API。
  2. Struts2 的劣势
    1. 单一职责局限:1 个 Action 通常仅服务 1 个请求,虽可通过配置method属性指向不同方法,但远不如 Spring MVC 注解直观。
    2. 强耦合:必须继承ActionSupport或实现Action,与框架绑定紧密。

3.2 请求处理生命周期

  1. Spring MVC 流程

DispatcherServlet(前端控制器) → 匹配HandlerMapping → 执行HandlerInterceptorController → 通过ViewResolver解析视图

  1. Struts2 流程

Filter(核心控制器) → 执行配置的拦截器栈 → 调用Action → 返回结果字符串 → 匹配视图

注:Struts2 拦截器栈功能(如验证、文件上传)虽强,但导致框架 “重量级” 与复杂度飙升。

四、HTTP 响应体解析

4.1 直观理解响应体

以下是一个完整的 HTTP 响应示例,空行后的内容即为响应体

HTTP/1.1 200 OKContent-Type: application/json; charset=UTF-8Content-Length: 56Date: Wed, 24 Jan 2024 10:30:00 GMT{"id": 1, "name": "张三", "email": "zhangsan@example.com"}
  1. 前 4 行:响应头(Response Headers),描述响应元信息;
  2. 空行后:响应体(Response Body),实际传输的数据内容。

4.2 响应体的核心属性

4.2.1 响应体的位置

HTTP 响应的固定结构:

[状态行](如HTTP/1.1 200 OK)[响应头1](如Content-Type: application/json)[响应头2](如Content-Length: 56)...[空行](分隔响应头与响应体)[响应体](实际数据,如JSON/HTML/二进制文件)
4.2.2 响应体的内容类型

不同内容类型对应不同的Content-Type头,常见类型如下:

内容类型

示例

对应 CONTENT-TYPE

JSON 数据

{"name": "John", "age": 25}

application/json

HTML 页面

<html><body>Hello</body></html>

text/html

纯文本

Hello World

text/plain

XML 数据

<user><name>John</name></user>

application/xml

图片文件

二进制图像数据

image/jpeg/image/png

文件下载

二进制文件数据(如.zip/.pdf)

application/octet-stream

4.3 @ResponseBody的作用机制

@ResponseBody用于告诉 Spring:方法返回值直接写入响应体,不解析为视图名称

4.3.1 基础示例
@GetMapping("/user/{id}")@ResponseBody // 返回的User对象自动放入响应体public User getUser(@PathVariable Long id) {return userService.findById(id); // 最终转为JSON写入响应体}
4.3.2 处理流程
  1. 控制器方法返回User对象;
  2. Spring 检测到@ResponseBody注解;
  3. 选择合适的HttpMessageConverter(如 Jackson,默认处理 JSON);
  4. User对象序列化为 JSON 字符串;
  5. 将 JSON 字符串写入 HTTP 响应体;
  6. 自动设置响应头Content-Type: application/json

五、Spring MVC 拦截器工作机制

5.1 拦截器配置顺序(核心)

拦截器按配置顺序执行,形成责任链,可通过order()显式指定优先级(数值越小,优先级越高)。

@Configurationpublic class WebConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 1. 日志拦截器:优先级最高(order=1),拦截/api/**路径registry.addInterceptor(loggingInterceptor()).addPathPatterns("/api/**").order(1);// 2. 权限拦截器:优先级次之(order=2),拦截/api/**与/admin/**registry.addInterceptor(authInterceptor()).addPathPatterns("/api/**", "/admin/**").order(2);// 3. 性能监控拦截器:优先级最低(order=3),拦截所有路径registry.addInterceptor(performanceInterceptor()).addPathPatterns("/**").order(3);}}

5.2 拦截路径匹配规则

通过addPathPatterns()(包含路径)和excludePathPatterns()(排除路径)定义拦截范围,常见规则:

  1. /api/**:匹配所有以/api/开头的路径(含子路径,如/api/user/1);
  2. /admin/*:匹配/admin/下一级路径(不含子路径,如/admin/login,不匹配/admin/user/1);
  3. /**:匹配所有路径;
  4. /public/*.html:匹配/public/下所有.html 文件(如/public/index.html)。

5.3 拦截器执行流程

当请求到达时,Spring MVC 按以下步骤执行:

  1. order顺序检查拦截器是否匹配当前路径;
  2. 执行所有匹配拦截器的preHandle()(顺序:order=1order=2order=3);
  3. 执行控制器方法(若任意preHandle()返回false,则终止流程);
  4. 执行所有匹配拦截器的postHandle()(逆序:order=3order=2order=1);
  5. 渲染视图(若有);
  6. 执行所有匹配拦截器的afterCompletion()(逆序,且无论是否异常都会执行)。

六、@RestControllerAdvice@ControllerAdvice的区别

6.1 核心区别:注解组合关系

@RestControllerAdvice = @ControllerAdvice + @ResponseBody,即@RestControllerAdvice会自动为所有方法添加@ResponseBody效果。

6.1.1 @RestControllerAdvice示例(适合 REST API)
// 自动为所有方法添加@ResponseBody,返回值直接序列化为JSON@RestControllerAdvicepublic class GlobalExceptionHandler {@ExceptionHandler(RuntimeException.class) // 捕获RuntimeExceptionpublic ErrorResponse handleException(RuntimeException ex) {return new ErrorResponse("500", ex.getMessage()); // 返回JSON格式错误信息}}
6.1.2 @ControllerAdvice示例(适合传统 Web)

// 需手动控制返回类型,默认返回视图名称@ControllerAdvicepublic class GlobalExceptionHandler {@ExceptionHandler(RuntimeException.class)public String handleException(RuntimeException ex, Model model) {model.addAttribute("errorMsg", ex.getMessage()); // 向视图传递数据return "error-page"; // 返回视图名称(如error-page.jsp/error-page.html)}}

6.2 返回类型的本质差异

注解

返回值处理方式

适合场景

@RestControllerAdvice

直接序列化为 JSON/XML(无视图解析)

前后端分离、REST API 项目

@ControllerAdvice

作为视图名称解析(需视图引擎)

传统 Web 项目(JSP/Thymeleaf 渲染)

6.3 如何正确选择

  1. 项目类型:前后端分离选@RestControllerAdvice,传统 Web 选@ControllerAdvice
  2. 返回内容:需返回 JSON/XML 选前者,需返回 HTML 页面选后者;
  3. 技术栈:纯 API 项目(如微服务接口)选前者,使用 JSP/Thymeleaf 选后者。

七、@ResponseBody核心概念与实践

7.1 核心作用

告诉 Spring:方法返回值直接写入 HTTP 响应体,跳过视图解析流程,是构建 REST API 的基础。

7.2 与@RestController的关系

@RestController是组合注解,等价于@Controller + @ResponseBody,即类上添加@RestController后,所有方法默认具有@ResponseBody效果。

// 以下两种写法完全等价// 写法1:@Controller + @ResponseBody@Controller@ResponseBodypublic class UserController {@GetMapping("/user/1")public User getUser() { return new User("张三", 20); }}// 写法2:@RestController(推荐,更简洁)@RestControllerpublic class UserController {@GetMapping("/user/1")public User getUser() { return new User("张三", 20); }}

7.3 消息转换器(HttpMessageConverter

@ResponseBody的底层依赖HttpMessageConverter,Spring 根据返回值类型和请求头Accept自动选择转换器,常见转换器:

转换器

功能

默认生效条件

MappingJackson2HttpMessageConverter

将对象转为 JSON

项目依赖 Jackson(如jackson-databind

Jaxb2RootElementHttpMessageConverter

将对象转为 XML

项目依赖 JAXB

StringHttpMessageConverter

直接返回字符串(不序列化)

方法返回值为String类型

7.4 支持的返回值类型

@RestControllerpublic class ExampleController {// 1. 返回对象:自动转为JSON@GetMapping("/object")public User returnObject() {return new User("John", 25);}// 2. 返回集合:自动转为JSON数组@GetMapping("/list")public List<User> returnList() {return Arrays.asList(new User("John"), new User("Jane"));}// 3. 返回Map:自动转为JSON对象@GetMapping("/map")public Map<String, Object> returnMap() {Map<String, Object> map = new HashMap<>();map.put("name", "John");map.put("age", 25);return map;}// 4. 返回字符串:直接写入响应体(Content-Type: text/plain)@GetMapping("/string")public String returnString() {return "Hello World";}// 5. 返回ResponseEntity:灵活控制响应头/状态码@GetMapping("/response-entity")public ResponseEntity<User> returnResponseEntity() {User user = new User("John");return ResponseEntity.ok() // 状态码200.header("Custom-Header", "spring-mvc") // 自定义响应头.body(user); // 响应体内容}}

7.5 内容协商(Content Negotiation)

Spring 支持根据请求头Accept返回不同格式的数据,通过produces指定支持的类型:

// 支持返回JSON或XML,根据请求头Accept选择@GetMapping(value = "/user/{id}",produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})public User getUser(@PathVariable Long id) {return userService.findById(id);}
  1. 客户端请求头Accept: application/json → 返回 JSON;
  2. 客户端请求头Accept: application/xml → 返回 XML。

7.6 常见问题与解决方案

问题 1:返回中文乱码

解决方案:通过produces指定字符集:

@GetMapping(value = "/data", produces = "application/json;charset=UTF-8")public String getData() {return "中文数据"; // 避免乱码}
问题 2:自定义 JSON 序列化(如忽略字段、格式化日期)

解决方案:使用 Jackson 注解:

@Data // Lombok注解,自动生成getter/setterpublic class User {@JsonIgnore // 序列化时忽略password字段private String password;@JsonProperty("user_name") // 序列化后字段名为user_name(而非username)private String username;@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") // 日期格式化private Date createTime;}
问题 3:全局配置字符编码

解决方案:配置HttpMessageConverter

@Configurationpublic class WebConfig implements WebMvcConfigurer {@Overridepublic void configureMessageConverters(List<HttpMessageConverter<?>> converters) {// 配置String转换器,全局使用UTF-8StringHttpMessageConverter stringConverter = new StringHttpMessageConverter(StandardCharsets.UTF_8);converters.add(0, stringConverter); // 优先使用自定义转换器}}

7.7 与@RequestBody的对比

注解

作用

使用位置

@ResponseBody

输出:将方法返回值写入响应体

方法上或类上

@RequestBody

输入:将请求体转为方法参数

方法参数上

示例:同时处理请求体输入与响应体输出

@PostMapping("/users")public User createUser(@RequestBody User user) {// @RequestBody:将请求体JSON转为User对象(输入)User savedUser = userService.save(user);return savedUser; // @ResponseBody(因类上有@RestController):将User转为JSON写入响应体(输出)}

7.8 总结与最佳实践

核心作用
  1. 跳过视图解析,直接操作响应体;
  2. 自动序列化(对象→JSON/XML);
  3. 支撑 RESTful API 构建;
  4. 支持内容协商与自定义配置。
适用场景
  1. ✅ 构建 RESTful Web 服务;
  2. ✅ 前后端分离项目;
  3. ✅ 提供 JSON/XML 数据接口;
  4. ✅ 处理 Ajax 请求响应。
最佳实践
  1. 纯 API 项目:类上用@RestController(无需重复加@ResponseBody);
  2. 混合项目(既有 API 也有页面):仅在 API 方法上加@ResponseBody
  3. 需控制响应头 / 状态码:用ResponseEntity
  4. 明确返回类型:通过produces指定Content-Type(如produces = "application/json;charset=UTF-8")。

八、为什么异常处理会触发拦截器?

8.1 拦截器在异常处理前执行

拦截器的preHandle()在控制器方法执行之前调用,即使控制器抛出异常,preHandle()已执行完成。

public class LoggingInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request,HttpServletResponse response,Object handler) {System.out.println("preHandle:在控制器执行前调用");return true; // 继续流程}}

8.2 异常处理后的完整流程

Spring MVC 会确保请求流程 “完整性”,即使发生异常,也会执行afterCompletion()。内部逻辑可简化为:

// 模拟Spring MVC内部流程try {// 1. 执行所有拦截器的preHandle()(顺序执行)for (Interceptor interceptor : interceptors) {if (!interceptor.preHandle(request, response, handler)) {return; // 若preHandle()返回false,终止流程}}// 2. 执行控制器方法(可能抛出异常)handler.handle(request, response);// 3. 执行所有拦截器的postHandle()(逆序执行)for (Interceptor interceptor : reverse(interceptors)) {interceptor.postHandle(request, response, handler, modelAndView);}// 4. 渲染视图renderView(modelAndView);} catch (Exception ex) {// 5. 异常处理(如@ControllerAdvice)handleException(ex, request, response);} finally {// 6. 执行所有拦截器的afterCompletion()(逆序执行,无论是否异常)for (Interceptor interceptor : reverse(interceptors)) {interceptor.afterCompletion(request, response, handler, ex);}}

8.3 afterCompletion():异常场景的 “兜底” 方法

afterCompletion()唯一无论是否异常都会执行的拦截器方法,常用于资源清理(如关闭流、释放连接)。

public class LoggingInterceptor implements HandlerInterceptor {@Overridepublic void afterCompletion(HttpServletRequest request,HttpServletResponse response,Object handler,Exception ex) {// 即使控制器抛出异常,此方法仍会执行System.out.println("afterCompletion:异常信息=" + (ex == null ? "无" : ex.getMessage()));}}

8.4 实际场景对比

场景 1:正常请求处理
  1. 拦截器preHandle() → 2. 控制器方法执行 → 3. 拦截器postHandle() → 4. 渲染视图 → 5. 拦截器afterCompletion()
场景 2:控制器抛出异常
  1. 拦截器preHandle()(已执行) → 2. 控制器抛出异常 → 3. 跳过postHandle() → 4. @ControllerAdvice处理异常 → 5. 拦截器afterCompletion()(仍执行)
http://www.dtcms.com/a/361138.html

相关文章:

  • Linux初始——编译器gcc
  • [Java]PTA:jmu-java-01入门-基本输入
  • Spark自定义累加器实现高效WordCount
  • 众擎机器人开源代码解读
  • 液态神经网络(LNN)2:LTC改进成CFC详细推导过程
  • Linux 孤儿进程 (Orphan Process)
  • 动作指令活体检测通过动态交互验证真实活人,保障安全
  • 【大模型】大模型微调-RLHF(强化学习)
  • 技术速递|构建你的第一个 MCP 服务器:如何使用自定义功能扩展 AI 工具
  • 分享智能电动窗帘方案
  • 串口通讯个人见解
  • 智能核心:机器人芯片的科技革新与未来挑战
  • 【STM32】贪吃蛇 [阶段 8] 嵌入式游戏引擎通用框架设计
  • 山东教育报省级报刊简介
  • Axios拦截器:前端通信的交通警察[特殊字符]
  • 手机网络IP归属地更改方法总结
  • 人工智能-python-深度学习-项目全流程解析
  • LeetCode刷题记录----74.搜索二维矩阵(Medium)
  • 2025年中国GEO优化服务商全景分析:技术演进、核心能力与选型指南
  • 设计模式14-组合模式
  • 内存管理 - 从虚拟到物理
  • ADSL 代理 Proxy API 申请与使用指南
  • 前端安全防护深度实践:从XSS到CSRF的完整安全解决方案
  • T507 音频调试
  • 在 Qt 中:QString 好,还是 std::string 好?
  • DVWA靶场通关笔记-Weak Session IDs (Impossible级别)
  • 【Flask】测试平台开发,实现全局邮件发送工具 第十二篇
  • 【SpringBoot】20 - SpringBoot中的Ajax和MyBatis究竟是什么?
  • 【lucene核心】impacts的由来
  • 【Web安全】CRLF注入攻击深度解析:原理、场景与安全测试防御指南