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实现:
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); }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。