• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

优化参数前置校验

武飞扬头像
hyzhan43
帮助3

前言

我们通常写接口都会用到 @Valid 注解,通过 @NotNull,@NotEmpty 等等来简单校验我们的接口入参。

但是有些入参,需要查询数据库,这时候 @Valid 自带的校验注解,就满足不了需求了。

所以我们来看看 一些前置校验 优化的“骚操作”

废话不多说,上代码。

场景

我们常常在 Service 层会做一些前置条件的判断,如判断这个用户是否存在,如下代码所示:

public void create(UserCreateDTO dto) {
  	 
    // ----> 前置判断 
  	User existUsername = userRepository.findByName(dto.getName());
    Fire.checkNotNull(existUsername, GlobalErrorCode.USER_NAME_IS_EXIST);
    // <----
  
    //...省略其他
    User user = new User();
    user.setName(dto.getName());
    user.setEmail(dto.getEmail());
    userRepository.save(user);
}

这里前置简单校验了 Username 是否存在,其中 Fire 是异常封装类,如果 existUsername 为空,则抛出异常。

简单优化

public void create(UserCreateDTO dto) {
  	 
    // ----> 前置判断 
  	usernameValidate.validate(dto.getName());
    // <----
  
    //...省略其他
    User user = new User();
    user.setName(dto.getName());
    user.setEmail(dto.getEmail());
    userRepository.save(user);
}

@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class UsernameValidate {

    private final UserRepository userRepository;

    public void validate(String name) {
        User existUsername = userRepository.findByName(name);
        Fire.checkNotNull(existUsername, GlobalErrorCode.USER_NAME_IS_EXIST);
    }
}

这种前置校验,当然也可以用简单抽取 UsernameValidate,进行封装处理。

但是这样也避免不了,手动调用 validate 进行校验。

@Valid优化

思考着,能不能通过注解,解决这种手动调用问题。

于是乎看见了,@NotNull 这些校验注解,感觉有搞头,尝试自定义,最终实现如下效果。

@Data
public class UserCreateDTO {

    // 增加自定义校验注解
    @CustomValid(NameRepeatValidator.class)
    @NotNull(message = "名称不能为空")
    @ApiModelProperty(value = "名称", required = true)
    private String name;

  	...省略其他参数
}

@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class NameRepeatValidator implements IValidator<String> {

    private final UserRepository userRepository;

    @Override
    public void valid(String arg) {
        User existUser = userRepository.findByName(arg);
        Fire.checkNotNull(existUser, GlobalErrorCode.USER_NAME_IS_EXIST);
    }
}

public void create(UserCreateDTO dto) {
  	
    // 移除
    // User existUser = userRepository.findByName(dto.getName());
    // Fire.checkNotNull(existUser, GlobalErrorCode.USER_NAME_IS_EXIST);
  
    // 专注业务逻辑
    User user = new User();
    user.setPassword(PasswordEncoder.encode(dto.getPassword()));
    user.setName(dto.getName());
    user.setEmail(dto.getEmail());
    userRepository.save(user);
}

只需在 校验的入参,增加 @CustomValid注解,指定对应 NameRepeatValidator 校验类,即可校验 name 是否存在。

NameRepeatValidator 类的 valid 方法,相信大家都看得懂,主要核心的是 IValidator 接口,接着往下看

当然别忘了在接口参数上 增加 @Validated 注解,开启校验.

原理

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
//												vvv 指定对应校验器 
@Constraint(validatedBy = CustomValidator.class)
public @interface CustomValid {

    // 额外增加参数
    Class<? extends IValidator<?>> value();

    String message() default "";  // 默认需要

    Class<?>[] groups() default {};  // 默认需要

    Class<? extends Payload>[] payload() default {}; // 默认需要
}

@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class CustomValidator implements ConstraintValidator<CustomValid, Object> {

    private CustomValid customValid;

    private final CustomValidatorStore customValidatorStore;

    @Override
    public void initialize(CustomValid constraintAnnotation) {
        this.customValid = constraintAnnotation;
    }

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        if (customValid != null) {
            customValidatorStore.valid(value, customValid.value());
        }
        return true;
    }
}

这里实现一个 CustomValidator自定义 @Valid 校验。

@CustomValid 注解实现,参考 @NotNull 实现

  • 指定 CustomValidator 为校验器

  • 增加多一个 value 参数,后续获取自定义前置校验器使用

CustomValidator 校验器需实现 ConstraintValidator 接口,指定自定义注解,以及校验参数类型

实现类还需实现 initializeisValid 方法

  • initialize:看着方法名,就知道是来初始化的~
  • isValid:这里主要做参数校验逻辑。CustomValidatorStore 实现如下代码所示:

更多 自定义 @Valid 用法,这里就不展开细讲了,可以出门右转看看其他大佬文章。

@Component
public class CustomValidatorStore {

    private final Map<Class<?>, IValidator<?>> validatorMap;

    @Autowired
    public CustomValidatorStore(List<IValidator<?>> validators) {
        validatorMap = new HashMap<>();
        validators.forEach(validator -> validatorMap.put(validator.getClass(), validator));
    }

    @SuppressWarnings("unchecked")
    public <V> void valid(Object object, Class<?> clazz) {
        IValidator<V> validator = (IValidator<V>) validatorMap.get(clazz);
        validator.valid((V) object);
    }
}

CustomValidatorStore 主要是管理 前置校验器,通过 Spring 构造函数注入对应 IValidator 实现类,然后根据 Class 转化成对应的 Map,便于后续 valid 处理。

总结

至此,通过一个 @CustomValid 就可以愉快校验入参了,这样 Service 层就可以专注于业务逻辑,无需关注入参。

但是这场景相对单一,同时也会引入新的问题:

  • 如果出现两个参数同时校验。

  • 如果返回校验参数值,如下代码,校验完 username 不存在重复,可能后续业务逻辑,需要这个 existUser 参数。

User existUser = userRepository.findByName(arg);
// 可能后续业务逻辑,需要这个 existUser 参数。

在这里埋下坑,后续想到好的方法在分享,或者有想法的小伙伴可以分享你的方法~

最后

如有不妥,欢迎指出,大家一起交流学习。

感谢阅读,下次再见。

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhfeegfa
系列文章
更多 icon
同类精品
更多 icon
继续加载