SprintBoot系列(六):参数校验机制

上一节讲到SpringBoot的异常处理机制,但异常处理忽略了参数校验。下面我们将开始讲解参数校验机制。

http://www.fcors.com/%e6%8a%80%e6%9c%af%e4%b8%8e%e6%a1%86%e6%9e%b6/sprintboot%e7%b3%bb%e5%88%97%ef%bc%88%e4%ba%94%ef%bc%89%ef%bc%9a%e5%bc%82%e5%b8%b8%e5%a4%84%e7%90%86%e6%9c%ba%e5%88%b6/

要实现参数校验机制,我们需要了解SpringBoot是如何接收参数;如何进行验证参数;如何把错误信息抛出。

参数的传输可分为:

1、Url参数传参,类似于GET。这种传参又存在两种方式

  • url传参,如:www.fcors.com/type/a
  • 查询传参,如:www.fcors.com/index.php?a=b

2、RequestBody请求,例如post请求

demo1:url传参的方式

传参url: http://192.168.8.10:8081/api/sample/v1/test/testnew/33/fcors

使用@PathVariable [类型] [变量]

package com.fcors.fcors.api.sample.v1;

import com.fcors.fcors.exception.ForbiddenException;
import com.fcors.fcors.exception.NotFoundException;
import com.fcors.fcors.sample.IConnect;
import com.fcors.fcors.sample.ISkill;
import com.fcors.fcors.sample.database.MySql;
import com.fcors.fcors.sample.hero.Diana;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Lazy;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/test")
public class TestController {
    @Autowired
    private ISkill iskill;

    @GetMapping("/testnew/{id}/{xtype}")
    public String test2(
            @PathVariable Integer id,@PathVariable String xtype
    ) throws RuntimeException {
        iskill.q();
        throw new ForbiddenException(10000);
//        return "Hello Fox is testing~";
    }

}
JAVA、基础技术、技术与框架SprintBoot系列(六):参数校验机制插图

如果url参数与接收的变量不一致的时候

使用@PathVariable(name=”xxx”) [类型] [变量]

package com.fcors.fcors.api.sample.v1;

import com.fcors.fcors.exception.ForbiddenException;
import com.fcors.fcors.exception.NotFoundException;
import com.fcors.fcors.sample.IConnect;
import com.fcors.fcors.sample.ISkill;
import com.fcors.fcors.sample.database.MySql;
import com.fcors.fcors.sample.hero.Diana;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Lazy;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.websocket.server.PathParam;

@RestController
@RequestMapping("/test")
public class TestController {
    @Autowired
    private ISkill iskill;

    @GetMapping("/testnew/id/{idx}/type/{xtypex}")
    public String test2(
            @PathVariable(name="idx") Integer id, @PathVariable(name="xtypex") String xtype
    ) throws RuntimeException {
        iskill.q();
        throw new ForbiddenException(10000);
//        return "Hello Fox is testing~";
    }
}
JAVA、基础技术、技术与框架SprintBoot系列(六):参数校验机制插图1

demo2: url查询参数

url: http://192.168.8.10:8081/api/sample/v1/test/testnew/id/2?xtype=1&xcate=2

@RequestParam(name=”XXXX”) [类型] [变量]

package com.fcors.fcors.api.sample.v1;

import com.fcors.fcors.exception.ForbiddenException;
import com.fcors.fcors.exception.NotFoundException;
import com.fcors.fcors.sample.IConnect;
import com.fcors.fcors.sample.ISkill;
import com.fcors.fcors.sample.database.MySql;
import com.fcors.fcors.sample.hero.Diana;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Lazy;
import org.springframework.web.bind.annotation.*;

import javax.websocket.server.PathParam;

@RestController
@RequestMapping("/test")
public class TestController {
    @Autowired
    private ISkill iskill;

    @GetMapping("/testnew/id/{idx}")
    public String test2(
            @PathVariable(name="idx") Integer id,
            @RequestParam(name="xtype") String type,
            @RequestParam(name="xcate") String cate
    ) throws RuntimeException {
        iskill.q();
        throw new ForbiddenException(10000);
//        return "Hello Fox is testing~";
    }
}
JAVA、基础技术、技术与框架SprintBoot系列(六):参数校验机制插图2

