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

文章目录
  1. I. Java Agent开发
    1. 1. 核心逻辑
    2. 2. 打包
      1. a. pom指定配置
      2. b. MANIFEST.MF 配置文件
  2. II. Agent使用
    1. 1. jvm参数
    2. 2. attach方式
    3. 3. 小结
  3. II. 其他
    1. 0. 源码
    2. 1. 一灰灰Blog: https://liuyueyi.github.io/hexblog
    3. 2. 声明
    4. 3. 扫描关注

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

本篇将作为Java Agent的入门篇,手把手教你开发一个统计方法耗时的Java Agent

I. Java Agent开发

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

1. 核心逻辑

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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标签内的参数

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
<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

文件内容如下

1
2
3
4
5
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配置,需要作出对应的修改

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
<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项目,写一个简单的测试类

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
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注入到目标的应用程序中,所以我们需要自己起一个程序来完成这件事情

1
2
3
4
5
6
7
8
9
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,然后挥一挥衣袖不带走任何云彩的离开了

1
2
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中指定配置

1
2
3
4
5
6
<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 中写好(需要注意最后一个空行不可或缺)

1
2
3
4
5
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

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×