【Spring Boot】插件化动态类加载解决方案
背景
我之前的一家公司是做物联网相关的
当时有一个长期任务,就是对接各个厂家的各类设备
一开始我们都把这些对接设备的代码直接写在服务里面
当然也用了一些设计模式,抽象了统一的接口
所以编码相关的部分其实没有什么大问题
但是当我们要上线一个新对接的设备或协议的时候
测试的工作量就会比较大
因为从代码的层面来说只是添加了几个类
而从功能模块的层面来说代码有改动就需要重新测试
当时我就在想,能不能把主业务和对接业务拆分开
主业务的代码没有变动就不需要重复测试了
对接业务以插件的方式集成上去不影响主业务
另外就算对接业务上线之后发现了问题
也能通过可插拔的方式快速优化,不需要重新发布整个服务
框架介绍
基于当时的想法就有了 Concept Plugin 2
使用
1. 在代码中配置需要提取的插件内容
@EnablePluginConcept
@Configuration
public class PluginConfig {
@OnPluginExtract
public void plugin(CustomPlugin plugin) {
//CustomPlugin 替换为我们业务中自己定义的接口或类即可
//匹配到 CustomPlugin 就会回调该方法
}
}
@EnablePluginConcept
用于启用插件功能,也可以标记在启动类上
@OnPluginExtract
标记在方法上表示插件回调
@OnPluginExtract
标记的方法只要能被Spring
扫描到就行
2. 通过管理页面上传插件
管理页面路径:/concept-plugin/management.html
3. 第三步
没有第三步啦,上面的内容就是全部啦
优势
使用方便
这我不得不说,Spring Boot
才是鼻祖,一个注解帮你集成所有功能
咱也是从开发者使用的角度来考虑,能省的都省了,变成了一堆默认配置
另外插件管理也提供了现成的页面,只要点一点就行了,测试运维都能用
学习门槛低
就以上面的示例为例
@EnablePluginConcept
@Configuration
public class PluginConfig {
@OnPluginExtract
public void plugin(CustomPlugin plugin) {
//CustomPlugin 替换为我们业务中自己定义的接口或类即可
//匹配到 CustomPlugin 就会回调该方法
}
}
除了两个注解@EnablePluginConcept
和@OnPluginExtract
对第一次使用的开发者来说是新的东西(但是很容易理解)
其他的内容都可以不涉及这个框架
包括我们实现的插件,也不需要标记什么注解,实现什么接口,没有任何侵入性
写法自由
有的朋友可能要问了
我们自己定义的方法需不需要什么固定的格式
会不会因为写法或顺序问题导致拿不到数据?
格式?没有那种东西
咱主打的就是一个 freestyle
什么?你想直接拿到类而不是实例,那就这样写
@EnablePluginConcept
@Configuration
public class PluginConfig {
@OnPluginExtract
public void plugin(Class<? extends CustomPlugin> plugin) {
}
}
什么?一个插件包里面定义了多个插件实现,那就这样写
@EnablePluginConcept
@Configuration
public class PluginConfig {
@OnPluginExtract
public void pluginList(List<CustomPlugin> plugins) {
}
@OnPluginExtract
public void pluginSet(Set<CustomPlugin> plugins) {
}
@OnPluginExtract
public void pluginArray(CustomPlugin[] plugins) {
}
}
什么?你平时习惯加范型,那就这样写
@EnablePluginConcept
@Configuration
public class PluginConfig {
@OnPluginExtract
public void plugin(List<? extends CustomPlugin> plugins) {
}
}
什么?插件包里还有一个 Properties 文件想一起读出来,那就这样写
@EnablePluginConcept
@Configuration
public class PluginConfig {
@OnPluginExtract
public void plugin(CustomPlugin plugin, Properties properties) {
}
}
主打一个,你想要啥,你就加啥,所见即所得
精确匹配
有的朋友可能又要问了
插件包不止一个 Properties 文件,想要分开单独读取,可以这样写
@EnablePluginConcept
@Configuration
public class PluginConfig {
@OnPluginExtract
public void plugin(@PluginEntry("first.properties") Properties properties,
@PluginEntry("second.properties") Properties properties) {
}
}
@PluginEntry
可以用来匹配路径和名称
使用AntPath
的风格,如:
@PluginEntry("content/**")
@PluginEntry(**/**.json)
Spring支持
集成了部分Spring
的能力,使用更加方便
插件配置
可以在插件包中添加plugin.properties
文件作为插件配置
java
项目在resources
目录下添加即可
zip
文件在根目录下添加即可
插件配置提供了基于Spring
的属性绑定功能
这是我们的插件配置文件
#自定义配置
custom.app=${spring.application.name} #支持Spring占位符
custom.value=custom
额外注入Plugin
通过PluginMetadata
进行读取
@EnablePluginConcept
@Configuration
public class PluginConfig {
@OnPluginExtract
public void plugin(Plugin plugin) {
PluginMetadata metadata = plugin.getMetadata();
//可以根据name直接读取
String app = metadata.get("custom.app");
String value = metadata.get("custom.value");
//也可以绑定对象
CustomData data = metadata.bind("custom", CustomData.class);
}
@Data
public static class CustomData {
private String app;
private String value;
}
}
自动注入
既然配置部分已经和Spring
结合了
那么实例部分也是支持了Spring
的依赖注入能力
在实现插件的时候可以直接使用Spring
相关的功能
public class SpringPlugin implements CustomPlugin, ApplicationContextAware {
@Value("${spring.application.name}")
private String applicationName;
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
大部分的依赖注入功能都是支持的
支持嵌套或关联依赖
在实现插件的时候有可能需要依赖其他的jar
中的类
如果插件依赖的类在我们的主服务中已经存在了那就不需要管
如果在主服务中没有,有下面两种方案:
嵌套依赖
可以把依赖的jar
打包进插件包里面,这样就能自动识别
插件依赖
如果被依赖的jar
比较通用,很多插件都需要依赖
那么可以把这个jar
作为一个基础插件(多个jar
可以打包成zip
)
在这个基础插件中添加配置plugin.properties
concept.plugin.name=common #插件名称
concept.plugin.handler.enabled=false #作为基础插件不进行解析匹配提取
然后在其他的插件包中添加配置plugin.properties
concept.plugin.dependency.names=common #依赖的插件,多个用逗号分隔
这样我们的插件就能加载到基础插件中的类了
可视化页面
为了方便插件管理,我还专门写了一个页面
提供插件上传,下载,加载,卸载,等基础功能
结束
如果大家感兴趣或有需要可以看下更详细的用法 Wiki
觉得不错的话记得给个Star Github
也可以看看其他的功能 Concept Wiki
转载自:https://juejin.cn/post/7393656776539340815