声明式数据验证


nSpring3开始支持JSR-303验证框架,JSR-303支持XML风格的和注解风格的验证,接下来我们首先看一下如何和Spring集成。



1、添加jar包:



此处使用Hibernate-validator实现(版本:hibernate-validator-4.3.0.Final-dist.zip),将如下jar包添加到classpath(WEB-INF/lib下即可):



dist/lib/required/validation-api-1.0.0.GA.jar     JSR-303规范API包



dist/hibernate-validator-4.3.0.Final.jar          Hibernate 参考实现



还需要加入jboss-logging-3.1.0.CR2.jar



 



2、在Spring配置总添加对JSR-303验证框架的支持



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



"validator"



"org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">



"providerClass"  value=  "org.hibernate.validator.HibernateValidator"/>



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



"validationMessageSource" ref=  "messageSource"/>



</bean> 
此处使用Hibernate validator实现:



validationMessageSource属性:指定国际化错误消息从哪里取,此处使用之前定义的messageSource来获取国际化消息;如果此处不指定该属性,则默认到classpath下的ValidationMessages.properties取国际化错误消息。



 



通过ConfigurableWebBindingInitializer注册validator:



"webBindingInitializer"



"org.springframework.web.bind.support.ConfigurableWebBindingInitializer">



"conversionService" ref=  "conversionService"/>



"validator" ref=  "validator"/>



</bean>



 



 



3、在Spring配置 中 添加message配置



"messageSource"



"org.springframework.context.support.ReloadableResourceBundleMessageSource">



"basename" value=  "classpath:messages"/>



"fileEncodings" value=  "utf-8"/>



"cacheSeconds" value=  "120"/>



</bean>



在src下放一个messages.properties,里面的内容是:



username.not.empty=\u7528\u6237\u59D3\u540D\u4E0D\u80FD\u4E3A\u7A7A



这是已经转义的,原始的信息是:用户姓名不能为空



nHelloWorld的Model,使用JSR-303验证框架注解为模型对象指定验证信息



public class UserModel {



@NotNull(message="{username.not.empty}")



private String username;



}



通过@NotNull指定此username字段不允许为空,当验证失败时将从之前指定的messageSource中获取“username.not.empty”对于的错误信息,此处只有通过“{错误消息键值}”格式指定的才能从messageSource获取。



nHelloWorld的Controller



@Controller



public class HelloWorldController {



@RequestMapping("/validate/hello")



public String validate(@Valid @ModelAttribute("user")UserModel user,Errors errors){



if(errors.hasErrors()) {



return "validate/error"; }



return "redirect:/success";



}}



通过在命令对象上注解@Valid来告诉Spring MVC此命令对象在绑定完毕后需要进行JSR-303验证,如果验证失败会将错误信息添加到errors错误对象中



说明:错误对象的代表者是Errors接口,并且提供了几个实现者,在Spring Web MVC中我们使用的是如下实现:Errors、BindingResult、BindException等



 



nHelloWorld的验证失败后需要展示的页面



"java" contentType=  "text/html; charset=UTF-8" pageEncoding=  "UTF-8"%>



"form" uri=  "http://www.springframework.org/tags/form"



 



"user">



"*" cssStyle=  "color:red"></form:errors><br/>



</form:form>



 



 



可以去测试啦,不给UserModel传递 name值看看效果。



 



n@AssertFalse



验证的数据类型 :Boolean,boolean



说明 :验证注解的元素值是false



n@AssertTrue



验证的数据类型 :Boolean,boolean



说明 :验证注解的元素值是true



n@NotNull



验证的数据类型 :任意类型 



说明 :验证注解的元素值不是null



n@Null



验证的数据类型 :任意类型 



说明 :验证注解的元素值是null



n@Min(value=值)



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



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



 



n@Max(value=值)



验证的数据类型 :和@Min要求一样 



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



n@DecimalMin(value=值)



验证的数据类型 :和@Min要求一样



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



n@DecimalMax(value=值)



验证的数据类型 :和@Min要求一样



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



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



验证的数据类型 :和@Min要求一样



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



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



验证的数据类型 :字符串、Collection、Map、数组等



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



n@Past



