W
e
l
c
o
m
e
: )

【SpringMVC从入门到精通】06-HttpMessageConverter

笔记来源:【尚硅谷】SpringMVC教程丨一套快速上手spring mvc

HttpMessageConverter

  • HttpMessageConverter:报文信息转换器,将请求报文转换为 Java 对象,或将 Java 对象转换为响应报文
  • HttpMessageConverter:提供了两个注解和两个类型:@RequestBody@ResponseBodyRequestEntityResponseEntity

1、@RequestBody

@RequestBody可以获取请求体,需要在控制器方法设置一个形参,使用@RequestBody进行标识,当前请求的请求体就会为当前注解所标识的形参赋值

后台测试代码

@Controller
@RequestMapping("/httpController")
public class HttpController {
    @PostMapping("/testRequestBody")
    public String testRequestBody(@RequestBody String requestBody) {
        System.out.println("requestBody: " + requestBody);
        return "success";
    }
}

前台测试代码

<form th:action="@{httpController/testRequestBody}" method="post">
    用户名:<input type="text" name="username"><br/>
    密码:<input type="password" name="password"><br/>
    <input type="submit" value="测试@RequestBody">
</form>

测试结果

动画  (1)

后台日志信息

requestBody: username=admin&password=123456

2、RequestEntity

RequestEntity:封装请求报文的一种类型,需要在控制器方法的形参中设置该类型的形参,当前请求的请求报文就会赋值给该形参

可以通过getHeaders()获取请求头信息,通过getBody()获取请求体信息

后台测试代码

@PostMapping("/testRequestEntity")
public String testRequestEntity(RequestEntity<String> requestEntity) {
    System.out.println("requestHeader: " + requestEntity.getHeaders());
    System.out.println("requestBody: " + requestEntity.getBody());
    return "success";
}

前台测试代码

<form th:action="@{httpController/testRequestEntity}" method="post">
    用户名:<input type="text" name="username"><br/>
    密码:<input type="password" name="password"><br/>
    <input type="submit" value="测试RequestEntity">
</form>

测试结果

动画  (2)

后台日志信息

requestHeader: [host:"localhost:8080", connection:"keep-alive", content-length:"30", cache-control:"max-age=0", sec-ch-ua:"" Not A;Brand";v="99", "Chromium";v="99", "Google Chrome";v="99"", sec-ch-ua-mobile:"?0", sec-ch-ua-platform:""Windows"", upgrade-insecure-requests:"1", origin:"http://localhost:8080", user-agent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36", accept:"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", sec-fetch-site:"same-origin", sec-fetch-mode:"navigate", sec-fetch-user:"?1", sec-fetch-dest:"document", referer:"http://localhost:8080/SpringMVC/", accept-encoding:"gzip, deflate, br", accept-language:"zh-CN,zh;q=0.9", Content-Type:"application/x-www-form-urlencoded;charset=UTF-8"]
requestBody: username=admin&password=123456

3、@ResponseBody

@ResponseBody用于标识一个控制器方法,可以将该方法的返回值直接作为响应报文的响应体响应到浏览器

3.1、通过 HttpServletResponse 响应浏览器数据

后台测试代码

@GetMapping("/testResponseByServletAPI")
public void testResponseByServletAPI(HttpServletResponse response) throws IOException {
    response.getWriter().print("hello, response");
}

前台测试代码

<a th:href="@{/httpController/testResponseByServletAPI}">通过 HttpServletResponse 响应浏览器数据</a>

测试结果

动画  (3)

3.2、通过 @ResponseBody 响应浏览器数据

后台测试代码

@GetMapping("/testResponseBody")
@ResponseBody
public String testResponseBody() throws IOException {
    return "success";
}

前台测试代码

<a th:href="@{/httpController/testResponseBody}">通过 @ResponseBody 响应浏览器数据</a>

测试结果

动画  (4)

3.3、通过 @ResponseBody 响应 User

后台测试代码

@GetMapping("/testResponseUser")
@ResponseBody
public User testResponseUser() throws IOException {
    return new User("admin", "123456");
}

前台测试代码

<a th:href="@{/httpController/testResponseUser}">通过 @ResponseBody 响应 User</a>

测试结果

动画  (5)

我这里报了406 - 不可接收的错误

存疑点:课程中报的是500 - No converter found for return value of type: class xxx,不知道是什么原因,此处存疑

image-20220328213941797

Q:那么如何解决这个问题?

A:不要响应 Java 对象,而是转换成 Json 对象

3.4、SpringMVC 处理 JSON

处理方式:引入jackson依赖

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.13.2</version>
</dependency>

这里不修改任何代码,直接重新部署项目,再来测试下

动画  (6)

这里需要强调的是,并不是只引入jackson依赖就够了。实际上,@ResponseBody处理json的步骤如下

  • 1)引入jackson依赖
  • 2)开启 MVC 注解驱动
    • 此时在HandlerAdaptor中会自动装配一个消息转换器MappingJackson2HttpMessageConverter,可以将响应到浏览器的 Java 对象转换json格式的字符串
  • 3)使用@ResponseBody注解标识控制器方法
  • 4)将 Java 对象作为控制器方法的返回值返回
    • 这里会自动转换为json类型的字符串

上述步骤缺一不可,少一步都实现不了效果

回顾:我们之前章节中就说过 MVC 注解驱动的功能很多,在 04-SpringMVC 视图 中处理了视图控制器问题,在 05-RESTful 案例 中处理静态资源问题,而在本章节中处理了 Java 对象转换为 JSON 对象的问题。这里再总结一下 MVC 注解驱动的作用:

1、解决视图控制器view-controller造成其他请求失效的问题

2、解决默认 Servlet 处理器default-servlet-handler造成DispatcherServlet失效的问题

3、解决 Java 对象转换为 Json 对象的问题

扩展:Json 对象和 Json 数组

1、Json 对象用{}包裹,如{"username":"admin","password":"123456"}

2、Json 数组用[]包裹,如["username":"admin","password":"123456"]

3、Json 对象和 Json 数组可以相互嵌套,即

  • Json 对象中可以包含 Json 对象和 Json 数组,如["username":"admin","password":"123456", "ext" : {"age": 18, "gender": "1"}]["username":"admin","password":"123456", "ext" : ["age": 18, "gender": "1"]]
  • Json 数组中也可以包含 Json 对象和 Json 数组,如{"username":"admin","password":"123456", "ext" : {"age": 18, "gender": "1"}}{"username":"admin","password":"123456", "ext" : ["age": 18, "gender": "1"]}

3.5、SpringMVC 处理 AJAX

后台测试代码

@PostMapping("/testAxios")
@ResponseBody
public String testAxios(User user) {
    return user.getUsername() + "," + user.getPassword();
}

前台测试代码

httpmessageconverter.html

<div id="app">
    <a @click="testAxios" th:href="@{/httpController/testAxios}">SpringMVC 操作 AJAX</a>
</div>
<script type="text/javascript" th:src="@{/static/js/vue.js}"></script>
<script type="text/javascript" th:src="@{/static/js/axios.min.js}"></script>
<script type="text/javascript" th:src="@{/static/js/httpmessageconverter.js}"></script>

httpmessageconverter.js

var vue = new Vue({
    el: "#app",
    methods: {
        testAxios: function (event) {
            testAxios(event.target.href);
        }
    }
});

function testAxios(url) {
    axios({
        method: "post",
        url: url,
        params: {
            username: "admin",
            password: "123456"
        }
    }).then(function (response) {
        alert(response.data);
    });
    event.preventDefault();
}

测试效果

动画  (7)

3.6、@RestController 注解

@RestController注解是 SpringMVC 提供的一个复合注解,标识在控制器的类上,就相当于为类添加了@Controller注解,并且为其中的每个方法添加了@ResponseBody注解

这里简单修改下后台代码,将@Controller注解替换为@RestController注解,并去除控制器方法上的@ResponseBody注解

@RestController
@RequestMapping("/httpController")
public class HttpController {
    @PostMapping("/testAxios")
    public String testAxios(User user) {
        return user.getUsername() + "," + user.getPassword();
    }
}

测试效果

动画  (8)

可以发现,虽然控制器方法上没有加@ResponseBody注解,但是效果是一样的,依然可以将控制器方法的返回值作为响应报文的响应体返回给浏览器

4、ResponseEntity

ResponseEntity用于控制器方法的返回值类型,该控制器方法的返回值就是响应到浏览器的响应报文

4.1、文件下载

后台测试代码

@Controller
@RequestMapping("/fileUploadDownloadController")
public class FileUploadDownloadController {
    @GetMapping("/testDownload")
    public ResponseEntity<byte[]> testDownload(HttpSession session) {
        ServletContext context = session.getServletContext();
        // 文件位置和名称
        final String path = "/static/img/";
        String fileName = "1.png";
        // 响应体
        String realPath = context.getRealPath(path + fileName);
        byte[] bytes = readFile(realPath);
        // 响应头
        MultiValueMap<String, String> headers = new HttpHeaders();
        headers.set("Content-Disposition", "attachment;filename=" + fileName);
        // 响应状态码
        HttpStatus status = HttpStatus.OK;
        // 响应实体
        return new ResponseEntity<>(bytes, headers, status);
    }

    /**
     * 读取文件流
     *
     * @param realPath
     * @return
     */
    private byte[] readFile(String realPath) {
        System.out.println(realPath);
        final int initSize = 0;
        byte[] bytes = new byte[initSize];
        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(realPath));) {
            bytes = new byte[bis.available()];
            bis.read(bytes);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bytes;
    }
}

前台测试代码

<a th:href="@{/fileUploadDownloadController/testDownload}">测试下载文件</a>

测试效果

动画  (8)

记低级失误

1、将

@Controller
@RequestMapping("/fileUploadDownloadController")

写成了

@Controller("/fileUploadDownloadController")

导致了请求直接报404找不到对应资源,需要格外注意!!!

2、读取文件代码缺少 bis.read(bytes);导致字节数组只做了初始化而没有赋值,导致下载文件出现“损坏”的问题,需要格外注意!!!

4.2、文件上传

文件上传要求form表单的请求方式必须为post,并且添加属性enctype="multipart/form-data"

SpringMVC 将上传的文件封装到MultipartFile对象中,通过此对象可以获取文件相关信息

1)添加依赖

commons-fileupload的 jar 包是上传功能必不可少的

<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.4</version>
</dependency>

2)配置文件上传解析器

SpringMVC 配置文件中添加CommonsMultipartResolver的依赖注入

<!--配置文件上传解析器,将上传文件自动封装为MutilpartFile对象-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>

3)后台代码

使用MultipartFile对象接收上传文件

@PostMapping("/testUpload")
public String testUpload(MultipartFile photo, HttpSession session) throws IOException {
    // 目标目录
    String photoPath = session.getServletContext().getRealPath("photo");
    File file = new File(photoPath);
    if (!file.exists()) {
        file.mkdir();
    }
    // 目标文件名
    String fileName = photo.getOriginalFilename();
    // 上传文件到服务器
    photo.transferTo(new File(photoPath + File.separator + fileName));
    return "success";
}

4)前台代码

要求请求方式必须为post,并且enctype属性值必须为multipart/form-data

Q:enctype 是什么?

A:enctype 即encode type,表示编码类型,它规定了在发送到服务器之前应该如何对表单数据进行编码

默认地,form 表单数据编码默认值为application/x-www-form-urlencoded。除此之外,enctype还可以设置为text/plain

这三种类型总结一下就是:

  • application/x-www-form-urlencoded:默认值,URL 编码
  • multipart/form-data:文件类型
  • text/plain:纯文本格式类型
<form th:action="@{/fileUploadDownloadController/testUpload}" method="post" enctype="multipart/form-data">
    头像:<input type="file" name="photo"/><br/>
    <input type="submit" value="上传"/>
</form>

测试结果

动画  (9)

查看文件是否上传成功

image-20220330224959389

5)处理同名问题

如果多次上传同名文件,会发现原文件会被同名新文件替换(覆盖)掉,如何解决这个问题呢?

其实,处理同名问题有多种方式,这里采用UUID生成随机序列来实现,只需要做简单的修改即可

@PostMapping("/testUpload")
public String testUpload(MultipartFile photo, HttpSession session) throws IOException {
    // 目标目录
    String photoPath = session.getServletContext().getRealPath("photo");
    File file = new File(photoPath);
    if (!file.exists()) {
        file.mkdir();
    }
    // 目标文件名
    String srcName = photo.getOriginalFilename();
    String suffixName = srcName.substring(srcName.lastIndexOf("."));
    String prefixName = UUID.randomUUID().toString();
    String destName = prefixName + suffixName;
    // 上传文件到服务器
    photo.transferTo(new File(photoPath + File.separator + destName));
    return "success";
}

再次测试

image-20220330230259507

可以看到,最新上传的文件名为一串随机序列,这样就避免同名文件上传出现覆盖的问题了

总结

本节知识点

HttpMessageConverter 请求报文转换器

  • 请求报文转为 JAVA 对象:@RequestBody、RequestEntity
  • JAVA 对象转为请求报文:@ResponseBody、ResponseEntity

另外

  • jackson-databind:处理json
  • commons-fileupload:上传必备

附上导图,仅供参考

06-HttpMessageConverter

posted @ 2022-03-30 23:16  VectorX  阅读(138)  评论(0编辑  收藏  举报