Статьи

Использование Maven для сборки с помощью Embedded Jetty

В предыдущих статьях, таких как  эта  , показано использование встроенной Jetty для REST-включения автономной Java-программы. В этих сообщениях отсутствовала важная функция для реальных приложений: упаковка в JAR, чтобы приложение работало вне Eclipse и не зависело от Maven и jetty: run. Чтобы это произошло, мы будем использовать Maven для создания исполняемого JAR-файла, который также включает все необходимые нам зависимости Jetty и Spring.

Цель этой работы — добраться до точки, где мы можем запустить пример приложения:

  1. Клонирование репозитория Git.
  2. Запуск  mvn package.
  3. Бег 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. Но интересно посмотреть, как реализовать более сложное решение, которое строит все, что нам нужно, из одного проекта.