Spring @Valid
@Valid基本用法
强烈推荐如果要学习@Valid JSR303, 建议看这里的API Bean Validation规范 !
Controller控制器中在需要校验的实体类上添加 @Valid 即可使用JSR303校验(前提记得添加hibernate-validator相关jar,<mvc:annotation-driven/>);
modelMap是为了将校验失败信息写回到request属性中返回给JSP页面展示
@RequestMapping("/demo2")
public String test2(@Valid User user, BindingResult result, ModelMap modelMap){
System.out.println(user);
List<FieldError> fieldErrors = result.getFieldErrors();
for (FieldError e:fieldErrors) {
System.out.println(e.getDefaultMessage()); //验证不通过的信息
System.out.println(e.getField());
modelMap.addAttribute(e.getField(),e.getDefaultMessage());
}
return "test";
}
校验的实体类User
@Setter
@Getter
@ToString
public class User {
@NotBlank
private String name;
@Min(1)
@Max(120)
private int age; public User(String name, int age) {
this.name = name;
this.age = age;
} public User() {
}
}
浏览器输入localhost:8090/binding/demo2?name=lvbinbin&age=150, 结果校验不通过

从上述用例看出来,我们没有指定message属性,默认校验不通过的提示消息 最大不能超过120 , 该信息是在hibernate-Validator.jar的ValidationMessages.properties中定义;
如果想要自定义校验不通过信息,我们可以指定message属性
@Min(value = 1,message = "年龄大于一岁")
@Max(value = 120,message = "常人活不到120岁")
private int age;

突然考虑到问题,国际化的问题由于对国际化没有过了解,我理解的国际化问题就是,请求头信息包含的地区信息Accpet-Language可以判断当前需要中文还是英文,于是有了下面进一步的改善;
Hibernate默认会查找classPath下的ValidationMessages.properties文件,我们只需要将国际化校验文件在classpath下添加即可。
classpath下添加ValidationMessages_en.properties (英文校验失败信息)
myValidation.min=can not be lower than {value}
myValidation.max=can not be bigger than {value}
age=age
classpath下添加ValidationMessages_zh.properties (中文校验失败信息)
myValidation.min=不能小于{value}
myValidation.max=不能大于{value}
age=年龄
在注解验证的message属性用{}来取ValidationMessages中的值
@Min(value = 1,message = "{age}{myValidation.min}")
@Max(value = 120,message = "{age}{myValidation.max}")
private int age;
使用POSTMAN模拟中文、英文测试一下:
英文测试:请求头Accpet-Language:en-Us , 结果的确是英文

中文测试:请求头Accpet-Language:zh-CN, 结果发现乱码问题

乱码问题解决方案:自定义Validator注册到SpringMvc中,指定国际化资源文件编码为UTF-8
<mvc:annotation-driven validator="validator"/>
<bean id="validator" class="org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean">
<property name="validationMessageSource" ref="messageSource"/>
<property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
</bean>
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basenames">
<list>
<value>classpath:ValidationMessages</value><!--国际化资源地址-->
</list>
</property>
<property name="defaultEncoding" value="UTF-8"/>
<property name="cacheSeconds" value="120"/>
</bean>
再次测试中文,就不存在问题,同样中文也是没有问题的

