Pull to refresh

Как писать на Spring в 2017

Reading time 10 min
Views 276K

В одной из классических статей для новичков, мелькавших недавно на Хабре, рассказывалось про создание базового Web приложения на Java. Все начиналось с сервлета, потом создания JSP страницы и, наконец, деплоймента в контейнер. Посмотрев на это свежим взглядом я понял, что для как раз для новичков это, наверняка, выглядит совершенно жутко — на фоне простых и понятных PHP или Node.js, где все просто — написал контроллер, вернул объект, он стал JSON или HTML. Чтобы немного развеять это ощущение, я решил написать "Гайд для новичков в Spring". Цель это статьи — показать, что создание Web приложений на Java, более того — на Spring Framework это не боль и мучительное продирание через web.xml, persistence.xml, beans.xml, и собирание приложения как карточного домика по кусочкам, а вполне себе быстрый и комфортный процесс. Аудитория — начинающие разработчики, разработчики на других языках, ну и те, кто видел Спринг в его не самые лучше времена.


Введение


В этой статье мы посмотрим, что включает в себя современный Спринг, как настроить локальное окружение для разработки Веб приложений, и создадим простое веб-приложение, которое берет данные из БД и отдает HTML страницу и JSON. Как ни странно, большинство статей (на русском языке) для начинающих, которые я нашел в топе поиска описывают и ручное создание контекста, и запуск приложения, и конфигурацию через XML — ничего из этого в современном Спринге делать, разумеется, не обязательно.


Что такое Spring?


Для начала пара слов, что же такое Spring. В настоящее время, под термином "Spring" часто подразумевают целое семейство проектов. В большинстве своем, они развиваются и курируются компанией Pivotal и силами сообщества. Ключевые (но не все) проекты семейства Spring это:


  • Spring Framework (или Spring Core)
    Ядро платформы, предоставляет базовые средства для создания приложений — управление компонентами (бинами, beans), внедрение зависимостей, MVC фреймворк, транзакции, базовый доступ к БД. В основном это низкоуровневые компоненты и абстракции. По сути, неявно используется всеми другими компонентами.


  • Spring MVC (часть Spring Framework)
    Стоит упомянуть отдельно, т.к. мы будем вести речь в основном о веб-приложениях. Оперирует понятиями контроллеров, маппингов запросов, различными HTTP абстракциями и т.п. Со Spring MVC интегрированы нормальные шаблонные движки, типа Thymeleaf, Freemaker, Mustache, плюс есть сторонние интеграции с кучей других. Так что никакого ужаса типа JSP или JSF писать не нужно.


  • Spring Data
    Доступ к данным: реляционные и нереляционные БД, KV хранилища и т.п.


  • Spring Cloud
    Много полезного для микросервисной архитектуры — service discovery, трасировка и диагностика, балансировщики запросов, circuit breaker-ы, роутеры и т.п.


  • Spring Security
    Авторизация и аутентификация, доступ к данным, методам и т.п. OAuth, LDAP, и куча разных провайдеров.


  • Spring Integration
    Обработка данных из разных источников. Если надо раз в час брать файл с ФТП, разбивать его на строки, которые потом фильтровать, а дальше отправлять в какую-то очередь — это к Spring Integration.

Типичное веб приложение скорее всего будет включать набор вроде Spring MVC, Data, Security. Ниже мы увидим, как это все работает вместе.


Особняком стоит отметить Spring Boot — это вишенка на торте (а некоторые думают, что собственно сам торт), которые позволяет избежать всего ужаса XML конфигурации. Boot позволяет быстро создать и сконфигурить (т.е. настроить зависимости между компонентами) приложение, упаковать его в исполняемый самодостаточный артефакт. Это то связующее звено, которое объединяет вместе набор компонентов в готовое приложение. Пару вещей, которые нужно знать про Spring Boot:


  • Он не использует кодогенерацию. Из кода, который генерится, присутствует только метод main. Если вы ищете утилиту для генерации приложений — это скорее JHipster
  • Не использует XML для конфигурации. Все конфигурится через аннотации
  • Используются автоконфигурации по максимуму. Если у вас добавлена зависимость на Mongo, и не указано, куда подключаться — Boot попробует localhost:27017
  • Используется convention over configuration. Для большинства конфигураций не нужно ничего настраивать
  • Его легко отодвинуть в сторону и "перекрыть" конфигурацию по умолчанию. Например, если в настройках указать хост для подключения к Монго — он автоматически перекроет localhost

