一、概述

dubbo SPI 在dubbo的作用是基础性的,要想分析研究dubbo的实现原理、dubbo源码,都绕不过 dubbo SPI,掌握dubbo SPI 是征服dubbo的必经之路。

本篇文章会详细介绍dubbo SPI相关的内容,通过源码分析,目标是让读者能通过本篇文章,彻底征服dubbo SPI。

文章的组织方式是先介绍SPI 的概念,通过Java SPI 让大家了解SPI 是什么,怎么用,有一个初步的概念,dubbo的SPI是扩展了Java SPI的内容,自己实现了一个SPI。

二、SPI概念介绍

SPI全称 Service Provider Interface,是一种服务发现机制。我们编程实现一个功能时,经常先抽象一个interface,内部再定一些方法。具体的实现交给 implments了此接口的类,实现了功能和实现类的分离。

我们设想一下,如果一个描述汽车功能的interface  Car,存在多个实现类BMW、Benz、Volvo,某个场景调用Car的行驶方法,程序就需要确认到底是需要BMW、Benz、Volvo中的那个类对象。如果硬编码到程序中,固然可以,但是出现了接口和实现类的耦合,缺点也显而易见。

有办法做到在调用代码中不涉及BMW、Benz、Volvo字符,也随意的指定实现类么?当然,SPI就是解决这个问题。

SPI的实现方式是,在指定的路径下增加一个文本文件,文件的名称是interface的全限定名(包名+接口名),文件内容每行是一个此接口的实现类的全限定名,多个实现类就会有多行。接口进行调用时,根据接口全限定名,先读取文本文件,解析出具体的实现类,通过反射进行实例化,再调用之。如果增加新的实现类,不需要修改调用代码,只需要在文本文件中增加一行实现类的全限定名即可,删除实现类同理。

三、Java SPI 介绍

我们先看看Java的SPI怎么实现的,通过一个demo,进行了解。

先定一个小车的接口,有一个方法 goBeijing():

 package cn.hui.spi
//Car 接口
public interface Car { // 开车去北京
void goBeijing(); }

我们建三个实现类:

 package cn.hui.spi.impl;
public class Bmw implements Car{
@Override
public void goBeijing() {
// TODO Auto-generated method stub
System.out.println("开着宝马去北京......");
}
} package cn.hui.spi.impl;
public class Benz implements Car{
@Override
public void goBeijing() {
// TODO Auto-generated method stub
System.out.println("开着奔驰去北京........");
}
} package cn.hui.spi.impl;
public class Volvo implements Car {
@Override
public void goBeijing() {
// TODO Auto-generated method stub
System.out.println("开着沃尔沃去北京......");
}
}

我们在 "META-INF/services" 文件夹下新建一个文件,名称为“cn.hui.spi.Car",文件内容:

 cn.hui.spi.impl.Bmw
cn.hui.spi.impl.Benz
cn.hui.spi.impl.Volvo

方法调用的代码如下:

 import java.util.Iterator;
import java.util.ServiceLoader;
public class App { public static void main(String[] args) {
ServiceLoader<Car> serviceLoader = ServiceLoader.load(Car.class);
Iterator<Car> iterator = serviceLoader.iterator();
while(iterator.hasNext()) {
Car car = iterator.next();
car.goBeijing();
}
}
}

打印结果:

 开着宝马去北京......
开着奔驰去北京........
开着沃尔沃去北京......

这个就是Java SPI简单实现方式。

三、Dubbo SPI介绍

dubbo 在Java SPI的基础上进行了功能扩展,我们再看上面的Java SPI示例,可以发现很明显的问题,对文本文件的加载后,实例化对象是一次性全部进行实例化,得到一个实现类的对象集合,调用的时候循环执行。不能唯一指定一个实现类进行唯一调用。dubbo通过在文本文件中指定每个实现类的key,唯一标识出每个实现类,调用的时候可以指定唯一的一个实现类。同样实例化也不需要一次性全部实例化了,只需要实例化需要调用的类即可。

