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 };
}
}