Dubbo 您所在的位置:网站首页 dubbo类加载器 Dubbo

Dubbo

2023-08-10 10:57| 来源: 网络整理| 查看: 265

[TOC]

扩展点加载机制

Dubbo的扩展点加载来自于JDK的标准SPI(Service Provider Interface)扩展点发现机制,并对其进行加强。

来自Dubbo官网的描述,Dubbo改进了JDK标准SPI,并且解决了一下问题:

JDK标准的SPI会一次性实例化扩展点所有实现,如果扩展实现初始化很耗时,但如果没有用上也加载就会很浪费资源。 如果扩展点加载失败,连扩展点的名称都拿不到了。 增加了对扩展点的IoC和AOP的支持,一个扩展点可以直接setter注入其他扩展点。

Dubbo约定在扩展类的jar包内,放置扩展点配置文件META-INF/dubbo/接口全限定名,内容为:配置名=扩展实现类全限定名,存在多个实现类时用换行符分割。

简单介绍Java SPI机制

SPI ,全称为Service Provider Interface, 是一种服务发现机制。它通过在ClassPath路径下的META-INF/service查找并自动加载实现类。

简单来说就是一种动态替换发现机制,接口运行时才发现具体的实现类,只需要在运行前添加一个实现即可,并且将实现描述给JDK即可,这里的描述便是上面提到的META-INF/service下的配置。也可以随时对该描述进行修改,完成具体实现的替换。

Java提供了很多SPI,允许第三方为这些接口提供实现;常见的SPI有JDBC、JNDI等。

这些SPI接口是由Java来提供的,而SPI的实现则是作为依赖的第三方jar加载进CLASSPATHzhong .

例如:Java提供的java.sql.Driver和MySql实现的com.mysql.cj.jdbc.Driver,并且在META-INF/service下存在该配置:com.mysql.cj.jdbc.Driver

写个Demo实现看看

下面简单写个Demo实现看看:

我们先提供一个接口

package cc.ccoder.loader; public interface DownloadStrategy { void download(); } 复制代码

分别提供两个实现类,这两个实现类可以理解成第三方jar中对标准接口的实现。

package cc.ccoder.loader; public class HttpDownloadStrategy implements DownloadStrategy{ @Override public void download() { System.out.println("采用Http下载方式"); } } 复制代码 package cc.ccoder.loader; public class SftpDownloadStrategy implements DownloadStrategy { @Override public void download() { System.out.println("采用SFTP方式下载"); } } 复制代码

在META-INF/services目录下创建一个扩展点配置文件,路径为META-INF/services/接口全限定名称。那么我们这个例子的文件名称是cc.ccoder.loader.DownloadStrategy 并且文件名内容为具体实现类全限定名称。

cc.ccoder.loader.HttpDownloadStrategy cc.ccoder.loader.SftpDownloadStrategy 复制代码

写一个测试类通过ServiceLoader加载具体的实现

