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. Docker的网络类型和固定IP设置

    Docker的网络机制 Docker的网络有三种类型(driver): bridge, host 和 null. birdge: 就如同桥接的switch/hub, 使用bridge网络的contai ...

  2. PythonStudy——高级语言 High-level programming language

    高级语言 高级语言(High-level programming language)相对于机器语言(machine language,是一种指令集的体系.这种指令集,称机器码(machine code ...

  3. 回顾ThreadLocal

    ThreadLocal作为解决特定场景下并发的一种方案,在Spring等框架及面试中经常会被问到,它是Java必须要掌握的基础知识之一. ThreadLocal类的作用是抽象线程内变量的抽象,这类对象 ...

  4. SoundManager 2 / API Demo and Code Examples

    http://www.schillmania.com/projects/soundmanager2/

  5. opencv的移植

    一.opencv在ARM上的移植 http://www.cnblogs.com/emouse/archive/2013/04/01/2993842.html http://blog.csdn.net/ ...

  6. FPGA中iic总线上,应答ACK解析

    首先要明白一点,有效ACK是指第9位为低电平,第十位,十一位就管不着了,(我写的代码发现第九位为低电平,之后复位为高电平,开始没注意后来搞的很是头痛) 主机发ack和主机检测ack,主机发ack是在从 ...

  7. synchronized 实现同步的基础

    1.普通同方法,锁是当前实例对象 2.静态同步方法,锁是当前类的class对象 3.同步代码块,锁是括号里的对象

  8. centos 设置中文环境

    方法1: [hl@localhost ~]$ LANG=zh_CN.UTF-8 #只对当前shell有效,临时设置 [hl@localhost ~]$ ll 总用量 drwxrwxr-x. hl hl ...

  9. 关于CoreData的用法

    有些同事觉得CoreData是一个看不懂,理解不清的神秘东东,其实ios的本地数据储存是一个sqlite数据库,一个简易的数据库,而这个CoreData是否支持所有储存的数据呢,显然不是的,站在我的角 ...

  10. 自然语言处理(NLP)入门学习资源清单

    Melanie Tosik目前就职于旅游搜索公司WayBlazer,她的工作内容是通过自然语言请求来生产个性化旅游推荐路线.回顾她的学习历程,她为期望入门自然语言处理的初学者列出了一份学习资源清单. ...