38

IDEA + maven 零基础构建 java agent 项目

 4 years ago
source link: https://mp.weixin.qq.com/s?__biz=MzU3MTAzNTMzMQ%3D%3D&%3Bmid=2247484544&%3Bidx=1&%3Bsn=d279217a75ca58b0d735fb594d423348&%3Bchksm=fce71f2ccb90963a972b4f46c84c30806a32e4f74a0e3465fca26d4da919321f5867f8af2793&%3Btoken=436571054&%3Blang=zh_CN
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
QfeauqU.jpg!web

200316-IDEA + maven 零基础构建 java agent 项目

Java Agent(java 探针)虽说在 jdk1.5 之后就有了,但是对于绝大多数的业务开发 javaer 来说,这个东西还是比较神奇和陌生的;虽说在实际的业务开发中,很少会涉及到 agent 开发,但是每个 java 开发都用过,比如使用 idea 写了个 HelloWorld.java,并运行一下, 仔细看控制台输出

FFZreiY.jpg!web

本篇将作为 Java Agent 的入门篇,手把手教你搭建一个Java Agent开发测试项目环境

I. Java Agent 开发

首先明确我们的开发环境,选择 IDEA 作为编辑器,maven 进行包管理

1. 核心逻辑

创建一个新的项目(or 子 module),然后我们新建一个 SimpleAgent 类

public class SimpleAgent {

    /**
     * jvm 参数形式启动,运行此方法
     *
     * @param agentArgs
     * @param inst
     */
    public static void premain(String agentArgs, Instrumentation inst) {
        System.out.println("premain");
    }

    /**
     * 动态 attach 方式启动,运行此方法
     *
     * @param agentArgs
     * @param inst
     */
    public static void agentmain(String agentArgs, Instrumentation inst) {
        System.out.println("agentmain");
    }
}

我们先忽略上面两个方法的具体玩法,先简单看一下这两个方法的区别,注释上也说了

  • jvm 参数形式:调用 premain 方法

  • attach 方式:调用 agentmain 方法

其中 jvm 方式,也就是说要使用这个 agent 的目标应用,在启动的时候,需要指定 jvm 参数 -javaagent:xxx.jar ,当我们提供的 agent 属于基础必备服务时,可以用这种方式

当目标应用程序启动之后,并没有添加 -javaagent 加载我们的 agent,依然希望目标程序使用我们的 agent,这时候就可以使用 attach 方式来使用(后面会介绍具体的使用姿势),自然而然的会想到如果我们的 agent 用来 debug 定位问题,就可以用这种方式

2. 打包

上面一个简单 SimpleAgent 就把我们的 Agent 的核心功能写完了(就是这么简单),接下来需要打一个 Jar 包

通过 maven 插件,可以比较简单的输出一个合规的 java agent 包,有两种常见的使用姿势

a. pom 指定配置

在 pom.xml 文件中,添加如下配置,请注意一下 manifestEntries 标签内的参数

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-assembly-plugin</artifactId>
            <configuration>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
                <archive>
                    <manifestEntries>
                        <Premain-Class>com.git.hui.agent.SimpleAgent</Premain-Class>
                        <Agent-Class>com.git.hui.agent.SimpleAgent</Agent-Class>
                        <Can-Redefine-Classes>true</Can-Redefine-Classes>
                        <Can-Retransform-Classes>true</Can-Retransform-Classes>
                    </manifestEntries>
                </archive>
            </configuration>

            <executions>
                <execution>
                    <goals>
                        <goal>attached</goal>
                    </goals>
                    <phase>package</phase>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

然后通过 mvn assembly:assembly 命令打包,在 target 目录下,可以看到一个后缀为 jar-with-dependencies 的 jar 包,就是我们的目标

b. MANIFEST.MF 配置文件

通过配置文件 MANIFEST.MF ,可能更加常见,这里也简单介绍下使用姿势

  • 在资源目录(Resources)下,新建目录 META-INF
  • META-INF 目录下,新建文件 MANIFEST.MF

文件内容如下

Manifest-Version: 1.0
Premain-Class: com.git.hui.agent.SimpleAgent
Agent-Class: com.git.hui.agent.SimpleAgent
Can-Redefine-Classes: true
Can-Retransform-Classes: true

