Перейти к основному содержимому

Запускаем Spring Boot приложение через Maven или исполняемый Jar?

· 7 мин. чтения

Задача: Медиана двух отсортированных массивов

Даны два отсортированных массива размерами n и m. Найдите медиану слияния этих двух массивов.
Временная сложность решения должна быть O(log(m + n)) ...

ANDROMEDA

1. Введение

В этом руководстве мы рассмотрим различия между запуском веб-приложения Spring Boot с помощью команды mvn spring-boot:run и запуском его после компиляции в пакет jar/war с помощью команды java -jar .

Предположим, вы уже знакомы с конфигурацией цели переупаковки Spring Boot. Дополнительные сведения по этой теме см. в статье Создание приложения Fat Jar с помощью Spring Boot .

2. Плагин Spring Boot Maven

При написании приложения Spring Boot плагин Spring Boot Maven является рекомендуемым инструментом для создания, тестирования и упаковки нашего кода.

Этот плагин поставляется с множеством удобных функций, таких как:

  • он разрешает нам правильные версии зависимостей

  • он может упаковать все наши зависимости (включая встроенный сервер приложений, если это необходимо) в один исполняемый файл jar/war, а также:

  • управлять для нас конфигурацией пути к классам, поэтому мы можем пропустить эту длинную опцию -cp в нашей команде java -jar

  • реализовать собственный ClassLoader для поиска и загрузки всех внешних jar-библиотек, теперь вложенных в пакет

  • автоматически найти метод main() и настроить его в манифесте, поэтому нам не нужно указывать основной класс в нашей команде java -jar

3. Запуск кода с помощью Maven в разобранном виде

Когда мы работаем над веб-приложением, мы можем использовать еще одну очень интересную функцию плагина Spring Boot Maven: возможность автоматического развертывания нашего веб-приложения на встроенном сервере приложений.

Нам нужна только одна зависимость, чтобы плагин знал, что мы хотим использовать Tomcat для запуска нашего кода:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

Теперь при выполнении команды mvn spring-boot:run в корневой папке нашего проекта плагин считывает конфигурацию pom и понимает, что нам требуется контейнер веб-приложения.

Выполнение команды mvn spring-boot:run запускает загрузку Apache Tomcat и инициализирует запуск Tomcat.

Давай попробуем:

$ mvn spring-boot:run
...
...
[INFO] --------------------< com.foreach:spring-boot-ops >--------------------
[INFO] Building spring-boot-ops 0.0.1-SNAPSHOT
[INFO] --------------------------------[ war ]---------------------------------
[INFO]
[INFO] >>> spring-boot-maven-plugin:2.1.3.RELEASE:run (default-cli) > test-compile @ spring-boot-ops >>>
Downloading from central: https://repo.maven.apache.org/maven2/org/apache/tomcat/embed/tomcat-embed-core/9.0.16/tomcat-embed-core-9.0.16.pom
Downloaded from central: https://repo.maven.apache.org/maven2/org/apache/tomcat/embed/tomcat-embed-core/9.0.16/tomcat-embed-core-9.0.16.pom (1.8 kB at 2.8 kB/s)
...
...
[INFO] --- spring-boot-maven-plugin:2.1.3.RELEASE:run (default-cli) @ spring-boot-ops ---
...
...
11:33:36.648 [main] INFO o.a.catalina.core.StandardService - Starting service [Tomcat]
11:33:36.649 [main] INFO o.a.catalina.core.StandardEngine - Starting Servlet engine: [Apache Tomcat/9.0.16]
...
...
11:33:36.952 [main] INFO o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring embedded WebApplicationContext
...
...
11:33:48.223 [main] INFO o.a.coyote.http11.Http11NioProtocol - Starting ProtocolHandler ["http-nio-8080"]
11:33:48.289 [main] INFO o.s.b.w.e.tomcat.TomcatWebServer - Tomcat started on port(s): 8080 (http) with context path ''
11:33:48.292 [main] INFO org.foreach.boot.Application - Started Application in 22.454 seconds (JVM running for 37.692)

Когда журнал показывает строку, содержащую «Запущенное приложение», наше веб-приложение готово для запроса через браузер по адресу http://localhost:8080/.

4. Запуск кода как отдельного упакованного приложения

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

К сожалению, если мы работаем с пакетом jar , основная цель пакета Maven не включает ни одну из внешних зависимостей.

Это означает, что мы можем использовать его только как библиотеку в более крупном проекте.

Чтобы обойти это ограничение, нам нужно использовать цель переупаковки плагина Maven Spring Boot , чтобы запустить наш jar/war как отдельное приложение.

4.1. Конфигурация

Обычно нам нужно только настроить плагин сборки:

<build>
<plugins>
...
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
...
</plugins>
</build>

Но наш пример проекта содержит более одного основного класса, поэтому мы должны указать Java, какой класс запускать, либо настроив плагин:

<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<configuration>
<mainClass>com.foreach.webjar.WebjarsdemoApplication</mainClass>
</configuration>
</execution>
</executions>
</plugin>

или установив свойство начального класса :

<properties>
<start-class>com.foreach.webjar.WebjarsdemoApplication</start-class>
</properties>

4.2. Запуск приложения

Теперь мы можем запустить наш пример войны с помощью двух простых команд:

