java 如何在 Spring 3 MVC REST API 中继承 RequestMappings

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/7228772/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-30 19:12:33  来源:igfitidea点击:

How to inherit RequestMappings in a Spring 3 MVC REST API

javaspringspring-mvc

提问by Andreas Wederbrand

I'm trying to build a RESTful API using Spring MVC. I'm shooting for clean and manageable code where the package structure follows the url structure.

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

So here is what I've got:

所以这就是我所拥有的:

// 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);
}

While this works, I find it messy and error-prone to have to repeat "/library/{libraryId}" in all inherited @RequestMappings, /library is likely to be to root of a big part of the API and it should be written once and reused instead of written everywhere.

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

I would like to rewrite the book-class to something like this:

我想将 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);
}

Is there any way Spring can help me here? It is acceptable for me to use normal java inheritance, spring annotation or anything else that helps me to not write "/library/{libraryId}" as a part of every url I ever write.

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

回答by jtoberon

I believe this question has been asked & answered before: Spring MVC @RequestMapping Inheritance

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

That said, here is one way to reduce the amount of duplicate information. I don't actually do this in my own code because I think having the URI right next to the code is more maintainable, even if it means a little duplication.

也就是说,这是减少重复信息量的一种方法。我实际上并没有在我自己的代码中这样做,因为我认为将 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);
  }
}

Since I wouldn't take this approach myself, I haven't actually tried this solution! Based on my understanding of Spring, I think it should work though...

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

回答by Alex

Use a polymorphic parent approach.

使用多态父方法。

@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";
        }
    }
}

Additionally, you could use a base controller to route the URI prefix to parent resources (/libraries/{library_id}/../..), add the parent models to the request scope, and then let the regular request mappings handle the rest of the URI to child resources (/../../books/1). I don't have an example of this off-hand.

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

Side note. Singular nested resources are generally regarded as an antipattern for URI design. A controller should handle its own resources. The most common implementations make the key for the singular nested resource unique, i.e., not dependent on its parent resource. For instance, a database record primary key. However, there are situations where the key might not be unique, such as an ordinal or position value (e.g., book 1, chapter 1, chapter 2), or maybe even a natural key (e.g., book ISBN, person SSN, email address, username, filename).

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

Example of canonical URIs for nested resources:

嵌套资源的规范 URI 示例:

  • /articles=> ArticlesController#index
  • /articles/1=> ArticlesController#show
  • /articles/1/comments=> CommentsController#index
  • /articles/1/comments/2=> CommentsController#show (okay, but not preferred)
  • /comments/2=> CommentsController#show (preferred)
  • /articles=> 文章控制器#index
  • /articles/1=> 文章控制器#show
  • /articles/1/comments=> 评论控制器#index
  • /articles/1/comments/2=> CommentsController#show(好的,但不是首选)
  • /comments/2=> CommentsController#show(首选)

回答by Bozho

I don't think it's possible. But you can have the @RequestMappingannotation on the class itself, so it will save you at least some typing.

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

回答by Anthony

@Controller
@RequestMapping("/library/{libraryId}")
public class HelloWorldController {

    @RequestMapping(value="/book/{bookId}")
    public ModelAndView helloWorld() {
        ....
    }

}