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
掃碼關注有驚喜!
- 如何基於Security框架相容多套使用者密碼加密方式
- 基於Kubernetes(k8s)部署Dubbo Nacos服務
- 基於jib-maven-plugin快速構建微服務docker映象
- 基於minikube快速搭建單節點環境
- 隱私計算FATE-多分類神經網路演算法測試
- 隱私計算FATE-離線預測
- 隱私計算FATE-模型訓練
- 隱私計算FATE-核心概念與單機部署
- Hyperledger Fabric 核心概念
- Hyperledger Fabric 2.x Java區塊鏈應用
- Hyperledger Fabric 2.x 自定義智慧合約
- Hyperledger Fabric 2.x 環境搭建
- Spring Boot 如何熱載入jar實現動態外掛?
- 如何基於Security實現OIDC單點登入?
- 第三方API對接如何設計介面認證?
- 免費正版 IntelliJ IDEA license 詳細指南
- ClickHouse效能優化?試試物化檢視
- 全量同步Elasticsearch方案之Canal
- Canal高可用架構部署
- 大資料量查詢容易OOM?試試MySQL流式查詢