顺着dubbo入口撸ExtensionLoader源码

参考

属于在这篇Dubbo扩展点加载基础上的展开学习。但原文有点小问题(Container启动那里),所以本文直接按自己的理解来组织。不加说明的引用都来自该文。

问题

Dubbo的原理、核心的概念很多文章都有详细的介绍(什么SPI、扩展点、Adaptive、ExtensionLoader等等),但是我的问题是它们是如何运行、如何起作用、整体的流程是什么?

1. Dubbo入口

执行Dubbo,实际上是执行了com.alibaba.dubbo.container.Main.main

Main这个类中有一些静态成员变量,最重要的是这个:

private static final ExtensionLoader<Container> loader = ExtensionLoader.getExtensionLoader(Container.class);

从这里开始使用ExtensionLoader。

2. 初始化Main的静态成员,获得Container类型的ExtensionLoader

ExtensionLoader类中有个静态的final变量EXTENSION_LOADERS缓存了各种类型的ExtensionLoader,如果已有缓存,就直接获取,如果没有就调用new方法新建。

启动时getExtensionLoader(Container.class)获取Container类型的ExtensionLoader肯定不存在,去构造:

private ExtensionLoader(Class<?> type) {
        this.type = type;
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }

ExtensionLoader的构造方法中可以看到,ExtensionLoader有两个属性,一个是type,标识了这个ExtensionLoader所属的类型,另一个是ObjectFactory,类型是ExtensionFactory

那么,ExtensionLoader<Container>this.type=Container.class
而根据构造函数,欲要得到Container.ClassObjectFactory,须先得到ExtensionFactory类型的ExtensionLoader

2.1 获取ExtensionFactory类型的ExtensionLoader

同样没有,去构造,根据构造函数ExtensionLoader<ExtensionFactory>this.type=ExtensionFactory.ClassObjectFactory=null
继续。

2.2 获取ExtensionFactory的扩展实现:getAdaptiveExtension()

Dubbo的微内核做得非常的彻底,ExtensionFactory也是一个扩展点,也需要通过ExtensionLoader<ExtensionFactory>加载

因为(Container的)ObjectFactoryExtensionFactory类型,而ExtensionFactory是一个扩展点,那么就要用ExtensionFactory.ClassExtensionLoader通过getAdaptiveExtension()获取到ExtensionFactory.Class的Adaptive实现,再给到(Container的)ObjectFactory

getAdaptiveExtension()中,首先也是试图从ExtensionLoader<ExtensionFactory>的一个缓存cachedAdaptiveInstance中取,取不到就调用createAdaptiveExtension()创建。

2.2.1 创建ExtensionFactory扩展: createAdaptiveExtension()

逻辑就一句:

injectExtension((T) getAdaptiveExtensionClass().newInstance());

先看 getAdaptiveExtensionClass()

2.2.1.1 获取ExtensionFactory扩展类:getAdaptiveExtensionClass()

private Class<?> getAdaptiveExtensionClass() {
        getExtensionClasses();
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }

这里整体的逻辑是,先getExtensionClasses(),看看有没有已经存在的扩展实现类,有的话取到这些类,把标记了@Adaptive的缓存到cachedAdaptiveClass(比如说ExtensionFactory就是有的),把没有标记@Adaptive的扩展实现类缓存到cachedClasses,返回。

如果没有的话,就调用createAdaptiveExtensionClass()现生成code并compile一个Adaptive类出来,同样缓存到cachedAdaptiveClass,返回。

Adaptive注解可以标记在类上,也可以标记在方法上。
如果Adaptive类选择扩展点实现的依据是根据上下文信息,即URL中的信息选择不同的实现,那么可以把Adaptive注解标记在服务的方法上,ExtensionLoader可以自动生成这种情况的Adaptive类。
如果是自己实现Adaptive类,那么需要在类上标记Adaptive注解,ExtensionLoader在加载扩展点时就能发现并创建Adaptive实现。

(来自Dubbo源码解析-2:Adaptive类,同样很厉害的文章嗯,本文对其也多有引用不一一列举了)

下面详细分析一下这两步。

2.2.1.1.1 获取已存在的扩展类:getExtensionClasses()

