Многие реализации JSR 311: JAX-RS: API Java для веб-сервисов RESTful обеспечивает готовую генерацию WADL во время выполнения: Apache CXF , Jersey и Restlet . RESTeasy все еще ждет. По сути, эти платформы исследуют код Java с аннотациями JSR-311 и генерируют документ WADL, доступный по некоторым URL-адресам. К сожалению, Spring MVC не только не реализует стандарт JSR-311 (см .: Поддерживает ли Spring MVC аннотации JSR 311? ), Но также не генерирует для нас WADL (см. SPR-8705 ), даже если он идеально подходит для предоставление услуг REST.
По разным причинам я начал разрабатывать REST-сервисы на стороне сервера с помощью Spring MVC, и через некоторое время (скажем, через треть ресурсов) я немного заблудился. Мне действительно нужен был способ каталогизировать и документировать все доступные ресурсы и операции. WADL показался мне отличным выбором.
К счастью, среда Spring открыта для расширения, и в нее легко добавить новые функции, основанные на существующей инфраструктуре, если вы хотите некоторое время копаться в коде. Для генерации WADL мне понадобился список URI, которые обрабатывает приложение, какие методы HTTP реализованы и — в идеале — какой метод Java обрабатывает каждый из них. Очевидно, что Spring выполняет эту работу уже где-то во время загрузки загрузчика MVC DispatcherServlet — сканирование на наличие @Controller , @RequestMapping , @PathVariable и т. Д. — поэтому кажется разумным повторно использовать эту информацию, а не выполнять ее снова.
Угадайте, что, похоже, вся необходимая информация хранится в странно названном классе RequestMappingHandlerMapping . Вот снимок экрана отладчика, чтобы дать вам представление о том, насколько доступна информация:
Но это становится еще лучше: RequestMappingHandlerMapping на самом деле является бином Spring, который вы можете легко внедрить и использовать:
1
2
3
4
5
6
7
|
@Controller class WadlController @Autowired ()(mapping: RequestMappingHandlerMapping) { @RequestMapping (method = Array(GET)) @ResponseBody def generate(request: HttpServletRequest) = new WadlApplication() } |
Правильно, мы будем использовать еще один Spring MVC-контроллер для генерации WADL-документа. В прошлый раз нам удалось сгенерировать классы JAXB, представляющие документ WADL (ведь WADL — это файл XML), поэтому, возвращая пустой экземпляр WadlApplication, мы фактически возвращаем пустой, но действительный WADL:
1
2
|
<? xml version = "1.0" encoding = "UTF-8" standalone = "yes" ?> |
Я не буду объяснять детали реализации ( полный исходный код доступен, включая пример приложения). Это был вопрос переписывания моделей Spring в классы WADL. Если вам интересно, взгляните на WadlGenerator.scala, который является центральной точкой решения и контрольных примеров . Вот один из них:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
test("should add parameter info for template parameter in URL") { given("") val mapping = Map( mappingInfo("/books", GET) -> handlerMethod("listBooks"), mappingInfo("/books/{bookId}", GET) -> handlerMethod("readBook") ) when("") val wadl = generate(mapping) then("") assertXMLEqual(wadlHeader + """ < resource path = "books" > < method name = "GET" > < doc title = "com.blogspot.nurkiewicz.springwadl.TestController.listBooks" /> </ method > < resource path = "{bookId}" > < param name = "bookId" required = "true" /> < method name = "GET" > < doc title = "com.blogspot.nurkiewicz.springwadl.TestController.readBook" /> </ method > </ resource > </ resource > """ + wadlFooter, wadl) } |
К сожалению, я был слишком ленив, чтобы правильно назвать данные / когда / затем блоки. Но тесты должны быть довольно читабельными.
Единственная техническая трудность, о которой я хотел бы упомянуть, — это преобразование шаблонов плоских URI, предоставляемых инфраструктурой Spring, в иерархические объекты WADL (в основном, в дерево). Вот упрощенная версия этой проблемы: список шаблонов URI выглядит следующим образом:
1
2
3
4
5
6
7
8
9
|
/books /books/ {bookId} /books/ {bookId} /reviews /books/best-sellers /readers /readers/ {readerId} /readers/ {readerId} /account/new-password /readers/active /readers/passive |
Создайте следующую древовидную структуру данных:
Конечно, структура данных так же проста, как объект Node, содержащий метку и список дочерних узлов. Не очень сложный, но, вероятно, интересный CodeKata .
Так что же это такое с этим WADL? Действительно ли XML более читабелен и помогает в управлении REST-приложениями? Я бы даже не потрудился поиграть с ним, если бы не отличная поддержка soapUI для WADL . WADL, сгенерированный для примера приложения, которое я также добавил, может быть легко импортирован в soapUI:
Стоит упомянуть две особенности. Прежде всего soapUI отображает дерево ресурсов REST (в отличие от простого списка операций при импорте WSDL). Рядом с каждым методом HTTP есть соответствующий метод Java, который его обрабатывает (это можно отключить) для устранения неполадок и отладки. Во-вторых, мы можем выбрать любой метод / ресурс HTTP и вызвать его. На основе описания WADL soapUI создаст удобный для пользователя мастер, в который можно вводить параметры. Значения по умолчанию заполняются автоматически. Когда мы закончим, приложение сгенерирует HTTP-запрос с правильными URL-адресом и содержимым, отображая ответ при его получении. Действительно полезно!
Кстати, вы заметили параметры запроса max и page? Наша небольшая библиотека использует отражение, чтобы найти аннотации @RequestParam, например, следующий контроллер:
01
02
03
04
05
06
07
08
09
10
11
12
13
|
@Controller @RequestMapping (value = Array( "/book/{bookId}/review" )) class ReviewController @Autowired ()(reviewService: ReviewService) { @RequestMapping (method = Array(GET)) @ResponseBody def listReviews( @RequestParam (value = "page" , required = false , defaultValue = "1" ) page: Int, @RequestParam (value = "max" , required = false , defaultValue = "20" ) max: Int) = new ResultPage(reviewService.listReviews( new PageRequest(page - 1 , max))) //... } |
будет переведено в WADL-совместимое описание:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
<? xml version = "1.0" encoding = "UTF-8" standalone = "yes" ?> < doc title = "Spring MVC REST appllication" /> < resource path = "book" > <!-- --> < resource path = "{bookId}" > < param required = "true" style = "template" name = "bookId" /> <!-- --> < resource path = "review" > < method name = "GET" > < doc title = "com.blogspot.nurkiewicz.web.ReviewController.listReviews" /> < request > < param required = "false" default = "1" style = "query" name = "page" /> < param required = "false" default = "20" style = "query" name = "max" /> </ request > </ resource > </ resource > </ resource > </ resource </application> |
Надеюсь, вам было весело с этой маленькой библиотекой, которую я написал. Не стесняйтесь включать его в свой проект и не стесняйтесь сообщать об ошибках. Полный исходный код под лицензией Apache доступен на GitHub: https://github.com/nurkiewicz/spring-rest-wadl.
Справка: Автоматическая генерация WADL в Spring MVC REST приложении от нашего партнера JCG