HandlerInterceptorAdapter, JSON encoding and post

2019-04-12 06:52发布

问题:

The previous design

I had a Spring controller built like follows:

@RequestMapping(value = "/rest/funf/{datatype}", method = RequestMethod.GET)
public ModelAndView getFunfData(@PathVariable("datatype") String dataType,
        WebRequest request) throws HttpException {

I also had a HandlerInterceptorAdapter that performed pre and post-processing (modifying the returned model according to a policy). The signature for the post-process handler is the following

@Override
public void postHandle(HttpServletRequest request,
        HttpServletResponse response, Object handler,
        ModelAndView modelAndView) throws Exception {

It actually worked well. The REST API returned a not-well-structured JSON payload but at least the interceptor (a Policy Enforcement Point) was able to anonymise (strip user personal data) from the model and replaced it.

The point is that with the old design I was able to retrieve response before it was sent to the output, modify and reinject it into the ModelAndView instance. No need to post code for brevity.

The new design

Now I discovered a new design that helps me overcome a flaw in the response structure.

When I had return new ModelAndView(jacksonView_instance, "data", dataToConvertToJson) it was structured like

{
    "data": {
        "myAttr1":"myVal1"
     }
}

which is bad when the receiving program wants to convert the payload to JSON. So I found

@RequestMapping(value = "/rest/funf/{datatype}", method = RequestMethod.GET, produces = { "application/json" })
    public @ResponseBody
    Object[] getFunfData(@PathVariable("datatype") String dataType,
            WebRequest request) throws HttpException {

This simplified code a little and most importantly made the response well structured, like

{
    "myAttr1":"myVal1"
}

That is perfectly OK with REST design contract, but

The problem

Now the HandlerInterceptor won't be able any more to retrieve the model and thus the object I return from my API (it's a FUNF-related API that handles personal data, even sensitive, recorded on mobile devices).

So if I need to anonymise records I can't do it in the interceptor like before

The workaround

I am conscious I can kill the interceptor and perform policy enforcement right in the API, but this is not a great design, even since the PEP that performs authorization in the preHandle and obligation enforcement in the postHandle is currently a Spring object automagically instantiated. Copying and pasting code in the API is a bad idea when dealing with multiple APIs and the possibility in the middle-term to change/extend the PEP implementation.

Anyway, let's formulate a straight question after having explained the context well

The question is

Given a generic Spring MVC API that returns an object to be handled by a content negotiator

@RequestMapping(value = "/path/{variable}", method = RequestMethod.GET)
public @ResponseBody Object api() {

how can I intercept that returned object in an external class to perform modifications to it? (e.g. replace with a new object instance to be returned to client)

回答1:

Looks like this is not possible in Spring MVC. I have just seen the code in a debug session.

When you use ModelAndView, Spring MVC stores the MaV in a variable that lives till postHandle and then is written in response body.

Instead, when you use @ResponseBody and return value that value is handled directly in the invocation cycle, using a MessageConverter to be written to the response.

Since the response is a one-way network stream, it's too late for intercepting it.

The solution should be based on a different approach than the handlers, like AOP interceptors applied directly to controller methods.