深度解析 Spring MVC @ExceptionHandler
注解
@ExceptionHandler
是 Spring MVC 异常处理机制的核心注解,它提供了一种声明式的方法来处理控制器抛出的异常。本文将全面解析其工作原理、源码实现、使用场景及最佳实践。
一、注解定义与核心作用
1. 源码定义
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExceptionHandler {
Class<? extends Throwable>[] value() default {};
}
2. 核心作用
- 异常处理:处理控制器方法抛出的指定异常
- 统一错误处理:集中管理不同异常类型的处理逻辑
- 自定义响应:生成特定的错误响应
二、工作原理与处理流程
1. 异常处理流程
2. 核心处理阶段
- 异常捕获:
DispatcherServlet
捕获控制器方法抛出的异常 - 处理器查找:
ExceptionHandlerExceptionResolver
查找匹配的异常处理方法 - 处理方法调用:调用匹配的
@ExceptionHandler
方法 - 响应生成:将处理方法的返回值转换为响应
三、源码深度解析
1. 异常解析器核心逻辑
ExceptionHandlerExceptionResolver
是核心实现类:
public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver {
// 查找异常处理方法
protected Method getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) {
// 1. 在当前控制器查找
Method method = getHandlerForException(exception, handlerMethod.getBeanType());
// 2. 在ControllerAdvice中查找
if (method == null) {
for (Object advice : this.controllerAdviceCache) {
method = getHandlerForException(exception, advice.getClass());
if (method != null) break;
}
}
return method;
}
// 调用异常处理方法
protected ModelAndView doInvokeMethod(Method method, Object handler, Exception ex,
HttpServletRequest request, HttpServletResponse response) {
// 反射调用处理方法
Object result = method.invoke(handler, buildHandlerArguments(method, ex, request, response));
// 处理返回值
return handleReturnValue(result, method, ex, request, response);
}
}
2. 异常处理方法查找
ExceptionHandlerMethodResolver
负责具体查找逻辑:
class ExceptionHandlerMethodResolver {
private final Map<Class<? extends Throwable>, Method> mappedMethods = new HashMap<>();
public void detectExceptionMappings(Class<?> handlerType) {
for (Method method : HandlerMethodSelector.selectMethods(handlerType, EXCEPTION_HANDLER_METHOD_FILTER)) {
// 解析@ExceptionHandler注解指定的异常类型
Class<? extends Throwable>[] exceptionTypes = method.getAnnotation(ExceptionHandler.class).value();
for (Class<? extends Throwable> exceptionType : exceptionTypes.length == 0 ?
resolveExceptionsFromMethodSignature(method) : exceptionTypes) {
// 注册异常类型与方法映射
addExceptionMapping(exceptionType, method);
}
}
}
}
3. 异常优先级机制
异常处理方法匹配优先级:
- 当前控制器 > @ControllerAdvice类
- 更具体的异常类型优先(子类 > 父类)
- 多个匹配时按声明顺序调用
四、使用场景与最佳实践
1. 控制器内异常处理
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userService.findById(id).orElseThrow(() ->
new UserNotFoundException("User not found: " + id));
}
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ErrorResponse> handleUserNotFound(UserNotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(new ErrorResponse("USER_NOT_FOUND", ex.getMessage()));
}
}
2. 全局异常处理
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(DataAccessException.class)
public ResponseEntity<ErrorResponse> handleDatabaseError(DataAccessException ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new ErrorResponse("DATABASE_ERROR", "Database operation failed"));
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidation(MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage()));
return ResponseEntity.badRequest().body(errors);
}
}
3. 多种异常统一处理
@ExceptionHandler({IllegalArgumentException.class,
ConstraintViolationException.class})
public ResponseEntity<ErrorResponse> handleBadRequests(Exception ex) {
return ResponseEntity.badRequest()
.body(new ErrorResponse("BAD_REQUEST", ex.getMessage()));
}
4. 自定义异常类
public class ApiException extends RuntimeException {
private String errorCode;
public ApiException(String errorCode, String message) {
super(message);
this.errorCode = errorCode;
}
// getter
}
@ExceptionHandler(ApiException.class)
public ResponseEntity<ErrorResponse> handleApiException(ApiException ex) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(new ErrorResponse(ex.getErrorCode(), ex.getMessage()));
}
五、高级特性详解
1. 多种返回值支持
返回值类型 | 使用场景 | 示例 |
---|---|---|
ResponseEntity | RESTful API | ResponseEntity.status(404).body(error) |
ModelAndView | 传统 MVC 视图 | new ModelAndView("error", "exception", ex) |
String | 视图名称 | "error-page" |
void | 直接写响应 | response.sendError(400) |
ErrorResponse | 自定义错误对象 | new ErrorResponse("ERR_001", "Error") |
2. 获取请求上下文
在异常处理方法中访问请求信息:
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleException(
Exception ex,
WebRequest request,
HttpServletRequest servletRequest,
HttpServletResponse response) {
// 记录请求信息
ErrorResponse error = new ErrorResponse("SERVER_ERROR", "Internal server error");
error.setPath(servletRequest.getRequestURI());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
3. 响应状态码控制
@ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "Invalid input")
@ExceptionHandler(InvalidInputException.class)
public void handleInvalidInput() {
// 直接通过注解设置状态码和原因
}
4. 异步异常处理
@ExceptionHandler(TimeoutException.class)
@ResponseBody
public CompletableFuture<ResponseEntity<?>> handleTimeout() {
return CompletableFuture.completedFuture(
ResponseEntity.status(HttpStatus.REQUEST_TIMEOUT).build()
);
}
六、常见问题解决方案
1. 异常处理器未生效
解决方案:
// 1. 确保@ControllerAdvice类被扫描
@ComponentScan(basePackages = "com.example.exception")
// 2. 检查异常类型是否匹配
@ExceptionHandler(SpecificException.class)
// 3. 查看是否被其他处理器拦截
@Order(Ordered.HIGHEST_PRECEDENCE)
@ControllerAdvice
public class PrimaryExceptionHandler { ... }
2. 异常处理冲突
解决方案:
// 1. 使用@Order指定优先级
@Order(Ordered.HIGHEST_PRECEDENCE)
@ControllerAdvice
public class HighPriorityHandler { ... }
// 2. 使用更具体的异常类型
@ExceptionHandler(SubException.class) // 替代父类异常
// 3. 在控制器内处理特有异常
public class UserController {
@ExceptionHandler(UserSpecificException.class)
}
3. 异常处理中的异常
解决方案:
@ControllerAdvice
public class FallbackExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleAll(Exception ex) {
return ResponseEntity.status(500).body("Internal error");
}
}
七、性能优化策略
1. 异常分类处理
避免使用过于通用的异常处理:
// 不推荐
@ExceptionHandler(Exception.class)
// 推荐处理特定异常
@ExceptionHandler({
ServiceException.class,
ValidationException.class
})
2. 异常处理缓存
Spring 内部缓存异常处理方法查找结果,无需额外优化。
3. 日志优化
@ExceptionHandler(DataAccessException.class)
public ResponseEntity<ErrorResponse> handleDatabaseError(DataAccessException ex) {
// 使用日志级别控制
if (ex instanceof NonTransientDataAccessException) {
log.error("Critical database error", ex);
} else {
log.warn("Temporary database issue", ex);
}
return ResponseEntity.internalServerError().build();
}
八、最佳实践总结
1. 异常处理架构设计
2. 三层异常处理
层级 | 处理方式 | 适用场景 |
---|---|---|
控制器层 | @ExceptionHandler | 控制器特定异常处理 |
全局层 | @ControllerAdvice | 通用异常处理 |
Fallback层 | @ExceptionHandler(Exception.class) | 未知异常兜底处理 |
3. 统一错误响应
public class ErrorResponse {
private String code;
private String message;
private Instant timestamp = Instant.now();
private String path;
private List<FieldError> fieldErrors;
// 构造器和方法
}
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidationError(
MethodArgumentNotValidException ex, HttpServletRequest request) {
ErrorResponse error = new ErrorResponse("VALIDATION_ERROR", "Validation failed");
error.setPath(request.getRequestURI());
ex.getBindingResult().getFieldErrors().forEach(fieldError ->
error.addFieldError(fieldError.getField(), fieldError.getDefaultMessage()));
return ResponseEntity.badRequest().body(error);
}
}
九、未来发展方向
1. 响应式异常处理
WebFlux 中的异常处理:
@RestControllerAdvice
public class ReactiveExceptionHandler {
@ExceptionHandler
public Mono<ResponseEntity<ErrorResponse>> handle(BusinessException ex) {
return Mono.just(ResponseEntity.badRequest()
.body(new ErrorResponse("BUSINESS_ERROR", ex.getMessage())));
}
}
2. 异常处理元数据
集成 OpenAPI 文档:
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseSchema(
responseCode = "404",
description = "Not found error",
schema = NotFoundErrorSchema.class
)
public ResponseEntity<NotFoundError> handleResourceNotFound() { ... }
3. AI 辅助异常分析
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleAll(Exception ex, AIDiagnosticService ai) {
ErrorResponse error = new ErrorResponse("UNKNOWN_ERROR", "Unexpected error");
error.setDiagnostic(ai.diagnose(ex));
return ResponseEntity.internalServerError().body(error);
}
十、总结
@ExceptionHandler
是 Spring MVC 异常处理的核心机制,其关键优势在于:
- 统一处理:集中管理异常处理逻辑
- 灵活定制:支持多种响应类型和状态码
- 精确匹配:按异常类型精确处理
- 上下文访问:获取请求信息增强响应
最佳实践建议:
- 层次化处理:控制器特定处理 + 全局通用处理
- 响应标准化:统一错误响应格式
- 异常分类:为不同业务异常定义专用类型
- 日志完备:记录关键异常信息
- 权限分离:控制器处理业务异常,全局处理技术异常
在现代应用开发中:
- 微服务架构:跨服务异常传播处理
- 云原生环境:与 Kubernetes 健康检查整合
- 响应式编程:适配 WebFlux 响应式模型
- Serverless:优化函数计算异常处理
掌握 @ExceptionHandler
的高级特性和最佳实践,能够帮助开发者构建出:
- 健壮的错误处理系统
- 一致的用户体验
- 高效的问题排查机制
- 安全的异常信息暴露策略
作为 Spring 异常处理的核心组件,@ExceptionHandler
在构建高质量 Web 应用中的重要性不可替代,深入理解其原理和应用是每个 Java Web 开发者的必备技能。