源码

173 阅读6分钟

ExtensionLoader 类解析

ExtensionLoader 是一个用于加载和管理扩展点(SPI,Service Provider Interface)的通用类。这种机制允许在运行时动态地加载实现特定接口的类,从而实现模块化和可扩展性。

该类的主要功能是根据指定的接口类型(Class<T>),通过扫描特定的目录(如 META-INF//),加载其对应的实现类,并提供缓存和单例管理等特性。

以下是对该类的详细解析:

1. 类的主要成员变量

  • clazz:接口类型,即需要加载其实现的扩展点接口。
  • classLoader:类加载器,用于加载类文件。
  • cachedClasses:缓存的扩展实现类信息,键为实现的名称,值为对应的类实体信息。
  • cachedInstances:缓存的扩展实现类的实例(单例),键为实现的名称,值为对应的实例持有者(Holder<Object>)。
  • joinInstances:缓存的扩展实现类的实例(用于非单例情况),键为类对象,值为其实例。
  • cachedDefaultName:默认的扩展实现名称,从接口的 @SPI 注解中获取。

2. 核心方法解析

2.1 获取 ExtensionLoader 实例

public static <T> ExtensionLoader<T> getExtensionLoader(final Class<T> clazz, final ClassLoader cl)
  • 功能:获取指定接口类型的 ExtensionLoader 实例。
  • 逻辑
    • 检查 clazz 是否为接口,且是否标注了 @SPI 注解。
    • 从缓存中获取已有的 ExtensionLoader 实例,如果没有,则创建新的实例并缓存。

2.2 获取默认的扩展实现

public T getDefaultJoin()
  • 功能:获取默认的扩展实现实例。
  • 逻辑
    • 首先加载所有的扩展实现类信息(getExtensionClassesEntity())。
    • 检查是否有默认名称(cachedDefaultName),如果有,则调用 getJoin(cachedDefaultName) 获取对应的实例。

2.3 获取指定名称的扩展实现

public T getJoin(final String name)
  • 功能:根据名称获取对应的扩展实现实例。
  • 逻辑
    • 检查名称是否为空,且是否存在对应的类实体信息。
    • 如果扩展实现是非单例的,每次都创建新的实例。
    • 如果是单例的,首先从缓存中获取实例,如果没有则创建并缓存。

2.4 获取所有的扩展实现实例列表

public List<T> getJoins()
  • 功能:获取所有已加载的扩展实现实例列表。
  • 逻辑
    • 加载所有的扩展实现类信息。
    • 遍历类实体信息,根据其顺序(order)进行排序。
    • 调用 getJoin(name) 获取每个实现的实例,收集成列表返回。

2.5 加载扩展实现类信息

private Map<String, ClassEntity> loadExtensionClass()
  • 功能:加载指定接口类型的所有扩展实现类信息。
  • 逻辑
    • 读取接口的 @SPI 注解,获取默认实现名称。
    • 调用 loadDirectory(classes) 方法,从指定目录加载配置文件并解析实现类。

2.6 从指定目录加载配置文件

private void loadDirectory(final Map<String, ClassEntity> classes)
  • 功能:从 META-INF/shenyu/ 目录加载配置文件。
  • 逻辑
    • 构造配置文件的路径,格式为 META-INF/shenyu/接口全限定名
    • 使用类加载器读取所有配置文件(可能有多个 jar 包中包含此配置)。
    • 遍历每个配置文件,调用 loadResources(classes, url) 解析配置。

2.7 解析配置文件并加载类

private void loadResources(final Map<String, ClassEntity> classes, final URL url)
  • 功能:解析单个配置文件,加载其中定义的扩展实现类。
  • 逻辑
    • 打开配置文件的输入流,读取属性(Properties)。
    • 遍历每个键值对(扩展名称 - 实现类全限定名),调用 loadClass(classes, name, classPath) 加载类。

2.8 加载具体的扩展实现类

private void loadClass(final Map<String, ClassEntity> classes, final String name, final String classPath)
  • 功能:加载指定的扩展实现类并保存其信息。
  • 逻辑
    • 使用类加载器加载实现类。
    • 检查实现类是否实现了扩展点接口(clazz.isAssignableFrom(subClass))。
    • 检查实现类是否标注了 @Join 注解。
    • 创建 ClassEntity(包含名称、顺序、类对象、是否单例等信息),保存到缓存中。

3. 相关的内部类

3.1 Holder 类

private static final class Holder<T>
  • 功能:用于持有对象实例及其顺序信息。
  • 成员变量
    • value:持有的对象实例。
    • order:顺序信息,用于排序。
  • 方法
    • getValue() / setValue(T value):获取或设置实例。
    • getOrder() / setOrder(Integer order):获取或设置顺序。

3.2 ClassEntity 类

private static final class ClassEntity
  • 功能:用于保存扩展实现类的相关信息。
  • 成员变量
    • name:扩展实现的名称。
    • order:顺序信息,用于排序。
    • clazz:扩展实现的类对象。
    • isSingleton:是否为单例。
  • 方法
    • 对应的 getter 和 setter 方法。

4. 注解的作用

4.1 @SPI 注解

  • 作用:标注在扩展点接口上,表示这是一个可扩展的接口。
  • 属性
    • value:默认的扩展实现名称。

4.2 @Join 注解

  • 作用:标注在扩展实现类上,提供元数据信息。
  • 属性
    • order:扩展实现的顺序。
    • isSingleton:是否为单例。

5. 扩展机制的工作流程

  1. 初始化 ExtensionLoader 实例

    • 调用 getExtensionLoader(Class<T> clazz) 方法,获取指定扩展点接口的 ExtensionLoader 实例。
    • 如果是首次创建,会构造新的 ExtensionLoader,并加载扩展实现类信息。
  2. 加载扩展实现类信息

    • META-INF/shenyu/ 目录下读取配置文件,文件名为扩展点接口的全限定名。
    • 配置文件内容为键值对,每一对代表一个扩展实现的名称和实现类的全限定名。
    • 加载实现类,检查其合法性(是否实现了扩展点接口,是否标注了 @Join 注解)。
    • 将实现类的信息保存到 cachedClasses 中。
  3. 获取扩展实现实例

    • 调用 getJoin(String name) 方法,根据名称获取对应的扩展实现实例。
    • 如果是单例,并且缓存中已有实例,直接返回缓存的实例。
    • 如果没有缓存实例,创建新的实例并缓存(对于单例)。
    • 对于非单例,每次都创建新的实例返回。
  4. 获取所有扩展实现实例列表

    • 调用 getJoins() 方法,遍历所有的扩展实现,按照顺序获取实例列表。

6. 注意点

  • 线程安全:使用了双重检查锁(Double-Checked Locking)和 volatile 变量,确保在多线程环境下的安全性。
  • 缓存机制:对扩展实现类信息和实例进行了缓存,减少反复加载和创建的开销。
  • 单例支持:通过 @Join 注解的 isSingleton 属性,控制扩展实现是单例模式还是多例模式。
  • 顺序控制:扩展实现可以通过 @Joinorder 属性指定加载顺序,在获取扩展列表时按照顺序返回。

7. 总结

ExtensionLoader 类通过 SPI 机制,实现了动态加载扩展点接口的实现类,并提供了缓存、单例、多例、顺序控制等功能。它在框架中用于解耦业务逻辑和具体实现,使得系统具有良好的可扩展性和维护性。

此类的设计借鉴了 Apache Dubbo 中的 ExtensionLoader,是实现插件化架构的关键组件之一。