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