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

April 15, 2009

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

Но перед тем как мы начнем рассматривать методики разделения больших проектов на составляющих их части (модули) следует рассказать о том, как различные известные среды разработки (IDE) поддерживают maven. Я пользуюсь в своей практике java-программиста такой известной ide как intellij idea (http://www.jetbrains.com/). На момент написания статьи самая “свежая” версия idea – это восьмая, которая поставляется вместе с “пачкой” плагинов, среди которых есть и maven-плагин. К примеру, после запуска idea, на первом экране вам будет предложены различные стратегии создания проекта. Среди которых, помимо создания “с нуля” или извлечения проекта из cvs/svn, есть и импорт в idea проекта созданного с помощью maven. Для этого вы на экране приветствия idea выбираете пункт “Create New Project”, затем “Import project from external model” -> “maven”. На появившемся диалоговом окне с настройками импорта maven-проекта (см. рис. 1)



вам нужно только указать месторасположение файла pom.xml, да и отметить checkbox-ы “Create idea modules for aggregate projects” и “Automatically re-import on project opening”. Первая опция важна в случае, когда ваш maven-проект состоит из нескольких модулей (вторая часть этой статьи как раз и посвящена этой теме). Опция “Automatically re-import on project openning” нужна для того, чтобы избежать проблемы синхронизации проекта maven и проекта idea. Т.к. после импорта проекта idea создаст “пачку” собственных файлов описывающих проект (с расширениями ipr, iml, iws) и именно эти файлы будут использоваться idea, для компиляции проекта. Это значит, в файлах ipr, iws, iml будут храниться дубли настроек maven (тот же список зависимостей). А теперь представьте себе, что нужно добавить в проект новую библиотеку. И где вы будете это делать, в idea-проекте или в maven-проекте? Для того, чтобы избежать потенциальной несогласованности проектов, idea может каждый раз при открытии проекта выполнять его синхронизацию с мастер-копией в виде maven-проекта (следовательно, все правки проекта вы будете выполнять только в pom-файле). Еще на диалоговом окне импорта maven-проекта советую обратить внимание на кнопку “Advanced”. После ее нажатия появится диалоговое окно с дополнительными настройками maven. Так вы можете указать путь к установленному у вас на компьютере maven, или изменить путь к каталогу репозитория (по умолчанию он располагается в "с:\Documents and Settings\UserName \.m2\repository"). В любом случае после окончания импорта idea создаст для вас проект, повторяющий структуру maven-модулей, и подключит к проекту все необходимые библиотеки-зависимости. Приятно, что каждая библиотека будет представлена тремя файлами: собственно, jar-файл с самой библиотекой, затем файл с исходными кодами библиотеки и файл документации (javadoc). Завершив импорт проекта, вы получите в свое распоряжение специальное окошко “Maven projects” (см. рис. 2)



из которого вы можете инициировать различные фазы жизненного цикла maven (compile, test, install), также мы можете выполнять запуск целей плагинов. Например, на картинке 2 выделен плагин jetty и его goal (цель) jettu-run. Инициировав эту цель, я “в один клик” запустил веб-сервер jetty и развернул на нем свое веб-приложение. Из “мелких вкусностей” могу упомянуть возможность привязать горячие клавиши (клавиатурные сокращения), которые можно привязать к фазам проекта, можно привязать перезапуск того же веб-сервера как действие после компиляции проекта и многое другое. Помимо intellij idea поддержка maven есть и в eclipse. Так выберите меню “File -> Import->Maven Projects”, затем укажите расположение pom-файла с проектом и, вуаля, eclipse сгенерирует набор файлов для проекта (.settings, .classpath, .project). Так вы получаете в свое распоряжение набор инструментов повторяющих почти один в один те, что были доступны в среде intellij idea. И, хоть мне как человеку, предпочитающему idea, не приятно это говорить, но eclipse представляет очень удобный визуальный редактор pom-файла. Т.е. вы можете не только править xml-код “ручками” но и просматривать различные аспекты maven-проекта и редактировать их в графической форме, к примеру я показал на рис.3



как выглядит окно с основными настройками проекта (название группы артефактов, название артефакта-проекта, его версия). А на рис. 4 показано дерево зависимостей артефактов, т.е. от каких артефактов наш проект зависит напрямую, а какие артефакты были разрешены для нашего проекта через поиск транзитивных зависимостей. В прошлой статье, я рассказывал о таком maven-плагине как dependency и его цели dependency:tree, формировавшей на экране “дерево” зависимостей артефактов, т.е. какой артефакт “потянул” какой артефакт и так далее. Это же дерево, но в более красивой, графической форме вы можете увидеть на рис. 4.



Некоторым недостатком (хотя это как посмотреть) поддержки maven в eclipse является “некая недосказанность”, т.е. рассматривать редактор pom-файла как средство быстрого изучения maven не стоит. И предварительно придется проштудировать “мануал” с основной терминологией maven и тем, что она означает. Специально, чтобы закрыть тему про maven и среды разработки я покажу то, как можно выполнить генерацию проекта для idea или для eclipse, что называется “вручную”. Генерацию проекта выполняет команда: “m2 idea:idea” или “m2 eclipse:eclipse”. Но мало вызвать плагин - нужно еще его правильно настроить. C основами настройки плагинов мы уже сталкивались ранее, во второй статье серии, когда я рассказывал о жизненном цикле проекта и плагинах, привязанных к фазам этого цикла. Maven позволяет по аналогии настроить и плагины, которые не участвуют непосредственно в жизненном цикле проекта, например, плагин idea и eclipse:
  1. <build>
  2.  <finalName>myapp</finalName>
  3.   <plugins>
  4.    <plugin>
  5.     <artifactId>maven-idea-plugin</artifactId>
  6.     <version>2.3-SNAPSHOT</version>
  7.     <configuration>
  8.       <downloadSources>true</downloadSources>
  9.       <downloadJavadocs>false</downloadJavadocs>
  10.       <dependenciesAsLibraries>true</dependenciesAsLibraries>
  11.       <useFullNames>false</useFullNames>
  12.      </configuration>
  13.    </plugin>
  14.  </plugins>
  15. </build>
Настройки плагина “idea” очевидны: каждая из зависимостей будет оформлена как отдельная библиотека, для каждой из библиотек будут загружены исходные коды (но не документация). Получившийся проект полностью готов к использованию в среде idea. Написав последний абзац о поддержке maven в idea и eclipse, я не удержался и решил проверить как с поддержкой maven в еще одной известной (хотя у меня с ней только “шапочное” знакомство) среде разработки netbeans (http://www.netbeans.org/). И оказалось, что помимо ожидаемой функции импорта maven-проекта в среду netbeans, есть еще и поддержка создания “с нуля” maven-проекта. Так выбрав пункт меню File -> New project -> Maven вы увидите диалоговое окно создания нового проекта на основании следующих заготовок: Web Application, EJB module, Enterprise Application. Как видите, поддержка maven есть в наиболее популярных средах разработки и это только радует.

До сего момента я упорно отмалчивался о том, как организовать много-модульный проект maven. Т.е. проект, состоящий из нескольких частей, когда та же бизнес-логика должна быть размещена отдельно от веб-модуля, формирующего веб-интерфейс приложения. Формально, мульти-модульный проект - это каталог, внутри которого находится pom-файл, содержимого которого сводится не к перечислению библиотек-зависимостей, а просто содержит перечисление модулей. Каждый из модулей проекта - это еще один подкаталог внутри родительского каталога, содержащий файл pom с описанием места, которое данный модуль занимает среди остальных модулей проект (т.е. того, от каких других модулей он зависит), а также перечня обычных зависимостей библиотек и плагинов. Очевидно, что каждый из модулей имеет собственный набор плагинов, привязанных к определенным фазам жизненного цикла проекта и модуля в его составе. Давайте создадим классический проект ear-приложения из трех модулей: ejb, war, ear. В практике количество модулей всегда больше, чем количество автономных частей приложения. Так модуль вполне может иметь пустой набор исходных кодов или ресурсов, и не содержать ни java-кода, ни html-страниц, ничего. Такой модуль-пустышка ценен тем, что внутри его мы описываем набор библиотек-зависимостей (например, библиотеки нужные для работы hibernate). А затем другой модуль (например, ejb) ссылается на модуль-пустышку и получает в свое распоряжение все зависимости, описанные внутри модуля-библиотеки. Такой подход необходим для того, чтобы избежать дубляжей зависимостей. К примеру, у вас в проекте два модуля ejb и для каждого из них нужна библиотека hibernate. Если вы будете описывать составляющие hibernate зависимости по отдельности в каждом из двух модулей, то очень скоро станет вопрос о поддержании их согласованностей, т.е. одинаковых списков составляющих hibernate артефактов и номеров их версией. Более подробно к вопросу создания модулей-пустышек я вернусь позже. А пока попробуем создать многомодульный проект и начнем с того, что внутри каталога testmultimodule я размещу pom файл проекта следующего вида:
  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.   <modelVersion>4.0.0</modelVersion>
  4.   <groupId>${myapp.groupid}</groupId>
  5.   <artifactId>main</artifactId>
  6.   <packaging>pom</packaging>
  7.   <version>${myapp.version}</version>
  8.   <name>main</name>
  9.   <modules>
  10.    <module>application</module>
  11.    <module>business-logic</module>
  12.    <module>web-interface</module>
  13.   </modules>
  14.   <build>
  15.    <pluginManagement>
  16.     <plugins>
  17.       <plugin>
  18.         <groupId>org.apache.maven.plugins</groupId>
  19.         <artifactId>maven-compiler-plugin</artifactId>
  20.         <configuration>
  21.           < source > 1.5 < / source > <!-- пробелы добавлены специально из-за особенностей форматирования mediawiki -->
  22.           <target>1.5</target>
  23.         </configuration>
  24.       </plugin>
  25.     </plugins>
  26.    </pluginManagement>
  27.   </build>
  28.   <!-- Project properties -->
  29.  <properties>
  30.    <myapp.groupid>test01</myapp.groupid>
  31.    <myapp.version>1.0</myapp.version>
  32.    <myapp.finalname>${artifactId}-${myapp.version}</myapp.finalname>
  33.  </properties>
  34. </project>
Если вы вспомните примеры файлов pom, которые я приводил в прошлых статьях серии, то сразу увидите отличия. Так даже указание того, к какой группе артефактов относится мой проект, указано каким-то необычный образом: ${myapp.groupid}. Что означают все эти значки “$” и фигурных скобок. Все дело в том, что раз мы создаем мульти-модульный проект, состоящих из нескольких pom-файлов, то нужно с самого начала позаботиться о том, чтобы избежать дубляжей. К примеру, название проекта, его версия будет повторяться во всех четырех (одном родительском и трех дочерних) проектах. А раз так, то почему бы мне не создать специальные переменные placeholder-ы, на которые можно сослаться в любом месте любого из pom-файлов. И действительно, в самом низу файла проекта внутри секции “properties” я создал три переменные “myapp.groupid”, “myapp.version” и “myapp.finalname”. На эти имена переменных я и ссылаюсь при объявлении maven-координат проекта (подробнее об maven-координатах см. часть 4). Обратите внимание на то, что значение элемента packaging равно pom, а не jar или ear. Считается, что сам многомодульный проект не производит никакого конечного продукта, а всего лишь служит для агрегации в единое целое других модулей, а вот они и производят конечный продукт (те же ejb, war, ear-модули). Есть правило, что настройки, определенные в родительском pom-файле, распространяются и на дочерние файлы. Этим я решил воспользоваться для того, чтобы не настраивать для каждого из модулей в отдельности параметры компиляции. Так я создал элемент build, внутри которого определил параметры для плагина “maven-compiler-plugin”, в частности версию используемого компилятора. Если вы хотите узнать больше о настройке плагинов, привязанных к фазам жизненного цикла, то обратитесь ко второй статье серии. Самое главное в файле проекта это перечисление списка модулей, которые его образуют, и делается это с помощью элемента modules. Как я и обещал, у меня получилось три дочерних модуля: business-logic (ejb-бины с логикой работы приложения), модуль web-interface хранит набор html-страниц, а модуль application служит для объединения предыдущих двух модулей в единое целое. Вдумчивый читатель тут же спросит: а важен ли порядок, под которым я поместил имена модулей внутри элемента modules? Ведь модули зависят друг от друга и очевидно, что сначала нужно выполнить компиляцию модуля с бизнес-логикой и только затем модуля с веб-интерфейсом, а затем результаты их работы разместить внутри ear-модуля. Формально, порядок размещения не важен, т.к. внутри файлов pom, описывающих дочерние модули вы будете явно декларировать список нужных для них зависимостей, а зависимость это не только “обычная” библиотека, но и модуль проекта. Так что maven самостоятельно перед запуском проекта на выполнение отсортирует модули в нужном порядке. Теперь перейдем к созданию дочерних подпроектов, и первым шагом я создам подкаталог business-logic со следующей структурой: подкаталоги src/main/java и src/main/resources (каталог для тестов я решил пропустить). Также внутрь подкаталога business-logic я поместил pom-файл модуля:
  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. <modelVersion>4.0.0</modelVersion>
  4.  <parent>
  5.  <artifactId>main</artifactId>
  6.   <groupId>${myapp.groupid}</groupId>
  7.   <version>${myapp.version}</version>
  8.  </parent>
  9. <groupId>${myapp.groupid}</groupId>
  10.  <artifactId>business-logic</artifactId>
  11.  <packaging>ejb</packaging>
  12.  <version>${myapp.version}</version>
  13.  <name>ejb container</name>
  14.  
  15.  <dependencies>
  16.   <dependency>
  17.    <groupId>org.apache.geronimo.specs</groupId>
  18.    <artifactId>geronimo-ejb_3.0_spec</artifactId>
  19.    <version>1.0.1</version>
  20.    </dependency>
  21.   <dependency>
  22.    <groupId>org.hibernate</groupId>
  23.    <artifactId>hibernate</artifactId>
  24.    <version>3.2.6.ga</version>
  25.   </dependency>
  26.  </dependencies>
  27.   <build>
  28.   <plugins>
  29.    <plugin>
  30.     <groupId>org.apache.maven.plugins</groupId>
  31.     <artifactId>maven-ejb-plugin</artifactId>
  32.     <configuration> 
  33.       <ejbVersion>3.0</ejbVersion>
  34.     </configuration>
  35.   </plugin>
  36.   </plugins>
  37.   </build>
  38. </project>
Для того, чтобы maven знал о том, что данный модуль “не просто так”, а является дочерним по отношению к другому проекту, то я в самом начале pom-файла разместил ссылку на родительский модуль внутри элемента parent. Здесь и далее при описании группы артефакта модуля и его версии я использую объявленные в родительском pom-файле переменные ${myapp.groupid} и ${myapp.version}. Что касается списка зависимостей модуля от внешних библиотек, то я подключил только hibernate и особую api-библиотеку geronimo-ejb_3.0_spec. Внутри этой библиотеки находятся описания аннотаций нужных для разработки ejb-компонентов (@Stateless, @Remote и т.д.). Полезным будет сказать, что в рамках проекта geronimo разработаны api-спецификации для многих других java-стандартов (geronimo-jms_1.1_spec, geronimo-servlet_2.5_spec, geronimo-jta_1.1_spec, geronimo-jaxws_2.1_spec …). Завершающим штрихом будет настройка maven, так чтобы он знал, что мы разрабатываем свой код внутри модуля business-logic согласно третьей редакции спецификации ejb. И делается это через настройку специального maven плагина maven-ejb-plugin и его параметра ejbVersion. На этом я заканчиваю рассказ о создании ejb-слоя приложения, а что касается веб-интерфейса (модуля web-interface), то это тема следующей статьи.