1.SPI基本介绍
SPI,全称即Service Provider Interface,是JDK内置的一种服务发现机制。简单来说,它就是个动态替换实现的机制,根据接口动态加载实现类,目前有不少框架用它来做服务的扩展发现。
比如我们系统里抽稀的模块,通常需要不同的实现方案,例如常见的日志模块、JDBC模块等等。这样情况下我们一般推荐模块基于接口编程,模块之间禁止硬编码,否则就违反了可插拔原则,如果换一种实现就需要改动代码,这样不太友好。为了实现模块之间的解耦,这就需要一种服务发现机制。Java SPI就是提供这样的一个机制:为某个接口寻找服务实现的机制。
Java SPI的具体约定为:当服务的提供者提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件,该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。
2.SPI在ebatis中的应用
在ebatis中,SPI主要应用于请求es的地方,可以看到以下代码在执行前后分别有拦截器的处理,拿到结果后对其提取的处理:
try {
INTERCEPTOR.preRequest(args);
R request = createActionRequest(method, args);
//打印request
INTERCEPTOR.postRequest(new DefaultRequestInfo<>(request, args));
ResponseExtractor<?> extractor = method.getResponseExtractor(args).orElseGet(() -> ResponseExtractorFactory.getResponseExtractor(method));
return getExecutor(method).apply(client, request, extractor);
} finally {
ContextHolder.remove();
}
2.1 Interceptor拦截器的应用
ebatis支持用户接入服务后自定义拦截器,ebatis提供了Interceptor接口,然后由Interceptors来实现,交由拦截器工厂InterceptorFactory延迟加载各拦截器,这样就组成了拦截器链,如下代码拦截器工厂:
static {
INTERCEPTORS = new LazyInitializer<Interceptor>() {
@Override
protected Interceptor initialize() {
ServiceLoader<Interceptor> interceptorServiceLoader = ServiceLoader.load(Interceptor.class);
List<Interceptor> interceptorList = new ArrayList<>();
for (Interceptor interceptor : interceptorServiceLoader) {
interceptorList.add(interceptor);
}
interceptorList.sort(Comparator.comparingInt(Interceptor::getOrder));
return new Interceptors(interceptorList);
}
};
}
可以看到ServiceLoader.load加载后,由Interceptor提供的接口排序来决定拦截器的先后执行顺序。
2.2 ResponseExtractor响应提取器的应用
代码如下:
ServiceLoader<ResponseExtractorProvider> providers = ServiceLoader.load(ResponseExtractorProvider.class);
for (ResponseExtractorProvider provider : providers) {
if (provider.support(method)) {
return provider.getResponseExtractor(method);
}
}
在ebatis-spring模块中META-INF/services/已经实现了各个提取器,通过method找到对应的提取器,我们通过引入spring-ebatis集成包便可直接使用,实现方案不对用户暴露。
3.ServiceLoader源码分析
我们看到ServiceLoader一个位于sun.misc包
,一个位于java.util包
,sun包下的源码看不到。通长我们一般用的是位于java.util包下的,就以ServiceLoader.load为例,通过源码看看它里面到底怎么做的。
3.1 load方法
ClassLoader cl = Thread.currentThread().getContextClassLoader();
可以看到load内部使用的是上下文类加载器,SPI的接口是Java核心库的一部分,是由启动类加载器来加载的;SPI的实现类是由系统类加载器来加载的。启动类加载器是无法找到 SPI 的实现类的,因为它只加载 Java 的核心库。它也不能委派给系统类加载器,因为它是系统类加载器的祖先类加载器。线程上下文类加载器正好解决了这个问题。如果不做任何的设置,Java 应用的线程的上下文类加载器默认就是系统上下文类加载器。在 SPI 接口的代码中使用线程上下文类加载器,就可以成功的加载到 SPI 实现的类。<br />**3.2 LazyIterator**<br /> 它是一个内部类,查找实现类和创建实现类的过程,都在LazyIterator完成。当我们调用iterator.hasNext和iterator.next方法的时候,实际上调用的都是LazyIterator的相应方法。
public Iterator<S> iterator() {
return new Iterator<S>() {
public boolean hasNext() {
return lookupIterator.hasNext();
}
public S next() {
return lookupIterator.next();
}
.......
};
}