一、Dubbo SPI加载机制,Dubbo扩展加载分为3类,普通扩展加载、自适应扩展加载,批量扩展加载。
Dubbo SPI和JDK SPI相比较:(dubbo官网描述)
Dubbo 的扩展点加载从 JDK 标准的 SPI (Service Provider Interface) 扩展点发现机制加强而来。
Dubbo 改进了 JDK 标准的 SPI 的以下问题:
- JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。
- 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过
getName()
获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。 - 增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。
二、普通扩展加载:
1、普通扩展简单应用:
修改上一章中接口,在接口Animal上加上@SPI注解。
package com.coke.spi;
import com.alibaba.dubbo.common.extension.SPI;
@SPI("cat")
public interface Animal {
void run();
}
修改配置文件。
运行测试。
package com.coke.spi;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class DubboSpiTest {
public static void main(String[] args) {
Animal animal = ExtensionLoader.getExtensionLoader(Animal.class).getExtension("dog");
animal.run();
}
}
运行结果。
dog run
Process finished with exit code 0
从获取扩展点的方式,我们可以发现,dubbo扩展点是获取特定实例,并不需要实力化所有实现类。
源码分析:
从ExtentionLoader.getExtension(String name)这个函数开始。
1、首先从缓存中获取扩展类实例,如果获取不到才开始创建。
public T getExtension(String name) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
if ("true".equals(name)) {
return getDefaultExtension();
}
//从缓存获取
final Holder<Object> holder = getOrCreateHolder(name);
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
//缓存中没有就创建
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
}
接下来看扩展点的创建。
1、首先获取扩展点的Class类,先从缓存获取,没有获取到就用ClassLoader去加载根目录下META-INF/services/、META-INF/services/、META-INF/services/internal/三个位置的配置文件,通过BufferReader一行一行读取并通过Class.forname加载Class类,将加载好的Class类放入缓存。
2、获取到Class后,先查看缓存中是否有实例,有就直接获取,没有通过反射创建实例,并缓存。
private T createExtension(String name) {
//获取扩展点的Class类
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
//从缓存获取
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
//获取不到直接反射创建
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
//inject注入,类似于spring IOC
injectExtension(instance);
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
for (Class<?> wrapperClass : wrapperClasses) {
//注入到包装类型
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
initExtension(instance);
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
private Map<String, Class<?>> loadExtensionClasses() {
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
//加载策略,分别是三个文件位置,META-INF/services/;META-INF/services/;META-INF/services/
internal/
for (LoadingStrategy strategy : strategies) {
loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.excludedPackages());
//兼容com.alibaba的包
loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.excludedPackages());
}
return extensionClasses;
}
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
boolean extensionLoaderClassLoaderFirst, String... excludedPackages) {
String fileName = dir + type;
try {
Enumeration<java.net.URL> urls = null;
ClassLoader classLoader = findClassLoader();
// try to load from ExtensionLoader's ClassLoader first
if (extensionLoaderClassLoaderFirst) {
ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();
if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) {
urls = extensionLoaderClassLoader.getResources(fileName);
}
}
if(urls == null || !urls.hasMoreElements()) {
if (classLoader != null) {
//直接通过classLoader加载资源
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
}
if (urls != null) {
while (urls.hasMoreElements()) {
java.net.URL resourceURL = urls.nextElement();
loadResource(extensionClasses, classLoader, resourceURL, excludedPackages);
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", description file: " + fileName + ").", t);
}
三、自适应加载。
自适应加载不通于普通加载,自适应加载是在类运行的时候,通过运行中的参数,动态的选择实现类。
1、简单实例
修改Animal接口,下周eat方法,参数为com.alibaba.dubbo.common.URL,在方法上添加@Adaptive注解
package com.coke.spi;
import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.extension.Adaptive;
import com.alibaba.dubbo.common.extension.SPI;
@SPI("cat")
public interface Animal {
void run();
@Adaptive(value = "animal")
void eat(URL url);
}
运行测试,新怎URL并设置animal属性为cat
package com.coke.spi;
import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
import com.alibaba.dubbo.common.utils.UrlUtils;
public class DubboSpiTest {
public static void main(String[] args) {
// Animal animal = ExtensionLoader.getExtensionLoader(Animal.class).getExtension("dog");
//
// animal.run();
Animal animal = ExtensionLoader.getExtensionLoader(Animal.class).getAdaptiveExtension();
URL url = URL.valueOf("url");
url.addParameter("animal", "cat");
animal.eat(url);
}
}
运行结果:属性缓存dog,运行结果为dog eat
cat eat
Process finished with exit code 0
源码分析:
从ExtensionLoader.getAdaptiveExtension开始
1、类似于普通扩展加载,首先从缓存中获取,缓存中没有就创建。
public T getAdaptiveExtension() {
//从缓存中获取
Object instance = this.cachedAdaptiveInstance.get();
if (instance == null) {
if (this.createAdaptiveInstanceError != null) {
throw new IllegalStateException("fail to create adaptive instance: " + this.createAdaptiveInstanceError.toString(), this.createAdaptiveInstanceError);
}
Holder var2 = this.cachedAdaptiveInstance;
synchronized(this.cachedAdaptiveInstance) {
instance = this.cachedAdaptiveInstance.get();
if (instance == null) {
try {
//缓存中没有就创建
instance = this.createAdaptiveExtension();
this.cachedAdaptiveInstance.set(instance);
} catch (Throwable var5) {
this.createAdaptiveInstanceError = var5;
throw new IllegalStateException("fail to create adaptive instance: " + var5.toString(), var5);
}
}
}
}
return instance;
}
2、获取自适应扩展类的Class类,从代码可以看出先通过createAdaptiveExtensionClassCode函数生成代码code,并通过编译器重新编译后获取Class对象,这里其实是用到了动态代理的设计模式,通过createAdaptiveExtensionClassCode组合出代理类的代码String字符串,并重新编译。
private Class<?> createAdaptiveExtensionClass() {
//代码重组
String code = this.createAdaptiveExtensionClassCode();
ClassLoader classLoader = findClassLoader();
//重新编译
Compiler compiler = (Compiler)getExtensionLoader(Compiler.class).getAdaptiveExtension();
return compiler.compile(code, classLoader);
}
可以通过工具获取到重新编译后的类,如在dubbo中有个Cluster类,首先会以@Adaptive注解中的参数作为key去获取Url中对应的属性名,然后通过普通扩展加载机制获取具体的实现类,最终执行交给加载的类来执行。
package org.apache.dubbo.demo.consumer;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Cluster$Adaptive implements org.apache.dubbo.rpc.cluster.Cluster {
public org.apache.dubbo.rpc.Invoker join(org.apache.dubbo.rpc.cluster.Directory arg0) throws org.apache.dubbo.rpc.RpcException {
if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.cluster.Directory argument == null");
if (arg0.getUrl() == null)
throw new IllegalArgumentException("org.apache.dubbo.rpc.cluster.Directory argument getUrl() == null");
org.apache.dubbo.common.URL url = arg0.getUrl();
String extName = url.getParameter("cluster", "failover");
if (extName == null)
throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.cluster.Cluster) name from url (" + url.toString() + ") use keys([cluster])");
//获取扩展实例
org.apache.dubbo.rpc.cluster.Cluster extension = (org.apache.dubbo.rpc.cluster.Cluster) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.cluster.Cluster.class).getExtension(extName);
//最终转由具体类执行
return extension.join(arg0);
}
}
四、批量扩展加载:
批量扩展加载类似于普通扩展加载,不同点是,普通扩展加载每次只加载一个类,而批量加载会根据url中的key去匹配多个扩展点实现类,返回匹配成功的扩展点