如何在Java中实现API/SPI模式



我正在创建一个框架,该框架公开了供开发人员使用的API:

public interface MyAPI {
    public void doSomeStuff();
    public int getWidgets(boolean hasRun);
}

开发人员所要做的就是根据这些API方法编写项目代码。我还希望他们能够在运行时类路径上放置不同的"驱动程序"/"API绑定"(与JDBC或SLF4J的工作方式相同),并使API方法调用(doSomeStuff()等)在不同的第三方资源(文件、服务器等)上运行。因此,根据运行时类路径看到的驱动程序/绑定(即myapi-ftpmyapi-sshmyapi-teleportation),相同的代码和API调用将映射到不同资源上的操作。

我如何编写(和打包)一个SPI来允许这样的运行时绑定,然后将MyAPI调用映射到正确的(具体的)实现?换句话说,如果myapi-ftp允许您从FTP服务器访问getWidgets(boolean),我怎么能做到这一点(同时使用API和SPI)?

具体的、可工作的Java代码示例的加分!提前感谢!

看看java.util.ServiceLoader类。

总的来说,想法是这样的:

API罐

  • 提供接口
  • 使用ServiceLoader类查找实现

    绑定/驱动程序Jar

  • 实现接口
  • 创建文件META-INF/并指定实现它的类名

    javadocs中有一个很好的例子:

    http://docs.oracle.com/javase/6/docs/api/java/util/ServiceLoader.html

    API罐

    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.
      }
    

    绑定Jar

    package com.myfoo;
    public class MyFooImpl implements FooInterface { ... }
    META-INF/com.foo.FooInterface
        com.myfoo.MyFooImpl
    

    编辑SPI示例

    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接口隔离开来。

    如果你想隐藏accepts方法,你可以总是有第二个接口,这是你的公共API。

  • 您知道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的实现。

    最新更新