четверг, 24 марта 2016 г.

Разработка приложений со Spring Boot. Часть 1: Основы разработки приложений со Spring Boot.

Этим постом я хочу начать серию статей о Spring Boot, в которой расскажу и покажу на наглядных примерах, как разрабатывать приложения используя Spring Boot и Spring Framework. За год активного использования этого фреймворка накопилось немало опыта, информации и нетривиальных моментов, которые приходилось решать, что пора бы и поделиться этим всем.


Что такое Spring Boot?


Spring Boot - это фреймворк для быстрой разработки приложений на основе Spring Framework и его компонентов, входящих в Spring Data, Spring Security и другие подпроекты. Spring Boot предоставляет огромное количество сконфигурированных компонентов, что позволяет сократить время, затрачиваемое на конфигурирование приложения и состредоточиться непосредственно на разработке, а так же упрощает работу с зависимостями. Ну и конечно Spring Boot позволяет легко и просто разрабатывать bootiful-приложения (так разработчики Spring называют standalone-приложения, основанные на Spring Boot). Но это всё лишь поверхностно, на самом деле возможности Spring Boot значительно мощнее. В целом, Spring Boot является идеальным инструментом для разработки микросервисов.


О демонстрационном проекте


В рамках цикла статей я буду демонстрировать примеры использования Spring Boot на примере разработки достаточно простого, но в то же время наглядного веб-приложения - сервисдеска/хелпдеска.

Для проекта потребуется JDK 1.8, Maven и любая среда разработки (в моём случае - NetBeans).


Подготовка проекта


Создадим новый maven-проект с упаковкой в WAR. В нашем случае это обусловлено тем, что в рамках статей будет продемонстрировано использование JSP для построения представлений, а так же будет продемонстрировано развёртывание приложения в сервере приложений. Если вам этого не нужно, то с лихвой хватит и JAR-упаковки. В любом случае, и JAR, и WAR являются исполняемыми при использовании Spring Boot.
Первое, что нужно сделать - добавить управление зависимостями, предоставляемое Spring Boot.
Это можно сделать двумя способами:

1. Указать в качестве родительского проекта spring-boot-starter-parent, если у проекта нет родительского:

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.3.3.RELEASE</version>
    </parent>

2. Указать dependencyManagement:

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>1.3.3.RELEASE</version>
                <scope>import</scope>
                <type>pom</type>
            </dependency>
        </dependencies>
    </dependencyManagement>

Это решит все возможные проблемы с версиями зависимостей, которые описаны в Spring Boot. Хоть этот шаг и необязателен, я рекомендую его проделывать при разработке приложений, так как разные стартеры одной версии Spring Boot могут ссылаться на разные версии одной и той же зависимости, что может привести к неожиданным и неочевидным ошибкам.


Второй шаг при подготовке проекта - указание maven-плагина, предоставляемого Spring Boot для сборки проекта:

1. При использовании родительского проекта:
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>

2. При использовании dependencyManagement:
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

Этот плагин найдёт класс, содержащий метод public static void main, пометит его главным и соберёт исполняемый JAR или WAR-файл, а так же скопирует в него все зависимости.

Ну и последний шаг, что бы получить работающее приложение - добавление в зависимости как минимум одного стартера (spring-boot-starter). В нашем случае понадобится spring-boot-starter-thymeleaf:
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

Spring Boot предоставляет большое количество стартеров практически на все случаи жизни. Стартер - это зависимость, содержащая все зависимости необходимые для реализации какой-либо функциональности в рамках разрабатывамого приложения. Так, например, что бы добавить приложению веб-функциональность, понадобится spring-boot-starter-web, который позволяет разрабатывать как стандартные веб-приложения, основанные на Spring WebMVC, так и REST-сервисы. Если же есть необходимость добавить приложению управление доступом, то можно добавить spring-boot-starter-security. Указанный мной стартер spring-boot-starter-thymeleaf содержит все зависимости, необходимые для разработки веб-приложения с использованием Thymeleaf в качестве фреймворка для построения представлений.

Теперь у нас всё готово для непосредственной разработки приложения на Spring Boot.

Разработка приложения

Самый простой пример - отображение представления в браузере без использования контроллера.

Первым делом создадим, класс, с которого будет начинаться работа нашего приложения:
package name.alexkosarev.bootdesk;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.web.SpringBootServletInitializer;

@SpringBootApplication
public class Application extends SpringBootServletInitializer {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args)
                .registerShutdownHook();
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(Application.class);
    }
}

