Context: Imagine you're writing a plugin to a program (let's say Foobar Plugin for Eclipse) and you want it to have an API which other people - such as the creators of Barbaz Plugin for Eclipse - can call in order to provide integration. ("Whenever a foo is barred, our plugin also bazzes it automatically! ... if the foobar plugin is actually installed, that is.")
If Barbaz just uses classes from Foobar, they'll crash if Foobar isn't actually installed. For licensing or other reasons, Barbaz also wants to be able to work without Foobar. Foobar might not be free-as-in-beer, or it might just be a very large or complicated plugin which users don't want to install.
You might solve this by distributing a small "API plugin", perhaps with just interface declarations IFoobarPlugin
. If Foobar is installed, Barbaz can acquire a real instance of IFoobarPlugin
- otherwise, they get a null object (or at least a null pointer). Either way, the class IFoobarPlugin
is still installed, so Barbaz Plugin doesn't crash with a NoClassDefFoundError
.
In short, instead of this dependency graph:
Barbaz
|
V
Foobar
the developers of both plugins would prefer this:
Barbaz Foobar
\ /
V V
FoobarAPI
The question is: How does Barbaz get a reference to the real instance of IFoobarPlugin
?
FoobarAPI directly wraps the Foobar classes.
- This relies on
NoClassDefFoundError
being thrown in a predictable location (e.g. the first time a method is called which references the non-existent class). It's conceivable that on some platforms, you can't even load FoobarAPI - FoobarAPI cannot be compiled from source without Foobar.
- Typically, the client must not call getFoobarPlugin until it detects that Foobar is actually installed.
package com.foobar.api; import com.foobar.internal.FoobarPlugin; public class FoobarPluginAPI { public static IFoo createFoo() { // may throw NoClassDefFoundError return FoobarPlugin.createFoo(); } }
- This relies on
FoobarAPI gets the plugin instance via reflection.
- The API can be compiled without the implementation.
- It is easy to predict what happens when Foobar isn't installed.
- The use of reflection may cause a performance hit, if the function is called repeatedly. (This can be avoided by returning a factory object)
package com.foobar.api; public class FoobarPluginAPI { public static IFoo createFoo() { try { return (IFoobarPlugin)Class.forName("com.pluginx.internal.FoobarPlugin").getMethod("createFoo").invoke(null); } catch(ReflectiveOperationException e) { return new NullFoobarPluginImpl(); // or null, or throw an exception, etc } } }
Foobar registers itself in the API when it initializes.
- The API is completely separate from the implementation.
- There is no string-typing with reflection. The compiler can type-check everything here.
- Alternative plugins ("Open Foobar") can register the same API.
- If the client doesn't find an instance of Foobar, it might not know whether none is installed, or whether it is installed but hasn't been registered yet.
package com.foobar.api; public abstract class FoobarPluginAPI { public abstract IFoo createFoo(); private static FoobarPluginAPI instance; public static FoobarPluginAPI getInstance() {return instance;} public static void setInstance(FoobarPluginAPI newInstance) { if(instance != null) throw new IllegalStateException("instance already set"); else instance = newInstance; } }
The same as above, but the client code needs to get the initial reference from the plugin system itself.
- No special considerations needed in the API module
- However, the client code is much more verbose.
// API package com.foobar.api; public interface FoobarPluginAPI { IFoo createFoo(); } // Implementation package com.foobar; import org.eclipse.blahblah.Plugin; import com.foobar.api.FoobarPluginAPI; public class FoobarPlugin extends Plugin implements FoobarPluginAPI { @Override public IFoo createFoo() { ... } } // Client code uses it like this FoobarPluginAPI foobar = (FoobarPluginAPI)EclipsePluginManager.getPluginByID("com.foobar"); if (foobar != null) { Foo foo = foobar.createFoo(); // ... }
Don't. Make it the client's problem. The client (Barbaz Plugin) will probably end up making their own wrapper which acts like one of the above.