SpringBoot Web请求响应

编程入门 行业动态 更新时间:2024-10-23 22:34:00

<a href=https://www.elefans.com/category/jswz/34/1769943.html style=SpringBoot Web请求响应"/>

SpringBoot Web请求响应

目录

  • 前言
  • 请求
    • Postman
      • Postman使用
    • 简单参数
      • 原始方式接收普通参数
      • SpringBoot方式接收普通参数
      • 参数名不一致问题
    • 实体参数
      • 简单实体参数
      • 复杂实体对象
    • 数组集合参数
      • 数组参数
      • 集合参数
    • 日期参数
    • JSON参数
    • 路径参数
  • 响应
    • @ResponseBody
    • 统一响应结果
    • 请求响应案例
      • 案例需求与准备工作
      • 案例实现
      • 测试与问题

前言

之前我们基于SpringBoot的方式开发了一个web应用,浏览器发起请求 /hello 后 ,给浏览器返回字符串 “Hello World ~”。

其实是我们在浏览器发起请求,请求了我们的后端web服务器(也就是内置的Tomcat)。而我们在开发web程序时呢,定义了一个控制器类Controller,请求会被部署在Tomcat中的Controller接收,然后Controller再给浏览器一个响应,响应一个字符串 “Hello World”。 而在请求响应的过程中是遵循HTTP协议的。

其实在Tomcat这类Web服务器中,是不识别我们自己定义的Controller的。但是Tomcat是一个Servlet容器,是支持Serlvet规范的,所以呢,在tomcat中是可以识别 Servlet程序的。 那我们所编写的XxxController 是如何处理请求的,又与Servlet之间有什么联系呢?

其实呢,在SpringBoot进行web程序开发时,它内置了一个核心的Servlet程序DispatcherServlet,称之为 核心控制器。 DispatcherServlet 负责接收页面发送的请求,然后根据执行的规则,将请求再转发给后面的请求处理器Controller,请求处理器处理完请求之后,最终再由DispatcherServlet给浏览器响应数据。

那将来浏览器发送请求,会携带请求数据,包括:请求行、请求头;请求到达tomcat之后,tomcat会负责解析这些请求数据,然后呢将解析后的请求数据会传递给Servlet程序的HttpServletRequest对象,那也就意味着 HttpServletRequest 对象就可以获取到请求数据。 而Tomcat,还给Servlet程序传递了一个参数 HttpServletResponse,通过这个对象,我们就可以给浏览器设置响应数据 。




请求

本章节主要讲解如何接收页面传递过来的请求数据。

Postman

由于前后端分离,对我们后端技术人员来讲,在开发过程中,是没有前端页面的,那我们怎么测试自己所开发的程序呢?

方式1:像之前SpringBoot入门案例中一样,直接使用浏览器。在浏览器中输入地址,测试后端程序。

弊端:在浏览器地址栏中输入地址这种方式都是GET请求,如何我们要用到POST请求怎么办呢?
要解决POST请求,需要程序员自己编写前端代码(比较麻烦)

方式2:使用专业的接口测试工具(我们使用Postman工具)

Postman使用

Postman是一款功能强大的网页调试与发送网页HTTP请求的Chrome插件。
可以模拟浏览器向后端服务器发起任何形式(如:get、post)的HTTP请求。
使用Postman还可以在发起请求时,携带一些请求参数、请求头等信息。

登录之后可以创建工作空间:

创建请求:

点击"Save",可以保存当前请求




简单参数

简单参数:在向服务器发起请求时,向服务器传递的是一些普通的请求数据。

在后端程序中,如何接收传递过来的普通参数数据呢?
在这里讲解两种方式:

  1. 原始方式
  2. SpringBoot方式

原始方式接收普通参数

这种方式仅做了解,开发不常用。

在原始的Web程序当中,需要通过Servlet中提供的API:HttpServletRequest(请求对象),获取请求的相关信息。比如获取请求参数:

Tomcat接收到http请求时:把请求的相关信息封装到HttpServletRequest对象中

在Controller中,我们要想获取Request对象,可以直接在方法的形参中声明
HttpServletRequest 对象。然后就可以通过该对象来获取请求信息:

