2

我正在尝试使用 Spring MVC 设置版本化服务,使用继承来扩展旧控制器以避免重写未更改的控制器方法。我的解决方案基于先前关于版本控制服务的问题,但是我遇到了不明确映射的问题。

@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具有相同映射的方法。作为一种解决方法,我尝试writeBookmarkBookmarkJsonControllerV2没有任何请求映射的情况下覆盖:

@Override
public Map<String, String> writeBookmark(@RequestParam String parameter) {
  return null; // Shouldn't actually be used
}

但是,当我编译并运行这段代码时,我仍然遇到了不明确映射的异常。但是,当我点击 URL 时,/rest/v2/bookmark/write我得到了一个空/空响应。更改return null为:

return new HashMap<String, String>() {{
  put("This is called from /rest/v2/bookmark/write", "?!");
}};

我会收到带有该映射的 JSON,表明尽管没有任何请求映射注释,但它显然是“继承”了超类的注释。在这一点上,我对控制器扩展进行未来验证的唯一“解决方案”是让每个控制器返回Object并且只有HttpServletRequestHttpServletResponse对象作为参数。这似乎是一个彻头彻尾的黑客攻击,我宁愿永远不要这样做。

那么有没有更好的方法来使用 Spring MVC 实现 URL 版本控制,它只允许我在后续版本中覆盖更新的方法,或者是我唯一真正的选择来完全重写每个控制器?

4

1 回答 1

3

无论出于何种原因,使用@RequestMapping注释都会导致不明确的映射异常。作为一种解决方法,我决定尝试将springmvc-router用于我的 REST 服务,这将允许我在我的控制器类上利用继承,这样我就不必重新实现没有根据需要在版本之间更改的端点。我的解决方案还允许我继续为我的非 REST 控制器使用注释映射。

注意:我使用的是 Spring 3.1,它的处理程序映射类与以前的版本不同。

springmvc-router 项目将路由器系统从 Play 框架引入 Spring MVC。在 my 内部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 */ }
}

通过这样的配置,URL/rest/v2/bookmark/write将命中方法BookmarkJsonControllerV2.write(Model model)并且 URL/rest/v2/bookmark/delete将命中继承的方法BookmarkJsonController.delete(String param)

唯一的缺点是必须为新版本重新定义整个路由,而不是更改类上的@RequestMapping(value = "/rest/bookmark")to @RequestMapping(value = "/rest/v2/bookmark")

于 2013-08-01T11:46:24.220 回答