参数校验是我们程序开发中必不可少的过程。用户在前端页面上填写表单时,前端js程序会校验参数的合法性,当数据到了后端,为了防止恶意操作,保持程序的健壮性,后端同样需要对数据进行校验。后端参数校验最简单的做法是直接在业务方法里面进行判断,当判断成功之后再继续往下执行。但这样带给我们的是代码的耦合,冗余。当我们多个地方需要校验时,我们就需要在每一个地方调用校验程序,导致代码很冗余,且不美观。

那么如何优雅的对参数进行校验呢?JSR303就是为了解决这个问题出现的,本篇文章主要是介绍 JSR303,Hibernate Validator 等校验工具的使用,以及自定义校验注解的使用。

校验框架介绍

JSR303 是一套JavaBean参数校验的标准,它定义了很多常用的校验注解,我们可以直接将这些注解加在我们JavaBean的属性上面,就可以在需要校验的时候进行校验了。注解如下:

Hibernate validator 在JSR303的基础上对校验注解进行了扩展,扩展注解如下:

Spring validtor 同样扩展了jsr303,并实现了方法参数和返回值的校验

Spring 提供了MethodValidationPostProcessor类,用于对方法的校验

代码实现

添加JAR包依赖

在pom.xml中添加如下依赖:

        <!--jsr 303-->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
<!-- hibernate validator-->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.2.0.Final</version>
</dependency>

最简单的参数校验

1、Model 中添加校验注解

public class Book {
private long id; /**
* 书名
*/
@NotEmpty(message = "书名不能为空")
private String bookName;
/**
* ISBN号
*/
@NotNull(message = "ISBN号不能为空")
private String bookIsbn;
/**
* 单价
*/
@DecimalMin(value = "0.1",message = "单价最低为0.1")
private double price; // getter setter ....... }

2、在controller中使用此校验

 /**
* 添加Book对象
* @param book
*/
@RequestMapping(value = "/book", method = RequestMethod.POST)
public void addBook(@RequestBody @Valid Book book) {
System.out.println(book.toString());
}

当访问这个post接口时,如果参数不符合Model中定义的话,程序中就回抛出400异常,并提示错误信息。

自定义校验注解

虽然jSR303和Hibernate Validtor 已经提供了很多校验注解,但是当面对复杂参数校验时,还是不能满足我们的要求,这时候我们就需要 自定义校验注解。

下面以“List数组中不能含有null元素”为实例自定义校验注解

1、注解定义如下:

package com.beiyan.validate.annotation;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*; import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME; /**
* 自定义参数校验注解
* 校验 List 集合中是否有null 元素
*/ @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = ListNotHasNullValidatorImpl.class)////此处指定了注解的实现类为ListNotHasNullValidatorImpl public @interface ListNotHasNull { /**
* 添加value属性,可以作为校验时的条件,若不需要,可去掉此处定义
*/
int value() default 0; String message() default "List集合中不能含有null元素"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; /**
* 定义List,为了让Bean的一个属性上可以添加多套规则
*/
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Documented
@interface List {
ListNotHasNull[] value();
}
}

2、注解实现类:

package com.beiyan.validate.annotation;

import org.springframework.stereotype.Service;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.List; /**
* 自定义注解ListNotHasNull 的实现类
* 用于判断List集合中是否含有null元素
*/ @Service
public class ListNotHasNullValidatorImpl implements ConstraintValidator<ListNotHasNull, List> { private int value; @Override
public void initialize(ListNotHasNull constraintAnnotation) {
//传入value 值,可以在校验中使用
this.value = constraintAnnotation.value();
} public boolean isValid(List list, ConstraintValidatorContext constraintValidatorContext) {
for (Object object : list) {
if (object == null) {
//如果List集合中含有Null元素,校验失败
return false;
}
}
return true;
} }

3、model添加注解:

public class User {

    //其他参数 .......

/**
* 所拥有的书籍列表
*/
@NotEmpty(message = "所拥有书籍不能为空")
@ListNotHasNull(message = "List 中不能含有null元素")
@Valid
private List<Book> books;
//getter setter 方法.......
}

使用方法同上,在在需要校验的Model上面加上@Valid 即可

分组验证

对同一个Model,我们在增加和修改时对参数的校验也是不一样的,这个时候我们就需要定义分组验证,步骤如下

1、定义两个空接口,分别代表Person对象的增加校验规则和修改校验规则

