2024-01-04 05:00:02

SPI和API的区别?

服务提供者接口(SPI)和应用程序编程接口(API)有什么区别?

更具体地说,对于Java库,是什么使它们成为API和/或SPI?


我认为SPI通过实现API的某些特性,然后通过服务查找机制将自身注册为可用,从而嵌入到更大的系统中。API由最终用户应用程序代码直接使用,但可以集成SPI组件。这就是封装和直接使用之间的区别。


在Java世界中,不同的技术都是模块化的,并且可以“插入”到应用服务器中。这两者之间是有区别的

应用服务器 (SPI) 可插拔技术 (API) 最终用户应用程序

这类技术的两个例子是JTA(事务管理器)和JCA (JMS或数据库适配器)。但还有其他原因。

这种可插技术的实现者必须在应用程序服务器中实现可插的SPI,并提供供最终用户应用程序使用的API。来自JCA的一个例子是ManagedConnection接口,它是SPI的一部分,而Connection是最终用户API的一部分。


API是类/接口/方法/…你用它来实现目标,还有 SPI是类/接口/方法的描述…你通过扩展和实现来实现一个目标。

换句话说,API告诉你一个特定的类/方法为你做了什么,而SPI告诉你必须做什么才能符合。

通常API和SPI是分开的。例如,在JDBC中,驱动程序类是SPI的一部分:如果你只是想使用JDBC,你不需要直接使用它,但是每个实现JDBC驱动程序的人都必须实现这个类。

然而,有时它们会重叠。Connection接口既是SPI也是API:当您使用JDBC驱动程序时,您通常会使用它,并且它需要由JDBC驱动程序的开发人员实现。


摘自Effective Java,第二版:

A service provider framework is a system in which multiple service providers implement a service, and the system makes the implementations available to its clients, decoupling them from the implementations. There are three essential components of a service provider framework: a service interface, which providers implement; a provider registration API, which the system uses to register implementations, giving clients access to them; and a service access API, which clients use to obtain an instance of the service. The service access API typically allows but does not require the client to specify some criteria for choosing a provider. In the absence of such a specification, the API returns an instance of a default implementation. The service access API is the “flexible static factory” that forms the basis of the service provider framework. An optional fourth component of a service provider framework is a service provider interface, which providers implement to create instances of their service implementation. In the absence of a service provider interface, implementations are registered by class name and instantiated reflectively (Item 53). In the case of JDBC, Connection plays the part of the service interface, DriverManager.registerDriver is the provider registration API, DriverManager.getConnection is the service access API, and Driver is the service provider interface. There are numerous variants of the service provider framework pattern. For example, the service access API can return a richer service interface than the one required of the provider, using the Adapter pattern [Gamma95, p. 139]. Here is a simple implementation with a service provider interface and a default provider:

// Service provider framework sketch

// Service interface
public interface Service {
    ... // Service-specific methods go here
}

// Service provider interface
public interface Provider {
    Service newService();
}

// Noninstantiable class for service registration and access
public class Services {
    private Services() { }  // Prevents instantiation (Item 4)

    // Maps service names to services
    private static final Map<String, Provider> providers =
        new ConcurrentHashMap<String, Provider>();
    public static final String DEFAULT_PROVIDER_NAME = "<def>";

    // Provider registration API
    public static void registerDefaultProvider(Provider p) {
        registerProvider(DEFAULT_PROVIDER_NAME, p);
    }
    public static void registerProvider(String name, Provider p){
        providers.put(name, p);
    }

    // Service access API
    public static Service newInstance() {
        return newInstance(DEFAULT_PROVIDER_NAME);
    }
    public static Service newInstance(String name) {
        Provider p = providers.get(name);
        if (p == null)
            throw new IllegalArgumentException(
                "No provider registered with name: " + name);
        return p.newService();
    }
}

NetBeans的常见问题:什么是SPI?它与API有何不同?

