java的Instrument机制
java的Instrument机制,可以对方法进行增强,甚至替换整个类,而这些过程是无侵入式的。类似于spring中的AOP。
Instrumentation类提供控制Java语言程序代码的服务。Instrumentation可以实现在方法插入额外的字节码从而达到收集使用中的数据到指定工具的目的。由于插入的字节码是附加的,这些更变不会修改原来程序的状态或者行为。也就是说,java.lang.instrument包的最大功能就是可以在已有的类上附加(修改)字节码来实现增强的逻辑
1 原理
instrument的底层实现依赖于JVMTI,也就是JVM Tool Interface,它是JVM暴露出来的一些供用户扩展的接口集合,JVMTI是基于事件驱动的,JVM每执行到一定的逻辑就会调用一些事件的回调接口(如果有的话),这些接口可以供开发者去扩展自己的逻辑。
我们使用JVMTIAgent这个名词表示:使用JVMTI暴露的接口提供额外功能的程序。在java1.5之前,只能使用c++实现。但是java1.6之后jdk为我们提供了新的工具JPLISAgent,它可以使用java语言调用JVMTI暴露的接口。
JVMTIAgent是一个利用JVMTI暴露出来的接口提供了代理启动时加载(agent on load)、代理通过attach形式加载(agent on attach)和代理卸载(agent on unload)功能的动态库。而instrument agent可以理解为一类JVMTIAgent动态库,别名是JPLISAgent(Java Programming Language Instrumentation Services Agent),也就是专门为java语言编写的插桩服务提供支持的代理。
使用JPLISAgent方式有2种:
- agent on load
- agent on attach
下面举例介绍启动时加载代理类的方式
2 实例
下面举一个例子说明一下它的使用。
Dog.java
package com.szj; public class Dog { public String hello() { return "wang wang~"; } }
MyMain.java
package com.szj; public class MyMain { public static void main(String[] args) { System.out.println(new Dog().hello()); } }
下面我们实现一个Agent,希望在调用Dog.hello()方法之前打印额外的信息。
GreetingTransformer.java
package com.szj; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.security.ProtectionDomain; import java.util.Date; public class GreetingTransformer implements ClassFileTransformer { @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { if ("com/szj/Dog".equals(className)) { System.out.println("Dog's method invoke at " + new Date()); } return null; } }
对类进行instrument(可翻译为装配)的第一步是编写一个GreetingTransformer类,其继承自ClassFileTransformer,打印的额外信息便编写在其中,对于其他参数,暂时先不用纠结,因为我们仅仅要实现一个类似AOP的功能,其他参数还不需要了解他们。
GreetingAgent.java
package com.szj; import java.lang.instrument.Instrumentation; public class GreetingAgent { public static void premain(String options, Instrumentation ins) { if (options != null) { System.out.printf("I've been called with options: %s", options); } else{ System.out.println("I've been called with no options."); } ins.addTransformer(new GreetingTransformer()); } }
这个类便是代理类,里面只有一个方法premain方法。
MANIFEST.MF
我们最终会把GreetingTransformer和GreetingAgent打成一个jar包,然后使用-javaagent参数使用它,所以我们需要一个MANIFEST.MF文件
在如下路径下创建resources\META-INF\MANIFEST.MF
Manifest-Version: 1.0 Premain-Class: com.szj.GreetingAgent Can-Redefine-Classes: true
为了打包,我们使用maven插件,创建一个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.szj</groupId> <artifactId>agent</artifactId> <version>1.0.0-SNAPSHOT</version> <build> <finalName>agent</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>2.3.1</version> <configuration> <archive> <manifestFile>resources/META-INF/MANIFEST.MF</manifestFile> </archive> </configuration> </plugin> <plugin> <artifactId>maven-assembly-plugin</artifactId> <configuration> <outputDirectory>${basedir}</outputDirectory> <archive> <index>true</index> <manifest> <addClasspath>true</addClasspath> </manifest> <manifestEntries> <Premain-Class>com.szj.GreetingAgent</Premain-Class> </manifestEntries> </archive> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build> </project>
然后我们编译这几个java文件为class文件
编译之后的class
打包
E:\2022\instrument\com\szj>mvn install [INFO] Scanning for projects... [INFO] [INFO] ---------------------------< com.szj:agent >---------------------------- [INFO] Building agent 1.0.0-SNAPSHOT [INFO] --------------------------------[ jar ]--------------------------------- Downloading from alimaven: http://maven.aliyun.com/nexus/content/groups/public/org/apache/maven/plugins/maven-resources-plugin/2.6/maven-resources-plugin-2.6.pom ...省略 [INFO] Installing E:\2022\instrument\com\szj\target\agent.jar to E:\maven\repository\com\szj\agent\1.0.0-SNAPSHOT\agent-1.0.0-SNAPSHOT.jar [INFO] Installing E:\2022\instrument\com\szj\pom.xml to E:\maven\repository\com\szj\agent\1.0.0-SNAPSHOT\agent-1.0.0-SNAPSHOT.pom [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 43.658 s [INFO] Finished at: 2022-08-11T17:56:01+08:00 [INFO] ------------------------------------------------------------------------
打包之后将会在当前目录下生成target目录,jar包就在这个target目录下
运行结果
红色表示我们使用-javaagent参数传递一个jar,这个jar使用Instructment机制实现。
蓝色为我们希望的效果,即在执行Dog的hello()之前无侵入的添加额外功能。