前言
在之前的文章里我们介绍了ASM字节码框架,使用它可以动态的修改class文件。但是仔细一想,你会发现仅仅ASM并不能真正用于生产,为什么?假如你已经有一个在运行的系统了,现在想要做一些字节码修改的动作,难道我们要去修改源代码吗?麻烦不说,而且污染了本来的系统。
所以我们就考虑,有没有什么方法,可以实现动态的无污染的织入,这就要引入今天的主角,Instrument了。
正文
Instrumentation是Javaagent
的一种具体实现,那javaagent
又是什么?如果你在终端里输入java
(当然前提是你已经安装了jdk), 你会看到这么几个参数:
其中,-javaagent
就是我们所说的,jdk提供的Instrument
允许我们在jvm启动或者运行时,动态地拦截要加载的类,并对其进行修改。无论是启动时还是运行时,其大致原理都是把我们的修改代码封装成一个Jar包,然后想办法让目标jvm进程加载。
instrumentation介绍
先上一张图:
instrumentation是什么?jdk中的一个接口,我们看看这个接口提供了哪些方法:
这里我也框出来了常用的几个方法,可以看到这几个方法基本都和ClassFileTransformer
这个接口有关,那我们继续看下ClassFileTransformer
的介绍。
其实从名字可以大概看出,ClassFileTransformer
是对class文件进行转换的,再通俗点,就是用来修改字节码的。ClassFileTransformer
这个接口只有一个方法transform
:
1 | byte[] |
其中loader
参数是加载这个类的类加载器,classfileBuffer
就是载入内存中的class文件。可以看到这个方法返回值是个字节数组,如果返回null, 则表示对class文件不做处理,否则就用返回的字节数组代替原来的类。
当我们使用addTransformer
给instrumentation
添加ClassFileTransformer
后,后续所有JVM加载类的时候都会被ClassFileTransformer
的transform
方法拦截。
上面大概介绍了instrumentation
和ClassFileTransformer
,下面的部分会介绍这两个东西如何结合起来使用。如上文所说,instrumentation
的原理大概是:把我们的修改代码封装成一个Jar包(即agent),然后想办法让目标jvm进程加载。那就涉及到一个问题:目标jvm何时加载?java agent提供了两种手段,分别是在jvm启动时加载和jvm运行时加载。
JVM启动时加载instrument agent
主要用到了premain()
方法, 从名字也可以看出,premain其实就是在main函数之前执行,所以也就是会在main函数执行之前拦截类的加载,并做一些改造,premain函数如下:
1 | public static void premain(String agentArgs, Instrumentation inst) |
其中第一个参数agentArgs是agent启动时的参数,第二个参数就是我们的主角,instrumentation. 一般的一个操作流程是使用instrumentation的addTransformer方法添加一个ClassFileTransformer
, 而这个ClassFileTransformer
里面的transform
方法一般就是我们施展拳脚的地方,在这里可以对字节码进行修改等操作。下面的代码实现了一个简单的Agent
1 | import java.lang.instrument.ClassFileTransformer; |
然后我们需要把这个agent打包成jar包,这里我使用maven打包,通过配置pom.xml文件,核心内容如下:
1 | <plugin> |
使用mvn package
打包成Jar包,打包后的文件在/target目录下。
完成上述操作后,我们再单独建一个项目,来测试我们的agent,
1 | public class Test { |
如果是在idea里运行的话,我们需要在add configuration
里面配置一下:
主要就是关注下这个javaagent参数就行,然后运行,走起,输出如下:
1 | agent args: null |
至此,流程走通。
那么这种jvm启动时就加载agent的方式有没有什么问题呢?首先好处肯定是有的,因为此时agent加载的时候大部分类还没加载,这个时候可以实现对新加载的类的进行字节码修改。但是!如果premain方法执行失败或者抛异常,那么jvm进程会被终止,这就有点难以接受了。(这段话摘自占小狼的博客)
因此,在jdk1.6中,又提出了另一种方法。
JVM运行时加载instrument agent
主要用到了agentmain()
方法,
instrument原理
使用instrument有什么问题
TODO:
- 类隔离
- 反射