Dubbo源码解析-SPI机制

本文深入解析Dubbo的SPI机制,介绍了Dubbo如何改进了Java的SPI,包括资源加载、扩展点的IoC和AOP支持,以及核心类ExtensionLoader的工作原理。Dubbo SPI的约定、文件路径和相关注解也在文中详细阐述。
摘要由CSDN通过智能技术生成

版本2.7.1

Dubbo有官方源码解读

SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。SPI 机制在第三方框架中也有所应用,比如 Dubbo 就是通过 SPI 机制加载所有的组件。不过,Dubbo 并未使用 Java 原生的 SPI 机制,而是对其进行了增强,使其能够更好的满足需求。在 Dubbo 中,SPI 是一个非常重要的模块。基于 SPI,我们可以很容易的对 Dubbo 进行拓展。

关于Java的SPI机制

Dubbo的SPI主要改进了JDK的SPI实现:

1,JDK标准的SPI会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。
2,如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK标准的ScriptEngine,通过getName();获取脚本类型的名称,但如果RubyScriptEngine因为所依赖的jruby.jar不存在,导致RubyScriptEngine类加载失败,这个失败原因被吃掉了,和ruby对应不起来,当用户执行ruby脚本时,会报不支持ruby,而不是真正失败的原因。
3,增加了对扩展点IoC和AOP的支持,一个扩展点可以直接setter注入其它扩展点

关于拓展点:Dubbo作用灵活的框架,并不会强制所有用户都一定使用Dubbo提供的某些架构。例如注册中心(Registry),Dubbo提供了zk和redis,但是如果我们更倾向于其他的注册中心的话,我们可以替换掉Dubbo提供的注册中心。针对这种可被替换的技术实现点我们称之为扩展点,类似的扩展点有很多,例如Protocol,Filter,Loadbalance等等。

Dubbo SPI 约定

SPI文件的存储路径在以下三个文件路径:

  • META-INF/dubbo/internal/ dubbo内部实现的各种扩展都放在了这个目录了
  • META-INF/dubbo/
  • META-INF/services/

文件名为接口的全限定名(包名+类名),内容为:配置名=扩展实现类全限定名,多个实现类用换行符分隔。
对于在扩展类的 jar 包内 ,放置扩展点配置文件 META-INF/dubbo/接口全限定名。

相关类注解说明

Dubbo SPI核心类是ExtensionLoader每个SPI接口都会对应一个ExtensionLoader实例

相关注解

  • @SPI: 拓展点接口上,可指定默认值
  • @Adaptive:这个注解和@SPI注解配置使用,用于它可以标注在SPI接口扩展实现类上,也可以标注在SPI接口的方法上。如果注解在标注在SPI接口的方法上说明就是一个动态代理类,它会通过dubbo里com.alibaba.dubbo.common.compiler.CompilerSPI接口通过字节码技术来创建对象。创建出来的对象名格式为SPI接口$Adaptive,例如Protocol接口创建的SPI对象为Protocol$Adaptive。
  • @Activate: 是一个 Duboo 框架提供的注解。在 Dubbo 官方文档上有记载:
    对于集合类扩展点,比如:Filter, InvokerListener, ExportListener, TelnetHandler, StatusChecker等, 可以同时加载多个实现,此时,可以用自动激活来简化配置。

关于Wrapper包装类
Wrapper: Dubbo在加载某个接口的扩展类时候,若其没有被@Adaptive标注,且其有单把该接口作为参数的构造函数的情况下,那么它就是该接口的包装类,此时Dubbo会在真正的实现类上层包装上盖Wrapper。即这个时候从ExtensionLoader中返回的实际扩展类是被Wrapper包装的接口实现类。

由于代码封装的好,导致调用链有点长,这里先贴个所调用到的方法
在这里插入图片描述

ExtensionLoader

Dubbo SPI核心类是ExtensionLoader,通过 ExtensionLoader 的 getExtensionLoader 方法获取该接口对应的 ExtensionLoader 对象,然后再通过 ExtensionLoader 的 getExtension 方法获取拓展类对象

