Spring Boot 集成 fluent-validator

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()等都要考虑怎么活用。总之先到这吧。各位道友,我们昆仑山见!




参考资料

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266