【Spring】SpringMVC

SpringMVC

  • MVC
    • [什么是 MVC?](#什么是 MVC?)
      • [1. Model(模型)](#1. Model(模型))
      • [2. View(视图)](#2. View(视图))
      • [3. Controller(控制器)](#3. Controller(控制器))
    • [Spring MVC 中的 MVC 实现](#Spring MVC 中的 MVC 实现)
      • 核心组件
      • [Spring MVC 的工作流程](#Spring MVC 的工作流程)
    • [MVC 的优势](#MVC 的优势)
    • [Spring MVC 的扩展](#Spring MVC 的扩展)
  • 建立请求连接
  • 请求处理
    • [查询参数 : @RequestParam](#查询参数 : @RequestParam)
      • [@RequestParam 对空字符串和默认值的独特处理规则](#@RequestParam 对空字符串和默认值的独特处理规则)
    • 路径变量:@PathVariable
    • [请求体(JSON / XML):@RequestBody](#请求体(JSON / XML):@RequestBody)
    • 表单参数与文件上传
    • 数组和集合上传
    • [请求头与 Cookie:@RequestHeader、@CookieValue](#请求头与 Cookie:@RequestHeader、@CookieValue)
    • [参数解析流程(Mermaid 图)](#参数解析流程(Mermaid 图))
  • 响应返回
    • 返回普通字符串
    • [返回 JSON 对象](#返回 JSON 对象)
    • 设置响应状态码
      • [方式一:使用 @ResponseStatus 注解](#方式一:使用 @ResponseStatus 注解)
      • [方式二:通过 HttpServletResponse 手动设置](#方式二:通过 HttpServletResponse 手动设置)
      • 两种方式对比
    • 返回HTML页面:@Controller
      • 准备工作
      • [使用 @Controller 返回页面](#使用 @Controller 返回页面)
      • [@Controller + @ResponseBody 返回数据](#@Controller + @ResponseBody 返回数据)
      • [@Controller 与 @RestController 的区别](#@Controller 与 @RestController 的区别)
    • 设置Content-Type
      • [通过 produces 属性设置](#通过 produces 属性设置)
      • [通过 HttpServletResponse 手动设置](#通过 HttpServletResponse 手动设置)
      • [常见 Content-Type 取值](#常见 Content-Type 取值)
      • 两种方式对比
    • 设置Header
      • [方式一:通过 HttpServletResponse 手动设置](#方式一:通过 HttpServletResponse 手动设置)
      • [方式二:通过 @ResponseHeader 注解(Spring 6.0+)](#方式二:通过 @ResponseHeader 注解(Spring 6.0+))
      • [方式三:通过 ResponseEntity 设置](#方式三:通过 ResponseEntity 设置)
      • [常见 Header 设置场景](#常见 Header 设置场景)
      • 三种方式对比

MVC

什么是 MVC?

MVC(Model-View-Controller)是一种经典的软件设计模式,它将应用程序分为三个核心组件:
Model View Controller Model View Controller 1. 请求 2. 业务处理 3. 返回结果 4. 响应

1. Model(模型)

  • 职责:处理业务逻辑和数据操作
  • 特点
    • 包含应用程序的数据和业务规则
    • 独立于用户界面
    • 负责数据的存取、验证和处理
  • 示例 :在 Spring MVC 中,Model 可以是实体类(如 User)、Service 层、DAO 层等

2. View(视图)

  • 职责:负责数据的展示
  • 特点
    • 将 Model 中的数据以特定格式呈现给用户
    • 不包含业务逻辑
    • 只负责显示数据
  • 示例:HTML 页面、JSP、Thymeleaf 模板、JSON 响应等

3. Controller(控制器)

  • 职责:接收用户输入,协调 Model 和 View
  • 特点
    • 处理用户请求
    • 调用 Model 处理业务逻辑
    • 选择合适的 View 进行渲染
  • 示例 :Spring MVC 中的 @Controller@RestController 注解的类

Spring MVC 中的 MVC 实现

核心组件

MVC 三层
客户端请求
DispatcherServlet
HandlerMapping
Controller

接收请求、协调
Model

业务逻辑、数据
返回 ModelAndView 或数据
ViewResolver
View

数据展示
返回响应

Spring MVC 的工作流程

  1. DispatcherServlet:前端控制器,接收所有 HTTP 请求
  2. HandlerMapping:根据请求 URL 找到对应的 Controller 方法
  3. Controller:处理请求,调用业务逻辑,返回结果
  4. ViewResolver:解析视图名称,找到对应的视图模板
  5. View:渲染最终结果,返回给客户端

MVC 的优势

  1. 分离关注点:业务逻辑、数据展示、用户输入处理分离
  2. 代码复用:Model 可以在多个 View 中复用
  3. 易于维护:各组件职责清晰,修改影响范围小
  4. 并行开发:前端和后端可以独立开发

Spring MVC 的扩展

除了传统的 MVC,Spring 还支持:

  • RESTful API :通过 @RestController 实现
  • WebFlux:响应式编程模型
  • WebSocket:实时双向通信
  • 文件上传MultipartFile 处理

通过理解 MVC 设计模式,可以更好地掌握 Spring MVC 的工作原理,编写出结构清晰、易于维护的 Web 应用程序。# 整体流程概览:从请求到响应

Spring MVC 处理请求的核心入口是 DispatcherServlet。它拦截所有 HTTP 请求,然后通过 处理器映射(Handler Mapping) 来查找对应的处理方法。@RequestMapping 正是定义映射规则的关键注解,而 @RestController 则决定了方法返回值如何写入响应体。下面的流程图展示了这一完整过程:


客户端HTTP请求
DispatcherServlet

前端控制器
HandlerMapping

根据注解查找处理器
匹配到

对应的Handler?
执行Controller方法

如@RestController方
返回404 Not Found
方法返回对象/字符串
HttpMessageConverter

将返回值序列化

(对象→JSON等)
返回HTTP响应给客户端

  • @RestController 标记的类会被 Spring 自动扫描并注册为 Bean,成为可以处理请求的控制器。
  • @RequestMapping 负责将特定的 URL 路径与控制器中的方法绑定,HandlerMapping 正是依赖这些注解来找到对应处理器。
  • 整个流程简单来讲:DispatcherServlet 借助 @RequestMapping 的映射找到 @RestController 中的方法,执行后直接将返回值序列化到响应体。

建立请求连接

单一路径映射

最简单的场景:请求一个路径,返回一个字符串。

java 复制代码
@RestController
public class UserController {

    @RequestMapping("/user")
    public String getUser() {
        return "user info";
    }
}

访问GET /user → 响应体为 user info

关键点

  • @RestController = @Controller + @ResponseBody
    • @Controller 将类标记为 Spring MVC 的控制器。
    • @ResponseBody 表示该类中所有方法的返回值都直接写入 HTTP 响应体(通常转换为 JSON 或纯文本),而不是跳转到视图(如 JSP)。
  • 如果忘记写 @RestController,Spring 不会把该类作为请求处理器,自然无法访问。
  • 方法的返回值 "user info" 会原样写回客户端,不需要我们手动操作 HttpServletResponse

组合映射:类级别与方法级别

当接口变多时,将路径拆分成类级别的"命名空间"和方法级别的子路径是最佳实践。

java 复制代码
@RestController
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/m1")
    public String m1() {
        return "m1";
    }

    @RequestMapping("/m2")
    public String m2() {
        return "m2";
    }
}
  • 访问 /user/m1 → 返回 "m1"
  • 访问 /user/m2 → 返回 "m2"

路径规则 :最终路径 = 类上的 @RequestMapping 路径 + 方法上的 @RequestMapping 路径。

如果类或方法上的路径以 / 开头,Spring 会自动规范化,确保拼接结果正确,因此写 @RequestMapping("/m1")@RequestMapping("m1") 效果相同。

限定请求方法

method 属性

java 复制代码
@RestController
@RequestMapping("/user")
public class UserController {

    // 既支持 GET,又支持 POST
    @RequestMapping("/m1")
    public String m1() {
        return "m1";
    }
}
  • 访问 /user/m1,无论使用 GET 还是 POST 方式,都能成功返回 "m1"
  • 原因:@RequestMapping 默认 method = {}(空数组),表示接受所有 HTTP 方法。

通过 method 属性可以指定一个或多个支持的 HTTP 方法。

1. 只支持 GET
java 复制代码
@RestController
@RequestMapping("/user")
public class UserController {

    @RequestMapping(value = "/m2", method = RequestMethod.GET)
    public String m2() {
        return "m2 (GET only)";
    }
}

如果使用 POST 请求 /user/m2,服务器将返回 405 Method Not Allowed
HTTP响应常见状态码

2. 只支持 POST
java 复制代码
@RequestMapping(value = "/m3", method = RequestMethod.POST)
public String m3() {
    return "m3 (POST only)";
}

如果使用GET请求 /user/m3,服务器将返回 405 Method Not Allowed
HTTP响应常见状态码

3. 同时支持 GET 和 POST
java 复制代码
@RequestMapping(value = "/m4", method = {RequestMethod.GET, RequestMethod.POST})
public String m4() {
    return "m4 (GET+POST)";
}

⚠️ 注意:同一个 URL 路径,如果既写了 GET 方法又写了 POST 方法(方法名相同)会冲突。Spring 允许重载,但方法参数必须不同,实际极少这样使用。推荐不同业务逻辑使用不同路径或用派生注解区分。

java 复制代码
// 错误示例:方法签名冲突
@RequestMapping(value = "/same", method = GET)
public String handle() { ... }

@RequestMapping(value = "/same", method = POST)
public String handle() { ... }   // 编译错误:方法名相同且参数相同

解决:使用不同方法名,或使用不同路径,或者利用请求参数进行区分(不推荐)。

派生注解简化方法映射

Spring 4.3 开始提供了专门针对常见 HTTP 方法的注解,它们是 @RequestMapping特化版本

注解 等效写法
@GetMapping @RequestMapping(method = RequestMethod.GET)
@PostMapping @RequestMapping(method = RequestMethod.POST)
@PutMapping @RequestMapping(method = RequestMethod.PUT)
@DeleteMapping @RequestMapping(method = RequestMethod.DELETE)
@PatchMapping @RequestMapping(method = RequestMethod.PATCH)

使用示例

java 复制代码
@RestController
@RequestMapping("/user")
public class UserController {

    // 等价于 @RequestMapping(value = "/get", method = GET)
    @GetMapping("/get")
    public String get() {
        return "GET request";
    }

    // 等价于 @RequestMapping(value = "/post", method = POST)
    @PostMapping("/post")
    public String post() {
        return "POST request";
    }

    @PutMapping("/put")
    public String put() {
        return "PUT request";
    }

    @DeleteMapping("/delete")
    public String delete() {
        return "DELETE request";
    }
}

优点:

  • 代码更短、语义更清晰。
  • 减少拼写错误(不用再写 RequestMethod.GET)。
  • 更容易被 IDE 和静态检查工具识别。

请求处理

在 Spring MVC 中,控制器可以通过多种注解灵活接收客户端传递的参数,包括查询参数、路径变量、请求体、表单字段、请求头等。下面逐一说明,并给出推荐用法。

查询参数 : @RequestParam

适用于 URL 问号后的参数(?key=value),常用于 GET 请求。

java 复制代码
@RestController
@RequestMapping("/user")
public class UserController {

    /**
     * @RequestParam 用于绑定 URL 问号后的查询参数(?key=value),常见于 GET 请求。
     * 
     *   默认必传,可通过 {@code required = false} 设为可选;
     *   使用 {@code defaultValue} 可指定缺省值,避免空值问题,此时 {@code required} 自动视为 false;
     *   若请求参数名与方法形参名一致(且编译时保留了参数名,如使用 -parameters),可省略注解中的名称;
     *       否则建议显式指定 {@code value} 属性,以保证清晰与兼容。
     * 
     */
    @GetMapping("/get")
    public String getUser(@RequestParam("n") String name,           // 请求参数 "n",绑定到形参 name,必传
                          @RequestParam(value = "age", defaultValue = "0") int age, // 可选,默认0
                          @RequestParam(value = "email", required = false) String email) { // 可选,无值时为 null
        return "name: " + name + ", age: " + age + ", email: " + email;
    }
}

测试结果

@RequestParam 对空字符串和默认值的独特处理规则

text 复制代码
http://127.0.0.1:8080/user/get?n=&age=&email=

会发现不报错,明明@RequestParam("n") String name的意思是必须传入一个值

空字符串""对于string也算值
参数 email 的处理
rawValue = ''
不为 null
无 defaultValue
required=false
直接绑定空字符串

email = ''
参数 age 的处理
rawValue = ''
不为 null
值空 且 有 defaultValue='0'
触发默认值回退
使用默认值

age = 0
参数 n 的处理
rawValue = ''
不为 null
无 defaultValue
required=true(默认)
直接绑定空字符串

name = ''
最终输出: name: , age: 0, email:

java 复制代码
 @GetMapping("/getTest")
 public String getUserTest(@RequestParam("n") String name,
                       @RequestParam int age,//遇到空字符串类型转换失败,报 400 错误
                       @RequestParam(value = "email", required = false) String email) {
     return "name: " + name + ", age: " + age + ", email: " + email;
 }


参数 email 的处理

rawValue=''
存在?
required=false, 无 defaultValue
绑定为 ''(但 age 已中断)
参数 age 的处理

rawValue=''
存在?
无 defaultValue
尝试转换为 int
TypeMismatchException

❌ 返回 400 错误
参数 n 的处理

rawValue=''
存在?
无 defaultValue
绑定为 ''
最终响应
400 Bad Request

(因为 age 转换失败)

路径变量:@PathVariable

RESTful 风格将参数直接放在 URL 路径中。

java 复制代码
@RestController
@RequestMapping("/user")
public class UserController {

    @GetMapping("/{id}")
    public String getUserById(@PathVariable int id) {
        return "user id = " + id;
    }

    // 多路径变量
    @GetMapping("/{userId}/orders/{orderId}")
    public String getOrder(@PathVariable int userId,
                           @PathVariable int orderId) {
        return "userId=" + userId + ", orderId=" + orderId;
    }
}
  • 占位符 {变量名} 必须与方法参数名一致,否则需用 @PathVariable("name") 显式指定。
  • 路径变量是 URL 的一部分,通常用于标识资源。

测试结果


请求体(JSON / XML):@RequestBody

适用于 POST、PUT 等请求,将请求体中的 JSON 自动反序列化为 Java 对象。

定义实体类:

java 复制代码
import lombok.Data;
@Data
public class User {
    private String name;
    private int age;
}

@Data说明

控制器方法:

java 复制代码
@RestController
@RequestMapping("/user")
public class UserController {

    // POST /user/create  请求体: {"name":"Alice","age":20}
    @PostMapping("/create")
    public String createUser(@RequestBody User user) {
        return "created: " + user.getName();
    }
}
  • 需引入 Jackson 依赖(Spring Boot 默认自带),JSON 字段名与对象属性名自动映射。
  • 常用 @Valid 配合 @RequestBody 进行参数校验。
  • 可额外接收 BindingResult 参数获取校验结果。

测试结果


为什么agea也可以?而namebagea不行👉【Spring】Jackson 属性映射

表单参数与文件上传

表单提交和文件上传也可用 @RequestParam 接收。

java 复制代码
@RestController
@RequestMapping("/user")
public class UserController {

    // POST 表单:name=zhangsan&age=20  (Content-Type: application/x-www-form-urlencoded)
    @PostMapping("/form")
    public String handleForm(@RequestParam String name,
                             @RequestParam int age) {
        return "name=" + name + ", age=" + age;
    }

    // 文件上传:需设置 enctype="multipart/form-data"
    @PostMapping("/avatar")
    public String uploadAvatar(@RequestParam("file") MultipartFile file) {
        return "上传文件:" + file.getOriginalFilename() + ",大小:" + file.getSize();
    }
}
  • 表单参数和查询参数共用 @RequestParam,Spring 自动识别来源(查询字符串或表单体)。
  • 文件上传必须使用 multipart/form-data,用 MultipartFile 类型接收。

测试结果

数组和集合上传

在实际开发中,经常需要一次性传递多个相同类型的参数,例如批量删除用户 ID、批量查询商品 ID 等。Spring MVC 支持直接通过 @RequestParam 绑定到数组或 List 集合。

传递数组上传

当请求参数名相同时,Spring 会自动将多个同名参数组装成一个数组。

java 复制代码
@RestController
@RequestMapping("/user")
public class UserController {

    // GET /user/batch?ids=1&ids=2&ids=3
    @GetMapping("/batch")
    public String batchQuery(@RequestParam("ids") int[] ids) {
        return "查询的 ID 数组:" + Arrays.toString(ids);
    }
}

测试结果

关键点

  • 多个同名参数 ids=1&ids=2&ids=3 会被自动收集到 int[] ids 中。
  • 也支持 String[]Integer[] 等数组类型。
  • 如果参数不存在且 required = true(默认),会报 400 错误;可设置 required = falsedefaultValue

集合(List)上传

除了数组,也可以直接绑定到 List 集合,但需要显式指定参数名。

java 复制代码
@RestController
@RequestMapping("/user")
public class UserController {

    // GET /user/list?ids=1&ids=2&ids=3
    @GetMapping("/list")
    public String batchList(@RequestParam("ids") List<Integer> ids) {
        return "查询的 ID 列表:" + ids;
    }
}

测试结果

关键点

  • 使用 List<Integer> 时,Spring 会自动将字符串参数转换为对应的泛型类型。
  • 如果泛型是自定义对象(如 List<User>),则无法通过 @RequestParam 直接绑定,需要使用 @RequestBody 传递 JSON 数组。

JSON 数组(通过 @RequestBody)上传

如果前端传递的是 JSON 数组(如 [1, 2, 3] 或对象数组),则需要使用 @RequestBody

java 复制代码
@RestController
@RequestMapping("/user")
public class UserController {

    // POST /user/batch  请求体: [1, 2, 3]
    @PostMapping("/batch")
    public String batchCreate(@RequestBody List<Integer> ids) {
        return "批量创建,ID 列表:" + ids;
    }

    // POST /user/batch-objects  
    @PostMapping("/batch-objects")
    public String batchCreateUsers(@RequestBody List<User> users) {
        return "批量创建用户:" + users.size() + " 个";
    }
}

测试结果

关键点

  • @RequestBody 可以直接接收 JSON 数组,并自动反序列化为 List<Integer>List<User>
  • 需要确保请求头 Content-Type: application/json
  • 适用于 POST、PUT 等需要传递复杂数据结构的场景。

请求头与 Cookie:@RequestHeader、@CookieValue

有时需要读取特定的请求头或 Cookie 值。

java 复制代码
@RestController
@RequestMapping("/user")
public class UserController {

    // 读取单个请求头
    @GetMapping("/header")
    public String getHeader(@RequestHeader("User-Agent") String userAgent) {
        return "User-Agent: " + userAgent;
    }

    // 读取 Cookie
    @GetMapping("/cookie")
    public String getCookie(@CookieValue(value = "JSESSIONID", defaultValue = "") String sessionId) {
        return "JSESSIONID: " + sessionId;
    }
}
  • 同样支持 requireddefaultValue 属性。

参数解析流程(Mermaid 图)

Controller 参数解析器(ArgumentResolvers) HandlerAdapter HandlerMapping DispatcherServlet 客户端 Controller 参数解析器(ArgumentResolvers) HandlerAdapter HandlerMapping DispatcherServlet 客户端 @PathVariable 从 URL 模板提取 @RequestParam 从 QueryString/表单提取 @RequestBody 读取请求体并反序列化 @RequestHeader/@CookieValue 从请求头/Cookie提取 其他类型按需解析 HTTP 请求 查找处理器 返回 HandlerMethod 调用处理器适配器 遍历方法参数进行解析 解析完成的参数数组 反射调用 Controller 方法(传入参数) 返回结果 ModelAndView 或响应体 HTTP 响应

  • 框架通过 HandlerMethodArgumentResolver 接口为每种注解提供专用解析器,开发者也可自定义解析逻辑。
  • 常用参数类型(如 HttpServletRequestHttpServletResponsePrincipal 等)同样由此机制注入。

响应返回

返回普通字符串

java 复制代码
@RequestMapping("/hello")
public String sayHello() {
    return "Hello Spring Web";
}

返回 JSON 对象

java 复制代码
@RestController
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/info")
    public User getUserInfo() {
        User user = new User();
        user.setName("zhangsan");
        user.setAge(25);
        return user;   // Spring 自动转为 JSON
    }
}
  • @RestController 另一个强大之处在于 自动序列化 。当方法返回的是一个对象(例如 User 实体),Spring 会调用配置好的 HttpMessageConverter(默认使用 Jackson 库)将其转换为 JSON 字符串,并设置 Content-Type: application/json

测试结果

设置响应状态码

在 Spring MVC 中,设置响应状态码主要有两种方式:使用 @ResponseStatus 注解,或通过 HttpServletResponse 对象手动设置。下面通过对比来理解它们的区别。

方式一:使用 @ResponseStatus 注解

直接在方法上声明,Spring 会在方法执行完成后自动设置状态码。

java 复制代码
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@RestController
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/delete")
    @ResponseStatus(HttpStatus.NO_CONTENT)  // 204
    public void deleteUser() {
        // 删除逻辑,不返回内容
    }
}

特点

  • 声明式,代码简洁,一目了然。
  • 状态码在编译期就确定,适合固定状态码的场景(如 204、201)。

测试结果

方式二:通过 HttpServletResponse 手动设置

在方法参数中注入 HttpServletResponse,调用 setStatus() 动态设置。

java 复制代码
@RestController
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/setstatus")
    public void setStatus(HttpServletResponse response) {
        response.setStatus(404);
    }
}

特点

  • 编程式,可在方法内部根据业务逻辑动态决定状态码。
  • 适合需要根据条件返回不同状态码的场景(如 200 成功、404 未找到、500 服务器错误)。

测试结果

两种方式对比

对比维度 @ResponseStatus 注解 HttpServletResponse.setStatus()
设置方式 声明式(注解) 编程式(代码)
状态码确定性 编译期固定 运行时动态决定
代码简洁性 更简洁 稍复杂
典型场景 固定状态码(204、201、404) 条件分支状态码(根据业务逻辑)
异常处理配合 可配合自定义异常使用 通常用于正常流程
  • 两种方式可以混合使用,但注意 @ResponseStatus 的优先级低于 HttpServletResponse.setStatus(),如果两者同时存在,以 setStatus() 为准。

返回HTML页面:@Controller

在 Spring MVC 中,返回 HTML 页面与返回 JSON/字符串的关键区别在于控制器类使用的注解。@Controller 用于返回视图(如 JSP、HTML 页面),而 @RestController 则直接返回数据。下面通过一个完整的示例来演示。

准备工作

首先在 src/main/resources/static 目录下创建一个简单的 HTML 页面 index.html

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
    <h1>欢迎来到 Spring MVC</h1>
    <p>这是一个返回的 HTML 页面</p>
</body>
</html>

使用 @Controller 返回页面

java 复制代码
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/resp")
public class RespController {

    /**
     * 返回 HTML 页面
     * 方法返回字符串,Spring 会将其解析为视图路径
     */
    @RequestMapping("/r1")
    public String returnPage() {
        return "/aa/index.html";
    }
}

关键点

  • 类上使用 @Controller,表示该类中的方法默认返回视图。
  • 方法返回的字符串 /index.html 会被 Spring MVC 的视图解析器解析为静态资源路径。
  • 访问 http://localhost:8080/resp/r1 会渲染 index.html 页面。

测试结果

@Controller + @ResponseBody 返回数据

如果希望在 @Controller 类中的某个方法直接返回数据(而非视图),可以在该方法上额外添加 @ResponseBody 注解。

java 复制代码
@Controller
@RequestMapping("/resp")
public class RespController {

    /**
     * 返回纯文本数据
     */
    @ResponseBody
    @RequestMapping("/r2")
    public String returnData() {
        return "我是前端需要的数据";
    }

    /**
     * 返回 HTML 片段(作为字符串数据返回)
     */
    @ResponseBody
    @RequestMapping("/r3")
    public String returnHTML() {
        return "<h1>我是一级标题</h1>";
    }

    /**
     * 返回纯文本(设置 Content-Type 为 text/plain)
     */
    @ResponseBody
    @RequestMapping(value = "/r4", produces = "text/plain")
    public String returnText() {
        return "<h1>我是一级标题</h1>";
    }

}

测试结果

/r2 返回纯文本:

/r3 返回 HTML 片段(默认 Content-Type 为 text/html):

/r4 返回纯文本(Content-Type 为 text/plain,浏览器不会渲染 HTML 标签):

@Controller 与 @RestController 的区别

对比维度 @Controller @RestController
默认行为 返回视图(JSP/HTML) 返回数据(JSON/字符串)
是否需要 @ResponseBody 需要手动添加 不需要,默认所有方法都带 @ResponseBody
典型场景 返回 HTML 页面 RESTful API、前后端分离
组合关系 - @RestController = @Controller + @ResponseBody
  • 返回页面 :使用 @Controller,方法返回视图路径字符串。
  • 返回数据 :使用 @RestController,或在 @Controller 方法上添加 @ResponseBody
  • 返回 HTML 片段 :使用 @ResponseBody 返回包含 HTML 标签的字符串,默认 Content-Type 为 text/html
  • 返回纯文本 :通过 produces = "text/plain" 指定 Content-Type,浏览器不会渲染 HTML 标签。

设置Content-Type

在 Spring MVC 中,Content-Type 响应头决定了浏览器如何解析返回的数据。默认情况下,@ResponseBody 会根据返回值类型自动设置 Content-Type,但有时我们需要手动指定,例如返回纯文本、XML 或自定义格式。

通过 produces 属性设置

@RequestMappingproduces 属性可以指定方法返回数据的 Content-Type,Spring 会据此设置响应头。

java 复制代码
@Controller
@RequestMapping("/resp")
public class RespController {


    /**
     * 返回 HTML 片段(作为字符串数据返回)
     */
    @ResponseBody
    @RequestMapping("/r3")
    public String returnHTML() {
        return "<h1>我是一级标题</h1>";
    }
    /**
     * 返回纯文本(设置 Content-Type 为 text/plain)
     */
    @ResponseBody
    @RequestMapping(value = "/r4", produces = "text/plain")
    public String returnText() {
        return "<h1>我是一级标题</h1>";
    }

    /**
     * 指定 Content-Type 为 application/json
     * 返回 JSON 格式数据
     */
    @ResponseBody
    @RequestMapping(value = "/r5", produces = "application/json")
    public User returnJson() {
        User userInfo = new User("zhangsan", 20);
        return userInfo;
    }

    /**
     * 指定 Content-Type 为 application/xml
     * 返回 XML 格式数据(需引入 Jackson XML 扩展)
     */
    @ResponseBody
    @RequestMapping(value = "/r6", produces = "application/xml")
    public User returnXml() {
        User userInfo = new User("lisa", 21);
        return userInfo;
    }
}

测试结果

/r3 默认 text/html,浏览器渲染 HTML 标签:

/r4 指定 text/plain,浏览器直接显示源码:

/r5 指定 application/json,浏览器显示json:

通过 HttpServletResponse 手动设置

也可以在方法内部通过 HttpServletResponse.setContentType() 动态设置。

java 复制代码
@Controller
@RequestMapping("/resp")
public class RespController {

    @ResponseBody
    @RequestMapping("/r7")
    public String setContentType(HttpServletResponse response) {
        // 手动设置 Content-Type
        response.setContentType("text/plain;charset=UTF-8");
        return "<h1>我是一级标题</h1>";
    }
}

测试结果

常见 Content-Type 取值

【Java EE】 HTTP协议

两种方式对比

对比维度 produces 属性 HttpServletResponse.setContentType()
设置方式 声明式(注解) 编程式(代码)
灵活性 编译期固定 运行时动态决定
代码简洁性 更简洁 稍复杂
典型场景 固定 Content-Type 根据业务逻辑动态设置
  • 两种方式可以混合使用,但 HttpServletResponse.setContentType() 的优先级更高。
  • 设置 Content-Type 时建议同时指定字符编码(如 text/plain;charset=UTF-8),避免中文乱码。

设置Header

在实际开发中,有时需要在响应中添加自定义 Header,例如设置分页信息、认证令牌、缓存控制等。Spring MVC 提供了多种方式来设置响应 Header。

方式一:通过 HttpServletResponse 手动设置

在方法参数中注入 HttpServletResponse,调用 setHeader()addHeader() 方法。

java 复制代码
@RestController
@RequestMapping("/resp")
public class RespController {

    @ResponseBody
    @RequestMapping("/r8")
    public String setHeader(HttpServletResponse response) {
        // 设置单个 Header
        response.setHeader("myHeader", "myHeaderValue");
        // 设置多个 Header
        response.setHeader("X-Custom-Header", "custom-value");
        // addHeader 与 setHeader 的区别:
        // setHeader:如果同名 Header 已存在,会覆盖
        // addHeader:如果同名 Header 已存在,会追加(允许重复)
        response.addHeader("X-Trace-Id", "trace-123456");
        return "设置Header成功";
    }
}

测试结果

关键点

方法 说明 示例
setHeader(String name, String value) 设置单个 Header,同名时覆盖 response.setHeader("myHeader", "myValue")
addHeader(String name, String value) 追加 Header,同名时不会覆盖,响应中会出现多个同名 Header response.addHeader("X-Trace-Id", "trace-123")
setIntHeader(String name, int value) 设置整数值的 Header(如 Content-Length response.setIntHeader("Content-Length", 1024)
setDateHeader(String name, long date) 设置日期类型的 Header(如 Expires response.setDateHeader("Expires", System.currentTimeMillis() + 86400000)

方式二:通过 @ResponseHeader 注解(Spring 6.0+)

从 Spring 6.0 开始,可以使用 @ResponseHeader 注解在方法上声明式地设置响应 Header。

java 复制代码
import org.springframework.web.bind.annotation.ResponseHeader;

@RestController
@RequestMapping("/resp")
public class RespController {

    @RequestMapping("/r9")
    @ResponseHeader(name = "X-Custom-Header", value = "annotation-value")
    public String setHeaderByAnnotation() {
        return "通过注解设置Header成功";
    }
}

⚠️ 注意:@ResponseHeader 是 Spring 6.0 / Spring Boot 3.0 新增的注解,如果使用旧版本,请使用 HttpServletResponse 方式。

方式三:通过 ResponseEntity 设置

ResponseEntity 允许同时设置状态码、Header 和响应体,是最灵活的方式。

java 复制代码
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

@RestController
@RequestMapping("/resp")
public class RespController {

    @RequestMapping("/r10")
    public ResponseEntity<String> setHeaderByResponseEntity() {
        HttpHeaders headers = new HttpHeaders();
        headers.add("X-Pagination-Page", "1");
        headers.add("X-Pagination-Total", "100");
        headers.add("X-Custom-Header", "response-entity-value");

        return new ResponseEntity<>("设置Header成功(ResponseEntity)", headers, HttpStatus.OK);
    }
}

测试结果

关键点

  • ResponseEntity 可以同时控制状态码、Header 和响应体。
  • HttpHeaders 支持链式调用:headers.add("key", "value")
  • 适合需要动态设置多个 Header 的场景。

常见 Header 设置场景

Header 名称 说明 示例值
X-Trace-Id 请求追踪 ID,用于日志链路 trace-123456
X-Pagination-Page 分页页码 1
X-Pagination-Total 分页总数 100
X-RateLimit-Remaining 接口限流剩余次数 50
Cache-Control 缓存控制 no-cache, no-store, must-revalidate
Expires 过期时间 0
Content-Disposition 文件下载时的文件名 attachment; filename="report.pdf"

三种方式对比

对比维度 HttpServletResponse @ResponseHeader 注解 ResponseEntity
设置方式 编程式(代码) 声明式(注解) 编程式(对象)
灵活性 高,可动态设置 低,编译期固定 高,可动态设置
代码简洁性 中等 最简洁 稍复杂
批量设置 需多次调用 需多个注解 支持链式添加
同时设置状态码 需额外调用 setStatus 需配合 @ResponseStatus 直接支持
典型场景 动态 Header、条件分支 固定 Header 需要同时控制状态码和 Header
  • 三种方式可以混合使用,但 HttpServletResponse 的优先级最高。
  • 推荐:简单固定 Header 用 @ResponseHeader(Spring 6.0+),复杂场景用 ResponseEntity,需要极致灵活性用 HttpServletResponse
相关推荐
想学习java初学者7 小时前
SpringBoot整合GS1编码解码
java·spring boot·后端
日月云棠7 小时前
2 快速入门实战指南
java·后端
日月云棠7 小时前
3 Dubbo 2.7 高级配置:检查控制、版本策略与协议选择
java·后端
砍材农夫8 小时前
物联网 基于netty构建mqtt协议规范(主题通配符订阅)
java·前端·javascript·物联网·netty
掉鱼的猫8 小时前
用 Solon AI 从零构建 MCP 工具服务:让 AI Agent 拥有真实世界的能力
java·llm·mcp
日月云棠8 小时前
1 分布式架构演进与Dubbo框架入门
java·后端
彩票管理中心秘书长8 小时前
智能体状态指示:何时思考、何时调用工具、何时出错
前端·后端·程序员
彩票管理中心秘书长8 小时前
React + TypeScript拆解一整套“AI 变现代码流程”
前端·后端·程序员
ohh68 小时前
从零打通 super-xiaoe:工单自动排查与评论闭环
后端