@RestController
public class RequestController {//原始方式@RequestMapping("/simpleParam")public String simpleParam(HttpServletRequest request) {
// http://localhost:8080/simpleParam?name=Tom&age=10
// 请求参数: name=Tom&age=10 (有2个请求参数)
// 第1个请求参数: name=Franklin 参数名:name,参数值:Franklin
// 第2个请求参数: age=10 参数名:age , 参数值:10String name = request.getParameter("name");//name就是请求参数名String ageStr = request.getParameter("age");//age就是请求参数名int age = Integer.parseInt(ageStr);//需要手动进行类型转换System.out.println(name + " : " + age);return "OK";}
}
//根据指定的参数名获取请求参数的数据值
String request.getParameter("参数名")



SpringBoot方式接收普通参数

在Springboot的环境中,对原始的API进行了封装,接收参数的形式更加简单。 如果是简单参数,参数名与形参变量名相同,定义同名的形参即可接收参数。

@RestController
public class RequestController {// http://localhost:8080/simpleParam?name=Tom&age=10
// 第1个请求参数: name=Franklin 参数名:name,参数值:Franklin
// 第2个请求参数: age=10 参数名:age , 参数值:10
//springboot方式@RequestMapping("/simpleParam")public String simpleParam(String name , Integer age ){//形参名和请求参数名保持一致System.out.println(name+" : "+age);return "OK";}
}

postman测试( GET 请求):

postman测试( POST请求 ):

结论:不论是GET请求还是POST请求,对于简单参数来讲,只要保证请求参数名和Controller方法中的形参名保持一致,就可以获取到请求参数中的数据值。




参数名不一致问题

如果方法形参名称与请求参数名称不一致,controller方法中的形参还能接收到请求参数值吗?

@RestController
public class RequestController {// http://localhost:8080/simpleParam?name=Tom&age=20
// 请求参数名:name
//springboot方式@RequestMapping("/simpleParam")public String simpleParam(String username , Integer age ){//请求参数名和形参名不相同System.out.println(username+" : "+age);return "OK";}
}

答案:运行没有报错。 controller方法中的username值为:null,age值为20

结论:对于简单参数来讲,请求参数名和controller方法中的形参名不一致时,无法接收到请求
数据

那么如果我们开发中,遇到了这种请求参数名和controller方法中的形参名不相同,怎么办?
解决方案:可以使用Spring提供的@RequestParam注解完成映射。
在方法形参前面加上 @RequestParam 然后通过value属性执行请求参数名,从而完成映射。代码如下:

@RestController
public class RequestController {// http://localhost:8080/simpleParam?name=Tom&age=20
// 请求参数名:name
//springboot方式@RequestMapping("/simpleParam")public String simpleParam(@RequestParam("name") String username, Integer age ){System.out.println(username+" : "+age);return "OK";}
}

注意:
@RequestParam中的required属性默认为true(默认值也是true),代表该请求参数必须传递,如果不传递将报错、

如果该参数是可选的,可以将required属性设置为false:

@RequestMapping("/simpleParam")public String simpleParam(@RequestParam(name = "name", required =false) String username, Integer age){System.out.println(username+ ":" + age);return "OK";}



实体参数

在使用简单参数做为数据传递方式时,前端传递了多少个请求参数,后端controller方法中的形参就要书写多少个。如果请求参数比较多,通过上述的方式一个参数一个参数的接收,会比较繁琐。

此时,我们可以考虑将请求参数封装到一个实体类对象中。 要想完成数据封装,需要遵守如下规则:
请求参数名与实体类的属性名相同

简单实体参数

定义实体类:

public class User {private String name;private Integer age;public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +'}';}
}

Controller方法:

@RestController
public class RequestController {//实体参数:简单实体对象@RequestMapping("/simplePojo")public String simplePojo(User user) {System.out.println(user);return "OK";}
}

Postman测试参数名和实体类属性名一致时正常输出,参数名和实体类属性名不一致时,不一致的参数名输出的是null。



复杂实体对象

复杂实体对象指的是,在实体类中有一个或多个属性,也是实体对象类型的。如下:
User类中有一个Address类型的属性(Address是一个实体类)

复杂实体对象的封装,需要遵守如下规则:
请求参数名与形参对象属性名相同,按照对象层次结构关系即可接收嵌套实体类属性参数。

