dubbo(一)SPI机制与实现路径

2020-09-18 15:11:40 浏览数 (1)

一、dubbo启动流程

了解dubbo内核之前,我们先看下dubbo的启动流程,参考了本地启动时日志打印的dubbo启动整理出来的流程图:

(1)启动provider时,会把所有暴露的接口注册到注册中心,并会订阅动态配置(configuration)。

(2)启动consumer时,会订阅服务端接口、动态配置、负载均衡集群(providers、configuration、routers)。

(3)provider:订阅的动态配置信息变更时,注册中心会推送动态配置信息(configuration)给provider。

consumer:订阅内容变更时,会推送订阅的信息(providers、configuration、routers)给consumer。

(4)客户端与服务端建立长连接,进行数据通讯。

(5)客户端与服务端启动后,后台会启动定时器,每隔一定的时间发送统计数据给monitor。

二、JDK标准的-SPI

在面向对象中,模块之间都是基于接口的,模块之间如果其中一个模块或接口实现需要进行更改,则就需要修改代码。为了实现不对模块或实现类之间不进行硬编码,即不在模块里写死代码,就可以使用spi这种服务发现机制。jdk中就提供了这种标准的spi机制,将模块实现移到代码之外。

· jdk-spi的具体约定

当服务的提供者提供了一个接口多种实现时,一般在jar包的META-INF/services/目录下,创建该接口的同名文件,该文件里的内容就是该服务接口的具体实现类的名称。

当外部加载这个模块的时候,就能通过jar包META-INF/services/里的配置文件得到具体的实现类名,并加载与实例化,完成模块之间的装配。

· jdk中使用spi的例子

mysql的Driver驱动的加载就使用到了spi机制。如下图,在META-INF中配置了Driver的实现类名为:com.mysql.cj.jdbc.Driver。

代码语言:javascript复制
com.mysql.cj.jdbc.Driver

jdk的spi通过ServiceLoader.load进行加载。虽然Driver的接口很多,但是META-INF中配置的类名只有一个,因此只会对com.mysql.cj.jdbc.Driver实现类进行加载。

代码语言:javascript复制
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);

· jdk标准的SPI机制的缺点

JDK标准的SPI会一次性实例化扩展点所有实现,因此不管是否用到这些实现类都会加载META-INF中配置的所有实现类,比较耗时。

dubbo内核中,也使用了spi机制,但是内部对spi的实现进行了修改来保证性能。

三、dubbo内核-SPI

· dubbo-spi的具体约定

spi文件的存储路径在META-INF/dubbo/internal目录下。

spi文件内容定义为:扩展名=具体的类型。如下图所示:

代码语言:javascript复制
dubbo=org.apache.dubbo.registry.dubbo.DubboRegistryFactory

dubbo-spi比jdk-spi的内容定义中多了扩展名,即dubbo对spi的实现类进行加载时,会根据key即扩展名对需要用到的spi实现类进行加载。

spi实现类则是通过ExtensionLoader进行加载。

· spi实现路径

(1)getExtensionLoader获取ExtensionLoader。

可以通过ExtensionLoader.getExtensionLoader(Class<T> type)来为type接口new一个ExtensionLoader,并缓存起来。

我们可以看dubbo容器Container启动时的main方法中的关键一行代码:

代码语言:javascript复制
public class Main {
    private static final ExtensionLoader<Container> loader             = ExtensionLoader.getExtensionLoader(Container.class);
代码语言:javascript复制
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
    //...    //判断缓存中是否存在 loader    ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    if (loader == null) {        //new出一个loader,到缓存中        EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
        loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    }
    return loader;
}

· ExtensionLoader构造函数

代码语言:javascript复制
private ExtensionLoader(Class<?> type) {
    this.type = type;
    objectFactory = (type == ExtensionFactory.class ? null        : ExtensionLoader.getExtensionLoader(ExtensionFactory.class)                                                .getAdaptiveExtension());
}

ExtensionLoader内部存放了type即Container接口名,与objectFactory即ExtensionFactory,后续可通过extensionFactory获取Container接口的实现。

ExtensionLoader.getExtensionLoader(Class<T> type)执行完毕,就表示Container接口的ExtensionLoader被创建了出来并被放置在了缓存中,并且ExtensionLoader内部完成了type和extensionFactory属性的初始化。

· ExtensionFactory的作用

可通过getExtension为dubbo的IOC提供所有对象。并且ExtensionFactory的objectFactory为null。

(2)getAdaptiveExtension获取扩展装饰类对象

被@Adaptive注解修饰的类就是扩展装饰类,点开注解后我们可以看到,@Adaptive注解只能修饰类和方法。

阅读getAdaptiveExtension源码:

代码语言:javascript复制
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;
}

上述代码主要为从缓存中获取adaptiveExtension,如果不存在则创建,创建成果后再放入到缓存中。

· createAdaptiveExtension

代码语言:javascript复制
private T createAdaptiveExtension() {
    try {
        return injectExtension((T) getAdaptiveExtensionClass().newInstance());
    } catch (Exception e) {
        throw new IllegalStateException("Can not create adaptive extension ");
    }
}

(1)getAdaptiveExtensionClass

代码语言:javascript复制
代码语言:javascript复制
private Class<?> getAdaptiveExtensionClass() {
    getExtensionClasses();
    if (cachedAdaptiveClass != null) {
        return cachedAdaptiveClass;
    }
    return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

第一步:getExtensionClasses中会加载SPI中的实现类。

代码语言:javascript复制
private Map<String, Class<?>> loadExtensionClasses() {
//...//加载META-INF/dubbo/internal/接口名 SPI文件中的实现类。    loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
    return extensionClasses;
}
代码语言:javascript复制
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
    //...    //SPI实现类 是否包含adaptive注解    if (clazz.isAnnotationPresent(Adaptive.class)) {
        if (cachedAdaptiveClass == null) {            //对cachedAdaptiveClass赋值            cachedAdaptiveClass = clazz;
            //...    //构造函数中是否包含目标接口    } else if (isWrapperClass(clazz)) {
        Set<Class<?>> wrappers = cachedWrapperClasses;
        if (wrappers == null) {
            cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
            wrappers = cachedWrapperClasses;
        }        //存在这样的实现类则放到wrappers中            wrappers.add(clazz);
    } else {
        //...
        if (names != null && names.length > 0) {            //包含activate注解的实现类存放在cachedActiviates中            Activate activate = clazz.getAnnotation(Activate.class);
            if (activate != null) {
                cachedActivates.put(names[0], activate);
            } else {
                com.alibaba.dubbo.common.extension.Activate oldActivate = clazz.getAnnotation(com.alibaba.dubbo.common.extension.Activate.class);
                if (oldActivate != null) {
                    cachedActivates.put(names[0], oldActivate);
                }
            }
            for (String n : names) {                //剩余的类就放在cachedNames                if (!cachedNames.containsKey(clazz)) {
                    cachedNames.put(clazz, n);
                }
                //...
            }
        }
    }
}

上述源码中会加载type-目标接口位于META-INF/dubbo/internal/接口名下的SPI实现类,并会根据对应策略将实现内容放在不同的缓存变量中。

如果实现类中包含了adapive注解修饰的类,则会把该目标接口放到cachedAdaptiveClass中,例如ExtensionFactory。

如果实现类中的构造函数中包含了目标接口,则会将实现类放到cachedWrapperClasses集合中。

如果实现类类中包含了activate注解修饰的类,则会把实现类放到cachedActivates中。

剩余其他的类,则放在cachedNames中。

代码语言:javascript复制
private Class<?> getAdaptiveExtensionClass() {    //加载目标接口的SPI实现类
    getExtensionClasses();
    if (cachedAdaptiveClass != null) {        //cachedAdaptiveClass在加载时被赋值了则直接返回        return cachedAdaptiveClass;
    }    //否则创建
    return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

(2)createAdaptiveExtensionClass

生成一个动态的adaptive类。

代码语言:javascript复制
private Class<?> createAdaptiveExtensionClass() {
    String code = createAdaptiveExtensionClassCode();
    ClassLoader classLoader = findClassLoader();
    org.apache.dubbo.common.compiler.Compiler compiler= ExtensionLoader.getExtensionLoader(Compiler.class).getAdaptiveExtension();
    return compiler.compile(code, classLoader);
}

这里和动态代理有点相似,生成了一个动态的adaptive类。类名则为<目标接口名>$Adaptive implements <目标接口>。

动态类中的方法,只有方法被@Adaptive修饰的方法才会实现。没有被修饰的方法则无法实现。

· injectExtension

代码语言:javascript复制
private T injectExtension(T instance) {
    //...

Object object = objectFactory.getExtension(pt, property);

代码语言:javascript复制
    //...
代码语言:javascript复制
    return instance;
}

在injectExtension中进行dubbo的IOC,本质上就是从spi和spring中获取对象进行赋。

SpiExtensionFactory.getExtension和SpringExtensionFactory.getExtension。

@Adaptive注解总结

官方文档中解释为:Adaptive可注解在类或方法上。当注解在类上时,dubbo不会为该类生成代理类,表示扩展的加载逻辑由人工编码完成。当注解在方法上时,dubbo会为该方法生成代理逻辑,表示拓展的加载逻辑需由框架自动生成。

(1)注解在接口上

在调用getAdaptiveExtension方法时,直接返回该类,然后执行IOC。

(2)注解在方法上

在调用getAdaptiveExtension方法时,会生成代理类,然后执行IOC。代码模板如下:

(3)getExtension获取对象

代码语言:javascript复制
public T getExtension(String 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;
}
代码语言:javascript复制
private T createExtension(String name) {    //从缓存中获取class对象,即对META-INF进行加载时会放到缓存中    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);
        }        //进行dubbo的IOC set注入        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);
    }
}

四、dubbo-spi实现路径总结

(1)通过getExtensionLoader获取ExtensionLoader。

(2)通过getAdaptiveExtension获取扩展装饰类对象,动态生成装饰类或动态代理类,进行依赖注入。

(3)最后可通过ExtensionLoader.getExtension(name)获取对象。

就是这么简单。

· getExtension中所用到的设计模式

(1)被@Adaptive修饰的类,使用了装饰者模式。

(2)被@Adaptive修饰的方法,生成了动态的Adaptive类,使用了动态代理模式。

(3)使用缓存确保只生成一个类,使用了单例模式。

(4)objectFactory将对象new出来的过程隐藏在工厂中,使用了工厂模式。

(5)injectExtension中使用了IOC,还有资料说试用了AOP,这个AOP我没看出来。

0 人点赞