【源测】-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的共享需要通过配置来实现,比较麻烦,需要仔细看开发文档,一不小心就会插件启动不了报错,不过代码写的很好,是学习的好项目。

相关文章
|
4天前
|
XML Java 测试技术
Spring5入门到实战------17、Spring5新功能 --Nullable注解和函数式注册对象。整合JUnit5单元测试框架
这篇文章介绍了Spring5框架的三个新特性:支持@Nullable注解以明确方法返回、参数和属性值可以为空;引入函数式风格的GenericApplicationContext进行对象注册和管理;以及如何整合JUnit5进行单元测试,同时讨论了JUnit4与JUnit5的整合方法,并提出了关于配置文件加载的疑问。
Spring5入门到实战------17、Spring5新功能 --Nullable注解和函数式注册对象。整合JUnit5单元测试框架
|
1天前
|
安全 前端开发 Java
随着企业应用复杂度提升,Java Spring框架以其强大与灵活特性简化开发流程,成为构建高效、可维护应用的理想选择
随着企业应用复杂度提升,Java Spring框架以其强大与灵活特性简化开发流程,成为构建高效、可维护应用的理想选择。依赖注入使对象管理交由Spring容器处理,实现低耦合高内聚;AOP则分离横切关注点如事务管理,增强代码模块化。Spring还提供MVC、Data、Security等模块满足多样需求,并通过Spring Boot简化配置与部署,加速微服务架构构建。掌握这些核心概念与工具,开发者能更从容应对挑战,打造卓越应用。
6 1
|
1天前
|
Java 微服务 Spring
SpringBoot+Vue+Spring Cloud Alibaba 实现大型电商系统【分布式微服务实现】
文章介绍了如何利用Spring Cloud Alibaba快速构建大型电商系统的分布式微服务,包括服务限流降级等主要功能的实现,并通过注解和配置简化了Spring Cloud应用的接入和搭建过程。
SpringBoot+Vue+Spring Cloud Alibaba 实现大型电商系统【分布式微服务实现】
|
1天前
|
NoSQL JavaScript 前端开发
SpringBoot+Vue实现校园二手系统。前后端分离技术【完整功能介绍+实现详情+源码】
文章介绍了如何使用SpringBoot和Vue实现一个校园二手系统,采用前后端分离技术。系统具备完整的功能,包括客户端和管理员端的界面设计、个人信息管理、商品浏览和交易、订单处理、公告发布等。技术栈包括Vue框架、ElementUI、SpringBoot、Mybatis-plus和Redis。文章还提供了部分源代码,展示了前后端的请求接口和Redis验证码功能实现,以及系统重构和模块化设计的一些思考。
SpringBoot+Vue实现校园二手系统。前后端分离技术【完整功能介绍+实现详情+源码】
|
3天前
|
运维 Java Nacos
Spring Cloud应用框架:Nacos作为服务注册中心和配置中心
Spring Cloud应用框架:Nacos作为服务注册中心和配置中心
|
4天前
|
XML Java Maven
Spring5入门到实战------16、Spring5新功能 --整合日志框架(Log4j2)
这篇文章是Spring5框架的入门到实战教程,介绍了Spring5的新功能——整合日志框架Log4j2,包括Spring5对日志框架的通用封装、如何在项目中引入Log4j2、编写Log4j2的XML配置文件,并通过测试类展示了如何使用Log4j2进行日志记录。
Spring5入门到实战------16、Spring5新功能 --整合日志框架(Log4j2)
|
4天前
|
Java API Spring
Spring5入门到实战------1、Spring5框架概述、入门案例
这篇文章是Spring5框架的入门教程,概述了Spring框架的核心概念和特点,并通过一个创建普通Java类的案例,详细演示了从下载Spring核心Jar包、创建配置文件、编写测试代码到运行测试结果的完整流程,涵盖了Spring IOC容器的使用和依赖注入的基本用法。
|
5天前
|
SQL JavaScript 前端开发
vue中使用分页组件、将从数据库中查询出来的数据分页展示(前后端分离SpringBoot+Vue)
这篇文章详细介绍了如何在Vue.js中使用分页组件展示从数据库查询出来的数据,包括前端Vue页面的表格和分页组件代码,以及后端SpringBoot的控制层和SQL查询语句。
vue中使用分页组件、将从数据库中查询出来的数据分页展示(前后端分离SpringBoot+Vue)
|
18小时前
|
JavaScript Java Maven
毕设项目&课程设计&毕设项目:springboot+vue实现的在线求职管理平台(含教程&源码&数据库数据)
本文介绍了一款基于Spring Boot和Vue.js实现的在线求职平台。该平台采用了前后端分离的架构,使用Spring Boot作为后端服务
毕设项目&课程设计&毕设项目:springboot+vue实现的在线求职管理平台(含教程&源码&数据库数据)
|
4天前
|
前端开发 JavaScript Java
SpringBoot+Vue+token实现(表单+图片)上传、图片地址保存到数据库。上传图片保存位置自己定义、图片可以在前端回显(一))
这篇文章详细介绍了在SpringBoot+Vue项目中实现表单和图片上传的完整流程,包括前端上传、后端接口处理、数据库保存图片路径,以及前端图片回显的方法,同时探讨了图片资源映射、token验证、过滤器配置等相关问题。