【问题标题】:How to inherit RequestMappings in a Spring 3 MVC REST API如何在 Spring 3 MVC REST API 中继承 RequestMappings
【发布时间】:2011-11-05 22:12:25
【问题描述】:

我正在尝试使用 Spring MVC 构建一个 RESTful API。我正在寻找干净且易于管理的代码,其中包结构遵循 url 结构。

这就是我所拥有的:

// com.test.api.library
@RequestMapping("/library/{libraryId}")
public Library getLibrary(@PathVariable long libraryId) {
   return service.getLibraryById(libraryId);
}

// com.test.api.library.book
@RequestMapping("/library/{libraryId}/book/{bookId}")
public Book getBook(@PathVariable long libraryId, @PathVariable long bookId) {
   Library library service.getLibraryById(libraryId);
   return library.getBookById(bookId);
}

虽然这可行,但我发现必须在所有继承的 @RequestMappings 中重复“/library/{libraryId}”很麻烦且容易出错,/library 很可能是 API 很大一部分的根目录,它应该写一次就重复使用,而不是到处写。

我想把 book-class 改写成这样:

// com.test.api.library.book
@RequestMapping("/book/{bookId}")
public Book getBook(@PathVariable long bookId) {
   // long libraryId magically given to me from the library-class's getLibrary()

   Library library service.getLibraryById(libraryId);
   return library.getBookById(bookId);
}

Spring 有什么办法可以帮助我吗?我可以使用普通的 java 继承、spring 注释或任何其他帮助我不写“/library/{libraryId}”作为我写过的每个 url 的一部分的东西。