同时dubbo还实现了IOC和AOP的功能,接口的实现类之间可以进行相互的注入,了解Spring的同学,应该很清楚IOC和AOP的逻辑,下面我们现在熟悉下dubbo SPI的相关概念,之后在通过一个简单的样例,了解dubbo SPI 的使用。

四、Dubbo SPI关键点

dubbo SPI的功能主要是通过ExtensionLoader类实现,dubbo启动时,默认扫描三个目录:META-INF/services/、META-INF/dubbo/、META-INF/internal/,在这三个目录下的文本文件都会加载解析,文本文件的内容:key=实现类的全限定名。

dubbo把接口定义为 扩展点,实现类定义为 扩展点实现,所有的扩展点(接口)需要进行@SPI注解,更多的功能和注解我们逐步介绍。

dubbo在启动的时候扫描文本文件,对文件内容进行解析,但是不会全部进行实例化,只有在调用到具体的扩展点实现时,才会进行特定扩展点的实例化。

同时dubbo SPI提供自适应扩展、默认扩展、自动激活扩展等功能,我们后面介绍。

五、Dubbo SPI示例

我们把上面Car接口的例子,改造成基于dubbo SPI的实现。进行配置的文本文件内容。

在扩展点实现类前都加上key,改为:

 bmw=cn.hui.spi.impl.Bmw
benz=cn.hui.spi.impl.Benz
volvo=cn.hui.spi.impl.Volvo

Car接口改造为:

 @SPI
public interface Car {
// 开车去北京
void goBeijing();
}

扩展点,暂时不做修改,我们看看调用方法:

 public class App {
public static void main(String[] args) {
Car car = ExtensionLoader.getExtensionLoader(Car.class).getExtension("bmw");
car.goBeijing();
car = ExtensionLoader.getExtensionLoader(Car.class).getExtension("benz");
car.goBeijing();
car = ExtensionLoader.getExtensionLoader(Car.class).getExtension("volvo");
car.goBeijing();
}
}

此时,控制台会出现:

 开着宝马去北京......
开着奔驰去北京........
开着沃尔沃去北京......

这个就是简单dubbo使用,复杂的功能我们放到源码分析的时候进行。

六、Dubbo SPI 源码分析

dubbo SPI的功能主要几种在ExtensionLoader类中实现,分析源码也就主要分析此类,我们通过ExtensionLoader对外提供的方法作为入口进行源码分析。

需要注意:一个type接口对应一个ExtensionLoader 实例。

上面的示例中,我们通过 getExtensionLoader(..)方法,获得ExtensionLoader实例,ExtensionLoader类的构造方法是私有的,只能通过此方法获取实例。

我们先看看此方法:

 @SuppressWarnings("unchecked")
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!");
}
// 必须被@SPI注解
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type(" + type + ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
}
// extension_loaders 为成员变量,是 type---> ExtensionLoader 实例的缓存
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
// putIfAbsent put不覆盖
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}

我们看到该方法主要是先对type进行校验,再根据type为key,从缓存EXTENSION_LOADERS中获取ExtensionLoader实例,如果缓存没有,则新建一个ExtensionLoader实例,并放入缓存。
注意,我们说过一个type对应一个ExtensionLoader实例,为什么还需要缓存呢,我们再看看 EXTENSION_LOADERS的定义:

// 扩展点接口和对应ExtensionLoader实例的缓存
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();

没错,EXTENSION_LOADERS 是一个static、final修饰的类静态变量。

我们接着看上面,看一下ExtensionLoader的构造方法:

 private ExtensionLoader(Class<?> type) {
this.type = type;
// type 为ExtensionFactory时,objectFactory为空
if (type == ExtensionFactory.class) {
objectFactory = null;
} else {
// type为普通接口时,objectFactory为AdaptiveExtensionFactory,负责dubbo spi 的IOC 功能
objectFactory = ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension();
}
// objectFactory = (type == ExtensionFactory.class ? null
// : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}

构造方法私有,不能直接在外部new出实例。

方法内部,参数type赋值给成员变量type,还会进行ExtensionFactory类判断,ExtensionFactory是实现IOC功能的,我们此处暂时绕过,后面进行介绍。
我们总结一下getExtensionLoader(..)方法,绕开ExtensionFactory,就是new 了一个ExtensionLoader对象实例,为成员变量type赋值为扩展点type,对象实例放入EXTENSION_LOADERS 缓存中。
现在我们有了ExtensionLoader实例对象,我们再看看获取type实例的方法:getExtension(..):

 @SuppressWarnings("unchecked")
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);
}
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
// 创建扩展实例,并设置到holder中
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
}

方法的入参name为提供配置的文本文件中的key,还记得我们的文本文件中的内容吧,其中一行:bmw=cn.hui.spi.impl.Bmw,此处的name 就是 bmw。 如果name为true,返回getDefaultExtension(),这个方法我们暂时绕过。

我们看到13行,根据name从cachedInstances中获取Holder对象,很明显 cachedInstances就是一个存放对象的缓存,缓存中没有new一个新的实例,至于Holder,我们看下这个类:

 // 持有目标对象
public class Holder<T> { private volatile T value; public void set(T value) {
this.value = value;
} public T get() {
return value;
} }

只是存放对象,没有任何逻辑。

我们接着看到ExtensionLoader类的代码,在拿到holder实例后,我们要从hodler中获取扩展点的实例:

 Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
// 创建扩展实例,并设置到holder中
instance = createExtension(name);
holder.set(instance);
}
}
}

如果holder中没有扩展点的实例,通过双检锁,通过调用 createExtension方法 返回扩展点实例。并放入holder对象中。

到此,我们发现new扩展点实例进到 createExtension方法中。

我们接着分析此方法:

 // 创建扩展对象实例
@SuppressWarnings("unchecked")
private T createExtension(String name) {
// 从配置文件中加载所有的扩展类,形成配置项名称到配置类Clazz的映射关系
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);
}
// 向实例中注入依赖,IOC实现
injectExtension(instance);
// 包装处理
// cachedWrapperClasses 加载@SPI配置时赋值,此处进行实例化
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
// 循环创建wrapper实例
for (Class<?> wrapperClass : wrapperClasses) {
// 将当前instance作为参数创建wrapper实例,然后向wrapper实例中注入属性值,
// 并将wrapper实例赋值给instance
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);
}
}

我们看到方法开始就通过 Class<?> clazz = getExtensionClasses().get(name); 获取Class对象,可以直观的看出通过name获得的这个clazz是在配置的文本文件中name对应的扩展点实现类的Class对象,关于getExtensionClasses方法,我们稍后分析,接着往下看:

 T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
// 通过反射创建实例
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}

通过clazz对象,从EXTENSION_INSTANCES获取缓存的实例,如果获取不到,通过反射clazz.newInstance() new一个新的实例对象,并放入EXTENSION_INSTANCES中。

我们可以看到,扩展点的实现类 必须要有一个默认无参的构造函数。

接着往下看:

  // 向实例中注入依赖,IOC实现
injectExtension(instance);

此方法是实现IOC功能,我们暂且绕过。

接下来,我们看到:

  // 包装处理
// cachedWrapperClasses 加载@SPI配置时赋值,此处进行实例化
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
// 循环创建wrapper实例
for (Class<?> wrapperClass : wrapperClasses) {
// 将当前instance作为参数创建wrapper实例,然后向wrapper实例中注入属性值,
// 并将wrapper实例赋值给instance
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}

此处是处理包装类的,我们也暂且绕过。下面就是直接返回扩展点的instance实例了

 return instance;

现在我们还有一个方法没有分析,就是加载扩展点实现类的Class对象的方法getExtensionClasses()。我们现在来看这个方法:

 private Map<String, Class<?>> getExtensionClasses() {
Map<String, Class<?>> classes = cachedClasses.get();
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}

我们看到,这个方法返回的是一个Map对象,可以确认的是,这个Map存放的是扩展点的所有实现类的Class,Map的key就是配置的文本文件的name。如果缓存cachedClasses 中存在,即返回,如果没有,通过loadExtensionClasses()加载,并设置到cachedClasses中。

