java agent的一次踩坑实验之旅

2,436 阅读2分钟

何为java agent

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

怎么实现

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

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

    public static void premain(String agentArgs, Instrumentation instrumentation) {
        System.out.println("preagent agentArgs:" + agentArgs);
        instrumentation.addTransformer(new PreMainTransformer(), true);
    }
}

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
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);
    }
}
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文件上。

<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一起打入。

<!-- 将依赖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依赖
<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,挂载对应的插件。

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