如何使用 Spring MVC 设计通用响应构建器/RESTful Web 服务?
Posted
技术标签:
【中文标题】如何使用 Spring MVC 设计通用响应构建器/RESTful Web 服务?【英文标题】:How do I design a generic Response builder / RESTful Web Service using Spring MVC? 【发布时间】:2013-01-07 05:22:39 【问题描述】:尝试使用 Spring MVC 构建 RESTful Web 服务。
控制器应返回特定的 Java 类型,但响应主体必须是通用信封。如何才能做到这一点?
以下代码部分是我目前所拥有的:
控制器方法:
@Controller
@RequestMapping(value = "/mycontroller")
public class MyController
public ServiceDetails getServiceDetails()
return new ServiceDetails("MyService");
响应信封:
public class Response<T>
private String message;
private T responseBody;
ServiceDetails
代码:
public class ServiceDetails
private String serviceName;
public ServiceDetails(String serviceName)
this.serviceName = serviceName;
对客户的预期最终响应应显示为:
"message" : "Operation OK"
"responseBody" :
"serviceName" : "MyService"
【问题讨论】:
如果您使用的是 Jackson 或 FlexJson,为什么不能直接在方法中返回 Response 对象呢? JSON 序列化器应该能够序列化任何复杂的对象。 我正在使用杰克逊。但我仍然希望控制器返回请求特定的 java 类型。我认为它更干净、更直观。 另外还有一些样板代码来构造响应对象,所以我想从一个地方做 我推荐的方法与@ben75 所述的方法相同:创建您的包装对象,返回它,让 Jackson 处理您对 JSON 的序列化。这就是我一直看到它完成的方式。您可以直接返回对象,也可以使用 Jackson 将其转换为字符串并返回。当我并不真正关心生成的 JSON 是什么样子(即我正在控制接收器,所以我可以让它做任何需要的事情)时,我使用了前者,而当我不控制接收器时,我使用了后一种方法(即其他人已指定格式)。 关键是 (at)ResponseBody,它告诉 Spring 接受返回的任何内容并将其用作 HttpResponse 主体,以及来自 Spring 的一些漂亮的 PFM 使用 Jackson 将对象自动转换为 JSON。我什至使用 (at)ResponseBody 来返回二进制文件,比如图像。 【参考方案1】:您可以做的是让MyRestController
将结果包装在Response
中,如下所示:
@Controller
@RequestMapping(value = "/mycontroller")
public class MyRestController
@Autowired
private MyController myController;
@RequestMapping(value = "/details")
public @ResponseBody Response<ServiceDetails> getServiceDetails()
return new Response(myController.getServiceDetails(),"Operation OK");
此解决方案使您的原始 MyController
独立于您的 REST 代码。看来您需要在类路径中包含 Jackson,以便 Spring 自动神奇地序列化为 JSON(有关详细信息,请参阅this)
编辑
看来你需要更通用的东西......所以这里有一个建议。
@Controller
@RequestMapping(value = "/mycontroller")
public class MyGenericRestController
@Autowired
private MyController myController;
//this will match all "/myController/*"
@RequestMapping(value = "/operation")
public @ResponseBody Response getGenericOperation(String @PathVariable operation)
Method operationToInvoke = findMethodWithRequestMapping(operation);
Object responseBody = null;
try
responseBody = operationToInvoke.invoke(myController);
catch(Exception e)
e.printStackTrace();
return new Response(null,"operation failed");
return new Response(responseBody ,"Operation OK");
private Method findMethodWithRequestMapping(String operation)
//TODO
//This method will use reflection to find a method annotated
//@RequestMapping(value=<operation>)
//in myController
return ...
并保持原来的“myController”几乎原样:
@Controller
public class MyController
//this method is not expected to be called directly by spring MVC
@RequestMapping(value = "/details")
public ServiceDetails getServiceDetails()
return new ServiceDetails("MyService");
主要问题:MyController
中的 @RequestMapping 可能需要被一些自定义注解替换(并调整 findMethodWithRequestMapping
以对此自定义注解执行自省)。
【讨论】:
问题是我想避免写 new Response() 很多次。我希望实现控制器方法的人不必知道响应信封,而只需返回特定类型并让拦截器采用该类型并插入响应信封。 非常感谢您的彻底重新部署。但我真的想避免使用某种包装控制器,而且我不太喜欢使用反射。看起来我最好的解决方案将来自 reidarok 在下面写的内容。你能分享你的见解吗?谢谢【参考方案2】:默认情况下,Spring MVC 使用 org.springframework.http.converter.json.MappingJacksonHttpMessageConverter 通过 Jackson 序列化/反序列化 JSON。
我不确定这是否是个好主意,但解决您的问题的一种方法是扩展此类,并覆盖 writeInternal 方法:
public class CustomMappingJacksonHttpMessageConverter extends MappingJacksonHttpMessageConverter
@Override
protected void writeInternal(Object object, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException
super.writeInternal(new Response(object, "Operation OK"), outputMessage);
如果您使用 XML 配置,您可以像这样启用自定义转换器:
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="path.to.CustomMappingJacksonHttpMessageConverter">
</mvc:message-converters>
</mvc:annotation-driven>
【讨论】:
看起来不错。但这意味着这个转换器将用于我所有的控制器?我只需要一个特定的...有可能吗?基本上我想覆盖 MappingJacksonJsonView 并使用自定义视图作为该特定控制器的视图,但这似乎不起作用。由于某种原因,spring 总是使用默认的 jackson 视图 我真的不确定如何为单个控制器设置特定的转换器。乏味,但也许可以接受的解决方案是为这个特定的控制器创建一个单独的 spring 上下文(即 servlet)?以上是关于如何使用 Spring MVC 设计通用响应构建器/RESTful Web 服务?的主要内容,如果未能解决你的问题,请参考以下文章