Dubbo扩展点加载机制 - ExtensionLoader

简介:

来源: 
Dubbo的扩展点加载从JDK标准的SPI(Service Provider Interface)扩展点发现机制加强而来。

Dubbo改进了JDK标准的SPI的以下问题:

  • JDK标准的SPI会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。

  • 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK标准的ScriptEngine,通过getName();获取脚本类型的名称,但如果RubyScriptEngine因为所依赖的jruby.jar不存在,导致RubyScriptEngine类加载失败,这个失败原因被吃掉了,和ruby对应不起来,当用户执行ruby脚本时,会报不支持ruby,而不是真正失败的原因。

  • 增加了对扩展点IoC和AOP的支持,一个扩展点可以直接setter注入其它扩展点。

约定: 
在扩展类的jar包内,放置扩展点配置文件:META-INF/dubbo/接口全限定名,内容为:配置名=扩展实现类全限定名,多个实现类用换行符分隔。 
(注意:这里的配置文件是放在你自己的jar包内,不是dubbo本身的jar包内,Dubbo会全ClassPath扫描所有jar包内同名的这个文件,然后进行合并)

扩展Dubbo的协议示例: 
在协议的实现jar包内放置文本文件:META-INF/dubbo/com.alibaba.dubbo.rpc.Protocol,内容为:

 

Properties代码   收藏代码
  1. xxx=com.alibaba.xxx.XxxProtocol  

 实现内容:

 

Java代码   收藏代码
  1. package com.alibaba.xxx;  
  2.   
  3. import com.alibaba.dubbo.rpc.Protocol;  
  4. public class XxxProtocol implemenets Protocol {  
  5.   
  6.     // ...  
  7. }  

 注意: 扩展点使用单一实例加载(请确保扩展实现的线程安全性),Cache在ExtensionLoader中

 

特性

  • 扩展点自动包装
  • 扩展点自动装配
  • 扩展点自适应
  • 扩展点自动激活

相关文档可以参考dubbo的官方文档 ,本文主要通过分析相关的源代码来体会dubbo的扩展点框架提供的特性。


源码分析

dubbo的扩展点框架主要位于这个包下:

    com.alibaba.dubbo.common.extension

大概结构如下:

 

Xml代码   收藏代码
  1. com.alibaba.dubbo.common.extension  
  2.  |  
  3.  |--factory  
  4.  |     |--AdaptiveExtensionFactory   #稍后解释  
  5.  |     |--SpiExtensionFactory        #稍后解释  
  6.  |  
  7.  |--support  
  8.  |     |--ActivateComparator  
  9.  |  
  10.  |--Activate  #自动激活加载扩展的注解  
  11.  |--Adaptive  #自适应扩展点的注解  
  12.  |--ExtensionFactory  #扩展点对象生成工厂接口  
  13.  |--ExtensionLoader   #扩展点加载器,扩展点的查找,校验,加载等核心逻辑的实现类  
  14.  |--SPI   #扩展点注解  

 其中最核心的类就是ExtensionLoader,几乎所有特性都在这个类中实现。

 

ExtensionLoader没有提供public的构造方法,但是提供了一个public staticgetExtensionLoader,这个方法就是获取ExtensionLoader实例的工厂方法。其public成员方法中有三个比较重要的方法:

  • getActivateExtension :根据条件获取当前扩展可自动激活的实现
  • getExtension : 根据名称获取当前扩展的指定实现
  • getAdaptiveExtension : 获取当前扩展的自适应实现

这三个方法将会是我们重点关注的方法;* 每一个ExtensionLoader实例仅负责加载特定SPI扩展的实现*。因此想要获取某个扩展的实现,首先要获取到该扩展对应的ExtensionLoader实例,下面我们就来看一下获取ExtensionLoader实例的工厂方法getExtensionLoader

 

Java代码   收藏代码
  1. public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {  
  2.     if (type == null)  
  3.         throw new IllegalArgumentException("Extension type == null");  
  4.     if(!type.isInterface()) {  
  5.         throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");  
  6.     }  
  7.     if(!withExtensionAnnotation(type)) { // 只接受使用@SPI注解注释的接口类型  
  8.         throw new IllegalArgumentException("Extension type(" + type +   
  9.                 ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");  
  10.     }  
  11.   
  12.     // 先从静态缓存中获取对应的ExtensionLoader实例  
  13.     ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);  
  14.     if (loader == null) {  
  15.         EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type)); // 为Extension类型创建ExtensionLoader实例,并放入静态缓存  
  16.         loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);  
  17.     }  
  18.     return loader;  
  19. }  

 该方法需要一个Class类型的参数,该参数表示希望加载的扩展点类型,该参数必须是接口,且该接口必须被@SPI注解注释,否则拒绝处理。检查通过之后首先会检查ExtensionLoader缓存中是否已经存在该扩展对应的ExtensionLoader,如果有则直接返回,否则创建一个新的ExtensionLoader负责加载该扩展实现,同时将其缓存起来。可以看到对于每一个扩展,dubbo中只会有一个对应的ExtensionLoader实例。

 

接下来看下ExtensionLoader的私有构造函数:

 

Java代码   收藏代码
  1. private ExtensionLoader(Class<?> type) {  
  2.     this.type = type;  
  3.   
  4.     // 如果扩展类型是ExtensionFactory,那么则设置为null  
  5.     // 这里通过getAdaptiveExtension方法获取一个运行时自适应的扩展类型(每个Extension只能有一个@Adaptive类型的实现,如果没有dubbo会动态生成一个类)  
  6.     objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());  
  7. }  

 这里保存了对应的扩展类型,并且设置了一个额外的objectFactory属性,他是一个ExtensionFactory类型,ExtensionFactory主要用于加载扩展的实现:

 

Java代码   收藏代码
  1. @SPI  
  2. public interface ExtensionFactory {  
  3.   
  4.     /** 
  5.      * Get extension. 
  6.      *  
  7.      * @param type object type. 
  8.      * @param name object name. 
  9.      * @return object instance. 
  10.      */  
  11.     <T> T getExtension(Class<T> type, String name);  
  12.   
  13. }  

 同时ExtensionFactory也被@SPI注解注释,说明他也是一个扩展点,从前面com.alibaba.dubbo.common.extension包的结构图中可以看到,dubbo内部提供了两个实现类:SpiExtensionFactory 和 AdaptiveExtensionFactory,实际上还有一个SpringExtensionFactory,不同的实现可以已不同的方式来完成扩展点实现的加载,这块稍后再来学习。从ExtensionLoader的构造函数中可以看到,如果要加载的扩展点类型是ExtensionFactory是,object字段被设置为null。由于ExtensionLoader的使用范围有限(基本上局限在ExtensionLoader中),因此对他做了特殊对待:在需要使用ExtensionFactory的地方,都是通过对应的自适应实现来代替。

 

默认的ExtensionFactory实现中,AdaptiveExtensionFactotry@Adaptive注解注释,也就是它就是ExtensionFactory对应的自适应扩展实现(每个扩展点最多只能有一个自适应实现,如果所有实现中没有被@Adaptive注释的,那么dubbo会动态生成一个自适应实现类),也就是说,所有对ExtensionFactory调用的地方,实际上调用的都是AdpativeExtensionFactory,那么我们看下他的实现代码:

 

Java代码   收藏代码
  1. @Adaptive  
  2. public class AdaptiveExtensionFactory implements ExtensionFactory {  
  3.   
  4.     private final List<ExtensionFactory> factories;  
  5.   
  6.     public AdaptiveExtensionFactory() {  
  7.         ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);  
  8.         List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();  
  9.         for (String name : loader.getSupportedExtensions()) { // 将所有ExtensionFactory实现保存起来  
  10.             list.add(loader.getExtension(name));  
  11.         }  
  12.         factories = Collections.unmodifiableList(list);  
  13.     }  
  14.   
  15.     public <T> T getExtension(Class<T> type, String name) {  
  16.         // 依次遍历各个ExtensionFactory实现的getExtension方法,一旦获取到Extension即返回  
  17.         // 如果遍历完所有的ExtensionFactory实现均无法找到Extension,则返回null  
  18.         for (ExtensionFactory factory : factories) {  
  19.             T extension = factory.getExtension(type, name);  
  20.             if (extension != null) {  
  21.                 return extension;  
  22.             }  
  23.         }  
  24.         return null;  
  25.     }  
  26.   
  27. }  

 看完代码大家都知道是怎么回事了,这货就相当于一个代理入口,他会遍历当前系统中所有的ExtensionFactory实现来获取指定的扩展实现,获取到扩展实现或遍历完所有的ExtensionFactory实现。这里调用了ExtensionLoadergetSupportedExtensions方法来获取ExtensionFactory的所有实现,又回到了ExtensionLoader类,下面我们就来分析ExtensionLoader的几个重要的实例方法。

 

方法调用流程

getExtension

 

Xml代码   收藏代码
  1. getExtension(name)  
  2.     -> createExtension(name) #如果无缓存则创建  
  3.         -> getExtensionClasses().get(name) #获取name对应的扩展类型  
  4.         -> 实例化扩展类  
  5.         -> injectExtension(instance) # 扩展点注入  
  6.         -> instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)) #循环遍历所有wrapper实现,实例化wrapper并进行扩展点注入    

 getAdaptiveExtension

 

Xml代码   收藏代码
  1. public T getAdaptiveExtension()  
  2.     -> createAdaptiveExtension() #如果无缓存则创建  
  3.         -> getAdaptiveExtensionClass().newInstance() #获取AdaptiveExtensionClass  
  4.             -> getExtensionClasses() # 加载当前扩展所有实现,看是否有实现被标注为@Adaptive  
  5.             -> createAdaptiveExtensionClass() #如果没有实现被标注为@Adaptive,则动态创建一个Adaptive实现类  
  6.                 -> createAdaptiveExtensionClassCode() #动态生成实现类java代码  
  7.                 -> compiler.compile(code, classLoader) #动态编译java代码,加载类并实例化  
  8.         -> injectExtension(instance)  

 getActivateExtesion 

 

该方法有多个重载方法,不过最终都是调用了三个参数的那一个重载形式。其代码结构也相对剪短,就不需要在列出概要流程了。


详细代码分析

getAdaptiveExtension 
从前面ExtensionLoader的私有构造函数中可以看出,在选择ExtensionFactory的时候,并不是调用getExtension(name)来获取某个具体的实现类,而是调用getAdaptiveExtension来获取一个自适应的实现。那么首先我们就来分析一下getAdaptiveExtension这个方法的实现吧:

 

Java代码   收藏代码
  1. public T getAdaptiveExtension() {  
  2.     Object instance = cachedAdaptiveInstance.get(); // 首先判断是否已经有缓存的实例对象  
  3.     if (instance == null) {  
  4.         if(createAdaptiveInstanceError == null) {  
  5.             synchronized (cachedAdaptiveInstance) {  
  6.                 instance = cachedAdaptiveInstance.get();  
  7.                 if (instance == null) {  
  8.                     try {  
  9.                         instance = createAdaptiveExtension(); // 没有缓存的实例,创建新的AdaptiveExtension实例  
  10.                         cachedAdaptiveInstance.set(instance);  
  11.                     } catch (Throwable t) {  
  12.                         createAdaptiveInstanceError = t;  
  13.                         throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t);  
  14.                     }  
  15.                 }  
  16.             }  
  17.         }  
  18.         else {  
  19.             throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);  
  20.         }  
  21.     }  
  22.   
  23.     return (T) instance;  
  24. }  

 首先检查缓存的adaptiveInstance是否存在,如果存在则直接使用,否则的话调用createAdaptiveExtension方法来创建新的adaptiveInstance并且缓存起来。也就是说对于某个扩展点,每次调用ExtensionLoader.getAdaptiveExtension获取到的都是同一个实例。

 

Java代码   收藏代码
  1. private T createAdaptiveExtension() {  
  2.     try {  
  3.         return injectExtension((T) getAdaptiveExtensionClass().newInstance()); // 先获取AdaptiveExtensionClass,在获取其实例,最后进行注入处理  
  4.     } catch (Exception e) {  
  5.         throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e);  
  6.     }  
  7. }  

 在createAdaptiveExtension方法中,首先通过getAdaptiveExtensionClass方法获取到最终的自适应实现类型,然后实例化一个自适应扩展实现的实例,最后进行扩展点注入操作。先看一个getAdaptiveExtensionClass方法的实现:

 

Java代码   收藏代码
  1. private Class<?> getAdaptiveExtensionClass() {  
  2.     getExtensionClasses(); // 加载当前Extension的所有实现,如果有@Adaptive类型,则会赋值为cachedAdaptiveClass属性缓存起来  
  3.     if (cachedAdaptiveClass != null) {  
  4.         return cachedAdaptiveClass;  
  5.     }  
  6.     return cachedAdaptiveClass = createAdaptiveExtensionClass(); // 没有找到@Adaptive类型实现,则动态创建一个AdaptiveExtensionClass  
  7. }  

 他只是简单的调用了getExtensionClasses方法,然后在判adaptiveCalss缓存是否被设置,如果被设置那么直接返回,否则调用createAdaptiveExntesionClass方法动态生成一个自适应实现,关于动态生成自适应实现类然后编译加载并且实例化的过程这里暂时不分析,留到后面在分析吧。这里我们看getExtensionClassses方法:

Java代码   收藏代码
  1. private Map<String, Class<?>> getExtensionClasses() {  
  2.     Map<String, Class<?>> classes = cachedClasses.get(); // 判断是否已经加载了当前Extension的所有实现类  
  3.     if (classes == null) {  
  4.         synchronized (cachedClasses) {  
  5.             classes = cachedClasses.get();  
  6.             if (classes == null) {  
  7.                 classes = loadExtensionClasses(); // 如果还没有加载Extension的实现,则进行扫描加载,完成后赋值给cachedClasses变量  
  8.                 cachedClasses.set(classes);  
  9.             }  
  10.         }  
  11.     }  
  12.     return classes;  
  13. }  

 在getExtensionClasses方法中,首先检查缓存的cachedClasses,如果没有再调用loadExtensionClasses方法来加载,加载完成之后就会进行缓存。也就是说对于每个扩展点,其实现的加载只会执行一次。我们看下loadExtensionClasses方法:

Java代码   收藏代码
  1. private Map<String, Class<?>> loadExtensionClasses() {  
  2.     final SPI defaultAnnotation = type.getAnnotation(SPI.class);  
  3.     if(defaultAnnotation != null) {  
  4.         String value = defaultAnnotation.value(); // 解析当前Extension配置的默认实现名,赋值给cachedDefaultName属性  
  5.         if(value != null && (value = value.trim()).length() > 0) {  
  6.             String[] names = NAME_SEPARATOR.split(value);  
  7.             if(names.length > 1) { // 每个扩展实现只能配置一个名称  
  8.                 throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()  
  9.                         + ": " + Arrays.toString(names));  
  10.             }  
  11.             if(names.length == 1) cachedDefaultName = names[0];  
  12.         }  
  13.     }  
  14.   
  15.     // 从配置文件中加载扩展实现类  
  16.     Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();  
  17.     loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);  
  18.     loadFile(extensionClasses, DUBBO_DIRECTORY);  
  19.     loadFile(extensionClasses, SERVICES_DIRECTORY);  
  20.     return extensionClasses;  
  21. }  

 从代码里面可以看到,在loadExtensionClasses中首先会检测扩展点在@SPI注解中配置的默认扩展实现的名称,并将其赋值给cachedDefaultName属性进行缓存,后面想要获取该扩展点的默认实现名称就可以直接通过访问cachedDefaultName字段来完成,比如getDefaultExtensionName方法就是这么实现的。从这里的代码中又可以看到,具体的扩展实现类型,是通过调用loadFile方法来加载,分别从一下三个地方加载:

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

那么这个loadFile方法则至关重要了,看看其源代码:

Java代码   收藏代码
  1. <span style="font-size: 10px;">private void loadFile(Map<String, Class<?>> extensionClasses, String dir) {  
  2.     String fileName = dir + type.getName(); // 配置文件名称,扫描整个classpath  
  3.     try {  
  4.         // 先获取该路径下所有文件  
  5.         Enumeration<java.net.URL> urls;  
  6.         ClassLoader classLoader = findClassLoader();  
  7.         if (classLoader != null) {  
  8.             urls = classLoader.getResources(fileName);  
  9.         } else {  
  10.             urls = ClassLoader.getSystemResources(fileName);  
  11.         }  
  12.         if (urls != null) {  
  13.             // 遍历这些文件并进行处理  
  14.             while (urls.hasMoreElements()) {  
  15.                 java.net.URL url = urls.nextElement(); // 获取配置文件路径  
  16.                 try {  
  17.                     BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8"));  
  18.                     try {  
  19.                         String line = null;  
  20.                         while ((line = reader.readLine()) != null) { // 一行一行读取(一行一个配置)  
  21.                             final int ci = line.indexOf('#');  
  22.                             if (ci >= 0) line = line.substring(0, ci);  
  23.                             line = line.trim();  
  24.                             if (line.length() > 0) {  
  25.                                 try {  
  26.                                     String name = null;  
  27.                                     int i = line.indexOf('='); // 等号分割  
  28.                                     if (i > 0) {  
  29.                                         name = line.substring(0, i).trim(); // 扩展名称  
  30.                                         line = line.substring(i + 1).trim(); // 扩展实现类  
  31.                                     }  
  32.                                     if (line.length() > 0) {  
  33.                                         Class<?> clazz = Class.forName(line, true, classLoader); // 加载扩展实现类  
  34.                                         if (! type.isAssignableFrom(clazz)) { // 判断类型是否匹配  
  35.                                             throw new IllegalStateException("Error when load extension class(interface: " +  
  36.                                                     type + ", class line: " + clazz.getName() + "), class "   
  37.                                                     + clazz.getName() + "is not subtype of interface.");  
  38.                                         }  
  39.                                         if (clazz.isAnnotationPresent(Adaptive.class)) { // 判断该实现类是否@Adaptive,是的话不会放入extensionClasses/cachedClasses缓存  
  40.                                             if(cachedAdaptiveClass == null) { // 第一个赋值给cachedAdaptiveClass属性  
  41.                                                 cachedAdaptiveClass = clazz;  
  42.                                             } else if (! cachedAdaptiveClass.equals(clazz)) { // 只能有一个@Adaptive实现,出现第二个就报错了  
  43.                                                 throw new IllegalStateException("More than 1 adaptive class found: "  
  44.                                                         + cachedAdaptiveClass.getClass().getName()  
  45.                                                         + ", " + clazz.getClass().getName());  
  46.                                             }  
  47.                                         } else { // 不是@Adaptive类型  
  48.                                             try {  
  49.                                                 clazz.getConstructor(type); // 判断是否Wrapper类型  
  50.                                                 Set<Class<?>> wrappers = cachedWrapperClasses;  
  51.                                                 if (wrappers == null) {  
  52.                                                     cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();  
  53.                                                     wrappers = cachedWrapperClasses;  
  54.                                                 }  
  55.                                                 wrappers.add(clazz); //放入到Wrapper实现类缓存中  
  56.                                             } catch (NoSuchMethodException e) { //不是Wrapper类型,普通实现类型  
  57.                                                 clazz.getConstructor();  
  58.                                                 if (name == null || name.length() == 0) {  
  59.                                                     name = findAnnotationName(clazz);  
  60.                                                     if (name == null || name.length() == 0) {  
  61.                                                         if (clazz.getSimpleName().length() > type.getSimpleName().length()  
  62.                                                                 && clazz.getSimpleName().endsWith(type.getSimpleName())) {  
  63.                                                             name = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() - type.getSimpleName().length()).toLowerCase();  
  64.                                                         } else {  
  65.                                                             throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + url);  
  66.                                                         }  
  67.                                                     }  
  68.                                                 }  
  69.                                                 String[] names = NAME_SEPARATOR.split(name); // 看是否配置了多个name  
  70.                                                 if (names != null && names.length > 0) {  
  71.                                                     Activate activate = clazz.getAnnotation(Activate.class); // 是否@Activate类型  
  72.                                                     if (activate != null) {  
  73.                                                         cachedActivates.put(names[0], activate);// 是则放入cachedActivates缓存  
  74.                                                     }  
  75.   
  76.                                                     // 遍历所有name  
  77.                                                     for (String n : names) {  
  78.                                                         if (! cachedNames.containsKey(clazz)) {  
  79.                                                             cachedNames.put(clazz, n); // 放入Extension实现类与名称映射缓存,每个class只对应第一个名称有效  
  80.                                                         }  
  81.                                                         Class<?> c = extensionClasses.get(n);  
  82.                                                         if (c == null) {  
  83.                                                             extensionClasses.put(n, clazz); // 放入到extensionClasses缓存,多个name可能对应一个Class  
  84.                                                         } else if (c != clazz) { // 存在重名  
  85.                                                             throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());  
  86.                                                         }  
  87.                                                     }  
  88.                                                 }  
  89.                                             }  
  90.                                         }  
  91.                                     }  
  92.                                 } catch (Throwable t) {  
  93.                                     IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + url + ", cause: " + t.getMessage(), t);  
  94.                                     exceptions.put(line, e);  
  95.                                 }  
  96.                             }  
  97.                         } // end of while read lines  
  98.                     } finally {  
  99.                         reader.close();  
  100.                     }  
  101.                 } catch (Throwable t) {  
  102.                     logger.error("Exception when load extension class(interface: " +  
  103.                                         type + ", class file: " + url + ") in " + url, t);  
  104.                 }  
  105.             } // end of while urls  
  106.         }  
  107.     } catch (Throwable t) {  
  108.         logger.error("Exception when load extension class(interface: " +  
  109.                 type + ", description file: " + fileName + ").", t);  
  110.     }  
  111. }</span>  

 

 代码比较长,大概的事情呢就是解析配置文件,获取扩展点实现对应的名称和实现类,并进行分类处理和缓存。当loadFile方法执行完成之后,以下几个变量就会被附上值:

  • cachedAdaptiveClass : 当前Extension类型对应的AdaptiveExtension类型(只能一个)
  • cachedWrapperClasses : 当前Extension类型对应的所有Wrapper实现类型(无顺序)
  • cachedActivates : 当前Extension实现自动激活实现缓存(map,无序)
  • cachedNames : 扩展点实现类对应的名称(如配置多个名称则值为第一个)

loadExtensionClasses方法执行完成之后,还有一下变量被赋值:

  • cachedDefaultName : 当前扩展点的默认实现名称

getExtensionClasses方法执行完成之后,除了上述变量被赋值之外,还有以下变量被赋值:

  • cachedClasses : 扩展点实现名称对应的实现类(一个实现类可能有多个名称)

其实也就是说,在调用了getExtensionClasses方法之后,当前扩展点对应的实现类的一些信息就已经加载进来了并且被缓存了。后面的许多操作都可以直接通过这些缓存数据来进行处理了。

回到createAdaptiveExtension方法,他调用了getExtesionClasses方法加载扩展点实现信息完成之后,就可以直接通过判断cachedAdaptiveClass缓存字段是否被赋值盘确定当前扩展点是否有默认的AdaptiveExtension实现。如果没有,那么就调用createAdaptiveExtensionClass方法来动态生成一个。在dubbo的扩展点框架中大量的使用了缓存技术。

创建自适应扩展点实现类型和实例化就已经完成了,下面就来看下扩展点自动注入的实现injectExtension

Java代码   收藏代码
  1. private T injectExtension(T instance) {  
  2.     try {  
  3.         if (objectFactory != null) {  
  4.             for (Method method : instance.getClass().getMethods()) {  
  5.                 if (method.getName().startsWith("set")  
  6.                         && method.getParameterTypes().length == 1  
  7.                         && Modifier.isPublic(method.getModifiers())) {// 处理所有set方法  
  8.                     Class<?> pt = method.getParameterTypes()[0];// 获取set方法参数类型  
  9.                     try {  
  10.                         // 获取setter对应的property名称  
  11.                         String property = method.getName().length() > 3 ? method.getName().substring(34).toLowerCase() + method.getName().substring(4) : "";  
  12.                         Object object = objectFactory.getExtension(pt, property); // 根据类型,名称信息从ExtensionFactory获取  
  13.                         if (object != null) { // 如果不为空,说set方法的参数是扩展点类型,那么进行注入  
  14.                             method.invoke(instance, object);  
  15.                         }  
  16.                     } catch (Exception e) {  
  17.                         logger.error("fail to inject via method " + method.getName()  
  18.                                 + " of interface " + type.getName() + ": " + e.getMessage(), e);  
  19.                     }  
  20.                 }  
  21.             }  
  22.         }  
  23.     } catch (Exception e) {  
  24.         logger.error(e.getMessage(), e);  
  25.     }  
  26.     return instance;  
  27. }  

 这里可以看到,扩展点自动注入的一句就是根据setter方法对应的参数类型和property名称从ExtensionFactory中查询,如果有返回扩展点实例,那么就进行注入操作。到这里getAdaptiveExtension方法就分析完毕了。

 

getExtension

这个方法的主要作用是用来获取ExtensionLoader实例代表的扩展的指定实现。已扩展实现的名字作为参数,结合前面学习getAdaptiveExtension的代码,我们可以推测,这方法中也使用了在调用getExtensionClasses方法的时候收集并缓存的数据,其中涉及到名字和具体实现类型对应关系的缓存属性是cachedClasses。具体是是否如我们猜想的那样呢,学习一下相关代码就知道了:

Java代码   收藏代码
  1. public T getExtension(String name) {  
  2.     if (name == null || name.length() == 0)  
  3.         throw new IllegalArgumentException("Extension name == null");  
  4.     if ("true".equals(name)) {  // 判断是否是获取默认实现  
  5.         return getDefaultExtension();  
  6.     }  
  7.     Holder<Object> holder = cachedInstances.get(name);// 缓存  
  8.     if (holder == null) {  
  9.         cachedInstances.putIfAbsent(name, new Holder<Object>());  
  10.         holder = cachedInstances.get(name);  
  11.     }  
  12.     Object instance = holder.get();  
  13.     if (instance == null) {  
  14.         synchronized (holder) {  
  15.             instance = holder.get();  
  16.             if (instance == null) {  
  17.                 instance = createExtension(name);// 没有缓存实例则创建  
  18.                 holder.set(instance);// 缓存起来  
  19.             }  
  20.         }  
  21.     }  
  22.     return (T) instance;  
  23. }  

 接着看createExtension方法的实现:

Java代码   收藏代码
  1. private T createExtension(String name) {  
  2.     Class<?> clazz = getExtensionClasses().get(name); // getExtensionClass内部使用cachedClasses缓存  
  3.     if (clazz == null) {  
  4.         throw findException(name);  
  5.     }  
  6.     try {  
  7.         T instance = (T) EXTENSION_INSTANCES.get(clazz); // 从已创建Extension实例缓存中获取  
  8.         if (instance == null) {  
  9.             EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());  
  10.             instance = (T) EXTENSION_INSTANCES.get(clazz);  
  11.         }  
  12.         injectExtension(instance); // 属性注入  
  13.   
  14.         // Wrapper类型进行包装,层层包裹  
  15.         Set<Class<?>> wrapperClasses = cachedWrapperClasses;  
  16.         if (wrapperClasses != null && wrapperClasses.size() > 0) {  
  17.             for (Class<?> wrapperClass : wrapperClasses) {  
  18.                 instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));  
  19.             }  
  20.         }  
  21.         return instance;  
  22.     } catch (Throwable t) {  
  23.         throw new IllegalStateException("Extension instance(name: " + name + ", class: " +  
  24.                 type + ")  could not be instantiated: " + t.getMessage(), t);  
  25.     }  
  26. }  

 从代码中可以看到,内部调用了getExtensionClasses方法来获取当前扩展的所有实现,而getExtensionClassse方法会在第一次被调用的时候将结果缓存到cachedClasses变量中,后面的调用就直接从缓存变量中获取了。这里还可以看到一个缓存EXTENSION_INSTANCES,这个缓存是ExtensionLoader的静态成员,也就是全局缓存,存放着所有的扩展点实现类型与其对应的已经实例化的实例对象(是所有扩展点,不是某一个扩展点),也就是说所有的扩展点实现在dubbo中最多都只会有一个实例。

拿到扩展点实现类型对应的实例之后,调用了injectExtension方法对该实例进行扩展点注入,紧接着就是遍历该扩展点接口的所有Wrapper来对真正的扩展点实例进行Wrap操作,都是对通过将上一次的结果作为下一个Wrapper的构造函数参数传递进去实例化一个Wrapper对象,最后总返回回去的是Wrapper类型的实例而不是具体实现类的实例。

这里或许有一个疑问: 从代码中看,不论instance是否存在于EXTENSION_INSTANCE,都会进行扩展点注入和Wrap操作。那么如果对于同一个扩展点,调用了两次createExtension方法的话,那不就进行了两次Wrap操作么?

如果外部能够直接调用createExtension方法,那么确实可能出现这个问题。但是由于createExtension方法是private的,因此外部无法直接调用。而在ExtensionLoader类中调用它的getExtension方法(只有它这一处调用),内部自己做了缓存(cachedInstances),因此当getExtension方法内部调用了一次createExtension方法之后,后面对getExtension方法执行同样的调用时,会直接使用cachedInstances缓存而不会再去调用createExtension方法了。

 

getActivateExtension

