Spring MVC中的自定义HttpMessageConverter

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

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

这导致我在任何地方使用重复的代码来包装数据:

 @Transactional @RequestMapping(value = "/", method = RequestMethod.GET) public @ResponseBody Result<List> books() { List books = booksDao.readBooks(); return Result.ok(books); // this gets repeated everywhere } 

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

像@Ralph建议你可以使用HandlerMethodReturnValueHandler来包装你的处理程序返回值。

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

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

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

 public class ResultResponseHandlerMethodProcessor extends RequestResponseBodyMethodProcessor { public ResultResponseHandlerMethodProcessor(final List> messageConverters) { super(messageConverters); } public ResultResponseHandlerMethodProcessor(final List> 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 returnValueHandlers) { List> messageConverters = new ArrayList<>(); messageConverters.add(new MappingJackson2HttpMessageConverter()); returnValueHandlers.add(new ResultResponseHandlerMethodProcessor(messageConverters)); } } 

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

您可以使用HandlerMethodReturnValueHandler来替换结果。

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

有关如何归档类似(不同)目标的示例,请参阅此博客: http : //martypitt.wordpress.com/2012/11/05/custom-json-views-with-spring-mvc-and-jackson/ 。 它还描述了一种注册HandlerMethodReturnValueHandler (对于其他人看到Bart的回答 )

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

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

 Result.ok(data) Result.forbidden() Result.badRequest(" caused a syntax error.") Result.notModified("The entity was not modified.") 

我假设这些方法旨在映射到各种HTTP状态代码 ,但具有自定义错误消息。

您的@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响应。

我认为有必要

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

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