定义POJO实体类,Address实体类:

public class Address {private String province;private String city;public String getProvince() {return province;}public void setProvince(String province) {this.province = province;}public String getCity() {return city;}public void setCity(String city) {this.city = city;}@Overridepublic String toString() {return "Address{" +"province='" + province + '\'' +", city='" + city + '\'' +'}';}
}

User实体类

public class User {private String name;private Integer age;private Address address; //地址对象public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}public Address getAddress() {return address;}public void setAddress(Address address) {this.address = address;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +", address=" + address +'}';}
}

Controller方法:

@RestController
public class RequestController {//实体参数:复杂实体对象@RequestMapping("/complexPojo")public String complexPojo(User user){System.out.println(user);return "OK";}
}

Postman测试:




数组集合参数

数组集合参数的使用场景:在HTML的表单中,有一个表单项是支持多选的(复选框),可以提交选择的多个值。

实多个值也是一个一个的提交:

后端程序接收上述多个值的方式有两种:

  1. 数组
  2. 集合

数组参数

数组参数:请求参数名与形参数组名称相同且请求参数为多个,定义数组类型形参即可接收参数

Controller方法:

@RestController
public class RequestController {//数组集合参数@RequestMapping("/arrayParam")public String arrayParam(String[] hobby){System.out.println(Arrays.toString(hobby));return "OK";}
}

Postman测试:
在前端请求时,有两种传递形式:
方式一: xxxxxxxxxx?hobby=game&hobby=java

方式二:xxxxxxxxxxxxx?hobby=game,java



集合参数

集合参数:请求参数名与形参集合对象名相同且请求参数为多个,需要使用@RequestParam 绑定参数关系

默认情况下,请求中参数名相同的多个值,是封装到数组。如果要封装到集合,要使用@RequestParam绑定参数关系

Controller方法:

@RestController
public class RequestController {//数组集合参数@RequestMapping("/listParam")public String listParam(@RequestParam List<String> hobby){System.out.println(hobby);return "OK";}
}

Postman测试:
方式一: xxxxxxxxxx?hobby=game&hobby=java

方式二:xxxxxxxxxxxxx?hobby=game,java




日期参数

在一些特殊的需求中,可能会涉及到日期类型数据的封装。比如,如下需求:

因为日期的格式多种多样(如:2022-12-12 10:05:45 、2022/12/12 10:05:45),那么对于日期类型的参数在进行封装的时候,需要通过@DateTimeFormat注解,以及其pattern属性来设置日期的格式:

@DateTimeFormat注解的pattern属性中指定了哪种日期格式,前端的日期参数就必须按照指定的格式传递。
后端controller方法中,需要使用Date类型或LocalDateTime类型,来封装传递的参数。

Controller方法:

@RestController
public class RequestController {//数组集合参数@RequestMapping("/listParam")public String listParam(@RequestParam List<String> hobby) {System.out.println(hobby);return "OK";}//日期时间参数@RequestMapping("/dateParam")public String dateParam(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime updateTime) {System.out.println(updateTime);return "OK";}
}

Postman测试:




JSON参数

在前后端进行交互时,如果是比较复杂的参数,前后端通过使用JSON格式的数据进行传输。 (JSON是开发中最常用的前后端数据交互方式)

Postman如何发送JSON格式数据:

服务端Controller方法如何接收JSON格式数据:

1.传递json格式的参数,在Controller中会使用实体类进行封装。
2.封装规则:JSON数据键名与形参对象属性名相同,定义POJO类型形参即可接收参数。需要使用@RequestBody标识。

@RequestBody注解:将JSON数据映射到形参的实体类对象中(JSON中的key和实体类中的属性名保持一致)

上图实体类省略了GET , SET 方法。Controller方法:

@RestController
public class RequestController {//JSON参数@RequestMapping("/jsonParam")public String jsonParam(@RequestBody User user){System.out.println(user);return "OK";}
}

Postman测试:

经测试GET和POST命令都可以,但是Postman上的数据类型必须从text改成json:




路径参数

传统的开发中请求参数是放在请求体(POST请求)传递或跟在URL后面通过?key=value的形式传递(GET请求)。

在现在的开发中,经常还会直接在请求的URL中传递参数。例如:

