Springboot实现优雅的参数校验(Spring Validation)和 if else说再见

这篇具有很好参考价值的文章主要介绍了Springboot实现优雅的参数校验(Spring Validation)和 if else说再见。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、概述

当我们想提供可靠的 API 接口,对参数的校验,以保证最终数据入库的正确性,是 必不可少 的活。比如下图就是 我们一个项目里 新增一个菜单校验 参数的函数,写了一大堆的 if else 进行校验,非常的不优雅,比起枯燥的CRUD来说,参数校验更是枯燥。这只是一个创建菜单的校验,只需要判断菜单,菜单url 以及菜单的父类id是否为空,上级菜单是否挂载正确,这样已经消耗掉了30,40行代码了,更不要说,管理后台创建商品这种参数贼多的接口。估计要写几百行校验代码了。

/**
	 * 验证参数是否正确
	 */
	private void verifyForm(SysMenuEntity menu){
		if(StringUtils.isBlank(menu.getName())){
			throw new RRException("菜单名称不能为空");
		}
		
		if(menu.getParentId() == null){
			throw new RRException("上级菜单不能为空");
		}
		
		//菜单
		if(menu.getType() == Constant.MenuType.MENU.getValue()){
			if(StringUtils.isBlank(menu.getUrl())){
				throw new RRException("菜单URL不能为空");
			}
		}
		
		//上级菜单类型
		int parentType = Constant.MenuType.CATALOG.getValue();
		if(menu.getParentId() != 0){
			SysMenuEntity parentMenu = sysMenuService.getById(menu.getParentId());
			parentType = parentMenu.getType();
		}
		
		//目录、菜单
		if(menu.getType() == Constant.MenuType.CATALOG.getValue() ||
				menu.getType() == Constant.MenuType.MENU.getValue()){
			if(parentType != Constant.MenuType.CATALOG.getValue()){
				throw new RRException("上级菜单只能为目录类型");
			}
			return ;
		}
		
		//按钮
		if(menu.getType() == Constant.MenuType.BUTTON.getValue()){
			if(parentType != Constant.MenuType.MENU.getValue()){
				throw new RRException("上级菜单只能为菜单类型");
			}
			return ;
		}
	}

可能小伙伴会说不加参数校验行不行?或者把参数校验放到前端不就行了?那你可是想的太简单了,世界比我们想象中的不安全,可能有“黑客”会绕过浏览器,直接使用 HTTP 工具,模拟请求向后端 API 接口传入违法的参数,以达到它们 “不可告人” 的目的。比如 sql 注入攻击,我相信,很多时候并不是我们不想添加,而是没有统一方便的方式,让我们快速的添加实现参数校验的功能。

世界上大多数碰到的困难,大多已经有了解决方案,特别是开发生态非常完整的java来说,早在 Java 2009 年就提出了 Bean Validation 规范,并且已经历经 JSR303、JSR349、JSR380 三次标准的置顶,发展到了 2.0 。
Springboot实现优雅的参数校验(Spring Validation)和 if else说再见有细心的小伙伴可能发现 Jakarta Bean Validation 3.0 里,这里3.0变化并不大,只是更改了一下包名 和命名空间而已。实际上还是对 Bean Validation 2.0 的实现。
Springboot实现优雅的参数校验(Spring Validation)和 if else说再见
Bean Validation 和我们很久以前学习过的 JPA 一样,只提供规范,不提供具体的实现,目前实现
Bean Validation 规范的数据校验框架,主要有:

  • Hibernate Validator
  • Apache BVal
    可能有小伙伴就要说 Hibernate 不就是个老掉牙的ORM框架吗?现在不都没人用了吗?其实不然 Hibernate 可是打着 Everything data 口号的,它还提供了 Hibernate Search、Hibernate OGM 等等解决方案的。Hibernate 只是在国内的用的少了,国内主要是还是用 mybatis 这种 半orm框架的多。我们可以通过 google 的 trends 来看一下:

在中国的 mybatis, jpa, Hibernate 搜索热度:
Springboot实现优雅的参数校验(Spring Validation)和 if else说再见
在全球的 mybatis, jpa, Hibernate 搜索热度:
Springboot实现优雅的参数校验(Spring Validation)和 if else说再见

