[英]Service Provider Interface without the Provider
我正在阅读Bloch的有效java书[1]并且遇到了以下SPI示例:
//Service interface
public interface Service {
//Service specific methods here
}
//Service provider interface
public interface Provider {
Service newService();
}
//Class for service registration and access
public class Services {
private Services(){}
private static final Map<String, Provider> providers =
new ConcurrentHashMap<String, Provider>();
public static final String DEFAULT_PROVIDER_NAME = "<def>";
//Registration
public static void registerDefaultProvider(Provider p) {
registerProvider(DEFAULT_PROVIDER_NAME, p);
}
public static void registerProvider(String name, Provider p) {
providers.put(name, p);
}
//Access
public static Service newInstance() {
return newInstance(DEFAULT_PROVIDER_NAME);
}
public static Service newInstance(String name) {
// you get the point..lookup in the map the provider by name
// and return provider.newService();
}
这是我的问题:为什么必须使用Provider接口? 难道我们不能轻易地自己注册服务 - 例如,维护服务实现的映射,然后在查找时返回实例? 为什么要额外的抽象层?
也许这个例子过于通用 - 任何“更好”的例子来说明这一点也会很棒。
[1] 第二版 ,第2章。第一版示例未涉及服务提供者接口。
为什么需要Provider接口? 难道我们不能轻易地自己注册服务 - 例如,维护服务实现的映射,然后在查找时返回实例?
正如其他人所说,Provider的目的是拥有一个可以创建Service
实例的AbstractFactory。 您并不总是希望保留对所有Service实现的引用,因为它们可能是短暂的和/或在执行后可能无法重用。
但是,如果您没有提供商,提供商的目的是什么以及如何使用“提供商注册API”
拥有Provider接口的最有力的原因之一是,您不需要在编译时拥有实现。 您的API用户可以稍后添加自己的实现。
让我们在另一个答案中使用JDBC作为Ajay的例子,但让我们更进一步:
有许多不同类型的数据库和数据库供应商,他们都有不同的管理和实现数据库的方式(以及可能如何查询它们)。 由于许多原因,Java的创建者不可能创建所有这些不同可能方式的实现:
那你怎么解决这个问题呢? 使用Service Provider
。
Provider
。 它提供了与特定供应商的数据库交互的方法。 Driver
中的一个方法是一个工厂方法,用于在给定url和其他属性(如用户名和密码等)的情况下为数据库创建Connection
实例(即Service
)。 每个数据库供应商都编写自己的Driver
实现,以了解如何与自己的数据库系统进 这些不包含在JDK中; 你必须去公司网站或其他一些代码库,并将它们作为一个单独的jar下载。
要使用这些驱动程序,必须将jar添加到类路径,然后使用JDK DriverManager
类来注册驱动程序。
Service Registration
。 DriverManager类有一个方法registerDriver(Driver)
,用于在服务注册中注册Driver实例,以便可以使用它。 按照惯例,大多数Driver
实现在类加载时注册,因此您在代码中所要做的就是写入
Class.forname("foo.bar.Driver");
要为供应商“foo.bar”注册驱动程序(假设您的类路径中有该类的jar。)
注册数据库驱动程序后,您可以获得连接到数据库的Service实现实例。
例如,如果您的本地计算机上有一个名为“test”的mysql数据库,并且您有一个用户名为“monty”且密码为“greatsqldb”的用户帐户,那么您可以创建一个这样的Service实现:
Connection conn =
DriverManager.getConnection("jdbc:mysql://localhost/test?" +
"user=monty&password=greatsqldb");
DriverManager类会看到您传入的String,并找到可以理解其含义的已注册驱动程序。 (这实际上是使用Chain of Responsibility
模式完成的,通过遍历所有已注册的驱动程序并调用其Driver.acceptsUrl(Stirng)
方法,直到URL被接受为止)
请注意,JDK中没有特定于mysql的代码。 您所要做的就是注册某个供应商的驱动程序,然后将格式正确的String传递给服务提供商。 如果我们后来决定使用不同的数据库供应商(如oracle或sybase),那么我们只需交换jar并修改我们的连接字符串。 DriverManager中的代码不会更改。
为什么我们不只是连接一次并保持它? 为什么我们需要服务?
我们可能希望在每次操作后连接/断开连接。 或者我们可能希望保持更长时间的连接。 拥有该服务允许我们随时创建新的连接,并且不妨碍我们保留对它的引用以便以后重用。
这是一个非常强大的概念,框架使用它来允许许多可能的排列和扩展,而不会使核心代码库混乱。
编辑
与提供多种Services
多家提供商和提供商合作:
没有什么可以阻止你拥有多个提供商。 您可以同时连接到使用不同数据库供应商软件创建的多个数据库。 您还可以同时连接到同一供应商生成的多个数据库。
多个服务 - 某些提供商甚至可能根据连接URL提供不同的Service
实现。 例如,H2可以创建基于文件系统或基于内存的数据库。 告诉H2你要使用哪一个是一种不同的url格式。 我没有看过H2代码,但我认为基于文件和基于内存的是不同的Service实现。
为什么DriverManager不管理Connections而Oracle可以实现OracleConnectionWrapper? 没有供应商!
这也需要您知道您有Oracle连接。 这是非常紧密的耦合,如果我改变供应商,我将不得不改变很多代码。
Service Registration
只需要一个字符串。 请记住,它使用chain of Responsiblity
来查找知道如何处理URL的第一个注册的Provider。 应用程序可以是供应商中立的,它可以从属性文件中获取连接URL和驱动程序类名称。 这样,如果我更换供应商,我就不必重新编译代码。 但是,如果我硬编码引用“OracleConnectionWrapper”然后我更改了供应商,我将不得不重写部分代码然后重新编译。
如果需要,没有什么能阻止某人支持多种数据库供应商URL格式。 所以如果我愿意,我可以制作一个可以处理mysql和oracle的GenericDriver。
如果您可能需要多种类型的服务,则不能只重用旧服务。 (此外,测试等可能希望为每个测试创建新的服务,而不是重用可能已被先前测试修改或更新的服务。)
我认为Effective Java
提到的答案和一个例子。
服务提供者框架的可选第四组件是服务提供者接口,提供者实现该接口以创建其服务实现的实例。 在缺少服务提供者接口的情况下,实现按类名注册并反射实例化(第53项)。
在JDBC
的情况下,
Connection
扮演服务界面的一部分,
DriverManager.registerDriver
是提供者注册API , DriverManager.getConnection
是服务访问API ,和
Driver
是服务提供者界面 。
因此,正如您已正确指出的那样,不必使用Provider接口,而只需要更简洁的方法。
因此,您似乎可以为同一Service
拥有多个Provider
,并且基于特定的提供程序名称,您可以获得同一服务的不同实例。 所以我想说每个提供商都有点像工厂,可以适当地创建服务。
例如,假设class PaymentService implements Service
,它需要一个Gateway
。 您有PayPal和Chase网关处理这些支付处理器。 现在,您创建一个PayPalProvider和ChaseProvider,每个都知道如何使用正确的网关创建正确的PaymentService实例。
但我同意,似乎做作了。
作为其他答案的综合(第四个组成部分是文本原因)我认为这是为了限制编译依赖性。 使用SPI,您可以使用所有工具来排除对实现的显式引用:
第一版没有提到SPI。 将它包含在关于静态工厂的项目中可能不是正确的地方。 文中提到的DriverManager是一个暗示,但Bloch并没有深入。 在某种程度上,平台实现了一种ServiceLocator模式,以减少编译依赖性,具体取决于环境。 在抽象工厂中使用SPI,它在ServiceLoader的帮助下成为ServiceLocator的ServiceFactory,用于模块化。
ServiceLoader迭代器可用于动态填充示例的服务映射。
[1]在OSGi环境中,这是一个微妙的操作 。
没有提供者的服务提供者接口
让我们看看没有提供商会是什么样子。
//Service interface
public interface Service {
//Service specific methods here
}
//Class for service registration and access
public class Services {
private Services(){}
private static final Map<String, Service> services =
new ConcurrentHashMap<String, Service>();
public static final String DEFAULT_SERVICE_NAME = "<def>";
//Registration
public static void registerDefaultService(Provider p) {
registerService(DEFAULT_SERVICE_NAME, p);
}
public static void registerService(String name, Provider p) {
services.put(name, p);
}
//Access
public static Service getInstance() {
return newInstance(DEFAULT_SERVICE_NAME);
}
public static Service getInstance(String name) {
// you get the point..lookup in the map the service by name
// and return it;
}
如您所见,可以在没有Provider接口的情况下创建服务提供者接口 。 #getInstance(..)
的#getInstance(..)
者最终不会注意到差异。
那为什么我们需要一个提供者呢?
Provider
接口是Abstract Factory , Services#newInstance(String)
是Factory方法 。 这两种设计模式都具有将服务实现与服务注册分离的优势。
而不是在注册所有服务的启动事件处理程序中实现服务实例化,而是为每个服务创建一个提供程序。 这使得它松散耦合并且更容易重构,因为服务和服务提供者可以彼此靠近,例如放到另一个JAR文件中。
“工厂方法在工具包和框架中很常见,其中库代码需要创建可能由使用框架的应用程序子类化的类型的对象。” [1]
终身管理 :
您可能已经在没有提供程序的上层代码中意识到我们正在注册服务实例而不是提供程序,这可能决定实例化新的服务实例。
这种方法有一些缺点:
1.必须在第一次服务呼叫之前创建服务实例。 懒惰初始化是不可能的。 这将延迟启动并将资源绑定到很少使用甚至从不使用的服务。
1B。 您在使用后“无法”关闭服务,因为无法重新实例化它们。 (使用提供程序,您可以以调用方必须调用的方式设计服务接口 #close()
,通知提供程序,提供程序决定保留或最终确定服务实例。)
2.所有调用者都将使用相同的服务实例,因此您必须确保它是线程安全的。 但是使它成为线程安全会使它变慢。 相反,提供者可能会选择创建几个服务实例以减少保留时间。
结论
不需要提供程序接口,但它封装了特定于服务的实例化逻辑并优化了资源分配。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.