Настройка окружения


Для того, чтобы создать простое приложение, знать, как создать проект Maven с нуля, как настроить плагины, чтобы создать JAR, какие бывают лейауты в JAR, как настроить Surefire для запуска тестов, как установить и запустить локально Tomcat, а уж тем более, как работает DispatcherServlet — совершенно не нужно.


Современное приложение на Spring создается в два шага:


  1. Идем на Spring Initializr.
  2. … и все, второго шага нет

Spring Initializr позволяет "набрать" в свое приложение нужных компонентов, которые потом Spring Boot (он автоматически включен во все проекты, созданные на Initializr) соберет воедино.


В качестве среды разработки подойдет что угодно, например бесплатная IntelliJ IDEA CE прекрасно справляется — просто импортируйте созданный pom.xml (Maven) или build.gradle (Gradle) файл в IDE.


Стоит отдельно отметить компонент Spring Boot который называется DevTools. Он решает проблему цикла локальной разработки, который раньше выглядел как:


  1. Собрать WAR локально
  2. Задеплоить его в Очень Крутой Энтерпрайз Аппликейшен Сервер на тестовом сервере, потому что настройка ОКЭАС локально требует недюжинной сноровки
  3. Выпить чашку кофе, т.е. ОКЭАС перезапустить приложение в лучшем случае через несколько минут
  4. Скопировать стектрейс ошибки
  5. Перейти на п. 1

В те древние времена даже родилась поговорка, что Spring это DSL для конвертации XML конфигов в стектрейсы.

С включенными Spring Boot DevTools цикл разработки сокращается до:


  1. Запустить приложение через зеленый треугольничек в IDEA

DevTools будут автоматом проверять изменения в скомпилированном коде или шаблонах, и очень быстро перезапускать (hot reload) только "боевую" часть приложения (как nodemon, если вы знакомы с миром node.js). Более того, DevTools включают интеграцию с Live Reload и после установки расширения в браузере, достаточно скомпилировать проект в IDEA, чтобы он автоматом обновился в браузере.


Разработка


Окей, пора приступать к практической части. Итак, наша цель — создать веб-приложение, которое отдает welcome страницу, обращается с нее же к собственному API, получает JSON с данными из базы и выводит их в таблицу.


Новый проект


Для начала, идем на start.spring.io и создаем проект с зависимостями Web, DevTools, JPA (доступ к реляционным базам), H2 (простая in-memory база), Mustache (движок шаблонов). Сгенерированный pom.xml импортируем в IDEA. Все, приложение готово к запуску! Можно его запустить из командной строки командой ./mvnw spring-boot:run или прямо из IDEA — запустив метод main. Да, серверов приложений, контейнеров и деплоймента не нужно.


Точнее, контейнер нужен — только он предоставлен и настроен Spring Boot-ом — используя Embedded Tomcat

Контроллер


Итак, наш следующий шаг — создать контроллер и вернуть "домашнюю" страницу. Код контроллера выглядит так просто, как и ожидается:


@Controller
public class IndexController {
    @GetMapping("/")
    public ModelAndView index() {
        Map<String, String> model = new HashMap<>();
        model.put("name", "Alexey");
        return new ModelAndView("index", model);
    }
}

Пара вещей, на которые стоит обратить внимание.


  1. Метод возвращает ModelAndView — дальше Spring знает, что нужно взять вью index.html из папки resources/templates (это соглашение по умолчанию) и передать туда модель
  2. Модель в нашем случае просто словарь, но это может быть и строго-типизированная модель (объект) тоже

С Котлин это бы выглядело еще лучше и проще, но это потребует введения сразу большого количества новых понятий — язык, фреймворк. Лучше начинать с малого.

Класс, помеченный как @Controller автоматически регистрируется в MVC роутере, а используя аннотации @(Get|Post|Put|Patch)Mapping можно регистрировать разные пути.


Все файлы из каталога resources/static/ считаются статическими, там можно хранить CSS и картинки.

Шаблон


Мы используем Mustache (Handlebar) синтаксис, поэтому шаблон очень напоминает обычный HTML


<!DOCTYPE html>

<html lang="en">

<body>
<h1>Welcome to Spring, {{ name }}</h1>
</body>

</html>

После компиляции проекта (⌘/Ctrl + F9) — можно сразу идти на http://localhost:8080 и увидеть созданную страницу.


Доступ к базе


Для начала, опишем нашу предметную область. Мы будем собирать статистику посещений — каждый раз, когда кто-то заходит на главную страницу, мы будем писать это в базу. Модель выглядит до крайности примитивно:


@Entity
public class Visit {

    @Id
    @GeneratedValue
    public Long id;

    public String description;
}

Предвидя череду комментариев "Как же без геттеров и сеттеров" и "Где же equals / hashCode" — эти элементы упущены сознательно с целью упрощения кода. Совершенно чудовищная ошибка дизайна Java которая заставляет писать эту ерунду (геттеры и методы сравнения), это, конечно, отдельный разговор. Котлин эту проблему, кстати, решает.

Мы снова очень активно используем аннотации — в этот раз из Spring Data (точнее, JPA — это дремучая спецификация для доступа к данным). Этот класс описывает модель с двумя полями, одно из которых генерится автоматически. По этому классу будет автоматически создана модель данных (таблицы) в БД.


Теперь для этой модели пора создать репозиторий. Это еще проще, чем контроллер.


@Repository
public interface VisitsRepository extends CrudRepository<Visit, Long> {
}

Все, репозиторий можно использовать для работы с базой — читать и писать записи. У внимательного читателя должен сработать WTF детектор — что здесь вообще происходит? Мы определяем интерфейс и внезапно он начинает работать с базой? Все так. Благодаря магии Spring Boot и Spring Data "под капотом" происходит следующее:


  • Увидев в зависимостях H2 (встраиваемая БД), Boot автоматически конфигурит DataSource (это ключевой компонент для подключения к базе) чтобы приложение работало с этой базой
  • Spring Data ищет всех наследников CrudRepository и автоматически генерит для них дефолтные реализации, которые включают базовые методы репозитория, типа findOne, findAll, save etc.
  • Spring автоматически конфигурит слой для доступа к данным — JPA (точнее, его реализацию Hibernate)
  • Благодаря аннотации @Repository этот компонент становится доступным в нашем приложении (и мы его используем через пару минут)

Чтобы использовать репозиторий в контроллере мы воспользуемся механизмом внедрения зависимостей, предоставляемый Spring Framework. Чтобы это сделать, как ни странно, нужно всего лишь объявить зависимость в нашем контроллере.


@Controller
public class IndexController {

    final VisitsRepository visitsRepository;

    public IndexController(VisitsRepository visitsRepository) {
        this.visitsRepository = visitsRepository;
    }
...
}

Увидев в нашем конструкторе параметр типа VisitRepository, Spring найдет созданный Spring Data-ой репозиторий и передаст его в конструктор.


Теперь можно писать в базу в методе контроллера.


@GetMapping("/")
    public ModelAndView index() {
        Map<String, String> model = new HashMap<>();
        model.put("name", "Alexey");

        Visit visit = new Visit();
        visit.description = String.format("Visited at %s", LocalDateTime.now());
        visitsRepository.save(visit);

        return new ModelAndView("index", model);
    }

REST контроллер


Следующий шаг — это вернуть все записи из базы в JSON формате, чтобы потом их можно было читать на клиенте.


Для REST в Spring есть отдельный тип контроллера который называется @RestController, код которого не сильно отличается от обычного контроллера.


@RestController
@RequestMapping("/api")
public class ApiController {

    final VisitsRepository visitsRepository;

    public ApiController(VisitsRepository visitsRepository) {
        this.visitsRepository = visitsRepository;
    }

    @GetMapping("/visits")
    public Iterable<Visit> getVisits() {
        return visitsRepository.findAll();
    }
}

На что обратить внимание:


  • В этот раз мы определяем "префикс" для всех методов контроллера использую базовый @RequestMapping
  • Внедрение зависимостей работает точно так же, как и для обычных контроллеров (как и вообще для всего в Spring)
  • Метод теперь возвращает не имя шаблона, а модель. Spring автоматически преобразует это в массив JSON объектов
  • Мы используем persistence модель для сериализации в JSON, что в общем случае не самая лучшая практика