【问题讨论】:

    标签: java spring spring-mvc


    【解决方案1】:

    我相信这个问题之前已经被问及回答过:Spring MVC @RequestMapping Inheritance

    也就是说,这是减少重复信息量的一种方法。我实际上并没有在我自己的代码中这样做,因为我认为将 URI 放在代码旁边更易于维护,即使这意味着一些重复。

    @RequestMapping(URI_LIBRARY)
    public interface LibraryNamespace {
      public static String URI_LIBRARY = "/library/{libraryId}";
    }
    
    @RequestMapping(URI_BOOK)
    public interface BookNamespace {
      public static String URI_BOOK = LibraryNamespace.URI_LIBRARY + "/book/{bookId}";
    }
    
    @Controller
    public class LibraryController implements LibraryNamespace {
      @RequestMapping("")
      public Library get(@PathVariable long libraryId) {
        return service.getLibraryById(libraryId);
      }
    }
    
    @Controller
    public class BookController implements BookNamespace {
      @RequestMapping("")
      public Book get(@PathVariable long libraryId, @PathVariable long bookId) {
        Library library service.getLibraryById(libraryId);
        return library.getBookById(bookId);
      }
    }
    

    由于我自己不会采用这种方法,因此我实际上没有尝试过这种解决方案!根据我对 Spring 的理解,我认为它应该可以工作......

    【讨论】:

    • 我开始认为答案是“不,这是不可能的”。您的想法增加了一个抽象级别,但仍然没有解决在更接近库类的地方处理“@PathVariable long libraryId”的主要问题。来自“普通”java,我习惯于继承并让父类处理它自己的变量,而让子类处理它们特有的东西。还是谢谢。
    • 不可能。抱歉,我没有在这里说得更清楚。我认为我链接到的问题/答案已经足够清楚了。
    • 我已经做到了。如果将此方法与多态父方法结合使用,则可以获得 DRY 路径和关注点分离参数。我会挖掘我的代码并发布答案。
    • 我同意你的看法。将 URI 放在代码旁边更易于维护和阅读。
    【解决方案2】:

    使用多态父方法。

    @Controller
    public class CommentsController {
        @RequestMapping(value="/comments", method = RequestMethod.GET)
        public @ResponseBody String index() {
            /* kludge to allow optional path parameters */
            return index(null, null);
        }
    
        @RequestMapping(value="/{parent_collection}/{parent_id}/comments", method = RequestMethod.GET)
        public @ResponseBody String index(@PathVariable("parent_collection") String parentCollection, @PathVariable("parent_id") String parentId) {
            if (parentCollection == null) {
                return "all comments";
            }
            else if ((parentCollection != null) && (parentCollection.equals("posts"))) {
                /* get parent, then get comments for parent */
                return "comments for single post";
            }
            else if ((parentCollection != null) && (parentCollection.equals("customers"))) {
                /* get parent, then get comments for parent */
                return "comments for single customer";
            }
            else if ((parentCollection != null) && (parentCollection.equals("movies"))) {
                /* get parent, then get comments for parent */
                return "comments for single movie";
            }
        }
    
        @RequestMapping(value = "/comments/{id}", method = RequestMethod.GET)
        public @ResponseBody String show(@PathVariable Integer id) {
            /* kludge to allow optional path parameters */
            return show(null, null, id);
        }
    
        @RequestMapping(value = "/{parent_collection}/{parent_id}/comments/{id}", method = RequestMethod.GET)
        public @ResponseBody String show(@PathVariable("parent_collection") String parentCollection, @PathVariable("parent_id") String parentId, @PathVariable Integer id) {
            /* get comment, then get parent from foreign key */
    
            if (parentCollection == null) {
                return "single comment";
            }
            else if ((parentCollection != null) && (parentCollection.equals("posts"))) {
                return "single comment for single post";
            }
            else if ((parentCollection != null) && (parentCollection.equals("customers"))) {
                return "single comment for single customer";
            }
            else if ((parentCollection != null) && (parentCollection.equals("movies"))) {
                return "single comment for single movie";
            }
        }
    }
    

    此外,您可以使用基本控制器将 URI 前缀路由到父资源 (/libraries/{library_id}/../..),将父模型添加到请求范围,然后让常规请求映射处理 URI 到子资源的其余部分(/../../books/1)。我没有这种副手的例子。

    旁注。单一嵌套资源通常被视为 URI 设计的反模式。控制器应该处理自己的资源。最常见的实现使单个嵌套资源的键是唯一的,即不依赖于其父资源。例如,数据库记录主键。但是,在某些情况下,键可能不是唯一的,例如序数或位置值(例如,书 1、第 1 章、第 2 章),甚至可能是自然键(例如,书 ISBN、个人 SSN、电子邮件地址, 用户名, 文件名)。

    嵌套资源的规范 URI 示例:

    • /articles => ArticlesController#index
    • /articles/1 => ArticlesController#show
    • /articles/1/comments => CommentsController#index
    • /articles/1/comments/2 => CommentsController#show(好的,但不是首选)
    • /comments/2 => CommentsController#show(首选)

    【讨论】:

      【解决方案3】:

      我认为这是不可能的。但是您可以在类本身上添加@RequestMapping 注释,这样至少可以节省一些打字时间。

      【讨论】:

      • 是的,我知道这种可能性,它比仅仅注释方法要好得多,但距离完美还有很长的路要走。
      • 根据您的回复,距离完美还有很长的路要走,我不清楚您想要什么。您能否显示几个资源的完整 url,并描述您希望它们处理的控制器?
      • 对,这可能是一个奇怪的例子,但是.../country/{countryCode}/state/{stateCode}/city/{cityCode}/street/{streetCode}/number/{streetNumber} 每个步骤(即国家、州、城市、街道和号码)都应该进入它自己的控制器,并且每个都有一个动词的子集。主要问题是当我执行 numberController 时,我不会一直从国家/地区重复 url,并且我不想“知道”countryCode 是该资源的关键。我只想“拥有”它。
      • 这有点偏离目标,但我通过映射命令/动词对实现了类似这样的系统排序(最终与实体/id对相同——命令是“查找”)到存储在数据库中的 bean,由 Groovy 实现。每个结果类型都有一个可用命令列表(因此一个国家有可用的州)。由于我的用例,它变得相当复杂,但很灵活,并且在运行时可扩展。也许它会给你一些想法,虽然我知道它不能满足你的迫切需要。
      • 我用赏金奖励这个答案。我不想把赏金浪费在任何东西上,在某种程度上这是“正确”的答案。
      【解决方案4】:
      @Controller
      @RequestMapping("/library/{libraryId}")
      public class HelloWorldController {
      
          @RequestMapping(value="/book/{bookId}")
          public ModelAndView helloWorld() {
              ....
          }
      
      }
      

      【讨论】:

      • 这不能回答我的问题。我很清楚这种可能性(如果没有其他在 Bozho 给出的答案中指出的话)。这样做意味着我必须将所有子资源放到同一个控制器中的库中。这当然是可能的,但不是我所要求的。无论如何,谢谢。
      猜你喜欢
      • 1970-01-01
      • 2019-07-16
      • 2011-07-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多