java.lang.instrument包初步学习

上周给小组几位师弟、实习生开会的时候,又谈到instrumentation这个现在还没解决的问题。我看过的论文中,对于Java的Instrumentation,大部分研究都在用 Soot、 JChord,后来想起,看过的论文中还有用 BCEL和 Javassist的。前两者我们花了很长时间学习,但是到现在效果并不是很好。

突然想起应该看看JDK自带的java.lang.instrument这个包,昨天花了点时间学习了一下。在JDK 5之前,相应的包是属于Java Virtual Machine Profiler Interface (JVMPI),在JDK 5之后,改为Java Virtual Machine Tool Interface (JVM TI),这个包就属于JVM TI的一个alternate。在页面:

http://docs.oracle.com/javase/6/docs/technotes/guides/instrumentation/index.html

中,有对这个包的详细介绍。

上面这个页面链接到这个包的详细介绍:http://docs.oracle.com/javase/6/docs/api/java/lang/instrument/package-summary.html

从这个页面中,我们看到,这个包主要的功能是:Provides services that allow Java programming language agents to instrument programs running on the JVM. The mechanism for instrumentation is modification of the byte-codes of methods.

从上面详细页面中,可以看到该包有ClassFileTransformer和Instrumentation,今天我们看一下怎么通过ClassFileTransformer这个接口来实现动态修改类代码。

依然在这个页面中,"An agent is deployed as a JAR file. An attribute in the JAR file manifest specifies the agent class which will be loaded to start the agent. For implementations that support a command-line interface, an agent is started by specifying an option on the command-line."

其实这句话就说的很清楚了,我们要实现动态修改类代码这个功能,需要实现一个agent,并且这个agent应该以Jar包的形式,通过命令行调用。

这里直接上代码解释比较好,首先,我们写的Agent要实例化ClassFileTransformer这个接口:

public class test implements ClassFileTransformer

其次,按照上面页面中的介绍,The agent class must implement a public static premain method similar in principle to themain application entry point. 这个agent类需要实现一个premain方法:

    public static void premain(String options, Instrumentation ins) {
        if (options != null) {
            System.out.printf("  I've been called with options: \"%s\"\n",
                    options);
        } else
            System.out.println("  I've been called with no options.");
        ins.addTransformer(new test());
    }

然后,ClassFileTransformer这个接口定义了一个方法:transform,我们需要对其进行实例化:

    public byte[] transform(ClassLoader loader, String className, Class cBR,
            java.security.ProtectionDomain pD, byte[] classfileBuffer)
            throws IllegalClassFormatException {
        if(!className.endsWith("HelloWorld"))
        	return(null);
        
        String line="";
        for(int i=0;i<classfileBuffer.length;i++){
        	line +=Byte.toString(classfileBuffer[i])+" ";
        	if(line.length()>60){
        		System.out.println(line);
        		line="";
        	}
        	if(classfileBuffer[i]==(byte)'6')
        		classfileBuffer[i]=(byte)'7';
        }
        System.out.println(line);
        System.out.println("The number of bytes in HelloWorld: "+classfileBuffer.length);
        return(classfileBuffer);
    }

其实这段代码的作用很简单,就是判断如果执行的类名称为HelloWorld的时候,print出这个类的内容,同时,如果HelloWorld类代码中有"6"这个字符,其将被自动替换为"7"。

为了验证上面的功能,我们生成一个HelloWorld类:

public class HelloWorld{
	public static void main(String[] args)
	{
		System.out.println("The number six is 6");
	}
}

如果要运行上面的test类,需要生成一个Jar包,在Eclipse中生成Jar包的方法很简单:我们首先需要生成一个MANIFEST.MF文件(保存在工程的任意位置均可),需要在这个文件中指定代理的名称:

Manifest-Version: 1.0
Premain-Class: test

其次,在工程名上点右键——"Export"——选择Java目录下的JAR File——一直点击Next直到需要设置MANIFEST.MF文件为止,选择"Use existing manifest from workspace"——然后选择刚才创建的MANIFEST.MF文件,再生成Jar包就可以了。

我们把生成的Jar包和HelloWorld类的Class文件放到同一个目录下,运行:

java -javaagent:test.jar="HelloWorld.main" HelloWorld

然后就可以得到如下图所示的运行结果:

说明通过test这个agent,已经动态更改了HelloWorld这个类的内容。到这里我们的目的也就实现了。

最后附上test.java的源代码:

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;

/**
 * A trivial example program that basically just says hello!
 */
public class test implements ClassFileTransformer {

    public static void premain(String options, Instrumentation ins) {
        if (options != null) {
            System.out.printf("  I've been called with options: \"%s\"\n",
                    options);
        } else
            System.out.println("  I've been called with no options.");
        ins.addTransformer(new test());
    }

    public byte[] transform(ClassLoader loader, String className, Class cBR,
            java.security.ProtectionDomain pD, byte[] classfileBuffer)
            throws IllegalClassFormatException {
        if(!className.endsWith("HelloWorld"))
        	return(null);
        
        String line="";
        for(int i=0;i<classfileBuffer.length;i++){
        	line +=Byte.toString(classfileBuffer[i])+" ";
        	if(line.length()>60){
        		System.out.println(line);
        		line="";
        	}
        	if(classfileBuffer[i]==(byte)'6')
        		classfileBuffer[i]=(byte)'7';
        }
        System.out.println(line);
        System.out.println("The number of bytes in HelloWorld: "+classfileBuffer.length);
        return(classfileBuffer);
    }
}

同时附带上jar包和HelloWorld的class文件:

http://files.cnblogs.com/quyu/instrument-ClassFileTransformer.rar

你可能感兴趣的:(java.lang.instrument包初步学习)