论坛首页 Java企业应用论坛

Java字节码框架asm快速入门

浏览 8251 次
精华帖 (0) :: 良好帖 (1) :: 新手帖 (3) :: 隐藏帖 (0)
作者 正文
   发表时间:2013-01-16  
asm
  asm是一个java的字节码框架,它能被用来动态生成类或者增强既有类的功能。
  一般asm的应用场景主要在aop上,比如Spring在底层就是用了asm,但asm不仅仅是只能在aop方面发挥它强大的能力,比如你现在要写一个rpc框架,可能会在序列化对象上犯难,使用java的序列化机制?有点慢;json?(比如阿里的大神搞的fastjson性能就很好),但我要序列化的对象很简单,没有嵌套对象,不需要深拷贝,并且我并不想按照json的格式来
  我的计划是将对象拼成如下格式的字符串:
fieldName1,Type1,value1;fieldName2,Type2,value2;fieldName3,Type3,value3;fieldName4,Type4,value4;...;

用反射实现的代码大致是这样:
for (Map.Entry<String, Pair<String, Field>> iter : argFields.entrySet()) {
			fieldName = iter.getKey();    // 名
			pair = iter.getValue();
			fieldType = pair.getKey();  // 类型
			fieldValue = pair.getValue().get(event);  // 值(反射)
			if (fieldValue instanceof Date) {
				sBuilder.append(fieldName).append(",").append(fieldType).append(",").append(((Date) fieldValue).getTime()).append(";");
			} else {
				sBuilder.append(fieldName).append(",").append(fieldType).append(",").append(fieldValue).append(";");
			}
		}

  速度还可以,不过我觉得还可以更快,于是我想到了用asm代替反射,由于我个人对字节码的指令只是略知一二(都是从《深入java虚拟机》一书来的些理论知识),所以用那200来个字节码指令直接写一个我想要的类实在有点难度,于是我先用java写了一个我想要的类,然后用
javap -verbose XXX

反编译了它,然后照着反编译出来的字节码指令以及注释一步一步最后完成了一个类

下面这个类是我想要的类,我先用java完成了它
public StringBuilder write(Event event) throws IOException {
		GNSEvent obj = (GNSEvent) event;
		StringBuilder sBuilder = new StringBuilder(256);
		sBuilder.append("cityCode").append(",").append("java.lang.String").append(obj.getCityCode());
		// obj循环序列化obj的所有属性
		return sBuilder;
	}


用javap反编译它,得到下面有用的信息:
public com.futurefleet.framework.serialization.Serializer_1GNSEvent();
  Code:
   Stack=1, Locals=1, Args_size=1
   0:	aload_0
   1:	invokespecial	#10; //Method java/lang/Object."<init>":()V
   4:	return
  LineNumberTable: 
   line 16: 0

  LocalVariableTable: 
   Start  Length  Slot  Name   Signature
   0      5      0    this       Lcom/futurefleet/framework/serialization/Serializer_1GNSEvent;


public java.lang.StringBuilder write(com.futurefleet.framework.base.event.Event)   throws java.io.IOException;
  Exceptions: 
   throws java.io.IOException  Code:
   Stack=3, Locals=4, Args_size=2
   0:	aload_1
   1:	checkcast	#21; //class com/futurefleet/gateway/event/GNSEvent
   4:	astore_2
   5:	new	#23; //class java/lang/StringBuilder
   8:	dup
   9:	sipush	256
   12:	invokespecial	#25; //Method java/lang/StringBuilder."<init>":(I)V
   15:	astore_3
   16:	aload_3
   17:	ldc	#28; //String cityCode
   19:	invokevirtual	#30; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   22:	ldc	#34; //String ,
   24:	invokevirtual	#30; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   27:	ldc	#36; //String java.lang.String
   29:	invokevirtual	#30; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   32:	aload_2
   33:	invokevirtual	#38; //Method com/futurefleet/gateway/event/GNSEvent.getCityCode:()Ljava/lang/String;
   36:	invokevirtual	#30; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   39:	pop
   40:	aload_3
   41:	areturn



上面的构造函数和write方法就是我需要的指令顺序,注意就连后面的注释信息也是很重要滴,自己意会吧,必须用得到。
接下来就模仿它来用asm直接编辑想要的类就可以了
先上类的定义以及构造函数:
ClassWriter classWriter = new ClassWriter(0);
		classWriter.visit(V1_6, ACC_PUBLIC + ACC_SUPER, className, null, "java/lang/Object",
				new String[] { "com/futurefleet/framework/serialization/EventSerializer" });

		// 构造函数
		MethodVisitor methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
		methodVisitor.visitVarInsn(ALOAD, 0);
		methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
		methodVisitor.visitInsn(RETURN);
		methodVisitor.visitMaxs(1, 1);
		methodVisitor.visitEnd();

仔细观察asm里的字节码指令以及参数还有Stack, Locals的值,跟javap反编译出来的一模一样哦亲
这里再说明一下,比如ALOAD指令,怎么知道它应该是MethodVisitor哪个方法的参数呢?这个很简单,参照org.objectweb.asm.Opcodes这个接口中的注释就可以了,比如:
 int ILOAD = 21; // visitVarInsn
    int LLOAD = 22; // -
    int FLOAD = 23; // -
    int DLOAD = 24; // -
    int ALOAD = 25; // -

