HttpMessageConverter
什么是Http消息?
HTTP 请求:
HTTP POST请求是最常见的HTTP方法之一,用于向服务器提交数据。POST请求协议的格式通常包括以下几个部分:
-
请求行 (Request Line):
- 包含HTTP方法(这里是
POST
)、请求的资源路径(URL)和HTTP版本。 - 例如:
POST /submit-form HTTP/1.1
- 包含HTTP方法(这里是
-
请求头 (Request Headers):
- 描述请求的元数据,常见的请求头包括:
Host
: 请求的服务器域名和端口。User-Agent
: 发送请求的浏览器或客户端的名称和版本。Accept
: 客户端能够处理的媒体类型。Content-Type
: 请求体的媒体类型,如application/x-www-form-urlencoded
、multipart/form-data
或application/json
。Content-Length
: 请求体的长度。Authorization
: 如果需要,包含认证信息。Cookie
: 如果有会话管理,包含客户端存储的cookie。Referer
: 请求发起的页面URL。
- 描述请求的元数据,常见的请求头包括:
-
空行 (Empty Line):
- 请求头和请求体之间的分隔符,是一个空行。
-
请求体 (Request Body):
- 包含发送到服务器的数据。数据的格式取决于
Content-Type
请求头的值。例如:- 对于
application/x-www-form-urlencoded
,数据可能是key1=value1&key2=value2
。 - 对于
multipart/form-data
,数据可能包含文件上传信息。 - 对于
application/json
,数据可能是JSON格式的对象或数组。
- 对于
- 包含发送到服务器的数据。数据的格式取决于
-
尾行 (End of Message):
- 表示HTTP请求消息的结束,通常是一个换行符。
一个典型的HTTP POST请求示例如下:
POST /submit-form HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
Accept: application/json, text/javascript, */*; q=0.01
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Content-Length: 27
X-Requested-With: XMLHttpRequest
Referer: http://www.example.com/form.html
Connection: keep-alive
username=user1&password=pass123
在这个示例中,请求行指定了使用POST方法请求/submit-form
资源。请求头包括了主机、用户代理、接受的媒体类型、内容类型、内容长度、请求方式和引用页面等信息。请求体包含了表单数据,使用application/x-www-form-urlencoded
格式。
HTTP响应:
HTTP响应是由服务器发送给客户端的,它遵循一定的格式,包括以下几个部分:
-
状态行 (Status Line):
- 包含HTTP版本,状态码和状态消息。
- 例如:
HTTP/1.1 200 OK
-
响应头 (Response Headers):
- 描述响应的元数据,例如:
Content-Type
: 响应体的媒体类型。Content-Length
: 响应体的长度。Set-Cookie
: 设置客户端的cookie。Cache-Control
: 控制缓存行为。Server
: 服务器软件的名称和版本。Date
: 响应生成的日期和时间。
- 描述响应的元数据,例如:
-
空行 (Empty Line):
- 响应头和响应体之间的分隔符,是一个空行。
-
响应体 (Response Body):
- 服务器返回的数据,可以是HTML页面、JSON、图片、视频等。
-
尾行 (End of Message):
- 表示HTTP消息的结束,通常是一个换行符。
一个典型的HTTP响应示例如下:
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Content-Length: 1024
Connection: keep-alive
Server: Apache/2.4.7 (Unix)
Date: Wed, 10 Jul 2024 12:00:00 GMT
<!DOCTYPE html>
<html>
<head>
<title>Example Page</title>
</head>
<body>
<h1>Hello, World!</h1>
<p>This is an example of an HTTP response body.</p>
</body>
</html>
在这个示例中,状态行为HTTP/1.1 200 OK
,表示请求成功。响应头包括了内容类型、内容长度、连接状态、服务器信息和日期等信息。响应体是一个简单的HTML页面。
HttpMessageConverter
SpingMVC中的AJAX请求
Vue3 +Axios + Thymeleaf + SpringMVC
<!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>
对于一个这样的AJAX请求,我们该怎么响应呢?我们之前都是Controller返回一个逻辑视图名称,然后由视图解析器解析。最后进行页面跳转。但是AJAX请求不需要页面跳转,因为AJAX请求只进行局部刷新。虽然我们不按之前那套了,但是我们仍然可以使用Servlet原生API进行响应。代码如下
package com.powernode;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import java.io.IOException;
import java.io.PrintWriter;
// 声明这是一个Controller
@Controller
public class HelloController {
// 定义一个请求映射,当访问/hello时,调用hello方法
@RequestMapping(value = "/hello")
public void hello(HttpServletResponse httpServletResponse) {
try (PrintWriter p = httpServletResponse.getWriter()) {
// 使用PrintWriter打印"Hello the World"
p.print("Hello the World");
} catch (IOException e) {
// 抛出一个运行时异常
throw new RuntimeException(e);
}
}
}
@Responsebody注解
StringHttpMessageConverter
事实上我们之前就说Servlet原生API不是很便捷,上面的代码要求我们给前端响应一个字符串"hello",这个"hello"就是响应协议中的响应体。我们可以使用 @ResponseBody 注解来启用对应的消息转换器。而这种消息转换器只负责将Controller返回的信息以响应体的形式写入响应协议。代码如下:
package com.powernode.springmvc.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class HelloController {
@RequestMapping(value = "/hello")
@ResponseBody
public String hello(){
// 由于你使用了 @ResponseBody 注解
// 以下的return语句返回的字符串则不再是“逻辑视图名”了
// 而是作为响应协议的响应体进行响应。
return "hello";
}
}
最核心需要理解的位置是:return "hello";这里的"hello"不是逻辑视图名了,而是作为响应体的内容进行响应。直接输出到浏览器客户端。
以上程序中使用的消息转换器是:StringHttpMessageConverter,为什么会启用这个消息转换器呢?因为你添加@ResponseBody
这个注解。
通常来说AJAX请求需要服务器返回一段JSON格式的字符串,如何返回一段JSON格式的字符串呢?
package com.powernode.springmvc.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class HelloController {
@RequestMapping(value = "/hello")
@ResponseBody
public String hello(){
return "{\"username\":\"zhangsan\",\"password\":\"1234\"}";
}
}
那么如果程序中的数据是pojo对象,那么怎么把pojo对象返回呢?有两种方式,第一种方式是将pojo对象转换为json字符串,然后再通过StringHttpConverter
方式返回。
或者是通过MappingJackson2HttpMessageConverter
MappingJackson2HttpMessageConverter
使用MappingJackson2HttpMessageConverter
步骤如下:
第一步:引入jackson依赖,他可以将Java对象转换为json格式字符串
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.0</version>
</dependency>
第二步:开启注解驱动
<mvc:annotation-driven/>
第三步:准备一个pojo对象:
package com.powernode;
public class User {
private String name;
private int age;
private String sex;
public User(String name, String sex, int age) {
this.name = name;
this.sex = sex;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
第四步:控制器方法使用 @ResponseBody 注解标注(非常重要),控制器方法返回这个POJO对象
package com.powernode;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.io.IOException;
import java.io.PrintWriter;
// 声明这是一个Controller
@Controller
public class HelloController {
// 通过@RequestMapping注解映射"/hello"的URL请求
@RequestMapping(value = "/hello")
// 通过@ResponseBody注解将方法的返回值作为响应体
@ResponseBody
// 定义一个public类型的User类型的方法hello,返回一个User类型的对象
public User hello() {
// 创建一个User类型的对象user,并赋值
User user = new User("周波波", "男", 21);
// 返回user对象
return user;
}
}
@RestController
@RestController
这个注解对我们来说非常重要,我们在使用@RestController
的时候难免使用@Controller
,现在我们可以使用RestController
,这是个复合注解,约等于@ResponseBody
+@Controller
,这个注解需要注解在类上,凡是被它标注的Controller中所有的方法上都会自动标注 @ResponseBody
package com.powernode;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.io.PrintWriter;
// 声明这是一个Controller
@RestController
@Controller
public class HelloController {
@RequestMapping(value = "/hello")
public User Hello(){
//创建一个User类型的对象user,并赋值
User user = new User("周波波", "男", 22);
// 返回user对象
return user;
}
}
@Requestbody注解
FormHttpMessageConverter
这个注解的作用是直接将请求体传递给Java程序,在Java程序中可以直接使用一个String类型的变量接收这个请求体的内容。
如果没有使用该注解。并且请求体的数据为username=zhangsan&password=1234&email=zhangsan@powernode.com
,那么Spring MVC会自动使用 FormHttpMessageConverter
消息转换器,将请求体转换成user对象。
@RequestMapping("/save")
public String save(User user){
// 执行保存的业务逻辑
userDao.save(user);
// 保存成功跳转到成功页面
return "success";
}
如果使用该注解。注意这个注解只能出现在方法的参数上。Spring MVC仍然会使用 FormHttpMessageConverter
消息转换器,将请求体直接以字符串形式传递给 requestBodyStr 变量。
@RequestMapping("/save")
public String save(@RequestBody String requestBodyStr){
System.out.println("请求体:" + requestBodyStr);
return "success";
}
MappingJackson2HttpMessageConverter
另外,如果在请求体中提交的是一个JSON格式的字符串,这个JSON字符串传递给Spring MVC之后,能不能将JSON字符串转换成POJO对象呢?答案是可以的。
此时必须使用@RequestBody 注解来完成 。并且底层使用的消息转换器是:MappingJackson2HttpMessageConverter
。实现步骤如下:
第一步:引入jackson依赖
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.0</version>
</dependency>
第二步:开启注解驱动
<mvc:annotation-driven/>
第三步:准备一个pojo对象:
package com.powernode;
public class User {
private String name;
private int age;
private String sex;
public User(String name, String sex, int age) {
this.name = name;
this.sex = sex;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
第四步:创建控制类同时将POJO类作为控制器方法的参数,并使用 @RequestBody 注解标注该参数
package com.powernode;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.io.IOException;
import java.io.PrintWriter;
@Controller
public class HelloController {
@RequestMapping(value = "/send")
@ResponseBody
public String send(@RequestBody User user){
System.out.println("----------------------");
System.out.println(user.toString());
System.out.println(user.getName());
System.out.println(user.getSex());
System.out.println(user.getAge());
System.out.println("----------------------");
return "success";
}
}
第五步:在请求体中提交json格式的数据
<!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>
<div id="app">
<button @click="sendJSON">通过POST请求发送JSON给服务器</button>
<h1>{{message}}</h1>
</div>
<script>
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>
RequestEntity类
RequestEntity不是一个注解,是一个普通的类。这个类的实例封装了整个请求协议:包括请求行、请求头、请求体所有信息。
出现在控制器方法的参数上:
@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";
}
<!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>
<div id="app">
<button @click="sendJSON">通过POST请求发送JSON给服务器</button>
<h1>{{message}}</h1>
</div>
<script>
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>
ResponseEntity类
ResponseEntity不是注解,是一个类。用该类的实例可以封装响应协议,包括:状态行、响应头、响应体。也就是说:如果你想定制属于自己的响应协议,可以使用该类。
假如我要完成这样一个需求:前端提交一个id,后端根据id进行查询,如果返回null,请在前端显示404错误。如果返回不是null,则输出返回的user。
@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);
}
}
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器
· 面试官:你是如何进行SQL调优的?