Сервіс взаємодії бандлів

Модульна система, яка підтримує тільки статичні залежності, є крихкою і не може підтримувати розширюваність. Крихкість означає, що не можна видалити будь-якої модуль, не порушуючи працездатності (компіляцію, зборку) додатки в цілому. Відсутність розширюваності означає, що не можна додати до системи новий функціональний модуль без перекомпіляції існуючої системи.

Одна з найважливіших завдань об'єктно-орієнтованого програмування пов'язана з підвищенням гнучкості і повторним використанням коду за рахунок зменшення зв'язку між постачальниками певної функціональності і споживачами цієї функціональності. В Java це досягається за рахунок використання інтерфейсів. які представляють чисто абстрактні класи, що описують функціональність у вигляді певного списку методів інших класів, що реалізують їх. Інтерфейси і абстрактні класи можна використовувати в якості посилань на певний об'єкт при пізньому зв'язуванні для побудові модульної системи.

пізніше зв'язування

OSGi вирішує проблему «пізнього зв'язування» використанням динамічних сервісів, в основі яких лежить принцип впровадження залежності Dependency injection (DI) - процес надання зовнішньої залежності програмного компоненту. DI є своєрідною формою «інверсії управління» (Inversion of control, IoC), коли вона застосовується до управління залежностями. У цьому випадку об'єкт (bundle) перекладає відповідальність за побудову необхідних йому залежностей зовнішньому, спеціально призначеному для цього спільного механізму. Слід згадати, що в специфікації OSGi оперують так званими бандл (bundle). У тексті статті поряд з бандл можна зустріти також і таку сутність, як плагін, значення якого віднесемо до синоніма.

На наступному малюнку представлена ​​схема взаємодії бандлів в OSGi контейнері з використанням сервісів.

Сервіс взаємодії бандлів

Надає певні послуги bundle повинен бути зареєстрований як сервіс (Registr) в реєстрі контейнера OSGi. Решта бандли (client) можуть отримати на нього «заслання» (Find). Після цього необхідно з сервісом зв'язатися (Bind) і використовувати його послуги (методи).

Тут слід звернути увагу на особливості використання даної схеми взаємодії:

  • bundle-сервіс повинен бути зареєстрований раніше bundle-клієнт;
  • bundle-клієнт повинен знати про наявність певного bundle-сервіс в реєстрі.

Якщо bundle-сервіс незалежний від інших сервісів, то його розробка спрощується. необхідно відповідним чином визначити структуру і методи, після чого інсталювати в контейнер OSGi і зареєструвати в якості сервісу. А ось з bundle-клієнтом завдання трохи по-складніше, оскільки вже при розробці необхідно використовувати методи bundle-сервіс. Подивимося, як це буде виглядати на прикладі.

Приклад використання OSGi сервісу для взаємодії бандлів

Розглянемо приклад взаємодії двох бандлів. Один bundle (сервіс) буде надавати список валют, а інший bundle (клієнт) буде підключатися до сервісу, отримувати список валют і виводити його в консоль. При розробці будуть створені два maven-проекту:

  • service-currency. сервіс надання списку валют;
  • bundle-exchange. клієнт використання сервісу.

Опис bundle згідно специфікації OSGi і його відмінність від звичайного jar-файлу докладно описано тут.

Опис проекту service-currency

Структура сервісу service-currency включає інтерфейс ICurrency.java, реалізацію CurrencyImpl.java і активатор CurrencyActivator.java.

Лістинг інтерфейсу ICurrency.java

Інтерфейс включає один метод getName (). повертає найменування сервісу.

Лістинг класу CurrencyImpl.java

Клас CurrencyImpl реалізує метод getName () інтерфейсу ICurrency і включає додатково метод listCurrencies (). повертає список валют в вигляді масиву рядків.

Лістинг активатора сервісу CurrencyActivator.java

Активатор сервісу при старті (метод start) отримує в якості параметра контекст контейнера context типу BundleContext, в якому реєструє сервіс викликом методу контексту registerService. Метод registerService повертає об'єкт типу ServiceRegistration. який використовується при зупинці сервісу в методі stop. Як параметри для реєстрації сервісу в метод registerService передаються найменування сервісу (класу), примірник сервісу та властивості. Властивості сервісу можна не визначати, тоді в метод потрібно передати значення null.

У методі stop звільняються ресурси сервісу (bundle) і скасовується реєстрація сервісу викликом метод unregister класу ServiceRegistration.

Опис збірки pom.xml

Для складання проекту service-currency використовується pom.xml такого змісту:

У файлі опису збірки pom.xml визначається залежність фреймворка osgi (секція ) І параметри bundle для формування маніфесту META-INF / MANIFEST.MF. В результаті збірки в піддиректорії проекту target буде створено bundle як файл service-currency-1.0.0.jar з маніфестом такого змісту:

Інсталяція та реєстрація сервісу

