您当前的位置: 首页 >  dubbo

庄小焱

暂无认证

  • 2浏览

    0关注

    805博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

Dubbo——扩展(SPI)加载原理

庄小焱 发布时间:2021-04-10 16:36:23 ,浏览量:2

摘要

Dubbo 为了更好地达到 OCP 原则(即“对扩展开放,对修改封闭”的原则),采用了“微内核+插件”的架构。那什么是微内核架构呢?微内核架构也被称为插件化架构(Plug-in Architecture),这是一种面向功能进行拆分的可扩展性架构。内核功能是比较稳定的,只负责管理插件的生命周期,不会因为系统功能的扩展而不断进行修改。功能上的扩展全部封装到插件之中,插件模块是独立存在的模块,包含特定的功能,能拓展内核系统的功能。微内核架构中,内核通常采用 Factory、IoC、OSGi 等方式管理插件生命周期,Dubbo 最终决定采用 SPI 机制来加载插件,Dubbo SPI 参考 JDK 原生的 SPI 机制,进行了性能优化以及功能增强。

一、SPI 机制原理

当服务的提供者提供了一种接口的实现之后,需要在 Classpath 下的 META-INF/services/ 目录里创建一个以服务接口命名的文件,此文件记录了该 jar 包提供的服务接口的具体实现类。当某个应用引入了该 jar 包且需要使用该服务时,JDK SPI 机制就可以通过查找这个 jar 包的 META-INF/services/ 中的配置文件来获得具体的实现类名,进行实现类的加载和实例化,最终使用该实现类完成业务功能。

package com.zhuangxiaoyan.dubbo;

import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.SPI;

/**
 * @Classname Car
 * @Description TODO
 * @Date 2021/12/8 23:00
 * @Created by xjl
 */
@SPI
public interface Car {

    //    @Adaptive
    public void test(URL url);
}
package com.zhuangxiaoyan.dubbo;

import org.apache.dubbo.common.URL;

/**
 * @Classname CarImpl
 * @Description TODO
 * @Date 2021/12/8 23:01
 * @Created by xjl
 */
public class CarImpl implements Car {

    @Override
    public void test(URL url) {
        System.out.println("SPI Test ……");
    }
}
package com.zhuangxiaoyan.dubbo;

import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.ExtensionLoader;

import java.net.MalformedURLException;


/**
 * @Classname SpiTest
 * @Description TODO
 * @Date 2021/12/8 23:00
 * @Created by xjl
 */
public class SpiTest {
    public static void main(String[] args) throws MalformedURLException {
        ExtensionLoader extensionLoader= ExtensionLoader.getExtensionLoader(Car.class);

        URL url=new URL("http","localhost", 8080);

        Car car=extensionLoader.getExtension("car");
        car.test(null);
    }
}
car=com.zhuangxiaoyan.dubbo.CarImpl
二、JDK SPI 源码分析

我们可以看到 JDK SPI 的入口方法是 ServiceLoader.load() 方法,在 ServiceLoader.load() 方法中,首先会尝试获取当前使用的 ClassLoader(获取当前线程绑定的 ClassLoader,查找失败后使用 SystemClassLoader),然后调用 reload() 方法,调用关系如下图所示:

在 reload() 方法中,首先会清理 providers 缓存(LinkedHashMap 类型的集合),该缓存用来记录 ServiceLoader 创建的实现对象,其中 Key 为实现类的完整类名,Value 为实现类的对象。之后创建 LazyIterator 迭代器,用于读取 SPI 配置文件并实例化实现类对象。

ServiceLoader.reload() 方法的具体实现,如下所示:

// 缓存,用来缓存 ServiceLoader创建的实现对象 

private LinkedHashMap providers = new LinkedHashMap(); 

public void reload() { 

    providers.clear(); // 清空缓存 

    lookupIterator = new LazyIterator(service, loader); // 迭代器 

} 

在前面的示例中,main() 方法中使用的迭代器底层就是调用了 ServiceLoader.LazyIterator 实现的。Iterator 接口有两个关键方法:hasNext() 方法和 next() 方法。这里的 LazyIterator 中的next() 方法最终调用的是其 nextService() 方法,hasNext() 方法最终调用的是 hasNextService() 方法,调用关系如下图所示:

首先来看 LazyIterator.hasNextService() 方法,该方法主要负责查找 META-INF/services 目录下的 SPI 配置文件,并进行遍历,大致实现如下所示:

private static final String PREFIX = "META-INF/services/"; 

Enumeration configs = null; 

Iterator pending = null; 

String nextName = null; 

