问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501

一种简单的热部署方式结合动态编译实现java文件热替换

发布网友 发布时间:2024-10-01 17:30

我来回答

1个回答

热心网友 时间:2024-10-17 18:58

前言日常开发中常常遇到这样的场景:

1、测试环境查证问题,想加日志来辅助查证,之前人们都是使用塞包的方式。使用这种方式还得重启,一些应用启动耗时也比较久。

2、测试环境联调,一些数据测试环境不一定造的出来,需要后台写死,这种如果采用提交代码的方式,复杂程度大,也容易被带上线,风险高。

如何解决:一、初级阶段:

采用代码热更新的方式,这种如果只是涉及一些添加日志,或者在方法内部做一些微小的改动,那么可以采用jvm提供的Instrumentation接口,通过该接口可以实现初级的热部署。

例如:

public static void agentmain(String agentArgs, Instrumentation inst) {// 从 agentArgs 获取外部参数System.out.println("开始热更新代码");String path = agentArgs;System.out.println("路径为:" + path);PrintStream out;try {RandomAccessFile f = new RandomAccessFile(path, "r");final byte[] bytes = new byte[(int) f.length()];f.readFully(bytes);final String clazzName = readClassName(bytes);// 加载for (Class clazz : inst.getAllLoadedClasses()) {//System.out.println("========hotswap=========" + clazz.getName());if (clazz.getName().equals(clazzName)) {ClassDefinition definition = new ClassDefinition(clazz, bytes);inst.redefineClasses(definition);}}} catch (UnmodifiableClassException | IOException | ClassNotFoundException e) {System.out.println("热更新数据失败");}}二、进阶阶段

一般情况下使用初级阶段就可以解决大部分问题,但是有一些比较老旧的代码可能开发人员本地编译环境都没有,没有办法生成class文件,那么这种时候就需要引入动态编译。

动态编译的原理是采用JavaCompiler的方式来实现内存编译。核心思想就是利用java的编译命令去编译。

例如

javac -classpath "a.jar;b.jar;c.jar" Test.java -- windows下的写法javac -classpath "a.jar:b.jar:c.jar" Test.java -- Linux下的写法,区别在于分隔符一个是;y一个是:

使用代码实现核心逻辑:

/** * 编译java文件 * @param javaContent 要编译的内容 * @param jarPath 依赖jar包路径 * @param saveClassPath 编译后的.class文件保存路径 */public String compileJava(String javaContent, String jarPath, String saveClassPath, String className) {try {// 创建java编译器实例JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();// 创建对象用于获取编译输出信息DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();// 获得JavaFileManager文件管理器对象,用于管理需要编译的文件StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnosticCollector, null, null);// 生成一个JavaFileObject对象,表示需要编译的源文件GenericJavaFileObject fileObject = new GenericJavaFileObject(className, javaContent);// 获取该工程下所有的jar文件。String importPagePathLogin = getJarFiles(jarPath);// 编译选项,在编译java文件时,编译程序会自动的去寻找java文件引用的其他的java源文件或者class。// -sourcepath选项就是定义java源文件的查找目录, -classpath选项就是定义class文件的查找目录,-d就是编译文件的输出目录。Map<String, String> cacheMap = Config.getConfig().getsysConfig();String isSave = cacheMap.get("isSave");Iterable<String> options = null;if ("true".equals(isSave)) {options = Arrays.asList("-d", saveClassPath, "-classpath", importPagePathLogin);} else {options =Arrays.asList( "-classpath", importPagePathLogin);}//Iterable<String> options = Arrays.asList("-d", saveClassPath, "-classpath", importPagePathLogin);//Iterable<String> options = Arrays.asList( "-classpath", importPagePathLogin);// 将java文件转化为listIterable<? extends JavaFileObject> fileObjects = Collections.singletonList(fileObject);// 获取编译任务(1、第一个参数为文件输出,这里我们可以不指定,我们采用javac命令的-d参数来指定class文件的生成目录// 2、第二个参数为文件管理器实例// 3、DiagnosticCollector<JavaFileObject> diagnostics是在编译出错时,存放编译错误信息// 4、第四个参数为编译命令选项,就是javac命令的可选项,这里我们主要使用了-d和-sourcepath这两个选项// 5、第五个参数为类名称// 6、第六个参数为上面提到的编译单元,就是我们需要编译的java源文件)JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnosticCollector, options, null, fileObjects);// 编译Boolean result = task.call();if (result) {System.out.println("编译成功");return "success";} else {System.out.println("编译失败");// 编译失败,打印错误信息StringBuilder errorInfo = new StringBuilder();for (Diagnostic<?> diagnostic : diagnosticCollector.getDiagnostics()) {errorInfo.append("编译错误。 Code:").append(diagnostic.getCode()).append("\r\n").append("Kind:").append(diagnostic.getKind()).append("\r\n").append("StartPosition:").append(diagnostic.getStartPosition()).append("\r\n").append("EndPosition:").append(diagnostic.getEndPosition()).append("\r\n").append("Position:").append(diagnostic.getPosition()).append("\r\n").append("Source:").append(diagnostic.getSource()).append("\r\n").append("Message:").append(diagnostic.getMessage(null)).append("\r\n").append("ColumnNumber:").append(diagnostic.getColumnNumber()).append("\r\n").append("LineNumber:").append(diagnostic.getLineNumber());}System.out.println("错误信息如下:" + errorInfo);return errorInfo.toString();}} catch(Exception e) {e.printStackTrace();}return null;}

这样就可以实现动态根据java文件生成class文件,那么再结合初级阶段的热更新方式基本上就可以解决日常开发中常见的问题了。

注意:

动态编译依赖的jar包需要在编译的时候加载进来,我的实现方式是,动态编译不在本地编译而是去测试环境编译,因为测试环境的依赖包肯定是完整的。所以我加载依赖包的路径指定的是容器发布目录下的lib文件夹中的所有文件。

原文:https://juejin.cn/post/7099710892845039647
声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com
在张家界旅游可以买到什么特产或纪念品? 古韵荡春风毛笔字 黑龙江省理科生高三一模和二模都考了490分高考走一表有希望吗? 中药材三七的作用 三七有哪些作用与功效 现场管理失职的检讨书 管理失职检讨书 关于管理失职检讨书怎么写 四川有生产采煤机的大型企业吗? 四川华蓥山广能集团嘉华机械有限责任公司实力怎么样? “烧心”、“胃部隐隐作病”等症状,是因为胃酸过多还是胃酸不足... 科鲁泽和科沃兹的区别 滑县地方特产 华为P10WLAN打不开/无法关闭 薛凯琪个人资料? 薛凯琪早年经历 薛凯琪演艺经历 大型工厂改变用洗地机的理由是什么? 外貌一般的女生怎么变好看? 外貌一般的人怎么快速变好看? 外貌一般的人怎么变好看? 显卡在我电脑的什么位子啊? 外貌一般的人如何逆袭变美? 这段时间孩子耳朵老是流黄水,还发臭,都影响他学习了,趁他放寒假想... ai软件里的置入和直接打开有什么不同ai软件里的置入和直接打开有什么不... ...的耳鼻喉医院是哪里?想看神经性耳鸣福州鼓楼医院耳鼻喉科看的好吗... 四大耳机品牌推荐 微信改名了别人能看到吗 请问亚飞除了9月27日来了后什么时候再还来广饶 2023年9月需要补班吗 2023中秋节需要补班吗 Java实现热加载的三种方式 ...说一个java语法问题,看了一下jasper.compiler.JDTCompiler类中的问题... 有谁知道阿里旺旺买家版与卖家版有什么区别,谢谢告知谢谢了,大神帮忙啊... 新买的小绵羊转绿灯了还要充多久 配电箱时控设置由电脑调整 淘宝 买家和卖家 使用的阿里旺旺 是一个版本的么 公园配电箱定的时间不到就亮了怎么回事 为什么配电箱内的漏电保护器在时控启动时总会有不同位置的开关跳闸,但 ... vivo手机的安全模式在哪里关闭 电视摁错了一个键不出人怎么办?我先嗯红的,在摁橙的 什么叫目标养老基金 养老目标基金频现清盘什么意思 新中国参加的历届奥运会分别都得过多少奖牌? 中国在奥运会上得了多少金牌 哪家快递公司邢台市贾村送货上门啊? ...哪一年发生的什么属性,大兴安岭火灾发生在哪一年_百度... 中通速递是不是分汽运和空运啊 学会使用数据分析的必备工具——EXCEL第二弹 求好看的古代泰剧,最好结局和 ...水反应生成硫酸锰,硫酸钾和硫酸 5SO2+2KMnO4+2H2O=K2SO4+2MnSO4+2...