demo3: Requestbody请求

url: http://192.168.8.10:8081/api/sample/v1/test/testnew/id/2

databody
{
    "name":"fcros",
    "sex":"box",
    "age":18
}

使用@Requestbody

方法一:偷懒方法,定义一个Map接收

定义个Map接收参数,缺点:无法进行序列化

@Requestbody Map<String,Object> person

package com.fcors.fcors.api.sample.v1;

import com.fcors.fcors.exception.ForbiddenException;
import com.fcors.fcors.exception.NotFoundException;
import com.fcors.fcors.sample.IConnect;
import com.fcors.fcors.sample.ISkill;
import com.fcors.fcors.sample.database.MySql;
import com.fcors.fcors.sample.hero.Diana;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Lazy;
import org.springframework.web.bind.annotation.*;

import javax.websocket.server.PathParam;
import java.util.Map;

@RestController
@RequestMapping("/test")
public class TestController {
    @Autowired
    private ISkill iskill;

    @PostMapping("/testnew/id/{idx}")
    public String test2(
            @PathVariable(name="idx") Integer id,
            @RequestBody Map<String,Object> person
    ) throws RuntimeException {
        iskill.q();
        throw new ForbiddenException(10000);
//        return "Hello Fox is testing~";
    }
}
JAVA、基础技术、技术与框架SprintBoot系列(六):参数校验机制插图3

方法二:定义一个 PersonDTO类接收

在项目下创建一个package,命名为dto,并创建一个类PersonDTO

定义成员变量:name、sex、age,并genrate生成setter和getter方法。

package com.fcors.fcors.dto;

public class PersonDTO {
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    private String name;
    private String sex;
    private Integer age;
}

代码执行层,设置引用接收的这个类

package com.fcors.fcors.api.sample.v1;

import com.fcors.fcors.dto.PersonDTO;
import com.fcors.fcors.exception.ForbiddenException;
import com.fcors.fcors.exception.NotFoundException;
import com.fcors.fcors.sample.IConnect;
import com.fcors.fcors.sample.ISkill;
import com.fcors.fcors.sample.database.MySql;
import com.fcors.fcors.sample.hero.Diana;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Lazy;
import org.springframework.web.bind.annotation.*;

import javax.websocket.server.PathParam;
import java.util.Map;

@RestController
@RequestMapping("/test")
public class TestController {
    @Autowired
    private ISkill iskill;

    @PostMapping("/testnew/id/{idx}")
    public String test2(
            @PathVariable(name="idx") Integer id,
            @RequestBody PersonDTO person
    ) throws RuntimeException {
        iskill.q();
        throw new ForbiddenException(10000);
//        return "Hello Fox is testing~";
    }
}
JAVA、基础技术、技术与框架SprintBoot系列(六):参数校验机制插图4

上述的例子,如果我们接收的变量又多而且存在变动的情况,对代码的维护工作量也挺大的。下面引入一个工具: lombok。使用它,就可以解决此类问题

先来一个简单的demo,基于demo3的修改

demo4-1:lombok的使用

4.1.1、SpringBoot引入lombok

在pom.xml文件中,添加引用

<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
</dependency>

添加后,在项目处右键,Maven>>>Reload project

JAVA、基础技术、技术与框架SprintBoot系列(六):参数校验机制插图5

4.1.2、使用lombok的注解

@Getter 编译的时候会自动创建getter

@Setter 编译时会自动创建setter

@Data 编译时除了会创建Getter和Setter,还会创建equals、hashCode、toString

JAVA、基础技术、技术与框架SprintBoot系列(六):参数校验机制插图6
JAVA、基础技术、技术与框架SprintBoot系列(六):参数校验机制插图7
JAVA、基础技术、技术与框架SprintBoot系列(六):参数校验机制插图8

扩展学习:

如果成员变量是final private String at=””; 那么不会创建Setter,只会创建Getter

JAVA、基础技术、技术与框架SprintBoot系列(六):参数校验机制插图9

demo4-2: lombok的构造函数

4.2.1、不使用lombok的全参数构造函数

右键“generate”>>>”Constructor”

