论坛首页 Java企业应用论坛

字节码工具asm使用的一个例子

浏览 13788 次
精华帖 (1) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2012-05-03   最后修改:2012-05-04
ASM 是一个 Java 字节码操控框架。它能够以二进制形式修改已有类或者动态生成类。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。

初识字节码,现学现卖。
示例中演示的功能:

  • 生成被代理类的子类,并重写所有方法(除java.lang.Object类定义的方法),增加before和after拦截。
  • 重新定义被代理类的所有属性(不包括属性的赋值)。
  • 把生成的class文件保存到硬盘中。
  • 从内存中加载新生成的class。


jdk自带的代理(InvocationHandler)的不足:

  • jdk只能代理接口(被代理的类必须实现至少一个接口)。
  • jdk提供的代理方法,不能拦截同一类中方法的调用。eg.A.method1(){this.method2();}此时method2将不会被拦截。


ps:附件中内置了asm4.0的核心类。不用下载jar包可直接运行。

下面是重写父类方法的代码:
@Override
	public void visitEnd() {
		// 如果originalClass定义了私有成员变量,那么直接在visitMethod中复制originalClass的<init>会报错。
//		ALOAD 0
//		INVOKESPECIAL cc/RoleService.<init>()V
//		RETURN
//		// 调用originalClassName的<init>方法,否则class不能实例化
		MethodVisitor mvInit = classWriter.visitMethod(ACC_PUBLIC, INIT, "()V", null, null); 
		mvInit.visitVarInsn(ALOAD, 0);
		mvInit.visitMethodInsn(INVOKESPECIAL, toAsmCls(originalClassName), INIT, "()V");
		mvInit.visitInsn(RETURN);
		mvInit.visitMaxs(0, 0);
		mvInit.visitEnd(); 


		// 获取所有方法,并重写(main方法 和 Object的方法除外)
		Method[] methods = originalClass.getMethods();
		for(Method m : methods) {
			if(!Aops.needOverride(m)) {
				continue;
			}
			Type mt = Type.getType(m);
			LOG.debug(mt);

			StringBuilder methodInfo = new StringBuilder(originalClassName);
			methodInfo.append(".").append(m.getName());
			methodInfo.append("|");

			Class<?>[] paramTypes = m.getParameterTypes();
			for(Class<?> t : paramTypes) {
				methodInfo.append(t.getName()).append(",");
			} 
			if(paramTypes.length > 0) {
				methodInfo.deleteCharAt(methodInfo.length() - 1);
			}

			// 方法是被哪个类定义的
			String declaringCls = toAsmCls(m.getDeclaringClass().getName());

			// 方法 description
			MethodVisitor mWriter = classWriter.visitMethod(ACC_PUBLIC, m.getName(), mt.toString(), null, null); 

			// insert code here (before)
			doBefore(mWriter,methodInfo.toString());


			int i = 0;
			// 如果不是静态方法 load this对象
			if(!Modifier.isStatic(m.getModifiers())) {
				mWriter.visitVarInsn(ALOAD, i++);
			}
			StringBuilder sb = new StringBuilder(m.getName());
			// load 出方法的所有参数
			for(Class<?> tCls : m.getParameterTypes()) {
				Type t = Type.getType(tCls);
				sb.append(loadCode(t)).append(",");
				mWriter.visitVarInsn(loadCode(t), i++); 
				if(t.getSort() == Type.LONG || t.getSort() == Type.DOUBLE) {
					i++;
				}
			}

			LOG.debug(sb.toString());

			// super.xxx();
			mWriter.visitMethodInsn(INVOKESPECIAL,declaringCls,m.getName(),mt.toString());

			// 处理返回值类型
			Type rt = Type.getReturnType(m);
			// 没有返回值
			if(rt.toString().equals("V")) {
				doAfter(mWriter,methodInfo.toString());
				mWriter.visitInsn(RETURN);
			}
			// 把return xxx() 转变成 : Object o = xxx(); return o;
			else {
				int storeCode = storeCode(rt);
				int loadCode = loadCode(rt);
				int returnCode = rtCode(rt);

				mWriter.visitVarInsn(storeCode, i);
				doAfter(mWriter,methodInfo.toString());
				mWriter.visitVarInsn(loadCode, i);
				mWriter.visitInsn(returnCode);
			}

			// 已设置了自动计算,但还是要调用一下,不然会报错
	        mWriter.visitMaxs(i, ++i);
	        mWriter.visitEnd(); 
		}
		cv.visitEnd();
	}




增加基于javassist 的实现做对比(代码比较长,新增了附件):
更详尽的代码可以到https://github.com/dijingran/dxx-cc/tree/master/cc-tx/src/org/cc/tx/aop/javassit查看。
  • asm的性能好,支持泛型,但要求使用者对java字节码有一定的了解。
  • javassist性能差一些,不支持泛型。主要优势是学习成本低,可以根据java源代码生成字节码,而不必直接和字节码打交道。


附上一个两者详细对比的链接:http://blog.sina.com.cn/s/blog_7c7348af0100re3u.html

  • asm_demo.zip (204.3 KB)
  • 描述: asm的实现类
  • 下载次数: 448
   发表时间:2012-05-04  
目前我使用asm用来做sql查询后的组装结果集,实现orm的操作
0 请登录后投票
   发表时间:2012-05-04  
很好,顺便问一个小问题
public String doLogin(String user_name,String user_pwd,int type)
{
......
}

用反射可以得到doLogin方法的参数类型,参数个数
但是,我想动态的得到 user_name , user_pwd , type 这三个字符串,
就像下面那样
第一个参数类型:java.lang.String,参数字符串:user_name
第二个参数类型:java.lang.String,参数字符串:user_pwd
第三个参数类型:java.lang.String,参数字符串:type

求楼主赐教,

求方法,求思路

谢谢。
0 请登录后投票
   发表时间:2012-05-04  
string2020 写道
很好,顺便问一个小问题
public String doLogin(String user_name,String user_pwd,int type)
{
......
}

用反射可以得到doLogin方法的参数类型,参数个数
但是,我想动态的得到 user_name , user_pwd , type 这三个字符串,
就像下面那样
第一个参数类型:java.lang.String,参数字符串:user_name
第二个参数类型:java.lang.String,参数字符串:user_pwd
第三个参数类型:java.lang.String,参数字符串:type

求楼主赐教,

求方法,求思路

谢谢。



附件里恰好有类似的方法,Classes.java这个类里面有获取参数名的方法,也是基于asm的。
0 请登录后投票
   发表时间:2012-05-04  
string2020 写道
很好,顺便问一个小问题
public String doLogin(String user_name,String user_pwd,int type)
{
......
}

用反射可以得到doLogin方法的参数类型,参数个数
但是,我想动态的得到 user_name , user_pwd , type 这三个字符串,
就像下面那样
第一个参数类型:java.lang.String,参数字符串:user_name
第二个参数类型:java.lang.String,参数字符串:user_pwd
第三个参数类型:java.lang.String,参数字符串:type

求楼主赐教,

求方法,求思路

谢谢。


也通过反射不就可以了么?
0 请登录后投票
   发表时间:2012-05-04  
ak478288 写道
string2020 写道
很好,顺便问一个小问题
public String doLogin(String user_name,String user_pwd,int type)
{
......
}

用反射可以得到doLogin方法的参数类型,参数个数
但是,我想动态的得到 user_name , user_pwd , type 这三个字符串,
就像下面那样
第一个参数类型:java.lang.String,参数字符串:user_name
第二个参数类型:java.lang.String,参数字符串:user_pwd
第三个参数类型:java.lang.String,参数字符串:type

求楼主赐教,

求方法,求思路

谢谢。


也通过反射不就可以了么?


反射貌似不行吧
0 请登录后投票
   发表时间:2012-05-04  
ak478288 写道
目前我使用asm用来做sql查询后的组装结果集,实现orm的操作


不是通过反射来做吗?不知道asm在这个场景下能做什么
0 请登录后投票
   发表时间:2012-05-04  
ak478288 写道
string2020 写道
很好,顺便问一个小问题
public String doLogin(String user_name,String user_pwd,int type)
{
......
}

用反射可以得到doLogin方法的参数类型,参数个数
但是,我想动态的得到 user_name , user_pwd , type 这三个字符串,
就像下面那样
第一个参数类型:java.lang.String,参数字符串:user_name
第二个参数类型:java.lang.String,参数字符串:user_pwd
第三个参数类型:java.lang.String,参数字符串:type

求楼主赐教,

求方法,求思路

谢谢。


也通过反射不就可以了么?


这个真不行。


另外:楼主提供的Classes类,里面的public static String[] getMethodParamNames(final Method m) 这个方法可行。

谢谢。。
0 请登录后投票
   发表时间:2012-05-04  
string2020 写道
ak478288 写道
string2020 写道
很好,顺便问一个小问题
public String doLogin(String user_name,String user_pwd,int type)
{
......
}

用反射可以得到doLogin方法的参数类型,参数个数
但是,我想动态的得到 user_name , user_pwd , type 这三个字符串,
就像下面那样
第一个参数类型:java.lang.String,参数字符串:user_name
第二个参数类型:java.lang.String,参数字符串:user_pwd
第三个参数类型:java.lang.String,参数字符串:type

求楼主赐教,

求方法,求思路

谢谢。


也通过反射不就可以了么?


这个真不行。


另外:楼主提供的Classes类,里面的public static String[] getMethodParamNames(final Method m) 这个方法可行。

谢谢。。


看了一下,确实不行。谢谢提醒
0 请登录后投票
   发表时间:2012-05-04  
mazzystar 写道
ak478288 写道
目前我使用asm用来做sql查询后的组装结果集,实现orm的操作


不是通过反射来做吗?不知道asm在这个场景下能做什么


首先通过反射获得类信息和属性字段映射信息,然后通过asm创建一个RowMapper的子类,并缓存这个类生成的对象,这样每次进行ResultSet的处理,全都是和普通编程一样的处理,都不用通过反射赋值。

我目前的另外一个准开源项目就是这样做的,
0 请登录后投票
论坛首页 Java企业应用版

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