参考视频:SpringMVC教程,SpringMVC从零到精通,老杜SpringMVC,动力节点SpringMVC

1. HttpMessageConverter

HttpMessageConverter是Spring MVC中非常重要的一个接口。翻译为:HTTP消息转换器。该接口下提供了很多实现类,不同的实现类有不同的转换方式。

HttpMessageConverter的实现类

1.1 什么是HTTP消息

HTTP消息其实就是HTTP协议。HTTP协议包括请求协议和响应协议。

以下是一份HTTP POST请求协议:

1
2
3
4
5
6
7
8
9
POST /springmvc/user/login HTTP/1.1																												--请求行
Content-Type: application/x-www-form-urlencoded --请求头
Content-Length: 32
Host: www.example.com
User-Agent: Mozilla/5.0
Connection: Keep-Alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
--空白行
username=admin&password=1234 --请求体

以下是一份HTTP GET请求协议:

1
2
3
4
5
GET /springmvc/user/del?id=1&name=zhangsan HTTP/1.1																				--请求行
Host: www.example.com --请求头
User-Agent: Mozilla/5.0
Connection: Keep-Alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

以下是一份HTTP响应协议:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
HTTP/1.1 200 OK																																					--状态行
Date: Thu, 01 Jul 2021 06:35:45 GMT --响应头
Content-Type: text/plain; charset=utf-8
Content-Length: 12
Connection: keep-alive
Server: Apache/2.4.43 (Win64) OpenSSL/1.1.1g
--空白行
<!DOCTYPE html> --响应体
<html>
<head>
<title>hello</title>
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>

1.2 转换器转换的是什么

转换的是HTTP协议Java程序中的对象之间的互相转换。请看下图:

FormHttpMessageConverter负责将请求协议转换为Java对象

上图是我们之前经常写的代码。请求体中的数据是如何转换成user对象的,底层实际上使用了HttpMessageConverter接口的其中一个实现类FormHttpMessageConverter

通过上图可以看出FormHttpMessageConverter是负责将请求协议转换为Java对象的。

再看下图:

StringHttpMessageConverter负责将Java对象转换为响应协议 上图的代码也是之前我们经常写的,Controller返回值看做逻辑视图名称,视图解析器将其转换成物理视图名称,生成视图对象,`StringHttpMessageConverter`负责将视图对象中的HTML字符串写入到HTTP协议的响应体中。最终完成响应。

通过上图可以看出StringHttpMessageConverter是负责将Java对象转换为响应协议的。

通过以上内容的学习,大家应该能够了解到HttpMessageConverter接口是用来做什么的了:

HttpMessageConverter接口作用

如上图所示:HttpMessageConverter接口的可以将请求协议转换成Java对象,也可以把Java对象转换为响应协议。

HttpMessageConverter是接口,SpringMVC帮我们提供了非常多而丰富的实现类。每个实现类都有自己不同的转换风格。

对于我们程序员来说,Spring MVC已经帮助我们写好了,我们只需要在不同的业务场景下,选择合适的HTTP消息转换器即可。

怎么选择呢?当然是通过SpringMVC为我们提供的注解,我们通过使用不同的注解来启用不同的消息转换器。

在HTTP消息转换器这一小节,我们重点要掌握的是两个注解两个类:

  • @ResponseBody
  • @RequestBody
  • ResponseEntity
  • RequestEntity

2. Spring MVC中的AJAX请求

SpringMVC+Vue3+Thymeleaf+Axios发送一个简单的AJAX请求。

1、引入Vue和Axios的js文件:

引入Vue和Axios的js文件

2、springmvc.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">

<!--组件扫描-->
<context:component-scan base-package="com.muyoukule.controller"/>

<!--视图解析器-->
<bean id="thymeleafViewResolver" class="org.thymeleaf.spring6.view.ThymeleafViewResolver">
<property name="characterEncoding" value="UTF-8"/>
<property name="order" value="1"/>
<property name="templateEngine">
<bean class="org.thymeleaf.spring6.SpringTemplateEngine">
<property name="templateResolver">
<bean class="org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver">
<property name="prefix" value="/WEB-INF/thymeleaf/"/>
<property name="suffix" value=".html"/>
<property name="templateMode" value="HTML"/>
<property name="characterEncoding" value="UTF-8"/>
</bean>
</property>
</bean>
</property>
</bean>

