1、Java Agent
相当于main方法之前的一个拦截器
本身是Java命令的一个参数,后面跟一个Jar包
对Jar包的要求
在META-INF目录下的MANIFEST.MF文件中必须指定premain-class配置项
premain-class配置项指定的类必须提供premain方法
针对Jar包的要求,我们可以使用maven插件来完成 maven-assembly-plugin
1.1 通过Java Agent调整加载的类信息
创建TestClass,修改返回值,生成新的class文件存放到其他目录,之后就是将原来的class信息,修改成其他目录下的class文件信息
package com.fh;
public class TestClass {
public int getNumber(){
return 1;
}
}
View Code
进行方法调用 启动类需要添加指定的参数
package com.fh;
/**
* -javaagent:E:\idea_workspace\workspace_skywalking\TestAgent\target\TestAgent-1.0-SNAPSHOT.jar
*/
public class Main {
public static void main(String[] args) throws InterruptedException {
System.out.println(new TestClass().getNumber());
}
}
View Code
创建一个新的项目作为agent的jar来使用
package com.fh;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.security.ProtectionDomain;
/**
* 存在方法重载,如果两个都存在,一般会执行第一个
* sun.instrument.InstrumentationImpl.java
*/
public class TestAgent1 {
/**
*
* @param agentArgs -javaagent命令携带的参数
* @param inst 提供了操作类定义的相关方法
*/
public static void premain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException {
// 注册/注销一个ClassFileTransformer类的实例,该Transformer实例会在类加载的时候被调用
// 可用于修改类定义
// inst.addTransformer();
// inst.removeTransformer();
// 针对的是已经加载的类,他会对已经传入的类进行重新定义
// inst.redefineClasses();
// 返回虚拟机已经加载的所有类
// inst.getAllLoadedClasses()
// 返回当前虚拟机已经初始化的类
// inst.getInitiatedClasses()
// 获取参数指定的对象的大小
// inst.getObjectSize()
// System.out.println("this is a Java agent with two args");
// System.out.println("参数:"+agentArgs);
inst.addTransformer(new Transformer(),true);
inst.retransformClasses(TestClass.class);
System.out.println("premain done");
}
public static void premain(String agentArgs){
System.out.println("this is a Java agent with only one args");
System.out.println("参数:"+agentArgs);
}
static class Transformer implements ClassFileTransformer{
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> c, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if(!c.getSimpleName().equals("TestClass")){
return null;
}
return getBytesFromFile("E:\\idea_workspace\\workspace_skywalking\\agent-demo\\TestClass.class");
}
public byte[] getBytesFromFile(String filename){
try {
File file = new File(filename);
InputStream is = new FileInputStream(file);
long length = file.length();
byte[] bytes = new byte[(int) length];
//read the bytes
int offset = 0;
int numRead = 0;
while (offset < bytes.length &&
(numRead = is.read(bytes,offset,bytes.length-offset))>=0){
offset +=numRead;
}
if(offset < bytes.length){
throw new Exception("Could not completely read file");
}
is.close();
return bytes;
}catch (Exception e){
e.printStackTrace();
return null;
}
}
}
}
View Code
pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.fh</groupId>
<artifactId>TestAgent</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>com.fh</groupId>
<artifactId>agent-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<!--统计方法耗时-->
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.10.19</version>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>1.10.19</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.4</version>
<configuration>
<appendAssemblyId>false</appendAssemblyId>
<!--将TestAgent的所有依赖包都打到jar包中-->
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<!-- 添加MANIFEST.MF中的各项配置-->
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
<addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
</manifest>
<!-- 将 premain-class 配置项设置为com.xxx.TestAgent-->
<manifestEntries>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
<Premain-Class>com.fh.TestAgent</Premain-Class>
</manifestEntries>
</archive>
</configuration>
<executions>
<execution>
<!-- 绑定到package生命周期阶段上 -->
<phase>package</phase>
<!-- 绑定到package生命周期阶段上 -->
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
View Code
1.2 通过byte-buddy计算方法的耗时 byte-buddy是一个开源的Java库,可以帮助用户屏蔽字节码操作
package com.fh;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.matcher.ElementMatchers;
import java.lang.instrument.Instrumentation;
public class TestAgent2 {
public static void premain(String agentArgs,
Instrumentation inst){
// Byte Buddy会根据 Transformer指定的规则进行拦截并增强代码
AgentBuilder.Transformer transformer = (builder, typeDescription, classLoader, javaModule) -> {
// method()指定哪些方法需要被拦截,ElementMatchers.any()表
return builder.method(ElementMatchers.<MethodDescription>any())
.intercept(MethodDelegation.to(TimeInterceptor.class));// intercept()指明拦截上述方法的拦截器
};
new AgentBuilder
.Default()
//根据包名前缀拦截类
.type(ElementMatchers.nameStartsWith("com.fh"))
.transform(transformer)//拦截到的类由transformer来处理
.installOn(inst);//安装到Instrumentation
}
}
View Code
package com.fh;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.SuperCall;
import java.lang.reflect.Method;
import java.util.concurrent.Callable;
public class TimeInterceptor {
/**
*
* @param method 被拦截方法的Method对象
* @param callable 可以调用到被拦截的目标方法,即使目标方法带参数,也不需要显示传递
* @return
* @throws Exception
*/
@RuntimeType
public static Object intercept(@Origin Method method,
@SuperCall Callable<?> callable) throws Exception {
long l = System.currentTimeMillis();
try {
return callable.call();
}finally {
System.out.println(method.getName()+":"+
(System.currentTimeMillis()-l)+"ms");
}
}
}
View Code
pom文件需要引入的信息,已经在上面的pom文件中添加,修改premain-class信息即可
还需要添加Can-Retransform-Classes标签为true
2、Attach API
为了更好的灵活性,在Java6之后提供的,可以在main方法之后后执行agentmain方法添加一下特殊的功能
添加要被监听的方法main 不需要添加javaagent参数
package com.fh;
/**
* -javaagent:E:\idea_workspace\workspace_skywalking\TestAgent\target\TestAgent-1.0-SNAPSHOT.jar
*/
public class Main {
public static void main(String[] args) throws InterruptedException {
System.out.println(new TestClass().getNumber());
while (true){
Thread.sleep(1000);
System.out.println(new TestClass().getNumber());
}
}
}
View Code
添加加载jar包附着在虚拟机上的程序
package com.fh;
import com.sun.tools.attach.*;
import java.io.IOException;
import java.util.List;
public class AttachMain {
public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException, InterruptedException {
List<VirtualMachineDescriptor> listBefore = VirtualMachine.list();
//agentmain()方法所在jar包
String jar = "E:\\idea_workspace\\workspace_skywalking\\TestAgent\\target\\TestAgent-1.0-SNAPSHOT.jar";
VirtualMachine virtualMachine = null;
List<VirtualMachineDescriptor> listAfter = null;
while (true){
listAfter = VirtualMachine.list();
for (VirtualMachineDescriptor descriptor : listAfter) {
if(!listBefore.contains(descriptor)){//发现新的JVM
System.out.println("attach JVM");
virtualMachine = VirtualMachine.attach(descriptor);//attach到新JVM
virtualMachine.loadAgent(jar);//加载agentmain所在的jar包
virtualMachine.detach();
return;
}
}
Thread.sleep(1000);
}
}
}
View Code
jar包内容
package com.fh;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
/**
* attach api
*/
public class TestAgent {
public static void agentmain(String agentArgs,
Instrumentation inst) throws UnmodifiableClassException {
// 是对一个Java虚拟机的抽象,在Attach工具程序监控目标虚拟机的时候会用到此类
// 提供类JVM枚举,Attach,Detach等基本操作
// VirtualMachine
// 描述虚拟机的容器类
// VirtualMachineDescriptor
inst.addTransformer(new TestAgent1.Transformer(),true);
inst.retransformClasses(TestClass.class);
System.out.println("premain done");
}
public static void agentmain(String agentArgs){
}
}
View Code
pom文件中的Premain-class替换成Agent-class,还需要添加Can-Retransform-Classes标签为true
之后进行进行启动,可以先启动attach程序,然后启动main方法,attachMain会检测运行中的虚拟机,然后将jar包附着着新的虚拟机上