package com.fcors.fcors.dto;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class PersonDTO {

    private String name;
    private String sex;
    private Integer age;

    public PersonDTO(String name, String sex, Integer age) {
        this.name = name;
        this.sex = sex;
        this.age = age;
    }
    final private String at="";
}
PersonDTO personnew = new PersonDTO("fcors","boy",18);

上述代码通过Lombok生成全参的构造函数

添加@AllArgsConstructor

JAVA、基础技术、技术与框架SprintBoot系列(六):参数校验机制插图10

4.2.2、如何实现实例化时,不一定传参呢?

@AllArgsConstructor:所有参数的构造函数

@NoArgsConstructor: 无参构造函数

两个参数一同使用,实例化的时候就不一定传参

4.2.3、如何实现实例化时,某些参数必填呢?

添加注解:@RequiredArgsConstructor 在参数前添加 @NonNull

package com.fcors.fcors.dto;

import lombok.*;

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@RequiredArgsConstructor
public class PersonDTO {
    @NonNull
    private String name;
    @NonNull
    private String sex;
    private Integer age;
    final private String at="";
}
JAVA、基础技术、技术与框架SprintBoot系列(六):参数校验机制插图11

demo4-3: lombok的build方法

lombok提供一个build方法,用这个方法可以不用定义@setter。

实例化的时候可以传参

package com.fcors.fcors.dto;

import lombok.*;

@Getter
@Builder
public class PersonDTO {
//    @NonNull
    private String name;
//    @NonNull
    private String sex;
    private Integer age;


    final private String at="";
}
PersonDTO dotnew = PersonDTO.builder()
        .sex("boy")
        .name("fcors")
        .build();

demo4-4: lombok的build 和setter同时可用

加入注解:@NoArgsConstructor和@Setter

package com.fcors.fcors.dto;

import lombok.*;

@Getter
@Builder
@NoArgsConstructor
@Setter
public class PersonDTO {
//    @NonNull
    private String name;
//    @NonNull
    private String sex;
    private Integer age;
    final private String at="";
}

实例化类

PersonDTO dotnew = PersonDTO.builder()
                  .sex("boy")
                  .build();
dotnew.setName("fcros");

demo4-5: lombok的build 和 Getter 同时可用

@Builder 默认的情况是不能序列化返回到前端

如果我们把DTO的@Getter屏蔽后,返回是会报错。

JAVA、基础技术、技术与框架SprintBoot系列(六):参数校验机制插图12

解决办法:引入@Getter注解

demo5: 基础注解的参数校验

在类的前面添加@Validated;在参数处加入注解

package com.fcors.fcors.api.sample.v1;

import com.fcors.fcors.dto.PersonDTO;
import com.fcors.fcors.exception.ForbiddenException;
import com.fcors.fcors.exception.NotFoundException;
import com.fcors.fcors.sample.IConnect;
import com.fcors.fcors.sample.ISkill;
import com.fcors.fcors.sample.database.MySql;
import com.fcors.fcors.sample.hero.Diana;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Lazy;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.constraints.Max;
import javax.websocket.server.PathParam;
import java.util.Map;

@RestController
@RequestMapping("/test")
@Validated
public class TestController {
    @Autowired
    private ISkill xing;

    @PostMapping("/testnew/id/{idx}")
    public String test2(
            @PathVariable(name="idx") @Max(value=10,message="不能大于10") Integer id,
            @RequestBody PersonDTO person
    ) throws RuntimeException {
        throw new ForbiddenException(10000);
//        return "Hello Fox is testing~";
    }
}
JAVA、基础技术、技术与框架SprintBoot系列(六):参数校验机制插图13
JAVA、基础技术、技术与框架SprintBoot系列(六):参数校验机制插图14

下面提供一些基础的参数校验注解:

  • @Null 被注释的元素必须为 null
  • @NotNull 被注释的元素必须不为 null
  • @AssertTrue 被注释的元素必须为 true
  • @AssertFalse 被注释的元素必须为 false
  • @Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
  • @Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
  • @Range(min=,max=) 被注释必须是一个数字,且满足范围
  • @Length 被注释的长度必须满足范围
  • @DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
  • @DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
  • @Size(max=, min=) 被注释的元素的大小必须在指定的范围内
  • @Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
  • @Past 被注释的元素必须是一个过去的日期
  • @Future 被注释的元素必须是一个将来的日期
  • @Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达

泛式builder