<!--视图控制器映射-->
<mvc:view-controller path="/" view-name="index"/>

<!--开启注解驱动-->
<mvc:annotation-driven/>

<!--静态资源处理-->
<mvc:default-servlet-handler/>

</beans>

重点是静态资源处理、开启注解驱动、视图控制器映射等相关配置。

3、index.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首页</title>
<script th:src="@{/static/js/vue3.4.21.js}"></script>
<script th:src="@{/static/js/axios.min.js}"></script>
</head>
<body>
<h1>首页</h1>
<hr>

<div id="app">
<h1>{{message}}</h1>
<button @click="getMessage">获取消息</button>
</div>

<script th:inline="javascript">
Vue.createApp({
data(){
return {
message : "这里的信息将被刷新"
}
},
methods:{
async getMessage(){
try {
const response = await axios.get([[@{/}]] + 'hello')
this.message = response.data
}catch (e) {
console.error(e)
}
}
}
}).mount("#app")
</script>

</body>
</html>

重点来了,Controller怎么写呢,之前我们都是传统的请求,Controller返回一个逻辑视图名,然后交给视图解析器解析。最后跳转页面。而AJAX请求是不需要跳转页面的,因为AJAX是页面局部刷新,以前我们在Servlet中使用response.getWriter().print("message")的方式响应。

在Spring MVC中怎么办呢?当然,我们在Spring MVC中也可以使用Servlet原生API来完成这个功能

4、代码如下:

1
2
3
4
5
6
7
8
9
@Controller
public class HelloController {

@RequestMapping(value = "/hello")
public String hello(HttpServletResponse response) throws IOException {
response.getWriter().print("hello");
return null;
}
}

或者这样也行:不需要有返回值

1
2
3
4
5
6
7
8
@Controller
public class HelloController {

@RequestMapping(value = "/hello")
public void hello(HttpServletResponse response) throws IOException {
response.getWriter().print("hello");
}
}

5、启动服务器测试:http://localhost:8080/springmvc/

测试发送AJAX请求 发送AJAX请求成功

PS:如果采用这种方式响应,则和 springmvc.xml 文件中配置的视图解析器没有关系,不走视图解析器了。

难道我们以后AJAX请求都要使用原生Servlet API吗?

不需要,我们可以使用SpringMVC中提供的HttpMessageConverter消息转换器。

我们要向前端响应一个字符串”hello”,这个”hello”就是响应协议中的响应体。

我们可以使用 @ResponseBody 注解来启用对应的消息转换器。而这种消息转换器只负责将Controller返回的信息以响应体的形式写入响应协议。

3. @ResponseBody

3.1 StringHttpMessageConverter

上面的AJAX案例,Controller的代码可以修改为:

1
2
3
4
5
6
7
8
9
10
11
12
@Controller
public class HelloController {

@RequestMapping(value = "/hello")
@ResponseBody
public String hello() {
// 由于你使用了 @ResponseBody 注解
// 以下的return语句返回的字符串则不再是“逻辑视图名”了
// 而是作为响应协议的响应体进行响应。
return "hello";
}
}

最核心需要理解的位置是:return "hello",这里的”hello”不是逻辑视图名了,而是作为响应体的内容进行响应。直接输出到浏览器客户端。

以上程序中添加@ResponseBody这个注解,使用的消息转换器是:StringHttpMessageConverter

通常AJAX请求需要服务器给返回一段JSON格式的字符串,可以返回JSON格式的字符串吗?当然可以。

代码如下:

1
2
3
4
5
6
7
8
9
10
@Controller
public class HelloController {

@RequestMapping(value = "/hello")
@ResponseBody
public String hello() {
return "{\"username\":\"zhangsan\",\"password\":\"1234\"}";
}

}

测试:

测试AJAX请求需要服务器返回JSON格式字符串

这是完全可以的,此时底层使用的消息转换器还是:StringHttpMessageConverter

如果在程序中是一个POJO对象,怎么将POJO对象以JSON格式的字符串响应给浏览器呢?

两种方式:

  • 自己写代码将POJO对象转换成JSON格式的字符串,用上面的方式直接return即可。
  • 启用MappingJackson2HttpMessageConverter消息转换器。

3.2 MappingJackson2HttpMessageConverter

启用MappingJackson2HttpMessageConverter消息转换器的步骤如下:

1、引入jackson依赖,可以将java对象转换为json格式字符串

