前言(SpringBoot程序请求响应流程)
以上一章的程序为例,一个基于SpringBoot的方式开发一个web应用,浏览器发起请求 /hello 后 ,给浏览器返回字符串 “Hello World ~”。
而我们在开发web程序时呢,定义了一个控制器类Controller,请求会被部署在Tomcat中的Controller接收,然后Controller再给浏览器一个响应,响应一个字符在浏览器发起请求,请求了我们的后端web服务器(也就是内置串 “Hello World”。 而在请求响应的过程中是遵循HTTP协议的。
但是在Tomcat这类Web服务器中,是不识别我们自己定义的Controller的。而Tomcat是一个Servlet容器,支持Serlvet规范,因此在tomcat中是可以识别 Servlet程序的。
- Servlet(服务器小程序)是一种运行在 Web 服务器或应用服务器上的 Java 程序,用于处理客户端(如浏览器)发送的 HTTP 请求并生成响应。它是 Java Web 开发的核心组件之一,遵循 Java Servlet API 规范。
- 简单来说,当用户通过浏览器访问一个网站时,浏览器会发送 HTTP 请求到 Web 服务器,服务器中的 Servlet 程序就会接收并处理这些请求,比如获取用户请求的页面内容、处理用户提交的表单数据等,然后生成一个响应返回给浏览器,这个响应可以是 HTML 页面、XML 数据、JSON 数据或者其他类型的内容。
那么在SpringBoot进行web程序开发时,它其实内置了一个核心的Servlet程序 DispatcherServlet,称之为 核心控制器,也可以叫做 前端控制器。 DispatcherServlet 负责接收页面发送的请求,然后根据执行的规则,将请求再转发给后面的请求处理器Controller,请求处理器处理完请求之后,最终再由DispatcherServlet给浏览器响应数据。
那将来浏览器发送请求,会携带请求数据,包括:请求行、请求头;请求到达tomcat之后,tomcat会负责解析这些请求数据,然后呢将解析后的请求数据会传递给Servlet程序中的HttpServletRequest对象,那也就意味着 HttpServletRequest 对象就可以获取到请求数据。 而Tomcat,还给Servlet程序传递了一个参数 HttpServletResponse,通过这个对象,我们就可以给浏览器设置响应数据 。
一 请求
1、Postman(接口测试工具)
postman的安装使用教程 我已经在另一篇文章详细介绍 Postman安装使用教程-CSDN博客
2、简单参数
①、原始方式
Tomcat接收到http请求时:把请求的相关信息封装到HttpServletRequest对象中
在Controller中,我们要想获取Request对象,可以直接在方法的形参中声明 HttpServletRequest 对象。然后就可以通过该对象来获取请求信息:
②、SpringBoot方法
在Springboot方法中可以自动进行类型转换。在Springboot的环境中,对原始的API进行了封装,接收参数的形式更加简单。 对于简单参数来讲,只要保证请求参数名和Controller方法中的形参名保持一致,就可以获取到请求参数中的数据值。
发送Post请求:
如果形参与请求参数对应不上,也可以通过注解@RequestParam来进行映射:
但是如果没有设置@RequestParam注解,且方法形参名与请求参数名不一致,那么虽然会无法接收到请求数据,但是它不会报错
注解@RequestParam中的required属性默认为true,代表该请求参数必须传递,如果不传递就会报错:
③、小结
3、实体参数
在使用简单参数做为数据传递方式时,前端传递了多少个请求参数,后端controller方法中的形参就要书写多少个。如果请求参数比较多,通过上述的方式一个参数一个参数的接收,会比较繁琐。
①、简单实体对象
此时,我们可以考虑将请求参数封装到一个实体类对象中。 要想完成数据封装,需要遵守如下规则:请求参数名与实体类的属性名相同
User类 定义在实体类Pojo中
②、复杂实体对象
③、小结
如果是复杂实体对象,也只需按照对象层次结构关系即可接收嵌套实体类属性参数
4、数组集合参数
数组集合参数的使用场景:在HTML的表单中,有一个表单项是支持多选的(复选框),可以提交选择的多个值
多个值是怎么提交的呢?其实多个值也是一个一个逐个提交的
①、数组
②、集合
requestparam适用场景:
**`@RequestParam`注解的使用场景**
1- **绑定单个请求参数** - 当你需要从HTTP请求(通常是GET或POST请求)中获取单个参数时,可以使用`@RequestParam`。
例如,在一个处理用户登录的方法中,如果前端通过表单提交了用户的用户名和密码,后端方法可以使用`@RequestParam`来获取这些参数。
```java @RequestMapping("/login")
public String login(@RequestParam("username") String username, @RequestParam("password") String password) {
// 在这里进行登录验证逻辑
return "loginSuccess";
} ```
- 这里`@RequestParam("username")`和`@RequestParam("password")`分别用于获取名为“username”和“password”的请求参数,并将其绑定到对应的方法参数上。
2- **参数非必需情况** - `@RequestParam`还可以处理参数不是必需的情况。你可以通过设置`required`属性来指定参数是否必需。例如:
```java @RequestMapping("/search")
public String search(@RequestParam(name = "keyword", required = false) String keyword) {
if (keyword!= null) { // 进行搜索逻辑 }
return "searchResult";
} ```
- 在这个例子中,“keyword”参数不是必需的。如果前端没有传递“keyword”参数,`keyword`方法参数将为`null`。
3- **设置默认值** - 当参数不是必需且你希望在参数未传递时给方法参数赋予一个默认值时,可以使用`@RequestParam`的`defaultValue`属性。
例如: ```java
@RequestMapping("/page")
public String showPage(@RequestParam(name = "pageNum", defaultValue = "1") int pageNum) {
// 根据pageNum进行分页逻辑
return "pageContent"; }
```
- 这里如果前端没有传递“pageNum”参数,`pageNum`方法参数将默认为`1`。
4- **绑定复杂类型列表或数组** - 除了单个参数,`@RequestParam`还可以用于绑定列表或数组类型的参数。例如,当你希望从前端获取多个同名参数的值时(比如多选框的值): ```java
@RequestMapping("/select") public String selectOptions(@RequestParam("selectedOptions") List<String> selectedOptions) {
// 处理选中的选项
return "selectionResult";
} ```
- 前端可能通过`?selectedOptions=option1&selectedOptions=option2`这种形式传递参数,后端使用`@RequestParam`将这些同名参数值绑定到`List<String>`中。
一个细节点:
Array需要toString转换为字符串 而 List不需要 是因为:
1. **`String[]`数组的情况**
- 在Java中,`System.out.println()`方法没有对数组类型进行特殊的重载处理。当直接传递一个数组给`System.out.println()`时,它实际上是调用`Object`类的`toString()`方法。对于数组来说,`Object`类的`toString()`方法返回的是一个类似`[类型@哈希码]`的字符串,这并不是我们想要的数组内容的表示形式。
- 而`Arrays.toString()`方法是`java.util.Arrays`类提供的一个工具方法,它专门用于将数组转换为一个包含数组元素的字符串,格式为`[元素1, 元素2,...]`。所以当我们有一个`String[]`类型的参数时,需要使用`Arrays.toString()`来将数组内容以可读的形式输出。
2. **`List<String>`列表的情况** - 对于`List`类型,`java.util.List`继承自`java.util.Collection`,而`Collection`类已经重写了`toString()`方法。当调用`System.out.println()`输出一个`List`对象时,实际上调用的是`List`类重写后的`toString()`方法。
- 重写后的`toString()`方法会按照`[元素1, 元素2,...]`的格式返回列表中的元素。所以,当我们有一个`List<String>`类型的参数时,可以直接将其传递给`System.out.println()`,就能得到列表内容的可读形式输出。
5、日期参数
因为日期的格式多种多样(如:2022-12-12 10:05:45 、2022/12/12 10:05:45),那么对于日期类型的参数在进行封装的时候,需要通过@DateTimeFormat注解,以及其pattern属性来设置日期的格式
两个细节点:
1. **关于日期时间格式化模式中的大小写**
- 在`SimpleDateFormat`(用于格式化`java.util.Date`类型)以及相关的日期时间格式化模式中,`MM`和`HH`大写是有特定含义的,并且在`@DateTimeFormat`注解用于格式化`java.time.LocalDateTime`等类型时也遵循类似规则。
- `MM`用于表示月份,是月份的数字表示,范围是`01 - 12`。如果写成`mm`,则表示分钟,范围是`00 - 59`。
- `HH`用于表示24小时制的小时数,范围是`00 - 23`。如果写成`hh`,则表示12小时制的小时数,范围是`01 - 12`。
所以,为了准确表示你想要的日期时间格式部分,这些字母的大小写是很重要的。
2. **关于`LocalDateTime`输出中的`T`**
- `LocalDateTime`是Java 8引入的日期时间类型,它遵循ISO 8601日期时间格式标准。在ISO 8601格式中,日期和时间部分是通过`T`来分隔的。
- 例如,`2024 - 12 - 08T15:25:05`表示2024年12月8日15时25分05秒。`T`只是一个标准的分隔符,用于清晰地区分日期部分(`yyyy - MM - dd`)和时间部分(`HH:mm:ss`)。当你将`LocalDateTime`对象打印输出或者序列化为字符串时,就会按照这种标准格式来显示,方便在不同系统和应用之间进行日期时间信息的统一表示和交换。
6、JSON参数
在前后端进行交互时,如果是比较复杂的参数,前后端通过会使用JSON格式的数据进行传输。 (JSON是开发中最常用的前后端数据交互方式)
①、Postman在发送请求时,如何传递json格式的请求参数
②、在服务端的controller方法中,如何接收json格式的请求参数
服务端Controller方法接收JSON格式数据:
- 传递json格式的参数,在Controller中会使用实体类进行封装。
- 封装规则:JSON数据键名与形参对象属性名相同,定义POJO类型形参即可接收参数。需要使用 @RequestBody标识。
- @RequestBody注解:将JSON数据映射到形参的实体类对象中(JSON中的key和实体类中的属性名保持一致)
requestbody注解
1. **处理HTTP请求体中的数据时使用`@RequestBody`**
- **接收JSON数据**
- 在现代Web开发中,当客户端(如前端应用或者其他外部系统)通过POST、PUT等请求方法发送JSON数据给后端服务时,后端通常使用`@RequestBody`来接收和解析这些数据。例如,在一个基于Spring Boot的Web应用中,有一个用户注册的功能。前端会收集用户的姓名、年龄、邮箱等信息,并将这些信息组装成一个JSON对象,像这样:
```json { "name": "John Doe",
"age": 30,
"email": "johndoe@example.com" } ```
后端的Spring Boot控制器方法可以这样接收数据:
```java
@RequestMapping(value = "/register", method = RequestMethod.POST)
public String registerUser(@RequestBody User user) {
// 将用户信息保存到数据库等操作
return "success"; }
``` 这里假设`User`是一个Java类,包含`name`、`age`、`email`等属性。`@RequestBody`注解会自动将JSON数据解析并绑定到`User`对象的相应属性上。
- **接收XML数据(虽然现在JSON更常用)**
- 如果客户端发送XML格式的数据,也可以使用`@RequestBody`来处理。例如,假设客户端发送如下XML数据来表示一个订单:
```xml
<order>
<productId>
123
</productId>
<quantity>
5
</quantity>
<customerName>
Alice
</customerName>
</order> ```
后端可以定义一个`Order`类来匹配XML数据的结构,并且在控制器方法中使用`@RequestBody`来接收: ```java
@RequestMapping(value = "/placeOrder", method = RequestMethod.POST)
public String placeOrder(@RequestBody Order order) {
// 处理订单逻辑,如保存订单到数据库等
return "orderPlaced";
} ```
- **复杂数据类型传输**
- 当需要传输复杂的数据结构,如包含嵌套对象或者集合的对象时,`@RequestBody`非常有用。例如,一个包含多个商品信息的购物车对象,其中每个商品对象又包含商品名称、价格、数量等信息。前端将购物车数据以JSON格式发送: ```json
{
"cartId": "C001",
"items": [
{
"productName": "Book",
"price": 20.0,
"quantity": 2
},
{
"productName": "Pen",
"price": 3.0,
"quantity": 5
}
]
} ```
后端可以通过`@RequestBody`接收并解析这个复杂的购物车对象: ```java @RequestMapping(value = "/checkout", method = RequestMethod.POST)
public String checkout(@RequestBody ShoppingCart cart) {
// 计算总价、处理库存等结账逻辑
return "checkoutSuccess";
} ```
requestmapping注解:
@RequestMapping
注解的基本概念和用途
@RequestMapping
是 Spring MVC 中用于处理请求地址映射的注解。它可以用在类和方法级别上。
@RequestMapping
注解的属性
value
属性(或path
属性,它们是等价的):用于指定请求路径。可以是一个简单的字符串路径,也可以是一个包含多个路径的数组method
属性:用于指定请求方法。除了RequestMethod.GET
和RequestMethod.POST
外,还可以指定RequestMethod.PUT
、RequestMethod.DELETE
、RequestMethod.HEAD
、RequestMethod.OPTIONS
等。consumes
属性:用于指定请求的MIME
类型(媒体类型)。例如,consumes = "application/json"
表示该方法只处理JSON
格式的请求体。produces
属性:用于指定响应的MIME
类型。例如,produces = "application/json"
表示该方法返回的响应是JSON
格式。
7、路径参数
传统的开发中请求参数是放在请求体(POST请求)传递或跟在URL后面通过?key=value的形式传递(GET请求)
而在现在的开发中,还是经常会直接在请求的URL中传递参数。例如:
http://localhost:8080/user/1
http://localhost:880/user/1/0
上述的这种传递请求参数的形式,就称之为 路径参数
①、传递单个参数
②、传递多个参数
和传递单个参数方法相同,无非就是多写一个形参和注解,需要注意的是:形参要和传递的参数相同才能接收成功
8、相关代码
RequestController.java
package com.example.demo.controller;import com.example.demo.pojo.User;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.*;import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;@RestController
public class RequestController {/*@RequestMapping("/simpleParam")public String simpleParam(String name, int age) {System.out.println(name + " " + age);return "OK";}*///1 简单参数@RequestMapping("/simpleParam")public String simpleParam(@RequestParam(name="name",required = false) String username, int age) {System.out.println(username + " " + age);return "OK";}//2 实体参数 简单实体参数 复杂实体参数@RequestMapping("/simplePojo")public String simplePojo(User user) {System.out.println(user);return "OK";}@RequestMapping("/complexPojo")public String complexPojo(User user) {System.out.println(user);return "OK";}//3 数组参数 集合参数@RequestMapping("/arrayParam")public String arrayParam(String[] hobby) {System.out.println(Arrays.toString(hobby));return "OK";}@RequestMapping("/listParam")public String listParam(@RequestParam List<String> hobby) {System.out.println(hobby);return "OK";}//4 日期参数@RequestMapping("/dateParam")public String dateParam(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime updateTime) {System.out.println(updateTime);return "OK";}//5 json参数@RequestMapping("/jsonParam")public String jsonParam(@RequestBody User user) {System.out.println(user);return "OK";}//6 路径参数@RequestMapping("/path/{id}")public String pathParam(@PathVariable Integer id) {System.out.println(id);return "OK";}@RequestMapping("/path/{id}/{name}")public String pathParam(@PathVariable Integer id, @PathVariable String name) {System.out.println(id + " " + name);return "OK";}
}
二、响应
1、@ResponseBody
在我们前面所编写的controller方法中,都已经设置了响应数据,那controller方法中的return的结果,怎么就可以响应给浏览器呢?
@RestController是两个注解的组合,@RestController = @Controller + @ResponseBody
2、统一响应结果
大家有没有发现一个问题,我们在前面所编写的这些Controller方法中,返回值各种各样,没有任何的规范
如果我们开发一个大型项目,项目中controller方法将成千上万,使用上述方式将造成整个项目难以维护。那在真实的项目开发中是什么样子的呢?
①、定义一个统一响应结构类 Result
在真实的项目开发中,无论是哪种方法,我们都会定义一个统一的返回结果。方案如下:
> 前端:只需要按照统一格式的返回结果进行解析(仅一种解析方案),就可以拿到数据
定义在一个实体类Result来包含以上信息,代码如下:
public class Result {
private Integer code;//响应码,1 代表成功; 0 代表失败
private String msg; //响应码 描述字符串
private Object data; //返回的数据
public Result() { }
public Result(Integer code, String msg, Object data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
//增删改 成功响应(不需要给前端返回数据)
public static Result success(){
return new Result(1,"success",null);
}
//查询 成功响应(把查询结果做为返回数据响应给前端)
public static Result success(Object data){
return new Result(1,"success",data);
}
//失败响应
public static Result error(String msg){
return new Result(0,msg,null);
}
}
效果展示: