1-dubbo源码分析之扩展机制

一.概览

整体描述
  • dubbo利用spi扩展机制实现大量的动态扩展,要想充分了解dubbo的扩展机制,首先必须弄明白三个注解:
  • @SPI: 被此注解标记的接口,便是是可扩展的接口,实现类便是动态抓取的了
  • @Adaptive: 自适应扩展~ 该注解可以作用在“类”上,也可以作用在方法上,除了AdaptiveCompiler、AdaptiveExtensionFactory两个类之外,所有的都是注解在方法上。ExtensionLoader根据接口定义动态的生成适配器代码,并实例化这个生成的动态类。被Adaptive注解的方法会生成具体的方法实现。没备注街自然不能成为自适应了。
  • @Activate: 激活扩展~ 可以注解在需要注解在类上或者方法上,并注明被激活的条件及指明排序信息。使用场景最多的就是dubbo的拦截器这一块了。
  • 扩展核心类:ExtensionLoader 加载扩展点就是靠这个类了,加载方式后面详叙,先弄清楚几个主要的变量:
  • SERVICES_DIRECTORY: 定义SPI文件的扫描路径,dubbo源码中设置了好几个:META-INF/services/、META-INF/dubbo/、META-INF/dubbo/internal/; 至于这些文件夹的文件格式如何看下图:


    image.png
    image.png

    文件名为需要扩展接口全路径,文件内容为自配key-->实现类的全路径,很简单;

  • EXTENSION_LOADERS: 拓展点加载器的缓存 --> 接口类型和ExtensionLoader实例的映射关系

  • EXTENSION_INSTANCES: 拓展点的缓存

  • cachedClasses: 扩展文件中的 key --> class

  • cachedInstances: 扩展文件中的 key --> Hold<反射出来的intance>

  • cachedDefaultName: 接口SPI默认的实现名(就是把接口上填的默认值), 这个扩展优先级最高就在这里体现了


二.扩展点加载流程

1-获取ExtensionLoader
  • 测试方法作为入口
    @Test
    public void testAdaptiveExtension() throws Exception {
        SimpleExt ext = ExtensionLoader.getExtensionLoader(SimpleExt.class).getAdaptiveExtension();

        Map<String, String> map = new HashMap<String, String>();
        map.put("key", "impl_wxshi");
        URL url = new URL("p1", "1.2.3.4", 1010, "path", map);

        String echo = ext.yell(url, "haha");
        assertEquals("xxxxx", echo);
    }
  • 方法内第一行很明显就是获取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!");
       }
       /**
        * 这个类型必须加上SPI注解,否则报错
        */
       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中
            */
           EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
           loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
       }
       return loader;
   }
  • 详细很简单:
  • 1.判断要获取的扩展接口是注解@SPI
  • 2.从缓存中获取EXTENSION_LOADERS,若没有直接创建并且放入缓存,即一类接口扩展点只需创建一次EXTENSION_LOADER
  • 3.返回EXTENSION_LOADER用于下一步getAdaptiveExtension();
  • 这里首次加载扩展点肯定会创建ExtensionLoader了,这里也是个很重要的点,跟着上面的代码EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));进入构造器:

    /**
    * 默认初始化一个ExtensionFactory,这个类是用来获取扩展点实体的,
    * adaptive就是通过这个类取得扩展点,从而将请求委派给合适的实体完成请求
    * Adaptive生成返回给用户后,用户对方法发起调用,adaptive通过getExtension取得provider。getExtension会通过cachedInstances判断此provider是否已cache。没有的话则通过createExtension生成。
    */
    private ExtensionLoader(Class<?> type) {
        this.type = type;
        /**
         * type如果是ExtensionFactory类型,那么objectFactory是null,否则是ExtensionFactory类型的适配器类型
         */
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }
    
  • 构造器里面又有一行这样的代码:ExtensionLoader.getExtensionLoader(ExtensionFactory.class);可想而知这里也是获取自适应点ExtensionFactory.class 用以构造ExtensionLoader了。

  • 看看ExtensionFactory的接口和一个主要实现:

/**
 * ExtensionFactory自身也是个扩展点,它被SPI注解
 * 它有3个provider,分别是adaptive,spi和spring
 * ExtensionFactory是主要是用来加载被注入的类的实现,分为SpiExtensionFactory和SpringExtensionFactory两个,分别用来加载SPI扩展实现和Spring中bean的实现。
 */
