zoukankan      html  css  js  c++  java
  • Spring @Valid

    @Valid基本用法

    强烈推荐如果要学习@Valid JSR303, 建议看这里的API  Bean Validation规范

    Controller控制器中在需要校验的实体类上添加  @Valid 即可使用JSR303校验(前提记得添加hibernate-validator相关jar,<mvc:annotation-driven/>);

    modelMap是为了将校验失败信息写回到request属性中返回给JSP页面展示

    @RequestMapping("/demo2")
        public String test2(@Valid User user, BindingResult result, ModelMap modelMap){
            System.out.println(user);
            List<FieldError> fieldErrors = result.getFieldErrors();
            for (FieldError e:fieldErrors) {
                System.out.println(e.getDefaultMessage());  //验证不通过的信息
                System.out.println(e.getField());
                modelMap.addAttribute(e.getField(),e.getDefaultMessage());
            }
            return "test";
    }

    校验的实体类User

    @Setter
    @Getter
    @ToString
    public class User {
        @NotBlank
        private String name;
        @Min(1)
        @Max(120)
        private int age;
    
        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        public User() {
        }
    }

    浏览器输入localhost:8090/binding/demo2?name=lvbinbin&age=150, 结果校验不通过

    image

    从上述用例看出来,我们没有指定message属性,默认校验不通过的提示消息  最大不能超过120  , 该信息是在hibernate-Validator.jar的ValidationMessages.properties中定义;

    如果想要自定义校验不通过信息,我们可以指定message属性

        @Min(value = 1,message = "年龄大于一岁")
        @Max(value = 120,message = "常人活不到120岁")
        private int age;

    image

    突然考虑到问题,国际化的问题由于对国际化没有过了解,我理解的国际化问题就是,请求头信息包含的地区信息Accpet-Language可以判断当前需要中文还是英文,于是有了下面进一步的改善;

    Hibernate默认会查找classPath下的ValidationMessages.properties文件,我们只需要将国际化校验文件在classpath下添加即可。

    classpath下添加ValidationMessages_en.properties   (英文校验失败信息)

    myValidation.min=can not be lower than {value}
    myValidation.max=can not be bigger than {value}
    age=age 

    classpath下添加ValidationMessages_zh.properties  (中文校验失败信息)

    myValidation.min=不能小于{value}
    myValidation.max=不能大于{value}
    age=年龄

    在注解验证的message属性用{}来取ValidationMessages中的值

        @Min(value = 1,message = "{age}{myValidation.min}")
        @Max(value = 120,message = "{age}{myValidation.max}")
        private int age;

    使用POSTMAN模拟中文、英文测试一下:

    英文测试:请求头Accpet-Language:en-Us , 结果的确是英文

    image

    中文测试:请求头Accpet-Language:zh-CN, 结果发现乱码问题

    image

    乱码问题解决方案:自定义Validator注册到SpringMvc中,指定国际化资源文件编码为UTF-8

    <mvc:annotation-driven validator="validator"/>
    
        <bean id="validator" class="org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean">
            <property name="validationMessageSource" ref="messageSource"/>
            <property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
        </bean>
    
        <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
            <property name="basenames">
                <list>
                    <value>classpath:ValidationMessages</value><!--国际化资源地址-->
                </list>
            </property>
            <property name="defaultEncoding" value="UTF-8"/>
            <property name="cacheSeconds" value="120"/>
        </bean>

    再次测试中文,就不存在问题,同样中文也是没有问题的

    image

    校验不通过返回给前端两种方案

    方案一.存到request属性中,在前端视图JSP 等渲染

    @RequestMapping("/demo2")
        public String test2(@Valid User user, BindingResult result, ModelMap modelMap){
            System.out.println(user);
            List<FieldError> fieldErrors = result.getFieldErrors();
            for (FieldError e:fieldErrors) {
                System.out.println(e.getDefaultMessage());  //验证不通过的信息
                System.out.println(e.getField());
                modelMap.addAttribute(e.getField(),e.getDefaultMessage());
            }
            return "test";
    }

    方案二.校验不通过返回异常信息JSON串给前端

    通过查看抛出异常信息,Spring4.3.0校验@Valid不通过抛出异常信息为BindException,捕获该种异常返回JSON,异常捕获方式见我的博客。

    @ExceptionHandler(value = {BindException.class})
        public ResponseEntity invalidArgument(BindException ex){
            Map result=new HashMap<String,Object>();
            result.put("status_code",500);
            System.out.println("捕获到异常");
            List<FieldError> fieldErrors = ex.getFieldErrors();
            StringBuffer sb=new StringBuffer();
            for (FieldError error:fieldErrors) {
                sb.append(error.getDefaultMessage());
            }
            result.put("message",sb.toString());
            return new ResponseEntity(result, HttpStatus.INTERNAL_SERVER_ERROR);
        }

    补充说明:@RequestMapping方法中你写了参数 BindingResult就代表告诉Spring  我自己来处理异常,你别管了,这种情况程序不会抛出异常;所以方式一程序是不会抛出异常。

    顺带提及Spring扩展JSR303的注解@Validated

    个人对于为什么会存在@Validated注解的看法:

    @Valid功能很丰富,有幸搜索到这样一篇典范API Bean Validation技术规范,弊病是@Valid的组、组顺序功能,需要对Spring、JavaxValidation有一定基础,不够简易上手,在此基础上Spring封装了@Validated来完成  组校验、组顺序校验的功能,我们只需要一个@Validated(value={xxx.class})即可指定组,对于我们来说不能在方便了!  以上就是个人对于@Validated存在的合理性分析,这里看来存在是合理的!

    假设这样一个情景介绍@Validate 里组的概念,也可以看Bean Validation技术规范里的介绍;

        @RequestMapping("/demo3")
        public String test3(@Validated Item item){
            System.out.println(item);
            return "test";
        }
    
    
        @ExceptionHandler(value = {BindException.class})
        public ResponseEntity invalidArgument(BindException ex){
            Map result=new HashMap<String,Object>();
            result.put("status_code",500);
            System.out.println("捕获到异常");
            List<FieldError> fieldErrors = ex.getFieldErrors();
            StringBuffer sb=new StringBuffer();
            for (FieldError error:fieldErrors) {
                sb.append(error.getDefaultMessage()).append(",");
            }
    
            result.put("message",sb.substring(0,sb.length()-1));
            return new ResponseEntity(result, HttpStatus.INTERNAL_SERVER_ERROR);
        }

    校验实体类Item

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Item {
        @NotBlank(message = "商品名称不建议为空")
        private String name;
        @DecimalMin(value = "0.5",message = "商品价格小于0.5")
        private double price;
        @Past(message = "生产日期伪冒")
        @NotNull(message = "生产日期不能不报")
        private Date produceDate;
    
    }

    尝试不输入任何属性,果然三个校验都没有通过;

    image

    对了,有个日期类型参数,这里就简单用@InitBinder解决一下子吧,在@Controller里添加方法:这样就可以将String转换成Date类型参数了.

    @InitBinder
        public void registryStringToDate(DataBinder binder){
            binder.registerCustomEditor(Date.class,new CustomDateEditor(new SimpleDateFormat("yyyy/MM/dd"),true));
    }

    再次测试,没有问题了,我们就可以开始介绍 组校验的方式了

    image

    比如现在只需要校验商品名字,其他的价格、日期都不需要管了:

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Item {
        @NotBlank(message = "商品名称不建议为空",groups = {ItemNameValid.class})
        private String name;
        @DecimalMin(value = "0.5",message = "商品价格小于0.5",groups = {ItemPriceValid.class})
        private double price;
        @Past(message = "生产日期伪冒")
        @NotNull(message = "生产日期不能不报")
        private Date produceDate;
    
        public static interface ItemNameValid{}
    
        public static interface ItemPriceValid extends ItemNameValid{}
    }

    @Controller写法:

    @RequestMapping("/demo3")
        public String test3(@Validated({Item.ItemNameValid.class}) Item item){
            System.out.println(item);
            return "test";
        }

    @Validated注解中value指定某个且必须是接口类型,ItemNameValid组校验时候只校验name属性,ItemPriceValid 组校验时候会校验name和price组;

    image

    image

    级联验证方式:

    Item类添加属性ItemProp

        @Valid
        @NotNull(message=”产品属性不能为空”)
        private ItemProp prop;
    @Setter
    @Getter
    @NoArgsConstructor
    public class ItemProp {
        @Pattern(regexp = "^白色$",message = "小布丁只能是白色的")
        @NotNull
        private String color;
        @NotBlank(message = "如实填报产地")
        private String Location;
    }

    image

    image

    注意:@Valid添加到级联属性上完成验证,前提是:  如果级联的属性没有初始化new,且是必须验证的项,@Valid下面跟上@NotNull才能级联验证,否则根本不去校验ItemProp属性.

    总结:@Valid和@Validated异同

    @Valid可以用来作为级联属性校验,@Validated没这个功能;级联校验时Bean Validation的特性,而非Spring特性.

    @Validated扩展JSR303,可以用来指定校验组验证,且只见过标注在@RequestMapping方法需要校验的入参中;

    除了使用Bean Validation规范来完成JavaBean校验,Spring另外提供一个接口Validator,供我们实现复杂校验逻辑。 下面完成了一个简单的Person入参校验,使用Spring的Validator实现

    @Controller
    @RequestMapping("/valid")
    public class ValidateController {
    
        @RequestMapping("/demo1")
        public String demo1(@Valid Person person){  //此处@valid不能省略,@Validated也一样使用,作用标识person开启校验
            System.out.println(person);
            return "test";
        }
    
        @InitBinder
        public void register(DataBinder binder){
            binder.setValidator(new PersonValidator());//替换原有validator;
    //        binder.addValidators(new PersonValidator()); //在原有validator基础上添加
        }
    
        @Setter
        @Getter
        @ToString
        @NoArgsConstructor
        private static class Person{
            String name;
            int age;
        }
    
        private  static class PersonValidator implements Validator{
            @Override
            public boolean supports(Class<?> clazz) {
                System.out.println(clazz==Person.class);
                return clazz==Person.class;
            }
    
            @Override
            public void validate(Object target, Errors errors) {
                //validate手动就需要校验
                System.out.println("validate");
                Person person = (Person) target;
                if (null==person.getName()||person.getName().isEmpty()) {
                    errors.rejectValue("name", "field.empty",new Object[]{person.getName()}, "用户名不得为空");
                }
                if(person.getAge()==0||person.getAge()>150){
                    errors.rejectValue("age", "field.max",new Object[]{person.getAge()}, "用户年龄虚假");
                }
    
            }
        }
        //异常捕获,目的:返回JSON给前端,可以设置成全局的异常捕获结合@ControllerAdvice
        @ExceptionHandler(value = {BindException.class})
        public ResponseEntity invalidArgument(BindException ex){
            Map result=new HashMap<String,Object>();
            result.put("status_code",500);
            System.out.println("捕获到异常");
            List<FieldError> fieldErrors = ex.getFieldErrors();
            StringBuffer sb=new StringBuffer();
            for (FieldError error:fieldErrors) {
                sb.append(error.getField()+":"+error.getDefaultMessage()).append(",");
            }
    
            result.put("message",sb.substring(0,sb.length()-1));
            return new ResponseEntity(result, HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    测试效果图:

    image

    说明:其中需要注意如果多种参数校验,一定要注意binder.addValidator(….)方法,它只是添加自定义的Validator实现类,比如一个实体类有很多String字段,避免在Validator实现类中重复地判断不为空,结合@NotEmpty等会节约篇幅,有利于代码整洁。

  • 相关阅读:
    OK335x mksd.sh hacking
    Qt jsoncpp 对象拷贝、删除、函数调用 demo
    OK335xS 256M 512M nand flash make ubifs hacking
    Qt QScrollArea and layout in code
    JsonCpp Documentation
    Qt 4.8.5 jsoncpp lib
    Oracle数据库生成UUID
    freemarker得到数组的长度
    FreeMarker中if标签内的判断条件
    freemarker语法
  • 原文地址:https://www.cnblogs.com/lvbinbin2yujie/p/10611144.html
Copyright © 2011-2022 走看看