插件化架构spring-brick,从入门到奔跑
本文介绍了一款Spring Boot插件化开发框架spring-brick,使用他能开发灵活、高内聚、低耦合、可扩展的springBoot应用,一起来看看吧
插件化架构spring-brick,从入门到奔跑
前言
在当今快速迭代的软件开发环境中,系统的灵活性和可扩展性越来越受到重视。传统的单体应用架构在面对频繁功能更新时,往往需要整体重新部署,这不仅影响服务稳定性,还降低了开发效率。本文将向您介绍一款国内开源的插件化开发框架——spring-brick,它为Spring Boot应用提供了优雅的插件化解决方案,让我们的系统像搭积木一样灵活可扩展。
1、spring-brick是什么?
spring-brick是一个基于Spring Boot的插件开发框架,它允许将功能模块作为插件动态加载到主应用程序中,为Spring Boot应用开发功能插件。其核心思想是"组件即砖,组合即应用"。插件和Spring Boot应用的关系,就类似VS Code中的插件之于VS Code一样。
使用spring-brick开发插件,好比开发一个小型的Spring Boot应用。本质上spring-brick也是一种组织代码的方式,将相同功能或业务的代码内聚在一起做成一个插件。插件的生命周期是独立的,支持独立开发、测试、版本管理。
值得一提的是,spring-brick并非Spring官方的项目,而是国内的一个程序员开发的,在Gitee上开源。
2、spring-brick的运行原理
2.1 架构设计
spring-brick采用分层架构设计,主要包含以下核心组件:
插件容器
├─ 插件1 (Plugin1) // 可扩展的插件实例
│ ├─ 独立类加载器 // 插件隔离的核心组件
│ │ └─ 插件业务逻辑 // 插件自身的功能实现
主程序 (Main App)
├─ Spring Boot 容器
│ ├─ spring-brick 核心
│ │ └─ 插件管理器 // 核心组件,负责管理插件
主程序通过插件管理器与插件容器进行对接,实现了主程序与插件的解耦。
2.2 核心特性
2.2.1 类加载器隔离
spring-brick的一个核心特性就是类加载器隔离,即主程序和每个插件都有一个自己的独立的类加载器,每个类加载器只加载自己模块的代码互不影响,这样可以避免各插件之间的类冲突。
由于插件的pom中通常会依赖主程序,所以插件可以直接访问主程序中的类,但主程序不能直接使用插件中的类。这种设计保证了插件的独立性和安全性。
2.2.2 类的动态加载
spring-brick采用双亲委派机制的变种来加载类,即优先使用主程序加载的类,没有再使用当前插件中加载的类。这种机制确保了类的正确加载顺序,同时避免了类重复加载的问题。
2.2.3 通信机制
主程序中调用插件,可以使用spring-brick的扩展点机制或HTTP接口调用,其他通信方式如消息队列、三方存储等也可以灵活使用。
2.3 启动流程
2.3.1 主程序启动流程
- SpringMainBootstrap.launch() - 启动spring-brick框架
- 扫描插件路径 - 根据配置扫描插件目录
- 加载插件 - 为每个插件创建独立的类加载器
- 初始化插件 - 调用插件的main方法
- 集成到主程序 - 将插件Bean集成到主程序Spring容器
2.3.2 插件启动流程
- 继承SpringPluginBootstrap - 插件入口类
- 独立Spring容器 - 每个插件有独立的Spring容器
- Bean注册 - 插件Bean通过扩展点机制注册到主程序
3、为什么要用spring-brick?
3.1 解决的主要问题
-
动态功能更新:在不重启主程序的情况下,动态地给主程序添加、减少、更新功能。
-
避免服务拆分过度:对于中小型功能场景,当不想要引入额外的微服务所带来的复杂时,就可以使用插件。大型复杂场景,建议使用微服务。
-
插件化架构:同微服务思想一样,以插件化架构思想,帮助系统实现高内聚、低耦合、可扩展的特点
-
开发效率提升:插件可以并行开发,独立测试,大大提高了团队协作效率。
3.2 适用场景
-
功能模块化需求强的系统:如内容管理系统、企业管理平台等,需要根据不同客户需求动态加载不同功能模块。
-
业务快速迭代的场景:市场需求变化快,需要频繁更新功能而不影响系统整体稳定性。
-
多租户系统:不同租户可能需要不同的功能模块,通过插件机制可以按需加载。
-
工具型应用:需要提供丰富的扩展功能,如开发工具、监控平台等。
3.3 与微服务的对比
| 特性 | spring-brick插件化 | 微服务 |
|---|---|---|
| 部署单元 | 单个应用+多个插件 | 多个独立服务 |
| 开发门槛 | 较低,类似Spring Boot开发 | 较高,需要考虑服务拆分、通信等 |
| 性能 | 高,进程内调用,共享JVM | 较低,网络开销 |
| 适用规模 | 中小型功能模块 | 大型复杂业务模块 |
| 启动速度 | 较快 | 较慢 |
| 事务处理 | 简单,遵守单体ACID事务 | 较复杂,分布式事务 |
| 通信方式 | 进程内方法调用 | 网络通信(RPC/HTTP) |
选择spring-brick当 :
- 业务模块耦合度较高
- 需要强一致性事务
- 性能要求高,延迟敏感
- 团队规模较小,技术栈统一
- 需要快速迭代和热部署
选择传统微服务当 :
- 业务模块完全独立
- 团队技术栈多样
- 需要不同语言开发
- 系统规模非常大
- 需要独立的伸缩能力
4、如何使用spring-brick?
spring-brick的使用步骤分为三步:组件开发、组件集成、应用运行。下面我们将详细介绍每个步骤。
4.1 组件开发
4.1.1 项目结构设置
- 按照官网推荐的开发目录结构创建项目(其实只要插件的路径能被正确扫描到,分开的两个项目也行)
spring-brick-demo/
├── example-main/ # 主程序模块
│ ├── pom.xml
│ └── src/main/
├── plugins-basic/ # 基础插件模块
│ ├── pom.xml
│ └── src/main/
└── pom.xml # 父项目pom
4.1.2 主程序开发
1、在主程序pom.xml中添加spring-brick依赖:
<dependency>
<groupId>com.gitee.starblues</groupId>
<artifactId>spring-brick</artifactId>
<version>3.1.0</version>
</dependency>
2、改造主程序springBoot入口类,继承SpringBootstrap
@SpringBootApplication
public class Application implements SpringBootstrap {
public static void main(String[] args) {
SpringMainBootstrap.launch(Application.class, args);
}
@Override
public void run(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}
- 加入配置插件
在application.yml中添加配置:
plugin:
runMode: dev
mainPackage: com.gitee.starblues.example
pluginPath:
#- D://project//plugins(替换为自己环境下)
- ~\plugins-basic #插件目录或插件上级目录
- 打包主程序
使用 mvn clean install 命令进行打包
4.1.3 插件开发
1、在插件的pom中加入依赖和定制的maven打包插件:
<!-- spring-boot-starter依赖 -->
<!--建议将spring-boot-starter依赖放到第一个位置, 以防止出现依赖冲突导致无法启动插件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>${和主程序一致的springboot版本}</version>
</dependency>
<!--插件不包含spring-boot-starter-web依赖,如果在主程序的pom.xml中已经定义了这个依赖,需要在插件的pom.xml中排除这个依赖。-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- spring-brick-bootstrap依赖 -->
<dependency>
<groupId>com.gitee.starblues</groupId>
<artifactId>spring-brick-bootstrap</artifactId>
<version>3.1.0</version>
</dependency>
<!-- 主程序依赖:将主程序以 provided 方式依赖到插件中,确保只编译不打包 -->
<dependency>
<groupId>主程序的 groupId</groupId>
<artifactId>主程序的 artifactId</artifactId>
<version>主程序 version</version>
<scope>provided</scope>
</dependency>
<build>
<plugins>
<plugin>
<groupId>com.gitee.starblues</groupId>
<artifactId>spring-brick-maven-packager</artifactId>
<version>3.1.0</version>
<configuration>
<!--当前打包模式为: 开发模式-->
<mode>dev</mode>
<!--插件信息定义-->
<pluginInfo>
<!--插件id-->
<id>plugin-example</id>
<!--插件入口类, 定义说明见: 定义插件入口类-->
<bootstrapClass>com.gitee.starblues.example.ExamplePlugin</bootstrapClass>
<!--插件版本号-->
<version>1.0.0</version>
</pluginInfo>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
- 创建插件入口类,继承SpringPluginBootstrap
@SpringBootApplication
public class ExamplePlugin extends SpringPluginBootstrap {
public static void main(String[] args) {
new ExamplePlugin().run(args);
}
}
- 实现主程序定义的扩展点接口
@Component
public class ExamplePluginExtension implements PluginExtensionPoint {
@Override
public String getName() {
return "example-plugin";
}
@Override
public String doSomething(String param) {
return "Plugin processed: " + param;
}
}
4、插件 测试controller
@RestController
@RequestMapping("/example")
public class ExampleController {
@GetMapping
public String hello(){
return "hello";
}
}
5、使用maven命令编译插件项目
mvn clean compile
6、启动主程序验证
启动主程序后,主程序中的插件管理器会根据配置的插件路径,到指定的位置去加载插件的类。看到如下信息说明插件加载成功:
c.g.s.i.operator.DefaultPluginOperator : 插件加载环境: dev
c.g.s.core.PluginLauncherManager : 插件[plugin-example@1.0.0-SNAPSHOT]加载成功
c.g.s.b.p.web.PluginControllerProcessor : 插件[plugin-example]注册接口: {GET [/plugins/module1/example]}
c.g.s.core.PluginLauncherManager : 插件[plugin-example@1.0.0-SNAPSHOT]启动成功
c.g.s.i.operator.DefaultPluginOperator : 插件初始化完成
7、访问接口
访问接口 http://127.0.0.1:8080/plugins/plugins-basic/example ,查看插件访问结果
4.1.4、关于主程序和插件的开发
- 把主程序和插件当成正常springBoot应用开发即可。由于类加载器隔离的原因,他俩是独立的
- 主程序的配置在主程序和插件之间是共享的,在插件中获取主程序的配置 通过spring-brick 提供的API 获取,参看:https://www.yuque.com/starblues/spring-brick-3.0.0/bhr0wo ;获取自身配置时还是正常获取。
- 插件尽量做到职责单一、功能内聚,不要太复杂
4.1.5、主程序和插件之间的通信
4.2 组件集成
1、 插件调用主程序:
由于插件依赖了主程序,所以插件中可以正常使用访问和注入主程序中的bean
2、 主程序调用插件:
原则上来说,主程序不应该过多的与插件交互,不然就耦合太严重。
如果想调用插件的话,可以使用spring-brick提供的扩展点机制 或者 http接口调用。
以下是扩展点机制示例:
可以根据不同的bus 参数调用不同的插件实现。
@RestController
@RequestMapping("/main/extract")
public class ExtracTestController {
@Autowired
private ExtractFactory extractFactory;
/**
* 获取指定的扩展插件实现
* @param bus 业务标识
* @return 返回用户列表
*/
@GetMapping("/getExtractByCoordinate2")
public List<UserDTO> getExtractByCoordinate2(@RequestParam("bus") String bus){
UserInterface userInterface = extractFactory.getExtractByCoordinate(ExtractCoordinate.build(bus, null, null));
List<UserDTO> userList = userInterface.getUserList();
return userList;
}
}
// 主程序中定义的扩展接口,让自身或不同的插件去实现
public interface UserInterface {
List<UserDTO> getUserList();
}
// 插件中的实现
@Extract(bus = "basicUserInterfaceImpl")
public class UserInterfaceImpl implements UserInterface {
@Autowired
private UserMapper userMapper;
@Override
public List<UserDTO> getUserList() {
List<User> userList = userMapper.selectList(null);
return JSON.parseArray(JSON.toJSONString(userList), UserDTO.class);
}
}
4.3 应用运行
4.3.1 打包与部署
-
打包主程序:
mvn clean package -
打包插件:
mvn clean package -
部署运行:
- 将插件jar包复制到配置的插件目录
- 启动主程序:
java -jar example-main.jar
4.3.2 动态管理插件
spring-brick提供了插件的动态加载、卸载、更新等功能:(只适用于prod环境)
@RestController
@RequestMapping("/plugin")
public class PluginManagerController {
@Autowired
private PluginOperator pluginOperator;
/**
* 上传并安装、启动插件。注意: 该操作只适用于生产环境
* @param multipartFile 上传文件 multipartFile
* @return 操作结果
*/
@PostMapping("/upload")
public String upload(@RequestParam("jarFile") MultipartFile multipartFile){
try {
UploadParam uploadParam = UploadParam.byMultipartFile(multipartFile)
.setBackOldPlugin(true)
.setStartPlugin(true)
.setUnpackPlugin(false);
PluginInfo pluginInfo = pluginOperator.uploadPlugin(uploadParam);
if(pluginInfo != null){
return "install success";
} else {
return "install failure";
}
} catch (Exception e) {
e.printStackTrace();
return "install failure : " + e.getMessage();
}
}
/**
* 获取插件信息
* @return 返回插件信息
*/
@GetMapping("/infoList")
public List<PluginInfo> getPluginInfo(){
return pluginOperator.getPluginInfo();
}
/**
* 根据插件路径安装插件。该插件jar必须在服务器上存在。注意: 该操作只适用于生产环境
* @param path 插件路径名称
* @return 操作结果
*/
@PostMapping("/installByPath")
public String install(@RequestParam("path") String path,
@RequestParam(value = "unpackPlugin", defaultValue = "false", required = false) Boolean unpackPlugin){
try {
PluginInfo pluginInfo = pluginOperator.install(Paths.get(path), unpackPlugin);
if(pluginInfo != null){
return "installByPath success";
} else {
return "installByPath failure";
}
} catch (Exception e) {
e.printStackTrace();
return "installByPath failure : " + e.getMessage();
}
}
/**
* 根据插件id停止插件
* @param id 插件id
* @return 返回操作结果
*/
@PostMapping("/stop/{id}")
public String stop(@PathVariable("id") String id){
try {
if(pluginOperator.stop(id)){
return "plugin '" + id +"' stop success";
} else {
return "plugin '" + id +"' stop failure";
}
} catch (Exception e) {
e.printStackTrace();
return "plugin '" + id +"' stop failure. " + e.getMessage();
}
}
/**
* 根据插件id启动插件
* @param id 插件id
* @return 返回操作结果
*/
@PostMapping("/start/{id}")
public String start(@PathVariable("id") String id){
try {
if(pluginOperator.start(id)){
return "plugin '" + id +"' start success";
} else {
return "plugin '" + id +"' start failure";
}
} catch (Exception e) {
e.printStackTrace();
return "plugin '" + id +"' start failure. " + e.getMessage();
}
}
/**
* 根据插件id卸载插件
* @param id 插件id
* @return 返回操作结果
*/
@PostMapping("/uninstall/{id}")
public String uninstall(@PathVariable("id") String id){
try {
pluginOperator.uninstall(id, true, true);
return "plugin '" + id +"' uninstall success";
} catch (Exception e) {
e.printStackTrace();
return "plugin '" + id +"' uninstall failure. " + e.getMessage();
}
}
}
5、企业应用建议
5.1 开发最佳实践
1、插件设计原则:每个插件应该只负责一个明确的业务功能,避免功能过于复杂。
-
接口设计:扩展点接口设计要稳定,避免频繁变更影响插件兼容性。
-
版本管理:插件和主程序的版本要做好管理,确保兼容性。
-
文档完善:为扩展点提供详细的文档,方便插件开发者使用。
5.2 常见使用场景:
场景1:模块化业务系统
适用场景 :大型企业ERP、CRM、OA系统等需要模块化扩展的系统
实现方式 :
- 主程序:核心框架、用户管理、权限控制
- 插件1:销售管理模块
- 插件2:采购管理模块
- 插件3:库存管理模块
- 插件4:财务管理模块
优势 :
- 各业务模块独立开发、测试、部署
- 可以根据客户需求选择安装特定模块
- 模块升级不影响其他功能
场景2:多租户SaaS平台
适用场景 :为不同客户提供定制化功能的SaaS平台
实现方式 :
- 主程序:基础平台、租户管理
- 插件:为不同客户定制的功能模块
- 动态加载:根据租户配置加载相应插件
优势 :
- 不同客户可以使用不同的功能组合
- 新功能可以作为插件快速上线
- 客户定制功能隔离,互不影响
场景3:功能热部署
适用场景 :需要7x24小时运行但又要频繁更新功能的系统
实现方式 :
- 主程序:稳定运行的核心服务
- 插件:需要频繁更新的业务功能
- 热部署:不停机更新插件
优势 :
- 避免系统重启带来的服务中断
- 快速响应业务变化
- 降低部署风险
场景4:金融行业风控系统
适用场景 :需要灵活配置风控规则的系统
实现方式 :
-
主程序:基础风控框架、数据采集
-
插件:各种风控规则引擎
-
动态组合:根据业务场景加载不同规则组合
优势 : -
风控规则可以快速迭代
-
不同业务线可以使用不同的风控策略
-
规则更新不影响系统稳定性
场景5:物联网设备管理平台
适用场景 :需要支持多种设备协议的物联网平台
实现方式 :
- 主程序:设备管理框架、数据存储
- 插件:各种设备协议解析器
- 动态加载:根据设备类型加载相应协议插件
优势 :
- 新设备协议可以快速接入
- 协议升级不影响现有设备
- 协议模块可以独立优化
参考资料:
官方文档:https://www.yuque.com/starblues/spring-brick-3.0.0/ypurcw
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐

所有评论(0)