Dependency injection in een OSGI container
4 June 2009 13:26 · Rob Schellhorn · Java
Bepaalde vraagstukken houden software engineers al decennia lang bezig. Een klassieker is hoe we componenten moeten maken met een kleine koppeling, zodat het product makkelijk uit te breiden blijft en goed te testen is. Deze vraag is van toepassing op verschillende lagen in de software. Dependency injection (DI) kijkt vanuit de kleinste componenten: objecten. OSGI biedt de handvaten om modules makkelijk samen te laten werken. Door de twee te combineren ontstaat een ijzersterk platform om makkelijk te onderhouden software te schrijven. In deze blog post laat ik zien hoe je het DI framework Guice kan inzetten binnen een OSGI container.
Dependency Injection
Er is sprake van koppeling tussen objecten wanneer het type van het ene object af hangt van een specifieke implementatie van een ander type. Zo is in onderstaand voorbeeld het type PaymentServiceImpl direct afhankelijk van LogServiceImpl.
class PaymentServiceImpl implements PaymentService { private LogService log = new LogServiceImpl(); }
DI verplaatst deze afhankelijkheid naar de configuratie. De makers van Guice hebben op hun wiki perfect geformuleerd waarom dat een goed idee is. In Guice leg je de configuratie vast in een of meerdere Modules.
class LogModule extends AbstractModule { public void configure() { bind(LogService.class).to(LogServiceImpl.class); } } class PaymentModule extends AbstractModule { public void configure() { bind(PaymentService.class).to(PaymentServiceImpl.class); } } class PaymentServiceImpl implements PaymentService { @Inject private LogService log; }
Ook al is de implementatie van de PaymentService niet meer afhankelijk van een specifieke LoggerService, toch rammelt er iets. De PaymentModule heeft een LogService nodig via de referentie naar PaymentServiceImpl, maar specificeert dat niet. Er wordt van uitgegaan dat die module ook geconfigureerd wordt.
Daarnaast verhindert niets mij om toch direct de LogServiceImpl aan te spreken. Deze klasse is immers toegankelijk voor de PaymentModule via het classpath.
OSGI: Strikte scheiding tussen modules
Deze gebreken heb je niet wanneer je elke module in een OSGI bundel verpakt. OSGI bundels moeten namelijk hun afhankelijkheid van andere bundels opgeven in hun manifest. Dit wordt tijdens runtime ook afgedwongen en gaat dus een stapje verder dan dependency management in build tools als Maven.
Ook kan je goed voorkomen dat een module toch stiekem direct een service implementatie aanspreekt. Je moet namelijk expliciet aangeven welke packages je beschikbaar wilt stellen voor andere bundels. Standaard is alle code alleen binnen de bundel te gebruiken.
De DI configuratie van de Log bundel ziet er nu als volgt uit:
class LogModule extends AbstractModule { public void configure() { bind(export(LogService.class)).toProvider( service(LogServiceImpl.class).export()); } }
Het export keyword komt van de Guice uitbreiding Peaberry en geeft aan dat de service ook beschikbaar moet worden gemaakt als OSGI service.
Ook de payment bundel krijgt uiteraard een Guice configuratie. In deze module wordt nu ook expliciet aangegeven dat een LogService nodig is.
class PaymentModule extends AbstractModule { bind(LogService.class).toProvider(service(LogService.class).direct()); bind(export(PaymentService.class)).toProvider( service(PaymentServiceImpl.class).export()); } }
Wanneer we bovenstaand voorbeeld starten in Equinox en de status opvragen, zien we dat de binding gemaakt is:
{com.finalist.logging.LogService}={bundle.id=15, service.id=50}
Registered by bundle: com.finalist.logging_1.0.0 [15]
Bundles using service:
com.finalist.payment_1.0.0 [22]
Tot slot
Guice en OSGI vullen elkaar goed aan. De benodigde boiler plate code om afhankelijkheden binnen en tussen bundels vast te leggen is beperkt tot een minimum en erg elegant. Alleen in de module, dus de configuratie, blijkt dat een bepaalde service vanuit een andere bundel geïmporteerd wordt.
Daarnaast is het goed om te weten dat services volgens de OSGI conventie geëxporteerd worden. Dat wil zeggen dat de service te identificeren is met zijn volledige gekwalificeerde naam (com.finalist.logging.LogService). De techniek werkt daardoor perfect samen met andere OSGI service frameworks, zoals OSGI Declarative Services of Spring Dynamic Modules.













Na het lezen van de eerste vier alinea’s was ik je al dankbaar voor dit artikel. Eindelijk snap ik het geheimzinnige probleem waarvoor al die “dependency injection” solutions geïntroduceerd worden – die de code meestal niet leesbaarder maken. Thanks! verder puik artikel trouwens.
Felix Ogg (Finalist) - June 8, 2009 18:29