一、背景

动态插件化编程是一件很酷的事情,能实现业务功能的 解耦 便于维护,另外也可以提升 可扩展性 随时可以在不停服务器的情况下扩展功能,也具有非常好的 开放性 除了自己的研发人员可以开发功能之外,也能接纳第三方开发商按照规范开发的插件。

常见的动态插件的实现方式有 SPIOSGI 等方案,由于脱离了 Spring IOC 的管理在插件中无法注入主程序的 Bean 对象,例如主程序中已经集成了 Redis 但是在插件中无法使用。

本文主要介绍在 Spring Boot 工程中热加载 jar 包并注册成为 Bean 对象的一种实现思路,在动态扩展功能的同时支持在插件中注入主程序的 Bean 实现功能更强大的插件。

二、热加载 jar 包

通过指定的链接或者路径动态加载 jar 包,可以使用 URLClassLoaderaddURL 方法来实现,样例代码如下:

ClassLoaderUtil 类

public class ClassLoaderUtil {
public static ClassLoader getClassLoader(String url) {
try {
Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
if (!method.isAccessible()) {
method.setAccessible(true);
}
URLClassLoader classLoader = new URLClassLoader(new URL[]{}, ClassLoader.getSystemClassLoader());
method.invoke(classLoader, new URL(url));
return classLoader;
} catch (Exception e) {
log.error("getClassLoader-error", e);
return null;
}
}
}

其中在创建 URLClassLoader 时,指定当前系统的 ClassLoader 为父类加载器 ClassLoader.getSystemClassLoader() 这步比较关键,用于打通主程序与插件之间的 ClassLoader ,解决把插件注册进 IOC 时的各种 ClassNotFoundException 问题。

三、动态注册 Bean

将插件 jar 中加载的实现类注册到 Spring 的 IOC 中,同时也会将 IOC 中已有的 Bean 注入进插件中;分别在程序启动时和运行时两种场景下的实现方式。

3.1. 启动时注册 Bean

使用 ImportBeanDefinitionRegistrar 实现在 Spring Boot 启动时动态注册插件的 Bean,样例代码如下:

PluginImportBeanDefinitionRegistrar 类

public class PluginImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
private final String targetUrl = "file:/D:/SpringBootPluginTest/plugins/plugin-impl-0.0.1-SNAPSHOT.jar";
private final String pluginClass = "com.plugin.impl.PluginImpl"; @SneakyThrows
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
ClassLoader classLoader = ClassLoaderUtil.getClassLoader(targetUrl);
Class<?> clazz = classLoader.loadClass(pluginClass);
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
BeanDefinition beanDefinition = builder.getBeanDefinition();
registry.registerBeanDefinition(clazz.getName(), beanDefinition);
}
}

3.2. 运行时注册 Bean

程序运行时动态注册插件的 Bean 通过使用 ApplicationContext 对象来实现,样例代码如下:

@GetMapping("/reload")
public Object reload() throws ClassNotFoundException {
ClassLoader classLoader = ClassLoaderUtil.getClassLoader(targetUrl);
Class<?> clazz = classLoader.loadClass(pluginClass);
springUtil.registerBean(clazz.getName(), clazz);
PluginInterface plugin = (PluginInterface)springUtil.getBean(clazz.getName());
return plugin.sayHello("test reload");
}

SpringUtil 类

@Component
public class SpringUtil implements ApplicationContextAware {
private DefaultListableBeanFactory defaultListableBeanFactory;
private ApplicationContext applicationContext; @Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
this.defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory();
} public void registerBean(String beanName, Class<?> clazz) {
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
defaultListableBeanFactory.registerBeanDefinition(beanName, beanDefinitionBuilder.getRawBeanDefinition());
} public Object getBean(String name) {
return applicationContext.getBean(name);
}
}

四、总结

本文介绍的插件化实现思路通过 共用 ClassLoader动态注册 Bean 的方式,打通了插件与主程序之间的类加载器和 Spring 容器,使得可以非常方便的实现插件与插件之间和插件与主程序之间的 类交互,例如在插件中注入主程序的 Redis、DataSource、调用远程 Dubbo 接口等等。

但是由于没有对插件之间的 ClassLoader 进行 隔离 也可能会存在如类冲突、版本冲突等问题;并且由于 ClassLoader 中的 Class 对象无法销毁,所以除非修改类名或者类路径,不然插件中已加载到 ClassLoader 的类是没办法动态修改的。

所以本方案比较适合插件数据量不会太多、具有较好的开发规范、插件经过测试后才能上线或发布的场景。

五、完整 demo

https://github.com/zlt2000/springs-boot-plugin-test

扫码关注有惊喜!

