Hello, суровый челябинский программист here. И мы вновь продолжаем наше знакомство с OSGi. В предыдущей заметке мы говорили о взаимодействии бандлов посредством зависимостей. Однако, в OSGi существует и другой - более гибкий - способ обеспечить совместную работу бандлов и имя ему - сервисы.
Впрочем, предлагаю перейти сразу к делу. На рисунке представлена схема взаимодействия сервисов. Один Java-объект, который называется Service, представляет некий интерфейс доступа к себе - контракт сервиса. Другой Java-объект, который называется Client, может взаимодействовать с сервисом, через предоставляемый тем контракт.
Чтобы данное взаимодействие было возможно Client должен найти нужный ему Service и получить к нему доступ. Для этого используется Service Broker, в роли которого предстает OSGi-реестр сервисов. Бандл, предоставляющий сервис, регистрирует его в реестре, а бандл-клиент извлекает сервис оттуда и использует.
Теперь займемся практикой. Создадим бандл org.beq.equinox.service, который будет выставлять ColorizerService - сервис, предоставляющий некую палитру цветов.
Для начала определим интерфейс IBeqService и договоримся, что все наши сервисы будут его реализовывать. Данный интерфейс определяет один метод - getName(), позволяющий идентифицировать сервисы в системе.
Код интерфейса:
Теперь напишем сам класс сервиса. Не будем сильно заморачиваться - определим лишь метод listColors, который возвращает массив цветов:
package org.beq.equinox.service;
public class ColorizerService implements IBeqService
{
private static final String NAME = ColorizerService.class.getName();
@Override
public String getName()
{
return NAME;
}
public String[] listColors()
{
return new String[] {
"Red", "Green", "White", "Black"
};
}
}
public class ColorizerService implements IBeqService
{
private static final String NAME = ColorizerService.class.getName();
@Override
public String getName()
{
return NAME;
}
public String[] listColors()
{
return new String[] {
"Red", "Green", "White", "Black"
};
}
}
Все, сервис готов. Теперь необходимо зарегистрировать сервис в реестре сервисов. Сделать это можно (да и нужно) в активаторе бандла, предоставляющего сервис. В нашем случае - в классе Activator бандла org.beq.equinox.service.
Сначала я приведу код активатора, затем позволю себе дать некоторые пояснения.
package org.beq.equinox.service;
import java.util.Properties;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
public class Activator implements BundleActivator {
public ServiceRegistration registration;
/*
* (non-Javadoc)
* @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
*/
public void start(BundleContext context) throws Exception {
// create a service
IBeqService service = new ColorizerService();
// set some params
Properties props = new Properties();
props.put("category", "basic");
// registrating the ColorizerService
registration = context.registerService(service.getName(), service, props);
}
/*
* (non-Javadoc)
* @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
*/
public void stop(BundleContext context) throws Exception {
registration.unregister();
}
}
import java.util.Properties;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
public class Activator implements BundleActivator {
public ServiceRegistration registration;
/*
* (non-Javadoc)
* @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
*/
public void start(BundleContext context) throws Exception {
// create a service
IBeqService service = new ColorizerService();
// set some params
Properties props = new Properties();
props.put("category", "basic");
// registrating the ColorizerService
registration = context.registerService(service.getName(), service, props);
}
/*
* (non-Javadoc)
* @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
*/
public void stop(BundleContext context) throws Exception {
registration.unregister();
}
}
Для регистрации сервиса в реестре используется объект класса ServiceRegistration, представляющий собой запись в реестре о регистрации сервиса. Вся работа непосредственно с реестром, как и любое взаимодействие бандла с OSGi осуществляется через контекст бандла. Метод registerService принимает в качестве параметров имя под которым регистрируется сервис, сам объект сервиса и набор параметров (Properties). Набор параметров может быть пустым, тогда в метод нужно передать null.
В методе stop активатора бандла необходимо освободить все ресурсы, в том числе и отменить регистрацию сервисов. Для этого используется метод unregister класса ServiceRegistration.
Теперь посмотрим на манифест бандла. В нем мы указываем активатор бандла и экспортируемые пэкеджи. В нашем случае - org.beq.equinox.service:
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Service
Bundle-SymbolicName: org.beq.equinox.service
Bundle-Version: 1.0.0
Bundle-Activator: org.beq.equinox.service.Activator
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Import-Package: org.osgi.framework;version="1.3.0"
Export-Package: org.beq.equinox.service
Bundle-ManifestVersion: 2
Bundle-Name: Service
Bundle-SymbolicName: org.beq.equinox.service
Bundle-Version: 1.0.0
Bundle-Activator: org.beq.equinox.service.Activator
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Import-Package: org.osgi.framework;version="1.3.0"
Export-Package: org.beq.equinox.service
Теперь запустим Equinox и установим наш бандл, подробно как это сделать можно прочитать в предыдущих статьях цикла. Если все прошло успешно - мы увидим следующую картину:
Теперь стартуем наш бандл с помощью команды start 1. Выполнится активатор бандла и в реестре сервисов зарегистрируется наш сервис. Посмотреть информацию о зарегистрированных в системе сервисах можно с помощью команды services. Данной команде можно передавать параметры, например имя класса, реализующего данный сервис. Причем в параметрах можно использовать заменители - например знак *. Так посмотреть информацию о нашем сервисе можно с помощью команды: services (objectClass=*ColorizerService):
Как видим сервис успешно зарегистрирован в системе (кстати, можно заметить среди его параметров те, что мы передали при старте), но его никто не использует. И правильно - мы же еще не написали клиента.
Создадим бандл org.beq.equinox.customer, в котором и воспользуемся созданным сервисом. Предположим, что наш клиент строит некое меню выбора цвета, а в качестве источника цветов будет использовать сервис ColorizerService из бандла org.beq.equinox.service. Меню выбора цвета опишем с помощью интерфеса IColorMenu. Код интерфейса следующий:
package org.beq.equinox.customer;
import java.util.List;
public interface IColorMenu
{
public List<String> getAllItems();
}
import java.util.List;
public interface IColorMenu
{
public List<String> getAllItems();
}
Как видим - все просто. Есть метод getAllItems, который возвращает список всех доступных цветов. Гораздо интереснее реализации класса-клиента ColorMenu:
package org.beq.equinox.customer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.beq.equinox.service.ColorizerService;
import org.osgi.util.tracker.ServiceTracker;
public class ColorMenu implements IColorMenu
{
private final ServiceTracker colorizerTracker;
public ColorMenu(ServiceTracker tracker)
{
colorizerTracker = tracker;
}
@Override
public List<String> getAllItems()
{
ColorizerService service = (ColorizerService) colorizerTracker.getService();
if (service == null)
return new ArrayList<String>();
return Arrays.asList(service.listColors());
}
}
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.beq.equinox.service.ColorizerService;
import org.osgi.util.tracker.ServiceTracker;
public class ColorMenu implements IColorMenu
{
private final ServiceTracker colorizerTracker;
public ColorMenu(ServiceTracker tracker)
{
colorizerTracker = tracker;
}
@Override
public List<String> getAllItems()
{
ColorizerService service = (ColorizerService) colorizerTracker.getService();
if (service == null)
return new ArrayList<String>();
return Arrays.asList(service.listColors());
}
}
В данном коде интерес представлют 2 момента. Первый - это некий объект класса ServiceTracker, который передается в конструктор нашего класса. ServiceTracker - это утилитный класс OSGi, который помогает получить сервис, зарегистрированный в системе. Правило здесь простое - для доступа к сервису создается ServiceTracker, после нехитрых манипуляций с которым мы и получим экземпляр сервиса.
Второй момент - сам процесс доступа к сервису, а именно зачем тут проверка на null? Все очень просто - возможно, что наш бандл-клиент будет стартован раньше, чем бандл-предоставляющий сервис, зарегистрирует его в системе. Или бандл, предоставляющий сервис, вообще не установлен. В таком случае на попытку получить сервис, ServiceTracker вернет нам null.
Теперь посмотрим на самое интересное - создание нужного нам экземпляра класса ServiceTracker и работу с клиентом. Все это мы будем делать в активаторе бандла - классе Activator. Код активатора следующий:
package org.beq.equinox.customer;
import java.util.List;
import org.beq.equinox.service.ColorizerService;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.util.tracker.ServiceTracker;
public class Activator implements BundleActivator {
private ServiceTracker tracker;
/*
* (non-Javadoc)
* @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
*/
public void start(BundleContext context) throws Exception {
// Create and open tracker
tracker = new ServiceTracker(context, ColorizerService.class.getName(), null);
tracker.open();
// Create customer service
ColorMenu menu = new ColorMenu(tracker);
// Execute the sample
List<String> items = menu.getAllItems();
for (String item : items)
System.out.println(item);
}
/*
* (non-Javadoc)
* @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
*/
public void stop(BundleContext context) throws Exception {
// Close the ColorizerService ServiceTracker
tracker.close();
}
}
import java.util.List;
import org.beq.equinox.service.ColorizerService;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.util.tracker.ServiceTracker;
public class Activator implements BundleActivator {
private ServiceTracker tracker;
/*
* (non-Javadoc)
* @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
*/
public void start(BundleContext context) throws Exception {
// Create and open tracker
tracker = new ServiceTracker(context, ColorizerService.class.getName(), null);
tracker.open();
// Create customer service
ColorMenu menu = new ColorMenu(tracker);
// Execute the sample
List<String> items = menu.getAllItems();
for (String item : items)
System.out.println(item);
}
/*
* (non-Javadoc)
* @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
*/
public void stop(BundleContext context) throws Exception {
// Close the ColorizerService ServiceTracker
tracker.close();
}
}
При создании экзепляра класса ServiceTracker в его конструктор передаются контекст бандла и имя сервиса, для которого создается трекер. Последним парметром можно передать так называемый ServiceTrackerCustomizer - объект, который может настраивать работу треккера. Если кастомайзер не нужен - передаем null.
После создания треккера его обязательно нужно открыть, иначе через него нельзя будет достучаться до сервиса.
После того как мы открыли треккер можно создать ColorMenu, получить все его элементы и вывести их на консоль.
Манифест бандла:
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Customer
Bundle-SymbolicName: org.beq.equinox.customer
Bundle-Version: 1.0.0
Bundle-Activator: org.beq.equinox.customer.Activator
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Import-Package: org.beq.equinox.service,
org.osgi.framework,
org.osgi.util.tracker
Bundle-ManifestVersion: 2
Bundle-Name: Customer
Bundle-SymbolicName: org.beq.equinox.customer
Bundle-Version: 1.0.0
Bundle-Activator: org.beq.equinox.customer.Activator
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Import-Package: org.beq.equinox.service,
org.osgi.framework,
org.osgi.util.tracker
Обратите внимание: мы импортируем пэкедж org.osgi.util.tracker.
Посмотрим как это все работает. Запустим Equinox и установим оба бандла - org.beq.equinox.service и org.beq.equinox.customer:
Теперь стартуем бандлы в правильном порядке, т.е. сначала сервис, потом клиент:
Как видим - все работает так, как надо. Клиент подключается к сервису, получает от него информацию и выводит ее на консоль.
Поиздеваемся над нашим клиентом - закомментируем строку tracker.open(); в активаторе:
//tracker.open();
Заново запустим Equinox, установим бандлы и активируем их в нужной последовательности. Как видим
на экран ничего не вывелось. Потому что закрытый треккер не может вернуть сервис.
Вернем все обратно. Давайте попробуем стартовать бандлы в неправильном порядке - сначала клиентский, а потом сервисный:
После старта клиентского бандла - ничего не вывелось. Потому что клиент не может получить сервис - сервиса в системе нет. После того как мы стартуем бандл с сервисом - необходимо будет выполнить команду refresh, чтобы заново выполнить активатор бандла клиента:
Вот сейчас - все в порядке. Интересно получить информацию по нашему сервису. Напоминаю, что сделать это можно командой services (objectClass=*ColorizerService):
Сделаем некоторые выводы. Самый важный - использование сервисов представляет собой очень гибкую технологию, но за это надо платить. Мы обязаны в нашем приложении обеспечить правильный порядок активации бандлов. А если бандлов много и граф, описывающий зависимости между ними, очень сложен? Тогда задача обеспечить активацию бандлов будет весьма нетривиальной. К счастью, разработчики спецификации OSGi кое-что для этого придумали, что именно - мы обсудим позже.
А сейчас, подумайте вот над чем: "А что если мы хотим зарегистрировать 2 ColorizerService в разных бандлах, каждый со своей палитрой?" Ведь при описанном способе взаимодействия клиент сможет получить доступ лишь к тому сервису, который будет зарегистрирован последним. О том, как решить данную проблему, мы и поговорим в следующей статье цикла.
Оставайтесь на связи!
Понравилось сообщение - подпишись на блог
5 комментариев:
Что-то я не понял, зачем два интерфейса создавалось? IColorMenu и IBeqService? Как мне показалось, они никакой роли не играют, можно было упростить код, не используя их
Читайте следующую заметку, там это объяснено сквозь строк :)
Конечно, можно было бы их выкинуть и переписать код. Но с интерфейсами в данном случае более наглядно, как мне кажется.
А есть какой-нибудь стандартный способ передачи объекта между двумя несвязанными плагинами? Т.е. некоторый singleton DataExchenger.
Я в эту сторону не копал, поэтому не скажу. Мне пока такой объект не пригождался. Можно задать вопрос на форуме Equinox: http://www.eclipse.org/forums/index.php?t=thread&frm_id=31& Если что-то нароете, пожалуйста дайте знать.
Вот эта строка уже не работает в новом equinox (Eclipse Luna):
registration = context.registerService(service.getName(), service, props);
метод требует объекта java.util.Dictionary вместо java.util.Properties
Отправить комментарий
Любой Ваш комментарий важен для меня, однако, помните, что действует предмодерация. Давайте уважать друг друга!