我们接着看loadExtensionClasses方法:

 private Map<String, Class<?>> loadExtensionClasses() {
// 获取注解 SPI的接口
// type为传入的扩展接口,必须有@SPI注解
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
// 获取默认扩展实现value,如果存在,赋值给cachedDefaultName
if (defaultAnnotation != null) {
String value = defaultAnnotation.value();
if ((value = value.trim()).length() > 0) {
// @SPI value 只能是一个,不能为逗号分割的多个
// @SPI 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));
}
if (names.length == 1)
cachedDefaultName = names[0];
}
}
// 加载三个目录配置的扩展类
Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
// META-INF/dubbo/internal
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
// META-INF/dubbo
loadDirectory(extensionClasses, DUBBO_DIRECTORY);
// META-INF/services/
loadDirectory(extensionClasses, SERVICES_DIRECTORY);
return extensionClasses;
}

我们看到方法内部的逻辑,首先判断扩展点接口type是否用@SPI注解,在前面的方法中,已经判断,如果没有@SPI注解,抛出异常,此处type必定存在@SPI注解。

根据注解获取到defaultAnnotation 对象,目的是拿到@SPI中的value,且value值不能用逗号分隔,只能有一个,赋值给cachedDefaultName。

接着定一个了Map对象extensionClasses,作为方法的返回值,我们知道,这个方法的返回值最后设置到了缓存cachedClasses中。我们看看这个extensionClasses是怎么赋值的。这个对象主要是”经历“了三个方法(其实是同一个方法loadDirectory,只是入参不同)。这三个方法的入参是extensionClasses 和一个目录参数,就是前面我们介绍的dubbo默认三个目录:

 META-INF/services/
META-INF/dubbo/
META-INF/dubbo/internal/

我们再具体看方法loadDirectory的内容:

 private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir) {
// 扩展配置文件完整文件路径+文件名
String fileName = dir + type.getName();
try {
Enumeration<java.net.URL> urls;
// 获取类加载器
ClassLoader classLoader = findClassLoader();
if (classLoader != null) {
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);
}
}
} catch (Throwable t) {
logger.error("Exception when load extension class(interface: " + type + ", description file: " + fileName + ").", t);
}
}

首先组合目录参数和type名称,作为文件的真实路径名,通过加载器进行加载,之后调用loadResource方法,同时extensionClasses 传入该方法。

 private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), "utf-8"));
try {
String line;
while ((line = reader.readLine()) != null) {
// 字符#是注释开始标志,只取#前面的字符
final int ci = line.indexOf('#');
if (ci >= 0)
line = line.substring(0, ci);
line = line.trim();
if (line.length() > 0) {
try {
String name = null;
int i = line.indexOf('=');
if (i > 0) {
// 解析出 name 和 实现类
name = line.substring(0, i).trim();
line = line.substring(i + 1).trim();
}
if (line.length() > 0) {
loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
}
} catch (Throwable t) {
IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
exceptions.put(line, e);
}
}
}
} finally {
reader.close();
}
} catch (Throwable t) {
logger.error("Exception when load extension class(interface: " + type + ", class file: " + resourceURL + ") in " + resourceURL, t);
}
}

这个方法就简单多了,解析文件流,拿到配置文本文件中的key、value,同时通过Class.forName(..)加载解析出来的扩展点实现类,传入方法loadClass,注意这个方法传入的参数还有存放key、Class的Map对象extensionClasses,以及配置文本文件中的key,我们再看这个方法:

 private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