1
2
3
4
5
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.0</version>
</dependency>

2、开启注解驱动

这一步非常关键,开启注解驱动后,在HandlerAdapter中会自动装配一个消息转换器:MappingJackson2HttpMessageConverter

1
<mvc:annotation-driven/>

3、准备一个POJO(使用Lombok简化)

1
2
3
4
5
6
7
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String username;
private String password;
}

4、控制器方法使用 @ResponseBody 注解标注!!!控制器方法返回这个POJO对象

1
2
3
4
5
6
7
8
9
10
@Controller
public class HelloController {

@RequestMapping(value = "/hello")
@ResponseBody
public User hello() {
User user = new User("zhangsan", "22222");
return user;
}
}

5、测试:

测试MappingJackson2HttpMessageConverter消息转换器

以上代码底层启用的就是 MappingJackson2HttpMessageConverter 消息转换器。

他的功能很强大,可以将POJO对象转换成JSON格式的字符串,响应给前端。

其实这个消息转换器MappingJackson2HttpMessageConverter本质上只是比StringHttpMessageConverter多了一个json字符串的转换,其他的还是一样。

4. @RestController

因为我们现代的开发方式都是基于AJAX方式的,因此 @ResponseBody 注解非常重要,很常用。

为了方便,Spring MVC中提供了一个注解 @RestController。这一个注解代表了:@Controller + @ResponseBody

@RestController 标注在类上即可。被它标注的Controller中所有的方法上都会自动标注 @ResponseBody

1
2
3
4
5
6
7
8
9
@RestController
public class HelloController {

@RequestMapping(value = "/hello")
public User hello() {
User user = new User("zhangsan", "22222");
return user;
}
}

测试:

测试@RestController注解

5. @RequestBody

5.1 FormHttpMessageConverter

这个注解的作用是直接将请求体传递给Java程序,在Java程序中可以直接使用一个String类型的变量接收这个请求体的内容。

在没有使用这个注解的时候:

1
2
3
4
5
6
7
@RequestMapping("/save")
public String save(User user){
// 执行保存的业务逻辑
userDao.save(user);
// 保存成功跳转到成功页面
return "success";
}

当请求体提交的数据是:

1
username=zhangsan&password=1234&email=zhangsan@example.com

那么Spring MVC会自动使用 FormHttpMessageConverter消息转换器,将请求体转换成user对象。

当使用这个注解的时候:这个注解只能出现在方法的参数上。

1
2
3
4
5
@RequestMapping("/save")
public String save(@RequestBody String requestBodyStr){
System.out.println("请求体:" + requestBodyStr);
return "success";
}

Spring MVC仍然会使用 FormHttpMessageConverter消息转换器,将请求体直接以字符串形式传递给 requestBodyStr 变量。

测试输出结果:

1
请求体:username=zhangsan&password=1234&email=zhangsan@example.com

5.2 MappingJackson2HttpMessageConverter

另外,如果在请求体中提交的是一个JSON格式的字符串,这个JSON字符串传递给Spring MVC之后,能不能将JSON字符串转换成POJO对象呢?答案是可以的。

此时必须使用 @RequestBody 注解来完成 。并且底层使用的消息转换器是:MappingJackson2HttpMessageConverter

JSON字符串转换成POJO对象实现步骤

1、引入jackson依赖

2、开启注解驱动

3、创建POJO类,将POJO类作为控制器方法的参数,并使用 @RequestBody 注解标注该参数

1
2
3
4
5
6
7
8
@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";
}

4、在请求体中提交json格式的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首页</title>
<script th:src="@{/static/js/vue3.4.21.js}"></script>
<script th:src="@{/static/js/axios.min.js}"></script>
</head>
<body>
<h1>首页</h1>
<hr>

<div id="app">
<button @click="sendJSON">通过POST请求发送JSON给服务器</button>
<h1>{{message}}</h1>
</div>

<script th:inline="javascript">
let jsonObj = {"username": "zhangsan", "password": "1234"}

Vue.createApp({
data() {
return {
message: ""
}
},
methods: {
async sendJSON() {
console.log("sendjson")
try {
const res = await axios.post('/springmvc/send', JSON.stringify(jsonObj), {
headers: {
"Content-Type": "application/json"
}
})
this.message = res.data
} catch (e) {
console.error(e)
}
}
}
}).mount("#app")
</script>

