引言

Dubbo虽然已交由apache管理,并且社区活跃度也不如SpringCloud,但也是国内应用比较广泛的RPC框架,其背后的设计思想非常值得我们学习借鉴。鉴于Dubbo官方文档对于基础的使用配置已经讲解的非常清楚了,这里就不再赘述。本系列文章将基于Dubbo2.5.3版本的源码做分析。而Dubbo中最核心的一点就是SPI和自适应扩展,Dubbo的高扩展性以及其它功能都是在这个基础上实现的,理解掌握其原理才能看懂后面的一系列功能的实现原理,对我们平时实现高扩展性也非常有帮助。(PS:Dubbo源码不像Zookeeper那样有明确的入口,可以根据官网的源码分析指导找到。)

正文

一、什么是SPI?

SPI(Service Provider Interface)是一种服务发现机制,它的作用是解耦接口和其具体实现,让各厂商可以自定义自己的扩展实现,并使得程序可以自动使用引入的组件。

什么意思呢?举个例子就清楚了,Java原生就提供SPI机制,比如数据库连接驱动的实现就是SPI很好的一个应用,在Java sql下提供了Driver接口,而具体的驱动程序是由各个数据库厂商实现的,平时我们要连接哪个数据库只需要引入对应的驱动jar包就可以了,非常方便,即使数据库变更也一样,我们不需要改变代码。而Dubbo的SPI机制则是在此基础上提供了更强大的功能,因此,学习了解Java SPI更益于深入了解Dubbo,下面就先来看看Java SPI的使用吧。

1. Java SPI的实现

  • 首先定义一个服务接口和两个自定义实现类(一般实现类会由第三方提供):
public interface SPI {

    void sayHello(String s);

}

public class SPIImpl1 implements SPI {

    @Override
public void sayHello(String s) {
System.out.println("Hello, " + s + "! I'm one");
}
} public class SPIImpl2 implements SPI { @Override
public void sayHello(String s) {
System.out.println("Hello, " + s + "! I'm two");
}
}
  • 然后在resources/META-INF/services创建一个以接口全类名为名称的文件cn.dark.SPI,并在文件中填入自定义实现类的全类名
cn.dark.SPIImpl1
cn.dark.SPIImpl2
  • 最后通过ServiceLoader类发现并调用服务:
ServiceLoader<SPI> load = ServiceLoader.load(SPI.class);
for (SPI spi : load) {
spi.sayHello("SPI");
}

输出:

Hello, SPI! I'm one
Hello, SPI! I'm two

如果需要扩展新的实现,只需要将实现类配置到资源文件中,并引入对应的Jar即可。Java SPI机制就这么简单,其实现原理也很简单,读者们可以自行阅读源码,这里就不再详细分析了,那Dubbo的SPI有何异同呢?

2. Dubbo SPI实现原理

由配置文件得到的猜想

Dubbo SPI是基于Java原生SPI思想重新实现的一套更加强大的SPI机制,类似的你可以在Dubbo的META-INF.dubbo.internal(不止这一个路径,后面在源码中会看到)路径下看到很多以接口全类名命名的配置文件,但是文件内容和JAVA SPI有点不一样,以Protocol扩展为例:

registry=com.alibaba.dubbo.registry.integration.RegistryProtocol
filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper
mock=com.alibaba.dubbo.rpc.support.MockProtocol
injvm=com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol
dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
rmi=com.alibaba.dubbo.rpc.protocol.rmi.RmiProtocol
hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol
com.alibaba.dubbo.rpc.protocol.http.HttpProtocol
com.alibaba.dubbo.rpc.protocol.webservice.WebServiceProtocol
thrift=com.alibaba.dubbo.rpc.protocol.thrift.ThriftProtocol
memcached=memcom.alibaba.dubbo.rpc.protocol.memcached.MemcachedProtocol
redis=com.alibaba.dubbo.rpc.protocol.redis.RedisProtocol