// type是否为clazz的超类,clazz是否实现了type接口
// 此处clazz 是扩展实现类的Class
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.");
}
// clazz是否注解了 Adaptive 自适应扩展
// 不允许多个类注解Adaptive
// 注解adaptive的实现类,赋值给cachedAdaptiveClass
if (clazz.isAnnotationPresent(Adaptive.class)) {
if (cachedAdaptiveClass == null) {
cachedAdaptiveClass = clazz;
// 不允许多个实现类都注解@Adaptive
} else if (!cachedAdaptiveClass.equals(clazz)) {
throw new IllegalStateException("More than 1 adaptive class found: " + cachedAdaptiveClass.getClass().getName() + ", " + clazz.getClass().getName());
}
// 是否为包装类,判断扩展类是否提供了参数是扩展点的构造函数
} else if (isWrapperClass(clazz)) {
Set<Class<?>> wrappers = cachedWrapperClasses;
if (wrappers == null) {
cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
wrappers = cachedWrapperClasses;
}
wrappers.add(clazz);
// 普通扩展类
} else {
// 检测 clazz 是否有默认的构造方法,如果没有,则抛出异常
clazz.getConstructor();
// 此处name为 SPI配置中的key
// @SPI配置中key可以为空,此时key为扩展类的类名(getSimpleName())小写
if (name == null || name.length() == 0) {
// 兼容旧版本
name = findAnnotationName(clazz);
if (name.length() == 0) {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
}
}
// 逗号分割
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);
}
// name不能重复
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());
}
}
}
}
}

方法参数clazz就是传过来的扩展点实现类的Class对象,首先判断是否实现了扩展点type接口。接着判断是否注解了@Adaptive以及是否为包装类isWrapperClass(clazz),这两个分支逻辑 我们暂且绕过,接下来会进行构造器检查,判断是否存在无参构造器,如果name为空,为了兼容老版本 会进行一次name赋值。

此处会再进行一次name的分隔,前门已经知道,name中不会存在逗号的,但经过上面兼容老版本的重新赋值,会再进行一次判断。@Activate注解的判断,我们也暂且绕过。

循环解析过的name字符串,把加载的扩展点实现Class对象和name存放到入参extensionClasses中。

至此,解析、加载配置文本文件的逻辑已经结束。最后的结果主要是有:把加载到的扩展点Class和key存入到缓存对象extensionClasses中,同时设置cachedDefaultName为扩展点注解@SPI中的value。

我们重新回到方法createExtension中,现在我们已经拿到了特定name对应的扩展点实现类的Class对象,如果对象为空,抛出异常。

接着,我们从缓存对象EXTENSION_INSTANCES中,通过Class对象获取实例,如果实例为空,通过clazz.newInstance()创建,并放入EXTENSION_INSTANCES中。

createExtension方法的后面的逻辑:

 // 向实例中注入依赖,IOC实现
injectExtension(instance);
// 包装处理
// cachedWrapperClasses 加载@SPI配置时赋值,此处进行实例化
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
// 循环创建wrapper实例
for (Class<?> wrapperClass : wrapperClasses) {
// 将当前instance作为参数创建wrapper实例,然后向wrapper实例中注入属性值,
// 并将wrapper实例赋值给instance
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}

是拿到扩展点的实例之后,后期的处理,包括对IOC的实现,包装类的处理等功能逻辑,这些知识点,我们稍后进行分析。

七、总结

总结一下,本篇文章,我们分析了dubbo SPI的主流程,从入门介绍、示例描述到源码分析,主流程基本介绍完了,中间涉及到的@Adaptive、@Activate注解,以及包装类、扩展点实现类的IOC功能等知识点,我们都暂且绕过了,后面我们会在下一篇文章中逐一介绍。

