Java Spring Boot 风格的 Web 服务有点新鲜——所以请保持温柔。 为什么大多数 Spring Boot 控制器示例没有显示捕获任何异常?我看到我的一些开发人员同事经常这样做。答案是:这是一个常见的约定。但为什么。我是否遗漏了使用 Spring Boot 创建的 Web 服务的一个关键概念?
例如:
@PostMapping(path = "/sampleEndpoint/v1/myEndpoint", produces = "application/json")
public ResponseEntity<String> myEndpoint(@RequestBody MyEndpointRequest myEndpointRequest) {
MyEndpointResponse response = someService.myEndpoint(myEndpointRequest);
return new ResponseEntity<>(response, HttpStatus.OK);
}
我认为,就架构而言,您至少要添加一个带有一些日志记录的 try/catch 块,并抛出一个带有异常消息的新异常:
@PostMapping(path = "/sampleEndpoint/v1/myEndpoint", produces = "application/json")
public ResponseEntity<String> myEndpoint(@RequestBody MyEndpointRequest myEndpointRequest) {
try{
MyEndpointResponse response = someService.myEndpoint(myEndpointRequest);
return new ResponseEntity<>(response, HttpStatus.OK);
}catch(Exception ex){
//Your favorite logger:
log.error("STACK_TRACE: {}", StaticClass.stackTraceToString(ex));
//throw exception for calling or consuming system/application:
throw new MiscException(ex.getMessage());
}
}
有几件事可以为这个问题提供背景(观察):
由开发人员实现异常捕获机制。但为此定义异常类型和错误代码/消息是一个很好的做法。假设您有一个端点,可以获取具有 id 的产品,但没有具有该 id 的产品,在这种情况下,客户端将收到带有
http 500
消息的 internal server error
代码。这会让用户和开发人员感到困惑,该错误的真正原因是什么。
因此,为了防止这些问题,您可以从
@ControllerAdvice
注释获得帮助,该注释将允许将异常处理程序应用于多个或所有控制器。
首先,您将定义自定义异常,例如:
public class ProductNotFoundException extends RuntimeException {
public ProductNotFoundException(Long id) {
super(String.format("Product with id %d not found", id));
}
}
然后你可以定义你的
ControllerAdvice
类:
@ControllerAdvice
public class ExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(ProductNotFound.class)
public ResponseEntity<Object> handleProductNotFoundException(
ProductNotFoundException ex, WebRequest request) {
Map<String, Object> body = new LinkedHashMap<>();
body.put("timestamp", LocalDateTime.now());
body.put("message", "Product not found");
return new ResponseEntity<>(body, HttpStatus.NOT_FOUND);
}
}
可以有很多方法来实现异常处理,但让我告诉你我的控制器必须清晰并与任何此类约定和异常处理隔离,并且您希望控制器执行的任何逻辑都必须位于处理异常的实现类中您的请求必须执行什么逻辑。
还有, Spring Boot鼓励开发人员将异常处理委托给集中式机制,例如使用带有@ControllerAdvice注释的全局异常处理程序。这有助于维护更清晰、更模块化的代码结构。
不必要的代码重复: 在每个控制器方法中放置 try/catch 块可能会导致代码重复。集中式异常处理允许您跨多个控制器一致地处理异常。
更清晰的代码流程: 关注控制器方法中的主要逻辑可以增强可读性。异常处理可以与核心功能分离,使代码更容易一目了然。
控制器之间的一致性: 遵循集中式异常处理约定可确保应用程序中所有控制器采用一致的方法。
但是,通常建议保持控制器简洁并将异常处理委托给专用组件。这是一个基本示例:
@PostMapping(path = "/sampleEndpoint/v1/myEndpoint", produces = "application/json")
public ResponseEntity<String> myEndpoint(@RequestBody MyEndpointRequest myEndpointRequest) {
try {
MyEndpointResponse response = someService.myEndpoint(myEndpointRequest);
return new ResponseEntity<>(response, HttpStatus.OK);
} catch (Exception e) {
// Log the exception for troubleshooting
log.error("Exception in myEndpoint:", e);
// Customize the response based on the exception if needed
return new ResponseEntity<>("An error occurred.", HttpStatus.INTERNAL_SERVER_ERROR);
}
}