JAX-RS существует уже большую часть десятилетия, и я потратил немалую часть своей карьеры, работая с ним. Я даже создал несколько собственных надстроек JAX-RS, которые мои коллеги неофициально называли «JoeRS».
Но я пошел дальше. Теперь, когда я последовательно использую Spring Boot в качестве основы для своих приложений, я обнаружил, что с Spring MVC работать проще, чем с JAX-RS. Это не потому, что JAX-RS плохой или потому что Spring Boot не хватает сильной поддержки JAX-RS. На самом деле, я считаю, что команда Spring Boot сделала все возможное, чтобы обеспечить работу JAX-RS в Spring Boot. Несмотря на это, вот семь причин, по которым я не обязан продолжать использовать JAX-RS в приложениях Spring Boot.
1. Spring MVC приводит к более «стандартным» приложениям (как ни странно)
Позвольте мне перефразировать разговор, который я имел с коллегой-архитектором по недавнему проекту Spring Boot.
- «Наш утвержденный технический стек требует, чтобы приложения в стиле REST использовали JAX-RS».
- «Какая версия спецификации JAX-RS?»
- «2,1»
- «Я удивлен, что наш стек технологий предприятия настолько актуален. Какая реализация одобрена?»
- «Ничего особенного, если он совместим с JAX-RS 2.1. Я предлагаю перейти на RESTEasy».
- «Последний стабильный выпуск RESTEasy поддерживает только JAX-RS 2.0. Кроме того, start.spring.io не предлагает стартер RESTEasy, только Jersey и Apache CXF».
- «CXF? Я не знал, что это была реализация JAX-RS. Что ж, давайте поговорим о Джерси. В конце концов, это эталонная реализация, поэтому она должна поддерживать JAX-RS 2.1».
- «Да, но самая последняя версия Spring Boot 1.5 Jersey Starter находится на Jersey 2.25, а это только JAX-RS 2.0.1. Может быть, мы могли бы сделать исключение POM, чтобы добавить Jersey 2.26 и JAX-RS 2.1».
- «Это раздражает. Может быть, нам стоит пока придерживаться JAX-RS 2.0 и надеяться, что скоро выйдет более новая версия Spring Boot, которая обновит Джерси до 2.26. Кроме того, что есть в JAX-RS 2.1, чего нет в 2.0?»
- «Я не знаю — скорее всего, ничего такого, что могло бы повлиять на наше приложение».
К этому моменту мы потратили столько же времени на обсуждение спецификационных версий и реализаций, сколько и написания наших начальных Spring MVC Rest Controllers. И для чего? Таким образом, мы могли бы использовать некоторую «стандартную» технологию, которая предотвращает нас от привязки к конкретному поставщику и / или обеспечивает большую согласованность между разрабатываемыми приложениями?
Стандарты имеют свое место, как в строительстве и сантехнике, но я обнаружил, что «стандарты» JavaEE / J2EE / EE4J / what -the-new-name-be-be будут довольно неплотными. Я никогда не видел .ear
порт без проблем из одного контейнера приложения в другой. Также я был на проекте , где я мог бы, скажем, заменить org.jboss.resteasy:resteasy-jaxrs:3.1.4.Final
с org.glassfish.jersey.core:jersey-server:2.25.1
(две реализации JAX-RS 2.0) в POM, перекомпилируйте и запустите приложение без проблем. Я знаю, как настроить глобальные ресурсы JNDI в Tomcat; Я не знаю, как это сделать в Jetty, несмотря на то, что они оба реализуют спецификацию сервлета. В конечном счете, технология реализации спецификации отчетливо проявляется в приложении, вводя некоторую степень связи с конкретной реализацией, уменьшая предполагаемое значение спецификации как «стандарта».
В частности, для JAX-RS разработчик приложения Spring Boot должен сначала решить, использовать ли доступный стартер Spring Boot JAX-RS или вручную интегрировать альтернативную реализацию, такую как RESTEasy или Restlet. Для тех, кто выбирает стартер, они должны выбрать между Apache CXF и Джерси. И как только реализация выбрана, разработчик должен решить, как конкретно интегрировать ее в приложение.
Следовательно, приложения Spring Boot, использующие JAX-RS, могут быть более разнообразными по своим вариантам реализации, интеграции и конфигурации, чем приложения, которые просто используют Spring MVC. Выбор использования Spring MVC избавляет разработчиков от обдумывания этих конкретных решений JAX-RS.
2. Мне не нужно больше думать о версиях
Одной из величайших функций Spring Boot (помимо настраиваемых баннеров , конечно) является spring-boot-dependencies
POM , который включается в POM благодаря наследованию от spring-boot-starter-parent
. Эта отдельная зависимость управляет версиями многих различных библиотек Java, API и плагинов Maven, освобождая меня от бремени знания, например, включаю ли я последнюю версию Lombok или плагин Surefire.
Spring Boot позволяет мне более блаженно не знать о конкретных версиях зависимостей, которые я использую. Напротив, спецификация JavaEE, такая как JAX-RS, по существу требует знания версии: версия API JAX-RS, которую я хочу использовать, и версия библиотеки реализации, предназначенная для этой версии, все для того, чтобы получить возможности, уже доступные в Сама весна.
Конечно, вы можете добавить стартовую версию Jersey Spring Boot и быть в блаженном неведении относительно конкретной версии Jersey и API JAX-RS в игре, но это, на мой взгляд, уменьшает «стандартный» аргумент для использования JAX-RS. Если ваш основной аргумент в пользу JAX-RS — «Я просто более знаком с ним», рассмотрите остальные причины, по которым я предпочитаю Spring MVC.
3. Мне не нужно думать о нескольких контейнерах IoC
Кто отвечает за управление жизненным циклом классов ресурсов JAX-RS в приложении Spring Boot? Весна? Реализация JAX-RS? Обе? По-разному.
При условии, что я использую только JAX-RS @Context
и различные @***Param
аннотации для ввода информации из запроса, реализация JAX-RS может управлять жизненным циклом класса без необходимости использования Spring. Однако, как только мне нужно внедрить другие управляемые зависимости, нужно что-то более тяжелое. JAX-RS 2 предоставляет подробную информацию о том, как контейнер CDI должен работать с классами ресурсов JAX-RS. Однако, так как мы используем Spring, кажется излишним добавлять некоторую реализацию CDI к миксу.
Чтобы Spring управлял жизненным циклом класса ресурсов, я могу пометить свои классы ресурсов @Component
аннотацией. Но я должен помнить о масштабах. По умолчанию классы ресурсов JAX-RS находятся в области запросов, а компоненты Spring — в приложениях. Рассмотрим следующий класс ресурсов:
@Component
@Path("/greeting")
public class GreetingResource {
@QueryParam("n")
private String name;
@GET
@Produces("text/plain")
public String greet() {
return "Hello, " + Optional.ofNullable(name).orElse("world") + "!";
}
}
Очевидно, что класс является bean-компонентом Spring в силу своей @Component
аннотации и фактически является одноэлементным, так как нет @Scope
аннотации Spring, переопределяющей область по умолчанию. Заметьте, однако, что name
поле получает свое значение из запроса благодаря @QueryParam
аннотации JAX-RS . Эта конструкция создает загадку: если есть два одновременных запроса /greeting?n=Bob
и /greeting?n=Alice
обслуживание одного экземпляра GreetingResource
, существует ли условие гонки при записи в name
поле, что может привести к приветствию Алисы «Привет, Боб» или наоборот?
Вы держите пари, что есть!
Теперь, для этого конкретного примерного класса, лучший вариант — удалить @Component
аннотацию и позволить Джерси управлять классом ресурсов, поскольку я не подключаю автосвязь ни в каких других зависимостях. Но в большинстве случаев я хочу, чтобы Spring управлял классами ресурсов в виде bean-компонентов, поэтому правильным решением будет добавить @Scope("request")
аннотацию к классу — или, что еще лучше, вообще не использовать JAX-RS и просто использовать Spring MVC.
(Примечание: если бы я попытался написать этот класс ресурсов как Spring MVC Controller с @RequestParam
аннотацией — эквивалентом — @QueryParam
на name
поле, я бы получил ошибку компилятора, потому что @RequestParam
может быть помещен только в параметры метода . это является ограничением Spring MVC по сравнению с JAX-RS, но я считаю, что это скорее функция безопасности.)
4. Мне не нужно регистрировать классы ресурсов JAX-RS.
С помощью Spring MVC я могу просто комментировать свои классы Controller, @RestController
и они готовы к работе (при условии, что я упаковал их таким образом, который позволяет их сканировать). Использование JAX-RS требует дополнительного шага . В частности, для Джерси предлагается создать ResourceConfig
компонент расширения, который регистрирует все классы ресурсов.
@Component
public class JerseyConfig extends ResourceConfig {
public JerseyConfig() {
register(Every.class);
register(Single.class);
register(ResourceClass.class);
register(Must.class);
register(Be.class);
register(Registered.class);
}
}
Я также обнаружил, что мне нужно настроить основной класс приложения, чтобы расширить его SpringBootServletInitializer
, даже если моя упаковка такова jar
. Другие реализации JAX-RS, включая Jersey 1.x, имеют разные подходы к регистрации.
Не такая уж большая проблема, но все же небольшое раздражение, которого нет при использовании чистого Spring MVC.
5. Я чувствую себя менее виноватым из-за того, что я не RESTful
JAX-RS расшифровывается как «Java API для веб-служб RESTful» и использует терминологию RESTful, в частности слово «ресурс». Это негативный , на мой взгляд, не только потому , что этот термин настолько перегружена — в проекте Spring загрузки с использованием JAX-RS, «ресурс» может относиться к ключевому REST архитектурной абстракции, класс ресурсов JAX-RS, весенним ресурс или даже разработчик, достаточно разочаровывающий — но он предполагает, что я создаю действительно RESTful API, изобилующий ресурсами, представлениями и гипермедиа (о боже!).
Spring MVC вместо этого использует номенклатуру из шаблона MVC (что неудивительно). Это означает, что я могу свободно реализовывать API-интерфейс JSON в стиле RPC без гипермедиа-ссылок в моих ответах JSON, если я так решу, и Spring MVC не осудит меня. Я гораздо более уверен в своей способности придерживаться шаблона MVC, чем стиль архитектуры REST таким образом, чтобы избежать гнева Роя Филдинга .
6. Привод просто работает
Помимо настраиваемых баннеров и вышеупомянутого spring-boot-dependencies
POM, еще одна замечательная функция Spring Boot — это Actuator. Просто добавив зависимость запуска от привода к моему POM, мое приложение получает ряд полезных для мониторинга и информационных конечных точек, готовых к работе … за исключением того, что я использую JAX-RS .
Это является следствием того, что: а) конечные точки исполнительного механизма были реализованы с помощью Spring MVC и б) сервлет-диспетчер Джерси был сопоставлен с путем /
, таким образом обрабатывая все запросы к потенциальным URL-адресам исполнительного механизма.
Решение этой проблемы является простым, хотя и раздражает; включите Spring MVC через spring-boot-starter-web
и сопоставьте сервлеты диспетчера Jersey и Spring MVC с разными путями.
Но обратите внимание, что даже после того, как я получаю, что конечные точки исполнительного механизма на основе Spring MVC работают вместе с ресурсами моего приложения JAX-RS, /mappings
конечная точка не может отобразить отображение классов ресурсов JAX-RS и /beans
не будет отображать те, которые не являются истинными компонентами Spring (т.е. с комментариями @Component
).
7. Я могу использовать MockMvc
MockMvc — хороший инструмент для интеграционного тестирования, который позволяет мне тестировать контроллеры Spring MVC без необходимости фактически запускать экземпляр приложения, сокращая общее время, необходимое для выполнения тестов. MockMvc работает только с Spring MVC; чтобы провести интеграционное тестирование классов ресурсов JAX-RS, вам нужно раскрутить экземпляры приложения и использовать TestRestTemplate
или библиотеку, подобную RestAssured.
Заключение
Как я уже говорил ранее, я ничего не имею против самого JAX-RS, и при этом я не чувствую, что Spring Boot не хватает хорошей поддержки JAX-RS, но я считаю, что если разработчик приложений работает с Spring Boot, нет веской причины использовать вместо этого JAX-RS. весны MVC. Если кто-то действительно знает какую-либо вескую причину, чтобы пойти с JAX-RS, дайте мне знать.