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
pom文件需要引入的信息,已经在上面的pom文件中添加,修改premain-class信息即可
还需要添加Can-Retransform-Classes标签为true
2、Attach API
为了更好的灵活性,在Java6之后提供的,可以在main方法之后后执行agentmain方法添加一下特殊的功能
添加要被监听的方法main
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
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文件中的Premain-class替换成Agent-class,还需要添加Can-Retransform-Classes标签为true
之后进行进行启动,可以先启动attach程序,然后启动main方法,attachMain会检测运行中的虚拟机,然后将jar包附着着新的虚拟机上