Filtering JSON feeds from Spring 3's REST support using addMixInAnnotations

来源:互联网 发布:上市公司毛利率算法 编辑:程序博客网 时间:2024/06/05 14:42
Spring3 allows you to output JSON payloads quite easily with its REST support. It uses Jackson (an advanced, fast API that does JSON serialization essentially converts Java object to JSON).

The problem is that depending on which REST request we get, we may want to show different parts of a particular object.

We don't want 10 different subclasses of the same object. Jackson provides a way to filter objects based on addMixInAnnotations (read previous post for more detail).

We would like to easily vary what gets returned on a request method handler by request handler basis.

[See earlier discussion on filtering JSON serialization on a use case by use case basis which was in the post just before this one]

A request handler in Spring3 that returns a JSON payload looks like this:

@RequestMapping(value = "/{locale}/apps/{publicSlug}/", method = RequestMethod.GET)
public @ResponseBody ApplicationEntry getAppDetail(@PathVariable String publicSlug, @PathVariable String locale, 
HttpServletResponse response, HttpServletRequest request) throws Exception {
                 ...
return ApplicationEntry.findApplicationEntry(publicSlug, country, language);
}


We would like to do this to apply a JSON filter.

Define an interface (which is the filter) as follows:

@JsonIgnoreProperties( { "country",
})
@JsonPropertyOrder(value={"title", "id", "version",  "price", "summary"})
public interface AppDetailFilter {

}

Then use the annotation to filter the response as follows:


@JsonFilter(mixin=AppDetailFilter.class)
@RequestMapping(value = "/{locale}/apps/{publicSlug}/", method = RequestMethod.GET)
public @ResponseBody ApplicationEntry getAppDetail(@PathVariable String publicSlug, @PathVariable String locale, 
HttpServletResponse response, HttpServletRequest request) throws Exception {
                 ...
return ApplicationEntry.findApplicationEntry(publicSlug, country, language);
}



Ok so we added this capability.

This is what the annotation looks like:

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;


@Retention(RetentionPolicy.RUNTIME)
public @interface JsonFilter {
Class<?> mixin();
}


Then we just write the glue code to apply the behavior of outputting the returned object to the HTTP stream as follows:

public aspect JsonFilterAspect {

protected final Logger log = Logger.getLogger(getClass());

pointcut filterJsonResponses(): execution(@JsonFilter public * com.somecompany.someapp.web..*.*(..));

Object around() : filterJsonResponses() {
MethodSignature msig = (MethodSignature) thisJoinPoint.getSignature();
                /* Grab the annotation. */
JsonFilter annotation = msig.getMethod().getAnnotation(JsonFilter.class);
Class<?> mixin = annotation.mixin();
ObjectMapper mapper = new ObjectMapper();
mapper.getSerializationConfig().addMixInAnnotations(msig.getMethod().getReturnType(), mixin);

try {
mapper.writeValue(WebContext.getInstance().getResponse().getOutputStream(), proceed());
} catch(Exception ex) {
throw new RuntimeException(ex);
}
return null;
}
}


The above applies custom JSON serialization code based on Jackson's support for adding mixin annotations (described in the previous post).


You might wonder what WebContext is. It is a helper class I wrote that gets populated by a ServletFilter. The context is stored in a ThreadLocal and then removed after the request is done (both by the filter). For completeness, here is the filter and WebContext object.

@Component("webContextFilter")
public class WebContextFilter extends GenericFilterBean {

@Override
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
ServletContext servletContext = this.getServletContext();
WebContext.create(request, response, servletContext);
chain.doFilter(request, response);
WebContext.clear();
}

}


@RooJavaBean
@RooToString
public class WebContext {

private static ThreadLocal<WebContext> tlv = new ThreadLocal<WebContext>();
private HttpServletRequest request;
private HttpServletResponse response;
private ServletContext servletContext;
protected WebContext() {}
private WebContext(HttpServletRequest request, HttpServletResponse response,
ServletContext servletContext) {
this.request = request;
this.response = response;
this.servletContext = servletContext;
}
public static WebContext getInstance() {
return tlv.get();
}
public static void create(HttpServletRequest request, HttpServletResponse response,
ServletContext servletContext) {
WebContext wc = new WebContext(request, response, servletContext);
tlv.set(wc);
}
public static void clear() {
tlv.set(null);
}
}

 

Now we can annotate any request handler with  @JsonFilter(mixin=AppDetailFilter.class) and it will apply the Jackson JSON custom streaming, i.e., filtering.

0 0
原创粉丝点击