javaagent

  1. Javaagent 能干什么
  2. Javaagent 原理
  3. premain 用法
    1. 依据惯例使用maven构建, 需要指定premain-class
    2. 然后是assembly插件的描述
  4. 字节码操作结果

其实 Javagent 一点也不神秘,它就是一个 jar 包,只不过启动方式不是跟普通 jar 包不一样,并不是类的静态 main 函数。agent 的 jar 包也不能单独启动,必须依附于 JVM 进程才可以。

Javaagent 是 JDK5 引入的,开发者可以通过修改字节码实现动态修改类代码。Javaagent 是运行在 main 方法之前的拦截器,它内定的方法名叫 premain ,也就是说先执行 premain 方法然后再执行 main 方法。我们也可以很方便地在运行过程中动态地设置加载代理类,以达到 instrumentation 的目的。 Instrumentation 的最大作用就是类定义动态改变和操作。

JDK5中只能通过命令行参数在启动JVM时指定javaagent参数来设置代理类,而JDK6中已经不仅限于在启动JVM时通过配置参数来设置代理类,JDK6中通过 Java Tool API 中的 attach 方式,我们也可以很方便地在运行过程中动态地设置加载代理类,以达到 instrumentation 的目的。
Instrumentation 的最大作用,就是类定义动态改变和操作。

Javaagent 能干什么

  • 可以在加载 class 文件之前做拦截,对字节码做修改(premain),可以实现 AOP、修改源代码等功能。

  • 可以在运行期对已加载类的字节码做变更(agentmain),可以实现热部署等功能。

  • 还有其他一些小众的功能

    获取所有已经加载过的类
    
    获取某个对象的大小

Javaagent 原理

下面是一个典型的 Java 源文件到运行的过程,Javaagent 发挥作用的是从 class 文件到类加载的这个中间过程,可以在加载 class 文件之前做拦截,对字节码做修改,实现任何想要的效果

agent

premain 用法

1
2
3
4
5
6
7
8
9
10
11
12
13
public class AgentMain {

public static void premain(String agentArgument, Instrumentation instrumentation) throws Exception {
instrumentation.addTransformer(new ClassFileTransformer() {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
// 打印加载的类
System.out.println("transform: " + className);
return null;
}
}, true);
}
}

最简单的一个例子,计算某个方法执行需要的时间,不修改源代码的方式,使用Instrumentation 代理来实现这个功能,给力的说,这种方式相当于在JVM级别做了AOP支持,这样我们可以在不修改应用程序的基础上就做到了AOP。

  1. 创建一个 ClassFileTransformer 接口的实现类 MyTransformer

    实现 ClassFileTransformer 这个接口的目的就是在class被装载到JVM之前将class字节码转换掉,从而达到动态注入代码的目的。那么首先要了解MonitorTransformer 这个类的目的,就是对想要修改的类做一次转换,这个用到了javassist对字节码进行修改,可以暂时不用关心jaavssist的原理,用ASM同样可以修改字节码,只不过比较麻烦些。

    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
    package com.shanhy.demo.agent;

    import java.lang.instrument.ClassFileTransformer;
    import java.lang.instrument.IllegalClassFormatException;
    import java.security.ProtectionDomain;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;

    import javassist.ClassPool;
    import javassist.CtClass;
    import javassist.CtMethod;
    import javassist.CtNewMethod;

    /**
    * 检测方法的执行时间
    *
    */
    public class MyTransformer implements ClassFileTransformer {

    final static String prefix = "\nlong startTime = System.currentTimeMillis();\n";
    final static String postfix = "\nlong endTime = System.currentTimeMillis();\n";

    // 被处理的方法列表
    final static Map<String, List<String>> methodMap = new HashMap<String, List<String>>();

    public MyTransformer() {
    add("com.shanhy.demo.TimeTest.sayHello");
    add("com.shanhy.demo.TimeTest.sayHello2");
    }

    private void add(String methodString) {
    String className = methodString.substring(0, methodString.lastIndexOf("."));
    String methodName = methodString.substring(methodString.lastIndexOf(".") + 1);
    List<String> list = methodMap.get(className);
    if (list == null) {
    list = new ArrayList<String>();
    methodMap.put(className, list);
    }
    list.add(methodName);
    }

    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
    ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
    className = className.replace("/", ".");
    if (methodMap.containsKey(className)) {// 判断加载的class的包路径是不是需要监控的类
    CtClass ctclass = null;
    try {
    ctclass = ClassPool.getDefault().get(className);// 使用全称,用于取得字节码类<使用javassist>
    for (String methodName : methodMap.get(className)) {
    String outputStr = "\nSystem.out.println(\"this method " + methodName
    + " cost:\" +(endTime - startTime) +\"ms.\");";

    CtMethod ctmethod = ctclass.getDeclaredMethod(methodName);// 得到这方法实例
    String newMethodName = methodName + "$old";// 新定义一个方法叫做比如sayHello$old
    ctmethod.setName(newMethodName);// 将原来的方法名字修改

    // 创建新的方法,复制原来的方法,名字为原来的名字
    CtMethod newMethod = CtNewMethod.copy(ctmethod, methodName, ctclass, null);

    // 构建新的方法体
    StringBuilder bodyStr = new StringBuilder();
    bodyStr.append("{");
    bodyStr.append(prefix);
    bodyStr.append(newMethodName + "($$);\n");// 调用原有代码,类似于method();($$)表示所有的参数
    bodyStr.append(postfix);
    bodyStr.append(outputStr);
    bodyStr.append("}");

    newMethod.setBody(bodyStr.toString());// 替换新方法
    ctclass.addMethod(newMethod);// 增加新方法
    }
    return ctclass.toBytecode();
    } catch (Exception e) {
    System.out.println(e.getMessage());
    e.printStackTrace();
    }
    }
    return null;
    }
    }

