【Spring Boot】插件化动态类加载解决方案

背景

我之前的一家公司是做物联网相关的

当时有一个长期任务,就是对接各个厂家的各类设备

一开始我们都把这些对接设备的代码直接写在服务里面

当然也用了一些设计模式,抽象了统一的接口

所以编码相关的部分其实没有什么大问题

但是当我们要上线一个新对接的设备或协议的时候

测试的工作量就会比较大

因为从代码的层面来说只是添加了几个类

而从功能模块的层面来说代码有改动就需要重新测试

当时我就在想,能不能把主业务和对接业务拆分开

主业务的代码没有变动就不需要重复测试了

对接业务以插件的方式集成上去不影响主业务

另外就算对接业务上线之后发现了问题

也能通过可插拔的方式快速优化,不需要重新发布整个服务

框架介绍

基于当时的想法就有了 Concept Plugin 2

GitHub传送门

Concept Wiki

使用

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

  • 11
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值