При знакомстве с Eclipse Communication Framework'ом мы отметили, что некоторые его контейнеры поддерживают несколько разнородных API. В частности, R-OSGi и Generiс контейнеры, а так же появившийся в недавно вышедшем ECF 3.1 REST контейнер, поддерживают API вызова удаленных сервисов, т.н. Remote Services API.
Давайте поговорим о том, что можно делать с помощью данного API, а затем о том, как его использовать.
ECF Remote Services API это еще один (наряду с R-OSGi) способ обеспечения работы бандлов в распределенной среде. Точно так же одни бандлы могут выставлять сервисы, доступные другим бандлам на другой JVM (и, естественно, даже на другой машине). Отличие от R-OSGi в том, что данная система, во-первых, не стандартизирована (т.е. это не OSGi подсистема, а часть непосредственно ECF), а, во-вторых, - более гибка, т.к. можно явно указывать, где находятся бандлы, выставляющие сервисы, (т.е. отпадает необходимость в процедуре поиска сервисов) и использовать для обеспечения взаимодействия различные протоколы и реализации (ECF Generic, Active MQ, Weblogic, JavaGroups и даже XMPP).
Основной механизм, через который осуществляется использование данного API (как, впрочем, и других API фреймворка) - механизм адаптеров. После создания экземпляра любого контейнера, поддерживающего Remote Services API, необходимо привести его к классу IRemoteServiceContainerAdapter с помощью вызова метода getAdapter(IRemoteServiceContainerAdapter.class).
Небольшое отступление. Использование такого механизма обеспечивает очень высокую гибкость. Основное свойство ECF заключается в том, что каждый контейнер может реализовывать несколько API, а каждый API быть реализовано несколькими контейнерами. Причем можно расширять как набор API, так и набор контейнеров. Поэтому контейнер реализует интерфейс IContainer и знать не знает об каких-то там API. Эта расширяемость как раз и обеспечивается за счет применения механизма адаптеров и их независимой регистрации.
После этого начинаются различия между хостовым бандлом и клиентским. Хостовый бандл регистрирует сервис с помощью вызова метода IRemoteServiceContainerAdapter#registerRemoteService, который получает на вход массив имен сервисов; экземпляр класса, реализующего сервис, и словарь свойств сервиса:
_context = context;
// Create R-OSGi Container
IContainerManager containerManager = getContainerManagerService();
_container = containerManager.getContainerFactory().createContainer("ecf.r_osgi.peer");
// Get remote service container adapter
IRemoteServiceContainerAdapter containerAdapter = (IRemoteServiceContainerAdapter) _container
.getAdapter(IRemoteServiceContainerAdapter.class);
// Register remote service
_serviceRegistration = containerAdapter.registerRemoteService(new String[] {IHello.class.getName()},
new SimpleHello(), null);
System.out.println("IHello RemoteService registered");
// Create R-OSGi Container
IContainerManager containerManager = getContainerManagerService();
_container = containerManager.getContainerFactory().createContainer("ecf.r_osgi.peer");
// Get remote service container adapter
IRemoteServiceContainerAdapter containerAdapter = (IRemoteServiceContainerAdapter) _container
.getAdapter(IRemoteServiceContainerAdapter.class);
// Register remote service
_serviceRegistration = containerAdapter.registerRemoteService(new String[] {IHello.class.getName()},
new SimpleHello(), null);
System.out.println("IHello RemoteService registered");
Клиент же должен найти ссылки на заданные сервисы и обеспечить получение нужного.
public static final String ROSGI_SERVICE_HOST = "r-osgi://localhost:9278";
//...
_context = context;
// Create R-OSGi Container
IContainerManager containerManager = getContainerManagerService();
_container = containerManager.getContainerFactory().createContainer("ecf.r_osgi.peer");
// Get remote service container adapter
IRemoteServiceContainerAdapter containerAdapter = (IRemoteServiceContainerAdapter) _container
.getAdapter(IRemoteServiceContainerAdapter.class);
// Lookup IRemoteServiceReference
IRemoteServiceReference[] helloReferences = containerAdapter.getRemoteServiceReferences(IDFactory.getDefault().createID(_container.getConnectNamespace(), ROSGI_SERVICE_HOST), IHello.class.getName(), null);
// Get remote service for reference
IRemoteService remoteService = containerAdapter.getRemoteService(helloReferences[0]);
//...
_context = context;
// Create R-OSGi Container
IContainerManager containerManager = getContainerManagerService();
_container = containerManager.getContainerFactory().createContainer("ecf.r_osgi.peer");
// Get remote service container adapter
IRemoteServiceContainerAdapter containerAdapter = (IRemoteServiceContainerAdapter) _container
.getAdapter(IRemoteServiceContainerAdapter.class);
// Lookup IRemoteServiceReference
IRemoteServiceReference[] helloReferences = containerAdapter.getRemoteServiceReferences(IDFactory.getDefault().createID(_container.getConnectNamespace(), ROSGI_SERVICE_HOST), IHello.class.getName(), null);
// Get remote service for reference
IRemoteService remoteService = containerAdapter.getRemoteService(helloReferences[0]);
Видно, что осуществляется поиск сервисов в конкретном удаленном контейнере, имеющем конкретный адрес.
После получения сервиса его можно вызвать. Remote Service API позволяет вызывать сервис по-разному:
1. С помощью прокси
// Get the proxy
IHello proxy = (IHello) remoteService.getProxy();
// Call the proxy
proxy.hello("RemoteService Client via Proxy");
System.out.println((new Date()) + " RemoteService Called via Proxy");
IHello proxy = (IHello) remoteService.getProxy();
// Call the proxy
proxy.hello("RemoteService Client via Proxy");
System.out.println((new Date()) + " RemoteService Called via Proxy");
2. Через синхронный вызов. "Вызов" в данном и нижеследующих случаях обозначает экземпляр класса, реализующего интерфейс IRemoteCall.
public void start(BundleContext context) throws Exception
{
// Call Sync
remoteService.callSync(createRemoteCall("RemoteService Client Sync"));
System.out.println((new Date()) + " RemoteService Called Sync");
}
// ...
private IRemoteCall createRemoteCall(final String message)
{
return new IRemoteCall()
{
@Override
public String getMethod()
{
return "hello";
}
@Override
public Object[] getParameters()
{
return new Object[] {message};
}
@Override
public long getTimeout()
{
return 0;
}
};
}
{
// Call Sync
remoteService.callSync(createRemoteCall("RemoteService Client Sync"));
System.out.println((new Date()) + " RemoteService Called Sync");
}
// ...
private IRemoteCall createRemoteCall(final String message)
{
return new IRemoteCall()
{
@Override
public String getMethod()
{
return "hello";
}
@Override
public Object[] getParameters()
{
return new Object[] {message};
}
@Override
public long getTimeout()
{
return 0;
}
};
}
3. Через асинхронный вызов. Напомню, что асинхронный вызов представляет собой команду, посылаемую сервису и обработчик, срабатывающий при возвращении сервисом значения. После отправки команды клиент может продолжать выполнять какой-то код.
Обработчик представляет собой объект класса, реализующего интерфейс IRemoteCallListener
public void start(BundleContext context) throws Exception
{
// ...
// Call Async
remoteService.callAsync(createRemoteCall("RemoteService Client Async"), createRemoteCallListener());
System.out.println((new Date()) + " RemoteService Called Async");
}
private IRemoteCallListener createRemoteCallListener()
{
return new IRemoteCallListener()
{
@Override
public void handleEvent(IRemoteCallEvent event)
{
if (event instanceof IRemoteCallCompleteEvent)
{
IRemoteCallCompleteEvent cce = (IRemoteCallCompleteEvent) event;
if (!cce.hadException())
System.out.println("Remote call completed successfully!");
else
System.out.println("Remote call completed with exception: " + cce.getException());
}
}
};
}
{
// ...
// Call Async
remoteService.callAsync(createRemoteCall("RemoteService Client Async"), createRemoteCallListener());
System.out.println((new Date()) + " RemoteService Called Async");
}
private IRemoteCallListener createRemoteCallListener()
{
return new IRemoteCallListener()
{
@Override
public void handleEvent(IRemoteCallEvent event)
{
if (event instanceof IRemoteCallCompleteEvent)
{
IRemoteCallCompleteEvent cce = (IRemoteCallCompleteEvent) event;
if (!cce.hadException())
System.out.println("Remote call completed successfully!");
else
System.out.println("Remote call completed with exception: " + cce.getException());
}
}
};
}
4. Через IFuture. Данный механизм так же асинхронный, но отличается от предыдущего тем, что результат от сервиса мы получаем тогда, когда хотим. Если к тому времени результат еще не вычислен - наш поток блокируется.
// Call Async via IFuture
IFuture future = remoteService.callAsync(createRemoteCall("RemoteService Client Async via Future"));
System.out.println((new Date()) + " Create IFuture");
// ...
future.get();
System.out.println((new Date()) + " RemoteService Called via IFuture");
IFuture future = remoteService.callAsync(createRemoteCall("RemoteService Client Async via Future"));
System.out.println((new Date()) + " Create IFuture");
// ...
future.get();
System.out.println((new Date()) + " RemoteService Called via IFuture");
Для запуска нужно создать соответствующую конфигурацию, куда обязательно добавить следующие бандлы:
name.samolisov.ecf.hello
name.samolisov.ecf.remoteservices.rosgi.host (для клиента - .client Auto-Start: true)
org.eclipse.ecf
org.eclipse.ecf.sharedobject
org.eclipse.ecf.remoteservice
org.eclipse.ecf.identity
org.eclipse.ecf.discovery
org.eclipse.ecf.provider
org.eclipse.ecf.provider.jmdns
org.eclipse.ecf.provider.r_osgi
org.eclipse.ecf.ssl
ch.ethz.iks.r_osgi.remote
org.eclipse.core.jobs
org.eclipse.core.runtime.compatibility.registry
org.eclipse.equinox.common
org.eclipse.equinox.concurrent
org.eclipse.equinox.registry
org.eclipse.osgi
org.eclipse.osgi.services
org.objectweb.asm
Пара слов об использовании протокола, специально разработанного для ECF. Этот протокол представлен двумя видами контейнеров: ecf.generic.server и ecf.generic.client. Сервер, соответственно, используется в хостовом бандле, а клиент - в клиентском. При создании сервера можно задать его порт и т.н. группу (часть имени после последнего слэша):
public static final String GENERIC_SERVICE_HOST = "ecftcp://localhost:4280/mygroup";
// ...
// Create R-OSGi Container
ID serverId = IDFactory.getDefault().createStringID(GENERIC_SERVICE_HOST);
IContainerManager containerManager = getContainerManagerService();
_container = containerManager.getContainerFactory().createContainer("ecf.generic.server", serverId);
// ...
// Create R-OSGi Container
ID serverId = IDFactory.getDefault().createStringID(GENERIC_SERVICE_HOST);
IContainerManager containerManager = getContainerManagerService();
_container = containerManager.getContainerFactory().createContainer("ecf.generic.server", serverId);
Чтобы при использовании generic в момент получения адаптера не возникло NPE, нужно немного изменить конфигурацию запуска - добавить туда бандл org.eclipse.ecf.provider.remoteservice. Относящиеся к R-OSGi бандлы (ch.ethz.iks.r_osgi.remote, org.objectweb.asm, org.eclipse.ecf.provider.jmdns, org.eclipse.ecf.provider.r_osgi) можно убрать.
Возможно, данная статья поможет вам разобраться с понятием remouting и заставит обратить внимание на соответствующие механизмы платформы Eclipse. В любом случае буду рад ответить на ваши вопросы.
Скачать примеры к статье (исходники, class-файлы и конфигурации запуска. Rar, 39 Кб)
Понравилось сообщение - подпишитесь на блог или читайте меня в twitter
Комментариев нет:
Отправить комментарий
Любой Ваш комментарий важен для меня, однако, помните, что действует предмодерация. Давайте уважать друг друга!