OSGi и Spring DM
Александр Яризов, сертифицированный специалист Sun Mycrosystems
Spring DM (полностью название звучит следующим образом: Spring Dynamic Modules for the OSGi Service Platforms) является одним из подпроектов Spring Framework -основного продукта компании SpringSource, позволяющий разработчикам использовать в своих проектах все предоставляемые OSGi преимущества наиболее «неагрессивным» (noninvasive) способом. Применение Spring DM является своего рода отдельным аспектом, который практически не влияет на все остальные части (классы, конфигурацию) программной системы и позволяет легко быть использованным в уже давно созданном или унаследованном ПО (legacy systems).
Spring framework является, как уже было отмечено, основным продуктом компании SpringSource. Это существующий с 2003 года легковесный контейнер (lightweight container), который в настоящее время является фактически стандартом де факто стандартом для Java EE при создании как обычных учетных приложений, так и в больших, критических для бизнеса приложениях. Его основными отличительные особенностями являются конфигурирование зависимостей (dependency injection-DI или inversion of control-IoC), аспектно-ориентированное программирование (Aspect Oriented Programming, AOP) и абстракция сервисов (Enterprise Service Abstraction). Эти свойства активно используются разработчиками при разработки приложений для регулирования зависимостей между различными компонентами, упрощения дальнейшего функционального расширения и облегчения сопровождения.
Однако что касается вопросов поставки системы конечному пользователю, установки системы на сервере, сопровождения приложения администраторами, то здесь большинство проблем остались нерешенными, прежде всего по той причине, что платформа Java никогда по настоящему не поддерживала разработку модульных приложений. Несмотря на то, что существует возможность разбить приложения на несколько частей (jar, war, ejb-jar и другие), тем не менее требуются немалые усилия и порой нетривиальные решения, чтобы преодолеть некоторые ограничения, присущие модулям java, например перечисленные ниже:
- Ограничение видимости классов возможно только на низком уровне и это не зависит от логического разбиения на различные модули. В языке Java существует понятие пакета (package) и чтобы один класс мог быть использован вне пакета, он должен быть объявлен публичным (public) (либо защищенным-protected, но тогда использующий его другой класс должен быть от него унаследован). И это правило совершенно не зависит от того, в каком физическом модуле (jar, war и другие модули) находится этот класс. В тоже время часто желательно иметь у модуля объявленные внешние интерфейсы (точки входа-выхода, public API), которые могут быть использованы внешними компонентами, и чтобы никакая другая часть не могла быть нигде использована, кроме как внутри этого модуля. То есть чтобы модуль представлял собой «черный ящик», который выполняет заданную функциональность, но нельзя «подсмотреть» каким образом это достигается. В литературе активно рекламируется использование интерфейсов, а имплементацию скрывать. Однако текущая модульная модель платформы Java не поддерживает этот подход.
- Невозможность одновременно использовать различные версии одной библиотеки. Легко себе представить ситуацию, когда два различных компонента работающей системы требуют для устойчивой работы различные версии одного и того же модуля. В платформе Java компоненты ищутся в порядке, предусмотренном системной переменной CLASSPATH начиная с первой указанной библиотеки (директории), потом берется следующая указанная и т.д. Поэтому если даже на пути и находятся 2 различные версии одного и того модуля, то только версия встретившаяся первой в CLASSPATH может быть использованной. Чтобы решить эту проблему администраторы берут как правило более «свежую» версию и надеются, что она подойдет так же и для компоненты, требующую более раннюю версию. Как правило этот подход срабатывает, но это происходит не всегда и тогда перед администраторами встает поистине нетривиальная задача
- Отсутствие встроенной возможности загружать модули в работающей системе, поддержки механизма динамических плагинов (plugin). Единственно возможным способом реализовать такое требование было реализация собственных загрузчиков классов (class loader), но такое подход является низкоуровневым, трудноисполнимым и труднотестируемым, требует соответствующей квалификации разработчиков,
OSGi
OSGi это технология, созданная организацией называемой OSGi альянс (OSGi Alliance), своей целью сделать Java платформу модульной. Она возникла в 1999 году и в первое время использовалась в основном во встраиваемых и мобильных приложениях. Однако в 2004 вышла новая версия наиболее широко используемого продукта используемого для разработки Java приложений – Eclipse, в ядре которой OSGi используется для управления работы встраиваемых плагинов (plugin).
Примерно в это же время появились версии популярных JEE серверов – WebSphere и Jonas, которые внутри использовали OSGi для управления серверными компонентами. И хотя в то время разработчики приложений, использовавшие эти контейнеры, не могли воспользоваться преимуществами, предоставляемые этой технологией, так как только внутренние компоненты этих серверов имели к ней доступ, в настоящее время почти все производители java middleware имеют планы по включению или уже используют OSGi. Кроме того, эта технология послужила основой для нового JSR 291 и в обозримом будущем скорее всего будет принята как новый стандарт платформы Java.
Платформа OSGi имеет две составляющие: OSGi фреймворк (OSGi framework) и стандартные сервисы OSGi (OSGi standart services). Фреймворк играет основную роль, он является средой, в которой исполняются приложения и которая обеспечивает предписываемую стандартом OSGi функциональность. Стандартные сервисы определяют интерфейс для общих задач, например ведение протокола работы (logging).
Спецификация OSGi концептуально разделяет фреймворк на 3 уровня:
Рисунок 1: Архитектура OSG i
- Module layer – уровень управления модулями системы, связанный с компоновкой (packaging), разделением и совместным использованием (sharing) кода. Основной задачей этого уровня является предоставление другим компонентам доступа к заданному внешнему интерфейсу компонента, скрывая при этом его внутреннюю реализацию, и определение модулей, которые необходимы для успешного функционирования данного компонента (то есть определение модулей, наличие которых необходимо). Чтобы различать традиционные модули java и OSGi модули, было введено понятие bundle, который в действительностиявляется обычным архивом jar с указанной в файле META-INF/MANIFEST дополнительной конфигурации, характеризующей этот bundle, например:
Bundle-ManifestVersion: 2
Bundle-Name: Name of the bundle
Bundle-SymbolicName: com.company.package
Bundle-Version: 1.0
Помимо общей информации, в файле META-INF/MANIFEST можно указать какие пакеты (package) из этого bundle могут быть использованы клиентами этого компонента:
Export-package:com. company.package
и (или) от каких внешних компонент зависит сам bundle:
Import-package:com.anothercompany.anotherpackage;version=”1.2.3”
Используя различные настройки можно скрыть доступа снаружи любой элемент из bundle, что позволяет рассматривать этот компонент как «черный ящик». Это достигается путем использования на уровне управления модулями не классических, иерархических загрузчиков классов (hierarchical classloader), а собственной OSGi системы, в основе которой лежат зависимые загрузчики классов (dependent classloaders): каждый OSGi компонент использует собственный выделенный загрузчик, который взаимодействует с загрузчиками других bundles. Такой подход называется связывание загрузчиков классов (classloader chaining).
Поскольку bundles по своей сути являются обычными jar архивами, то применять их можно также на обычной платформе java, что является подтверждением того, что использование OSGi не является инвазивным (non invasive).
- Lifecycle layer – уровень управления жизненного цикла OSGi компонент. На этом уровне, как видно из названия, предоставлена возможность отслеживать жизненный цикл OSGi компонента и в определенные моменты производить необходимые действия, такие как установить соединение с базой данных или стартовать фоновый процесс во время инициализации работы модуля. OSGi компонент в каждый конкретный момент времени находится в одном из состояний показанных на рисунке 2.
Рисунок 2: Cостояния модуля OSGi
Любой переход из одного состояния в другой может быть перехвачен и дополнен некоторыми желаемыми действиями. Это делается реализуя необходимые методы интерфейсов BundleActivator, FrameworkListener, BundleListener и других. В приведенном примере продемонстрировано, как возможно при запуске OSGi компонента инициализировать соединение с базой данной:
import org.osgi.framework.BundleActivator;
public Class DataBaseActivator implements BundleActivator {
Connection connection;
public void start (BundleContext ctx) {
String jdbcDriver, jdbcUrl, username, password;
/* пропущен код инициализирующий переменные jdbcDriver, jdbcUrl, username, password; */
…………………
Class.forName(jdbcDriver).newInstance();
connection = DriverManager.getConnection(jdbcUrl, user, password);
}
public void stop (BundleContext ctx) {
connection.close();
}
}
Кроме этого необходимо также сообщить о созданном классе OSGi фреймворк, что делается добавлением новых метаданных в файл META-INF/MANIFEST:
Bundle-Activator: com.company.package.DataBaseActivator
Import-Package: org.osgi.framework
- Service Layer – уровень управления сервисами, выполняющий особою роль в OSGi. Благодаря этому уровню OSGi часто называют «малой СОА» или «SOA in small» (подробнее о СОА можно в частности узнать из статьи на нашем сайте www.finecosoft.ru/soa). Используя этот уровень можно динамически зарегистрировать интерфейсы (которые могут быть рассмотрены как сервисы в терминологии СОА) в реестре сервисов OSGi (SOA Registry). Другие компоненты могут потом динамически находить и использовать зарегистрированные сервисы.
Рисунок 3: Взаимодействие сервисов в СОА
Рассмотрим небольшой пример, поясняющий функциональность, предоставляемую уровнем управления сервисами в OSGi. Предположим, что в пакете OfferingServiceBundle существуют сервис BusinessService, который используется в пакете UsingServiceBundle. Как уже было показано в разделе module layer, есть возможность эту зависимость прописать статически в файле META-INF/MANIFEST, то есть в в пакете UsingServiceBundle указать зависимость от пакета OfferingServiceBundle и импортировать сервис BusinessService, а в пакете OfferingServiceBundle экспортировать этот сервис. К сожалению не всегда существует такая возможность, поскольку какой метод надо использовать выясняется уже в ходе работы системы. Реализовать такой сценарий можно используя возможности, предоставляемые OSGi Service Layer.
Для этого пакет OfferingServiceBundle должен во время инициализации зарегистрировать BusinessService в реестре, например следующим образом:
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import com.company.package.BusinessService;
import com.company.package.BusinessServiceImpl;
public Class OfferingServiceBundleActivator implements BundleActivator {
public void start (BundleContext ctx) {
ctx.registerService(BusinessService.class.getName(), new BusinessServiceImpl(), null);
}
}
Bundle UsingServiceBundle находит и использует желаемый сервис при активации следующим образом:
import org.osgi.framework.BundleActivator;
import org.osgi.framework.*;
import com.company.package.BusinessService;
public Class UsingServiceClass implements BundleActivator {
public void start (BundleContext ctx) {
ServiceReference ref =
ctx.getServiceReference(BusinessService.class.getName());
((BusinessService) ctx.getService(ref)).someMethode();
}
}
Конечно динамический поиск, регистрация и использование сервисов возможен не только при активации модулей и данный сценарий служит только для демонстрации возможностей Service Layer. Следует особо подчеркнуть, что в клиентской части нужно импортировать только интерфейс сервиса, нет явной зависимости от его реализующего класса. Такой подход стимулирует разработчиков использовать активно интерфейсы при построении системы и разделять реализацию и объявление, что ведет к более низкой связанности (loose coupling). В результате внесение изменений в функциональность сервисов не затрагивает использующие их компоненты, также используя предоставляемые OSGi преимущества, возможно динамически в ходе эксплуатации системы, без ее остановки и переконфигурирования, переключать различные варианты реализации.
Совместное использование Spring и OSGi.
Архитектура
При рассмотрении концепции пакетов OSGi (bundles) и контекста приложения Spring (Spring Application-Context) очевидно, что оба подхода могут служить основой для разбиения приложений на составляющие его модули, однако подходят к решению этого вопроса как бы с разных сторон. Контексты приложения Spring позволяют разбивать приложения на функциональные составляющие при разработке приложения, в то время как пакеты OSGi решает вопросы взаимодействия модулей во время эксплуатации, предоставляя свои ресурсы внешним клиентам и потребляя сервисы других пакетов, динамически проверяя и разрешая зависимости между различными частями. И пакеты OSGi, и контексты приложения Spring могут являться компонентами модульной системы. Для совместного использования этих двух технологий, и таким образом одновременного использования преимуществ как OSGi так и Spring, компанией SpringSource и был разработан Spring DM. Как большинство других продуктов этой компании, при разработке продукта одним из основных подходов был абстракция сервисов, позволяющий разрабатывать OSGi приложения без прямого применения OSGi API. Spring DM не является сам по себе средой исполнения OSGi, а инструментом облегчающим создание приложений, которые выполняются на практически всех известных в настоящее время OSGi контейнеров: Equinox, Knopflerfish и Felix.
На рисунке 1 изображена архитектура высокого уровня построенной на Spring DM системы. В пакеты OSGi (bundles) включается новый для Spring DM тип контекста системы – OsgiApplicationContext (изображен в виде прямоугольника с белым фоном внутри пакета OSGi), через который можно управлять объектами и экспортировать или импортировать интерфейсы Spring бинов как OSGi сервисы.
Рисунок 4: Архитектура высокого уровня механизма Extender Spring DM
OsgiApplicationContext создается внутри пакетов с помощью поставляемого вместе с Spring DM служебного бандла org.osgi.service.context.extender, который активно используют для этой задачи возможности уровня управления сервисами и уровня управления жизненного цикла, вкратце уже описанные выше. Последовательность действий, выполняемых этим пакетом можно представить следующим образом:
- пакет extender получает сообщение от ядра OSGi, что установлен новый пользовательский пакет.
- extender просматривает XML описания, которые пользовательский пакет содержит в директории META-INF/spring. Помимо этого, в манифесте можно указать альтернативные пути, где находятся XML описания контекстов (путем определения переменной SpringContext)
- если описание контекста содержит, что определенные сервисы необходимы для функционирования данного пакета, то в этом случае extender использует API уровня управления сервисами для их нахождения. Создание экземпляра OsgiApplicationContext задерживается в таком случае до момента, когда все необходимые связи полностью разрешены.
- создание OsgiApplicationContext контекста модуля используя API уровня управления жизненного цикла.
Декларативное использование сервисов.
Как уже говорилось, Spring Dynamic Modules позволяет использовать все преимущества, предоставляемые OSGi, без прямого применения API и директив OSGi, таким образом значительно облегчая использование этой платформы. Продемонстрируем эту возможность на несложном примере.
Предположим, что одна из составных частей системы формирует счет клиенту за покупку какого-нибудь продукта, при этом существует возможность включить в счет рекламу других продуктов, которые могут быть использованы вместе с покупаемым, или которые согласно статистике часто покупаются другими клиентами одновременно. Таким образом, у нас могут существовать в системе два модуля: BillCreator, формирующий счет, и Advertisement, который может выбрать «родственные» продукты. Очевидно, что если модуль Advertisement по каким-либо причинам не работает, это не должно влиять на деятельность модуля BillCreator. То есть наш пример состоит из пакета, предоставляющий сервис – Advertisement, и пакета BillCreator, являющимся клиентом.
Пакет, предоставляющий сервис
Начнем с определения сервиса, предоставляемого модулем Advertisement.
<bean id=“advertisementService“
class=“com.company.advertisement.impl.AdvertisementServiceImpl“>
<!— определение параметров сервиса —>
</bean>
Как видно, данное объявление является типичным определением бина в Spring Framework. Чтобы экспортировать его в качестве OSGi сервиса потребуется другое определение, которое использует как идентификатор сервиса, так и интерфейс, который этот сервис реализует. Интерфейс необходим, поскольку extender при поиске в реестре сервисов ищет подходящих кандидатов на основании реализуемых интерфейсов:
<osgi:service id=“advertisementServiceOSGi“ ref=“advertisementService“
interface=“com.company.advertisement.AdvertisementService “/>
Объявление пакета-клиента
Для того чтобы использовать какой-либо внешний сервис, в модуле-клиенте необходимо объявить его ссылку, что можно сделать например следующим образом:
<osgi:reference id=“advertisementServiceOSGi”
interface=“com.company.advertisement.AdvertisementService”
cardinality=“0..1” >
<osgi:listener bind-method=“onBind” unbind-method=“onUnbind”>
<bean class=“com.company.billcreator.BillCreatorListener”/>
</osgi:listener>
</osgi:reference>
При инициализации модуля будет автоматически произведен поиск подходящих сервисов и, если такой существует, ссылка на этот сервис будет автоматически определена для клиента. При этом возможно, что в реестре сервисов существует одновременно несколько активных сервисов, реализующих интерфейс com.company.advertisement.AdvertisementService. В этом случае, если используемый сервис в ходе эксплуатации становится не активным, то система автоматически заменит ссылку в модуле-клиенте, причем замена будет произведена без каких-либо действий со стороны клиента.
Как уже было написано, использование модулем-клиентом сервиса AdvertisementService не является обязательным, он может отсутствовать в системе по каким либо причинам и это не должно влиять на работоспособность системы. Данную возможность описывается свойством cardinality при конфигурации. Если этот атрибут не задан, по умолчанию подразумевается значение «1..1», то есть ссылка на сервис должна существовать обязательно и, если подходящий сервис не найден, то модуль не буден активирован.
Что бы в ходе работы системы в режиме online можно было реагировать да активацию или деактивацию сервиса AdvertisementService, сконфигурирован листенер BillCreatorListener. В случае появления в системе сервиса будет вызван метод, указанный в атрибуте «bind-method», на прекращение работы отреагирует метод листенера, заданный в атрибуте «unbind-method»
Осталось только сконфигурировать сервис BillCreator, использующий AdvertisementService, но здесь нет ничего, что можно считать особенностью Spring DM, поскольку использование OSGi сервиса ничем не отличается от использования обычного:
<bean name=“billCreator” class=“com.company.billcreator.BillCreatorServiceImpl”
init-method=“start”
destroy-method=“stop”
<property name=“advertisementService” ref=“advertisementServiceOSGi”/>
</bean>
Собственно на этом и все, что необходимо сделать для того, чтобы одновременно воспользоваться преимуществами, предоставляемыми OSGi и Spring.