java agent的一次踩坑实验之旅 何为java age

何为java agent

java agent也可以称之为java代理,相当于在JVM级别做了AOP支持,我们可以在运行main方法之前对程序进行修改或新增逻辑。

怎么实现

java agent可以通过两种方式实现

  1. 静态挂载
    首先需要实现premain方法,在这里会通过Instrumentation进行类修改,所以还需要实现Transformer,(本篇不对Instrumentation做详细阐述,可以自行百度学习。)
1
2
3
4
5
6
7
typescript复制代码public class PreMainDemoAgent {

public static void premain(String agentArgs, Instrumentation instrumentation) {
System.out.println("preagent agentArgs:" + agentArgs);
instrumentation.addTransformer(new PreMainTransformer(), true);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
csharp复制代码public class PreMainTransformer implements ClassFileTransformer {

@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if ("com/vernunft/agent/demo/controller/TestController".equals(className)) {
System.out.println("=================premain testController transform==================");
try {
CtClass demoClass = ClassPool.getDefault().get("com.vernunft.agent.demo.controller.TestController");
CtMethod testMethod = demoClass.getDeclaredMethod("test");
String methodBody = "return "premain";";
testMethod.setBody(methodBody);
demoClass.detach();
System.out.println("----处理好了-----");
return demoClass.toBytecode();
} catch (Throwable e) {
System.out.println("=========testController transform error========" + e.getMessage());
e.printStackTrace();
}
}
return null;
}
}
  1. 动态挂载
    动态挂载则需要定义agentmain方法,同样也需要定义对应的Transformer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
csharp复制代码public class AgentMainDemo {
public static void agentmain(String agentArgs, Instrumentation instrumentation) throws UnmodifiableClassException, NotFoundException, CannotCompileException, IOException, ClassNotFoundException {
System.out.println("agentmain agentArgs:" + agentArgs);
Class[] loadedClass = instrumentation.getAllLoadedClasses();
Class demoClass = null;
for (Class clazz : loadedClass) {
if (clazz.getName().equals("com.vernunft.agent.demo.controller.TestController")) {
demoClass = clazz;
}
}
instrumentation.addTransformer(new AgentMainTransformer(), true);
instrumentation.retransformClasses(demoClass);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
java复制代码public class AgentMainTransformer implements ClassFileTransformer {

@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if ("com/vernunft/agent/demo/controller/TestController".equals(className)) {
System.out.println("=================agentmain testController transform========================");
try {
ClassPool classPool = ClassPool.getDefault();
CtClass demoClass = classPool.getCtClass("com.vernunft.agent.demo.controller.TestController");
CtMethod testMethod = demoClass.getDeclaredMethod("test");
String methodBody = "return "agentmain";";
testMethod.setBody(methodBody);
demoClass.detach();
return demoClass.toBytecode();
} catch (Exception e) {
System.out.println("error" + e);
e.printStackTrace();
}
}
return null;
}
}

代码编写完就可以进行配置了,这边直接应用maven进行配置,也可以定义在META-INF文件上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
xml复制代码<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
</manifest>
<manifestEntries>
<!-- 静态挂载 -->
<Premain-Class>com.vernunft.agent.demo.PreMainDemoAgent</Premain-Class>
<!-- 动态挂载 -->
<Agent-Class>com.vernunft.agent.demo.AgentMainDemo</Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
<Can-Set-Native-Method-Prefix>true</Can-Set-Native-Method-Prefix>
</manifestEntries>
</archive>
</configuration>
</plugin>

原本以为到这里这个插件就可以使用了,直接mvn clean install打包成jar,在目标项目直接挂载跑起来测试,就遇到坑了。

这里我直接在项目里面跑,jvm配置如下:
image.png

遇到的问题&解决方案

  • NoClassDefFoundError:javassist/ClassPool
    image.png
    这个现象是这样:执行后,我的agent代码跑到

CtClass demoClass = ClassPool.getDefault().get(“com.vernunft.agent.demo.controller.TestController”);

就没有继续往下执行,而且刚开始我用Exception捕获,日志竟然也打不出来,最后我用Throwable才打印出错误。
可以发现这打包时就没把依赖jar打入,得在maven上加入配置,把引的jar一起打入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
xml复制代码<!-- 将依赖jar打入项目的插件-->
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<id>shade-when-package</id>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<artifactSet>
<includes>
<!-- 在这里把需要打入的jar包引进来-->
<include>org.javassist:javassist</include>
</includes>
</artifactSet>
<shadeSourcesContent>true</shadeSourcesContent>
</configuration>
</execution>
</executions>
</plugin>

标签的值为这里标记的

image.png
重新打包执行测试成功,按照预期返回结果。

  • 动态挂载时报com.sun.tools.attach.AttachNotSupportedException: no providers installed
    解决方案:在目标项目的pom文件增加tools.jar依赖
1
2
3
4
5
6
7
xml复制代码<dependency>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
<version>1.5.0</version>
<scope>system</scope>
<systemPath>D:/Program Files/Java/jdk1.8.0_60/lib/tools.jar</systemPath>
</dependency>

重新构建目标项目执行,发现问题解决~

最后附上目标项目的测试文件,动态挂载就是通过指定进程id,挂载对应的插件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
less复制代码@RestController
@RequestMapping("/agent")
public class TestController {
@GetMapping("/attach/{pid}")
public String attachAgent(@PathVariable String pid) throws IOException, AttachNotSupportedException,
AgentLoadException, AgentInitializationException {
// JVM attach机制
VirtualMachine virtualMachine = VirtualMachine.attach(pid);
virtualMachine.loadAgent("E:\coding\lbb-agent\target\lbb-agent.jar");
return "success";
}

@GetMapping("/test")
public String test() {
return "test";
}

Over,因为踩坑才能学习更多的东西,fighting。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%