SpringMVC 使用验证框架 Bean Validation(上)

对于任何一个应用而言在客户端做的数据有效性验证都不是安全有效的,这时候就要求我们在开发的时候在服务端也对数据的有效性进行验证。 SpringMVC 自身对数据在服务端的校验(Hibernate Validator)有一个比较好的支持,它能将我们提交到服务端的数据按照我们事先的约定进行数据有效性验证,对于不合格的数据信息 SpringMVC 会把它保存在错误对象中(Errors接口的子类),这些错误信息我们也可以通过 SpringMVC 提供的标签(form:errors)在前端JSP页面上进行展示。或者使用拦截器 after 方法对处理错误信息进行处理后传递给页面(我们使用JSON请求的时候就需要这样做)。

本文来介绍,如何在 SpringMVC 中进行 Validator 的使用。

一、添加POM依赖

<!-- Hibernate Validator -->

<dependency>

<groupId>org.hibernate</groupId>

<artifactId>hibernate-validator</artifactId>

</dependency>

二、配置要验证的实体

public class ValidatorTest {

// message 直接提供错误信息

@NotNull(message = "username 不能为空")

// message 使用 {} 代表错误内容,从 resources 目录下的 ValidationMessages.properties 文件中读取

@Pattern(regexp = "[a-zA-Z0-9_]{5,10}", message = "{user.username.illegal}")

private String username;

@Size(min = 5, max = 10, message = "{password.length.illegal}")

private String password;

// 省略 get\set

}

ValidationMessages.properties 文件内容:

user.username.illegal=用户名格式不正确

password.length.illegal=密码[${validatedValue}]长度必须为{min}到{max}个字符

其中${validatedValue} 用来获取预校验属性的值。

{min} 和 {max} 用来读取 @Size 注解中对应的属性值。

你还可以像 ${max > 1 ? '大于1' : '小于等于1'} 这样使用el表达式。

另外我们还可以拿到一个java.util.Formatter类型的formatter变量进行格式化:

${formatter.format("%04d", min)}

如果EL表达式不起作用,可以添加如下依赖尝试,如果没有问题请忽略

<dependency>

<groupId>javax.el</groupId>

<artifactId>javax.el-api</artifactId>

<version>2.2.4</version>

<scope>provided</scope>

</dependency>

内置的验证约束注解 
内置的验证约束注解如下表所示(摘自hibernate validator reference):

验证注解

验证的数据类型

说明

@AssertFalse

Boolean,boolean

验证注解的元素值是false

@AssertTrue

Boolean,boolean

验证注解的元素值是true

@NotNull

任意类型

验证注解的元素值不是null

@Null

任意类型

验证注解的元素值是null

@MIN(value=值)

BigDecimal,BigInteger, byte,short, int, long,等任何Number或CharSequence(存储的是数字)子类型

验证注解的元素值大于等于@Min指定的value值

@MAX(value=值)

和@Min要求一样

验证注解的元素值小于等于@Max指定的value值

@DecimalMin(value=值)

和@Min要求一样

验证注解的元素值大于等于@ DecimalMin指定的value值

@DecimalMax(value=值)

和@Min要求一样

验证注解的元素值小于等于@ DecimalMax指定的value值

@Digits(integer=整数位数, fraction=小数位数)

和@Min要求一样

验证注解的元素值的整数位数和小数位数上限

@Size(min=下限, max=上限)

字符串、Collection、Map、数组等

验证注解的元素值的在min和max(包含)指定区间之内,如字符长度、集合大小

@Past

java.util.Date,java.util.Calendar;Joda
Time类库的日期类型

验证注解的元素值(日期类型)比当前时间早

@Future

与@Past要求一样

验证注解的元素值(日期类型)比当前时间晚

@NotBlank

CharSequence子类型

验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的首位空格

@Length(min=下限, max=上限)

CharSequence子类型

验证注解的元素值长度在min和max区间内

@NotEmpty

CharSequence子类型、Collection、Map、数组

验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0)

@Range(min=最小值, max=最大值)

BigDecimal,BigInteger,CharSequence, byte,
short, int, long等原子类型和包装类型

验证注解的元素值在最小值和最大值之间

@Email(regexp=正则表达式,flag=标志的模式)

CharSequence子类型(如String)

验证注解的元素值是Email,也可以通过regexp和flag指定自定义的email格式

@Pattern(regexp=正则表达式,flag=标志的模式)