由于国内的开发环境可以说 99.99% 的开发者肯定都在用 spring,且正好 Spring Validation 提供了对 Bean Validation 的内置封装支持,可以使用 @Validated 注解,实现声明式校验,而无需直接调用 Bean Validation 提供的 API 方法。而在实现原理上,也是基于 Spring AOP 拦截,最终还是调用不同的 Bean Validation 的实现框架。例如说,Hibernate Validator 。实现校验相关的操作这一点,类似 Spring Transaction 事务,通过 @Transactional 注解,实现声明式事务。下面,让我们开始学习如何在 Spring Boot 中,实现参数校验。

二、注解

在开始入门之前,我们先了解下本文可能会涉及到的注解。javax.validation.constraints 包下,定义了一系列的约束( constraint )注解。共 22个,如下:
Springboot实现优雅的参数校验(Spring Validation)和 if else说再见
大致可以分为以下几类:

2.1 空和非空检查

  • @NotBlank :只能用于字符串不为 null ,并且字符串 #trim() 以后 length 要大于 0 。
  • @NotEmpty :集合对象的元素不为 0 ,即集合不为空,也可以用于字符串不为 null 。
  • @NotNull :不能为 null 。
  • @Null :必须为 null 。

2.2 数值检查

  • @DecimalMax(value) :被注释的元素必须是一个数字,其值必须小于等于指定的最大值。
  • @DecimalMin(value) :被注释的元素必须是一个数字,其值必须大于等于指定的最小值。
  • @Digits(integer, fraction) :被注释的元素必须是一个数字,其值必须在可接受的范围内。
  • @Positive :判断正数。
  • @PositiveOrZero :判断正数或 0 。
  • @Max(value) :该字段的值只能小于或等于该值。
  • @Min(value) :该字段的值只能大于或等于该值。
  • @Negative :判断负数。
  • @NegativeOrZero :判断负数或 0 。

2.3 Boolean 值检查

  • @AssertFalse 所注解的元素必须是Boolean类型,且值为false
  • @AssertTrue 所注解的元素必须是Boolean类型,且值为true

2.4 长度检查

  • @Size(max, min) :检查该字段的 size 是否在 min 和 max 之间,可以是字符串、数组、集合、Map 等。

2.5 日期检查

  • @Future :被注释的元素必须是一个将来的日期。
  • @FutureOrPresent :判断日期是否是将来或现在日期。
  • @Past :检查该字段的日期是在过去。
  • @PastOrPresent :判断日期是否是过去或现在日期。

2.6 其它检查

  • @Email :被注释的元素必须是电子邮箱地址。
  • @Pattern(value) :被注释的元素必须符合指定的正则表达式。

2.7 Hibernate Validator 附加的约束注解

org.hibernate.validator.constraints 包下,定义了一系列的约束( constraint )注解。如下:

  • @Range(min=, max=) :被注释的元素必须在合适的范围内。
  • @Length(min=, max=) :被注释的字符串的大小必须在指定的范围内。
  • @URL(protocol=,host=,port=,regexp=,flags=) :被注释的字符串必须是一个有效的 URL 。
  • @SafeHtml :判断提交的 HTML 是否安全。例如说,不能包含 javascript 脚本等等。

Springboot实现优雅的参数校验(Spring Validation)和 if else说再见

2.8 @Valid 和 @Validated

@Valid 注解,是 Bean Validation 所定义,可以添加在普通方法、构造方法、方法参数、方法返回、成员变量上,表示它们需要进行约束校验。

@Validated 注解,是 Spring Validation 锁定义,可以添加在类、方法参数、普通方法上,表示它们需要进行约束校验。同时,@Validated 有 value 属性,支持分组校验。属性如下:

@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Validated {

	/**
	 * Specify one or more validation groups to apply to the validation step
	 * kicked off by this annotation.
	 * <p>JSR-303 defines validation groups as custom annotations which an application declares
	 * for the sole purpose of using them as type-safe group arguments, as implemented in
	 * {@link org.springframework.validation.beanvalidation.SpringValidatorAdapter}.
	 * <p>Other {@link org.springframework.validation.SmartValidator} implementations may
	 * support class arguments in other ways as well.
	 */
	Class<?>[] value() default {};

}