意思是ILOAD、LLOAD、FLOAD等都是visitVarInsn方法的参数
然后上最关键的,我需要的write方法的代码
		// StringBuilder write(Event event) throws IOException;
		{
			methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "write", "(Lcom/futurefleet/framework/base/event/Event;)Ljava/lang/StringBuilder;", null,
					new String[] { "java/io/IOException" });
			methodVisitor.visitVarInsn(ALOAD, 1);
			methodVisitor.visitTypeInsn(CHECKCAST, eventTypeString);
			methodVisitor.visitVarInsn(ASTORE, 2);
			methodVisitor.visitTypeInsn(NEW, "java/lang/StringBuilder");
			methodVisitor.visitInsn(DUP);
			methodVisitor.visitIntInsn(SIPUSH, 256);
			methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(I)V");
			methodVisitor.visitVarInsn(ASTORE, 3);
			methodVisitor.visitVarInsn(ALOAD, 3);

			FieldInfo fieldInfo = null;
			Class<?> fieldClass = null;
			for (Map.Entry<String, FieldInfo> iter : getters.entrySet()) {
				fieldInfo = iter.getValue();
				fieldClass = TypeUtils.getClass(fieldInfo.getFieldType());

				methodVisitor.visitLdcInsn(fieldInfo.getName());
				methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
				methodVisitor.visitLdcInsn(",");
				methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
				methodVisitor.visitLdcInsn(TypeUtils.getClass(fieldInfo.getFieldType()).getName());
				methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
				methodVisitor.visitLdcInsn(",");
				methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
				methodVisitor.visitVarInsn(ALOAD, 2);
				methodVisitor.visitMethodInsn(INVOKEVIRTUAL, eventTypeString, fieldInfo.getMethod().getName(), TypeUtils.getDesc(fieldInfo.getMethod()));

				if (fieldClass == Date.class || fieldClass == java.sql.Date.class || fieldClass == java.sql.Time.class
						|| fieldClass == java.sql.Timestamp.class) {
					methodVisitor.visitMethodInsn(INVOKEVIRTUAL, TypeUtils.getType(fieldClass), "getTime", "()J");
					methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;");
				} else {
					methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append",
							"(" + TypeUtils.getDesc(TypeUtils.getClass(fieldInfo.getFieldType())) + ")Ljava/lang/StringBuilder;");
				}
				methodVisitor.visitLdcInsn(";");
				methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
			}
			methodVisitor.visitInsn(POP);
			methodVisitor.visitVarInsn(ALOAD, 3);
			methodVisitor.visitInsn(ARETURN);
			methodVisitor.visitMaxs(3, 4);
			methodVisitor.visitEnd();
		}

		byte[] classCode = classWriter.toByteArray();

		// {
		// FileOutputStream fos = new FileOutputStream(new File("/Users/fengjiachun/Documents/" + className + ".class"));
		// fos.write(classCode, 0, classCode.length);
		// }

		Class<?> asmClass = this.classLoader.defineClassPublic(className, classCode, 0, classCode.length);

相关的类装载器就不贴出来了,主要是将defineClass公开出来就可以了
FieldInfo用来存放要序列化的类的对应信息,数据结构如下:
private final String name; // 字段名称
private final Method method; // 对应方法(序列化是为getter,反序列化时为setter)
private final Field field; // 字段

打开代码中被注释掉的代码,将生成的class文件写入到磁盘中,然后用JD等反编译工具看看成果吧
public class Serializer_1GNSEvent
  implements EventSerializer
{
  public StringBuilder write(Event paramEvent)
    throws IOException
  {
    GNSEvent localGNSEvent = (GNSEvent)paramEvent;
    StringBuilder localStringBuilder = new StringBuilder(256);
    localStringBuilder.append("serverTimeStamp").append(",").append("long").append(",").append(localGNSEvent.getServerTimeStamp()).append(";").append("hasOffset").append(",").append("java.lang.String").append(",").append(localGNSEvent.getHasOffset()).append(";").append("clientTimeStamp").append(",").append("long").append(",").append(localGNSEvent.getClientTimeStamp()).append(";").append("liveCity").append(",").append("java.lang.String").append(",").append(localGNSEvent.getLiveCity()).append(";").append("cityCode").append(",").append("java.lang.String").append(",").append(localGNSEvent.getCityCode()).append(";").append("lng").append(",").append("double").append(",").append(localGNSEvent.getLng()).append(";").append("type").append(",").append("int").append(",").append(localGNSEvent.getType()).append(";").append("lat").append(",").append("double").append(",").append(localGNSEvent.getLat()).append(";").append("version").append(",").append("java.lang.String").append(",").append(localGNSEvent.getVersion()).append(";").append("parseSucceed").append(",").append("boolean").append(",").append(localGNSEvent.isParseSucceed()).append(";");
    return localStringBuilder;
  }
}


生成的这个类需要缓存下来,这样相同类型的对象序列化是就不需要重新生成新类了,具体跟这次的内容无关,就不细说了

接下来要按照同样的思路来生成反序列化的类就可以了,相同的道理,不重复了
最后,经过测试,比反射的效率要高,由于我生成的序列化以及反序列化类非常的适合我目前的需求,没有一点多余的代码,对于我的应用场景来说,也比使用fastjson将对象序列化成json要快一些,总之适合自己的就是最好的
   发表时间:2013-01-22  
结合相应的插件bytecode outline,基本上的流程就变成了:用java写好自己想要的代码,用bytecode outline看用asm怎么写,ctrl+c ,ctrl+v,ok,生成代码那块就解决了。
0 请登录后投票
   发表时间:2013-01-22  
faye.feelcool 写道
结合相应的插件bytecode outline,基本上的流程就变成了:用java写好自己想要的代码,用bytecode outline看用asm怎么写,ctrl+c ,ctrl+v,ok,生成代码那块就解决了。

没用过bytecode outline插件,多谢提醒
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics