Какво е OSGi? Различен подход към модулността на Java

OSGi улеснява създаването и управлението на модулни Java компоненти (наречени пакети ), които могат да бъдат разположени в контейнер. Като разработчик използвате OSGi спецификацията и инструментите, за да създадете един или повече пакета. OSGi определя жизнения цикъл на тези пакети. Той също така ги хоства и поддържа взаимодействието им в контейнер. Можете да мислите за OSGi контейнер като приблизително аналогичен на JVM, с допълнителни правомощия. По същия начин мислете за пакетите като Java приложения с уникални способности. Пакетите се изпълняват в контейнера OSGi като клиентски и сървърни компоненти.

Съюзът OSGi

OSGi започна през 1999 г. и за разлика от много други спецификации стандартът не се управлява от Oracle, Java Community Process или Eclipse Foundation. Вместо това той се управлява от алианса OSGi.

Как OSGi е различен

Философията на OSGi се различава от тази на други базирани на Java рамки, най-вече Spring. В OSGi могат да съществуват множество приложения в един и същ контейнер: средата за изпълнение на пакета OSGi . Контейнерът гарантира, че всеки компонент е достатъчно изолиран и също така има достъп до всички зависимости, които изисква. OSGi може да поддържа инжектиране на зависимост, което е стандартизирано от проекта Aries Blueprint. В допълнение към предоставянето на OSGi за инверсия на контрол (IoC) контейнер, Aries поддържа стандартни Java рамки като Java Persistence API (JPA).

В OSGi пакетите могат да предоставят услуги, които другите пакети използват. Пакетът също може да декларира версия и може да определи от какви други пакети зависи. След това изпълнението автоматично ще зареди всичките си пакети в зависимост от зависимостта. В OSGi могат да съществуват една до друга множество версии на един и същи пакет, ако това се изисква от зависимости на пакета.

OSGi в Eclipse IDE и Equinox

OSGi съществува под някаква форма от няколко десетилетия. Използва се за много добре познати приложения, от вградени мобилни устройства до сървъри за приложения и IDE.

Популярната Eclipse IDE е изградена върху OSGi. Внедряването на контейнера OSGi от Eclipse се нарича Equinox. Това е чудесен пример за разбиране на OSGi. Базирането на OSGi означава, че Equinox е модулна платформа. Той е домакин на разнообразни услуги, които разработчиците могат да добавят по желание. Всеки от тях предлага възможност, от която разработчикът може да се нуждае в своята IDE. Можете да добавите редактори за Java и JavaScript, сървър на приложения и конектор за база данни. Всеки от тях е реализиран като пакет OSGi, който се добавя към контейнера и може да взаимодейства с други услуги в контейнера.

Напоследък има голям интерес към използването на OSGi за Интернет на нещата (IoT). OSGi е естествено подходящ за този тип разработка, която има разнообразие от софтуерни компоненти, работещи успоредно на устройства, без непременно да знаят един за друг. Контейнерът на OSGi осигурява прост и стандартизиран начин за хостване на тези динамични софтуерни компоненти.

Използване на OSGi в проект на Java: Knoplerfish OSGi

Ще работим чрез примерно приложение, което ще направи концепциите на OSGi по-конкретни. Нашият пример се основава на изпълнението на Knoplerfish OSGi, което се използва в много производствени внедрения. Knoplerfish включва GUI и интерфейс за команден ред (CLI) за управление на OSGi контейнера и неговите пакети.

Първото нещо, което ще направите, е да изтеглите Knoplerfish. Текущата версия към момента на писане е Knoplerfish OSGi 6.1.3. Можете да замените тази версия с най-актуалната, когато прочетете тази статия.

След като сте изтеглили и инсталирали Knoplerfish, използвайте CLI да пуснете в директорията, където сте изтеглили файла JAR, и въведете: java -jar framework.jar. Това ще стартира изпълнимия JAR и трябва да бъдете посрещнати с GUI прозорец.

Графичният потребителски интерфейс на Knoplerfish OSGi

GUI на Knoplerfish OSGi може да изглежда поразителен в началото, но основите са прости:

  • В горната част на екрана е менюто.
  • Вляво е наборът от пакети, които са били заредени в средата на изпълнение.
  • Вдясно е информационен прозорец.
  • В долната част има конзола за извеждане на текст.
  • В самото дъно има входна конзола.
Матю Тайсън

Въведете helpвъв входната конзола, ако искате да видите опциите за помощ.

Преди да преминем към примера, разгледайте набора от работещи пакети. Ще видите пакет, наречен HTTP Server, което означава, че пакет, работещ с HTTP сървър, е стартиран. Отидете в браузъра си и проверете // localhost: 8080. Със сигурност ще видите уеб страница на Knoplerfish.

Пакетът „Hello JavaWorld“

Нека използваме времето на изпълнение на OSGi, за да изградим прост пакет, който ще извикам Hello JavaWorld. Този пакет извежда съобщение към конзолата.

В листинг 1 използваме Maven за изграждане на пакета. Той има само една зависимост, която се осигурява от алианса OSGi.

Листинг 1. Зависимост на OSGi в Maven POM

   org.osgi org.osgi.core   

Сега ще използваме и приставка, благодарение на проекта Apache Felix. Този плъгин се грижи за опаковането на приложението като OSGi пакет за използване. Листинг 2 показва конфигурацията, която ще използваме.

Листинг 2. Приставка OSGi Felix в Maven POM

   org.apache.felix maven-bundle-plugin true   org.javaworld.osgi org.javaworld.osgi.Hello     

Сега можем да разгледаме простия клас, който ще изведе „Здравейте“.