校验不通过返回给前端两种方案
方案一.存到request属性中,在前端视图JSP 等渲染
@RequestMapping("/demo2")
public String test2(@Valid User user, BindingResult result, ModelMap modelMap){
System.out.println(user);
List<FieldError> fieldErrors = result.getFieldErrors();
for (FieldError e:fieldErrors) {
System.out.println(e.getDefaultMessage()); //验证不通过的信息
System.out.println(e.getField());
modelMap.addAttribute(e.getField(),e.getDefaultMessage());
}
return "test";
}
方案二.校验不通过返回异常信息JSON串给前端
通过查看抛出异常信息,Spring4.3.0校验@Valid不通过抛出异常信息为BindException,捕获该种异常返回JSON,异常捕获方式见我的博客。
@ExceptionHandler(value = {BindException.class})
public ResponseEntity invalidArgument(BindException ex){
Map result=new HashMap<String,Object>();
result.put("status_code",500);
System.out.println("捕获到异常");
List<FieldError> fieldErrors = ex.getFieldErrors();
StringBuffer sb=new StringBuffer();
for (FieldError error:fieldErrors) {
sb.append(error.getDefaultMessage());
}
result.put("message",sb.toString());
return new ResponseEntity(result, HttpStatus.INTERNAL_SERVER_ERROR);
}
补充说明:@RequestMapping方法中你写了参数 BindingResult就代表告诉Spring 我自己来处理异常,你别管了,这种情况程序不会抛出异常;所以方式一程序是不会抛出异常。
顺带提及Spring扩展JSR303的注解@Validated
个人对于为什么会存在@Validated注解的看法:
@Valid功能很丰富,有幸搜索到这样一篇典范API Bean Validation技术规范,弊病是@Valid的组、组顺序功能,需要对Spring、JavaxValidation有一定基础,不够简易上手,在此基础上Spring封装了@Validated来完成 组校验、组顺序校验的功能,我们只需要一个@Validated(value={xxx.class})即可指定组,对于我们来说不能在方便了! 以上就是个人对于@Validated存在的合理性分析,这里看来存在是合理的!
假设这样一个情景介绍@Validate 里组的概念,也可以看Bean Validation技术规范里的介绍;
@RequestMapping("/demo3")
public String test3(@Validated Item item){
System.out.println(item);
return "test";
}
@ExceptionHandler(value = {BindException.class})
public ResponseEntity invalidArgument(BindException ex){
Map result=new HashMap<String,Object>();
result.put("status_code",500);
System.out.println("捕获到异常");
List<FieldError> fieldErrors = ex.getFieldErrors();
StringBuffer sb=new StringBuffer();
for (FieldError error:fieldErrors) {
sb.append(error.getDefaultMessage()).append(",");
}
result.put("message",sb.substring(0,sb.length()-1));
return new ResponseEntity(result, HttpStatus.INTERNAL_SERVER_ERROR);
}
校验实体类Item
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Item {
@NotBlank(message = "商品名称不建议为空")
private String name;
@DecimalMin(value = "0.5",message = "商品价格小于0.5")
private double price;
@Past(message = "生产日期伪冒")
@NotNull(message = "生产日期不能不报")
private Date produceDate; }
尝试不输入任何属性,果然三个校验都没有通过;

对了,有个日期类型参数,这里就简单用@InitBinder解决一下子吧,在@Controller里添加方法:这样就可以将String转换成Date类型参数了.
@InitBinder
public void registryStringToDate(DataBinder binder){
binder.registerCustomEditor(Date.class,new CustomDateEditor(new SimpleDateFormat("yyyy/MM/dd"),true));
}
再次测试,没有问题了,我们就可以开始介绍 组校验的方式了

比如现在只需要校验商品名字,其他的价格、日期都不需要管了:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Item {
@NotBlank(message = "商品名称不建议为空",groups = {ItemNameValid.class})
private String name;
@DecimalMin(value = "0.5",message = "商品价格小于0.5",groups = {ItemPriceValid.class})
private double price;
@Past(message = "生产日期伪冒")
@NotNull(message = "生产日期不能不报")
private Date produceDate; public static interface ItemNameValid{} public static interface ItemPriceValid extends ItemNameValid{}
}
@Controller写法:
@RequestMapping("/demo3")
public String test3(@Validated({Item.ItemNameValid.class}) Item item){
System.out.println(item);
return "test";
}
@Validated注解中value指定某个且必须是接口类型,ItemNameValid组校验时候只校验name属性,ItemPriceValid 组校验时候会校验name和price组;