$ mvn clean package spring-boot:repackage
$ java -jar target/spring-boot-ops.war

Подробнее о том, как запускать jar-файл, можно прочитать в нашей статье Запуск JAR-приложения с аргументами командной строки .

4.3. Внутри файла войны

Чтобы лучше понять, как упомянутая выше команда может запустить полноценное серверное приложение, мы можем заглянуть в наш файл spring-boot-ops.war .

Если мы распакуем его и заглянем внутрь, то обнаружим обычных подозреваемых:

  • META-INF с автоматически сгенерированным MANIFEST.MF
  • WEB-INF/classes , содержащий наши скомпилированные классы
  • WEB-INF/lib , в котором хранятся наши военные зависимости и встроенные jar-файлы Tomcat.

Это еще не все, так как есть несколько папок, специфичных для конфигурации нашего толстого пакета:

  • WEB-INF/lib-provided , содержащий внешние библиотеки, необходимые при запуске встроенного ПО, но не требуемые при развертывании.
  • org/springframework/boot/loader , который содержит пользовательский загрузчик классов Spring Boot — эта библиотека отвечает за загрузку наших внешних зависимостей и делает их доступными во время выполнения.

4.4. Внутри военного манифеста

Как упоминалось ранее, подключаемый модуль Maven Spring Boot находит основной класс и создает конфигурацию, необходимую для запуска команды java .

Полученный файл MANIFEST.MF содержит несколько дополнительных строк:

Start-Class: com.foreach.webjar.WebjarsdemoApplication
Main-Class: org.springframework.boot.loader.WarLauncher

В частности, мы можем заметить, что последний указывает, какую программу запуска загрузчика классов Spring Boot использовать.

4.5. Внутри JAR-файла

Из-за стратегии упаковки по умолчанию наш сценарий боевой упаковки не сильно отличается, независимо от того, используем ли мы плагин Spring Boot Maven или нет.

Чтобы лучше оценить преимущества плагина, мы можем попробовать изменить конфигурацию упаковки pom на jar и снова запустить mvn clean package .

Теперь мы можем заметить, что наша толстая банка организована немного иначе, чем наш предыдущий военный файл:

  • Все наши папки классов и ресурсов теперь находятся в BOOT-INF/classes.
  • BOOT-INF/lib содержит все внешние библиотеки

Без плагина папки lib не существовало бы, а все содержимое BOOT-INF/classes находилось бы в корне пакета.

4.6. Внутри Jar-манифеста

также МАНИФЕСТ. MF изменился, появились следующие дополнительные строки:

Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Spring-Boot-Version: 2.1.3.RELEASE
Main-Class: org.springframework.boot.loader.JarLauncher

Spring-Boot-Classes и Spring-Boot-Lib особенно интересны, так как они сообщают нам, где загрузчик классов будет искать классы и внешние библиотеки.

5. Как выбрать

При анализе инструментов необходимо учитывать цель, для которой эти инструменты создаются. Хотим ли мы упростить разработку или обеспечить плавное развертывание и переносимость? Давайте посмотрим на фазы, наиболее затронутые этим выбором.

5.1. Разработка

Как разработчики, мы часто тратим большую часть нашего времени на кодирование, не тратя много времени на настройку нашей среды для локального запуска кода. В простых приложениях это обычно не проблема. Но для более сложных проектов нам может потребоваться установить переменные среды, запустить серверы и заполнить базы данных.

Настраивать правильную среду каждый раз, когда мы хотим запустить приложение, было бы очень непрактично , особенно если одновременно должно работать более одной службы.

Вот где нам помогает запуск кода с помощью Maven. У нас уже есть вся кодовая база, проверенная локально, поэтому мы можем использовать файлы конфигурации и ресурсов pom. Мы можем установить переменные среды, создать базу данных в памяти и даже загрузить правильную версию сервера и развернуть наше приложение с помощью одной команды.

Даже в кодовой базе с несколькими модулями, где каждому модулю нужны разные переменные и версии сервера, мы можем легко запустить нужную среду с помощью профилей Maven.

5.2. Производство

Чем больше мы движемся к производству, тем больше разговор смещается в сторону стабильности и безопасности. Вот почему мы не можем применить процесс, используемый для нашей машины разработки, к серверу с живыми клиентами.

Запуск кода через Maven на этом этапе является плохой практикой по нескольким причинам:

  • Прежде всего, нам нужно установить Maven
  • Затем, только потому, что нам нужно скомпилировать код, нам нужен полный комплект Java Development Kit (JDK).
  • Затем мы должны скопировать кодовую базу на наш сервер, оставив весь наш проприетарный код в виде обычного текста.
  • Команда mvn должна выполнять все этапы жизненного цикла (найти исходники, скомпилировать и запустить).
  • Благодаря предыдущему пункту мы также будем тратить ЦП и, в случае с облачным сервером, деньги.
  • Maven порождает несколько процессов Java, каждый из которых использует память (по умолчанию каждый из них использует тот же объем памяти, что и родительский процесс).
  • Наконец, если у нас есть несколько серверов для развертывания, все вышеперечисленное повторяется на каждом из них.

Это всего лишь несколько причин, по которым поставка приложения в виде пакета более практична для производства .

6. Заключение

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

Исходный код, использованный в этой статье, доступен на GitHub .