Листинг 3. Здравейте пакет JavaWorld OSGi

 package com.javaworld.osgi; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; public class HelloJavaWorld implements BundleActivator { public void start(BundleContext ctx) { System.out.println("Hello JavaWorld."); } public void stop(BundleContext bundleContext) { } } 

Изградете пакета, като отидете в командния ред и напишете mvn clean install. Това ще изведе JAR файл, съдържащ пакета. Сега отидете в Fileменюто в Knoplerfish GUI и изберете Add Bundle. Това ще осигури файлов браузър. Намерете JAR, който току-що създадохме, и го изберете.

Управление на пакети OSGi в контейнера

In the output window of the Knoplerfish UI, you’ll see your “Hello, JavaWorld” message appear. Click on the bundle in the Knoplerfish GUI, and you can see the ID the container has assigned to it. When you are ready to stop the bundle, you could click the Stop menu item. Another way is to enter stop [bundle number] on the command line. You can manage bundles in the container using either the GUI or the command line.

Now you have a sense of how a simple bundle works in the OSGi container. Anywhere an OSGi container exists, you will find the same simplicity in starting and stopping bundles. OSGi creates an environment and lifecycle for the bundle.

Bundle Interactions: Services and clients

Next, we’ll look at how bundles communicate with each other.

The first thing we’ll do is create a service bundle. A service bundle is analogous to an EJB session bean: It provides a component that can be accessed by other bundles via a remote interface. To create a service bundle, we need to provide both an interface and an implementation class.

Listing 4. The service bundle interface

 package com.javaworld.osgi.service; public interface WhatIsOsgi { public Integer addNum(Integer x, Integer y); } 

Listing 4 is a simple interface. The only method is a addNum() method that will do what it implies: return the addition of two numbers. The implementation shown in Listing 5 is equally simple but adds a couple of OSGi-specific methods.

Listing 5. The service bundle implementation

 package com.javaworld.osgi.service; public class WhatIsOsgiImpl implements WhatIsOsgi, BundleActivator { private ServiceReference ref; private ServiceRegistration reg; @Override public Integer addNum(Integer x, Integer y){ return x + y; } @Override public void start(BundleContext context) throws Exception { reg = context.registerService( WhatIsOsgi.class, new WhatIsOsgiImpl(), new Hashtable()); ref = reg.getReference(); } @Override public void stop(BundleContext context) throws Exception { reg.unregister(); } } 

Let’s look closer at what’s happening in Listing 5:

  1. public class WhatIsOsgiImpl implements WhatIsOsgi, BundleActivator: Here we are implementing the interface we created. Note that we also implement the BundleActivator interface, as we did with the HelloJavaWorld example. The latter is because this bundle will activate itself.
  2. private ServiceReference ref; private ServiceRegistration reg;: These are variables for the OSGi registration service and the bundle reference for this service, respectively.
  3. public Integer addNum(Integer x, Integer y): This is the simple implementation of the add method.
  4. public void start(BundleContext context): This start method is part of the BundleActivator interface, and is executed by the container. In this example, we obtain a reference to the OSGi registration service and apply it to our WhatIsOsgi interface and implementation. The empty Hashtable is for config params, which we aren’t using here. We also get a reference to the service we have just created.
  5. public void stop(BundleContext context): Here, we simply unregister the service. This simple service just manages the barest elements of its lifecycle. Its main purpose is to expose the addNum method to the OSGi container.

The OSGi client

Next up, let’s write a client that can use the service. This client will again make use of the BundleActivator interface. It will also add the ServiceListener interface, as shown in Listing 6.

Listing 6. The OSGi service client bundle

 public class OsgiClient implements BundleActivator, ServiceListener { private BundleContext ctx; private ServiceReference service; public void start(BundleContext ctx) { this.ctx = ctx; try { ctx.addServiceListener(this, "(objectclass=" + WhatIsOsgi.class.getName() + ")"); } catch (InvalidSyntaxException ise) { ise.printStackTrace(); } } } 

Listing 6 has a start method that will add a service listener. This listener is filtered by the class name of the service we created in Listing 5. When the service is updated, it will call the serviceChanged() method, as shown in Listing 7.

Listing 7. serviceChanged method

 public void serviceChanged(ServiceEvent event) { int type = event.getType(); switch (type){ case(ServiceEvent.REGISTERED): serviceReference = event.getServiceReference(); Greeter service = (Greeter)(ctx.getService(service)); System.out.println("Adding 10 and 100: " + service.addNum(10, 100) ); break; case(ServiceEvent.UNREGISTERING): System.out.println("Service unregistered."); ctx.ungetService(event.getServiceReference()); // Releases reference to service so it can be GC'd break; default: break; } } 

Note that the serviceChanged method is used to determine what event has occurred for a service we are interested in. The service will then respond as specified. In this case, when the REGISTERED event appears, we make use of the addNum() method.

The OSGi alternative

This has been a quick introduction to OSGi, the Open Services Gateway Initiative. As you’ve seen through the Knoplerfish example, OSGi provides a runtime environment where you can define modular Java components (bundles). It provides a defined lifecycle for hosting bundles in the client, and it supports bundles interacting as clients and services within the container. All of these capabilities taken together provide an interesting alternative to standard Java runtimes and frameworks, especially for mobile and IoT applications.

И накрая, имайте предвид, че предишната статия от поредицата „Какво е: Java“ представи системата на Java Platform Module, която предлага различен подход към същото предизвикателство на модулността на Java.

Тази история „Какво е OSGi? Различен подход към модулността на Java“ първоначално е публикувана от JavaWorld.