计划自己手写字节码而不是采用asm(完整版)

原创
2014/04/08 09:38
阅读数 601

Beetl2.0 目前采用ASM来写字节码,但我发现ASM占用空间还是比较大,大约45K(压缩成jar后),如果自己手写的话,占用仅仅6K(估算的),所以打算在beetl2.0发布前,改成手写bytecode。

ByteCodeWriter实现了一个属性访问类,通过javap,内容如下

E:\>javap -c Test.class
public class org.beetl.sample.s01.Test extends org.beetl.core.resolver.AttributeAccess {
  public org.beetl.sample.s01.Test();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method org/beetl/core/resolver/AttributeAccess."<init>":()V
       4: return


  public java.lang.Object value(java.lang.Object, java.lang.Object);
    Code:
       0: aload_1
       1: checkcast     #12                 // class org/beetl/core/User
       4: invokevirtual #14                 // Method org/beetl/core/User.getAge:()I
       7: invokestatic  #18                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      10: areturn
}


package org.beetl.core.resolver;

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.beetl.core.lab.TestUser;

public class FiledAccessByteCodeWriter
{

	static final int MAGIC = 0xCAFEBABE;
	static final byte CONS_CLASS = 7;
	static final byte CONS_UTF8 = 1;
	static final byte CONS_METHODREF = 10;
	static final byte CONS_NAME_AND_TYPE = 12;
	static final byte CONS_DOUBLE = 6;

	static final short ALOAD_0 = 42;
	static final short ALOAD_1 = 43;
	static final short ALOAD_2 = 44;
	static final short INVOKE_SPECIAL = 183;
	static final short INVOKE_VIRTUAL = 182;
	static final short RETURN = 177;
	static final short ARETURN = 176;
	static final short CHECK_CAST = 192;
	static final short INVOKE_STATIC = 184;
	static final String parentCls = "org/beetl/core/resolver/AttributeAccess";
	static final String initFunction = "<init>";
	static final String initFunctionDesc = "()V";
	static final String valueFunction = "value";
	static final String valueFunctionDesc = "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;";
	static final String code = "Code";

	//outbox,inbox
	static final String integerClass = "org/beetl/core/util/NumberUtil"; //优化过的拆箱
	static final String valueOfFunction = "valueOf";
	static final String intValueOfFunctionDesc = "(I)Ljava/lang/Integer;";

	static final String shortClass = "java/lang/Short";
	static final String shortValueOfFunctionDesc = "(S)Ljava/lang/Short;";

	static final String booleanClass = "java/lang/Boolean";
	static final String booleanValueOfFunctionDesc = "(Z)Ljava/lang/Boolean;";

	static final String doubleClass = "java/lang/Double";
	static final String doubleValueOfFunctionDesc = "(D)Ljava/lang/Double;";

	static String longClass = "java/lang/Long";
	static String longValueOfFunctionDesc = "(J)Ljava/lang/Long;";

	byte[] cached = new byte[256];
	List<Object[]> constPool = new ArrayList<Object[]>();
	Map<String, Short> utfMap = new HashMap<String, Short>();
	Map<String, Short> classMap = new HashMap<String, Short>();

	String cls = "org/beetl/sample/s01/Test1";
	String targetCls = "org/beetl/core/lab/TestUser";
	String targetFunction = "getAge";
	String targetFunctionDesc = "()I";
	String retByteCodeType = "I";

	static ASMClassLoader loader = new ASMClassLoader();

	static class ASMClassLoader extends ClassLoader
	{
		public Class defineClass(String name, byte[] b)
		{
			return defineClass(name, b, 0, b.length);
		}
	}

	/**
	 * 尝试自己写bytecode代码,代替asm.jar,以缩小beetl大小
	 * @param args
	 */
	public static void main(String[] args) throws Exception
	{
		Class c = TestUser.class;
		String name = "contacts";
		String methodName = "getContacts";
		Class returnType = String[].class;
		FiledAccessByteCodeWriter cw = new FiledAccessByteCodeWriter(c, name, methodName, returnType);
		byte[] bs = cw.getClassByte();

		//		OutputStream ins = new FileOutputStream("e:/Test1.class");
		//		ins.write(bs);
		//		ins.close();
		TestUser user = new TestUser("joelli");
		Class fieldAccessorClass = loader.defineClass(c.getName() + "_" + name, bs);
		AttributeAccess ac = (AttributeAccess) fieldAccessorClass.newInstance();
		Object result = ac.value(user, name);
		System.out.println(result);

	}

	public FiledAccessByteCodeWriter(Class c, String name, String methodName, Class returnType)
	{
		String cname = c.getName().replace(".", "/");
		this.targetCls = cname;
		this.cls = cname + "_" + name;
		this.targetFunction = methodName;
		String[] returnArray = this.getRetrunTypeDesc(returnType);
		String returnTypeClass = returnArray[0];
		this.retByteCodeType = returnArray[1];
		this.targetFunctionDesc = "()" + returnTypeClass;

	}

