JavaWeb优雅实现接口参数校验

简介: JavaWeb优雅实现接口参数校验

1 背景

要对方法的参数进行校验,最简单暴力的写法是这个样子:

    public static void utilA(String a,BigDecimal b){
        if (StringUtils.isEmpty(a)){
            System.out.println("a不可为空");
            return;
        }
        if (b == null){
            System.out.println("b不可为空");
            return;
        }
        if (b.compareTo(BigDecimal.ZERO) != 1){
            System.out.println("b的取值范围不正确");
            return;
        }
        System.out.println("do something");
    }

复制代码


这样做从功能角度来说一点问题也没有。


但是从代码的长期维护性上来说,代码复用率低,校验规则一旦多起来很难维护,而且怎么看怎么显得笨拙,对于有一点追求的工程师来说,这么一大坨还是挺难接受的。


虽然有一些诸如 Preconditions(com.google) 的解决方案,但很难适应所有的场景,用起来也没到非常得心应有的地步。

2 如何优雅地校验参数

Spring官方推荐的,语义清晰的优雅的方法级别校验(入参校验、返回值校验)

2.1 官方指导意见

Spring官方在SpringBoot文档中,关于参数校验(Validation)给出的解决方案是这样的:

@Service
@Validated
public class MyBean {
    public Archive findByCodeAndAuthor(@Size(min = 8, max = 10) String code,
            Author author) {
        ...
    }
}

复制代码


Spring Boot 官网文档 《37. Validation》


也就是说,使用 JSR-303 规范,直接利用注解进行参数校验。


(JSR-303 是 JAVA EE 6 中的一项子规范,叫做 Bean Validation,官方参考实现是 Hibernate Validator)

2.2 注解用法说明

2.2.1.注解简介

对于简单类型参数(非Bean),直接在参数前,使用注解添加约束规则。注解如下所示:

@AssertTrue / @AssertFalse

验证适用字段:boolean

注解说明:验证值是否为true / false


@DecimalMax / @DecimalMin


验证适用字段:BigDecimal,BigInteger,String,byte,short,int,long


注解说明:验证值是否小于或者等于指定的小数值,要注意小数存在精度问题


@Digits

验证适用字段:BigDecimal,BigInteger,String,byte,short,int,long


注解说明:验证值的数字构成是否合法


属性说明:integer:指定整数部分的数字的位数。fraction: 指定小数部分的数字的位数。


@Future / @Past


验证适用字段:Date,Calendar


注解说明:验证值是否在当前时间之后 / 之前


属性说明:公共


@Max / @Min


验证适用字段:BigDecimal,BigInteger,String,byte,short,int,long


注解说明:验证值是否小于或者等于指定的整数值


属性说明:公共


注意事项:建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单提交的值为“”时无法转换为int


@NotNull / @Null


验证适用字段:引用数据类型


注解说明:验证值是否为非空 / 空


属性说明:公共


@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.


@NotEmpty 检查约束元素是否为Null或者是EMPTY.


@NotBlank 与 @NotEmpty 的区别:空格(" ")对于 NotEmpty 是合法的,而 NotBlank 会抛出校验异常


@Pattern


验证适用字段:String


注解说明:验证值是否配备正则表达式


属性说明:regexp:正则表达式flags: 指定Pattern.Flag 的数组,表示正则表达式的相关选项。


@Size


验证适用字段:String,Collection,Map,数组


注解说明:验证值是否满足长度要求


属性说明:max:指定最大长度,min:指定最小长度。


@Length(min=, max=):专门应用于String类型


@Valid


验证适用字段:递归的对关联对象进行校验


注解说明:如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验(是否进行递归验证)


属性说明:无


@Range(min=, max=) 被指定的元素必须在合适的范围内


@CreditCardNumber信用卡验证


@Email 验证是否是邮件地址,如果为null,不进行验证,算通过验证。


@URL(protocol=,host=, port=,regexp=, flags=)

2.2.2使用

1.引入依赖

 <!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator -->
        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>6.1.5.Final</version>
        </dependency>

复制代码

2.在对应字段上添加注解,方法被调用时,如果传入的实际参数与约束规则不符,会直接抛出 ConstraintViolationException ,表明参数校验失败。

import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
/**
 * @Author: wangxia
 * @Date: 2021/10/20 16:30
 */
