广告
返回顶部
首页 > 资讯 > 后端开发 > Python >浅析Java SPI 与 dubbo SPI
  • 693
分享到

浅析Java SPI 与 dubbo SPI

2024-04-02 19:04:59 693人浏览 泡泡鱼

Python 官方文档:入门教程 => 点击学习

摘要

Java原生SPI 面向接口编程+策略模式 实现 建立接口 Robot public interface Robot { void sayHello(); }

Java原生SPI

面向接口编程+策略模式

实现

建立接口

Robot


public interface Robot {
    
    void sayHello();
}

多个实现类实现接口

RobotA


public class RobotA implements Robot {
    public RobotA() {
        System.out.println("Happy RobotA is loaded");
    }
    @Override
    public void sayHello() {
        System.out.println("i am a very very happy Robot ");
    }
    public void sayBye(){}
}

RobotB


public class RobotB implements Robot {
    public RobotB() {
        System.out.println("SB RobotB is loaded");
    }
    @Override
    public void sayHello() {
        System.out.println("i am a da sha bi ");
    }
    public void sayBye(){}
}

配置实现类与接口

META-INF/services目录下建立一个以接口全限定名为名字的文件,里面的内容是实现类的全限定名

原理

通过ServiceLoader与配置文件中的全限定名加载所有实现类,根据迭代器获取具体的某一个类

我们通过对下面一段代码的分析来说明


ServiceLoader<Robot> serviceLoader=ServiceLoader.load(Robot.class);
serviceLoader.forEach(Robot::sayHello);

load(Robot.class)这个方法的目的只是为了设置类加载器为线程上下文加载器,我们当然可以不这么做,直接调用load(Class service,ClassLoader loader)方法


public static <S> ServiceLoader<S> load(Class<S> service) {
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
}

这个load方法其实也没有做什么实质的事,仅仅是实例化了一个ServiceLoad对象返回罢了


public static <S> ServiceLoader<S> load(Class<S> service,
                                        ClassLoader loader)
{
    return new ServiceLoader<>(service, loader);
}

那是不是构造方法做了最核心的事呢?


private ServiceLoader(Class<S> svc, ClassLoader cl) {
    service = Objects.requireNonNull(svc, "Service interface cannot be null");
    loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
    acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
    reload();
}
public void reload() {
    //这里的provider是一个对于已实例化对象的缓存,为Map类型
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }

没有,这里仅仅只是检验了参数和权限这样一些准备操作.然后实例化了一个LazyIterator

这是LazyIterator的构造函数


private LazyIterator(Class<S> service, ClassLoader loader) {
    this.service = service;
    this.loader = loader;
}

然后....,没了,ServiceLoader<Robot> serviceLoader=ServiceLoader.load(Robot.class);执行完毕了,到这里,并没有实例化我们所需要的Robot对象,而仅仅只是返回了一个ServiceLoader对象

这时候如果我们去看serviceLoader的对象方法是这样的

有用的只有这三个方法,reload上面已经提到过,只是重新实例化一个对象而已.

而另外两个iterator()是个迭代器,foreach也只是用于迭代的语法糖罢了.如果我们debug的话,会发现foreach的核心依旧会变成iterator(),好了,接下来重点看iterator()


public Iterator<S> iterator() {
    return new Iterator<S>() {

        Iterator<Map.Entry<String,S>> knownProviders
            = providers.entrySet().iterator();

        public boolean hasNext() {
            if (knownProviders.hasNext())
                return true;
            return lookupIterator.hasNext();
        }

        public S next() {
            if (knownProviders.hasNext())
                return knownProviders.next().getValue();
            return lookupIterator.next();
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }

    };

这个方法实际上是返回了一个Iterator对象.而通过这个Iterator,我们可以遍历获取我们所需要的Robot对象.

我们来看其用于获取对象的next方法


 public S next() {
            if (knownProviders.hasNext())
                return knownProviders.next().getValue();
            return lookupIterator.next();
        }

这个方法是先在缓存里找,缓存里找不到,就需要用最开始的实例化的lookupIterator

再来看看它的next方法


public S next() {
    if (acc == null) {
        return nextService();
    } else {
        PrivilegedAction<S> action = new PrivilegedAction<S>() {
            public S run() { return nextService(); }
        };
        return AccessController.doPrivileged(action, acc);
    }
}

这方法的核心是nextService,我们继续看实现,这个方法比较长,我贴一部分核心


if (!hasNextService())
    throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
    c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
    fail(service,
         "Provider " + cn + " not found");
}

