Declarative Services i OSGi del 1

När man implementerar en tjänst i OSGi och dessutom är beroende av någon annan tjänst så tar det inte lång tid innan ens kod blir nerkletad med massa boilerplate-kod för att hantera service trackers, hämta service references, services, etc.

När man hackat ihop sin 115:e service tracker och börjar känna en viss trötthet kan det vara dags att titta på alternativa lösningar. Lyckligtvis finns en lösning redan implementerad i OSGi-ramverket: Declarative Services.

Med Declarative Services kan man glömma BundleActivator, ServiceTracker, m.m och övergå till en programmeringsmodell mer lik dependency injection-baserade ramverk som Spring.

Att använda Declarative Services medför följande fördelar:

  • Minskad komplexitet eftersom man
    • kan skriva sin tjänst som en POJO som implementerar tjänstegränssnittet utan att behöva en aktivator eller registrera tjänsten
    • slipper hantera dynamiken med att tjänster kommer och går eftersom man får alla tjänster som man beror av injicerade och bundeln kommer inte aktiverats förrän alla är uppfyllda (den är baspolicyn för 1..1-relationer, men man kan ha andra relationer: 0..*, 0..1, etc). Likaså avaktiveras den om någon obligatorisk tjänst försvinner.
  • Minskad uppstartstid eftersom den vanliga BundleActivator-baserade modellen bygger på att alla bundlar aktivt registrerar sina tjänster och binder till andra tjänster. Detta kräver att alla bundlar aktiveras även om de inte används.
  • Minskad minnesanvändning eftersom man kan styra så att tjänsten inte aktiveras och registreras förrän någon annan tjänst verkligen vill binda till den.
  • Minskat beroende mot OSGi-ramverket, vilket ger möjlighet att kunna återanvända bundlar i andra miljöer.

Omedelbar eller fördröjd aktivering

En tjänst som deklareras som immediate kommer att aktiveras så fort alla beroenden är uppfyllda (dock är ett krav att tjänsten startas).

En tjänst som deklareras  delayed kommer inte att aktiveras förrän någon annan tjänst binder till den. En form av lazy-loading alltså. Detta medför att minnesanvändning och start-tid reduceras om en tjänst inte används (åtminstone fram till att den används första gången).

Kardinalitet

Genom att ange kardinalitet på sina beroenden kan OSGi-ramverket avgöra huruvida komponenten har alla beroenden uppfyllda för att aktiveras. Kardinaliteterna är de gamla vanliga med innebörden:

  • 0..1 och 0..n:  komponenten kan aktiveras utan att beroendet är uppfyllt.
  • 1..1 och 1..n: komponenten måste ha beroendet uppfyllt (minst en referens) för att få aktiveras.

Ner på metallen

(All kod nedan finns åtkomlig under projektet my-service-provider-dsgithub.)

OK, så nu har vi lite bakgrund till syftet med Declarative Services, så då är det dax att komma till lite konkret kodning.

När man baserar sin bundle på  Declarative Services deklarerar man sin tjänst och sina beroenden i en tjänstebeskrivning i form av en XML-fil som refereras från en manifest-property, Service-Component.

Vi börjar med ett grundläggande exempel:

Enkel tjänst som implementerar MyService och beror av LogService

Vilket implementeras med innehållet i följande bundle-jar:

META-INF/MANIFEST.MF
META-INF/
META-INF/maven/
META-INF/maven/cag.labs.osgi/
META-INF/maven/cag.labs.osgi/my-service-provider-ds/
META-INF/maven/cag.labs.osgi/my-service-provider-ds/pom.properties
META-INF/maven/cag.labs.osgi/my-service-provider-ds/pom.xml
OSGI-INF/
OSGI-INF/cag.labs.osgi.myserviceimpl.MyServiceImpl.xml
cag/
cag/labs/
cag/labs/osgi/
cag/labs/osgi/myserviceimpl/
cag/labs/osgi/myserviceimpl/MyServiceImpl.class

Det som tillkommer jämfört med en vanlig bundle är OSGI-INF/cag.labs.osgi.myserviceimpl.MyServiceImpl.xml, tjänstebeskrivningen. Detta är ett XML-dokument som beskriver tjänsten. I detta exempel har följande tjänstebeskrivning:

<?xml version='1.0' encoding='utf-8'?>
 <component name='cag.labs.osgi.myserviceimpl.MyServiceImpl'
   immediate='true'>
   <implementation class='cag.labs.osgi.myserviceimpl.MyServiceImpl'/>
   <service>
     <provide interface='cag.labs.osgi.myservice.MyService'/>
   </service>
   <property name='myservice.messageprefix' value='Tjohoo'/>
   <reference name='logService' interface='org.osgi.service.log.LogService'
     bind='setLogService' unbind='unsetLogService'/>
 </component>

Betydelsen är ungefär: Denna bundle tillhandahåller en implementation, (cag.labs.osgi.myserviceimpl.MyServiceImpl) , av tjänstegränssnittet cag.labs.osgi.myservice.MyService. Den kräver att referensen logService injiceras med en org.osgi.service.log.LogService (defaultkardinalitet är 1..1) för att aktiveras. Tjänsten skall erhålla parametern myservice.messageprefix med värdet ”Tjohoo”.

För att OSGi-ramverket skall hitta den måste den pekas ut i MANIFEST.MF:

Manifest-Version: 1
 Bnd-LastModified: 1316170738504
 Build-Jdk: 1.6.0_20
 Built-By: joel
 Bundle-ManifestVersion: 2
 Bundle-Name: My service provider - 1.0
 Bundle-SymbolicName: cag.labs.osgi.my-service-provider-ds
 Bundle-Version: 1.0
 Created-By: Apache Maven Bundle Plugin
 Export-Package: cag.labs.osgi.myserviceimpl;uses:="cag.labs.osgi.myservi
 ce,org.osgi.service.log,org.osgi.service.component";version="1.0"
 Import-Package: cag.labs.osgi.myservice;version="1.0",org.osgi.service.c
 omponent;version="[1.0,2.0)",org.osgi.service.log;version="[1.0,2.0)"
 Service-Component: OSGI-INF/cag.labs.osgi.myserviceimpl.MyServiceImpl.xm
  l
 Tool: Bnd-1.43.0

Detta mappas mot följande metoder i implementationsklassen:

package cag.labs.osgi.myserviceimpl;

import cag.labs.osgi.myservice.MyService;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.log.LogService;
import org.osgi.service.upnp.UPnPDevice;

import java.io.PrintStream;
import java.util.Date;

/**
 * Implementation of MyService
 */
public class MyServiceImpl implements MyService {
    /**
     * Sets the log service when a log service becomes available.<br>
     * Handled by Declarative Services according to the specification
     * in the Service-Component tag in pom.xml
     *
     * @param logService the log service
     */
    public void setLogService(LogService logService) {...}

    /**
     * Unsets the log service when a log service is deregistered from
     * the service registry.<br>
     * Handled by Declarative Services according to the specification
     * in the Service-Component tag in pom.xml
     *
     * @param logService the log service to remove
     */
    public void unsetLogService(LogService logService) {...}

    /**
     * Activation of this service. This method will be invoked by the
     * OSGi framework when the bundle is started and all dependencies
     * (in this case only the log service) is satisfied.
     *
     * @param context The context for this Component.
     */
    protected void activate(ComponentContext context) {...}

    /**
     * Deactivation of this service. This method will be invoked by
     * the OSGi framework when any of the dependencies are no longer
     * satisfied or the bundle is stopped.
     *
     * @param context The context for this Component.
     */
    protected void deactivate(ComponentContext context) {...}
    :
    :
}

OK, som vanligt är ju inte det här något man vill sitta och handjaga, men det är ju inte heller nödvändigt då maven-bundle-plugin stödjer detta. Dock är det lite dåligt beskrivet. Man måste dels läsa maven-bundle-plugin-dokumentationen, men framförallt läsa BND-dokumentationen, i detta fall rörande Components.

För att generera tjänstebeskrivningen ovan använde jag följande konfiguration för maven-bundle-plugin:

<Service-Component>
  cag.labs.osgi.myserviceimpl.MyServiceImpl;
  immediate:=true;
  logService=org.osgi.service.log.LogService;
  provide:="cag.labs.osgi.myservice.MyService";
  properties:="myservice.messageprefix=Tjohoo"
</Service-Component>

Med följande betydelse:

cag.labs.osgi.myserviceimpl.MyServiceImpl  Anger vilken klass som implementerar tjänsten (dvs som skall registreras)
immediate:=true Anger att den skall aktiveras direkt när beroenden är uppfyllda (måste dock startas explicit)
logService=org.osgi.service.log.LogService Anger beroende till LogService, OSGi-ramverket injicerar denna och när alla sådana beroenden (i detta fall endast detta) uppfyllts aktiveras komponenten. Mappar mot metoderna setLogService() och unsetLogService() i MyServiceImpl
provide:=”cag.labs.osgi.myservice.MyService” Anger vilka interface tjänsten skall registreras under
properties:=”myservice.messageprefix=Tjohoo”  Eventuella properties som kan plockas upp i implementationen

Ta en titt på koden, bygg modulerna my-service, my-service-provider-ds och my-service-consumer, installera i OSGi-ramverket (t.ex Knopflerfish) och provkör.

Jag återkommer med lite fler exempel på tjänster som kräver multipla beroenden, fördröjd aktivering, flera tjänster i en bundle, m.m.

IT consultant and Java specialist at CAG Contactor.

Publicerad i Java, OSGi Taggar: , ,

Kategorier

WP to LinkedIn Auto Publish Powered By : XYZScripts.com