二Dubbo设计基础--1扩展点加载
二Dubbo设计基础--1扩展点加载
2.1 扩展点加载
2.1.1 java的SPI
SPI是Service Provider Interfaces的简称。根据Java的SPI规范,我们可以定义一个服务接口,具体的实现由对应的实现者去提供,即Service Provider(服务提供者)。然后在使用的时候只要根据SPI的规范去获取对应的服务提供者的服务实现即可。
假设我们有一个日志服务LogService,其只定义了一个info方法用于输出日志信息,我们希望把它作为SPI,然后具体的实现由对应的服务提供者去实现。示例:
step1 定义spi接口,并代码实现
package com.elim.learn.basic.spi.service;
public interface LogService {
public void info(String msg);
}
三个实现类省略;
step2 实现配置
根据SPI的规范我们的服务实现类必须有一个无参构造方法。我们的SPI服务提供者需要将其在classpath下的META-INF/services目录下以服务接口全路径名命名的文件中写对应的实现类的全路径名称,每一行代表一个实现,如果需要注释信息可以使用“#”进行注释,根据官方的要求,这个文件的编码格式必须是UTF-8。我们示例中的LogService的全路径名是com.elim.learn.basic.spi.service.LogService,所以我们需要在类路径下的META-INF/services目录下创建一个名称为com.elim.learn.basic.spi.service.LogService文件。在本示例中我们一共提供了三个实现,所以该文件的内容如下。
#console
com.elim.learn.basic.spi.service.impl.ConsoleLogService
#file
com.elim.learn.basic.spi.service.impl.FileLogService
#db
com.elim.learn.basic.spi.service.impl.DBLogService
step3 使用
使用的时候核心是ServiceLoader类,我们需要通过这个工具类来加载服务提供者,即对应的服务实现,也需要通过它来获得对应的服务实现。ServiceLoader类的核心入口是其提供的三个可以创建ServiceLoader实例的静态方法,分别是load(Class , ClassLoader)、load(Class)和loadInstalled(Class),三者的区别就在于使用的ClassLoader不一样。load(Class)方法将使用当前线程持有的ClassLoader,loadInstalled(Class<?>)方法将使用最顶级的ClassLoader。
public final class ServiceLoader<S> implements Iterable<S> {
public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader){
return new ServiceLoader<>(service, loader);
}
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
public static <S> ServiceLoader<S> loadInstalled(Class<S> service) {
ClassLoader cl = ClassLoader.getSystemClassLoader();
ClassLoader prev = null;
while (cl != null) {
prev = cl;
cl = cl.getParent();
}
return ServiceLoader.load(service, prev);
}
}
ServiceLoader是实现了java.util.Iterator接口的,而且是基于我们所使用的服务的实现,所以可以通过ServiceLoader的实例来遍历其中的服务实现者,从而调用对应的服务提供者。示例如下
@Test
public void test() {
ServiceLoader<LogService> serviceLoader = ServiceLoader.load(LogService.class);
LogService logService = null;
for (Iterator<LogService> iter = serviceLoader.iterator(); iter.hasNext(); ) {
logService = iter.next();
logService.info("Hello SPI");
}
//由于ServiceLoader是实现了java.util.Iterator接口的,也可以使用增强的for循环
for (LogService service : serviceLoader ) {
service.info("Hello SPI");
}
}
注意:
(1)在上述示例中我们的基于SPI规范的服务定义和服务实现都是在一个工程里面的,且都是可以看到源码的,但在实际应用中我们的服务提供者往往是以jar包的形式来提供对应的服务实现的。
(2)ServiceLoader不是一实例化以后立马就去读配置文件中的服务实现者,并且进行对应的实例化工作的。而是会等到需要通过其Iterator实现获取对应的服务提供者时才会加载对应的配置文件进行解析,具体来说是在调用Iterator的hasNext方法时会去加载配置文件进行解析,在调用next方法时会将对应的服务提供者进行实例化并进行缓存。
(3)所有的配置文件只加载一次,服务提供者也只实例化一次,如需要重新加载配置文件可调用ServiceLoader的reload方法。
2.1.2 Dubbo的SPI
2.1.2.1 为何实现spi
1.原始的JDK SPI不支持缓存: Dubbo设计了缓存对象-cachedInstances 是一个 new ConcurrentHashMap<String, Holder