	public byte[] getClassByte() throws Exception
	{
		ByteArrayOutputStream bs = new ByteArrayOutputStream();
		DataOutputStream out = new DataOutputStream(bs);
		write(out);
		return bs.toByteArray();
	}

	//	public Object getObject() throws Exception
	//	{
	//		FiledAccessByteCodeWriter cw = new FiledAccessByteCodeWriter();
	//		byte[] bs = cw.getClassByte();
	//		Class c = loader.defineClass("org.beetl.sample.s01.Test1", bs);
	//		Object o = c.newInstance();
	//
	//		return o;
	//
	//	}

	public void write(DataOutputStream out) throws Exception
	{

		//第一个占位用
		out.writeInt(MAGIC);
		out.writeShort(0);
		//jdk5
		out.writeShort(49);

		int clsIndex = this.registerClass(this.cls);
		int parentIndex = this.registerClass(this.parentCls);

		byte[] initMethod = getInitMethod();
		byte[] valueMethod = this.getProxyMethod();

		//constpool-size
		out.writeShort(this.constPool.size() + 1);
		writeConstPool(out);
		out.writeShort(33);//public class
		out.writeShort(clsIndex);
		out.writeShort(parentIndex);
		out.writeShort(0); //interface count;
		out.writeShort(0); //filed count;
		//写方法
		out.writeShort(2); //method count;
		out.write(initMethod);
		out.write(valueMethod);

		out.writeShort(0); //class-attribute-info

	}

	public void writeConstPool(DataOutputStream out) throws Exception
	{
		for (Object[] array : this.constPool)
		{
			int tag = (Byte) array[0];
			out.writeByte(tag);
			switch (tag)
			{
				case CONS_CLASS:
					out.writeShort((Short) array[1]);
					break;
				case CONS_UTF8:
					out.writeShort((Short) array[1]);
					byte[] content = (byte[]) array[2];
					out.write(content);
					break;
				case CONS_METHODREF:
					//class & nameAndType
					out.writeShort((Short) array[1]);
					out.writeShort((Short) array[2]);
					break;
				case CONS_NAME_AND_TYPE:
					//name & desc 
					out.writeShort((Short) array[1]);
					out.writeShort((Short) array[2]);
					break;
				default:
					throw new RuntimeException("tag=" + tag);

			}
		}
	}

	public byte[] getProxyMethod() throws Exception
	{
		ByteArrayOutputStream bs = new ByteArrayOutputStream();
		DataOutputStream out = new DataOutputStream(bs);
		out.writeShort(1); //public 
		int nameIndex = this.registerUTFString(this.valueFunction);
		out.writeShort(nameIndex);
		int descIndex = this.registerUTFString(this.valueFunctionDesc);
		out.writeShort(descIndex);
		out.writeShort(1); //attributeCount
		byte[] initCodeAttr = proxyCodeAttr();
		out.write(initCodeAttr);
		return bs.toByteArray();
	}

	public byte[] proxyCodeAttr() throws Exception
	{
		ByteArrayOutputStream bs = new ByteArrayOutputStream();
		DataOutputStream out = new DataOutputStream(bs);
		int index = this.registerUTFString("Code");
		out.writeShort(index);
		byte[] codes = proxyCodes();
		//属性长度
		int attrlen = 4 + 4 + codes.length + 4;
		out.writeInt(attrlen);
		if (this.retByteCodeType.equals("D") || this.retByteCodeType.equals("J"))
		{
			out.writeShort(2);
		}
		else
		{
			out.writeShort(1); //stack,default 1,long or double shoud be 2.
		}

		out.writeShort(3); //local var
		out.writeInt(codes.length);
		out.write(codes); //codes;
		out.writeShort(0); //exceptions
		out.writeShort(0); //attr-info
		return bs.toByteArray();

	}

	public byte[] proxyCodes() throws Exception
	{
		ByteArrayOutputStream bs = new ByteArrayOutputStream();
		DataOutputStream out = new DataOutputStream(bs);
		out.writeByte(ALOAD_1);

		out.writeByte(this.CHECK_CAST);
		short classIndex = this.registerClass(this.targetCls);
		out.writeShort(classIndex);

		out.writeByte(INVOKE_VIRTUAL);
		int methodIndex = registerMethod(this.targetCls, this.targetFunction, this.targetFunctionDesc);
		out.writeShort(methodIndex);

		if (this.retByteCodeType.equals("I"))
		{
			out.writeByte(INVOKE_STATIC);
			methodIndex = registerMethod(this.integerClass, this.valueOfFunction, this.intValueOfFunctionDesc);
			out.writeShort(methodIndex);
		}
		else if (this.retByteCodeType.equals("S"))
		{
			out.writeByte(INVOKE_STATIC);
			methodIndex = registerMethod(this.shortClass, this.valueOfFunction, this.shortValueOfFunctionDesc);
			out.writeShort(methodIndex);
		}
		else if (this.retByteCodeType.equals("D"))
		{
			out.writeByte(INVOKE_STATIC);
			methodIndex = registerMethod(this.doubleClass, this.valueOfFunction, this.doubleValueOfFunctionDesc);
			out.writeShort(methodIndex);
		}
		else if (this.retByteCodeType.equals("J"))
		{
			out.writeByte(INVOKE_STATIC);
			methodIndex = registerMethod(this.longClass, this.valueOfFunction, this.longValueOfFunctionDesc);
			out.writeShort(methodIndex);
		}

		out.writeByte(ARETURN - 256);
		return bs.toByteArray();

	}

