SPI - Service Provider Interface
ServiceLoader
ServiceLoader是一种加载类的规范,底层依赖于ClassLoader
- 一个简单的服务提供商加载设施
- 服务 是一个熟知的接口和类(通常为抽象类)集合。服务提供者 是服务的特定实现
- 服务提供者可以以扩展的形式安装在 Java 平台的实现中.也就是将 jar 文件放入任意常用的扩展目录中
- 也可通过将提供者加入应用程序类路径,或者通过其他某些特定于平台的方式使其可用(所以并不限定你的方式,不在类路径也无所谓哟)
服务提供者必须要提供一个无参的构造器
用于类加载实例化
ServiceLoader的使用步骤
- 创建一个接口文件
- 在resources资源目录下创建META-INF/services文件夹
- 在上面services文件夹中创建文件:以接口全类名命名
- 在该文件内,写好实现类的全类名们
示例
// 一个服务
public interface IService {
String sayHello();
String getScheme();
}
// 一个服务提供者
public class HDFSServiceImpl implements IService {
@Override
public String sayHello() {
return "Hello, HDFSServer";
}
@Override
public String getScheme() {
return "HDFS";
}
}
// 另一个服务提供者
public class LocalServiceImpl implements IService {
@Override
public String sayHello() {
return "Hello, LocalServer";
}
@Override
public String getScheme() {
return "Local";
}
}
// 测试类
public class ServiceLoaderDemo {
public static void main(String[] args) {
final ServiceLoader<IService> serviceLoader = ServiceLoader.load(IService.class);
serviceLoader.forEach(service -> System.out.println(service.getScheme() + "=" + service.sayHello()));
}
}
准备META-INF/services/com.daniel.serviceloader.IService
文件,并将对应的实现类写入
该文件中只能写实现类,不能写接口
运行上述代码,输出
HDFS=Hello, HDFSServer
Local=Hello, LocalServer
ServiceLoader实现了Iterable接口,可以遍历获取所有实现类
ServiceLoader是有缓存机制的,其策略为
- ServiceLoader维护到目前为止已经加载的服务提供者缓存
- 每次调用 iterator 方法返回一个迭代器,它首先按照实例化顺序生成缓存的所有元素
- 然后以延迟方式查找和实例化所有剩余的服务提供者,并且依次将每个提供者添加到缓存
- 若清除缓存,可调用
ServiceLoader.reload()
方法
ServletContainerInitializer
在web容器启动时为提供给第三方组件机会做一些初始化的工作,例如注册servlet或者filtes等,servlet规范中通过ServletContainerInitializer实现此功能。
使用方式同上,需要在resources目录下的META-INF/services
中创建一个javax.servlet.ServletContainerInitializer
文件,文件中指定其实现类。
Tomcat中的实现
在Tomcat启动时,会执行ContextConfig
,它实现了LifeCycleListener
,执行ContexConfig#processServletContainerInitializers
方法来加载所有的ServletContainerInitializer
的实现类
Tomcat调用SCI的时机
- 解析Web.xml
- 将
<context-param>
参数注入到ServletContext
实例中 - 回调
ServletContainerInitializer
实现类 - 触发Listener事件(只会触发ServletContextListener类型的时间),
beforeContextInitialized
,afterContextInitialized
- 初始化Filter,调用其init方法
- 加载Servlet
Spring中的实现
SpringFactoriesLoader,是Spring中的SPI
实现,支持key-value
形式的配置文件,如com.daniel.serviceloader.IService=com.daniel.serviceloader.impl.ServiceImpl,com.daniel.serviceloader.impl.ServiceImpl2
该类暴露了两个方法loadFactories
和loadFactoriesNames
,其中loadFactories依赖loadFactoriesNames获取类的全限定名,之后由loadFactories进行实例化
此处特别注意:loadFactories实例化完成所有实例后,会调用AnnotationAwareOrderComparator.sort(result)排序,所以它是支持Ordered接口排序的
SPI能够解决什么问题?
在我们设计一套API供别人调用的时候,如果同一个功能的要求特别多,或者同一个接口要面对很复杂的业务场景的时候,就可以用到SPI