18

I have several controllers that return the same generic Response object with @ResponseBody annotation, like this:

@RequestMapping(value = "/status", method = RequestMethod.GET)
    @Transactional(readOnly = true)
    public @ResponseBody Response<StatusVM> status()

I need to perform an operation on every controller, after the Response is returned. This operation will enrich the Response object with new data.

I don't want to duplicate code, so I need a single point of intervention. I thought I could do this with Interceptors, however, according to the docs http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-handlermapping-interceptor this does not work well with @ResponseBody:

Note that the postHandle method of HandlerInterceptor is not always ideally suited for use with @ResponseBody and ResponseEntity methods. In such cases an HttpMessageConverter writes to and commits the response before postHandle is called which makes it impossible to change the response, for example to add a header. Instead an application can implement ResponseBodyAdvice and either declare it as an @ControllerAdvice bean or configure it directly on RequestMappingHandlerAdapter.

I haven't been able to find an example of this tecnique, could anybody help me?

As an alternative I could work with aspects, but then I'd need to annotate every controller, which is something I'd like to avoid.

LittleSquinky
  • 569
  • 1
  • 5
  • 14

1 Answers1

31

In the end I implemented ResponseBodyAdvice like this:

@ControllerAdvice
public class StatusAdvice implements ResponseBodyAdvice<Response<?>> {


    @Override
    public boolean supports(MethodParameter returnType,
            Class<? extends HttpMessageConverter<?>> converterType) {

        if (returnTypeIsReponseVM(returnType)&&responseConverterIsJackson2(converterType)){
            return true;
        }

        return false;
    }

....

    @Override
    public Response<?> beforeBodyWrite(Response<?> body, MethodParameter returnType,
            MediaType selectedContentType,
            Class<? extends HttpMessageConverter<?>> selectedConverterType,
            ServerHttpRequest request, ServerHttpResponse response) {

        ....

        return body;
    }

}

So it was easier then expected.

LittleSquinky
  • 569
  • 1
  • 5
  • 14
  • 1
    Sweet! Just making this `ResponseBodyAdvice` and `if (body == null) { response.setStatus(HttpStatus.NO_CONTENT); }` seems to work. Can you think of any reason why this won't work? (ie: in the case of BAD_REQUEST, or INTERNAL_SERVER_ERROR, or anything like that?) Unfortunately, there is no `response.getStatus()` so I can't also check for status of OK before setting it to NO_CONTENT. :( – Shadow Man Sep 10 '18 at 23:29
  • 2
    @ShadowMan I haven't got the relevant code anymore, however was able to retrieve something similar from another project. The response body should be null when the status is not OK. In this context the response **should** always be an instance of ServletServerHttpResponse, so if you have to get the response status, something like this should work: `if (response instanceof ServletServerHttpResponse) { ((ServletServerHttpResponse)response).getServletResponse().getStatus(); }` – LittleSquinky Sep 11 '18 at 16:28