hasNextService()判断是否还可以继续迭代,通过class.forName反射获取实例,最后再加入到provider缓存中.于是基本逻辑就完成了.那nextName哪来的.是在hasNextService()中获取的.

依旧只有核心代码


//获取文件
String fullName = PREFIX + service.getName();
if (loader == null)
    configs = ClassLoader.getSystemResources(fullName);
else
    configs = loader.getResources(fullName);
//解析文件配置
while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();

根据前缀(即META-INF/services)和接口的全限定名去找到对应的配置文件.然后加载里面的配置,获取具体实现类的名字.

dubbo增强SPI

实现

建立接口

与原生SPI不同,dubbo需要加入@SPI注解

Robot


@SPI
public interface Robot {
    
    void sayHello();
}

多个实现类实现接口

RobotA


public class RobotA implements Robot {
    public RobotA() {
        System.out.println("Happy RobotA is loaded");
    }
    @Override
    public void sayHello() {
        System.out.println("i am a very very happy Robot ");
    }
    public void sayBye(){}
}

RobotB


public class RobotB implements Robot {
    public RobotB() {
        System.out.println("SB RobotB is loaded");
    }
    @Override
    public void sayHello() {
        System.out.println("i am a da sha bi ");
    }
    public void sayBye(){}
}

配置实现类与接口

META-INF/dubbo目录下建立一个以接口全限定名为名字的文件,里面的内容是自定义名字与类的全限定名的键值对,举个例子


robotA = cn.testlove.double_dubbo.inter.impl.RobotA
robotB=cn.testlove.double_dubbo.inter.impl.RobotB

原理

我们通过对下列代码的调用来进行分析


ExtensionLoader<Robot> extensionLoader= ExtensionLoader.getExtensionLoader(Robot.class);
Robot robotB = extensionLoader.getExtension("robotB");

第一句代码没什么好说的,只是获取一个RobotExtensionLoader对象并且缓存在Map中,下次如果是同样的接口可以直接从map中获取


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

再来看第二句代码


//从缓存中找
final Holder<Object> holder = getOrCreateHolder(name);
Object instance = holder.get();
//双重检查
if (instance == null) {
    synchronized (holder) {
        instance = holder.get();
        if (instance == null) {
            instance = createExtension(name);
            holder.set(instance);
        }
    }
}

首先从缓存里找,找不到再创建一个新的对象。

再看createExtension(name)方法


Class<?> clazz = getExtensionClasses().get(name);

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 (CollectionUtils.isNotEmpty(wrapperClasses)) {
    for (Class<?> wrapperClass : wrapperClasses) {
        instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
    }
}
initExtension(instance);
return instance;

注意对于Class<?> clazz = getExtensionClasses().get(name);这一句的理解,这一句是获取配置文件中所有类的Class实例,而不是获取所有扩展类的实例。

接下来的流程其实也就简单了从EXTENSION_INSTANCES缓存中获取instance实例,如果没有,就借助Class对象实例化一个,再放入缓存中

接着用这个instance去实例化一个包装类然后返回.自此,一个我们需要的对象产生了.

最后我们看看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;

这里的classes就是用来存各个扩展类Class的Map缓存,如果不存在的话,会调用loadExtensionClasses();去加载,剩下的就是找到对应路径下的配置文件,获取全限定名了

上文我在分析Dubbo SPI时,多次提到Map,缓存二词,我们可以具体有以下这些.其实看名字就大概知道作用了