http://localhost:8080/user/1
http://localhost:880/user/1/0

上述的这种传递请求参数的形式称之为:路径参数。

前端:通过请求URL直接传递参数
后端:使用{…}来标识该路径参数,需要使@PathVariable获取路径参数

Controller方法:

@RestController
public class RequestController {//路径参数@RequestMapping("/path/{id}")public String pathParam(@PathVariable Integer id){System.out.println(id);return "OK";}
}

Postman测试:




传递多个路径参数:
Controller方法:

@RestController
public class RequestController {//路径参数@RequestMapping("/path/{id}/{name}")public String pathParam2(@PathVariable Integer id, @PathVariableString name){System.out.println(id+ " : " +name);return "OK";}
}

Postman:




响应

Controller程序除了接收请求外,还可以进行响应。

@ResponseBody

在我们前面所编写的controller方法中,都已经设置了响应数据。

controller方法中的return的结果,怎么就可以响应给浏览器呢?因为我们使用了@ResponseBody注解。

但是在我们所书写的Controller中,只在类上添加了@RestController注解、方法添加了@RequestMapping注解,并没有使用@ResponseBody注解,怎么给浏览器响应呢?

我们前面说过@RestController代表该类是请求处理类,其是一个组合注解:

@RestController = @Controller + @ResponseBody

可以看一下@RestController的源码:

@Target({ElementType.TYPE})  //元注解(修饰注解的注解)
@Retention(RetentionPolicy.RUNTIME)  //元注解
@Documented  //元注解
@Controller
@ResponseBody
public @interface RestController {@AliasFor(annotation = Controller.class)String value() default "";
}

@ResponseBody注解:

类型:方法注解、类注解
位置:书写在Controller方法上或类上
作用:将方法返回值直接响应给浏览器
如果返回值类型是实体对象/集合,将会转换为JSON格式后在响应给浏览器


下面我们来测试下响应数据:

@RestController
public class ResponseController {//响应字符串@RequestMapping("/hello")public String hello(){System.out.println("Hello World ~");return "Hello World ~";}//响应实体对象@RequestMapping("/getAddr")public Address getAddr(){Address addr = new Address();//创建实体类对象addr.setProvince("广东");addr.setCity("深圳");return addr;}//响应集合数据@RequestMapping("/listAddr")public List<Address> listAddr(){List<Address> list = new ArrayList<>();//集合对象Address addr = new Address();addr.setProvince("广东");addr.setCity("深圳");Address addr2 = new Address();addr2.setProvince("陕西");addr2.setCity("西安");list.add(addr);list.add(addr2);return list;}
}

在服务端响应了一个对象或者集合,那私前端获取到的数据是什么样子的呢?我们使用postman发送请求来测试下。测试效果如下,可以看到返回的是JSON:




统一响应结果

前面所编写的这些Controller方法中,返回值各种各样,没有任何的规范。

在真实的项目开发中,无论是哪种方法,我们都会定义一个统一的返回结果。前端:只需要按照统一格式的返回结果进行解析(仅一种解析方案),就可以拿到数据。方案如下:

定义在一个实体类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);}
}

改造Controller:

@RestController
public class ResponseController {//响应统一格式的结果@RequestMapping("/hello")public Result hello(){System.out.println("Hello World ~");
//return new Result(1,"success","Hello World ~");return Result.success("Hello World ~");}//响应统一格式的结果@RequestMapping("/getAddr")public Result getAddr(){Address addr = new Address();addr.setProvince("广东");addr.setCity("深圳");return Result.success(addr);}//响应统一格式的结果@RequestMapping("/listAddr")public Result listAddr(){List<Address> list = new ArrayList<>();Address addr = new Address();addr.setProvince("广东");addr.setCity("深圳");Address addr2 = new Address();addr2.setProvince("陕西");addr2.setCity("西安");list.add(addr);list.add(addr2);return Result.success(list);}
}

Postman测试:




请求响应案例

通过一个案例,来加强对请求响应的学习

案例需求与准备工作

需求:加载并解析xml文件中的数据,完成数据处理,并在页面展示

