SpringBoot创建及Web请求响应

245 阅读13分钟

SpringBoot快速入门

1.开发步骤

  1. 创建SpringBoot项目,准备基本环境:修改pom.xml,添加依赖坐标

    • 锁定SpringBoot的版本号
     <!-- 锁定SpringBoot依赖的版本号-->
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.7.3</version>
        </parent>
    
    • 添加SpringBoot开发的依赖坐标
     <!--添加web起步依赖,这个依赖里提供了web服务端开发必须的jar包-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    
  2. 创建引导类:

    @SpringBootApplication
    public class 引导类名 {
        public static void main(String[] args) {
            SpringApplication.run(引导类名.class,args);
        }
    }
    
  3. 编写一个Controller类,用于处理客户端的请求,给客户端返回响应

    @RestController
    public class XxxController {
        //方法返回值:是返回给客户端的结果数据。 /hello是请求的路径
        @GetMapping("/hello")
        public String hello(){
            System.out.println("hello---");
            return "hello ,world";
        }
    }
    
  4. 启动服务:运行引导类的main方法就启动了

    浏览器访问:http://ip:端口/请求路径 例如 http:localhost:8080/hello

2.入门程序解析

2.1 SpringBoot起步依赖

所谓起步依赖,其实是一批依赖的集合。导入一个起步依赖,实际上会通过依赖传递导入一大批相关依赖。开发中需要什么操作,就找相应的起步依赖导入进来。

例如:spring-boot-starter-web就是web开发相关的依赖集合体。

image.png SpringBoot项目中大量使用起步依赖,用于减少pom.xml代码,减少依赖冲突的机率。

  • SpringBoot官方提供的起步依赖,名称规律:spring-boot-starter-xxx

    官方地址:docs.spring.io/spring-boot…

  • 非SpringBoot官方的起步依赖,通常是:xxx-starter(并非强制标准)

image.png

3.2 SpringBoot父工程

在我们之前开发的SpringBoot入门案例中,我们通过maven引入的依赖,是没有指定具体的依赖版本号的。

为什么没有指定版本号,可以正常使用呢? image.png

  • 因为每一个SpringBoot工程,都有一个父工程。依赖的版本号,在父工程中统一管理。

image.png

3.3 SpringBoot内嵌Tomcat

我们之前书写的SpringBoot入门程序中,并没有把程序部署到Tomcat的webapps目录下,为什么也可以运行呢?

因为spring-boot-starter-web起步依赖内置了Tomcat服务器。

image.png

当我们运行SpringBoot的引导类时(运行main方法),就会看到命令行输出的日志,其中占用8080端口的就是Tomcat。

image.png

SpringBootWeb请求响应

1.接收请求数据

接收HTTP请求的数据,主要是接收客户端提交过来的各种参数。而分析HTTP请求的格式,需要接收的数据主要有:
  • 表单参数。客户端在请求行或者请求体里提交的表单参数,格式:参数名=值&参数名=值&...

    如果是GET或DELETE请求,表单参数在请求行里提交过来

    如果是POST或PUT请求,表单参数在请求体里提交过来

  • JSON参数。客户端使用axios的post和put方法可以提交json参数,格式:{"key1":value1, "key2":value2}

  • 路径参数。直接在请求路径里携带参数,例如 GET http://locahost:8080/emp/1,其中的1就是路径参数

    表示要查询id为1的员工信息

2. 接收表单参数

2.1 简单参数

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

image.png

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

  1. 原始方式
  2. SpringBoot方式

2.1.1 原始方式【了解】

说明

在原始的Web程序当中,需要通过Servlet中提供的API:HttpServletRequest请求对象 来 获取请求信息。它提供了获取表单参数的API:

  • String value = HttpServletRequest对象.getParameter("参数名")
用法
  • 在Controller的方法中,直接增加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=Tom   参数名:name,参数值:Tom
        // 第2个请求参数: age=10     参数名:age , 参数值:10
