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. 扩展机制的工作流程
-
初始化 ExtensionLoader 实例:
- 调用
getExtensionLoader(Class<T> clazz)
方法,获取指定扩展点接口的 ExtensionLoader 实例。 - 如果是首次创建,会构造新的 ExtensionLoader,并加载扩展实现类信息。
- 调用
-
加载扩展实现类信息:
- 从
META-INF/shenyu/
目录下读取配置文件,文件名为扩展点接口的全限定名。 - 配置文件内容为键值对,每一对代表一个扩展实现的名称和实现类的全限定名。
- 加载实现类,检查其合法性(是否实现了扩展点接口,是否标注了
@Join
注解)。 - 将实现类的信息保存到
cachedClasses
中。
- 从
-
获取扩展实现实例:
- 调用
getJoin(String name)
方法,根据名称获取对应的扩展实现实例。 - 如果是单例,并且缓存中已有实例,直接返回缓存的实例。
- 如果没有缓存实例,创建新的实例并缓存(对于单例)。
- 对于非单例,每次都创建新的实例返回。
- 调用
-
获取所有扩展实现实例列表:
- 调用
getJoins()
方法,遍历所有的扩展实现,按照顺序获取实例列表。
- 调用
6. 注意点
- 线程安全:使用了双重检查锁(Double-Checked Locking)和
volatile
变量,确保在多线程环境下的安全性。 - 缓存机制:对扩展实现类信息和实例进行了缓存,减少反复加载和创建的开销。
- 单例支持:通过
@Join
注解的isSingleton
属性,控制扩展实现是单例模式还是多例模式。 - 顺序控制:扩展实现可以通过
@Join
的order
属性指定加载顺序,在获取扩展列表时按照顺序返回。
7. 总结
ExtensionLoader
类通过 SPI 机制,实现了动态加载扩展点接口的实现类,并提供了缓存、单例、多例、顺序控制等功能。它在框架中用于解耦业务逻辑和具体实现,使得系统具有良好的可扩展性和维护性。
此类的设计借鉴了 Apache Dubbo 中的 ExtensionLoader
,是实现插件化架构的关键组件之一。