Dubbo如何扩展Java的1个小知识点
1.Java SPI 与 Dubbo ExtensionLoader
- 1.SPI全称Service Provider Interface是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。你可以简单的理解为Java提供了一个接口,这个接口可也由A厂商实现,也可以由B厂商实现。也就是Client调用接口时,Java会加载这个接口的所有的实现类。废话不多说上例子
- 定义一个接口和两个类
``` Java
public interface Robot {
void sayHello();
}
```
``` Java 实现Robot
public class Bumblebee implements Robot {
public void sayHello() {
System.out.println("hello ,I am Bumblebee");
}
}
```
``` Java 实现Robot
public class OptimusPrime implements Robot {
public void sayHello() {
System.out.println("hello i am Optimus prime ");
}
}
```
- 接着在resources文件夹下对应创建目录services如图,并在里面吸入两个实现类的全类名
- 程序执行结果
- hello ,I am Bumblebee
- hello i am Optimus prime
- 原理解析
``` Java 原理分析
Iterator iterator = load.iterator();
while (iterator.hasNext()) {
iterator.next().sayHello();
}
```
- 在ServiceLoader内部有一个内部类LazyIterator
- 执行过程
- private static final String PREFIX = "META-INF/services/";
- 获取对应"META-INF/services/+对应接口的类全名
- 如果有则通过pending = parse(service, configs.nextElement());进行解析按Iterator顺序返回对应的实现类。
- 服务实现:一个是本地服务的加载,
- 模式 使用策略模式+配置文件的方法记载对应接口的实现类。
- JavaSPI的实现有什么坏处?
- 1.全量加载服务实现
- 2.没有缓存
- 3.不可以单个Service Provider
Dubbo ExtensionLoader
- 例子:
- 类与接口 与Java Spi相同的实现类
- 配置文件这里的不同点是beanName = 类全名
```Java
ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(Robot.class);
// 获取对应的元素
Robot bumblebee = extensionLoader.getExtension("bumblebee");
bumblebee.sayHello();
Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
optimusPrime.sayHello();
```
- 程序运行结果
- hello ,I am Bumblebee
- hello i am Optimus prime
- 执行原理分析
- 调用过程ExtensionLoader
- ExtensionLoader.getExtensionLoader(Robot.class);
- com.alibaba.dubbo.common.extension.ExtensionLoader#getExtensionLoader()
- com.alibaba.dubbo.common.extension.ExtensionLoader#ExtensionLoader()
- 下面会先初始化ExtensionFactory中SpiFactory
- objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
- 加载完ExtensionFactory后就初始化业务类
- 授信会加载对应的Classes
- com.alibaba.dubbo.common.extension.ExtensionLoader#getExtensionClasses()
- com.alibaba.dubbo.common.extension.ExtensionLoader#loadExtensionClasses()
```Java
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if (defaultAnnotation != null) {
String value = defaultAnnotation.value();
if ((value = value.trim()).length() > 0) {
// 分割@SPI("impl1") 中的value
String[] names = NAME_SEPARATOR.split(value);
if (names.length > 1) {
throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
+ ": " + Arrays.toString(names));
}
// 将value放到cachedDefaultName中
if (names.length == 1) cachedDefaultName = names[0];
}
}
Map> extensionClasses = new HashMap>();
// DUBBO_INTERNAL_DIRECTORY : "META-INF/dubbo/internal/"
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
// DUBBO_DIRECTORY : "META-INF/dubbo/"
loadDirectory(extensionClasses, DUBBO_DIRECTORY);
// 这里会加载对应目录下的文件与JavaSpIy一致
// SERVICES_DIRECTORY : "META-INF/services/"
loadDirectory(extensionClasses, SERVICES_DIRECTORY);
return extensionClasses;
```
- 加载完类文件返回的结果如下
-
接着获取对应的Class类,通过createExtension()获取对应的Interface Provider通过clazz.newInstance()最后返回实例 com.alibaba.dubbo.common.extension.ExtensionLoader#createExtension();
-
高性能的体现ExtensionLoader中有不同的缓存。不论缓存Classes或者是Instance都是用的单例模式,双重校验的方法去做的即先从缓存中拿,拿不到则加载并创建,创建后放回缓存中。
```Java
// 所有标注@SPI注解的 class->ExtensionLoader
private static final ConcurrentMap, ExtensionLoader> EXTENSION_LOADERS = new ConcurrentHashMap, ExtensionLoader>();
// 所有标注@SPI的实例
private static final ConcurrentMap, Object> EXTENSION_INSTANCES = new ConcurrentHashMap, Object>();
// ==============================
// 当前Extension的类 类型
private final Class type;
// ExtensionFactory扩展类创建工厂; SpiExtensionFactory , AdaptiveExtensionFactory , SpringFactory
private final ExtensionFactory objectFactory;
// 当前类缓存的 class->String,拓展类(extension)的名字(name)标识
private final ConcurrentMap, String> cachedNames = new ConcurrentHashMap, String>();
// 缓存的extension实例
private final Holder>> cachedClasses = new Holder>>();
// 可自动激活的实现类,name->Activate,其中Activate包含了这个自动激活的类的各种信息,比如时序 这个不太懂
private final Map cachedActivates = new ConcurrentHashMap();
//
private final ConcurrentMap> cachedInstances = new ConcurrentHashMap>();
// 自适应类@Adaptive类自动加载
private final Holder cachedAdaptiveInstance = new Holder();
// 自适应类@Adaptice字节码文件
private volatile Class cachedAdaptiveClass = null;
// 缓存扩展类名称
private String cachedDefaultName;
```
- 可以通过BeanName去获取不同的Service Provider解决了JavaSpI的问题。
- 加载的时候先先从缓存判断有无再进行加载而不是想JavaSpI中的全量加载。