В предыдущих статьях, таких как эта , показано использование встроенной Jetty для REST-включения автономной Java-программы. В этих сообщениях отсутствовала важная функция для реальных приложений: упаковка в JAR, чтобы приложение работало вне Eclipse и не зависело от Maven и jetty: run. Чтобы это произошло, мы будем использовать Maven для создания исполняемого JAR-файла, который также включает все необходимые нам зависимости Jetty и Spring.
Цель этой работы — добраться до точки, где мы можем запустить пример приложения:
- Клонирование репозитория Git.
- Запуск
mvn package
. - Бег
java -jar target/webmvc-standalone.jar
Когда я начал добавлять необходимые биты в pom.xml
файл моего примера приложения , я ожидал относительно простого решения. Я получил относительно простое решение, которое полностью отличалось от того, что я ожидал. Поэтому я думаю, что стоит подробно обсудить, как работает это решение и что Maven делает для нас.
Наше желание создать исполняемый JAR-файл осложняется тем фактом, что мы хотим, чтобы наш проект Maven создавал WAR как пакет по умолчанию, чтобы при желании мы могли использовать этот код в веб-контейнере Java. Кроме того, мы вносим некоторую сложность, создавая один JAR со всеми зависимостями, потому что это вызывает конфликт файлов в Spring JAR. Я покажу, что я сделал для решения каждого из них.
Постройте как JAR, так и WAR
Основная идея здесь заключается в том, что мы хотим, чтобы Maven создавал как JAR-файл, так и WAR-файл на этапе «package». Наш pom.xml
файл указывает war
как упаковку для этого проекта, поэтому файл WAR будет создан, как ожидается. Нам нужно добавить файл JAR, не нарушая этого.
Я нашел отличный пост здесь , что у меня началась. Основная идея заключается в том, чтобы добавить следующее к pom.xml
Under build/plugins
:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>2.4</version> <executions> <execution> <id>package-jar</id> <phase>package</phase> <goals> <goal>jar</goal> </goals> </execution> </executions> </plugin>
Такое поведение мы бы получили «бесплатно», если бы использовали jar
упаковку pom.xml
. В execution
разделе завязывает к package
фазе , так что он работает во время процесса сборки по умолчанию. jar
Цель говорит плагину , что делать. Это дает нам базовый JAR с классами в обычном месте для JAR (а не в том WEB-INF/classes
виде, в котором они должны быть в файле WAR).
В то же время нам нужно учитывать тот факт, что плагин ресурсов Maven рассматривает только src/main/resources
каталог ресурсов, в то время как в нашем случае у нас есть файлы, src/main/webapp
которые также должны быть включены. Мы хотим скопировать эти ресурсы в целевой каталог, чтобы плагин JAR забрал их. (Это важное различие; типичный вопрос Maven «как включить дополнительные ресурсы в мой JAR?» Должен звучать так: «как мне получить дополнительные ресурсы, target
чтобы плагин JAR их подобрал?»)
Мы добавим это в build
раздел pom.xml
:
<resources> <resource> <directory>src/main/resources</directory> </resource> <resource> <directory>src/main/webapp</directory> </resource> </resources>
Это заставляет наш новый webmvc.jar
файл включать HTML, JavaScript и т. Д., Необходимые для нашего встроенного веб-приложения Jetty.
JAR с зависимостями
Затем мы создаем дополнительный JAR, который имеет правильную Main-Class
запись в MANIFEST.MF
файле и включает в себя необходимые зависимости, поэтому нам нужно отправить только один файл. Это делается с помощью подключаемого модуля Maven . Плагин сборки выполняет только переупаковку; вот почему мы должны были добавить артефакт JAR выше. Без этого артефакта JAR плагин сборки переупаковывает WAR, и мы получаем классы в WEB-INF/classes
. Это заставляет Java жаловаться на то, что он не может найти наш основной класс, когда мы пытаемся запустить JAR.
Плагин сборки поставляется с jar-with-dependencies
конфигурацией, которую можно использовать, просто добавив его descriptorRef
в соответствующий раздел pom.xml
, как показано в этом вопросе StackOverflow . Однако эта конфигурация не работает в нашем конкретном случае, поскольку в зависимости от Spring нам нужны файлы с перекрывающимися именами. В результате нам необходимо создать собственную конфигурацию сборки. К счастью, это довольно просто. Сначала мы добавим это в build/plugins
раздел pom.xml
:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>2.4</version> <configuration> <descriptors> <descriptor>src/assemble/distribution.xml</descriptor> </descriptors> <archive> <manifest> <mainClass>org.anvard.webmvc.server.EmbeddedServer</mainClass> </manifest> </archive> </configuration> <executions> <execution> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin>
Как и прежде, мы используем executions
раздел, чтобы убедиться, что он запускается автоматически во время package
. Мы также указываем основной класс для нашего приложения. Наконец, мы указываем плагин на наш файл конфигурации сборки, который находится в src/assemble
. Я представляю конфигурацию сборки ниже, но сначала нам нужно поговорить о проблеме с Spring JAR, которая сделала эту пользовательскую сборку необходимой.
Spring-схемы и обработчики
В этом примере приложения мы используем Spring WebMVC для предоставления REST API для обычных классов Java, как обсуждалось в этом посте . Используемый нами код Spring распространяется на несколько разных JAR-файлов.
В последних версиях Spring добавлена функция «настраиваемого пространства имен XML», которая позволяет расширять содержимое файла конфигурации Spring XML. Spring WebMVC и другие библиотеки Spring используют эту функцию для предоставления пользовательских тегов XML. Чтобы проанализировать файл XML с этими пользовательскими тегами, Spring должен иметь возможность сопоставить эти пользовательские пространства имен с обработчиками. Для этого Spring ожидает найти файлы, вызываемые spring.handlers
и находящиеся spring.schemas
в META-INF
каталоге любого JAR- файла, предоставляющего пользовательское пространство имен Spring.
Некоторые из Spring JAR-файлов, используемых этим приложением, включают файлы spring.handlers
и spring.schemas
файлы. Конечно, каждый JAR включает только свои собственные обработчики и схемы. Когда подключаемый модуль сборки Maven использует jar-with-dependencies
конфигурацию, «выигрывает» только одна копия этих файлов и превращает ее в исполняемый файл JAR.
Нам действительно нужен только один, spring.handlers
и spring.schemas
это конкатенация соответствующих файлов. Возможно, для этого есть какая-то магия мавенов, но я решил сделать это вручную, поскольку мой башфу намного больше моего мавенфу. Я добавил в src/assemble
каталог два файла, которые содержат объединенное содержимое различных файлов в JAR-файлах Spring.
Конфигурация сборки Maven
Файл сборки выглядит так:
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd"> <id>standalone</id> <formats> <format>jar</format> </formats> <baseDirectory></baseDirectory> <dependencySets> <dependencySet> <unpack>true</unpack> <unpackOptions> <excludes> <exclude>META-INF/spring.handlers</exclude> <exclude>META-INF/spring.schemas</exclude> </excludes> </unpackOptions> </dependencySet> </dependencySets> <files> <file> <source>src/assemble/spring.handlers</source> <outputDirectory>/META-INF</outputDirectory> <filtered>false</filtered> </file> <file> <source>src/assemble/spring.schemas</source> <outputDirectory>/META-INF</outputDirectory> <filtered>false</filtered> </file> </files> </assembly>
id
Будет использоваться , чтобы назвать эту сборку. Модуль baseDirectory
сообщает сборочному плагину, что части, которые он собирает, должны идти в корень нового JAR. (В противном случае они попадают в каталог, используя имя проекта, в данном случае «webapp».)
Следующие два раздела важны. Мы хотим исключить JAR-файлы spring.handlers
и spring.schemas
из Spring (или набор зависимостей). Вместо этого мы хотим явно включить их из нашего src/assemble
каталога и поместить их в нужное место. Мы также хотим, чтобы плагин сборки распаковывал JAR-файлы набора зависимостей, поэтому мы получаем файлы классов Java в нашем новом JAR-файле, а не просто JAR-files-inside-JAR-file, который не будет работать правильно.
Обратите внимание, что в Spring нет директивы, указывающей на включение всех зависимостей из набора зависимостей, включая транзитивные зависимости. Это значение по умолчанию, поэтому нам не нужно его указывать. Также по умолчанию включаются распакованные файлы из нашего собственного артефакта ( webmvc.jar
) в новый JAR.
Вывод
Реальное приложение, вероятно, выберет либо WAR-упаковку, либо исполняемую JAR-упаковку, и будет проще. Кроме того, можно было бы использовать несколько модулей Maven для создания JAR и встраивания его в WAR. Но интересно посмотреть, как реализовать более сложное решение, которое строит все, что нам нужно, из одного проекта.