Java SPI(服务提供商接口)和ServiceLoader

时间:2020-02-23 14:36:51  来源:igfitidea点击:

Java SPI(服务提供商接口)是动态加载服务的机制。
通过遵循特定的规则集,我们可以在我们的应用程序中实现Java SPI,并使用ServiceLoader类加载服务。

Java SPI组件

SPI实现中包含四个组件。

  • 服务提供者接口:为服务提供者实现类定义合同的接口或者抽象类。

  • 服务提供者:实际提供服务的实现类。

  • SPI配置文件:一个特殊的文件,提供了寻找服务实现的逻辑。
    文件名必须存在于META-INF/services目录中。
    文件名应与服务提供商接口标准名称完全相同。
    文件中的每一行都有一个实现服务类详细信息,再次是服务提供者类的完全限定名称。

  • ServiceLoader:Java SPI主类,用于为服务提供商接口加载服务。
    ServiceLoader中有多种实用程序方法来获取特定的实现,对其进行迭代或者再次重新加载服务。

Java服务提供者接口示例

java.util.spi包提供了许多服务提供者接口,可以实现这些接口来提供服务。

  • ResourceBundleProvider,ResourceBundleControlProvider和AbstractResourceBundleProvider:服务提供程序接口和Resource Bundle实现类的抽象类。

  • LocaleServiceProvider,CalendarDataProvider,CalendarNameProvider,CurrencyNameProvider,TimeZoneNameProvider和LocaleNameProvider:用于实现特定于区域设置的服务提供者。

Java SPI接口列表

Java SPI示例

让我们创建一个SPI的实现,并使用ServiceLoader类加载一些服务。

1.服务提供商接口

假设我们有一个" MessageServiceProvider"接口,用于定义服务提供商实施的合同。

package com.theitroad.serviceproviders;

public interface MessageServiceProvider {

	void sendMessage(String message);
}

2.服务提供商实施类

我们希望支持电子邮件和推送通知消息。
因此,我们将创建" MessageServiceProvider"接口的两个服务提供程序实现– EmailServiceProvider和PushNotificationServiceProvider。

package com.theitroad.serviceproviders;

public class EmailServiceProvider implements MessageServiceProvider {

	public void sendMessage(String message) {
		System.out.println("Sending Email with Message = "+message);
	}

}
package com.theitroad.serviceproviders;

public class PushNotificationServiceProvider implements MessageServiceProvider {

	public void sendMessage(String message) {
		System.out.println("Sending Push Notification with Message = "+message);
	}

}

3.服务提供商配置文件

必须在META-INF/services目录中创建配置文件。
其名称应为" com.theitroad.serviceproviders.MessageServiceProvider"。
我们将在此文件中指定两个实现类。

com.theitroad.serviceproviders.EmailServiceProvider
com.theitroad.serviceproviders.PushNotificationServiceProvider

4.加载服务的ServiceLoader示例

最后,我们必须使用ServiceLoader类加载服务。
这是一个显示其用法的简单测试程序。

package com.theitroad.test;

import java.util.Optional;
import java.util.ServiceLoader;

import com.theitroad.serviceproviders.MessageServiceProvider;

public class ServiceLoaderTest {

	public static void main(String[] args) {
		ServiceLoader<MessageServiceProvider> serviceLoader = ServiceLoader.load(MessageServiceProvider.class);

		for (MessageServiceProvider service : serviceLoader) {
			service.sendMessage("Hello");
		}

		//using Java 8 Optional to get the first service
		Optional<MessageServiceProvider> firstService = serviceLoader.findFirst();
		if (firstService.isPresent()) {
			firstService.get().sendMessage("Hello Friend");
		}

		//using Java 8 forEach() method
		serviceLoader.forEach((service) -> service.sendMessage("Have a Nice Day!"));

		//Total Number of Loaded Services
		System.out.println(serviceLoader.stream().count());

	}

}

当我们运行上面的程序时,我们得到以下输出。

Sending Email with Message = Hello
Sending Push Notification with Message = Hello
Sending Email with Message = Hello Friend
Sending Email with Message = Have a Nice Day!
Sending Push Notification with Message = Have a Nice Day!
2

下图显示了我们的最终项目结构和SPI组件。

Java SPI示例项目结构

ServiceLoader重要方法

让我们看一下ServiceLoader类的重要方法。

  • load():加载特定SPI服务的静态方法。

  • findFirst():返回可用于该服务提供者的第一个服务。

  • forEach():在此服务加载器实例中为每个服务提供者运行一些代码很有用。

  • stream():返回此服务加载器中服务提供者的流。

  • iterator():返回服务提供者的迭代器。

  • reload():重新加载服务提供商。
    当我们即时更改服务提供商的配置并希望重新加载服务列表时,这很有用。