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