对于初学的胖友来说,很容易搞混 @Valid(javax.validation包下) 和 @Validated (org.springframework.validation.annotation包下)注解。两者大致有以下的区别:

名称 spring Validation是否实现了声明式校验 是否支持嵌套校验 是否支持分组校验
@Validated
@Valid

@Valid 有嵌套对象校验功能 例如说:如果不在 User.profile 属性上,添加 @Valid 注解,就会导致 UserProfile.nickname 属性,不会进行校验。

// User.java
public class User {
    
    private String id;

    @Valid
    private UserProfile profile;

}

// UserProfile.java
public class UserProfile {

    @NotBlank
    private String nickname;

}

总的来说,绝大多数场景下,我们使用 @Validated 注解即可。而在有嵌套校验的场景,我们使用 @Valid 注解添加到成员属性上。

三、快速入门

3.1 引入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>


    <groupId>com.ratel</groupId>
    <artifactId>java-validation</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>java-validation</name>
    <description>java validation action</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>


    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>


        <!--在一些高版本springboot中默认并不会引入这个依赖,需要手动引入-->
<!--        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
            <scope>compile</scope>
        </dependency>-->

        <!-- 保证 Spring AOP 相关的依赖包 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
        </dependency>


        <!--lombok相关 方便开发-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>

        <!--knife4j接口文档 方便待会进行接口测试-->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>3.0.3</version>
        </dependency>

    </dependencies>


    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

在 Spring Boot 体系中,也提供了 spring-boot-starter-validation 依赖。在这里,我们并没有引入。为什么呢? 因为 spring-boot-starter-web 已经引入了 spring-boot-starter-validation ,而 spring-boot-starter-validation 中也引入了 hibernate-validator 依赖,所以无需重复引入。三者的依赖引入关系可见下图
Springboot实现优雅的参数校验(Spring Validation)和 if else说再见

3.2 创建基本的类

UserAddDTO 实体类:

package com.ratel.validation.entity;

import lombok.Data;
import org.hibernate.validator.constraints.Length;

import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;

/**
 * @Description
 * @Author ratelfu
 * @Date 2023/04/07
 * @Version 1.0
 */
@Data
public class UserAddDTO {

    /**
     * 账号
     */
    @NotEmpty(message = "登录账号不能为空")
    @Length(min = 5, max = 16, message = "账号长度为 5-16 位")
    @Pattern(regexp = "^[A-Za-z0-9]+$", message = "账号格式为数字以及字母")
    private String username;
    /**
     * 密码
     */
    @NotEmpty(message = "密码不能为空")
    @Length(min = 4, max = 16, message = "密码长度为 4-16 位")
    private String password;
}

UserController 用来写接口,在类上,添加 @Validated 注解,表示 UserController 是所有接口都需要进行参数校验。

package com.ratel.validation.cotroller;

import com.ratel.validation.entity.UserAddDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import javax.validation.constraints.Min;

@RestController
@RequestMapping("/users")
@Validated
public class UserController {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @GetMapping("/get")
    public UserAddDTO get(@RequestParam("id") @Min(value = 1L, message = "编号必须大于 0") Integer id) {
        logger.info("[get][id: {}]", id);
        UserAddDTO userAddDTO = new UserAddDTO();
        userAddDTO.setUsername("张三");
        userAddDTO.setPassword("123456");
        return userAddDTO;
    }

    @PostMapping("/add")
    public void add(@Valid @RequestBody UserAddDTO addDTO) {
        logger.info("[add][addDTO: {}]", addDTO);
    }

}

3.3 启动程序,进行测试

启动程序,然后再浏览器里我们就可以进行输入: swagger访问地址: http://localhost:8080/doc.html#/home 打开swagger文档 就可以进行测试了:

Springboot实现优雅的参数校验(Spring Validation)和 if else说再见
首先我们访问 http://localhost:8080/users/get?id=-1 进行测试,查看返回结果,果然对我们的 id 进行校验。
Springboot实现优雅的参数校验(Spring Validation)和 if else说再见
接下来我们访问 http://localhost:8080/users/add 进行新增用户的校验:请求体我们写成:

