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 ...
随机推荐
- configparser生成模块写
# -*-coding:utf-8-*-__author__ = "logan.xu"#写配置文件import configparserconfig = configparser. ...
- BUUCTF-[CISCN2019 华东北赛区]Web2
BUUCTF-[CISCN2019 华东北赛区]Web2 看题 一个论坛,内容不错:) 可以投稿,点击投稿发现要注册,那就先注册登录.随便账号密码就行. 常规操作,扫一下站点,发现有admin.php ...
- 存储系统管理(二)——Linux系统的swap分区、磁盘加密、磁盘阵列
磁盘驱动器上的空间 , 用作当前未使用部分内存的溢出.这样 , 系统就能在主内存中留出空间用于储存当前正在处理的数据 , 并在系统面临主内存空间不足的风险时提供应急溢出. swap分区的建立: fdi ...
- Linux学习笔记 - Linux快捷操作及常用命令
一.快捷键 剪切光标前的内容 Ctrl + u 剪切光标至行末的内容 Ctrl + k 粘贴 Ctrl + u 或 Ctrl +k 的内容 Ctrl + y 移动光标到行末 Ctrl + e 移动光标 ...
- Python脚本导出AWS EC2资源清单
环境需求 单位现在每隔一段时间需要核对一下 AWS 正在运行的 EC2 资源清单,为了避免核对失误以及重复性的工作,打算用脚本来解决这一重复性的工作.大概思路为 通过 AWS AK.SK 来索取 AW ...
- github搜索技巧小结
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- aes加解密前后端-前端
一.ajax请求前端 f12请求和响应参数效果: 1.在前端封装ajax的公共Util的js中,封装ajax请求的地方,在beforeSend方法和成功之后的回调函数success方法中: var p ...
- 简单操作:10分钟实现在kubernetes(k8s)里面部署服务器集群并访问项目(docker三)
前言 经过docker安装.k8s开启并登录,我们终于到 "部署k8s服务器集群并访问项目" 这一步了,实现的过程中有太多坑,好在都填平了,普天同庆. 在进行当前课题之前,我们需要 ...
- PTA面向对象程序设计6-3 面积计算器(函数重载)
实现一个面积计算器,它能够计算矩形或长方体的面积. 函数接口定义: int area(int x, int y); int area(int x, int y, int z); 第一个函数计算长方形的 ...
- Django学习day01随堂笔记
每日测验 """ 每日测验 1.你所知道的前端框架和实用插件有哪些,他们各有什么特点 2.使用bootstrap需要注意什么,常用的bootstrap样式有哪些 &quo ...