【源测】-Spring Boot系统动态扩展框架

简介: 【源测】-Spring Boot系统动态扩展框架

image.png

前言

像插件化的开发,在PHP、JS等系统很常见,但在Java领域非常少,成熟的框架我知道的就是OSGI,记得以前银行项目就是用的这个,比较老的技术了。

在2017年的时候自己也尝试过在博客系统中加入插件功能,达到动态扩展的目的,但只能够实现一些简单功能。

现在发现了一个优秀的框架-"Spring-brick",功能、文档完善。

项目地址:

https://gitee.com/starblues/springboot-plugin-framework-parent

文档地址:

https://www.yuque.com/starblues/spring-brick-3.0.0

介绍

"Spring-brick",是一个可以动态扩展系统的框架,最早在2019年开始开发,该框架可以在SpringBoot项目上开发插件功能,开发插件就像开发独立应用一样,根据网站的介绍,使用该框架可以实现如下需求:

  • 在插件中,您可以当成一个微型的SpringBoot项目来开发,简单易用。
  • 在插件中扩展出系统各种功能点,用于系统灵活扩展,再也不用使用分支来交付不同需求的项目了。
  • 在插件中可以集成各种框架及其各种spring-boot-xxx-starter。
  • 在插件中可以定义独立依赖包了,再也不用在主程序中定义依赖包了。
  • 可以完美解决插件包与插件包、插件包与主程序因为同一框架的不同版本冲突问题了。各个插件可以定义同一依赖的不同版本框架。
  • 无需重启主程序,可以自由实现插件包的动态安装部署,来动态扩展系统的功能。
  • 插件也可以不依赖主程序独立集成微服务模块。

项目更新蛮活跃的:

image.pngimage.gif

文档比较丰富:

image.gifimage.gifimage.gifimage.png

快速入门

image.gifimage.png

示例地址:

https://gitee.com/starblues/springboot-plugin-framework-example

作者提供了一个项目示例,我们来跑一下看看,跑之前看下文档的快速入门:

1、将sql/plugin-test-example.sql文件导入到mysql数据库
2、修改各个模块对数据库连接配置信息,插件目录地址
3、打包插件: mvn clean package
4、进入主程序example-main 启动 Application

运行日志:

.   ____   _     __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::  (v2.5.6)
 [Thread-1] c.g.s.l.launcher.SpringMainBootstrap     : Starting SpringMainBootstrap using Java 1.8.0_311 on MacBookAir.local with PID 21618 (/Users/chenjujun/java-projects/springboot-plugin-framework-parent/spring-brick-loader/target/classes started by apple in /Users/chenjujun/java-projects/springboot-plugin-framework-example/example-main)
 [Thread-1] c.g.s.l.launcher.SpringMainBootstrap     : The following profiles are active: dev
 [Thread-1] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
 [Thread-1] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
 [Thread-1] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.54]
 [Thread-1] o.a.c.c.C.[Tomcat].[localhost].[/]: Initializing Spring embedded WebApplicationContext
 [Thread-1] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1571 ms
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
Parsed mapper file: 'file [/Users/chenjujun/java-projects/springboot-plugin-framework-example/example-main/target/classes/mapper/MainUserMapper.xml]'
 _ _   |_  _ _|_. ___ _ |    _ 