package cc.ccoder.loader; import java.util.ServiceLoader; public class JavaSpiLoaderTest { public static void main(String[] args) { ServiceLoader serviceLoader = ServiceLoader.load(DownloadStrategy.class); //这里会一次实例化所有接口实现 for (DownloadStrategy downloadStrategy : serviceLoader) { downloadStrategy.download(); } } } 复制代码

看看输出结果

和我们预期一样,会一次实例化所所有接口实现。

Connected to the target VM, address: '127.0.0.1:64219', transport: 'socket' 采用Http下载方式 采用SFTP方式下载 Disconnected from the target VM, address: '127.0.0.1:64219', transport: 'socket' Process finished with exit code 0 复制代码 普通扩展点加载

上面提到了Java的SPI机制,那么Dubbo的ExtensionLoader则是对SPI的加强。这里我们改造一下上面那个例子并且写一个测试看看。

package cc.ccoder.loader; import org.apache.dubbo.common.extension.SPI; @SPI("http") public interface DownloadStrategy { void download(); } 复制代码

在DownloadStrategy接口上添加@SPI注解。

修改META-INF/services的配置文件

http=cc.ccoder.loader.HttpDownloadStrategy sftp=cc.ccoder.loader.SftpDownloadStrategy 复制代码

运行一下测试方法

package cc.ccoder.loader; import org.apache.dubbo.common.extension.ExtensionLoader; public class DubboSpiLoaderTest { public static void main(String[] args) { ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(DownloadStrategy.class); //这里getExtension方法进行加载具体扩展点 DownloadStrategy downloadStrategy = extensionLoader.getExtension("http"); downloadStrategy.download(); } } 复制代码 采用Http下载方式 Process finished with exit code 0 复制代码

从获取扩展点的方式我们可以发现,dubbo扩展点是获取具体的实例,并不需要实例化所有的实现类。

getExtensionLoader

从示例中我们了解到从ExtensionLoader.getExtensionLoader方法中可以看到类似于工厂方法,获取扩展点的Extension,再通过调用getExtension方法传入指定的名字即可获取到指定的扩展点实现类。

public static ExtensionLoader getExtensionLoader(Class 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() + "!"); } // 查看本地是否存在扩展点类型的Loader // 创建一个ExtensionLoader 并且将ExtensionFactory 的实现类均放入这个EXTENSION_LOADERS的本地map缓存中 // ExtensionFactory 的实现有SpiExtensionFactory 和 SpringExtensionFactory ExtensionLoader loader = (ExtensionLoader) EXTENSION_LOADERS.get(type); if (loader == null) { EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type)); loader = (ExtensionLoader) EXTENSION_LOADERS.get(type); } return loader; } 复制代码

image-20210720183149860

getExtension getExtension(name) -> createExtension(name) #缓存获取 无缓存则创建 -> getExtensionClasses().get(name) #从缓存中获取name的扩展点类型 -> 实例化扩展类 -> injectExtension(instance) # 扩展点IOC注入 -> instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)) #循环遍历所有wrapper实现,实例化wrapper并进行扩展点IOC注入 复制代码

获取Dubbo的扩展点就从这个方法ExtensionLoader.getExtension(String name)出发吧。

public T getExtension(String name, boolean wrap) { if (StringUtils.isEmpty(name)) { throw new IllegalArgumentException("Extension name == null"); } //如果传入的name为字符串true 则返回默认的扩展点 if ("true".equals(name)) { return getDefaultExtension(); } //从缓存中获取Holder 不存在则创建该缓存对象 并且从缓存中拿,可以防止放入缓存中失败的情况、 //dubbo 实际将扩展类封装在Holder类中 final Holder holder = getOrCreateHolder(name); Object instance = holder.get(); if (instance == null) { //double-check 保证线程安全 synchronized (holder) { instance = holder.get(); if (instance == null) { //缓存中没有就创建该扩展点 instance = createExtension(name, wrap); //放入缓存 下次直接从缓存中拿取 holder.set(instance); } } } return (T) instance; } 复制代码

在上述getDefaultExtension()方法主要是去拿默认扩展点名称,在load所有扩展点的方法里,会判断@SPI中的value值,即为默认的扩展点名称,将会赋值给cachedDefaultName,所以这里可以拿到一个默认扩展类。

createExtension

接下来看看createExtension(String name,boolean wrap)方法如何创建扩展点。

private T createExtension(String name, boolean wrap) { //name:spi 获取到SpiExtensionFactory Class clazz = getExtensionClasses().get(name); if (clazz == null || unacceptableExceptions.contains(name)) { throw findException(name); } try { //加载本地缓存Map(SpringExtensionFactory. SpiExtensionFactory)中的SpiExtensionFactory T instance = (T) EXTENSION_INSTANCES.get(clazz); if (instance == null) { //name:spi clazz:SpiExtensionFactory 这里反射将其加载到缓存中 EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.getDeclaredConstructor().newInstance()); instance = (T) EXTENSION_INSTANCES.get(clazz); } //IOC注入 injectExtension(instance); if (wrap) { //包装类装饰 List


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有