String,任何CharSequence的子类型

验证注解的元素值与指定的正则表达式匹配

@Valid

任何非原子类型

指定递归验证关联的对象;如用户对象中有个地址对象属性,如果想在验证用户对象时一起验证地址对象的话,在地址对象上加@Valid注解即可级联验证

三、Controller 实体验证与视图错误信息的展示

JSP 页面:

错误信息使用 form:errors 展示,这个标签,必须放在 form:form 中使用。

<%@ page
language="java" pageEncoding="UTF-8"%>

<%@ taglib
prefix="form" uri="http://www.springframework.org/tags/form" %>

<!DOCTYPE
HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>

<head>

<title>Java验证框架测试</title>

</head>

<body>

<form:form method="post" modelAttribute="testModel" action="${pageContext.request.contextPath }/validator/test3">

<h1><form:errors path="username" /></h1><!-- path的值可以为 * ,表示显示所有错误 -->

<h1><form:errors path="password" /></h1>

<form:input path="username" /><br/>

<form:input path="password" /><br/>

<input type="submit" value="提交"/>

</form:form>

</body>

</html>

对应的 Controller 的方法:

@Controller

@RequestMapping("/validator")

public class ValidatorController {

/**

* 响应到JSP页面

*

* @param test

* @param result

*     
这里的BindingResult必须紧挨着@Valid参数的,即必须紧挨着需要校验的参数,

*     
这就意味着我们有多少个@Valid参数就需要有多少个对应的Errors参数,它们是一一对应的。

* @return

* @author SHANHY

* @create  2016年4月14日

*/

@RequestMapping("/test3")

public String test3(@Valid @ModelAttribute("testModel") ValidatorTest test,

BindingResult result, Model model){

model.addAttribute("test", test);

if(result.hasErrors())

return "validator1";

return "validator2";

}

}


四、Controller 普通参数验证与视图错误信息的展示

对于 form 表单提交绑定到对象的验证方式,上面已经介绍了。但是在很多时候,我们是通过普通传参来调用接口的。 
比如:http://localhost:8080/myproject/hello?name=Shanhy&age=27&password=pwd 
那么对于这种情况,我们该如何校验 name、age、password 的值呢?

看下面的 Controller 代码:

五、JSON 请求响应错误信息

package org.springboot.sample.interceptor;

import java.util.HashMap;

import java.util.Map;

import java.util.Map.Entry;

import java.util.Set;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import javax.validation.ConstraintViolation;

import javax.validation.ConstraintViolationException;

import org.springframework.validation.BindingResult;

import org.springframework.validation.FieldError;

import org.springframework.web.bind.annotation.ResponseBody;

import org.springframework.web.bind.annotation.RestController;

import org.springframework.web.method.HandlerMethod;

import org.springframework.web.servlet.HandlerInterceptor;

import org.springframework.web.servlet.ModelAndView;

/**

* JSON请求响应错误消息处理

*

* @author 单红宇(365384722)

* @myblog http://blog.csdn.net/catoop/

* @create 2016年4月17日

*/

public class JsonErrorMsgInterceptor implements HandlerInterceptor {

@Override

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)

throws Exception {

return true;

}

@Override

public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,

ModelAndView modelAndView) throws Exception {

if(modelAndView == null)

return;

// 因为MappingJackson2JsonView默认会把BindingResult全部过滤掉。

// 所以我们要想将错误消息输出,要在这里自己处理好。

// 判断请求是否是.json、方法上是否有@ResponseBody注解,或者类上面是否有@RestController注解

// 表示为json请求

if (!request.getRequestURI().endsWith(".json")) {

HandlerMethod handlerMethod =
(HandlerMethod)handler;

if(handlerMethod.getMethodAnnotation(ResponseBody.class)
== null){

if(handlerMethod.getBeanType().getAnnotation(RestController.class)
== null){

return;

}

}

}

Map<String, Object> modelMap = modelAndView.getModel();

if (modelMap != null) {

Map<String, String> errorMsg
= null;

if(modelMap.containsKey("errorMsg")){

errorMsg = (Map<String,
String>)modelMap.get("errorMsg");

}

if(errorMsg == null){

errorMsg = new HashMap<>();

modelMap.put("errorMsg", errorMsg);

}

for (Entry<String, Object> entry :
modelMap.entrySet()) {

if (entry.getValue() instanceof BindingResult) {

BindingResult bindingResult
= (BindingResult) entry.getValue();

if (bindingResult.hasErrors()) {

for (FieldError fieldError :
bindingResult.getFieldErrors()) {

errorMsg.put(fieldError.getObjectName()
+ "." + fieldError.getField(),

fieldError.getDefaultMessage());

}

}

}

}

}

}

@Override

public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex)

throws Exception {

}

}

注意配置该拦截器配置,使其生效。

效果:

六、错误信息的配置文件

默认在 ValidationMessages.properties 配置文件中(参考 《SpringMVC 使用验证框架 Bean Validation(上)》中的第二点有样例)。

如果想统一使用 messaeg.properties 配置文件中的配置,下面的配置提供参考:

<!-- 指定自己定义的validator -->

<mvc:annotation-driven validator="validator"/>

<!-- 以下 validator  ConversionService 在使用 mvc:annotation-driven 会 自动注册-->

<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">

<property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>

<!-- 如果不加默认到 使用classpath下的 ValidationMessages.properties -->

<property name="validationMessageSource" ref="messageSource"/>

</bean>

<!-- 国际化的消息资源文件(本系统中主要用于显示/错误消息定制) -->

<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">

<property name="basenames">

<list>

<!-- 在web环境中一定要定位到classpath 否则默认到当前web应用下找  -->

<value>classpath:messages</value>

<value>classpath:org/hibernate/validator/ValidationMessages</value>

</list>

</property>

<property name="useCodeAsDefaultMessage" value="false"/>

<property name="defaultEncoding" value="UTF-8"/>

<property name="cacheSeconds" value="60"/>

</bean>

七、错误信息中使用EL表达式

请参考 《SpringMVC 使用验证框架 Bean Validation(上)》中的第二点。

八、方法级别的验证

首先注册 MethodValidationPostProcessor

<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor">

<!-- 可以引用自己的 validator 配置,在本文中(下面)可以找到 validator 的参考配置,如果不指定则系统使用默认的 -->

<property name="validator" ref="validator"/>

</bean>

某个 Service 的方法:

@Validated // 告诉MethodValidationPostProcessor此Bean需要开启方法级别验证支持

@Service

public class ValidatorTestService {

public @Length(min = 12, max = 16, message = "返回值长度应该为12-16")

String getContent(

@NotBlank(message = "name不能为空")

String name,

@Size(min = 5, max = 10, message="{password.length.illegal}")

String password) {

return name + ":" + password;

}

}

在 Controller 调用该方法测试:

/**

* 测试方法级别的验证(如果验证失败,则会抛出异常 ConstraintViolationException)

*

* @param name

* @param model

* @return

* @author SHANHY

* @create  2016年4月17日

*/

@RequestMapping("/test5")

@ResponseBody

public Model test5(String name, String password, Model model){

try {

String content =
validatorTestService.getContent(name, password);

model.addAttribute("name", content);

} catch (ConstraintViolationException e) {

addErrorMessage(model, e);

}

return model;

}

/**

* 添加错误消息,建议将该方法提取为一个公共的方法使用。

*

* @param model

* @param e

* @author SHANHY

* @create  2016年5月4日

*/

protected void addErrorMessage(Model model, ConstraintViolationException e){

Map<String, String> errorMsg = new HashMap<>();

model.addAttribute("errorMsg", errorMsg);

for (ConstraintViolation<?>
constraintViolation : e.getConstraintViolations()) {

// 获得验证失败的类 constraintViolation.getLeafBean()

// 获得验证失败的值 constraintViolation.getInvalidValue()

// 获取参数值 constraintViolation.getExecutableParameters()

// 获得返回值 constraintViolation.getExecutableReturnValue()

errorMsg.put(constraintViolation.getLeafBean().getClass().getName()
+ "-" + constraintViolation.getPropertyPath().toString(),
constraintViolation.getMessage());

}

}


工具类

最后提供一个校验工具类,注意看第一个方法就行了:

/**

* Copyright (c) 2005-2012 springside.org.cn

*/

package org.springboot.sample.util;

import java.util.ArrayList;

import java.util.HashMap;

import java.util.List;

import java.util.Map;

import java.util.Set;

import javax.validation.ConstraintViolation;

import javax.validation.ConstraintViolationException;

import javax.validation.Validator;

