跳过正文
  1. 文章/
  2. Java/
  3. SpringFramework/
  4. SpringBoot/

9、全局异常处理

·3037 字·7 分钟· loading · loading · ·
Java SpringFramework SpringBoot
GradyYoung
作者
GradyYoung
SpringBoot - 点击查看当前系列文章
§ 9、全局异常处理 「 当前文章 」

全局异常处理
#

参数校验失败或业务操作抛出的异常,当然不可能再去手动捕捉异常进行处理,不然还不如用之前BindingResult方式呢。又不想手动捕捉这个异常,又要对这个异常进行处理,那正好使用SpringBoot全局异常处理来达到一劳永逸的效果!

首先,我们需要新建一个类,在这个类上加上@ControllerAdvice@RestControllerAdvice注解,这个类就配置成全局处理类了。这个根据你的Controller层用的是@Controller还是@RestController来决定。

@RestControllerAdvice也可以作用于@Controller

@RestControllerAdvice
public class ControllerExceptionHandler {

    /**
     * 其他异常处理
     * @param e
     * @return
     */
    @ExceptionHandler(Exception.class)
    public ResultVO otherExceptionHandler(Exception e){
        return ResultVO.failed("操作失败,服务器异常");
    }
}

spring-boot-starter-validation
#

spring-boot-starter-validation 是 Spring Boot 中的一个启动器依赖,用于在项目中引入数据校验的功能。它基于 javax.validation(现为 jakarta.validation)和 Hibernate Validator 实现,为对象属性提供了丰富的校验注解,同时支持自定义校验逻辑。

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

基本概念
#

Java API规范即JSR-303是JavaEE 6中的一项子规范,又称作 Bean Validation,提供了针对 Java Bean字段的一些校验注解,如@NotNull,@Min等。JSR-349是其升级版本,添加了一些新特性。

JSR定义了Bean校验的标准validation-api,但没有提供实现

Hibernate Validator是对这个规范的实现(与ORM框架无关),并在它的基础上增加了一些新的校验注解如@Email@Length等。

Spring全面支持了 JSR-303、JSR-349规范,对 Hibernate Validation 进行二次封装,在 SpringMVC 模块中添加了自动校验机制,可以利用注解对 Java Bean 的字段的值进行校验,并将校验信息封装进特定的类中。

依赖作用
#

spring-boot-starter-validation 的主要作用是提供一套基于注解的校验框架,用于验证数据是否合法。该依赖通常用于 Spring Boot 应用中的以下场景:

  • 前端数据校验:自动校验传入的请求数据是否符合规定的格式和要求。

  • 服务端逻辑校验:确保服务内部的数据符合业务逻辑,以防止数据不一致或异常情况。

  • 数据层保护:通过校验确保入库的数据是符合规范的,有助于保持数据的完整性和一致性。

常用注解
#

@NotNull
#
  • 作用:确保字段不为 null
  • 适用类型:所有类型(不能为基础数据类型如 int,因为它们不能为 null)。
@NotNull(message = "用户名不能为空")
private String username;
@Null
#
  • 作用:确保字段为 null
  • 适用类型:所有类型。
@NotEmpty
#
  • 作用:确保集合、字符串、数组等不为空(不能为空且大小/长度不能为0)。
  • 适用类型:字符串、集合、数组等。
@NotBlank
#
  • 作用:确保字符串不为空白(即不能为空,且至少包含一个非空白字符)。
  • 适用类型:字符串。
@Size
#
  • 作用:限制集合、数组或字符串的大小或长度在指定范围内。
  • 属性
    • min:最小长度(默认为0)。
    • max:最大长度。
  • 适用类型:字符串、集合、数组等。
@Size(min = 1, max = 10, message = "用户名长度必须在1到10之间")
private String username;
@Min和@Max
#
  • 作用:限制数值类型的字段值的最小值和最大值。
  • 属性
    • value:允许的最小/最大值。
  • 适用类型:数字类型(如 intlongdouble 等)。
@Positive和@PositiveOrZero
#
  • 作用
    • @Positive:确保字段值为正数。
    • @PositiveOrZero:确保字段值为非负数(即正数或零)。
  • 适用类型:数字类型。
@Negative和@NegativeOrZero
#
  • 作用
    • @Negative:确保字段值为负数。
    • @NegativeOrZero:确保字段值为非正数(即负数或零)。
  • 适用类型:数字类型。
@Past和@PastOrPresent
#
  • 作用
    • @Past:确保日期在当前日期之前。
    • @PastOrPresent:确保日期在当前日期或之前。
  • 适用类型java.util.Datejava.time.LocalDate 等日期类型。
@Past(message = "生日必须是过去的日期")
private LocalDate birthDate;
@Future和@FutureOrPresent
#
  • 作用
    • @Future:确保日期在当前日期之后。
    • @FutureOrPresent:确保日期在当前日期或之后。
  • 适用类型java.util.Datejava.time.LocalDate 等日期类型。
