JavaAgent(探针、代理)初体验

前言

在上文 SpringBoot 项目如何接入 SkyWalking 中介绍 SkyWalking 接入的项目时:启动项目的方式由 java -jar demo.jar 变成java -java -javaagent:agent.jar -jar demo.jar。那么 Agent 到底是什么样的存在,今天就入门 Java Agent。

Java Agent 简介

image.png

Java 代理 (agent) 是在你的 main 方法前的一个拦截器 (interceptor),也就是在main方法执行之前,执行 agent 的代码。

通常,java 代理只是一个特制的 jar 文件。它利用 JVM 提供的 Instrumentation API 来更改加载到 JVM 中的现有字节码。

通常在使用 Agent的时候我们需要定义两个方法:

  • premain – 将在 JVM 启动时使用 -javaagent 参数静态加载代理
  • agentmain – 使用 Java Attach API 将代理动态加载到 JVM 中

总结来说:Java Agent 本质上是一个Jar包(需要实现特定的方法),只是启动方式和普通Jar包有所不同。对于普通的Jar包,通过指定类的 main 函数进行启动,但是 Java Agent并不能单独启动,必须依附在一个Java应用程序运行。下面我们就看看如何实现一个极简的 demo。

开发 Agent

项目为 Idea 中初始化的 maven 项目:

  • Java 版本 11
  • Maven 版本 3.6.3

项目目录如下:

├── agent-demo.iml
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   ├── AgentDemo.java
    │   │   └── AgentMain.java
    │   └── resources
    │       └── META-INF
    │           └── MANIFEST.MF
    └── test
        └── java

复制代码

演示程序

此代码没有任何的逻辑,知识为了展示程序正常运行,所以只定义两行文字输出

public class AgentMain {
    public static void main(String[] args) {
        System.out.println("Agent main start");
        System.out.println("Agent main end");
    }
}
复制代码

编译后运行我们的程序,正常情况下运行我们的代码:

java  AgentMain
Agent main start
Agent main end
复制代码

定义 Agent

  • 自定义 Agent 必须要实现 premain 方法
  • ClassFileTransformer 可以查看 JVM 默认加载的类
public class AgentDemo {

    public static void premain(String agentArgs, Instrumentation inst) {
        System.out.println("agentArgs : " + agentArgs);
        inst.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;
        }
    }
}
复制代码

打包 Agent

新建 resource/META-INF/MANIFEST.MF :

Manifest-Version: 1.0
Implementation-Version: 0.0.1-SNAPSHOT
Premain-Class: AgentDemo
Can-Redefine-Classes: true
复制代码

使用 maven 打包程序,这里只关注 build 相关的配置即可:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <version>3.2.0</version>
            <configuration>
                <archive>
                    <manifest>
                        <addClasspath>true</addClasspath>
                    </manifest>
                    <manifestEntries>
                        <Premain-Class>AgentDemo</Premain-Class>
                        <Agent-Class>AgentDemo</Agent-Class>
                        <Can-Redefine-Classes>true</Can-Redefine-Classes>
                        <Can-Retransform-Classes>true</Can-Retransform-Classes>
                    </manifestEntries>
                </archive>
            </configuration>
        </plugin>
    </plugins>
</build>
复制代码

构建程序:

mvn package
复制代码

测试 agent

进入 maven 构建产物的目录 target/classes 执行 启动代码:

java  -javaagent:../agent-demo-0.0.1-SNAPSHOT.jar="Hello World" AgentMain
复制代码

具体的执行情况如下:

premain agentArgs : Hello World
premain load Class:sun/launcher/LauncherHelper
premain load Class:java/lang/WeakPairMap$Pair$Weak
premain load Class:java/lang/WeakPairMap$WeakRefPeer
premain load Class:java/lang/WeakPairMap$Pair$Weak$1
premain load Class:java/lang/StringCoding
premain load Class:sun/nio/cs/ISO_8859_1
premain load Class:sun/nio/cs/US_ASCII
premain load Class:java/lang/StringCoding$1
premain load Class:java/lang/ThreadLocal$ThreadLocalMap
premain load Class:java/lang/ThreadLocal$ThreadLocalMap$Entry
premain load Class:jdk/internal/misc/TerminatingThreadLocal
premain load Class:java/lang/StringCoding$Result
premain load Class:AgentMain
Agent main start
Agent main end
premain load Class:jdk/internal/misc/TerminatingThreadLocal$1
premain load Class:java/lang/Shutdown
premain load Class:java/lang/Shutdown$Lock
复制代码

可以看到除了正常的类加载输出,输出的内容中包含程序正常运行的输出以及传入的参数。

总结

可以看到 Agent 的引入可以在不侵入代码的情况下控制程序。实现与 AOP 类似的功能,但是比起 AOP 更加轻量,对程序更加友好。

除了上文中的 SkyWalking 中用到 Java Agent 技术。类似 Java 调式、热部署中都会用到此技术。只是在具体的使用中会可能会用到类似 Byte Buddy (JVM 运行时代码生成)等技术。

参考文章