首先检查ExtensionLoader<ExtensionFactory>的缓存cachedClasses,有就get没有就调用loadExtensionClasses()创建。

  • loadExtensionClasses()
    首先去拿SPI注解,如果注解不为null,获取注解的value:
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
...
String value = defaultAnnotation.value();

ExtensionFactory定义如下:

@SPI
public interface ExtensionFactory {

    /**
     * Get extension.
     * 
     * @param type object type.
     * @param name object name.
     * @return object instance.
     */
    <T> T getExtension(Class<T> type, String name);

}

SPI注解类定义如下:

public @interface SPI {

    /**
     * 缺省扩展点名。
     */
    String value() default "";

}

可以看到ExtensionFactory没有定义缺省扩展点名(后面会看到Container类有缺省定义)。

有缺省扩展点名就缓存到ExtensionLoader<ExtensionFactory>的另一个缓存cachedDefaultName,没有就继续。

继续,调用loadFile装载扩展实现类。

Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
loadFile(extensionClasses, DUBBO_DIRECTORY);
loadFile(extensionClasses, SERVICES_DIRECTORY);
return extensionClasses;
  • loadFile(Map<String, Class<?>> extensionClasses, String dir)

loadFile首先在指定目录下找名字是type的配置文件,然后读出来:

String fileName = dir + type.getName();

对 于 ExtensionFactory来 说 , 会读到:

//META-INF/dubbo/internal/com.alibaba.dubbo.common.extension.ExtensionFactory
adaptive=com.alibaba.dubbo.common.extension.factory.AdaptiveExtensionFactory
spi=com.alibaba.dubbo.common.extension.factory.SpiExtensionFactory
//META-INF/dubbo/internal/com.alibaba.dubbo.common.extension.ExtensionFactory
spring=com.alibaba.dubbo.config.spring.extension.SpringExtensionFactory

随后:

  1. loadfile读入第一行数据,初始化:name=adaptive, line = com.alibaba.dubbo.common.extension.factory.AdaptiveExtensionFactory

  2. 使用Class<?> clazz = Class.forName(line, true, classLoader)加载类。

  3. !type.isAssignableFrom(clazz):验证加载的类是否是当前type的一个实现。

  4. clazz.isAnnotationPresent(Adaptive.class):检查如果设置了@Adaptive,则把clazz保存在cachedAdaptiveClass
    对于ExtensionFactory来说,AdaptiveExtensionFactory设置了@Adaptive注解:

@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {
...
}
  1. 对于没有设置@Adaptive的类,则存入loadExtensionClasses()传到loadFile()中的参数extensionClasses,返回后在getExtensionClasses() 中赋给cachedClasses缓存。
classes = loadExtensionClasses();
cachedClasses.set(classes);

对于ExtensionFactory来说,SpiExtensionFactorySpringExtensionFactory都没有设置@Adaptive注解(同一个类只能有一个Adaptive实现),所以都被存入了ExtensionLoader<ExtensionFactory>cachedClasses

2.2.1.1.2 动态生成没有自己实现的Adaptive类:createAdaptiveExtensionClass()

执行完getExtensionClasses(),回到了2.2.1.1的getAdaptiveExtensionClass(),如果此时cachedAdaptiveClass仍为null,说明没有找到标记了@Adaptive的类,需要根据上下文动态生成。

private Class<?> getAdaptiveExtensionClass() {
        getExtensionClasses();
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }

动态生成的过程简单来说就是找到标记了@Adaptive的方法,根据type通过StringBuilder拼接一个类,加载Compiler编译。

前面提到的参考文章(Dubbo源码解析-2:Adaptive类)里举了例子,可以看到生成了什么样子的代码。这里不详细说了。

2.2.1.2 为扩展注入依赖的其他扩展实现:injectExtension(T instance)

好的终于回来了,2.2.1.1节完成了getAdaptiveExtensionClass(),并返回了cachedAdaptiveClass。 为这个cachedAdaptiveClassnew了一个Instance以后,开始injectExtension注入。

扩展点注入的代码如下,首先查找以set开头、带一个参数的public方法,如果set方法的名称大于3,则根据名称获取参数扩展点的名称,然后获取扩展点实现的实例,这可能又是一个次配置的解析和实例化过程。最后调用set方法将实例设置进去。

