百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

JDK SPI和Dubbo SPI jdk是什么快递公司

liebian365 2024-11-09 13:48 25 浏览 0 评论

SPI,全称为(Service Provider Interface),是JDK内置的一种服务提供机制。

一、JDK SPI实现

约定规范:

1.配置文件必须在指定文件夹 META-INF/services/ 内。

2.配置文件编码格式必须为:UTF-8。

3.配置文件名必须是 **类的全限定名**。

4.接口如果有多个实现,在配置文件中以 # 作为分隔符。

JDK SPI源码:
// 重载函数, 默认为 当前线程的 ContextClassLoader

public static <S> ServiceLoader<S> load(Class<S> service) {

        ClassLoader cl = Thread.currentThread().getContextClassLoader();

        return ServiceLoader.load(service, cl);

}

//返回 ServiceLoader 实例

 public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) {

        return new ServiceLoader<>(service, loader);

}

示例:

一个SPI的实现类,比如实现UserService接口为例。

需要SPI扩展接口

io.service.UserService

需要实现类

io.service.impl.UserServiceImpl

加载流程:

以 java.util.ServiceLoader 类的 load 方法作为入口

ServiceLoader<UserService> serviceLoader =  ServiceLoader.load(UserService.class);


0.在 resource 文件夹下创建子文件夹 META-INF.services,并新增名为: io.service.UserService 的文件,文件内容如下所示:

io.service.impl.UserServiceImpl

1.获取META-INF/services/目录下文件

```
 private Iterator<String> parse(Class<?> service, URL u)
        throws ServiceConfigurationError{}
```

其中service 表示需要加载的类对象, u 表示 META-INF.services 下文件 在系统中的全路径。


2.解析文件

根据#分割成多个文件,写入List<String>集合


3.上面集合迭代实现加载(public final class ServiceLoader<S> implements Iterable<S>),然后通过反射,cn为文件全路径名,当前线程的ClassLoader

```
       //通过反射,获取目标类
       c = Class.forName(cn, false, loader);
```


4.目标类转换成相应类

```
// 目标类实例转换为响应的类,并将其加载到 providers 列表中
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
```

写入将ServiceLoader类的属性provides,底层是LinkedHashMap,保证调用顺序

```
// Cached providers, in instantiation order
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
```

5.使用 Iterator 接口的实现类LazyIterator,将服务加载的这个动作延迟到使用服务时。这样做的好处是:**只有在真正使用时才加载,避免造成没必要的加载。但未解决,未使用扩展加载的问题**。

// 用于延迟加载接口的实现类
private LazyIterator lookupIterator;

缺点:

虽然使用延迟加载,但是只能通过遍历全部获取,接口的实现类全部加载并实例化一遍。如果不想用某些实现类,它也被加载并实例化,这就造成了资源浪费。

二、Dubbo SPI扩展机制

Dubbo对JDK SPI进行了扩展,对服务提供者配置文件中的内容进行了改造,由原来的提供者类的全限定名列表改成了KV形式的列表,这也导致了Dubbo中无法直接使用JDK ServiceLoader。

Dubbo默认依次扫描META-INF/dubbo/internal/、META-INF/dubbo/、META-INF/services/三个classpath目录下的配置文件。配置文件以具体扩展接口全名命名

流程:

1.dubbo核心实现类ExtensionLoader,通过接口类名和key值获取一个实现类。

2.通过Adaptive实现,生成一个代理类,根据实际调用时,先静态,后动态决定要调用的类。

3.采用装饰器模式进行功能增强,自动包装实现,这种实现的类一般是自动激活的。

源码解析:

1首先加载实例:

ExtensionLoader充当插件工厂角色,提供了一个私有的构造器。其入参type为扩展接口类型。Dubbo通过SPI注解定义了可扩展的接口,如Filter、Transporter等。每个类型的扩展对应一个ExtensionLoader。SPI的value参数决定了默认的扩展实现

当扩展类型是ExtensionFactory时,不指定objectFactory,否则初始化ExtensionFactory的ExtensionLoader并获取一个扩展适配器


@SPI("dubbo")
public interface Protocol {
    
    /**
     * 获取缺省端口,当用户没有配置端口时使用。
     * 
     * @return 缺省端口
     */
    int getDefaultPort();

    /**
     * 暴露远程服务:
     * 1. 协议在接收请求时,应记录请求来源方地址信息:RpcContext.getContext().setRemoteAddress();<br>
     * 2. export()必须是幂等的,也就是暴露同一个URL的Invoker两次,和暴露一次没有区别。<br>
     * 3. export()传入的Invoker由框架实现并传入,协议不需要关心。<br>
     * 
     * @param invoker 服务的执行体
     */
    @Adaptive
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;

    /**
     * 引用远程服务:
     * 1. 当用户调用refer()所返回的Invoker对象的invoke()方法时,协议需相应执行同URL远端export()传入的Invoker对象的invoke()方法。<br>
     * 2. refer()返回的Invoker由协议实现,协议通常需要在此Invoker中发送远程请求。<br>
     * 3. 当url中有设置check=false时,连接失败不能抛出异常,并内部自动恢复。<br>
     * 
     * @param type 服务的类型
     * @param url 远程服务的URL地址
     */
    @Adaptive
    <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;

    /**
     * 释放协议:<br>
     */
    void destroy();

}

public class DubboProtocol extends AbstractProtocol {

    public static final String NAME = "dubbo";
    ...
    ...
}

// 示例:
ExtensionLoader<Protocol> protocolLoader = ExtensionLoader.getExtensionLoader(Protocol.class);
// 根据Key获取相应的扩展实现类实例
Protocol dubboProtocol = protocolLoader.getExtension(DubboProtocol.NAME);

其次,getExtension是实现整个扩展加载器ExtensionLoader中最核心的方法。分为4步,

1.1 读取配置文件,并缓存;

     // 1. 先从缓存中取相应的扩展实现类实例
     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);
             }
         }
     }

1.2 根据传入的名称初始化扩展类;

     // 2. 根据name获取相应扩展类的类实例
     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, (T) clazz.newInstance());
             instance = (T) EXTENSION_INSTANCES.get(clazz);
         }
       	 //注入依赖,即IOC
         injectExtension(instance);
         Set<Class<?>> wrapperClasses = cachedWrapperClasses;
         if (wrapperClasses != null && wrapperClasses.size() > 0) {
             for (Class<?> wrapperClass : wrapperClasses) {
               	 //通过反射创建Wrapper实例,向Wrapper实例注入依赖,最后赋值给instance,自动包装实现类似aop功能
                 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);
     }

1.3 尝试查找符合条件的包装类,符合条件的注入扩展类(反射实现的,查询set开头的方法),并注入类的实例;

private T injectExtension(T instance) {
    try {
        if (this.objectFactory != null) {
            Method[] var2 = instance.getClass().getMethods();            
          	int var3 = var2.length;            
          	for(int var4 = 0; var4 < var3; ++var4) {
                Method method = var2[var4];                
                if (this.isSetter(method) && method.getAnnotation(DisableInject.class) == null) {
                    Class<?> pt = method.getParameterTypes()[0];                    
                  	if (!ReflectUtils.isPrimitives(pt)) {
                        try {
                            String property = this.getSetterProperty(method);                            
                          	Object object = this.objectFactory.getExtension(pt, property);                            
														if (object != null) {
                                // 注入类的实例
                                method.invoke(instance, object);    
                            }
                        } catch (Exception var9) {
                            logger.error("Failed to inject via method " + method.getName() 
                                         + " of interface " + this.type.getName() + ": " 
                                         + var9.getMessage(), var9);                       
                        }
                    }
                }
            }
        }
    } catch (Exception var10) {
        logger.error(var10.getMessage(), var10);    }

    return instance;
}

