springMVC引入Validation的具体步骤详解

时间:2022-09-04 21:59:44

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

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

基本配置

pom引入maven依赖

?
1
2
3
4
5
6
7
8
9
10
11
12
<!-- 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中增加如下配置:

?
1
2
3
4
5
6
7
<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中增加如下配置:

?
1
2
3
4
5
6
7
8
9
10
11
<!-- 加载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资源文件:

?
1
2
3
4
5
6
#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实现:

validationexceptionresovler.java

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
@slf4j
public class validationexceptionresovler extends abstracthandlerexceptionresolver {
  public validationexceptionresovler() {
    // 设置order,在defaulthandlerexceptionresolver之前执行
    this.setorder(0);
  }
  /**
   * handle the case where an argument annotated with {@code @valid} such as
   * an {@link } or {@link } argument fails validation.
   * <p>
   * 自定义validationexception 异常处理器
   * 获取到具体的validation 错误信息,并组装commonresponse,返回给调用方。
   *
   * @param request current http request
   * @param response current http response
   * @param handler the executed handler
   * @return an empty modelandview indicating the exception was handled
   * @throws ioexception potentially thrown from response.senderror()
   */
  @responsebody
  protected modelandview handlemethodargumentnotvalidexception(bindingresult bindingresult,
                                 httpservletrequest request,
                                 httpservletresponse response,
                                 object handler)
      throws ioexception {
 
    list<objecterror> errors = bindingresult.getallerrors();
    stringbuffer errmsgbf = new stringbuffer();
    for (objecterror error : errors) {
      string massage = error.getdefaultmessage();
      errmsgbf.append(massage);
      errmsgbf.append("||");
    }
    string errmsgstring = errmsgbf.tostring();
    errmsgstring = errmsgstring.length() > 2 ? errmsgstring.substring(0, errmsgstring.length() - 2) : errmsgstring;
    log.error("validation failed! {} ", errmsgstring);
 
    map<string, object> map = new treemap<string, object>();
    map.put("success", false);
    map.put("errorcode", "9999");
    map.put("errormsg", errmsgstring);
 
    modelandview mav = new modelandview();
    mappingjackson2jsonview view = new mappingjackson2jsonview();
    view.setattributesmap(map);
    mav.setview(view);
 
    return mav;
  }
 
  @override
  protected modelandview doresolveexception(httpservletrequest request,
                       httpservletresponse response, object handler,
                       exception ex) {
    bindingresult bindingresult = null;
    if (ex instanceof methodargumentnotvalidexception) {
      bindingresult = ((methodargumentnotvalidexception) ex).getbindingresult();
    } else if(ex instanceof bindexception) {
      bindingresult = ((bindexception) ex).getbindingresult();
    } else {
      //other exception , ignore
    }
 
    if(bindingresult != null) {
      try {
        return handlemethodargumentnotvalidexception(bindingresult, request, response, handler);
      } catch (ioexception e) {
        log.error("doresolveexception: ", e);
      }
    }
    return null;
  }
}

在controller中增加@valid 

?
1
2
3
4
5
@requestmapping("/buy")
@responsebody
public baseresponse buy(@requestbody @valid buyflowerrequest request) throws exception {
 //......
}

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

?
1
2
3
4
5
6
7
@setter
@getter
public class buyflowerrequest {
 
@notempty(message = "{name.can.not.be.null}")
private string name;
}

二级对象的validation

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

如下写法:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@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;
}

进一步减少编码量

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

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

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

notnull.java

?
1
2
3
4
5
6
7
8
9
@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 {};
}

2、定义validator,所有的validator均实现constraintvalidator接口:

notnullvalidator.java

?
1
2
3
4
5
6
7
8
9
10
11
12
public class notnullvalidator implements constraintvalidator<notnull, object> {
 
  @override
  public void initialize(notnull annotation) {
 
  }
 
  @override
  public boolean isvalid(object str, constraintvalidatorcontext constraintvalidatorcontext) {
    return str != null;
  }
}

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

buyflowerrequest.java

?
1
2
3
4
5
6
7
8
9
10
@setter
@getter
public class buyflowerrequest {
 
  @notempty(field = "花名")
  private string name;
 
  @min(field = "价格", value = 1)
  private int price;
}

注:@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()方法。

使用示例:

?
1
2
3
4
5
6
7
8
9
@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);
}

todo

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

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

 附:spring validation实现关键代码

@requestbody

实现类:requestresponsebodymethodprocessor.java

?
1
2
3
4
5
6
7
8
9
10
11
12
13
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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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);
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。

原文链接:http://www.cnblogs.com/daoqidelv/p/9061862.html