
本文详解 spring boot 中自定义异常(如 resourcenotfoundexception)无法正确返回 json 响应的常见原因,重点修复全局异常处理器未生效、异常响应体为空、逻辑判断错误等问题,并提供可直接运行的规范实现。
本文详解 spring boot 中自定义异常(如 resourcenotfoundexception)无法正确返回 json 响应的常见原因,重点修复全局异常处理器未生效、异常响应体为空、逻辑判断错误等问题,并提供可直接运行的规范实现。
在 Spring Boot 项目中,当通过 @ControllerAdvice 实现全局异常处理时,若自定义异常(如 ResourceNotFoundException)在 Postman 中无响应或返回空内容,通常并非配置遗漏,而是存在逻辑缺陷 + 响应结构不匹配 + 异常未被正确捕获三重问题。下面将逐层剖析并给出生产级解决方案。
? 根本问题定位
-
getSingleToDo()方法逻辑错误
原代码中:ToDo todo = new ToDo(); // 初始化为新对象,非 null! for (ToDo todo1 : todos) { if (todo1.getId() == id) return todo1; todo = todo1; // 每次都赋值,循环结束 todo 必不为 null } if (todo == null) { ... } // 此条件永远不成立 → 异常永不抛出!✅ 修正方案:移除冗余变量,直接在循环后抛出异常:
public ToDo getSingleToDo(int id) { for (ToDo todo : todos) { if (todo.getId() == id) { return todo; } } // 循环结束仍未找到 → 明确抛出异常 throw new ResourceNotFoundException("Todo with id " + id + " not found", HttpStatus.NOT_FOUND); } -
ExceptionResponse缺少必要字段序列化支持
当前类仅有 getter/setter,但未标注@Getter/@Setter(Lombok)或未确保字段可被 Jackson 序列化。更关键的是:HttpStatus类型无法直接 JSON 序列化(默认输出为枚举全限定名或空对象),导致响应体为空或格式异常。✅ 修正方案:改用
int或String表示状态码,并添加@JsonInclude(Include.NON_NULL)避免空字段:package com.lcwd.todo.exceptions; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; @JsonInclude(Include.NON_NULL) public class ExceptionResponse { private String message; private boolean success; private int statusCode; // ✅ 使用 int 替代 HttpStatus private String status; // ✅ 可选:如 "NOT_FOUND" public ExceptionResponse(String message, boolean success, int statusCode, String status) { this.message = message; this.success = success; this.statusCode = statusCode; this.status = status; } // 省略 getter/setter(务必生成!) } -
GlobalExceptionHandler中的响应构造不严谨
原代码重复调用response.setMessage(...),且未设置statusCode和status字段:@ExceptionHandler(ResourceNotFoundException.class) public ResponseEntity<ExceptionResponse> resourceNotFoundExceptionHandler(ResourceNotFoundException ex) { ExceptionResponse response = new ExceptionResponse(); response.setMessage(ex.getMessage()); // ❌ 重复设置 response.setMessage(ex.getMessage()); response.setSuccess(false); // ❌ statusCode 和 status 未赋值 → JSON 中为 0/null return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response); }✅ 修正方案:使用构造函数初始化,并显式传递状态码与状态文本:
@ExceptionHandler(ResourceNotFoundException.class) public ResponseEntity<ExceptionResponse> resourceNotFoundExceptionHandler(ResourceNotFoundException ex) { ExceptionResponse response = new ExceptionResponse( ex.getMessage(), false, ex.getStatus().value(), // ✅ 获取 HTTP 状态码数值 ex.getStatus().getReasonPhrase() // ✅ 如 "Not Found" ); return ResponseEntity.status(ex.getStatus()).body(response); } -
ResourceNotFoundException构造函数未透传HttpStatus
当前构造函数接收HttpStatus但未在异常类中持久化(仅在父类RuntimeException中存储 message),导致处理器中ex.getStatus()无法获取。✅ 修正方案:在异常类中保留
HttpStatus字段,并提供安全的getStatus()方法:public class ResourceNotFoundException extends RuntimeException { private final HttpStatus status; public ResourceNotFoundException(String message, HttpStatus status) { super(message); this.status = status != null ? status : HttpStatus.NOT_FOUND; } public HttpStatus getStatus() { return status; } }
✅ 完整可运行示例(整合后)
// ExceptionResponse.java
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ExceptionResponse {
private String message;
private boolean success;
private int statusCode;
private String status;
public ExceptionResponse(String message, boolean success, int statusCode, String status) {
this.message = message;
this.success = success;
this.statusCode = statusCode;
this.status = status;
}
// ... getter/setter(IDE 自动生成)
}// GlobalExceptionHandler.java
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ExceptionResponse> handleResourceNotFound(ResourceNotFoundException ex) {
ExceptionResponse response = new ExceptionResponse(
ex.getMessage(),
false,
ex.getStatus().value(),
ex.getStatus().getReasonPhrase()
);
logger.warn("Resource not found: {}", ex.getMessage());
return ResponseEntity.status(ex.getStatus()).body(response);
}
// 兜底异常处理器(推荐)
@ExceptionHandler(Exception.class)
public ResponseEntity<ExceptionResponse> handleGeneric(Exception ex) {
ExceptionResponse response = new ExceptionResponse(
"Internal server error",
false,
HttpStatus.INTERNAL_SERVER_ERROR.value(),
HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()
);
logger.error("Unhandled exception", ex);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}⚠️ 注意事项与最佳实践
-
确保
@ControllerAdvice被组件扫描到:检查包路径是否在@SpringBootApplication扫描范围内,或显式指定@ComponentScan(basePackages = "com.lcwd.todo")。 -
避免在 DTO 中使用
HttpStatus:JSON 序列化器无法自动转换HttpStatus,始终转为int或String。 -
启用日志验证:在
GlobalExceptionHandler中添加logger.warn()/logger.error(),确认异常是否真正进入处理器。 -
Postman 测试技巧:发送不存在 ID 的 GET 请求(如
/api/todos/999),观察响应状态码是否为404,响应体是否为标准 JSON(如{"message":"Todo with id 999 not found","success":false,"statusCode":404,"status":"Not Found"})。 -
进阶建议:使用
@ResponseStatus注解简化单异常处理,或集成Problem DetailsRFC 7807 标准提升 API 规范性。
遵循以上修正,你的自定义异常将精准捕获、结构化响应,并在 Postman 中清晰可见——这才是 Spring Boot 全局异常处理的正确打开方式。


















