Wprowadzenie do OSGi na przykładzie wielomodułowej aplikacji z użyciem Apache Maven

Wprowadzenie do OSGi na przykładzie wielomodułowej aplikacji z użyciem Apache Maven

Ten artykuł ma na celu przedstawić możliwości technologii OSGi. W tym celu zaprojektujemy wielomodułową aplikację z użyciem Mavena a poszczególne moduły wdrożymy na platformie Apache ServiceMix. Do uruchomienia szyny Apache ServiceMix użyjemy Dockera. Do dzieła! Tworzymy bez wyspecyfikowanego archetypu nowy projekt Apache Maven – plik pom.xml – niezbędne zależności:

<properties>
    <osgi.version>6.0.0</osgi.version>
    <maven-bundle-plugin.version>3.3.0</maven-bundle-plugin.version>
</properties>
 
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.osgi</groupId>
            <artifactId>org.osgi.core</artifactId>
            <version>${osgi.version}</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
 
<build>
    <pluginManagement>
        <plugins>
            <plugin>
                <groupId>org.apache.felix</groupId>
                <artifactId>maven-bundle-plugin</artifactId>
                <version>${maven-bundle-plugin.version}</version>
                <extensions>true</extensions>
            </plugin>
        </plugins>
    </pluginManagement>
</build>

Jest to projekt wielomodułowy zatem tworzymy nowy moduł (w IDE Intellij prawy klawisz myszy na nazwie projektu i wybieramy new -> module) nadając mu nazwę activator.

Domyślnym sposobem pakowania projektu z użyciem Mavena jest jar. Projekt który zawiera inne moduły to projekt gdzie typ pakowania to pom:

<groupId>pl.javaleader</groupId>
<artifactId>osgi</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
 
<modules>
    <module>activator</module>
</modules>

W tym momencie plik pom.xml głównego projektu przedstawia się następująco:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
 
<groupId>pl.javaleader</groupId>
<artifactId>osgi</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
 
<modules>
    <module>activator</module>
</modules>
 
<properties>
    <osgi.version>6.0.0</osgi.version>
    <maven-bundle-plugin.version>3.3.0</maven-bundle-plugin.version>
</properties>
 
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.osgi</groupId>
            <artifactId>org.osgi.core</artifactId>
            <version>${osgi.version}</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
 
<build>
    <pluginManagement>
        <plugins>
            <plugin>
                <groupId>org.apache.felix</groupId>
                <artifactId>maven-bundle-plugin</artifactId>
                <version>${maven-bundle-plugin.version}</version>
                <extensions>true</extensions>
            </plugin>
        </plugins>
    </pluginManagement>
</build>
 
</project>

widać, że jest to główny projekt który zawiera jak dotąd jedynie moduł activator.

Plik pom.xml modułu Activator (jest to moduł OSGi zatem typ pakowania zmieniamy na bundle – pakowanie bundle możliwe jest bo posiadamy zależność do pluginu – maven-bundle-plugin):

<parent>
    <artifactId>osgi</artifactId>
    <groupId>pl.javaleader</groupId>
    <version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
 
<artifactId>activator</artifactId>
<packaging>bundle</packaging>
 
<dependencies>
    <dependency>
        <groupId>org.osgi</groupId>
        <artifactId>org.osgi.core</artifactId>
    </dependency>
</dependencies>
 
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.felix</groupId>
            <artifactId>maven-bundle-plugin</artifactId>
            <extensions>true</extensions>
            <configuration>
                <instructions>
                    <Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
                    <Bundle-Name>${project.artifactId}</Bundle-Name>
                    <Bundle-Version>${project.version}</Bundle-Version>
                    <Bundle-Activator>pl.javaleder.activator.JavaLeaderMessage</Bundle-Activator>
                    <Private-Package>pl.javaleader.activator</Private-Package>
                </instructions>
            </configuration>
        </plugin>
    </plugins>
</build>

Rodzicem modułu activator jest moduł osgi. W głównym module dodano zarządzanie zależnościami (z ang. dependency management) dzięki czemu nie trzeba definiować wersji frameworka OSGi za każdym to razem w nowych modułach:

<dependencies>
    <dependency>
        <groupId>org.osgi</groupId>
        <artifactId>org.osgi.core</artifactId>
    </dependency>
</dependencies>

W tagach <Bundle-Activator> definiujemy lokalizację klasy aktywatora w której zdefiniowane są metody które wykonywane są przy starcie i zatrzymaniu modułu:

Klasa aktywatora przedstawia się następująco:

public class JavaLeaderMessage implements BundleActivator {
    public void start(BundleContext ctx) {
        System.out.println("JavaLeader.pl activator start");
    }
    public void stop(BundleContext bundleContext) {
        System.out.println("JavaLeader.pl activator stop");
    }
}

Podczas uruchomienia modułu wywoływana jest metoda start(BundleContext context) aktywatora, podczas gdy przy jego zatrzymaniu wywołana zostanie metoda stop(BundleContext context). Każdy moduł ma swój prywatny egzemplarz BundleContext który służy do instalowania innych modułów OSGi, pozyskiwania informacji o środowisku i zainstalowanych modułach, rejestracji usług dostarczanych przez moduł oraz korzystania z usług innych oraz nasłuchiwania zdarzeń emitowanych przez Platformę OSGi (np. Apache ServiceMix). Przykładowo:

getBundles() - zwraca tablicę zainstalowanych modułów

Przykładowy wynik:

jenkins@root>start 304
[Lorg.osgi.framework.Bundle;@54c5f983
jenkins@root>

UWAGA:

Każdy moduł ma swój prywatny egzemplarz BundleContext, który nie powinien być przekazywany dla innych modułów!

polecam w tym miejscu materiał Jacka Laskowskigo – http://jlaskowski.blogspot.com/2008/05/egzaminy-osgi-i-ejb3-na-javablackbelt.html

<Private-Package> pakiety, których nie chcemy udostępniać na zewnątrz – innymi słowy nie będą one widoczne dla innych modułów. Tworzymy nowy moduł OSGiservice – plik pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>osgi</artifactId>
        <groupId>pl.javaleader</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
 
    <artifactId>service</artifactId>
    <packaging>bundle</packaging>
 
    <dependencies>
        <dependency>
            <groupId>org.osgi</groupId>
            <artifactId>org.osgi.core</artifactId>
        </dependency>
    </dependencies>
 
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.felix</groupId>
                <artifactId>maven-bundle-plugin</artifactId>
                <extensions>true</extensions>
                <configuration>
                    <instructions>
                        <Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
                        <Bundle-Name>${project.artifactId}</Bundle-Name>
                        <Bundle-Version>${project.version}</Bundle-Version>
                        <Bundle-Activator>pl.javaleader.service.impl.PrinterImpl</Bundle-Activator>
                        <Private-Package>pl.javaleader.service.impl</Private-Package>
                        <Export-Package>pl.javaleader.service</Export-Package>
                    </instructions>
                </configuration>
            </plugin>
        </plugins>
    </build>
 
</project>

<Export-Package> – pakiety które są widoczne dla innych modułów. Tworzymy teraz aktywator:

Serwis:

public interface Printer {
     String print(Object name);
}

Implementacja:

public class PrinterImpl implements Printer, BundleActivator {
 
    private ServiceReference<Printer> reference;
    private ServiceRegistration<Printer> registration;
 
    public void start(BundleContext context) throws Exception {
        System.out.println("Registering service.");
        registration = context.registerService(Printer.class, new PrinterImpl(), new Hashtable<String, String>());
        reference = registration.getReference();
    }
 
    public void stop(BundleContext context) throws Exception {
        System.out.println("Unregistering service.");
        registration.unregister();
    }
 
    public String print(Object name) {
        return "[LOG] " + name;
    }
}

Rejestrujemy serwis – Printer z użyciem kontekstu – BundleContext. Tworzymy teraz nowy moduł – client – plik pom.xml jest analogiczny:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>osgi</artifactId>
        <groupId>pl.javaleader</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <packaging>bundle</packaging>
 
    <artifactId>client</artifactId>
 
    <dependencies>
 
        <dependency>
            <groupId>org.osgi</groupId>
            <artifactId>org.osgi.core</artifactId>
            <version>6.0.0</version>
        </dependency>
 
        <dependency>
            <groupId>pl.javaleader</groupId>
            <artifactId>service</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>provided</scope>
        </dependency>
 
    </dependencies>
 
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.felix</groupId>
                <artifactId>maven-bundle-plugin</artifactId>
                <configuration>
                    <instructions>
                        <Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
                        <Bundle-Name>${project.artifactId}</Bundle-Name>
                        <Bundle-Version>${project.version}</Bundle-Version>
                        <Bundle-Activator>pl.javaleader.client.JavaLeaderClient</Bundle-Activator>
                        <Private-Package>pl.javaleader.client</Private-Package>
                    </instructions>
                </configuration>
            </plugin>
        </plugins>
    </build>
 
</project>

Po włączeniu bądź wyłączeniu modułu service chcemy mieć stosowną notyfikację. W tym celu należy zaimplementować interfejs ServiceListener który z użyciem metody serviceChanged przechwyci “zdarzenie startu” bądź “zatrzymania modułu” (w momencie startu modułu service rejestrujemy usługę Printer, interfejs przechwyci zarejestrowanie danej usługi co pozwoli zlokalizować moment kiedy moduł jest rejestrowany na platformie OSGi).

public class JavaLeaderClient implements BundleActivator, ServiceListener {
 
    private BundleContext ctx;
    private ServiceReference serviceReference;
 
    public void start(BundleContext ctx) {
        System.out.println(ctx);
        this.ctx = ctx;
        try {
            ctx.addServiceListener(this, "(objectclass=" + Printer.class.getName() + ")");
        } catch (InvalidSyntaxException ise) {
            ise.printStackTrace();
        }
    }
 
    public void stop(BundleContext bundleContext) {
        if (serviceReference != null) {
            ctx.ungetService(serviceReference);
        }
        this.ctx = null;
    }
 
    public void serviceChanged(ServiceEvent serviceEvent) {
        int type = serviceEvent.getType();
        switch (type) {
            case (ServiceEvent.REGISTERED):
                System.out.println("Notification of service registered.");
                serviceReference = serviceEvent.getServiceReference();
                Printer service = (Printer) (ctx.getService(serviceReference));
                System.out.println(service.print("JavaLeader.pl"));
                break;
            case (ServiceEvent.UNREGISTERING):
                System.out.println("Notification of service unregistered.");
                ctx.ungetService(serviceEvent.getServiceReference());
                break;
            default:
                break;
        }
    }
}

Należy teraz zainstalować na platformie OSGi – Apache ServiceMix po kolei wszystkie moduły, zanim to jednak zrobimy należy je zainstalować w lokalnym repozytorium Apache Maven czyli domyślnie w katalogu .m2.

install mvn:pl.javaleader/client/1.0-SNAPSHOT
install mvn:pl.javaleader/service/1.0-SNAPSHOT
install mvn:pl.javaleader/activator/1.0-SNAPSHOT

startujemy moduły OSGi:

start 317
start 318
start 319

zatrzyujemy moduł – service:

jenkins@root>stop 318
Unregistering service.
Notification of service unregistered.
jenkins@root>

rejestrujemy ponownie moduł – service:

jenkins@root>start 318
Registering service. org.apache.felix.framework.BundleContextImpl@32ebfffd
Notification of service registered.
[LOG] JavaLeader.pl
jenkins@root>

za każdym razem otrzymaliśmy notyfikację odpowiednio kiedy moduł wystartował bądź kiedy został zatrzymany. Ponadto skorzystaliśmy z implementacji usługi Printer w module – client – która została zarejestrowana przez moduł – service.

Zobacz kod na GitHubie i zapisz się na bezpłatny newsletter!

.

Leave a comment

Your email address will not be published.


*