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,是实现插件化架构的关键组件之一。