zoukankan      html  css  js  c++  java
  • IDEA + maven 零基础构建 java agent 项目

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

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

    本篇将作为 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 中,删除最后一行时,会有错误提醒

    然后我们的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 参数,如下

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

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

    请注意上面的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获取目标应用的进程号

    当上面的 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 被成功注入进去了

    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. 源码

    1. 一灰灰 Bloghttps://liuyueyi.github.io/hexblog

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

    2. 声明

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

    3. 扫描关注

    一灰灰 blog

    QrCode

  • 相关阅读:
    Spring配置文件中的那些标签意味着什么(持续更新)
    转 spring配置文件
    Web.xml配置详解之context-param
    web.xml 中的listener、 filter、servlet 加载顺序及其详解
    spring mvc 中web.xml配置信息解释
    转 一个web项目web.xml的配置中<context-param>配置作用
    在web.xml中通过contextConfigLocation配置spring
    (转)web.xml中的contextConfigLocation在spring中的作用
    Android菜鸟的成长笔记(20)——IntentService
    PhotoSwipe源码解读系列(二)
  • 原文地址:https://www.cnblogs.com/yihuihui/p/12503020.html
Copyright © 2011-2022 走看看