Наконец-то я довольно успешно сдал вступительные экзамены в аспирантуру и появилось время поделиться со своими читателями чем-то новым. В частности - выполнить свое обещание и рассказать про работу с событиями в OSGi-платформе.
Начнем с того, что в спецификации OSGi R4 определены механизмы работы с событиями, такие, как источник события - EventAdmin, обработчик события - EventHandler и непосредственно само событие - Event. Разработка диспетчера событий ложится на плечи создателей конкретной реализации OSGi-платформы. Тем самым, мы, как пользователи платформы, получаем в свое распоряжение уже готовый мощный инструмент для обеспечения взаимодействия бандлов. Осталось только научиться с ним работать.
Генерация событий
Для генерации событий служит OSGi-сервис EventAdmin. В интерфейсе EventAdmin определено два метода: postEvent - отправить событие для асинхронной обработки и sendEvent - отправить событие для синхронной обработки.
Оба этих метода в качестве параметра принимают Event - собственно событие. Каждое событие включает в себя два параметра (передаются через конструктор): топик события (String) и свойства, описывающие конкретное событие (Dictionary). Поговорим об этих параметрах подробнее.
Топик события - строка, по которой диспетчер событий сможет определить обработчик для данного события. Строка должна удовлетворять доменной модели, причем домены отделяются символом "/", например "org/beq/equinox/events/test1". Это очень важно, потому что обработчики могут подписываться сразу на группу топиков событий, используя "*" в качестве последнего домена. Например так: "org/beq/equinox/*".
Свойства события - словарь параметров, которые содержат информацию, описывающую событие. Дело в том, что OSGi не поддерживает иерархию классов над Event, поэтому предполагается сериализация конкретного события в словарь свойств. Более того, следует быть осторожными при использовании в качестве значений данных, отличающихся от примитивов и строк. При запуске бандлов, например, на разных машинах могут возникнуть проблемы при сериализации/десериализации таких данных.
Итак, чтобы послать событие, необходимо:
1. Получить экземпляр сервиса EventAdmin из OSGi.
2. Выбрать топик события, удовлетворяющий доменной модели.
3. Заполнить словарь (класс Dictionary) свойств события.
4. Вызвать метод sendEvent или postEvent полученного экземпляра сервиса.
Ну а теперь слайды. Создадим бандл org.beq.equinox.events.injector, в котором определим класс EventInjector, генерирующий в отдельном потоке группу событий с периодом раз в 10 секунд. В активаторе этого бандла получим EventAdmin и свяжем его с объектом класса EventInjector, после чего запустим генерацию событий.
Класс EventInjector:
package org.beq.equinox.events.injector;
import java.util.Dictionary;
import java.util.Hashtable;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;
public class EventInjector {
private EventAdmin eventAdmin;
public void inject() {
if (getEventAdmin() != null) {
getEventAdmin().postEvent(new Event("org/beq/equinox/events/test1", getEventProperties("1")));
getEventAdmin().postEvent(new Event("org/beq/equinox/events/test1", getEventProperties("2")));
getEventAdmin().postEvent(new Event("org/beq/equinox/events/test2", getEventProperties("1")));
System.out.println("All events have been sent!");
}
}
protected Dictionary<String, Object> getEventProperties(String property1Value) {
Dictionary<String, Object> result = new Hashtable<String, Object>();
result.put("property1", property1Value);
return result;
}
public EventAdmin getEventAdmin() {
return eventAdmin;
}
public void setEventAdmin(EventAdmin eventAdmin) {
this.eventAdmin = eventAdmin;
}
public void start() {
getTaskThread().start();
}
public void stop() {
getTaskThread().interrupt();
}
private Thread taskThread = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try
{
inject();
Thread.sleep(10000);
}
catch (InterruptedException ex) {
}
}
}
});
protected Thread getTaskThread() {
return taskThread;
}
}
Активатор бандла:
package org.beq.equinox.events.injector;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.service.event.EventAdmin;
import org.osgi.util.tracker.ServiceTracker;
public class Activator implements BundleActivator {
private EventInjector eventPublisher = new EventInjector();
private ServiceTracker serviceTracker;
/*
* (non-Javadoc)
* @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
*/
@Override
public void start(BundleContext context) throws Exception {
bindEventAdmin(context);
getEventInjector().start();
System.out.println("org.beq.equinox.events.injector has been started!");
}
private void bindEventAdmin(BundleContext context) {
serviceTracker = new ServiceTracker(context, EventAdmin.class.getName(), null);
serviceTracker.open();
EventAdmin eventAdmin = (EventAdmin) serviceTracker.getService();
getEventInjector().setEventAdmin(eventAdmin);
}
/*
* (non-Javadoc)
* @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
*/
@Override
public void stop(BundleContext context) throws Exception {
getEventInjector().stop();
serviceTracker.close();
System.out.println("org.beq.equinox.events.injector has been stoped!");
}
public EventInjector getEventInjector() {
return eventPublisher;
}
}
На всякий случай приведу манифест бандла:
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Injector
Bundle-SymbolicName: org.beq.equinox.events.injector
Bundle-Version: 1.0.0
Bundle-Activator: org.beq.equinox.events.injector.Activator
Bundle-ActivationPolicy: lazy
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Import-Package: org.osgi.framework;version="1.3.0",
org.osgi.service.event;version="1.2.0",
org.osgi.util.tracker;version="1.4.2"
Обработка событий
Обработка событий осуществляется в классе, реализующем интерфейс EventHandler. Данный класс регистрируется в OSGi в качестве сервиса. При регистрации сервиса указывается словарь параметров, включающий в себя топик обрабатываемого события (обязательный параметр) и фильтр по свойствам события (необязательный параметр).
Топик события может быть строкой (String) или массивом строк (String[]). Как было отмечено выше - последним доменом в топике может быть "*", обозначающая любую подстроку. Обратите внимание, что звездочка может быть ТОЛЬКО доменом. Т.е. можно написать smth/*, но не smth/smth*. При регистрации сервиса в словаре свойств, топик имеет имя, определяемое константой org.osgi.service.event.EventConstants.EVENT_TOPIC.
Фильтр. Довольно интересная вещь, OSGi позволяет фильтровать события не только по топику, но и по их свойствам. Фильтр представляет собой LDAP-строку, например "'(&(productCategory=books)(price>=80))". Чем то напоминает LISP :) При регистрации сервиса в словаре свойств фильтр имеет имя, определяемое константой org.osgi.service.event.EventConstants.EVENT_FILTER.
Давайте снова рассмотрим пример. Создадим бандл org.beq.equinox.events.handler и определим в нем обработчик событий - класс ExampleEventHandler, который будет просто выводить в консоль параметры обрабатываемого события. В активаторе бандла зарегистрируем этот класс как сервис с разными топиками событий и параметрами фильтрации.
Обработчик событий:
package org.beq.equinox.events.handler;
import java.text.MessageFormat;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventConstants;
import org.osgi.service.event.EventHandler;
public class ExampleEventHandler implements EventHandler {
private final String handlerName;
@Override
public void handleEvent(Event event) {
System.out.println(MessageFormat.format(
"Event Handler {0} handled event on topic {1}: Value of 'property1' = {2}",
getHandlerName(),
event.getProperty(EventConstants.EVENT_TOPIC),
event.getProperty("property1")));
}
public ExampleEventHandler(String handlerName) {
this.handlerName = handlerName;
}
protected String getHandlerName() {
return handlerName;
}
}
Активатор бандла:
package org.beq.equinox.events.handler;
import java.util.Dictionary;
import java.util.Hashtable;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.service.event.EventConstants;
import org.osgi.service.event.EventHandler;
public class Activator implements BundleActivator {
/*
* (non-Javadoc)
* @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
*/
@Override
public void start(BundleContext context) throws Exception {
context.registerService(EventHandler.class.getName(),
new ExampleEventHandler("Handler 1"),
getHandlerServiceProperties("org/beq/equinox/events/test1"));
context.registerService(EventHandler.class.getName(),
new ExampleEventHandler("Handler 2"),
getHandlerServiceProperties(new String[] { "org/beq/equinox/*" }, "(property1=2)"));
System.out.println("org.beq.equinox.events.handler has been started!");
}
/*
* (non-Javadoc)
* @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
*/
@Override
public void stop(BundleContext context) throws Exception {
System.out.println("org.beq.equinox.events.handler has been stoped!");
}
protected Dictionary<String, Object> getHandlerServiceProperties(String... topics) {
Dictionary<String, Object> result = new Hashtable<String, Object>();
result.put(EventConstants.EVENT_TOPIC, topics);
return result;
}
protected Dictionary<String, Object> getHandlerServiceProperties(String[] topics, String filter) {
Dictionary<String, Object> result = new Hashtable<String, Object>();
result.put(EventConstants.EVENT_TOPIC, topics);
result.put(EventConstants.EVENT_FILTER, filter);
return result;
}
}
Манифест бандла:
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Handler
Bundle-SymbolicName: org.beq.equinox.events.handler
Bundle-Version: 1.0.0
Bundle-Activator: org.beq.equinox.events.handler.Activator
Bundle-ActivationPolicy: lazy
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Import-Package: org.osgi.framework;version="1.3.0",
org.osgi.service.event;version="1.2.0"
Теперь нужно заставить все это работать.
Реализация диспетчера событий в Equinox и запуск бандлов
Если сейчас скомпилировать описанные выше классы в Eclipse - все скомпилируется, но работать не будет. Дело в том, что в Equinox с целью совместимости со стандартом разделены определения всех сущностей OSGi R4 и их реализация. В частности, интерфейс EventAdmin определен в бандле org.eclipse.osgi.services. Реализация же диспетчера событий находится в бандле org.eclipse.equinox.event, который можно скачать отсюда. Впрочем, начиная с Eclipse 3.6M2, реализация EventAdmin включена в Equinox SDK, поставляемую вместе с Eclipse.
Запустить созданные нами бандлы можно с помощью эклипсовского Run as OSGi Framework. В настройках запуска, на вкладке Bundles необходимо выбрать бандлы из нашего воркспейса (org.beq.equinox.event.handler и org.beq.equinox.event.injector) и необходимое окружение (бандлы org.eclipse.equinox.event, org.eclipse.osgi, org.eclipse.osgi.services, org.eclipse.osgi.util). После этого приложение можно запускать.
Вот пример того, что будет выведено в OSGi-консоль:
osgi> org.beq.equinox.events.handler has been started!
org.beq.equinox.events.injector has been started!
Event Handler Handler 1 handled event on topic org/beq/equinox/events/test1: Value of property1 = 1
Event Handler Handler 1 handled event on topic org/beq/equinox/events/test1: Value of property1 = 2
All events have been sent!
All events have been sent!
Event Handler Handler 1 handled event on topic org/beq/equinox/events/test1: Value of property1 = 1
Event Handler Handler 1 handled event on topic org/beq/equinox/events/test1: Value of property1 = 2
All events have been sent!
Event Handler Handler 1 handled event on topic org/beq/equinox/events/test1: Value of property1 = 1
Event Handler Handler 1 handled event on topic org/beq/equinox/events/test1: Value of property1 = 2
Стоит обратить внимание, что диспетчер событий в OSGi не сохраняет события, т.е., если бандл-обработчик еще не запущен - событие будет потеряно. Это необходимо помнить при организации правильного порядка запуска бандлов.
Понравилось сообщение - подпишитесь на блог или читайте меня в twitter
7 комментариев:
Надо ли в методе stop() активатора бандра Handler разрегистрировать сервис (unregister)
Да, по хорошему сервисы нужно разрегистрировать. Собственно, из-за отсутствия такой возни мне все больше нравятся декларативные сервисы.
Извините за глупый вопрос и заранее спасибо за ответ=)
никак не получается прикрутить события к к моей View в EclipseRcp, хочется чтобы при нажатии на кнопку бандл отправлял события.
не могу понять как из активатора можно передать EventAdmin в мой View, или как получить бандл контекст во View чтобы из него получить EventAdmin.
Бандлы, участвующие в RCP-приложении, как правило синглетые (любой бандл, имеющий plugin.xml, должен быть синглетным, синглетность прописывается в манифесте). Соответственно, можно сохранить контекст бандла в статической переменной и возвращать статическим методом активатора
public static BundleCondext getPluginContext()
{
return _context;
}
А уже где нужно внутри бандла использовать:
Activator.getPluginContext()
Согласно коду я получил что и ожидал, но вывод отличается от ващего. в чём проблема: вы код поменяли перед выкладыванием или у меня окружение глючит ?
Event Handler Handler 1 handled event on topic org/beq/equinox/events/test1: Value of property1 = 1
Event Handler Handler 1 handled event on topic org/beq/equinox/events/test1: Value of property1 = 2
Event Handler Handler 2 handled event on topic org/beq/equinox/events/test1: Value of property1 = 2
All events have been sent!
Не совсем понял, что именно вас не устраивает? Нужно понимать, что события в приведенном коде отправляются асинхронно, поэтому порядок их обработки недетерминирован.
Спасибо за пост, помог решить проблему с сервисом событий.
Отправить комментарий
Любой Ваш комментарий важен для меня, однако, помните, что действует предмодерация. Давайте уважать друг друга!