/**
* 可以在一个Model上面添加多套参数验证规则,此接口定义添加Person模型新增时的参数校验规则
*/
public interface PersonAddView {
} /**
* 可以在一个Model上面添加多套参数验证规则,此接口定义添加Person模型修改时的参数校验规则
*/
public interface PersonModifyView {
}

2、Model上添加注解时使用指明所述的分组

public class Person {
private long id;
/**
* 添加groups 属性,说明只在特定的验证规则里面起作用,不加则表示在使用Deafault规则时起作用
*/
@NotNull(groups = {PersonAddView.class, PersonModifyView.class}, message = "添加、修改用户时名字不能为空", payload = ValidateErrorLevel.Info.class)
@ListNotHasNull.List({
@ListNotHasNull(groups = {PersonAddView.class}, message = "添加上Name不能为空"),
@ListNotHasNull(groups = {PersonModifyView.class}, message = "修改时Name不能为空")})
private String name; @NotNull(groups = {PersonAddView.class}, message = "添加用户时地址不能为空")
private String address; @Min(value = 18, groups = {PersonAddView.class}, message = "姓名不能低于18岁")
@Max(value = 30, groups = {PersonModifyView.class}, message = "姓名不能超过30岁")
private int age;
//getter setter 方法......
}

3、启用校验

此时启用校验和之前的不同,需要指明启用哪一组规则

  /**
* 添加一个Person对象
* 此处启用PersonAddView 这个验证规则
* 备注:此处@Validated(PersonAddView.class) 表示使用PersonAndView这套校验规则,若使用@Valid 则表示使用默认校验规则,
* 若两个规则同时加上去,则只有第一套起作用
*/
@RequestMapping(value = "/person", method = RequestMethod.POST)
public void addPerson(@RequestBody @Validated({PersonAddView.class, Default.class}) Person person) {
System.out.println(person.toString());
} /**
* 修改Person对象
* 此处启用PersonModifyView 这个验证规则
*/
@RequestMapping(value = "/person", method = RequestMethod.PUT)
public void modifyPerson(@RequestBody @Validated(value = {PersonModifyView.class}) Person person) {
System.out.println(person.toString());
}

Spring validator 方法级别的校验

JSR和Hibernate validator的校验只能对Object的属性进行校验,不能对单个的参数进行校验,spring 在此基础上进行了扩展,添加了MethodValidationPostProcessor拦截器,可以实现对方法参数的校验,实现如下:

1、实例化MethodValidationPostProcessor

@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}

2、在所要实现方法参数校验的类上面添加@Validated,如下

@RestController
@Validated
public class ValidateController {
}

3、在方法上面添加校验规则:

  @RequestMapping(value = "/test", method = RequestMethod.GET)
public String paramCheck(@Length(min = 10) @RequestParam String name) {
System.out.println(name);
return null;
}

当方法上面的参数校验失败,spring 框架就回抛出异常

{
"timestamp": 1476108200558,
"status": 500,
"error": "Internal Server Error",
"exception": "javax.validation.ConstraintViolationException",
"message": "No message available",
"path": "/test"
}

从此可以优雅的对参数进行校验了

写在后面的话:

本篇文章只列举了常用的几种校验方法,其实关于校验的内容还有很多:

校验信息的国际化显示,

组合参数校验,

message中使用EL表达式,

将校验信息绑定到ModelAndView等,这里就不一一列出了,下面这几篇文章写的也不错,读者可以参考:

将校验信息绑定到ModelAndView    http://www.voidcn.com/blog/983836259/article/p-5794496.html

集成Bean Validation 1.1(JSR-349)到SpringMVC   https://my.oschina.net/qjx1208/blog/200946

本文的全部代码已上传开源中国git仓库: http://git.oschina.net/beiyan/Validate

