Java 如何在Spring3MVCRestAPI中继承RequestMappings

Java 如何在Spring3MVCRestAPI中继承RequestMappings,java,spring,spring-mvc,Java,Spring,Spring Mvc,我正在尝试使用SpringMVC构建一个RESTfulAPI。我正在寻找干净且易于管理的代码,其中包结构遵循url结构 这就是我得到的: // com.test.api.library @RequestMapping("/library/{libraryId}") public Library getLibrary(@PathVariable long libraryId) { return service.getLibraryById(libraryId); } 虽然这样做很有效,但

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

这就是我得到的:

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

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

我想将book类改写为以下内容:

// 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继承、spring注释或任何其他帮助我不将“/library/{libraryId}”作为我编写的每个url的一部分。

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

我相信这个问题以前已经被问过并回答过了:

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

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

}
也就是说,这里有一种减少重复信息量的方法。实际上,我在自己的代码中并没有这样做,因为我认为将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的理解,我认为它应该是有效的,尽管…

使用多态父方法

@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\u id}/./..
),将父模型添加到请求范围,然后让常规请求映射将URI的其余部分处理到子资源(
/../books/1
)。我手头没有这方面的例子

旁注。单一嵌套资源通常被视为URI设计的反模式。控制器应该处理自己的资源。最常见的实现使单个嵌套资源的键唯一,即不依赖于其父资源。例如,数据库记录主键。但是,在某些情况下,密钥可能不是唯一的,例如序号或位置值(例如,book 1,chapter 1,chapter 2),甚至可能是自然密钥(例如,book ISBN,person SSN,email address,username,filename)

嵌套资源的规范URI示例:

  • /articles
    =>ArticlesController#索引
  • /articles/1
    =>ArticlesController#show
  • /articles/1/comments
    =>CommentsController#索引
  • /articles/1/comments/2
    =>CommentsController#show(可以,但不是首选)
  • /comments/2
    =>CommentsController#show(首选)

是的,我意识到这种可能性,这比仅仅注释方法要好得多,但离完美还有很长的路要走。根据你的回答,离完美还有很长的路,我不清楚你想要什么。您是否可以显示几个资源的完整URL,并描述您希望由哪个控制器处理它们?对,这可能是一个奇怪的示例,但
/country/{countryCode}/state/{stateCode}/city/{cityCode}/street/{streetCode}/number/{streetNumber}
这些步骤中的每一步(即国家、州、城市、街道和号码)都应该进入它自己的控制器,并且每个步骤上都有一个子动词。主要的问题是,当我使用numberController时,我不想从国家一路重复url,我也不想“知道”国家代码是这个资源的关键。我只是想“拥有”它。这有点偏离目标,但我通过将命令/动词对(最终与实体/id对相同——命令是“查找”)映射到存储在数据库中的bean(由Groovy实现)来实现类似的系统。每个结果类型都有一个可用命令列表(因此一个国家有可用的州)。由于我的用例,它变得相当复杂,但在运行时是灵活的、可扩展的。也许它会给你们一些想法,尽管我知道它不能满足你们的迫切需要。我会悬赏给你们这个答案。我不想把赏金白白浪费在任何事情上,这在某种程度上是一个“正确”的答案,但这并不能回答我的问题。我很清楚这种可能性(如果没有别的,那就是波佐在回答中指出的)。这样做意味着我必须将库中的所有子资源放在同一个控制器中。这当然是可能的,但不是我想要的。无论如何,谢谢。我开始认为答案是“不,不可能”。您的想法增加了一个抽象级别,但仍然没有解决主要问题,即在更接近库类的地方处理“@PathVariable long libraryId”。来自“普通”java,我习惯于继承,让父类处理自己的变量,只让子类处理特定于它们的变量。无论如何,谢谢。这是不可能的。对不起,我没有在这里说得更清楚。我认为我链接的问题/答案足够清楚。我已经做到了。如果将此方法与多态父方法相结合,则可以获得干路径和关注点分离参数。我会找出我的密码并给出答案。我同意你的看法。将URI放在代码旁边更易于维护和可读。
@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";
        }
    }
}