Подтвердить что ты не робот

Как наследовать RequestMappings в Spring 3 MVC REST API

Я пытаюсь создать RESTful API с помощью Spring MVC. Я снимаю для чистого и управляемого кода, где структура пакета следует структуре 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);
}

Пока это работает, я считаю бесполезным и подверженным ошибкам повторять "/library/{libraryId}" во всех унаследованных @RequestMappings,/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 getLibrary()

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

Есть ли способ Spring помочь мне здесь? Для меня приемлемо использовать нормальное наследование java, аннотацию Spring или что-то еще, что помогает мне не писать "/library/{libraryId}" как часть каждого URL, который я когда-либо пишу.

4b9b3361

Ответ 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, я думаю, что он должен работать, хотя...

Ответ 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 = > КомментарииКонтроллер # index
  • /articles/1/comments/2 = > КомментарииController # show (хорошо, но не рекомендуется)
  • /comments/2 = > КомментарииController # show (предпочтительно)

Ответ 3

Я не думаю, что это возможно. Но вы можете иметь аннотацию @RequestMapping для самого класса, поэтому она сэкономит вам хотя бы некоторую типизацию.

Ответ 4

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

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

}