Hi Bartek, Regarding your comment 1 - yes the AOP approach needs to list exactly which APIs to wrap.
I also agree with your comment 2 - in general the non-OSGi approach to using SPIs has drawbacks in terms of lifecycle. Generally there will be no support for dynamic replacement of the SPI - this limitation goes away when clients use the OSGi way of accessing the SPIs: through the service registry :) I'll try to find some time to add my experimental code to the spi-fly code base so we can maybe improve it in a larger group. One thing that I haven't figured out is whether it is possible to dynamically add aspects to the system whenever a bundle gets installed. This would probably be necessary if we want the aspects to have different mappings per client bundle as I haven't yet found a way to otherwise figure out in the aspect who the client bundle is. Best regards, David On 14 June 2010 21:56, Bartosz Kowalewski <[email protected]> wrote: > 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 >> >