请注意,最后的一个空行(如果我上面没有显示的话,多半是 markdown 渲染有问题),不能少,在 idea 中,删除最后一行时,会有错误提醒

RJrEJvf.jpg!web

然后我们的 pom.xml 配置,需要作出对应的修改

<build>
  <plugins>
      <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-assembly-plugin</artifactId>
          <configuration>
              <descriptorRefs>
                  <descriptorRef>jar-with-dependencies</descriptorRef>
              </descriptorRefs>
              <archive>
                  <manifestFile>
                      src/main/resources/META-INF/MANIFEST.MF
                  </manifestFile>
                  <!--<manifestEntries>-->
                      <!--<Premain-Class>com.git.hui.agent.SimpleAgent</Premain-Class>-->
                      <!--<Agent-Class>com.git.hui.agent.SimpleAgent</Agent-Class>-->
                      <!--<Can-Redefine-Classes>true</Can-Redefine-Classes>-->
                      <!--<Can-Retransform-Classes>true</Can-Retransform-Classes>-->
                  <!--</manifestEntries>-->
              </archive>
          </configuration>

          <executions>
              <execution>
                  <goals>
                      <goal>attached</goal>
                  </goals>
                  <phase>package</phase>
              </execution>
          </executions>
      </plugin>
  </plugins>
</build>

同样通过 mvn assembly:assembly 命令打包

II. Agent 使用

agent 有了,接下来就是需要测试一下使用 agent 的使用了,上面提出了两种方式,我们下面分别进行说明

1. jvm 参数

首先新建一个 demo 项目,写一个简单的测试类

public class BaseMain {

    public int print(int i) {
        System.out.println("i: " + i);
        return i + 2;
    }

