阿尔萨斯 原理探究 premain实践

前言

这篇主要来记录一下实现Instrumentation的方式–premain。premain是在 Java SE 5中新引入的 ,开发者只能在 premain 当中施展想象力,所作的 Instrumentation 也仅限与 main 函数执行前。

preMain编写

随便创建一个类,在类中定义premain的方法:

1
2
3
4
5
6
7
public class IPreMain {
public static void premain(String agentOps, Instrumentation inst) throws Exception{
System.out.println("preAgent begin...");
System.out.println("add transformer...");
inst.addTransformer(new ITransformer());
}
}

这个方法的大体意思为:在执行虚拟机创建后,大多数的类都还没被加载进来,这个时候我们给Instrumentation添加了一个转换器,Instrumentation会在类被加载的过程中使用该转换器。

ITransformer 编写

这个转换器,因为在premain中被添加到了Instrumentation中,它在每个类被加载的时候对类文件会有一个转换的过程。

来看一下ITransformer的实现:

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
public class ITransformer implements ClassFileTransformer {

@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer)
throws IllegalClassFormatException {
className = className.replace("/", ".");

// 对agent.Main类中所有申明的方法进行加强,这里特定指明了agent.Main类,不然所有将要被加载的类(包括系统类如Thread)都会经过这个转换器。
if (className.equals("agent.Main")) {
try {
// 这里使用了Java assist框架对字节码进行修改
// 获取了目标类
CtClass ctClass = ClassPool.getDefault().get(className);
// 获取目标类中所有申明的方法
CtMethod[] ctMethods = ctClass.getDeclaredMethods();
// 对方法进行加强
for (CtMethod method : ctMethods) {
method.insertBefore("System.out.println(\"method begin time:\"+System.currentTimeMillis());");
method.insertAfter("System.out.println(\"method end time:\"+System.currentTimeMillis());");
}
//把修改后的类字节码返回
return ctClass.toBytecode();
} catch (Exception e) {
System.out.println(e);
}
}
return new byte[0];
}
}

Main 编写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Main {

public static void main( String[] args ) throws InterruptedException {
Main demo = new Main();
while (true) {
demo.sayHello();
demo.sayBye();
Thread.sleep(1000);
}
}

public void sayHello() {
System.out.println("Hello World!");
}

public void sayBye() {
System.out.println("Bye Bye");
}

}

pom.xml

需要把premain对应的类打成Jar包,这里通过maven来实现,不清楚如何打包可以百度一下。

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
<build>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.7.1</version>
</plugin>
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.0.0</version>
</plugin>
<!-- 打包agent -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>utf-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Premain-Class>agent.agentDomain.IPreMain</Premain-Class>
</manifestEntries>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>


</plugins>
</pluginManagement>

<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>utf-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Premain-Class>agent.agentDomain.IPreMain</Premain-Class>
<!--
<Agent-Class>MyAgent.Agent.MyAgentMain</Agent-Class>
-->
<Can-Retransform-Classes>true</Can-Retransform-Classes>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
</manifestEntries>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>

</build>

项目结构和启动配置

在启动Main函数指定生成的jar:

1
-javaagent:F:\agentTest\target\agentTest-1.0-SNAPSHOT.jar

你想输入的替代文字

调用结果:

你想输入的替代文字

每个方法被加上了启动时间与结束时间