一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - Spring Boot熱加載jar實現動態插件的思路

Spring Boot熱加載jar實現動態插件的思路

2022-02-22 00:38zlt2000 Java教程

本文主要介紹在 Spring Boot 工程中熱加載 jar 包并注冊成為 Bean 對象的一種實現思路,在動態擴展功能的同時支持在插件中注入主程序的 Bean 實現功能更強大的插件

Spring Boot熱加載jar實現動態插件的思路

一、背景

動態插件化編程是一件很酷的事情,能實現業務功能的 解耦 便于維護,另外也可以提升 可擴展性 隨時可以在不停服務器的情況下擴展功能,也具有非常好的 開放性 除了自己的研發人員可以開發功能之外,也能接納第三方開發商按照規范開發的插件。

常見的動態插件的實現方式有 SPI、OSGI 等方案,由于脫離了 Spring IOC 的管理在插件中無法注入主程序的 Bean 對象,例如主程序中已經集成了 Redis 但是在插件中無法使用。

本文主要介紹在 Spring Boot 工程中熱加載 jar 包并注冊成為 Bean 對象的一種實現思路,在動態擴展功能的同時支持在插件中注入主程序的 Bean 實現功能更強大的插件。

二、熱加載 jar 包

通過指定的鏈接或者路徑動態加載 jar 包,可以使用 URLClassLoaderaddURL 方法來實現,樣例代碼如下:

ClassLoaderUtil 類

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 類

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 對象來實現,樣例代碼如下:

?
1
2
3
4
5
6
7
8
@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 類

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@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熱加載jar內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!

原文鏈接:https://blog.csdn.net/zlt2000/article/details/120820770

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 国产成人小视频在线观看 | 青青草国产一区二区三区 | 99视频导航| 99色在线观看 | 99精品国产成人一区二区 | 99精品影院| a级精品九九九大片免费看 a级动漫 | 色老板影视 | 色婷婷婷丁香亚洲综合不卡 | 日本免费在线观看视频 | 亚洲国产精品久久人人爱 | 无码人妻99久久密AV | 国产精品视频视频久久 | 九九九久久久 | 亚洲高清一区二区三区久久 | 日韩成人一级 | 国产午夜精品理论片 | 欧美精品综合一区二区三区 | 四虎影院永久网站 | 拔插拔插成人 | 婚前试爱免费观看 | 女主被当众调教虐np | 日韩欧美色图 | 麻豆找网服 | 波多 在线播放 | 饭冈加奈子在线播放观看 | 国产日韩综合 | 污书屋 | 亚洲网站在线看 | 99久久99热久久精品免费看 | 成人天堂入口网站 | 人妖女天堂视频在线96 | 好舒服好爽再快点视频 | 亚洲欧美另类综合 | 添逼逼视频 | 欧美va在线 | 国产剧情一区 | 国产精品免费一级在线观看 | 无码乱人伦一区二区亚洲 | 青青青国产 | 女人被男人躁得好爽免费视频 |