{
  "password": "233",
  "username": "33"
}

然后返回结果的如下:

{
  "timestamp": "2023-04-09T13:33:58.864+0000",
  "status": 400,
  "error": "Bad Request",
  "errors": [
    {
      "codes": [
        "Length.userAddDTO.password",
        "Length.password",
        "Length.java.lang.String",
        "Length"
      ],
      "arguments": [
        {
          "codes": [
            "userAddDTO.password",
            "password"
          ],
          "arguments": null,
          "defaultMessage": "password",
          "code": "password"
        },
        16,
        4
      ],
      "defaultMessage": "密码长度为 4-16 位",
      "objectName": "userAddDTO",
      "field": "password",
      "rejectedValue": "233",
      "bindingFailure": false,
      "code": "Length"
    },
    {
      "codes": [
        "Length.userAddDTO.username",
        "Length.username",
        "Length.java.lang.String",
        "Length"
      ],
      "arguments": [
        {
          "codes": [
            "userAddDTO.username",
            "username"
          ],
          "arguments": null,
          "defaultMessage": "username",
          "code": "username"
        },
        16,
        5
      ],
      "defaultMessage": "账号长度为 5-16 位",
      "objectName": "userAddDTO",
      "field": "username",
      "rejectedValue": "33",
      "bindingFailure": false,
      "code": "Length"
    }
  ],
  "message": "Validation failed for object='userAddDTO'. Error count: 2",
  "path": "/users/add"
}

返回结果的json串中的 errors 字段,参数错误明细数组。每一个数组元素,对应一个参数错误明细。这里,username 违背了 账号长度为 5-16 位规定。 password违反了 密码长度为 4-16 位的规定。
返回结果示意图:
Springboot实现优雅的参数校验(Spring Validation)和 if else说再见

3.3 一些疑问

在这里 细心的小伙伴可能会有几个疑问:

3.3.1 疑问一

#get(id) 方法上,我们并没有给 id 添加 @Valid 注解,而 #add(addDTO) 方法上,我们给 addDTO 添加 @Valid 注解。这个差异,是为什么呢?

因为 UserController 使用了 @Validated 注解,那么 Spring Validation 就会使用 AOP 进行切面,进行参数校验。而该切面的拦截器,使用的是 MethodValidationInterceptor

  • 对于 #get(id) 方法,需要校验的参数 id ,是平铺开的,所以无需添加 @Valid 注解。
  • 对于 #add(addDTO) 方法,需要校验的参数 addDTO ,实际相当于嵌套校验,要校验的参数的都在 addDTO 里面,所以需要添加 @Valid (其实实测加@Validated也行,暂时不知道为啥 为了好区分就先用 @Valid 吧 )注解。

3.3.2 疑问二

#get(id) 方法的返回的结果是 status = 500 ,而 #add(addDTO) 方法的返回的结果是 status = 400 。

  • 对于 #get(id) 方法,在 MethodValidationInterceptor 拦截器中,校验到参数不正确,会抛出 ConstraintViolationException 异常。
  • 对于 #add(addDTO) 方法,因为 addDTO 是个 POJO 对象,所以会走 SpringMVC 的 DataBinder 机制,它会调用 DataBinder#validate(Object... validationHints) 方法,进行校验。在校验不通过时,会抛出 BindException

在 SpringMVC 中,默认使用 DefaultHandlerExceptionResolver 处理异常。

  • 对于 BindException 异常,处理成 400 的状态码。
  • 对于 ConstraintViolationException 异常,没有特殊处理,所以处理成 500 的状态码。
    这里,我们在抛个问题,如果 #add(addDTO 方法,如果参数正确,在走完 DataBinder 中的参数校验后,会不会在走一遍 MethodValidationInterceptor 的拦截器呢?思考 100 毫秒…

答案是会。这样,就会导致浪费。所以 Controller 类里,如果 只有 类似的 #add(addDTO) 方法的 嵌套校验,那么我可以不在 Controller 类上添加 @Validated 注解。从而实现,仅使用 DataBinder 中来做参数校验。

