引言
在现代Java Web开发中,异常处理是构建健壮、可维护应用程序的关键环节。Spring Boot作为主流的Java开发框架,提供了丰富的异常处理机制来帮助开发者优雅地处理各种运行时错误。本文将深入探讨Spring Boot中的异常处理最佳实践,涵盖自定义异常类设计、@ControllerAdvice全局异常处理,以及WebFlux响应式编程中的异常捕获机制。
异常处理不仅仅是简单的错误记录,更是用户体验和系统稳定性的关键。一个设计良好的异常处理系统能够:
- 提供清晰、一致的错误信息
- 隐藏敏感的系统内部信息
- 支持不同类型的错误响应格式
- 在分布式环境中提供统一的错误处理策略
自定义异常类设计
异常类设计原则
在Spring Boot应用中,合理的异常类设计是构建良好错误处理系统的基础。自定义异常类应该遵循以下原则:
- 继承关系清晰:合理的继承层次结构能够帮助区分不同类型的错误
- 业务语义明确:异常类名称和内容应该准确反映业务场景
- 可扩展性强:设计时要考虑未来可能的扩展需求
基础异常类设计
// 基础业务异常类
public class BusinessException extends RuntimeException {
private String code;
private Object[] params;
public BusinessException(String code, String message) {
super(message);
this.code = code;
}
public BusinessException(String code, String message, Object... params) {
super(message);
this.code = code;
this.params = params;
}
public BusinessException(String code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}
// getter和setter方法
public String getCode() {
return code;
}
public Object[] getParams() {
return params;
}
}
具体业务异常类
// 用户不存在异常
public class UserNotFoundException extends BusinessException {
public UserNotFoundException(String userId) {
super("USER_NOT_FOUND", "用户不存在,用户ID: " + userId);
}
}
// 参数验证异常
public class ValidationException extends BusinessException {
public ValidationException(String message) {
super("VALIDATION_ERROR", message);
}
public ValidationException(String message, Object... params) {
super("VALIDATION_ERROR", message, params);
}
}
// 权限不足异常
public class AccessDeniedException extends BusinessException {
public AccessDeniedException(String message) {
super("ACCESS_DENIED", message);
}
}
异常枚举设计
为了更好地管理异常代码,可以使用枚举来定义异常类型:
public enum ExceptionCode {
USER_NOT_FOUND("USER_NOT_FOUND", "用户不存在"),
USER_ALREADY_EXISTS("USER_ALREADY_EXISTS", "用户已存在"),
VALIDATION_ERROR("VALIDATION_ERROR", "参数验证失败"),
ACCESS_DENIED("ACCESS_DENIED", "权限不足"),
INTERNAL_ERROR("INTERNAL_ERROR", "系统内部错误");
private final String code;
private final String message;
ExceptionCode(String code, String message) {
this.code = code;
this.message = message;
}
public String getCode() {
return code;
}
public String getMessage() {
return message;
}
}
@ControllerAdvice全局异常处理
全局异常处理器基础概念
@ControllerAdvice是Spring Boot中实现全局异常处理的核心注解。它能够拦截所有Controller中的异常,并提供统一的错误响应格式。
基础全局异常处理器
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* 处理业务异常
*/
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException ex) {
log.warn("业务异常: {}", ex.getMessage(), ex);
ErrorResponse errorResponse = ErrorResponse.builder()
.code(ex.getCode())
.message(ex.getMessage())
.timestamp(LocalDateTime.now())
.build();
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
}
/**
* 处理参数验证异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidationException(MethodArgumentNotValidException ex) {
log.warn("参数验证失败: {}", ex.getMessage());
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage())
);
ErrorResponse errorResponse = ErrorResponse.builder()
.code("VALIDATION_ERROR")
.message("参数验证失败")
.details(errors)
.timestamp(LocalDateTime.now())
.build();
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
}
/**
* 处理通用异常
*/
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGeneralException(Exception ex) {
log.error("系统内部错误: ", ex);
ErrorResponse errorResponse = ErrorResponse.builder()
.code("INTERNAL_ERROR")
.message("系统内部错误,请稍后重试")
.timestamp(LocalDateTime.now())
.build();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
}
}
错误响应对象设计
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ErrorResponse {
private String code;
private String message;
private Object details;
private LocalDateTime timestamp;
private String path;
public static ErrorResponse of(String code, String message) {
return ErrorResponse.builder()
.code(code)
.message(message)
.timestamp(LocalDateTime.now())
.build();
}
}
高级异常处理器示例
@ControllerAdvice
@Slf4j
public class AdvancedGlobalExceptionHandler {
/**
* 处理HTTP状态异常
*/
@ExceptionHandler(HttpClientErrorException.class)
public ResponseEntity<ErrorResponse> handleHttpClientError(HttpClientErrorException ex) {
log.warn("客户端错误: {}", ex.getMessage());
ErrorResponse errorResponse = ErrorResponse.builder()
.code("CLIENT_ERROR")
.message(ex.getMessage())
.timestamp(LocalDateTime.now())
.build();
return ResponseEntity.status(ex.getStatusCode()).body(errorResponse);
}
/**
* 处理HTTP状态异常
*/
@ExceptionHandler(HttpServerErrorException.class)
public ResponseEntity<ErrorResponse> handleHttpServerError(HttpServerErrorException ex) {
log.error("服务器错误: {}", ex.getMessage());
ErrorResponse errorResponse = ErrorResponse.builder()
.code("SERVER_ERROR")
.message("服务器内部错误")
.timestamp(LocalDateTime.now())
.build();
return ResponseEntity.status(ex.getStatusCode()).body(errorResponse);
}
/**
* 处理方法参数不匹配异常
*/
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public ResponseEntity<ErrorResponse> handleMethodArgumentTypeMismatch(
MethodArgumentTypeMismatchException ex) {
log.warn("参数类型不匹配: {}", ex.getMessage());
ErrorResponse errorResponse = ErrorResponse.builder()
.code("ARGUMENT_TYPE_MISMATCH")
.message(String.format("参数 '%s' 类型不匹配,期望类型: %s",
ex.getName(), ex.getRequiredType().getSimpleName()))
.timestamp(LocalDateTime.now())
.build();
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
}
/**
* 处理JSON解析异常
*/
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<ErrorResponse> handleHttpMessageNotReadable(
HttpMessageNotReadableException ex) {
log.warn("JSON解析失败: {}", ex.getMessage());
ErrorResponse errorResponse = ErrorResponse.builder()
.code("JSON_PARSE_ERROR")
.message("请求数据格式错误")
.timestamp(LocalDateTime.now())
.build();
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
}
}
响应式编程异常捕获
WebFlux异常处理基础
Spring Boot WebFlux响应式编程中的异常处理与传统同步编程有所不同。在响应式编程中,异常处理需要考虑异步和非阻塞的特性。
响应式异常处理器设计
@Component
@Slf4j
public class ReactiveGlobalExceptionHandler {
/**
* 处理响应式业务异常
*/
public Mono<ServerResponse> handleBusinessException(BusinessException ex, ServerWebExchange exchange) {
log.warn("响应式业务异常: {}", ex.getMessage());
ErrorResponse errorResponse = ErrorResponse.builder()
.code(ex.getCode())
.message(ex.getMessage())
.timestamp(LocalDateTime.now())
.build();
return ServerResponse.status(HttpStatus.BAD_REQUEST)
.bodyValue(errorResponse);
}
/**
* 处理响应式通用异常
*/
public Mono<ServerResponse> handleGeneralException(Exception ex, ServerWebExchange exchange) {
log.error("响应式通用异常: ", ex);
ErrorResponse errorResponse = ErrorResponse.builder()
.code("INTERNAL_ERROR")
.message("系统内部错误,请稍后重试")
.timestamp(LocalDateTime.now())
.build();
return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR)
.bodyValue(errorResponse);
}
/**
* 处理响应式验证异常
*/
public Mono<ServerResponse> handleValidationException(MethodArgumentNotValidException ex,
ServerWebExchange exchange) {
log.warn("响应式验证异常: {}", ex.getMessage());
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage())
);
ErrorResponse errorResponse = ErrorResponse.builder()
.code("VALIDATION_ERROR")
.message("参数验证失败")
.details(errors)
.timestamp(LocalDateTime.now())
.build();
return ServerResponse.status(HttpStatus.BAD_REQUEST)
.bodyValue(errorResponse);
}
}
WebFilter异常处理
@Component
@Slf4j
public class ReactiveExceptionFilter implements WebFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return chain.filter(exchange)
.onErrorMap(BusinessException.class, this::mapToErrorResponse)
.onErrorMap(Exception.class, this::mapToGeneralErrorResponse);
}
private RuntimeException mapToErrorResponse(BusinessException ex) {
log.warn("映射业务异常: {}", ex.getMessage());
return ex;
}
private RuntimeException mapToGeneralErrorResponse(Exception ex) {
log.error("映射通用异常: ", ex);
return new BusinessException("INTERNAL_ERROR", "系统内部错误");
}
}
响应式控制器异常处理
@RestController
@RequestMapping("/api/users")
@Slf4j
public class ReactiveUserController {
private final UserService userService;
public ReactiveUserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/{id}")
public Mono<User> getUserById(@PathVariable String id) {
return userService.findById(id)
.switchIfEmpty(Mono.error(new UserNotFoundException(id)))
.doOnNext(user -> log.info("获取用户成功: {}", user.getId()));
}
@PostMapping
public Mono<User> createUser(@Valid @RequestBody CreateUserRequest request) {
return userService.createUser(request)
.doOnSuccess(user -> log.info("创建用户成功: {}", user.getId()))
.doOnError(error -> log.error("创建用户失败: ", error));
}
@PutMapping("/{id}")
public Mono<User> updateUser(@PathVariable String id, @Valid @RequestBody UpdateUserRequest request) {
return userService.updateUser(id, request)
.switchIfEmpty(Mono.error(new UserNotFoundException(id)))
.doOnSuccess(user -> log.info("更新用户成功: {}", user.getId()));
}
@DeleteMapping("/{id}")
public Mono<Void> deleteUser(@PathVariable String id) {
return userService.deleteUser(id)
.switchIfEmpty(Mono.error(new UserNotFoundException(id)))
.doOnSuccess(v -> log.info("删除用户成功: {}", id));
}
}
异常处理最佳实践
异常处理策略
- 区分业务异常和系统异常:业务异常应该被明确处理,系统异常应该被记录并返回通用错误信息
- 保持错误信息的一致性:统一的错误响应格式能够提升用户体验
- 避免敏感信息泄露:在生产环境中不应该暴露详细的系统内部信息
日志记录最佳实践
@ControllerAdvice
@Slf4j
public class LoggingGlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException ex) {
// 记录详细的错误日志
log.warn("业务异常 - 代码: {}, 消息: {}, 参数: {}, 堆栈: {}",
ex.getCode(), ex.getMessage(), ex.getParams(),
Arrays.toString(ex.getStackTrace()));
// 返回简化错误信息给客户端
ErrorResponse errorResponse = ErrorResponse.builder()
.code(ex.getCode())
.message(ex.getMessage())
.timestamp(LocalDateTime.now())
.build();
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGeneralException(Exception ex) {
// 记录完整错误信息
log.error("系统异常 - 消息: {}, 堆栈: {}", ex.getMessage(),
Arrays.toString(ex.getStackTrace()));
// 返回通用错误信息
ErrorResponse errorResponse = ErrorResponse.builder()
.code("INTERNAL_ERROR")
.message("系统内部错误,请稍后重试")
.timestamp(LocalDateTime.now())
.build();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
}
}
异常处理性能优化
@ControllerAdvice
public class PerformanceOptimizedExceptionHandler {
// 缓存错误响应模板
private static final Map<String, ErrorResponse> ERROR_TEMPLATES = new ConcurrentHashMap<>();
static {
ERROR_TEMPLATES.put("VALIDATION_ERROR", ErrorResponse.builder()
.code("VALIDATION_ERROR")
.message("参数验证失败")
.timestamp(LocalDateTime.now())
.build());
}
@ExceptionHandler(ValidationException.class)
public ResponseEntity<ErrorResponse> handleValidationException(ValidationException ex) {
// 使用缓存的错误模板
ErrorResponse errorResponse = ERROR_TEMPLATES.get("VALIDATION_ERROR");
errorResponse.setDetails(ex.getParams());
errorResponse.setTimestamp(LocalDateTime.now());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
}
}
高级异常处理模式
异常链处理
@ControllerAdvice
public class ExceptionChainHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleException(Exception ex) {
// 分析异常链
Throwable cause = ex.getCause();
while (cause != null) {
if (cause instanceof BusinessException) {
return handleBusinessException((BusinessException) cause);
}
cause = cause.getCause();
}
// 如果没有找到业务异常,处理通用异常
return handleGeneralException(ex);
}
private ResponseEntity<ErrorResponse> handleBusinessException(BusinessException ex) {
// 业务异常处理逻辑
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(ErrorResponse.builder()
.code(ex.getCode())
.message(ex.getMessage())
.timestamp(LocalDateTime.now())
.build());
}
private ResponseEntity<ErrorResponse> handleGeneralException(Exception ex) {
// 通用异常处理逻辑
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ErrorResponse.builder()
.code("INTERNAL_ERROR")
.message("系统内部错误")
.timestamp(LocalDateTime.now())
.build());
}
}
异常响应格式化
@ControllerAdvice
public class FormattedExceptionHandler {
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException ex) {
ErrorResponse errorResponse = buildErrorResponse(ex);
// 根据请求类型返回不同格式
ServerHttpRequest request = RequestContextHolder.getRequestAttributes().getRequest();
String accept = request.getHeaders().getFirst("Accept");
if (accept != null && accept.contains("application/json")) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
} else {
// 返回HTML格式错误页面
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.contentType(MediaType.TEXT_HTML)
.body(buildHtmlErrorPage(errorResponse));
}
}
private ErrorResponse buildErrorResponse(BusinessException ex) {
return ErrorResponse.builder()
.code(ex.getCode())
.message(ex.getMessage())
.timestamp(LocalDateTime.now())
.build();
}
private String buildHtmlErrorPage(ErrorResponse errorResponse) {
return "<html><body><h1>错误</h1>" +
"<p>错误代码: " + errorResponse.getCode() + "</p>" +
"<p>错误信息: " + errorResponse.getMessage() + "</p>" +
"</body></html>";
}
}
测试异常处理
单元测试异常处理
@SpringBootTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class ExceptionHandlerTest {
@Autowired
private TestRestTemplate restTemplate;
@Test
void testBusinessExceptionHandling() {
// 测试业务异常处理
ResponseEntity<ErrorResponse> response = restTemplate.getForEntity(
"/api/users/999", ErrorResponse.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
assertThat(response.getBody().getCode()).isEqualTo("USER_NOT_FOUND");
}
@Test
void testValidationExceptionHandling() {
// 测试参数验证异常处理
Map<String, Object> request = new HashMap<>();
request.put("name", "");
ResponseEntity<ErrorResponse> response = restTemplate.postForEntity(
"/api/users", request, ErrorResponse.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
assertThat(response.getBody().getCode()).isEqualTo("VALIDATION_ERROR");
}
@Test
void testGeneralExceptionHandling() {
// 测试通用异常处理
ResponseEntity<ErrorResponse> response = restTemplate.getForEntity(
"/api/users/error", ErrorResponse.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
assertThat(response.getBody().getCode()).isEqualTo("INTERNAL_ERROR");
}
}
总结
Spring Boot异常处理是一个复杂但至关重要的主题。通过本文的详细介绍,我们了解了:
- 自定义异常类设计:合理的异常类设计是构建健壮错误处理系统的基础
- @ControllerAdvice全局异常处理:提供了统一的异常处理机制
- 响应式编程异常捕获:针对WebFlux的特殊异常处理需求
- 最佳实践:包括性能优化、日志记录、异常链处理等高级模式
一个优秀的异常处理系统应该具备:
- 清晰的异常分类和处理逻辑
- 一致的错误响应格式
- 详细的日志记录能力
- 良好的性能表现
- 适配不同编程模式的能力
通过合理运用这些技术和最佳实践,开发者能够构建出更加健壮、用户友好的Spring Boot应用程序。记住,异常处理不仅仅是错误处理,更是提升应用程序整体质量和用户体验的重要环节。

评论 (0)