9.3.3 实现
在程序实现部分,我们主要看看代码和里面的注释。首先看看实现过程中需要用到的4个支持类。 第一个类用于实现“同一个类的代码可以被多次加载”这个需求,即用于解决9.2节列举的第二个问题的HotSwapClassLoader,具体程序如代码清单9-3所示。
HotSwapClassLoader所做的事情仅仅是公开父类(即java.lang.ClassLoader)中的protected方法defineClass(),我们将会使用这个方法把提交执行的Java类的byte[]数组转变为Class对象。 HotSwapClassLoader中并没有重写loadClass()或findClass()方法,因此如果不算外部手工调用loadByte() 方法的话,这个类加载器的类查找范围与它的父类加载器是完全一致的,在被虚拟机调用时,它会按照双亲委派模型交给父类加载。构造函数中指定为加载HotSwapClassLoader类的类加载器作为父类加载器,这一步是实现提交的执行代码可以访问服务端引用类库的关键,下面我们来看看代码清单9-3。
代码清单9-3 HotSwapClassLoader的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
public class HotSwapClassLoader extends ClassLoader { public HotSwapClassLoader() { super(HotSwapClassLoader.class.getClassLoader()); } public Class loadByte(byte[] classByte) { return defineClass(null, classByte, 0, classByte.length); } }
|
第二个类是实现将java.lang.System替换为我们自己定义的HackSystem类的过程,它直接修改符合Class文件格式的byte[]
数组中的常量池部分,将常量池中指定内容的CONSTANT_Utf8_info常量替换为新的字符串,具体代码如下面的代码清单9-4所示。ClassModifier中涉及对byte[]
数组操作的部分, 主要是将byte[]
与int和String互相转换,以及把对byte[]
数据的替换操作封装在代码清单9-5所示的ByteUtils中。
经过ClassModifier处理后的byte[]数组才会传给HotSwapClassLoader.loadByte()方法进行类加载, byte[]数组在这里替换符号引用之后,与客户端直接在Java代码中引用HackSystem类再编译生成的Class 是完全一样的。这样的实现既避免了客户端编写临时执行代码时要依赖特定的类(不然无法引入HackSystem),又避免了服务端修改标准输出后影响到其他程序的输出。下面我们来看看代码清单9-4 和代码清单9-5。
代码清单9-4 ClassModifier的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
|
public class ClassModifier {
private static final int CONSTANT_POOL_COUNT_INDEX = 8;
private static final int CONSTANT_Utf8_info = 1;
private static final int[] CONSTANT_ITEM_LENGTH = {-1, -1, -1, 5, 5, 9, 9, 3, 3, 5, 5, 5, 5}; private static final int u1 = 1; private static final int u2 = 2; private byte[] classByte; public ClassModifier(byte[] classByte) { this.classByte = classByte; }
public byte[] modifyUTF8Constant(String oldStr, String newStr) { int cpc = getConstantPoolCount(); int offset = CONSTANT_POOL_COUNT_INDEX + u2; for (int i = 0;i < cpc;i++) { int tag = ByteUtils.bytes2Int(classByte, offset, u1); if (tag == CONSTANT_Utf8_info) { int len = ByteUtils.bytes2Int(classByte, offset + u1, u2); offset += (u1 + u2); String str = ByteUtils.bytes2String(classByte, offset, len); if (str.equalsIgnoreCase(oldStr)) { byte[] strBytes = ByteUtils.string2Bytes(newStr); byte[] strLen = ByteUtils.int2Bytes(newStr.length(), u2); classByte = ByteUtils.bytesReplace(classByte, offset - u2, u2, strLen); classByte = ByteUtils.bytesReplace(classByte, offset, len, strBytes); return classByte; } else { offset += len; } } else { offset += CONSTANT_ITEM_LENGTH[tag]; } } return classByte; }
public int getConstantPoolCount() { return ByteUtils.bytes2Int(classByte, CONSTANT_POOL_COUNT_INDEX, u2); } }
|
代码清单9-5 ByteUtils的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
public class ByteUtils { 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; n <<= (--len) * 8; sum = n + sum; } return sum; } public static byte[] int2Bytes(int value, int len) { byte[] b = new byte[len]; for (int i = 0;i < len;i++) { b[len - i - 1] = (byte) ((value >> 8 * i) & 0xff); } return b; } public static String bytes2String(byte[] b, int start, int len) { return new String(b, start, len); } public static byte[] string2Bytes(String str) { return str.getBytes(); } public static byte[] bytesReplace(byte[] originalBytes, int offset, int len, byte[] replaceBytes) { byte[] newBytes = new byte[originalBytes.length + (replaceBytes.length - len)]; System.arraycopy(originalBytes, 0, newBytes, 0, offset); System.arraycopy(replaceBytes, 0, newBytes, offset, replaceBytes.length); System.arraycopy(originalBytes, offset + len, newBytes, offset + replaceBytes.length, originalBytes.length - offset - len); return newBytes; } }
|
最后一个类就是前面提到过的用来代替java.lang.System的HackSystem,这个类中的方法看起来不少,但其实除了把out和err两个静态变量改成使用ByteArrayOutputStream作为打印目标的同一个PrintStream对象,以及增加了读取、清理ByteArrayOutputStream中内容的getBufferString()和clearBuffer()方法外,就再没有其他新鲜的内容了。其余的方法全部都来自于System类的public方法, 方法名字、参数、返回值都完全一样,并且实现也是直接转调了System类的对应方法而已。保留这些方法的目的,是为了在Sytem被替换成HackSystem之后,保证执行代码中调用的System的其余方法仍然可以继续使用,HackSystem的实现如代码清单9-6所示。
代码清单9-6 HackSystem的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
|
public class HackSystem { public final static InputStream in = System.in; private static ByteArrayOutputStream buffer = new ByteArrayOutputStream(); public final static PrintStream out = new PrintStream(buffer); public final static PrintStream err = out; public static String getBufferString() { return buffer.toString(); } public static void clearBuffer() { buffer.reset(); } public static void setSecurityManager(final SecurityManager s) { System.setSecurityManager(s); } public static SecurityManager getSecurityManager() { return System.getSecurityManager(); } public static long currentTimeMillis() { return System.currentTimeMillis(); } public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length) { System.arraycopy(src, srcPos, dest, destPos, length); } public static int identityHashCode(Object x) { return System.identityHashCode(x); } }
|
4个支持类已经讲解完毕,我们来看看最后一个类JavaclassExecuter,它是提供给外部调用的入 口,调用前面几个支持类组装逻辑,完成类加载工作。JavaclassExecuter只有一个execute()方法,用输 入的符合Class文件格式的byte[]数组替换掉java.lang.System的符号引用后,使用HotSwapClassLoader加 载生成一个Class对象,由于每次执行execute()方法都会生成一个新的类加载器实例,因此同一个类可 以实现重复加载。然后反射调用这个Class对象的main()方法,如果期间出现任何异常,将异常信息打 印到HackSystem.out中,最后把缓冲区中的信息作为方法的结果来返回。JavaclassExecuter的实现代码 如代码清单9-7所示。
代码清单9-7 JavaclassExecuter的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
public class JavaclassExecuter {
public static String execute(byte[] classByte) { HackSystem.clearBuffer(); ClassModifier cm = new ClassModifier(classByte); byte[] modiBytes = cm.modifyUTF8Constant("java/lang/System", "org/fenixsoft/classloading/execute/HackSystem"); HotSwapClassLoader loader = new HotSwapClassLoader(); Class clazz = loader.loadByte(modiBytes); try { Method method = clazz.getMethod("main", new Class[] {String[].class }); method.invoke(null, new String[] {null }); } catch (Throwable e) { e.printStackTrace(HackSystem.out); } return HackSystem.getBufferString(); } }
|