zoukankan      html  css  js  c++  java
  • springMVC引入Validation详解

    本文简单介绍如何引入validation的步骤,如何通过自定义validation减少代码量,提高生产力。特别提及:非基本类型属性的valid,GET方法的处理,validation错误信息的统一resolve。

    本文中validation的实际实现委托给Hibernate validation处理

    基本配置

    pom引入maven依赖

    <!-- validation begin -->
    <dependency>
        <groupId>javax.validation</groupId>
        <artifactId>validation-api</artifactId>
        <version>1.1.0.Final</version>
    </dependency>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>5.4.0.Final</version>
    </dependency>
    <!-- validation end -->

    增加validation配置

    在spring-mvc-servlet.xml中增加如下配置:

    <mvc:annotation-driven validator="validator">
    
    <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
        <property name="providerClass" value="org.hibernate.validator.HibernateValidator" />
        <property name="validationMessageSource" ref="messageSource"/>
    </bean>
     
    //messageSource 为i18n资源管理bean,见applicationContext.xml配置

    自定义exceptionHandler

    个性化处理validation错误信息,返回给调用方的信息更加友好,在applicationContext.xml中增加如下配置:

    <!--  加载i18n消息资源文件 -->
    <bean id="messageSource"  class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basenames">
            <list>
                <value>errormsg</value>
                <value>validation_error</value>
            </list>
        </property>
    </bean>
    
    <bean id="validationExceptionResolver" class="com.*.exception.ValidationExceptionResovler"/>

    在项目类路径上增加:validation_error_zh_CN.properties资源文件:

    #the error msg for input validation
    #common field.can.not.be.null={field}不能为空 field.can.not.be.empty={field}不能为空或者空字符串 field.must.be.greater.than.min={field}不能小于{value} field.must.be.letter.than.max={field}不能大于{value}
    ValidationExceptionResovler实现:
     1 @Slf4j
     2 public class ValidationExceptionResovler extends AbstractHandlerExceptionResolver {
     3     public ValidationExceptionResovler() {
     4         // 设置order,在DefaultHandlerExceptionResolver之前执行
     5         this.setOrder(0);
     6     }
     7 
     8     /**
     9      * Handle the case where an argument annotated with {@code @Valid} such as
    10      * an {@link } or {@link } argument fails validation.
    11      * <p>
    12      * 自定义ValidationException 异常处理器
    13      * 获取到具体的validation 错误信息,并组装CommonResponse,返回给调用方。
    14      *
    15      * @param request  current HTTP request
    16      * @param response current HTTP response
    17      * @param handler  the executed handler
    18      * @return an empty ModelAndView indicating the exception was handled
    19      * @throws IOException potentially thrown from response.sendError()
    20      */
    21     @ResponseBody
    22     protected ModelAndView handleMethodArgumentNotValidException(BindingResult bindingResult,
    23                                                                  HttpServletRequest request,
    24                                                                  HttpServletResponse response,
    25                                                                  Object handler)
    26             throws IOException {
    27 
    28         List<ObjectError> errors = bindingResult.getAllErrors();
    29         StringBuffer errmsgBF = new StringBuffer();
    30         for (ObjectError error : errors) {
    31             String massage = error.getDefaultMessage();
    32             errmsgBF.append(massage);
    33             errmsgBF.append("||");
    34         }
    35         String errmsgString = errmsgBF.toString();
    36         errmsgString = errmsgString.length() > 2 ? errmsgString.substring(0, errmsgString.length() - 2) : errmsgString;
    37         log.error("Validation failed! {} ", errmsgString);
    38 
    39         Map<String, Object> map = new TreeMap<String, Object>();
    40         map.put("success", false);
    41         map.put("errorCode", "9999");
    42         map.put("errorMsg", errmsgString);
    43 
    44         ModelAndView mav = new ModelAndView();
    45         MappingJackson2JsonView view = new MappingJackson2JsonView();
    46         view.setAttributesMap(map);
    47         mav.setView(view);
    48 
    49         return mav;
    50     }
    51 
    52     @Override
    53     protected ModelAndView doResolveException(HttpServletRequest request,
    54                                               HttpServletResponse response, Object handler,
    55                                               Exception ex) {
    56         BindingResult bindingResult = null;
    57         if (ex instanceof MethodArgumentNotValidException) {
    58             bindingResult = ((MethodArgumentNotValidException) ex).getBindingResult();
    59         } else if(ex instanceof BindException) {
    60             bindingResult = ((BindException) ex).getBindingResult();
    61         } else {
    62             //other exception , ignore
    63         }
    64 
    65         if(bindingResult != null) {
    66             try {
    67                 return handleMethodArgumentNotValidException(bindingResult, request, response, handler);
    68             } catch (IOException e) {
    69                 log.error("doResolveException: ", e);
    70             }
    71         }
    72 
    73         return null;
    74     }
    75 }
    ValidationExceptionResovler.java

    在controller中增加@Valid

    @RequestMapping("/buy")
    @ResponseBody
    public BaseResponse buy(@RequestBody @Valid BuyFlowerRequest request) throws Exception {
      //......
    }

    在request bean上为需要validation的属性增加validation注解

    @Setter
    @Getter
    public class BuyFlowerRequest {
    
        @NotEmpty(message = "{name.can.not.be.null}") 
    private String name;

    二级对象的validation

    上面的写法,只能对BuyFlowerRequest在基本类型属性上做校验,但是没有办法对对象属性的属性进行validation,如果需要对二级对象的属性进行validation,则需要在二级对象及二级对象属性上同时添加@Valid 和 具体的validation注解.

    如下写法:

    @Setter
    @Getter
    public class BuyFlowerRequest {
    
        @NotEmpty(field = "花名")
        private String name;
    
        @Min(field = "价格", value = 1)
        private int price;
    
        @NotNull
        private List<PayType> payTypeList;
    
    } 
    
    @Setter
    @Getter
    public class PayType {
    
        @Valid
        @Min(value = 1)
        private int payType;
    
        @Valid
        @Min(value = 1)
        private int payAmount;
    
    }
    View Code 

    进一步减少编码量

    为了减少编码工作量,通过自定义Validation注解,尝试将validation作用的filed名称传递到 错误信息的资源文件中,从而避免为每个域编写不同的message模版.

    下面以重写的@NotNull为例讲解:

    1、定义Validation注解,注意相比原生注解增加了field(),用于传递被validated的filed名字

    @Target( { ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER })
    @Constraint(validatedBy = { NotNullValidator.class })
    @Retention(RetentionPolicy.RUNTIME)
    public @interface NotNull {
    
        String field() default "";
    
        String message() default "{field.can.not.be.null}";
    
        Class<?>[] groups() default {};
    
        Class<? extends Payload>[] payload() default {};
    }
    NotNull.java

    2、定义Validator,所有的Validator均实现ConstraintValidator接口:

     1 public class NotNullValidator implements ConstraintValidator<NotNull, Object> {
     2 
     3     @Override
     4     public void initialize(NotNull annotation) {
     5 
     6     }
     7 
     8     @Override
     9     public boolean isValid(Object str, ConstraintValidatorContext constraintValidatorContext) {
    10         return str != null;
    11     }
    12 
    13 }
    NotNullValidator.java

    3、在filed上加入Validation注解,注意指定filed值,message如果没有个性化需求,可以不用指明,validation组件会自行填充default message。

    @Setter
    @Getter
    public class BuyFlowerRequest {
    
        @NotEmpty(field = "花名")
        private String name;
    
        @Min(field = "价格", value = 1)
        private int price;
    
    } 
    BuyFlowerRequest.java


    注:@NotNull注解已经支持对list的特殊校验,对于List类型节点,如果list==null || list.size() == 0都会返回false,validation失败。目前已按照此思路自定义实现了@NotNull、@NotEmpty、@Min、@Max注解,在goods工程中可以找到.

    支持GET请求

    上面的示例都是POST请求,@RequestBody可以 resolve POST请求,但是不支持GET请求,阅读spring的文档和源码,发现@ModelAttribute可以将GET请求resolve成Bean,且支持Validation。具体可以翻阅spring源码:ModelAttributeMethodProcessor.resolveArgument()方法。

    使用示例:

    @RequestMapping(value = "/buy", method = RequestMethod.GET)
    @ResponseBody
    public BaseResponse detail(@Valid @ModelAttribute DetailFlowerRequest request) throws Exception {
    
        DetailFlowerResponse response = new DetailFlowerResponse();
        response.setName(request.getName());
    
        return ResultFactory.success(response, BaseResponse.class);
    }
    View Code

    TODO

    1、根据业务场景扩展validation,如:日期格式、金额等

    2、支持多个field关系校验的validation

     附:spring validation实现关键代码

    @RequestBody

    实现类:RequestResponseBodyMethodProcessor.java

    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
    Object arg = this.readWithMessageConverters(webRequest, parameter, parameter.getGenericParameterType());
    String name = Conventions.getVariableNameForParameter(parameter);
    WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
    if (arg != null) {
    this.validateIfApplicable(binder, parameter);
    if (binder.getBindingResult().hasErrors() && this.isBindExceptionRequired(binder, parameter)) {
    throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
    }
    }

    mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
    return arg;
    }

    @ModelAttibute

    实现类:ModelAttributeMethodProcessor.java

    public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
    String name = ModelFactory.getNameForParameter(parameter);
    Object attribute = mavContainer.containsAttribute(name) ? mavContainer.getModel().get(name) : this.createAttribute(name, parameter, binderFactory, webRequest);
    if (!mavContainer.isBindingDisabled(name)) {
    ModelAttribute ann = (ModelAttribute)parameter.getParameterAnnotation(ModelAttribute.class);
    if (ann != null && !ann.binding()) {
    mavContainer.setBindingDisabled(name);
    }
    }

    WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
    if (binder.getTarget() != null) {
    if (!mavContainer.isBindingDisabled(name)) {
    this.bindRequestParameters(binder, webRequest);
    }

    this.validateIfApplicable(binder, parameter);
    if (binder.getBindingResult().hasErrors() && this.isBindExceptionRequired(binder, parameter)) {
    throw new BindException(binder.getBindingResult());
    }
    }

    Map<String, Object> bindingResultModel = binder.getBindingResult().getModel();
    mavContainer.removeAttributes(bindingResultModel);
    mavContainer.addAllAttributes(bindingResultModel);
    return binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
    }
  • 相关阅读:
    hdu5628 Clarke and math
    LOJ#2452. 「POI2010」反对称 Antisymmetry
    LOJ#2444. 「NOI2011」阿狸的打字机
    BZOJ2795: [Poi2012]A Horrible Poem
    LOJ#2427. 「POI2010」珍珠项链 Beads
    云主机文件系统readonly处理案例
    兼容性测试中如何切换和管理多个JDK版本
    Promise之你看得懂的Promise
    一次MySQL线上慢查询分析及索引使用
    考拉消息中心消息盒子处理重构(策略模式)
  • 原文地址:https://www.cnblogs.com/daoqidelv/p/9061862.html
Copyright © 2011-2022 走看看