@SPI
public interface ExtensionFactory {

    /**
     * Get extension.
     *
     * 获取指定类型的指定名称的扩展点
     *
     * @param type object type.
     * @param name object name.
     * @return object instance.
     */
    @Adaptive
    <T> T getExtension(Class<T> type, String name);
 }
/**
 * AdaptiveExtensionFactory
 * AdaptiveExtesnionFactory是dubbo自定义的adaptive,由于聚合了SpringExtensionFactory和SpiExtensionFactory,从而能对需要注入的对象的所有set方法进行注入
 */
@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {

    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);
    }

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

看见@SPI了,同时重点关注下 @Adaptive注解就明确了,这个注解在上面讲了,如果在类上,优先级最高,那取得自适应点就是这个AdaptiveExtensionFactory了,干的事情看后面的作用就知道了,获取扩展点的流程我们用自己的接口解释,这里基本都是一致的,继续往后划重点。


2-自定义扩展点加载
  • 主要进入测试方法中的第一句后半截getAdaptiveExtension()方法,看下图注释

    /**
     * 对于扩展类型是ExtensionFactory的,设置为null
     * getAdaptiveExtension方法获取一个运行时自适应的扩展类型
     * 每个Extension只能有一个@Adaptive类型的实现,如果么有,dubbo会自动生成一个类
     * objectFactory是一个ExtensionFactory类型的属性,主要用于加载需要注入的类型的实现
     * 这里记住非ExtensionFactory类型的返回的都是一个AdaptiveExtensionFactory
     */
    @SuppressWarnings("unchecked")
    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;
    }
    

    这里就是创建自定义自适应扩展点的入口了,很简单

    • 1.从缓存中获取
    • 2.获取不到直接创建,再加入缓存
  • 直接进入 cachedAdaptiveInstance.set(instance);方法,创建扩展点

    /**
     * 获取到适配器类的Class,利用反射创建适配器类的实例
     * 如果需要自己声明一个adaptive,一定要有个无参构造方法。其实除了wrapper外的所有provider都必须要有个无参构造方法
     */
    @SuppressWarnings("unchecked")
    private T createAdaptiveExtension() {
        try {
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e);
        }
    }
    
  • 此处主要点就出现了:Class<?> getAdaptiveExtensionClass();

    private Class<?> getAdaptiveExtensionClass() {
        /**
         * 触发SPI流程的扫描
         */
        getExtensionClasses();
    
        /**
         * 如果通过上面的步骤可以获取到cachedAdaptiveClass直接返回,如果不行的话,就得考虑自己进行利用动态代理创建一个了
         * 什么情况下不为空?当扩展类上打上@Adaptive注解的时候,就会将这个类直接返回。如果没有上注解,怎么办,就得自己生成了,也就是createAdaptiveExtensionClass
         */
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
    
        /**
         * 利用动态代理创建一个扩展类
         * 如果用户没有声明adaptive(cachedAdaptiveClass为空)则dubbo自动生成一个adaptive
         */
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }
    

    看注释一目了然,继续跟踪getExtensionClasses(),该方法没有返回值,从前面那么多缓存就可以看出,这个必然是出发了各种相关缓存的动作,为后面return作用。

  • 继续看重点:SPI流程的扫描 getExtensionClasses();

    /** 当cachedClasses为空时则开始load spi classes */
    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;
    }
    

    风格跟之前的一个入口完全相似,先从缓存中取,没有就创建再加缓存,那重点就在加载处了;

  • 继续跟进重点:loadExtensionClasses();

    // synchronized in getExtensionClasses
    private Map<String, Class<?>> loadExtensionClasses() {
        /**
         * 获取到类型的SPI注解,所以利用SPI扩展点的地方,需要加入SPI注解
         */
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if (defaultAnnotation != null) {
            String value = defaultAnnotation.value();
            if ((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));
                }
                /**
                 * 如果注解中有value,说明有默认的实现,那么将value放到cachedDefaultName中
                 */
                if (names.length == 1) cachedDefaultName = names[0];
            }
        }
    
        /**
         * 开始load spi文件了,这是整个过程中最为关键也重要的一部
         * 从下面的地址中加在这个类型的数据的extensionClasses中,地址包括
         * META-INF/dubbo/internal/
         * META-INF/dubbo/
         * META-INF/services/
         */
        Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
        loadDirectory(extensionClasses, DUBBO_DIRECTORY);
        loadDirectory(extensionClasses, SERVICES_DIRECTORY);
        return extensionClasses;
    }
    

    该方法做了两件事:
    - 1.获取默认的注解值(即@SPI(XXX))中的这个XXX,并缓存;
    - 2.扫描默认路径,加载扩展点

    还记得概览中的三个文件路径吗,这个就是主要的配置扩展点的文件路径了,dubbo会全局扫描所有该路径,遍历加载扩展点。至于加载出来的优先级,最后当然比不上类上配置@Adaptive了,但如果指明了key,那优先级也就不管了。

  • 看loadDirectory(Map<String, Class<?>> extensionClasses, String dir)

    /**
     * load spi文件
     * dubbo spi的文件位置位于META-INF/dubbo/, META-INF/dubbo/internal和META-INF/services/,文件名就是接口class的full name
     * 对所有文件按行循环,读取的每行内容,以”=”号做分割,前面的是扩展点名称,后面的是实体处理类full name
     */
    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);
        }
    }
    

    加载资源,获取urls,遍历解析扩展类,放入缓存,跟核心方法

  • loadResource(extensionClasses, classLoader, resourceURL);

     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 = line.substring(0, i).trim();
                                 line = line.substring(i + 1).trim();
                             }
                             if (line.length() > 0) {
                                 /**
                                  *  Class.forName(line, true, classLoader)-->加载解析出来的类的全限定名称
                                  */
                                 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);
         }
    }
    
    

