java jsr 303_java对象校验(validation)-JSR303规范
JSR303 规范简介
web开发有一句名言:永远不要相信用户输入,在任何时候,当你要处理一个应用程序的业务逻辑,数据校验是你必须要考虑和面对的事情。应用程序必须通过某种手段来确保输入进来的数据从语义上来讲是正确的。在通常的情况下,应用程序是分层的,不同的层由不同的开发人员来完成。很多时候同样的数据验证逻辑会出现在不同的层,这样就会导致代码冗余和一些管理的问题,比如说语义的一致性等。为了避免这样的情况发生,最好是将验证逻辑与相应的域模型进行绑定。于是就有了JSR303。JSR 303 – Bean Validation 是一个数据验证的规范,2009 年 11 月确定最终方案。2009 年 12 月 Java EE 6 发布,Bean Validation 作为一个重要特性被包含其中。参见官方文档
JSR303一些验证约束
要将验证逻辑绑定到bean上,我们可能最容易显想到的方法是,在bean中写一个isValide(),方法即可完成业务需求。但是大部分的验证可能是相似的,比如限制某个字段不能为空,限制某个数据类型的取值范围,限制某个字段符合某个正则表达式等。因此优雅的做法是在java bean中加上注解用来表明限制,让后使用统一的验证器来对bean进行合法性验证。JSR303与jdbc其规范一样,只制定规范实现由都三方来实现。使用做多最有名的是Hibernate Validator。Hibernate Validator在实现jsr303的基础上,还进行了自己一些扩展。
下面是JSR303一些制定的一些注解:
Constraint(约束)
说明
@Null
被注释的元素必须为 null
@NotNull
被注释的元素必须不为 null
@AssertTrue
被注释的元素必须为 true
@AssertFalse
被注释的元素必须为 false
@Min(value)
被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value)
被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value)
被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value)
被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max, min)
被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction)
被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past
被注释的元素必须是一个过去的日期
@Future
被注释的元素必须是一个将来的日期
@Pattern(value)
被注释的元素必须符合指定的正则表达式
Hibernate Validator 附加的 constraint
Constraint(约束)
说明
被注释的元素必须是电子邮箱地址
@Length
被注释的字符串的大小必须在指定的范围内
@NotEmpty
被注释的字符串的必须非空
@Range
被注释的元素必须在合适的范围内
用户自定义(custom)约束
用户也可以编写符合自己业务需求的约束,和验证器。
bean validation简单使用
Bean Validation 规范规定在对 Java Bean 进行约束验证前,目标元素必须满足以下条件:
如果验证的是属性(getter 方法),那么必须遵从 Java Bean 的命名习惯(JavaBeans 规范);
静态的字段和方法不能进行约束验证;
约束适用于接口和基类;
约束注解定义的目标元素可以是字段、属性或者类型等;
可以在类或者接口上使用约束验证,它将对该类或实现该接口的实例进行状态验证;
字段和属性均可以使用约束验证,但是不能将相同的约束重复声明在字段和相关属性(字段的 getter 方法)上。
下面是bean validation 的简单步奏。
1. 导包
本文JSR303的实现的是使用的Hibernate Validator,使用maven进行管理,因此需要在maven 的pom.xml文件中添加如下依赖。本文使用的javax包为1.1版本。实际上1.1是bean validation的新规范,随java EE 7一同推出的。因为hibernate-validator version 5+才支持bean validation1.1,并且1.1支持了el表达式因此同时也添加了el表达式包。使用bean validation1.1(JSR349)并不影响我们对bean validation1.1(JSR303)的验证,因为规范是向下兼容的。
javax.validation
validation-api
1.1.0.Final
org.hibernate
hibernate-validator
5.2.4.Final
javax.el
el-api
2.2
2. bean的书写
// User.java
public class User{
@NotNull(message = "姓名不能为空")
private String name;
@Min(value = 1 ,message = "年龄不能小于0")
@NotNull(message = "age不能为空")
private Integer age;
@NotNull(message = "id不能为空")
private Integer id;
@Valid
@NotNull
private Dog dog;
//省略get set方法
}
// Dog.java
public class Dog{
@NotBlank
private String type;
@NotBlank
private String name;
@NotBlank
private String gender;
//省略get set方法
}
3. 验证代码
// Main.java
public class Main{
public static void main(String[] args){
User user = new User();
user.setDog(new Dog());
validate(user);
}
static void validate(Object o){
ValidatorFactory vf = Validation.buildDefaultValidatorFactory();
Validator validator = vf.getValidator();
Set> set = validator.validate(o);
for (ConstraintViolation constraintViolation : set) {
System.out.println(constraintViolation.getPropertyPath()+":"+constraintViolation.getMessage());
}
}
}
4. 示例说明:
运行商代码应该不出意外应该可以得到如下验证信息:
name:姓名不能为空
id:id不能为空
phoneNumber:电话号码格式不正确
age:age不能为空
dog.name:不能为空
dog.type:不能为空
dog.gender:不能为空
以上注解,基本都比较好懂。注解里面的message属性是可选的,不填则用默认的提示。默认的提示配置会根据位置信息给出友好的提示,在cn地区则会以如上因为进行提示,国际化配置文件位于包hibernate-validator-5.2.4.Final.jar下/org/hibernate/validator/ValidationMessages.properties还需要注意的,当校验的对象还依赖其他对象时,验证依赖对象需要用到JSR303级联特性,要验证依赖对象需要加@Valid注解。
自定义约束&约束验证器
想要定义自己的规则,需要完成约束注解的定义,对应的验证器的定义。下面来看看官方的实现。
典型的约束定义
// length约束,带参数
@Documented
@Constraint(
validatedBy = {}//这里没有指定相应的验证器,实际上在org.hibernate.validator.internal.metadata.core.ConstraintHelper初始化的时候,已经加上。
)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface Length {
int min() default 0;
int max() default 2147483647;
String message() default "{org.hibernate.validator.constraints.Length.message}";
Class>[] groups() default {};
Class extends Payload>[] payload() default {};
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface List {
Length[] value();
}
}
// notBlank约束,不带参数
@Documented
@Constraint(
validatedBy = {}
)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@ReportAsSingleViolation
@NotNull
public @interface NotBlank {
String message() default "{org.hibernate.validator.constraints.NotBlank.message}";
Class>[] groups() default {};
Class extends Payload>[] payload() default {};
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface List {
NotBlank[] value();
}
}
对应的校验器
// length的约束验证器
public class LengthValidator implements ConstraintValidator{
private static final Log log = LoggerFactory.make();
private int min;
private int max;
public LengthValidator(){
}
public void initialize(Length parameters){
this.min = parameters.min();
this.max = parameters.max();
this.validateParameters();
}
public boolean isValid(CharSequence value, ConstraintValidatorContext constraintValidatorContext){
if(value == null) {
return true;
} else {
int length = value.length();
return length >= this.min && length <= this.max;
}
}
private void validateParameters(){
if(this.min < 0) {
throw log.getMinCannotBeNegativeException();
} else if(this.max < 0){
throw log.getMaxCannotBeNegativeException();
} else if(this.max < this.min){
throw log.getLengthCannotBeNegativeException();
}
}
}
// notBlank的约束验证器
public class NotBlankValidator implements ConstraintValidator{
public NotBlankValidator(){
}
public void initialize(NotBlank annotation){
}
public boolean isValid(CharSequence charSequence, ConstraintValidatorContext constraintValidatorContext){
return charSequence == null?true:charSequence.toString().trim().length() > 0;
}
}
一个符合规范的约束注解至少应该包含message,group,payLoad等3个字段,message用于校验信息提示,group用于分组校验,payLoad常用来将一些元数据信息与该约束注解相关联,常用的一种情况是用负载表示验证结果的严重程度
@Target({ }) // 约束注解应用的目标元素类型
@Retention() // 约束注解应用的时机
@Constraint(validatedBy ={}) // 与约束注解关联的验证器
public @interface ConstraintName{
String message() default " "; // 约束注解验证时的输出消息
Class>[] groups() default { }; // 约束注解在验证时所属的组别
Class extends Payload>[] payload() default { }; // 约束注解的有效负载
}
一个验证器需要实现ConstraintValidator接口,如下。
public class ValidatorName implements ConstraintValidator{
public void initialize(ConstraintAnnotation constraintAnnotation){
//初始化验证器,参数为注解对象,可以将注解里面属性值注入到该对象供校验时使用。例如Length约束注解的max,min注解。
}
public boolean isValid(TargetValiValue targetValue, ConstraintValidatorContext constraintValidatorContext){
//校验逻辑,校验通过,返回true,校验不同过返回false,参数为校验目标的值,以及校验器的上下文,通过上下午可以获取验证更多内容
}
}
自定义校验示例
目标:定义一个校验手机号的的验证器。
1. 定义注解:
@Target({ElementType.FIELD,ElementType.METHOD,ElementType.PARAMETER})//申明注解的作用位置
@Retention(RetentionPolicy.RUNTIME)//运行时机
@Constraint(validatedBy={MobilePhoneValidator.class})//定义对应的校验器,自定义注解必须指定
public @interfaceMobileNumber {
String message() default "电话号码格式不正确";//错误提示信息默认值,可以使用el表达式。
Class>[] groups() default {};//约束注解在验证时所属的组别
Class extends Payload>[] payload() default {};//约束注解的有效负载
}
2. 定义验证器
public class MobilePhoneValidator implements ConstraintValidator{
private final Pattern mobilePhonePattern = Pattern.compile("1([\\d]{10})");
public void initialize(MobileNumber mobileNumber){
//do nothing
}
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext){
if(s == null){
return false;
}
return mobilePhonePattern.matcher(s).matches();
}
}
3. 使用
public class User{
@NotNull(message = "姓名不能为空")
private String name;
@Min(value = 1 ,message = "年龄不能小于0")
@NotNull(message = "age不能为空")
private Integer age;
@NotNull(message = "id不能为空")
private Integer id;
@MobileNumber(payload = MobilePayLoad.class)
private String phoneNumber;
//省略get set方法
}
//验证代码
public static void main(String[] args){
User user = new User();
user.setPhoneNumber("136896972");
validate(user);
}
static void validate(Object o){
ValidatorFactory vf = Validation.buildDefaultValidatorFactory();
Validator validator = vf.getValidator();
Set> set = validator.validate(o);
for (ConstraintViolation constraintViolation : set) {
System.out.println(constraintViolation.getPropertyPath()+":"+constraintViolation.getMessage());
}
}
JSR303常用特性介绍
1. 级联校验
上面有提到,当一个对象依赖另外一个对象在校验对象时需要校验管理对象就需要用到规范的级联特性。这个具体实现由bean validation来进行,我们只需要在关联字段上添加。@Vali注解即可。
2. 分组验证
试想这样的场景,我们定义了一个公共的类,在不同的业务方法里,我们我们需要对其进行不同的校验,比如在业务a,需要a约束,在业务b需要b约束。我们很自然的想到编写两套bean不就可以了,若这样做我们的bean类就不能重用了,好在JSR303已经支持分组验证了,我们在签名的约束注解定义中发现,有一个字段Class>[] groups() default {},该字段即用来做分组验证的。下面介绍如何使用。
public interface Group{
interface A{}
interface B{}
}
public class Person{
// Group.A.class为小孩约束
// Group.B.class为成人约束
@NotEmpty(groups = {Group.A.class,Group.B.class})
private String name;
@Min(value = 20,groups = Group.B.class)
@Max(value = 14,groups = Group.A.class)
private Integer age;
//省略get set方法
public static void main(String[] args){
ValidatorFactory vf = Validation.buildDefaultValidatorFactory();
Validator validator = vf.getValidator();
Person child = new Person();
child.setName("小明同学");
child.setAge(20);//20岁的小孩不合法,待会我们使用Group.A.class这一组约束进行验证
System.out.println("下面验证小明同学是不是是不是孩子");
Set> set = validator.validate(child, Group.A.class);
for (ConstraintViolation constraintViolation : set) {
System.out.println(constraintViolation.getPropertyPath()+":"+constraintViolation.getMessage());
}
System.out.println("下面验证怪叔叔是不是成人");
Person adult = new Person();
adult.setName("怪叔叔");
adult.setAge(18);//18岁不能叫叔叔,我们待会使用Group.B.class这一组进行验证
Set> set02 = validator.validate(adult, Group.B.class);
for (ConstraintViolation constraintViolation : set02) {
System.out.println(constraintViolation.getPropertyPath()+":"+constraintViolation.getMessage());
}
}
}
输出结果:
下面验证小明同学是不是是不是孩子
age:最大不能超过14
下面验证怪叔叔是不是成人
age:最小不能小于20
3.组序列验证
默认情况下,不同组别的约束验证是无序的,然而在某些情况下,约束验证的顺序却很重要,如下面两个例子:(1)第二个组中的约束验证依赖于一个稳定状态来运行,而这个稳定状态是由第一个组来进行验证的。(2)某个组的验证比较耗时,CPU 和内存的使用率相对比较大,最优的选择是将其放在最后进行验证。因此,在进行组验证的时候尚需提供一种有序的验证方式,这就提出了组序列的概念。一个组可以定义为其他组的序列,使用它进行验证的时候必须符合该序列规定的顺序。在使用组序列验证的时候,如果序列前边的组验证失败,则后面的组将不再给予验证。
@GroupSequence({Group.Default.class,Group.A.class,Group.B.class})
public interface Group{
interface Default{}
interface A{}
interface B{}
}
public class Name{
@NotBlank(groups = Group.Default.class)
private String firstName;
@NotBlank(groups = Group.A.class)
private String middleName;
@NotBlank(groups = Group.B.class)
private String lastName;
@NotBlank //没有指定组,当使用组验证时,忽略该字段
private String belongCompanyName;
//省略get set方法
public static void main(String[] args){
ValidatorFactory vf = Validation.buildDefaultValidatorFactory();
Validator validator = vf.getValidator();
Name name01 = new Name();
System.out.println("校验name01==========>");
Set> set01 = validator.validate(name01, Group.class);
for (ConstraintViolation constraintViolation : set01) {
System.out.println(constraintViolation.getPropertyPath()+":"+constraintViolation.getMessage());
}
Name name02 = new Name();
name02.setFirstName("firstName");
name02.setMiddleName("middleName");
System.out.println("校验name02===========>");
Set> set02 = validator.validate(name02, Group.class);
for (ConstraintViolation constraintViolation : set02) {
System.out.println(constraintViolation.getPropertyPath()+":"+constraintViolation.getMessage());
}
}
}
得到如下结果:
校验name01==========>
firstName:不能为空
校验name02===========>
lastName:不能为空
name01的firstName验证不通过,中断验证,后面验证被取消。
name02的firstName,middleName验证通过,lastName验证没通过
4. 组合约束
Bean Validation 规范允许将不同的约束进行组合来创建级别较高且功能较多的约束,从而避免原子级别约束的重复使用。
查看hibernate 中@NotEmpty约束
@Documented
@Constraint(
validatedBy = {}
)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@ReportAsSingleViolation
@NotNull
@Size(
min = 1
)
public @interface NotEmpty {
String message() default "{org.hibernate.validator.constraints.NotEmpty.message}";
Class>[] groups() default {};
Class extends Payload>[] payload() default {};
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface List {
NotEmpty[] value();
}
}
我们试图去找对应的NotEmptyValidator时,竟然找不到!再看看NotEmpty约束注解内容,发现有两个我们熟悉的注解@NotNull,@Size(min=1)。以上两个组合起来不正是NotEmpty的含义么?看到这里相信你已经组合约束是怎么回事了。实际使用中 @NotEmpty2 约束注解可以得到与 @NotEmpty 约束注解同样的验证结果。
the END .....
java jsr 303_java对象校验(validation)-JSR303规范相关推荐
- jsr 正则验证_Java数据校验(Bean Validation / JSR303)
#简介 JSR303是JAVA EE6中的子规范.用于对Java Bean的字段值进行校验,确保输入进来的数据在语义上是正确的,使验证逻辑从业务代码中脱离出来.JSR303是运行时数据验证框架,验证之 ...
- Java参数校验validation和validator区别
Java参数校验validation和validator区别 1. 参数校验概述 2. validation与validator区别 3. validation注解说明 4. validator注解说 ...
- java oval_Java对象校验框架Oval怎么使用 | 学步园
OVal是一个可扩展的Java对象数据验证框架,验证的规则可以通过配置文件.Annotation.POJOs进行设定.可以使用纯Java语言.JavaScript.Groovy.BeanShell等进 ...
- java自定义注解实现校验对象属性是否为空
前面学习了如何自定义一个注解:java如何优雅的自定义一个注解 下面来实战演示一下如何使用自定义注解做一些实际的功能.比如校验对象属性是否为空. 一.自定义一个NotNull注解 我们自定义一个Not ...
- 数据校验validation
概述 form表单传输到后端的数据,需要经过校验,前端的JS校验虽然可以涵盖大部分的校验职责,如生日格式,邮箱格式校验等.但是为了避免用户绕过浏览器,使用http/curl等工具直接向后端请求一些违法 ...
- Java虚拟机:对象创建过程与类加载机制、双亲委派模型
一.对象的创建过程: 1.对象的创建过程: 对象的创建过程一般是从 new 指令(JVM层面)开始的,整个创建过程如下: (1)首先检查 new 指令的参数是否能在常量池中定位到一个类的符号引用: ( ...
- Java 语法规定之外的命名注释规范
Java 语法规定之外的命名注释规范 命名规范 1. 项目名 2. 包名 3. 类名 4. 常量名 5. 变量名 6. 方法名 8. 其它命名技巧 9. 应当避免的行为 10. 经典的命名法 11. ...
- Java类加载及对象创建过程详解
类加载过程 类加载的五个过程:加载.验证.准备.解析.初始化. 加载 在加载阶段,虚拟机主要完成三件事: 通过一个类的全限定名来获取定义此类的二进制字节流. 将这个字节流所代表的静态存储结构转化为方法 ...
- finalize java,Java中Object对象finalize方法详细解析
简书:capo 转载请注明原创出处,谢谢! 前言: 今天我们来看看Object中一个经常被人遗忘的方法,finalize方法.老规矩,我们先看看Javadoc是怎样描述这个方法的 /** * Call ...
最新文章
- 023 判断出栈顺序是否正确
- Android chromium 1
- QT的QDBusPendingCallWatcher类的使用
- 使用完成端口监控文件目录的例子
- 高精度——A+B Problem(洛谷 P1601)
- 如何在JSP里添加删除cookie
- 推荐 ADO.NET Entity Framework (EDM) 相关技术文章
- (转)探寻区块链的源头——“重回拜占庭”
- STM32智能锁指纹锁密码锁WIFI远程开锁微信小程序临时密码源码PCBA方案
- Macromedia Flash 8 Video Encoder安装
- 什么音频剪辑软件好用?
- 如何正确撰写sci论文中的作者信息
- marked + mathjax 实现支持数学公式的 markdown 转 html
- 农村信用社计算机类资料,农村信用社笔试复习资料:计算机知识(3)
- 计算机专业有非全日制研究生,计算机专业有双证在职研究生吗?
- [leetcode 913] 猫和老鼠(博弈、dp)
- 令人头大的慢查询分析
- python调用golang dataframe_用Python获取摄像头并实时控制人脸
- php发送邮件封装类,使用nette/mail 封装一个发送邮件类 (通用)
- 5码默认版块_用字节码解释try、catch、finally、i++、++i的执行结果?