Unc*_*air 25 spring servlets exception-handling spring-boot
我使用的是Spring Boot 1.4.1,其中包括spring-web-4.3.3.我有一个带有@ControllerAdvice注释的类和带有注释的方法@ExceptionHandler来处理服务代码抛出的异常.在处理这些异常时,我想记录@RequestBody那是PUT和POST操作请求的一部分,这样我就可以看到导致问题的请求体,在我的情况下,这对于诊断至关重要.
Per Spring Docs方法的方法签名@ExceptionHandler可以包括各种各样的东西,包括HttpServletRequest.请求体通常可以从这里通过getInputStream()或获得getReader(),但是如果我的控制器方法解析请求体,就像"@RequestBody Foo fooBody"我所做的一样,HttpServletRequest's输入流或读取器在调用异常处理程序方法时已经关闭.本质上,Spring已经读取了请求主体,类似于此处描述的问题.使用servlet是一个常见问题,请求体只能读取一次.
不幸的@RequestBody是,不是异常处理程序方法可用的选项之一,如果是那样的话,我可以使用它.
我可以添加一个InputStream异常处理程序方法,但最终与HttpServletRequest的InputStream相同,因此具有相同的问题.
我也尝试获取当前请求,((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest()这是获取当前请求的另一个技巧,但这最终是Spring传递到异常处理程序方法的相同HttpServletRequest,因此也有同样的问题.
我已经阅读了一些像这样的解决方案,这涉及在过滤器链中插入一个自定义请求包装器,它将读取请求的内容并缓存它们,以便可以多次读取它们.我不喜欢这个解决方案,因为我不想中断整个过滤器/请求/响应链(并可能引入性能或稳定性问题)只是为了实现日志记录,如果我有任何大的请求,如上传的文件(我这样做,我不想在内存中缓存它.此外,@RequestBody如果我只能找到它,Spring可能已经在某处缓存了.
顺便提一下,许多解决方案建议使用ContentCachingRequestWrapperSpring类,但根据我的经验,这不起作用.除了没有记录之外,查看其源代码看起来它只是缓存参数,而不是请求体.尝试从此类获取请求正文总是会产生一个空字符串.
所以我正在寻找我可能错过的任何其他选择.谢谢阅读.
您可以将请求正文对象引用到请求范围的 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;
}
}
Run Code Online (Sandbox Code Playgroud)
接受的答案会创建一个新的 POJO 来传递信息,但通过重用 http 请求,无需创建其他对象即可实现相同的行为。
控制器映射的示例代码:
public ResponseEntity savePerson(@RequestBody Person person, WebRequest webRequest) {
webRequest.setAttribute("person", person, RequestAttributes.SCOPE_REQUEST);
Run Code Online (Sandbox Code Playgroud)
稍后在 ExceptionHandler 类/方法中,您可以使用:
@ExceptionHandler(Exception.class)
public ResponseEntity exceptionHandling(WebRequest request,Exception thrown) {
Person person = (Person) request.getAttribute("person", RequestAttributes.SCOPE_REQUEST);
Run Code Online (Sandbox Code Playgroud)
您应该能够使用RequestBodyAdvice接口获取请求正文的内容。如果您在使用@ControllerAdvice注释的类上实现此功能,则应该会自动选取它。
为了获取其他请求信息(例如 HTTP 方法和查询参数),我使用了拦截器。我正在捕获所有这些请求信息,以在ThreadLocal变量中进行错误报告,并在同一拦截器中的afterCompletion挂钩上清除该变量。
下面的类实现了这一点,并且可以在 ExceptionHandler 中使用它来获取所有请求信息:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
@ControllerAdvice
public class RequestInfo extends HandlerInterceptorAdapter implements RequestBodyAdvice {
private static final Logger logger = LoggerFactory.getLogger(RequestInfo.class);
private static final ThreadLocal<RequestInfo> requestInfoThreadLocal = new ThreadLocal<>();
private String method;
private String body;
private String queryString;
private String ip;
private String user;
private String referrer;
private String url;
public static RequestInfo get() {
RequestInfo requestInfo = requestInfoThreadLocal.get();
if (requestInfo == null) {
requestInfo = new RequestInfo();
requestInfoThreadLocal.set(requestInfo);
}
return requestInfo;
}
public Map<String,String> asMap() {
Map<String,String> map = new HashMap<>();
map.put("method", this.method);
map.put("url", this.url);
map.put("queryParams", this.queryString);
map.put("body", this.body);
map.put("ip", this.ip);
map.put("referrer", this.referrer);
map.put("user", this.user);
return map;
}
private void setInfoFromRequest(HttpServletRequest request) {
this.method = request.getMethod();
this.queryString = request.getQueryString();
this.ip = request.getRemoteAddr();
this.referrer = request.getRemoteHost();
this.url = request.getRequestURI();
if (request.getUserPrincipal() != null) {
this.user = request.getUserPrincipal().getName();
}
}
public void setBody(String body) {
this.body = body;
}
private static void setInfoFrom(HttpServletRequest request) {
RequestInfo requestInfo = requestInfoThreadLocal.get();
if (requestInfo == null) {
requestInfo = new RequestInfo();
}
requestInfo.setInfoFromRequest(request);
requestInfoThreadLocal.set(requestInfo);
}
private static void clear() {
requestInfoThreadLocal.remove();
}
private static void setBodyInThreadLocal(String body) {
RequestInfo requestInfo = get();
requestInfo.setBody(body);
setRequestInfo(requestInfo);
}
private static void setRequestInfo(RequestInfo requestInfo) {
requestInfoThreadLocal.set(requestInfo);
}
// Implementation of HandlerInterceptorAdapter to capture the request info (except body) and be able to add it to the report in case of an error
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
RequestInfo.setInfoFrom(request);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception) {
RequestInfo.clear();
}
// Implementation of RequestBodyAdvice to capture the request body and be able to add it to the report in case of an error
@Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return inputMessage;
}
@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
RequestInfo.setBodyInThreadLocal(body.toString());
return body;
}
@Override
public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
7172 次 |
| 最近记录: |