无非就是解析配置项,获取扩展点类路径,通过类加载器加载,继续

  • loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);

    /** 开始进入loadAdaptive */
    private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
    
        /**
         * 判断是新加载clazz是否是type的子类,不是报错
         */
        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.");
        }
        /**
         * 如果class上有@Adaptive,则是一个adaptive,此时开始下一个循环
         * 注意这个点,查询type类型适配器类的时候会优先寻找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());
            }
        }
    
        /**
         * 如果不是则开始loadWrapper。判断wrapper的标准是class有没有一个参数为接口类型的构造参数。如果是wrapper则塞入到cachedWrapperClasses中
         * 不是wrapper时会抛出NoSuchMethodException异常,从而进入loadActivation和loadEntity的过程
         *
         * 如果这个扩展类没有打上@Adaptive注解就更有意思了。
         * 首先第一步会验证下有没有type这个类型作为入参的构造方法,为什么要这么做?因为Wrapper,有的类型需要包装一下,例如type=Protocol.class 就会看到有DubboProtocol真实的Protocal类
         * 还会有ProtocolFilterWrapper和ProtocolListenerWrapper这种Wrapper类,这种Wrapper类的共同点就是构造函数的入参是type类型,所以在解析的时候有这么一步
         * 如果有这种构造函数的就是Warpper类,将这些Warpper类型的数据放到cachedWrapperClasses这个集合中缓存
         * 如果没有这种类型的构造函数,就是正常的type类型的实例了
         *
         */
        else if (isWrapperClass(clazz)) {
            Set<Class<?>> wrappers = cachedWrapperClasses;
            if (wrappers == null) {
                cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
                wrappers = cachedWrapperClasses;
            }
            /**
             * 将此wrapper放入wrappers容器里面,也就是cachedWrapperClasses里面
             */
            wrappers.add(clazz);
        }
    
    
        /** 进入loadActivation和loadEntity的过程 */
        else {
    
            /** 到了这里,说明是个正常的类 */
            clazz.getConstructor();
    
            /**
             * 1.处理name为null的情况,为空时截取provider的simpleName的一部分作为name。即:如果在文件中没有声明这个扩展的名称(=左边的部分),就会根据这个类名创建一个名称
             * 2.上面提到过dubbo从三个文件夹,其中包括META-INF/services。熟悉jdk spi的对这个应该不陌生,这是jdk spi的默认加载路径,但是jdk声明provider只要声明一个full name就行。通过这一步dubbo spi就可以兼容jdk语法的spi了。
             */
            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);
                }
            }
    
            /**
             * 看下这个类上有没有Activate注解
             * 被@Activate声明的provider会被cache到cachedActivates里
             *
             *
             * 然后进入下一个环节@Activate数据的解析。
             * 1.查看type类上有没有@Activate注解,如果有的话,将名称与注解放到cachedActivates这个Map中进行缓存。
             * 2.将扩展类和名称放入cachedNames这个Map中进行缓存,将名称和扩展类的class放入传递进来的extensionClasses中
             * 3.最后这个extensionClasses会被返回出来被使用
             */
            String[] names = NAME_SEPARATOR.split(name);
            if (names != null && names.length > 0) {
                Activate activate = clazz.getAnnotation(Activate.class);
    
                //存在,就往cachedActivates里面添加名称与注解
                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);
                    /**
                     * 非wrapper和adaptive的class都会被put到extensionClasses里,它是个map,这个map会被返回,然后被cache到cachedClasses。
                     */
                    if (c == null) {
                        //最后往extensionClasses添加名称与class
                        extensionClasses.put(n, clazz);
                    } else if (c != clazz) {
                        throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
                    }
                }
            }
        }
    }
    

    注释很清楚,这里说明几个主要的点:

    • 1.判断当前类上是否有@Adaptive注解,如果有则记录下,因为毕竟次注解优先级最高,但是该注解既然是默认优先级,因此肯定只能存在一个,多了就不知道找谁了。
    • 2.是不是WrapperClass,从词义就知道这是一个包装类,其实类似于Spring的切面,将真正的扩展点类以构造方式包在其中,这样就可以实现一些额外逻辑了,即Wrapper 代理了扩展点。
    • 3.当然是解析正常类了,现获取类名,判断是否有@Activate注解,加入缓存等。
  • 到这里getExtensionClasses()就告一段落了,将刚才这里的出发点再贴一下,防止不能回头

 private Class<?> getAdaptiveExtensionClass() {
     /**
      * 触发SPI流程的扫描
      */
     getExtensionClasses();

     /**
      * 如果通过上面的步骤可以获取到cachedAdaptiveClass直接返回,如果不行的话,就得考虑自己进行利用动态代理创建一个了
      * 什么情况下不为空?当扩展类上打上@Adaptive注解的时候,就会将这个类直接返回。如果没有上注解,怎么办,就得自己生成了,也就是createAdaptiveExtensionClass
      */
     if (cachedAdaptiveClass != null) {
         return cachedAdaptiveClass;
     }

     /**
      * 利用动态代理创建一个扩展类
      * 如果用户没有声明adaptive(cachedAdaptiveClass为空)则dubbo自动生成一个adaptive
      */
     return cachedAdaptiveClass = createAdaptiveExtensionClass();
 }
