网站Logo GESONG

javaagent+bytebuddy

gesong
0
2025-09-05

javaagent介绍

在JDK 1.5之后,JVM提供了探针接口(Instrumentation接口),便于开发人员基于Instrumentation接口编写Java Agent。

但是,Instrumentation接口底层依然依赖JVMTI语义的Native API,相当于给用户封装了一下,降低了使用成本。

在JDK 1.6及之后的版本,JVM又提供了Attach接口,便于开发人员使用Attach接口实现Java Agent。和Instrumentation接口一样,Attach接口底层也依赖JVMTI语义的Native API。

简而言之,Java Agent可以理解为是一种特殊的Java程序,是Instrumentation接口的客户端。

与普通Java程序通过main方法启动不同,Java Agent并不是一个可以单独启动的程序,它必须依附在一个Java应用程序(JVM)上,与主程序运行在同一个JVM进程中,通过Instrumentation 接口与JVM进行交互。

Java Agent提供了一种在加载字节码时对字节码进行修改的能力,有两种执行方式:

一、在应用运行之前,通过premain()方法来实现「在应用启动时侵入并代理应用」,这种方式是利用Instrumentation接口实现的;

二、在应用运行之后,通过Attach API和agentmain()方法来实现「在应用启动后的某一个运行阶段中侵入并代理应用」,这种方式是利用Attach接口实现的。

本文主要介绍agentmain,字节码增强借助bytebuddy

引入依赖

<dependency>
    <groupId>net.bytebuddy</groupId>
    <artifactId>byte-buddy</artifactId>
    <version>1.14.13</version>
</dependency>
<dependency>
    <groupId>net.bytebuddy</groupId>
    <artifactId>byte-buddy-agent</artifactId>
    <version>1.14.13</version>
</dependency>

添加打包插件

<plugin>
    <artifactId>maven-assembly-plugin</artifactId>
    <configuration>
        <appendAssemblyId>false</appendAssemblyId>
        <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
        </descriptorRefs>
        <archive> <!--自动添加META-INF/MANIFEST.MF -->
            <manifest>
                <!-- 添加 mplementation-*和Specification-*配置项-->
                <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
            </manifest>
            <manifestEntries>
                <!--指定premain方法所在的类-->
                <Premain-Class>org.example.InstrumentAttach</Premain-Class>
                <!--指定agentmain方法所在的类-->
                <Agent-Class>org.example.InstrumentAttach</Agent-Class>
                <Can-Redefine-Classes>true</Can-Redefine-Classes>
                <Can-Retransform-Classes>true</Can-Retransform-Classes>
            </manifestEntries>
        </archive>
    </configuration>
    <executions>
        <execution>
            <id>make-assembly</id>
            <phase>package</phase>
            <goals>
                <goal>single</goal>
            </goals>
        </execution>
    </executions>
</plugin>

agentmain方法

package org.example;

import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.utility.JavaModule;
import org.springframework.web.bind.annotation.RestController;

import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;

public class InstrumentAttach {
    public static void agentmain(String agentArgs, Instrumentation inst) {
        // 拦截器方式总是不生效,以后再研究吧
//        agentmain1(agentArgs, inst);
        // Advice方式
        agentmain2(agentArgs, inst);
    }

    public static void agentmain1(String agentArgs, Instrumentation inst) {
        new AgentBuilder.Default()
                .ignore(ElementMatchers.none())
                .disableClassFormatChanges()// 禁止修改类结构
//                .type(ElementMatchers.nameEndsWith("WxjavaMpController"))
                .type(ElementMatchers.isAnnotatedWith(RestController.class))
                .transform((DynamicType.Builder<?> builder,
                            TypeDescription type,
                            ClassLoader loader,
                            JavaModule module,
                            ProtectionDomain protectionDomain) -> {
                    System.out.println("Transforming: " + type.getName()); // 添加日志
                    return builder.method(ElementMatchers.any())
                            .intercept(MethodDelegation.to(MyInterceptor.class));

                })
                .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION) // 添加重转换能力
                .with(AgentBuilder.RedefinitionStrategy.Listener.StreamWriting.toSystemOut()) // 添加监听器
                .installOn(inst);
    }

    public static void agentmain2(String agentArgs, Instrumentation inst) {
        Advice advice = Advice.to(TimeMeasurementAdvice.class);
        new AgentBuilder
                .Default()
//                .ignore(ElementMatchers.none())
                .disableClassFormatChanges()// 禁止修改类结构
                //拦截匹配方式:类以com.tencent.wxcloudrun开始(其实就是com.tencent.wxcloudrun包下的所有类)
//                .type(ElementMatchers.nameStartsWith("com.tencent.wxcloudrun"))
                .type(ElementMatchers.isAnnotatedWith(RestController.class))
                //拦截到的类由transformer处理
                .transform((DynamicType.Builder<?> builder,
                            TypeDescription type,
                            ClassLoader loader,
                            JavaModule module,
                            ProtectionDomain protectionDomain) -> {
                    System.out.println("Transforming: " + type.getName()); // 添加日志
                    return builder.visit(advice.on(ElementMatchers.isMethod()));
                })
                .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION) // 添加重转换能力
                .with(AgentBuilder.RedefinitionStrategy.Listener.StreamWriting.toSystemOut()) // 添加监听器
                //安装到 Instrumentation
                .installOn(inst);
    }
}

attach程序

将以上项目打包,记录下生成的jar包路径,再起一个程序用来链接到主程序

package org.example;

import com.sun.tools.attach.*;

import java.io.IOException;
import java.util.List;

public class Main {
    public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
        List<VirtualMachineDescriptor> list = VirtualMachine.list();
        for (VirtualMachineDescriptor vir : list) {
            System.out.println(vir.displayName());
            if (vir.displayName().endsWith("WxCloudRunApplication")) {
                VirtualMachine virtualMachine = VirtualMachine.attach(vir.id());
                try {
                    // agent jar包路径
                    virtualMachine.loadAgent("D:\\code\\springboot_test\\target\\springboot_test-1.0-SNAPSHOT.jar");
                }finally {
                    virtualMachine.detach();
                }
            }
        }
    }
}

动物装饰