`
budairenqin
  • 浏览: 199387 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

Java字节码框架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要快一些,总之适合自己的就是最好的

后续:我山炮了,asm官网有个插件http://asm.ow2.org/eclipse/index.html
安装以后基本能直接生成代码,左边代码直接拷贝过来即可,如下图:


  • 大小: 565.6 KB
分享到:
评论

相关推荐

    ASM Java字节码操作框架

    ASM Java字节码操作框架PPT,结合已有AOP实现方法,对比所有对Java字节码操作方法做比较

    Java字节码操纵框架 asm-3.1组件包大集合

    ASM 是一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class ...

    Java字节码和asm入门资料

    NULL 博文链接:https://xpenxpen.iteye.com/blog/2194267

    java字节码框架ASM操作字节码的方法浅析

    主要给大家介绍了关于java字节码框架ASM如何操作字节码的相关资料,文中通过示例代码介绍的很详细,有需要的朋友可以参考借鉴,下面来一起看看吧。

    java字节码框架ASM的深入学习

    主要给大家介绍了java中字节码框架ASM的相关资料,文中介绍的非常详细,相信对大家的理解和学习具有一定的参考借鉴价值,有需要的朋友们下面来一起学习学习吧。

    cglib,字节码生成库是生成和转换Java字节码的高级API。它被aop、测试、数据访问框架用来生成动态代理对象和拦截字段访问。.zip

    字节码生成库是生成和转换java字节码的高级api。

    ASM操作字节码,动态生成Java类class文件

    ASM操作字节码,动态生成Java类class文件,模拟Spring的AOP实现原理。

    ASM 帮助文档(java字节码操作)

    ASM 帮助文档(java字节码操作) 对字节码进行操作的jar包。

    ASM字节码操作简单实例

    一个简单的通过ASM修改字节码实现AOP功能的实例,简单易懂,可运行...

    asm4-guide.pdf

    ASM 是一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class ...

    asm操作指南(中文)

    ASM是一个java字节码操纵框架,它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class...

    AsmTools-Java字节码的反汇编器工具

    org.openjdk.asmtools.jdis.Main Java字节码的反汇编器工具 Java字节码的反汇编器工具

    ASM4使用指南

    ASM是一个通用的Java字节码操作和分析框架。它可以用于修改现有类或直接以二进制形式动态生成类。ASM提供了一些常见的字节码转换和分析算法,可以从中构建自定义复杂转换和代码分析工具。ASM提供与其他Java字节码...

    ASM4中文使用指南.zip

    ASM 是一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class ...

    asm-9.0.jar

    ASM是一个java字节码操纵框架,它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class文件...

    asm-master.zip

    ASM是一个通用的Java字节码操作和分析框架。 它可以用于修改现有类或直接以二进制形式动态生成类。 ASM提供了一些常见的字节码转换和分析算法,可以从中构建自定义复杂转换和代码分析工具。 ASM提供与其他Java字节码...

    asm5.0安装包

    asm是assembly的缩写,是汇编的称号,对于java而言,asm就是字节码级别的编程。 而这里说到的asm是指objectweb asm,一种.class的代码生成器的开源项目. ASM是一套java字节码生成架构,它可以动态生成二进制格式的...

    ASM4.0 中文教程与多种字节码编程实战案例

    内容包含ASM4.0中文手册,以及四种ASM常见的字节码操作应用范例,包含最新版本的ASM9.2的jar包,包含asm-9.2.jar,asm-commons-9.2.jar,asm-util-9.2.jar。 学习文章地址 ...

    ASM 字节码修改工具中文帮助手册

    ASM 4 的中文文档 网上的博文都是从这上边摘得,大家下载下来学学就会了,这个是最全的了。我就是通过这个学会的。

    JavaAgent:Javassist 与 Asm JavaAgent 字节码动态编程项目

    JavaAgent Javassist 与 Asm JavaAgent 字节码动态编程项目

Global site tag (gtag.js) - Google Analytics