Наводим порядок в разработке ПО вместе с maven. Часть 6

April 29, 2009Comments Off on Наводим порядок в разработке ПО вместе с maven. Часть 6

Я продолжаю начатый в прошлой статье рассказ о том, как maven умеет работать с многомодульными проектами. В качестве примера я пытаюсь создать enterprise приложение на java, состоящее трех модулей: business-logic (логика работы приложения), web-interface (пользовательский интерфейс) и, собирающий предыдущие две части в единое целое, модуль application.

Обсуждаемый пример демонстрирует парочку полезных приемов, которые пригодятся любому, кто занимается серьезным java-программированием и размышляет над тем стоит ли переходить на maven для ведения проекта и управления его жизненным циклом. Хотя я буду стараться рассказывать об сложных вещах простым языком, однако без общего представления о том, что такое ejb, enterprise, веб-модули, вам будет тяжело. В этом случае советую обратиться к какой-нибудь хорошей книжке по java ee 1.5 (EJB 3 in Action или Enterprise JavaBeans, 3.0). Напомню, что многомодульный проект представляет собой не более чем набор каталогов следующего вида: корневой каталог “testmultimodule”, внутри которого размещается главный pom-файл проекта. Этот pom-файл – просто перечисление списка модулей, образующих проект, и общих настроек плагинов, которые применимы для каждого из модулей. Каждый модуль, в свою очередь, – это подкаталог, внутри которого находится (куда уж без него) pom-файл модуля, с ссылкой на родительский проект. И еще в подкаталоге модуля размещается набор ресурсов приложения (исходный код, текстовые и графические файлы). В прошлый раз я закончил рассказ на том, что показал пример pom-файла для самого проекта и для первого модуля с бизнес-логикой – business-logic. Что касается оставшихся модулей (ejb и ear), то в них нет ничего особенного по сравнению с первым: и так и там мы описываем набор зависимостей, нужных для работы модуля, плюс выполняется настройка плагинов maven (очевидно, что каждый вид модулей нуждается в своем особенном наборе плагинов). Вот как будет выглядеть модуль с веб-интерфейсом:
  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  2.   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  3. <parent>
  4.   <artifactId>main</artifactId>
  5.   <groupId>${myapp.groupid}</groupId>
  6.   <version>${myapp.version}</version>
  7. </parent>
  8.  <modelVersion>4.0.0</modelVersion>
  9.  <groupId>${myapp.groupid}</groupId>
  10.  <artifactId>web-interface</artifactId>
  11.  <packaging>war</packaging>
  12.  <version>${myapp.version}</version>
  13.  <name>web interface layer</name>
  14.  <dependencies>
  15.    <dependency>
  16.     <groupId>${myapp.groupid}</groupId>
  17.     <artifactId>business-logic</artifactId>
  18.     <version>${myapp.version}</version>
  19.     <type>ejb</type> 
  20.    </dependency>
  21.  </dependencies>
  22.  <build>
  23.   <plugins>
  24.   <plugin>
  25.    <groupId>org.apache.maven.plugins</groupId>
  26.    <artifactId>maven-war-plugin</artifactId>
  27.    <configuration>
  28.     <webResources>
  29.      <resource>
  30.        <targetPath>WEB-INF/css</targetPath>
  31.        <directory>../styles</directory>
  32.        <includes> <include>**/*.css</include> <includes>
  33.       </resource>
  34.      </webResources>
  35.    </configuration>
  36.    </plugin>
  37.  </plugins>
  38.  </build>
  39. </project>
Хотя выглядит этот пример крайне громоздко, однако в почти все элементы pom-файла нам уже знакомы по предыдущим статьям. Во-первых, т.к. этот pom-файл является частью многомодульного проекта, то нам нужно указать ссылку на родительский проект и делает это элемент “parent”. Для задания конкретных значений имени группы артефактов и номера версии проекта (${myapp.groupid} и ${myapp.version}) я использую переменные, объявленные в родительском файле проекта (снова отсылка к прошлой статье серии). Следующим элементом pom-файла идет перечисление зависимостей “dependencies”. Я определил только одну зависимость – зависимость от модуля business-logic. Если бы я разрабатывал что-либо большее, чем простая демонстрация maven, то мне потребовались бы дополнительные библиотеки (например, веб-framework spring mvc или tapestry). Тем не менее, и на этом примере можно узнать кое-что новое. Давайте, выполним команду “m2 clean package” внутри каталога web-interface и посмотрим на то, как maven подготовит для нас файл веб-приложения, подготовленный для последующего размещения на веб-сервере. В каталоге target я нашел файл “web-interface-1.0.war” размером в добрые пять мегабайт. Из которых 99% занимает подкаталог WEB-INF/lib с библиотеками нужными для работы веб-приложения. Среди этих библиотек есть файл business-logic-1.0.jar (скомпилированный код модуля business-logic от которого мы заявили зависимость). Также в каталог WEB-INF/lib maven скопировал все файлы библиотек, от которых зависит модуль business-interface (а это hibernate и spring). С одной стороны это хорошо т.к. веб-модуль можно сразу инсталлировать на веб-сервер и мы не столкнемся с проблемами, когда какой-то библиотеки-зависимости не будет хватать. И все было бы замечательно, если бы я разрабатывал обычное web-приложение, а не ear приложение. Здесь же все три модуля должны работать совместно и не мешать друг другу. А что получилось? Сейчас веб-модуль даже не догадывается о том, что он работает в команде с ejb модулем (business-interface) и ear-модулем (application). По правилам разработки много-модульных enterprise приложений на java мы должны поместить внутрь каждого из модулей только те библиотеки, которые специфичны именно для этого модуля. А те библиотеки, которые общие для всех модулей, должны быть размещены внутри корня финального архива ear (файла в который упаковывается все приложение). Т.е. если бы наше веб-приложение использовало бы такую часть framework-а spring как mvc, то составляющие его библиотеки нужно было бы упаковать внутрь war-файла. А остальные части spring (core, jdbc, dao) или “совсем постороннюю” для веб и html библиотеку hibernate упаковывать в веб-приложение нельзя. Это довольно логично, т.к. ни логика приложения, ни нужные для работы этой логики библиотеки не являются неотъемлемой частью, именно, веб-интерфейса. Что касается, файла business-logic-1.0.jar, то его размещение внутри веб-приложения не лезет ни в какие ворота: и по правилам спецификации и по здравому рассуждению модуль бизнес-логики не является составной частью веб-приложения, а скорее его компаньоном. Ведь доступ к логике может выполняться не только с помощью веб-браузера, но и через “толстые” клиенты и веб-сервисы и т.д. Как ни удивительно, но вы уже знаете способ, которым можно исключить из веб-модуля, не являющиеся его частью, ресурсы. В четвертой статье серии я рассказывал об управлении зависимостями, о разрешении транзитивных зависимостей, и о таком понятии как scope или область действия артефакта-зависимости. Когда я подключил к веб-модулю зависимость business-logic, то, не указал область действия scope. А значит, попросил maven подключать к веб-модулю и business-logic и нужные для business-logic ресурсы (вспоминайте табличку с правилами “распространения” scope) всегда. Т.е. и при компиляции проекта и при упаковке модуля в war-файл. Для того, чтобы сообщить maven о том, что какой-то артефакт не нужно упаковывать в war-файл, нужно установить для этого артефакта область scope равной “provided” (будет предоставлен кем-то еще). Я добавлю к элементу зависимости (dependency) для артефакта business-logic новый тег provided, а затем пересоздам архив веб-приложение командой “m2 clean package”. Так мы увидим то, что теперь war-файл уменьшился в размере до десятка килобайт, а каталог WEB-INF/lib стал пустым - это нам и требовалось. На самом деле, при разработке веб-модулей рекомендуется помещать в каталог WEB-INF/lib так называемый тонкий клиент бизнес-слоя (тип артефакта ejb-client). Но углубляться в хитрости разработки ejb-приложений на java я не буду, так что если тема вам интересна, то прошу лучше перенести разговор в сферу электронной почты. Я завершу рассказ о создании модуля веб-интерфейса тем, что расскажу подробнее об плагине maven-war-plugin. Этот плагин автоматически вызывается naven, когда мы запускаем команду package или install, и выполняет упаковку исходных кодов модуля и нужных библиотек-зависимостей в распространяемую (ready-to-deploy) форму. Для тонкой настройки того, какие ресурсы должны быть включены в war-файл, следует использовать элемент configuration и элемент resource. Каждый ресурс состоит из набора характеристик: откуда копировать файлы (directory), по какой маске отбирать файлы (includes) и куда их нужно поместить – targetPath. Так в описанном выше примере я скопировал в каталог веб-приложения “WEB-INF/css” все файлы с расширением css из каталога “../styles”.

Рассказ о создании веб-приложения был бы не полон без рассказа об одной “ну очень страшной” проблеме в мире java-разработки (да и не только в java). Не секрет, что хорошая программа должна быть “покрыта” (проверена) тестами. Написав набор функциональных тестов, мы можем проверить логику работы приложения без “подъема” (запуска, инициализации) всего объема той инфраструктуры, которая будет нужна для production стадии (т.е. когда приложение развертывается на сервере заказчика). Однако, некоторые части приложения требует для своей проверки специфические ресурсы, которые нельзя или запрещено эмулировать (jms, распределенные транзакции т.д.). Т.е. часть проверок корректности нашего кода требует создания самодостаточного ear-приложения и развертывания его на сервере приложений. И почти всегда эта операция, ох, и долгая же. Как вывод, нужно максимально стремиться и разрабатывать и тестировать части, составляющие “большое” приложение, по отдельности. Например, если вы работаете над пользовательским интерфейсом и подгоняете css-стили под шаблон, то верхом глупости было бы выполнять весь рабочий цикл “собрать проект и развернуть его на страшно ресурсоемком и медленном сервере приложений”. Гораздо правильнее было бы отлаживать веб-часть проекта на быстром и легком веб-сервере. Здесь я четко противопоставляю с одной стороны сервера приложений, такие как jboss, glassfish, websphere, weblogic, jonas и веб-сервера (tomcat, jetty). К счастью, maven позволяет на основании информации хранящейся внутри веб-модуля запустить веб-сервер (jetty), который будет обслуживать все html/css/jsp файлы этого модуля. Более того, мы можем так настроить jetty, что она будет постоянно сканировать файлы веб-модуля на предмет их изменений и если это потребуется, то автоматически перезагрузит веб-приложение. Веб-сервер jetty будет скачан и настроен для работы самим maven, а с нашей стороны потребуется только добавить в pom-файл веб-модуля следующие строки конфигурации плагина “maven-war-plugin”:
  1. <plugin>
  2.  <groupId>org.mortbay.jetty</groupId>
  3.  <artifactId>maven-jetty-plugin</artifactId>
  4.  <version>6.1.10</version>
  5.  <configuration>
  6.   <scanIntervalSeconds>10</scanIntervalSeconds>
  7.   <connectors>
  8.     <connector implementation="org.mortbay.jetty.nio.SelectChannelConnector">
  9.       <port>9090</port>
  10.     </connector>
  11.    </connectors>
  12.  </configuration>
  13. </plugin>
Размещается описание jetty плагина там же где и настройки всех остальных плагинов модуля: внутри элементов project/build/plugins. Настройки плагина не сложны: главное указать версию плагина, затем интервал, с которым jetty будет сканировать файлы вашего проекта на предмет изменения (scanIntervalSeconds). В случае, если стандартный порт для java веб-серверов (8080) уже кем-то занят, то нужно подсказать jetty какой свободный номер порта она будет использовать. Если вас заинтересуют другие настройки плагина, то милости прошу на официальную веб-страницу разработчиков jetty (http://docs.codehaus.org/display/JETTY/Maven+Jetty+Plugin).

Теперь для того, чтобы выполнить запуск jetty мы заходим в каталог с веб-модулем и выполняем одну из следующих команд: jetty:run, jetty:run-war, jetty:run-exploded. Отличия между этими командами в том, как они конструируют список каталогов и файлов, составляющих веб-приложение. Так jetty-run собирает веб-приложение из статических ресурсов расположенных внутри каталога /src/main/webapp и скомпилированных классов приложения внутри каталога target/classes (компиляция выполняется автоматически). Кроме того, по упомянутой выше ссылке на странице с описанием плагина вы найдете примеры того, как в веб-приложение включить каталоги, размещенные за пределами веб-модуля. Команда “Jetty:run” наиболее удобна почти во всех случаях, за исключением тех редких ситуаций, когда целевое приложение нуждается в генерации war-архива. Например, ситуация когда вы решили привязать к фазе цикла package (создания war-файла) какие-то особые действия, например, собираете ресурсы не относящихся к самому модулю и упаковываете их внутрь war-файла. Если вы выполните команду “m2 jetty:run-war”, в этом случае перед запуском jetty будет выполнена и компиляция проекта и упаковка его в war-файл, который затем развертывается на веб-сервере jetty. В этом случае, jetty уже не может корректно отслеживать изменения в исходный файлах проекта и вы должны в случае необходимости перезагрузки jetty веб-приложения, изменить либо war-файл, либо pom-файл. Только так jetty может узнать о том, что нужно перекомпиляцию и переупаковку проекта, а затем его перезагрузить (работает медленнее, чем первый вариант). Третий способ запуска jetty с помощью команды “m2 jetty:run-exploded” практически идентичен второму способу: также выполняется компиляция и упаковка проекта, но собранный war-файл перед развертыванием на веб-сервере предварительно распаковывается в каталог target.

Описанный выше сценарий применим не только для простеньких веб-приложений, но и в ситуациях, когда у вас есть ejb-модули, требующие развертывания на настоящем сервере приложений. Тут можно либо использовать легковесные контейнеры ejb такие как openejb, OW2 EasyBeans. Эти микроконтейнеры представляют большую часть функциональности согласно спецификации java ee 5.0, но зато гораздо быстрее, чем их полновесные собратья (jboss, websphere, glassfish). Второй вариант предполагает одновременный запуск двух серверов: один из которых медленный сервер приложений, на котором размещена бизнес-логика приложения в форме ejb-модулей. А вот веб-интерфейс можно разместить и на быстром jetty-сервере. И это правильно: ведь мы сейчас активно меняем веб-интерфейс, те же css-стили, разметку страниц, а, значит, нуждается в быстром цикле “попробовать - посмотреть что получилось”.

Одна из самых “вкусных” вещей связанных с внедрением jetty в maven-проект – это совместное использование maven, jetty и jmeter для автоматизации тестирования кода. Jmeter – это популярный и открытый инструмент, предназначенный для имитации веб-запросов клиентов (количество “клиентов” и правила их поведения) к сайту. Затем полученные результаты могут быть визуализированы в виде таблиц, графиков, так чтобы не только определить работает ли приложение, но узнать насколько быстро “отзывается” каждая из веб-страниц. Весной прошлого года я написал серию статей посвященных тестированию веб-приложений и, в частности, там рассказывалось и об jmeter. В любом случае, если вас заинтересует вопрос интеграции maven и средств тестирования, то посетите следующую страничку http://wiki.apache.org/jakarta-jmeter/JMeterMavenPlugin Там вы найдете подробную инструкцию, как подключить jmeter плагин к maven проекту.

Чтобы полностью закрыть рассказ, посвященный созданию многомодульного java-приложения, остается только привести пример кода для последнего, третьего модуля - application. Файл pom для этого модуля похож почти один в один на pom-файлы для web и ejb модулей. Первое отличие в том, что нужно подключить к модулю как зависимости одновременно web и ejb модули, а значение атрибута packaging для файла проекта установить в “ear”. А второе отличие – это подключение к модулю плагина maven-ear-plugin. Разбираться с настройками плагина, нужно держа одновременно открытым и сайт с документацией по самому плагину (http://maven.apache.org/plugins/maven-ear-plugin/index.html) и сайт с документацией по тому серверу приложений, которое вы используете. Т.к. есть нюансы и в расположении файлов модулей и в генерации специальных конфигурационных файлов (к примеру, weblogic и jboss размещают файлы библиотек в разных каталогах). Вот пример моих настроек плагина для сервера приложений weblogic:
  1. <plugin>
  2.  <groupId>org.apache.maven.plugins</groupId>
  3.  <artifactId>maven-ear-plugin</artifactId>
  4.  <configuration>
  5.   <version>5</version>
  6.   <defaultLibBundleDir>APP-INF/lib</defaultLibBundleDir>
  7.   <modules>
  8.    <ejbModule>
  9.     <groupId>${myapp.groupid}</groupId>
  10.     <artifactId>business-logic</artifactId>
  11.     <bundleFileName>logic.jar</bundleFileName>
  12.    </ejbModule>
  13.    <webModule>
  14.     <groupId>${myapp.groupid}</groupId>
  15.     <artifactId>web-interface </artifactId>
  16.     <contextRoot>/renamed-context</contextRoot>
  17.    </webModule>
  18.    </modules>
  19.   </configuration>
  20. </plugin>
На самом деле для формирования правильного ear-файла даже не обязательно настраивать ear-плагин, т.к., создаваемый по-умолчанию, ear-файл вполне работоспособен. Тем не менее, я решил кое-что изменить. Во-первых, я сказал плагину “maven-ear-plugin”, что нужно выполнить генерацию конфигурационных файлов ear приложения в соответствии со спецификацией java ee 5.0 (за это отвечает элемент version). Файлы библиотек нужных для работы ejb-модуля должны быть скопированы в каталог “APP-INF/lib”. Затем внутри элемента ”modules” я перечислил образующие проект два модуля. Сначала модуль бизнес-логики (тег ejbModule), а затем модуль с веб-интерфесом (webModule). Кроме того, я решил переименовать файл ejb-модуля: ранее он назывался “business-interface-1.0.jar”, а теперь будет называться “logic.jar”. Файл war-модуля я решил не переименовывать, а изменить для него имя каталога, под которым на веб-сервере будет доступен интерфейс (значение по-умолчанию равно имени артефакта веб-модуля, но я решил его переопределить на “/renamed-context”). Если у вас возникло желание увидеть еще несколько примеров наборов pom-файлов для java enterprise приложений, то посетите следующие ссылки:

http://learntechnology.net/content/ejb/maven-ejb3.jsp или http://famvdploeg.com/blog/?p=41