How to get the @RequestBody in an @ExceptionHandler (Spring REST)

前端 未结 3 971
一生所求
一生所求 2020-12-01 14:03

I am using Spring Boot 1.4.1 which includes spring-web-4.3.3. I have a class annotated with @ControllerAdvice and methods annotated with @ExceptionHandle

相关标签:
3条回答
  • 2020-12-01 14:36

    Accepted answer creates a new POJO to pass things around, but the same behaviour can be achieved without creating additional objects by reusing the http request.

    Example code for Controller mapping:

    public ResponseEntity savePerson(@RequestBody Person person, WebRequest webRequest) {
        webRequest.setAttribute("person", person, RequestAttributes.SCOPE_REQUEST);
    

    And later in the ExceptionHandler class / method you can use:

    @ExceptionHandler(Exception.class)
    public ResponseEntity exceptionHandling(WebRequest request,Exception thrown) {
    
        Person person = (Person) request.getAttribute("person", RequestAttributes.SCOPE_REQUEST);
    
    0 讨论(0)
  • 2020-12-01 14:39

    You should be able to get the content of the request body by using the RequestBodyAdvice interface. If you implement this on a class annotated with @ControllerAdvice it should be picked up automatically.

    To get other request information like the HTTP method and query params I'm using an interceptor. I'm capturing all this request info for error reporting in a ThreadLocal variable which I clear on the afterCompletion hook in that same interceptor.

    The class below implements this and can be used in your ExceptionHandler to get all request information:

    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;
        }
    }
    
    0 讨论(0)
  • 2020-12-01 14:41

    You can reference the request body object to a request-scoped bean. And then inject that request-scoped bean in your exception handler to retrieve the request body (or other request-context beans that you wish to reference).

    // @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;
        }
    }
    
    0 讨论(0)
提交回复
热议问题