我用自定义注解优雅的实现了业务的复杂校验

本文介绍了一种通过自定义注解实现业务参数校验的方法,将验证逻辑与业务逻辑分离,提高代码清晰度并遵循单一职责原则。作者详细展示了如何创建@ValidatorHandler注解,以及如何配合校验类进行参数校验的实践和测试过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景

假设我们要开发一个创建超级俱乐部会员的功能,要求创建的条件为:

  1. 会员的id要求为【1-10】

  2. 电话号码要求为185开头

  3. 注册日期不能小于5月15日

通常情况下我们会这么写:

public void creatSupperClubMember(Member member) {
  //1.校验会员id
  //2.校验电话号码
  //3.校验注册日期
  
  //4.创建超级俱乐部会员
}

上面这个方法中,我们发现除了超级会员的创建,我们有一大半的代码是在校验参数。这样有个问题就是我们正真需要的代码是4,从某种程度上来说1、2、3是多余的代码。

如果我们的校验非常复杂,那么这个方法就会显得难以阅读,通过分析我们发现这个方法其实做了两件事

  1. 校验参数

  2. 创建超级会员

因此我们将代码重构下:

public void creatSupperClubMember(Member member) {
 //1.参数校验
    validatorBeforeCreate()
 //2.创建超级俱乐部会员
}

public void validatorBeforeCreate() {
  //1.校验会员id
  //2.校验电话号码
  //3.校验注册日期
}

这样我们的代码会显得更加清晰。事实上,我们在编写方法时,需要考虑单一职责原则,业务的参数校验从某种程度上来说属于非业务代码,上面的功能我们可以抽象出:

校验

没错,就是业务逻辑与非业务逻辑,我们有没有方法可以将业务逻辑与非业务逻辑解耦呢。

我们可以使用注解校验。

其实我们在平时的开发中,很多地方都是用了注解的校验:

上面的代码相信大家都写过,我们不需要在方法中去写参数的校验,我们在字段上使用注解,就是实现了参数的必填校验,范围校验。

但是已有的注解无法满足我们的要求,实际的参数校验比较复杂。因此我决定自己写一个参数校验的注解。

注解模型

我想要的需求是

@ValidatorHandler(validators = XXXXValidator.class)
public int createXXX(XXX xxx) {

}

我们在写的业务代码上面添加一个@ValidatorHandler注解,注解里面的XXXXValidator类就是真正写校验功能的类,会把业务参数传到这个校验类中。

public class XXXValidator extends AbstractValidator {
 @Override
 public void check(Object o) {
        //获取参数
  XXXx xxx = (XXX)o;
        //相关的业务校验
 }
}

校验类模型如上所示。

注解

我们通过注解将业务代码与校验代码分开。

编写校验注解

首先我们来定义注解:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidatorHandler {
    /**
     *  校验的类
     */
    Class<?> validators();
}

该注解作用与方法上,里面的参数validators为校验类的class。

然后编写实现校验注解的功能

为了清晰展现代码,我用图片表示。

上述代码实现了几个功能:

实例化validators对应的校验类

获取业务参数,并将参数传递到校验类中的check方法的参数中

执行校验类中的check方法

执行业务代码

为了规范校验类编写,我们需要定义一个接口

public interface AbstractValidator {
    void check(Object o);
}

所有的校验类都要是AbstractValidator的实现,并实现check方法。

这里的注解实现功能中,我只获取了业务功能中第一个参数,也就是说我们的业务方法的第一个参数会被校验,大家可以思考下:如果业务功能的参数有多个,该注解的功能类怎么编写?

代码测试

以上我们的注解就开发好了,我们开始测试下功能:

首先我们编写校验类:

如上图,校验类中我们实现了文章开头要求的三个校验功能。

然后我们编写业务代码,创建超级俱乐部会员

@ValidatorHandler(validators = MemberValidator.class)
public void creatSupperClubMember(Member member) {
    System.out.println("member开始新增");
    //这里就是我们的业务代码
    System.out.println("member新增结束");
}

我们再写一个方法,模拟前端传入的数据:

测试方法如下:

@Test
public void testCreateMember() {
    Member member = initData();
    testMemberService.creatSupperClubMember(member);
    System.out.println("创建结束");
}

校验id

private Member initData() {
    Member member = new Member();
    member.setId("11");
    member.setMobile("17790990033");
    member.setCreateBy("李老师");
    member.setCreateDate(new Date());
    member.setIsActive("Y");
    return member;
}

执行测试方法,发现控制台打印如下,我们发现id = 11不符合要求,校验成功。

我们修改id = 5,id的校验通过。

member.setId("5");

校验电话号码moblie

然后我们再次执行发现手机号不是185开头,这里的校验也成功。

修改手机号为185的,校验通过。

member.setMobile("18590990033");

校验注册日期

member的参数中,注册日期为:2021-05-15小于2021-05-20,不满足要求。

private Member initData() {
    String registerDateStr = "2021-05-15";
    Date registerDate = DateUtil.parse(registerDateStr);

    Member member = new Member();
    member.setId("5");
    member.setMobile("18590990033");
    member.setCreateBy("李老师");
    member.setCreateDate(registerDate);
    member.setIsActive("Y");
    return member;
}

控制台会出现如下错误,校验成功。

校验全部通过

private Member initData() {
    Member member = new Member();
    member.setId("5");
    member.setMobile("18590990033");
    member.setCreateBy("李老师");
    member.setCreateDate(new Date());
    member.setIsActive("Y");
    return member;
}

当我们的参数全部满足业务校验需求时,我们的校验通过,就会创建超级俱乐部会员。

以上就是这篇文章的全部内容了,当我们的业务逻辑校验很复杂时,我们可以使用上面的校验注解将校验逻辑与业务逻辑分开,这样有利于业务与非业务解耦,也满足设计原则的单一职责原则。除了方便阅读,还有的好处就是,当我们不需要校验时,我们可以将业务方法上的校验注解注释掉,这样我们就不必在业务代码中去修改了,从而减少了因修改业务代码导致bug的风险。

如果文章对你有用,欢迎点赞转发😀。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值