-----
@Data
@Builder
public class ResponseResultVO<T> {
    private long code;
    private String msg;
    private T data;
}
------
ResponseResultVO<SysUsers> vo = ResponseResultVO.<SysUsers>builder()
                .code(1)
                .data(sysUsers)
                .build();
        SysUsers sysUsers1 = vo.getData();

demo6: 自定义的类如何实现基础注解

6.1、在类的成员变量前添加参数校验基础注解

package com.fcors.fcors.dto;

import lombok.*;
import org.hibernate.validator.constraints.Length;

import javax.validation.Valid;

//@Getter
@Builder
//@NoArgsConstructor
//@Setter

public class PersonDTO {
//    @NonNull
    @Length(min=2,max=10,message = "字符转字节不在2~10范围")
    private String name;
//    @NonNull
    private String sex;
    private Integer age;
    final private String at="";
}

6.2、在接收变量前添加@Validated

package com.fcors.fcors.api.sample.v1;

import com.fcors.fcors.dto.PersonDTO;
import com.fcors.fcors.exception.ForbiddenException;
import com.fcors.fcors.exception.NotFoundException;
import com.fcors.fcors.sample.IConnect;
import com.fcors.fcors.sample.ISkill;
import com.fcors.fcors.sample.database.MySql;
import com.fcors.fcors.sample.hero.Diana;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Lazy;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.constraints.Max;
import javax.websocket.server.PathParam;
import java.util.Map;

@RestController
@RequestMapping("/test")
@Validated
public class TestController {
    @Autowired
    private ISkill xing;

    @PostMapping("/testnew/id/{idx}")
    public String test2(
            @PathVariable(name="idx") @Max(value=10,message="不能大于10") Integer id,
            @RequestBody @Validated PersonDTO person
    ) throws RuntimeException {
        throw new ForbiddenException(10000);
//        return "Hello Fox is testing~";
    }
}
JAVA、基础技术、技术与框架SprintBoot系列(六):参数校验机制插图15

demo7: 级联参数校验

例如上述的PersonDTO存在一个成员变量是一个类,如何对这个子类进行参数校验呢?

7.1、首先在dto下面创建一个类 SchoolDTO

package com.fcors.fcors.dto;

import lombok.Getter;
import lombok.Setter;
import org.hibernate.validator.constraints.Length;

import javax.validation.Valid;

@Getter
@Setter
public class SchoolDTO {
    @Length(min=2,message = "schoolName 字符串必须大于2位")
    private String schoolName;
}

7.2、修改PersonDTO

新增成员变量private SchoolDTO schoolDTO;

在成员变量 的前面加 @Valid

package com.fcors.fcors.dto;

import lombok.*;
import org.hibernate.validator.constraints.Length;

import javax.validation.Valid;

//@Getter
@Builder
//@NoArgsConstructor
//@Setter

public class PersonDTO {
//    @NonNull
    @Length(min=2,max=10,message = "字符转字节不在2~10范围")
    private String name;
//    @NonNull
    private String sex;
    private Integer age;
    final private String at="";
    @Valid
    private SchoolDTO schoolDTO;
}
JAVA、基础技术、技术与框架SprintBoot系列(六):参数校验机制插图16
JAVA、基础技术、技术与框架SprintBoot系列(六):参数校验机制插图17

demo8:自定义校验方法

下面通过一个例子来演示如何自定义校验方法。检测两次密码是否一致

首先在PersonDTO添加两个成员变量private String Password1; private String Password2;

8.1创建一个 PasswordValidator 的类

在项目中创建package,并命名为validators,并创建一个 PasswordValidator 的Annotation

注解解析

@Target:说明 Annotation 所修饰对象范围,可用作package、types[类、接口、枚举和 Annotation 类型等]、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(循环变量、catch参数)

  • TYPE **用于描述类、接口(包括注解类型)或enum声明Class
  • FIELD **用于描述域
  • METHOD **用于描述方法 Method declaration
  • PARAMETER **用于描述参数
  • CONSTRUCTOR **用于描述构造器
  • LOCAL_VARIABLE **用于描述局部变量
  • PACKAGE 用于描述包
