服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Java教程 - Spring Boot 如何热加载Jar实现动态插件?

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

2021-10-18 20:51陶陶技术笔记zlt2000 Java教程

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

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

一、背景

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

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

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

二、热加载 jar 包

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

「ClassLoaderUtil 类」

  1. publicclassClassLoaderUtil{
  2. publicstaticClassLoadergetClassLoader(Stringurl){
  3. try{
  4. Methodmethod=URLClassLoader.class.getDeclaredMethod("addURL",URL.class);
  5. if(!method.isAccessible()){
  6. method.setAccessible(true);
  7. }
  8. URLClassLoaderclassLoader=newURLClassLoader(newURL[]{},ClassLoader.getSystemClassLoader());
  9. method.invoke(classLoader,newURL(url));
  10. returnclassLoader;
  11. }catch(Exceptione){
  12. log.error("getClassLoader-error",e);
  13. returnnull;
  14. }
  15. }
  16. }

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

三、动态注册 Bean

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

3.1. 启动时注册

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

  1. publicclassPluginImportBeanDefinitionRegistrarimplementsImportBeanDefinitionRegistrar{
  2. privatefinalStringtargetUrl="file:/D:/SpringBootPluginTest/plugins/plugin-impl-0.0.1-SNAPSHOT.jar";
  3. privatefinalStringpluginClass="com.plugin.impl.PluginImpl";
  4. @SneakyThrows
  5. @Override
  6. publicvoidregisterBeanDefinitions(AnnotationMetadataimportingClassMetadata,BeanDefinitionRegistryregistry){
  7. ClassLoaderclassLoader=ClassLoaderUtil.getClassLoader(targetUrl);
  8. Classclazz=classLoader.loadClass(pluginClass);
  9. BeanDefinitionBuilderbuilder=BeanDefinitionBuilder.genericBeanDefinition(clazz);
  10. BeanDefinitionbeanDefinition=builder.getBeanDefinition();
  11. registry.registerBeanDefinition(clazz.getName(),beanDefinition);
  12. }
  13. }

3.2. 运行时注册

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

  1. @GetMapping("/reload")
  2. publicObjectreload()throwsClassNotFoundException{
  3. ClassLoaderclassLoader=ClassLoaderUtil.getClassLoader(targetUrl);
  4. Classclazz=classLoader.loadClass(pluginClass);
  5. springUtil.registerBean(clazz.getName(),clazz);
  6. PluginInterfaceplugin=(PluginInterface)springUtil.getBean(clazz.getName());
  7. returnplugin.sayHello("testreload");
  8. }

「SpringUtil 类」

  1. @Component
  2. publicclassSpringUtilimplementsApplicationContextAware{
  3. privateDefaultListableBeanFactorydefaultListableBeanFactory;
  4. privateApplicationContextapplicationContext;
  5. @Override
  6. publicvoidsetApplicationContext(ApplicationContextapplicationContext)throwsBeansException{
  7. this.applicationContext=applicationContext;
  8. ConfigurableApplicationContextconfigurableApplicationContext=(ConfigurableApplicationContext)applicationContext;
  9. this.defaultListableBeanFactory=(DefaultListableBeanFactory)configurableApplicationContext.getBeanFactory();
  10. }
  11. publicvoidregisterBean(StringbeanName,Classclazz){
  12. BeanDefinitionBuilderbeanDefinitionBuilder=BeanDefinitionBuilder.genericBeanDefinition(clazz);
  13. defaultListableBeanFactory.registerBeanDefinition(beanName,beanDefinitionBuilder.getRawBeanDefinition());
  14. }
  15. publicObjectgetBean(Stringname){
  16. returnapplicationContext.getBean(name);
  17. }
  18. }

四、总结

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

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

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

五、完整 demo

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

原文链接:https://mp.weixin.qq.com/s/Fg-jsoFon5LwsPAaBbeiew

延伸 · 阅读

精彩推荐
  • Java教程Java实现抢红包功能

    Java实现抢红包功能

    这篇文章主要为大家详细介绍了Java实现抢红包功能,采用多线程模拟多人同时抢红包,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙...

    littleschemer13532021-05-16
  • Java教程Java BufferWriter写文件写不进去或缺失数据的解决

    Java BufferWriter写文件写不进去或缺失数据的解决

    这篇文章主要介绍了Java BufferWriter写文件写不进去或缺失数据的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望...

    spcoder14552021-10-18
  • Java教程小米推送Java代码

    小米推送Java代码

    今天小编就为大家分享一篇关于小米推送Java代码,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧...

    富贵稳中求8032021-07-12
  • Java教程xml与Java对象的转换详解

    xml与Java对象的转换详解

    这篇文章主要介绍了xml与Java对象的转换详解的相关资料,需要的朋友可以参考下...

    Java教程网2942020-09-17
  • Java教程升级IDEA后Lombok不能使用的解决方法

    升级IDEA后Lombok不能使用的解决方法

    最近看到提示IDEA提示升级,寻思已经有好久没有升过级了。升级完毕重启之后,突然发现好多错误,本文就来介绍一下如何解决,感兴趣的可以了解一下...

    程序猿DD9332021-10-08
  • Java教程20个非常实用的Java程序代码片段

    20个非常实用的Java程序代码片段

    这篇文章主要为大家分享了20个非常实用的Java程序片段,对java开发项目有所帮助,感兴趣的小伙伴们可以参考一下 ...

    lijiao5352020-04-06
  • Java教程Java8中Stream使用的一个注意事项

    Java8中Stream使用的一个注意事项

    最近在工作中发现了对于集合操作转换的神器,java8新特性 stream,但在使用中遇到了一个非常重要的注意点,所以这篇文章主要给大家介绍了关于Java8中S...

    阿杜7472021-02-04
  • Java教程Java使用SAX解析xml的示例

    Java使用SAX解析xml的示例

    这篇文章主要介绍了Java使用SAX解析xml的示例,帮助大家更好的理解和学习使用Java,感兴趣的朋友可以了解下...

    大行者10067412021-08-30