[英]How to get the @RequestBody in an @ExceptionHandler (Spring REST) 2.0
[英]How to get the @RequestBody in an @ExceptionHandler (Spring REST)
我正在使用 Spring Boot 1.4.1,其中包括 spring-web-4.3.3。 我有一個用@ControllerAdvice
注釋的 class 和用@ExceptionHandler
注釋的方法來處理服務代碼拋出的異常。 在處理這些異常時,我想記錄作為 PUT 和 POST 操作請求一部分的@RequestBody
,這樣我就可以看到導致問題的請求正文,這在我的案例中對於診斷至關重要。
根據Spring 文檔, @ExceptionHandler
方法的方法簽名可以包括各種內容,包括HttpServletRequest
。 請求主體通常可以通過getInputStream()
或getReader()
從此處獲取,但如果我的 controller 方法像我的所有方法一樣解析請求主體,如"@RequestBody Foo fooBody"
,則HttpServletRequest's
輸入 stream 或閱讀器已被關閉我的異常處理程序方法被調用的時間。 本質上,請求正文已被 Spring 讀取,類似於此處描述的問題。 使用 servlets 時的一個常見問題是請求正文只能讀取一次。
不幸的是, @RequestBody
不是異常處理程序方法可用的選項之一,如果是的話我可以使用它。
我可以將InputStream
添加到異常處理程序方法中,但這最終與 HttpServletRequest 的 InputStream 相同,因此存在相同的問題。
我還嘗試使用((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest()
獲取當前請求,這是獲取當前請求的另一個技巧,但這最終與 Spring 傳遞給異常處理程序方法的 HttpServletRequest 相同,因此有同樣的問題。
我已經閱讀了一些類似這樣的解決方案, 這些解決方案涉及在過濾器鏈中插入自定義請求包裝器,該包裝器將讀取請求的內容並緩存它們,以便可以多次讀取它們。 我不喜歡這個解決方案,因為我不想中斷整個過濾器/請求/響應鏈(並可能引入性能或穩定性問題)只是為了實現日志記錄,如果我有任何大型請求,例如上傳的文檔(其中我願意),我不想在 memory 中緩存它。此外,如果我只能找到它,Spring 可能已經將@RequestBody
緩存在某個地方。
順便說一下,許多解決方案建議使用ContentCachingRequestWrapper
Spring class 但根據我的經驗,這是行不通的。 除了沒有記錄外,查看它的源代碼看起來它只緩存參數,而不緩存請求體。 嘗試從此 class 獲取請求正文始終會導致空字符串。
所以我正在尋找我可能錯過的任何其他選項。 謝謝閱讀。
您可以將請求主體對象引用到請求范圍的 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;
}
}
接受的答案會創建一個新的 POJO 來傳遞東西,但是可以通過重用 http 請求來實現相同的行為,而無需創建其他對象。
控制器映射的示例代碼:
public ResponseEntity savePerson(@RequestBody Person person, WebRequest webRequest) {
webRequest.setAttribute("person", person, RequestAttributes.SCOPE_REQUEST);
稍后在 ExceptionHandler 類/方法中,您可以使用:
@ExceptionHandler(Exception.class)
public ResponseEntity exceptionHandling(WebRequest request,Exception thrown) {
Person person = (Person) request.getAttribute("person", RequestAttributes.SCOPE_REQUEST);
您應該能夠使用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;
}
}
只是對quintencls 答案的增強
我得到了請求正文,可以在異常處理程序 class 中的任何地方使用它。
@ControllerAdvice
public class CustomErrorHandler extends ResponseEntityExceptionHandler implements RequestBodyAdvice {
...
private Object reqBody;
...
@ExceptionHandler(NoSuchElementException.class)
public ResponseEntity<Object> handleNoSuchElementException(final NoSuchElementException ex,
final WebRequest request) {
System.out.println("===================================" + reqBody);
return handleNotFoundException(ex, request);
}
...
@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) throws IOException {
return inputMessage;
}
@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
// capture request body here to use in our controller advice class
this.reqBody = body;
return body;
}
@Override
public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}
}
請參閱此處: https ://stackoverflow.com/a/61813076/1036433 - 獲取訪問 HttpServerRequest 的干凈方法。
這是我用於某些字段驗證控制的 Kotlin 語法解決方案。
我需要從@RestControllerAdvice
增強默認的handleMethodArgumentNotValid(...)
方法,以系統地記錄嵌入在同一請求正文對象中的字段。
override fun handleMethodArgumentNotValid(e: MethodArgumentNotValidException, headers: HttpHeaders, status: HttpStatus, request: WebRequest): ResponseEntity<Any> {
val error = e.bindingResult.fieldErrors.first()
val requestBody = try {
val field = error.javaClass.getDeclaredField("violation").apply { trySetAccessible() }
((field[error] as ConstraintViolationImpl<Any>).rootBean as MyRequestBodyObject)
} catch (ex : Exception) {
//do some failsafe here
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.