看到这么多扩展类(每一个配置文件中都有很多),我们首先应该思考一个问题:Dubbo一启动,就加载所有的扩展类么?作为一个优秀的RPC框架,肯定不会耗时耗力做这样的无用功,所以肯定会通过一种方式拿到指定的扩展才对。我们可以看到大多是以键值对方式(表示为extName-value)配置的扩展,那么不难猜测,这里的extName就是用来实现上面所说的功能的。

那到底是不是呢?以上纯属猜测,下面就到源码中去验证。

SPI源码

Dubbo中实现SPI的核心类是ExtensionLoader,该类并未提供公共的构造方法来初始化,而是通过getExtensionLoader方法获取一个loader对象:

// loader缓存
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
if (type == null)
throw new IllegalArgumentException("Extension type == null");
if(!type.isInterface()) {
throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
}
if(!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type(" + type +
") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
} ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
} private final ExtensionFactory objectFactory;
private ExtensionLoader(Class<?> type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}

这里的class参数就是扩展点的接口类型,每一个loader都需要绑定一个扩展点类型。然后首先从缓存中获取loader,未获取到就初始化一个loader并放入缓存。而在私有构造器初始化的时候我们需要注意objectFactory这个变量,先大概有个映像,后面会用到。

拿到loader之后,就可以调用getExtension方法去获取指定的扩展点了,该方法传入了一个name参数,不难猜测这个就是配置文件中的键,可以debugger验证一下:

private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();
public T getExtension(String name) {
if (name == null || name.length() == 0)
throw new IllegalArgumentException("Extension name == null");
if ("true".equals(name)) {
return getDefaultExtension();
}
// 从缓存中获取Holder对象,该对象的值就是扩展对象
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
cachedInstances.putIfAbsent(name, new Holder<Object>());
holder = cachedInstances.get(name);
}
// 缓存中没有该扩展,说明还没有加载扩展类,就去配置文件中加载并创建对应的扩展对象
// 这里通过双重校验锁的方式保证线程安全,Dubbo中大量运用了该技巧
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
}

同样的也是先从缓存拿,缓存没有就创建并添加到缓存,因此主要看createExtension方法:

// 扩展类实例缓存对象
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();
private T createExtension(String name) {
// 从配置文件中加载扩展类并获取指定的扩展类,没有就抛出异常
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, (T) clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
// 依赖注入,这里及后面都和当前流程无关,可以先略过,有个印象就好
injectExtension(instance);
// 获取包装类并实例化,最后注入依赖对象
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && wrapperClasses.size() > 0) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
type + ") could not be instantiated: " + t.getMessage(), t);
}
}

关键点代码就在getExtensionClasses方法中,怎么从配置文件中加载扩展类的。而该方法主要是调用了loadExtensionClasses方法:

private Map<String, Class<?>> loadExtensionClasses() {
// 判断接口上是否标注有 @SPI注解,该注解的值就是默认使用的扩展类,
// 赋值给cachedDefaultName变量缓存起来
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if(defaultAnnotation != null) {
String value = defaultAnnotation.value();
if(value != null && (value = value.trim()).length() > 0) {
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));
}
if(names.length == 1) cachedDefaultName = names[0];
}
} // 真正读取配置文件的方法
Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
loadFile(extensionClasses, DUBBO_DIRECTORY);
loadFile(extensionClasses, SERVICES_DIRECTORY);
return extensionClasses;
}

该方法主要是缓存当前扩展接口指定的默认扩展实现类(@SPI注解指定),并调用loadFile读取配置文件,从这里我们可以看到Dubbo默认是读取以下3个文件夹中的配置文件:

 private static final String SERVICES_DIRECTORY = "META-INF/services/";
private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";

然后是loadFile,该方法很长,不全部放上来了,这里提取关键的代码:

String fileName = dir + type.getName();

首先通过文件全路径找到对应的文件,并用BufferedReader一行行读取文件内容:

