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将控制处理返回值。