This is NOT an ideal solution. See my second answer.
I solved this using ModelAndViewResolver . You can register them directly using the AnnotationMethodHandlerAdapter with a perk, knowing that they will always kick first before processing by default. Hence the Spring documentation is
public void setCustomModelAndViewResolver(ModelAndViewResolver customModelAndViewResolver) { this.customModelAndViewResolvers = new ModelAndViewResolver[] {customModelAndViewResolver}; }
ModelAndViewResolver looked at the ModelAndViewResolver interface, I knew that it contains all the arguments needed to extend some functions in how the handler works.
public interface ModelAndViewResolver { ModelAndView UNRESOLVED = new ModelAndView(); ModelAndView resolveModelAndView(Method handlerMethod, Class handlerType, Object returnValue, ExtendedModelMap implicitModel, NativeWebRequest webRequest); }
Take a look at all of these delicious arguments in resolveModelAndView ! I have access to almost everything Spring knows about the request. Here, as I implemented the interface, to act very similar to MappingJacksonHttpMessageConverter with the exception of unidirectional (outward):
public class JsonModelAndViewResolver implements ModelAndViewResolver { public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); public static final MediaType DEFAULT_MEDIA_TYPE = new MediaType("application", "json", DEFAULT_CHARSET); private boolean prefixJson = false; public void setPrefixJson(boolean prefixJson) { this.prefixJson = prefixJson; } protected Map<Class<?>, Class<?>> getMixins(Json jsonFilter) { Map<Class<?>, Class<?>> mixins = new HashMap<Class<?>, Class<?>>(); if(jsonFilter != null) { for(JsonMixin jsonMixin : jsonFilter.mixins()) { mixins.put(jsonMixin.target(), jsonMixin.mixin()); } } return mixins; } @Override public ModelAndView resolveModelAndView(Method handlerMethod, Class handlerType, Object returnValue, ExtendedModelMap implicitModel, NativeWebRequest webRequest) { if(handlerMethod.getAnnotation(Json.class) != null) { try { HttpServletResponse httpResponse = webRequest.getNativeResponse(HttpServletResponse.class); httpResponse.setContentType(DEFAULT_MEDIA_TYPE.toString()); OutputStream out = httpResponse.getOutputStream(); ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setMixInAnnotations(getMixins(handlerMethod.getAnnotation(Json.class))); JsonGenerator jsonGenerator = objectMapper.getJsonFactory().createJsonGenerator(out, JsonEncoding.UTF8); if (this.prefixJson) { jsonGenerator.writeRaw("{} && "); } objectMapper.writeValue(jsonGenerator, returnValue); out.flush(); out.close(); return null; } catch (JsonProcessingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } return UNRESOLVED; } }
The only user class used above is my @Json annotation @Json , which includes one parameter named mixins . Here, how I implement it on the controller side.
@Controller public class Controller { @Json({ @JsonMixin(target=MyTargetObject.class, mixin=MyTargetMixin.class) }) @RequestMapping(value="/my-rest/{id}/my-obj", method=RequestMethod.GET) public @ResponseBody List<MyTargetObject> getListOfFoo(@PathVariable("id") Integer id) { return MyServiceImpl.getInstance().getBarObj(id).getFoos(); } }
This is a pretty pretty simplicity. ModelAndViewResolver automatically converts the returned object to JSON and also applies annotated mixes.
One “downside” (if you call it that) should be back to the Spring 2.5 configuration option, since the new 3.0 tag does not allow you to directly configure ModelAndViewResolver. Maybe they just missed it?
My old configuration (using Spring 3.1 style)
<mvc:annotation-driven />
My new configuration (using Spring style 2.5 )
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"> <property name="customModelAndViewResolvers"> <list> <bean class="my.package.mvc.JsonModelAndViewResolver" /> </list> </property> </bean>
^^ 3.0+ does not have the ability to connect to a custom ModelAndViewResolver. Consequently, the transition back to the old style.
Here's the user annotations:
Json
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface Json { JsonMixin[] mixins() default {}; }
Jsonmixin
public @interface JsonMixin { public Class<? extends Serializable> target(); public Class<?> mixin(); }