级联验证方式:
Item类添加属性ItemProp
@Valid
@NotNull(message=”产品属性不能为空”)
private ItemProp prop;
@Setter
@Getter
@NoArgsConstructor
public class ItemProp {
@Pattern(regexp = "^白色$",message = "小布丁只能是白色的")
@NotNull
private String color;
@NotBlank(message = "如实填报产地")
private String Location;
}


注意:@Valid添加到级联属性上完成验证,前提是: 如果级联的属性没有初始化new,且是必须验证的项,@Valid下面跟上@NotNull才能级联验证,否则根本不去校验ItemProp属性.
总结:@Valid和@Validated异同
@Valid可以用来作为级联属性校验,@Validated没这个功能;级联校验时Bean Validation的特性,而非Spring特性.
@Validated扩展JSR303,可以用来指定校验组验证,且只见过标注在@RequestMapping方法需要校验的入参中;
除了使用Bean Validation规范来完成JavaBean校验,Spring另外提供一个接口Validator,供我们实现复杂校验逻辑。 下面完成了一个简单的Person入参校验,使用Spring的Validator实现
@Controller
@RequestMapping("/valid")
public class ValidateController { @RequestMapping("/demo1")
public String demo1(@Valid Person person){ //此处@valid不能省略,@Validated也一样使用,作用标识person开启校验
System.out.println(person);
return "test";
} @InitBinder
public void register(DataBinder binder){
binder.setValidator(new PersonValidator());//替换原有validator;
// binder.addValidators(new PersonValidator()); //在原有validator基础上添加
} @Setter
@Getter
@ToString
@NoArgsConstructor
private static class Person{
String name;
int age;
} private static class PersonValidator implements Validator{
@Override
public boolean supports(Class<?> clazz) {
System.out.println(clazz==Person.class);
return clazz==Person.class;
} @Override
public void validate(Object target, Errors errors) {
//validate手动就需要校验
System.out.println("validate");
Person person = (Person) target;
if (null==person.getName()||person.getName().isEmpty()) {
errors.rejectValue("name", "field.empty",new Object[]{person.getName()}, "用户名不得为空");
}
if(person.getAge()==0||person.getAge()>150){
errors.rejectValue("age", "field.max",new Object[]{person.getAge()}, "用户年龄虚假");
} }
}
//异常捕获,目的:返回JSON给前端,可以设置成全局的异常捕获结合@ControllerAdvice
@ExceptionHandler(value = {BindException.class})
public ResponseEntity invalidArgument(BindException ex){
Map result=new HashMap<String,Object>();
result.put("status_code",500);
System.out.println("捕获到异常");
List<FieldError> fieldErrors = ex.getFieldErrors();
StringBuffer sb=new StringBuffer();
for (FieldError error:fieldErrors) {
sb.append(error.getField()+":"+error.getDefaultMessage()).append(",");
} result.put("message",sb.substring(0,sb.length()-1));
return new ResponseEntity(result, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
测试效果图:

说明:其中需要注意如果多种参数校验,一定要注意binder.addValidator(….)方法,它只是添加自定义的Validator实现类,比如一个实体类有很多String字段,避免在Validator实现类中重复地判断不为空,结合@NotEmpty等会节约篇幅,有利于代码整洁。
Spring @Valid的更多相关文章
- spring @valid 注解
用于验证注解是否符合要求,直接加在变量之前,在变量中添加验证信息的要求,当不符合要求时就会在方法中返回message 的错误提示信息. @PostMapping public User create ...
- Spring @Valid 和 @Validated 的区别和使用
两者区别 @Valid @Validated 标准 标准JSR-303规范 增强JSR-303规范 包 javax.validation org.springframework.validation ...
- 自定义spring valid方式实现验证
推荐:http://blog.csdn.net/xulianboblog/article/details/51694924
- spring Valid @Pattern 常见的验证表达式
1 匹配首尾空格的正则表达式:(^\s*)|(\s*$) 2 整数或者小数:^[0-9]+\.{0,1}[0-9]{0,2}$ 3 只能输入数字:"^[0-9]*$". 4 只 ...
- Spring - Spring 常用注解
概述 简单整理一些 Spring 的注解 这个算是一个 水一波 类型的整理 内容不全 分类可能有的地方不会太符合逻辑 而且时间也不太充裕 先把自己想写的写下来, 然后随缘整理吧 约定 版本 Sprin ...
- 《Spring全局异常处理》从零掌握@ControllerAdvice注解
一.开门见山 在前后端分离框架的大趋势下,前后端基本的职责已经确定. 前端主要负责界面的处理以及基本的判空检验.数据来源则通过vue调用后端发布的接口. 后端的原型还是mvc的模式: controll ...
- 利用ajax获取网页表单数据,并存储到数据库之二(使用SSH)
上篇介绍了如何使用JDBC链接ORACLE数据库实现对数据库的增删改查,本例是使用框架SSH来对数据库的数据进行操作. 首先说框架,现在流行的框架很多,如Struts.Hibernate.Spring ...
- spring整合hibernate的时候报异常org.hibernate.HibernateException: createQuery is not valid without active transaction
在整合Spring4+hibernate4时候,当代码执行到dao中CRUD操作时,报了一个异常, org.hibernate.HibernateException: createQuery is n ...
- SPRING IN ACTION 第4版笔记-第五章BUILDING SPRING WEB APPLICATIONS-007-表单验证@Valid、Error
一. Starting with Spring 3.0, Spring supports the Java Validation API in Spring MVC . No extra config ...
随机推荐
- Android学习之可滑动当前的Activity视图看见上一个活动的视图
先看一下我的效果图吧: 好大的图啊!!! 百度音乐由一个很酷的功能,当前的UI可以滑动,然后看见上一个活动的UI,当时顿时觉得百度的牛人好多啊,能将如此前沿的技术应用到app上.当然如果你熟悉了And ...
- cnn公式推导
CNN公式推导 1 前言 在看此blog之前,请确保已经看懂我的前两篇blog[深度学习笔记1(卷积神经网络)]和[BP算法与公式推导].并且已经看过文献[1]的论文[Notes on Convolu ...
- JS学习笔记6_事件
1.事件冒泡 由内而外的事件传播(从屏幕里飞出来一支箭的感觉) 2.事件捕获 由表及里的事件传播(力透纸背的感觉) 3.DOM事件流(DOM2级) 事件捕获阶段 -> 处于目标阶段 -> ...
- C# if else 使物体在X轴循环移动
if( transform.position.x > -15 && transform.rotation.y == 0 ) { //小鸟X轴反方向移动速度 transform.p ...
- SignalR2结合ujtopo实现拓扑图动态变化
上一篇文章基于jTopo的拓扑图设计工具库ujtopo,介绍了拓扑设计工具,这一篇我们使用SignalR2结合ujtopo实现拓扑图的动态变化. 仅仅作为演示,之前的文章SignalR2简易数据看板演 ...
- MVVM Light 新手入门(1):准备阶段
1.新建WPF空白项目. 2.NuGet 程序包中安装 3.根据MVVM分层结构,建立包含Model.View.ViewModel三层文件夹 如图: 1.View负责前端展示,与ViewModel进行 ...
- Python 模块之wxpython 的应用
第一个应用程序:“Hello World” 作为传统,我们首先将要写一个小的“Hello World”程序,下面是他的代码: #!/usr/bin/env python import wx app = ...
- 07_python_集合深浅拷贝
一.join li = ["李嘉诚", "麻花藤", "林海峰", "刘嘉玲"] s = "_".j ...
- safe close tcp connection
https://blog.netherlabs.nl/articles/2009/01/18/the-ultimate-so_linger-page-or-why-is-my-tcp-not-reli ...
- Color the ball(HDU1556)树状数组
每次对区间内气球进行一次染色,求n次操作后后所有气球染色次数. 树状数组,上下区间更新都可以,差别不大. 1.对于[x,y]区间,对第x-1位减1,第y位加1,之后向上统计 #include<b ...