【JavaWeb】SpringBootWeb请求响应

【JavaWeb】SpringBootWeb请求响应

🔥 本文由 程序喵正在路上 原创,CSDN首发!
💖 系列专栏:javaWeb开发
🌠 首发时间:2024年2月6日
🦋 欢迎关注🖱点赞👍收藏🌟留言🐾

概述

请求响应:

  • 请求(HttpServletRequest):获取请求数据
  • 响应(HttpServletResponse):设置响应数据
  • BS架构:Browser/Server,浏览器/服务器架构模式。客户端只需要浏览器,应用程序的逻辑和数据都存储在服务端
  • CS架构:Client/Server,客户端/服务器架构模式

请求

Postman

后端分离开发

前面,我们介绍了最为主流的开发模式——前后端分离。在这种开发模式中,前端程序和后端程序是分开开发。前面我们测试后端程序的时候,是通过浏览器地址栏输入地址进行测试,但是这种发出的都是 GET 请求,无法测试 POST 请求。

postman

  • postman是一款功能强大的网页调试与发送网页HTTP请求的Chrome插件
  • 作用:常用于进行接口测试

下载安装postman

  • 点击连接 https://pan.baidu.***/s/1R7aK65AOaehS1OtafsVrHA?pwd=y0wc 进入下载

  • 双击 .exe 文件进行安装即可,不用其他操作

  • 安装完成

  • 注册登录

如何创建工作空间

  • 点击 Workspaces 新建 workspace:

  • 设置一下信息:

  • 创建完自动进入工作空间:

如何添加请求

  • 点击这个加号即可添加请求

  • 添加请求,点击 Send 发起请求:

  • 添加请求界面说明:

  • 保存请求

  • 给请求命名和创建一个文件夹来存放请求

简单参数

原始方式

  • 在原始的web程序中,获取请求参数,需要通过HttpServletRequest 对象手动获取

步骤

  1. 创建一个新的 springboot 工程

  2. 创建类,写入原始方式获取参数的代码

    import jakarta.servlet.http.HttpServletRequest;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class RequestController {
        //原始方式
        @RequestMapping("/simpleParam")
        public String simpleParam(HttpServletRequest request) {
            String name = request.getParameter("name");
            String ageStr = request.getParameter("age");
            int age = Integer.parseInt(ageStr);
            System.out.println(name + ": " + age);
            return "Ok";
        }
    }
    
  3. 启动 springboot 工程,打开 postman,新建文件夹,新建请求进行测试

原始方式有点繁琐,并且需要我们手动进行类型转换

SpringBoot方式

  • 简单参数:参数名与形参变量名相同,定义形参即可接收参数

SpringBoot 方式的代码简洁了很多:

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class RequestController {
    //springboot方式
    @RequestMapping("/simpleParam")
    public String simpleParam(String name, Integer age) {
        System.out.println(name + ": " + age);
        return "Ok";
    }
}

再进行测试:

POST 请求

如果是 POST 请求,那么我们就要到 Body 里面去设置参数:

@RequestParam注解

  • 简单参数:如果方法形参名称与请求参数名称不匹配,可以使用 @RequestParam 完成映射
  • 注意: @RequestParam中有一个 required 属性,其默认值为 true,代表该请求参数必须传递,如果不传递可能会报错。 如果该参数是可选的,可以将 required 属性设置为false

比如,我们将形参 name 改为 username(修改了代码后记得重启工程),会发现获取不到参数:

我们加上 @RequestParam 注解,默认该参数必须传递:

实体参数

前面我们只传递了两个参数,如果现在有 20 个参数,我们一个个写就会很繁琐,这种情况,我们可以定义一个实体对象来存储所有的参数。

简单实体对象

  • 简单实体对象:请求参数名与形参对象属性名相同,定义POJO接收即可

这里我们准备了一个测试案例,要求在代码中用实体对象来接收参数:

步骤

  1. 创建包 pojo,在 pojo 下创建实体类 User

    快捷键 Alt + Insert 快速生成 Get、Set 和 toString 方法:

  2. RequestController.java 中添加 simplePojo 方法

  3. 启动工程,到 postman 中点击测试

如果我们将请求中的 name 改一下,就获取不到参数了:

如果现在 User 类增加了一个属性 Address,并且这个属性也是一个类,那么实体对象应该怎么写呢?

复杂实体对象

  • 复杂实体对象:请求参数名与形参对象属性名相同,按照对象层次结构关系即可接收嵌套POJO属性参数

这里,我们准备了一个比较复杂的测试案例:

步骤:

  1. 添加 Address 类

  2. 修改一下 User 类,别忘了重新生成 toString() 方法

    package ***.xixi.pojo;
    
    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;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", address=" + address +
                    '}';
        }
    }
    
  3. RequestController.java 中添加 ***plexPojo 方法

    //复杂实体参数
    @RequestMapping("/***plexPojo")
    public String ***plexPojo(User user) {
        System.out.println(user);
        return "Ok";
    }
    
  4. 重启工程,进行测试

数组集合参数

前面我们学习 Form 表单的时候,用户填的信息有时候是多选的,每个选项在提交的时候都要提交给服务端,那么服务端应该如何接收呢?

答案是用数组或者集合。

数组参数

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

这里,我们准备了一个测试案例:

步骤:

  1. RequestController.java 中添加 arrayParam 方法

    //数组参数
    @RequestMapping("/arrayParam")
    public String arrayParam(String[] hobby) {
        System.out.println(Arrays.toString(hobby));
        return "Ok";
    }
    
  2. 启动服务,测试

集合参数

  • 集合参数:请求参数名与形参集合名称相同且请求参数为多个,@RequestParam 绑定参数关系

准备一个测试案例,将数组参数的案例改一下路径即可:

步骤:

  1. RequestController.java 中添加 listParam 方法

    //集合参数
    @RequestMapping("/listParam")
    public String listParam(@RequestParam List<String> hobby) {
        System.out.println(hobby);
        return "Ok";
    }
    
  2. 重启服务,进行测试

日期参数

日期参数:使用 @DateTimeFormat 注解完成日期参数格式转换

首先,我们准备一个测试案例:

步骤:

  1. RequestController.java 中添加 dateParam 方法

    //日期参数
    @RequestMapping("/dateParam")
    public String dateParam(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime updateTime) {
        System.out.println(updateTime);
        return "Ok";
    }
    
  2. 重启服务,进行测试

Json参数

JSON参数:JSON数据键名与形参对象属性名相同,定义POJO类型形参即可接收参数,需要使用 @RequestBody 标识

首先,我们准备一个测试案例:

步骤:

  1. 创建实体类 User 和 Address,用来接收 Body 中的内容,前面我们已经创建了,所以这一步可以省略

  2. RequestController.java 中添加 jsonParam 方法

    //json参数
    @RequestMapping("/jsonParam")
    public String jsonParam(@RequestBody User user) {
        System.out.println(user);
        return "OK";
    }
    
  3. 重启服务,进行测试

路径参数

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

首先,我们准备一个测试案例:

步骤:

  1. RequestController.java 中添加 pathParam 方法

    //路径参数
    @RequestMapping("/path/{id}")
    public String pathParam(@PathVariable Integer id) {
        System.out.println(id);
        return "Ok";
    }
    
  2. 重启服务,进行测试

如果有多个路径参数,应该怎么写呢?

比如,现在我们的请求是这样的:

在这种情况下,我们的 pathParam 方法就要写成下面这样:

//多个路径参数
@RequestMapping("/path/{id}/{name}")
public String pathParam2(@PathVariable Integer id, @PathVariable String name) {
    System.out.println(id + " : " + name);
    return "Ok";
}

测试:

响应

前面,我们在方法中直接返回一个字符串,就能响应客户端,这都是注解 @ResponseBody 的功劳

