`
budairenqin
  • 浏览: 201483 次
  • 性别: 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
分享到:
评论

相关推荐

    Java字节码和asm入门资料

    ASM是一个开源的Java字节码操控和分析框架,它可以直接用来生成和修改Java类文件,是Java动态代理和字节码增强技术的重要工具。在深入学习Java字节码和ASM之前,我们需要先理解Java编译和运行的基本过程。 1. **...

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

    首先,Javassist是一个开源的Java字节码操作框架,它使得开发者可以在运行时或编译时方便地修改Java类。通过Javassist,我们能够动态创建新的类、接口,或者修改已存在的类的结构,包括添加、修改或删除方法和字段。...

    ASM使用指南-中文版

    ASM是一个强大的Java字节码操控和分析框架,它允许开发者动态生成或修改Java类和运行时的类。这个“ASM使用指南-中文版”提供了全面的教程和参考信息,帮助开发者深入理解并有效地利用ASM库。 ASM的核心功能在于...

    ASM中文帮助文档

    ASM是Java字节码操作和分析框架,它允许程序员在运行时动态生成和修改类和方法。ASM的主要用途包括创建动态代理、代码分析、代码优化以及AOP(面向切面编程)实现。以下是对ASM这一高级Java技术的详细解读: 1. **...

    bytebuddy 字节码增强 创建注解

    ByteBuddy是一个强大的字节码库,它允许开发者在不使用Java代理(Java Proxy)或者ASM等底层字节码库的情况下,便捷地创建和修改Java类与接口。本资源主要关注如何使用ByteBuddy来创建和处理注解,这对于理解和实现...

    CGLIB入门实例.doc

    它底层基于ASM库,但为开发者提供了更友好的API,简化了对Java字节码的操作。 CGLIB的核心功能包括以下几点: 1. **Proxy实现**:CGLIB提供了动态代理的功能,可以创建目标类的子类,从而实现代理。这种代理方式比...

    mybatis必备jar包(最基本)

    9. **asm-3.3.1.jar**:ASM是一个Java字节码操控和分析框架,MyBatis可能在某些情况下使用ASM来直接操作和分析字节码,如动态代理的生成。 这些jar包构成了MyBatis基本运行环境,开发者可以直接将它们导入到项目中...

    Hibernate入门jar包

    - `cglib-nodep.jar`和`asm.jar`:这两个库通常用于动态代理和字节码操作,Hibernate在处理实体类的动态代理时可能会用到。 - `dom4j.jar`或`jdom.jar`:XML处理库,Hibernate的HQL查询结果集转换为XML时会用到。 ...

    luaj.luajc:将LuaJC移植到ASM框架

    卢阿 LuaJ,但速度更快这是LuaJ的Lua到Java字节码编译器的分支。 它已转换为使用ASM框架,并且已修复了许多错误。与原来的不同核心调试支持( debug.traceback , debug.getinfo和所有调试钩子-尽管您无法获取或更改...

    Struts入门

    - **asm-3.3.jar**:操作 Java 字节码的类库。 - **commons-fileupload-1.3.2.jar**:文件上传支持。 - **freemarker-2.3.22.jar**:模板引擎,用于生成文本输出。 - **javassist-3.11.0.GA.jar**:用于分析、...

    javaasm源码-psgg-starter-kit:混合代码可视化编程StateGo入门套件资源

    ASM 是一个 Java 字节码操控和分析框架,它可以直接生成和修改 Java 类的字节码。这个库被广泛用于动态代理、代码混淆、性能监控以及字节码级别的元编程。ASM 提供了底层 API,允许开发者对类的结构进行精细控制,...

    spring AOP入门教程

    2. **基于字节码增强的AOP**:通过ASM库,Spring可以在运行时修改字节码,将切面代码织入目标类,实现更高效和灵活的AOP操作。 ### 四、Spring AOP的配置和使用 在Spring中,我们可以通过XML配置或注解的方式来...

    黑马程序员_struts2框架2016版视频_struts2_day01笔记

    - **javassist**: 字节码操作库。 #### 1.4 Struts2入门理论 **JavaEE设计模式** 对于理解Struts2的工作原理非常重要。其中,**前端控制器模式**是Struts2中最为关键的设计模式之一。这种模式的主要作用是集中处理...

    Spring AOP @AspectJ 入门实例

    基于代理的织入通常用于Spring的IoC容器中的bean,而基于字节码的织入则是在运行时通过ASM库动态修改类的字节码来实现,这正是@AspectJ所采用的方式。 @AspectJ是Spring AOP的一个扩展,它提供了一种更接近传统编程...

    c#学习笔记.txt

    很多人觉得它应该像C或者C++,但事实上它更像是java的一个clone,所以作为入门,读一下清华大学出版社出版的《Java 语言与面向对象程序设计》可能会对你有所帮助。本文假定你具备一切学习此语言所需的知识,没有也不...

Global site tag (gtag.js) - Google Analytics