javaAgent初探
# agent 初探
- premain 命令式 在main方法之前执行
- agentmain 以Attach的方式载入,在Java程序启动后执行,1.6提供
public interface Instrumentation {
/**
* 添加Transformer(转换器)
* ClassFileTransformer类是一个接口,通常用户只需实现这个接口的 byte[] transform()方法即可;
* transform这个方法会返回一个已经转换过的对象的byte[]数组
* @param transformer 拦截器
* @return canRetransform 是否能重新转换
*/
void addTransformer(ClassFileTransformer transformer, boolean canRetransform);
/**
* 重新触发类加载,
* 该方法可以修改方法体、常量池和属性值,但不能新增、删除、重命名属性或方法,也不能修改方法的签名
* @param classes Class对象
* @throws UnmodifiableClassException 异常
*/
void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;
/**
* 直接替换类的定义
* 重新转换某个对象,并已一个新的class格式,进行转化。
* 该方法可以修改方法体、常量池和属性值,但不能新增、删除、重命名属性或方法,也不能修改方法的签名
* @param definitions ClassDefinition对象[Class定义对象]
* @throws ClassNotFoundException,UnmodifiableClassException 异常
*/
void redefineClasses(ClassDefinition... definitions)throws ClassNotFoundException, UnmodifiableClassException;
/**
* 获取当前被JVM加载的所有类对象
* @return Class[] class数组
*/
Class[] getAllLoadedClasses();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# premain方法介绍
public class AgentWithTransFormer {
public static void premain(String agentOps, Instrumentation instrumentation) {
System.out.println("premian start 。。");
System.out.println("agentOps = " + agentOps);
instrumentation.addTransformer(new DefineTransformer(),true);
}
/**
*
*/
static class DefineTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
System.out.println("premain load class :" + className);
return classfileBuffer;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
premain方法会拦截大部分类(不包含有些系统类),实现类DefineTransformer
需要实现transform
方法,比如我们可以拦截某个类,将某个类的class文件修改,可以整个文件替换或者利用javassist自定义添加或者删除方法。
# premain实现更改class文件
被代理类
/**
* @author ggBall
* @version 1.0.0
* @ClassName AgentTest.java
* @Description
* 测试命令 需要在 D:\project\idea\ggball_test\agent_demo\agent_test\src\main\java 目录下执行
* javac -encoding UTF-8 .\com\zhu\AgentMainTest.java
* 在項目所在java目录下运行
* cd D:\project\idea\ggball_test\agent_demo\agent_test\src\main\java
* java -javaagent:D:\project\idea\ggball_test\agent_demo\agent\target\java_agent.jar com.zhu.AgentMainTest
* @createTime 2022年09月27日 19:00:00
*/
public class AgentMainTest {
public static void main(String[] args) throws Exception {
Thread.sleep(1000);
Console.print();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
console类
public class Console {
public static void print() {
System.out.println("hello!");
}
}
1
2
3
4
5
6
7
2
3
4
5
6
7
agent类
public class ReplaceFileAgent {
public static void premain(String agentOps, Instrumentation instrumentation) {
System.out.println("ReplaceFileAgent premian start 。。");
System.out.println("agentOps = " + agentOps);
instrumentation.addTransformer(new ClassFileFormer(),true);
}
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
ClassFileTransformer
public class ClassFileFormer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if (className.equals("com/zhu/Console")) {
String root = System.getProperty("user.dir")+"\\agent_demo";
String classPath = root + "\\agent_test\\src\\main\\java\\com\\zhu\\Console.class";
byte[] bytes = FileUtil.readBytes(classPath);
return bytes;
}
return classfileBuffer;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
主要作用就是修改Console.class文件 在 目录 D:\project\idea\ggball_test\agent_demo\agent_test\src\main\java 下执行
- 先修改print 为 System.out.println("hello agent!");
- 然后编译 C:\Users\16678.jdks\corretto-1.8.0_292\bin\javac -encoding UTF-8 .\com\zhu\Console.java (注意点: 编译的jdk版本需要与运行时的jdk)
- 最后 将 print方法改回 System.out.println("hello!");
# 测试过程:
首先需要在代理项目resources目录下 添加META-INF 目录 再添加
MANIFEST.MF
,MANIFREST.MF
文件(我自己可能打包方式有问题,MANIFEST.MF文件内容不完全,导致只能在jar里手动改)
填写内容:
Manifest-Version: 1.0
Premain-Class: com.zhu.ReplaceFileAgent
Built-By: 16678
Agent-Class: com.zhu.ReplaceFileAgent
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Class-Path: javassist-3.28.0-GA.jar hutool-all-5.8.7.jar
Created-By: Apache Maven 3.8.1
Build-Jdk: 1.8.0_292
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
- Premain-Class :包含 premain 方法的类(类的全路径名)
- Agent-Class :包含 agentmain 方法的类(类的全路径名)
- Boot-Class-Path :设置引导类加载器搜索的路径列表。查找类的特定于平台的机制失败后,引导类加载器会搜索这些路径。按列出的顺序搜索路径。列表中的路径由一个或多个空格分开。路径使用分层 URI 的路径组件语法。如果该路径以斜杠字符(“/”)开头,则为绝对路径,否则为相对路径。相对路径根据代理 JAR 文件的绝对路径解析。忽略格式不正确的路径和不存在的路径。如果代理是在 VM 启动之后某一时刻启动的,则忽略不表示 JAR 文件的路径。(可选)
- Can-Redefine-Classes :true表示能重定义此代理所需的类,默认值为 false(可选)
- Can-Retransform-Classes :true 表示能重转换此代理所需的类,默认值为 false (可选)
- Can-Set-Native-Method-Prefix: true表示能设置此代理所需的本机方法前缀,默认值为 false(可选) 即在该文件中主要定义了程序运行相关的配置信息,程序运行前会先检测该文件中的配置项。
或者添加maven插件
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<archive>
<!--自动添加META-INF/MANIFEST.MF -->
<manifest>
<addClasspath>true</addClasspath>
</manifest>
<manifestEntries>
<Premain-Class>com.zhu.AgentWithTimeTransFormer</Premain-Class>
<Agent-Class>com.zhu.AgentWithTimeTransFormer</Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
</configuration>
</plugin>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
idea添加vm option参数 类似于
java -javaagent:D:\project\idea\ggball_test\agent_demo\agent\target\java_agent.jar com.zhu.AgentMainTest
1
如果不被代理情况下 只会打印 hello,由于console class文件被替换 所以执行结果
上次更新: 2022/10/23, 22:52:26