Статьи

7 причин, по которым я не использую JAX-RS в веб-приложениях Spring Boot

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-dependenciesPOM , который включается в 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, дайте мне знать.