全局异常处理 #
参数校验失败或业务操作抛出的异常,当然不可能再去手动捕捉异常进行处理,不然还不如用之前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:允许的最小/最大值。
- 适用类型:数字类型(如
int、long、double等)。
@Positive和@PositiveOrZero #
- 作用:
@Positive:确保字段值为正数。@PositiveOrZero:确保字段值为非负数(即正数或零)。
- 适用类型:数字类型。
@Negative和@NegativeOrZero #
- 作用:
@Negative:确保字段值为负数。@NegativeOrZero:确保字段值为非正数(即负数或零)。
- 适用类型:数字类型。
@Past和@PastOrPresent #
- 作用:
@Past:确保日期在当前日期之前。@PastOrPresent:确保日期在当前日期或之前。
- 适用类型:
java.util.Date、java.time.LocalDate等日期类型。
@Past(message = "生日必须是过去的日期")
private LocalDate birthDate;
@Future和@FutureOrPresent #
- 作用:
@Future:确保日期在当前日期之后。@FutureOrPresent:确保日期在当前日期或之后。
- 适用类型:
java.util.Date、java.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();
}
}