Для тестування створеного сервісу використовуємо включений в архів прикладу папку , в якій розміщується org.eclipse.osgi_ХХХ.jar для створення контейнера Equinox. Про командах роботи в контейнері можна прочитати тут. Запустимо контейнер Equinox, інсталюємо в контейнер bundle-сервіс і стартуємо його:

В процесі тестування bundle-сервісу ми проводимо його через всі стадії життєвого циклу виконанням команд install, start, stop. Командою services з відповідним параметром в консоль була виведена інформація про сервіс:

  • використовуваний в якості сервісу клас com.osgi.service.CurrencyImpl;
  • властивість props (service = currency), визначене при реєстрації;
  • ідентифікаційний номер сервісу, в нашому випадку service.id = 29;
  • ким зареєстрований сервіс;
  • відсутність підключених бандлів.

Якщо викликати команду services без параметрів, то можна буде побачити весь список сервісів, завантажених контейнером Equinox.

Таким чином, можна зробити висновок, що сервіс успішно завантажений і зареєстрований в системі, але ніхто до нього не підключений.

Опис проекту bundle-exchange

Структура плагіна bundle-exchange включає інтерфейс IExchange.java, реалізацію ExchangeImpl.java і активатор ExchangeActivator.java.

Лістинг інтерфейсу IExchange.java

Інтерфейс включає один метод getCurrencies (). повертає список текстових значень.

Лістинг класу ExchangeImpl.java

При доступі до сервісу в методі getCurrencies () виконується перевірка на null. Якщо сервіс не знайдений, то метод поверне порожній список. Це може бути пов'язано з тим, що сервіс може бути відсутнім чи не бути зареєстрованим в системі. У цьому випадку на спробу отримати сервіс, ServiceTracker поверне нам null.

Утілітний клас OSGi ServiceTracker передається в якості параметра в конструктор і використовується для отримання зареєстрованого в системі сервісу.

C допомогою ServiceTracker можна:
- створити початковий список зазначених сервісів;
- відслідковувати події Service`Event цих сервісів;
- дозволяти власнику списку програмно його змінювати, так само як проводити будь-які дії в момент додавання або видалення сервісу в список.

Конструктори об'єкта ServiceTracker

Кожен з конструкторів приймає як параметр BundleContext. Перший конструктор використовується для отримання імені інтерфейсу сервісу в якості критерію пошуку. Другий конструктор використовується для відстеження всіх сервісів, які підпадають під зазначений фільтр. Третій конструктор приймає як аргумент ServiceReference. який дозволяє відстежувати конкретний сервіс.

Як тільки ServiceTracker створений, він починає відстежувати сервіси і дозволяє використовувати такі свої методи:

  • getService () / getServices () - повертають відслідковують трекера сервіс / и;
  • getServiceReference () / getServiceReferences () - дозволяють викликав метод знаходиться в очікуванні до тих пір, поки не з'явиться хоча б одна інстанція відслідковується сервісу, або до закінчення часу очікування.

Примітка. не рекомендується використовувати getServiceReference () / getServiceReferences () в активаторах бандла, так як передбачається, що активатори бандла повинні швидко завантажуватися, а фреймворк може призупинити виконання активатора до завантаження бандла, що завантажує потрібний сервіс, що може привести до deadlock-у, якщо сервіс не буде завантажений.

Лістинг активатора ExchangeActivator.java

Для створення трекера при старті в конструктор ServiceTracker другим параметром передається найменування сервісу. Після цього трекер відкривається, створюється екземпляр бандла і в консоль виводиться список валют. У методі stop разом з бандлом закривається і трекер.

Опис збірки pom.xml

У файлі опису збірки pom.xml визначається залежність плагіна від артіфактов service-currency на час розробки (provided), необхідна для компіляції і створення bundle. Щоб maven знайшов даний артіфактов в репозиторії необхідно виконати в попередньому проекті service-currency команду mvn install. яка завантажить артіфактов в репозиторій.

В результаті збірки в піддиректорії проекту target буде створено bundle як файл bundle-exchange-1.0.0.jar з маніфестом такого змісту:

тестування

Як було зазначено вище, в архів прикладів входить папка «osgi.container», в якій розміщується org.eclipse.osgi_ХХХ.jar для створення контейнера Equinox. Для автоматичного завантаження плагінів (бандлів) їх необхідно розмістити в піддиректорії «osgi.container / bundles», а в піддиректорії «osgi.container / configuration» налаштувати файл ініціалізації config.ini. Після цього бандли будуть автоматично завантажені в контейнер, сервіс буде зареєстрований і до нього підключиться клієнт. А в консолі ми побачимо наступну «картину»:

Можна сказати поставлена ​​задача вирішена і бандли «знайшли один одного» на просторах контейнера Equinox.

Завантажити вихідний код

Тексти програм розглянутого прикладу взаємодії bundle для платформи OSGi у вигляді проектів Eclipse можна скачати тут (1.2 Мб). До архіву проекту додатково включена директорія osgi.container для створення контейнера Equinox і тестування взаємодії бандлів.