基于SpringBoot参数校验器拓展自定义参数校验

简介: 想必工作中大家为了保证接口的稳定性与安全性都会对入参进行校验。五花八门的校验写法会让我们的代码不够整洁,本文将介绍如何使用SpringBoot为我们提供的参数校验器,并对其进行扩展,让其能够实现自定义校验。当然在一些互联网项目中,为保证接口的高性能,校验都是放在前端做的,但是在阿里开发规约中是这样说的越是简单的接口越不需要进行参数校验,越是复杂的接口越需要参数校验,因为复杂的接口试错成本很高,校验对接口性能的影响微乎其微。

工程中的使用可参照我的开源项目:https://gitee.com/zhuhuijie/base-platform

在common-web模块中引入在example-business中使用

SpringBoot 参数校验器的使用

本章通过怎么引入SpringBoot的参数校验器,让大家能够搭建一个简单的Demo,文章的第二部分,自定义扩展才是本文的重头戏。

1 首先,pom文件引入参数校验器的依赖

<!--参数校验-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

2 入参VO,加入相关的注解

常用注解:

  • @NotNull 非空验证
  • @Min(value = 1, message = "年龄不能小于1") @Max(value = 25, message = "年龄不能超过25")

    值区间验证

  • @Email(message = "必须是邮箱格式") 邮箱格式验证
  • @Past(message = "生日范围不正确,生日必须是今天以前的")

    验证是否是过去的时间

import com.fasterxml.jackson.annotation.JsonFormat;
import com.zhj.business.protocol.validhandler.BirthdayValidHandler;
import com.zhj.common.web.valid.annotation.MyValid;
import lombok.Data;
​
import javax.validation.constraints.*;
import java.util.Date;
​
/**
 * @author zhj
 */
@Data
public class StudentInput {
​
    @NotNull(message = "名字不能为空")
    private String name;
    @Min(value = 1, message = "年龄不能小于1")
    @Max(value = 25, message = "年龄不能超过25")
    private Integer age;
    @NotNull(message = "邮箱不能为空")
    @Email(message = "必须是邮箱格式")
    private String email;
    @NotNull(message = "性别不能为空")
    private Integer sex;
    @NotNull(message = "生日不能为空")
    @Past(message = "生日范围不正确,生日必须是今天以前的")
    @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
    private Date birthday;
}

3 Controller 中开启校验,切记开启才会生效

import com.zhj.business.protocol.input.StudentInput;
import com.zhj.business.service.StudentService;
import com.zhj.common.core.result.Result;
import com.zhj.common.core.util.ResultUtils;
import com.zhj.data.entity.example.Student;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
​
import javax.validation.Valid;
import java.util.List;
​
@Slf4j
@RestController
@RequestMapping("/student")
public class StudentController {
​
    @Autowired
    private StudentService studentService;
​
    @PostMapping("/add")
    public Result add(@Valid @RequestBody StudentInput studentInput) {
        log.info("接收到的学生信息:" + studentInput);
        Student student = new Student();
        BeanUtils.copyProperties(studentInput, student);
        boolean result = studentService.save(student);
        log.info("保存学生的结果" + result);
        return ResultUtils.createSuccess(student);
    }
}

SpringBoot 参数校验器的扩展

本章将通过实现年龄与生日是否匹配的校验为例,为我们展示如何通过注解实现这类复杂的参数校验

1 首先呢,我们扩展需要参数校验器需要通过自定义注解实现

import com.zhj.common.web.valid.constraint.MyParameterValid;
import com.zhj.common.web.valid.handler.MyValidHandler;
​
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
​
/**
 * 自定义校验注解
 * @author zhj
 */
@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = MyParameterValid.class) // 对应校验类型
public @interface MyValid {
​
    /**
     * 失败提示
     * @return
     */
    String message() default "校验失败";
​
    /**
     * 校验分组
     * @return
     */
    Class<?>[] groups() default {};
​
    /**
     * 校验的负载
     * @return
     */
    Class<? extends Payload>[] payload() default {};
​
    /**
     * 扩展校验方法
     * @return
     */
    Class<? extends MyValidHandler> handler();
}

2 实现对Springboot参数校验器的自定义扩展

  • 通过实现ConstraintValidator<MyValid, Object> 官方为我们提供的扩展接口,完成自定义注解的初始化
  • 从新实现校验方法

    • 通过Spring容器获取自定义参数校验处理器MyValidHandler
    • 然后将注解与校验对象传入自定义的校验方法
    • 执行自定义校验方法,根据返回结果判断是否校验通过
import com.zhj.common.web.util.ApplicationContextUtils;
import com.zhj.common.web.valid.annotation.MyValid;
import com.zhj.common.web.valid.handler.MyValidHandler;
import lombok.extern.slf4j.Slf4j;
​
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.Optional;
​
/**
 * 自定义参数校验类
 * @author zhj
 */
@Slf4j
public class MyParameterValid implements ConstraintValidator<MyValid, Object> {
​
    private MyValid myValid;
​
    @Override
    public void initialize(MyValid constraintAnnotation) {
        this.myValid = constraintAnnotation;
    }
​
    /**
     * 校验方法
     * @param o
     * @param constraintValidatorContext
     * @return
     */
    @Override
    public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {
        log.info("自定义参数校验触发" + o);
        if (null != o) {
            Class<? extends MyValidHandler> handler = myValid.handler();
            // 交给 MyValidHandler 处理校验
            MyValidHandler myValidHandler = ApplicationContextUtils.getBean(handler);
            return Optional
                    .ofNullable(myValidHandler)
                    .map(myValidHandler1 -> {
                return myValidHandler.valid(myValid, o);
            }).orElse(false);
        }
        return true;
    }
}

3 自定义处理接口

自定义校验方法通过实现该接口,重写校验方法

通过接口的多实现从而实现各种自定义处理。

import com.zhj.common.web.valid.annotation.MyValid;
​
/**
 * 扩展接口,开发者实现该接口,扩展校验方式
 * @author zhj
 */
public interface MyValidHandler<T> {
​
    /**
     * 实现校验方法
     * @param data
     * @return
     */
    boolean valid(MyValid myValid, T data);
}

4 获取Spring容器中对象的工具类

通过Spring上下文根据类名查找对应的实现

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
​
import javax.annotation.PostConstruct;
​
/**
 * Spring 容器工具方法
 * @author zhj
 */
@Component
public class ApplicationContextUtils {
​
    @Autowired
    private ApplicationContext applicationContext;
​
    /**
     * 静态容器对象
     */
    private static ApplicationContext staticApplicationContext;
​
    @PostConstruct
    private void init() {
        ApplicationContextUtils.staticApplicationContext = applicationContext;
    }
​
    public static <T> T getBean(Class<T> cls) {
        if (staticApplicationContext != null) {
            return staticApplicationContext.getBean(cls);
        }
        return null;
    }
}

5 在入参VO类上添加自定义注解

handler = BirthdayValidHandler.class 将自定义校验方法的类名传入,该类需要实现自定义处理接口

import com.fasterxml.jackson.annotation.JsonFormat;
import com.zhj.business.protocol.validhandler.BirthdayValidHandler;
import com.zhj.common.web.valid.annotation.MyValid;
import lombok.Data;
​
import javax.validation.constraints.*;
import java.util.Date;
​
/**
 * @author zhj
 */
@Data
@MyValid(message = "年龄和生日不匹配", handler = BirthdayValidHandler.class)
public class StudentInput {
​
    @NotNull(message = "名字不能为空")
    private String name;
    @Min(value = 1, message = "年龄不能小于1")
    @Max(value = 25, message = "年龄不能超过25")
    private Integer age;
    @NotNull(message = "邮箱不能为空")
    @Email(message = "必须是邮箱格式")
    private String email;
    @NotNull(message = "性别不能为空")
    private Integer sex;
    @NotNull(message = "生日不能为空")
    @Past(message = "生日范围不正确,生日必须是今天以前的")
    @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
    private Date birthday;
}

6 创建注解所写类名,实现自定义校验

切记需要将该对象注册进Spring容器中,否则扩展方式无法获取到该实例

本示例是实现,年龄与生日关系的校验,年龄和生日必须匹配才会校验成功

import com.zhj.business.protocol.input.StudentInput;
import com.zhj.common.web.valid.annotation.MyValid;
import com.zhj.common.web.valid.handler.MyValidHandler;
import org.springframework.stereotype.Component;
​
import java.util.Calendar;
import java.util.Date;
​
/**
 * @author zhj
 */
@Component
public class BirthdayValidHandler implements MyValidHandler<StudentInput> {
​
    @Override
    public boolean valid(MyValid myValid, StudentInput data) {
        Integer age = data.getAge();
        Date birthday = data.getBirthday();
        if (age == null || birthday == null) return true;
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        int currYear = calendar.get(Calendar.YEAR);
        calendar.setTime(birthday);
        int birYear = calendar.get(Calendar.YEAR);
        return currYear - birYear == age;
    }
}
目录
相关文章
|
20天前
|
Java 数据库连接 Spring
Spring Boot命令行启动添加参数
Spring Boot命令行启动添加参数
|
1月前
|
XML Java 数据库连接
spring boot 参数的过滤注解与实战
在Spring Boot应用中,对于入参的过滤,通常会涉及到对Web层的数据验证和处理。Spring Boot借助Spring框架提供了强大的验证框架支持,主要基于JSR-303/JSR-380(Bean Validation API)规范,以及Spring自身的@Valid或@Validated注解来实现请求参数的验证。以下是一些常见的使用案例来展示如何对参数进行过滤和验证。
29 1
|
1月前
|
缓存 前端开发 Java
【二十八】springboot之通过threadLocal+参数解析器实现同session一样保存当前登录信息的功能
【二十八】springboot之通过threadLocal+参数解析器实现同session一样保存当前登录信息的功能
36 1
|
4天前
|
缓存 Java Sentinel
Springboot 中使用 Redisson+AOP+自定义注解 实现访问限流与黑名单拦截
Springboot 中使用 Redisson+AOP+自定义注解 实现访问限流与黑名单拦截
|
1月前
|
Java 数据库 数据安全/隐私保护
【SpringBoot】Validator组件+自定义约束注解实现手机号码校验和密码格式限制
【SpringBoot】Validator组件+自定义约束注解实现手机号码校验和密码格式限制
118 1
|
3天前
|
Java Spring
Spring Boot脚手架集成校验框架
Spring Boot脚手架集成校验框架
11 0
|
8天前
|
前端开发 Java
SpringBoot之实体参数的详细解析
SpringBoot之实体参数的详细解析
11 0
|
10天前
|
JSON Java 数据格式
Spring Boot实现各种参数校验
这些是Spring Boot中实现参数校验的一些常见方法,你可以根据项目需求选择适合的方式来进行参数校验。
13 0
|
1月前
|
前端开发 Java Spring
Java 新手如何使用Spring MVC 中的查询字符串和查询参数
Java 新手如何使用Spring MVC 中的查询字符串和查询参数
|
1月前
|
前端开发 NoSQL Java
【SpringBoot】秒杀业务:redis+拦截器+自定义注解+验证码简单实现限流
【SpringBoot】秒杀业务:redis+拦截器+自定义注解+验证码简单实现限流