private T injectExtension(T instance) {
        try {
            if (objectFactory != null) {
                for (Method method : instance.getClass().getMethods()) {
                    if (method.getName().startsWith("set")&&...) {
                        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) {
                            ...
                        }
                    }
                }
            }
        } catch (Exception e) {
           ...
        }
        return instance;
    }

对于ExtensionFactory来说,首先判断,if (objectFactory != null),则可以直接返回cachedAdaptiveClass所new的instance了。

2.2.2 ExtensionLoader<Container>构造完毕

整个2.2.1过程结束,拿到了ExtensionFactory.class的扩展实现,也就是所返回的cachedAdaptiveClass,即AdaptiveExtensionFactory

private ExtensionLoader(Class<?> type) {
        this.type = type;
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }

回到构造函数,ExtensionLoader<Container>objectFactory =AdaptiveExtensionFactory

ExtensionLoader<Container>构造完毕。

3. 在main()中调用loader.getExtension()加载Container的扩展实现

Main函数的整体逻辑是,如果没有传入参数,就装载Container的SPI注解指定的默认Container,如果传入的参数(main函数参数、JVM启动参数、classpath下的dubbo.properties配置等)指定了Container,就装载通过参数指定的Container。

public class Main {
    public static final String CONTAINER_KEY = "dubbo.container";
    private static final ExtensionLoader<Container> loader = ExtensionLoader.getExtensionLoader(Container.class);
    ...    
    public static void main(String[] args) {
        try {
            if (args == null || args.length == 0) {
                String config = ConfigUtils.getProperty(CONTAINER_KEY, loader.getDefaultExtensionName());
                args = Constants.COMMA_SPLIT_PATTERN.split(config);
            }
            
            final List<Container> containers = new ArrayList<Container>();
            for (int i = 0; i < args.length; i ++) {
                containers.add(loader.getExtension(args[i]));
            }
            ...
            for (Container container : containers) {
                container.start();
               ...
            }
            ...
    }
    
}

首先看Container类的定义:

@SPI("spring")
public interface Container {
    
    /**
     * start.
     */
    void start();
    
    /**
     * stop.
     */
    void stop();

}

它将SPI的value值设置Spring,也就是指定默认的扩展实现名称是spring(默认情况只加载一个spring容器)。

再看关于Container的配置(以spring为例,其他jetty、log4j等都类似):

\\META-INF/dubbo/internal/com.alibaba.dubbo.container.Container
spring=com.alibaba.dubbo.container.spring.SpringContainer

而SpringContainer类没有加注解什么的,所以这里是通过SPI注解的默认值控制的,和ExtensionFactory的机制不同。

下面详细分析main函数加载Container扩展点过程。

3.1 如果没有传参,获得默认扩展名:loader.getDefaultExtensionName()

    public String getDefaultExtensionName() {
        getExtensionClasses();
        return cachedDefaultName;
    }

这里调用了getExtensionClasses(),与2.2.1.1.1节介绍的过程相同。

不同的是,对于ExtensionLoader<Container>,通过SPI注解定义了缺省扩展点名spring,因此会将spring缓存到cachedDefaultName

而由于Container的扩展实现类都没有设置@Adaptive,则这些实现类都被缓存在cachedClasses中,不会被缓存在cachedAdaptiveClass

3.2 获得Container:loader.getExtension()

  • getExtension()
    先去缓存cachedInstances中找,如果没有,则调用createExtension创建。
public T getExtension(String name) {
        ...
        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;
    }
  • createExtension()
    通过getExtensionClasses()获得扩展实现类,调用injectExtension注入依赖的其他扩展和包装类。返回获取了包装后的扩展instance。
private T createExtension(String name) {
        Class<?> clazz = getExtensionClasses().get(name);
        ...
        try {
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
           ...
            injectExtension(instance);
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (wrapperClasses != null && wrapperClasses.size() > 0) {
                for (Class<?> wrapperClass : wrapperClasses) {
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }
            return instance;
        } catch (Throwable t) {
            ...
        }
    }
  • getExtensionClasses()

其实是从获取之前load到cachedClassed中的所有扩展实现类。没有的话就调用loadExtensionClasses()(见2.2.1.1.1节)获取。

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

3.3 获取Container扩展后,启动

扩展加载结束,启动Container~

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

推荐阅读更多精彩内容