我正在创建一个框架,公开API供开发人员使用:
public interface MyAPI {
public void doSomeStuff();
public int getWidgets(boolean hasRun);
}
所有开发人员应该做的是根据这些API方法编写项目代码。我还希望他们能够在运行时类路径上放置不同的“驱动程序”/“API绑定”(与JDBC或SLF4J的工作方式相同),并让API方法调用(doSomeStuff()
等)在不同的操作上运行第三方资源(文件,服务器,等等)。因此,相同的代码和API调用将映射到不同资源上的操作,具体取决于运行时类路径所看到的驱动程序/绑定(即myapi-ftp
,myapi-ssh
,myapi-teleportation
)。
如何编写(和打包)允许这种运行时绑定的SPI,和然后将MyAPI
调用映射到正确的(具体)实现?换句话说,如果myapi-ftp
允许您从FTP服务器getWidgets(boolean)
,我怎么能这样做(使用API和SPI)?
具体的工作Java代码示例的奖励点!提前谢谢!
答案 0 :(得分:16)
看一下java.util.ServiceLoader类。
一般来说,这个想法是这样的:
API Jar
Binding / Driver Jar
在javadocs中有一个很好的例子:
http://docs.oracle.com/javase/6/docs/api/java/util/ServiceLoader.html
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.
}
package com.myfoo;
public class MyFooImpl implements FooInterface { ... }
META-INF/com.foo.FooInterface
com.myfoo.MyFooImpl
修改强>
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;
}
}
当然,将文件名更改为com.foo.FooSpi并提供FooSpi的实现。这将允许您将公共API与Spi接口隔离开来。
如果你想隐藏accept方法,你总是可以有第二个接口,它是你的公共API,而且
答案 1 :(得分:2)
嗯,你知道API是客户使用的,而SPI是你的库内部使用的。
您将拥有实现API类的类,这些类依赖于SPI接口,并为您的SPI提供一些实现。
大多数情况下,SPI接口包含低级方法(在您的示例中直接使用FTP,SSH和...的抽象),并且您的库为您的客户提供更高级别的操作。
也许你的SPI接口如下:
public interface ProtocolSPI {
boolean isCompatibleWithUrl(String url);
Handle connect(String url, Map<String, Object> parameters);
int readData(Handle handle, byte[] bytes);
void writeData(Handle handle, byte[] bytes, int startIndex, int length);
void closeHandle(Handle handle);
}
并且您拥有依赖于此接口的代码来处理可替换部件。
您可能有一个ProtocolSPIFactory使用java.util.ServiceLoader
来查找ProtocolSPI的可用实现(在类路径中),然后实例化它们并通过调用isCompatibleWithUrl
找出要用于特定的实现url
。