概述
你是否曾经想过JPA实现(EclipseLink或Hibernate)是如何被JPA获得的。或者java.sql.DriverManager是如何加载java.sql.Driver的可用实现。又或者就此而言,消息传递框架如何获取JMS实现。
其实他们都遵循JAVA SPI(服务提供商接口)机制。该机制是Java 6引入了一个特性,用于服务的发现和加载。
摘自Effective Java第二版 :
一个服务提供框架是一个由服务被多个服务提供者实现的系统,系统使其客户端可以使用这些实现,从而将它们与实现解耦。
组件
- 服务提供接口(Service Provider Interface)
public interface IGetIdService {
long getId();
}
按照SPI标准,我们必须在META-INF/services资源目录下定义一个以接口完全限定名命名的配置文件。
- 服务提供实现(Service Provider Implementation)
public class GetIdServiceImpl1 implements IGetIdService {
@Override
public long getId() {
return 1L;
}
}
public class GetIdServiceImpl2 implements IGetIdService {
@Override
public long getId() {
return 2L;
}
}
同样,服务提供实现也必须在上述配置文件中以实现类的全限定名配置
com.dek.service.impl.GetIdServiceImpl1
com.dek.service.impl.GetIdServiceImpl2
-
ServiceLoader
SPI的核心是java.util.ServiceLoader类。它的作用是发现服务并懒加载。它使用上下文类路径来定位提供者实现,并将它们放在内部缓存中。
测试
- 调用ServiceLoader的静态工厂方法load()获取一个ServiceLoader的实例
ServiceLoader<IGetIdService> loaders = ServiceLoader.load(IGetIdService.class);
- 调用 iterate() 方法获取服务所有可用的实现。
Iterator<IGetIdService> it = loaders.iterator();
- 这个结果会被缓存起来,当有新的实现时,可以调用ServiceLoader.reload() 刷新结果。
Iterator<IGetIdService> = loaders.reload();
完整代码如下:
public class GetIdTest {
@Test
public void spiTest() {
ServiceLoader<IGetIdService> loaders = ServiceLoader.load(IGetIdService.class);
Iterator<IGetIdService> it = loaders.iterator();
while (it.hasNext()) {
System.out.println(it.next().getId());
}
}
}