HttpMessageConverter

什么是Http消息?


HTTP 请求:


HTTP POST请求是最常见的HTTP方法之一,用于向服务器提交数据。POST请求协议的格式通常包括以下几个部分:

  1. 请求行 (Request Line):

    • 包含HTTP方法(这里是POST)、请求的资源路径(URL)和HTTP版本。
    • 例如:POST /submit-form HTTP/1.1
  2. 请求头 (Request Headers):

    • 描述请求的元数据,常见的请求头包括:
      • Host: 请求的服务器域名和端口。
      • User-Agent: 发送请求的浏览器或客户端的名称和版本。
      • Accept: 客户端能够处理的媒体类型。
      • Content-Type: 请求体的媒体类型,如application/x-www-form-urlencodedmultipart/form-dataapplication/json
      • Content-Length: 请求体的长度。
      • Authorization: 如果需要,包含认证信息。
      • Cookie: 如果有会话管理,包含客户端存储的cookie。
      • Referer: 请求发起的页面URL。
  3. 空行 (Empty Line):

    • 请求头和请求体之间的分隔符,是一个空行。
  4. 请求体 (Request Body):

    • 包含发送到服务器的数据。数据的格式取决于Content-Type请求头的值。例如:
      • 对于application/x-www-form-urlencoded,数据可能是key1=value1&key2=value2
      • 对于multipart/form-data,数据可能包含文件上传信息。
      • 对于application/json,数据可能是JSON格式的对象或数组。
  5. 尾行 (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响应是由服务器发送给客户端的,它遵循一定的格式,包括以下几个部分:

  1. 状态行 (Status Line):

    • 包含HTTP版本,状态码和状态消息。
    • 例如:HTTP/1.1 200 OK
  2. 响应头 (Response Headers):

    • 描述响应的元数据,例如:
      • Content-Type: 响应体的媒体类型。
      • Content-Length: 响应体的长度。
      • Set-Cookie: 设置客户端的cookie。
      • Cache-Control: 控制缓存行为。
      • Server: 服务器软件的名称和版本。
      • Date: 响应生成的日期和时间。
  3. 空行 (Empty Line):

    • 响应头和响应体之间的分隔符,是一个空行。
  4. 响应体 (Response Body):

    • 服务器返回的数据,可以是HTML页面、JSON、图片、视频等。
  5. 尾行 (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";
}

image.png


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);
        }
    }
}
posted @ 2024-07-11 16:18  BLBALDMAN  阅读(3)  评论(0编辑  收藏  举报