参考视频:SpringMVC教程,SpringMVC从零到精通,老杜SpringMVC,动力节点SpringMVC
1. HttpMessageConverter
HttpMessageConverter是Spring MVC中非常重要的一个接口。翻译为:HTTP消息转换器。该接口下提供了很多实现类,不同的实现类有不同的转换方式。
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程序中的对象
之间的互相转换。请看下图:
上图是我们之前经常写的代码。请求体中的数据是如何转换成user对象的,底层实际上使用了HttpMessageConverter
接口的其中一个实现类FormHttpMessageConverter
。
通过上图可以看出FormHttpMessageConverter
是负责将请求协议
转换为Java对象
的。
再看下图:
上图的代码也是之前我们经常写的,Controller返回值看做逻辑视图名称,视图解析器将其转换成物理视图名称,生成视图对象,`StringHttpMessageConverter`负责将视图对象中的HTML字符串写入到HTTP协议的响应体中。最终完成响应。
通过上图可以看出StringHttpMessageConverter
是负责将Java对象
转换为响应协议
的。
通过以上内容的学习,大家应该能够了解到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文件:
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/
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() { 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\"}"; }
}
|
测试:
这是完全可以的,此时底层使用的消息转换器还是: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
消息转换器。
他的功能很强大,可以将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; } }
|
测试:
5. @RequestBody
这个注解的作用是直接将请求体传递给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、测试结果:
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、测试:当用户存在时