Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom HttpMessageConverter in Spring MVC

When implementing RESTful API I wrap all my data in an object so it looks like this.

{error: null, code: 200, data: {...actual data...}}

This results in repetitive code I use everywhere to wrap data:

@Transactional
@RequestMapping(value = "/", method = RequestMethod.GET)
public @ResponseBody Result<List<BookShortDTO>> books() {

    List<Book> books = booksDao.readBooks();
    return Result.ok(books); // this gets repeated everywhere
}

So the question is how do I modify this (maybe with use of custom HttpMessageConverter maybe some other ways?) to just return booksDao.readBooks() and to get it wrapped automatically.

like image 524
Yanis Avatar asked Apr 06 '14 06:04

Yanis


1 Answers

Like @Ralph suggested you can use a HandlerMethodReturnValueHandler to wrap your handlers return value.

The easiest way to achieve this is by extending RequestResponseBodyMethodProcessor and alter it's behavior a bit. Best is to create a custom annotation to mark your handler methods with. This will make sure your HandlerMethodReturnValueHandler will be called instead of others included by RequestMappingHandlerAdapter by default.

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ResultResponseBody {}

Here is a simple implementation of the custom HandlerMethodReturnValueHandler named ResultResponseHandlerMethodProcessor which will support values returned from methods annotated with ResultResponseBody. It's pretty simple. Just override the supportsReturnType() and handleReturnValue() methods to suit your needs (wrap the return value into a Result type).

public class ResultResponseHandlerMethodProcessor extends RequestResponseBodyMethodProcessor {
    public ResultResponseHandlerMethodProcessor(final List<HttpMessageConverter<?>> messageConverters) {
        super(messageConverters);
    }

    public ResultResponseHandlerMethodProcessor(final List<HttpMessageConverter<?>> messageConverters, final ContentNegotiationManager contentNegotiationManager) {
        super(messageConverters, contentNegotiationManager);
    }

    @Override
    public boolean supportsReturnType(final MethodParameter returnType) {
        return returnType.getMethodAnnotation(ResultResponseBody.class) != null;
    }

    @Override
    public void handleReturnValue(final Object returnValue, final MethodParameter returnType, final ModelAndViewContainer mavContainer, final NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException {
        super.handleReturnValue(Result.ok(returnValue), returnType, mavContainer, webRequest);
    }
}

The only thing left is to add this class to the list of custom HandlerMethodReturnValueHandlers and provide it with a MappingJackson2HttpMessageConverter instance.

@EnableWebMvc
@Configuration
public class ApplicationConfiguration extends WebMvcConfigurerAdapter
    @Override
    public void addReturnValueHandlers(final List<HandlerMethodReturnValueHandler> returnValueHandlers) {
        List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
        messageConverters.add(new MappingJackson2HttpMessageConverter());
        returnValueHandlers.add(new ResultResponseHandlerMethodProcessor(messageConverters));
    }
}
like image 182
Bart Avatar answered Oct 27 '22 21:10

Bart