小编典典

Spring Boot REST 服务异常处理

all

我正在尝试建立一个大型 REST 服务服务器。我们使用的是 Spring Boot 1.2.1 Spring 4.1.5 和 Java
8。我们的控制器正在实现 @RestController 和标准的 @RequestMapping 注解。

我的问题是 Spring Boot 为控制器异常设置了默认重定向到/error. 从文档:

Spring Boot 默认提供了一个 /error 映射,以一种合理的方式处理所有错误,并在 servlet 容器中注册为“全局”错误页面。

多年来使用 Node.js 编写 REST
应用程序,对我来说,这绝不是明智之举。服务端点生成的任何异常都应在响应中返回。我不明白您为什么要将重定向发送给最有可能只是寻找答案并且不能或不会对重定向采取任何操作的
Angular 或 JQuery SPA 消费者。

我想要做的是设置一个可以接受任何异常的全局错误处理程序 - 有目的地从请求映射方法抛出或由 Spring
自动生成(如果没有找到请求路径签名的处理程序方法,则为 404),并返回一个没有任何 MVC
重定向的客户端的标准格式错误响应(400、500、503、404)。具体来说,我们将获取错误,使用 UUID 将其记录到 NoSQL,然后使用 JSON
正文中日志条目的 UUID 将正确的 HTTP 错误代码返回给客户端。

文档对如何执行此操作含糊不清。在我看来,您必须要么创建自己的ErrorController实现,要么以某种方式使用ControllerAdvice,但我看到的所有示例仍然包括将响应转发到某种错误映射,这无济于事。其他示例表明您必须列出要处理的每种异常类型,而不是仅列出“可抛出”并获取所有内容。

谁能告诉我我错过了什么,或者在不建议 Node.js 更容易处理的链条的情况下为我指出如何做到这一点的正确方向?


阅读 153

收藏
2022-07-12

共1个答案

小编典典

新答案 (2016-04-20)

使用 Spring Boot 1.3.1.RELEASE

新的第 1 步 - 将以下属性添加到 application.properties 很容易且侵入性较小:

spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false

比修改现有的 DispatcherServlet 实例(如下)容易得多!- 乔’

如果使用完整的 RESTful 应用程序,禁用静态资源的自动映射非常重要,因为如果您使用 Spring Boot
的默认配置来处理静态资源,那么资源处理程序将处理请求(它最后排序并映射到 / **
这意味着它会拾取应用程序中任何其他处理程序未处理的任何请求),因此调度程序 servlet 没有机会抛出异常。


新答案 (2015-12-04)

使用 Spring Boot 1.2.7.RELEASE

新的第 1 步 -
我发现了一种设置“throExceptionIfNoHandlerFound”标志的侵入性要小得多的方法。在应用程序初始化类中将下面的
DispatcherServlet 替换代码(步骤 1)替换为:

@ComponentScan()
@EnableAutoConfiguration
public class MyApplication extends SpringBootServletInitializer {
    private static Logger LOG = LoggerFactory.getLogger(MyApplication.class);
    public static void main(String[] args) {
        ApplicationContext ctx = SpringApplication.run(MyApplication.class, args);
        DispatcherServlet dispatcherServlet = (DispatcherServlet)ctx.getBean("dispatcherServlet");
        dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
    }

在这种情况下,我们在现有的 DispatcherServlet 上设置标志,它保留了 Spring Boot 框架的任何自动配置。

我发现的另一件事 - @EnableWebMvc 注释对 Spring Boot
来说是致命的。是的,该注解可以像下面描述的那样捕获所有控制器异常,但它也扼杀了 Spring Boot 通常会提供的许多有用的自动配置。使用 Spring
Boot 时要格外小心地使用该注释。


原答案:

经过大量研究和跟进此处发布的解决方案(感谢您的帮助!)以及对 Spring
代码的大量运行时跟踪,我终于找到了一个可以处理所有异常的配置(不是错误,但请继续阅读)包括404。

第 1 步 - 告诉 SpringBoot 在“找不到处理程序”的情况下停止使用 MVC。我们希望 Spring
抛出异常,而不是向客户端返回重定向到“/error”的视图。为此,您需要在您的配置类之一中有一个条目:

// NEW CODE ABOVE REPLACES THIS! (2015-12-04)
@Configuration
public class MyAppConfig {
    @Bean  // Magic entry 
    public DispatcherServlet dispatcherServlet() {
        DispatcherServlet ds = new DispatcherServlet();
        ds.setThrowExceptionIfNoHandlerFound(true);
        return ds;
    }
}

这样做的缺点是它替换了默认的调度程序 servlet。这对我们来说还不是问题,没有出现副作用或执行问题。如果您出于其他原因要对调度程序 servlet
执行任何其他操作,那么这里就是执行这些操作的地方。

第 2 步 - 现在 spring boot 将在找不到处理程序时抛出异常,该异常可以与统一异常处理程序中的任何其他异常一起处理:

@EnableWebMvc
@ControllerAdvice
public class ServiceExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(Throwable.class)
    @ResponseBody
    ResponseEntity<Object> handleControllerException(HttpServletRequest req, Throwable ex) {
        ErrorResponse errorResponse = new ErrorResponse(ex);
        if(ex instanceof ServiceException) {
            errorResponse.setDetails(((ServiceException)ex).getDetails());
        }
        if(ex instanceof ServiceHttpException) {
            return new ResponseEntity<Object>(errorResponse,((ServiceHttpException)ex).getStatus());
        } else {
            return new ResponseEntity<Object>(errorResponse,HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    @Override
    protected ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        Map<String,String> responseBody = new HashMap<>();
        responseBody.put("path",request.getContextPath());
        responseBody.put("message","The URL you have reached is not in service at this time (404).");
        return new ResponseEntity<Object>(responseBody,HttpStatus.NOT_FOUND);
    }
    ...
}

请记住,我认为“@EnableWebMvc”注释在这里很重要。似乎没有它,这一切都行不通。就是这样 - 您的 Spring boot
应用程序现在将捕获上述处理程序类中的所有异常,包括 404,您可以随意处理它们。

最后一点 -
似乎没有办法让它捕捉抛出的错误。我有一个古怪的想法,即使用方面来捕获错误并将它们转换为上面的代码可以处理的异常,但我还没有时间实际尝试实现它。希望这可以帮助某人。

任何评论/更正/增强将不胜感激。

2022-07-12