SPI和API有什么区别?

366
9个回答

460
  • API是描述类、接口、方法等可供调用和使用以实现目标的内容,而
  • SPI是描述类、接口、方法等可供扩展和实现以实现目标的内容。

换句话说,API告诉您特定类/方法为您做什么,而SPI告诉您要符合什么要求。

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

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


听起来大致正确。只是SPI和API之间的界限可能有点模糊,因为大多数API都有抽象类/接口供用户实现以完成任务。 - kctang
1
@koss:大多数API?我知道你的意思,但我并没有经常看到这些东西。 - Joachim Sauer
3
API对开发人员更为相关,而SPI则对供应商更为相关。 - Azfar Niaz
10
由于供应商雇用开发人员构建其产品,因此两者对开发人员都是相关的;-) - Joachim Sauer
为什么会有区别呢?对于一个没有Java™编程经验的程序员来说,两者都只是API。 - mirabilos
显示剩余2条评论

71

摘自Effective Java, 2nd Edition

服务提供者框架是一种系统,其中多个服务提供者实现一个服务,而该系统使这些实现对其客户端可用,从而将它们与实现分离。

服务提供者框架有三个基本组件:一个服务接口,服务提供者实现它; 一个提供者注册API,系统用它来注册实现,让客户端可以访问它们; 以及一个服务访问API,客户端用它来获取服务的实例。服务访问API通常允许但不要求客户端指定某些选择提供者的标准。如果没有这样的规定,API将返回一个默认实现的实例。服务访问API是“灵活的静态工厂”,构成了服务提供者框架的基础。

服务提供者框架的第四个可选组件是服务提供者接口,服务提供者通过该接口创建其服务实现的实例。如果没有服务提供者接口,则通过类名进行反射实例化进行注册。(第53项)对于JDBC,Connection扮演了服务接口的角色,DriverManager.registerDriver是提供者注册API,DriverManager.getConnection是服务访问API,而Driver则是服务提供者接口。

服务提供者框架模式有许多变种。例如,服务访问API可以返回比提供者要求的更丰富的服务接口,使用适配器模式[Gamma95,第139页]。这是一个简单的实现,带有服务提供者接口和默认提供者:

// 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();
    }
}

5
感谢您对服务提供者框架的描述,这将会有所帮助。然而,它并没有清晰地描述API和SPI之间的“区别”,所以我不会在这里打勾...;-) - kctang

28
API和SPI之间的区别在于,API提供了一些具体的实现。在这种情况下,服务提供者必须实现一些API(称为SPI)。
以JNDI为例:
JNDI提供了用于上下文查找的接口和一些类。查找上下文的默认方式由InitialContext提供。该类内部将使用SPI接口(使用NamingManager)进行提供程序特定的实现。
请参阅下面的JNDI架构图以获得更好的理解。 Enter image description here

27

API代表应用程序接口,API是访问某种软件或平台提供的服务/功能的一种方式。

SPI代表服务提供商接口,SPI是注入、扩展或修改软件或平台行为的一种方式。

API通常面向客户端访问服务,具有以下特性:

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

-->从API演进的角度来看,对于客户端来说增加不成问题

-->但API一旦被客户端利用,就不能(也不应该)进行修改/删除,除非存在适当的沟通, 因为这会完全破坏客户端的期望

SPI则是针对提供者的,并具有以下特性:

-->SPI是扩展/修改软件或平台行为的一种方式(可编程与编程方式)

-->SPI演变与API演变不同,在SPI中删除不是问题

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

更多详细解释请点击此处:服务提供商接口


15

NetBeans常见问题解答:SPI是什么?它与API有何不同之处?

API是一个通用术语,全称为应用程序编程接口。它指的是软件(在Java中通常是一些Java类)暴露出来的东西,使得其他软件可以与其进行通信。

SPI代表服务提供者接口。它是所有可能的API的一个子集,专门针对库提供的类被应用程序(或API库)调用的情况,这些类通常会改变应用程序能够做到的事情。