案例准备:

  1. XML文件
    已经准备好(emp.xml),直接导入进来,放在 src/main/resources目录下
  2. 工具类
    已经准备好解析XML文件的工具类,无需自己实现
    直接在创建一个包 com.itheima.utils ,然后将工具类拷贝进来
  3. 前端页面资源
    已经准备好,直接拷贝进来,放在src/main/resources下的static目录下

Springboot项目的静态资源(html,css,js等前端资源)默认存放目录为:classpath:/static、 classpath:/public、 classpath:/resources

在SpringBoot项目中,静态资源默认可以存放的目录:
classpath:/static/
classpath:/public/
classpath:/resources/
classpath:/META-INF/resources/classpath:
代表的是类路径,在maven的项目中,其实指的就是 src/main/resources 或者
src/main/java,但是java目录是存放java代码的,所以相关的配置文件及静态资源文
档,就放在 src/main/resources下。

案例实现

  1. 在pom.xml文件中引入dom4j的依赖,用于解析XML文件
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.3</version>
</dependency>

2.引入资料中提供的:解析XML的工具类XMLParserUtils、实体类Emp、XML文件emp.xml

3.引入资料中提供的静态页面文件,放在resources下的static目录下

  1. 创建EmpController类,编写Controller程序,处理请求,响应数据

Contriller代码:

@RestController
public class EmpController {@RequestMapping("/listEmp")public Result list() {//1. 加载并解析emp.xmlString file =this.getClass().getClassLoader().getResource("emp.xml").getFile();//System.out.println(file);List<Emp> empList = XmlParserUtils.parse(file, Emp.class);//2. 对数据进行转换处理 - gender, jobempList.stream().forEach(emp -> {//处理 gender 1: 男, 2: 女String gender = emp.getGender();if ("1".equals(gender)) {emp.setGender("男");} else if ("2".equals(gender)) {emp.setGender("女");}//处理job - 1: 讲师, 2: 班主任 , 3: 就业指导String job = emp.getJob();if ("1".equals(job)) {emp.setJob("讲师");} else if ("2".equals(job)) {emp.setJob("班主任");} else if ("3".equals(job)) {emp.setJob("就业指导");}});//3. 响应数据return Result.success(empList);}
}

ps:

String file = this.getClass().getClassLoader().getResource("emp.xml").getFile();
这个语句用到了类加载器(getClassLoader())来获取emp.xml,什么原理呢?在Java的resources文件夹src/main/resources文件夹下的内容会被打包到生成的JAR(或者WAR)文件中,因此这些资源文件会被ClassLoader加载。在我们的代码中,this.getClass().getClassLoader().getResource("emp.xml").getFile() 这句代码的作用是通过类加载器获取emp.xml文件的路径。
这里的getClassLoader()方法是java.lang.Class类的一个方法,用于获取类的ClassLoader。
getResource("emp.xml")方法会在类路径下查找名为emp.xml的文件,并返回一个表示该资源的URL对象。
最后,getFile()方法获取该URL的文件表示形式,即文件的路径。由于emp.xml位于resources文件夹下,它会被编译到类路径中。因此,通过类加载器就可以获取到这个文件的路径。
这种方式通常用于获取类路径下的配置文件、模板文件等资源。
当应用程序打包成可执行的JAR文件时,这种方式仍然适用,因为resources文件夹中的内容会被包含在JAR文件中,ClassLoader可以正确地加载这些资源。

测试与问题

代码编写完毕,运行引导类,启动服务进行测试了。Postman测试:

打开浏览器,在浏览器地址栏输入:localhost:8080/emp.html

上述案例的功能,虽然已经实现,但是解析XML数据,获取数据的代码,处理数据的逻辑的代码,给页面响应的代码全部都堆积在一起了,全部都写在controller方法中了。

当前程序的这个业务逻辑还是比较简单的,如果业务逻辑再稍微复杂一点,我们会看到Controller方法的代码量就很大了。

当我们要修改操作数据部分的代码,需要改动Controller
当我们要完善逻辑处理部分的代码,需要改动Controller
当我们需要修改数据响应的代码,还是需要改动Controller

这样呢,就会造成我们整个工程代码的复用性比较差,而且代码难以维护。
其实在现在的开发中,有非常成熟的解决思路,那就是分层开发。




更多推荐

SpringBoot Web请求响应

本文发布于:2023-12-03 06:50:59,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1652341.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:SpringBoot   Web

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!