8.1 JavaAgent
Java Agent 是一种强大的技术,在运行时动态修改已加载类的字节码,为应用程序注入额外的功能和行为。
JDK 1.5 支持静态 Instrumentation,基本的思路是在 JVM 启动的时候添加一个代理(javaagent),每个代理是一个 jar 包,其 MANIFEST.MF 文件里指定了代理类,这个代理类包含一个 premain 方法。JVM 在类加载时候会先执行代理类的 premain 方法,再执行 Java 程序本身的 main 方法,这就是 premain 名字的来源。在 premain 方法中可以对加载前的 class 文件进行修改。
8.1.1 应用场景:
代码注入:通过 Java Agent 技术,我们可以在运行时将自定义的字节码注入到已加载的类中。这样可以实现 AOP 编程思想,将通用的逻辑代码动态地注入到目标类中,简化代码的编写和维护。
性能监控:Java Agent 可以用于实时监控应用程序的性能指标,如方法的执行时间、方法的调用次数等。通过在方法的前后插入字节码,我们可以收集这些信息,并进行实时监控、统计和分析,帮助优化和调优应用程序的性能。
调试工具:Java Agent 可以用作调试工具的基础。我们可以通过动态修改字节码,将额外的调试信息插入到目标类中,例如打印方法的参数、返回值等。这样,在调试过程中可以更加方便地获取和分析这些信息,帮助快速定位问题。
安全检查:Java Agent 还可以用于实现安全检查机制。通过对正在加载的类进行拦截和检查,我们可以在运行时进行代码审计、防止恶意代码注入等,提高应用程序的安全性。
8.1.2 启动方式
启动挂载(Agent):
所有的类在加载时都会执行transform,我们在此方法中通过 Instrumentation 增强目标类。
随着JVM启动一起启动(java -javaagent:(jar包名) (方法类名))
premain(): 实现premain方法,并且添加一个自定义transformer,把操作放在自定义的transformer内,该transformer要实现ClassFileTransformer.transform() 主程序运行时:加载每个类前都会进入transform(),可以获取到它的加载器、类名、字节码buffer等
动态挂载(Attach):
动态挂载之后,类加载时都会执行transform,我们在此方法中通过 Instrumentation 增强目标类。
动态挂载前 已经加载的类,都未经历增强的处理。 可通过调用retransformClasses方法,让已加载的类重新加载, 重新加载时也会执行transform,我们在此方法中增强目标类。
在已经运行的JVM进程中,动态的插入。通过attach API调用,在sandbox中执行./sandbox -p pid(目标 jvm 进程 id)是使用的 attach api 方式进行 agent 的挂载
agentmain():对于agentmain也是一样的流程,但多一步inst.retransformClasses()的操作让JVM重新加载修改过的类的字节码,否则修改不会生效 使用com.sun.tools.attach.VirtualMachine进行动态挂载Agent:
private void attachAgentToTargetJVM() throws Exception {
List<VirtualMachineDescriptor> virtualMachineDescriptors = VirtualMachine.list();
VirtualMachineDescriptor targetVM = null;
for (VirtualMachineDescriptor descriptor : virtualMachineDescriptors) {
if (descriptor.id().equals(configure.getPid())) {
targetVM = descriptor;
break;
}
}
if (targetVM == null) {
throw new IllegalArgumentException("could not find the target jvm by process id:" + configure.getPid());
}
VirtualMachine virtualMachine = null;
try {
virtualMachine = VirtualMachine.attach(targetVM);
virtualMachine.loadAgent("{agent}", "{params}");
} catch (Exception e) {
if (virtualMachine != null) {
virtualMachine.detach();
}
}
}
它的 addTransformer 给 Instrumentation 注册一个 transformer,transformer 是 ClassFileTransformer 接口的实例,这个接口就只有一个 transform 方法,调用 addTransformer 设置 transformer 以后,后续JVM 加载所有类之前都会被这个 transform 方法拦截,这个方法接收原类文件的字节数组,返回转换过的字节数组,在这个方法中可以做任意的类文件改写。
8.1.3 JavaAgent使用demo
使用premain的方式进行javaAgent的启动(不详细展示对java类的增强demo)
首先在一个maven项目中创建我们的核心demo-AgentDemo
package com.xhz.demo;
import java.lang.instrument.Instrumentation;
/**
* @version V1.0
* @Author : xiehuizhi
* @create 2023/12/20 14:33
*/
public class AgentDemo {
public static void premain(String agentArgs, Instrumentation instrumentation){
System.out.println(agentArgs+"============尊贵的xxxx===============");
}
public static void premain(String agentArgs){
System.out.println(agentArgs+"============尊贵的xxxx================");
}
}
创建完成之后可以手动在resources中创建MANIFEST.MF文件(也可以在pom中配置)
Manifest-Version: 1.0
Premain-Class: com.xhz.demo.AgentDemo
Can-Redefine-Classes: true
Can-Retransform-Classes: true
pom配置:
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.2</version>
<configuration>
<archive>
<manifestEntries>
<Premain-Class>club.throwable.permain.PermainAgent</Premain-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
然后要实现agent的功能,需要将你的项目打成jar包
打包命令:mvn clean package
然后新建一个可以启动的Main类
package Utils;
/**
* @version V1.0
* @Author : xiehuizhi
* @create 2023/12/20 14:29
*/
public class Main {
public static void main(String[] args) {
System.out.println("流量回放");
}
}
在springboot启动项增加启动参数
8.2 Sandbox启动与Agent挂载的流程
sandbox-spy:这个包里的代码是一系列的模板代码,后期Sandbox会通过ASM框架将这些代码转化成字节码增加到业务代码的字节码当中,实现对业务代码的增强功能
但spy类为什么要被BootstrapClassLoader加载呢?我们不仅要增强业务代码,也要增强JDK里面的代码,将spy添加到BootstrapClassLoader的类路径,只要不破坏双亲委托机制,都能够被spy类增强,这是一种比较保险的做法。
8.3 JVM-SandBox实现字节码增强
一般的Spring AOP:实现业务切面,针对于JAVA后端应用也需要AOP
JVM-Sandbox的设计目的是实现一种在不重启、不侵入目标JVM应用情况下的AOP解决方案。
Jvm-SandBox字节码增强流程梳理:
8.3.1 增强过程
8.3.2 实现字节码增强逻辑
通过AsmMethods接口获取Sandbox自定义Spy类完成字节码增强
spy增强的静态方法
JavaAgent的本质就是字节码增强,这章主要介绍了Sandbox字节码增强功能的核心逻辑,包括增强逻辑、SandboxClassFileTransformer类形变器、Spy增强的静态方法。