Вкратце пробегусь по написанному коду:
  • Аннтоация @SpringBootApplication объединяет аннотации @Configuration, @EnableAutoConfiguration и @ComponentScan, объявляет Application классом-конфигурацией, включает автоматическую конфигурацию приложения и включает автоматический поиск компонентов в пакете name.alexkosarev.bootdesk и во всех вложенных.
  • SpringApplication.run(Application.class, args) запускает приложение при запуске при помощи java -jar
  • Класс Application расширяет класс SpringBootServletInitializer и переопределяет метод SpringApplicationBuilder configure(SpringApplicationBuilder builder) для запуска приложения при развёртывании в сервере приложении.

Кстати, на данном этапе приложение уже можно запустить. Но мы не добавили в приложение ничего, что можно было бы увидеть.
Создадим класс WebConfig в name.alexkosarev.bootdesk.config, в котором сконфигурируем перенаправление с / на /site/index и добавим отображение главной страницы при переходе на /site/index:
package name.alexkosarev.bootdesk.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addRedirectViewController("/", "/site/index");

        registry.addViewController("/site/index")
                .setViewName("site/index");
    }

}

Осталось добавить представление. Spring WebMVC по умолчанию в Spring Boot ищет представления в директории templates. Создадим html-файл в директории templates/site (как мы указали в WebConfig). Обратите внимание, что Thymeleaf в режиме HTML5, который выставлен по умолчанию, ожидает XML-валидный HTML (по сути XHTML). Если же вы привыкли писать простой HTML, то нужно проделать следующие два шага:
  1. Добавить в файл свойств application.properties, расположенном в ресурсах проекта, строку spring.thymeleaf.mode=LEGACYHTML5
  2. Добавить в зависимости проекта nekohtml из группы nekohtml:
            <dependency>
                <groupId>nekohtml</groupId>
                <artifactId>nekohtml</artifactId>
                <version>1.9.6.2</version>
            </dependency>
    

NekoHTML будет превращать обычный HTML-код в XML-валидный.

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

Сборка и запуск приложения


Для начала соберём приложение стандартным способом: командой mvn package или при помощи IDE. В директории target окажется два варианта WAR-архива: bootiful, с именем файла, заканчивающимся на .war, и обычный, заканчивающийся на .war.orginal. Обычный WAR-файл не является исполняемым, не содержит provided-зависимости, но может быть развёрнут в сервере приложений. Bootiful-вариант содержит все необходимые для работы зависимости, не может быть развёрнут в сервере приложений (попытка развернуть его в сервере приложений приведёт к ошибке), но может быть запущен как самостоятельное приложение при помощи команды java -jar bootdesk-1.0.0.war.

Кстати, если вы разрабатываете приложение на основе Spring Boot с упаковкой в JAR, то после сборки получите так же два варианта: bootiful, содержащий все необходимые для работы зависимости, и обычный вариант, зависимости для запуска которого нужно будет указывать при помощи -classpath.

Запустив приложение при помощи команды java -jar bootdesk-1.0.0.war мы увидим вывод нашего приложения.
Если мы откроем адрес http://localhost:8080, приложение сначала нас перенаправит на http://localhost:8080/site/index, а затем покажет нам содержимое index.html:


Spring Boot и JSP


По умолчанию ни один стартер из предоставляемых Spring Boot не предоставляет возможности работать с JSP. Но это решается достаточно просто:
1. В зависимости проекта нужно добавить tomcat-embed-jasper и jstl:
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>jstl</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>

Обратите внимание на scope=provided для tomcat-embed-jasper.

2. В WebConfig сконфигурировать ViewResolver:
    @Bean
    public ViewResolver viewResolver() {
        UrlBasedViewResolver urlBasedViewResolver = new UrlBasedViewResolver();
        urlBasedViewResolver.setViewClass(JstlView.class);
        urlBasedViewResolver.setPrefix("/WEB-INF/templates/");
        urlBasedViewResolver.setSuffix(".jspx");
        
        return urlBasedViewResolver;
    }

После этого можно будет использовать JSP-представления, а так же использовать Apache Tiles, если в этом будет необходимость.

В следующем посте я добавлю приложению немного функцональности, добавив взаимодействие с базой данных посредством Spring Data JPA. Так же я постараюсь в ближайшие несколько дней выложить проект в GitHub и опубликую видеоподкаст.

3 комментария:

  1. Очень полезный туториал. К тому же на русском языке.
    А я правильно понимаю, tomcat-embed-jasper должен быть provided даже в JAR-варианте приложения?

    ОтветитьУдалить
    Ответы
    1. Нет, в jar-варианте данная зависимость должна быть compile или runtime.

      Удалить
  2. у меня при добавлении provided страница открывает как текст, сборка war. Без provided норм но ресурсы не видит.

    ОтветитьУдалить