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
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);
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;
}
}
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;
}
}