3.3.3 返回提示很不友好,太长了

第三点,无论是 #get(id) 方法,还是 #add(addDTO) 方法,它们的返回提示都非常不友好,那么该怎么办呢?我们将在 第四章节通过 处理校验异常 进行处理。

四、处理校验异常

4.1 校验不通过的枚举类

package com.ratel.validation.enums;

/**
 * 业务异常枚举
 */
public enum ServiceExceptionEnum {

    // ========== 系统级别 ==========
    SUCCESS(0, "成功"),
    SYS_ERROR(2001001000, "服务端发生异常"),
    MISSING_REQUEST_PARAM_ERROR(2001001001, "参数缺失"),
    INVALID_REQUEST_PARAM_ERROR(2001001002, "请求参数不合法"),

    // ========== 用户模块 ==========
    USER_NOT_FOUND(1001002000, "用户不存在"),

    // ========== 订单模块 ==========

    // ========== 商品模块 ==========
    ;

    /**
     * 错误码
     */
    private final int code;
    /**
     * 错误提示
     */
    private final String message;

    ServiceExceptionEnum(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public int getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }

}

4.2 统一返回结果实体类

package com.ratel.validation.common;

import com.fasterxml.jackson.annotation.JsonIgnore;
import org.springframework.util.Assert;

import java.io.Serializable;

/**
 * 通用返回结果
 *
 * @param <T> 结果泛型
 */
public class CommonResult<T> implements Serializable {

    public static Integer CODE_SUCCESS = 0;

    /**
     * 错误码
     */
    private Integer code;
    /**
     * 错误提示
     */
    private String message;
    /**
     * 返回数据
     */
    private T data;

    /**
     * 将传入的 result 对象,转换成另外一个泛型结果的对象
     *
     * 因为 A 方法返回的 CommonResult 对象,不满足调用其的 B 方法的返回,所以需要进行转换。
     *
     * @param result 传入的 result 对象
     * @param <T> 返回的泛型
     * @return 新的 CommonResult 对象
     */
    public static <T> CommonResult<T> error(CommonResult<?> result) {
        return error(result.getCode(), result.getMessage());
    }

    public static <T> CommonResult<T> error(Integer code, String message) {
        Assert.isTrue(!CODE_SUCCESS.equals(code), "code 必须是错误的!");
        CommonResult<T> result = new CommonResult<>();
        result.code = code;
        result.message = message;
        return result;
    }

    public static <T> CommonResult<T> success(T data) {
        CommonResult<T> result = new CommonResult<>();
        result.code = CODE_SUCCESS;
        result.data = data;
        result.message = "";
        return result;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    @JsonIgnore
    public boolean isSuccess() {
        return CODE_SUCCESS.equals(code);
    }

    @JsonIgnore
    public boolean isError() {
        return !isSuccess();
    }

    @Override
    public String toString() {
        return "CommonResult{" +
                "code=" + code +
                ", message='" + message + '\'' +
                ", data=" + data +
                '}';
    }

}

4.3 增加全局异常处理类 GlobalExceptionHandler

package com.ratel.validation.exception;


import com.ratel.validation.common.CommonResult;
import com.ratel.validation.enums.ServiceExceptionEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;

@ControllerAdvice(basePackages = "com.ratel.validation.cotroller")
public class GlobalExceptionHandler {

    private Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * 处理 MissingServletRequestParameterException 异常
     *
     * SpringMVC 参数不正确
     */
    @ResponseBody
    @ExceptionHandler(value = MissingServletRequestParameterException.class)
    public CommonResult missingServletRequestParameterExceptionHandler(HttpServletRequest req, MissingServletRequestParameterException ex) {
        logger.error("[missingServletRequestParameterExceptionHandler]", ex);
        // 包装 CommonResult 结果
        return CommonResult.error(ServiceExceptionEnum.MISSING_REQUEST_PARAM_ERROR.getCode(),
                ServiceExceptionEnum.MISSING_REQUEST_PARAM_ERROR.getMessage());
    }

    @ResponseBody
    @ExceptionHandler(value = ConstraintViolationException.class)
    public CommonResult constraintViolationExceptionHandler(HttpServletRequest req, ConstraintViolationException ex) {
        logger.error("[constraintViolationExceptionHandler]", ex);
        // 拼接错误
        StringBuilder detailMessage = new StringBuilder();
        for (ConstraintViolation<?> constraintViolation : ex.getConstraintViolations()) {
            // 使用 ; 分隔多个错误
            if (detailMessage.length() > 0) {
                detailMessage.append(";");
            }
            // 拼接内容到其中
            detailMessage.append(constraintViolation.getMessage());
        }
        // 包装 CommonResult 结果
        return CommonResult.error(ServiceExceptionEnum.INVALID_REQUEST_PARAM_ERROR.getCode(),
                ServiceExceptionEnum.INVALID_REQUEST_PARAM_ERROR.getMessage() + ":" + detailMessage.toString());
    }

    @ResponseBody
    @ExceptionHandler(value = BindException.class)
    public CommonResult bindExceptionHandler(HttpServletRequest req, BindException ex) {
        logger.info("========进入了 bindException======");
        logger.error("[bindExceptionHandler]", ex);
        // 拼接错误
        StringBuilder detailMessage = new StringBuilder();
        for (ObjectError objectError : ex.getAllErrors()) {
            // 使用 ; 分隔多个错误
            if (detailMessage.length() > 0) {
                detailMessage.append(";");
            }
            // 拼接内容到其中
            detailMessage.append(objectError.getDefaultMessage());
        }
        // 包装 CommonResult 结果
        return CommonResult.error(ServiceExceptionEnum.INVALID_REQUEST_PARAM_ERROR.getCode(),
                ServiceExceptionEnum.INVALID_REQUEST_PARAM_ERROR.getMessage() + ":" + detailMessage.toString());
    }

    @ResponseBody
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public CommonResult MethodArgumentNotValidExceptionHandler(HttpServletRequest req, MethodArgumentNotValidException ex) {
        logger.info("-----------------进入了 MethodArgumentNotValidException-----------------");
        logger.error("[MethodArgumentNotValidException]", ex);
        // 拼接错误
        StringBuilder detailMessage = new StringBuilder();
        for (ObjectError objectError : ex.getBindingResult().getAllErrors()) {
            // 使用 ; 分隔多个错误
            if (detailMessage.length() > 0) {
                detailMessage.append(";");
            }
            // 拼接内容到其中
            detailMessage.append(objectError.getDefaultMessage());
        }
        // 包装 CommonResult 结果
        return CommonResult.error(ServiceExceptionEnum.INVALID_REQUEST_PARAM_ERROR.getCode(),
                ServiceExceptionEnum.INVALID_REQUEST_PARAM_ERROR.getMessage() + ":" + detailMessage.toString());
    }


    /**
     * 处理其它 Exception 异常
     * @param req
     * @param e
     * @return
     */
    @ResponseBody
    @ExceptionHandler(value = Exception.class)
    public CommonResult exceptionHandler(HttpServletRequest req, Exception e) {
        // 记录异常日志
        logger.error("[exceptionHandler]", e);
        // 返回 ERROR CommonResult
        return CommonResult.error(ServiceExceptionEnum.SYS_ERROR.getCode(),
                ServiceExceptionEnum.SYS_ERROR.getMessage());
    }
}

4.4 测试

访问:http://localhost:8080/users/add 可以看到异常结果已经被拼接成一个字符串,相比之前清新 易懂了不少。
Springboot实现优雅的参数校验(Spring Validation)和 if else说再见
Springboot实现优雅的参数校验(Spring Validation)和 if else说再见文章来源地址https://www.toymoban.com/news/detail-414344.html

到了这里,关于Springboot实现优雅的参数校验(Spring Validation)和 if else说再见的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 如何在Spring Boot中优雅地进行参数校验

    在平时的开发工作中,我们通常需要对接口进行参数格式验证。当参数个数较少(个数小于3)时,可以使用 if ... else ... 手动进行参数验证。当参数个数大于3个时,使用 if ... else ... 进行参数验证就会让代码显得臃肿,这个时候推荐使用注解来进行参数验证。 在Java中,注解

    2024年01月17日
    浏览(43)
  • 完美实现校验:利用Spring Validation实现强大的输入验证

    校验例子 大家平时编码中经常涉及参数的校验,对于一个用户注册的方法来说会校验用户名密码信息: 上述例子中需要手动编写参数校验逻辑的过程。虽然对于这个简单的示例而言,手动编写校验逻辑可能是可行的,但是对于复杂的验证规则和多个参数的情况,手动编写校

    2024年01月20日
    浏览(39)
  • java如何优雅的实现参数非空校验,快速实现参数非空校验,使用@valid实现参数非空校验

    在java项目接口中,有些必传参数需要进行非空校验,如果参数过多,代码会繁杂且冗余,如何优雅的对参数进行非空校验,下面是实现流程 用实体类接收参数,使用非空注解编辑参数内容 使用 @Valid 注解对参数进行拦截,整体进行非空校验 如果是SpringBoot项目,引入web开发包

    2024年02月08日
    浏览(49)
  • java的validation框架(参数校验)

    空值校验类:@Null,@NotNull,@NotEmpty,@NotBlank等 范围校验类:@Min,@Size,@Digits,@Future,@Negative等 其它校验类:@Email,@URL,@AssertTrue,@Pattern等 1. @NotNull(message = “用户id不能为空”) 没有设置值时会返回 设置为userInfo.setUserId(\\\"\\\");时不会返回 userInfo.setUserId(\\\" \\\");时不会返回 2.@Not

    2024年01月22日
    浏览(49)
  • 7.13 在SpringBoot中 正确使用Validation实现参数效验

    先说下 Spring Validation 参数校验,它是前后端分离项目SpringBoot后端Controller层必做的工作之一,目的是防止非法调用, Java JSR303 定义了校验标准 validation-api ,但没有实现, Hibernate validation 是规范的实现,另外还增加了一些 constraints 注解,最后 Spring Validation 对 Hibernate validati

    2024年02月09日
    浏览(43)
  • Spring6 数据校验 Validation

    在开发中,经常遇到参数校验的需求,比如用户注册的时候,要校验用户名不能为空、用户名长度不超过20个字符、手机号是合法的手机号格式等等。如果使用普通方式,会把校验的代码和真正的业务处理逻辑 耦合 在一起,而且如果未来要新增一种校验逻辑也需要在修改多个

    2024年02月11日
    浏览(30)
  • Spring Validation 接口入参校验

    JSR 是 Java Specification Requests 的缩写,含义为 JAVA 规范提案。 JSR 303 - Bean Validation 规范, 正是一套基于 JavaBean 参数校验的标准。 Hibernate Validator 是 JSR 303 的实现,它提供了 JSR 303 规范中所有约束(constraint)的实现,同时也对其作出一些拓展。 Spring Validation 是对 Hibernate validati

    2024年02月07日
    浏览(85)
  • 还在用 if else 做参数校验?快来学习高级参数校验吧

    在上一篇文章 Springboot实现优雅的参数校验(Spring Validation)和 if else说再见,我们介绍了 Spring Validation 的初级用法,在实际开发中,无论是 Bean Validation 定义的约束,还是 Hibernate Validator 附加的约束,都是无法满足我们复杂的业务场景。所以,我们需要自定义约束。开发自定

    2023年04月14日
    浏览(55)
  • spring6-国际化:i18n | 数据校验:Validation

    1.1、i18n概述 国际化也称作i18n,其来源是英文单词 internationalization的首末字符i和n,18为中间的字符数。由于软件发行可能面向多个国家,对于不同国家的用户,软件显示不同语言的过程就是国际化。通常来讲,软件中的国际化是通过配置文件来实现的,假设要支撑两种语言,

    2024年02月08日
    浏览(49)
  • 一起学SF框架系列5.11-spring-beans-数据校验validation

        在日常的项目开发中,应用在执行业务逻辑之前,为了防止非法参数对业务造成的影响,必须通过校验保证传入数据是合法正确的,但很多时候同样的校验出现了多次,在不同的层,不同的方法上,导致代码冗余,违反DRY原则。     Java提供了数据校验规范来解

    2024年02月15日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包