一、java agent和byte-buddy组合的使用场景
java agent开了一扇门,bytebuddy在开的这扇门中打开了一片新的天地。比较典型的就是skywalking、sermant、arthas、mockito、fastjson等。是不是很好奇skywalking、sermant、arthas、mockito、fastjson的原理。下面我们来了解一下。
二、Java Agent技术的框架
Java Agent技术是一种在Java虚拟机(JVM)启动时或运行时,可以插入到JVM中的程序。这种技术主要用于实现一些高级功能,如字节码操作、性能监控、调试、热修复等。
在Java Agent技术的框架下,常用的框架有以下几个:
- Byte Buddy: 这是一个强大的库,用于在运行时创建和修改Java类。Byte Buddy提供了一个简单易用的API,用于生成、修改和加载Java字节码。它支持Java 5及更高版本,并且与Java Agent技术非常配合。
- ASM: ASM是一个Java字节码操控框架。它能直接生成或以二进制形式修改已有类或者核心类的字节码。ASM可以直接生成字节码,而不需要了解Java虚拟机指令。ASM比其他的Java字节码操控框架(例如Javassist, BCEL, CGLIB)更快更小。
- Javassist: Javassist是一个开源的分析、编辑和创建Java字节码的库。它已经被许多其他的Java类库和工具使用,包括Hibernate和Spring。Javassist是分析字节码的工具,并且提供了一个简单的API来操作和生成字节码。
- Instrumentation API: 这是Java Agent技术的核心API,用于在运行时修改类的字节码。使用这个API,你可以实现自己的类加载器,并在类被加载到JVM时修改其字节码。
- HotSwapAgent: HotSwapAgent是一个Java类重新加载器,它支持在不停止和重启JVM的情况下重新加载已修改的类。HotSwapAgent基于Java Instrumentation API,并提供了更多的功能,如条件断点、类变量查看和修改等。
三、基于java agent和byte-buddy组合简单使用
首先需要准备好premain,然后基于premain中定义的转换器,在转换器中,添加需要拦截的方法,拦截的规则,最终将其安装到Instrumentation。
1)首先准备好java agent的premain
代码语言:javascript复制public class PreMainAgent {
public static void premain(String agentArgs, Instrumentation inst) {
//创建一个转换器,转换器可以修改类的实现
//ByteBuddy对java agent提供了转换器的实现,直接使用即可
AgentBuilder.Transformer transformer = new AgentBuilder.Transformer() {
public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule javaModule) {
return builder
// 拦截任意方法
.method(ElementMatchers.<MethodDescription>any())
// 拦截到的方法委托给TimeInterceptor
.intercept(MethodDelegation.to(MyInterceptor.class));
}
};
new AgentBuilder
.Default()
// 根据包名前缀拦截类
.type(ElementMatchers.nameStartsWith("com.agent"))
// 拦截到的类由transformer处理
.transform(transformer)
.installOn(inst);
}
}
2)在pom.xml中新增premain的信息
代码语言:javascript复制<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>com.example.demo.bytecode.PreMainAgent</Premain-Class>
<!--添加这个即可-->
<Agent-Class>com.example.demo.bytecode.PreMainAgent</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>
3)准备好拦截器
代码语言:javascript复制public class MyInterceptor {
@RuntimeType
public static Object intercept(@Origin Method method,
@SuperCall Callable<?> callable)
throws Exception {
long start = System.currentTimeMillis();
try {
//执行原方法
return callable.call();
} finally {
//打印调用时长
System.out.println(method.getName() ":" (System.currentTimeMillis() - start) "ms");
}
}
}
将其打好包,然后在业务系统中使用即可
4)在业务系统中引入byte-buddy的依赖
代码语言:javascript复制<dependencies>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.9.2</version>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>1.9.2</version>
</dependency>
</dependencies>
5)在业务系统项目中使用
注意:这里的包应该是com.example.biz打头的
代码语言:javascript复制public class Main {
public static void main(String[] args) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Hello Byte-Buddy");
}
}
以上是对byte-buddy的简单入门案例,通过上面的案例可以很好的理解sermant的原理。
参考:
byte-buddy开源地址:https://github.com/raphw/byte-buddy
sermant开源地址:https://github.com/huaweicloud/Sermant