写点什么

如何在 SpringBoot 中优雅的做参数校验?

  • 2024-07-30
    福建
  • 本文字数:3976 字

    阅读完需:约 13 分钟

一、故事背景


关于参数合法性验证的重要性就不多说了,即使前端对参数做了基本验证,后端依然也需要进行验证,以防不合规的数据直接进入服务器,如果不对其进行拦截,严重的甚至会造成系统直接崩溃


本文结合自己在项目中的实际使用经验,主要以实用为主,对数据合法性验证做一次总结,不了解的朋友可以学习一下,同时可以立马实践到项目上去。


下面我们通过几个示例来演示如何判断参数是否合法,废话不多说,直接撸起来!


二、断言验证


对于参数的合法性验证,最初的做法比较简单,自定义一个异常类。

public class CommonException extends RuntimeException {
private Integer code;
public Integer getCode() { return code; }
public CommonException(String message) { super(message); this.code = 500; }
public CommonException(Integer code, String message) { super(message); this.code = code; }}
复制代码


当检查到某个参数不合法的时候,直接抛异常!

@RestControllerpublic class HelloController {
@RequestMapping("/upload") public void upload(MultipartFile file) { if (file == null) { throw new CommonException("请选择上传文件!"); } //..... }}
复制代码


最后写一个统一异常拦截器,对抛异常的逻辑进行兜底处理。


这种做法比较简单直观,如果当前参数既要判断是否为空,又要判断长度是否超过最大限制的时候,代码就会显得很臃肿,而且复用性很差


于是,程序界的大佬想到了一个更加优雅又能节省代码的方式,创建一个断言类工具类,专门用来判断参数的是否合法,如果不合法就抛异常,示例如下:

/** * 断言工具类 */public abstract class LocalAssert {        public static void isTrue(boolean expression, String message) throws CommonException {        if (!expression) {            throw new CommonException(message);        }    }    public static void isStringEmpty(String param, String message) throws CommonException{        if(StringUtils.isEmpty(param)) {            throw new CommonException(message);        }    }
public static void isObjectEmpty(Object object, String message) throws CommonException { if (object == null) { throw new CommonException(message); } }
public static void isCollectionEmpty(Collection coll, String message) throws CommonException { if (coll == null || (coll.size() == 0)) { throw new CommonException(message); } }}
复制代码


当我们需要对参数进行验证的时候,直接通过这个类就可以完成,示例如下:

@RestControllerpublic class HelloController {
@RequestMapping("/save") public void save(String name, String email) { LocalAssert.isStringEmpty(name, "用户名不能为空!"); LocalAssert.isStringEmpty(email, "邮箱不能为空!"); //..... }}
复制代码


相比上面的实现方式,这种处理逻辑,代码明显要简洁的多!


类似这样的工具类还很多,比如spring也提供了一个名为Assert的断言工具类,在开发的时候,可以直接使用!


三、注解验证


下面我们要介绍的是另一种更简洁的参数验证逻辑,使用注解来对数据进行合法性验证,不仅代码会变得很简洁,阅读起来也十分令人赏心悦目!


以 Spring Boot 工程为例,下面我们一起来看看具体的实践方式。


3.1、添加依赖包


首先在pom.xml中引入spring-boot-starter-web依赖包即可,它会自动将注解验证相关的依赖包打入工程!

<!-- spring boot web --><dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-web</artifactId></dependency>
复制代码


3.2、编写注解校验请求对象


接着创建一个实体User,用于封装用户注册时的请求参数,并在参数属性上添加对应的注解验证规则

import javax.validation.constraints.Email;import javax.validation.constraints.NotBlank;import javax.validation.constraints.Size;
public class User {
@NotBlank(message = "用户名不能为空!") private String userName;
@Email(message = "邮箱格式不正确") @NotBlank(message = "邮箱不能为空!") private String email;
@NotBlank(message = "密码不能为空!") @Size(min = 8, max = 16,message = "请输入长度在8~16位的密码") private String userPwd;
@NotBlank(message = "确认密码不能为空!") private String confirmPwd;
// set、get方法等...}
复制代码


3.3、编写请求接口


web层创建一个register()注册接口方法,同时在请求参数上添加@Valid注解,示例如下:

import javax.validation.Valid;
@RestControllerpublic class UserController {
@RequestMapping("/register") public ResultMsg register(@RequestBody @Valid User user){ if(!user.getUserPwd().equals(user.getConfirmPwd())){ throw new CommonException(4001, "确认密码与密码不相同,请确认!"); } //业务处理... return ResultMsg.success(); }}
复制代码


3.4、编写全局异常处理器


最后自定义一个异常全局处理器,用于处理异常逻辑,如下:

@ControllerAdvicepublic class GlobalExceptionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/** * 拦截Controller层的异常 * @param e * @return */ @ExceptionHandler(value = {Exception.class}) @ResponseBody public Object exceptionHandler(HttpServletRequest request, Exception e){ LOGGER.error("【统一异常拦截】请求地址:{}, 错误信息:{}", request.getRequestURI(), e.getMessage()); // 注解验证抛出的异常 if(e instanceof MethodArgumentNotValidException){ // 获取错误信息 String error = ((MethodArgumentNotValidException) e).getBindingResult().getFieldError().getDefaultMessage(); return ResultMsg.fail(500, error); } // 自定义抛出的异常 if(e instanceof CommonException){ return ResultMsg.fail(((CommonException) e).getCode(), e.getMessage()); } return ResultMsg.fail(999, e.getMessage()); }}
复制代码


统一响应对象ResultMsg,如下:

public class ResultMsg<T> {
/**状态码**/ private int code;
/**结果描述**/ private String message;
/**结果集**/ private T data;
/**时间戳**/ private long timestamp;
// set、get方法等...}
复制代码


3.5、服务测试


启动项目,使用postman来验证一下代码的正确性,看看效果如何?


  • 测试字段是否为空



  • 测试邮箱是否合法



  • 测试密码长度是否符合要求



  • 测试密码与确认密码是否相同



可以看到,验证结果与预期一致!


四、自定义注解验证


事实上,熟悉 SpringMVC 源码的同学可能知道,Spring Boot 内置了一个hibernate-validator校验组件,上文就是利用它来完成对请求时入参上的注解验证。


默认的情况下,依赖包已经给我们提供了非常多的校验注解,如下!


  • JSR 提供的校验注解!



  • Hibernate Validator 提供的校验注解



但是某些情况,例如性别这个参数,可能需要我们自己去手动验证。


针对这种情况,我们也可以自定义一个注解来完成参数的校验,也便于进一步了解注解验证的原理。


自定义注解验证,实现方式如下!


首先,创建一个Sex注解。

@Target({FIELD})@Retention(RUNTIME)@Constraint(validatedBy = SexValidator.class)@Documentedpublic @interface Sex {
String message() default "性别值不在可选范围内";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};}
复制代码


然后,创建一个SexValidator类,实现自ConstraintValidator接口

public class SexValidator implements ConstraintValidator<Sex, String> {
@Override public boolean isValid(String value, ConstraintValidatorContext context) { Set<String> sexSet = new HashSet<String>(); sexSet.add("男"); sexSet.add("女"); return sexSet.contains(value); }}
复制代码


最后,在User实体类上加入一个性别参数,使用自定义注解进行校验!

public class User {
@NotBlank(message = "用户名不能为空!") private String userName;
@Email(message = "邮箱格式不正确") @NotBlank(message = "邮箱不能为空!") private String email;
@NotBlank(message = "密码不能为空!") @Size(min = 8, max = 16,message = "请输入长度在8~16位的密码") private String userPwd;
/** * 自定义注解校验 */ @Sex(message = "性别输入有误!") private String sex;
// set、get方法等...}
复制代码


启动服务,重新请求,运行结果如下:



结果与预期一致!


五、总结


参数验证,在开发中使用非常频繁,如何优雅的进行验证,让代码变得更加可读,是业界大佬一直在追求的目标!


本文主要围绕在 Spring Boot 中实现参数统一验证进行相关的知识总结和介绍,如果有描述不对的地方,欢迎留言支持。


文章转载自:潘志的研发笔记

原文链接:https://www.cnblogs.com/dxflqm/p/18320328

体验地址:http://www.jnpfsoft.com/?from=infoq

用户头像

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
如何在 SpringBoot 中优雅的做参数校验?_Java_快乐非自愿限量之名_InfoQ写作社区