Spring Boot 集成 fluent-validator

阅读本文之前,请先熟读官网文档


阅读总结

  1. 验证结果对象
// 省缺结果对象:
toSimple() ---> Result
toComplex() ---> ComplexResult

// 如果你想自己实现一个结果类型,完全可以定制,实现ResultCollector接口即可
ResultCollector<T>
  1. 错误消息抛出
context.addErrorMsg("Something is wrong about the car seat count!");
// 简单错误消息
context.addError(ValidationError.create("Something is wrong about the car seat count!").setErrorCode(100).setField("seatCount").setInvalidValue(t));
// 完整错误消息

  1. 验证器
Validator<T> // 验证器
ValidatorChain // 调用链
onEach //验证集合
failFast() // 校验一个错误打回
failOver() // 完成所有检验打回
when() // 满足条件进行校验
ValidateCallback // doValidate()方法接受一个ValidateCallback接口

……

冗余的东西就不多写了,剩下的全都融合到Spring Boot的集成里去,官方文档写的很好,推荐耐心嚼烂。

开始集成

pox.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>demo</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>com.baidu.unbiz</groupId>
            <artifactId>fluent-validator-spring</artifactId>
            <version>1.0.9</version>
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>

创建实体类Car

可以看到我们用了HibernateValidator与fluent-validator验证注解,HibernateValidator负责简单的校验,fluent-validato去校验我们更高级的业务逻辑。

package com.example.demo.entity;

import com.baidu.unbiz.fluentvalidator.annotation.FluentValidate;
import com.example.demo.fluent.validator.CarSeatCountValidator;
import org.hibernate.validator.constraints.NotEmpty;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;

/**
 * @Author Z.xichao
 * @Create 2018-5-30
 * @Comments
 */
public class Car {

    @NotEmpty(message = "厂商信息不能为空")
    @Pattern(regexp = "[0-9a-zA-Z\4e00-\u9fa5]+",message = "厂商信息字符不合法")
    private String manufacturer;

    @NotNull(message = "车牌号不能为空")
    private String licensePlate;

    @FluentValidate({CarSeatCountValidator.class})
    private int seatCount;

    public String getManufacturer() {
        return manufacturer;
    }

    public void setManufacturer(String manufacturer) {
        this.manufacturer = manufacturer;
    }

    public String getLicensePlate() {
        return licensePlate;
    }

    public void setLicensePlate(String licensePlate) {
        this.licensePlate = licensePlate;
    }

    public int getSeatCount() {
        return seatCount;
    }

    public void setSeatCount(int seatCount) {
        this.seatCount = seatCount;
    }
}

Validator 校验器
package com.example.demo.fluent.validator;

import com.baidu.unbiz.fluentvalidator.ValidationError;
import com.baidu.unbiz.fluentvalidator.Validator;
import com.baidu.unbiz.fluentvalidator.ValidatorContext;
import com.baidu.unbiz.fluentvalidator.ValidatorHandler;
import org.springframework.stereotype.Component;

/**
 * @Author Z.xichao
 * @Create 2018-5-30
 * @Comments
 */
@Component
public class CarSeatCountValidator extends ValidatorHandler<Integer> implements Validator<Integer> {

    @Override
    public boolean validate(ValidatorContext context, Integer t) {
        if (t < 2) {
            context.addError(ValidationError.create("Something is wrong about the car seat count!").setErrorCode(100).setField("seatCount").setInvalidValue(t));
            return false;
        }
        return true;
    }
}

验证回调 ValidateCarCallback
package com.example.demo.validate_car_callback;

import com.baidu.unbiz.fluentvalidator.ValidateCallback;
import com.baidu.unbiz.fluentvalidator.ValidationError;
import com.baidu.unbiz.fluentvalidator.Validator;
import com.baidu.unbiz.fluentvalidator.validator.element.ValidatorElementList;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * @Author Z.xichao
 * @Create 2018-5-30
 * @Comments
 */
@Component
public class ValidateCarCallback implements ValidateCallback {
    /**
     * 所有验证完成并且成功后
     *
     * @param validatorElementList 验证器list
     */
    @Override
    public void onSuccess(ValidatorElementList validatorElementList) {
        System.out.println("Everything works fine!");
    }

    /**
     * 所有验证步骤结束,发现验证存在失败后
     *
     * @param validatorElementList 验证器list
     * @param errors               验证过程中发生的错误
     */
    @Override
    public void onFail(ValidatorElementList validatorElementList, List<ValidationError> errors) {
        throw new RuntimeException(errors.get(0).getErrorMsg()); //可自定义异常
    }

    /**
     * 执行验证过程中发生了异常后
     *
     * @param validator 验证器
     * @param e         异常
     * @param target    正在验证的对象
     * @throws Exception
     */
    @Override
    public void onUncaughtException(Validator validator, Exception e, Object target) throws Exception {
        throw new RuntimeException(e); //可自定义异常
    }
}

配置拦截器
package com.example.demo.config;

import com.baidu.unbiz.fluentvalidator.interceptor.FluentValidateInterceptor;
import com.example.demo.validate_car_callback.ValidateCarCallback;
import org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


/**
 * @Author Z.xichao
 * @Create 2018-5-30
 * @Comments
 */
@Configuration
public class FluentValidatorConfiguration {

    @Autowired
    private ValidateCarCallback validateCarCallback; // 注入我们的验证回调

    @Bean("fluentValidateInterceptor")
    public FluentValidateInterceptor fluentValidateInterceptor(){
        FluentValidateInterceptor validateInterceptor = new FluentValidateInterceptor();
        validateInterceptor.setCallback(validateCarCallback);
        validateInterceptor.setLocale("zh_CN");
        validateInterceptor.setHibernateDefaultErrorCode(10000);
        return  validateInterceptor;
    }

    @Bean
    public BeanNameAutoProxyCreator beanNameAutoProxyCreator(){
        BeanNameAutoProxyCreator proxyCreator = new BeanNameAutoProxyCreator();
        proxyCreator.setBeanNames("*Controller"); // 配置拦截对象 如拦截service(*ServiceImpl)
        proxyCreator.setInterceptorNames("fluentValidateInterceptor");
        return proxyCreator;
    }
}


好了万事俱备,只欠东风,让我们跑起来!!
package com.example.demo.controller;

import com.baidu.unbiz.fluentvalidator.annotation.FluentValid;
import com.example.demo.entity.Car;
import com.example.demo.service.CarService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author Z.xichao
 * @Create 2018-5-30
 * @Comments
 */
@RestController
public class TestController {

    @Autowired
    private CarService carService;

    @RequestMapping("/")
    public void root(@FluentValid Car car) {
        carService.addCart(car);
    }
}

访问http://localhost:8080/?manufacturer=aa&licensePlate=b

2018-05-30 17:47:24.128  INFO 5108 --- [  restartedMain] .ConditionEvaluationDeltaLoggingListener : Condition evaluation unchanged
2018-05-30 17:47:44.171  INFO 5108 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring FrameworkServlet 'dispatcherServlet'
2018-05-30 17:47:44.171  INFO 5108 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
2018-05-30 17:47:44.174  INFO 5108 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 3 ms
2018-05-30 17:47:44.182  INFO 5108 --- [nio-8080-exec-1] c.b.u.f.AnnotationValidatorCache         : Cached validator CarSeatCountValidator
2018-05-30 17:47:44.187 ERROR 5108 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: [ValidationError{errorCode=100, errorMsg='Something is wrong about the car seat count!', field='seatCount', invalidValue=0}]] with root cause

java.lang.RuntimeException: [ValidationError{errorCode=100, errorMsg='Something is wrong about the car seat count!', field='seatCount', invalidValue=0}]
    at com.example.demo.validate_car_callback.ValidateCarCallback.onFail(ValidateCarCallback.java:36) ~[classes/:na]
    at com.baidu.unbiz.fluentvalidator.FluentValidator.doValidate(FluentValidator.java:518) ~[fluent-validator-1.0.9.jar:na]
    at com.baidu.unbiz.fluentvalidator.interceptor.FluentValidateInterceptor.invoke(FluentValidateInterceptor.java:189) ~[fluent-validator-spring-1.0.9.jar:na]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185) ~[spring-aop-5.0.6.RELEASE.jar:5.0.6.RELEASE]
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688) ~[spring-aop-5.0.6.RELEASE.jar:5.0.6.RELEASE]
    at com.example.demo.controller.TestController$$EnhancerBySpringCGLIB$$feb78357.root(<generated>) ~[classes/:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_45]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_45]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_45]
    at java.lang.reflect.Method.invoke(Method.java:497) ~[na:1.8.0_45]
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:209) ~[spring-web-5.0.6.RELEASE.jar:5.0.6.RELEASE]
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:136) ~[spring-web-5.0.6.RELEASE.jar:5.0.6.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102) ~[spring-webmvc-5.0.6.RELEASE.jar:5.0.6.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:877) ~[spring-webmvc-5.0.6.RELEASE.jar:5.0.6.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:783) ~[spring-webmvc-5.0.6.RELEASE.jar:5.0.6.RELEASE]
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.0.6.RELEASE.jar:5.0.6.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:991) ~[spring-webmvc-5.0.6.RELEASE.jar:5.0.6.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925) ~[spring-webmvc-5.0.6.RELEASE.jar:5.0.6.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:974) ~[spring-webmvc-5.0.6.RELEASE.jar:5.0.6.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:866) ~[spring-webmvc-5.0.6.RELEASE.jar:5.0.6.RELEASE]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:635) ~[tomcat-embed-core-8.5.31.jar:8.5.31]
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:851) ~[spring-webmvc-5.0.6.RELEASE.jar:5.0.6.RELEASE]

结语

本篇博客能算个学习笔记,然后拿大家一起分享。重在抛砖引玉,可能以后会继续完善,先留几个坑。

比如context上下文共享、闭包、以及验证器的配置、when()、failFast()、failOver()等都要考虑怎么活用。总之先到这吧。各位道友,我们昆仑山见!

 

参考资料

点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注

This site uses Akismet to reduce spam. Learn how your comment data is processed.