​
        String name = request.getParameter("name");//name就是请求参数名
        String ageStr = request.getParameter("age");//age就是请求参数名
​
        int age = Integer.parseInt(ageStr);//需要手动进行类型转换
        System.out.println(name+"  :  "+age);
        return "OK";
    }
}

2.1.2 SpringBoot方式

说明

在Springboot的环境中,对原始的API进行了封装,接收参数的形式更加简单。

用法
  • 给Controller里的方法添加形参,一个形参用于接收一个表单参数
  • 要求:方法的形参名 和 表单参数名 相同
示例
@RestController
public class RequestController {
    // http://localhost:8080/simpleParam?name=Tom&age=10
    // 第1个请求参数: name=Tom   参数名:name,参数值:Tom
    // 第2个请求参数: age=10     参数名:age , 参数值:10
    
    //springboot方式
    @RequestMapping("/simpleParam")
    public String simpleParam(String name , Integer age ){//形参名和请求参数名保持一致
        System.out.println(name+"  :  "+age);
        return "OK";
    }
}

postman测试( GET 请求): image.png

postman测试( POST请求 ): image.png

2.1.3 参数名不一致@RequestParam

说明

如果方法形参名称与请求参数名称不一致,controller就接收不到参数值了。但是实际开发中,不可避免的会遇到这种情况,怎么办?可以使用Spring提供的@RequestParam注解完成映射

用法
  • 在方法形参前面加上 @RequestParam("表单参数名")

  • 注解的属性说明:

    • value:用于配置表单参数名。表示要接收哪个表单参数
    • required:设置为true的话,表示要求客户端必须传这个参数,否则会报错
示例
@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),代表该请求参数必须传递,如果不传递将报错

image.png

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

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

2.2 实体参数

2.2.1 简单实体对象

说明

刚刚我们已经知道怎么接收参数了,但还有问题:前端传递了多少个参数,controller方法中就要写几个形参。如果请求参数比较多,就需要写大量的方法形参,非常繁琐。

SpringBoot允许我们将多个请求参数封装到一个实体类对象中。

用法
  • 给Controller里方法上添加JavaBean形参
  • 要求:请求参数名与实体类的属性名相同 image.png
示例

定义User类:

public class User {
    private String name;
    private Integer age;
​
    //getset方法 略
    //toString方法 略
}
​

Controller方法:

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

Postman测试:

  • 参数名和实体类属性名一致时 image.png
参数名和实体类属性名不一致时

image.png

2.2.2 复杂实体对象

说明

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

用法
  • 在Controller里方法中直接添加实体类形参
  • 要求:请求参数名与形参对象属性名相同,按照对象层次结构关系即可接收嵌套实体类属性参数。
示例
  • Address实体类
public class Address {
    private String province;
    private String city;
​
    //get和set方法 略
    //toString方法 略
}
  • User实体类
public class User {
    private String name;
    private Integer age;
    private Address address; //地址对象
​
    //get和set方法 略
    //toString方法 略
}

Controller方法:

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

Postman测试:

image.png

2.3 数组集合参数

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

image.png

多个值是怎么提交的呢?其实多个值也是一个一个的提交。

image.png

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

  1. 数组
  2. 集合

2.3.1 接收成数组

用法
  • 在Controller的方法上直接添加数组形参
  • 要求表单参数名与形参名称相同

image.png

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 image.png 方式二:xxxxxxxxxxxxx?hobby=game,java image.png

2.3.2 接收成集合

用法
  • 在Controller里方法上直接添加集合形参
  • 要求表单参数名与形参名相同
  • 给形参添加@RequestParam注解

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

image.png

示例

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

image.png

方式二:xxxxxxxxxxxxx?hobby=game,java image.png

2.4 日期参数

说明

上述演示的都是一些普通的参数,在一些特殊的需求中,可能会涉及到日期类型数据的封装。比如,如下需求:

image.png

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

用法

  • 在日期类型(Date,LocalDate,LocalDateTime)的参数/属性上,添加注解@DateTimeFormat(pattern="日期格式")
  • 客户端提交的日期格式, 和注解里指定的日期格式 必须是匹配的

image.png

示例

Controller方法:

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

Postman测试: image.png

3. JSON参数

客户端在提交参数时,也会经常使用JSON格式。例如:

  • axios.post("url地址", {"name":"tom", "age":20})
  • axios.put("url地址", {"name":"tom", "age":20})

我们需要学习的内容有:

  1. Postman在发送请求时,如何传递json格式的请求参数
  2. 服务端的controller方法中,如何接收json格式的请求参数

3.1 Postman发送JSON数据

image.png

3.2 服务端接收JSON数据

做法

  • Controller的方法中,要求使用实体类封装参数,所以要添加一个实体类形参
  • 实体类要求:JSON的键名与实体类属性名相同
  • 在方法的形参实体类上加@RequestBody注解

image.png

示例

实体类:Address

public class Address {
    private String province;
    private String city;
    
    //get和set方法 略
    //toString方法 略
}

实体类:User

public class User {
    private String name;
    private Integer age;
    private Address address;
    
    //getset方法 略
    //toString方法 略
}    

Controller方法:

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

Postman测试: image.png

4. 路径参数

说明

传统的开发中,请求参数是放在请求体中,或追加在URL后面通过?key=value&key=value&...的形式传递。而现在的开发中,经常还会直接在请求的URL中传递参数,这样的参数称为“路径参数”或“路径变量”。例如:

做法

  • 前端:通过请求URL直接传递参数

  • 后端:

    • 在Controller的方法上标识请求路径时,使用{…}占位符来匹配路径参数;有几个参数,就写几个占位符
    • 有几个路径参数要接收,就在Controller的方法上添加几个形参,每个形参加@PathVariable("占位符名")获取路径参数

单参数示例

image.png

Controller方法:

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

Postman测试:

image.png

多参数示例

Postman:

image.png

Controller方法:

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

1、返回响应结果

1.1 介绍

如果想要给客户端返回响应结果,只需要:

  1. Controller里的方法直接return结果
  2. 方法上添加@ResponseBody注解

@ResponseBody注解用于加在Controller类上,也可以加在方法上。它的作用是:

  • 将方法返回值直接响应给浏览器
  • 如果返回值类型是实体对象/集合,将会转换为JSON格式,然后再响应给浏览器

1.2 用法

在前后端分离开发中,服务端Controller里每个方法都要返回结果,但并不需要在每个方法上都加此注解。

SpringBootWeb提供了一个 @RestController,加在Controller类上。这一个注解相当于@Controller + @ResponseBody

  • @Controller:加在Controller类上,SpringBoot才会扫描、识别这个类,才能被客户端访问到
  • @ResponseBody:加在Controller类上,相当于类里所有方法都加了@ResponseBody

所以,只要Controller类上加了@RestController,我们就不用关心响应结果的注解了

@RestController源码:

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

1.3 示例

@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发送请求来测试下。测试效果如下:

image.png

image.png

2. 统一响应结果

2.1 响应结果不规范的问题

在我们前面所编写的这些Controller方法中,返回值各种各样,没有任何的规范。如果我们开发一个大型项目,项目中controller方法将成千上万,使用这种方式将造成整个项目难以维护。

image.png

2.2 如何统一返回结果

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

方案如下:

image.png

统一的返回结果使用类来描述,在这个结果中包含:

  • 响应状态码:当前请求是成功,还是失败
  • 状态码信息:给页面的提示信息
  • 返回的数据:给前端响应的数据(字符串、对象、集合)

2.3 使用示例

定义在一个实体类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;
    }
​
    //get和set方法 略
​
    //增删改 成功响应(不需要给前端返回数据)
    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测试:

image.png

image.png image.png