往下走就会发现,如果cachedAdaptiveClass != null,直接返回,这个就是体现优先级的地方了。若没有,继续深入createAdaptiveExtensionClass()
  • 利用动态代理创建一个扩展类

     /**
      * 将类以字符串的形式拼接出来,然后利用编译器进行编译,返回编译后的class对象
      */
     private Class<?> createAdaptiveExtensionClass() {
         /**
          * 创建代码的字符串形式
          */
         String code = createAdaptiveExtensionClassCode();
         /**
          * 寻找类加载器
          */
         ClassLoader classLoader = findClassLoader();
         /**
          * 寻找Compiler的适配器扩展类
          */
         com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
         /**
          * 进行编译成Class的实例返回
          */
         return compiler.compile(code, classLoader);
     }
    

    核心就在那个code的解释了,这里的code形式如下:

     package com.alibaba.dubbo.common.extensionloader.ext1;
     import com.alibaba.dubbo.common.extension.ExtensionLoader;
     
     public class SimpleExt$Adaptive implements com.alibaba.dubbo.common.extensionloader.ext1.SimpleExt {
         
         public java.lang.String yell(com.alibaba.dubbo.common.URL arg0, java.lang.String arg1) {
             if (arg0 == null) throw new IllegalArgumentException("url == null");
             com.alibaba.dubbo.common.URL url = arg0;
             String extName = url.getParameter("key1", url.getParameter("key2", "impl1"));
             if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.common.extensionloader.ext1.SimpleExt) name from url(" + url.toString() + ") use keys([key1, key2])");
             com.alibaba.dubbo.common.extensionloader.ext1.SimpleExt extension = (com.alibaba.dubbo.common.extensionloader.ext1.SimpleExt)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.extensionloader.ext1.SimpleExt.class).getExtension(extName);
             return extension.yell(arg0, arg1);
         }
         
         public java.lang.String echo(com.alibaba.dubbo.common.URL arg0, java.lang.String arg1) {
             if (arg0 == null) throw new IllegalArgumentException("url == null");
             com.alibaba.dubbo.common.URL url = arg0;
             String extName = url.getParameter("simple.ext", "impl1");
             if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.common.extensionloader.ext1.SimpleExt) name from url(" + url.toString() + ") use keys([simple.ext])");
             com.alibaba.dubbo.common.extensionloader.ext1.SimpleExt extension = (com.alibaba.dubbo.common.extensionloader.ext1.SimpleExt)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.extensionloader.ext1.SimpleExt.class).getExtension(extName);
             return extension.echo(arg0, arg1);
         }
         public java.lang.String bang(com.alibaba.dubbo.common.URL arg0, int arg1) {
             throw new UnsupportedOperationException("method public abstract java.lang.String com.alibaba.dubbo.common.extensionloader.ext1.SimpleExt.bang(com.alibaba.dubbo.common.URL,int) of interface com.alibaba.dubbo.common.extensionloader.ext1.SimpleExt is not adaptive method!");
         }
     }
    

