一.背景
在深入理解Java虚拟机的过程中,理解java程序在虚拟机层次如何执行十分重要。了解了深层次的东西,才可以实现一般情况下做不到的特殊功能,而这种特殊功能面向的对象往往是程序员本身。下面我们通过一个实例进行学习。
二.需求
已有一个编译好的class文件,这个文件中只有一个类,并且有一个main方法。这个方法中调用了System.out.println()输出了一些信息。现在我们想运行这个程序,确切的说是调用这个文件的main方法,将输出的信息打印到一个文件中,但是与此同时,为了不影响其他程序的正常输出,不能改变System的标准输出对象。此外,我们还希望在向文件中输出信息时,可以在每条信息前加上序号。另外我们没有这个class文件的源代码。
三.思路
要运行这个类的main方法,可以使用反射的方式。难点在于标准输出重定向,如果直接使用System.setOut()方法,会改变System的标准输出对象,因此不能采用。
这里通过一个偷梁换柱的方式,直接修改class文件,将对System类的调用指向我们自己定义的HackSystem类,并重新封装一个PrintStream类作为HackSystem的out对象,从而实现添加行号的功能。
四.实现
字节工具类
package main;
import java.io.UnsupportedEncodingException;
import javax.xml.stream.events.StartDocument;
public class BytesUtil
{
/**
*
* @param b 字节数组高位在前,第0个字节是最高位字节
* @param start
* @param len
* @return
*/
public static int bytes2Int(byte[] b, int start, int len)
{
int sum = 0;
int end = start + len;
for (int i = start; i < end; i++)
{
// 字节转无符号整数
int n = ((int) b[i]) & 0xff;
// 考虑到一个字节八位,将高位字节的值左移 右侧字节个数*8位
n <<= (--len) * 8;
sum += n;
}
return sum;
}
/**
* 将value用len长度的字节数组表示,要求value为无符号整数,字节数组高位在前
* @param value
* @param len
* @return
*/
public static byte[] int2Bytes(int value,int len) {
byte[] b = new byte[len];
for (int i = 0; i < len; i++)
{
//从低位到高位填充字节数组
//只考虑无符号情况,不考虑value为负
b[len-1-i] = (byte) (value >>> (8*i));
}
return b;
}
/**
* 返回字节数组UTF-8解码后的字符串
* @param b
* @param start
* @param len
* @return
*/
public static String bytes2String(byte[] b,int start,int len)
{
try
{
return new String(b,start,len,"UTF-8");
} catch (UnsupportedEncodingException e)
{
e.printStackTrace();
return null;
}
}
/**
*
* @param string
* @return
*/
public static byte[] string2Bytes(String string)
{
try
{
return string.getBytes("UTF-8");
} catch (UnsupportedEncodingException e)
{
e.printStackTrace();
return null;
}
}
/**
* 用给定的字节数组替换指定字节数组中的部分字节
* @param src
* @param offset
* @param length
* @param replaceBytes
* @return
*/