概述
在SpringMVC中有两种方式可以进行验证输入
- 利用Spring自带的验证框架
- 利用JSR 303实现
本篇博文我们将分别讲述这两种输入验证方法
验证概览
Converter和Formatter作用域Field级。 在MVC应用程序中,它们将String转换或者格式化成另外一种Java类型,比如java.util.Date.
验证器则作用于object级。它决定某一个对象中的所有field是否均是有效的,以及是否遵循某些规则。
那么,思考一个问题如果一个应用程序中即使用了Formatter也使用了validator ,则他们的事件顺序是怎么的呢?
在调用Controller期间,将会有一个或者多个Formatter,视图将输入字符串转换成domain对象的field值,一旦格式化成功,则验证器就会介入。
Spring验证器
Spring的输入验证甚至早于JSR 303(Java验证规范),尽管对于新的项目,一般建议使用JSR303验证器
为了创建Spring验证器,需要实现org.springframework.validation.Validator接口。
接口源码如下
public interface Validator { boolean supports(Class<?> clazz); void validate(Object target, Errors errors); }
supports:验证器可以处理可以处理指定的Class ,supports方法将返回true。 validate方法会验证目标对象,并将验证错误填入Errors对象
Errors对象是org.springframework.validation.Errors接口的一个实例,包含了一系列FieldError和ObjectError对象
编写验证器,不需要直接创建Error对象,因为实例化ObjectError或者FieldError。 大多数时候,只给reject或者rejectValue方法传入一个错误码,Spring就会在属性文件中查找错误码没回去相应的错误消息, 还可以传入一个默认的消息,当没有找到指定的错误码时,就会使用默认消息
Errors对象中的错误消息可以利用表单标签库的Errors标签显示在页面中, 错误消息可以通过Spring支持的国际化特性本地化。
ValidationUtils类
org.springframework.validation.ValidationUtils是一个工具类,有助于编写Spring验证器
方法如下
Spring验证器Demo
这个demo中,我们使用了一个ProductValidator的验证器,用于验证Product对象。
Product类
package com.artisan.domain; import java.io.Serializable; import java.util.Date; public class Product implements Serializable { private static final long serialVersionUID = 748392348L; private String name; private String description; private Float price; private Date productionDate; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public Float getPrice() { return price; } public void setPrice(Float price) { this.price = price; } public Date getProductionDate() { return productionDate; } public void setProductionDate(Date productionDate) { this.productionDate = productionDate; } }
验证器类
package com.artisan.validator; import java.util.Date; import org.springframework.validation.Errors; import org.springframework.validation.ValidationUtils; import org.springframework.validation.Validator; import com.artisan.domain.Product; /** * * @ClassName: ProductValidator * @Description: 实现Validator接口,对Product进行校验 * @author Mr.Yang * @date 2018年2月26日 * */ public class ProductValidator implements Validator { @Override public boolean supports(Class<?> clazz) { return Product.class.isAssignableFrom(clazz); } @Override public void validate(Object target, Errors errors) { // 强制转成校验对象 Product product = (Product) target; // 校验必填字段 ValidationUtils.rejectIfEmpty(errors, "name", "productname.required"); ValidationUtils.rejectIfEmpty(errors, "price", "price.required"); ValidationUtils.rejectIfEmpty(errors, "productionDate", "productiondate.required"); // 校验price Float price = product.getPrice(); if (price != null && price < 0) { errors.rejectValue("price", "price.negative"); } // 校验productionDate Date productionDate = product.getProductionDate(); if (productionDate != null) { // The hour,minute,second components of productionDate are 0 if (productionDate.after(new Date())) { errors.rejectValue("productionDate", "productiondate.invalid"); } } } }
ProductValidator是一个非常简单的校验器,它的validate方法校验Product方法是否有名称和价格,且价格不能为负数,它还会确保生产日期不能晚于今天的日期。
源文件
验证器不需要显式注册,但是如果想从某个属性文件中获取错误消息,则需要通过声明messageSourceBean,告诉Spring去哪里查找这个文件
完整的SpringMVC的配置文件如下
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 扫描控制层的注解,使其成为Spring管理的Bean --> <context:component-scan base-package="com.artisan.controller"/> <!-- 静态资源文件 --> <mvc:annotation-driven conversion-service="conversionService"/> <mvc:resources mapping="/css/**" location="/css/"/> <mvc:resources mapping="/*.jsp" location="/"/> <!-- 视图解析器 --> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource"> <property name="basename" value="/WEB-INF/resource/messages" /> </bean> <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <property name="formatters"> <set> <bean class="com.artisan.formatter.DateFormatter"> <constructor-arg type="java.lang.String" value="MM-dd-yyyy" /> </bean> </set> </property> </bean> </beans>
国际化资源文件 messages.properties
productname.required=Please enter a product name price.required=Please enter a price price.negative=Please enter a price > 0 productiondate.required=Please enter a production date productiondate.invalid=Invalid production date. Please ensure the production date is not later than today.
Controller类
在Controller类中通过实例化validator类,就可以使用Spring验证器了。为了校验改验证器是否生成错误的消息,需要找BindingResult中调用hasErrors方法
package com.artisan.controller; import org.apache.log4j.Logger; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import com.artisan.domain.Product; import com.artisan.validator.ProductValidator; @Controller @RequestMapping("/product") public class ProductController { private static final Logger logger = Logger.getLogger(ProductController.class); @RequestMapping(value = "/product_input") public String inputProduct(Model model) { model.addAttribute("product", new Product()); return "ProductForm"; } @RequestMapping(value = "/product_save") public String saveProduct(@ModelAttribute Product product, BindingResult bindingResult, Model model) { logger.info("product_save"); // 校验Product ProductValidator productValidator = new ProductValidator(); productValidator.validate(product, bindingResult); if (bindingResult.hasErrors()) { FieldError fieldError = bindingResult.getFieldError(); logger.info("Code:" + fieldError.getCode() + ", field:" + fieldError.getField()); return "ProductForm"; } // save product here model.addAttribute("product", product); return "ProductDetails"; } }
使用Spring验证器的第二种方式: 在Controller中编写initBinder方法,并将验证器传到WebDataBinder ,并调用validate方法
@org.springframework.web.bind.annotation.InitBinder public void initBinder(WebDataBinder binder){ // this will apply the validator to all request-handling methods binder.setValidator(new ProductValidator()); binder.validate(); }
将验证器传到WebDataBinder,会使该验证器应用于Controller类中所有请求的方法。
或者利用@javax.validation.Valid对要验证的对象参数进行标注
public String saveProduct(@Valid @ModelAttribute Product product,BindingResult bindingResult,Model model){ }
Valid是JSR303中定义的,下篇博文将介绍。
测试验证器
什么都不输入的情况下
价格输入一个小于0 , 时间输入一个大于今天的日期
输入正确的结果
源码
代码已提交到github