package com.fcors.fcors.validators;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.TYPE,ElementType.FIELD})
/*{ElementType.TYPE} 注解可打在类上
* {ElementType.FIELD} 注解可打在成员变量上
* */
@Constraint(validatedBy = PasswordValidator.class)
public @interface PasswordEqual {
    String message() default "password are not equal";

    /* groups && payload 是一定要写上的 不然不生效 */
    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default  {};
}

8.2、创建一个 PasswordEqual 的 Annotation

在 validators 创建一个 PasswordEqual 的 Annotation,并 implements ConstraintValidator<@value1,@value2>

@value1:自定义注解的命名

@value2:被该自定义注解修饰的目标类

例如下面的例子:

自定义的注解是@PasswordEqual,这个就是@value1

在PersonDTO上被使用到,所以@value2= PersonDTO

public class PasswordValidator implements ConstraintValidator<PasswordEqual, PersonDTO> {
    
}

右键“ genrate “>>>”Override”>>>”isValid”

完善代码

package com.fcors.fcors.validators;

import com.fcors.fcors.dto.PersonDTO;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class PasswordValidator implements ConstraintValidator<PasswordEqual, PersonDTO> {
    @Override
    public boolean isValid(PersonDTO personDTO, ConstraintValidatorContext constraintValidatorContext) {
        String password1 = personDTO.getPassword1();
        String password2 = personDTO.getPassword2();
        boolean match = password1.equals(password2);
        
        return match;
    }
}

如果我们需要自定义校验中使用参数,例如定义最大值和最小值。

设置一个min参数key和max的参数key

package com.fcors.fcors.dto.validators;

import lombok.Setter;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD})
/*{ElementType.TYPE} 注解可打在类上
 * {ElementType.FIELD} 注解可打在成员变量上
 * */

@Constraint(validatedBy = PasswordValidator.class )
public @interface PasswordEqual {
    int min() default 4;

    int max() default 6;

    String message() default "passwords are not equal";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
    //关联类 编程模式
}

PersonDTO在使用自定义的参数:min和max

package com.fcors.fcors.dto;

import com.fcors.fcors.validators.PasswordEqual;
import lombok.*;
import org.hibernate.validator.constraints.Length;

import javax.validation.Valid;

@Getter
@Builder
//@NoArgsConstructor
//@Setter
@PasswordEqual(min=2)
public class PersonDTO {
//    @NonNull
    @Length(min=2,max=10,message = "字符转字节不在2~10范围")
    private String name;
//    @NonNull
    private String sex;
    private Integer age;
    final private String at="";
    private String Password1;
    private String Password2;
    @Valid
    private SchoolDTO schoolDTO;
}

PasswordValidator接收参数,处理逻辑(未完待续,参数并没有修改)

/**
 * @作者 7七月
 * @微信公号 林间有风
 * @开源项目 $ http://7yue.pro
 * @免费专栏 $ http://course.7yue.pro
 * @我的课程 $ http://imooc.com/t/4294850
 * @创建时间 2020-01-21 15:34
 */
package com.fcors.fcors.dto.validators;

import com.fcors.fcors.dto.PersonDTO;
import com.fcors.fcors.dto.validators.PasswordEqual;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class PasswordValidator implements ConstraintValidator<PasswordEqual, PersonDTO> {
    private int min;
    private int max;


    @Override
    public void initialize(PasswordEqual constraintAnnotation) {
        this.min = constraintAnnotation.min();
        this.max = constraintAnnotation.max();

    }

    @Override
    public boolean isValid(PersonDTO personDTO, ConstraintValidatorContext constraintValidatorContext) {
        String password1 = personDTO.getPassword1();
        String password2 = personDTO.getPassword2();

        Boolean match = false;
        if (true == password1.equals(password2)) {
            match = true;
        }
        if (true == password1.length() < min) {
            Fox_error(constraintValidatorContext,"password1字节小于" + min);
            match = false;
        }
        if (true == password1.length() > max) {
            Fox_error(constraintValidatorContext,"password1字节大于" + max);
            match = false;
        }
        if (true == password2.length() < min) {
            Fox_error(constraintValidatorContext,"password2字节小于" + min);

            match = false;
        }
        if (true == password2.length() > max) {
            Fox_error(constraintValidatorContext,"password2字节大于" + max);

            match = false;
        }
        return match;
    }

    private static void Fox_error(ConstraintValidatorContext context, String message) {
        context.disableDefaultConstraintViolation();
        context.buildConstraintViolationWithTemplate(message).addConstraintViolation();
    }

    //第二个:自定义注解修饰的目标的类型
}

