How to implement API / SPI pattern in Java?
I am creating a framework that exposes APIs for developers to use:
public interface MyAPI { public void doSomeStuff(); public int getWidgets(boolean hasRun); }
What all developers should do is write their projects according to these API methods I also hope they can place different "drivers" / "API bindings" (the same way as JDBC or slf4j) on the runtime classpath, and have API method calls (dosomestuff()) on different third-party resources (files, servers, any) Therefore, according to the driver / binding (i.e. myapi FTP, myapi SSH, myapi reporting) seen in the runtime classpath, the same code and API calls will be mapped to operations on different resources
How do I write (and package) an SPI that allows such runtime binding, and then map my API calls to the correct (concrete) implementation? In other words, if myapi FTP allows you to get widgets (Boolean) from the FTP server, what should I do (using API and SPI)?
Bonus points, specific work, java code example! Thank you in advance!
Solution
Look at Java util. Serviceloader class
Generally speaking, the idea is:
API tank
>Provide interface > use serviceloader class to find implementation
Binding / driver tank
>Implementation interface > create file meta inf / and specify the class name to implement it
There is a good example in Javadoc:
http://docs.oracle.com/javase/6/docs/api/java/util/ServiceLoader.html
API tank
package com.foo; public interface FooInterface { ... } public class FooInterfaceFactory { public static FooInterface newFooInstance() { ServiceLoader<FooInterface> loader = ServiceLoader.load(FooInterface.class); Iterator<FooInterface> it = loader.iterator(); // pick one of the provided implementations and return it. }
Binding can
package com.myfoo; public class MyFooImpl implements FooInterface { ... } Meta-INF/com.foo.FooInterface com.myfoo.MyFooImpl
edit
SPI example
public interface FooSpi { void accepts(String url); FooInterface getFooInstance(); } public class FooInterfaceFactory { public static FooInterface getFooInterfaceInstance(String url) { ServiceLoader<FooSpi> loader = ServiceLoader.load(FooSpi.class); Iterator<FooSpi> it = loader.iterator(); while (it.hasNext()) { FooSpi fooSpi = it.next(); if (fooSpi .accepts(url)) { return fooSpi.getFooInstance(); } } return null; } }
Of course, change the file name to com foo. Foospi and provide the implementation of foospi This will allow you to isolate the public API from the SPI interface
If you want to hide the acceptance method, you can always have a second interface, which is your public API, and t