@ResponseBody

  • 类型:方法注解、类注解
  • 位置:Controller方法上/类上
  • 作用:将方法返回值直接响应,如果返回值类型是 实体对象/集合 ,将会转换为JSON格式响应
  • 说明:@RestController = @Controller + @ResponseBody,所以我们添加了 @RestController 注解,就不用添加 @ResponseBody

接下来,我们就来演示一下 @ResponseBody 这个注解的作用。

首先,我们准备一个名为 ResponseController.java 的程序,将其放在 Controller 包下:

package ***.xixi.controller;

import ***.xixi.pojo.Address;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;

/**
 * 测试响应数据
 */
@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格式响应

ResponseController.java 中每个方法都称为一个功能接口:

统一的响应结果

在前面的测试中,三个功能接口响应的形式都不一样。在大型项目中,我们会有成千上万个功能接口。如果没有一套统一的开发规范,前端人员发来请求后,会收到各式各样的响应,那么前端人员就需要对每个响应进行单独的解析,因为它们的形式都不相同,这个工程量是非常大的,成本也会直线上升,而且不便于管理和难以维护。

所以我们需要一个统一的响应结果,这个响应结果要具备通用性,需要满足所有的业务场景。这个统一的响应结果我们可以考虑使用一个实体对象 Result 来接收。

有了 Result 类后,我们功能接口的返回结果都要封装为一个 Result 类返回。

下面,我会给出 Result 类的代码,请将其添加到 pojo 目录下

package ***.xixi.pojo;

/**
 * 统一响应结果封装类
 */
public class Result {
    private Integer code;//1 成功 , 0 失败
    private String msg; //提示信息
    private Object data; //数据 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 su***ess(Object data) {
        return new Result(1, "su***ess", data);
    }

    public static Result su***ess() {
        return new Result(1, "su***ess", null);
    }

    public static Result error(String msg) {
        return new Result(0, msg, null);
    }

    @Override
    public String toString() {
        return "Result{" +
                "code=" + code +
                ", msg='" + msg + '\'' +
                ", data=" + data +
                '}';
    }
}

接下来,我们要对 ResponseController.java 中的方法进行改造,将它们的返回结果用 Result 类封装起来:

再测试一下:

案例

需求:获取员工数据,返回统一响应结果,在页面渲染展示

加载并解析 emp.xml 文件中的数据,完成数据处理,并在页面展示。

