Spring Boot War
Несколько часов назад вышел релиз Spring-Boot-1.3.0 Появилась возможность создавать war архивы через утилиту spring следующей командой
spring war example.war script.groovy
Рабочее веб приложение может уместиться в 1 твит. Никакие конфигурационные файлы не требуются. Можно запустить(или собрать в jar,war) следующий код:
// script.groovy. Да, кроме него ничего не надо
package ru.d10xa.springwar;
@RestController
@RequestMapping('/')
class Ctrl{
@RequestMapping
def map(){
return ['a':'b']
}
}
Не смотря на то, что это WAR, он всё так же является запускаемым.
Spring Boot умеет создавать запускаемые jar и war файлы благодаря проекту
spring-boot-loader.
По умолчанию, в java нет возможности загружать вложенные jar файлы. Этим занимаются загрузчики,
которых spring подкидывает в проект при сборке.
В манифест добавляется строка Main-Class: org.springframework.boot.loader.WarLauncher
(или JarLauncher).
В WarLauncher есть public static void main который занимается запуском нашего приложения.
Можно запускать как обычный jar:
java -jar example.war
Или на jetty:
wget http://central.maven.org/maven2/org/eclipse/jetty/jetty-runner/9.3.3.v20150827/jetty-runner-9.3.3.v20150827.jar
java -jar jetty-runner-9.3.3.v20150827.jar example.war
Или на wildfly
# Dockerfile
FROM jboss/wildfly
ADD build/app.war /opt/jboss/wildfly/standalone/deployments/
docker build --tag=wildfly-app-war .
docker run -it --rm -p 8080:8080 wildfly-app-war
Структура Jar
example.jar | +-META-INF | +-MANIFEST.MF +-org | +-springframework | +-boot | +-loader | +-<spring boot loader classes> +-com | +-mycompany | + project | +-YouClasses.class +-lib +-dependency1.jar +-dependency2.jar
Структура War
example.war | +-META-INF | +-MANIFEST.MF +-org | +-springframework | +-boot | +-loader | +-<spring boot loader classes> +-WEB-INF +-classes | +-com | +-mycompany | +-project | +-YouClasses.class +-lib | +-dependency1.jar | +-dependency2.jar +-lib-provided +-servlet-api.jar +-dependency3.jar
Основные отличия WAR от JAR, собранных spring-boot-cli
- Строка указывающая на главный класс в MANIFEST.MF (JarLauncher, WarLauncher)
- Для War нужен класс наследник SpringBootServletInitializer
- Структура (Layout) архивов отличается
- В случае с war, зависимость
tomcat
помещается отдельно от основных зависимостей (lib-provided)
Перепаковка из jar в war (bash)
Создадим класс, наследующийся от SpringBootServletInitializer (в той же директории, где script.groovy)
package ru.d10xa.springwar;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.web.SpringBootServletInitializer;
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Ctrl.class);
}
}
Выполним скрипт:
# Удаляем директорию build
rm -rf build
# Создаем структуру war архива в build/tmp
mkdir -p build/tmp build/tmp/WEB-INF/ build/tmp/WEB-INF/classes/templates build/tmp/WEB-INF/lib build/tmp/WEB-INF/lib-provided
# Утилитой spring-boot-cli создаем jar
spring jar build/app.jar App.groovy ServletInitializer.groovy
# Распаковываем jar
unzip build/app.jar -d build/extracted_jar
# Библиотеки для встроенного сервера закидываем в папку lib-provided, остальные в lib
cp -p $(find build/extracted_jar/lib -name '*tomcat*') build/tmp/WEB-INF/lib-provided
cp -p $(find build/extracted_jar/lib -not -name '*tomcat*') build/tmp/WEB-INF/lib
# Копируем META-INF и спринговые классы в корень будующего war
cp -r build/extracted_jar/META-INF/ build/extracted_jar/org/ build/tmp/
# В манифесте меняем Main-Class JarLauncher на WarLauncher
sed -i -- 's/JarLauncher/WarLauncher/g' build/tmp/META-INF/MANIFEST.MF
# Копируем классы из пакета ru в WEB-INF/classes
cp -r build/extracted_jar/ru/ build/tmp/WEB-INF/classes
# Архивируем без сжатия, и размещаем war рядом с jar
cd build/tmp
zip -r --compression-method=store app.war *
mv app.war ../
Для сравнения, соберем war утилитой spring-boot-cli
spring war build/spring-cli-app.war App.groovy
Сравним
groovy scripts/zipdiff.groovy build/app.war build/spring-cli-app.war
Добавление загрузчиков осуществляется в классе
ArchiveCommand.java
, где в зависимости от Layout
, классы попадают либо в корень архива (jar), либо в WEB-INF/classes/
(war).
Поэтому классы PackagedSpringApplicationLauncher и SpringApplicationLauncher при сравнении,
находятся в разных директориях. Но в classpath они всёравно попадают.
Отличается только SpringApplicationWebApplicationInitializer . Вместо него мы добавили ru/d10xa/springwar/ServletInitializer
---unique in build/app.war WEB-INF/classes/ru/d10xa/springwar/ServletInitializer.class org/springframework/boot/cli/app/SpringApplicationLauncher.class org/springframework/boot/cli/archive/PackagedSpringApplicationLauncher.class ---unique in build/spring-cli-app.war WEB-INF/classes/org/springframework/boot/cli/app/SpringApplicationLauncher.class WEB-INF/classes/org/springframework/boot/cli/app/SpringApplicationWebApplicationInitializer.class WEB-INF/classes/org/springframework/boot/cli/archive/PackagedSpringApplicationLauncher.class
spring war VS gradle build
Сборка с утилитой spring отличается от gradle наличием groovy в classpath + несколько вспомогательных классов (например, для тестов).
gradle clean build war
spring war example.war script.groovy
Классы и jar’ы которые были добавлены spring-boot-cli при сборке архива:
WEB-INF/classes/org/springframework/boot/cli/app/SpringApplicationLauncher.class WEB-INF/classes/org/springframework/boot/cli/app/SpringApplicationWebApplicationInitializer.class WEB-INF/classes/org/springframework/boot/cli/archive/PackagedSpringApplicationLauncher.class WEB-INF/lib/groovy-2.4.4.jar WEB-INF/lib/groovy-templates-2.4.4.jar WEB-INF/lib/groovy-xml-2.4.4.jar org/springframework/boot/groovy/DelegateTestRunner.class org/springframework/boot/groovy/DependencyManagementBom.class org/springframework/boot/groovy/EnableDeviceResolver.class org/springframework/boot/groovy/EnableGroovyTemplates.class org/springframework/boot/groovy/GroovyTemplate.class
А зачем вообще нужно собирать war? Issue
на гитхабе, в котором просили добавить сборку war был открыт больше года
и пул реквесты ни кто не присылал, хотя пофиксить это можно довольно просто.
Я собирал war потому, что на OpenShift PaaS можно было создать себе контейнер
с “каким нибудь” application-сервером в несколько кликов и потом через scp
кидать war в папку автодеплой.
Но, как оказалось, от application-сервера я получил только проблемы.
Локально с встроенным в jar томкатом всё работало хорошо, но
application-сервер на openshift некорректно преобразовывал русские буквы в url.
Теперь spring boot приложения я собираю только в jar.
Почитать как запускать jar на openshift можно в спринговой документации