Spring MVC 中那些“看不见的转换”:HttpMessageConverter 与文件上传下载实战
一、HttpMessageConverter
HttpMessageConverter是Spring MVC中非常重要的一个接口。翻译为:HTTP消息转换器,转换的是HTTP协议与Java程序中的对象之间的互相转换。
1.1 HttpMessageConverter
该接口下提供了很多实现类,不同的实现类有不同的转换方式。
| 分类 | 常见实现类 | 核心功能与特点 |
|---|---|---|
| 处理JSON | MappingJackson2HttpMessageConverter | “翻译官中的主力”——它专门负责把后端的 Java 对象“翻译”成前端能看懂的 JSON 格式,或者把前端发来的 JSON 请求“翻译”回 Java 对象。就像你和一个说不同语言的人交流,它充当了同声传译的角色。 |
| 处理字符串 | StringHttpMessageConverter | “快递小哥”——它只送一种东西:纯文本。不管是 text/html、text/plain 还是其他文本格式,它只管把内容原封不动地递过去,不拆箱、不加工,简单直接。 |
| 处理表单 | FormHttpMessageConverter | “分拣员”——它专门处理 HTML 表单提交的数据(比如 username=张三&age=20 这种格式)。它会把这一长串“乱糟糟”的键值对拆开,分门别类地放进一个叫 MultiValueMap 的“收纳盒”里,方便后端取用。 |

