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();
}
}
}
}
}