    public void run() {
        int i = 1;
        while (true) {
            i = print(i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        BaseMain main = new BaseMain();
        main.run();
        Thread.sleep(1000 * 60 * 60);
    }
}

测试类中,有一个死循环,各 1s 调用一下 print 方法,IDEA 测试时,可以直接在配置类,添加 jvm 参数,如下

qyuaInY.jpg!web

请注意上面红框的内容为上一节打包的 agent 绝对地址: -javaagent:/Users/..../target/java-agent-1.0-SNAPSHOT-jar-with-dependencies.jar

执行 main 方法之后,会看到控制台输出

3YJ7v2U.jpg!web

请注意上面的 premain , 这个就是我们上面的 SimpleAgent 中的 premain 方法输出,且只输出了一次

2. attach 方式

在使用 attach 方式时,可以简单的理解为要将我们的 agent 注入到目标的应用程序中,所以我们需要自己起一个程序来完成这件事情

public class AttachMain {
    public static void main(String[] args)
            throws IOException, AgentLoadException, AgentInitializationException, AttachNotSupportedException {
        // attach方法参数为目标应用程序的进程号
        VirtualMachine vm = VirtualMachine.attach("36633");
        // 请用你自己的agent绝对地址,替换这个
        vm.loadAgent("/Users/......./target/java-agent-1.0-SNAPSHOT-jar-with-dependencies.jar");
    }
}

上面的逻辑比较简单,首先通过 jps -l 获取目标应用的进程号

fEZNj2f.jpg!web

当上面的 main 方法执行完毕之后,控制台会输出类似下面的两行日志,可以简单的理解为我连上目标应用,并丢了一个 agent,然后挥一挥衣袖不带走任何云彩的离开了

Connected to the target VM, address: '127.0.0.1:63710', transport: 'socket'
Disconnected from the target VM, address: '127.0.0.1:63710', transport: 'socket'

接下来再看一下上面的 BaseMain 的输出,中间夹着一行 agentmain , 就表明 agent 被成功注入进去了

vQfqMrU.jpg!web

3. 小结

本文介绍了 maven + idea 环境下,手把手教你开发一个 hello world 版 JavaAgent 并打包的全过程

两个方法

方法 说明 使用姿势 premain() agent 以 jvm 方式加载时调用,即目标应用在启动时,指定了 agent -javaagent:xxx.jar agentmain() agent 以 attach 方式运行时调用,目标应用程序正常工作时使用 VirtualMachine.attach(pid) 来指定目标进程号
vm.loadAgent("...jar") 加载 agent

两种打包姿势

打包为可用的 java agent 时,需要注意配置参数,上面提供了两种方式,一个是直接在 pom.xml 中指定配置

<manifestEntries>
    <Premain-Class>com.git.hui.agent.SimpleAgent</Premain-Class>
    <Agent-Class>com.git.hui.agent.SimpleAgent</Agent-Class>
    <Can-Redefine-Classes>true</Can-Redefine-Classes>
    <Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>

另外一个是在配置文件 META-INF/MANIFEST.MF 中写好(需要注意最后一个空行不可或缺)

Manifest-Version: 1.0
Premain-Class: com.git.hui.agent.SimpleAgent
Agent-Class: com.git.hui.agent.SimpleAgent
Can-Redefine-Classes: true
Can-Retransform-Classes: true

当然本篇内容看完之后,会发现对 java agent 的实际开发还是不太清楚,难道 agent 就是在前面输出一行 hello world 就完事了么,这和想象中的完全不一样啊

下一篇博文(明天推送...)将手把手教你实现一个方法统计耗时的 java agent 包,将详细说明利用接口 Instrumentation 来实现字节码修改,从而是实现功能增强

II. 其他

0. 源码

  • https://github.com/liuyueyi/java-agent

1. 一灰灰 Blog:https://liuyueyi.github.io/hexblog

一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

2. 声明

尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现 bug 或者有更好的建议,欢迎批评指正,不吝感激

  • 微博地址: 小灰灰 Blog

  • QQ:一灰灰/3302797840

3. 扫描关注

一灰灰 blog

FVJvai3.jpg!web QrCode

Recommend

  • 20
    • www.zhyea.com 4 years ago
    • Cache

    使用maven构建scala项目

    构建及编译scala工程我使用过三种工具:sbt、gradle和maven。 感谢GFW,sbt的速度慢得让人心碎。即使使用诸如广谈公服等加速方案,也还是会遇到各种各样的问题(比如时不时的lock住)。所以pass。 gradle是最让人惊喜的...

  • 5

    从零构建Java项目(Maven+SpringBoot+Git) #02 奥斯丁项目前两天我说要写个项目来持续

  • 5
    • blog.leixin.wang 2 years ago
    • Cache

    jenkins+maven java自动构建环境搭建

    jenkins+maven java自动构建环境搭建 发表于...

  • 6
    • tianmingxing.com 2 years ago
    • Cache

    基于 Apache Maven 构建多模块项目

    多模块项目由管理一组子模块的聚合器 POM 来构建。在大多数情况下聚合器位于项目的根目录中,并且必须是 pom 类型的项目。 子模块是常规的 Maven 项目,它们可以单独构建,也可以通过聚合器 POM 构建。 通过聚合器 PO...

  • 2
    • tianyijian.github.io 2 years ago
    • Cache

    Maven 构建 java 项目(二)

    Maven 构建 java 项目(二) 发表于 2018-05-19

  • 4
    • tianyijian.github.io 2 years ago
    • Cache

    Maven 构建 java 项目(一)

    Maven 构建 java 项目(一) 发表于 ...

  • 2
    • rovo98.github.io 2 years ago
    • Cache

    Maven 项目构建提速

    Maven 项目构建提速 2021-02-28 Java 2 Maven 作为 Java 项目的一个常用构建工具,它很容易上手使用,但若不对其进行...

  • 4

    之前接触过的项目大都是单体应用,各应用之间有功能重合但无代码共用。同样的功能,要么重复开发,要么代码拷贝,开发繁琐且效率低下。一但在多个应用中均有使用的相同的代码出现了 bug,开发人员更是要在多个应用中逐个修复,劳神伤身。 故引入模块化开发概...

  • 4
    • blog.51cto.com 1 year ago
    • Cache

    Intellij IDEA如何导入Maven项目

    Intellij IDEA如何导入Maven项目 精选 原创 是温度呀 2022-09-20 11:26:38...

  • 5
    • feiju12138.github.io 1 year ago
    • Cache

    【笔记】IDEA利用Maven创建Javaweb项目

    【笔记】IDEA利用Maven创建Javaweb项目 2022-09-29 2...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK