在系统中使用Bean Validation验证参数

为什么要使用Bean Validation?

 当我们实现某个接口时,都需要对入参数进行校验。例如下面的代码

[code lang=”java”]
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");
[/code]


该方法输入的四个参数都是必填项。用代码进行参数验证带来几个问题

  • 需要写大量的代码来进行参数验证。
  • 需要通过注释来直到每个入参的约束是什么。
  • 每个程序员做参数验证的方式不一样,参数验证不通过抛出的异常也不一样。

什么是Bean Validation?

Bean Validation是一个通过配置注解来验证参数的框架,它包含两部分Bean Validation API和Hibernate Validator。

  • Bean Validation API是Java定义的一个验证参数的规范。
  • Hibernate Validator是Bean Validation API的一个实现。

快速开始

引入POM

[code]
<!– 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 –>
[/code]

实例代码如下,可以验证Bean,也可以验证方法参数

[code lang=”java”]

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) {

}

}
}
[/code]

执行代码后,输出如下:

[code lang=”java”]
车主不能为空
最大不能超过75
[/code]

使用代码验证方法参数

Validation验证不成功可能返回多个验证错误信息,我们可以包装下,当有错误时直接返回第一个错误的异常。

[code lang=”java”]
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);
}
}

}
[/code]
我们可以在每个方法的第一行调用BeanValidator.validate来验证参数,测试代码如下,
[code lang=”java”]
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) {

}

}

}
[/code]

使用拦截器验证方法参数

我们在对外暴露的接口的入参中使用Bean Validation API配置参数约束,如下XXXService接口

[code lang=”java”]
public interface XXXService {

GetObjectResponse getObject(GetObjectRequest getObjectRequest);

}
[/code]

在getObject的GetObjectRequest参数中配置注解来约束参数。

[code lang=”java”]
public class GetObjectRequest {

@Valid
@NotNull
private ObjectKey objectKey;

@Size(max = 9)
private Map&lt;String, Object&gt; parameters;

@AssertTrue
public boolean isEntityNameOrCodeAtLeastOneIsNotBlank() {
return isNotBlank(entityName) || isNotBlank(entityCode);
}
//代码省略
}
[/code]

编写参数验证拦截器,当方法被调用时,触发Validator验证器执行验证,如果不通过则抛出ParameterValidationException。

[code lang=”java”]
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();
}

}
[/code]

配置拦截器core-service.xml,拦截XXXService的所有方法。
[code]
<?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>

[/code]

参考资料

原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: 在系统中使用Bean Validation验证参数

  • Trackback 关闭
  • 评论 (2)
    • FranklinD
    • 2016/01/01 3:54下午

    求问一个问题,比方说我有两个业务,一个是修改car信息,一个是添加car信息,但是修改car信息的时候参数中的id不能为空,但是添加car信息时id就可能是空的,那这样的话该怎么验证呢?

      • gordianyuan
      • 2016/01/06 8:58上午

      可以在修改CAR信息的时候对CAR ID做非NULL验证,checkNotNull(car.getId(), “Car ID must not be null.”)。

      Bean Validation用于验证通用且必须符合的验证条件。

return top