当前位置: 首页 > 后端技术 > Java

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

时间:2023-04-02 00:02:23 Java

1。背景动态插件编程是一件很酷的事情。可以实现业务功能的解耦,方便维护。此外,它还可以提高可扩展性。随时可以在不停止服务器的情况下扩展功能,而且它的开放性也很好,不仅允许自己的开发者开发功能,也接受第三方开发者按照规范开发的插件。动态插件常见的实现方式有SPI、OSGI等方案。由于SpringIOC的管理是分离的,所以主程序的Bean对象无法注入到插件中。比如Redis已经集成在主程序中,但不能在插件中使用。本文主要介绍一种在SpringBoot项目中热加载jar包并注册为Bean对象的实现思路。在动态扩展功能的同时,支持在插件中向主程序注入bean,实现更强大的插件。2、热加载jar包通过指定的链接或路径动态加载jar包,可以使用URLClassLoader的addURL方法实现。示例代码如下:如果(!method.isAccessible()){method.setAccessible(true);}URLClassLoaderclassLoader=newURLClassLoader(newURL[]{},ClassLoader.getSystemClassLoader());method.invoke(classLoader,newURL(url));返回类加载器;}catch(Exceptione){log.error("getClassLoader-error",e);返回空值;}}}whereURLClassLoadercreated当指定当前系统的ClassLoader为父类加载器时ClassLoader.getSystemClassLoader()这一步比较关键,用来打通主程序和插件之间的ClassLoader,以及解决插件注册到IOC时各种ClassNotFoundException问题。3、bean的动态注册将插件jar中加载的实现类注册到Spring的IOC中,同时将IOC中已有的bean注入到插件中;分别在程序启动和运行时两种场景下的实现方法。3.1.启动时注册bean使用ImportBeanDefinitionRegistrar在SpringBoot启动时动态注册插件bean。示例代码如下:PluginImportBeanDefinitionRegistrarclasspublicclassPluginImportBeanDefinitionRegistrarimplementsImportBeanDefinitionRegistrar{privatefinalStringtargetUrl="file:/D:/Tplugins/SpringBootPlugins/SpringBoot-impl-0.0.1-SNAPSHOT.jar";privatefinalStringpluginClass="com.plugin.impl.PluginImpl";@SneakyThrows@OverridepublicvoidregisterBeanDefinitions(AnnotationMetadataimportingClassMetadata,BeanDefinitionRegistryregistry){ClassLoaderclassUiltgetLoader=ClassLoaderU);类clazz=classLoader.loadClass(pluginClass);BeanDefinitionBuilder构建器=BeanDefinitionBuilder.genericBeanDefinition(clazz);BeanDefinitionbeanDefinition=builder.getBeanDefinition();registry.registerBeanDefinition(clazz.getName(),beanDefinition);}}3.2。在运行时注册Bean。使用Ap在运行时动态注册插件BeanplicationContext对象来现实,示例代码如下:@GetMapping("/reload")publicObjectreload()throwsClassNotFoundException{ClassLoaderclassLoader=ClassLoaderUtil.getClassLoader(targetUrl);类clazz=classLoader.loadClass(pluginClass);springUtil.registerBean(clazz.getName(),clazz);PluginInterface插件=(PluginInterface)springUtil.getBean(clazz.getName());returnplugin.sayHello("testreload");}SpringUtil类@ComponentpublicclassSpringUtilimplementsApplicationContextAware{privateDefaultListableBeanFactorydefaultListableBeanFactory;私有应用程序上下文应用程序上下文;@OverridepublicvoidsetApplicationContext(ApplicationContextapplicationContext)throwsBeansException{this.applicationContext=applicationContext;ConfigurableApplicationContextconfigurableApplicationContext=(ConfigurableApplicationContext)applicationContext;this.defaultListableBeanFacstory=(DefaultListableBeanFactory)configurableApplicationContext.getBeanFactory();}publicvoidregisterBean(StringbeanName,Classclazz){BeanDefinitionBuilderbeanDefinitionBuilder=BeanDefinitionBuilder.genericBeanDefinition(clazz);publicObjectgetBean(Stringname){returnapplicationContext.getBean(name);}}4.总结本文介绍的插件实现思路。通过共享ClassLoader,动态注册bean,打通插件与主程序之间的类加载器。而Spring容器使得插件之间以及插件与主程序之间的类交互变得非常方便,比如在插件中注入主程序的Redis和DataSource,调用远程Dubbo接口,等,在ClassLoader之间隔离时,也可能会出现类冲突、版本冲突等问题;并且因为ClassLoader中的Class对象是无法销毁的,除非修改了类名或者类路径,否则插件中加载到ClassLoader中的类是不能被动态修改的。因此,该方案更适用于插件数据量不多,开发规范良好,插件测试后才能上线或发布的场景。5.完整demohttps://github.com/zlt2000/springs-boot-plugin-test扫描二维码关注,有惊喜哦!