手把手写一个基于Spring Boot框架下的参数校验组件(JSR-303)

前言  

        之前参与的新开放平台研发的过程中,由于不同的接口需要对不同的入参进行校验,这就涉及到通用参数的校验封装,如果不进行封装,那么写出来的校验代码将会风格不统一、校验工具类不一致、维护风险高等其它因素,于是我对其公共的校验做了一个封装,达到了通过注解的方式即可实现参数统一校验。

遇到的问题

                      在封装的时候就发现了一个问题,我们是开放平台,返回的报文都必须是统一风格,也就是类似于{code:999,msg:"参数校验失败",data:null} 这种,但是原生的JSR303并不支持自定义的字段,所以需要自定义校验注解。针对这个问题我参考一些JSR303的资料,对其进行了一个定制扩展,以达到开发人员不需要关注捕捉和封装返回信息。

  

传统的校验做法 

        如下校验如果一个实体里面上百个字段需要校验的话,对于维护起来是一个很麻烦的事情,而且很多校验可以通过jsr-303的注解方式统一处理,无需写一大堆if和else

 if(name == null) {
     //返回错误信息
 }else if(age == null) {
     //返回错误信息
}

基于jsr-303定制后的校验

  1. 定义一个自定义非空注解
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = { CorpNotEmptyValidator.class })
public @interface CorpNotEmpty {

    //自定义字段
    String field() default  "";
   //返回错误码
    String code() default  "0";
    //错误消息
    String message() default "{javax.validation.constraints.NotNull.message}";

    Class<?>[] groups() default { };

    Class<? extends Payload>[] payload() default { };

    @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
    @Retention(RUNTIME)
    @Documented
    @interface List {
        CorpNotEmpty[] value();
    }
}

   2.定义非空注解对应的校验器, initialize和isValid作用描述如下:

  •   initialize方法主要是初始化ReturnCodeModel,用于当校验参数不通过后返回,ReturnCodeModel里面主要是封装了返回体,如 code,message等
  • isValid主要是自定义校验器的校验规则,如下判断是否为空使用 StringUtils.isEmpty方法,如果校验不通过则set flag为false,然后调用基类的isValid方法,该基类方法会判断flag是否为false,如果是false说明不通过
public class CorpNotEmptyValidator extends BaseCorpValidator<CorpNotEmpty,String> {
    @Override
    public void initialize(CorpNotEmpty constraintAnnotation) {
        model = new ReturnCodeModel();
        model.setCode(constraintAnnotation.code());
        model.setErrorMsg(constraintAnnotation.message());
        model.setField(constraintAnnotation.field());
    }

    @Override
    public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
        System.out.println("1");
        if(StringUtils.isEmpty(s)){
            model.setFlag(false);
        }else{
            model.setFlag(true);
        }
        return super.isValid(s,constraintValidatorContext);
    }
}

  3.定义一个基类,实现 ConstraintValidator,主要是因为需要把isValid这个方法定义成抽象方法提供给不同的校验器使用,避免其它校验器写重复的代码

public abstract class BaseCorpValidator<A extends Annotation,B> implements ConstraintValidator<A ,B> {
    protected ReturnCodeModel model = null;
    @Override
    public boolean isValid(B value, ConstraintValidatorContext context) {
        if(!model.getFlag()){
            context.disableDefaultConstraintViolation();
            context.buildConstraintViolationWithTemplate(JSON.toJSONString(model)).addConstraintViolation();
            return false;
        }
        return true;
    }

}

  4.测试类

public class TestV1 {
    static ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
    static Validator validator = validatorFactory.getValidator();
    public static void main(String[] args) {
        UserModel userModel = new UserModel();
        userModel.setName("aa");
        userModel.setDate("2011");
        Set<ConstraintViolation<UserModel>> constraintViolations  = validator.validate(userModel);
        //判断constraintViolations是否为空,不为空说明校验不通过,拿到ReturnCodeModel里面的错误信息后返回给客户端
        if(!constraintViolations.isEmpty()){
            for (ConstraintViolation<?> item : constraintViolations) {
                ReturnCodeModel codeModel = JSON.parseObject(item.getMessage(), ReturnCodeModel.class);
                System.out.println(JSON.toJSONString(codeModel));
            }
        }
    }
}

画外音:场景考虑

  1.比如name这个字段,要满足既不能为空又只能为数字这2个情况,如果把2个校验方法都写在同一个校验器,则其他开发使用的时候也会影响到,所以需要有2个注解的方式,一个是校验为空,一个是校验是否位数字,分析完后那么就存在一个先后顺序问题(因为自己在本地测试出现有可能会先执行校验是否位数字的校验器,这时候就会出现空指针异常), 所以针对这个场景需要自定义一个顺序注解。

  如下代码,在需要校验的model实体上加入@GroupSequence注解,这样校验器底层会帮我们按照顺序依次处理

//顺序注解
@GroupSequence({
    First.class,
    Two.class,
    Three.class,
    UserModel.class
})
public class UserModel {
	@CorpMustNumber(code="-2",message="必须数字",groups=Two.class)//在执行数字校验
	@CorpNotEmpty(code="-1",message="姓名必填",groups=First.class)//先执行非空
	private String name;
	@CorpNotEmpty(code="-1",message="日期必填")
	private String date;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getDate() {
		return date;
	}
	public void setDate(String date) {
		this.date = date;
	}

}

  

First