String name = null;
int i = line.indexOf('=');
if (i > 0) {
// 配置文件中的键
name = line.substring(0, i).trim();
// 扩展点全类名
line = line.substring(i + 1).trim();
}
if (line.length() > 0) {
// 加载class,如果有些类的依赖jar包未导入,这里就会抛出异常(比如WebserviceProtocol)
Class<?> clazz = Class.forName(line, true, classLoader);
// 验证当前类型是否是扩展类的父类型
if (! type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error when load extension class(interface: " +
type + ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + "is not subtype of interface.");
}
// 扩展类是否标注了 @Adaptive 注解,表示为一个自定义的自适应扩展类
// 如果是将其缓存到cachedAdaptiveClass
if (clazz.isAnnotationPresent(Adaptive.class)) {
if(cachedAdaptiveClass == null) {
cachedAdaptiveClass = clazz;
} else if (! cachedAdaptiveClass.equals(clazz)) {
// 超过一个自定义的自适应扩展类就抛出异常
throw new IllegalStateException("More than 1 adaptive class found: "
+ cachedAdaptiveClass.getClass().getName()
+ ", " + clazz.getClass().getName());
}
} else {
try {
// 进入到该分支表示为Wrapper装饰扩展类,该类都有一个特征:包含
// 一个有参的构造器,如果没有,就抛出异常进入到另一个分支,
// Wrapper类的作用我们后面再分析
clazz.getConstructor(type);
// 缓存Wrapper到cachedWrapperClasses中
Set<Class<?>> wrappers = cachedWrapperClasses;
if (wrappers == null) {
cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
wrappers = cachedWrapperClasses;
}
wrappers.add(clazz);
} catch (NoSuchMethodException e) {
// 进入此分支表示为一个普通的扩展类
clazz.getConstructor();
if (name == null || name.length() == 0) {
// 由于历史原因,Dubbo最开始配置文件中并不是以K-V来配置的
// 扩展点,而是会通过@Extension注解指定,所以这里会通过
// 该注解去获取到name
name = findAnnotationName(clazz);
// 由于@Extension废弃使用,但配置文件中仍存在非K-V的配置,
// 所以这里是直接通过类名获取简单的name
if (name == null || name.length() == 0) {
if (clazz.getSimpleName().length() > type.getSimpleName().length()
&& clazz.getSimpleName().endsWith(type.getSimpleName())) {
name = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() - type.getSimpleName().length()).toLowerCase();
} else {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + url);
}
}
}
String[] names = NAME_SEPARATOR.split(name);
if (names != null && names.length > 0) {
// 判断当前扩展点是否标注有@Activate注解,该注解表示
// 该扩展点自动激活
Activate activate = clazz.getAnnotation(Activate.class);
if (activate != null) {
cachedActivates.put(names[0], activate);
}
for (String n : names) {
if (! cachedNames.containsKey(clazz)) {
cachedNames.put(clazz, n);
}
Class<?> c = extensionClasses.get(n);
if (c == null) {
extensionClasses.put(n, clazz);
} else if (c != clazz) {
throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
}
}
}
}
}
}

至此,我们就看到了Dubbo SPI的实现全过程,我们也了解了Dubbo强大的扩展性是如何实现的,但是这么多扩展,Dubbo在运行中是如何决定调用哪一个扩展点的方法呢?这就是Dubbo另一强大的机制:自适应扩展。(PS:这里需要留意cachedAdaptiveClasscachedWrapperClasses两个变量的赋值,后面会用到。)

二、自适应扩展机制

什么是自适应扩展?上文刚刚也说了,Dubbo中存在很多的扩展类,这些扩展类不可能一开始就全部初始化,那样非常的耗费资源,所以我们应该在使用到该类的时候再进行初始化,也就是懒加载。但是这是比较矛盾的,拓展未被加载,那么拓展方法就无法被调用(静态方法除外)。拓展方法未被调用,拓展就无法被加载(官网原话)。所以也就有了自适应扩展机制,那么这个原理是怎样的呢?

