1. 全局异常处理器 RestControllerAdvice 实战一、基础概念​​RestControllerAdvice​​ 是 Spring Boot 中用于处理全局异常的注解它结合了 ​​ControllerAdvice​​ 和 ​​ResponseBody​​ 的功能主要用于 RESTful API 的异常处理。二、基础实现创建全局异常处理器import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; RestControllerAdvice public class GlobalExceptionHandler { // 处理所有未捕获的异常 ExceptionHandler(Exception.class) public ResponseEntityErrorResponse handleException(Exception e) { ErrorResponse error new ErrorResponse( HttpStatus.INTERNAL_SERVER_ERROR.value(), 服务器内部错误, e.getMessage() ); return new ResponseEntity(error, HttpStatus.INTERNAL_SERVER_ERROR); } // 处理自定义业务异常 ExceptionHandler(BusinessException.class) public ResponseEntityErrorResponse handleBusinessException(BusinessException e) { ErrorResponse error new ErrorResponse( e.getCode(), e.getMessage(), e.getDetail() ); return new ResponseEntity(error, HttpStatus.BAD_REQUEST); } // 处理参数校验异常 ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntityErrorResponse handleValidationException( MethodArgumentNotValidException e) { String message e.getBindingResult() .getFieldErrors() .stream() .map(FieldError::getDefaultMessage) .collect(Collectors.joining(, )); ErrorResponse error new ErrorResponse( HttpStatus.BAD_REQUEST.value(), 参数校验失败, message ); return new ResponseEntity(error, HttpStatus.BAD_REQUEST); } }错误响应实体类import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; Data NoArgsConstructor AllArgsConstructor public class ErrorResponse { private Integer code; // 错误码 private String message; // 错误信息 private String detail; // 错误详情 private Long timestamp; // 时间戳 private String path; // 请求路径 public ErrorResponse(Integer code, String message, String detail) { this.code code; this.message message; this.detail detail; this.timestamp System.currentTimeMillis(); } }自定义业务异常类public class BusinessException extends RuntimeException { private Integer code; private String detail; public BusinessException(String message) { super(message); this.code 400; } public BusinessException(Integer code, String message) { super(message); this.code code; } public BusinessException(Integer code, String message, String detail) { super(message); this.code code; this.detail detail; } // getters and setters }三、进阶功能异常分类处理RestControllerAdvice public class GlobalExceptionHandler { // 404 资源不存在 ExceptionHandler(ResourceNotFoundException.class) public ResponseEntityErrorResponse handleResourceNotFound( ResourceNotFoundException e, HttpServletRequest request) { ErrorResponse error ErrorResponse.builder() .code(404) .message(资源不存在) .detail(e.getMessage()) .timestamp(System.currentTimeMillis()) .path(request.getRequestURI()) .build(); return new ResponseEntity(error, HttpStatus.NOT_FOUND); } // 401 未授权 ExceptionHandler(UnauthorizedException.class) public ResponseEntityErrorResponse handleUnauthorized( UnauthorizedException e, HttpServletRequest request) { ErrorResponse error new ErrorResponse( 401, 未授权访问, e.getMessage(), System.currentTimeMillis(), request.getRequestURI() ); return new ResponseEntity(error, HttpStatus.UNAUTHORIZED); } // 403 禁止访问 ExceptionHandler(AccessDeniedException.class) public ResponseEntityErrorResponse handleAccessDenied( AccessDeniedException e, HttpServletRequest request) { ErrorResponse error new ErrorResponse( 403, 禁止访问, e.getMessage(), System.currentTimeMillis(), request.getRequestURI() ); return new ResponseEntity(error, HttpStatus.FORBIDDEN); } }参数校验增强RestControllerAdvice public class GlobalExceptionHandler { // 处理 Valid 参数校验异常 ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntityValidationErrorResponse handleValidationException( MethodArgumentNotValidException e) { ListFieldError fieldErrors e.getBindingResult().getFieldErrors(); ValidationErrorResponse error new ValidationErrorResponse(); error.setCode(400); error.setMessage(参数校验失败); error.setTimestamp(System.currentTimeMillis()); ListValidationError errors fieldErrors.stream() .map(fieldError - new ValidationError( fieldError.getField(), fieldError.getDefaultMessage(), fieldError.getRejectedValue() )) .collect(Collectors.toList()); error.setErrors(errors); return new ResponseEntity(error, HttpStatus.BAD_REQUEST); } // 处理 RequestParam 参数校验异常 ExceptionHandler(ConstraintViolationException.class) public ResponseEntityValidationErrorResponse handleConstraintViolation( ConstraintViolationException e) { ValidationErrorResponse error new ValidationErrorResponse(); error.setCode(400); error.setMessage(参数校验失败); error.setTimestamp(System.currentTimeMillis()); ListValidationError errors e.getConstraintViolations().stream() .map(violation - { String field violation.getPropertyPath().toString(); return new ValidationError( field.substring(field.lastIndexOf(.) 1), violation.getMessage(), violation.getInvalidValue() ); }) .collect(Collectors.toList()); error.setErrors(errors); return new ResponseEntity(error, HttpStatus.BAD_REQUEST); } } // 校验错误响应类 Data class ValidationErrorResponse { private Integer code; private String message; private Long timestamp; private ListValidationError errors; } Data AllArgsConstructor class ValidationError { private String field; private String message; private Object rejectedValue; }国际化支持RestControllerAdvice public class GlobalExceptionHandler { Autowired private MessageSource messageSource; ExceptionHandler(BusinessException.class) public ResponseEntityErrorResponse handleBusinessException( BusinessException e, HttpServletRequest request, Locale locale) { // 从国际化文件中获取错误信息 String localizedMessage messageSource.getMessage( e.getMessageKey(), e.getArgs(), e.getMessage(), locale ); ErrorResponse error new ErrorResponse( e.getCode(), localizedMessage, e.getDetail(), System.currentTimeMillis(), request.getRequestURI() ); return new ResponseEntity(error, HttpStatus.BAD_REQUEST); } }四、最佳实践异常码枚举public enum ErrorCode { SUCCESS(0, 成功), PARAM_ERROR(400, 参数错误), UNAUTHORIZED(401, 未授权), FORBIDDEN(403, 禁止访问), NOT_FOUND(404, 资源不存在), INTERNAL_ERROR(500, 服务器内部错误), // 业务异常码 USER_NOT_EXIST(1001, 用户不存在), PRODUCT_OUT_OF_STOCK(1002, 商品库存不足), ORDER_CREATE_FAILED(1003, 订单创建失败); private final int code; private final String message; ErrorCode(int code, String message) { this.code code; this.message message; } // getters }统一响应体Data public class ApiResponseT { private Integer code; private String message; private T data; private Long timestamp; public static T ApiResponseT success(T data) { ApiResponseT response new ApiResponse(); response.setCode(ErrorCode.SUCCESS.getCode()); response.setMessage(ErrorCode.SUCCESS.getMessage()); response.setData(data); response.setTimestamp(System.currentTimeMillis()); return response; } public static ApiResponse? error(ErrorCode errorCode) { ApiResponse? response new ApiResponse(); response.setCode(errorCode.getCode()); response.setMessage(errorCode.getMessage()); response.setTimestamp(System.currentTimeMillis()); return response; } public static ApiResponse? error(Integer code, String message) { ApiResponse? response new ApiResponse(); response.setCode(code); response.setMessage(message); response.setTimestamp(System.currentTimeMillis()); return response; } }五、总结使用 ​​RestControllerAdvice​​ 实现全局异常处理的最佳实践统一异常处理 所有异常通过统一的方式处理返回格式一致的响应异常分类 根据异常类型返回合适的 HTTP 状态码错误信息友好 生产环境隐藏敏感信息开发环境提供详细错误日志记录 记录异常信息便于排查问题国际化支持 根据客户端语言返回对应的错误信息参数校验 统一处理参数校验异常提供清晰的错误信息业务异常分离 业务异常和系统异常分开处理这样设计的全局异常处理器能够提高代码的可维护性提供更好的用户体验并方便问题排查。2. 自定义业务异常、统一异常返回格式创建自定义异常类// 基础业务异常类 public class BusinessException extends RuntimeException { private final Integer code; private final String message; public BusinessException(Integer code, String message) { super(message); this.code code; this.message message; } public BusinessException(ErrorCode errorCode) { super(errorCode.getMessage()); this.code errorCode.getCode(); this.message errorCode.getMessage(); } public BusinessException(ErrorCode errorCode, String message) { super(message); this.code errorCode.getCode(); this.message message; } // getters public Integer getCode() { return code; } Override public String getMessage() { return message; } } // 参数验证异常 public class ValidationException extends BusinessException { private MapString, String errors; public ValidationException(Integer code, String message) { super(code, message); } public ValidationException(Integer code, String message, MapString, String errors) { super(code, message); this.errors errors; } public MapString, String getErrors() { return errors; } }定义错误码枚举Getter public enum ErrorCode { // 成功 SUCCESS(200, 操作成功), // 客户端错误 BAD_REQUEST(400, 请求参数错误), UNAUTHORIZED(401, 未授权), FORBIDDEN(403, 禁止访问), NOT_FOUND(404, 资源不存在), // 业务错误 BUSINESS_ERROR(1000, 业务异常), VALIDATION_ERROR(1001, 参数验证失败), DATA_NOT_FOUND(1002, 数据不存在), DATA_EXISTS(1003, 数据已存在), OPERATION_FAILED(1004, 操作失败), // 系统错误 INTERNAL_ERROR(500, 系统内部错误), SERVICE_UNAVAILABLE(503, 服务不可用); private final Integer code; private final String message; ErrorCode(Integer code, String message) { this.code code; this.message message; } public static ErrorCode fromCode(Integer code) { for (ErrorCode errorCode : ErrorCode.values()) { if (errorCode.getCode().equals(code)) { return errorCode; } } return INTERNAL_ERROR; } }统一响应对象Data Builder NoArgsConstructor AllArgsConstructor public class ApiResponseT { private Integer code; private String message; private T data; private Long timestamp; public static T ApiResponseT success() { return success(null); } public static T ApiResponseT success(T data) { return ApiResponse.Tbuilder() .code(ErrorCode.SUCCESS.getCode()) .message(ErrorCode.SUCCESS.getMessage()) .data(data) .timestamp(System.currentTimeMillis()) .build(); } public static T ApiResponseT error(ErrorCode errorCode) { return ApiResponse.Tbuilder() .code(errorCode.getCode()) .message(errorCode.getMessage()) .timestamp(System.currentTimeMillis()) .build(); } public static T ApiResponseT error(Integer code, String message) { return ApiResponse.Tbuilder() .code(code) .message(message) .timestamp(System.currentTimeMillis()) .build(); } }全局异常处理器Slf4j RestControllerAdvice public class GlobalExceptionHandler { /** * 处理业务异常 */ ExceptionHandler(BusinessException.class) public ResponseEntityApiResponseObject handleBusinessException(BusinessException e) { log.warn(业务异常: {}, e.getMessage(), e); ApiResponseObject response ApiResponse.builder() .code(e.getCode()) .message(e.getMessage()) .timestamp(System.currentTimeMillis()) .build(); return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); } /** * 处理参数验证异常 */ ExceptionHandler(ValidationException.class) public ResponseEntityApiResponseMapString, String handleValidationException(ValidationException e) { log.warn(参数验证异常: {}, e.getMessage()); ApiResponseMapString, String response ApiResponse.MapString, Stringbuilder() .code(e.getCode()) .message(e.getMessage()) .data(e.getErrors()) .timestamp(System.currentTimeMillis()) .build(); return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); } /** * 处理 Spring Validation 异常 */ ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntityApiResponseMapString, String handleValidationException(MethodArgumentNotValidException e) { log.warn(参数验证异常: {}, e.getMessage()); MapString, String errors new HashMap(); e.getBindingResult().getFieldErrors().forEach(error - errors.put(error.getField(), error.getDefaultMessage()) ); ApiResponseMapString, String response ApiResponse.MapString, Stringbuilder() .code(ErrorCode.VALIDATION_ERROR.getCode()) .message(ErrorCode.VALIDATION_ERROR.getMessage()) .data(errors) .timestamp(System.currentTimeMillis()) .build(); return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); } /** * 处理所有未捕获的异常 */ ExceptionHandler(Exception.class) public ResponseEntityApiResponseObject handleException(Exception e) { log.error(系统异常: , e); ApiResponseObject response ApiResponse.builder() .code(ErrorCode.INTERNAL_ERROR.getCode()) .message(ErrorCode.INTERNAL_ERROR.getMessage()) .timestamp(System.currentTimeMillis()) .build(); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response); } }统一响应处理器ControllerAdvice public class ResponseAdvice implements ResponseBodyAdviceObject { Override public boolean supports(MethodParameter returnType, Class? extends HttpMessageConverter? converterType) { // 排除特定的返回类型 return !returnType.getParameterType().equals(ApiResponse.class) !returnType.getParameterType().equals(Void.class) !returnType.hasMethodAnnotation(IgnoreResponseAdvice.class); } Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class? extends HttpMessageConverter? selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { // 处理字符串类型返回 if (body instanceof String) { return JSON.toJSONString(ApiResponse.success(body)); } // 处理 void 返回类型 if (body null returnType.getParameterType().equals(Void.TYPE)) { return ApiResponse.success(); } return ApiResponse.success(body); } } // 忽略统一响应的注解 Target({ElementType.METHOD, ElementType.TYPE}) Retention(RetentionPolicy.RUNTIME) public interface IgnoreResponseAdvice { }主要优点统一响应格式 所有接口返回统一格式的 JSON异常分类处理 不同类型的异常有不同的处理逻辑错误码管理 通过枚举统一管理错误码和错误信息灵活的异常抛出 可以在业务层任意位置抛出异常易于扩展 可以方便地添加新的异常类型良好的日志记录 异常发生时记录详细的日志信息生产环境友好 避免将敏感异常信息暴露给客户端这个方案可以根据实际业务需求进行调整和扩展。3. 参数校验 JSR303NotBlank、NotNull、Size 等注解使用一、基础配置添加依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency启用校验Configuration public class ValidatorConfig { // Spring Boot 2.3 自动配置无需手动配置 }二、常用校验注解基础注解public class UserDTO { NotBlank(message 用户名不能为空) private String username; NotNull(message 年龄不能为null) Min(value 0, message 年龄不能小于0) Max(value 150, message 年龄不能大于150) private Integer age; Email(message 邮箱格式不正确) private String email; Size(min 6, max 20, message 密码长度必须在6-20位之间) private String password; Pattern(regexp ^1[3-9]\\d{9}$, message 手机号格式不正确) private String phone; AssertTrue(message 必须同意协议) private Boolean agree; Future(message 必须是将来时间) private LocalDateTime appointmentTime; Past(message 必须是过去时间) private LocalDate birthDate; DecimalMin(value 0.0, inclusive false, message 金额必须大于0) DecimalMax(value 10000.0, message 金额不能超过10000) private BigDecimal amount; // getters and setters }集合校验public class OrderDTO { NotEmpty(message 商品列表不能为空) private ListValid ProductDTO products; Size(min 1, max 10, message 标签数量必须在1-10个之间) private ListString tags; NotEmpty(message 收货地址不能为空) private MapString, NotBlank String addresses; }三、使用方法Controller层校验RestController RequestMapping(/api/users) Validated // 类级别开启校验 public class UserController { // 方法参数校验 PostMapping public ResponseEntity? createUser(Valid RequestBody UserDTO userDTO) { // 业务逻辑 return ResponseEntity.ok(创建成功); } // 路径变量校验 GetMapping(/{id}) public ResponseEntity? getUser(PathVariable Min(1) Long id) { return ResponseEntity.ok(查询成功); } // 查询参数校验 GetMapping public ResponseEntity? listUsers( RequestParam Min(0) Integer page, RequestParam Range(min 1, max 100) Integer size) { return ResponseEntity.ok(查询成功); } }Service层校验Service Validated public class UserService { public void createUser(Valid UserDTO userDTO) { // 业务逻辑 } public UserDTO getUser(Min(1) Long id) { // 业务逻辑 return new UserDTO(); } }四、分组校验定义分组接口public interface ValidationGroups { interface Create {} interface Update {} }使用分组public class UserDTO { Null(groups Create.class, message 创建时ID必须为空) NotNull(groups Update.class, message 更新时ID不能为空) private Long id; NotBlank(groups {Create.class, Update.class}, message 用户名不能为空) private String username; }控制器中使用分组PostMapping public ResponseEntity? createUser(Validated(ValidationGroups.Create.class) RequestBody UserDTO userDTO) { return ResponseEntity.ok(创建成功); } PutMapping(/{id}) public ResponseEntity? updateUser(Validated(ValidationGroups.Update.class) RequestBody UserDTO userDTO) { return ResponseEntity.ok(更新成功); }五、自定义校验注解创建自定义注解Target({ElementType.FIELD, ElementType.PARAMETER}) Retention(RetentionPolicy.RUNTIME) Constraint(validatedBy PhoneValidator.class) Documented public interface Phone { String message() default 手机号格式不正确; Class?[] groups() default {}; Class? extends Payload[] payload() default {}; }实现校验逻辑public class PhoneValidator implements ConstraintValidatorPhone, String { private static final Pattern PHONE_PATTERN Pattern.compile(^1[3-9]\\d{9}$); Override public boolean isValid(String phone, ConstraintValidatorContext context) { if (phone null) { return true; // 配合 NotNull 使用 } return PHONE_PATTERN.matcher(phone).matches(); } }使用自定义注解public class UserDTO { Phone private String phone; }六、全局异常处理RestControllerAdvice public class GlobalExceptionHandler { // 处理参数校验异常 ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntityErrorResponse handleValidationExceptions( MethodArgumentNotValidException ex) { ListString errors ex.getBindingResult() .getFieldErrors() .stream() .map(error - error.getField() : error.getDefaultMessage()) .collect(Collectors.toList()); ErrorResponse response new ErrorResponse( 参数校验失败, HttpStatus.BAD_REQUEST.value(), errors ); return ResponseEntity.badRequest().body(response); } // 处理约束违反异常Validated ExceptionHandler(ConstraintViolationException.class) public ResponseEntityErrorResponse handleConstraintViolationException( ConstraintViolationException ex) { ListString errors ex.getConstraintViolations() .stream() .map(violation - violation.getPropertyPath() : violation.getMessage()) .collect(Collectors.toList()); ErrorResponse response new ErrorResponse( 参数校验失败, HttpStatus.BAD_REQUEST.value(), errors ); return ResponseEntity.badRequest().body(response); } // 错误响应类 Data AllArgsConstructor public static class ErrorResponse { private String message; private int status; private ListString errors; private LocalDateTime timestamp LocalDateTime.now(); } }七、嵌套对象校验public class OrderDTO { Valid NotNull(message 用户信息不能为空) private UserDTO user; Valid NotEmpty(message 商品列表不能为空) private ListOrderItemDTO items; } public class OrderItemDTO { NotBlank(message 商品名称不能为空) private String productName; NotNull(message 数量不能为空) Min(value 1, message 数量至少为1) private Integer quantity; }八、国际化消息创建ValidationMessages.propertiesuser.username.notblank用户名不能为空 user.email.invalid邮箱格式不正确在注解中使用public class UserDTO { NotBlank(message {user.username.notblank}) private String username; Email(message {user.email.invalid}) private String email; }九、最佳实践分层校验 Controller层参数格式校验Service层业务逻辑校验数据库层数据完整性校验合理使用分组 根据不同场景使用不同的校验规则自定义错误消息 提供清晰、友好的错误提示性能考虑 避免复杂的正则表达式和自定义校验逻辑组合使用 多个注解可以组合使用增强校验十、常见问题NotNull vs NotEmpty vs NotBlank​​NotNull​​对象不为null但可以是空字符串​​NotEmpty​​集合/数组/Map/字符串不为null且长度/大小大于0​​NotBlank​​字符串不为null且去除空格后长度大于0校验不生效可能原因未添加Valid或Validated注解校验注解放在错误的类型上未添加spring-boot-starter-validation依赖校验顺序使用​​GroupSequence​​定义校验顺序GroupSequence({First.class, Second.class, UserDTO.class}) public class UserDTO { NotBlank(groups First.class) private String username; Email(groups Second.class) private String email; }通过合理使用Spring Boot的参数校验功能可以大大减少业务代码中的参数校验逻辑提高代码的可读性和可维护性。