JSR-303是一个数据验证的规范,这里我不会讲这个规范是怎么回事,只会讲一下JSR-303在SpringMVC中的应用。JSR-303只是一个规范,而Spring也没有对这一规范进行实现,那么当我们在SpringMVC中需要使用到JSR-303的时候就需要我们提供一个对JSR-303规范的实现,Hibernate Validator是实现了这一规范的,这里我将以它作为JSR-303的实现来讲解SpringMVC对JSR-303的支持。
JSR-303的校验是基于注解的,它内部已经定义好了一系列的限制注解,我们只需要把这些注解标记在需要验证的实体类的属性上或是其对应的get方法上。来看以下一个需要验证的实体类Book的代码:
package cn.entity;
import javax.validation.constraints.Min;
import jrs303.Money;
public class Book {
private String id;
private String bookName;
private Double price;
public Book() {
}
public Book(String id, String bookName, Double price) {
this.id = id;
this.bookName = bookName;
this.price = price;
}
@NotBlank(message="id不能为空!")
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@NotEmpty(message="书名不能为空!")
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
@Money(message = "默认金额为xxx.xx元")
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
}
我们可以看到我们在id,bookName和price对应的get方法上都加上了一个注解,这些注解就是JSR-303里面定义的限制,其中@NotBlank是Hibernate Validator的扩展。不难发现,使用JSR-303来进行校验比使用Spring提供的Validator接口要简单的多。我们知道注解只是起到一个标记性的作用,它是不会直接影响到代码的运行的,它需要被某些类识别到才能起到限制作用。使用SpringMVC的时候我们只需要把JSR-303的实现者对应的jar包放到classpath中,然后在SpringMVC的配置文件中引入MVC Namespace,并加上就可以非常方便的使用JSR-303来进行实体对象的验证。加上了之后Spring会自动检测classpath下的JSR-303提供者并自动启用对JSR-303的支持,把对应的校验错误信息放到Spring的Errors对象中。这时候SpringMVC的配置文件如下所示:
<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-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd">
<mvc:annotation-driven />
<context:annotation-config />
<context:component-scan base-package="cn.controller" />
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/">property>
<property name="suffix" value=".jsp">property>
bean>
beans>
接着我们定义BookController类来接收book对象的方法
package cn.controller;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import cn.entity.Book;
@Controller
public class BookController {
@RequestMapping(value="/saveBookInfo",method = RequestMethod.GET)
public String saveBookInfo(@ModelAttribute("book") Book book){
return "user/book";
}
@RequestMapping(value="/isSaveBookInfo")
public String isSaveBookInfo(@Validated Book book,BindingResult bindingResult){
if( bindingResult.hasErrors() ) {
System.out.println("error");
return "user/book";
}
System.out.println("good time");
return "redirect:/";
}
}
当我们请求服务器映射的saveBookInfo会执行/jsp/user/book.jsp页面,
提交表单会将数据POST给BookController中的isSaveBookInfo参数Book,通过Book的注解判断是否有错误,然后会把对应的错误信息放置在当前的Errors对象中。
<%@ page language="java" import="java.util.*"
contentType="text/html; charset=utf-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<html>
<head>
<title>My JSP 'book.jsp' starting pagetitle>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
head>
<body>
<form:form modelAttribute="book" action="isSaveBookInfo" method="post">
<table>
<tr>
<td>id:td>
<td><form:input path="id" />td>
<td><form:errors path="id">form:errors>td>
tr>
<tr>
<td>bookName:td>
<td><form:input path="bookName" />td>
<td><form:errors path="bookName">form:errors>td>
tr>
<tr>
<td>price:td>
<td><form:input path="price" />td>
<td><form:errors path="price">form:errors>td>
tr>
table>
<input type="submit" value="提交" />
form:form>
body>
html>
除了JSR-303原生支持的限制类型之外我们还可以定义自己的限制类型。定义自己的限制类型首先我们得定义一个该种限制类型的注解,而且该注解需要使用@Constraint标注。现在假设我们需要定义一个表示金额的限制类型,那么我们可以这样定义:
package jrs303;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;//Retention rɪ'tenʃ(ə)n 保留
import java.lang.annotation.RetentionPolicy;//Policy 'pɑləsi 策略
import java.lang.annotation.Target;
import javax.validation.Payload;
import javax.validation.Constraint;
import jrs303.MoneyValidator;
@Target({ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = MoneyValidator.class )
public @interface Money {
String message() default "不是金额格式";
Class>[] groups() default{};
Class extends Payload>[] payload() default{}; //记住payload p为小写否则报错
}
我们可以看到在上面代码中我们定义了一个Money注解,而且该注解上标注了@Constraint注解,使用@Constraint注解标注表明我们定义了一个用于限制的注解。@Constraint注解的validatedBy属性用于指定我们定义的当前限制类型需要被哪个ConstraintValidator进行校验。在上面代码中我们指定了Money限制类型的校验类是MoneyValidator。另外需要注意的是我们在定义自己的限制类型的注解时有三个属性是必须定义的,如上面代码所示的message、groups和payload属性。
在定义了限制类型Money之后,接下来就是定义我们的限制类型校验类MoneyValidator了。限制类型校验类必须实现接口javax.validation.ConstraintValidator,并实现它的initialize和isValid方法。我们先来看一下MoneyValidator的代码示例:
package jrs303;
import java.util.regex.Pattern;
import jrs303.Money;
import javax.validation.ConstraintValidator; //ConstraintValidator 约束验证接口
import javax.validation.ConstraintValidatorContext;
public class MoneyValidator implements ConstraintValidator<Money, Double> {
private String moneyReg = "^\\d+(\\.\\d{1,2})?$";//表示金额的正则表达式
private Pattern pattern = Pattern.compile(moneyReg) ;
@Override
public void initialize(Money constraintAnnotation) {
}
@Override
public boolean isValid(Double value, ConstraintValidatorContext context) {
if( value == null ) return true;
//返回true表示是一个money的double值
return pattern.matcher(value.toString()).matches();
}
}