2017-04-19 372 views
11

我使用Spring Boot 1.4.1,其中包含spring-web-4.3.3。我有一个注释类为@ControllerAdvice的类和使用@ExceptionHandler注解的方法来处理服务代码抛出的异常。在处理这些异常时,我想记录作为PUT和POST操作请求一部分的@RequestBody,以便我可以看到导致问题的请求主体,这在我的情况下对诊断至关重要。如何在@ExceptionHandler(Spring REST)中获取@RequestBody

Spring Docs方法签名@ExceptionHandler方法可以包括各种东西,包括HttpServletRequest。请求正文通常可以通过getInputStream()getReader()从这里获得,但是如果我的控制器方法像我所有的那样解析请求体"@RequestBody Foo fooBody",那么HttpServletRequest's输入流或读取器在调用异常处理程序方法时已经关闭。本质上,Spring的请求体已经被读取,类似于here所描述的问题。使用servlet是一个常见的问题,请求主体只能被读取一次。

不幸的是@RequestBody不是可用于异常处理程序方法的选项之一,如果那时我可以使用它。

我可以将InputStream添加到异常处理程序方法,但最终与HttpServletRequest的InputStream相同,因此具有相同的问题。

我也试图让与((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest()当前请求是获取当前的请求的另一个伎俩,但这最终是相同的HttpServletRequest了Spring进入到异常处理方法等有同样的问题。

我已阅读了几个解决方案,如thisthis,它们涉及在筛选器链中插入一个自定义请求包装器,它将读取请求的内容并缓存它们,以便它们可以多次读取。我不喜欢这个解决方案,因为我不想中断整个过滤器/请求/响应链(并且可能引入性能或稳定性问题)来实现日志记录,并且如果我有任何大的请求,例如上传的文档我这样做),我不想将它缓存在内存中。此外,如果我只能找到它,Spring可能已经在某处缓存了@RequestBody

顺便提一下,许多解决方案建议使用ContentCachingRequestWrapper春季班,但根据我的经验,这是行不通的。除了未被记录,查看它的源代码,它看起来像只缓存参数,而不是请求体。试图从这个类中获取请求体总是会产生一个空字符串。

所以我正在寻找任何其他选项,我可能错过了。谢谢阅读。

+0

没有快速内置的解决方案来做你想做的事情。 Spring不会在任何地方缓存请求主体。你可以创建你自己的'HandlerMapping'和'HttpServletRequestWrapper'。 –

回答

2

您可以将请求正文对象引用到请求范围的bean。然后在异常处理程序中注入请求范围的bean以检索请求主体(或其他您希望引用的请求上下文bean)。

// @Component 
// @Scope("request") 
@ManagedBean 
@RequestScope 
public class RequestContext { 
    // fields, getters, and setters for request-scoped beans 
} 

@RestController 
@RequestMapping("/api/v1/persons") 
public class PersonController { 

    @Inject 
    private RequestContext requestContext; 

    @Inject 
    private PersonService personService; 

    @PostMapping 
    public Person savePerson(@RequestBody Person person) throws PersonServiceException { 
     requestContext.setRequestBody(person); 
     return personService.save(person); 
    } 

} 

@ControllerAdvice 
public class ExceptionMapper { 

    @Inject 
    private RequestContext requestContext; 

    @ExceptionHandler(PersonServiceException.class) 
    protected ResponseEntity<?> onPersonServiceException(PersonServiceException exception) { 
     Object requestBody = requestContext.getRequestBody(); 
     // ... 
     return responseEntity; 
    } 
} 
+1

是的,谢谢你,我终于开始验证这一点。它是“@RequestScope”,而不是“@RequestScoped”,我必须添加一些东西给我的所有POST方法,但这是我想要的,谢谢你的答案。 –

+0

为了使它更容易组织,您可以添加一个拦截器/建议来将请求范围的bean捕获到您希望的所有端点。 –