在系統中使用Bean Validation驗證引數
阿新 • • 發佈:2018-12-22
為什麼要使用Bean Validation?
當我們實現某個介面時,都需要對入引數進行校驗。例如下面的程式碼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
<!-- 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,也可以驗證方法引數
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) { } } }
執行程式碼後,輸出如下:
車主不能為空 最大不能超過75
使用程式碼驗證方法引數
Validation驗證不成功可能返回多個驗證錯誤資訊,我們可以包裝下,當有錯誤時直接返回第一個錯誤的異常。
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來驗證引數,測試程式碼如下,
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介面
public interface XXXService { GetObjectResponse getObject(GetObjectRequest getObjectRequest); }
在getObject的GetObjectRequest引數中配置註解來約束引數。
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。
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的所有方法。
<?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>