JavaAgent探针技术第一篇:在主程序运行之前的代理程序

一、Java Agent是什么?

通过操作Instrumentation的api就可以实现不重启服务对单个类进行简单的修改。Instrumentation是一个interface,它的实现类InstrumentationImpl只有一个private的构造方法。

怎么拿到这个对象呢?下面是Instrumentation类的一段注释说明:

There are two ways to botain an instance of the Instrumentation interface:

  1. When a JVM is launched in a way that indicates an agent class. In that case an Instrumentation instance is passed to the parent method of the agent class.
  2. When a JVM provides a mechanism to start agents sometime after the JVM is
    launched . In that case an Instrumentation instance is passed yo the agentmain method of the agent code.
    These mechainsms are described in the package specification.
    Once an agent acquires an Instrumentation instance, the agent may call methods on the instance at any time.

这里对上面的英文原文的注释做一个简单的理解:一共有两种方式拿到Instrumentation对象:

  1. JVM启动时指定agent,Instrumentation对象会通过agent的premain方法传递过去。
  2. JVM启动后通过JVM提供的机制加载agent,Instrumentation对象会通过agent的agentmain方法传递过去。

本文重点介绍,在主程序运行之前启动agent的基本用法。

二、如何使用Java Agent技术?

2.1 简单示例

新起一个简单的Java工程AgentDemo,新建包路径com.alibaba.ei.agent,新增一个类AgentDemo,编写代码如下:

public class AgentDemo {

    private static Instrumentation instrumentation;

    /**
     * 该方法在main方法之前运行,与main方法运行在同一个JVM中
     *
     * @param agentArgs 是 premain 函数得到的程序参数,随同 “– javaagent”一起传入。与 main 函数不同的是,这个参数是一个字符串而不是一个字符串数组,如果程序参数有多个,程序将自行解析这个字符串。
     * @param inst      是一个 java.lang.instrument.Instrumentation 的实例,由 JVM 自动传入。java.lang.instrument.Instrumentation 是 instrument 包中定义的一个接口,也是这个包的核心部分,集中了其中几乎所有的功能方法,例如类定义的转换和操作等等。
     */
    public static void premain(String agentArgs, Instrumentation inst) {
        System.out.println("=========premain方法执行1========");
        System.out.println(agentArgs);

        instrumentation = inst;

        SimpleClassTransformer transformer = new SimpleClassTransformer();
        inst.addTransformer(transformer);
    }

    /**
     * 如果不存在 premain(String agentArgs, Instrumentation inst)
     * 则会执行 premain(String agentArgs)
     *
     * @param agentArgs
     * @author xifeijian
     * @create 2018年4月18日
     */
    public static void premain(String agentArgs) {
        System.out.println("=========premain方法执行2========");
        System.out.println(agentArgs);
    }

}

在这个 premain 函数中,开发者可以进行对类的各种操作。

  1. agentArgs 是 premain 函数得到的程序参数,随同 “– javaagent”一起传入。与 main 函数不同的是,这个参数是一个字符串而不是一个字符串数组,如果程序参数有多个,程序将自行解析这个字符串。
  2. Inst 是一个 java.lang.instrument.Instrumentation 的实例,由 JVM 自动传入。java.lang.instrument.Instrumentation 是 instrument 包中定义的一个接口,它是agent技术主要使用的API,我们可以使用它来改变和重新定义类的行为。

编写转换类SimpleClassTransformer:

/**
 * @Description
 * @Author louxiujun
 * @Date 2020/2/4 11:49
 **/
public class SimpleClassTransformer implements ClassFileTransformer {

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

        if (className.endsWith("sun/net/www/protocol/http/HttpURLConnection")) {
            ClassPool classPool = ClassPool.getDefault();
            CtClass clazz = null;
            try {
                clazz = classPool.get("sun.net.www.protocol.http.HttpURLConnection");

                CtConstructor[] cs = clazz.getConstructors();
                for (CtConstructor constructor : cs) {
                    // 在构造函数结束的位置插入如下的内容
                    constructor.insertAfter("System.out.println(this.getURL());");
                }

                byte[] byteCode = clazz.toBytecode();

                // 将类移出
                clazz.detach();

                return byteCode;
            } catch (NotFoundException | CannotCompileException | IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}

写完这个类后,我们还需要做一步配置工作。
1)如果项目是普通Java项目的话,则在 src 目录下生成 META-INF/MANIFEST.MF 文件。
切换到工程设置面板,切换到Artifacts面板,点击按钮,新增一个JAR,选择From modules with dependencies...选项,如下图所示:

20191222203456.jpg

Main Class一栏留空不填,下面的单选按钮选择copy to the output directory and link via manifest选项,其他的按照默认生成的走就可以,完成之后点击OK按钮。

20191222203522.jpg

完成之后面板显示如下,点击OK按钮完成配置。

20191222203534.jpg

然后编辑META-INF/MANIFEST.MF,MANIFEST.MF文件用于描述Jar包的信息,例如指定入口函数等。我们需要在该文件中加入如下配置,指定我们编写的含有premain方法类的全路径,然后将agent类打成Jar包。

1 Manifest-Version: 1.0
2 Premain-Class: com.alibaba.ei.agent.AgentDemo
3
4

要特别注意的是:最后一行是空行,还有就是Premain-Class冒号后面有个空格。

接下来选择菜单栏中的Build下拉中的Build Artifacts..选项,

20191222203943.jpg

然后我们在弹出的快捷菜单中选择Action为Build,从而将整个工程打包代码为 javaagent.jar

20191222203957.jpg

此时,会在工程目录的out文件夹成生成一个jar文件,复制下这个jar文件的绝对路径备用。本文的jar文件所在目录为/Users/XXX/Documents/code/javaagent/agentDemo/out/artifacts/agentDemo_jar/agentDemo.jar。当前整个工程的结构图如下图所示:

image.png

2)如果你是使用Maven来构建的项目,则在pom.xml文件中添加如下的内容,Maven帮助生成MANIFEST.MF文件。

 <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                 <version>2.3.1</version>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                        </manifest>
                        <manifestEntries>
                            <!--方式1:在主程序运行之前的代理程序-->
                            <Premain-Class>com.alibaba.ei.agent.AgentDemo</Premain-Class>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>

再添加一下pom依赖:

<javassist.version>3.20.0-GA</javassist.version>

<dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>${javassist.version}</version>
        </dependency>

执行mvn clean install指令,则会在当前工程目录下生成一个target文件夹,里面有一个agentDemo-1.0-SNAPSHOT.jar的jar包,拷贝起文件路径备用即可,感兴趣的可以解压看一下里面是否有一个MANIFEST.MF文件。

接着我们再创建一个新的工程agentTest,新建包路径com.alibaba.ei.agent,新建文件AgentTest.java

public class AgentTest {

    public static void main(String[] args) {
        System.out.println("===========执行main方法=============");
        HttpUtil.fetch("http://www.baidu.com");
        HttpUtil.fetch("http://www.163.com");
    }
}

这里的程序就是我们要代理的程序,我们在主程序的VM options添加上启动参数

-javaagent: 你的路径/test-1.0-SNAPSHOT.jar=hello

其中hello为上文中传入permain方法的agentArgs参数。运行我们的主程序

编辑应用的JVM启动参数如下


image.png

点击运行按钮后输出如下:

=========premain方法执行1========
 Hello
=========premain方法执行2========
World
===========执行main方法=============
http://www.baidu.com
Content size:2283
http://www.163.com
Content size:488569

我们也可以将项目打包成jar包,再以命令行的方式启动:

java -javaagent:/Users/XXX/Documents/code/javaagent/agentDemo/out/artifacts/agentDemo_jar/agentDemo.jar=Hello -javaagent:/Users/XXX/Documents/code/javaagent/agentDemo/out/artifacts/agentDemo_jar/agentDemo.jar=World -jar agentTest.jar
objc[18080]: Class JavaLaunchHelper is implemented in both /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/bin/java (0x106aea4c0) and /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/libinstrument.dylib (0x106b964e0). One of the two will be used. Which one is undefined.
=========premain方法执行1========
Hello
=========premain方法执行2========
World
=========Main主方法执行=========
http://www.baidu.com
Content size:2283
http://www.163.com
Content size:488569

特别提醒:如果你把 -javaagent相关的参数放在-jar相关参数的后面,则不会生效。也就是说,放在主程序后面的 agent 是无效的。

四、参考资料

  1. 一个最简单的javaagent demo实例
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容

  • 对于agent,是在vm启动,执行方法前,将字节码修改的服务代理。 对于javassist,是修改字节码具体实现。...
    城市里永远的学习者阅读 5,047评论 2 55
  • 概述 本文重点讲述javaagent的具体实现,因为它面向的是我们Java程序员,而且agent都是用Java编写...
    西华子阅读 775评论 0 1
  • 原文:Java探针-Java Agent技术利用JAVA探针分析复杂代码运维实践 总结: 使用java代理来实现j...
    小小少年Boy阅读 16,587评论 3 7
  • 朋友给我说她辞职了,不知道要做什么。 聊着聊着突然想到了自己这些年所经历的事,一桩桩一件件从脑海中略过,印象最深的...
    Ping_c737阅读 329评论 0 0
  • 几株巨大的芭蕉树下流出一股泉水,能看到泉水汩汩地往上冒,还不时地窜出几个泡泡。这泉水似乎永远也不会干涸。附近的村民...
    向上有阳光阅读 845评论 0 4