Статьи

OSGi Dependency Injection

OSGi следует стандартной парадигме модели обслуживания. Это необходимо, потому что Java показывает, как трудно писать совместно, используя только совместное использование классов. Стандартное решение в Java — использовать фабрики, которые используют динамическую загрузку классов и статику. Например, если вам нужна фабрика, вы вызываете статический фабричный метод Factory.newInstance (). За этим фасадом методы newInstance пробуют каждый прием загрузчика классов для создания экземпляра подкласса реализации класса BuilderFactory.

Решением всех этих проблем является просто реестр служб OSGi. Пакет может создать объект и зарегистрировать его в реестре службы OSGi под одним или несколькими интерфейсами. Другие пакеты могут перейти в реестр и перечислить все объекты, которые зарегистрированы в определенном интерфейсе или классе. Например, пакет обеспечивает реализацию Builder. Когда он запускается, он создает экземпляр своего класса BuilderFactoryImpl и регистрирует его в реестре в классе BuilderFactory. Пакет, для которого требуется BuilderFactory, может перейти в реестр и запросить все доступные службы с классом BuilderFactory. Более того, пакет может дождаться появления определенной службы и затем получить обратный вызов.

Таким образом, пакет может зарегистрировать сервис, получить сервис и прослушать, чтобы сервис появлялся или исчезал. Любое количество пакетов может зарегистрировать один и тот же тип сервиса, и любое количество пакетов может получить один и тот же сервис. Это изображено на следующем рисунке.

альтернативный текст

Хорошо известные методы введения зависимостей

  1. Декларативная служба
  2. ServiceTracker и ServiceTrackerCustomizer
  3. Обычный способ получения ServiceReferences
  4. ServiceListener
  5. ServiceFactory
  6. iPOJO
  7. Феликс Менеджер зависимостей

Шаблон доски

Шаблон whiteboard использует реестр служб платформы OSGi вместо реализации частного реестра, как того требует шаблон прослушивателя. Вместо того, чтобы прослушиватели событий отслеживали источники событий, а затем регистрировали себя в источнике событий, шаблон доски содержит прослушиватели событий, регистрирующие себя в качестве службы в среде OSGi. Когда у источника события есть объект события для доставки, источник события вызывает все прослушиватели событий в реестре службы.

Примечательно, что источник события не зарегистрирован в платформе как сервис. Это делает пакеты, использующие шаблон доски, значительно меньше и проще в реализации. Зависимость между пакетами между источником события и слушателем события обрабатывается платформой и почти не требует кода в связках источника события и слушателя события.

Следующие методы внедрения зависимостей следуют шаблону доски

  1. Декларативные услуги
  2. ServiceTracker и ServiceTrackerCustomizer
  3. ServiceFactory

Декларативные услуги

Декларативные службы делают написание реализации службы столь же простым, как написание POJO с несколькими аннотациями. Хотя есть и другие системы, которые делают инъекции, подобные декларативным службам, эти другие системы игнорируют время и зависимости. Обрабатывая временные и (динамические) зависимости без каких-либо накладных расходов на код, OSGi предоставляет набор инструментов, который является таким же инновационным, как объекты в 90-е годы.

ServiceTracker Полезные методы

public class ServiceTracker < S, T > implements ServiceTrackerCustomizer < S, T > {
    .....
    public void open() {..
    }
    public void open(boolean trackAllServices) {..
    }
    public T getService() {..
    }
    public Object[] getServices() {..
    }
    public T waitForService(long timeout) throws InterruptedException {..
    }
    public void close() {..
    }
    ....
}

public interface ServiceTrackerCustomizer < S, T > {
    public T addingService(ServiceReference < S > reference);
    public void modifiedService(ServiceReference < S > reference, T service);
    public void removedService(ServiceReference < S > reference, T service);
}

Пример ServiceTracker

public final class ServiceTrackerExample {

    private ServiceTracker logTracker;

    void init() {
        logTracker = new ServiceTracker(bc, LogService.class.getName(), null);
        logTracker.open();
    }

    LogService getLog() {
        return (LogService) logTracker.getService();
    }

    void test() {
        getLog().doLog(...);
    }

    void destroy() {
        logTracker.close();
    }

}

Пример ServiceReference

public final class ServiceReferenceExample {

    private BundleContext bc = ...;

    void doSomething() {
        final ServiceReference sr = bc.getServiceReference(HttpService.class.getName());
        if (sr != null) {
            final HttpService http = (HttpService) bc.getService(sr);
            if (http != null) {
                http.registerServlet(....)
            }
        }
    }

    void destroy() {
        bc.ungetService(sr)
    }

}

Причуды

Эти API легко могут быть использованы неправильно, если не будут приняты надлежащие меры предосторожности. Несколько примеров были продемонстрированы следующим образом.

Пример 1

public final class ServiceReferenceExample {

    private final BundleContext bc;

    public ServiceReferenceExample() {
        bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
    }

    void doSomething() {
        final ServiceReference sr = bc.getServiceReference(HttpService.class.getName());
        if (sr != null) {
            final HttpService http = (HttpService) bc.getService(sr);
            if (http != null) {
                http.registerServlet(....);
            }
        }
    }

    void destroy() {
        // bc.ungetService(sr)
    }

}

Вызов getService (..) увеличивает счетчик использования HttpService этого пакета. OSGi Service Registry отслеживает счетчики использования службы для оптимизации внедрения и очистки служб. Если мы забудем вызвать ungetService (..), счетчик сервисов никогда не уменьшится, что приведет к проблемам с оптимизацией.

Использование пакета сервисным объектом, полученным из этого метода getService (..), отслеживается счетчиком использования пакета этой службы. Каждый раз, когда объект службы возвращается функцией getService (..), счетчик использования пакета контекста для службы увеличивается на единицу. Каждый раз, когда объект службы освобождается с помощью ungetService (..), счетчик использования комплекта контекста для службы уменьшается на единицу.

Пример 2

public final class ServiceReferenceExample {

    private final BundleContext bc;
    private ServiceReference < HttpService > sr;

    public ServiceReferenceExample() {
        bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
    }
    void doThis() {
        sr = bc.getServiceReference(HttpService.class.getName());
        if (sr != null) {
            final HttpService http = (HttpService) bc.getService(sr);
            if (http != null) {
                http.registerServlet(....);
            }
        }
    }
    void doThat() {
        if (sr != null) {
            final HttpService http = (HttpService) bc.getService(sr);
            if (http != null) {
                http.registerServlet(....);
            }
        }
    }
    void destroy() {
        bc.ungetService(sr);
    }

}

В этом примере несколько вызовов getService (..) приводят к тому, что счетчик использования службы увеличивается на 1, и, наконец, в этом примере счетчик служб будет равен 2. Вызов ungetService (..) только уменьшит счетчик использования сервиса на 1.

Пример 3

public final class ServiceReferenceExample {

    private final BundleContext bc1;
    private final BundleContext bc2;

    public ServiceReferenceExample() {
        bc1 = FrameworkUtil.getBundle(getClass()).getBundleContext();
        bc2 = FrameworkUtil.getBundle(A.class).getBundleContext(); //A.java in other bundle and has been imported
    }

    void doSomething() {
        final ServiceReference sr = bc1.getServiceReference(HttpService.class.getName());
        if (sr != null) {
            final HttpService http = (HttpService) bc1.getService(sr);
            if (http != null) {
                http.registerServlet(....)
            }
        }
    }

    void destroy() {
        bc2.ungetService(sr)
    }

}

В этом примере BundleContext, который вызывает getService (..), будет иметь счетчик сервисов 1 для HttpService. С другой стороны, BundleContext, который вызывает ungetService (..), не приведет к уменьшению счетчика сервисов, поскольку счетчик сервисов уже равен 0 для пакета, в котором находится A.java. И счетчик сервисов, который вызывает getService (.. ) останется 1.

Пример 4

public final class ServiceReferenceExample {

    private volatile HttpService httpService;

    public ServiceReferenceExample() {
        final ServiceTracker << ? , ? > serviceTracker = new CustomServiceTracker(
            Activator.getBundleContext());
        serviceTracker.open();
    }
    void doSomething() {
        ...
    }
    private final class CustomServiceTracker extends ServiceTracker {

        public DbManagerServiceTracker(BundleContext context) {
            super(context, HttpService.class.getName(), null);
        }

        @Override
        public Object addingService(ServiceReference reference) {
            httpService = (HttpService) context.getService(reference);
            return httpService;
        }

        @Override
        public void removedService(ServiceReference reference, Object service) {
            context.ungetService(reference);
            dbManager = null;
        }
    }
}

Вот типичное использование, которое разработчики делают довольно часто. Это можно было бы просто сделать, вызвав getService () в ServiceTracker. Расширение ServiceTracker не легко обслуживать. Мы должны реализовать ServiceTrackerCustomizer тогда и только тогда, когда нам нужны определенные операции, выполняемые при отслеживании услуг.

Пример 5

public final class ServiceReferenceExample {

    private volatile DBManager dbManager;

    public ServiceReferenceExample() {
        final ServiceTracker << ? , ? > serviceTracker = new ServiceTracker(
            Activator.getBundleContext(), DBManager.class.getName(), null);
        serviceTracker.open();
    }

    void doSomething() {
        dbManager = serviceTracker.getService();
        dbManager.save()
    }

}

Если ServiceTracker больше не требуется для отслеживания каких-либо изменений, рекомендуется закрыть его.

Лучшие практики (По мне и могут быть изменены)

  1. Всегда снимайте ServiceReference после получения экземпляра службы.
  2. Снимите ServiceReference с того же BundleContext, который его запросил.
  3. Будьте осторожны, вызывая getServiceReference () для BundleContext, поскольку он возвращает первый найденный ServiceReference. Вместо этого вызовите getServiceReferences ().
  4. Кошмар программиста : служба многопотокового запроса на одном и том же BundleContext. В этом случае поток совместно использует тот же экземпляр BundleContext.
  5. Всегда закрывайте ServiceTracker, если вам не нужно продолжать прослушивание.
  6. Не используйте несколько BundleContexts для запроса ServiceReferences.
  7. Если потреблением услуг нельзя манипулировать по требованию, переключитесь на Декларативные услуги или Felix DM.
  8. ServiceTracker может прослушивать несколько экземпляров запрошенного сервиса. Поэтому лучше вместо этого вызывать getServiceReferences (), поскольку вы не знаете, сколько сервисов будет отслеживаться (если вы не уверены на 100%).
  9. Как только вы позвоните, getServiceReferences () на ServiceTracker, затем следуйте от # 1 до # 3.
  10. Не переопределяйте ServiceTracker. Реализуйте ServiceTrackerCustomizer вместо этого. Это более удобно для потребителя.