1.4 返回扩展类实例。

2.自适应扩展器

dubbo提供了两种方式来生成扩展适配器:静态适配器扩展和动态适配器扩展。

静态适配器扩展,就是提前通过编码的形式确定扩展的具体实现,且该实现类由Adaptive注解标注,如:AdaptiveCompiler。在加载配置文件的loadFile方法中,已经描述过处理该类型扩展的逻辑,具体可参考loadFile()方法源码。

动态适配器扩展,即通过动态代理生成扩展类的动态代理类,在dubbo中是通过javassist技术生成的。与传统的jdk动态代理、cglib不同,javassist提供封装后的API对字节码进行间接操作,简单易用,不关心具体字节码,灵活性更高,且处理效率也较高,是dubbo默认的编译器。

2.1 首先,从ExtensionLoader构造器中会调用getAdaptiveExtension()方法触发为当前扩展类型生成适配器:

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

public T getAdaptiveExtension() {
        // 1. 首先,检查是否存在当前扩展类静态适配器
        Object instance = cachedAdaptiveInstance.get();
        if (instance == null) {
            if(createAdaptiveInstanceError == null) {
                synchronized (cachedAdaptiveInstance) {
                    instance = cachedAdaptiveInstance.get();
                    if (instance == null) {
                        try {
                            // 2. 创建当前扩展类动态适配器
                            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;
    }

2.2 创建动态类扩展适配器

 private T createAdaptiveExtension() {
        try {
            // IOC属性注入
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e);
        }
    }

2.3 根据adaptive注解的value数组值,及SPI注解定义的默认扩展名,确定适配逻辑,即扩展获取的优先级,以下代码为例其获取优先级是server,transporter,netty

package com.alibaba.dubbo.remoting;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Transporter$Adpative implements com.alibaba.dubbo.remoting.Transporter {
    public com.alibaba.dubbo.remoting.Client connect(com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) throws com.alibaba.dubbo.common.URL {
        if (arg0 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg0;
        String extName = url.getParameter("client", url.getParameter("transporter", "netty")); // 处理顺序
        if(extName == null) 
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([client, transporter])");
        com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);
        return extension.connect(arg0, arg1);
    }
    public com.alibaba.dubbo.remoting.Server bind(com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) throws com.alibaba.dubbo.common.URL {
        if (arg0 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg0;
        String extName = url.getParameter("server", url.getParameter("transporter", "netty")); // 处理顺序
        if(extName == null) 
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([server, transporter])");
        com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);
        return extension.bind(arg0, arg1);
    }
}

2.4 拿到扩展名后,再从ExtensionLoader获取到扩展实例,调用具体的bind方法。
源码生成后,ExtensionLoader再调用默认的JavassitCompiler进行编译和类加载。


3.1 dubbo中存在一种对于扩展的封装类,其功能是将各扩展实例串联起来,形成扩展链,比如过滤器链,监听链。当调用ExtensionLoader的getExtension方法时,会做拦截处理,如果存在封装器,则返回封装器实现,而将真实实现通过构造方法注入到封装器中

        Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (wrapperClasses != null && wrapperClasses.size() > 0) {
                for (Class<?> wrapperClass : wrapperClasses) {
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }

相关推荐

“版本末期”了?下周平衡补丁!国服最强5套牌!上分首选

明天,酒馆战棋就将迎来大更新,也聊了很多天战棋相关的内容了,趁此机会,给兄弟们穿插一篇构筑模式的卡组推荐!老规矩,我们先来看10职业胜率。目前10职业胜率排名与一周前基本类似,没有太多的变化。平衡补丁...

VS2017 C++ 程序报错“error C2065:“M_PI”: 未声明的标识符&quot;

首先,程序中头文件的选择,要选择头文件,在文件中是没有对M_PI的定义的。选择:项目——>”XXX属性"——>配置属性——>C/C++——>预处理器——>预处理器定义,...

东营交警实名曝光一批酒驾人员名单 88人受处罚

齐鲁网·闪电新闻5月24日讯酒后驾驶是对自己和他人生命安全极不负责的行为,为守护大家的平安出行路,东营交警一直将酒驾作为重点打击对象。5月23日,东营交警公布最新一批饮酒、醉酒名单。对以下驾驶人醉酒...

Qt界面——搭配QCustomPlot(qt platform)

这是我第一个使用QCustomPlot控件的上位机,通过串口精确的5ms发送一次数据,再将读取的数据绘制到图表中。界面方面,尝试卡片式设计,外加QSS简单的配了个色。QCustomPlot官网:Qt...

大话西游2分享赢取种族坐骑手办!PK趣闻录由你书写

老友相聚,仗剑江湖!《大话西游2》2021全民PK季4月激燃打响,各PK玩法鏖战齐开,零门槛参与热情高涨。PK季期间,不仅各种玩法奖励丰厚,参与PK趣闻录活动,投稿自己在PK季遇到的趣事,还有机会带走...

测试谷歌VS Code AI 编程插件 Gemini Code Assist

用ClaudeSonnet3.7的天气测试编码,让谷歌VSCodeAI编程插件GeminiCodeAssist自动编程。生成的文件在浏览器中的效果如下:(附源代码)VSCode...

顾爷想知道第4.5期 国服便利性到底需优化啥?

前段时间DNF国服推出了名为“阿拉德B计划”的系列改版计划,截至目前我们已经看到了两项实装。不过关于便利性上,国服似乎还有很多路要走。自从顾爷回归DNF以来,几乎每天都在跟我抱怨关于DNF里面各种各样...

掌握Visual Studio项目配置【基础篇】

1.前言VisualStudio是Windows上最常用的C++集成开发环境之一,简称VS。VS功能十分强大,对应的,其配置系统较为复杂。不管是对于初学者还是有一定开发经验的开发者来说,捋清楚VS...

还嫌LED驱动设计套路深?那就来看看这篇文章吧

随着LED在各个领域的不同应用需求,LED驱动电路也在不断进步和发展。本文从LED的特性入手,推导出适合LED的电源驱动类型,再进一步介绍各类LED驱动设计。设计必读:LED四个关键特性特性一:非线...

Visual Studio Community 2022(VS2022)安装图文方法

直接上步骤:1,首先可以下载安装一个VisualStudio安装器,叫做VisualStudioinstaller。这个安装文件很小,很快就安装完成了。2,打开VisualStudioins...

Qt添加MSVC构建套件的方法(qt添加c++11)

前言有些时候,在Windows下因为某些需求需要使用MSVC编译器对程序进行编译,假设我们安装Qt的时候又只是安装了MingW构建套件,那么此时我们该如何给现有的Qt添加一个MSVC构建套件呢?本文以...

Qt为什么站稳c++GUI的top1(qt c)

为什么现在QT越来越成为c++界面编程的第一选择,从事QT编程多年,在这之前做C++界面都是基于MFC。当时为什么会从MFC转到QT?主要原因是MFC开发界面想做得好看一些十分困难,引用第三方基于MF...

qt开发IDE应该选择VS还是qt creator

如果一个公司选择了qt来开发自己的产品,在面临IDE的选择时会出现vs或者qtcreator,选择qt的IDE需要结合产品需求、部署平台、项目定位、程序猿本身和公司战略,因为大的软件产品需要明确IDE...

Qt 5.14.2超详细安装教程,不会来打我

Qt简介Qt(官方发音[kju:t],音同cute)是一个跨平台的C++开库,主要用来开发图形用户界面(GraphicalUserInterface,GUI)程序。Qt是纯C++开...

Cygwin配置与使用(四)——VI字体和颜色的配置

简介:VI的操作模式,基本上VI可以分为三种状态,分别是命令模式(commandmode)、插入模式(Insertmode)和底行模式(lastlinemode),各模式的功能区分如下:1)...

取消回复欢迎 发表评论: