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

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

Я продолжаю рассказ об maven – инструменте, с помощью которого мы можем организовать унифицированное, не зависящее от конкретной среды разработки (IDE) представление проекта java. В прошлых двух статьях я пробежался по основным “вкусностям” maven: управление зависимостями проекта, способности maven загружать из internet и сохранять в локальном репозитории артефакты. Также я рассказал о жизненном цикле maven, о том из каких фаз он состоит, и как мы можем сами инициировать определенные этапы из “жизни” проекта. Все это было, и было, скажем честно, очень поверхностно: некоторые из аспектов, например, упаковка проекта были практически не затронуты, и сегодня пришло время это исправить. Я снова попытаюсь создать проект maven “с нуля” и пройтись по всем его шагам, вот только сделаю это более основательно и подробно.

Создание проекта начинается с создания заготовки: структуры каталогов и файла pom, в котором мы описываем нужные для проекта библиотеки-зависимости. В общем случае, количество подобных библиотек довольно велико. И если вы, к примеру, хотите создать большой проект, в котором есть и веб-интерфейс в виде war-модулей, и логика приложений в виде jar и ejb-модулей, то вам нужно настроить большое количество зависимостей. Нужно настроить плагины, обрабатывающие специфические для модуля ресурсы. Например, для web-модуля, нужно грамотно настроить список библиотек, которые будут помещены в папку WEB-INF/lib, для ear-модулей, нужно настроить генерацию специальных файлов-дескрипторов, специфических для конкретных серверов приложений. Я могу сколь угодно долго перечислять подобные “очень специальные” задачи, но суть одна: количество действий по настройке проекта даже с помощью maven все равно очень велико и поэтому появились шаблоны проектов или их заготовки. Эти заготовки проектов называются archetypes. Управление архетипами автоматизировано плагином “Maven Archetype Plugin”. К примеру, для создания нового проекта я выполняю команду “m2 archetype:generate”. После этого на экране появится огромный список перечисления проектов-заготовок, на основании которых я могу создать и свой проект. К примеру, там есть заготовки проектов, построенных на использовании технологий “Hibernate, Spring and JSF”, еще есть “Hibernate, Spring and Spring MVC”, “Hibernate, Spring and Struts 2”, “Hibernate, Spring and Tapestry 4”, “Hibernate and Spring and XFire” и еще множество других наборов технологий. Так в моем случае список доступных вариантов составил 44 позиции. Тут я сделаю небольшое отступление и могу посоветовать, в особенности начинающему java-программисту, обратить свое внимание на проект AppFuse (http://appfuse.org/). Назначение этого проекта – собрать в единое место сведения (документацию) и ресурсы (те же “комплекты” библиотек) нужные для быстрого старта нового проекта построенного на базе “стека” технологий. Т.е. цепочки инструментов, начиная от слоя хранения данных, до слоя их визуализации. Конечно, у меня с вами список доступных шаблонов проектов может отличаться, но все же обращу внимание на столь занимательный факт как “confluence-plugin-archetype (Atlassian Confluence plugin archetype)”. Этот плагин (шаблон проекта) нас интересует не сам по себе, а больше как проявление интересной тенденции. Для тех, кто ни разу не слышал об “confluence”, то кратко расскажу, что это продукт компании Atlassian, которая “съела целую стаю собак” на разработке различных инструментов помогающих управлять проектами. Во-первых, ведение списка задач проекта и багов в jira (я про jira писал серию статей еще год назад). Затем идет confluence – популярная система представления документации и базы знаний по проекту (ближайший аналог такого инструмента как mediawiki), с множеством “заточек” именно под разработку проектов программного обеспечения. Еще Atlassian создает fisheye (инструмент наблюдения за состоянием проекта в cvs-репозитории), crucible (инструмента аудита кода и оценки его качества), bamboo (continuous integration server, подобрать адекватный и короткий русский перевод этого термина мне очень тяжело). В предыдущем абзаце помимо краткого упоминания об инструментах, о которых мне очень хотелось бы когда-нибудь вам рассказать, главное другое: то, что многие компании и индивидуальные разработчики ПО, предлагают свои заготовки проектов maven. Т.е. посещая сайт условной компании “X” вы можете на странице “загрузить библиотеку” найти не только ссылки на архивы с библиотеками, а одну единственную строку “m2 archetype:generate” (конечно, более длинную с указанием дополнительных опций-настроек проекта). И вы можете у себя на компьютере выполнить команду “m2 archetype:generate”. К примеру, для упомянутого чуть ранее appfuse, я нашел на их сайте следующую строку:
mvn archetype:create -DarchetypeGroupId=org.appfuse.archetypes 
  -DarchetypeArtifactId=appfuse-basic-jsf
  -DremoteRepositories=http://static.appfuse.org/releases 
  -DarchetypeVersion=2.0.2 
  -DgroupId=com.mycompany.app 
  -DartifactId=myproject
Затем maven для вас загрузит из internet и файлы библиотек, причем “умно”, ведь не нужно тянуть все то огромное количество файлов, которые уже есть у вас в репозитории. Следующим шагом maven создаст заготовку проекта с каким-никаким но наполнением, которое можно тут же скомпилировать и запустить “поиграться”. Подобная тенденция появления заготовок для “быстрого старта” меня крайне радует, более того я покажу то, как можно создать собственный, управляемый maven, проект-заготовку.

Возвратимся назад к генерации проекта. Выполнив команду “m2 archetype:generate” мы попали на первый “экран” мастера создания нового проекта, там мы выберем в списке вариантов “maven-archetype-quickstart” (я решил пока создать простенький java-проект, не веб и не ejb). Идя по шагам мастера, мы указываем такие параметры будущего проекта как название проекта-артефакта, затем название группы артефактов, к которой он относится, затем идет номер проекта. Если вы посмотрите внимательно еще раз на приведенную выше строку пример вызова команды “mvn archetype:create”, то увидите, что те же параметры проекта указаны как значения переменных через “-D”. Завершающим шагом создания проекта будет окошко подтверждения, где мы должны согласиться с тем, что все указанные на предыдущих шагах сведения верны и проект будет полностью создан и готов к дальнейшей разработке.

Теперь рассмотрим подробнее такой аспект проекта как репозитории артефактов. Нужные для работы проекта зависимости-библиотеки-артефакты должны быть автоматически загружаться на ваш компьютер, в папку “.m2/repository” из internet. “Из коробки” maven знает о существовании репозитория размещенного по адресу http://repo1.maven.org/maven2. В практике этого не достаточно, т.к. часть артефактов либо мало известна и еще “не заслужила” права быть размещенной в этом репозитории, либо проект распространяется по лицензии запрещающей размещение двоичных файлов (jar-библиотек) в каких-либо public репозиториях. Либо существует запаздывание копирования новых версий артефактов-проектов из репозитория поддерживаемого непосредственно разработчиком библиотеки в public-репозиторий. Одним словом, причин много, а нам стоит научиться настраивать список используемых в проекте public-репозиторий. Для этого в pom-файле проекта нужно создать секцию repositories, внутри которой мы задаем список репозиториев, из которых maven будет загружать зависимости. Каждый репозиторий представляется набором характеристик, основные из которых это название репозитория (name) и адрес репозиторий (url).
  1. <repositories>
  2.   <repository>
  3.     <id>main.1</id>
  4.     <name>official maven repository</name>
  5.     <url>http://repo1.maven.org/maven2</url>
  6.   </repository>
  7. </repositories>
Аналогично, для хранения списка плагинов мы задаем перечень репозиториев внутри элементов pluginRepositories и pluginRepository.
  1. <pluginRepositories>
  2.   <pluginRepository>
  3.     <id>main.1</id>
  4.     <name>official maven repository</name>
  5.     <url>http://repo1.maven.org/maven2</url>
  6.   </pluginRepository>
  7. </pluginRepositories>
Для каждого репозитория (основных артефактов и плагинов) можно задать сведения об еще двух характеристиках. Насколько они важны судить вам, но пару слов сказать стоит. Когда мы нумеруем артефакты, задаем их номера версий, то можем задать номер, например, так: 1.1.0 –SNAPSHOT. На конце номера артефакта присутствует слово SNAPSHOT, это значит что данный артефакт находится в процессе разработки, а следовательно, его код и функциональность меняются чуть ли не каждый день. Таким образом нужно четко отделять стабильные версии артефактов от не стабильных. Связываясь с нестабильными нужно быть готовыми к тому, что их поведение может измениться и наш проект, использующий такой артефакт перестанет компилироваться. Следовательно, нужно определиться с вопросом: нужно ли обновлять из репозитория артефакт, ведь его номер формально остался неизменным. Поэтому для каждого из репозиториев и repository и pluginRepository есть элементы releases и snapshots. Устройство каждого из элементов идентично и управляет тем, как с помощью этого репозитория будут обрабатываться “стабильные” и “snapshot” артефакты, например:
  1. <snapshots>
  2.   <enabled>true</enabled>
  3.   <checksumPolicy>fail</checksumPolicy>
  4.   <updatePolicy>always</updatePolicy>
  5. </snapshots>
Итак, репозиторий может использоваться для загрузки snapshot-артефактов, политика проверки наличия новых артефактов в репозитории “проверять всегда”. Более полезен режим “interval:1000” – он значит, что обновления нужно проверять каждые 1000 минут. Параметр checksumPolicy служит для решения одной неприятной проблемы связанной с возможными сбоями при загрузке артефактов. Посмотрите еще раз на содержимое каталога “.m2/repository”и на то, как хранится любой из артефактов. Как видите, в каталоге артефакта, помимо самого файла (в формате jar, war, zip) хранится файл pom, с описанием характеристик артефакта (о нем подробнее чуть позже) и пара файлов с расширениями sha1. Я думаю, что вы знаете в общих чертах устройство и назначение таких алгоритмов как crc32, md5, sha1 – все они формируют на основании произвольной длины строки (или целого файла) короткое число. Например, для sha1 это число занимает 40 байт и может выглядеть, например, так fb60123c10c469795c7cd349f21e2b98e02eaefb. Если исходный файл в ходе загрузки через internet получил повреждения, то вычисленное число sha1 после загрузки будет отличаться от сохраненного числа на сервере. А, следовательно, maven должен отвергнуть такой артефакт и попытаться загрузить его из другого репозитория. Значение по-умолчанию для checksumPolicy равно warn, это значит, что выдается предупреждение об ошибке, но сам артефакт не отвергается. У меня пару раз были неприятные ситуации, когда на слабом модемном соединении артефакт “бился”, установленная политика warn игнорировала этот факт. А затем попытка компиляции проекта завершалась неудачей и разобраться в ее причине было не легко, так что уж лучше fail. С другой стороны, мне попадались (хотя и крайне редко) ситуации, когда разработчики артефактов не соблюдали соответствие между файлом артефакта и его sha1-снимком. Как они умудрялись это сделать, если maven при установке артефакта в репозиторий автоматически обновляет его sha1-код – загадка. Упомянув про установку артефактов, я поднял один неприятный вопрос. Большой ложкой дегтя в бочке с maven-медом является тот факт, что maven не является все еще стандартом де-факто для многих библиотеки и framework-ов. В таком случае очень резко ощущаешь диссонанс с тем сколько усилий будет требуется для подключения такой библиотеки. Плюс, приходится тратить драгоценное время на детальный анализ нужных для библиотеки дополнительных библиотек, того какие у них версии и попыток все это согласовать с уже построенной инфраструктурой проекта maven. Итак, если какой-то проект не доступен в виде maven-репозиториев, а только в виде скомпилированных jar-файлов или файлов с исходными кодами и ant-скриптом, который выполняет их компиляцию. То единственным выходом является искусственное maven-изирование такого проекта. Предположим, что библиотека “bar” состоит из трех файлов-артефактов “db-bar”, “commons-bar”, “web-bar”. Тогда эти файлы можно инсталлировать в локальный репозиторий самостоятельно и делается это одной командой install-file. Хотя репозиторий - это всего лишь набор каталогов определенной структуры и вы можете “ручками” создавать новые каталоги с файлами артефактами, но все же я это делать не рекомендую, т.к. в любом случае вам нужно создать файл pom. Лучше сделать так:
mvn install:install-file 
  -DgroupId=bar-group -DartifactId=commons-bar -Dversion=1.0 -Dpackaging=jar -Dfile=file-bar-commons.jar
Разработка проекта в команде вносит новый оттенок в задачу maven-изации: ведь подобную процедуру установки локального артефакта нужно выполнить на всех компьютерах разработчиков. Это, конечно не сложно, но поднимает интересный вопрос: а можно ли создать в сети предприятия собственный сервер-репозиторий maven. С одной стороны это очень легко, т.к. репозиторий это всего лишь веб-сервер (да хоть старый добрый apache) на котором размещены каталоги и файлы в определенном порядке. С другой стороны, такой подход не удобен необходимостью явно и “руками” регистрировать большое количество зависимостей на сервере. Гораздо лучше, если локальный сервер-репозиторий похож на привычный всем proxy-сервер, который используется для доступа в internet, т.е. на сервере настроен список удаленных репозиториев. И если компьютер разработчика пытается загрузить артефакт которого нет в репозитории сервера. То сервер загружает артефакт с одного из серверов зеркал (обычные public репозитории maven, тот же repo1.maven.org/maven2) и сохраняет копию артефакта у себя. Так что при последующих обращениях к артефакту, сервер-proxy уже не лазит в internet, а может быстро вернуть клиенту запрошенный им артефакт. Наиболее ярким представителем такого класса продуктов является apache archiva. Загрузка артефактов из internet имеет особенности в том, случае если вам компьютер не подключен к internet прозрачно, а работа идет через proxy-сервер: вам нужно подсказать maven-у адрес proxy-сервера, номер порта, и, если это необходимо, имя и пароль доступа к proxy-серверу. В файле pom нет такого раздела, который служил для настроки proxy-сервера. В действительности, maven использует многоуровневую схему настройки проекта. Первый уровень – это уровень проекта, т.е. настройки специфические для конкретного проекта и только для него в файле pom.xml. Второй уровень – глобальный для всех проектов на этом компьютере. Согласитесь, что настройки proxy-сервера, как раз и должны быть размещены отдельно от файла проекта, который хранится в репозитории и является общим для всех разработчиков в команде (то же имя и пароль для доступа к proxy-серверу у каждого из работников должно быть свое). Итак, глобальные настройки хранятся внутри файла settings.xml, внутри каталога “.m2”.
  1. <settings>
  2.   <proxies>
  3.     <proxy>
  4.       <id>optional</id>
  5.       <active>true</active>
  6.       <protocol>http</protocol>
  7.       <username>proxyuser</username>
  8.       <password>proxypass</password>
  9.       <host>proxy.host.net</host>
  10.       <port>80</port>
  11.       <nonProxyHosts>local.net,some.host.com</nonProxyHosts>
  12.     </proxy>
  13.   </proxies>
  14. </settings>
Назначение элементов очевидно из их названия, поэтому дополнительные комментарии я опущу. В некоторых случаях, файл settings.xml может быть project wide и в таком случае вы должны его разместить в том же каталоге, что и файл проекта. Или даже в любом другом месте, только нужно при запуске maven-а указать расположение файла settings.xml, например, так:

-Dorg.apache.maven.user-settings=/path/to/user/settings.xml

Одна из самых вкусных вещей, которая только есть в maven – это управление транзитивными зависимостями. К примеру, вы решили разработать проект с помощью spring, но сама библиотека spring не самодостаточна: ей потребуются библиотеки из семейства apache commons, logging и многое другое. К счастью, мы не должны сами прослеживать цепочки зависимостей и перечислять сто и одну позицию библиотек (избегая конфликта версий) внутри файла проекта pom.xml. Т.к. каждый файл артефакта (jar) имеет компаньона в виде файла pom, в котором перечисляются сведения об артефакте и, в частности, от каких других библиотек артефакт зависит. Maven сам построит дерево зависимостей и загрузит нужные для проекта артефакты в локальный репозиторий, а затем “подсунет” их проекту при компиляции или упаковке. Начнем мы с того, что еще раз рассмотрим области действия артефактов. Когда к проекту подключается артефакт, то мы указываем не только его имя, версию и родительскую группу артефактов, но еще две величины: область действия и классификатор. Классификатор – относительно простая и редко используемая вещь, нужная в тех случаях, когда деление артефакта по версиям является не достаточным. К примеру, вы разработали библиотеку математических функций, под номером 1.0, но есть два подвида этой библиотеки: одна из которых использует быстрый но не точный алгоритм расчета, да хоть какого-нибудь хитроумного интеграла, а вторая версия библиотеки – медленный но точный. Или библиотеки содержат вставки кода специфические для различных операционных систем: для windows и linux. Давать этим библиотекам различные номера – идеологически не верно, а вот дать разные классификаторы, самое то (классификатор добавляется к концу имени файла артефакта после его номера):
  1. <dependency>
  2.   <groupId>mygroup</groupId>
  3.   <artifactId>myartifact</artifactId>
  4.   <version>1.0</version>
  5.   <classifier>linux</classifier>
  6. </dependency>
Вторая же характеристика артефакта – scope – гораздо сложнее. Ее возможные значения: compile, runtime, test, system, provided. В прошлой статье, когда я показывал пример проекта maven, использующего для тестирования кода junit, то я промаркировал зависимость junit как относящуюся к scope test. Это значило, что артефакт junit не загружался из internet до тех пор пока в жизни проекта не была начата фаза “test” и, следовательно, junit не потребовался для компиляции файлов с тестами.