ServiceLoader was introduced in Java 6 to generalize the various forms of 'FactoryFinder' patterns that were in existence throughout the JRE. It provides a basic plug-in model where implementations of a class (subclasses) or an interface can advertise themselves via a file that has the name of that interface (or class) in the META-INF/services directory of a Jar file. When looking up implementations via ServiceLoader.load(someClass.class), ServiceLoader scans all the Jars visible to the Thread Context Classloader for the associated file in META-INF/services and then uses a zero-arg constructor to instantiate the classes found.
There are a number of issues with this for a modular environment. First of all, ServiceLoader is non-modular by design in that it expects visibility of all Jars in the system, as they can all potentially contain an implementation. Additionally, depending on the Thread Context Classloader is also a questionable design decision as that can only really work well in situations where there is only 1 classloader (e.g. the system classpath) or in situations where all threads are managed by a container. In OSGi this is clearly not the case, as OSGi bundles are free to create threads and an OSGi system typically contains many classloaders.
Note that ServiceLoader also has a load(Class, ClassLoader) API which does not suffer from the issues described above.Still we want to be able to make existing libraries that rely on ServiceLoader work in OSGi. The OSGi Enterprise R5 specs contain the ServiceLoader Mediator specification which addresses this issue.
ServiceLoader provider
Let's first look at the provider side. An obvious thing to do with bundles that contain META-INF/services resources is to register them as OSGi Services so that OSGi-aware consumers can simply use them from the OSGi Service Registry. So you'll get that with an implementation of this spec. For example take the following provider bundle where the org.acme.MySpiImpl class implements the foo.bar.MySPI interface. In this bundle,
the META-INF/services/foo.bar.MySPI file contains:
org.acme.MySpiImpl
META-INF/MANIFEST.MF contains:
Require-Capability: osgi.extender;
filter:="(osgi.extender=osgi.serviceloader.registrar)"
Provide-Capability: osgi.serviceloader;
osgi.serviceloader=foo.bar.MySPI
Once the provider bundle has been extended by the ServiceLoader mediator, you can find its corresponding service present in the OSGi Service Registry:
g! inspect capability service 8
org.acme.provider.bundle [8] provides:
-------------------------------------------
service; foo.bar.MySPI with properties:
service.id = 17
serviceloader.mediator = 6
you can identify services that are registered this way by the fact that the serviceloader.mediator property is present.
The OSGi Service Registry provides a much richer service environment than the functionality provided by java.util.ServiceLoader as besides the ability to plug-in service implementations it supports a dynamic lifecycle (services can come and go) and it has its rich service lookup query mechanism - plus it is designed to work in a modular environment.
If you can change the client-side code that uses the provider to consume it from the OSGi Service Registry, you have all you need at this point.
ServiceLoader consumer
However, in some cases you may not have access to the client-side code that is using ServiceLoader.load(). Maybe you're using an existing library that was written in another project. Maybe you're using a commercial library that you can't modify. For these cases the Service Loader Mediator specification also describes a way to get existing ServiceLoader.load() consumer code to work. While implementations can implement this in any way they like, the Reference Implementation in Apache Aries (SPI Fly) makes this work by keeping track of the providers and then, using byte code instrumentation, set the Thread Context ClassLoader to provide visibility of the appropriate provider jars for the duration of the ServiceLoader.load() call in the consumer bundle. Here's an example:
The consumer may contain code similar to:
ServiceLoader<MySPI> ldr = ServiceLoader.load(MySPI.class);
for (MySPI spiObject : ldr) {
spiObject.doit(); // invoke the SPI object
}
In the META-INF/MANIFEST.MF, the consumer bundle has:
Require-Capability: osgi.extender;
filter:="(osgi.extender=osgi.serviceloader.processor)",
osgi.serviceloader;
filter:="(osgi.serviceloader=foo.bar.MySPI)";
cardinality:=multiple
As with the provider, the consumer needs to opt in to the process by specifying the requirement headers in the OSGi manifest, however you don't need to change the actual client side code. If you don't want to modify the original Jar file at all you can wrap it as-is in an OSGi bundle, and provide the additional Manifest header in the wrapped bundle. Or you can simply add the additional manifest header to the existing Jar, whichever you prefer.
The Service Loader Mediator specification helps with migrating existing libraries that use java.util.ServiceLoader to OSGi and is available since June 2012 as part of the Enterprise R5 release. The Reference Implementation at Apache Aries is now released at version 1.0. For the consumer side, Aries comes in 2 variants: one that uses static weaving: you run a command-line tool on the consumer jar which then produces a new jar that works in OSGi. The other variant uses dynamic weaving, which means that you can install your consumer bundle as-is and have it processed at run-time in the OSGi Framework. More details on the RI can be found here: http://aries.apache.org/modules/spi-fly.html
As with any OSGi Specification, you can always check this Wikipedia page for additional implementations: http://en.wikipedia.org/wiki/OSGi_Specification_Implementations
Hi,
ReplyDeleteCan we use fragment bundle if the service is known to be consumed only by one bundle ?
Thanks
It might work, but I would not recommend it. In general it's better to avoid fragments if there are solutions that use ordinary bundles.
DeleteIs is it possible that the service type definition and the consumer reside in same bundle, OSGi spec (OSGi Enterprise 5.0.0, 133.5.3) discourages this but ... my legacy code is legacy code.
ReplyDeleteWhen they are in different bundle it works perfectly !
I think that having them in the same bundle should work. I guess you're using the Aries implementation? Maybe you can file a bug for this in the 'SPI Fly' component at https://issues.apache.org/jira/browse/ARIES
ReplyDeleteIt works ... I had an issue with maven ... sorry for the noise.
ReplyDeleteBTW I think I have found an (real) issue in weaving https://issues.apache.org/jira/browse/ARIES-1321 :)
AFAIU the spi-fly weaver expect only to find LDCInstruction.
Thanks a lot.