Spring Boot 如何热加载jar实现动态插件?的更多相关文章

  1. spring boot的热加载(hotswap)

    官网上是叫hotswap,有人翻译成热部署,有人翻译成热加载 个人倾向于使用热加载在这个词,和谷歌翻译的热插拔相似. 关于个人理解 http://www.cnblogs.com/ptqueen/p/8 ...

  2. 在IDEA下使用Spring Boot的热加载(Hotswap)

    你是否遇到过这样的困扰: 当你写完一段代码后,要看到效果,必须点击IDEA的停止按钮,然后再次重启启动项目,你是否觉得这样很烦呢? 如果你觉得很烦,本文就是用来解决你的问题的. 所谓热加载,就是让我们 ...

  3. Spring Boot的属性加载顺序

        伴随着团队的不断壮大,往往不需要开发人员知道测试或者生产环境的全部配置细节,比如数据库密码,帐号信息等.而是希望由运维或者指定的人员去维护配置信息,那么如果要修改某项配置信息,就不得不去修改项 ...

  4. Spring boot 国际化自动加载资源文件问题

    Spring boot 国际化自动加载资源文件问题 最近在做基于Spring boot配置的项目.中间遇到一个国际化资源加载的问题,正常来说只要在application.properties文件中定义 ...

  5. Spring Boot JPA 懒加载

    最近在使用spring jpa 的过程中经常遇到懒加载的错误:"` org.hibernate.LazyInitializationException: could not initiali ...

  6. (转)mybatis热加载(依赖mybatis-plus插件)的实现

    最近在使用mybatis,由于是刚刚开始用,用的并不顺手,目前是感觉有2个地方非常的不好用: 1.mybatis调试不方便 由于dao层只有接口,实现只是一个map的xml文件,想加断点都没有地方加, ...

  7. Spring Boot JDBC:加载DataSource过程的源码分析及yml中DataSource的配置

    装载至:https://www.cnblogs.com/storml/p/8611388.html Spring Boot实现了自动加载DataSource及相关配置.当然,使用时加上@EnableA ...

  8. spring boot 是如何加载jackson的?

    Spring Boot 自动引入jackson: 通过:Spring-Boot-starter-web Jackson自动配置 这里的configurations是读取的这里: 通过反射加载Jacks ...

  9. 【串线篇】spring boot外部配置加载顺序

    SpringBoot也可以从以下位置加载配置: 原则仍然是优先级从高到低:高优先级的配置覆盖低优先级的配置,所有的配置会形成互补配置 1.命令行参数 所有的配置都可以在命令行上进行指定 java -j ...

随机推荐

  1. configparser生成模块写

    # -*-coding:utf-8-*-__author__ = "logan.xu"#写配置文件import configparserconfig = configparser. ...

  2. BUUCTF-[CISCN2019 华东北赛区]Web2

    BUUCTF-[CISCN2019 华东北赛区]Web2 看题 一个论坛,内容不错:) 可以投稿,点击投稿发现要注册,那就先注册登录.随便账号密码就行. 常规操作,扫一下站点,发现有admin.php ...

  3. 存储系统管理(二)——Linux系统的swap分区、磁盘加密、磁盘阵列

    磁盘驱动器上的空间 , 用作当前未使用部分内存的溢出.这样 , 系统就能在主内存中留出空间用于储存当前正在处理的数据 , 并在系统面临主内存空间不足的风险时提供应急溢出. swap分区的建立: fdi ...

  4. Linux学习笔记 - Linux快捷操作及常用命令

    一.快捷键 剪切光标前的内容 Ctrl + u 剪切光标至行末的内容 Ctrl + k 粘贴 Ctrl + u 或 Ctrl +k 的内容 Ctrl + y 移动光标到行末 Ctrl + e 移动光标 ...

  5. Python脚本导出AWS EC2资源清单

    环境需求 单位现在每隔一段时间需要核对一下 AWS 正在运行的 EC2 资源清单,为了避免核对失误以及重复性的工作,打算用脚本来解决这一重复性的工作.大概思路为 通过 AWS AK.SK 来索取 AW ...

  6. github搜索技巧小结

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  7. aes加解密前后端-前端

    一.ajax请求前端 f12请求和响应参数效果: 1.在前端封装ajax的公共Util的js中,封装ajax请求的地方,在beforeSend方法和成功之后的回调函数success方法中: var p ...

  8. 简单操作:10分钟实现在kubernetes(k8s)里面部署服务器集群并访问项目(docker三)

    前言 经过docker安装.k8s开启并登录,我们终于到 "部署k8s服务器集群并访问项目" 这一步了,实现的过程中有太多坑,好在都填平了,普天同庆. 在进行当前课题之前,我们需要 ...

  9. PTA面向对象程序设计6-3 面积计算器(函数重载)

    实现一个面积计算器,它能够计算矩形或长方体的面积. 函数接口定义: int area(int x, int y); int area(int x, int y, int z); 第一个函数计算长方形的 ...

  10. Django学习day01随堂笔记

    每日测验 """ 每日测验 1.你所知道的前端框架和实用插件有哪些,他们各有什么特点 2.使用bootstrap需要注意什么,常用的bootstrap样式有哪些 &quo ...