Spring Boot 如何热加载jar实现动态插件?

一、背景
动态插件化编程是一件很酷的事情,能实现业务功能的 解耦 便于维护,另外也可以提升 可扩展性 随时可以在不停服务器的情况下扩展功能,也具有非常好的 开放性 除了自己的研发人员可以开发功能之外,也能接纳第三方开发商按照规范开发的插件。
常见的动态插件的实现方式有 SPI、OSGI 等方案,由于脱离了 Spring IOC 的管理在插件中无法注入主程序的 Bean 对象,例如主程序中已经集成了 Redis 但是在插件中无法使用。
本文主要介绍在 Spring Boot 工程中热加载 jar 包并注册成为 Bean 对象的一种实现思路,在动态扩展功能的同时支持在插件中注入主程序的 Bean 实现功能更强大的插件。
二、热加载 jar 包
通过指定的链接或者路径动态加载 jar 包,可以使用 URLClassLoader 的 addURL 方法来实现,样例代码如下:
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实现动态插件?的更多相关文章
- spring boot的热加载(hotswap)
官网上是叫hotswap,有人翻译成热部署,有人翻译成热加载 个人倾向于使用热加载在这个词,和谷歌翻译的热插拔相似. 关于个人理解 http://www.cnblogs.com/ptqueen/p/8 ...
- 在IDEA下使用Spring Boot的热加载(Hotswap)
你是否遇到过这样的困扰: 当你写完一段代码后,要看到效果,必须点击IDEA的停止按钮,然后再次重启启动项目,你是否觉得这样很烦呢? 如果你觉得很烦,本文就是用来解决你的问题的. 所谓热加载,就是让我们 ...
- Spring Boot的属性加载顺序
伴随着团队的不断壮大,往往不需要开发人员知道测试或者生产环境的全部配置细节,比如数据库密码,帐号信息等.而是希望由运维或者指定的人员去维护配置信息,那么如果要修改某项配置信息,就不得不去修改项 ...
- Spring boot 国际化自动加载资源文件问题
Spring boot 国际化自动加载资源文件问题 最近在做基于Spring boot配置的项目.中间遇到一个国际化资源加载的问题,正常来说只要在application.properties文件中定义 ...
- Spring Boot JPA 懒加载
最近在使用spring jpa 的过程中经常遇到懒加载的错误:"` org.hibernate.LazyInitializationException: could not initiali ...
- (转)mybatis热加载(依赖mybatis-plus插件)的实现
最近在使用mybatis,由于是刚刚开始用,用的并不顺手,目前是感觉有2个地方非常的不好用: 1.mybatis调试不方便 由于dao层只有接口,实现只是一个map的xml文件,想加断点都没有地方加, ...
- Spring Boot JDBC:加载DataSource过程的源码分析及yml中DataSource的配置
装载至:https://www.cnblogs.com/storml/p/8611388.html Spring Boot实现了自动加载DataSource及相关配置.当然,使用时加上@EnableA ...
- spring boot 是如何加载jackson的?
Spring Boot 自动引入jackson: 通过:Spring-Boot-starter-web Jackson自动配置 这里的configurations是读取的这里: 通过反射加载Jacks ...
- 【串线篇】spring boot外部配置加载顺序
SpringBoot也可以从以下位置加载配置: 原则仍然是优先级从高到低:高优先级的配置覆盖低优先级的配置,所有的配置会形成互补配置 1.命令行参数 所有的配置都可以在命令行上进行指定 java -j ...
随机推荐
- Python3-sqlalchemy-orm
1 #-*-coding:utf-8-*- 2 #__author__ = "logan.xu" 3 4 5 import sqlalchemy 6 from sqlalchemy ...
- centos7 shell 计算器 bc 命令
2021-08-03 1. 安装 yum -y install bc 2. 简介 bc 命令是任意精度计算器语言,通常在 linux 下当计算器使用 类似基本的计算器, 使用这个计算器可以做基本的数学 ...
- Windows系统定时备份MySQL数据库
当一个网站投入使用时,定期备份数据库是必要的事.那么,在Windows系统上,我们该如何做呢? 如下语句可以实现备份及还原MySQL数据库: 备份MySQL数据库 mysqldump -uroot - ...
- 菜鸟入门Linux之路(方法论浅谈)
Linux是为人熟知的OS之王,已"统治"世界.要想学好绝非易事. 作为菜鸟,可以与Linux亲密接触的方法很多,如视频.书籍.各种企培资料等等,如今的在线教育也如火如荼. 总结说 ...
- Flask - 解决 app.run() 添加 host、port、debug 参数后运行不生效的问题
问题背景 app.run() 添加了 host.port.debug 参数,运行后发现没有生效,咋肥事! 解决方案 要打开 debug 模式的话,勾选 FLASK_DEBUG 就好啦 再次运行,发现已 ...
- PTA——c++面向对象基础
1.结构不是面向对象的主要特征 2.每个 C++程序中都必须包含有这样一个函数,该函数的函数名为main 3.C++对C语言作了很多改进,下列描述中()使得C语言发生了质变,从面向过程变成了面向对象. ...
- 数据结构逆向分析-Map
数据结构逆向分析-Map map是一个典型的二叉树结构,准确的来说是一个平衡二叉树或者红黑树,特点是数据存储是有序的存储. 参考侯杰老师的stl源码剖析,map里面采用的是RB-TREE也就是红黑树 ...
- Kubernetes-Pod介绍(三)-Pod调度
前言 本篇是Kubernetes第六篇,大家一定要把环境搭建起来,看是解决不了问题的,必须实战. Kubernetes系列文章: Kubernetes介绍 Kubernetes环境搭建 Kuberne ...
- 4.深入TiDB:执行计划执行过程详解
本文基于 TiDB release-5.1进行分析,需要用到 Go 1.16以后的版本 转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com/ ...
- windows系统升级python
卸载python最干净的办法 https://blog.csdn.net/ic_zswdbk/article/details/88315779?utm_medium=distribute.pc_rel ...