转自: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> |