Hi David, Your approach is much cleaner than my headache-causing fragment-based one. It doesn't leave any resources that could be messed up by the user, while in the approach that I described anybody could uninstall those fragments. Moreover, the AOP-based solution enables one to use different implementations simultaneously. This would not be possible with the fragment based approach which would cause a single provider to be attached to the API bundle.
A few thoughts off the top of my head (these might potentially be drawbacks): 1. There are many different mechanisms that would need to be wrapped with aspects: ServiceLoader, FactoryFinders in several packages, ContextFinder (JAXB), javax.mail.Session which (as far as I know) loads resources directly from a classloader - without any finder class. The ugly fragment-based approach would probably handle all these cases in the same (uniform) way. 2. A general comment to libraries using SPI: Some of them might not be prepared to cleanly handle a dynamic environment where classes (bundles, providers) come and go. I think that the javamail library uses some kind of a caching mechanism to optimize loading configuration the second time it is called. As far as I remember it keeps a mapping from context classloader <-> some class. Best regards, Bartek 2010/6/14 David Bosschaert <[email protected]>: > Hi all, > > On 11 June 2010 16:25, David Bosschaert <[email protected]> wrote: >> The main trick around the FactoryFinder/ServiceLoader is to set the >> Thread Context classloader to the right classloader when >> FactoryFinder.find()/ServiceLoader.load() is invoked. Although >> ServiceLoader.load() has an overload that accepts a classloader we >> can't assume that all clients use it. >> You have already provided a fairly painful idea to how an >> implementation could work (or not as the case might be;). Another idea >> would be to declare in the bundle manifest what SPIs are being used >> (remember we *can* modify the bundle manifest, we might be writing an >> OSGi bundle or bundelizing a non-OSGi jar). Maybe some entity could >> intercept the ServiceLoader.load() calls using some AOP tricks and set >> the Thread context classloader to the one specified in the bundle >> manifest for the duration of that call. The bundle would have to have >> the right set of imports and this would possibly bind the bundle to >> one particular SPI impl but then again that would highlight the >> benefit of using the OSGi Service Registry instead... >> Again the above is just another wild idea to add to the mix... I might >> do some experimentation to see how far I can get this... > > I did some experiments with AOP and ended up using AspectJ for my > prototype because it works with Equinox. Weaving is not supported in a > standard way yet across all the OSGi frameworks but that's something > that is being worked on in the OSGi alliance through RFP 139. > > Here's what I was able to do: > I wrote a simple SPI for use with the JRE java.util.ServiceLoader > (which are typically realized via an abstract class): > public abstract class SPIProvider { > public abstract void doit(); > } > > I put an implementation of this SPI in a bundelized jar that > advertises this SPI as any JRE SPI would: via a > META-INF/services/...SPIProvider file which holds the name of the impl > class. > > Then in the activator of my client bundle I was able to obtain the > service using normal ServiceLoader calls: > public class Activator implements BundleActivator { > public void start(BundleContext context) throws Exception { > ServiceLoader<SPIProvider> ldr = ServiceLoader.load(SPIProvider.class); > for (SPIProvider spiObject : ldr) { > spiObject.doit(); // invoke the SPI object > } } } > So you can see, nothing special here. Existing clients can load the > SPI the way they have always been, which is essential if you're > integrating a library for which you don't have the source. > > To get ServiceLoader to load the the service through the right > classloader I wrote an aspect that wraps the actual > ServiceLoader.load() call and sets the ThreadContext for the duration > of the call. What it should set the classloader to is figured out via > an OSGi Service that I introduced: SPIClassloaderAdviceService. The > aspect invokes on the service to ask it what classloader should be > used for a particular SPI class. I can get to the Service Registry > from the aspect through the BundleReference API. > > aspect BundleAspect { > pointcut serviceloader(Class cls) : > args(cls) && call(ServiceLoader ServiceLoader.load(Class)); > > ServiceLoader around(Class cls) : serviceloader(cls) { > /* details ommitted, use cls.getClassLoader() as > BundleReference to get to ServiceRegistry... */ > SPIClassloaderAdviceService svc = ... // obtain from OSGi Service Registry > ClassLoader targetLoader = svc.getServiceClassLoader(cls); > ClassLoader prevCl = > Thread.currentThread().getContextClassLoader(); > try { > Thread.currentThread().setContextClassLoader(targetLoader); > return proceed(cls); > } finally { > Thread.currentThread().setContextClassLoader(prevCl); > } } } > > The SPIClassloaderAdviceService needs to be configured somehow. In my > prototype I let the SPI wrapper bundle register it, a bit like > OSGiLocator in the SMX bundles, but it could also be configured purely > declaratively through an extender that reads specific instructions > from the manifests of wrapped bundles. > > I can see two disadvantages to the approach: > 1. It requires weaving support in OSGi. But hopefully that will be > there in a standard way by the next OSGi release. > 2. My current strategy is global. However it should be possible to > create a strategy that takes the calling bundle into account so you > can say for SPI x bundle a should use x1 and bundle b should use x2... > > Thoughts anyone? > > Best regards, > > David >
