SITUATION(背景情境)
1.1 业务场景定位
在 Lattice 框架的插件化架构中,lattice-dynamic-loading 模块解决了以下核心问题:
插件热加载:无需重启应用即可加载新业务插件
运行时扩展:支持业务方在运行时动态添加/删除业务逻辑
隔离性保障:每个插件使用独立的类加载器,避免类冲突
Spring 生态集成:将插件中的 Bean 和 MVC Controller 动态注册到 Spring 容器
1.2 技术依赖分析
依赖 |
作用 |
| lattice-model | 提供核心注解和模型定义 |
| lattice-runtime | 提供运行时注册和执行能力 |
| commons-configuration2 | 读取配置文件 |
| commons-io | 文件操作工具 |
| spring-boot-starter-web | Spring MVC 动态注册 |
二、TASK(设计目标)
lattice-dynamic-loading ├── 插件生命周期管理 │ ├── 插件安装(Install) │ └── 插件卸载(Uninstall) ├── 类加载隔离 │ ├── 自定义 ClassLoader │ └── 类加载策略 ├── 资源注册 │ ├── Business 注册 │ ├── Product 注册 │ └── Spring Bean 注册 └── 配置管理 ├── 插件目录配置 └── 运行时属性管理
设计原则遵循
单一职责:每个 Installer/Uninstaller 只负责一种资源类型
开闭原则:通过接口扩展,可轻松添加新的安装器
依赖倒置:依赖抽象的 LatticeInstaller 接口
里氏替换:所有 Installer 实现可互相替换
三、ACTION(实现架构)
3.1 目录结构分析
lattice-dynamic-loading/ │ ├── 📦 classloader/ 【类加载层】 │ ├── LatticeClassLoader.java // 自定义类加载器 │ └── LatticeDynamicClassLoaderBuilder.java // SPI 实现,提供类加载器 │ ├── 🔧 installer/ 【安装层】 │ ├── LatticeInstaller.java // 安装器接口 │ ├── BusinessInstaller.java // Business 安装器 │ ├── ProductInstaller.java // Product 安装器 │ ├── SpringInstaller.java // Spring Bean/MVC 安装器 │ └── InstallResult.java // 安装结果封装 │ ├── 🗑️ destroy/ 【卸载层】 │ ├── LatticeUninstaller.java // 卸载器接口 │ ├── BusinessUninstaller.java // Business 卸载器 │ ├── ProductUninstaller.java // Product 卸载器 │ ├── SpringUninstaller.java // Spring Bean/MVC 卸载器 │ └── DestroyResult.java // 卸载结果封装 │ ├── 📋 model/ 【模型层】 │ ├── PluginFileInfo.java // 插件文件元信息 │ └── SpringBeanInfo.java // Spring Bean 信息 │ ├── ⚙️ config/ 【配置层】 │ └── LatticeDynamicConfig.java // 动态加载配置 │ ├── 🎛️ properties/ 【属性层】 │ ├── LatticeDynamicProperties.java // 插件目录配置 │ └── DynamicApplicationProperties.java // 应用属性读取 │ ├── 🛠️ utils/ 【工具层】 │ ├── DynamicUtils.java // 动态加载工具 │ └── SpringUtils.java // Spring Bean 操作工具 │ └── 🎯 LatticeDynamic.java 【核心门面】 // 插件管理的统一入口
3.2 核心流程设计(执行链路)
3.2.1 插件安装流程(STAR 分析)
Situation:用户上传一个插件 JAR 文件
Task:将 JAR 中的 Business/Product/Spring Bean 注册到系统
Action:installPlugin() 方法执行
// 执行链路分析 installPlugin(originFile) ↓ 1. copyAndCreatePluginFile() // 文件复制到插件目录 ↓ 2. new LatticeClassLoader() // 创建隔离的类加载器 ↓ 3. BusinessInstaller.install() // 注册 Business 和 Realization ↓ 4. ProductInstaller.install() // 注册 Product ↓ 5. SpringInstaller.install() // 注册 Spring Bean 和 MVC ↓ 6. Lattice.getInstance().reload() // 重新加载缓存
Result:插件成功注册,业务逻辑立即生效
3.2.2 插件卸载流程
Situation:需要移除某个插件
Task:清理所有注册的资源
Action:uninstallPlugin() 方法执行
// 执行链路分析 uninstallPlugin(id) ↓ 1. 查找 PluginFileInfo // 根据 MD5 ID 查找插件 ↓ 2. BusinessUninstaller.uninstall() // 清除 Business 缓存 ↓ 3. ProductUninstaller.uninstall() // 清除 Product 缓存 ↓ 4. SpringUninstaller.uninstall() // 注销 Spring Bean 和 MVC ↓ 5. file.delete() // 删除插件文件
3.3 关键设计模式 3.3.1 策略模式(Installer/Uninstaller) // 接口定义 interface LatticeInstaller { InstallResult install(LatticeClassLoader classLoader, PluginFileInfo fileInfo); } // 具体策略 - BusinessInstaller // 处理业务逻辑注册 - ProductInstaller // 处理产品注册 - SpringInstaller // 处理 Spring 生态注册
优势:
✅ 易于扩展新的资源类型
✅ 每个 Installer 职责单一
✅ 可独立测试和维护
3.3.2 门面模式(LatticeDynamic)
[LatticeDynamic](file:///Volumes/xuan-pan/lattice/lattice-tools/lattice-dynamic-loading/src/main/java/org/hiforce/lattice/dynamic/LatticeDynamic.java#L34-L203) 提供统一的插件管理接口:
public class LatticeDynamic { // 对外暴露的简单接口 public void installPlugin(PluginFileInfo file) public void uninstallPlugin(String id) // 内部协调多个 Installer/Uninstaller private List<LatticeInstaller> installers = Lists.newArrayList( new BusinessInstaller(), new ProductInstaller(), new SpringInstaller() ); }
优势:
✅ 隐藏内部复杂性
✅ 提供统一的操作入口
✅ 便于集中管理插件状态
3.3.3 SPI 机制(CustomClassLoaderSpi)
LatticeDynamicClassLoaderBuilder 通过 @AutoService 实现 SPI:
✅ 自动被 Lattice 框架发现
✅ 将插件 ClassLoader 注入到框架核心
✅ 实现插件类的运行时加载
3.4 核心技术实现剖析
3.4.1 插件隔离机制
问题:如何防止插件之间的类冲突?
解决方案:PluginFileInfo 使用 MD5 唯一标识
public class PluginFileInfo { private String id; // 文件内容的 MD5 值 private void buildMD5Value() { MessageDigest digest = MessageDigest.getInstance("MD5"); byte[] bytes = digest.digest(FileUtils.readFileToByteArray(file)); // 转换为 16 进制字符串作为唯一 ID } }
优势:
✅ 基于内容的唯一性,防止重复安装
✅ 可通过 ID 精确定位插件
✅ 支持版本升级(内容变化则 ID 变化)
3.4.2 Spring Bean 动态注册
挑战:如何将插件中的 @Service 和 @RestController 注册到运行中的 Spring 容器?
实现:SpringInstaller 的精妙设计
// 1. 扫描插件中带有 Spring 注解的类 List<Class<?>> classList = fileInfo.getJarFile().stream() .filter(p -> p.endsWith(".class")) .map(className -> classLoader.loadClass(className)) .filter(this::hasSpringAnnotation) .collect(Collectors.toList()); // 2. 动态注册 Bean for (Class<?> clazz : classList) { Object bean = SpringUtils.registerBean(clazz.getSimpleName(), clazz); } // 3. 动态注册 MVC 映射 RequestMappingHandlerMapping mapping = ...; for (Method method : methods) { RequestMappingInfo info = buildMappingInfo(method); mapping.registerMapping(info, bean, method); }
关键点:
✅ 通过反射扫描 JAR 包内的所有类
✅ 使用 BeanDefinitionRegistry 动态注册
✅ 手动注册 RequestMapping 到 Spring MVC
✅ 支持自动装配(autowireBean)
3.4.3 资源清理机制
问题:卸载插件时如何彻底清理?
方案:SpringUninstaller 精准清理
public DestroyResult uninstall(...) { // 1. 清理 MVC 映射 beans.stream() .filter(SpringBeanInfo::isMvc) .forEach(info -> { RequestMappingHandlerMapping mapping = ...; info.getMappingInfos().forEach(mapping::unregisterMapping); }); // 2. 清理 Spring Bean beans.forEach(p -> SpringUtils.removeBean(p.getBeanName())); return DestroyResult.success(); }
保障措施:
✅ 记录所有注册的 Bean 和 Mapping
✅ 卸载时逆向操作
✅ 防止内存泄漏
四、RESULT(设计成果)
4.1 架构优势
维度 |
实现效果 |
技术保障 |
可扩展性 |
新增资源类型只需实现 Installer 接口 |
策略模式 + 接口编程 |
隔离性 |
插件间类互不干扰 |
独立 ClassLoader |
| 可靠性 | 安装失败自动回滚 |
Try-with-resources + 异常处理 |
可观测性 |
详细的日志输出 |
SLF4J 日志集成 |
热部署 |
零停机更新业务逻辑 |
动态注册 + 缓存刷新 |
4.2 性能考量
类加载优化:
使用 try-with-resources 自动关闭 ClassLoader
避免类加载器泄漏
并发安全:
installPlugin() 和 uninstallPlugin() 使用 synchronized
currentFiles 使用 ConcurrentHashSet
资源管理:
插件卸载时删除物理文件
清理所有注册的 Bean 和 Mapping
4.3 潜在风险与改进建议
⚠️ 风险点
类加载器内存泄漏:
问题:如果插件类被系统持有引用,ClassLoader 无法回收
建议:增加引用检测和强制清理机制
Spring Bean 冲突:
问题:不同插件可能注册同名 Bean
建议:Bean 名称加插件前缀
并发安装冲突:
问题:多个插件同时安装可能冲突
建议:使用队列顺序处理
五、总结:设计哲学
lattice-dynamic-loading 的设计体现了以下哲学:
职责分离:安装、卸载、类加载各司其职
插件化思维:连 Spring 集成都视为可选的 Installer
防御性编程:大量的异常处理和日志记录
运行时增强:不改变框架核心,通过插件扩展能力
核心价值:让 Lattice 框架从"编译时固定"进化为"运行时可变",实现真正的业务敏捷性。