Java Bean Validation 最佳实践的更多相关文章

  1. 深入了解数据校验:Java Bean Validation 2.0(JSR380)

    每篇一句 吾皇一日不退役,尔等都是臣子 相关阅读 [小家Java]深入了解数据校验(Bean Validation):基础类打点(ValidationProvider.ConstraintDescri ...

  2. Java bean validation 规范与参考实现

    1.Apache Bval 依赖包:validation-api-1.1.0.Final.jar org.apache.bval.bundle-1.1.1.jar bval-core-1.1.1.ja ...

  3. paip.复制文件 文件操作 api的设计uapi java python php 最佳实践

    paip.复制文件 文件操作 api的设计uapi java python php 最佳实践 =====uapi   copy() =====java的无,要自己写... ====php   copy ...

  4. Java 网络编程最佳实践(转载)

    http://yihongwei.com/2015/09/remoting-practice/ Java 网络编程最佳实践 Sep 10, 2015 | [Java, Network] 1. 通信层 ...

  5. 避免Java中NullPointerException的Java技巧和最佳实践

    Java中的NullPointerException是我们最经常遇到的异常了,那我们到底应该如何在编写代码是防患于未然呢.下面我们就从几个方面来入手,解决这个棘手的​问题吧.​ 值得庆幸的是,通过应用 ...

  6. 使用DataStax Java驱动程序的最佳实践

    引言 如果您想开始建立自己的基于Cassandra的Java程序,欢迎! 也许您已经参加过我们精彩的DataStax Academy课程或开发者大会,又或者仔细阅读过Cassandra Java驱动的 ...

  7. Java Bean Validation(参数校验) 最佳实践

    转载来自:http://www.cnblogs.com 参数校验是我们程序开发中必不可少的过程.用户在前端页面上填写表单时,前端js程序会校验参数的合法性,当数据到了后端,为了防止恶意操作,保持程序的 ...

  8. Spring Validation最佳实践及其实现原理,参数校验没那么简单!

    之前也写过一篇关于Spring Validation使用的文章,不过自我感觉还是浮于表面,本次打算彻底搞懂Spring Validation.本文会详细介绍Spring Validation各种场景下 ...

  9. java 导出 excel 最佳实践,java 大文件 excel 避免OOM(内存溢出) excel 工具框架

    产品需求 产品经理需要导出一个页面的所有的信息到 EXCEL 文件. 需求分析 对于 excel 导出,是一个很常见的需求. 最常见的解决方案就是使用 poi 直接同步导出一个 excel 文件. 客 ...

随机推荐

  1. Android Handler机制

    http://blog.csdn.net/erliangcode/article/details/52117831

  2. UNIX常用命令

    以下只说明各常用指令的基本用法, 若需详细说明, 请用 man 去读详细的 manual. 1.关於档案/目录处理的命令 ls--列目录 这是最基本的档案指令. ls 的意义为 "list& ...

  3. FIFO简记

    FIFO( First In First Out)简单说就是指先进先出的存储方式,在高速外设读取时经常使用. FIFO从读写的时钟可分为同步和异步两种,从信号来说都有读.写使能信号,读.写数据总线,满 ...

  4. 在.net中调用Delphi dll的Pchar转换

    Pchar是非托管代码,要在.net中调用Delphi dll中的功能,请使用MarshalAs属性告知.net调用PInvoke去转换.net中标准的string类型.如果Delphi dll是De ...

  5. Spark:读取hdfs gz压缩包

    spark 1.5.1是支持直接读取gz格式的压缩包的,和普通文件没有什么区别: 使用spark-shell进入spark shell 交互界面: 输入命令: sc.textFile("\h ...

  6. MonkeyTest简单实用介绍

    什么是Monkeytest? monkey测试是Android平台自动化测试的一种手段,通过Monkey程序模拟用户触摸屏幕.滑动Trackball.按键灯操作来对设备上的程序进行压力测试,检测程序发 ...

  7. 向指定URL发送GET方法获取资源,编码问题。 Rest风格

    http编码.今天遇到获取网页上的数据,用HTTP的GET请求访问url获取资源,网上有相应的方法.以前一直不知道什么事rest风格,现在我想就是开一个Controller,然后使人可以调用你的后台代 ...

  8. Url通配符映射

    原文:http://www.cnblogs.com/liukemng/p/3726897.ht 1.URL路径映射 1.1.对一个action配置多个URL映射: 我们把上一篇中的HelloWorld ...

  9. SQL 数据分页查询

    最近学习了一下SQL的分页查询,总结了以下几种方法. 首先建立了一个表,随意插入的一些测试数据,表结构和数据如下图: 现在假设我们要做的是每页5条数据,而现在我们要取第三页的数据.(数据太少,就每页5 ...

  10. (最重要)学了这么久的编程,你知道byte吗?

    在c#中,Byte b=12; 上面是合法的,因为Byte是一个字节.他的范围是0-255. 如果是 Byte b=4096;这是错的 ,必须加上强制类型转换. 这个小问题,虽然很小但是值得注意.