API is a general term - an acronym for Application Programming Interface - it means something (in Java, usually some Java classes) a piece of software exposes, which allows other software to communicate with it. SPI stands for Service Provider Interface. It is a subset of all things that can be API specific to situations where a library is providing classes which are called by the application (or API library), and which typically change the things the application is able to do. The classic example is JavaMail. Its API has two sides: The API side — which you call if you are writing a mail client or want to read a mailbox The SPI side if you are providing a wire-protocol handler to allow JavaMail to talk to a new kind of server, such as a news or IMAP server Users of the API rarely need to see or talk to the SPI classes, and vice-versa. In NetBeans, when you see the term SPI, it is usually talking about classes that a module can inject at runtime which allow NetBeans to do new things. For example, there is a general SPI for implementing version control systems. Different modules provide implementations of that SPI for CVS, Subversion, Mercurial and other revision control systems. However, the code that deals with files (the API side) does not need to care if there is a version control system, or what it is.


服务提供者接口是所有提供者都必须实现的服务接口。如果现有的提供者实现都不适合您,那么您需要编写自己的服务提供者(实现服务接口)并在某处注册(请参阅Roman的有用文章)。

如果重用服务接口的现有提供者实现,基本上就是在使用该特定提供者的API,其中包括服务接口的所有方法以及它自己的一些公共方法。如果您在SPI之外使用提供者API的方法,那么您使用的是提供者特定的特性。


API和SPI之间的区别在于API还提供了一些具体的实现。在这种情况下,服务提供者必须实现一些api(称为SPI)

一个例子是JNDI:

JNDI为上下文查找提供了接口和一些类。在IntialContext中提供了默认的查找上下文的方法。该类将在内部使用SPI接口(使用NamingManager)实现特定于提供者的实现。

为了更好地理解,请参阅下面的JNDI体系结构。


API代表应用程序编程接口,其中API是访问某种软件或平台提供的服务/功能的手段。

SPI代表服务提供者接口(Service Provider Interface), SPI是为软件或平台注入、扩展或改变行为的方法。

API通常是客户端访问服务的目标,它具有以下属性:

>API是一种访问服务以实现特定行为或输出的编程方式

从API发展的角度来看,添加对客户端来说根本不是问题

但是API一旦被客户端使用,它就不能(也不应该)被修改/删除 除非有适当的沟通,否则就彻底退化了 客户的期望

另一部分的SPI是针对提供者的,具有以下属性:

——>SPI是一种扩展/改变软件或平台行为的方法(可编程vs。 编程)

->SPI的进化不同于API的进化,SPI的移除不是一个问题

添加SPI接口会导致问题,可能会破坏现有的实现

更多解释请点击这里:服务提供者接口


有一个方面似乎没有被强调太多,但对于理解为什么以及何时使用API/SPI非常重要。

API/SPI的分离只在你实际编写一个平台时才需要,而且它预计会不断发展。如果你写了一个API,并且“知道”它将来不需要任何向后兼容的改进,那么就没有真正的理由把你的代码分成两部分。此外,如果你根本不写平台(你有所有的API消费者代码在控制之下),唯一的好处将是良好和干净的对象设计,因为你可以在任何时候重构你需要的东西。

但是,一旦您的平台至少有一个第三方客户端,并且您希望以向后兼容的方式进行更改,那么您很可能应该使用API/SPI拆分。

让我们在一个众所周知的Java对象Collection和Collections上展示它。


Collections是一组实用程序静态方法。通常表示API对象的类被定义为final,因为它确保(在编译时)没有客户端可以“实现”该对象,并且它们可以依赖于“调用”其静态方法。

Collections.emptySet();

因为所有的客户端都是“调用”而不是“实现”,JDK的作者可以在未来版本的JDK中自由地向Collections对象中添加新方法。他们可以确定它不会破坏任何客户端,即使可能有数百万的使用。


SPI:集合是一个接口,它意味着任何人都可以实现自己的版本。因此,JDK的作者不能向其中添加新方法,因为这会破坏所有编写自己的Collection实现(*)的客户端。

通常情况下,当需要添加额外的方法时,需要创建新的接口,如Collection2,它是对前者的扩展。然后SPI客户端可以决定是否迁移到新版本的SPI并实现它的附加方法,或者是否坚持使用旧的方法。


你可能已经看到了这一点。如果你把这两个部分合并到一个类中,你的API将被阻止任何添加。这也是为什么优秀的Java api和框架不公开抽象类的原因,因为它们会阻碍它们未来在向后兼容性方面的发展。

如果还有什么不清楚的地方,我建议你去看看这个页面,上面有更详细的解释。


(*)注意,这只适用于Java 1.8,它引入了在接口中定义默认方法的概念。