深入源码理解Dubbo SPI机制

本文深入探讨了Dubbo的SPI(Service Provider Interface)机制,解释了其作为服务发现的原理。内容包括SPI的触发入口、加载策略、@Adaptive和@Activate注解的区别以及在Dubbo源码中的应用。此外,还提供了SPI流程图以帮助理解。
摘要由CSDN通过智能技术生成

什么是Dubbo SPI

用Dubbo.apache.org官方的话概况就是SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。

那么Dubbo SPI到底是怎么实现的呢?

首先我们来看一下触发SPI的入口

ExtensionLoader.getExtensionLoader(FrameworkExt.class).getSupportedExtensionInstances();

Dubbo SPI机制的前提:接口必须标注@SPI注解。
当dubbo源码中需要获取到FrameworkExt接口的实现类时,会先获取一个ExtensionLoader类。

@SPI
public interface FrameworkExt extends Lifecycle {

}

getSupportedExtensionInstances()则是获取当前接口的所有支持的实现类。
首先,会去ExtensionLoader类对应的缓存中获取类的实例。如果是第一次加载,则加载特定路径下的资源文件。然后根据资源文件中规定的别名和类名加载和实例化接口的实现类。

这边需要注意的是,加载资源文件有多个加载策略。下面附上策略接口的类关系图以及接口源码。
在这里插入图片描述

public interface LoadingStrategy extends Prioritized {

    String directory();

    default boolean preferExtensionClassLoader() {
        return false;
    }

    default String[] excludedPackages() {
        return null;
    }

    /**
     * Indicates current {@link LoadingStrategy} supports overriding other lower prioritized instances or not.
     *
     * @return if supports, return <code>true</code>, or <code>false</code>
     * @since 2.7.7
     */
    default boolean overridden() {
        return false;
    }
}

可以看到,LoadingStrategy接口定义了directory方法。通过实现接口实现directory方法来规定每个加载策略加载的路径。

DubboExternalLoadingStrategy: META-INF/dubbo/external/
DubboInternalLoadingStrategy: META-INF/dubbo/internal/
DubboLoadingStrategy: META-INF/dubbo/
ServiceLoadingStrategy: META-INF/services/

举个栗子:

我们当前使用SPI机制去加载FramworkExt所有的实现类,那么程序就会默认通过JDK的类加载器加载上述四个包路径下(META-INF/dubbo/external/、META-INF/dubbo/internal/、META-INF/dubbo/、META-INF/services/)以org.apache.dubbo.common.context.FrameworkExt为文件名的资源文件。

打开META-INF\dubbo\internal\org.apache.dubbo.common.context.FrameworkExt文件

#别名=类名
config=org.apache.dubbo.config.context.ConfigManager
environment=org.apache.dubbo.common.config.Environment
repository=org.apache.dubbo.rpc.model.ServiceRepository

分别加载并缓存对应的ConfigManager、Environment、ServiceRepository实现类。

不同的类缓存的区别

ExtensionLoader加载的类,缓存的地方是有区别的。
在Dubbo源码中,通过ExtensionLoader加载的类大致上分为四种。

  1. 标注了@Adaptive的类
  2. 泛型T的包装类
  3. 标注了@Activate的类
  4. 其他的类

标注了@Adaptive的类是缓存在ExtensionLoader类中的Class<?> cachedAdaptiveClass变量下,用volatile进行修饰。

而包装类则是缓存在Set<Class<?>> cachedWrapperClasses变量下,为一个Set集合。

最后标注了@Activate注解的类和其他的类则都缓存在ConcurrentMap<Class<?>, String> cachedNames变量中,其中Map的value为实现类的别名。未设置别名的类则获取类名进行缓存。

思考:标注了@Adaptive和@Activate的类与一般的类的区别

自适应拓展

标注了@Adaptive的类会根据URL配置的不同去动态加载对应的类。
以Protocol接口为例。

@Adaptive
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;

Protocol接口的export方法标注了@Adaptive,如果是方法上标注了@Adaptive那么会自动生成一个代理类。

下面附上生成的代理类的源码

package org.apache.dubbo.rpc;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {

	public void destroy()  {
		throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
	}
	
	public int getDefaultPort()  {
		throw new UnsupportedOperationException("The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
	}
	
	public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
		if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
		if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
		org.apache.dubbo.common.URL url = arg0.getUrl();
		String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
		if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
		org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
		return extension.export(arg0);
	}
	
	public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
		if (arg1 == null) throw new IllegalArgumentException("url == null");
		org.apache.dubbo.common.URL url = arg1;
		String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
		if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
		org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
		return extension.refer(arg0, arg1);
	}

}

可以看到自动生成的Protocal$Adaptive类,继承了Protocol接口,并重写了export方法,通过export方法获取当前url配置信息,并调用SPI机制动态的获取对应的协议的处理类。

在这里插入图片描述

可激活拓展

标注了@Activate注解的类,可以通过URL配置动态加载使用。

@Activate(group = PROVIDER, value = ACCESS_LOG_KEY)
public class AccessLogFilter implements Filter {

比方说AccessLogFilter类,如果URL配置中配置了accesslog=value,那么该过滤器会被自动加载到过滤器链中,并进行相应的业务处理。

SPI机制在dubbo源码中的应用

SPI机制便于拓展和动态转换接口的实现类。SPI在Dubbo源码中的应用是随处可见的,最常见的就是在Dubbo外部环境初始化的时候,Dubbo会通过SPI机制获取对应的FrameworkExt类的实现类,并完成外部环境初始化步骤,加载配置信息以及相关环境信息。

那么这样就可以在初始化的时候,非常优雅地初始化外部环境。不需要手动一个个调用对应的实现类来进行初始化,也非常的便于管理源代码。

更多的应用就不在这里举例说明了。

SPI流程图

最后附上Dubbo SPI机制的流程图。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值