转自:http://www.importnew.com/18561.html
为什么要使用Bean Validation?
当我们实现某个接口时,都需要对入参数进行校验。例如下面的代码
1
2
3
4
5
|
public
String queryValueByKey(String parmTemplateCode, String conditionName, String conditionKey, String resultName) {
checkNotNull(parmTemplateCode,
"parmTemplateCode not null"
);
checkNotNull(conditionName,
"conditionName not null"
);
checkNotNull(conditionKey,
"conditionKey not null"
);
checkNotNull(resultName,
"resultName not null"
);
|
该方法输入的四个参数都是必填项。用代码进行参数验证带来几个问题
- 需要写大量的代码来进行参数验证。
- 需要通过注释来直到每个入参的约束是什么。
- 每个程序员做参数验证的方式不一样,参数验证不通过抛出的异常也不一样。
什么是Bean Validation?
Bean Validation是一个通过配置注解来验证参数的框架,它包含两部分Bean Validation API和Hibernate Validator。
- Bean Validation API是Java定义的一个验证参数的规范。
- Hibernate Validator是Bean Validation API的一个实现。
快速开始
引入POM
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
|
<!-- Bean Validation start -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>
5.1
.
1
.Final</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>
1.1
.
0
.Final</version>
</dependency>
<dependency>
<groupId>javax.el</groupId>
<artifactId>el-api</artifactId>
<version>
2.2
</version>
</dependency>
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>javax.el</artifactId>
<version>
2.2
.
4
</version>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>
3.1
.
3
.GA</version>
</dependency>
<dependency>
<groupId>com.fasterxml</groupId>
<artifactId>classmate</artifactId>
<version>
1.0
.
0
</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>
1.2
.
13
</version>
</dependency>
<!-- Bean Validation end -->
|
实例代码如下,可以验证Bean,也可以验证方法参数
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
|
import
java.lang.reflect.Method;
import
java.util.Set;
import
javax.validation.ConstraintViolation;
import
javax.validation.Validation;
import
javax.validation.Validator;
import
javax.validation.constraints.Max;
import
javax.validation.constraints.NotNull;
import
javax.validation.executable.ExecutableValidator;
public
class
BeanValidatorTest {
public
static
void
main(String[] args) {
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
//验证Bean参数,并返回验证结果信息
Car car =
new
Car();
Set<ConstraintViolation<Car>> validators = validator.validate(car);
for
(ConstraintViolation<Car> constraintViolation : validators) {
System.out.println(constraintViolation.getMessage());
}
// 验证方法参数
Method method =
null
;
try
{
method = Car.
class
.getMethod(
"drive"
,
int
.
class
);
}
catch
(SecurityException e) {
}
catch
(NoSuchMethodException e) {
}
Object[] parameterValues = {
80
};
ExecutableValidator executableValidator = validator.forExecutables();
Set<ConstraintViolation<Car>> methodValidators = executableValidator.validateParameters(car,
method, parameterValues);
for
(ConstraintViolation<Car> constraintViolation : methodValidators) {
System.out.println(constraintViolation.getMessage());
}
}
public
static
class
Car {
private
String name;
@NotNull
(message =
"车主不能为空"
)
public
String getRentalStation() {
return
name;
}
public
void
drive(
@Max
(
75
)
int
speedInMph) {
}
}
}
|
执行代码后,输出如下:
1
2
|
车主不能为空
最大不能超过
75
|
使用代码验证方法参数
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
27
28
29
30
31
32
33
34
35
|
import
static
com.google.common.collect.Iterables.getFirst;
import
java.util.Set;
import
javax.validation.ConstraintViolation;
import
javax.validation.Validation;
import
javax.validation.Validator;
/**
* 对象验证器
*
* @author tengfei.fangtf
* @version $Id: BeanValidator.java, v 0.1 Dec 30, 2015 11:33:40 PM tengfei.fangtf Exp $
*/
public
class
BeanValidator {
/**
* 验证某个bean的参数
*
* @param object 被校验的参数
* @throws ValidationException 如果参数校验不成功则抛出此异常
*/
public
static
<T>
void
validate(T object) {
//获得验证器
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
//执行验证
Set<ConstraintViolation<T>> constraintViolations = validator.validate(object);
//如果有验证信息,则将第一个取出来包装成异常返回
ConstraintViolation<T> constraintViolation = getFirst(constraintViolations,
null
);
if
(constraintViolation !=
null
) {
throw
new
ValidationException(constraintViolation);
}
}
}
|
我们可以在每个方法的第一行调用BeanValidator.validate来验证参数,测试代码如下,
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
|
import
static
junit.framework.Assert.assertEquals;
import
javax.validation.constraints.Max;
import
javax.validation.constraints.NotNull;
import
org.junit.Test;
/**
*
* @author tengfei.fangtf
* @version $Id: BeanValidatorTest.java, v 0.1 Dec 30, 2015 11:33:56 PM tengfei.fangtf Exp $
*/
public
class
BeanValidatorTest {
@Test
public
void
test() {
try
{
BeanValidator.validate(
new
Car());
}
catch
(Exception e) {
assertEquals(
"rentalStation 车主不能为空"
, e.getMessage());
}
}
public
static
class
Car {
private
String name;
@NotNull
(message =
"车主不能为空"
)
public
String getRentalStation() {
return
name;
}
public
void
drive(
@Max
(
75
)
int
speedInMph) {
}
}
}
|
使用拦截器验证方法参数
我们在对外暴露的接口的入参中使用Bean Validation API配置参数约束,如下XXXService接口
1
2
3
4
5
|
public
interface
XXXService {
GetObjectResponse getObject(GetObjectRequest getObjectRequest);
}
|
在getObject的GetObjectRequest参数中配置注解来约束参数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public
class
GetObjectRequest {
@Valid
@NotNull
private
ObjectKey objectKey;
@Size
(max =
9
)
private
Map<String, Object> parameters;
@AssertTrue
public
boolean
isEntityNameOrCodeAtLeastOneIsNotBlank() {
return
isNotBlank(entityName) || isNotBlank(entityCode);
}
//代码省略
}
|
编写参数验证拦截器,当方法被调用时,触发Validator验证器执行验证,如果不通过则抛出ParameterValidationException。
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
|
import
static
com.google.common.collect.Iterables.getFirst;
import
java.util.Set;
import
javax.validation.ConstraintViolation;
import
javax.validation.Validation;
import
javax.validation.Validator;
import
org.aopalliance.intercept.MethodInterceptor;
import
org.aopalliance.intercept.MethodInvocation;
import
org.slf4j.Logger;
import
org.slf4j.LoggerFactory;
import
com.xx.ParameterValidationException;
/**
* 参数验证拦截器,基于JSR-303 BeanValidation
*
* @author tengfei.fangtf
*
* @version $Id: TitanValidateInterceptor.java, v 0.1 Nov 23, 2015 11:13:55 PM tengfei.fangtf Exp $
*/
public
class
TitanValidateInterceptor
implements
MethodInterceptor {
private
static
final
Logger LOGGER = LoggerFactory.getLogger(TitanValidateInterceptor.
class
);
private
final
Validator validator;
public
TitanValidateInterceptor() {
validator = Validation.buildDefaultValidatorFactory().getValidator();
}
@Override
public
Object invoke(MethodInvocation invocation)
throws
Throwable {
if
(LOGGER.isDebugEnabled()) {
LOGGER.debug(
"Validate arguments"
);
}
//获取参数,并检查是否应该验证
Object[] arguments = invocation.getArguments();
for
(Object argument : arguments) {
if
(LOGGER.isDebugEnabled()) {
LOGGER.debug(
"Validate argument: {}"
, argument);
}
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(argument);
ConstraintViolation<Object> constraintViolation = getFirst(constraintViolations,
null
);
if
(constraintViolation ==
null
) {
continue
;
}
if
(LOGGER.isInfoEnabled()) {
LOGGER.info(
"ConstraintViolation: {}"
, constraintViolation);
}
throw
new
ParameterValidationException(constraintViolation.getPropertyPath() +
" "
+ constraintViolation.getMessage());
}
return
invocation.proceed();
}
}
|
配置拦截器core-service.xml,拦截XXXService的所有方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
<?xml version=
"1.0"
encoding=
"UTF-8"
?>
<beans xmlns=
"http://www.springframework.org/schema/beans"
xmlns:xsi=
"http://www.w3.org/2001/XMLSchema-instance"
xmlns:p=
"http://www.springframework.org/schema/p"
xmlns:context=
"http://www.springframework.org/schema/context"
xmlns:webflow=
"http://www.springframework.org/schema/webflow-config"
xsi:schemaLocation="http:
//www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http:
//www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http:
//www.springframework.org/schema/webflow-config http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.0.xsd"
default
-autowire=
"byName"
>
<bean id=
"XXXService"
class
=
"org.springframework.aop.framework.ProxyFactoryBean"
>
<property name=
"target"
>
<bean
class
=
"com.XXXService"
/>
</property>
<property name=
"interceptorNames"
>
<list>
<value>validateInterceptor</value>
</list>
</property>
</bean>
<bean id=
"validateInterceptor"
class
=
"com.mybank.bkloanapply.common.validator.ValidateInterceptor"
/>
</beans>
|