在现代Java微服务架构中,异常处理是构建健壮应用系统的关键环节。Spring Boot作为主流的微服务开发框架,提供了丰富的异常处理机制。本文将深入解析Spring Boot中的异常处理机制,从基础概念到高级实践,帮助开发者构建完善的异常处理体系。
一、Spring Boot异常处理基础概念
1.1 异常处理的重要性
在微服务架构中,异常处理不仅仅是代码的健壮性保障,更是用户体验和系统稳定性的关键。良好的异常处理机制能够:
- 提供清晰的错误信息给客户端
- 记录详细的错误日志便于问题排查
- 实现优雅降级,提升系统可用性
- 统一错误响应格式,便于前端处理
1.2 Spring Boot中的异常处理机制
Spring Boot通过多种机制实现异常处理:
- @ControllerAdvice:全局异常处理器
- @ExceptionHandler:方法级别的异常处理
- @ResponseStatus:设置HTTP状态码
- ErrorController:自定义错误页面
二、自定义异常类设计
2.1 自定义异常类的重要性
在实际开发中,我们不应该直接抛出系统内置的异常,而应该创建业务相关的自定义异常。这样做的好处包括:
- 提供更清晰的业务语义
- 便于区分不同类型的错误
- 支持统一的错误码管理
- 有利于异常处理逻辑的复用
2.2 自定义异常类设计模式
/**
* 基础业务异常类
*/
public class BusinessException extends RuntimeException {
private String code;
private Object[] args;
public BusinessException(String code, String message) {
super(message);
this.code = code;
}
public BusinessException(String code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}
public BusinessException(String code, String message, Object... args) {
super(message);
this.code = code;
this.args = args;
}
// getter和setter方法
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public Object[] getArgs() {
return args;
}
public void setArgs(Object[] args) {
this.args = args;
}
}
/**
* 参数验证异常
*/
public class ValidationException extends BusinessException {
public ValidationException(String message) {
super("VALIDATION_ERROR", message);
}
public ValidationException(String code, String message) {
super(code, message);
}
}
/**
* 资源未找到异常
*/
public class ResourceNotFoundException extends BusinessException {
public ResourceNotFoundException(String message) {
super("RESOURCE_NOT_FOUND", message);
}
public ResourceNotFoundException(String resourceType, Long id) {
super("RESOURCE_NOT_FOUND",
String.format("%s with id %d not found", resourceType, id));
}
}
/**
* 权限异常
*/
public class AccessDeniedException extends BusinessException {
public AccessDeniedException(String message) {
super("ACCESS_DENIED", message);
}
}
2.3 异常码管理策略
/**
* 错误码枚举类
*/
public enum ErrorCode {
// 通用错误码
SUCCESS(0, "操作成功"),
INTERNAL_ERROR(500, "服务器内部错误"),
VALIDATION_ERROR(400, "参数验证失败"),
RESOURCE_NOT_FOUND(404, "资源未找到"),
ACCESS_DENIED(403, "权限不足"),
// 业务错误码
USER_EXISTS(1001, "用户已存在"),
USER_NOT_FOUND(1002, "用户不存在"),
PASSWORD_ERROR(1003, "密码错误"),
TOKEN_EXPIRED(1004, "token已过期"),
TOKEN_INVALID(1005, "token无效");
private final int code;
private final String message;
ErrorCode(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}
/**
* 基础异常响应类
*/
public class ErrorResponse {
private int code;
private String message;
private String timestamp;
private String path;
public ErrorResponse(int code, String message) {
this.code = code;
this.message = message;
this.timestamp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
}
// getter和setter方法
public int getCode() { return code; }
public void setCode(int code) { this.code = code; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
public String getTimestamp() { return timestamp; }
public void setTimestamp(String timestamp) { this.timestamp = timestamp; }
public String getPath() { return path; }
public void setPath(String path) { this.path = path; }
}
三、@ControllerAdvice全局异常捕获
3.1 @ControllerAdvice核心概念
@ControllerAdvice是Spring MVC提供的全局异常处理器注解,它能够拦截所有Controller中的异常,并统一处理。使用@ControllerAdvice可以避免在每个Controller中重复编写异常处理代码。
3.2 基础全局异常处理器实现
/**
* 全局异常处理器
*/
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* 处理自定义业务异常
*/
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException ex, WebRequest request) {
log.error("Business exception occurred: {}", ex.getMessage(), ex);
ErrorResponse errorResponse = new ErrorResponse(
Integer.parseInt(ex.getCode()),
ex.getMessage()
);
errorResponse.setPath(getRequestPath(request));
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
}
/**
* 处理参数验证异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidationException(
MethodArgumentNotValidException ex, WebRequest request) {
log.error("Validation exception occurred: {}", ex.getMessage());
StringBuilder errorMsg = new StringBuilder();
ex.getBindingResult().getFieldErrors().forEach(error ->
errorMsg.append(error.getField()).append(": ").append(error.getDefaultMessage()).append("; ")
);
ErrorResponse errorResponse = new ErrorResponse(
ErrorCode.VALIDATION_ERROR.getCode(),
errorMsg.toString()
);
errorResponse.setPath(getRequestPath(request));
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
}
/**
* 处理请求参数类型转换异常
*/
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public ResponseEntity<ErrorResponse> handleTypeMismatchException(
MethodArgumentTypeMismatchException ex, WebRequest request) {
log.error("Type mismatch exception occurred: {}", ex.getMessage());
ErrorResponse errorResponse = new ErrorResponse(
ErrorCode.VALIDATION_ERROR.getCode(),
String.format("Parameter '%s' should be of type %s",
ex.getName(), ex.getRequiredType().getSimpleName())
);
errorResponse.setPath(getRequestPath(request));
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
}
/**
* 处理资源未找到异常
*/
@ExceptionHandler(NoHandlerFoundException.class)
public ResponseEntity<ErrorResponse> handleResourceNotFoundException(
NoHandlerFoundException ex, WebRequest request) {
log.error("Resource not found exception occurred: {}", ex.getMessage());
ErrorResponse errorResponse = new ErrorResponse(
ErrorCode.RESOURCE_NOT_FOUND.getCode(),
"Requested resource not found"
);
errorResponse.setPath(getRequestPath(request));
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse);
}
/**
* 处理全局异常
*/
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGlobalException(Exception ex, WebRequest request) {
log.error("Global exception occurred: {}", ex.getMessage(), ex);
ErrorResponse errorResponse = new ErrorResponse(
ErrorCode.INTERNAL_ERROR.getCode(),
"Internal server error occurred"
);
errorResponse.setPath(getRequestPath(request));
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
}
/**
* 获取请求路径
*/
private String getRequestPath(WebRequest request) {
if (request instanceof ServletWebRequest) {
return ((ServletWebRequest) request).getRequest().getRequestURI();
}
return "";
}
}
3.3 高级异常处理器实现
/**
* 增强版全局异常处理器
*/
@ControllerAdvice
@Slf4j
public class EnhancedGlobalExceptionHandler {
/**
* 处理Spring Security权限异常
*/
@ExceptionHandler(AccessDeniedException.class)
public ResponseEntity<ErrorResponse> handleAccessDeniedException(
AccessDeniedException ex, WebRequest request) {
log.warn("Access denied: {}", ex.getMessage());
ErrorResponse errorResponse = new ErrorResponse(
ErrorCode.ACCESS_DENIED.getCode(),
"Access denied"
);
errorResponse.setPath(getRequestPath(request));
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(errorResponse);
}
/**
* 处理认证异常
*/
@ExceptionHandler(AuthenticationException.class)
public ResponseEntity<ErrorResponse> handleAuthenticationException(
AuthenticationException ex, WebRequest request) {
log.warn("Authentication failed: {}", ex.getMessage());
ErrorResponse errorResponse = new ErrorResponse(
401,
"Authentication required"
);
errorResponse.setPath(getRequestPath(request));
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(errorResponse);
}
/**
* 处理HTTP状态码异常
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ResponseEntity<ErrorResponse> handleMethodNotAllowedException(
HttpRequestMethodNotSupportedException ex, WebRequest request) {
log.warn("Method not allowed: {}", ex.getMessage());
ErrorResponse errorResponse = new ErrorResponse(
405,
String.format("Method %s not allowed", ex.getMethod())
);
errorResponse.setPath(getRequestPath(request));
return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED).body(errorResponse);
}
/**
* 处理HTTP消息转换异常
*/
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<ErrorResponse> handleHttpMessageNotReadable(
HttpMessageNotReadableException ex, WebRequest request) {
log.error("Http message not readable: {}", ex.getMessage());
ErrorResponse errorResponse = new ErrorResponse(
ErrorCode.VALIDATION_ERROR.getCode(),
"Invalid request body format"
);
errorResponse.setPath(getRequestPath(request));
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
}
/**
* 处理请求超时异常
*/
@ExceptionHandler(TimeoutException.class)
public ResponseEntity<ErrorResponse> handleTimeoutException(
TimeoutException ex, WebRequest request) {
log.error("Request timeout: {}", ex.getMessage());
ErrorResponse errorResponse = new ErrorResponse(
408,
"Request timeout"
);
errorResponse.setPath(getRequestPath(request));
return ResponseEntity.status(HttpStatus.REQUEST_TIMEOUT).body(errorResponse);
}
/**
* 处理服务降级异常
*/
@ExceptionHandler(FeignException.class)
public ResponseEntity<ErrorResponse> handleFeignException(
FeignException ex, WebRequest request) {
log.error("Feign client error: {}", ex.getMessage());
ErrorResponse errorResponse = new ErrorResponse(
ex.status(),
"Service unavailable"
);
errorResponse.setPath(getRequestPath(request));
return ResponseEntity.status(ex.status()).body(errorResponse);
}
/**
* 处理重复请求异常
*/
@ExceptionHandler(DuplicateKeyException.class)
public ResponseEntity<ErrorResponse> handleDuplicateKeyException(
DuplicateKeyException ex, WebRequest request) {
log.error("Duplicate key exception: {}", ex.getMessage());
ErrorResponse errorResponse = new ErrorResponse(
409,
"Resource already exists"
);
errorResponse.setPath(getRequestPath(request));
return ResponseEntity.status(HttpStatus.CONFLICT).body(errorResponse);
}
private String getRequestPath(WebRequest request) {
if (request instanceof ServletWebRequest) {
HttpServletRequest httpRequest = ((ServletWebRequest) request).getRequest();
return httpRequest.getRequestURI();
}
return "";
}
}
四、微服务场景下的异常处理
4.1 微服务异常传播机制
在微服务架构中,异常往往需要跨服务传播。我们需要设计合理的异常传播机制:
/**
* 微服务异常传播工具类
*/
@Component
public class ServiceExceptionHandler {
/**
* 构建服务间传递的异常信息
*/
public ServiceException buildServiceException(String service, Exception ex) {
return new ServiceException(
service,
ex.getClass().getSimpleName(),
ex.getMessage(),
System.currentTimeMillis()
);
}
/**
* 从服务异常中恢复原始异常
*/
public Exception restoreException(ServiceException serviceEx) {
try {
Class<? extends Exception> exceptionClass =
(Class<? extends Exception>) Class.forName(serviceEx.getExceptionType());
return exceptionClass.getConstructor(String.class)
.newInstance(serviceEx.getMessage());
} catch (Exception e) {
return new RuntimeException("Failed to restore original exception", e);
}
}
}
/**
* 服务异常类
*/
public class ServiceException extends RuntimeException {
private String service;
private String exceptionType;
private long timestamp;
public ServiceException(String service, String exceptionType, String message, long timestamp) {
super(message);
this.service = service;
this.exceptionType = exceptionType;
this.timestamp = timestamp;
}
// getter和setter方法
public String getService() { return service; }
public void setService(String service) { this.service = service; }
public String getExceptionType() { return exceptionType; }
public void setExceptionType(String exceptionType) { this.exceptionType = exceptionType; }
public long getTimestamp() { return timestamp; }
public void setTimestamp(long timestamp) { this.timestamp = timestamp; }
}
4.2 Feign客户端异常处理
/**
* Feign客户端配置和异常处理
*/
@Configuration
public class FeignConfig {
@Bean
public Request.Options options() {
return new Request.Options(5000, 10000); // 连接超时5s,读取超时10s
}
@Bean
public ErrorDecoder errorDecoder() {
return new CustomErrorDecoder();
}
}
/**
* 自定义Feign错误解码器
*/
public class CustomErrorDecoder implements ErrorDecoder {
private static final ObjectMapper objectMapper = new ObjectMapper();
@Override
public Exception decode(String methodKey, Response response) {
try {
// 读取响应体中的错误信息
String body = Util.toString(response.body().asReader(StandardCharsets.UTF_8));
if (response.status() >= 400 && response.status() < 500) {
return new BusinessException(
"CLIENT_ERROR",
"Client error occurred: " + body
);
} else if (response.status() >= 500) {
return new BusinessException(
"SERVER_ERROR",
"Server error occurred: " + body
);
}
} catch (IOException e) {
log.error("Failed to decode response body", e);
}
return new RuntimeException("Unknown error occurred");
}
}
五、优雅降级策略实现
5.1 Hystrix/Resilience4j优雅降级
/**
* 基于Resilience4j的优雅降级实现
*/
@Service
public class UserService {
private final UserClient userClient;
private final CircuitBreaker circuitBreaker;
private final Retry retry;
public UserService(UserClient userClient, CircuitBreakerRegistry registry) {
this.userClient = userClient;
this.circuitBreaker = registry.circuitBreaker("user-service");
this.retry = Retry.ofDefaults("user-service");
}
/**
* 获取用户信息 - 带降级
*/
public User getUserById(Long id) {
// 使用Resilience4j包装调用
Supplier<User> userSupplier = () -> userClient.getUserById(id);
return circuitBreaker.executeSupplier(
retry.executeSupplier(userSupplier)
);
}
/**
* 获取用户信息 - 降级处理
*/
public User getUserByIdWithFallback(Long id) {
try {
return circuitBreaker.executeSupplier(() -> userClient.getUserById(id));
} catch (Exception e) {
log.warn("Failed to get user from service, using fallback: {}", e.getMessage());
// 返回默认用户信息或空值
return new User(id, "Fallback User", "fallback@example.com");
}
}
}
5.2 服务降级策略配置
# application.yml
resilience4j:
circuitbreaker:
instances:
user-service:
failure-rate-threshold: 50
wait-duration-in-open-state: 30s
permitted-number-of-calls-in-half-open-state: 10
sliding-window-size: 100
sliding-window-type: COUNT_BASED
retry:
instances:
user-service:
max-attempts: 3
wait-duration: 1000ms
retryable-exceptions:
- java.lang.RuntimeException
- org.springframework.web.client.ResourceAccessException
5.3 自定义降级逻辑
/**
* 自定义服务降级处理器
*/
@Component
public class ServiceFallbackHandler {
private static final Logger log = LoggerFactory.getLogger(ServiceFallbackHandler.class);
/**
* 用户服务降级处理
*/
public User getUserByIdFallback(Long id, Throwable throwable) {
log.warn("User service fallback called for user id: {}, reason: {}",
id, throwable.getMessage());
// 根据异常类型返回不同降级策略
if (throwable instanceof TimeoutException) {
return createDefaultUser(id, "Service timeout");
} else if (throwable instanceof CircuitBreakerOpenException) {
return createDefaultUser(id, "Service unavailable due to circuit breaker");
} else {
return createDefaultUser(id, "Service error occurred");
}
}
/**
* 创建默认用户
*/
private User createDefaultUser(Long id, String reason) {
return new User(
id,
"Default User " + id,
"default" + id + "@example.com",
reason
);
}
}
六、异常处理最佳实践
6.1 异常日志记录策略
/**
* 完善的异常日志记录
*/
@Component
@Slf4j
public class ExceptionLoggingService {
/**
* 记录详细异常信息
*/
public void logException(Exception ex, String context, Map<String, Object> additionalInfo) {
// 构建详细的异常日志
StringBuilder logMessage = new StringBuilder();
logMessage.append("Exception occurred in ").append(context).append("\n");
logMessage.append("Exception type: ").append(ex.getClass().getName()).append("\n");
logMessage.append("Exception message: ").append(ex.getMessage()).append("\n");
if (additionalInfo != null) {
additionalInfo.forEach((key, value) ->
logMessage.append(key).append(": ").append(value).append("\n")
);
}
// 记录堆栈跟踪
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
ex.printStackTrace(pw);
logMessage.append("Stack trace:\n").append(sw.toString());
log.error(logMessage.toString());
}
/**
* 记录业务异常
*/
public void logBusinessException(BusinessException ex, String context) {
Map<String, Object> additionalInfo = new HashMap<>();
additionalInfo.put("code", ex.getCode());
additionalInfo.put("args", Arrays.toString(ex.getArgs()));
logException(ex, context, additionalInfo);
}
}
6.2 异常响应格式标准化
/**
* 统一异常响应格式
*/
public class ApiResponse<T> {
private int code;
private String message;
private T data;
private long timestamp;
private String traceId;
public ApiResponse() {
this.timestamp = System.currentTimeMillis();
this.traceId = UUID.randomUUID().toString();
}
public ApiResponse(int code, String message) {
this();
this.code = code;
this.message = message;
}
public ApiResponse(int code, String message, T data) {
this(code, message);
this.data = data;
}
// 静态工厂方法
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(0, "success", data);
}
public static <T> ApiResponse<T> error(int code, String message) {
return new ApiResponse<>(code, message);
}
public static <T> ApiResponse<T> error(String message) {
return new ApiResponse<>(500, message);
}
// getter和setter方法
public int getCode() { return code; }
public void setCode(int code) { this.code = code; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
public T getData() { return data; }
public void setData(T data) { this.data = data; }
public long getTimestamp() { return timestamp; }
public void setTimestamp(long timestamp) { this.timestamp = timestamp; }
public String getTraceId() { return traceId; }
public void setTraceId(String traceId) { this.traceId = traceId; }
}
6.3 异常处理性能优化
/**
* 异常处理性能优化
*/
@Component
public class OptimizedExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(OptimizedExceptionHandler.class);
/**
* 异步记录异常日志
*/
@Async
public void asyncLogException(Exception ex, String context) {
// 使用异步方式记录日志,避免阻塞主线程
log.error("Async exception logging - Context: {}, Message: {}",
context, ex.getMessage(), ex);
}
/**
* 异常频率限制
*/
private final Map<String, Long> exceptionCounter = new ConcurrentHashMap<>();
private static final long TIME_WINDOW_MS = 60000; // 1分钟窗口
public boolean shouldLogException(String exceptionKey) {
long now = System.currentTimeMillis();
Long lastTime = exceptionCounter.get(exceptionKey);
if (lastTime == null || now - lastTime > TIME_WINDOW_MS) {
exceptionCounter.put(exceptionKey, now);
return true;
}
return false; // 在时间窗口内,不记录重复异常
}
/**
* 异常统计监控
*/
public void monitorException(Exception ex, String context) {
// 统计异常发生次数
String key = ex.getClass().getSimpleName() + "_" + context;
Metrics.counter("exception_count", "type", ex.getClass().getSimpleName(),
"context", context).increment();
}
}
七、完整示例项目结构
7.1 项目目录结构
src/
├── main/
│ ├── java/
│ │ └── com/example/demo/
│ │ ├── exception/
│ │ │ ├── BusinessException.java
│ │ │ ├── ErrorCode.java
│ │ │ ├── ErrorResponse.java
│ │ │ └── GlobalExceptionHandler.java
│ │ ├── config/
│ │ │ └── WebConfig.java
│ │ ├── controller/
│ │ │ └── UserController.java
│ │ ├── service/
│ │ │ └── UserService.java
│ │ └── DemoApplication.java
│ └── resources/
│ ├── application.yml
│ └── messages.properties
7.2 完整控制器示例
/**
* 用户控制器示例
*/
@RestController
@RequestMapping("/api/users")
@Slf4j
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
/**
* 获取用户信息
*/
@GetMapping("/{id}")
public ApiResponse<User> getUser(@PathVariable Long id) {
try {
User user = userService.getUserById(id);
return ApiResponse.success(user);
} catch (ResourceNotFoundException ex) {
log.warn("User not found: {}", id);
throw ex; // 交给全局异常处理器处理
} catch (Exception ex) {
log.error("Failed to get user: {}", id, ex);
throw new BusinessException("INTERNAL_ERROR", "Failed to retrieve user");
}
}
/**
* 创建用户
*/
@PostMapping
public ApiResponse<User> createUser(@Valid @RequestBody CreateUserRequest request) {
try {
User user = userService.createUser(request);
return ApiResponse.success(user);
} catch (ValidationException ex) {
log.warn("Validation failed: {}", ex.getMessage());
throw ex;
} catch (BusinessException ex) {
log.warn("Business exception: {}", ex.getMessage());
throw ex;
} catch (Exception ex) {
log.error("Failed to create user", ex);
throw new BusinessException("INTERNAL_ERROR", "Failed to create user");
}
}
/**
* 更新用户
*/
@PutMapping("/{id}")
public ApiResponse<User> updateUser(@PathVariable Long id,
@Valid @RequestBody UpdateUserRequest request) {
try {
User user = userService.updateUser(id, request);
return ApiResponse.success(user);
} catch (ResourceNotFoundException ex) {
log.warn("User not found: {}", id);
throw ex;
} catch (Exception ex) {
log.error("Failed to update user: {}", id, ex);
throw new BusinessException("INTERNAL_ERROR", "Failed to update user");
}
}
}
八、总结与展望
通过本文的详细介绍,我们全面了解了Spring Boot异常处理的核心概念和实践方法。从基础的自定义异常设计,到全局异常捕获机制,再到微服务场景下的优雅降级策略,每一个环节都至关重要。
关键要点回顾:
- 自定义异常类:创建语义清晰、结构化的业务异常类
- 全局异常处理:使用@ControllerAdvice统一处理各类异常
- 微服务异常传播:设计合理的异常传递和降级机制
- 优雅降级策略:实现服务熔断、重试等容错机制
- 最佳实践:包括日志记录、响应格式标准化、性能优化等
未来发展趋势:
随着Spring Cloud生态系统的发展,我们期待看到:
- 更加智能化的异常检测和处理机制
- 与可观测性工具更深度的集成
- 基于AI的异常预测和自动修复能力
- 更完善的微服务间异常传递协议
通过构建健壮的异常处理体系,我们不仅能够提升应用

评论 (0)