在运维过程中,有时候需要临时修改下服务器上已运行项目的代码。此时可使用arthas修改代码并热交换,达到不停机修改的目的。
测试案例
假设服务器上已经运行一段代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package com.example.demo;
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication @RestController public class Demo1Application {
public static void main(String[] args) { SpringApplication.run(Demo1Application.class, args); }
@GetMapping("/test") public String test() { return "aaa"; }
}
|
此时放问接口返回aaa
,需要修改返回结果为bbb
过程
安装
1 2
| curl -O https://arthas.aliyun.com/arthas-boot.jar java -jar arthas-boot.jar
|
输出结果如下:
1 2 3 4 5 6 7 8
| [INFO] arthas-boot version: 3.5.4 [INFO] Process 28783 already using port 3658 [INFO] Process 28783 already using port 8563 [INFO] Found existing java process, please choose one and input the serial number of the process, eg : 1. Then hit ENTER. * [1]: 28783 target/demo1-0.0.1-SNAPSHOT.jar [2]: 33008 org.jetbrains.jps.cmdline.Launcher [3]: 33009 org.jetbrains.jps.cmdline.Launcher [4]: 75256
|
输入1
选择需要调试的进程
搜索
搜索需要修改的类:
1
| com.example.demo.Demo1Application
|
“Search-Method” 的简写,这个命令能搜索出所有已经加载了 Class 信息的方法信息。
反编译
将class反编译为java文件到当前目录的Demo1Application.java
中
1
| jad --source-only --lineNumber=false com.example.demo.Demo1Application > ./Demo1Application.java
|
jad
命令将 JVM 中实际运行的 class 的 byte code 反编译成 java 代码,便于你理解业务逻辑。
修改代码
其他终端使用vim或其他编辑器修改生成在当前目录下的Demo1Application.java
为如下:
1 2 3 4
| @GetMapping("/test") public String test() { return "bbb"; }
|
查看classloader
输入命令
如下:
1 2 3 4 5 6 7 8 9 10
| classloader name numberOfInstances loadedCountTotal org.springframework.boot.loader.LaunchedURLClassLoader 1 4208 BootstrapClassLoader 1 3922 com.taobao.arthas.agent.ArthasClassloader 1 2238 jdk.internal.loader.ClassLoaders$AppClassLoader 1 1198 jdk.internal.loader.ClassLoaders$PlatformClassLoader 1 157 jdk.internal.reflect.DelegatingClassLoader 47 47 Affect(row-cnt:6) cost in 7 ms.
|
classloader 命令将 JVM 中所有的classloader的信息统计出来,并可以展示继承树,urls等。
重新编译
选择SpringBoot的classLoader重新编译。
1
| mc --classLoaderClass=org.springframework.boot.loader.LaunchedURLClassLoader ./Demo1Application.java
|
1 2 3 4 5 6 7
| Memory compiler output: /Users/yhan219/IdeaProjects/demo1/com/example/demo/Demo1Application.class Affect(row-cnt:1) cost in 280 ms. [arthas@28783]$ retransform com/example/demo/Demo1Application.class retransform success, size: 1, classes: com.example.demo.Demo1Application
|
生成class文件到当前目录的com/example/demo/
目录下。
Memory Compiler/内存编译器,编译.java
文件生成.class
。
交换
1
| retransform com/example/demo/Demo1Application.class
|
1 2
| retransform success, size: 1, classes: com.example.demo.Demo1Application
|
结果
此时再次访问该接口地址即返回了bbb
。