在prometheus监控jvm信息,java启动的时候要加上-javaagent:jmx_prometheus_javaagent-0.17.0.jar
skywaliking的时候也要-javaagent:skywalking-agent.jar启动项目,那么为什么要使用
javaagent的方式呢,是为了实现对应的功能的基础上又要实现项目的零侵入.Java字节码增强技术技术
Java Agent 又叫做 Java 探针,是在 JDK1.5 引入的一种可以动态修改 Java 字节码的技术。Java 类编译之后形成字节码被 JVM 执行,在 JVM 在执行这些字节码之前获取这些字节码信息,并且通过字节码转换器对这些字节码进行修改,来完成一些额外的功能。
gti地址:https://gitee.com/zhangjijige/agent-test.git
可以参考:https://www.jianshu.com/p/6967d4dfbc49
agent有两个接口
// 用于JVM刚启动时调用,其执行时应用类文件还未加载到JVM
public static void premain(String agentArgs, Instrumentation inst);
// 用于JVM启动后,在运行时刻加载
public static void agentmain(String agentArgs, Instrumentation inst);
项目运行的时候加上配置
-javaagent:C:\Users\DuoDuo\Desktop\agent\agentTest\target\agentTest-1.0-SNAPSHOT.jar
要使用自己的jar,jar包是agenTest生成的jar包,所以要用maven创建agentTest包,再调用MainClass类的main方法
示例中只使用了premain方法,当在jvm启动的时候,在premain方法中使用addTransformer方法添加了回调函数,每当创建类的时候,都会调用回调函数,有特殊的类比如Thead类,就会在调用premain方法之前被创建,也就是在后续创建Thread类时不回调用premain方法,不过是可以通过retransformClasses方法解决这个问题
替换原有的类
inst.addTransformer((loader, className, classBeingRedefined, protectionDomain, classfileBuffer)->{
//如果创建AgentServer类,用其余的类代替
if(!"com.agent.AgentServer".equals(className)){
return null;
}else{
FileInputStream fis = null;
try{
//用自己的类,代替项目中的class文件
fis = new FileInputStream("C:\\Users\\Desktop\\com.agent.AgentServer.class");
}catch(FileNotFoundException e){
e.printStackTrace();
}
int len = 0;
try{
len = fis.available();
}catch(IOException e){
e.printStackTrace();
}
byte[] data = new byte[len];
try{
fis.read(data);
fis.close();
return data;
}catch(IOException e){
e.printStackTrace();
return null;
}
}
}, true);
inst.addTransformer((loader, className, classBeingRedefined, protectionDomain, classfileBuffer)->{
if(className == null || className != null && !className.contains("Thread")){
return null;
}else{
System.out.println("Theard类重新被加载定义");
return null;
}
}, true);
//retransformClasses方法的作用,因为在调用premain方法之前,有些类时加载完的,比如Thread类
//这个应该理解,没有线程项目都没法运行,通过retransformClasses方法让Thread类再创建的时候,
//还会调用premain方法
inst.retransformClasses(Thread.class);
System.out.println("测试agent");
bytebuddy应用
AgentBuilder.Transformer transformer = new AgentBuilder.Transformer() {
@Override
public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder,
TypeDescription typeDescription,
ClassLoader classLoader){
return builder
// 拦截任意方法
.method(ElementMatchers.<MethodDescription>any())
// 指定方法拦截器,此拦截器中做具体的操作
.intercept(MethodDelegation.to(TimeInterceptor.class));
}
};
AgentBuilder.Listener listener = new AgentBuilder.Listener() {
@Override
public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, DynamicType dynamicType){
System.out.println("onTransformation");
}
@Override
public void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module){
System.out.println("onIgnored"+typeDescription.getName());
}
@Override
public void onError(String typeName, ClassLoader classLoader, JavaModule module, Throwable throwable){
System.out.println("onError"+typeName);
}
@Override
public void onComplete(String typeName, ClassLoader classLoader, JavaModule module){
System.out.println("onComplete"+typeName);
}
};
new AgentBuilder
.Default()
// 指定需要拦截的类
.type(ElementMatchers.nameStartsWith("com.agent"))
.transform(transformer)
.with(listener)
.installOn(inst);
}
TimeInterceptor类,写了方法的运行时间
public class TimeInterceptor {
@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 + ": cost " + (System.currentTimeMillis() - start) + "ms");
}
}
}
配置参数
在META-INF目录下的MANIFEST.MF文件需要配置的参数
Premain-Class: 包含 premain 方法的类(类的全路径名)
Agent-Class: 包含 agentmain 方法的类(类的全路径名)
Boot-Class-Path: 设置引导类加载器搜索的路径列表。查找类的特定于平台的机制失败后,引导类加载器会搜索这些路径。按列出的顺序搜索路径。列表中的路径由一个或多个空格分开。路径使用分层 URI 的路径组件语法。如果该路径以斜杠字符(“/”)开头,则为绝对路径,否则为相对路径。相对路径根据代理 JAR 文件的绝对路径解析。忽略格式不正确的路径和不存在的路径。如果代理是在 VM 启动之后某一时刻启动的,则忽略不表示 JAR 文件的路径。(可选)
Can-Redefine-Classes: true表示能重定义此代理所需的类,默认值为 false(可选)
Can-Retransform-Classes: true 表示能重转换此代理所需的类,默认值为 false (可选)
Can-Set-Native-Method-Prefix: true表示能设置此代理所需的本机方法前缀,默认值为 false(可选)
这些配置参数是通过maven的配置,再打包jar的时候会自动生成
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.2</version>
<configuration>
<archive>
<manifestEntries>
<Project-name>${project.name}</Project-name>
<Project-version>${project.version}</Project-version>
<Premain-Class>AgentTest</Premain-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
<configuration>
<artifactSet>
<includes>
<include>javassist:javassist:jar:</include>
<include>net.bytebuddy:byte-buddy:jar:</include>
<include>net.bytebuddy:byte-buddy-agent:jar:</include>
</includes>
</artifactSet>
</configuration>
</plugin>
</plugins>
</build>
运行的结果
可以看到实际的效果,只是启动项目的时候,使用-javaagent配置,就实现了方法调用时间的记录,实现的代码了零侵入