OSGi: привязка сервисов без управления жизненным циклом
Я создаю Java-приложение на платформе Equinox OSGi и использую DS (декларативные сервисы) для декларирования ссылочных и предоставляемых сервисов. До сих пор все потребители услуг, которых я внедрил, также являлись поставщиками услуг, поэтому для меня было естественным сделать их лицами без гражданства (чтобы их можно было повторно использовать для нескольких потребителей, а не привязывать к одному потребителю) и позволить им быть создается в фреймворке (конструктор по умолчанию, нигде в моем коде не используется).
Теперь у меня другая ситуация: у меня есть класс MyClass
который ссылается на услугу MyService
но сам по себе не является поставщиком услуг. Я должен быть в состоянии создать экземпляр MyClass
Я сам, вместо того, чтобы позволить платформе OSGi создавать его. Я бы тогда хотел, чтобы структура прошла существующий MyService
экземпляр к MyClass
экземпляр (ы). Что-то вроде этого:
public class MyClass {
private String myString;
private int myInt;
private MyService myService;
public MyClass(String myString, int myInt) {
this.myString = myString;
this.myInt= myInt;
}
// bind
private void setMyService(MyService myService) {
this.myService = myService;
}
// unbind
private void unsetMyService(MyService myService) {
this.myService = null;
}
public void doStuff() {
if (myService != null) {
myService.doTheStuff();
} else {
// Some fallback mechanism
}
}
}
public class AnotherClass {
public void doSomething(String myString, int myInt) {
MyClass myClass = new MyClass(myString, myInt);
// At this point I would want the OSGi framework to invoke
// the setMyService method of myClass with an instance of
// MyService, if available.
myClass.doStuff();
}
}
Моей первой попыткой было использование DS для создания определения компонента для MyClass
и ссылка MyService
оттуда:
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="My Class">
<implementation class="my.package.MyClass"/>
<reference bind="setMyService" cardinality="0..1" interface="my.other.package.MyService" name="MyService" policy="static" unbind="unsetMyService"/>
</scr:component>
Тем не мение, MyClass
на самом деле не является компонентом, так как я не хочу, чтобы его жизненный цикл управлялся - я хочу сам позаботиться о реализации. Как Neil Bartlett указывает здесь:
Например, вы можете сказать, что ваш компонент "зависит" от конкретной службы, и в этом случае компонент будет создан и активирован только тогда, когда эта служба доступна, а также он будет уничтожен, когда служба станет недоступной.
Это не то, что я хочу. Я хочу привязку без управления жизненным циклом.
[ Примечание: даже если я установил мощность 0..1
(необязательный и унарный) MyClass
(и не удалось из-за отсутствия конструктора без аргументов)]
Итак, мой вопрос: есть ли способ использовать DS, чтобы получить эту функциональность "только связывание, без управления жизненным циклом", которую я ищу? Если это невозможно с DS, какие есть альтернативы и что бы вы порекомендовали?
Обновление: использовать ServiceTracker
(предложено Нилом Бартлеттом)
ВАЖНО: я опубликовал улучшенную версию этого в качестве ответа. Я просто держу это здесь для "исторических" целей.
Я не уверен, как подать заявку ServiceTracker
в этом случае. Вы бы использовали статический реестр, как показано ниже?
public class Activator implements BundleActivator {
private ServiceTracker<MyService, MyService> tracker;
@Override
public void start(BundleContext bundleContext) throws Exception {
MyServiceTrackerCustomizer customizer = new MyServiceTrackerCustomizer(bundleContext);
tracker = new ServiceTracker<MyService, MyService>(bundleContext, MyService.class, customizer);
tracker.open();
}
@Override
public void stop(BundleContext bundleContext) throws Exception {
tracker.close();
}
}
public class MyServiceTrackerCustomizer implements ServiceTrackerCustomizer<MyService, MyService> {
private BundleContext bundleContext;
public MyServiceTrackerCustomizer(BundleContext bundleContext) {
this.bundleContext = bundleContext;
}
@Override
public MyService addingService(ServiceReference<MyService> reference) {
MyService myService = bundleContext.getService(reference);
MyServiceRegistry.register(myService); // any better suggestion?
return myService;
}
@Override
public void modifiedService(ServiceReference<MyService> reference, MyService service) {
}
@Override
public void removedService(ServiceReference<MyService> reference, MyService service) {
bundleContext.ungetService(reference);
MyServiceRegistry.unregister(service); // any better suggestion?
}
}
public class MyServiceRegistry {
// I'm not sure about using a Set here... What if the MyService instances
// don't have proper equals and hashCode methods? But I need some way to
// compare services in isActive(MyService). Should I just express this
// need to implement equals and hashCode in the javadoc of the MyService
// interface? And if MyService is not defined by me, but is 3rd-party?
private static Set<MyService> myServices = new HashSet<MyService>();
public static void register(MyService service) {
myServices.add(service);
}
public static void unregister(MyService service) {
myServices.remove(service);
}
public static MyService getService() {
// Return whatever service the iterator returns first.
for (MyService service : myServices) {
return service;
}
return null;
}
public static boolean isActive(MyService service) {
return myServices.contains(service);
}
}
public class MyClass {
private String myString;
private int myInt;
private MyService myService;
public MyClass(String myString, int myInt) {
this.myString = myString;
this.myInt= myInt;
}
public void doStuff() {
// There's a race condition here: what if the service becomes
// inactive after I get it?
MyService myService = getMyService();
if (myService != null) {
myService.doTheStuff();
} else {
// Some fallback mechanism
}
}
protected MyService getMyService() {
if (myService != null && !MyServiceRegistry.isActive(myService)) {
myService = null;
}
if (myService == null) {
myService = MyServiceRegistry.getService();
}
return myService;
}
}
Это как ты это сделаешь? И не могли бы вы прокомментировать вопросы, которые я написал в комментариях выше? То есть:
- Проблемы с
Set
если реализации службы не реализуют должным образомequals
а такжеhashCode
, - Состояние гонки: услуга может стать неактивной после моего
isActive
проверять.
2 ответа
Решение: использовать ServiceTracker
(как предложил Нил Бартлетт)
Примечание: если вы хотите увидеть причину понижения голосов, просмотрите ответ Нейла и наши комментарии в комментариях.
В конце концов я решил это с помощью ServiceTracker
и статический реестр (MyServiceRegistry
), как показано ниже.
public class Activator implements BundleActivator {
private ServiceTracker<MyService, MyService> tracker;
@Override
public void start(BundleContext bundleContext) throws Exception {
MyServiceTrackerCustomizer customizer = new MyServiceTrackerCustomizer(bundleContext);
tracker = new ServiceTracker<MyService, MyService>(bundleContext, MyService.class, customizer);
tracker.open();
}
@Override
public void stop(BundleContext bundleContext) throws Exception {
tracker.close();
}
}
public class MyServiceTrackerCustomizer implements ServiceTrackerCustomizer<MyService, MyService> {
private BundleContext bundleContext;
public MyServiceTrackerCustomizer(BundleContext bundleContext) {
this.bundleContext = bundleContext;
}
@Override
public MyService addingService(ServiceReference<MyService> reference) {
MyService myService = bundleContext.getService(reference);
MyServiceRegistry.getInstance().register(myService);
return myService;
}
@Override
public void modifiedService(ServiceReference<MyService> reference, MyService service) {
}
@Override
public void removedService(ServiceReference<MyService> reference, MyService service) {
bundleContext.ungetService(reference);
MyServiceRegistry.getInstance().unregister(service);
}
}
/**
* A registry for services of type {@code <S>}.
*
* @param <S> Type of the services registered in this {@code ServiceRegistry}.<br>
* <strong>Important:</strong> implementations of {@code <S>} must implement
* {@link #equals(Object)} and {@link #hashCode()}
*/
public interface ServiceRegistry<S> {
/**
* Register service {@code service}.<br>
* If the service is already registered this method has no effect.
*
* @param service the service to register
*/
void register(S service);
/**
* Unregister service {@code service}.<br>
* If the service is not currently registered this method has no effect.
*
* @param service the service to unregister
*/
void unregister(S service);
/**
* Get an arbitrary service registered in the registry, or {@code null} if none are available.
* <p/>
* <strong>Important:</strong> note that a service may become inactive <i>after</i> it has been retrieved
* from the registry. To check whether a service is still active, use {@link #isActive(Object)}. Better
* still, if possible don't store a reference to the service but rather ask for a new one every time you
* need to use the service. Of course, the service may still become inactive between its retrieval from
* the registry and its use, but the likelihood of this is reduced and this way we also avoid holding
* references to inactive services, which would prevent them from being garbage-collected.
*
* @return an arbitrary service registered in the registry, or {@code null} if none are available.
*/
S getService();
/**
* Is {@code service} currently active (i.e., running, available for use)?
* <p/>
* <strong>Important:</strong> it is recommended <em>not</em> to store references to services, but rather
* to get a new one from the registry every time the service is needed -- please read more details in
* {@link #getService()}.
*
* @param service the service to check
* @return {@code true} if {@code service} is currently active; {@code false} otherwise
*/
boolean isActive(S service);
}
/**
* Implementation of {@link ServiceRegistry}.
*/
public class ServiceRegistryImpl<S> implements ServiceRegistry<S> {
/**
* Services that are currently registered.<br>
* <strong>Important:</strong> as noted in {@link ServiceRegistry}, implementations of {@code <S>} must
* implement {@link #equals(Object)} and {@link #hashCode()}; otherwise the {@link Set} will not work
* properly.
*/
private Set<S> myServices = new HashSet<S>();
@Override
public void register(S service) {
myServices.add(service);
}
@Override
public void unregister(S service) {
myServices.remove(service);
}
@Override
public S getService() {
// Return whatever service the iterator returns first.
for (S service : myServices) {
return service;
}
return null;
}
@Override
public boolean isActive(S service) {
return myServices.contains(service);
}
}
public class MyServiceRegistry extends ServiceRegistryImpl<MyService> {
private static final MyServiceRegistry instance = new MyServiceRegistry();
private MyServiceRegistry() {
// Singleton
}
public static MyServiceRegistry getInstance() {
return instance;
}
}
public class MyClass {
private String myString;
private int myInt;
public MyClass(String myString, int myInt) {
this.myString = myString;
this.myInt= myInt;
}
public void doStuff() {
MyService myService = MyServiceRegistry.getInstance().getService();
if (myService != null) {
myService.doTheStuff();
} else {
// Some fallback mechanism
}
}
}
Если кто-то хочет использовать этот код для каких-либо целей, продолжайте.
Нет, это выходит за рамки DS. Если вы хотите напрямую создать экземпляр класса, вам придется использовать OSGi API, такие как ServiceTracker
получить сервисные ссылки.
Обновить:
Смотрите следующий предложенный код. Очевидно, есть много разных способов сделать это, в зависимости от того, чего вы действительно хотите достичь.
public interface MyServiceProvider {
MyService getService();
}
...
public class MyClass {
private final MyServiceProvider serviceProvider;
public MyClass(MyServiceProvider serviceProvider) {
this.serviceProvider = serviceProvider;
}
void doStuff() {
MyService service = serviceProvider.getService();
if (service != null) {
// do stuff with service
}
}
}
...
public class ExampleActivator implements BundleActivator {
private MyServiceTracker tracker;
static class MyServiceTracker extends ServiceTracker<MyService,MyService> implements MyServiceProvider {
public MyServiceTracker(BundleContext context) {
super(context, MyService.class, null);
}
};
@Override
public void start(BundleContext context) throws Exception {
tracker = new MyServiceTracker(context);
tracker.open();
MyClass myClass = new MyClass(tracker);
// whatever you wanted to do with myClass
}
@Override
public void stop(BundleContext context) throws Exception {
tracker.close();
}
}