验证的数据类型 :java.util.Date,java.util.Calendar,Joda Time类库的日期类型



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



 



n@Future



验证的数据类型 :与@Past要求一样



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



n@NotBlank



验证的数据类型 :CharSequence子类型



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



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



验证的数据类型 : CharSequence子类型



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



n@NotEmpty



验证的数据类型 :CharSequence子类型、Collection、Map、数组



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



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



验证的数据类型 :BigDecimal,BigInteger,CharSequence, byte, short, int, long等原子类型和包装类型



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



 



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



验证的数据类型 :CharSequence子类型(如String)



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



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



验证的数据类型 :String,任何CharSequence的子类型



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



n@Valid



验证的数据类型 :任何非原子类型



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



 



注意:此处只列出Hibernate Validator提供的大部分验证约束注解,请参考hibernate validator官方文档了解其他验证约束注解和进行自定义的验证约束注解定义



 



错误消息



n概述



当验证出错时,我们需要给用户展示错误消息告诉用户出错的原因,因此我们要为验证约束注解指定错误消息。错误消息是通过在验证约束注解的message属性指定。验证约束注解指定错误消息有如下两种方式:



1、硬编码错误消息;



2、从资源消息文件中根据消息键读取错误消息。



n硬编码错误消息



直接在验证约束注解上指定错误消息,如下所示:



@NotNull(message = "用户名不能为空")



@Length(min=5, max=20, message="用户名长度必须在5-20之间")



@Pattern(regexp = "^[a-zA-Z_]\\w{4,19}$", message = "用户名必须以字母下划线开头,可由字母数字下划线组成")



private String username;



如上所示,错误消息使用硬编码指定,这种方式是不推荐的,因为在如下场景是不适用的:



1、在国际化场景下,需要对不同的国家显示不同的错误消息;



2、需要更换错误消息时是比较麻烦的,需要找到相应的类进行更换,并重新编译发布。



从资源消息文件中根据消息键读取错误消息



这个最为推荐的方式就是直接使用Spring的MessageSource Bean进行消息的匹配和管理,前面HelloWorld就是使用的这个方式,这儿就不赘述了。



 



功能处理方法上多个验证参数的处理



n当我们在一个功能处理方法上需要验证多个模型对象时,需要通过如下形式来获取验证结果:


@RequestMapping("/validate/multi") 
         
 
         

           public String multi( 
         
 
         

           @Valid @ModelAttribute("a") A a, BindingResult aErrors, 
         
 
         

           @Valid @ModelAttribute("b") B b, BindingResult bErrors) {


 

if(aErrors.hasErrors()) { //如果a模型对象验证失败 
         
 
         

           return "validate/error"; 
         
 
         

           } 
         
 
         

           if(bErrors.hasErrors()) { //如果a模型对象验证失败 
         
 
         

           return "validate/error"; 
         
 
         

           } 
         
 
         

           return "redirect:/success"; 
         
 
         

           }



每一个模型对象后边都需要跟一个Errors或BindingResult对象来保存验证结果,其方法体内部可以使用这两个验证结果对象来选择出错时跳转的页面。



 



n在错误页面,需要针对不同的模型来显示错误消息 :


"a"> 
         
 
         
"*" cssStyle=  
          "color:red"></form:errors><br/> 
         
 
         

           </form:form> 
         
 
         
"b"> 
         
 
         
"*" cssStyle=  
          "color:red"></form:errors><br/> 
         
 
         

           </form:for

m>



 



然后就可以测试了。



 



异常处理的支持



nSpring Web MVC对异常处理的支持有两种方式



1:一种是直接实现自己的HandlerExceptionResolver ,通常用来实现全局异常控制, Spring Web MVC已经有了一个缺省的实现:SimpleMappingExceptionResolver,



2:用注解的方式实现一个专门用于处理异常的Controller——ExceptionHandler,通常用来在Controller内部实现更个性化点异常处理方式,灵活性更高。



n自定义实现的方式,只需要实现HandlerExceptionResolver接口即可,示例如下: 
         
 
         

           public class MyExceptionHandler implements HandlerExceptionResolver{ 
         
 
         

           public ModelAndView resolveException(HttpServletRequest request, 
         
 
         

           HttpServletResponse response, Object handler, Exception ex) { 
         
 
         

           if(ex instanceof NullPointerException){ 
         
 
         

           //这里就写如何处理,比如:记日志 
         
 
         

           System.out.println("now is NullPointer"); 
         
 
         

           }else if(ex instanceof ArrayIndexOutOfBoundsException){ 
         
 
         

           //这里就写如何处理,比如:记日志 
         
 
         

           } 
         
 
         

           return new ModelAndView("exception"); 
         
 
         

           } 
         
 
         

           }



n上述类需要在spring的配置文件中配置,示例如下:



"myExceptionHandler" class=  "cn.javass.springmvc.exception.MyExceptionHandler"/>



n说明:



1:上述示例的resolveException方法的第四个参数,就是具体的例外类型



2:如果该方法返回了null,则Spring会继续寻找其他的实现了HandlerExceptionResolver 接口的Bean。也就是说,Spring会搜索所有注册在其环境中的实现了HandlerExceptionResolver接口的Bean,逐个执行,直到返回了一个ModelAndView对象。



 



n当然我们也可以直接使用Spring提供的SimpleMappingExceptionResolver类,示例如下:

<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">  
         
 
         

               <!-- 定义默认的异常处理页面,当该异常类型的注册时使用 -->  
         
 
         

               <property name="defaultErrorView" value="exception"></property>  
         
 
         

               <!-- 定义异常处理页面用来获取异常信息的变量名,默认名为exception -->  
         
 
         

               <property name="exceptionAttribute" value="ex"></property>


<!-- 定义需要特殊处理的异常,用类名或完全路径名作为key,异常显示的页面名作为值 --> 



<property name="exceptionMappings">  
         
 
         

                   <props>  
         
 
         

                       <prop key="NullPointerException">error/nullPage</prop>  
         
 
         

                       <prop key="ArrayIndexOutOfBoundsException">error/ArrayIndexOutOfBoundsPage</prop>  
         
 
         

                   </props>  
         
 
         

               </property>  
         
 
         

           </bean> 
         
 
         

             
         
 
         

             n说明:出了错过后,例外信息会以ex为key存放在request属性里面,因此在错误页面就可以通过request的属性取到例外对象了 
           
 
           

               
           
 
           

             n典型的异常显示页面示例如下: 
           
 
           
"java" contentType=  
            "text/html; charset=UTF-8" pageEncoding=  
            "UTF-8"%> 
           
 
           

             <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> 
           
 
           

               
           
 
           

             <% Exception ex = (Exception)request.getAttribute("ex"); %> 
           
 
           

             <H2>Exception: <%= ex.getMessage()%></H2>

 



n基于@ExceptionHandler的异常处理



该方法需要定义在Controller内部,然后创建一个方法并用@ExceptionHandler注解来修饰用来处理异常,这个方法基本和 @RequestMapping修饰的方法差不多,只是可以多一个类型为Exception的参数,@ExceptionHandler中可以添加一个或多个异常的类型,如果为空的话则认为可以触发所有的异常类型错误。



nController中示例如下: 
           
 
           

             @ExceptionHandler(value={NullPointerException.class,ArrayIndexOutOfBoundsException.class}) 
           
 
           

             public String exceptionExecute(Exception ex,HttpServletRequest request){ 
           
 
           

             request.setAttribute("ex", ex); 
           
 
           

             if(ex instanceof NullPointerException){ 
           
 
           

             //这里就写如何处理,比如:记日志 
           
 
           

             System.out.println("now is NullPointer2"); 
           
 
           

             return "error/nullPage2"; 
           
 
           

             }else if(ex instanceof ArrayIndexOutOfBoundsException){ 
           
 
           

             //这里就写如何处理,比如:记日志 
           
 
           

             } 
           
 
           

             return "error/nullPage2"; 
           
 
           

             }


 



n三种处理方式都有的运行顺序



1:优先在自己Controller里面寻找@ExceptionHandler,看能不能处理



2:如果不能,然后在Spring中,寻找实现HandlerExceptionResolver 的Bean



3:对于多个都能处理的Bean,则按照配置的先后顺序进行处理



4:只要有一个能处理,不返回null,那么就结束