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/7099710892845039647logo设计
创造品牌价值
¥500元起
APP开发
量身定制,源码交付
¥2000元起
商标注册
一个好品牌从商标开始
¥1480元起
公司注册
注册公司全程代办
¥0元起
查
看
更
多
一种简单的热部署方式结合动态编译实现java文件热替换
表示需要编译的源文件GenericJavaFileObject fileObject = new GenericJavaFileObject(className, javaContent);\/\/ 获取该工程下所有的jar文件。String importPagePathLogin = getJarFiles(jarPath);\/\/ 编译选项,在编译java文件时,编译程序会自动的去寻找java文件引用的其他的java源文件或者class。\/\/ -sour...
...频繁打成war包部署使用tomcat和maven实现热部署配置
常用的部署方式是将项目打包成war包放到tomcat的webapps下,然后重启tomcat,然后通过ip地址+端口号访问。这样部署本身是没问题的,但问题在于,如果还是在生产环境下的话,需要频繁的更改优化项目,那么就需要频繁的将项目打war包,替换webapps下的war包,操作繁琐。接下来我们讲述如何实现本地编程,然后部署...
『IDEA』代码热部署和热加载
首先,严格区分热部署和热加载:热部署在服务器运行时重新部署整个应用,虽然能彻底释放内存,但过程较长;相比之下,热加载只在运行时重新加载类,利用Java的类加载机制,如Spring Boot的devtools。然而,devtools的热加载速度较慢,不推荐常规使用。IDEA中,我们可以通过两种方式启动项目:手动重启,通过Ctrl...
Java远程热部署插件HotSeconds的使用
HotSeconds支持单文件热更新,如java文件直接编译并热部署,资源文件和jar包也有相应的处理方式。对于复杂的资源文件,通过路径映射上传后,依赖于插件扩展的逻辑来刷新缓存。对于多语言支持和多IP管理,也有相应的设置选项。虽然HotSeconds在本地和测试环境中能提供便利,但不推荐在生产线上使用,因为Java运行...
个人学习系列 - SpringBoot整合devtools实现热部署
Spring Boot devtools采用双类加载器(ClassLoader)机制来实现热部署功能。其中一个类加载器负责加载稳定性较高的第三方库,而另一个类加载器则专用于加载可能发生变化的自定义类。后台启动的文件监听线程(File Watcher)监控指定目录内文件变动情况,一旦发现变动,原类加载器将被替换为新的类加载器,仅...
osgi是什么
首先,OSGi是一种基于Java的模块化技术,它将Java的软件系统构建为模块化的服务组件,并通过模块化技术实现了服务的高可用性、可配置性和可管理性。简单来说,OSGi技术可以帮助开发者构建更为灵活和可维护的应用程序。通过这种方式,应用程序中的组件可以根据需要动态加载或卸载,无需重启整个应用程序。其次...
java游戏服务器怎么实现热更新
在Java中,要实现热部署,首先,你得明白,Java中类的加载方式。每一个应用程序的类都会被ClassLoader加载,所以,要实现一个支持热部署的应用,我们可以对每一个用户自定义的应用程序使用一个单独的ClassLoader进行加载。然后,当某个用户自定义的应用程序发生变化的时候,我们首先销毁原来的应用,然后使用...
java如何热部署? 本地起服务,运行环境是eclipse+maven+jetty。 我...
貌似修改配置文件都需要重启服务,有些项目会扩展一下添加热部署。至于使用配置直接实现热部署还有遇到过。
一文详解JDK9之前的ClassLoader
OSGi(Open Service Gateway Initiative)技术提供了一种动态化模块化系统的方式。在JDK9之前,OSGi是业界Java模块化标准,通过自定义类加载器实现模块的热部署。每个Bundle(模块)都有自己的类加载器,使得当替换模块时,只需替换类加载器即可实现热部署。打破双亲委派机制主要有四种方式:通过重写ClassLoader...
Java Agent 介绍
Java Agent的入口通常在main方法之前,通过premain方法实现,这个方法在JVM启动时或类加载时都会执行。只需在启动命令中添加-javaagent参数,即可启用Java Agent。Java 6之后,甚至可以动态为运行中的程序加载代理。例如,在APM产品如Pinpoint和SkyWalking中,Java Agent被用于收集统计信息和实现分布式链路追踪,...