欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

springMVC引入Validation的具体步骤详解

程序员文章站 2023-12-18 12:26:58
本文简单介绍如何引入validation的步骤,如何通过自定义validation减少代码量,提高生产力。特别提及:非基本类型属性的valid,get方法的处理,valid...

本文简单介绍如何引入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实现:

validationexceptionresovler.java

@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 

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

进一步减少编码量

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

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

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

notnull.java

@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

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

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

使用示例:

@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

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

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

上一篇:

下一篇: