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);
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM