我正在尝试使用Spring MVC设置版本化服务,使用继承来扩展旧控制器以避免重写未更改的控制器方法。我的解决方案基于previous question关于版本控制服务,但是我遇到了模糊映射的问题。
@Controller
@RequestMapping({"/rest/v1/bookmark"})
public class BookmarkJsonController {
@ResponseBody
@RequestMapping(value = "/write", produces = "application/json", method = RequestMethod.POST)
public Map<String, String> writeBookmark(@RequestParam String parameter) {
// Perform some operations and return String
}
}
@Controller
@RequestMapping({"/rest/v2/bookmark"})
public class BookmarkJsonControllerV2 extends BookmarkJsonController {
@ResponseBody
@RequestMapping(value = "/write", produces = "application/json", method = RequestMethod.POST)
public BookmarkJsonModel writeBookmark(@RequestBody @Valid BookmarkJsonModel bookmark) {
// Perform some operations and return BookmarkJsonModel
}
}
通过此设置,我得到IllegalStateException: Ambiguous mapping found
。我对此的想法是因为我有两个具有不同返回/参数类型的方法,我在BookmarkJsonControllerV2
中有两个具有相同映射的方法。作为一种解决方法,我尝试在writeBookmark
中覆盖BookmarkJsonControllerV2
而没有任何请求映射:
@Override
public Map<String, String> writeBookmark(@RequestParam String parameter) {
return null; // Shouldn't actually be used
}
然而,当我编译并运行此代码时,我仍然得到一个模糊映射的异常。但是,当我点击URL /rest/v2/bookmark/write
时,我得到了一个空的/ null响应。将return null
更改为:
return new HashMap<String, String>() {{
put("This is called from /rest/v2/bookmark/write", "?!");
}};
我会收到带有该地图的JSON,表明尽管没有任何请求映射注释,但它显然是从超类“继承”注释。此时,我对控制器扩展的唯一“解决方案”是让每个控制器返回Object
并且仅将HttpServletRequest
和HttpServletResponse
个对象作为参数。这似乎完全是黑客,我宁愿永远不要这样做。
有没有更好的方法来使用Spring MVC实现URL版本化,这允许我只覆盖后续版本中的更新方法,或者是我唯一可以完全重写每个控制器的选项?
答案 0 :(得分:3)
无论出于何种原因,使用@RequestMapping
注释会导致模糊的映射异常。作为一种解决方法,我决定尝试将springmvc-router用于我的REST服务,这将允许我在我的控制器类上利用继承,因此我不必重新实现根据需要在版本之间没有更改的端点。我的解决方案还允许我继续为非REST控制器使用注释映射。
注意:我使用的是Spring 3.1,与以前的版本相比,它具有不同的处理程序映射类。
springmvc-router项目将Play框架中的路由器系统引入Spring MVC。在application-context.xml
内,相关设置如下:
<mvc:annotation-driven/>
<bean id="handlerAdapter" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter" />
<bean class="org.resthub.web.springmvc.router.RouterHandlerMapping">
<property name="routeFiles">
<list>
<value>routes/routes.conf</value>
</list>
</property>
<property name="order" value="0" />
</bean>
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
<property name="order" value="1" />
</bean>
这允许我继续在路由器旁边使用带注释的控制器。 Spring使用责任链系统,因此我们可以分配多个映射处理程序。从这里开始,我有一个像这样的路由器配置:
# Original Services
POST /rest/bookmark/write bookmarkJsonController.write
POST /rest/bookmark/delete bookmarkJsonController.delete
# Version 2 Services
POST /rest/v2/bookmark/write bookmarkJsonControllerV2.write
POST /rest/v2/bookmark/delete bookmarkJsonControllerV2.delete
与控制器一起看起来像:
@Controller
public class BookmarkJsonController {
@ResponseBody
public Map<String, Boolean> write(@RequestParam String param) { /* Actions go here */ }
@ResponseBody
public Map<String, Boolean> delete(@RequestParam String param) { /* Actions go here */ }
}
@Controller
public class BookmarkJsonControllerV2 extends BoomarkJsonController {
@ResponseBody
public Model write(@RequestBody Model model) { /* Actions go here */ }
}
使用这样的配置,网址/rest/v2/bookmark/write
会点击方法BookmarkJsonControllerV2.write(Model model)
,网址/rest/v2/bookmark/delete
会点击继承的方法BookmarkJsonController.delete(String param)
。
唯一的缺点是必须重新定义新版本的整个路线,而不是将班级上的@RequestMapping(value = "/rest/bookmark")
更改为@RequestMapping(value = "/rest/v2/bookmark")
。