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

2022-09-03 07:51:52

我正在尝试使用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很大一部分的根,它应该编写一次并重用,而不是到处编写。

我想将书籍类重写为如下所示:

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

春天有什么办法可以帮助我吗?对我来说,使用正常的java继承,弹簧注释或其他任何可以帮助我不写“/library/{libraryId}”作为我写过的每个URL的一部分的东西是可以接受的。


答案 1

我相信这个问题以前已经问过并回答过:春季MVC@RequestMapping继承

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

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


答案 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 前缀路由到父资源 (),将父模型添加到请求范围,然后让常规请求映射处理到子资源的 URI 的其余部分 ()。我没有现成的例子。/libraries/{library_id}/../../../../books/1

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

嵌套资源的规范 URI 示例:

  • /articles=> 文章控制器#索引
  • /articles/1=> 文章控制器#显示
  • /articles/1/comments=> 评论控制器#索引
  • /articles/1/comments/2=> CommentsController#show(好的,但不是首选)
  • /comments/2=> 评论控制器#显示(首选)

推荐