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
@NotNull(message = "用户名不能为空")
private String username;
@Null
@NotEmpty
@NotBlank
@Size
@Size(min = 1, max = 10, message = "用户名长度必须在1到10之间")
private String username;
@Min和@Max
@Positive和@PositiveOrZero
@Negative和@NegativeOrZero
@Past和@PastOrPresent
@Past(message = "生日必须是过去的日期")
private LocalDate birthDate;
@Future和@FutureOrPresent
@Pattern
@Pattern(regexp = "^[a-zA-Z0-9]{6,12}$", message = "密码必须是6到12位的字母和数字组合")
private String password;
@Email
@Digits
@Digits(integer = 5, fraction = 2, message = "金额整数部分不能超过5位,小数部分不能超过2位")
private BigDecimal amount;
@DecimalMin和@DecimalMax
@DecimalMin(value = "0.0", inclusive = false, message = "金额必须大于0")
private BigDecimal price;
@AssertTrue和@AssertFalse
@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();
    }
}