/**

* JSR303 Validator(Hibernate Validator)工具类.

*

* ConstraintViolation中包含propertyPath, message 和invalidValue等信息.

* 提供了各种convert方法,适合不同的i18n需求:

* 1. List<String>, String内容为message

* 2. List<String>, String内容为propertyPath + separator + message

* 3. Map<propertyPath, message>

*

* 详情见wiki:
https://github.com/springside/springside4/wiki/HibernateValidator

*/

public class BeanValidatorUtils {

/**

* 调用JSR303的validate方法, 验证失败时抛出ConstraintViolationException.

*

* 参数 Validator 可以直接注入,如:

*

* @Autowired

* protected Validator validator;

*

*/

public static void validateWithException(Validator validator, Object object, Class<?>... groups)

throws ConstraintViolationException {

Set<? extends
ConstraintViolation<?>> constraintViolations =
validator.validate(object, groups);

if (!constraintViolations.isEmpty()) {

throw new
ConstraintViolationException(constraintViolations);

// 调用处捕获异常后,获取错误信息的方法

// List<String> list =
BeanValidatorUtils.extractPropertyAndMessageAsList(ex, ": ");

}

}

/**

* 辅助方法, 转换ConstraintViolationException中的Set<ConstraintViolations>中为List<message>.

*/

public static List<String> extractMessage(ConstraintViolationException e) {

return
extractMessage(e.getConstraintViolations());

}

/**

* 辅助方法, 转换Set<ConstraintViolation>为List<message>

*/

@SuppressWarnings("rawtypes")

public static List<String> extractMessage(Set<? extends ConstraintViolation> constraintViolations) {

List<String> errorMessages = new ArrayList<String>();

for (ConstraintViolation violation :
constraintViolations) {

errorMessages.add(violation.getMessage());

}

return errorMessages;

}

/**

* 辅助方法, 转换ConstraintViolationException中的Set<ConstraintViolations>为Map<property, message>.

*/

public static Map<String, String> extractPropertyAndMessage(ConstraintViolationException e) {

return
extractPropertyAndMessage(e.getConstraintViolations());

}

/**

* 辅助方法, 转换Set<ConstraintViolation>为Map<property, message>.

*/

@SuppressWarnings("rawtypes")

public static Map<String, String> extractPropertyAndMessage(Set<? extends ConstraintViolation> constraintViolations) {

Map<String, String> errorMessages
= new HashMap<String, String>();

for (ConstraintViolation violation :
constraintViolations) {

errorMessages.put(violation.getPropertyPath().toString(),
violation.getMessage());

}

return errorMessages;

}

/**

* 辅助方法, 转换ConstraintViolationException中的Set<ConstraintViolations>为List<propertyPath message>.

*/

public static List<String> extractPropertyAndMessageAsList(ConstraintViolationException e) {

return
extractPropertyAndMessageAsList(e.getConstraintViolations(), " ");

}

/**

* 辅助方法, 转换Set<ConstraintViolations>为List<propertyPath
message>.

*/

@SuppressWarnings("rawtypes")

public static List<String> extractPropertyAndMessageAsList(Set<? extends ConstraintViolation> constraintViolations) {

return
extractPropertyAndMessageAsList(constraintViolations, " ");

}

/**

* 辅助方法, 转换ConstraintViolationException中的Set<ConstraintViolations>为List<propertyPath +separator+ message>.

*/

public static List<String> extractPropertyAndMessageAsList(ConstraintViolationException e, String separator) {

return extractPropertyAndMessageAsList(e.getConstraintViolations(),
separator);

}

/**

* 辅助方法, 转换Set<ConstraintViolation>为List<propertyPath +separator+
message>.

*/

@SuppressWarnings("rawtypes")

public static List<String> extractPropertyAndMessageAsList(Set<? extends ConstraintViolation> constraintViolations,

String separator) {

List<String> errorMessages = new ArrayList<String>();

for (ConstraintViolation violation :
constraintViolations) {

errorMessages.add(violation.getPropertyPath()
+ separator + violation.getMessage());

}

return errorMessages;

}

}

