Прошло много времени с нашей статьи о загрузке классов JBoss . В течение этого времени мы были очень заняты новой версией серии Microcontainer 2.2.x, которая уже включена в последнюю версию JBossAS6_M2.
В этой статье я расскажу о нашей новой платформе виртуального развертывания (VDF).
Одной из первых вещей, помимо модульного ядра JMX на основе MBeans, которое пользователи заметили (по крайней мере, я это делал, когда я был еще пользователем) в предыдущих версиях JBossAS (до v5), был элегантный механизм расширения развертывания в виде . за развертывание
Это было действительно легко расширить или улучшить существующее поведение развертывания: Симпли у продлить один из вспомогательных классов Deployer и осуществить его развертывание и Undeploy методов с пользовательской логикой, т курица просто зарегистрировать новый Deployer как еще один сервис в модульном ядре.
Хотя все это было хорошо и легко, были некоторые проблемы с дизайном. Например, вам пришлось скопировать / вставить логику распознавания структуры развертывания. Также было трудно добавить небольшое новое поведение развертывания или изменить существующее поведение. Кроме того, слишком много деталей реализации были выставлены. Все это было учтено при переписывании уровня развертывания.
Мы можем подвести итог новой архитектуры по четырем функциям:
- обработка, не зависящая от типа развертывания; например, нет необходимости в развертывании с файловой поддержкой
- распознавание структуры отделено от фактической логики жизненного цикла развертывания
- управление естественным потоком в виде насадок
- отдельные детали использования и реализации на стороне клиента, пользователя и сервера
Давайте теперь рассмотрим каждую функцию более подробно.
Прочитайте другие части эксклюзивной серии микроконтейнеров JBoss от DZone :
Обработка, не зависящая от типа развертывания
Иногда все, что мы хотим сделать, это создать виртуальное развертывание на основе программно описанных метаданных; например, требуемые классы уже существуют в некотором общем пространстве классов / домене.
Это будет распространенный сценарий, когда вы хотите установить новую службу на сервер с вашего клиента администратора.
Таким образом, вместо загрузки некоторого файла дескриптора, вы просто передаете байты и десериализуете их в экземпляр развертывания.
У этого подхода есть некоторые ограничения в новом VDF, но выполнять эту задачу все же должно быть тривиально.
Другой тип развертывания (который с точки зрения классов реализации расширяет первый) — это простое развертывание на основе файловой системы, поддерживаемое нашей VFS (которое было описано в одной из предыдущих статей).
Распознавание структуры отделено от фактической логики жизненного цикла развертывания
Чтобы выполнить какую-либо реальную работу поверх развертывания, мы должны сначала распознать ее структуру. Под структурой мы подразумеваем пути к классам и расположение метаданных.
Расположение метаданных — это место, где находятся наши файлы конфигурации; my-jboss-beans.xml, web.xml, ejb-jar.xml, … Пути к классам —
это то, что составляет корни загрузчика классов развертывания; WEB-INF / classes, myapp.ear / lib, …
Только когда мы успешно распознаем структуру, мы можем приступить к фактической обработке развертывания — с учетом информации о структуре.
Вот как выглядит простая диаграмма жизненного цикла развертывания:
[Deployment] -> MainDeployer <— [DeploymentContext] —> Deployers <- [DeploymentUnit] -> настоящие Deployers на этапах развертывания
^
|
распознать структуру
|
V
StructuralDeployers <-> набор StructureDeployers
Как мы видим, когда мы передаем экземпляр Deployment в MainDeployer, он передается в StructuralDeployers для распознавания.
В случае виртуального / программного развертывания мы требуем, чтобы существующая предварительно определенная информация StructureMetaData была доступна — здесь мы читаем информацию о структуре.
Для развертываний на основе VFS мы пересылаем распознавание структуры на набор StructureDeployers.
Для структур, определенных в JEE, у нас есть соответствующие реализации StructureDeployer:
- EarStructure
- WarStructure
- JarStructure
В дополнение к этому у нас также есть DeclarativeStructure и FileStructure.
DeclarativeStructure ищет файл META-INF / jboss-structure.xml внутри вашего развертывания и анализирует его для создания правильной StructureMetaData.
FileStructure, с другой стороны, просто распознает известные файлы конфигурации; например, -jboss-beans.xml, -service.xml, …
<structure>
<context comparator="org.jboss.test.deployment.test.SomeDeploymentComparatorTop">
<path name=""/>
<metaDataPath>
<path name="META-INF"/>
</metaDataPath>
<classpath>
<path name="lib" suffixes=".jar"/>
</classpath>
</context>
</structure>
Пример jboss-structure.xml — так вы бы описали старый архив JBoss .sar.
В случае EarStructure мы сначала распознаем развертывание верхнего уровня, а затем рекурсивно обрабатываем вложенные развертывания.
Реализовать свой собственный StructureDeployer очень просто, особенно с помощью универсальной GroupingStructure.
На данный момент у нас есть распознанная структура развертывания, и пришло время передать ее реальным разработчикам.
Это объект Deployers (конкретная реализация интерфейса Deployers), который знает, как обращаться с реальными развертывателями — используя цепочку развертывателей на DeploymentStage.
public interface DeploymentStages
{
/** The not installed stage - nothing is done here */
DeploymentStage NOT_INSTALLED = new DeploymentStage("Not Installed");
/** The pre parse stage - where pre parsing stuff can be prepared; altDD, ignore, ... */
DeploymentStage PRE_PARSE = new DeploymentStage("PreParse", NOT_INSTALLED);
/** The parse stage - where metadata is read */
DeploymentStage PARSE = new DeploymentStage("Parse", PRE_PARSE);
/** The post parse stage - where metadata can be fixed up */
DeploymentStage POST_PARSE = new DeploymentStage("PostParse", PARSE);
/** The pre describe stage - where default dependencies metadata can be created */
DeploymentStage PRE_DESCRIBE = new DeploymentStage("PreDescribe", POST_PARSE);
/** The describe stage - where dependencies are established */
DeploymentStage DESCRIBE = new DeploymentStage("Describe", PRE_DESCRIBE);
/** The classloader stage - where classloaders are created */
DeploymentStage CLASSLOADER = new DeploymentStage("ClassLoader", DESCRIBE);
/** The post classloader stage - e.g. aop */
DeploymentStage POST_CLASSLOADER = new DeploymentStage("PostClassLoader", CLASSLOADER);
/** The pre real stage - where before real deployments are done */
DeploymentStage PRE_REAL = new DeploymentStage("PreReal", POST_CLASSLOADER);
/** The real stage - where real deployment processing is done */
DeploymentStage REAL = new DeploymentStage("Real", PRE_REAL);
/** The installed stage - could be used to provide valve in future? */
DeploymentStage INSTALLED = new DeploymentStage("Installed", REAL);
}
Это набор уже существующих этапов развертывания. Эти состояния отображаются на состояния встроенного контроллера MC. Они обеспечивают представление, ориентированное на жизненный цикл развертывания, для общих состояний контроллера.
Внутри Deployers мы преобразуем развертывание в компонент MC — DeploymentControllerContext — и оставляем его конечному автомату MC для правильной обработки зависимостей (среди развертываний и служб).
Мы вручную просматриваем соответствующие этапы / состояния развертывания и их соответствующие развертыватели, выполняя развертывания в ширину — это означает, что мы сначала обрабатываем все данные развертывания для определенного этапа развертывания, а затем переходим к следующему этапу.
Для каждого развертывателя мы обрабатываем всю иерархию развертывания — порядок зависит от родительского свойства развертывателя (по умолчанию — true).
Мы также можем просто указать, какой уровень (уровни) иерархии обрабатывает наш установщик — все, только верхний уровень, только компоненты, без компонентов, … (компоненты описаны далее в разделе «Детали реализации»).
Все, что мы узнали о компонентных моделях и обработке зависимостей MC, сохраняется и здесь. Если есть некоторые неразрешенные зависимости, развертывание будет ожидать в этом состоянии, потенциально сообщая об ошибке, если текущее состояние не является обязательным состоянием.
Добавление нового развертывателя тривиально. Просто расширьте один из множества существующих помощников развертывания. Следует отметить одну вещь — как мы упоминали в пункте (а) — есть развертыватели, которым действительно нужно развертывание с поддержкой VFS, и есть такие, которые могут работать в общем развертывании. В большинстве случаев только средства развертывания нуждаются в поддержке VFS.
Еще одно важное замечание: развертыватели должны «замкнуть накоротко». Каждый развертыватель работает с каждым развертыванием, вложенным развертыванием, компонентом, … что может привести к ненужной обработке, если развертыватель не написан по-существу. Важно как можно скорее определить, действительно ли текущее развертывание должно полностью выполняться развертывателем.
public class StdioDeployer extends AbstractDeployer
{
public void deploy(DeploymentUnit unit) throws DeploymentException
{
System.out.println("Deploying unit: " + unit);
}
@Override
public void undeploy(DeploymentUnit unit)
{
System.out.println("Undeploying unit: " + unit);
}
}
Простой пример развертывания, который выводит информацию о развертывании, которое он обрабатывает.
<bean name="StdioDeployer" class="org.jboss.acme.StdioDeployer"/>
Просто добавьте это описание в один из файлов -jboss-beans.xml в каталоге deployers / (в JBossAS), и наш компонент MainDeployerImpl подберет этого развертывателя с помощью обработки обратного вызова IoC MC.
Естественный контроль потока в виде вложений
Должен быть какой-то механизм, облегчающий передачу информации от одного развертывателя другому.
В VDF мы называем этот механизм «вложениями», и он реализован как чуть-чуть улучшенный java.util.Map, записи которого мы называем «вложениями».
Идея заключается в том, что некоторые развертыватели являются производителями, а другие — потребителями (конечно, могут быть оба). В нашем случае это приводит к тому, что некоторые развертыватели создают метаданные или экземпляры утилит, помещая их в карту «вложений», а некоторые другие развертыватели просто заявляют о своей потребности в этих вложениях и извлекают данные из карты «вложений», а затем выполняют дополнительную работу над эти данные.
Естественный поток, о котором мы говорим, относится к тому, как заказчики развернуты. Простая и распространенная идея — упорядочить вещи в относительном выражении (до / после). Однако, с механизмом «вложений» уже на месте,мы можем просто заказать развертывание по тому, как они производят и / или потребляют вложения.
Каждое вложение имеет ключ, и разработчики передают ключи вложения, которые они создают. Если средство развертывания создает вложение, то этот ключ называется выходным, если средство развертывания использует вложение, этот ключ называется вводом.
У развертывателей есть «обычные» входы и «обязательные» входы. Обычные входные данные используются только для определения естественного порядка. Необходимые входные данные помогают определить порядок, но также и то, действительно ли средство развертывания релевантно для данного развертывания, проверяя, существует ли вложение, соответствующее этому необходимому входному сигналу, на карте «вложений».
Хотя мы по-прежнему поддерживаем относительное упорядочение, это считается плохой практикой и может исчезнуть в следующем основном выпуске.
Отдельное использование на стороне клиента, пользователя и сервера, а также детали реализации
Этот набор изменений в основном был сделан для того, чтобы скрыть детали реализации, сделать использование менее подверженным ошибкам и в то же время сделать жизнь пользователей / разработчиков проще.
Идея состоит в том, что клиенты видят только API развертывания, а разработчики развертывания видят DeploymentUnit, а подробности реализации сервера содержатся в DeploymentContext. Таким образом, мы предоставляем информацию, необходимую только для определенного уровня жизненного цикла развертывания.
Мы уже упоминали компоненты в обработке иерархии развертывателя, но мы не объясняли, что они на самом деле или как, и почему, они используются. Хотя развертывание и развертывание верхнего уровня являются естественным представлением иерархии структуры развертывания, компоненты представляют собой несколько новую концепцию VDF.
Первоначальная идея компонентов заключается в том, что они представляют собой 1-1 сопоставления с ControllerContexts внутри MC.
Есть ряд мест, которые используют это предположение, то есть, что имя компонента компонента совпадает с именем ControllerContext.
Два наиболее очевидных из них:
- get * Scope () и get * MetaData ()
который возвратит тот же контекст MDR, который будет использоваться MC для этого экземпляра.
2. IncompleteDeploymentException (IDE)
Чтобы среда IDE распечатывала, какие зависимости отсутствуют для развертывания, ей необходимо знать имена ControllerContext.
Это делается путем сбора имен Component DeploymentUnit в Component Deployers, которые указывают это, например, BeanMetaDataDeployer или см. SetUseUnitName () в AbstractRealDeployer.
Скрытые драгоценные камни
Я всегда хотел бы упомянуть, как все наши компоненты MC обрабатываются одной точкой входа — одним конечным автоматом, и, как мы узнали, развертывания не являются исключением.
Итак, давайте теперь посмотрим, как мы можем воспользоваться этой возможностью — используя файл конфигурации jboss-dependency.xml в наших развертываниях.
jboss-dependency.xml — это простое общее описание зависимостей нашего развертывания.
<dependency xmlns="urn:jboss:dependency:1.0">
<item whenRequired="Real" dependentState="Create">TransactionManager</item> (1)
<item>my-human-readable-deployment-alias</item> (2)
</dependency>
С помощью (1) мы можем увидеть, как мы описываем зависимость от другого сервиса. В этом случае мы требуем, чтобы «TransactionManager» был создан до того, как наше развертывание находится в «реальной» стадии.
Пункт (2) выглядит немного более запутанным, так как нам не хватает дополнительной информации. По умолчанию имена развертывания внутри MC являются «некрасивыми» именами URI, что делает ввод их вручную ошибочным предложением.
Таким образом, чтобы все еще иметь возможность легко объявлять зависимость от других развертываний, нам нужен механизм псевдонимов, чтобы избежать этих «уродливых» имен URI. Чтобы сделать это как можно более простым, просто поместите простой текстовый файл с именем aliases.txt в свое развертывание, где каждая строка содержит новый псевдоним, тем самым давая архиву развертывания одно или несколько простых имен, используемых для ссылки на него.
Еще одна интересная функция, которую мы только что добавили, — это ленивая обработка ClassLoader для развертывания — с помощью файла конфигурации jboss-deploy.xml.
<deployment xmlns="urn:jboss:deployment:1.0" required-stage="PreDescribe" lazy-resolve="true">
<lazy-start-filter recurse="false">org.foo.bar</lazy-start-filter>
<lazy-start-filter recurse="true">com.acme.somepackage</lazy-start-filter>
</deployment>
Объявление атрибута lazy-resolver как true приведет к тому, что наше развертывание будет ожидать в обязательном этапе (по умолчанию обязательный этап имеет значение ‘Describe’), пока какое-либо другое развертывание не нуждается в нашем развертывании для разрешения его ClassLoader (эта функциональность интегрирована с MC ClassLoading).
Если есть некоторые lazy-start-filters или флаг lazy-start установлен в true, наше развертывание будет ожидать на этапе ClassLoader, пока какой-либо ресурс не будет загружен (и попадет в объявленные фильтры) из ClassLoader нашего развертывания. Только тогда он переместит наше развертывание на этап Установлено.
На практике это означает, что вы можете написать и развернуть службу, предоставляющую API, но вам не нужно создавать экземпляры необходимых объектов времени выполнения, которые предоставляют службу во время запуска контейнера. Они могут быть созданы по требованию, когда какой-то другой выполняющийся код сначала пытается загрузить классы API, предоставляемые службой.
Текущие спецификации JEE сократили количество файлов конфигурации, но теперь они требуют, чтобы контейнер выполнял большую часть работы на основе @annotations.
Чтобы получить информацию @annotation, контейнеры должны сканировать классы. Часто это снижение производительности.
MC не исключение, когда речь заходит о необходимости сканирования. Но чтобы уменьшить объем сканирования, мы ввели еще один хук дескриптора — jboss-scan.xml.
<scanning xmlns="urn:jboss:scanning:1.0">
<path name="myejbs.jar">
<include name="com.acme.foo"/>
<exclude name="com.acme.foo.bar"/>
</path>
<path name="my.war/WEB-INF/classes">
<include name="com.acme.foo"/>
</path>
</scanning>
Здесь мы видим простое описание относительных путей, которые мы хотим включить или исключить при сканировании информации метаданных JEE5 с комментариями. Затем эта информация будет использоваться в нашей среде сканирования MCScan нашей VDF (в настоящее время используется старая MCAnn, MCScan все еще находится в стадии разработки).
Хорошо, мы наконец-то познакомились с Deployers и всем VDF. То, что мы уже довольно широко использовали в предыдущих статьях, но никогда не вдавались в подробности. Как видите, существующую среду развертывания легко расширить. Это уже доказано элегантными решениями, используемыми в TorqueBox и Weld-интеграции JBoss.
В следующей статье я расскажу о работе, которую мы делаем в нашей собственной среде OSGi — полностью основанной на Microcontainer. Я объясню новые концепции набора услуг — новую компонентную модель в MC — и как мы используем все возможности MC для написания новой платформы OSS OSGi.
PS: Еще раз спасибо Марко за редактирование этой статьи и Адриан за его отзыв.
об авторе
Он влюбился в Java восемь лет назад и большую часть времени занимался разработкой информационных систем — от обслуживания клиентов до управления энергопотреблением. Он присоединился к JBoss в 2006 году, чтобы полностью посвятить себя работе над проектом Microcontainer, который в настоящее время является его руководителем. Он также участвует в JBoss AS и является специалистом по интеграции Seam, Weld и Spring. Он представляет JBoss в экспертных группах OSGi.