参数校验失败或业务操作抛出的异常,当然不可能再去手动捕捉异常进行处理,不然还不如用之前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 中的一个启动器依赖,用于在项目中引入数据校验的功能。它基于 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 应用中的以下场景:
前端数据校验:自动校验传入的请求数据是否符合规定的格式和要求。
服务端逻辑校验:确保服务内部的数据符合业务逻辑,以防止数据不一致或异常情况。
数据层保护:通过校验确保入库的数据是符合规范的,有助于保持数据的完整性和一致性。
null
。int
,因为它们不能为 null
)。@NotNull(message = "用户名不能为空")
private String username;
null
。min
:最小长度(默认为0)。max
:最大长度。@Size(min = 1, max = 10, message = "用户名长度必须在1到10之间")
private String username;
value
:允许的最小/最大值。int
、long
、double
等)。@Positive
:确保字段值为正数。@PositiveOrZero
:确保字段值为非负数(即正数或零)。@Negative
:确保字段值为负数。@NegativeOrZero
:确保字段值为非正数(即负数或零)。@Past
:确保日期在当前日期之前。@PastOrPresent
:确保日期在当前日期或之前。java.util.Date
、java.time.LocalDate
等日期类型。@Past(message = "生日必须是过去的日期")
private LocalDate birthDate;
@Future
:确保日期在当前日期之后。@FutureOrPresent
:确保日期在当前日期或之后。java.util.Date
、java.time.LocalDate
等日期类型。regexp
:指定的正则表达式。flags
:正则表达式的匹配标志(如大小写敏感性)。@Pattern(regexp = "^[a-zA-Z0-9]{6,12}$", message = "密码必须是6到12位的字母和数字组合")
private String password;
regexp
:正则表达式,默认符合标准的邮箱格式。flags
:正则表达式的匹配标志。integer
:最大整数位数。fraction
:最大小数位数。@Digits(integer = 5, fraction = 2, message = "金额整数部分不能超过5位,小数部分不能超过2位")
private BigDecimal amount;
value
:允许的最小或最大值。inclusive
:是否包含边界值,默认为 true
。@DecimalMin(value = "0.0", inclusive = false, message = "金额必须大于0")
private BigDecimal price;
@AssertTrue
:确保字段值为 true
。@AssertFalse
:确保字段值为 false
。@AssertTrue(message = "必须同意条款")
private Boolean termsAccepted;
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";
}
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 {};
}
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,}$");
}
}
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();
}
}