什么是服务提供者接口(SPI)和应用程序编程接口(API)的区别?
更具体地说,对于Java库,是什么使它们成为API和/或SPI?
换句话说,API告诉您特定类/方法为您做什么,而SPI告诉您要符合什么要求。
通常API和SPI是分开的。例如,在JDBC中,Driver类是SPI的一部分:如果您只想使用JDBC,则不需要直接使用它,但是实现JDBC驱动程序的所有人都必须实现该类。
然而,有时它们会重叠。 Connection接口既是SPI也是API:在使用JDBC驱动程序时,您通常会使用它,而且需要由JDBC驱动程序的开发人员实现。
摘自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();
}
}
API代表应用程序接口,API是访问某种软件或平台提供的服务/功能的一种方式。
SPI代表服务提供商接口,SPI是注入、扩展或修改软件或平台行为的一种方式。
API通常面向客户端访问服务,具有以下特性:
-->API是访问服务以实现特定行为或输出的编程方式
-->从API演进的角度来看,对于客户端来说增加不成问题
-->但API一旦被客户端利用,就不能(也不应该)进行修改/删除,除非存在适当的沟通, 因为这会完全破坏客户端的期望
SPI则是针对提供者的,并具有以下特性:
-->SPI是扩展/修改软件或平台行为的一种方式(可编程与编程方式)
-->SPI演变与API演变不同,在SPI中删除不是问题
-->添加SPI接口会导致问题,并可能破坏现有实现
更多详细解释请点击此处:服务提供商接口
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方面)不需要关心是否有版本控制系统,或者它是什么。
有一个方面似乎并没有得到很多关注,但对于理解何时以及为什么使用API/SPI非常重要。
只有在你实际编写一个预计会不断发展的平台时,才需要进行API/SPI分离。如果你不是在编写平台(你完全控制所有API消费者代码),那么唯一的好处将是良好且清晰的对象设计,因为你可以随时进行重构。
但一旦你拥有至少一个第三方客户端,并且希望以向后兼容的方式进行更改,你很可能应该使用API/SPI分离。
让我们以其中一个众所周知的Java对象Collection
和Collections
来展示它。
Collections
是一组实用的静态方法。通常,表示 API 对象的类被定义为 final
,这样可以确保(在编译时)没有客户端能够“实现”该对象,而只能依赖于“调用”其静态方法,例如。Collections.emptySet();
Collection
是一个接口,意味着任何人都可以实现自己的版本。因此,JDK的作者不能向其中添加新方法,因为这会破坏所有编写了自己的Collection
实现的客户端(*)。Collection2
,该接口扩展了前一个接口。然后,SPI客户端可以决定是否迁移到SPI的新版本并实现其附加的方法,或者是否使用旧版本。
我认为SPI通过实现API的某些功能,并通过服务查找机制将自己注册为可用组件,从而嵌入到更大的系统中。API直接被最终用户应用程序代码使用,但可以集成SPI组件。这是封装和直接使用之间的区别。
服务提供者接口是所有提供者必须实现的服务接口。如果现有的提供者实现都不适用于您,您需要编写自己的服务提供者(实现服务接口)并注册到某个地方(请参阅Roman的有用帖子)。
如果您正在重用服务接口的现有提供者实现,则基本上正在使用该特定提供者的API,其中包括服务接口的所有方法以及其自己的一些公共方法。如果在SPI之外使用提供程序API的方法,则正在使用特定于提供者的功能。