В последнее время я больше экспериментировал с OSGi и хочу поделиться некоторыми примерами, которые я собрал. Примеры включают Felix , Spring Dynamic Modules и Jetty , хотя их можно легко использовать с Equinox . После того, как я закончу с этими упражнениями, я надеюсь сравнить и сопоставить различные подходы, которые я использовал, а также сравнить встроенную Jetty с сервлетным мостом Equinox . Я верю, что OSGi — это прорывная технология, которая должна трансформировать разработку Java в том виде, в каком мы ее знаем сегодня.
В Интернете опубликовано множество учебных пособий по OSGi, блогов и примеров. На моей странице ресурсов OSGi перечислены некоторые из них, и если вы знаете о других хороших ресурсах, которых нет в списке, пожалуйста, дайте мне знать. Когда я экспериментирую с новой технологией, мне нравится концентрироваться исключительно на самой технологии, без генераторов кода, мастеров или других утилит, которые выполняют за меня половину работы. Я чувствую, что это максимизирует опыт. Для этого все мои упражнения избегают использования таких инструментов, как Maven или Eclipse. Я нахожу Maven запутанным, и использование Eclipse для некоторых из этих небольших упражнений является излишним. За исключением нескольких необходимых инструментов, таких как Ant и сам фреймворк Felix, я все сверну вручную. Надеюсь, это поможет прояснить суть OSGi.
Первый пример — канонический пример HelloWorld, где я создаю два простых пакета — client.jar и service.jar. Опять же, на языке OSGi, пакет представляет собой файл .jar. Вы можете найти рабочий код в репозитории My Google Code (проект HelloWorld), в комплекте со скриптом сборки Ant, кешем комплекта и скриптом .bat или .sh для запуска Felix в зависимости от вашей ОС. Единственными двумя предварительными условиями для этих примеров являются Felix (я использовал версию 1.0.0) и Ant, которые вам придется загружать отдельно. Давайте пройдемся по созданию этих двух простых связок.
Сначала давайте посмотрим на HelloConsumer в client.jar. HelloConsumer использует HelloService для получения приветственных и прощальных сообщений при запуске и остановке пакета client.jar соответственно. HelloConsumer реализует BundleActivator OSGi, что означает, что Феликс будет вызывать метод start при запуске пакета и метод stop при остановке пакета. Как видно из строки 26, HelloConsumer получает ссылку на HelloService с помощью класса OSGi ServiceTracker.
package com.extensiblejava.hello.client; import com.extensiblejava.hello.service.HelloService; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; import org.osgi.util.tracker.ServiceTracker; public class HelloConsumer implements BundleActivator { private ServiceTracker helloWorldTracker; private HelloService helloService; public void setService(HelloService helloService) { this.helloService = helloService; } public void removeService() { this.helloService = null; } public void start(BundleContext context) throws Exception { helloWorldTracker = new ServiceTracker(context, HelloService.class.getName(), null); helloWorldTracker.open(); HelloService hello = (HelloService) helloWorldTracker.getService(); if (hello == null) { System.out.println("Hello service unavailable on HelloConsumer start"); } else { System.out.println(hello.sayHello()); } } public void stop(BundleContext context) { HelloService hello = (HelloService) helloWorldTracker.getService(); if (hello == null) { System.out.println("Hello service unavailable on HelloConsumer stop"); } else { System.out.println(hello.sayGoodbye()); } helloWorldTracker.close(); } }
The HelloService and HelloServiceImpl form the OSGi service. HelloService is a simple Java interface that defines the API, while HelloServiceImpl implements the BundleActivator and is the class invoked when the server bundle is started and stopped. As seen on line 18, HelloServiceImpl registers itself as an OSGi service. Without registration, HelloConsumer would not be able to use HelloService as an OSGi service.
package com.extensiblejava.hello.service.impl; import java.util.Properties; import com.extensiblejava.hello.service.HelloService; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceListener; import org.osgi.framework.ServiceEvent; import org.osgi.framework.ServiceRegistration; public class HelloServiceImpl implements HelloService, BundleActivator { private ServiceRegistration registration; public void start(BundleContext context) { Properties props = new Properties(); props.put("Language", "English"); registration = context.registerService(HelloService.class.getName(), this, props); } public void stop(BundleContext context) { } public String sayHello() { return "Hello World!! "; } public String sayGoodbye() { return "Goodbye World!!"; } }
There’s a bit more magic here that makes this all work. A wonderful aspect of OSGi is that dependencies must be managed explicitly, with each bundle’s manifest declaring the packages the bundle imports and exports. Bundle manifests specify other key pieces of information, too. Notably, it tells the OSGi environment of the BundleActivator to invoke on start and stop. If a bundle imports a package, then another bundle within that OSGi run-time must export that same package. The client bundle manifest imports the package containing HelloService while the service bundle manifest exports that same package. Were these imports and exports not explicitly declared, we’d receive OSGi run-time errors. In our example above, HelloServiceImpl registers itself as an OSGi service allowing clients to obtain a reference to HelloService using the OSGi ServiceTracker.
The client and service OSGi bundles can be deployed to any OSGi run-time. I’ve used Felix for this example, but could have used Equinox just as easily. To run these examples, you can checkout the code from the Google Code repository. Since I include the bundle cache in the repository, you should be able to start felix and experiment with OSGi. You’ll likely have to modify the start script (OS-based) to ensure Felix is in your classpath. Upon starting Felix, you’ll be asked to enter a profile name. To use the bundle cache included with the example, simply use HelloWorld as your profile.
If you’re feeling a bit more adventurous, you can make some changes to the HelloServiceImpl message and rebuild the project by invoking the Ant build script. Once compiled, move back into the Felix shell and update the service bundle using the Felix update command. For a listing of all Felix commands, simply type ‘help’.
If you want to deploy the bundles to your own Felix cache, restart Felix and enter a different profile name. Any name will do, and Felix will create a new bundle cache for you. Here are some more suggested steps to build, install, and continue experimenting with the bundles and OSGi:
- Build service by executing ant within service directory
- Build client by executing ant within client directory
- Run startfelix.bat or startfelix.sh depending on your OS
- install the bundles from the felix shell
- install file:service/bin/service.jar
- install file:client/bin/client.jar
- ps
- start {service-bundle-id}
- start {client-bundle-id}
- Experiment by starting and stopping client and service to get a feel for OSGi.
- Now change the service message printed in HelloServiceImpl.java and compile. Then do the following while client is running.
- stop {service-bundle-id}
- update {service-bundle-id}
- start {service-bundle-id}
- stop {client-bundle-id}
When stopping the client bundle, you should see a different goodbye message than what you saw in step 4.
OSGi offers a very dynamic and adaptable run-time environment. Even when not using OSGi services, reuse across bundles can occur so long as the package dependencies are explicitly managed in the bundle manifests. The benefit with OSGi services is that the OSGi run-time manages the service lifecycle, however. Many of the presentations I give on architecture & design talk about the importance of managing .jar relationships, and I’ve long felt that the .jar file is a great candidate as a first class component on the Java platform. In the next post, I’ll explore the simple benefits of an incredibly important heuristic that states we should “separate interface from implementation.” You can get a head start by taking a look at the HelloWorldSpec project on My Google Code repository.