Spring MVC中的URL版本控制器的控制器继承和模糊映射

时间:2013-07-30 11:48:43

标签: spring-mvc versioning

我正在尝试使用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并且仅将HttpServletRequestHttpServletResponse个对象作为参数。这似乎完全是黑客,我宁愿永远不要这样做。

有没有更好的方法来使用Spring MVC实现URL版本化,这允许我只覆盖后续版本中的更新方法,或者是我唯一可以完全重写每个控制器的选项?

1 个答案:

答案 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")