Spring MVC中的自定义HttpMessageConverter

时间:2014-04-06 06:57:15

标签: java spring spring-mvc

实现RESTful API时,我将所有数据都包装在一个对象中,看起来就像这样。

{error: null, code: 200, data: {...actual 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
}

所以问题是如何修改它(可能使用自定义HttpMessageConverter可能还有其他一些方法?)只返回booksDao.readBooks()并自动包装它。

5 个答案:

答案 0 :(得分:9)

与@Ralph建议一样,您可以使用HandlerMethodReturnValueHandler来包装处理程序的返回值。

实现这一目标的最简单方法是扩展RequestResponseBodyMethodProcessor并稍微改变它的行为。最好的方法是创建一个自定义注释来标记处理程序方法。这样可以确保默认情况下会调用HandlerMethodReturnValueHandler代替RequestMappingHandlerAdapter所包含的其他内容。

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

以下是名为HandlerMethodReturnValueHandler的自定义ResultResponseHandlerMethodProcessor的简单实现,它将支持从使用ResultResponseBody注释的方法返回的值。这很简单。只需覆盖supportsReturnType()handleReturnValue()方法以满足您的需求(将返回值包装为Result类型)。

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);
    }
}

唯一剩下的就是将此类添加到自定义HandlerMethodReturnValueHandler列表中,并为其提供MappingJackson2HttpMessageConverter实例。

@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));
    }
}

答案 1 :(得分:2)

我认为,不是改变消息转换器(可以工作),而是使用AOP方法 - 对所有相关控制器方法的建议都很容易设置。它还可以为您提供更好的编程模型,以及对截获哪些方法的更细粒度的控制。

答案 2 :(得分:2)

您可以使用HandlerMethodReturnValueHandler替换结果。

关键点是:在将(修改的)retunr值委托给序列化之前替换返回值。

有关如何归档类似(不同)目标的示例,请参阅此博客:http://martypitt.wordpress.com/2012/11/05/custom-json-views-with-spring-mvc-and-jackson/。它还描述了一种注册HandlerMethodReturnValueHandler(另一种见Bart´s answer

的方法

答案 3 :(得分:2)

我想试着说服你,你所做的是对的,不需要任何改变。

正如您在问题的评论中发布的那样,您有许多不同的Result方法来设置错误消息,代码和数据。像

这样的东西
Result.ok(data)
Result.forbidden()
Result.badRequest("<Something> caused a syntax error.") 
Result.notModified("The entity was not modified.")

我假设这些方法旨在映射到各种HTTP status codes,但带有自定义错误消息。

您的@Controller处理程序方法用于处理请求并准备响应。这就是您的方法目前正在做什么,它非常清楚它的作用。关于Result应该属于处理程序方法的逻辑,而不是像HandlerMethodReturnValueHandler那样的逻辑正如其他人所提出的那样。

我甚至建议使用ResponseEntity代替@ResponseBody。您可以返回ResponseEntity并明确设置HTTP响应标头和状态代码。您还可以设置响应正文。

这样的东西
return new ResponseEntity<>(Result.ok(books)); 

在这种情况下,默认状态代码为200。

但是如果你想使用

return Result.forbidden();

你会用

return new ResponseEntity<>(Result.forbidden(), HttpStatus.FORBIDDEN);

Spring会使用相同的HttpMessageConverter将您的Result转换为JSON,但在这里您可以更好地控制HTTP响应。

答案 4 :(得分:0)

我认为有必要

  

将您的所有者替换为默认的RequestResponseBodyMethodProcessor   处理器

,否则默认的RequestResponseBodyMethodProcessor将控制处理返回值。