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注解,提高自己的局部异常处理类的加载顺序就行了

代码:

模拟效果:


目录
相关文章
|
3天前
|
安全 Java API
java如何请求接口然后终止某个线程
通过本文的介绍,您应该能够理解如何在Java中请求接口并根据返回结果终止某个线程。合理使用标志位或 `interrupt`方法可以确保线程的安全终止,而处理好网络请求中的各种异常情况,可以提高程序的稳定性和可靠性。
26 6
|
20天前
|
Java API
Java中内置的函数式接口
Java中内置的函数式接口
20 2
|
21天前
|
Java
实现java执行kettle并传参数
实现java执行kettle并传参数
24 1
|
24天前
|
Java
在Java中如何实现接口?
实现接口是 Java 编程中的一个重要环节,它有助于提高代码的规范性、可扩展性和复用性。通过正确地实现接口,可以使代码更加灵活、易于维护和扩展。
46 3
|
23天前
|
Java
在Java中,接口之间可以继承吗?
接口继承是一种重要的机制,它允许一个接口从另一个或多个接口继承方法和常量。
66 1
|
23天前
|
Java 开发者
在 Java 中,一个类可以实现多个接口吗?
这是 Java 面向对象编程的一个重要特性,它提供了极大的灵活性和扩展性。
46 1
|
23天前
|
Java
在Java中实现接口的具体代码示例
可以根据具体的需求,创建更多的类来实现这个接口,以满足不同形状的计算需求。希望这个示例对你理解在 Java 中如何实现接口有所帮助。
37 1
|
28天前
|
Java
在Java中定义一个不做事且没有参数的构造方法的作用
Java程序在执行子类的构造方法之前,如果没有用super()来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用super()来调用父类中特定的构造方法,则编译时将发生错误,因为Java程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。
|
18天前
|
设计模式 Java 开发者
Java多线程编程的陷阱与解决方案####
本文深入探讨了Java多线程编程中常见的问题及其解决策略。通过分析竞态条件、死锁、活锁等典型场景,并结合代码示例和实用技巧,帮助开发者有效避免这些陷阱,提升并发程序的稳定性和性能。 ####
|
16天前
|
存储 监控 小程序
Java中的线程池优化实践####
本文深入探讨了Java中线程池的工作原理,分析了常见的线程池类型及其适用场景,并通过实际案例展示了如何根据应用需求进行线程池的优化配置。文章首先介绍了线程池的基本概念和核心参数,随后详细阐述了几种常见的线程池实现(如FixedThreadPool、CachedThreadPool、ScheduledThreadPool等)的特点及使用场景。接着,通过一个电商系统订单处理的实际案例,分析了线程池参数设置不当导致的性能问题,并提出了相应的优化策略。最终,总结了线程池优化的最佳实践,旨在帮助开发者更好地利用Java线程池提升应用性能和稳定性。 ####