首先需要了解@Adaptive注解,该注解可以标注在类和方法上:

  • 标注在类上,表明该类为自定义的适配类
  • 标注在方法上,表明需要动态的为该方法创建适配类

当有地方调用扩展类的方法时,首先会调用适配类的方法,然后适配类再根据扩展名称调用getExtension方法拿到对应的扩展类对象,最后调用该对象的方法即可。流程就这么简单,下面看看代码怎么实现的。

首先我们回到ExtensionLoader的构造方法中:

objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());

其中调用了getAdaptiveExtension方法,从方法名不难看出就是去获取一个适配类对象:

private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();
public T getAdaptiveExtension() {
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
if(createAdaptiveInstanceError == null) {
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {
instance = createAdaptiveExtension();
cachedAdaptiveInstance.set(instance);
} catch (Throwable t) {
createAdaptiveInstanceError = t;
throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t);
}
}
}
}
else {
throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
}
} return (T) instance;
}

该方法很简单,就是从缓存中获取适配类对象,未获取到就调用createAdaptiveExtension方法加载适配类并通过反射创建对象:

private T createAdaptiveExtension() {
try {
// 这里又注入了些东西,先略过
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e);
}
}

调用getAdaptiveExtensionClass加载适配类:

private Class<?> getAdaptiveExtensionClass() {
// 这里刚刚分析过了,从配置文件中加载配置类
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

cachedAdaptiveClass这个变量应该还没忘,在loadFile里赋值的,即我们自定义的适配扩展类,若没有则调用createAdaptiveExtensionClass动态创建:

private Class<?> createAdaptiveExtensionClass() {
// 生成适配类的Java代码,主要实现标注了@Adaptive的方法逻辑
String code = createAdaptiveExtensionClassCode();
ClassLoader classLoader = findClassLoader();
// 调用compiler编译
com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
return compiler.compile(code, classLoader);
}

该方法就是生成适配类的字节码,你一定好奇适配类的代码是怎样的,只需要打断点就可以看到了,这里我们以Protocol类的适配类为例:

import com.alibaba.dubbo.common.extension.ExtensionLoader;

public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol {
public void destroy() {
throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
} public int getDefaultPort() {
throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
} public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) {
if (arg1 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg1;
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
if (extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1);
} public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) {
if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
if (arg0.getUrl() == null)
throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
com.alibaba.dubbo.common.URL url = arg0.getUrl();
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
if (extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.export(arg0);
}
}

后面会讲到Protocol扩展类都是通过export方法暴露服务,refer方法引用服务,而这两个方法在接口中都标注了@Adaptive注解,所以Dubbo为其生成了动态的适配类(这个和Java的动态代理的原理有点像,感兴趣的可以阅读我之前的文章《设计之禅——深入剖析代理模式》)。同时我们看到这两个方法中都通过getExtension方法去获取指定的扩展类的实例(这个扩展类名称来自于Invoker(后面会讲)中的url,因为dubbo是基于url驱动的,所有的配置都在url中)。

这就是Dubbo强大的自适应扩展机制的实现原理,我们可以将其运用到我们的项目中去,这就是看源码的好处。不过还有个问题,刚刚在createAdaptiveExtensionClass方法中你一定疑惑compiler是什么,它也是调用的getAdaptiveExtension获取适配类,这不就进入了死循环么?

当然不会,首先我们可以去配置文件看看Compiler的扩展类都有哪些:

adaptive=com.alibaba.dubbo.common.compiler.support.AdaptiveCompiler
jdk=com.alibaba.dubbo.common.compiler.support.JdkCompiler
javassist=com.alibaba.dubbo.common.compiler.support.JavassistCompiler

有一个AdaptiveCompiler类,从名字上我们就能猜到它是一个自定义的适配类了,然后在其类上可以看到@Adaptive注解验证我们的猜想,那么上文也说了在loadFile方法中会将该类赋值给cachedAdaptiveClass变量缓存,然后在createAdaptiveExtension -> getAdaptiveExtensionClass方法中获取并实例化对象,所以并不会死循环,那么在该类中做了什么呢?

public class AdaptiveCompiler implements Compiler {

	// 这个是在哪赋值的?
private static volatile String DEFAULT_COMPILER; public static void setDefaultCompiler(String compiler) {
DEFAULT_COMPILER = compiler;
} public Class<?> compile(String code, ClassLoader classLoader) {
Compiler compiler;
ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class);
String name = DEFAULT_COMPILER; // copy reference
if (name != null && name.length() > 0) {
// 根据 DEFAULT_COMPILER 名称获取
compiler = loader.getExtension(name);
} else {
// 获取@SPI注解值指定的默认扩展
compiler = loader.getDefaultExtension();
}
return compiler.compile(code, classLoader);
} }

