前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >为什么要使用Bean Validation?

为什么要使用Bean Validation?

作者头像
周三不加班
发布2019-09-03 10:15:10
8040
发布2019-09-03 10:15:10
举报
文章被收录于专栏:程序猿杂货铺程序猿杂货铺

代码预览格式不佳的可以点击最下方 阅读原文

为什么要使用Bean Validation?

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

代码语言:javascript
复制
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

注意:springboot中是内置了Bean Validation 支持的 因此不需要引入任何包即可使用

验证 Bean
代码语言:javascript
复制
<!-- 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>

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

代码语言:javascript
复制
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) {

        }

    }

执行代码后,输出如下:

代码语言:javascript
复制
车主不能为空
最大不能超过75
使用代码验证方法参数

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

代码语言:javascript
复制
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 tengzhoudangtf
 * @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来验证参数,测试代码如下,

代码语言:javascript
复制
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接口

代码语言:javascript
复制
public interface XXXService {
    GetObjectResponse getObject(GetObjectRequest getObjectRequest);
}

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

代码语言:javascript
复制
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);
    }
//代码省略
}

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

代码语言:javascript
复制
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的所有方法。

代码语言:javascript
复制
<?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>
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-11-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员啊粥 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 为什么要使用Bean Validation?
  • 什么是Bean Validation?
  • 快速开始
    • 引入POM
      • 验证 Bean
        • 使用代码验证方法参数
          • 使用拦截器验证方法参数
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档