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;
    }

}

Это как ты это сделаешь? И не могли бы вы прокомментировать вопросы, которые я написал в комментариях выше? То есть:

  1. Проблемы с Set если реализации службы не реализуют должным образом equals а также hashCode,
  2. Состояние гонки: услуга может стать неактивной после моего 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();
    }

}
Другие вопросы по тегам