</body>
</html>

5、测试结果:

测试JSON字符串转换成POJO对象

6、控制台打印:

1
2
3
User(username=zhangsan, password=1234)
zhangsan
1234

6. RequestEntity

RequestEntity不是一个注解,是一个普通的类。这个类的实例封装了整个请求协议:包括请求行、请求头、请求体所有信息。

出现在控制器方法的参数上:

1、index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首页</title>
<script th:src="@{/static/js/vue3.4.21.js}"></script>
<script th:src="@{/static/js/axios.min.js}"></script>
</head>
<body>
<h1>首页</h1>
<hr>

<div id="app">
<button @click="sendJSON">通过POST请求发送JSON给服务器</button>
<h1>{{message}}</h1>
</div>

<script th:inline="javascript">
let jsonObj = {"username": "zhangsan", "password": "1234"}

Vue.createApp({
data() {
return {
message: ""
}
},
methods: {
async sendJSON() {
console.log("sendjson")
try {
const res = await axios.post('/springmvc/send', JSON.stringify(jsonObj), {
headers: {
"Content-Type": "application/json"
}
})
this.message = res.data
} catch (e) {
console.error(e)
}
}
}
}).mount("#app")
</script>

</body>
</html>

2、编写Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@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";
}

3、测试结果:

1
2
3
4
5
6
7
请求方式:POST
请求URL:http://localhost:8080/springmvc/send
请求的内容类型:application/json;charset=UTF-8
请求头:[host:"localhost:8080", connection:"keep-alive", content-length:"41", sec-ch-ua:""Microsoft Edge";v="123", "Not:A-Brand";v="8", "Chromium";v="123"", accept:"application/json, text/plain, */*", sec-ch-ua-mobile:"?0", user-agent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.0.0", sec-ch-ua-platform:""Windows"", origin:"http://localhost:8080", sec-fetch-site:"same-origin", sec-fetch-mode:"cors", sec-fetch-dest:"empty", referer:"http://localhost:8080/springmvc/", accept-encoding:"gzip, deflate, br, zstd", accept-language:"zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6", cookie:"username-localhost-8888="2|1:0|10:1712017810|23:username-localhost-8888|192:eyJ1c2VybmFtZSI6ICIzNWI3MjhjZDkyMmI0OGFlYjM1OGYxMmY4NTFlZTMzNCIsICJuYW1lIjogIkFub255bW91cyBBb2VkZSIsICJkaXNwbGF5X25hbWUiOiAiQW5vbnltb3VzIEFvZWRlIiwgImluaXRpYWxzIjogIkFBIiwgImNvbG9yIjogbnVsbH0=|a447ec864609b4c8600bddfe5ddf9ca59ff01b60f92cdc6ee819d5fd84bf761d"; _xsrf=2|810e7615|c7a87f219cbac7ee02d1fe7e6b6c093c|1712017810; id=123456789; Idea-fa2b739c=6fadb531-b45d-4f4b-9773-90a820641d74", Content-Type:"application/json;charset=UTF-8"]
User(username=zhangsan, password=1234)
zhangsan
1234

在实际的开发中,如果你需要获取更详细的请求协议中的信息。可以使用RequestEntity

7. ResponseEntity

ResponseEntity不是注解,是一个类。用该类的实例可以封装响应协议,包括:状态行、响应头、响应体。也就是说:如果你想定制属于自己的响应协议,可以使用该类。

假如我要完成这样一个需求:前端提交一个id,后端根据id进行查询,如果返回null,请在前端显示404错误。如果返回不是null,则输出返回的user。

1、编写UserService.java

1
2
3
4
5
6
7
8
9
10
@Service
public class UserService {

public User getById(Long id) {
if (id == 1) {
return new User("zhangsan", "13234");
}
return null;
}
}

2、配置组件扫描

1
2
<!--组件扫描-->
<context:component-scan base-package="com.muyoukule.controller,com.muyoukule.service"/>

3、在 index.html 添加超链接

1
<a th:href="@{/user/2}">查找id=1的用户信息</a>

4、编写Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
@Controller
public class UserController {

@GetMapping("/users/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
User user = userService.getUserById(id);
if (user == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
} else {
return ResponseEntity.ok(user);
}
}
}

5、测试:当用户不存在时

测试当用户不存在时

6、测试:当用户存在时

测试当用户存在时