=======扩展学习=======

如果在PasswordEqual的@Target添加上ElementType.FIELD,则注解可以使用在变量上

JAVA、基础技术、技术与框架SprintBoot系列(六):参数校验机制插图18

此时我们应该如何接收参数呢?因为修饰的是String,因此@value2=String

JAVA、基础技术、技术与框架SprintBoot系列(六):参数校验机制插图19

demo10:修改报错信息

url: http://192.168.8.10:8081/api/sample/v1/test/testnew/id/89

RequestBody:
{
    "name":"fcors",
    "sex":"box",
    "age":18,
    "Password1":"1",
    "Password2":"111",
    "schoolDTO":{
        "schoolName":"hello"
    }
}
JAVA、基础技术、技术与框架SprintBoot系列(六):参数校验机制插图20

通过报错可以看到两种错误类型:MethodArgumentNotValidException和ConstraintViolationException

下面通过全局异常机制,重新定义返回信息。修改core/GolbalExceptionAdvice

package com.fcors.fcors.core;

import com.fcors.fcors.core.configuration.ExceptionCodeConfiguration;
import com.fcors.fcors.exception.HttpException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolationException;
import java.util.List;

@ControllerAdvice
public class GolbalExceptionAdvice {
    @Autowired
    private ExceptionCodeConfiguration codeConfiguration;

    @ExceptionHandler(value=Exception.class)
    @ResponseBody
    @ResponseStatus(code= HttpStatus.INTERNAL_SERVER_ERROR)
    public UnifyResponse handleException(HttpServletRequest req, Exception e){
        System.out.println("全局异常报错处理");
        UnifyResponse message = new UnifyResponse(999,"全局异常测试错误","url");
        return message;
    }
    @ExceptionHandler(value=HttpException.class)
    public ResponseEntity handelHttpException(HttpServletRequest req, HttpException e){
        String requestUrl = req.getRequestURI();
        String method = req.getMethod();

        UnifyResponse message = new UnifyResponse(e.getCode(),codeConfiguration.getMessage(e.getCode()),method+" "+requestUrl);
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);

        HttpStatus httpStatus = HttpStatus.resolve(e.getHttpStatusCode());
        ResponseEntity<UnifyResponse> r = new ResponseEntity<>(message,headers,httpStatus);
        System.out.println("专用HttpException异常报错处理");
        return r;
    }
    /* 定义MethodArgumentNotValidException专属 */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    @ResponseStatus(code = HttpStatus.BAD_REQUEST)
    public UnifyResponse handleBeanValidation(HttpServletRequest req, MethodArgumentNotValidException e) {
        String requestUrl = req.getRequestURI();
        String method = req.getMethod();

        List<ObjectError> errors = e.getBindingResult().getAllErrors();
        String message = this.formatAllErrorMessages(errors);

        return new UnifyResponse(10001, message,method + " " + requestUrl);
    }
    /* 定义ConstraintViolationException专属 */
    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseStatus(code= HttpStatus.BAD_REQUEST)
    @ResponseBody
    public UnifyResponse handleConstraintException(HttpServletRequest req, ConstraintViolationException e){
        String requestUrl = req.getRequestURI();
        String method = req.getMethod();
        String message = e.getMessage();

        return new UnifyResponse(10001, message, method + " " + requestUrl);
    }

    private String formatAllErrorMessages(List<ObjectError> errors) {
        StringBuffer errorMsg = new StringBuffer();
        errors.forEach(error ->
                errorMsg.append(error.getDefaultMessage()).append(';')
        );
        return errorMsg.toString();
    }
}

下面我们讲解一下,如何创建目录路由

https://www.fcors.com/%E6%8A%80%E6%9C%AF%E4%B8%8E%E6%A1%86%E6%9E%B6/sprintboot%E7%B3%BB%E5%88%97%EF%BC%88%E4%B8%83%EF%BC%89%EF%BC%9A%E8%87%AA%E5%AE%9A%E4%B9%89%E8%B7%AF%E7%94%B1/