Niedawno, tworząc pluginy do JIRA, natknąłem się na dość irytujący problem, który objawiał się dziwnymi komunikatami o niemożności zaimportowania niektórych pakietów. Ponieważ pluginami tymi są moduły OSGi, postanowiłem nieco zgłębić to zagadnienie.
Na początek słowo wstępu o tym, czym jest OSGi. Open Services Gateway Initiative - bo tak należy rozwinąć ten skrót – to nic innego jak system dynamicznych modułów dla Java. Definiuje architekturę modułowych aplikacji, które mogą być uruchamiane w kontenerach wspierających OSGi. Do tychże zaliczyć możemy
Po co dodatkowy kontener? Co daje modularność? Pytania są jak najbardziej słuszne. Jeżeli idzie o kontenery, to są one tworzone specjalnie po to, by umożliwić zarządzanie aplikacjami modularnymi w sposób najwygodniejszy z możliwych – podmienianie modułów, ich dołączanie i odłączanie bez konieczności restartu całego serwera. Umożliwiają zarządzanie zależnościami między modułami i pozwalają budować aplikacje najróżniejszych typów – od mobilnych do RIA.
Coraz większa popularność OSGi z pewnością ma też coś wspólnego ze wsparciem tego modelu przez Eclipse IDE. Środowisko to posiada wbudowane wizardy do tworzenia modułów OSGi, jak i własny kontener do ich testowania i debugowania.
Utwórzmy prosty moduł OSGi (zwany Bundle) właśnie w środowisku Eclipse (Europa). Aby to zrobić, wybieramy New->Project->Plug-in Development -> Plug-in Project. Dostaniemy do wypełnienia wizard, który utworzy dla nas przykładowy projekt.
Nazwa: TestBundle
Target Platform: OSGi framework -> standard
Klikamy Next, Next. Jako szablon wybieramy Hello OSGi Bundle i klikamy next do końca kreatora.
Po zakończeniu powinniśmy dostać projekt z klasą Activator, plikiem MANIFEST.FM oraz build.properties.
Przyjrzyjmy się wszystkim tym plikom.
Activator
public class Activator implements BundleActivator {
/*
* (non-Javadoc)
* @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
*/
public void start(BundleContext context) throws Exception {
System.out.println("Hello World!!");
}
/*
* (non-Javadoc)
* @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
*/
public void stop(BundleContext context) throws Exception {
System.out.println("Goodbye World!!");
}
}
Activator to klasa implementująca BundleActivator – interfejs, który wskazuje, jak nasz moduł ma być startowany i zatrzymywany. W metodach start/stop wykonywane są operacje dostarczające danych do dalszej pracy i sprzątające przy zamknięciu (nawiązanie połączenia z bazą, zakończenie połączenia)
MANIFEST.FM
Na pierwszy rzut oka, plik MANIFEST to nic nadzwyczajnego, jest on zaszyty w niemal każdej bibliotece JAR. Dla mnie miał on jednak szczególne znaczenie, gdyż był przyczyną owych nieszczęsnych komunikatów o nieprawidłowych pakietach. Wygenerowany plik ma następującą treść
Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: TestBundle Plug-in Bundle-SymbolicName: TestBundle Bundle-Version: 1.0.0 Bundle-Activator: testbundle.Activator Import-Package: org.osgi.framework;version="1.3.0"
Pierwsze, co powinno rzucić się nam w oczy, to dyrektywa Bundle-Activator. Wymieniona jest tam nasza klasa Activator. Sugeruje to, że plik MANIFEST, może odgrywać dużo większą rolę niż się wydaje. Prześledźmy więc kolejno wszystkie dyrektywy
- Bundle-ManifestVersion: 2 – informuje o tym, z jakiej wersji specyfikacji OSGi korzysta ten bundle. 2 oznacza kompatybilność z release 4 specyfikacji, 1 z wcześniejszymi wersjami
- Bundle-Name: – nazwa bundle w formie czytelnej dla człowieka
- Bundle-SymbolicName – nazwa bundle, pod którą będzie widziany z innych modułów
- Bundle-Version: wersja tego bundle (w kontenerach może jednocześnie działać kilka wersji tego samego bundle’a).
- Bundle-Activator: klasa aktywatora bundle’a, czyli z jakiej klasy należy skorzystać przy uruchamianiu i zatrzymywaniu modułu
- Import-Package: tu definiujemy jakie pakiety chcemy zaimportować do naszego bundle’a. Możliwe jest importowanie pakietów z innych modułów, a więc ich wzajemne wiązanie i wykorzystywanie funkcjonalności
W powyższym zestawieniu brak jednak dyrektywy najbardziej istotnej z punktu widzenia mojego problemu. A mianowicie:
- Export-Package: tu określamy jakie pakiety z naszego bundle’a chcemy wyeksportować. Tylko wyeksportowane pakiety mogą być importowane przez inne moduły.
Plik manifestu może posiadać jeszcze parę innych ważnych dyrektyw. Są to
- Private-Package: tu wymienione są pakiety, których nie chcemy udostępnić. Początkowo wydaje się to nie potrzebne, ale przecież może zaistnieć sytuacja, w której będziemy chcieli wyeksportować klasy pakietu pl.atena.* ale nie pakietu pl.atena.secret.*. Header ten nie mieści się jednak w oficjalnej specyfikacji, wykorzystywany jest przez narzędzie BND do budowania bundle’a. Może to być różnie interpretowane w różnych kontenerach
- Bundle-Classpath: tu możemy określić, gdzie w naszej bibliotece znajdują się inne wykorzystywane biblioteki. Domyślnie specyfikacja zakłada, że wszelkie dodatkowe pliki jar są w korzeniu bundle’a.
- Ignore-Package: tu wymienione są pakiety ignorowane w trakcie budowania za pomocą narzędzia BND. Podobnie jak Private-Package nie jest to oficjalny nagłówek.
build.properties
Plik ten określa tylko ścieżki do wykorzystania w trakcie budowania modułu.
Aplikacje modularne robią coraz większą furorę, więc przyszłość OSGi wydaje się jasna i pewna. Niewykluczone, że jeszcze nie raz przyjdzie mi spotkać się z nimi. Z pewnością podzielę się wtedy swoimi doświadczeniami w ich tworzeniu.
Źródła:
Autor: Adam Andrzejewski


