如何使用validation类似于AOP切面的形式抽象参数的校验逻辑?

开发中,通常操作逻辑都是先进性数据校验,校验完毕以后才进行真正的逻辑处理。往往数据的校验逻辑比较众多,并且校验的逻辑由易到难,并且相同的校验逻辑在多处使用。例如,添加商品规格时,对价格,库存,商品重量等数值需要进行大于等于0的数值校验;对关联的商品id进行id的数据有效性校验。在修改商品规格时,也同样会执行相似的校验。这时,我们会想到应该对相似的业务逻辑进行抽象处理,封装校验逻辑。

这时,可以使用javax.validation校验注解,类似于AOP面向切面的实现数据的校验。在springboot中引入spring-boot-starter-web依赖,就会自动引入hibernate-validator。如图:

如何使用validation类似于AOP切面的形式抽象参数的校验逻辑?_第1张图片

 

内置注解

使用javax.validation校验参数,一般在controller层,对方法参数进行校验。常用的注解包括:@NotNull,值不能为空,@Positive,数字为正数,@Size,字符串大小限制。示例:

/**
 * @Author iloveoverfly
 **/
@Data
public class UserAddDto implements Serializable {
    private static final long serialVersionUID = -6630904002198113779L;
    
    @NotEmpty(message = "用户名称为空")
    private String username;
    
    @NotNull(message = "用户的类型为空")
    private Integer category;

}

 

扩展注解

在系统中,会根据业务新增不同的校验逻辑,例如,电话号码的校验,数字类型枚举值校验,数据id的有效性校验等等。示例,定义注解@EffectiveValue

进行数据有效性校验,定义ValueValidator接口实现具体的校验逻辑,EffectiveValue注解定义如下:

@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(EffectiveValue.List.class)
@Documented
@Constraint(
        validatedBy = {EffectiveValueConstraint.class}
)
public @interface EffectiveValue {

    String message() default "{javax.validation.constraints.EffectiveValue.message}";

    Class[] groups() default {};

    Class[] payload() default {};

    /**
     * spring 容器的中服务bean
     */
    Class serviceBean();

    /**
     * 被校验值允许为空
     */
    boolean shouldBeNull() default false;

    @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface List {
        EffectiveValue[] value();
    }
}

ValueValidator接口定义如下:

public interface ValueValidator {

    boolean validate(T value);
}

示例,定义产品id数据有效性校验,代码如下:

@Slf4j
@Component
public class IdOfProductValidator implements ValueValidator {

    @Autowired
    private ProductService productService;

    @Override
    public boolean validate(Long id) {

        if (Objects.isNull(id)) {
            return false;
        }
        // id对应数据在数据库已经存在
        return Objects.nonNull(productService.get(ProductPageQueryVo.builder()
                .id(id)
                .notStatus(ProductStatus.ABANDONED.getStatus()).build()));
    }
}

在添加商品规格和修改商品时,对商品id进行校验,使用注解实现了类似AOP的横切处理,示例如下:

// 添加商品规格时,产品id校验
public class AddGoodsVo implements Serializable {

    private static final long serialVersionUID = -2980901104711589147L;

    @NotNull(message = PRODUCT_ID_IS_NULL)
    @EffectiveValue(shouldBeNull = true, serviceBean = IdOfProductValidator.class, message = PRODUCT_ID_IS_INVALID)
    private Long productId;
}

// 修改商品规格时,产品id校验
public class UpdateGoodsVo implements Serializable {

    // 如果需要修改产品id,就对该id就行数据有效性校验
    @EffectiveValue(shouldBeNull = true, serviceBean = IdOfProductValidator.class, message = PRODUCT_ID_IS_INVALID, groups = {ValidateGroup.Second.class})
    private Long productId;
}

 

 

@Validated与@Valid使用

Validated和Valid都可以实现参数校验。Validated能够对参数校验可以进行分组,用于根据不同的分组生效指定的校验逻辑。定义ValidateGroup,用于却分不同校验的分组,UserAddOrUpdateDto,用于用户新增或者修改的共同参数,代码如下:

// 校验规则的分组
public class ValidateGroup {

    /**
     * 新增分组
     */
    public interface Add {
    }

    /**
     * 修改操作分组
     */
    public interface Update {
    }
}

// 用户新增或者修改的参数部分规则
@Data
public class UserAddOrUpdateDto implements Serializable {

	private static final long serialVersionUID = -3968061262394340781L;

	@NotNull(message = "id为空", groups = {ValidateGroup.Update.class})
	private Long id;

	@NotEmpty(message = "名称为空", groups = {ValidateGroup.Add.class, ValidateGroup.Update.class})
	private String name;
}

// 用户新增和修改api
@RestController
@RequestMapping("/users")
@Slf4j
public class UserController {

    @Autowired
    private IUserManager userManager;

    // 用户新增
    @PostMapping("/add")
    public Response addUser(@Validated(ValidateGroup.Add.class) @RequestBody UserAddOrUpdateDto userAddDto) {
        return Response.success(this.userManager.addUser(userAddDto));
    }
    // 用户修改
    @PutMapping("/update")
    public Response updateUser(@Validated(ValidateGroup.Update.class) @RequestBody UserAddOrUpdateDto userUpdateDto) {
        return Response.success(userManager.userUpdateDto(userUpdateDto));
    }
}

 

Valid用于生效包含类参数的校验逻辑,示例,用户参数中的权限校验参数中,权限编码不能为空。代码如下:

// 用户新增或者修改的参数部分规则
@Data
public class UserAddOrUpdateDto implements Serializable {

	private static final long serialVersionUID = -3968061262394340781L;

	@NotNull(message = "id为空", groups = {ValidateGroup.Update.class})
	private Long id;

	@NotEmpty(message = "名称为空", groups = {ValidateGroup.Add.class, ValidateGroup.Update.class})
	private String name;

    // 权限校验字段生效
    @Valid
    private List permissions;

    // 用户拥有权限
    @Data
    public final class PermissionDto implements Serializable {
        private static final long serialVersionUID = 9057606562562888975L;

        private Long id;

        private String name;

        @NotEmpty(message = "权限编码为空")
        private String code;
    }
    
}

校验顺序的控制

在逻辑校验的过程中,一般会有一个校验顺序。例如,对产品id进行校验时,首先判断id是否存在;其次,根据该id查询数据库进行有效性校验。这就存在一个先后顺序。在validation中,可以通过分组来实现。例如,定义分组的类,代码如下:

public class ValidateGroup {

    /**
     * 第一组
     */
    public interface First {
    }

    /**
     * 第二组
     */
    public interface Second {
    }
}

在校验注解上添加对应的分组,代码如下:

public class AddGoodsVo implements Serializable {

    private static final long serialVersionUID = -2980901104711589147L;

    // 第一组校验
    @NotNull(message = PRODUCT_ID_IS_NULL, groups = {ValidateGroup.First.class})
    // 第二组校验
    @EffectiveValue(shouldBeNull = true, serviceBean = IdOfProductValidator.class, message = PRODUCT_ID_IS_INVALID, groups = {ValidateGroup.Second.class})
    private Long productId

}

在校验的对象上,使用@Validated定义校验的顺序,代码如下:

// 定义  First ,Second的校验顺序
public Response saveGoods(@Validated({ValidateGroup.First.class, ValidateGroup.Second.class})
                                         @RequestBody AddGoodsVo addGoodsVo) {
.......
}

 

 

你可能感兴趣的:(java,#,java插件,java,spring,validation)