该适配类会从两个地方获取扩展类,先来看看getDefaultExtension

public T getDefaultExtension() {
getExtensionClasses();
if(null == cachedDefaultName || cachedDefaultName.length() == 0
|| "true".equals(cachedDefaultName)) {
return null;
}
return getExtension(cachedDefaultName);
}

cachedDefaultName 这个不陌生吧,在loadExtensionClasses方法中赋值的,其值为@SPI的值,这里就是javassist。再看另外一条分支,是通过DEFAULT_COMPILER的值去获取的,这个变量提供了一个setter方法,点过去我们可以看到是在ApplicationConfig类中的setCompiler方法调用的,因为该类是配置类实例,也就是说可以通过dubbo:application的compiler参数来配置编译器类型,查看文档,也确实有这个配置参数。所以看源码能让我们了解平时项目中配置各个参数的意义,从而有针对的选择和配置适当的参数,而不是一味的照搬文档就完事。

三、Dubbo IOC

在上文中我们看到injectExtension这样一个方法,它是做什么的呢?接下来就详细分析它的作用和实现。

private T injectExtension(T instance) {
try {
if (objectFactory != null) {
for (Method method : instance.getClass().getMethods()) {
if (method.getName().startsWith("set")
&& method.getParameterTypes().length == 1
&& Modifier.isPublic(method.getModifiers())) {
Class<?> pt = method.getParameterTypes()[0];
try {
String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("fail to inject via method " + method.getName()
+ " of interface " + type.getName() + ": " + e.getMessage(), e);
}
}
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}

这个方法就是Dubbo依赖注入的实现,从上面代码中我们可以看出该方法是通过setter方法注入依赖扩展的(因为有些扩展点是需要依赖其它扩展点的,所以单单初始化当前扩展点还不行,还需要注入依赖的扩展):首先通过反射拿到参数的类型,然后从setter方法名中获取到扩展点的名称,最后从objectFactory中获取依赖的扩展实例并通过反射注入。objectFactory这个参数还记得是什么,怎么初始化赋值的么?这里具体的实例对象是(不清楚怎么来的忘记了就往上翻翻)AdaptiveExtensionFactory适配类类的对象,首先看看该类的初始化:

private final List<ExtensionFactory> factories;
public AdaptiveExtensionFactory() {
ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
for (String name : loader.getSupportedExtensions()) {
list.add(loader.getExtension(name));
}
factories = Collections.unmodifiableList(list);
}

这里不难理解,初始化时将所有的ExtensionFactory的扩展对象缓存到factories对象,然后在getExtension中循环,别分别调用它们的getExtension方法:

public <T> T getExtension(Class<T> type, String name) {
// SpiExtensionFactory和SpringExtensionFactory
for (ExtensionFactory factory : factories) {
T extension = factory.getExtension(type, name);
if (extension != null) {
return extension;
}
}
return null;
}

ExtensionFactory的扩展配置文件中只有三个类:

adaptive=com.alibaba.dubbo.common.extension.factory.AdaptiveExtensionFactory
spi=com.alibaba.dubbo.common.extension.factory.SpiExtensionFactory
spring=com.alibaba.dubbo.config.spring.extension.SpringExtensionFactory

除开上面的适配类,下面分别看看spi和spring做了哪些事:

public class SpiExtensionFactory implements ExtensionFactory {

    public <T> T getExtension(Class<T> type, String name) {
// 判断是否为@SPI标注的扩展接口
if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
if (loader.getSupportedExtensions().size() > 0) {
return loader.getAdaptiveExtension();
}
}
return null;
} }

该类主要是获取标了@SPI的扩展接口的适配类,其中getSupportedExtensions就是加载所有的扩展类。想一想ExtensionFactory本身就是被@SPI标注的,会在这里再次返回适配类么?

再来看SpringExtensionFactory类:

public class SpringExtensionFactory implements ExtensionFactory {

    private static final Set<ApplicationContext> contexts = new ConcurrentHashSet<ApplicationContext>();

    public static void addApplicationContext(ApplicationContext context) {
contexts.add(context);
} public static void removeApplicationContext(ApplicationContext context) {
contexts.remove(context);
} @SuppressWarnings("unchecked")
public <T> T getExtension(Class<T> type, String name) {
for (ApplicationContext context : contexts) {
if (context.containsBean(name)) {
Object bean = context.getBean(name);
if (type.isInstance(bean)) {
return (T) bean;
}
}
}
return null;
} }

这个类也很简单,就是从Spring IOC容器中返回对应的扩展对象。

以上就是Dubbo IOC的实现原理,非常简单,但也很重要,我们通过idea快捷键可以看到只有以下两处调用:

一个是createExtension创建扩展类实例时:

injectExtension(instance);
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && wrapperClasses.size() > 0) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}

另一个是createAdaptiveExtension创建适配类实例的时候:

private T createAdaptiveExtension() {
try {
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e);
}
}

记住这两个地方,后面再深入服务注册调用时,时常会联系到这里。

总结

今天这部分源码我们可以从中看到Dubbo是如何是实现对扩展开放,对修改关闭以及如何优雅地使用设计模式的,今后在实际的Dubbo的使用中,也可以轻易的进行自定义扩展开发。最后我们可以想一想,之前的项目是否可以运用今天的所学进行重构呢?

Dubbo——SPI及自适应扩展原理的更多相关文章

  1. Dubbo 扩展点加载机制:从 Java SPI 到 Dubbo SPI

    SPI 全称为 Service Provider Interface,是一种服务发现机制.当程序运行调用接口时,会根据配置文件或默认规则信息加载对应的实现类.所以在程序中并没有直接指定使用接口的哪个实 ...

  2. Dubbo系列讲解之扩展点实现原理分析【2万字分享】

    Apache Dubbo 是一款微服务开发框架,它提供了 RPC通信 与 微服务治理 两大关键能力.这意味着,使用 Dubbo 开发的微服务,将具备相互之间的远程发现与通信能力, 同时利用 Dubbo ...

  3. Dubbo 源码分析 - 自适应拓展原理

    1.原理 我在上一篇文章中分析了 Dubbo 的 SPI 机制,Dubbo SPI 是 Dubbo 框架的核心.Dubbo 中的很多拓展都是通过 SPI 机制进行加载的,比如 Protocol.Clu ...

  4. Dubbo源码学习之-Adaptive自适应扩展

    前言 最近三周基本处于9-10-6与9-10-7之间,忙碌的节奏机会丢失了自己.除了之前干施工的那段经历,只看参加软件开发以来,前段时间是最繁忙的了.忙的原因,不是要完成的工作量大,而是各种环境问题, ...

  5. dubbo源码分析之基于SPI的强大扩展

    https://blog.csdn.net/luoyang_java/article/details/86609045 Dubbo采用微内核+插件体系,使得设计优雅,扩展性强.那所谓的微内核+插件体系 ...

  6. dubbo源码阅读之自适应扩展

    自适应扩展机制 刚开始看代码,其实并不能很好地理解dubbo的自适应扩展机制的作用,我们不妨先把代码的主要逻辑过一遍,梳理一下,在了解了代码细节之后,回过头再来思考自适应扩展的作用,dubbo为什么要 ...

  7. Dubbo SPI机制之三Adaptive自适应功能

    JDK标准中SPI机制的一个问题就是其一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源:扩展点加载失败,其他扩展点都用不了了.Dubbo是如何解决该问题动态的选 ...

  8. 理解 Dubbo SPI 扩展机制

    写在前面 最近接触了 gRPC 体会到虽然众多 RPC 框架各有各的特点但是他们提供的特性和功能有很多的相似之处 , 这就说明他们面对同样的分布式系统带来的问题.从 2016 年左右开始接触到 dub ...

  9. [转] 理解 Dubbo SPI 扩展机制

    写在前面 最近接触了 gRPC 体会到虽然众多 RPC 框架各有各的特点但是他们提供的特性和功能有很多的相似之处 , 这就说明他们面对同样的分布式系统带来的问题.从 2016 年左右开始接触到 dub ...