public class TestPerson {
    @NotEmpty(message = "用户名不能为空")
    private String username;
    @Min(value = 0,message = "年龄不能小于0岁")
    @Max(value =150,message = "年龄不能大于150岁")
    private int age;
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

复制代码

3…对于Bean类型的参数,在Bean内部的各个字段上面追加约束注解,然后在方法的参数前面添加 @Validated或@Valid注解即可。示例:

@RequestMapping("/")
@RestController
public class TestValidatController {
    @PostMapping("/testValid")
    public String testValid(@Validated @RequestBody TestPerson testPerson){
        return "测试成功";
    }
}

复制代码

4.优雅捕获异常,这一步可以省略,但是请求时会直接返回,400的异常提示,不太优雅。

@ControllerAdvice
@ResponseBody 
public class MethodArgumentNotValidHandel {
    @ExceptionHandler(value=MethodArgumentNotValidException.class)
    public JSONObject MethodArgumentNotValidHandler(HttpServletRequest request,
                                                    MethodArgumentNotValidException exception) throws Exception
    {
        JSONObject result=new JSONObject();
        result.put("code","fail");
        JSONObject errorMsg=new JSONObject();
        for (FieldError error : exception.getBindingResult().getFieldErrors()) {
            errorMsg.put(error.getField(),error.getDefaultMessage());
        }
        result.put("msg",errorMsg);
        return result;
    }
}

复制代码

添加优雅捕获的异常提示:

未添加优雅捕获的异常提示:

3 @ControllerAdvice同时配置过滤多个包

//@ControllerAdvice("com.automvc")  
//配置过滤一个的时候``
@ControllerAdvice``(basePackages={``"com.automvc"``, ``"com.test"``})  
``//同时配置过滤多个包

3.1 springboot 多个@RestControllerAdvice时的拦截顺序

我们的项目中经常会使用到别人的模块,例如我的项目demo,要依赖别人的A模块,以及基础的核心core模块,此时core模块有一个使用了@RestControllerAdvice的类,负责拦截所有的controller异常。


但是呢,他的异常处理不符合我们demo项目的要求,这就导致我们demo项目要重写自己的controller异常拦截。


此时我们可以用的解决异常的方法有三种:


1、使用aop进行切面拦截异常


2、controller每个方法都用try-catch捕获异常


3、增加一个@RestControllerAdvice标注的类,负责处理我们项目的controller异常。


我选用第三种方法,但是当我写了个PartControllerAdvice类,指定basePackages为我自己的项目包,依旧还是被core模块的全局异常处理类拦截了。


查资料和找博客发现如果有多个加了@RestControllerAdvice的类,他们会依次加载,遇到异常时,按照类加载顺序进行判断,如果前面的类有能处理这个异常的方法,就给前面的类处理。


我的项目中有两个标注了@RestControllerAdvice的类,core模块的类被先加载,且core模块的异常处理类有个方法专门处理Exception类型的异常,所以我的局部异常处理类始终不执行。

3.2 解决方法

@Order(Ordered.HIGHEST_PRECEDENCE) 使用@Order注解,提高自己的局部异常处理类的加载顺序就行了

代码:

模拟效果:


目录
相关文章
|
1天前
|
NoSQL Java API
java一行代码实现RESTFul接口
Spring Data REST是构建在Spring Data之上的库,可自动将repository转换为REST服务,支持JPA、MongoDB、Neo4j、GemFire和Cassandra。无需手动创建Service和Controller层。要开始,需配置JPA数据源,创建实体类和Repository接口。快速实现REST接口,只需引入spring-boot-starter-data-rest Maven依赖,并在Repository接口上添加@RepositoryRestResource注解。
|
2天前
|
Java 程序员 数据格式
关于Java抽象类和接口的总结和一点个人的看法
关于Java抽象类和接口的总结和一点个人的看法
|
7天前
|
存储 安全 Java
[Java基础面试题] Map 接口相关
[Java基础面试题] Map 接口相关
|
7天前
|
Java
一文搞清楚Java中的包、类、接口
包、类、接口、方法、变量、参数、代码块,这些都是构成Java程序的核心部分,即便最简单的一段代码里都至少要包含里面的三四个内容,这两天花点时间梳理了一下,理解又深刻了几分。
31 10
|
12天前
|
Java 开发者
探索 Java 的函数式接口和 Lambda 表达式
【4月更文挑战第19天】Java 中的函数式接口和 Lambda 表达式提供了简洁、灵活的编程方式。函数式接口有且仅有一个抽象方法,用于与 Lambda(一种匿名函数语法)配合,简化代码并增强可读性。Lambda 表达式的优点在于其简洁性和灵活性,常用于事件处理、过滤和排序等场景。使用时注意兼容性和变量作用域,它们能提高代码效率和可维护性。
|
13天前
|
Java
Java接口中可以定义哪些方法?
【4月更文挑战第13天】
14 0
Java接口中可以定义哪些方法?
|
13天前
|
Java API
什么是Java函数式接口?
【4月更文挑战第13天】
15 0
什么是Java函数式接口?
|
15天前
|
设计模式 Java
Java接口与抽象类
Java接口与抽象类
17 0
|
19天前
|
安全 Java 编译器
接口之美,内部之妙:深入解析Java的接口与内部类
接口之美,内部之妙:深入解析Java的接口与内部类
37 0
接口之美,内部之妙:深入解析Java的接口与内部类
|
19天前
|
Java
Java中的抽象类和接口之间的异同点
抽象类顾名思义是之抽象出来的类,具有多个类的共性,是抽象的。抽象类和具体类是相对的概念。“抽象”是一种存在思想逻辑中的概念,而“具体”是一种可见可触摸的现实对象。简单说,法拉利,保时捷,兰博基尼,布加迪等多种车子,可以抽象出车,也可以抽象出跑车,这就是抽象出来的类别,称为抽象类。
10 0