code还是字符串形式,那后面的代码就清楚了

  • 1.寻找类加载器

  • 2.再利用自适应扩展点(javassist)寻找编译器Compiler

  • 3.将字符串code生成具体的类,这个类就是最终的扩展点代理类啦。
    对于第二点为啥是javassist,看下图:

    /**
     * Compiler. (SPI, Singleton, ThreadSafe)
     */
    @SPI("javassist")
    public interface Compiler {
    
        /**
         * Compile java source code.
         *
         * @param code        Java source code
         * @param classLoader classloader
         * @return Compiled class
         */
        Class<?> compile(String code, ClassLoader classLoader);
    }
    

    该@SPI("javassist")应该足以说明默认取的是哪个代理器了。

  • 到此扩展点的代理就产生了,后面执行具体方法的时候,执行的实际是代理的方法体,再去执行具体逻辑,看字节码就清楚了执行过程。

3.执行具体的方法
  • String echo = ext.echo(url, "haha");其实执行的代理的如下方法体,看上图字节码

     public java.lang.String echo(com.alibaba.dubbo.common.URL arg0, java.lang.String arg1) {
            if (arg0 == null) throw new IllegalArgumentException("url == null");
            com.alibaba.dubbo.common.URL url = arg0;
            String extName = url.getParameter("simple.ext", "impl1");
            if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.common.extensionloader.ext1.SimpleExt) name from url(" + url.toString() + ") use keys([simple.ext])");
            com.alibaba.dubbo.common.extensionloader.ext1.SimpleExt extension = (com.alibaba.dubbo.common.extensionloader.ext1.SimpleExt)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.extensionloader.ext1.SimpleExt.class).getExtension(extName);
            return extension.echo(arg0, arg1);
    }
    

看到执行逻辑如下:
- 1.获取扩展点的Extensionloader,之前的流程中已经缓存了,所以直接获取
- 2.调用getExtension(extName)方法获取具体的扩展点,这个就是最后获取真正代理的时候了

  • 获取实际扩展点的真正代理

    /**
     * Find the extension with the given name. If the specified name is not found, then {@link IllegalStateException}
     * will be thrown.
     */
    @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<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) {
                    instance = createExtension(name);
    
                    /** 缓存 */
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }
    

    重点代码就是instance = createExtension(name); 返回instance,且缓存。继续跟踪:

    /**
     * 1.getExtensionClasses在cachedClasses有值时直接返回cachedClasses。上一节介绍过,loadFile结束后会把provider calss都cache到cachedClasses上
     * 2.通过cachedClasses就可以获得name对应的provider了
     * 3.国际惯例还是要inject
     * 4.存在wrapper的话则迭代包装,上一节也介绍过,先定义的包装后定义的。
     */
    @SuppressWarnings("unchecked")
    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, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            injectExtension(instance);
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
                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);
        }
    }
    

    风格完全不变,依然先尝试从缓存中获取,没有则EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
    这里就根据具体的class newInstance()出一个对象了,这个对象就是指定的具体的实际调用对象了。
    这里还有一个小的点,就是注释描述的那样,inject + wrapper,wrapper前面介绍过,inject如下:

    /**
     * 如果适配器类有属性的set方法,会自动注入
     * 生成provider后 通过injectExtension对其进行注入。注入是通过ExtensionFactory完成的
     * */
    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;
    }
    
  • 最后返回的instance就执行具体的方法。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容