private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<>();
private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>()    
private final Map<String, Object> cachedActivates = new ConcurrentHashMap<>();
private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
private final Holder<Object> cachedAdaptiveInstance = new Holder<>();
private volatile Class<?> cachedAdaptiveClass = null;
private String cachedDefaultName;

对比原生的java SPI,dubbo的无疑更灵活,可以按需去加载某个类,也可以很便捷的通过自定义的名字去获取类.而且Dubbo还支持setter注入.这点以后再讲.

最后提一个问题,java原生的SPI只有在用iterator遍历到的时候才会实例化对象,那能不能在遇到自己想要的实现对象时就停止遍历,避免不必要的资源消耗呢?

补充:下面看下Dubbo SPI 和 Java SPI 区别?

jdk SPI

JDK 标准的 SPI 会一次性加载所有的扩展实现,如果有的扩展吃实话很耗时,但

也没用上,很浪费资源。

所以只希望加载某个的实现,就不现实了

DUBBO SPI

1,对 Dubbo 进行扩展,不需要改动 Dubbo 的源码

2,延迟加载,可以一次只加载自己想要加载的扩展实现。

3,增加了对扩展点 iocaop 的支持,一个扩展点可以直接 setter 注入其它扩展点。

3,Dubbo 的扩展机制能很好的支持第三方 IoC 容器,默认支持 spring Bean。

以上就是Java SPI 与 dubbo SPI的详细内容,更多关于Java SPI 与 dubbo SPI的资料请关注编程网其它相关文章!

--结束END--

本文标题: 浅析Java SPI 与 dubbo SPI

本文链接: https://www.lsjlt.com/news/127116.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 浅析Java SPI 与 dubbo SPI
    Java原生SPI 面向接口编程+策略模式 实现 建立接口 Robot public interface Robot { void sayHello(); } ...
    99+
    2022-11-12
  • 浅析Java中的SPI原理
    在面向对象的程序设计中,模块之间交互采用接口编程,通常情况下调用方不需要知道被调用方的内部实现细节,因为一旦涉及到了具体实现,如果需要换一种实现就需要修改代码,这违反了程序设计的&q...
    99+
    2022-11-13
  • Java和Dubbo的SPI机制原理解析
    SPI: 简单理解就是,你一个接口有多种实现,然后在代码运行时候,具体选用那个实现,这时候我们就可以通过一些特定的方式来告诉程序寻用那个实现类,这就是SPI。 JAVA的SPI 全称...
    99+
    2022-11-11
  • Dubbo 系列JDK SPI 原理解析
    目录正文为什么要使用SPI?什么是 SPIJDK SPI 机制JDK SPI原理为什么不直接使用 JDK SPI正文 上一篇文章讲到了如何去找到 Dubbo 源码的调试入口,如果你...
    99+
    2023-02-24
    Dubbo JDK SPI原理 Dubbo JDK SPI
  • JDK与Dubbo中的SPI详细介绍
    目录1、SPI简介2、JDK中的SPI3、Dubbo中的SPI4、Dubbo中扩展点使用方式5、DubboSPI中的Adaptive功能1、SPI简介 SPI 全称为 (Servic...
    99+
    2022-11-13
  • 解析Apache Dubbo的SPI实现机制
    目录一、SPI1.1、JDK自带SPI实现1.2、Dubbo SPI二、加载-ExtensionLoader2.1、获取ExtensionLoader的实例2.2、加载扩展类2.2....
    99+
    2022-11-12
  • dubbo的SPI应用与原理是什么
    dubbo的SPI应用与原理是什么,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。dubboSPI(Service Provider Interface)本质是将接口实现类的全限...
    99+
    2023-06-05
  • Dubbo扩展点SPI实践示例解析
    目录正文扩展点配置:扩展实现类:拦截配置文件:调用拦截扩展:拦截扩展说明:常用约定:实现细节:扩展点的几个特点:扩展点自动包装扩展点自动装配扩展点自适应扩展点自动激活正文 Dubbo...
    99+
    2022-11-13
    Dubbo扩展点SPI实践 Dubbo SPI扩展
  • Java Spring Dubbo三种SPI机制的区别
    目录前言SPI 有什么用?​JDK SPI​Dubbo SPISpring SPI​对比​前言 SPI 全称为 Service Provider Interface,是一种服务发现机...
    99+
    2022-11-13
  • Dubbo源码解析之SPI(一):扩展类的加载过程
    Dubbo是一款开源的、高性能且轻量级的Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用、智能容错和负载均衡,以及服务自动注册和发现。Dubbo最早是阿里公司内部的RPC框架,于 2011 年开源,之后迅速成为国内该类开源...
    99+
    2023-06-05
  • 怎么进行Java SPI机制的分析
    这篇文章将为大家详细讲解有关怎么进行Java SPI机制的分析,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。为什么需要SPI?思考一个场景,我们封装了一套服务,别人通过引入我们写好...
    99+
    2023-06-22
  • Java进阶之SPI机制的示例分析
    这篇文章将为大家详细讲解有关Java进阶之SPI机制的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。一、前言SPI的英文全称为Service Provider Interface,字面意思为服务提...
    99+
    2023-06-15
  • Java插件扩展机制之SPI的示例分析
    这篇文章给大家分享的是有关Java插件扩展机制之SPI的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。什么是SPISPI ,全称为 Service Provider Interface,是一种服务发现机制...
    99+
    2023-06-20
  • 从源码全面解析 Java SPI 的来龙去脉
    👏作者简介:大家好,我是爱敲代码的小黄,独角兽企业的Java开发工程师,CSDN博客专家,阿里云专家博主📕系列专栏:Java设计模式、Spring源码系列、Netty源码系列、Kafka源码系列、JUC源...
    99+
    2023-08-17
    java 开发语言 面试 后端 SPI 原力计划
  • java框架基础之SPI机制实现及源码解析
    目录1 定义2 案例实现标准接口厂商的具体接口实现3 SPI机制源码分析3.1 load加载过程3.2 实例化过程1 定义 SPI 的全名为 Service Pr...
    99+
    2022-11-13
  • Spring高手之路14——深入浅出:SPI机制在JDK与Spring Boot中的应用
    文章目录 1. SPI解读:什么是SPI2. SPI在JDK中的应用示例3. SPI在Spring框架中的应用3.1 传统Spring框架中的SPI思想3.2 Spring Boot中的SPI...
    99+
    2023-09-09
    Java SPI spring.factorie Spring Boot自动配置 服务加载机制 Spring Boot高级教程 原力计划
  • 浅析Java中clone()方法浅克隆与深度克隆
       现在Clone已经不是一个新鲜词语了,伴随着“多莉”的产生这个词语确实很“火”过一阵子,在Java中也有这么一个概念,它可以让我们很方便的“制造”出一个对象的副本来,下面来具体看看Java中的Clone机制是如何工...
    99+
    2023-05-31
    java clone 浅克隆
  • 浅析java中next与nextLine用法对比
    java中next与nextLine用法区别:next()一定要读取到有效字符后才可以结束输入,对输入有效字符之前遇到的空格键、Tab键或Enter键等结束符next()方法会自动将其去掉,只有在输入有效字符之后,next()方法才将其后输...
    99+
    2023-05-31
    java next nextline
  • 深入浅析Java中对象的深复制与浅复制
    本篇文章为大家展示了深入浅析Java中对象的深复制与浅复制,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。 Java对象深复制与浅复制实例详解我们在遇到一些业务场景的时候经常需要对对象进行复...
    99+
    2023-05-31
    java 对象 中对
  • 深入浅析java中TCP与UDP的区别
    深入浅析java中TCP与UDP的区别?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。TCP/UDP:TCP主要是面向连接的协议,它包含有建立和拆除连接,保证数据流的顺序和正...
    99+
    2023-05-31
    java tcp udp
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作