如上图所示:HttpMessageConverter接口的可以将请求协议转换成Java对象,也可以把Java对象转换为响应协议。
HttpMessageConverter是接口,SpringMVC帮我们提供了非常多而丰富的实现类。每个实现类都有自己不同的转换风格。对于我们程序员来说,Spring MVC已经帮助我们写好了,我们只需要在不同的业务场景下,选择合适的HTTP消息转换器即可。
怎么选择呢?当然是通过SpringMVC为我们提供的注解,我们通过使用不同的注解来启用不同的消息转换器。
1.2 @ResponseBody
@ResponseBody 是 Spring MVC 中的一个注解,它的作用是告诉 Spring:“方法的返回值不要进行视图解析,直接作为 HTTP 响应的内容体(response body)返回给客户端。”
@Controller
public class HelloController {
@RequestMapping(value = "/hello")
@ResponseBody
public String hello(){
return "{\"username\":\"zhangsan\",\"password\":\"1234\"}";
}
}
在使用@ResponseBody注解之后,这里的"{“username”:“zhangsan”,“password”:“1234”}"不是逻辑视图名称了,而是作为响应体的内容进行响应。直接输出到浏览器客户端。以上程序中使用的消息转换器是:StringHttpMessageConverter。
同样我们可以将POJO对象以JSON格式的字符串响应给浏览器
第一步:引入jackson依赖,可以将java对象转换为json格式字符串
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.0</version>
</dependency>
第二步:开启注解驱动<mvc:annotation-driven/>
第三步:控制器方法使用 @ResponseBody 注解标注(非常重要),控制器方法返回这个POJO对象
@Controller
public class HelloController {
@RequestMapping(value = "/hello")
@ResponseBody
public User hello(){
User user = new User("zhangsan", "22222");
return user;
}
}
1.3 @RestController
@RestController。这一个注解代表了:@Controller + @ResponseBody。
@RestController
public class HelloController {
@RequestMapping(value = "/hello")
public User hello(){
User user = new User("zhangsan", "22222");
return user;
}
}
1.4 @RequestBody
@RequestBody作用请把 HTTP 请求体(request body)中的内容,完整地交给spring,并帮我转换成spring想要的 Java 类型。
在没有使用这个注解的时候:
@RequestMapping("/save")
public String save(User user){
// 执行保存的业务逻辑
userDao.save(user);
// 保存成功跳转到成功页面
return "success";
}
处理流程:
Spring MVC 收到请求:username=zhangsan&password=1234
不经过 HttpMessageConverter!
Spring 创建空的 User 对象
通过 WebDataBinder 遍历所有请求参数
找到 User 类中有 setUsername()、setPassword() 方法
调用这些 setter 方法,把参数值赋进去
给你一个组装好的 User 对象
不加 @RequestBody 时,Spring MVC 也能创建 Java 对象,但那是另一套完全不同的机制——数据绑定,并不是 @RequestBody 的作用。
使用这个注解的时候:
@RequestMapping("/save")
public String save(@RequestBody String requestBodyStr){
System.out.println("请求体:" + requestBodyStr);
return "success";
}
处理流程:
Spring MVC 看到 @RequestBody 注解
启动消息转换器机制,寻找能处理请求体的转换器
根据请求头 Content-Type: application/x-www-form-urlencoded
找到 FormHttpMessageConverterFormHttpMessageConverter 读取整个请求体,作为原始字符串返回
把这个字符串传给你的 requestBodyStr 参数
当然他也可以将JSON字符串转换成POJO对象:
第一步:引入jackson依赖
第二步:开启注解驱动
第三步:创建POJO类,将POJO类作为控制器方法的参数,并使用 @RequestBody 注解标注该参数
@RequestMapping("/send")
@ResponseBody
public String send(@RequestBody User user){
System.out.println(user);
System.out.println(user.getUsername());
System.out.println(user.getPassword());
return "success";
}
1.5 RequestEntity
RequestEntity不是一个注解,是一个普通的类。这个类的实例封装了整个请求协议:包括请求行、请求头、请求体所有信息。RequestEntity 的核心组成:
public class RequestEntity<T> {
private HttpMethod method; // 请求方法(GET/POST等)
private URI url; // 请求地址
private HttpHeaders headers; // 请求头
private T body; // 请求体(泛型,可以是任何类型)
// ...
}
出现在控制器方法的参数上:
@RequestMapping("/send")
@ResponseBody
public String send(RequestEntity<User> requestEntity){
System.out.println("请求方式:" + requestEntity.getMethod());
System.out.println("请求URL:" + requestEntity.getUrl());
HttpHeaders headers = requestEntity.getHeaders();
System.out.println("请求的内容类型:" + headers.getContentType());
System.out.println("请求头:" + headers);
User user = requestEntity.getBody();
System.out.println(user);
System.out.println(user.getUsername());
System.out.println(user.getPassword());
return "success";
}
1.6 ResponseEntity
ResponseEntity不是注解,是一个类。用该类的实例可以封装响应协议,包括:状态行、响应头、响应体。也就是说:如果你想定制属于自己的响应协议,可以使用该类。ResponseEntity 的核心组成:
public class ResponseEntity<T> {
private final HttpStatus statusCode; // 响应状态码(如 200 OK)
private final HttpHeaders headers; // 响应头
private final T body; // 响应体(泛型,可以是任何类型)
// ...
}
使用 ResponseEntity:完全控制响应:
@GetMapping("/user/{id}")
public ResponseEntity<User> getUser(@PathVariable Integer id) {
User user = userService.findById(id);
if (user == null) {
// 可以返回404状态码
return ResponseEntity.notFound().build();
}
// 可以自定义响应头和状态码
return ResponseEntity.ok()
.header("Custom-Header", "custom-value")
.body(user);
}
二、文件上传与下载
1.1 文件上传
前端页面:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>文件上传</title>
</head>
<body>
<!--文件上传表单-->
<form th:action="@{/file/up}" method="post" enctype="multipart/form-data">
文件:<input type="file" name="fileName"/><br>
<input type="submit" value="上传">
</form>
</body>
</html>
web.xml文件:
<!--前端控制器-->
<servlet>
<multipart-config>
<!--设置单个支持最大文件的大小-->
<max-file-size>102400</max-file-size>
<!--设置整个表单所有文件上传的最大值-->
<max-request-size>102400</max-request-size>
<!--设置最小上传文件大小-->
<file-size-threshold>0</file-size-threshold>
</multipart-config>
</servlet>
Controller中的代码:
@Controller
public class FileController {
@RequestMapping(value = "/file/up", method = RequestMethod.POST)
public String fileUp(@RequestParam("fileName") MultipartFile multipartFile, HttpServletRequest request) throws IOException {
String name = multipartFile.getName();
System.out.println(name);
// 获取文件名
String originalFilename = multipartFile.getOriginalFilename();
System.out.println(originalFilename);
// 将文件存储到服务器中
// 获取输入流
InputStream in = multipartFile.getInputStream();
// 获取上传之后的存放目录
File file = new File(request.getServletContext().getRealPath("/upload"));
// 如果服务器目录不存在则新建
if(!file.exists()){
file.mkdirs();
}
// 开始写
//BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file.getAbsolutePath() + "/" + originalFilename));
// 可以采用UUID来生成文件名,防止服务器上传文件时产生覆盖
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file.getAbsolutePath() + "/" + UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf("."))));
byte[] bytes = new byte[1024 * 100];
int readCount = 0;
while((readCount = in.read(bytes)) != -1){
out.write(bytes,0,readCount);
}
// 刷新缓冲流
out.flush();
// 关闭流
in.close();
out.close();
return "ok";
}
}
1.2 文件下载
前端页面:
<!--文件下载-->
<a th:href="@{/download}">文件下载</a>
文件下载核心程序,使用ResponseEntity:
@GetMapping("/download")
public ResponseEntity<byte[]> downloadFile(HttpServletResponse response, HttpServletRequest request) throws IOException {
File file = new File(request.getServletContext().getRealPath("/upload") + "/1.jpeg");
// 创建响应头对象
HttpHeaders headers = new HttpHeaders();
// 设置响应内容类型
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
// 设置下载文件的名称
headers.setContentDispositionFormData("attachment", file.getName());
// 下载文件
ResponseEntity<byte[]> entity = new ResponseEntity<byte[]>(Files.readAllBytes(file.toPath()), headers, HttpStatus.OK);
return entity;
}
最后感谢动力节点提供的优质学习资源,让我们在技术道路上能够站在巨人的肩膀上继续前行。本资料仅为学习过程的副产品,希望能帮助到更多同样在努力学习的开发者。
更多推荐




所有评论(0)