Dubbo中的SPI机制实现,自定义对接SPI

SPI在java中称为Service Provider Interface,Dubbo中对java的spi机制自己实现相对应的逻辑。
我们以Dubbo中的Protociol为例,一般我们去获取对应的Protocol的时候都是通过如下方式:

ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
// 或者
ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName);

我们看下Dubbo中具体怎么实现,

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 an interface!");
        }
        if (!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type (" + type +
                    ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
        }

        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        if (loader == null) {
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }

可以看到,Dubbo中是每个通过SPI加载类型都有一个对应的ExtensionLoader,我们看看具体怎么加载类的:

 public T getExtension(String name) {
        T extension = getExtension(name, true);
        if (extension == null) {
            throw new IllegalArgumentException("Not find extension: " + name);
        }
        return extension;
    }
public T getExtension(String name, boolean wrap) {
        if (StringUtils.isEmpty(name)) {
            throw new IllegalArgumentException("Extension name == null");
        }
        if ("true".equals(name)) {
            return getDefaultExtension();
        }
        final Holder<Object> holder = getOrCreateHolder(name);
        Object instance = holder.get();
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                    instance = createExtension(name, wrap);
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }
private T createExtension(String name, boolean wrap) {
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null || unacceptableExceptions.contains(name)) {
            throw findException(name);
        }
        try {
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.getDeclaredConstructor().newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            injectExtension(instance);


            if (wrap) {

                List<Class<?>> wrapperClassesList = new ArrayList<>();
                if (cachedWrapperClasses != null) {
                    wrapperClassesList.addAll(cachedWrapperClasses);
                    wrapperClassesList.sort(WrapperComparator.COMPARATOR);
                    Collections.reverse(wrapperClassesList);
                }

                if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
                    for (Class<?> wrapperClass : wrapperClassesList) {
                        Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
                        if (wrapper == null
                                || (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {
                            instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                        }
                    }
                }
            }

            initExtension(instance);
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                    type + ") couldn't be instantiated: " + t.getMessage(), t);
        }
    }
private Map<String, Class<?>> loadExtensionClasses() {
        cacheDefaultExtensionName();

        Map<String, Class<?>> extensionClasses = new HashMap<>();

        for (LoadingStrategy strategy : strategies) {
            loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
            loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
        }

        return extensionClasses;
    }

这里我们首先会通过getExtensionClasses去加载对应的SPI扩展类,通过loadExtensionClasses去进行实际的加载,这里首先会用java的 ServiceLoader去加载对应的LoadingStrategy,而ServiceLoader会加载当前classpath下META-INF.services下对应去路径类对应的文件中列出的类:
在这里插入图片描述
而这里LoadingStrategy则会加载如下三个实现类:

org.apache.dubbo.common.extension.DubboInternalLoadingStrategy
org.apache.dubbo.common.extension.DubboLoadingStrategy
org.apache.dubbo.common.extension.ServicesLoadingStrategy

而这里这几个LoadingStrategy主要是定义了要扫描哪些文件夹:
DubboInternalLoadingStrategy会加载如下文件夹:

public class DubboInternalLoadingStrategy implements LoadingStrategy {
    public String directory() {
        return "META-INF/dubbo/internal/";
    }
    public int getPriority() {
        return MAX_PRIORITY;
    }
}

DubboLoadingStrategy:

public class DubboLoadingStrategy implements LoadingStrategy {
    public String directory() {
        return "META-INF/dubbo/";
    }
    public boolean overridden() {
        return true;
    }
    public int getPriority() {
        return NORMAL_PRIORITY;
    }
}

ServicesLoadingStrategy:

public class ServicesLoadingStrategy implements LoadingStrategy {
    public String directory() {
        return "META-INF/services/";
    }
    public boolean overridden() {
        return true;
    }
    public int getPriority() {
        return MIN_PRIORITY;
    }
}

也就是说,如果我们想要Dubbo的SPI区加载我们指定的目录下的实现的话,那么我们需要实现LoadingStrategy然后在classpath下新建预估ieorg.apache.dubbo.common.extension.LoadingStrategy名称的文件,文件的内容为我们自己实现的LoadingStrategy全名称,多个换行即可

从上面可以看到,Dubbo会从如下几个文件夹去加载对应的SPI扩展类:
META-INF/dubbo/internal/META-INF/dubbo/META-INF/services/
最终通过loadResource去加载对应需要加载的类全名称文件里面的内容:

private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader,
                              java.net.URL resourceURL, boolean overridden, String... excludedPackages) {
        try {
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
                String line;
                String clazz = null;
                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();
                                clazz = line.substring(i + 1).trim();
                            } else {
                                clazz = line;
                            }
                            if (StringUtils.isNotEmpty(clazz) && !isExcluded(clazz, excludedPackages)) {
                                loadClass(extensionClasses, resourceURL, Class.forName(clazz, true, classLoader), name, overridden);
                            }
                        } catch (Throwable t) {
                        }
                    }
                }
            }
        } catch (Throwable t) {
        }
    }

可以看到,这里是按照行进行读取的,然后需要注意的是这里会判断每行是否有=,如果有=号的化,那么会进行分割,拿到对应的SPI需要加载的类

拿到类之后,接下来就去加载类,通过loadClass实现:

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name,
                           boolean overridden) throws NoSuchMethodException {
        if (!type.isAssignableFrom(clazz)) {
            throw new IllegalStateException("Error occurred when loading extension class (interface: " +
                    type + ", class line: " + clazz.getName() + "), class "
                    + clazz.getName() + " is not subtype of interface.");
        }
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            cacheAdaptiveClass(clazz, overridden);
        } else if (isWrapperClass(clazz)) {
            cacheWrapperClass(clazz);
        } else {
            clazz.getConstructor();
            if (StringUtils.isEmpty(name)) {
                name = findAnnotationName(clazz);
                if (name.length() == 0) {
                    throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
                }
            }

            String[] names = NAME_SEPARATOR.split(name);
            if (ArrayUtils.isNotEmpty(names)) {
                cacheActivateClass(clazz, names[0]);
                for (String n : names) {
                    cacheName(clazz, n);
                    saveInExtensionClass(extensionClasses, clazz, n, overridden);
                }
            }
        }
    }

上面逻辑主要有三个:

  1. 加载的类上面是否有Adaptive注解,如果有Adaptive那么会设置cacheAdaptiveClass
  2. 如果没有Adaptive注解,那么判断是否是Wrapper类,这里Dubbo判断是否是Wrapper的方式很简单,就是判断这个类的构造函数是否支持只传入一个参数,且这个参数是待加载的类型:
private boolean isWrapperClass(Class<?> clazz) {
        try {
            clazz.getConstructor(type);
            return true;
        } catch (NoSuchMethodException e) {
            return false;
        }
    }
  1. 如果上面两项都不符合的话,如果name为空,那么查看类上Extension注解对应的值作为name,如果没有Extension注解,那么name的逻辑为判断类名称的是否以待获取的类名称结尾,如果是直接直接取类名称去除待获取类名称的部分并转小写然后将name和对应的class类型设置到缓存中去:
private String findAnnotationName(Class<?> clazz) {
        org.apache.dubbo.common.Extension extension = clazz.getAnnotation(org.apache.dubbo.common.Extension.class);
        if (extension != null) {
            return extension.value();
        }
        String name = clazz.getSimpleName();
        if (name.endsWith(type.getSimpleName())) {
            name = name.substring(0, name.length() - type.getSimpleName().length());
        }
        return name.toLowerCase();
    }

也就是说,我们通过

这样我们就加载到了所有的类,并设置到了对应的类中,如果不是AdaptiveWrapperClass那么会将类和对应name放置到一个map中并返回,然后在createExtension中根据name就能够获取到对应的类型

对于createExtension,首先是根据获取到的类型进行实例化,并注入相关的属性(注入属性也是通过SPI机制,如果有set方法会进行注入,如果方法上有DisableInject注解或忽略

实例化类之后,会进行wrap也就是包装,会实例化Wrapper,然后将生成的实际类的实例注入进去,这里由于会存在多个Warapper类,会进行排序,通过获取Activate中的order属性,这里是按照从小到大的顺序排列。
这样多个Wrapper实际上是一个Wraprer嵌套这一个Wrapper

所以说,Dubbo的SPI机制如果有warapper实现返回的是一个Wrapper类。

另外对于getAdaptiveExtension,Dubbo中的处理也很简单暴力,直接加载对应要加载类名称后面加上$Adaptive

private Class<?> createAdaptiveExtensionClass() {
        ClassLoader classLoader = findClassLoader();
        if (ApplicationModel.getEnvironment().getConfiguration().getBoolean(NATIVE, false)) {
            try {
                return classLoader.loadClass(type.getName() + "$Adaptive");
            } catch (ClassNotFoundException e) {
                //ignore
                e.printStackTrace();
            }
        }
        String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
        org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        return compiler.compile(code, classLoader);
    }

比如ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();加载的就是Protocol$Adaptive然后在Protocol$Adaptive中仍然通过ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName)去加载对应的类

到这里Dubbo的SPI机制原理大概就分析完了,我们总结下整个流程以org.apache.dubbo.rpc.Protocol为例:

  1. 当我们通过ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName);去加载一个Protocol实例的时候,首先Dubbo会通过JAVA的ServiceLoader去加载当前classpath下META-INF/dubbo/internal/META-INF/dubbo/META-INF/services/下文件名称为org.apache.dubbo.rpc.Protocol
  2. 加载到org.apache.dubbo.rpc.Protocol文件后,会按行读取,每行会按照=进行分割
  3. 对于读取出来的类的类型,会进行判断,1. 是否有Adaptive注解,如果有会设到cachedAdaptiveClass属性中去 2. 是否是WrapperClass,判断很简单,就是看这个类是否有一个构造函数只有一个参数且参数类型是Protocol类型 3. 如果这行没有=,获取对应类型的名称(先看是否有Extension注解,如果没有,那么将该类型名称去除接口部分后转小写), 然后将name和class类型放到一个map中并设置cachedActivates
  4. 循环处理所有的文件内容,返回一个map,map的key为name,value为对应的类型
  5. 对获取到的类型进行实例化,直接调用clazz.getDeclaredConstructor().newInstance()进行实例化,实例化后,还会进行初始化,初始化也是通过SPI机制,对于有set方法的属性,通过SPI机制去加载对应的类型并注入
  6. 判断是否有WarapperClass,如果有的话且需要进行wrap,会将步骤3中获取的WrapperClass列表进行排序,这里会进行reverse,也即是按照从大到小的顺序排序,如果WrapperClass没有Wrapper注解或者Wrapper注解的matches包含这个name,那么沪江这个wrapper进行实例化,多个Wrapper会一个嵌套一个。

THE END

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了小程序应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值