0

For all my RestControllers handlers, which all return a subtype of MyResponseType, I would like to write a Spring ResponseBodyAdvice which manipulates the response based on some @ModelAttribute. However, I don't know how to access the ModelView at this place.

@ControllerAdvice
public class MyGoldenAdvice implements ResponseBodyAdvice<MyResponseType> {

    @Override
    public boolean supports(final MethodParameter returnType, final Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    @Override
    public RepresentationModel<?> beforeBodyWrite(
        final MyResponseType body,
        final MethodParameter returnType,
        final MediaType selectedContentType,
        final Class<? extends HttpMessageConverter<?>> selectedConverterType,
        final ServerHttpRequest request,
        final ServerHttpResponse response) {
        // here I would like to access the ModelView
        return body;
    }

}

Unfortunately, since I have to implement the interface ResponseBodyAdvice, I cannot have @ModelAttribute arguments.

Of course, I could also use an interceptor. However, there the response is already written, thus I could only manipulate the JSON of my MyResponseType instance.

Any idea?

rainer198
  • 3,195
  • 2
  • 27
  • 42

1 Answers1

1

Although it is not a solution to the concrete question (Access zu @ModelAttributes) and violates the idea behind @ControllerAdvice, you can use @RequestAttributes for the same purpose. For this, the advice needs a dependency to current HttpServletRequest to store and get the attribute (the ServerHttpRequest argument of beforeBodyWrite does not provide request attributes).

public class MyGoldenAdvice implements ResponseBodyAdvice<MyResponseType> {

    public final static String ATTRIBUTE_NAME="MyGoldenAdviceAttribute";

    private final HttpServletRequest currentRequest;

   // spring injects a proxy to the current request
    @Autowired
    public MyGoldenAdvice(HttpServletRequest currentRequest) {
        this.currentRequest = currentRequest;
    }

    // in order that the advice method is triggered, we have to annotate it
    // with @ModelAttribute, although we store the attribute in the request
    @ModelAttribute
    public MyModelAttributeType createModelAttribute() {
        MyModelAttributeType attribute = MyModelAttributeType.build();
        currentRequest.setAttribute(ATTRIBUTE_NAME, attribute);
        return attribute;
    }

    @Override
    public boolean supports(final MethodParameter returnType, final Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

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

        // get the request attribute
        MyModelAttributeType attribute = (MyModelAttributeType) currentRequest.getAttribute(ATTRIBUTE_NAME);

        // do something with the body

        return body;
    }
}

In some REST controller, you can use the request attribute as follows

@GetMapping
public handleGetRequest(@RequestAttribute(name=MyGoldenAdvice.ATTRIBUTE_NAME) 
    MyModelAttributeType attribute) {
    // pass attribute around in services and so on...
}
rainer198
  • 3,195
  • 2
  • 27
  • 42