Java 服务端参数校验 - JSR 303 介绍及实践

可以说几乎所有的应用场景中,参数验证都在编写业务逻辑前完成,严格确保进来的数据是合法且符合要求的。

Java Web 开发领域,也早有较为完善的 Bean Validation 为 Java Bean 验证定义了相应的元数据模型和 API。首先,在项目中引入 web 模块的依赖:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

Hibernate Validator 是 Bean Validation 的一种实现 . Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的实现,以及一些附加的 constraint。如果想了解更多请查看 http://www.hibernate.org/subprojects/validator.html

具体以及常用的 constraint 包含如下:

@Data
public class Validate {

    // 空和非空检查: @Null、@NotNull、@NotBlank、@NotEmpty

    @Null(message = "验证是否为 null")
    private Integer isNull;

    @NotNull(message = "验证是否不为 null, 但无法查检长度为0的空字符串")
    private Integer id;

    @NotBlank(message = "检查字符串是不是为 null,以及去除空格后长度是否大于0")
    private String name;

    @NotEmpty(message = "检查是否为 NULL 或者是 EMPTY")
    private List<String> stringList;

    // Boolean值检查: @AssertTrue、@AssertFalse

    @AssertTrue(message = " 验证 Boolean参数是否为 true")
    private Boolean isTrue;

    @AssertFalse(message = "验证 Boolean 参数是否为 false ")
    private Boolean isFalse;

    // 长度检查: @Size、@Length

    @Size(min = 1, max = 2, message = "验证(Array,Collection,Map,String)长度是否在给定范围内")
    private List<Integer> integerList;

    @Length(min = 8, max = 30, message = "验证字符串长度是否在给定范围内")
    private String address;

    // 日期检查: @Future、@FutureOrPresent、@Past、@PastOrPresent

    @Future(message = "验证日期是否在当前时间之后")
    private Date futureDate;

    @FutureOrPresent(message = "验证日期是否为当前时间或之后")
    private Date futureOrPresentDate;

    @Past(message = "验证日期是否在当前时间之前")
    private Date pastDate;

    @PastOrPresent(message = "验证日期是否为当前时间或之前")
    private Date pastOrPresentDate;

    // 其它检查: @Email、@CreditCardNumber、@URL、@Pattern、@ScriptAssert、@UniqueElements

    @Email(message = "校验是否为正确的邮箱格式")
    private String email;

    @CreditCardNumber(message = "校验是否为正确的信用卡号")
    private String creditCardNumber;

    @URL(protocol = "http", host = "127.0.0.1", port = 8080, message = "校验是否为正确的URL地址")
    private String url;

    @Pattern(regexp = "^1[3|4|5|7|8][0-9]{9}$", message = "正则校验是否为正确的手机号")
    private String phone;

    // 对关联对象元素进行递归校验检查

    @Valid
    @UniqueElements(message = "校验集合中的元素是否唯一")
    private List<CalendarEvent> calendarEvent;

    @Data
    @ScriptAssert(lang = "javascript", script = "_this.startDate.before(_this.endDate)",
            message = "通过脚本表达式校验参数")
    private class CalendarEvent {

        private Date startDate;

        private Date endDate;

    }

    // 数值检查: @Min、@Max、@Range、@DecimalMin、@DecimalMax、@Digits

    @Min(value = 0, message = "验证数值是否大于等于指定值")
    @Max(value = 100, message = "验证数值是否小于等于指定值")
    @Range(min = 0, max = 100, message = "验证数值是否在指定值区间范围内")
    private Integer score;

    @DecimalMin(value = "10.01", inclusive = false, message = "验证数值是否大于等于指定值")
    @DecimalMax(value = "199.99", message = "验证数值是否小于等于指定值")
    @Digits(integer = 3, fraction = 2, message = "限制整数位最多为3,小数位最多为2")
    private BigDecimal money;

}

常见的前后端分离开发模式,数据通信通常以 JSON 为主。针对 POST 和 PUT 请求,一般通过新建域(对象)模型来进行数据绑定和校验,constraint 通常附加在这些域模型的字段上(如上):

     /**
     * Valid注解标明要对参数对象进行数据校验
     */
    @PutMapping
    @PostMapping
    public Map<String, Object> test01(@RequestBody @Valid Validate validate, BindingResult bindingResult) {
        Map<String, Object> map = new HashMap<>(4);

        if (bindingResult.hasErrors()) {
            String errorMsg = bindingResult.getFieldErrors().stream().map(FieldError::getDefaultMessage)
                    .collect(Collectors.joining(","));
            map.put("errorMsg", errorMsg);
        }

        map.put("params", validate.toString());
        return map;
    }

此外,对于 GET 和 DELETE 请求,参数通常为 key1=value1&key2=value2 这种形式。默认情况下,Hibernate Validator 只能对 Object 属性进行校验,并不能对单个参数进行校验,Spring 在此基础上进行了扩展,通过配置 MethodValidationPostProcessor 处理器,可以实现对方法参数的拦截校验。

@Configuration
public class ValidateConfig {

    /**
     * 配置MethodValidationPostProcessor拦截器,以实现对方法参数的校验
     */
    @Bean
    public MethodValidationPostProcessor methodValidationPostProcessor() {
        return new MethodValidationPostProcessor();
    }

}

注意,要在 Controller 类上明确标明 @Validated

@Validated
@RestController
@RequestMapping("validate")
public class ValidateController {

    @GetMapping
    @DeleteMapping
    public Map<String, Object> test02(@NotNull(message = "id不能为空") @Range(min = 1, max = 100, message = "id最小为1最大为100") Integer id,
                                                           @NotBlank(message = "email不能为空") @Email(message = "邮箱格式错误") String email,
                                                           @ModelAttribute @Valid Validate validate) {
        Map<String, Object> map = new HashMap<>(4);

        map.put("id", id);
        map.put("email", email);
        map.put("params", validate.toString());

        return map;
    }

}

上述这种形式的参数要是校验失败,错误提示明显并不友好,通过捕获此类异常就可以解决,这里就不再介绍了。

参考链接

JSR 303 - Bean Validation 介绍及最佳实践
SpringBoot-服务端参数验证-JSR-303验证框架

示例源码

文章已授权转载,原文链接:Spring Boot 项目参数校验的常见使用场景

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容