随机推荐

  1. DQN(Deep Q-learning)入门教程(二)之最优选择

    在上一篇博客:DQN(Deep Q-learning)入门教程(一)之强化学习介绍中有三个很重要的函数: 策略:\(\pi(a|s) = P(A_t=a | S_t=s)\) 状态价值函数:\(v_\ ...

  2. 剑指Offer之替换空格

    题目描述:请实现一个函数,将一个字符串中的每个空格替换成“%20”.例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy. 函数实现: package s ...

  3. JavaScript 简版-菜鸟中的菜鸟

    JavaScript 简介 JavaScript 是互联网上最流行的脚本语言,这门语言可用于 HTML 和 web,更可广泛用于服务器.PC.笔记本电脑.平板电脑和智能手机等设备. JavaScrip ...

  4. 关于Backus-Naur Form巴克斯诺尔范式和扩展巴克斯范式的知识点和相关词语中英文对照

    巴克斯诺尔范式的相关词语中英文对照和知识点 syntax 语法 强调的是编程语言的组形式,例如一个句子中会包含表达式.陈述还有各种单元等等 semantics 语义 强调的是这个编程语言的实际含义,例 ...

  5. 低功耗SRAM主要三部分功耗来源

    随着SOC 技术的迅猛发展,由电池供电的便携式电子产品得到了广泛应用,如智能手机.运动手环.ipad.部分汽车电子等.近年来半导体工艺已进入深亚微米甚至纳米阶段,工艺尺寸不断缩小,但是由于电池技术的缓 ...

  6. Java Word中的文本、图片替换功能

    Word中的替换功能以查找指定文本然后替换为新的文本,可单个替换或全部替换.以下将要介绍的内容,除常见的以文本替换文本外,还将介绍使用不同对象进行替换的方法,具体可包括: 1. 指定字符串内容替换文本 ...

  7. Chisel3-Intellij IDEA中使用sbt构建Chisel项目

    https://mp.weixin.qq.com/s/gssjiiPW6zUzKwCFZdNduw   1. 使用Intellij IDEA创建Scala项目   Chisel项目,就是构建Scala ...

  8. Java实现荷兰国旗问题

    问题描述 现有n个红白蓝三种不同颜色的小球,乱序排列在一起,请通过两两交换任意两个球,使得从左至右的球依次为红球.白球.蓝球.这个问题之所以叫荷兰国旗,是因为将红白蓝三色的小球弄成条状物,并有序排列后 ...

  9. Java实现哥德巴赫猜想

    验证哥德巴赫猜想:任何一个大于 6 的偶数,都能分解成两个质数的和.要求输入一个整数,输出这个 数能被分解成哪两个质数的和. eg : 14 14=3+11 14=7+7 public class T ...

  10. Linux 系统资源查看

    vmstat监控系统资源 vm [刷新延时 刷新次数],vmstat 1 3 dmesg查看开机时内核检测信息 dmesg | grep CPU free命令查看内存使用状态 查看cpu信息:cat ...