zoukankan      html  css  js  c++  java
  • 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在调用处理方法时发生了错误

  • 相关阅读:
    Coursera机器学习week11 单元测试
    关于 TypeReference 的解释
    getModifiers 方法解释。
    instanceof isInstance isAssignableFrom 比较
    elasticsearch 基础 语法总结
    kibana 启动 关闭 和进程查找
    MD5 SHA1 SHA256 SHA512 SHA1WithRSA 的区别
    spring boot 项目 热启动
    java zip 压缩文件
    Packet for query is too large (1660 > 1024). You can change this value on the server by setting the max_allowed_packet' variable.
  • 原文地址:https://www.cnblogs.com/yanxiaoge/p/10943955.html
Copyright © 2011-2022 走看看