典型的例子是JavaMail。其API有两个方面:

  • API方面 - 如果您正在编写邮件客户端或想要读取邮箱,则需要调用此方面。
  • SPI方面 - 如果您要提供线协议处理程序以允许JavaMail与新类型的服务器进行通信,例如新闻或IMAP服务器,则需要调用此方面。

API的用户很少需要查看或与SPI类进行交互,反之亦然。

在NetBeans中,当您看到SPI这个术语时,通常是在谈论模块可以在运行时注入的类,这些类允许NetBeans执行新任务。例如,有一个通用的SPI用于实现版本控制系统。不同的模块提供该SPI的实现,包括CVS、Subversion、Mercurial和其他版本控制系统。然而,处理文件的代码(API方面)不需要关心是否有版本控制系统,或者它是什么。


12

有一个方面似乎并没有得到很多关注,但对于理解何时以及为什么使用API/SPI非常重要。

只有在你实际编写一个预计会不断发展的平台时,才需要进行API/SPI分离。如果你不是在编写平台(你完全控制所有API消费者代码),那么唯一的好处将是良好且清晰的对象设计,因为你可以随时进行重构。

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

让我们以其中一个众所周知的Java对象CollectionCollections来展示它。


API: Collections 是一组实用的静态方法。通常,表示 API 对象的类被定义为 final,这样可以确保(在编译时)没有客户端能够“实现”该对象,而只能依赖于“调用”其静态方法,例如。
Collections.emptySet();

由于所有客户端都是“调用”而不是“实现”,JDK的作者可以自由地在未来版本的JDK中向Collections对象中添加新方法。他们可以确信这不会破坏任何客户端,即使可能有数百万次的使用。
SPI: Collection是一个接口,意味着任何人都可以实现自己的版本。因此,JDK的作者不能向其中添加新方法,因为这会破坏所有编写了自己的Collection实现的客户端(*)。
通常情况下,如果需要添加额外的方法,则需要创建一个新的接口,例如Collection2,该接口扩展了前一个接口。然后,SPI客户端可以决定是否迁移到SPI的新版本并实现其附加的方法,或者是否使用旧版本。
你可能已经看到了重点。如果将这两个部分合并为一个单独的类,你的API将无法进行任何添加。这也是为什么好的Java API和框架不会暴露抽象类的原因,因为它们会阻碍其未来发展与向后兼容性的关系。
如果还有不清楚的地方,我建议查看此页面,该页面会更详细地解释上述内容。
(*) 请注意,这只适用于 Java 1.8 版本之前,该版本引入了接口中定义的 "default" 方法的概念。

4

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


谢谢你的回复,克里斯。有现有Java(例如Servlet、JDBC等)库的示例吗?...就像它如何使它们成为API / SPI一样。仅通过描述很难想象出差异。 - kctang
1
一个API是JDBC,JDBC的SPI是数据库连接器接口,SPI开发人员可以使用它来创建新的数据库连接器,开发人员可以选择使用。 - Chris Dennett

4

服务提供者接口是所有提供者必须实现的服务接口。如果现有的提供者实现都不适用于您,您需要编写自己的服务提供者(实现服务接口)并注册到某个地方(请参阅Roman的有用帖子)。

如果您正在重用服务接口的现有提供者实现,则基本上正在使用该特定提供者的API,其中包括服务接口的所有方法以及其自己的一些公共方法。如果在SPI之外使用提供程序API的方法,则正在使用特定于提供者的功能。


3
在Java世界中,不同的技术被设计成模块化并可“插入”应用服务器。因此,以下是不同的部分:
- 应用服务器
- [SPI]
- 可插拔技术
- [API]
- 终端用户应用程序
JTA(事务管理器)和JCA(用于JMS或数据库的适配器)是这种技术的两个例子,但还有其他技术。
这种可插拔技术的实现者必须实现SPI以便能够插入应用服务器,并提供API供终端用户应用程序使用。例如,在JCA中,ManagedConnection接口是SPI的一部分,而Connection是终端用户API的一部分。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接