基于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;
    }
}
目录
相关文章
|
2月前
|
SQL 监控 druid
springboot-druid数据源的配置方式及配置后台监控-自定义和导入stater(推荐-简单方便使用)两种方式配置druid数据源
这篇文章介绍了如何在Spring Boot项目中配置和监控Druid数据源,包括自定义配置和使用Spring Boot Starter两种方法。
|
1月前
|
并行计算 Java 数据处理
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
163 0
|
1月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
167 2
|
1月前
|
JSON 前端开发 Java
Spring MVC——获取参数和响应
本文介绍了如何在Spring框架中通过不同的注解和方法获取URL参数、上传文件、处理cookie和session、以及响应不同类型的数据。具体内容包括使用`@PathVariable`获取URL中的参数,使用`MultipartFile`上传文件,通过`HttpServletRequest`和`@CookieValue`获取cookie,通过`HttpSession`和`@SessionAttribute`获取session,以及如何返回静态页面、HTML代码片段、JSON数据,并设置HTTP状态码和响应头。
50 1
Spring MVC——获取参数和响应
|
2月前
|
Java Spring
springboot静态资源目录访问,及自定义静态资源路径,index页面的访问
本文介绍了Spring Boot中静态资源的访问位置、如何进行静态资源访问测试、自定义静态资源路径和静态资源请求映射,以及如何处理自定义静态资源映射对index页面访问的影响。提供了两种解决方案:取消自定义静态资源映射或编写Controller来截获index.html的请求并重定向。
springboot静态资源目录访问,及自定义静态资源路径,index页面的访问
|
1月前
|
JSON NoSQL Java
springBoot:jwt&redis&文件操作&常见请求错误代码&参数注解 (九)
该文档涵盖JWT(JSON Web Token)的组成、依赖、工具类创建及拦截器配置,并介绍了Redis的依赖配置与文件操作相关功能,包括文件上传、下载、删除及批量删除的方法。同时,文档还列举了常见的HTTP请求错误代码及其含义,并详细解释了@RequestParam与@PathVariable等参数注解的区别与用法。
|
2月前
|
缓存 前端开发 Java
springboot 的单体服务 字典参数转译
本文介绍了如何在Spring Boot项目中使用缓存来管理字典参数,并确保前后端数据一致性。首先,通过`@EnableCaching`启用缓存功能,接着创建一个自定义的字典缓存类`DicCache`。然后,通过配置类将`DicCache`添加到`cacheManager`中。此外,对字典服务进行改造,使用`@CachePut`和`@CacheEvict`注解保证数据一致性。最后,实现自定义注解`@DicSerializer`和序列化处理类`DictSerializerHandel`,用于在序列化过程中自动转换字典值。通过这种方式,可最小化代码改动并提高系统性能。
springboot 的单体服务 字典参数转译
|
1月前
|
前端开发 Java Spring
【Spring】“请求“ 之传递单个参数、传递多个参数和传递对象
【Spring】“请求“ 之传递单个参数、传递多个参数和传递对象
114 2
|
1月前
|
缓存 NoSQL Java
Springboot自定义注解+aop实现redis自动清除缓存功能
通过上述步骤,我们不仅实现了一个高度灵活的缓存管理机制,还保证了代码的整洁与可维护性。自定义注解与AOP的结合,让缓存清除逻辑与业务逻辑分离,便于未来的扩展和修改。这种设计模式非常适合需要频繁更新缓存的应用场景,大大提高了开发效率和系统的响应速度。
62 2
|
2月前
|
JSON Java 数据格式
springboot 参数统一处理
springboot 参数统一处理
下一篇
无影云桌面