loveqq 框架!真正不停机热部署,支持新增类字段、类方法,不再局限于只能修改方法体!
为什么需要热部署?
主要是因为大型 java 项目启动耗时长,对于开发阶段来说,增加大量调试成本。对于生产环境来说,则会对客户造成短暂的服务不可用,特别是仅仅修改了少量的情况,确需要重启整个应用,实在是没有必要。
热部署难点?
java 项目热部署的最大的难点是,class 字节码一旦被 jvm 加载生成 class,就不能再修改。即使使用 agent 也只能修改方法体,而不能新增方法、新增字段。
其次,是对各个框架的支持,如果新的 class 是个配置类,对应的三方组件能否正确更新。
还有是否支持远程部署等。
loveqq 如何实现真正的热部署?
1、如何实现修改方法体的热部署?
loveqq 框架内置了 HotSwapAgent,通过 Instrumentation#redefineClasses 实现方法体的热更新,这个是 jvm 直接支持的
2、如何实现新增字段、方法?
这个功能用到 decevm,这是一个开源项目。支持 jdk8、jdk17,更新的 jdk 版本可以直接使用 JetBrainsRuntime jdk 即可
3、如何支持第三方框架、插件?
loveqq 框架实现热部署的方式是自定义 JarIndexClassLoader,直接继承 PlatformClassLoader,摒弃掉了内置的 AppClassLoader。
因此 JarIndexClassLoader 接管了全部的类加载细节。所以 JarIndexClassLoader 支持直接接受一个 JarFile 对象来更新自己的资源。
然后利用 loveqq 框架的全量 ioc 容器刷新机制,直接全量刷新容器即可。由于仅仅指定的 JarFile 被卸载,所以全量刷新效率很高,一般在 1s 以内。
4、全量刷新如何做到不停机?
有的朋友可能疑问,全量刷新,那么 WebServer 是不是也重建了,这难道不会中断服务吗?
不会!因为 loveqq 框架具有 ApplicationScope 作用域,可以保证全量刷新也不会重建 WebServer,所以是真正的不停机热部署!
接入示例
首先创建一个多模块项目,然后添加一个下面的控制器:
package com.kfyty.hot.deploy.controller;
import com.kfyty.loveqq.framework.boot.context.ContextRefresher;
import com.kfyty.loveqq.framework.core.autoconfig.annotation.Value;
import com.kfyty.loveqq.framework.core.lang.JarIndexClassLoader;
import com.kfyty.loveqq.framework.core.utils.ClassLoaderUtil;
import com.kfyty.loveqq.framework.core.utils.IOUtil;
import com.kfyty.loveqq.framework.web.core.annotation.RequestMapping;
import com.kfyty.loveqq.framework.web.core.annotation.RestController;
import com.kfyty.loveqq.framework.web.core.multipart.MultipartFile;
import java.io.File;
import java.io.FileOutputStream;
import java.util.LinkedList;
import java.util.List;
import java.util.jar.JarInputStream;
import java.util.jar.JarOutputStream;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
@RestController
@RequestMapping(value = "/hot", expose = true)
public class HotDeployController {
/**
* 这个值是打包后的 boot-lib 文件夹路径,里面是依赖包
*/
@Value("${bootLibPath:C:\\Users\\admin\\Desktop\\boot-lib}")
private String bootLibPath;
/**
* 热部署,覆盖 boot-lib 文件夹下的依赖包,然后进行热部署
*
* @param jars jar 包,命名必须和已有的库名称相同
*/
public String deploy(List<MultipartFile> jars) throws Exception {
// 构建的 JarFile
List<File> jarFiles = new LinkedList<>();
// 覆盖到依赖 jar 包
for (MultipartFile jar : jars) {
File file = new File(this.bootLibPath, jar.getOriginalFilename());
try (ZipInputStream in = new JarInputStream(jar.getInputStream());
ZipOutputStream out = new JarOutputStream(new FileOutputStream(file))) {
IOUtil.copy(in, out, "");
jarFiles.add(file);
}
}
// 部署到框架 ClassLoader
JarIndexClassLoader classLoader = ClassLoaderUtil.getIndexedClassloader();
classLoader.deploy(jarFiles);
// 刷新上下文
ContextRefresher.refresh();
return "ok";
}
}
打包后使用 jar 包启动,然后通过 http://127.0.0.1:8080/hot/deploy 上传修改后的 jar 包即可观察热部署效果。
感兴趣的同学可以尝试一下。