文章目录
一、前言
日常开发中避免不了,修改了代码重新启动应用去验证问题,如果没有热部署,则需要每次修改完就去编译代码再启动,这样子的操作看似简单,
但很耗时,特别电脑配置不高,或者项目比较大的情况
二、热部署初识
热部署其实就是在代码运行时去加载我们动态现在修改过的代码到服务器上,诸如 SpringBoot的devtools插件,jrebel插件等等,都是热部署的插件
三、Java Instrumentation
自Jdk5开始,就引入了 Java Instrumentation,它可以通过 addTransformer 方法设置一个 ClassFileTransformer,可以在这个
ClassFileTransformer 实现类的转换
jdk5提供的Instrumentation 是静态的,基本思路就是在java程序启动前去加载一个代理(javaagent),这个javaagent是一个jar,然后需要编写
一个premain() 方法,然后记录在MANIFEST.MF中,在运行main()方法前,会先运行premain()方法里的逻辑
整个代理的过程基本是:先将代理类打成一个jar,然后在主程序中加上 -javaagent 的参数,参数的值是代理jar的全路径
四、Java Instrumentation 静态代码示例
4.1、编写permain()方法(示例):
import java.lang.instrument.Instrumentation;
/**
* @author LGY
* @date 2021/10/25 22:46
*/
public class TestAgent {
public static void premain(String agentArgs, Instrumentation instrumentation){
System.out.println("agent start");
System.out.println(agentArgs);
}
4.2、在MANIFEST.MF中指定premain()的路径
Manifest-Version: 1.0
Premain-Class: com.lgydojava.jvmdemo.agent.TestAgent
4.3、在maven中加入如下插件,在maven中指定Premain-Class的目的是:在maven将程序打成jar时,会替换掉MANIFEST.MF中的内容,所以这样要指定Premain-Class的值
<build>
<finalName>my-javaagent</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestEntries>
<Premain-Class>com.lgydojava.jvmdemo.agent.TestAgent</Premain-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
4.4、在主程序中加入 -javaagent 启动参数
4.5、启动主程序 main()方法
五、Java Agent 示例 —— attach的使用
Java Instrumentation 动态加载被修改的代码 —— attach
业内很出名的arthas 也是利用了attach的原理来实现的
5.1、修改pom文件
<dependencies>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>9.1</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-commons</artifactId>
<version>8.0</version>
</dependency>
</dependencies>
<build>
<finalName>my-attach-agent-project</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<archive>
<manifestEntries>
<Agent-Class>com.lgydojava.AgentMain</Agent-Class>
<!-- <Premain-Class>AgentMain</Premain-Class>-->
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<relocations>
<relocation>
<pattern>org.ow2.asm</pattern>
<shadedPattern>me.ya.agent.hidden.org.objectweb.asm</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
5.2、创建AgentMain类,实现在每个函数进入和结束时都打印一行日志,实现调用过程的追踪的效果
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.commons.AdviceAdapter;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.security.ProtectionDomain;
import static org.objectweb.asm.Opcodes.ASM7;
/**
* @author LGY
* @date 2021/10/26 23:03
*/
public class AgentMain {
public static class MyMethodVisitor extends AdviceAdapter {
protected MyMethodVisitor( MethodVisitor methodVisitor,int access, String name, String desc) {
super(ASM7,methodVisitor,access, name, desc);
}
@Override
protected void onMethodEnter(){
// mv.
mv.visitIntInsn(BIPUSH,50);
mv.visitInsn(IRETURN);
}
}
public static class MyClassVisitor extends ClassVisitor {
public MyClassVisitor(ClassVisitor classVisitor) {
super(ASM7, classVisitor);
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions){
MethodVisitor mv = super.visitMethod(access,name,descriptor,signature,exceptions);
System.out.println(name);
if ("foo".equalsIgnoreCase(name)) {
return new MyMethodVisitor(mv,access,name,descriptor);
}
return mv;
}
}
public static class MyClassFileTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
System.out.println("className:"+className);
// if (!"com.lgydojava.jvmdemo.MyTestMain".equalsIgnoreCase(className)) {
// return classfileBuffer;
// }
ClassReader cr = new ClassReader(classfileBuffer);
ClassWriter cw = new ClassWriter(cr,ClassWriter.COMPUTE_FRAMES);
ClassVisitor cv = new MyClassVisitor(cw);
cr.accept(cv,ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
return cw.toByteArray();
}
}
public static void agentmain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException {
System.out.println("agentmain called");
inst.addTransformer(new MyClassFileTransformer(),true);
Class classes[] = inst.getAllLoadedClasses();
for (int i = 0; i < classes.length; i++) {
// inst.retransformClasses(classes[i]);
Class aClass = classes[i];
String name = aClass.getName();
System.out.println(name);
if (name.equals("com.lgydojava.jvmdemo.MyTestMain")) {
System.out.println("Reloading: " + name);
inst.retransformClasses(aClass);
break;
}
}
}
}
5.5、 在MANIFEST.MF中指定Agent类
Manifest-Version: 1.0
Can-Redefine-Classes: true
Agent-Class: com.lgydojava.AgentMain
Can-Retransform-Classes: true
Permissions: all-permissions
5.6、 将程序打成jar包
5.7、 创建 attach类 MyAttachMain.java,PID是运行程序的ID,window可以在任务管理器中查看,loadAgent是agent.jar的全路径
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;
import java.io.IOException;
/**
* @author LGY
* @date 2021/10/26 23:31
*/
public class MyAttachMain {
public static void main(String[] args) throws IOException, AttachNotSupportedException {
VirtualMachine vm = VirtualMachine.attach(PID);
try {
vm.loadAgent("E:\\Idea_workspace\\jvmdemo\\target\\my-attach-agent-project.jar");
} catch (Exception e) {
e.printStackTrace();
}finally {
vm.detach();
}
}
}
5.8、编写MyTestMain 主程序,并运行起来
import java.util.concurrent.TimeUnit;
/**
* @author LGY
* @date 2021/10/27 22:39
*/
public class MyTestMain {
public static void main(String[] args) throws InterruptedException {
while (true){
System.out.println(foo());
TimeUnit.SECONDS.sleep(3);
}
}
private static int foo() {
//将原来返回100的,改为返回50
return 100;
}
}
5.9、运行验证
输入MyTestMain 的PID,然后运行 MyAttachMain ,查看结果,可以看到原来是返回100的,当我们修改后,就返回了50
- 1