依据惯例使用maven构建, 需要指定premain-class

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
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptors>
<descriptor>descriptor.xml</descriptor>
</descriptors>
<appendAssemblyId>false</appendAssemblyId>
<archive>
<manifestEntries>
<premain-class>u.lol.awacs.Boot</premain-class>
</manifestEntries>
</archive>
<finalName>hello-agent</finalName>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

然后是assembly插件的描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="ISO-8859-1"?>
<assembly>
<id>single</id>
<formats>
<format>jar</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<dependencySets>
<dependencySet>
<unpack>true</unpack>
<scope>runtime</scope>
</dependencySet>
</dependencySets>
</assembly>

字节码操作结果

Before transformed:

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
public java.lang.String hello1(java.lang.String);
descriptor: (Ljava/lang/String;)Ljava/lang/String;
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=2
0: new #2 // class java/util/Random
3: dup
4: invokespecial #3 // Method java/util/Random."<init>":()V
7: astore_2
8: aload_2
9: invokevirtual #19 // Method java/util/Random.nextInt:()I
12: iconst_2
13: irem
14: ifne 20
17: ldc #23 // String hello, world
19: areturn
20: new #10 // class java/lang/RuntimeException
23: dup
24: invokespecial #21 // Method java/lang/RuntimeException."<init>":()V
27: athrow
LineNumberTable:
line 76: 0
line 77: 8
line 78: 17
line 79: 20
LocalVariableTable:
Start Length Slot Name Signature
0 28 0 this Lio/awacs/demo/TestController;
0 28 1 s Ljava/lang/String;
8 20 2 r Ljava/util/Random;
StackMapTable: number_of_entries = 1
frame_type = 252 /* append */
offset_delta = 20
locals = [ class java/util/Random ]
RuntimeVisibleAnnotations:
0: #79(#80=[s#95])

After transformed:

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
public java.lang.String hello1(java.lang.String);
descriptor: (Ljava/lang/String;)Ljava/lang/String;
flags: ACC_PUBLIC
Code:
stack=4, locals=4, args_size=2
0: invokestatic #38 // Method io/awacs/plugin/stacktrace/CallStack.initStack:()V
3: ldc #40 // String io.awacs.demo.TestController
5: ldc #123 // String hello1
7: invokestatic #45 // Method io/awacs/plugin/stacktrace/CallStack.methodEnter:(Ljava/lang/String;Ljava/lang/String;)V
10: new #19 // class java/util/Random
13: dup
14: invokespecial #20 // Method java/util/Random."<init>":()V
17: astore_2
18: aload_2
19: invokevirtual #111 // Method java/util/Random.nextInt:()I
22: iconst_2
23: irem
24: ifne 36
27: ldc #125 // String hello, world
29: invokestatic #77 // Method io/awacs/plugin/stacktrace/CallStack.methodQuit:()V
32: invokestatic #80 // Method io/awacs/plugin/stacktrace/CallStack.incrAccess:()V
35: areturn
36: new #33 // class java/lang/RuntimeException
39: dup
40: invokespecial #114 // Method java/lang/RuntimeException."<init>":()V
43: invokestatic #77 // Method io/awacs/plugin/stacktrace/CallStack.methodQuit:()V
46: invokestatic #80 // Method io/awacs/plugin/stacktrace/CallStack.incrAccess:()V
49: athrow
50: astore_2
51: aload_2
52: invokestatic #101 // Method io/awacs/plugin/stacktrace/CallStack.incrFailure:(Ljava/lang/Throwable;)V
55: aload_2
56: athrow
Exception table:
from to target type
10 35 50 Class java/lang/RuntimeException
36 49 50 Class java/lang/RuntimeException
LocalVariableTable:
Start Length Slot Name Signature
10 40 0 this Lio/awacs/demo/TestController;
10 40 1 s Ljava/lang/String;
18 32 2 r Ljava/util/Random;
LineNumberTable:
line 76: 10
line 77: 18
line 78: 27
line 79: 36
StackMapTable: number_of_entries = 2
frame_type = 252 /* append */
offset_delta = 36
locals = [ class java/util/Random ]
frame_type = 255 /* full_frame */
offset_delta = 13
locals = [ class io/awacs/demo/TestController, class java/lang/String ]
stack = [ class java/lang/RuntimeException ]
RuntimeVisibleAnnotations:
0: #7(#8=[s#122])

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 wshten@gmail.com

文章标题:javaagent

本文作者:KevinTen

发布时间:2019-09-09, 00:00:00

最后更新:2019-09-09, 11:55:13

原始链接:http://github.com/kevinten10/2019/09/09/Java/bytecode/Java-Bytecode-Javaagent/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录
×

喜欢就点赞,疼爱就打赏

csdn zhihu github