Two

总结

    以上就是本篇博客涉及到技术点的所有代码,通过定制自己的校验器以满足公司业务场景,对于开发来说统一了规范,统一风格,对以后维护还是扩展都非常方便。 如果博文对你有帮助麻烦点个关注或者赞,谢谢!

原文地址:https://www.cnblogs.com/zdd-java/p/12240929.html

时间: 2024-08-24 13:52:12

手把手写一个基于Spring Boot框架下的参数校验组件(JSR-303)的相关文章

手把手写一个基于Spring Boot框架下的参数校验组件

手把手写一个基于Spring Boot框架下的参数校验组件(JSR-303) 前言 之前参与的新开放平台研发的过程中,由于不同的接口需要对不同的入参进行校验,这就涉及到通用参数的校验封装,如果不进行封装,那么写出来的校验代码将会风格不统一.校验工具类不一致.维护风险高等其它因素,于是我对其公共的校验做了一个封装,达到了通过注解的方式即可实现参数统一校验. 遇到的问题                    在封装的时候就发现了一个问题,我们是开放平台,返回的报文都必须是统一风格,也就是类似于{co

在Spring Boot框架下使用WebSocket实现消息推送

Spring Boot的学习持续进行中.前面两篇博客我们介绍了如何使用Spring Boot容器搭建Web项目(使用Spring Boot开发Web项目)以及怎样为我们的Project添加HTTPS的支持(使用Spring Boot开发Web项目(二)之添加HTTPS支持),在这两篇文章的基础上,我们今天来看看如何在Spring Boot中使用WebSocket. 什么是WebSocket WebSocket为浏览器和服务器之间提供了双工异步通信功能,也就是说我们可以利用浏览器给服务器发送消息,

基于Spring Boot框架企业级应用系统开发全面实战

Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置.通过这种方式,Boot致力于在蓬勃发展的快速应用开发领域(rapid application development)成为领导者.    教程由浅入深,一步一步学习Spring Boot,最后学到的不单单是基础! 使用Spring Boot 进行Web 开发.数据访问.安全控制.批处理.异步消息.系统集

Spring Boot入门第二天:一个基于Spring Boot的Web应用,使用了Spring Data JPA和Freemarker。

今天打算从数据库中取数据,并展示到视图中.不多说,先上图: 第一步:添加依赖.打开pom.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&q

基于Spring Boot和Spring Cloud实现微服务架构学习(四)

Spring Cloud介绍 Spring Cloud是一个基于Spring Boot实现的云应用开发工具,它为基于JVM的云应用开发中的配置管理.服务发现.断路器.智能路由.微代理.控制总线.全局锁.决策竞选.分布式会话和集群状态管理等操作提供了一种简单的开发方式. Spring Cloud与Dubbo对比 提到Dubbo,我想顺便提下ESB,目前央视新华社也在用ESB来做任务编排,这里先比较下Dubbo和ESB: ESB(企业数据总线),一般采用集中式转发请求,适合大量异构系统集成,侧重任务

基于Spring Boot和Spring Cloud实现微服务架构学习

Spring Cloud介绍 Spring Cloud是一个基于Spring Boot实现的云应用开发工具,它为基于JVM的云应用开发中的配置管理.服务发现.断路器.智能路由.微代理.控制总线.全局锁.决策竞选.分布式会话和集群状态管理等操作提供了一种简单的开发方式. Spring Cloud与Dubbo对比 提到Dubbo,我想顺便提下ESB,目前央视新华社也在用ESB来做任务编排,这里先比较下Dubbo和ESB: ESB(企业数据总线),一般采用集中式转发请求,适合大量异构系统集成,侧重任务

基于Spring Boot+Cloud构建微云架构

前言 首先,最想说的是,当你要学习一套最新的技术时,官网的英文文档是学习的最佳渠道.因为网上流传的多数资料是官网翻译而来,很多描述的重点也都偏向于作者自身碰到的问题,这样就很容易让你理解和操作出现偏差,最开始我就进入了这样误区.官网的技术导读真的描述的很详细,虽然对于我们看英文很费劲,但如果英文不是很差,请选择沉下心去读,你一定能收获好多. 我的学习是先从Spring boot开始的,然后接触到微服务架构,当然,这一切最大的启迪还是感谢我的一个老师,是他给我指明了新的道路,让我眼前一亮,再次感谢

基于Spring Boot构建的Spring MVC快速入门

原文地址:http://tianmaying.com/tutorial/spring-mvc-quickstart 环境准备 一个称手的文本编辑器(例如Vim.Emacs.Sublime Text)或者IDE(Eclipse.Idea Intellij) Java环境(JDK 1.7或以上版本) Maven 3.0+(Eclipse和Idea IntelliJ内置,如果使用IDE并且不使用命令行工具可以不安装) 一个最简单的Web应用 使用Spring Boot框架可以大大加速Web应用的开发过

(Spring Boot框架)快速入门

Spring Boot 系列文章推荐 Spring Boot 入门 Spring Boot 属性配置和使用 Spring Boot 集成MyBatis Spring Boot 静态资源处理 今天介绍一下如何利用Spring MVC快速的搭建一个简单的web应用. 环境准备 一个称手的文本编辑器(例如Vim.Emacs.Sublime Text)或者IDE(Eclipse.Idea Intellij) Java环境(JDK 1.7或以上版本) Maven 3.0+(Eclipse和Idea Int