	public byte[] getInitMethod() throws Exception
	{
		ByteArrayOutputStream bs = new ByteArrayOutputStream();
		DataOutputStream out = new DataOutputStream(bs);
		out.writeShort(1); //public 
		int nameIndex = this.registerUTFString("<init>");
		out.writeShort(nameIndex);
		int descIndex = this.registerUTFString("()V");
		out.writeShort(descIndex);
		out.writeShort(1); //attributeCount
		byte[] initCodeAttr = initCodeAttr();
		out.write(initCodeAttr);
		return bs.toByteArray();

	}

	public byte[] initCodeAttr() throws Exception
	{
		ByteArrayOutputStream bs = new ByteArrayOutputStream();
		DataOutputStream out = new DataOutputStream(bs);
		int index = this.registerUTFString("Code");
		out.writeShort(index);
		byte[] codes = initCodes();
		//属性长度
		int attrlen = 4 + 4 + codes.length + 4;
		out.writeInt(attrlen);
		out.writeShort(1); //stack
		out.writeShort(1); //local var
		out.writeInt(codes.length);
		out.write(codes); //codes;
		out.writeShort(0); //exceptions
		out.writeShort(0); //attr-info
		return bs.toByteArray();

	}

	public byte[] initCodes() throws Exception
	{
		ByteArrayOutputStream bs = new ByteArrayOutputStream();
		DataOutputStream out = new DataOutputStream(bs);
		out.writeByte(ALOAD_0);
		out.writeByte(INVOKE_SPECIAL);
		int index = registerMethod(this.parentCls, "<init>", "()V");
		out.writeShort(index);
		out.writeByte(RETURN - 256);
		return bs.toByteArray();

	}

	public short registerClass(String clsName)
	{
		Short index = classMap.get(clsName);
		if (index != null)
			return index;
		short clsNameIndex = registerUTFString(clsName);
		Object[] array = new Object[]
		{ this.CONS_CLASS, clsNameIndex };
		this.constPool.add(array);
		index = getCurrentIndex();
		classMap.put(clsName, index);
		return index;
	}

	public short registerMethod(String clsName, String method, String desc)
	{

		short clsNameIndex = this.registerClass(clsName);
		short nameAndTypeIndex = registerNameAndType(method, desc);

		Object[] array = new Object[]
		{ this.CONS_METHODREF, clsNameIndex, nameAndTypeIndex };
		this.constPool.add(array);
		return getCurrentIndex();
	}

	public short registerNameAndType(String method, String desc)
	{
		short nameIndex = registerUTFString(method);
		short descIndex = registerUTFString(desc);
		Object[] array = new Object[]
		{ this.CONS_NAME_AND_TYPE, nameIndex, descIndex };
		this.constPool.add(array);
		return getCurrentIndex();
	}

	public short registerUTFString(String str)
	{
		Short index = this.utfMap.get(str);
		if (index != null)
			return index;
		try
		{
			byte[] bs = str.getBytes("UTF-8");
			short len = (short) bs.length;
			Object[] array = new Object[]
			{ CONS_UTF8, len, bs };
			this.constPool.add(array);
			index = getCurrentIndex();
			this.utfMap.put(str, index);
			return index;
		}
		catch (UnsupportedEncodingException e)
		{
			//not happen
			throw new RuntimeException(e);
		}

	}

	public short getCurrentIndex()
	{
		return (short) (this.constPool.size());
	}

	private String[] getRetrunTypeDesc(Class c)
	{
		StringBuilder sb = new StringBuilder();
		String returnType = "";
		if (c.isArray())
		{
			sb.append(c.getName().replace(".", "/"));
		}
		else if (c == int.class)
		{
			sb.append("I");
			returnType = "I";
		}
		else if (c == boolean.class)
		{
			sb.append("Z");
			returnType = "Z";
		}
		else if (c == char.class)
		{
			sb.append("C");
			returnType = "C";
		}
		else if (c == short.class)
		{
			sb.append("S");
			returnType = "S";
		}
		else if (c == float.class)
		{
			sb.append("F");
			returnType = "F";
		}
		else if (c == long.class)
		{
			sb.append("J");
			returnType = "J";
		}
		else if (c == double.class)
		{
			sb.append("D");
			returnType = "D";
		}
		else
		{
			sb.append("L").append(c.getName().replace(".", "/")).append(";");
			returnType = "L";
		}
		return new String[]
		{ sb.toString(), returnType };
	}
}



展开阅读全文
加载中
点击加入讨论🔥(2) 发布并加入讨论🔥
打赏
2 评论
3 收藏
0
分享
返回顶部
顶部