I have a simple REST API, build using Spring MVC @Controller
s and @RequestMapping
. I'd like to start wrapping responses to provide additional with metadata.
For example, given a call that would return
HTTP GET: /users/1
{
"userName" : "Jack Jackerson"
}
I'd like to wrap it, as follows:
{
"metadata" :
{
"callLimit" : "50",
"callsRemaining" : "49"
},
"result" :
{
"userName" : "Jack Jackerson"
}
} ..etc..
Additionally, I'd like to support standard set of parameters for managing lists (limit
and offset
).
As this touches all the api methods, I'd like to implement it as a decorator of some spring internal service, so the methods themselves can focus on their actual logic, and keep this boilerplate stuff centralized.
I've started down the path of decorating the HttpMessageConverter
's that are registered, and wrapping them with a decorator.
However, this doesn't provide me access to the inbound request for methods that don't declare a @RequestBody
. (Many don't)
Ideally, I need to be higher in the stack - RequestResponseBodyMethodProcessor.writeWithMessageConverters()
looks like a good candidate, but I don't know how to hook in here.
What options are available with Spring MVC to implement this type of API-Wide processing of request / responses?
Here's the implementation I used:
public class MetadataInjectingReturnValueHandler implements HandlerMethodReturnValueHandler {
private final HandlerMethodReturnValueHandler delegate;
public MetadataInjectingReturnValueHandler(HandlerMethodReturnValueHandler delegate)
{
this.delegate = delegate;
}
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return delegate.supportsReturnType(returnType);
}
@Override
public void handleReturnValue(Object returnValue,
MethodParameter returnType, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest) throws Exception {
returnValue = wrapResult(returnValue); //Omitted
delegate.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
}
@Component
public class MetadataInjectionFactoryBean implements InitializingBean {
@Autowired
private RequestMappingHandlerAdapter adapter;
@Override
public void afterPropertiesSet() throws Exception {
HandlerMethodReturnValueHandlerComposite returnValueHandlers = adapter.getReturnValueHandlers();
List<HandlerMethodReturnValueHandler> handlers = Lists.newArrayList(returnValueHandlers.getHandlers());
decorateHandlers(handlers);
adapter.setReturnValueHandlers(handlers);
}
private void decorateHandlers(List<HandlerMethodReturnValueHandler> handlers) {
for (HandlerMethodReturnValueHandler handler : handlers) {
if (handler instanceof RequestResponseBodyMethodProcessor)
{
MetadataInjectingReturnValueHandler decorator = new MetadataInjectingReturnValueHandler(handler);
int index = handlers.indexOf(handler);
handlers.set(index, decorator);
log.info("Metadata Injecting decorator wired up");
break;
}
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With