• Spring MVC----Validation(数据校验)


    简化服务器验证

    JSR-303 简介

    JSR-303 是 JavaEE 6 中的一项子规范,叫做 Bean Validation,官方参考实现是 Hibernate Validator。

    此实现与 Hibernate ORM 没有任何关系。JSR-303 用于对 Java Bean 中的字段的值进行验证。 Spring MVC 3.x 之中也大力支持 JSR-303,可以在控制器中使用注解的方式对表单提交的数据方便地验证。

    Spring 4.0 开始支持 Bean Validation 功能。

    JSR-303 基本的校验规则

    空检查

    • @Null 验证对象是否为 null
    • @NotNull 验证对象是否不为 null, 无法查检长度为 0 的字符串
    • @NotBlank 检查约束字符串是不是 Null 还有被 Trim 的长度是否大于 0,只对字符串,且会去掉前后空格
    • @NotEmpty 检查约束元素是否为 NULL 或者是 EMPTY

    布尔检查

    • @AssertTrue 验证 Boolean 对象是否为 true
    • @AssertFalse 验证 Boolean 对象是否为 false

    长度检查

    • @Size(min=, max=) 验证对象(ArrayCollection , MapString)长度是否在给定的范围之内
    • @Length(min=, max=) 验证字符串长度介于 min 和 max 之间

    日期检查

    • @Past 验证 Date 和 Calendar 对象是否在当前时间之前,验证成立的话被注释的元素一定是一个过去的日期
    • @Future 验证 Date 和 Calendar 对象是否在当前时间之后 ,验证成立的话被注释的元素一定是一个将来的日期

    正则检查

    • @Pattern 验证 String 对象是否符合正则表达式的规则,被注释的元素符合制定的正则表达式
      • regexp:正则表达式
      • flags:指定 Pattern.Flag 的数组,表示正则表达式的相关选项

    数值检查

    注意: 建议使用在 String ,Integer 类型,不建议使用在 int 类型上,因为表单值为 “” 时无法转换为 int,但可以转换为 String 为 “”Integer 为 null

    • @Min 验证 Number 和 String 对象是否大等于指定的值
    • @Max 验证 Number 和 String 对象是否小等于指定的值
    • @DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过 BigDecimal定义的最大值的字符串表示 .小数 存在精度
    • @DecimalMin 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过 BigDecimal定义的最小值的字符串表示 .小数 存在精度
    • @Digits 验证 Number 和 String 的构成是否合法
    • @Digits(integer=,fraction=) 验证字符串是否是符合指定格式的数字,integer 指定整数精度,fraction 指定小数精度
    • @Range(min=, max=) 被指定的元素必须在合适的范围内
    • @Range(min=10000,max=50000,message=”range.bean.wage”)
    • @Valid 递归的对关联对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个 map,则对其中的值部分进行校验.(是否进行递归验证)
    • @CreditCardNumber 信用卡验证
    • @Email 验证是否是邮件地址,如果为 null,不进行验证,算通过验证
    • @ScriptAssert(lang= ,script=, alias=)
    • @URL(protocol=,host=, port=,regexp=, flags=)

    使用 Spring Validation 验证

    通过工具来进行验证对象的合法性

    POM

    这里我们使用 Hibernate Validator 5.x 来实现 Spring Validation 接口,pom.xml 文件如下:

    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>5.3.4.Final</version>
    </dependency>
    

      

    定义验证工具类

    已为大家封装好了工具类,享用即可。创建一个名为 BeanValidator 的工具类,代码如下:

    package com.funtl.my.shop.commons.validator;
    
    import org.springframework.beans.factory.annotation.Autowired;
    
    import javax.validation.ConstraintViolation;
    import javax.validation.ConstraintViolationException;
    import javax.validation.Validator;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    
    /**
     * JSR303 Validator(Hibernate Validator)工具类.
     * <p>
     * ConstraintViolation 中包含 propertyPath, message 和 invalidValue 等信息.
     * 提供了各种 convert 方法,适合不同的 i18n 需求:
     * 1. List<String>, String 内容为 message
     * 2. List<String>, String 内容为 propertyPath + separator + message
     * 3. Map<propertyPath, message>
     * <p>
     * 详情见wiki: https://github.com/springside/springside4/wiki/HibernateValidator
     *
     * <p>Title: BeanValidator</p>
     * <p>Description: </p>
     *
     * @author Lusifer
     * @version 1.0.0
     * @date 2018/6/26 17:21
     */
    public class BeanValidator {
    
        @Autowired  //不能自动注入,静态是属性不能注入,这个注释可以删除,下面需要自己手动注入
        private static Validator validator;
    
        public static void setValidator(Validator validator) {
            BeanValidator.validator = validator;
        }
    
        /**
         * 调用 JSR303 的 validate 方法, 验证失败时抛出 ConstraintViolationException.
         */
        private static void validateWithException(Validator validator, Object object, Class<?>... groups) throws ConstraintViolationException {
            Set constraintViolations = validator.validate(object, groups);
            if (!constraintViolations.isEmpty()) {
                throw new ConstraintViolationException(constraintViolations);
            }
        }
    
        /**
         * 辅助方法, 转换 ConstraintViolationException 中的 Set<ConstraintViolations> 中为 List<message>.
         */
        private static List<String> extractMessage(ConstraintViolationException e) {
            return extractMessage(e.getConstraintViolations());
        }
    
        /**
         * 辅助方法, 转换 Set<ConstraintViolation> 为 List<message>
         */
        private static List<String> extractMessage(Set<? extends ConstraintViolation> constraintViolations) {
            List<String> errorMessages = new ArrayList<>();
            for (ConstraintViolation violation : constraintViolations) {
                errorMessages.add(violation.getMessage());
            }
            return errorMessages;
        }
    
        /**
         * 辅助方法, 转换 ConstraintViolationException 中的 Set<ConstraintViolations> 为 Map<property, message>.
         */
        private static Map<String, String> extractPropertyAndMessage(ConstraintViolationException e) {
            return extractPropertyAndMessage(e.getConstraintViolations());
        }
    
        /**
         * 辅助方法, 转换 Set<ConstraintViolation> 为 Map<property, message>.
         */
        private static Map<String, String> extractPropertyAndMessage(Set<? extends ConstraintViolation> constraintViolations) {
            Map<String, String> errorMessages = new HashMap<>();
            for (ConstraintViolation violation : constraintViolations) {
                errorMessages.put(violation.getPropertyPath().toString(), violation.getMessage());
            }
            return errorMessages;
        }
    
        /**
         * 辅助方法, 转换 ConstraintViolationException 中的 Set<ConstraintViolations> 为 List<propertyPath message>.
         */
        private static List<String> extractPropertyAndMessageAsList(ConstraintViolationException e) {
            return extractPropertyAndMessageAsList(e.getConstraintViolations(), " ");
        }
    
        /**
         * 辅助方法, 转换 Set<ConstraintViolations> 为 List<propertyPath message>.
         */
        private static List<String> extractPropertyAndMessageAsList(Set<? extends ConstraintViolation> constraintViolations) {
            return extractPropertyAndMessageAsList(constraintViolations, " ");
        }
    
        /**
         * 辅助方法, 转换 ConstraintViolationException 中的 Set<ConstraintViolations> 为 List<propertyPath + separator + message>.
         */
        private static List<String> extractPropertyAndMessageAsList(ConstraintViolationException e, String separator) {
            return extractPropertyAndMessageAsList(e.getConstraintViolations(), separator);
        }
    
        /**
         * 辅助方法, 转换 Set<ConstraintViolation> 为 List<propertyPath + separator + message>.
         */
        private static List<String> extractPropertyAndMessageAsList(Set<? extends ConstraintViolation> constraintViolations, String separator) {
            List<String> errorMessages = new ArrayList<>();
            for (ConstraintViolation violation : constraintViolations) {
                errorMessages.add(violation.getPropertyPath() + separator + violation.getMessage());
            }
            return errorMessages;
        }
    
        /**
         * 服务端参数有效性验证
         *
         * @param object 验证的实体对象
         * @param groups 验证组
         * @return 验证成功:返回 null;验证失败:返回错误信息
         */
        public static String validator(Object object, Class<?>... groups) {
            try {
                validateWithException(validator, object, groups);
            } catch (ConstraintViolationException ex) {
                List<String> list = extractMessage(ex);
                list.add(0, "数据验证失败:");
    
                // 封装错误消息为字符串
                StringBuilder sb = new StringBuilder();
                for (int i = 0; i < list.size(); i++) {
                    String exMsg = list.get(i);
                    if (i != 0 ){
                        sb.append(String.format("%s. %s", i, exMsg)).append(list.size() > 1 ? "<br/>" : "");
                    } else {
                        sb.append(exMsg).append(list.size() > 1 ? "<br/>" : "");
                    }
                }
    
                return sb.toString();
            }
    
            return null;
        }
    }
    

      

    修改实体类

    修改实体类,增加验证注解,以后我们只需要在实体类的属性上使用 JSR-303 注解即可完成相关数据的验证工作,关键代码如下:

    @Length(min = 6, max = 20, message = "用户名长度必须介于 6 和 20 之间")
    private String username;
    @Length(min = 6, max = 20, message = "密码长度必须介于 6 和 20 之间")
    private String password;
    @Pattern(regexp = RegexpUtils.PHONE, message = "手机号格式不正确")
    private String phone;
    @Pattern(regexp = RegexpUtils.EMAIL, message = "邮箱格式不正确")
    private String email;
    

      

    注入工具类

    修改 spring-context.xml 文件,注入 Validator 工具类,配置如下:

    <!-- 配置 Bean Validator 定义 -->
    <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
    <bean id="beanValidator" class="com.funtl.my.shop.commons.validator.BeanValidator">
        <property name="validator" ref="validator" />
    </bean>
    

      

    效果演示

    配置完成后,在浏览器端测试直接提交数据,效果如下:

     使用Validator工具类进行验证

     

    补充

    BindingResult可以使用Errors代替,这两个类都位于org.springframework.validation中。

    使用注意:需校验的Bean 对象和其绑定结果对象或错误对象时成对出现的,它们之间不允许声明其他的入参。就是说 @Valid User user, BindingResult bindingResult,之间不能与其他参数。

    通过 BindingResult  也可以获取错误,但是需要添加@Valid注解,此时就不要之前的工具类

    @RequestMapping(value = "/useradd",method = RequestMethod.POST)
        public String addUser(@Valid User user, BindingResult bindingResult,Map<String,Object> map){
            if (bindingResult.getErrorCount()>0){
                for (FieldError error:bindingResult.getFieldErrors()){
                    if ("username".equals(error.getField())){
                        //String usernameMessage = error.getDefaultMessage();
                        //map.put("usernameMessage",usernameMessage);
                    }
                }
                //不需要重定向,还是放回之前的页面
                return "userForm";
            }
            return "login";
        }
    

      

    验证处理

    方式1

    controller

        @RequestMapping(value = "/useradd",method = RequestMethod.POST)
        public String addUser(@Valid User user, BindingResult bindingResult,Map<String,Object> map){
            if (bindingResult.getErrorCount()>0){
                for (FieldError error:bindingResult.getFieldErrors()){
                    if ("username".equals(error.getField())){
                        String usernameMessage = error.getDefaultMessage();
                        map.put("usernameMessage",usernameMessage); //给请求域中添加错误消息
                    }
                }
                //不需要重定向,还是放回之前的页面
                return "userForm";
            }
            return "login";
        }
    

    html,获取错误消息

    <c:if test="${usernameMessage!=null}">${usernameMessage}</c:if>
    

      

    方式2

        @RequestMapping(value = "/useradd",method = RequestMethod.POST)
        public String addUser(@Valid User user, BindingResult bindingResult,Map<String,Object> map){
            if (bindingResult.getErrorCount()>0){
                //不需要重定向,还是放回之前的页面
                return "userForm";
            }
            return "login";
        }
    

    html,使用<form:errors>来获取错误

    <form:errors path="username"></form:errors>//username 就是我们验证的字段(添加注解的字段),获取某一个错误
    <form:errors path="*"></form:errors>//获取所有的错误
    

      

    提示消息国际化(验证处理方式2使用)

    原理

      每个属性在数据绑定和数据校验发生错误时,都会生成一个对应的FieldError对象。

      当一个属性校验失败后,校验框架会为该属性生成4个消息代码,这些代码以校验注解类名为前缀,结合modleAttribute、属性名及属性类型名生成多个对应的消息代码:例如User 类中的 password 属性注解了一个@Pattern注解,当该属性值不满足@Pattern所定义的规则时,就会产生以下4个错误代码:
      Pattern.user.password
      Pattern.password
      Pattern.java.lang.String
      Pattern

      当使用Spring MVC标签(<form:errors path="username"></form:errors>)显示错误消息时,Spring MVC会查看WEB 上下文是否装配了对应的国际化消息,如果没有,则显示默认的错误消息,否则使用国际化消息。

    修改实体类

    @Length(min = 6, max = 20)  //不需要我们添加message
    private String username;

    spring-MVC.xml

        <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
            <property name="basename" value="info"></property>
        </bean>
    

    info_zh_CN.properties

    Length.user.username=u4e0du80fdu4e3au7a7au54e6
    

    key的格式:注解.类.字段

    补充

    若数据类型转换或数据格式转换时发生错误,或该有的参数不存在,或调用处理方法时发生错误,都会在隐含模型中创建错误消息。其错误代码前缀(都是小写)说明如下:

    -required:必要的参数不存在。如@RequiredParam("param11)标注了一个入参,但是该参数不存在
    -typeMismatch:在数据绑定时,发生数据类型不匹配的问题
    -methodInvocation:Spring MVC在调用处理方法时发生了错误

  • 相关阅读:
    省选知识点
    寒假练习
    水题欢乐赛-套路
    2019年12月(2)
    洛谷P1347 排序
    Aizu
    2019年12月(1)
    【CSP2019】
    联系博主
    UVA1420 Priest John's Busiest Day【贪心】
  • 原文地址:https://www.cnblogs.com/yanxiaoge/p/10943955.html
Copyright © 2020-2023  润新知