SpringMVC 使用验证框架 Bean Validation(上)的更多相关文章

  1. jquery 悬浮验证框架 jQuery Validation Engine

    中文api 地址  http://code.ciaoca.com/jquery/validation-engine/   和bootstarp 一起使用不会像easyui  验证那样生硬 修改版 原版 ...

  2. Spring3.1 对Bean Validation规范的新支持(方法级别验证)

    上接Spring提供的BeanPostProcessor的扩展点-1继续学习. 一.Bean Validation框架简介 写道Bean Validation standardizes constra ...

  3. JSR303 Bean Validation 技术规范特性概述

    概述 Bean Validation 规范 Bean 是 Java Bean 的缩写,在 Java 分层架构的实际应用中,从表示层到持久化层,每一层都需要对 Java Bean 进行业务符合性验证,如 ...

  4. SpringMVC数据验证(AOP处理Errors和方法验证)

    什么是JSR303? JSR 303 – Bean Validation 是一个数据验证的规范,2009 年 11 月确定最终方案. Hibernate Validator 是 Bean Valida ...

  5. ASP.NET MVC验证框架中关于属性标记的通用扩展方法

    http://www.cnblogs.com/wlb/archive/2009/12/01/1614209.html 之前写过一篇文章<ASP.NET MVC中的验证>,唯一的遗憾就是在使 ...

  6. bean validation 技术规范

    Bean Validation 技术规范特性概述 张 冠楠 和 陈 志娴2011 年 3 月 24 日发布 WeiboGoogle+用电子邮件发送本页面 2 概述 Bean Validation 规范 ...

  7. 3. 站在使用层面,Bean Validation这些标准接口你需要烂熟于胸

    乔丹是我听过的篮球之神,科比是我亲眼见过的篮球之神.本文已被 https://www.yourbatman.cn 收录,里面一并有Spring技术栈.MyBatis.JVM.中间件等小而美的专栏供以免 ...

  8. ssm框架实现图片上传显示并保存地址到数据库

    本案例是通过springmvc+spring+mybatis框架以商品上传为例,实现的图片上传功能,并把图片的地址保存到数据库并在前台显示上传的图片. 本项目是使用maven搭建的项目,首先看下项目结 ...

  9. Java参数验证Bean Validation 框架

    1.为什么要做参数校验? 参数校验和业务逻辑代码分离,参数校验代码复用,统一参数校验方式.校验不太通过时统一异常描述. 2.bean validation规范 JSR303 规范(Bean Valid ...

随机推荐

  1. day052 django第三天 url和视图

    一.基本格式 from django.conf.urls import url from . import views #循环urlpatterns,找到对应的函数执行,匹配上一个路径就找到对应的函数 ...

  2. SVN简单使用

    如果是window操作系统,默认安装.右键菜单就会有显示SVN 如果已经配置好SVN,直接确定既可以检出. 如果没有配置,那么会显示下面的验证: 输入用户名和密码即可

  3. forward reference前向引用,gloal values and local values全局变量和局部变量,recursive function递归函数

    1.全局变量与局部变量练习 1 # -*- coding: UTF-8 -*- 2 def bar(): 3 print('from bar') 4 def foo(): 5 print('from ...

  4. debian删除i386的包

    sudo apt-get remove --purge `dpkg --get-selections | grep i386 | awk '{print $1}'`; sudo dpkg --remo ...

  5. python http请求及多线程应用

    目录 概述 tomorrow包准备 运行环境遇到的问题 其他尝试未果 概述 今天, 使用python3 模拟下发包, http get 请求, 然后 采用tomorrow 多线程. 代码如下: # c ...

  6. 用户名、密码等15个常用的js正则表达式

    本文收集整理了15个常用的javaScript正则表达式,其中包括用户名.密码强度.整数.数字.电子邮件地址(Email).手机号码.身份证号.URL地址. IPv4地址. 十六进制颜色. 日期. Q ...

  7. atnodes命令使用方法

    一条命令可以同时执行多台机器,结果会输出列表. atnodes -L 'grep -c "查询订单列表,userId=bing.wang03" /home/w/www/order- ...

  8. c++ :

    (1)表示机构内位域的定义(即该变量占几个bit空间)   typedef struct _XXX{ unsigned char a:4; unsigned char c; } ; XXX

  9. SpringBoot 之热部署

    默认情况下, 我们修改 class 或者 修改模板文件(templates目录 下面的文件) 等动态资源, 都不会立即自动生效. 在IDEA中, 我通过Ctrl + F9 , 仍然是无效. 当然, 静 ...

  10. spring boot 错误处理总结

    在boot 中, 对404  和 异常 有了额外的处理. 当然,我们可以定制, 如何做呢? 1 写一个继承 ErrorController 的Controller 注意, 这里一定要继承 ErrorC ...