几个重要方法:

  • getExtensionLoader(Class type) 从缓存中获取ExtensionLoader,然后缓存起来
  • getAdaptiveExtension() 获取一个扩展类,如果@Adaptive注解在类上就是一个装饰类;如果注解在方法上就是一个动态代理类,例如Protocol$Adaptive对象。
  • getExtension(String name) 获取一个指定对象
  • getActivateExtension(URL url, String[] values, String group):方法主要获取当前扩展的所有可自动激活的实现标注了@Activate注解

在开始分析这些方法先贴一些要用到的变量:实现过程对配置名,实现类Class对象,实现类对象等各种关系做了缓存,这里先全部贴出来,不用管这直接在实际代码中来看

// map<拓展点接口, ExtensionLoader>,每个拓展点接口都有一个对应的ExtensionLoader对象
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = 
							new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();

// 拓展点接口在@SPI注解中指定的实现类的名字
private String cachedDefaultName;

//是个ConcurrentHashSet,存储该接口所有的包装类Class对象
private Set<Class<?>> cachedWrapperClasses;

//当厂商为该拓展点接口指定的实现类即没有被Adaptive标注也非接口的包装类
//但其被@Activate标注,便将其存储进cachedActivates
//key为第一个配置名name,因为可能是:name1,name2=xxxxx的情况
//value为实现类对象
private final Map<String, Object> cachedActivates = new ConcurrentHashMap<String, Object>();

//key为实现类的class对象,该实现类即没有被Adaptive标注也非接口的包装类
//value为第一个配置名name
private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<Class<?>, String>();

//该Holder的value是个map,其key是属性名,value是实现类的class对象
//该实现类即没有被Adaptive标注也非接口的包装类
private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<Map<String, Class<?>>>();


//在介绍下面这两个变量之前先介绍一个名词:自适应扩展点实现类
//对于SPI接口,首先去指定目录找文件名为接口全限定名的文件,加载文件里指定的类
//但是若该指定类并非是@Adaptive标注的类,则Dubbo会为该接口生成一个类作为该接口的实现类
//这样的类叫做自适应扩展点实现类,这种类的名字也很特殊,<拓展点接口名>$Adpative 
//如Protocol$Adpative

// 拓展点接口实现类对象,被存储进Holder里的value,该类就是个简单的存储类
// 其value是volatile的,cachedAdaptiveInstance对象在创建实现类的操作中充当锁
private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();

// volatile的,拓展点接口实现类的Class对象
private volatile Class<?> cachedAdaptiveClass = null;

先总述下下面代码的逻辑:去约定的目录下查找文件名为拓展点接口全限定名的文件,加载该文件中指定的实现类,若该指定类上没有@Adaptive标注,而在该拓展点接口方法上有@Adaptive标注,则生成自适应扩展点实现类对象作为其实现类。在这一过程中对多种对象等信息进行了缓存,以用于其它的功能,就如上面介绍的那些。

在阅读代码中带着这三个问题:1,类加载器如何选择。2,线程安全问题。3,如何扫描遍历文件。

1,getExtensionLoader

初始化一个ExtensionLoader,缓存起来。并为非ExtensionFactory的对象创建一个objectFactory用来依赖注入。

// map<拓展点接口, ExtensionLoader>,
// 每个拓展点接口都有一个对应的ExtensionLoader对象
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> 
	EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
	
    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 interface!");
        }
        //接口需要@SPI注解
        // type.isAnnotationPresent(SPI.class)
        if (!withExtensionAnnotation(type)) {
   
            throw new IllegalArgumentException("Extension type (" + type +
                    ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
        }
		//从缓存中获取该SPI接口对应的ExtensionLoader,没有就创建
        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;
    }

接口对应的ExtensionLoader,利用其来获取拓展实现类。

    private ExtensionLoader(Class<?> type) {
   
        this.type = type;
        objectFactory = (type == ExtensionFactory.class ? null : 
        	ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
   
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值