Java模块 -- jar包热部署/热卸载

本文介绍了一种基于Java WatchService的动态加载和卸载jar包的方法,实现服务框架的灵活配置和动态扩展,以应对不同公司的服务对接需求。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

之前一篇文章Java模块 -- WatchService监听服务 实现了监听目录的功能。

后来我寻摸着,可以根据这个功能,实现另一个功能:动态加载jar包和卸载jar包。


我来详细说说我这个功能的需求,顺便说说我经手的服务框架:

之前在工作中,主要是开发服务层的框架任务中心框架、ServiceCenter发布中心框架等。

这些框架是干嘛的呢?

举个现实的的需求啊...

我现在要和好几家公司做服务对接,每个提供的查询服务是不同的。

按照正常的思路,我们需要写好几个个服务端,提供好几种不同的查询服务,至于提供Http还是WebSerice服务这个另说。

你想想 后面如果还有其他公司要做服务对接,我是不是还要再写服务?

是不是 很麻烦?


有人提出,我可以写一个大的服务端,统一入口,在服务端中,通过不同的查询类型,通过代码if...else...if...else...来判断它想要调用哪个查询服务。

这个主意不错,不过这种方式灵活嘛?能只支持动态扩展嘛?

你想,如果要新增一个查询类型,我是不是要改动服务端的代码?要增加一个if判断语句?


为了实现灵活和动态扩展的要求,我们可以将业务代码从服务端代码中剥离出来。


专门写一个框架,这个框架的作用就是对外发布服务(HTTP、WebService),客户端查询鉴权、资源隔离等功能。

具体的查询业务逻辑,使用SDK应用模块的方式集成到服务框架中,这样子,框架就是框架,模块就是模块,新增业务模块,无需改动框架代码。

实现 框架与业务模块的分离。


如果要新对接一个公司,提供某种查询,我们就不用重新开发一个服务,仅需要编写查询业务的代码,然后将jar包放到框架目录中,框架自动加载,实现灵活配置,动态扩展。


你看啊...每次我新增一个业务jar包,都需要重新框架程序,是不是很麻烦...

如此,我就想,是不是需要一个动态加载和卸载的功能。


关于卸载,关于jar包卸载,我们并不能真正的从JVM中卸载JVM...

我使用的是"假卸载",我将加载的 业务主类放在放在集合中,每次需要的时候,从集合中取出,并实例化...

卸载的时候,我将集合中的类,删除即可....

实现"假卸载"...

加载类的问题...

由于我这里采用的是Java注解的方式,使用自定义的ClassLoader会出现问题...

这个后面还需要仔细测测...

这里参考的是:

http://www.cnblogs.com/cm4j/p/hot_deploy.html


下面是核心代码:

// ---------------------------热部署--------------------------------------

static {
	// 当前进程pid
	String name = ManagementFactory.getRuntimeMXBean().getName();
	pid = name.split("@")[0];
	logger.info("当前进程pid:" + pid);
}

/**
 * 动态监控modules目录,sdk应用热部署
 * 
 * @throws Exception
 */
private void watchModulesDirWithHotDeployment() throws Exception {

	String modulesDirPath = new File(ServiceCenterConstant.SERVICECENTER_MODULE_DIR_PATH).getAbsolutePath();

	try {

		// 获取文件系统的WatchService对象
		modulesDirHotDeploymentWatchService = FileSystems.getDefault().newWatchService();

		// 监听'filePath'下是否有新建的目录;register()方法后面监听事件种类还可以增加。
		Paths.get(modulesDirPath).register(modulesDirHotDeploymentWatchService,
				StandardWatchEventKinds.ENTRY_CREATE);

	} catch (Exception e) {
		logger.error(e.getMessage(), e);
	}

	// 设置后台线程
	Thread watchThread = new Thread(new Runnable() {

		@Override
		public void run() {

			while (true) {

				try {

					// 获取下一个文件改动事件
					WatchKey key = modulesDirHotDeploymentWatchService.take();

					List<File> moduleLibsPath = new ArrayList<File>();
					for (WatchEvent<?> event : key.pollEvents()) {

						/** 睡眠一会儿,防止目录移动慢、解压缩慢 */
						Thread.sleep(2000);

						String moduleName = event.context().toString();

						String moduleDirPath = modulesDirPath + "/" + moduleName;

						moduleLibsPath.addAll(findMouldLibs(new File(moduleDirPath + "/lib")));

						for (File moduleLib : moduleLibsPath) {

							// 虚拟机加载
							vm = VirtualMachine.attach(pid);
							vm.loadAgent(moduleLib.getAbsolutePath());

						}

						// 测试使用,模拟调用sdk
						Class clazz = Class.forName("com.sc.service.sample.SampleSDKQueryServiceModule");
						Object obj = clazz.newInstance();
						ServiceCenterBaseClass service = (ServiceCenterBaseClass) obj;
						service.loadLogger(logger);
						service.process("123");

						/** SDK应用模块注解解析 */
						getInformationBySDKAnnotation();

						/** 解析SDK应用模块下的配置文件 */
						analyModuleConfigFile(new File(moduleDirPath));

						/** 加载对应模块的线程池 */
						loadThreadPool(moduleName);

					}

					// 重设WatchKey
					boolean valid = key.reset();

					// 如果重设失败,退出监听
					if (!valid) {
						break;
					}

					logger.info("configs : " + LoadConfig.getInstance().toString());

					Thread.sleep(5000);

				} catch (Exception e) {
					logger.error(e.getMessage(), e);
				}

			}
		}

	});

	// 设置为后台守护线程
	watchThread.setDaemon(true);
	watchThread.start();

}


// -------------------------------热卸载-------------------------------

private void watchModuleDirWithUninstalling() throws Exception {

	String modulesDirPath = new File(ServiceCenterConstant.SERVICECENTER_MODULE_DIR_PATH).getAbsolutePath();

	try {

		// 获取文件系统的WatchService对象
		modulesDirUninstallWatchService = FileSystems.getDefault().newWatchService();

		// 监听'filePath'下是否有新建的目录;register()方法后面监听事件种类还可以增加。
		Paths.get(modulesDirPath).register(modulesDirUninstallWatchService, StandardWatchEventKinds.ENTRY_DELETE);

	} catch (Exception e) {
		logger.error(e.getMessage(), e);
	}

	// 设置后台线程
	Thread watchThread = new Thread(new Runnable() {

		@Override
		public void run() {

			while (true) {

				try {

					// 获取下一个文件改动事件
					WatchKey key = modulesDirUninstallWatchService.take();

					for (WatchEvent<?> event : key.pollEvents()) {

						/** 被删除SDK的模块名、模块类型 */
						String moduleName = event.context().toString();

						// 测试使用,模拟调用sdk
						Class clazz = Class.forName("com.sc.service.sample.SampleSDKQueryServiceModule");
						Object obj = clazz.newInstance();
						ServiceCenterBaseClass service = (ServiceCenterBaseClass) obj;
						service.loadLogger(logger);
						service.process("123");

						logger.info("unloaded moduleName : " + moduleName + " is start...");

						String moduleType = moduleNameWithModuleType.get(moduleName);

						/** 删除内存中对应模块名、模块类型的配置 */
						/** 客户端再调用,就无法获取到对应的SDK-Class,以达到卸载的目的 */
						/** 同时,每次客户端调用完成之后,手动将ServiceCenterBaseClass对象置为null */
						moduleTypeWithClass.remove(moduleType);
						moduleTypeWithModuleName.remove(moduleType);
						moduleNameWithModuleType.remove(moduleName);
						moduleNameWithModulePoolParams.remove(moduleName);
						moduleNameWithModuleLog4jFilePath.remove(moduleName);
						moduleTypeWithModuleConfigFile.remove(moduleType);
						moduleNameWithModuleThreadPool.remove(moduleName);

						logger.info("unloaded moduleName : " + moduleName + " is end...");

					}

					// 重设WatchKey
					boolean valid = key.reset();

					// 如果重设失败,退出监听
					if (!valid) {
						break;
					}

					logger.info("configs : " + LoadConfig.getInstance().toString());

					Thread.sleep(2000);
					System.gc();

					Thread.sleep(5000);

				} catch (Exception e) {
					logger.error(e.getMessage(), e);
				}

			}
		}

	});

	// 设置为后台守护线程
	watchThread.setDaemon(true);
	watchThread.start();

}


cordova-hot-code-push-plugin 是 Cordova 平台下的一个插件,可以帮助开发者实现 APK 更新。下面是详细的使用教程: 1. 安装插件 在 Cordova 项目中执行以下命令安装插件: ```bash cordova plugin add cordova-hot-code-push-plugin ``` 2. 配置插件 在 Cordova 项目的 `config.xml` 文件中添加以下配置: ```xml <hcp> <auto-download enabled="true" /> <auto-install enabled="true" /> <widget id="com.example.app" version="0.0.1"> <content src="index.html" /> <platform name="android"> <preference name="android-minSdkVersion" value="16" /> <preference name="android-targetSdkVersion" value="30" /> <icon src="res/android/ldpi.png" density="ldpi" /> <icon src="res/android/mdpi.png" density="mdpi" /> <icon src="res/android/hdpi.png" density="hdpi" /> <icon src="res/android/xhdpi.png" density="xhdpi" /> <icon src="res/android/xxhdpi.png" density="xxhdpi" /> <icon src="res/android/xxxhdpi.png" density="xxxhdpi" /> <allow-navigation href="*" /> <allow-intent href="*" /> <access origin="*" /> </platform> </widget> </hcp> ``` 其中,`auto-download` 和 `auto-install` 分别表示是否开启自动下载和自动安装更新。`widget` 标签下的其他配置项与 Cordova 项目一致。 3. 打 APK 在 Cordova 项目中执行以下命令打 APK: ```bash cordova build android --release ``` 4. 生成更新 在 Cordova 项目中执行以下命令生成更新: ```bash cordova-hcp build ``` 该命令会在项目根目录下生成一个 `www.zip` 文件,该文件含了需要更新的代码和资源。 5. 上传更新 将 `www.zip` 文件上传到服务器上。可以使用任何支持 HTTP 文件下载的服务器,例如 Apache、Nginx 等。 6. 应用更新 在 Cordova 项目中引入 `chcp.js` 文件,并在应用程序启动时执行以下代码: ```javascript chcp.fetchUpdate(function(error, data) { if (error) { console.error('Failed to fetch update:', error); } else if (data) { console.log('Update is available:', data); chcp.installUpdate(function(error) { if (error) { console.error('Failed to install update:', error); } else { console.log('Update installed successfully'); } }); } else { console.log('No update available'); } }); ``` 该代码会检查是否有更新可用,并在有更新时下载并安装更新。 以上就是使用 cordova-hot-code-push-plugin 实现 APK 更新的详细教程。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值