getActivateExtension方法主要获取当前扩展的所有可自动激活的实现。可根据入参(values)调整指定实现的顺序,在这个方法里面也使用到getExtensionClasses方法中收集的缓存数据。

Java代码   收藏代码
  1. public List<T> getActivateExtension(URL url, String[] values, String group) {  
  2.     List<T> exts = new ArrayList<T>();  
  3.     List<String> names = values == null ? new ArrayList<String>(0) : Arrays.asList(values); // 解析配置要使用的名称  
  4.   
  5.     // 如果未配置"-default",则加载所有Activates扩展(names指定的扩展)  
  6.     if (! names.contains(Constants.REMOVE_VALUE_PREFIX + Constants.DEFAULT_KEY)) {  
  7.         getExtensionClasses(); // 加载当前Extension所有实现,会获取到当前Extension中所有@Active实现,赋值给cachedActivates变量  
  8.         for (Map.Entry<String, Activate> entry : cachedActivates.entrySet()) { // 遍历当前扩展所有的@Activate扩展  
  9.             String name = entry.getKey();  
  10.             Activate activate = entry.getValue();  
  11.             if (isMatchGroup(group, activate.group())) { // 判断group是否满足,group为null则直接返回true  
  12.                 T ext = getExtension(name); // 获取扩展示例  
  13.   
  14.                 // 排除names指定的扩展;并且如果names中没有指定移除该扩展(-name),且当前url匹配结果显示可激活才进行使用  
  15.                 if (! names.contains(name)  
  16.                         && ! names.contains(Constants.REMOVE_VALUE_PREFIX + name)   
  17.                         && isActive(activate, url)) {  
  18.                     exts.add(ext);  
  19.                 }  
  20.             }  
  21.         }  
  22.         Collections.sort(exts, ActivateComparator.COMPARATOR); // 默认排序  
  23.     }  
  24.   
  25.     // 对names指定的扩展进行专门的处理  
  26.     List<T> usrs = new ArrayList<T>();  
  27.     for (int i = 0; i < names.size(); i ++) { // 遍历names指定的扩展名  
  28.         String name = names.get(i);  
  29.         if (! name.startsWith(Constants.REMOVE_VALUE_PREFIX)  
  30.                 && ! names.contains(Constants.REMOVE_VALUE_PREFIX + name)) { // 未设置移除该扩展  
  31.             if (Constants.DEFAULT_KEY.equals(name)) { // default表示上面已经加载并且排序的exts,将排在default之前的Activate扩展放置到default组之前,例如:ext1,default,ext2  
  32.                 if (usrs.size() > 0) { // 如果此时user不为空,则user中存放的是配置在default之前的Activate扩展  
  33.                     exts.addAll(0, usrs); // 注意index是0,放在default前面  
  34.                     usrs.clear(); // 放到default之前,然后清空  
  35.                 }  
  36.             } else {  
  37.                 T ext = getExtension(name);  
  38.                 usrs.add(ext);  
  39.             }  
  40.         }  
  41.     }  
  42.     if (usrs.size() > 0) { // 这里留下的都是配置在default之后的  
  43.         exts.addAll(usrs); // 添加到default排序之后  
  44.     }  
  45.     return exts;  
  46. }  

 

总结

基本上将dubbo的扩展点加载机制学习了一遍,有几点可能需要注意的地方:

  • 每个ExtensionLoader实例只负责加载一个特定扩展点实现
  • 每个扩展点对应最多只有一个ExtensionLoader实例
  • 对于每个扩展点实现,最多只会有一个实例
  • 一个扩展点实现可以对应多个名称(逗号分隔)
  • 对于需要等到运行时才能决定使用哪一个具体实现的扩展点,应获取其自使用扩展点实现(AdaptiveExtension)
  • @Adaptive注解要么注释在扩展点@SPI的方法上,要么注释在其实现类的类定义上
  • 如果@Adaptive注解注释在@SPI接口的方法上,那么原则上该接口所有方法都应该加@Adaptive注解(自动生成的实现中默认为注解的方法抛异常)
  • 每个扩展点最多只能有一个被AdaptiveExtension
  • 每个扩展点可以有多个可自动激活的扩展点实现(使用@Activate注解)
  • 由于每个扩展点实现最多只有一个实例,因此扩展点实现应保证线程安全
  • 如果扩展点有多个Wrapper,那么最终其执行的顺序不确定(内部使用ConcurrentHashSet存储)

TODO:

  • 学习一下动态生成AdaptiveExtension类的实现过程 
    官方文档描述动态生成的AdaptiveExtension代码如下:
Java代码   收藏代码
  1. package <扩展点接口所在包>;  
  2.   
  3. public class <扩展点接口名>$Adpative implements <扩展点接口> {  
  4.     public <有@Adaptive注解的接口方法>(<方法参数>) {  
  5.         if(是否有URL类型方法参数?) 使用该URL参数  
  6.         else if(是否有方法类型上有URL属性) 使用该URL属性  
  7.         # <else 在加载扩展点生成自适应扩展点类时抛异常,即加载扩展点失败!>  
  8.   
  9.         if(获取的URL == null) {  
  10.             throw new IllegalArgumentException("url == null");  
  11.         }  
  12.   
  13.         根据@Adaptive注解上声明的Key的顺序,从URL获致Value,作为实际扩展点名。  
  14.         如URL没有Value,则使用缺省扩展点实现。如没有扩展点, throw new IllegalStateException("Fail to get extension");  
  15.   
  16.         在扩展点实现调用该方法,并返回结果。  
  17.     }  
  18.   
  19.     public <有@Adaptive注解的接口方法>(<方法参数>) {  
  20.         throw new UnsupportedOperationException("is not adaptive method!");  
  21.     }  
  22. }  

 规则如下:

  • 先在URL上找@Adaptive注解指定的Extension名;
  • 如果不设置则缺省使用Extension接口类名的点分隔小写字串(即对于Extension接口com.alibaba.dubbo.xxx.YyyInvokerWrapper的缺省值为String[] {“yyy.invoker.wrapper”})。
  • 使用默认实现(@SPI指定),如果没有设定缺省扩展,则方法调用会抛出IllegalStateException。    
  • 原文链接:[http://wely.iteye.com/blog/2304718]
相关文章
|
3月前
|
Dubbo Java 应用服务中间件
Dubbo服务暴露机制解密:深入探讨服务提供者的奥秘【九】
Dubbo服务暴露机制解密:深入探讨服务提供者的奥秘【九】
25 0
|
4月前
|
存储 负载均衡 Dubbo
深入理解Dubbo-4.Dubbo扩展SPI
深入理解Dubbo-4.Dubbo扩展SPI
76 1
|
4月前
|
Dubbo Java 应用服务中间件
微服务框架(十一)Dubbo调用拦截及参数校检扩展
  此系列文章将会描述Java框架Spring Boot、服务治理框架Dubbo、应用容器引擎Docker,及使用Spring Boot集成Dubbo、Mybatis等开源框架,其中穿插着Spring Boot中日志切面等技术的实现,然后通过gitlab-CI以持续集成为Docker镜像。   使用Dubbo框架时,面对自身的业务场景,需根据定制的需求编写SPI拓展实现,再根据配置来加载拓展点。
|
4月前
|
缓存 Dubbo Java
Dubbo 第三节_ Dubbo的可扩展机制SPI源码解析
Dubbo会对DubboProtocol对象进⾏依赖注⼊(也就是⾃动给属性赋值,属性的类型为⼀个接⼝,记为A接⼝),这个时候,对于Dubbo来说它并不知道该给这个属性赋什么值,换句话说,Dubbo并不知道在进⾏依赖注⼊时该找⼀个什么的的扩展点对象给这个属性,这时就会预先赋值⼀个A接⼝的⾃适应扩展点实例,也就是A接⼝的⼀个代理对象。在调⽤getExtension去获取⼀个扩展点实例后,会对实例进⾏缓存,下次再获取同样名字的扩展点实例时就会从缓存中拿了。Protocol是⼀个接。但是,不是只要在⽅法上加了。
|
1月前
|
设计模式 JSON Dubbo
超越接口:探索Dubbo的泛化调用机制
超越接口:探索Dubbo的泛化调用机制
29 0
|
2月前
|
Dubbo 网络协议 应用服务中间件
分布式微服务框架dubbo原理与机制
分布式微服务框架dubbo原理与机制
|
3月前
|
XML 负载均衡 Dubbo
了解Dubbo配置:优先级、重试和容错机制的秘密【五】
了解Dubbo配置:优先级、重试和容错机制的秘密【五】
42 0
|
4月前
|
Dubbo Java 应用服务中间件
微服务框架(十五)Dubbo 超时机制及服务降级
此系列文章将会描述Java框架Spring Boot、服务治理框架Dubbo、应用容器引擎Docker,及使用Spring Boot集成Dubbo、Mybatis等开源框架,其中穿插着Spring Boot中日志切面等技术的实现,然后通过gitlab-CI以持续集成为Docker镜像。 本文为Dubbo超时机制及服务降级 当服务出现创建超时的时候,TimeoutFilter会打印该创建记录的详细信息,日志级别为WARN,即为可恢复异常,或瞬时的状态不一致
|
6月前
|
Dubbo Java 应用服务中间件
阿里一面:说一说Java、Spring、Dubbo三者SPI机制的原理和区别
大家好,我是三友~~ 今天来跟大家聊一聊Java、Spring、Dubbo三者SPI机制的原理和区别。 其实我之前写过一篇类似的文章,但是这篇文章主要是剖析dubbo的SPI机制的源码,中间只是简单地介绍了一下Java、Spring的SPI机制,并没有进行深入,所以本篇就来深入聊一聊这三者的原理和区别。
|
7月前
|
Dubbo Java 应用服务中间件
JDK SPI、Spring SPI、Dubbo SPI三种机制的细节与演化
Java SPI(Service Provider Interface)是JDK提供的一种服务发现机制,用于在运行时动态加载和扩展应用程序中的服务提供者。
175 0