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

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

问题描述

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



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


默认情况下,Spring Boot提供/错误映射,以合理的方式处理所有错误,并将其注册为servlet容器中的全局错误页面。


从使用Node.js编写REST应用程序的年代开始,对我来说,除了明智之外,服务端点生成的任何异常都应在响应中返回。我不明白为什么你会发送一个重定向到最可能是一个Angular或JQuery SPA消费者只是寻找一个答案,不能也不会采取任何行动的重定向。



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



文档对于如何执行此操作一直模糊不清。在我看来,你必须创建自己的ErrorController 实现或使用 ControllerAdvice ,但我看到的所有示例仍然包括将响应转发到某种错误映射,这没有帮助。其他例子表明,你必须列出你要处理的每个异常类型,而不是列出Throwable并获得一切。



任何人都可以告诉我我错过了什么,或者指出我在如何做到这一点的正确方向,而不建议Node.js将更容易处理的链条?

解决方案

附录(4/20/16)



匿名用户建议编辑并被拒绝,但他的建议看起来很好,有帮助,所以我添加它:



使用Spring Boot 1.3.1 .RELEASE



新建步骤1 - 将以下属性添加到application.properties中是非常简单而且不太简单的:

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




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


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






新的回答(12/04 / 15)



使用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代码,我终于找到了一个配置这将处理所有例外(不是错误,但阅读),包括404s。



步骤1 - 告诉SpringBoot停止使用MVC处理程序未找到情况。我们希望Spring抛出一个异常,而不是返回给客户端一个视图重定向到/ error。要执行此操作,您需要在其中一个配置类中输入条目:

  //新的代码上面替换了这个! (12/04/15)
@Configuration
public class MyAppConfig {
@Bean // Magic entry
public DispatcherServlet dispatcherServlet(){
DispatcherServlet ds = new DispatcherServlet ();
ds.setThrowExceptionIfNoHandlerFound(true);
return ds;
}
}

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



步骤2 - 现在,当没有找到处理程序时,spring引导将抛出异常,该异常可以在统一的异常处理程序中与任何其他异常处理程序一起处理:

  @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,您所达到的URL目前尚未使用(404));
返回新的ResponseEntity< Object>(responseBody,HttpStatus.NOT_FOUND);
}
...
}

请记住我认为@EnableWebMvc注释在这里很重要。似乎没有一个没有它。就是这样 - 你的Spring引导程序现在可以在上面的处理程序类中捕获所有的例外,包括404s,你可以按照他们的方式来处理。



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



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


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 see 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?

解决方案

Addendum (4/20/16)

An anonymous user suggested an edit and got rejected, but his suggestion looks good and helpful so I'm adding it:

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 (12/04/15)

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! (12/04/15)
@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启动REST服务异常处理的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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