private boolean hasNextService() { 

    if (nextName != null) { 

        return true; 

    } 

    if (configs == null) { 

        // PREFIX前缀与服务接口的名称拼接起来,就是META-INF目录下定义的SPI配 

        // 置文件(即示例中的META-INF/services/com.xxx.Log) 

        String fullName = PREFIX + service.getName(); 

        // 加载配置文件 

        if (loader == null) 

            configs = ClassLoader.getSystemResources(fullName); 

        else 

            configs = loader.getResources(fullName); 

    } 

    // 按行SPI遍历配置文件的内容 

    while ((pending == null) || !pending.hasNext()) {  

        if (!configs.hasMoreElements()) { 

            return false; 

        } 

        // 解析配置文件 

        pending = parse(service, configs.nextElement());  

    } 

    nextName = pending.next(); // 更新 nextName字段 

    return true; 

}

在 hasNextService() 方法中完成 SPI 配置文件的解析之后,再来看 LazyIterator.nextService() 方法,该方法负责实例化 hasNextService() 方法读取到的实现类,其中会将实例化的对象放到 providers 集合中缓存起来,核心实现如下所示:

private S nextService() { 

    String cn = nextName; 

    nextName = null; 

    // 加载 nextName字段指定的类 

    Class c = Class.forName(cn, false, loader); 

    if (!service.isAssignableFrom(c)) { // 检测类型 

        fail(service, "Provider " + cn  + " not a subtype"); 

    } 

    S p = service.cast(c.newInstance()); // 创建实现类的对象 

    providers.put(cn, p); // 将实现类名称以及相应实例对象添加到缓存 

    return p; 

} 

以上就是在 main() 方法中使用的迭代器的底层实现。最后,我们再来看一下 main() 方法中使用ServiceLoader.iterator() 方法拿到的迭代器是如何实现的,这个迭代器是依赖 LazyIterator 实现的一个匿名内部类,核心实现如下:

public Iterator iterator() { 

    return new Iterator() { 

        // knownProviders用来迭代providers缓存 

        Iterator knownProviders 

            = providers.entrySet().iterator(); 

        public boolean hasNext() { 

            // 先走查询缓存,缓存查询失败,再通过LazyIterator加载 

            if (knownProviders.hasNext())  

                return true; 

            return lookupIterator.hasNext(); 

        } 

        public S next() { 

            // 先走查询缓存,缓存查询失败,再通过 LazyIterator加载 

            if (knownProviders.hasNext()) 

                return knownProviders.next().getValue(); 

            return lookupIterator.next(); 

        } 

        // 省略remove()方法 

    }; 

} 
三、Dubbo SPI机制原理
  • 扩展点:通过 SPI 机制查找并加载实现的接口(又称“扩展接口”)。前文示例中介绍的 Log 接口、com.mysql.cj.jdbc.Driver 接口,都是扩展点。
  • 扩展点实现:实现了扩展接口的实现类。

JDK SPI 在查找扩展实现类的过程中,需要遍历 SPI 配置文件中定义的所有实现类,该过程中会将这些实现类全部实例化。如果 SPI 配置文件中定义了多个实现类,而我们只需要使用其中一个实现类时,就会生成不必要的对象。例如,org.apache.dubbo.rpc.Protocol 接口有 InjvmProtocol、DubboProtocol、RmiProtocol、HttpProtocol、HessianProtocol、ThriftProtocol 等多个实现,如果使用 JDK SPI,就会加载全部实现类,导致资源的浪费。

Dubbo SPI 不仅解决了上述资源浪费的问题,还对 SPI 配置文件扩展和修改。

首先,Dubbo 按照 SPI 配置文件的用途,将其分成了三类目录。

  • META-INF/services/ 目录:该目录下的 SPI 配置文件用来兼容 JDK SPI 。
  • META-INF/dubbo/ 目录:该目录用于存放用户自定义 SPI 配置文件。
  • META-INF/dubbo/internal/ 目录:该目录用于存放 Dubbo 内部使用的 SPI 配置文件。

然后,Dubbo 将 SPI 配置文件改成了 KV 格式,例如:

dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol

其中 key 被称为扩展名(也就是 ExtensionName),当我们在为一个接口查找具体实现类时,可以指定扩展名来选择相应的扩展实现。例如,这里指定扩展名为 dubbo,Dubbo SPI 就知道我们要使用:org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol 这个扩展实现类,只实例化这一个扩展实现即可,无须实例化 SPI 配置文件中的其他扩展实现类。

使用 KV 格式的 SPI 配置文件的另一个好处是:让我们更容易定位到问题。假设我们使用的一个扩展实现类所在的 jar 包没有引入到项目中,那么 Dubbo SPI 在抛出异常的时候,会携带该扩展名信息,而不是简单地提示扩展实现类无法加载。这些更加准确的异常信息降低了排查问题的难度,提高了排查问题的效率。

四、Dubbo SPI 源码分析

Dubbo 中某个接口被 @SPI注解修饰时,就表示该接口是扩展接口,前文示例中的 org.apache.dubbo.rpc.Protocol 接口就是一个扩展接口:

@SPI 注解的 value 值指定了默认的扩展名称,例如,在通过 Dubbo SPI 加载 Protocol 接口实现时,如果没有明确指定扩展名,则默认会将 @SPI 注解的 value 值作为扩展名,即加载 dubbo 这个扩展名对应的 org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol 这个扩展实现类,相关的 SPI 配置文件在 dubbo-rpc-dubbo 模块中,如下图所示:

4.1 ExtensionLoader源码

该类是扩展加载器,这是dubbo实现SPI扩展机制等核心,几乎所有实现的逻辑都被封装在ExtensionLoader中。

1关于存放配置文件的路径变量:

    private static final String SERVICES_DIRECTORY = "META-INF/services/";
    private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
    private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";

"META-INF/services/"、"META-INF/dubbo/"、"META-INF/dubbo/internal/"三个值,都是dubbo寻找扩展实现类的配置文件存放路径,也就是我在上述(一)注解@SPI中讲到的以接口全限定名命名的配置文件存放的路径。区别在于"META-INF/services/"是dubbo为了兼容jdk的SPI扩展机制思想而设存在的,"META-INF/dubbo/internal/"是dubbo内部提供的扩展的配置文件路径,而"META-INF/dubbo/"是为了给用户自定义的扩展实现配置文件存放。

2扩展加载器集合,key为扩展接口,例如Protocol等:

    private static final ConcurrentMap> EXTENSION_LOADERS = new ConcurrentHashMap>();

3扩展实现类集合,key为扩展实现类,value为扩展对象,例如key为

    private static final ConcurrentMap, Object>();

4以下属性都是cache开头的,都是出于性能和资源的优化,才做的缓存,读取扩展配置后,会先进行缓存,等到真正需要用到某个实现时,再对该实现类的对象进行初始化,然后对该对象也进行缓存。

    //以下提到的扩展名就是在配置文件中的key值,类似于“dubbo”等

    //缓存的扩展名与拓展类映射,和cachedClasses的key和value对换。
    private final ConcurrentMap, String>();
    //缓存的扩展实现类集合
    private final Holder>>();
    //扩展名与加有@Activate的自动激活类的映射
    private final Map cachedActivates = new ConcurrentHashMap();
    //缓存的扩展对象集合,key为扩展名,value为扩展对象
    //例如Protocol扩展,key为dubbo,value为DubboProcotol
    private final ConcurrentMap cachedInstances = new ConcurrentHashMap 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);
            //创建 Wrapper 扩展对象(自动包装)
            Set pt = method.getParameterTypes()[0];
                        try {
                            //获得属性,比如StubProxyFactoryWrapper类中有Protocol protocol属性,
                            String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                            //获得属性值,比如Protocol对象,也可能是Bean对象
                            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;
    }
4.10 getExtensionClass源码

获得扩展名对应的扩展实现类

    private Class getExtensionClass(String name) {
        if (type == null)
            throw new IllegalArgumentException("Extension type == null");
        if (name == null)
            throw new IllegalArgumentException("Extension name == null");
        Class clazz = getExtensionClasses().get(name);
        if (clazz == null)
            throw new IllegalStateException("No such extension \"" + name + "\" for " + type.getName() + "!");
        return clazz;
    }
4.11 getExtensionClasses源码

获得扩展实现类数组,这里思路就是先从缓存中取,如果缓存为空,则从配置文件中读取扩展实现类。

    private Map> classes = cachedClasses.get();
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    //从配置文件中,加载扩展实现类数组
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }
4.12 loadExtensionClasses源码
   private Map> extensionClasses = new HashMap> wrappers = cachedWrapperClasses;
            if (wrappers == null) {
                cachedWrapperClasses = new ConcurrentHashSet 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());
                    }
                }
            }
        }
    }
4.16 createAdaptiveExtensionClass源码

创建适配器类,类似于dubbo动态生成的Transporter$Adpative这样的类。

    private Class createAdaptiveExtensionClass() {
        //创建动态生成的适配器类代码
        String code = createAdaptiveExtensionClassCode();
        ClassLoader classLoader = findClassLoader();
        com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        //编译代码,返回该类
        return compiler.compile(code, classLoader);
    }

这个方法中就做了编译代码的逻辑,生成代码在createAdaptiveExtensionClassCode方法中,createAdaptiveExtensionClassCode方法由于过长,我不在这边列出,下面会给出github的网址,读者可自行查看相关的源码解析。createAdaptiveExtensionClassCode生成的代码逻辑可以对照上述讲的注解@Adaptive中的Transporter$Adpative类来看。

4.17 AdaptiveExtensionFactory源码

该类是ExtensionFactory的适配器类

@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {

    //扩展对象的集合,默认的可以分为dubbo 的SPI中接口实现类对象或者Spring bean对象
    private final List factories;

    public AdaptiveExtensionFactory() {
        ExtensionLoader loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
        List list = new ArrayList();
        //遍历所有支持的扩展名
        for (String name : loader.getSupportedExtensions()) {
            //扩展对象加入到集合中
            list.add(loader.getExtension(name));
        }
        //返回一个不可修改的集合
        factories = Collections.unmodifiableList(list);
    }

    @Override
    public  T getExtension(Class type, String name) {
        for (ExtensionFactory factory : factories) {
            //通过扩展接口和扩展名获得扩展对象
            T extension = factory.getExtension(type, name);
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }

}
  1. factories是扩展对象的集合,当用户没有自己实现ExtensionFactory接口,则这个属性就只会有两种对象,分别是 SpiExtensionFactory 和 SpringExtensionFactory 。
  2. 构造器中是把所有支持的扩展名的扩展对象加入到集合
  3. 实现了接口的getExtension方法,通过接口和扩展名来获取扩展对象。
4.18 SpiExtensionFactory源码
public class SpiExtensionFactory implements ExtensionFactory {

    @Override
    public  T getExtension(Class type, String name) {
        //判断是否为接口,接口上是否有@SPI注解
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            //获得扩展加载器
            ExtensionLoader loader = ExtensionLoader.getExtensionLoader(type);
            if (!loader.getSupportedExtensions().isEmpty()) {
                //返回适配器类的对象
                return loader.getAdaptiveExtension();
            }
        }
        return null;
    }

}
4.19 ActivateComparator源码

该类在ExtensionLoader类的getActivateExtension方法中被运用到,作为自动激活拓展对象的排序器。

public class ActivateComparator implements Comparator {

    public static final Comparator COMPARATOR = new ActivateComparator();

    @Override
    public int compare(Object o1, Object o2) {
        //基本排序
        if (o1 == null && o2 == null) {
            return 0;
        }
        if (o1 == null) {
            return -1;
        }
        if (o2 == null) {
            return 1;
        }
        if (o1.equals(o2)) {
            return 0;
        }
        Activate a1 = o1.getClass().getAnnotation(Activate.class);
        Activate a2 = o2.getClass().getAnnotation(Activate.class);
        //使用Activate注解的 `after` 和 `before` 属性,排序
        if ((a1.before().length > 0 || a1.after().length > 0
                || a2.before().length > 0 || a2.after().length > 0)
                && o1.getClass().getInterfaces().length > 0
                && o1.getClass().getInterfaces()[0].isAnnotationPresent(SPI.class)) {
            ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(o1.getClass().getInterfaces()[0]);
            if (a1.before().length > 0 || a1.after().length > 0) {
                String n2 = extensionLoader.getExtensionName(o2.getClass());
                for (String before : a1.before()) {
                    if (before.equals(n2)) {
                        return -1;
                    }
                }
                for (String after : a1.after()) {
                    if (after.equals(n2)) {
                        return 1;
                    }
                }
            }
            if (a2.before().length > 0 || a2.after().length > 0) {
                String n1 = extensionLoader.getExtensionName(o1.getClass());
                for (String before : a2.before()) {
                    if (before.equals(n1)) {
                        return 1;
                    }
                }
                for (String after : a2.after()) {
                    if (after.equals(n1)) {
                        return -1;
                    }
                }
            }
        }
        // 使用Activate注解的 `order` 属性,排序。
        int n1 = a1 == null ? 0 : a1.order();
        int n2 = a2 == null ? 0 : a2.order();
        // never return 0 even if n1 equals n2, otherwise, o1 and o2 will override each other in collection like HashSet
        return n1 > n2 ? 1 : -1;
    }

}
博文参考

深入理解Apache Dubbo与实战.pdf

https://segmentfault.com/a/1190000016842868

面试官问烂的Dubbo中SPI机制的源码解析_哔哩哔哩_bilibili

关注
打赏
1657692713
查看更多评论
立即登录/注册

微信扫码登录

0.0414s