| | |\/|_)(_| | |_\  |_)||_|_\ 
     / |  
   3.4.1 
 [Thread-1] pertySourcedRequestMappingHandlerMapping : Mapped URL path [/v2/api-docs] onto method [springfox.documentation.swagger2.web.Swagger2ControllerWebMvc#getDocumentation(String, HttpServletRequest)]
 [Thread-1] ion$DefaultTemplateResolverConfiguration : Cannot find template location: classpath:/templates/ (please add some templates or check your Thymeleaf configuration)
 [Thread-1] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
 [Thread-1] d.s.w.p.DocumentationPluginsBootstrapper : Documentation plugins bootstrapped
 [Thread-1] d.s.w.p.DocumentationPluginsBootstrapper : Found 1 custom documentation plugin(s)
 [Thread-1] s.d.s.w.s.ApiListingReferenceScanner     : Scanning for api listing references
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: getBeanUsingGET_1
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: getBeanByInterfaceUsingGET_1
 [Thread-1] c.g.s.l.launcher.SpringMainBootstrap     : Started SpringMainBootstrap in 4.107 seconds (JVM running for 6.445)
 [Thread-1] c.g.s.i.operator.DefaultPluginOperator   : 插件加载环境: dev
 [Thread-1] c.g.s.i.operator.DefaultPluginOperator   : 开始加载插件, 插件根路径为: 
/Users/chenjujun/java-projects/springboot-plugin-framework-example/example-plugins-basic
 [Thread-1] c.g.s.s.web.PluginStaticResourceConfig   : 插件静态资源访问前缀配置为: /static-plugin/{pluginId}
 [Thread-1] c.g.s.core.PluginLauncherManager  : 插件[example-basic-2@1.0.0-SNAPSHOT]加载成功
 [Thread-1] c.g.s.example.listener.MyPluginListener  : 插件[example-basic-2]加载成功.
 [Thread-1] c.g.s.core.PluginLauncherManager  : 插件[example-basic-1@1.0.0]加载成功
 [Thread-1] c.g.s.example.listener.MyPluginListener  : 插件[example-basic-1]加载成功.
 [Thread-1] c.g.s.e.l.MyPluginInitializerListener    : 初始化之前
 [Thread-1] c.g.s.b.p.web.PluginControllerProcessor  : 插件[example-basic-2]注册接口: {GET [/plugins/example-basic-2/hello/config]}
 [Thread-1] c.g.s.b.p.web.PluginControllerProcessor  : 插件[example-basic-2]注册接口: {GET [/plugins/example-basic-2/hello]}
 [Thread-1] c.g.s.core.PluginLauncherManager  : 插件[example-basic-2@1.0.0-SNAPSHOT]启动成功
 [Thread-1] c.g.s.example.listener.MyPluginListener  : 插件[example-basic-2]启动成功
 [Thread-1] c.g.s.b.p.web.PluginControllerProcessor  : 插件[example-basic-1]注册接口: {GET [/plugins/example-basic-1/caller/test-param-map]}
 [Thread-1] c.g.s.b.p.web.PluginControllerProcessor  : 插件[example-basic-1]注册接口: {GET [/plugins/example-basic-1/caller/test]}
 [Thread-1] c.g.s.b.p.web.PluginControllerProcessor  : 插件[example-basic-1]注册接口: {GET [/plugins/example-basic-1/caller/test-param-list]}
 [Thread-1] c.g.s.b.p.web.PluginControllerProcessor  : 插件[example-basic-1]注册接口: {GET [/plugins/example-basic-1/caller/test-param]}
 [Thread-1] c.g.s.b.p.web.PluginControllerProcessor  : 插件[example-basic-1]注册接口: {GET [/plugins/example-basic-1/hello/name]}
 [Thread-1] c.g.s.b.p.web.PluginControllerProcessor  : 插件[example-basic-1]注册接口: {GET [/plugins/example-basic-1/hello]}
 [Thread-1] c.g.s.b.p.web.PluginControllerProcessor  : 插件[example-basic-1]注册接口: {GET [/plugins/example-basic-1/hello/config]}
 [Thread-1] c.g.s.b.p.web.PluginControllerProcessor  : 插件[example-basic-1]注册接口: {GET [/plugins/example-basic-1/main/main-user]}
 [Thread-1] c.g.s.b.p.web.PluginControllerProcessor  : 插件[example-basic-1]注册接口: {GET [/plugins/example-basic-1/thy]}
 [Thread-1] c.g.s.s.w.PluginStaticResourceResolver   : 插件[example-basic-1@1.0.0]配置的静态资源: classpath[[static/]], file[[]]
 [Thread-1] c.g.s.core.PluginLauncherManager  : 插件[example-basic-1@1.0.0]启动成功
 [Thread-1] c.g.s.example.listener.MyPluginListener  : 插件[example-basic-1]启动成功
 [Thread-1] d.s.w.p.DocumentationPluginsBootstrapper : Documentation plugins bootstrapped
 [Thread-1] d.s.w.p.DocumentationPluginsBootstrapper : Found 1 custom documentation plugin(s)
 [Thread-1] s.d.s.w.s.ApiListingReferenceScanner     : Scanning for api listing references
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: getExtractImplUsingGET_1
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: getExtractByInterClassUsingGET_1
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: getExtractByInterClassOfMainUsingGET_1
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: helloUsingGET_1
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: helloUsingGET_2
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: getConfigUsingGET_1
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: helloUsingGET_3
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: parseUsingPOST_1
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: backupPluginUsingPOST_1
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: installUsingPOST_1
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: startUsingPOST_1
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: stopUsingPOST_1
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: uninstallUsingPOST_1
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: uploadUsingPOST_1
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: verifyUsingPOST_1
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: getPluginInfoUsingGET_1
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: getBeanByInterfaceUsingGET_2
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: getObjectWithAnnotationUsingGET_1
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: getBeanNameUsingGET_1
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: getBeanUsingGET_2
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: getBeanUsingGET_3
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: getBeanByInterfaceErrorUsingGET_1
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: getBeanByInterfaceUsingGET_3
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: getObjectWithAnnotationByPluginIdUsingGET_1
 [Thread-1] c.g.s.e.l.MyPluginInitializerListener    : 初始化完成
 [Thread-1] c.g.s.i.operator.DefaultPluginOperator   : 插件初始化完成

这是已经加载完成了。

试试怎么动态部署。

动态部署需要使用prod模式启动,毕竟是要模拟线上环境。

插件目录没有插件的时候是这样的:

[Thread-1] c.g.s.i.operator.DefaultPluginOperator   : 插件加载环境: prod
[Thread-1] c.g.s.i.operator.DefaultPluginOperator   : 开始加载插件, 插件根路径为: 
../plugins
[Thread-1] c.g.s.s.web.PluginStaticResourceConfig   : 插件静态资源访问前缀配置为: /static-plugin/{pluginId}
[Thread-1] c.g.s.core.PluginLauncherManager         : 以下路径未发现插件: 
../plugins
请检查路径是否合适.
请检查配置[plugin.runMode]是否合适.
请检查插件是否合法.

调用下插件上传安装接口:

image.gifimage.png

返回成功了:

image.gifimage.png

看下日志,已经安装成功了:image.gif

image.gifimage.gifimage.png

image.png

后面就可以跟调用插件的HTTP接口了。

调用流程

为了能够更好的理解,我整理了一个调用流程图

image.png

总结

该框架由于主程序跟插件使用的独立ClassLoader,对于一些bean的共享需要通过配置来实现,比较麻烦,需要仔细看开发文档,一不小心就会插件启动不了报错,不过代码写的很好,是学习的好项目。

相关文章
|
1月前
|
JavaScript Java 关系型数据库
基于springboot的项目管理系统
本文探讨项目管理系统在现代企业中的应用与实现,分析其研究背景、意义及现状,阐述基于SSM、Java、MySQL和Vue等技术构建系统的关键方法,展现其在提升管理效率、协同水平与风险管控方面的价值。
|
28天前
|
监控 安全 JavaScript
2025基于springboot的校车预定全流程管理系统
针对传统校车管理效率低、信息不透明等问题,本研究设计并实现了一套校车预定全流程管理系统。系统采用Spring Boot、Java、Vue和MySQL等技术,实现校车信息管理、在线预定、实时监控等功能,提升学校管理效率,保障学生出行安全,推动教育信息化发展。
|
29天前
|
安全 前端开发 Java
《深入理解Spring》:现代Java开发的核心框架
Spring自2003年诞生以来,已成为Java企业级开发的基石,凭借IoC、AOP、声明式编程等核心特性,极大简化了开发复杂度。本系列将深入解析Spring框架核心原理及Spring Boot、Cloud、Security等生态组件,助力开发者构建高效、可扩展的应用体系。(238字)
|
29天前
|
JavaScript Java 关系型数据库
基于springboot的高校运动会系统
本系统基于Spring Boot、Vue与MySQL,实现高校运动会报名、赛程安排及成绩管理的全流程信息化,提升组织效率,杜绝信息错漏与冒名顶替,推动体育赛事智能化发展。
|
25天前
|
JavaScript 安全 Java
基于springboot的大学生兼职系统
本课题针对大学生兼职信息不对称、权益难保障等问题,研究基于Spring Boot、Vue、MySQL等技术的兼职系统,旨在构建安全、高效、功能完善的平台,提升大学生就业竞争力与兼职质量。
|
29天前
|
JavaScript Java 关系型数据库
基于springboot的美食城服务管理系统
本系统基于Spring Boot、Java、Vue和MySQL技术,构建集消费者服务、商家管理与后台监管于一体的美食城综合管理平台,提升运营效率与用户体验。
|
30天前
|
Java 关系型数据库 MySQL
基于springboot的网咖网吧管理系统
本文探讨了基于Java、MySQL和SpringBoot的网吧管理系统的设计与实现。随着信息化发展,传统管理方式难以满足需求,而该系统通过先进技术提升管理效率、保障数据安全、降低运营成本,具有重要意义。
|
1月前
|
JavaScript Java 关系型数据库
基于springboot的摄影师分享交流社区系统
本系统基于Spring Boot与Vue构建摄影师分享交流平台,旨在打造专业社区,支持作品展示、技术交流与合作互动。采用Java、MySQL等成熟技术,提升摄影爱好者创作水平,推动行业发展。
|
1月前
|
JavaScript 搜索推荐 Java
基于SpringBoot的社区老年食堂系统
针对老龄化社会饮食难题,智慧社区老年食堂系统应运而生。融合Spring Boot、Vue、Java与MySQL技术,实现餐饮服务智能化、个性化,提升老年人生活质量与幸福感,推动社区养老服务升级。