@Pattern
#
  • 作用:确保字符串符合指定的正则表达式。
  • 属性
    • regexp:指定的正则表达式。
    • flags:正则表达式的匹配标志(如大小写敏感性)。
  • 适用类型:字符串。
@Pattern(regexp = "^[a-zA-Z0-9]{6,12}$", message = "密码必须是6到12位的字母和数字组合")
private String password;
@Email
#
  • 作用:确保字符串符合电子邮件格式。
  • 属性
    • regexp:正则表达式,默认符合标准的邮箱格式。
    • flags:正则表达式的匹配标志。
  • 适用类型:字符串。
@Digits
#
  • 作用:限制数值字段的整数位和小数位的最大位数。
  • 属性
    • integer:最大整数位数。
    • fraction:最大小数位数。
  • 适用类型:数字类型。
@Digits(integer = 5, fraction = 2, message = "金额整数部分不能超过5位,小数部分不能超过2位")
private BigDecimal amount;
@DecimalMin和@DecimalMax
#
  • 作用:限制字段数值的最小值和最大值(包含边界)。
  • 属性
    • value:允许的最小或最大值。
    • inclusive:是否包含边界值,默认为 true
  • 适用类型:数字类型。
@DecimalMin(value = "0.0", inclusive = false, message = "金额必须大于0")
private BigDecimal price;
@AssertTrue和@AssertFalse
#
  • 作用
    • @AssertTrue:确保字段值为 true
    • @AssertFalse:确保字段值为 false
  • 适用类型:布尔类型。
@AssertTrue(message = "必须同意条款")
private Boolean termsAccepted;

常见用法
#

在 DTO 中使用校验注解
#

import javax.validation.constraints.*;

public class UserDto {
    @NotBlank(message = "用户名不能为空")
    private String username;
    @Email(message = "邮箱格式不正确")
    private String email;
    @Size(min = 8, max = 20, message = "密码长度必须在8到20个字符之间")
    private String password;
    @Min(value = 18, message = "年龄不能小于18")
    private int age;
}

在控制器中应用校验
#

在控制器中使用 @Validated@Valid 注解,使 Spring 自动触发校验规则,如果校验失败会抛出异常。

import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;

@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @PostMapping
    public ResponseEntity<String> createObject(@Validated @RequestBody ObjectDto obj) {
        // 处理业务逻辑
        return ResponseEntity.ok("实体创建成功");
    }
    @PostMapping
    public ResponseEntity<String> createUser(@Valid @RequestBody UserDto user) {
        // 处理业务逻辑
        return ResponseEntity.ok("用户创建成功");
    }
}

参数较少时,也可直接在参数前使用校验注解。

// 路径变量
@GetMapping("{userId}")
public String detail(@PathVariable("userId") @Min(10000000000000000L) Long userId) {
    // 校验通过,才会执行业务逻辑处理
    UserDTO userDTO = new UserDTO();
    userDTO.setUserId(userId);
    userDTO.setAccount("11111111111111111");
    userDTO.setUserName("xixi");
    userDTO.setAccount("11111111111111111");
    return "ok";
}

// 查询参数
@GetMapping("detail")
public String getDetail(@Length(min = 6, max = 20) @NotNull String  account) {
    // 校验通过,才会执行业务逻辑处理
    return "ok";
}

自定义校验
#

1、定义注解
#

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

@Documented
// 此处是自定义校验器类
@Constraint(validatedBy = PasswordValidator.class)
@Target({ ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidPassword {
    //默认错误消息
    String message() default "密码不符合复杂度要求";
    //分组
    Class<?>[] groups() default {};
    //负载
    Class<? extends Payload>[] payload() default {};
}

2、自定义校验器
#

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class PasswordValidator implements ConstraintValidator<ValidPassword, String> {
  
    @Override
    public void initialize(ValidPassword constraintAnnotation) {
    }
  
    @Override
    public boolean isValid(String password, ConstraintValidatorContext context) {
        return password != null && password.matches("^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d]{8,}$");
    }
}

3、使用
#

public class UserDto {
    @ValidPassword
    private String password;
}

全局校验异常处理
#

当校验失败时,Spring 会抛出如下两种异常

MethodArgumentNotValidException :是在 Spring MVC 或 Spring Boot 中处理参数校验异常时抛出的异常。

ConstraintViolationException :是在 Java Bean Validation(JSR 380)规范中定义的一个异常类。

可以通过全局异常处理来捕获这些异常并返回自定义的错误响应:

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import javax.validation.ConstraintViolationException;

@ControllerAdvice
public class GlobalExceptionHandler {
  
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public Map<String, String> handleValidationExceptions(MethodArgumentNotValidException ex) {
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getFieldErrors().forEach(error -> 
            errors.put(error.getField(), error.getDefaultMessage()));
        return errors;
    }
  
    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public String handleConstraintViolationException(ConstraintViolationException ex) {
        return ex.getMessage();
    }
}
SpringBoot - 点击查看当前系列文章
§ 9、全局异常处理 「 当前文章 」