具体步骤:

  • 在 pom.xml 文件中引入 dom4j 的依赖,用于解析 XML 文件
  • 引入资料中的解析 XML 的工具类 XMLParserUtils、对应的实体类 Emp、XML文件 emp.xml
  • 引入资料中的静态页面文件,放在 resources 下的 static 目录下
  • 编写 Controller 程序,处理请求,响应数据
  1. 点击此处下载资料

  2. 在 pom.xml 文件中引入 dom4j 的依赖,用于解析 XML 文件

    <!-- 解析XML -->
    <dependency>
        <groupId>org.dom4j</groupId>
        <artifactId>dom4j</artifactId>
        <version>2.1.3</version>
    </dependency>
    
  3. 引入解析 XML 的工具类 XMLParserUtils、对应的实体类 Emp、XML文件 emp.xml

    在 ***.xixi 下创建一个包 utils 专门用来放工具类:

    XMLParserUtils.java 工具类粘贴到 utils 包下;

    将员工类 Emp.java 粘贴到 pojo 下;

    将员工信息资源 emp.xml 粘贴到 resources 下;

  4. 引入准备好的静态页面文件,放在 resources 下的 static 目录下

  5. 编写 Controller 程序,处理请求,响应数据

    在 Controller 目录下创建 EmpController.java:

    import ***.xixi.pojo.Emp;
    import ***.xixi.pojo.Result;
    import ***.xixi.utils.XmlParserUtils;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.List;
    
    @RestController
    public class EmpController {
        @RequestMapping("/listEmp")
        public Result list() {
            //1. 加载并解析emp.xml
            String file = this.getClass().getClassLoader().getResource("emp.xml").getFile();
            System.out.println(file);
            List<Emp> empList = XmlParserUtils.parse(file, Emp.class);
    
            //2. 对数据进行转换处理
            empList.forEach(emp -> {
                //处理gender
                String gender = emp.getGender();
                if ("1".equals(gender)) {
                    emp.setGender("男");
                } else if ("2".equals(gender)) {
                    emp.setGender("女");
                }
    
                //处理job
                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.su***ess(empList);
        }
    }
    

    需要说明一下,list() 方法响应的请求路径需要与前端页面中 axios 异步请求的路径一致,同时因为 axios 异步请求没有添加参数,所以 list() 方法也不用添加参数

  6. 启动服务,进行测试

    来到浏览器,输入 localhost:8080/emp.html 访问我们的页面:

分层解耦

在前面写的 EmpController 中,我们将所有代码都写在一个方法中,包括数据访问、逻辑处理和接受请求、响应数据。但是在大型项目中,我们要求每个功能接口只拥有单一的功能,也就是每个接口只做一件事,这才符合单一职责原则。

三层架构

  • controller:控制层,接收前端发送的请求,对请求进行处理,并响应数据
  • service:业务逻辑层,处理具体的业务逻辑
  • dao:数据访问层(Data A***ess Object)(持久层),负责数据访问操作,包括数据的增、删、改、查

接下来,我们利用三层架构来对 EmpController 的代码进行优化。

步骤:

  1. 在 ***.xixi 包下创建包 dao 和包 service,用来存放数据访问和逻辑处理的代码

  2. 先写 dao 部分的代码,为了让代码更具备灵活性,我们写一个接口 EmpDao,然后在 dao 目录下创建一个包 impl 来存放接口的实现类,在 impl 下再添加一个实现类 EmpDaoA

    EmpDao.java

    import ***.xixi.pojo.Emp;
    
    import java.util.List;
    
    public interface EmpDao {
        //获取员工列表数据
        public List<Emp> listEmp();
    }
    

    EmpDaoA.java

    import ***.xixi.dao.EmpDao;
    import ***.xixi.pojo.Emp;
    import ***.xixi.utils.XmlParserUtils;
    
    import java.util.List;
    
    public class EmpDaoA implements EmpDao {
        @Override
        public List<Emp> listEmp() {
            //1. 加载并解析emp.xml
            String file = this.getClass().getClassLoader().getResource("emp.xml").getFile();
            System.out.println(file);
            List<Emp> empList = XmlParserUtils.parse(file, Emp.class);
            return empList;
        }
    }
    
  3. 接下来写 service 部分的代码,和 dao 操作差不多,也是要创建接口,然后再写实现类

    EmpService.java

    import ***.xixi.pojo.Emp;
    
    import java.util.List;
    
    public interface EmpService {
        //获取员工列表
        public List<Emp> listEmp();
    }
    

    EmpServiceA.java

    import ***.xixi.dao.EmpDao;
    import ***.xixi.dao.impl.EmpDaoA;
    import ***.xixi.pojo.Emp;
    import ***.xixi.service.EmpService;
    
    import java.util.List;
    
    public class EmpServiceA implements EmpService {
    
        private EmpDao empDao = new EmpDaoA();
    
        @Override
        public List<Emp> listEmp() {
            //1. 调用dao, 获取数据
            List<Emp> empList = empDao.listEmp();
    
            //2. 对数据进行转换处理 - gender, job
            empList.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("就业指导");
                }
            });
            return empList;
        }
    }
    
  4. 最后改造 Controller 层

    EmpController.java

    import ***.xixi.pojo.Emp;
    import ***.xixi.pojo.Result;
    import ***.xixi.service.EmpService;
    import ***.xixi.service.impl.EmpServiceA;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.List;
    
    @RestController
    public class EmpController {
    
        private EmpService empService = new EmpServiceA();
    
        @RequestMapping("/listEmp")
        public Result list() {
            //1. 调用service, 获取数据
            List<Emp> empList = empService.listEmp();
    
            //2. 响应数据
            return Result.su***ess(empList);
        }
    }
    
  5. 重启服务,进行测试

    还是可以成功获取到页面,说明优化成功

    拆分后的调用顺序:

    拆分前后对比:

分层解耦

  • 内聚:软件中各个功能模块内部的功能联系
  • 耦合:衡量软件中各个层/模块之间的依赖、关联的程度,最好的状态是解耦合,也就是各个层/模块之间没有依赖关系
  • 软件设计原则:高内聚低耦合

在前面我们利用三层架构改造的代码中,Controller 层中需要用到 EmpServiceA 的对象,当我们想将 EmpServiceA 换为 EmpServiceB 时,Controller 层的代码也要改动。这说明 Controller 层和 Service 层是耦合的。

那我们该如何解除它们之间的耦合呢?

我们想出了一种办法,就是增加一个容器,用来存放各种类的对象,Controller 层里面只定义对象而不实现对象,需要对象就到容器里找,而 Service 层的对象就可以存放在容器中。这样,当我们想把 EmpServiceA 换为 EmpServiceB 时,只需要将 EmpServiceB 的对象放到容器中,然后 Controller 层再去容器里找即可。

这两个过程分别称为控制反转和依赖注入

  • 控制反转: Inversion Of Control,简称IOC。对象的创建控制权由程序自身转移到外部(容器),这种思想称为控制反转。
  • 依赖注入: Dependency Injection,简称DI。容器为应用程序提供运行时,所依赖的资源,称之为依赖注入。
  • Bean对象:IOC容器中创建、管理的对象,称之为bean。

IOC & DI入门

①. Service 层及 Dao 层的实现类,交给 IOC 容器管理

在类前面加个 @***ponent 注解即可

②. 为 Controller 及 Service 注入运行时,依赖的对象

将 new 的部分删掉,在定义的对象前面加上 @Autowired 注解即可

③. 运行测试

界面加载成功,没问题

这样,当我们需要用哪个类的对象时,就在哪个类前面加上 @***ponent 注解即可,很方便,其他类也不用改动

IOC详解

Bean的声明

要把某个对象交给IOC容器管理,需要在对应的类上加上如下注解之一:

后面 3 个注解分别对应 Controller 层、Service 层和 Dao 层,在 web 开发中,推荐使用这 3 个注解。当遇到除 Controller 层、Service 层和 Dao 层外的类时,才使用 @***ponent 注解

注意事项

  • 声明 bean 的时候,可以通过 value 属性指定 bean 的名字,如果没有指定,默认为首字母小写的类名
  • 使用以上四个注解都可以声明 bean,但是在 springboot 集成 web 开发中,声明控制器 bean 只能用 @Controller

Bean组件扫描

  • 前面声明bean的四大注解,要想生效,还需要被组件扫描注解 @***ponentScan 扫描

  • @***ponentScan 注解虽然没有显式配置,但是实际上已经包含在了启动类声明注解 @SpringBootApplication 中,默认扫描的范围是启动类所在包及其子包

  • 所以,我们尽量将所有代码都写在启动类所在包及其子包,这样我们就不用再去显式配置 @***ponentScan 注解

DI详解

Bean注入

  • @Autowired 注解,默认是按照类型进行,如果存在多个相同类型的 bean,将会报出如下错误:

  • 通过以下几种方案来解决:

    • @Primary

      在要使用类前面再加上 @Primary 注解

    • @Qualifier

    • @Resource

      改用 @Resource

@Resource@Autowired 区别

  • @Autowired 是 spring 框架提供的注解,而 @Resource 是 JDK 提供的注解
  • @Autowired 默认是按照类型注入,而 @Resource 默认是按照名称注入
转载请说明出处内容投诉
CSS教程_站长资源网 » 【JavaWeb】SpringBootWeb请求响应

发表评论

欢迎 访客 发表评论

一个令你着迷的主题!

查看演示 官网购买