在Java SE5时代,Instrument只提供了premain一种方式,即在真正的应用程序(包含main方法的程序)main方法启动前启动一个代理程序。而且JDK5之后又提供了类似的新特性,大家百度上找吧。
第1步:DEMO APP
我有一个读文件的,或者是发送URL的请求,例如我想知道读这文件,或者URL请求的耗时情况。
这里有个前提就是无代码侵入。
代码:
package jl.demo; import java.io.File; import java.io.IOException; import org.apache.commons.io.FileUtils; /** * Hello world! * */ public class App { public static void main(String[] args) throws IOException { String content = FileUtils.readFileToString(new File("C:\\test\\openstack.txt")); System.out.println("Hello World!"); } }
FileUtils为apache的 commons-io的2.5版本的类。
其中readFileToString方法如下:
public static String readFileToString(final File file) throws IOException { return readFileToString(file, Charset.defaultCharset()); }
如果把埋点放在这个里面,这样所有调用这个方法的请求的耗时情况,都会统计进来。
pom.xml
<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>jl</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>demo</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.5</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>3.0.0</version> <configuration> <archive> <manifest> <mainClass>jl.demo.App</mainClass> </manifest> </archive> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
如上,需求准备完毕,开始埋点。
第2步:Agent
修改原理就是premain方法,使用的Jar包就是javassist,也有asm可以用,自己随便选。
agent:
package jl.agent; import java.lang.instrument.Instrumentation; public class Agent { /** * This method is called before the application’s main-method is called, when * this agent is specified to the Java VM. **/ public static void premain(String agentArgs, Instrumentation inst) { inst.addTransformer(new MyClassFileTransformer()); } }
transformer:
package jl.agent; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.security.ProtectionDomain; import javassist.CannotCompileException; import javassist.ClassPool; import javassist.CtBehavior; import javassist.CtClass; import javassist.NotFoundException; public class MyClassFileTransformer implements ClassFileTransformer { public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] oldClassBuffer) throws IllegalClassFormatException { if (!className.equals("org/apache/commons/io/FileUtils")) { return oldClassBuffer; } System.out.println("Transforming class:" + className); CtClass ctClass = null; try { ctClass = ClassPool.getDefault().makeClass(new java.io.ByteArrayInputStream(oldClassBuffer)); if (ctClass.isInterface() == false) { CtBehavior[] methods = ctClass.getDeclaredBehaviors(); for (int i = 0; i < methods.length; i++) { if (!isTargetMethod(methods[i])) { continue; } transformMethod(methods[i]); } return ctClass.toBytecode(); } } catch (Exception e) { e.printStackTrace(); } finally { if (ctClass != null) { ctClass.detach(); } } return oldClassBuffer; } private boolean isTargetMethod(CtBehavior method) throws NotFoundException { if (!"readFileToString".equals(method.getName())) { return false; } CtClass[] parameterTypes = method.getParameterTypes(); if (parameterTypes.length == 1) { // 这里只验证一下个数,省事了,重名的方法,我们调用的只有一个参数 return true; } return false; } private void transformMethod(CtBehavior method) throws NotFoundException, CannotCompileException { System.out.println("Transforming method:" + method.getName()); // 第1种方式: // 这里必须先定义本地变量,网上的没有这1步,直接是insertBefore=long // stime=System.nanoTime();,不晓得他们是怎么跑的通的 method.addLocalVariable("stime", CtClass.longType); method.insertBefore("stime=System.nanoTime();"); method.insertAfter("System.out.println(\"" + method.getName() + " cost:\"+(System.nanoTime()-stime));"); // 第2种方式:可以使用ExprEditor直接修改方法体,没有验证过 // method.instrument(new ExprEditor() { // public void edit(MethodCall m) throws CannotCompileException { // m.replace("{ long stime = System.nanoTime(); $_ = $proceed($$); // System.out.println(\"" // + m.getClassName() + "." + m.getMethodName() + // ":\"+(System.nanoTime()-stime));}"); // } // }); } }
MANIFEST.MF
Manifest-Version: 1.0 Built-By: Administrator Class-Path: javassist-3.12.1.GA.jar Created-By: Apache Maven 3.5.3 Build-Jdk: 1.8.0_121 Premain-Class: jl.agent.Agent
pom.xml,这里注意,配置了MANIFEST.MF位置,因为Premain-Class在这个plugin指定不了,所以这样配置了。
<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>jl</groupId> <artifactId>agent</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>agent</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>javassist</groupId> <artifactId>javassist</artifactId> <version>3.12.1.GA</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>3.0.0</version> <configuration> <archive> <manifestFile>src/main/resources/META-INF/MANIFEST.MF</manifestFile> <manifest> <addClasspath>true</addClasspath> </manifest> </archive> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
第3步:打包
run as --> manven install后会在target目录下生成对应的打包文件。
第4步:运行
第1种方式:eclipse中
在App.java的run configuractions中配置arguments-->vm arguments:
-javaagent:E:\workspaces\eclipse\agent\target\agent-0.0.1-SNAPSHOT-jar-with-dependencies.jar
然后直接run支持,结果如下:
Transforming class:org/apache/commons/io/FileUtils Transforming method:readFileToString readFileToString cost:6521989 Hello World!
可以看到已经成功拦截
第2种方式:命令行
进入demo的jar包所在目录:E:\workspaces\eclipse\demo\target
命令:
PS E:\workspaces\eclipse\demo\target> java -javaagent:E:\workspaces\eclipse\agent\target\agent-0.0.1-SNAPSHOT-jar-with-dependencies.jar -cp demo-0.0.1-SNAPSHOT-jar-with-dependencies .jar jl.demo.App
输出:
PS E:\workspaces\eclipse\demo\target> java -javaagent:E:\workspaces\eclipse\agent\target\agent-0.0.1-SNAPSHOT-jar-with-dependencies.jar -cp demo-0.0.1-SNAPSHOT-jar-with-dependencies .jar jl.demo.App Transforming class:org/apache/commons/io/FileUtils Transforming method:readFileToString readFileToString cost:6736224 Hello World! PS E:\workspaces\eclipse\demo\target>
参考:
实战的:
https://blog.csdn.net/pwlazy/article/details/5109742
如下这个详细描述了Instrument相关功能,有很多其他特性及实例参考,非常好。
其实原先参考的那一篇文章,忘了名字,没有搜索到,发现这篇是一样的,记录下,方便参考。
http://zhxing.iteye.com/blog/1703305
有一往篇介绍原理的,图文配,很好理解,介绍了字码节切入的时机,没有找到这篇文件。。。
回头
再看一下我们拦截的方法:
public static String readFileToString(final File file) throws IOException { return readFileToString(file, Charset.defaultCharset()); }
我们在方法前插入了句,在方法后插入了一句,来统计耗时。但是,对于这个只有一个return内容的方法,在方法后到底插入到了哪里?是否真正统计到了该方法的全部执行过程的耗时。
因此,在MyClassFileTransformer中,将替换好的字节码:return ctClass.toBytecode();保存到文件FileUtils
中。
然后使用命令:
PS C:\test> javap -v FileUtils >> aaa.txt
将字节码反编译的内容保存到aaa.txt中,打开这个文件,找到拦截的方法java.lang.String readFileToString(java.io.File) ,可以看到我们插入的内容在哪里,在其调用readFileToString方法的前面和后面:
public static java.lang.String readFileToString(java.io.File) throws java.io.IOException; descriptor: (Ljava/io/File;)Ljava/lang/String; flags: ACC_PUBLIC, ACC_STATIC Code: stack=8, locals=5, args_size=1 0: invokestatic #1167 // Method java/lang/System.nanoTime:()J 3: lstore_1 4: aload_0 5: invokestatic #103 // Method java/nio/charset/Charset.defaultCharset:()Ljava/nio/charset/Charset; 8: invokestatic #214 // Method readFileToString:(Ljava/io/File;Ljava/nio/charset/Charset;)Ljava/lang/String; 11: goto 14 14: astore 4 16: getstatic #1170 // Field java/lang/System.out:Ljava/io/PrintStream; 19: new #1172 // class java/lang/StringBuffer 22: dup 23: invokespecial #1174 // Method java/lang/StringBuffer."<init>":()V 26: ldc_w #1176 // String readFileToString cost: 29: invokevirtual #1179 // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer; 32: invokestatic #1167 // Method java/lang/System.nanoTime:()J 35: lload_1 36: lsub 37: invokevirtual #1182 // Method java/lang/StringBuffer.append:(J)Ljava/lang/StringBuffer; 40: invokevirtual #1184 // Method java/lang/StringBuffer.toString:()Ljava/lang/String; 43: invokevirtual #1189 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 46: aload 4 48: areturn LineNumberTable: line 1800: 4 LocalVariableTable: Start Length Slot Name Signature 0 14 0 file Ljava/io/File; 0 14 1 stime J StackMapTable: number_of_entries = 1 frame_type = 255 /* full_frame */ offset_delta = 14 locals = [ class java/io/File, long ] stack = [ class java/lang/String ] Exceptions: throws java.io.IOException Deprecated: true RuntimeVisibleAnnotations: 0: #575()
javap命令,及字节码的参考:
https://www.cnblogs.com/royi123/p/3569926.html
完结。
相关推荐
1. **编写JavaAgent**:首先创建一个实现了`java.lang.instrument.Instrumentation`接口的类,该类包含`agentmain`方法,用于接收`Instrumentation`实例,这个实例提供了对类加载和字节码操作的接口。 2. **定义...
3. **操作类字节码**:Java Agent接收到Instrumentation实例后,可以通过`retransformClasses`或`appendToBootstrapClassLoaderSearch`等方法来改变类的行为。其中,`retransformClasses`可以在运行时改变已加载类的...
通过解析类的字节码,我们可以插入新的方法、修改现有方法的实现,甚至改变类的结构。在JavaAgent的例子中,Javaassist是用于在程序运行时实现代码织入的关键工具。 `VirtualMachine`类来自`sun.jvm.attach`包(在...
2. **Agent类**:实现了`java.lang.instrument.Instrumentation`接口的类,用于接收JVM传递的`Instrumentation`实例,通过这个实例可以实现字节码的修改。 3. **Advice**:这部分代码定义了如何修改目标类的方法,...
在Java中,通过`PreMain-Class`属性启动Agent或者使用`instrument.addTransformer`方法将自定义的`ClassFileTransformer`注册到Instrumentation实例中,即可实现在类被加载到JVM之前或之后对其进行修改。 在自测和...
3. **使用`javassist`修改字节码**: 在`premain`方法中,你可以使用`javassist`来查找和修改类。首先,加载目标类,然后创建`CtClass`对象,接着就可以修改类的方法或者添加新的方法。以下是一个简单的例子,展示...
当一个JavaAgent被加载时,JVM会调用`premain`方法,并传递一个`Instrumentation`实例,我们可以用这个实例来实现字节码的修改。例如,我们可以使用ASM、ByteBuddy或javassist等字节码库来进行增强。 对于"补丁不重...
Java Agent可以使用`ClassFileTransformer`接口来转换类文件,实现在运行时对字节码的操作。例如,我们可以使用ASM、ByteBuddy或Javassist等库来动态生成或修改字节码,从而在方法调用前后插入数据收集代码。 ```...
当一个类被加载时,Instrumentation 可以调用这个接口来修改类的字节码。 - **JVMTI (Java Virtual Machine Tool Interface)**:是一个低级别的接口,提供了更多的控制能力,包括访问底层虚拟机的状态和事件通知等...
Java开发工具包(JDK)中的Instrumentation接口是Java虚拟机(JVM)的一个强大特性,它允许在类被加载到内存之前对字节码进行动态修改。这种能力使得开发者可以实现诸如性能监控、内存分析、代码插桩等高级功能。在...
本示例“测量Java对象大小的demo”提供了一种方法,通过使用Java的`java.lang.instrument`包来注入`javaagent`,进而利用`Instrumentation`接口测量Java对象的精确内存占用。下面我们将详细探讨这一过程。 首先,`...
Java Instrument API提供了一套工具和接口,使得开发者可以插入自定义的字节码来修改已加载的类或者在类加载时进行干预。BTrace使用这个API获取`Instrumentation`实例,进而利用ASM生成的字节码对目标类进行改造。...
在Java中,通过使用`java.lang.instrument`包提供的API,可以实现对网络流量的拦截、分析甚至修改,这在网络安全、性能优化和故障排查等领域有着广泛的应用。 Ja-Netfilter的核心概念可能包括以下几点: 1. **...
现在,当类被加载或重新加载时,`MyTransformer`的`transform()`方法会被调用,我们可以在这里进行类的重定义操作,例如添加新的方法、修改已有方法的字节码等。 总的来说,Java动态编译和`Instrumentation`结合...
2. 实现一个ClassFileTransformer,它负责拦截类的加载过程,并在类被加载之前修改字节码。 3. 使用java.lang.instrument.Instrumentation接口的retransformClasses方法来重新加载和应用新的字节码。 4. 在...
类文件转换器是实现自定义字节码操作的关键,它接收一个字节码表示的类,并返回可能修改过的字节码。常见的使用场景包括:添加日志,性能统计,AOP代理等。 5. **ASM、ByteBuddy等字节码库**: 在实际开发中,...
`java.lang.instrument.Instrumentation` 接口提供了与代理相关的功能,如类加载前的拦截和字节码修改。 总结来说,“my-java-explore”项目涵盖了Java编程中的多个高级主题,通过学习这些内容,开发者可以更好地...