Теперь при запросе http://localhost:8080/api/visits (предварительно скомпилировав проект и дав DevTools обновить приложение) мы получим JSON с нужными данными.


Клиентский код


Оставим за рамками этой статьи, пример можно увидеть в исходном коде. Цель этого кода — исключительно продемонстрировать как получить JSON данные с сервера, интеграции с клиентскими фреймворками React, Angular etc намеренно оставлены вне рамок этой статьи.


Тестирование


Spring так же предоставляет мощные средства для Integration и Unit тестирования приложения. Пример кода, который проверяет контроллер:


@Test
    public void indexControllerShouldReturnHtmlPage() throws Exception {
        mockMvc.perform(get("/"))
                .andExpect(status().isOk())
                .andExpect(content().string(containsString("Welcome to Spring")));
    }

Используя абстракции типа MockMvc можно легко тестировать внешний интерфейс приложения, в то же время имея доступ к его внутренностям. Например, можно целиком заменить компоненты приложения на моки (заглушки).


Аналогично для API тестов есть набор хелперов для проверки JsonPath выражений.


@Test
public void apiControllerShouldReturnVisits() throws Exception {
    mockMvc.perform(get("/"));

    mockMvc.perform(get("/api/visits"))
            .andExpect(jsonPath("$.*.description", iterableWithSize(1)));
}

Тестирование в Spring это все таки отдельная тема, поэтому мы не будем сильно на этом останавливаться сейчас.


Деплоймент


Чтобы собрать и запустить наше приложение в продакшене есть несколько вариантов.


  1. Задеплоить полученный JAR (или даже WAR) в сервлет контейнер, например Tomcat. Это не самый простой путь, его нужно выбирать только если у вас уже есть работающий сервлет контейнер или сервер приложений.
  2. Использовать магию Spring Boot. JAR файл, собранный используя плагин Spring Boot (который автоматически добавляется в проекты созданные через Spring Initializr), является полностью самодостаточным.

Таким образом сборка и запуск приложения выглядит как:


  1. ./mvnw package
  2. java -jar ./target/demo-0.0.1-SNAPSHOT.jar

Для деплоймента этого JAR файла не нужно ничего, кроме установленной Java (JRE). Это так называемый fat JAR — он включает в себя и встроенный сервлет контейнер (Tomcat по умолчанию) и фреймворк, и все библиотеки-зависимости. По сути, он является единственным артефактом деплоймтента — его можно просто копировать на целевой сервер и запускать там.


Более того, файл можно сделать "выполняемым" и запускать его просто из командной строки (Java, конечно, все равно необходима).


На базе этого файла можно легко создать Docker образ или установить его как демон. Больше деталей доступно в официальной документации.


Заключение


Получилось, все же, очень сжато — но уложить даже самый простой вводный курс по Spring в рамки одной статьи не очень просто. Надеюсь, это поможет кому-то сделать первый шаги в Spring-е, и хотя понять его фундаментальные концепции.


Как вы успели заметить, в тексте статьи много раз звучало слово "магия Spring". По сути своей, это очень "магический" фреймворк — даже взглянув на самую верхушку айсберга мы уже видели, что Spring много всего делает в фоне. Это является и плюсом, и минусом фреймворка. Плюс несомненно в том, что многие сложные вещи (очень многие) можно сделать одной аннотацией или зависимостью. Минус же это скрытая сложность — чтобы решить какие-то сложные проблемы, заставить фреймворк работать в крайних случаях или понимать все тонкости и аспекты нужно его неплохо знать.


Чтобы сделать этап "знать" как можно проще, Spring обладает отличной документацией, огромным сообществом, и чистыми исходниками, которые вполне можно читать. Если расположить Spring на шкале Рича Хики, он (Spring) несомненно попадет в easy, но уж точно не simple. Но для современного энтерпрайза (и не только энтерпрайза) он дает невероятные возможности чтобы получить production-ready приложение очень быстро и концентрироваться на логике приложения, а не инфраструктуры вокруг.


Ссылки


Tags:
Hubs:
+41
Comments 110
Comments Comments 110

Articles