Dubbo源码分析之 SPI(一)的更多相关文章

  1. dubbo源码分析2——SPI机制中的SPI实现类的读取和预处理

    SPI机制中的SPI实现类的读取和预处理是由ExtensionLoader类的loadFile方法来完成的 loadFile方法的作用是读取dubbo的某个SPI接口的spi描述文件,然后进行缓存,缓 ...

  2. Dubbo源码分析之SPI(二)

    一.概述 本篇文章是dubbo SPI源码分析的第二篇,接着第一篇继续分析dubbo SPI的内容,我们主要介绍 getDefaultExtension() 获取默认扩展点方法. 由于此方法比较简单, ...

  3. dubbo源码分析6——SPI机制中的AOP

    在 ExtensionLoader 类的loadFile方法中有下图的这段代码: 类如现在这个ExtensionLoader中的type 是Protocol.class,也就是SPI接口的实现类中Xx ...

  4. dubbo源码分析5——SPI机制_AdaptiveExtension的原理和作用

    private T createAdaptiveExtension() { try { return injectExtension((T) getAdaptiveExtensionClass().n ...

  5. dubbo源码分析4——SPI机制_ExtensionFactory类的作用

    ExtensionFactory的源码: @SPI public interface ExtensionFactory { /** * Get extension. * * @param type o ...

  6. dubbo源码分析3——SPI机制中的ExtensionLoader类的objectFactory属性分析

    ExtensionLoader类是整个SPI的核心类,每个SPI都会对应一个ExtensionLoader类实例,这个类的构造方法如下: private ExtensionLoader(Class&l ...

  7. dubbo源码分析1——SPI机制的概要介绍

    插件机制是Dubbo用于可插拔地扩展底层的一些实现而定制的一套机制,比如dubbo底层的RPC协议.注册中心的注册方式等等.具体的实现方式是参照了JDK的SPI思想,由于JDK的SPI的机制比较简单, ...

  8. Dubbo源码分析之SPI(三)

    一.概述 本篇介绍自适应扩展,方法getAdaptiveExtension()的实现.ExtensionLoader类本身很多功能也使用到了自适应扩展.包括ExtensionFactory扩展. 通俗 ...

  9. dubbo源码分析6-telnet方式的管理实现

    dubbo源码分析1-reference bean创建 dubbo源码分析2-reference bean发起服务方法调用 dubbo源码分析3-service bean的创建与发布 dubbo源码分 ...

随机推荐

  1. hdu 1087 Super Jumping! Jumping! Jumping!(动态规划DP)

    Super Jumping! Jumping! Jumping!Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 ...

  2. nyoj 35-表达式求值(stack, 栈的应用)

    35-表达式求值 内存限制:64MB 时间限制:3000ms Special Judge: No accepted:37 submit:53 题目描述: ACM队的mdd想做一个计算器,但是,他要做的 ...

  3. 100天搞定机器学习|Day56 随机森林工作原理及调参实战(信用卡欺诈预测)

    本文是对100天搞定机器学习|Day33-34 随机森林的补充 前文对随机森林的概念.工作原理.使用方法做了简单介绍,并提供了分类和回归的实例. 本期我们重点讲一下: 1.集成学习.Bagging和随 ...

  4. 力扣(LeetCode)键盘行 个人题解

    给定一个单词列表,只返回可以使用在键盘同一行的字母打印出来的单词.键盘如下图所示. 示例: 输入: ["Hello", "Alaska", "Dad& ...

  5. 工作日志,error parsing query: unable to find time zone

    工作日志,error parsing query: unable to find time zone 坑 Windows 系统使用influxdb数据库,在执行查询语句时提示 ERR: error p ...

  6. vue项目页面切换到默认显示顶部

    页面切换到默认显示顶部 方法一 使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样. vue-router 能做到,而且更好,它让你可以自定义路由切换时页 ...

  7. 【控制系统数字仿真与CAD】实验三:离散相似法数字仿真

    一.实验目的 1. 了解离散相似法的基本原理 2. 掌握离散相似法仿真的基本过程 3. 应用离散相似法仿真非线性系统 4. MATLAB实现离散相似法的非线性系统仿真 5. 掌握SIMULINK仿真方 ...

  8. python3 之 闭包实例解析

    一.实例1: def make_power(y): def fn(x): return x**y return fn pow3 = make_power(3) pow2 = make_power(2) ...

  9. 高德JS依赖分析工程及关键原理

    一.背景 高德 App 进行 Bundle 化后,由于业务的复杂性,Bundle 的数量非常多.而这带来了一个新的问题——Bundle 之间的依赖关系错综复杂,需要进行管控,使 Bundle 之间的依 ...

  10. /etc/security/limits.conf配置文件详解

    这个文件主要是用来限制用户对系统资源的使用.是/lib64/security/pam_limits.so模块对应的/etc/serurity/pam_limits的配置文件. # /etc/secur ...