Spring Boot REST 服务异常处理 [英] Spring Boot REST service exception handling

查看:71
本文介绍了Spring Boot REST 服务异常处理的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试设置大型 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),并向客户端返回标准格式的错误响应(400、500、503、404),无需任何 MVC 重定向.具体来说,我们将获取错误,使用 UUID 将其记录到 NoSQL,然后将正确的 HTTP 错误代码与 JSON 正文中日志条目的 UUID 返回给客户端.

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

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

解决方案

新答案 (2016-04-20)

使用 Spring Boot 1.3.1.RELEASE

新的第 1 步 - 将以下属性添加到 application.properties 中既简单又不易侵入:

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

<块引用>

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

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

<小时>

新答案 (2015-12-04)

使用 Spring Boot 1.2.7.RELEASE

新的第 1 步 - 我发现设置throExceptionIfNoHandlerFound"标志的方式要少得多.在您的应用程序初始化类中将下面的 DispatcherServlet 替换代码(步骤 1)替换为:

@ComponentScan()@EnableAutoConfiguration公共类 MyApplication 扩展 SpringBootServletInitializer {私有静态记录器日志 = LoggerFactory.getLogger(MyApplication.class);公共静态无效主(字符串 [] 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"返回给客户端.为此,您需要在其中一个配置类中有一个条目:

//上面的新代码取代了这个!(2015-12-04)@配置公共类 MyAppConfig {@Bean//魔法入口公共 DispatcherServlet dispatcherServlet() {DispatcherServlet ds = new DispatcherServlet();ds.setThrowExceptionIfNoHandlerFound(true);返回 ds;}}

这样做的缺点是它取代了默认的调度程序 servlet.这对我们来说还不是问题,没有出现副作用或执行问题.如果您出于其他原因要对调度程序 servlet 执行任何其他操作,则可以在此处执行操作.

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

@EnableWebMvc@ControllerAdvice公共类 ServiceExceptionHandler 扩展 ResponseEntityExceptionHandler {@ExceptionHandler(Throwable.class)@ResponseBodyResponseEntity<对象>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(errorResponse,((ServiceHttpException)ex).getStatus());} 别的 {返回新的 ResponseEntity(errorResponse,HttpStatus.INTERNAL_SERVER_ERROR);}}@覆盖受保护的 ResponseEntityhandleNoHandlerFoundException(NoHandlerFoundException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {映射<字符串,字符串>responseBody = new HashMap<>();responseBody.put("path",request.getContextPath());responseBody.put("message","您访问的 URL 暂时不在服务中 (404).");返回新的 ResponseEntity(responseBody,HttpStatus.NOT_FOUND);}...}

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

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

任何评论/更正/改进将不胜感激.

I am trying to set up a large-scale REST services server. We're using Spring Boot 1.2.1 Spring 4.1.5, and Java 8. Our controllers are implementing @RestController and the standard @RequestMapping annotations.

My problem is that Spring Boot sets up a default redirect for controller exceptions to /error. From the docs:

Spring Boot provides an /error mapping by default that handles all errors in a sensible way, and it is registered as a ‘global’ error page in the servlet container.

Coming from years writing REST applications with Node.js, this is, to me, anything but sensible. Any exception a service endpoint generates should return in the response. I can't understand why you'd send a redirect to what is most likely an Angular or JQuery SPA consumer which is only looking for an answer and can't or won't take any action on a redirect.

What I want to do is set up a global error handler that can take any exception - either purposefully thrown from a request mapping method or auto-generated by Spring (404 if no handler method is found for the request path signature), and return a standard formatted error response (400, 500, 503, 404) to the client without any MVC redirects. Specifically, we are going to take the error, log it to NoSQL with a UUID, then return to the client the right HTTP error code with the UUID of the log entry in the JSON body.

The docs have been vague on how to do this. It seems to me that you have to either create your own ErrorController implementation or use ControllerAdvice in some fashion, but all the examples I've seen still include forwarding the response to some kind of error mapping, which doesn't help. Other examples suggest that you'd have to list every Exception type you want to handle instead of just listing "Throwable" and getting everything.

Can anyone tell me what I missed, or point me in the right direction on how to do this without suggesting up the chain that Node.js would be easier to deal with?

解决方案

New answer (2016-04-20)

Using Spring Boot 1.3.1.RELEASE

New Step 1 - It is easy and less intrusive to add the following properties to the application.properties:

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

Much easier than modifying the existing DispatcherServlet instance (as below)! - JO'

If working with a full RESTful Application, it is very important to disable the automatic mapping of static resources since if you are using Spring Boot's default configuration for handling static resources then the resource handler will be handling the request (it's ordered last and mapped to /** which means that it picks up any requests that haven't been handled by any other handler in the application) so the dispatcher servlet doesn't get a chance to throw an exception.


New Answer (2015-12-04)

Using Spring Boot 1.2.7.RELEASE

New Step 1 - I found a much less intrusive way of setting the "throExceptionIfNoHandlerFound" flag. Replace the DispatcherServlet replacement code below (Step 1) with this in your application initialization class:

@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);
    }

In this case, we're setting the flag on the existing DispatcherServlet, which preserves any auto-configuration by the Spring Boot framework.

One more thing I've found - the @EnableWebMvc annotation is deadly to Spring Boot. Yes, that annotation enables things like being able to catch all the controller exceptions as described below, but it also kills a LOT of the helpful auto-configuration that Spring Boot would normally provide. Use that annotation with extreme caution when you use Spring Boot.


Original Answer:

After a lot more research and following up on the solutions posted here (thanks for the help!) and no small amount of runtime tracing into the Spring code, I finally found a configuration that will handle all Exceptions (not Errors, but read on) including 404s.

Step 1 - tell SpringBoot to stop using MVC for "handler not found" situations. We want Spring to throw an exception instead of returning to the client a view redirect to "/error". To do this, you need to have an entry in one of your configuration classes:

// 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;
    }
}

The downside of this is that it replaces the default dispatcher servlet. This hasn't been a problem for us yet, with no side effects or execution problems showing up. If you're going to do anything else with the dispatcher servlet for other reasons, this is the place to do them.

Step 2 - Now that spring boot will throw an exception when no handler is found, that exception can be handled with any others in a unified exception handler:

@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);
    }
    ...
}

Keep in mind that I think the "@EnableWebMvc" annotation is significant here. It seems that none of this works without it. And that's it - your Spring boot app will now catch all exceptions, including 404s, in the above handler class and you may do with them as you please.

One last point - there doesn't seem to be a way to get this to catch thrown Errors. I have a wacky idea of using aspects to catch errors and turn them into Exceptions that the above code can then deal with, but I have not yet had time to actually try implementing that. Hope this helps someone.

Any comments/corrections/enhancements will be appreciated.

这篇关于Spring Boot REST 服务异常处理的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