Dubbo源码分析之 SPI(一)
一、概述
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(一)的更多相关文章
- dubbo源码分析2——SPI机制中的SPI实现类的读取和预处理
		SPI机制中的SPI实现类的读取和预处理是由ExtensionLoader类的loadFile方法来完成的 loadFile方法的作用是读取dubbo的某个SPI接口的spi描述文件,然后进行缓存,缓 ... 
- Dubbo源码分析之SPI(二)
		一.概述 本篇文章是dubbo SPI源码分析的第二篇,接着第一篇继续分析dubbo SPI的内容,我们主要介绍 getDefaultExtension() 获取默认扩展点方法. 由于此方法比较简单, ... 
- dubbo源码分析6——SPI机制中的AOP
		在 ExtensionLoader 类的loadFile方法中有下图的这段代码: 类如现在这个ExtensionLoader中的type 是Protocol.class,也就是SPI接口的实现类中Xx ... 
- dubbo源码分析5——SPI机制_AdaptiveExtension的原理和作用
		private T createAdaptiveExtension() { try { return injectExtension((T) getAdaptiveExtensionClass().n ... 
- dubbo源码分析4——SPI机制_ExtensionFactory类的作用
		ExtensionFactory的源码: @SPI public interface ExtensionFactory { /** * Get extension. * * @param type o ... 
- dubbo源码分析3——SPI机制中的ExtensionLoader类的objectFactory属性分析
		ExtensionLoader类是整个SPI的核心类,每个SPI都会对应一个ExtensionLoader类实例,这个类的构造方法如下: private ExtensionLoader(Class&l ... 
- dubbo源码分析1——SPI机制的概要介绍
		插件机制是Dubbo用于可插拔地扩展底层的一些实现而定制的一套机制,比如dubbo底层的RPC协议.注册中心的注册方式等等.具体的实现方式是参照了JDK的SPI思想,由于JDK的SPI的机制比较简单, ... 
- Dubbo源码分析之SPI(三)
		一.概述 本篇介绍自适应扩展,方法getAdaptiveExtension()的实现.ExtensionLoader类本身很多功能也使用到了自适应扩展.包括ExtensionFactory扩展. 通俗 ... 
- dubbo源码分析6-telnet方式的管理实现
		dubbo源码分析1-reference bean创建 dubbo源码分析2-reference bean发起服务方法调用 dubbo源码分析3-service bean的创建与发布 dubbo源码分 ... 
随机推荐
- ReentrantLock 如何实现非公平锁?和公平锁实现有什么区别
			reentrant 英[riːˈɛntrənt] 美[ˌriˈɛntrənt] 先学会读.单词原意是可重入的 考察显示锁的使用.可延伸知识点 独占锁 & 共享锁 独占锁 - 悲观锁(不能同时被 ... 
- T-SQL Part VIII: CROSS APPLY, OUTER APPLY
			除了CROSS JOIN, INNER JOIN, OUTER JOIN之外,T-SQL还提供了CROSS APPLY和OUTER APPLY这两个较为另类的Set操作符. 首先来看CROSS APP ... 
- supervisor服务
			描述: 遇到各种各样的各种坑, 可以通过python2 的pip安装, 可以通过apt安装, 不支持python3: 如若用apt安装可能会自动启动并且加入开机自启(不保证成功),pip安装一定不会需 ... 
- django_0:项目流程
			1.django-admin(.py) startproject mysite——创建项目project 得到__init__.py(说明工程以包结构存在) settings.py(当前工程的一些配置 ... 
- objc里的伪指针TaggedPointer
			如果你看过我前面两篇objc函数枢纽msgSend和你印象中的NSString是这样吗,相信已经多次看过它的身影了,到底它是何物何作用,我今日就来揭开谜团.我之所为称呼它为伪指针,是因为它像幽灵一样, ... 
- opencv MatchTemplate()模板匹配寻找最匹配部分
			通常,随着从简单的测量(平方差)到更复杂的测量(相关系数),可以获得越来越准确的匹配,然而,这同时也会以越来越大的计算量为代价.比较科学的方法是对所有这些方法多次测试实验,以便为自己的应用选择同时兼顾 ... 
- centos7清理矿机木马qw3xT,kpgrbcc
			腾讯云报告了root口令被暴力破解,并种了木马kpgrbcc 昨晚找到/usr/bin/ rm -rf kpgrbcc 删除 rm -rf kpgrbcb 删除 并ps -ef | grep kpg ... 
- EFK教程(3) - ElasticSearch冷热数据分离
			基于ElasticSearch多实例架构,实现资源合理分配.冷热数据分离 作者:"发颠的小狼",欢迎转载与投稿 目录 ▪ 用途 ▪ 架构 ▪ 192.168.1.51 elasti ... 
- goroutiine同步/channel、互斥锁、读写锁、死锁/条件变量
			1. Goroutine同步[数据同步] 为什么需要goroutine同步 gorotine同步概念.以及同步的几种方式 1.1 为什么需要goroutine同步 package main impor ... 
- Spring中常见的设计模式——工厂模式
			一.简单工厂模式 简单工厂模式(Simple Factory Pattern)由一个工厂对象决定创建哪一种产品类的实例,简单工厂模式适用于工厂类负责创建对象较少的情况,且客户端只需要传入工厂类的参数, ... 
