欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Nutz:重新发明*:自己动手,用字节码工具做一个Aop拦截器 博客分类: Nutz AOPJavaF#SVNGoogle 

程序员文章站 2024-02-04 16:55:10
...
Aop听得太多了, 用过Aop的JEer也不少,什么Spring Aop,AspectJ等等.

换个角度,为啥不自己写一个Aop拦截器呢? 重新发明*又如何?

现在就用最基本的字节码工具来DIY一个Aop. 用反射就太没风度了,那东西,谁不会?!

效果预览:
原有的类
public class Aop1{

    public void doSomething() throws Throwable{
        //打我啊,拍我啊!
    }

}


改造后

public class Aop1$$Aop extend Aop1{

    @Override
    public void doSomething() throws Throwable{
	try {
		if (_Nut_before(188)) {
			super.doSomething();
		}
		_Nut_after(188, null);
	} catch (Exception e) {
		if(_Nut_Exception(188, e))
			throw e;
	} catch (Throwable e) {
		if(_Nut_Error(188, e))
			throw e;
	}
    }

    private static Method[] _$$Nut_methodArray;

    private static List<MethodInterceptor>[] _$$Nut_methodInterceptorList;

    private boolean _Nut_before(int flag_int, Object... args) {
	Method method = _$$Nut_methodArray[flag_int];
	List<MethodInterceptor> miList = _$$Nut_methodInterceptorList[flag_int];
	boolean flag = true;
	for (MethodInterceptor methodInterceptor : miList)
		flag &= methodInterceptor.beforeInvoke(this, method, args);
	return flag;
    }

    private Object _Nut_after(int flag_int, Object src_return, Object... args) {
	Method method = _$$Nut_methodArray[flag_int];
	List<MethodInterceptor> miList = _$$Nut_methodInterceptorList[flag_int];
	for (MethodInterceptor methodInterceptor : miList)
		src_return = methodInterceptor.afterInvoke(this, src_return, method, args);
	return src_return;
    }

    private boolean _Nut_Exception(int flag_int, Exception e, Object... args) {
	Method method = _$$Nut_methodArray[flag_int];
	List<MethodInterceptor> miList = _$$Nut_methodInterceptorList[flag_int];
	boolean flag = true;
	for (MethodInterceptor methodInterceptor : miList)
		flag &= methodInterceptor.whenException(e, this, method, args);
	return flag;
    }

    private boolean _Nut_Error(int flag_int, Throwable e, Object... args) {
	Method method = _$$Nut_methodArray[flag_int];
	List<MethodInterceptor> miList = _$$Nut_methodInterceptorList[flag_int];
	boolean flag = true;
	for (MethodInterceptor methodInterceptor : miList)
		flag &= methodInterceptor.whenError(e, this, method, args);
	return flag;
    }

}


其中MethodInterceptor是方法拦截器接口:
public interface MethodInterceptor {

	/**
	 * 在被拦截方法调用之前,将调用该方法。 你可用通过这个方法的返回值,来控制是否真正的调用"被拦截方法"。
	 * 
	 * @param obj
	 *            被调用实例
	 * @param method
	 *            实例被调用方法
	 * @param args
	 *            被调用方法所需参数
	 * @return true,继续调用被拦截方法。false 将不会调用被拦截方法
	 */
	boolean beforeInvoke(Object obj, Method method, Object... args);

	/**
	 * 你可以通过这个函数,修改被拦截方法的返回值。默认的,你直接将 returnObj 返回即可
	 * 
	 * @param obj
	 *            被调用实例
	 * @param returnObj
	 *            实例被调用方法的返回
	 * @param method
	 *            实例被调用方法
	 * @param args
	 *            被调用方法所需参数
	 * @return 调用者真正将拿到的对象。 如果,你返回的对象类型是错误的,比如调用者希望得到一个 long, 但是,你拦截了这个方法,并返回一个
	 *         String,那么将发生一个类型转换的错误
	 */
	Object afterInvoke(Object obj, Object returnObj, Method method, Object... args);

	/**
	 * 当被拦截方法发生异常(Exception),这个方法会被调用。
	 * 
	 * @param e
	 *            异常
	 * @param obj
	 *            被调用实例
	 * @param method
	 *            被调用方法
	 * @param args
	 *            被调用方法所需参数
	 * 
	 * @return 是否继续抛出异常
	 */
	boolean whenException(Exception e, Object obj, Method method, Object... args);

	/**
	 * 当被拦截方法发生错误(Error),这个方法会被调用。
	 * 
	 * @param e
	 *            错误
	 * @param obj
	 *            被调用实例
	 * @param method
	 *            被调用方法
	 * @param args
	 *            被调用方法所需参数
	 * 
	 * @return 是否继续抛出错误
	 */
	boolean whenError(Throwable e, Object obj, Method method, Object... args);

}


好,开工!

实现步骤,用个接口来表达吧:
public interface ClassEnhander{

    void addFields();
    void addConstructors();
    void addAopMethods();
    void enhandMethod();
}


第一步,新建一个类Aop1$$Aop, 等一下, 是用你的字节码工具新建一个类哦,可不要打开eclipse的new class对话框了
asm示例代码:
ClassWriter cw= new ClassWriter(ClassWriter.COMPUTE_MAXS);
cw.visit(V1_6, ACC_PUBLIC, myName, "", enhancedSuperName, new String[]{});


其中,myName就是"Aop1$$Aop", enhancedSuperName就是父类的名字

第二步, 插入字段(回想一下改造后的类,可是有两个静态私有字段的哦)
asm示例代码:
FieldVisitor fv = cv.visitField(ACC_PRIVATE + ACC_STATIC, "_$$Nut_methodArray", "[Ljava/lang/reflect/Method;", null, null);
fv.visitEnd();


FieldVisitor fv = cv.visitField(ACC_PRIVATE + ACC_STATIC, "_$$Nut_methodInterceptorList", "[Ljava/util/List;", 
"[Ljava/util/List<Lorg/nutz/aop/MethodInterceptor;>;", null);
fv.visitEnd();


第三步,继承父类的构造方法(除了private的构造方法),记得添加父类构造方法抛出的异常哦:
asm示例代码:
MethodVisitor mv = cw.visitMethod(access, "<init>", desc,null, expClasses);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
loadArgs();
mv.visitMethodInsn(INVOKESPECIAL, superClassName, "<init>", desc);
mv.visitInsn(RETURN);
mv.visitMaxs(2, 2);
mv.visitEnd();


第四步,插入Aop模板方法(就是那几个private的_Nut_开头的方法)
asm示例代码:
例如插入_Nut_whenError
MethodVisitor mv = cw.visitMethod(ACC_PRIVATE + ACC_VARARGS, "_Nut_Error", "(ILjava/lang/Throwable;[Ljava/lang/Object;)Z", null, null);
mv.visitCode();
mv.visitFieldInsn(GETSTATIC, _Nut_myName, "_$$Nut_methodArray", "[Ljava/lang/reflect/Method;");
mv.visitVarInsn(ILOAD, 1);
mv.visitInsn(AALOAD);
mv.visitVarInsn(ASTORE, 4);
mv.visitFieldInsn(GETSTATIC, _Nut_myName, "_$$Nut_methodInterceptorList", "[Ljava/util/List;");
mv.visitVarInsn(ILOAD, 1);
mv.visitInsn(AALOAD);
mv.visitVarInsn(ASTORE, 5);
mv.visitInsn(ICONST_1);
mv.visitVarInsn(ISTORE, 6);
mv.visitVarInsn(ALOAD, 5);
mv.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "iterator", "()Ljava/util/Iterator;");
mv.visitVarInsn(ASTORE, 8);
Label l0 = new Label();
mv.visitJumpInsn(GOTO, l0);
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitFrame(Opcodes.F_FULL, 9, new Object[] {_Nut_myName, Opcodes.INTEGER, "java/lang/Throwable", "[Ljava/lang/Object;", 

"java/lang/reflect/Method", "java/util/List", Opcodes.INTEGER, Opcodes.TOP, "java/util/Iterator"}, 0, new Object[] {});
mv.visitVarInsn(ALOAD, 8);
mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Iterator", "next", "()Ljava/lang/Object;");
mv.visitTypeInsn(CHECKCAST, "org/nutz/aop/MethodInterceptor");
mv.visitVarInsn(ASTORE, 7);
mv.visitVarInsn(ILOAD, 6);
mv.visitVarInsn(ALOAD, 7);
mv.visitVarInsn(ALOAD, 2);
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 4);
mv.visitVarInsn(ALOAD, 3);
mv.visitMethodInsn(INVOKEINTERFACE, "org/nutz/aop/MethodInterceptor", "whenError", 

"(Ljava/lang/Throwable;Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Z");
mv.visitInsn(IAND);
mv.visitVarInsn(ISTORE, 6);
mv.visitLabel(l0);
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
mv.visitVarInsn(ALOAD, 8);
mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Iterator", "hasNext", "()Z");
mv.visitJumpInsn(IFNE, l1);
mv.visitVarInsn(ILOAD, 6);
mv.visitInsn(IRETURN);
mv.visitMaxs(6, 9);
mv.visitEnd();


第五步,重头戏,覆写需要Aop拦截的方法:
首先,需要处理第一个难题: 有无返回值
对于无返回值的方法,解决方案比较简单:
asm示例代码:
MethodVisitor mv = cw.visitMethod(methodAccess, methodName,methodDesc,null, convertExp(method.getExceptionTypes()));
mv.visitCode();
Label l0 = new Label();
Label l1 = new Label();
Label l2 = new Label();
		mv.visitTryCatchBlock(l0, l1, l2, "java/lang/Exception");
		Label l3 = new Label();
		mv.visitTryCatchBlock(l0, l1, l3, "java/lang/Throwable");
		mv.visitLabel(l0);
		mv.visitVarInsn(ALOAD, 0);
		mv.visitIntInsn(SIPUSH, methodIndex);
		loadArgsAsArray();
		mv.visitMethodInsn(INVOKESPECIAL, myName, "_Nut_before", "(I[Ljava/lang/Object;)Z");
		Label l4 = new Label();
		mv.visitJumpInsn(IFEQ, l4);
		mv.visitVarInsn(ALOAD, 0);
		loadArgs();
		mv.visitMethodInsn(INVOKESPECIAL, enhancedSuperName, methodName, desc);
		mv.visitLabel(l4);
		mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
		mv.visitVarInsn(ALOAD, 0);
		mv.visitIntInsn(SIPUSH, methodIndex);
		mv.visitInsn(ACONST_NULL);
		loadArgsAsArray();
		mv.visitMethodInsn(INVOKESPECIAL, myName, "_Nut_after", "(ILjava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");
		mv.visitInsn(POP);
		mv.visitLabel(l1);
		Label l5 = new Label();
		mv.visitJumpInsn(GOTO, l5);
		mv.visitLabel(l2);
		mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/Exception"});
		mv.visitVarInsn(ASTORE, lastIndex);
		mv.visitVarInsn(ALOAD, 0);
		mv.visitIntInsn(SIPUSH, methodIndex);
		mv.visitVarInsn(ALOAD, lastIndex);
		loadArgsAsArray();
		mv.visitMethodInsn(INVOKESPECIAL, myName, "_Nut_Exception", "(ILjava/lang/Exception;[Ljava/lang/Object;)Z");
		mv.visitJumpInsn(IFEQ, l5);
		mv.visitVarInsn(ALOAD, lastIndex);
		mv.visitInsn(ATHROW);
		mv.visitLabel(l3);
		mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/Throwable"});
		mv.visitVarInsn(ASTORE, lastIndex);
		mv.visitVarInsn(ALOAD, 0);
		mv.visitIntInsn(SIPUSH, methodIndex);
		mv.visitVarInsn(ALOAD, lastIndex);
		loadArgsAsArray();
		mv.visitMethodInsn(INVOKESPECIAL, myName, "_Nut_Error", "(ILjava/lang/Throwable;[Ljava/lang/Object;)Z");
		mv.visitJumpInsn(IFEQ, l5);
		mv.visitVarInsn(ALOAD, lastIndex);
		mv.visitInsn(ATHROW);
		mv.visitLabel(l5);
		mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
		mv.visitInsn(RETURN);
		mv.visitMaxs(1, 1); // 自动计算
		mv.visitEnd();


对于有返回值的方法,分三种情况, 返回Object(单单指这类方法 public Object dz()),返回基本数据类型,返回其他对象(如返回数组,字符串,枚举,接口,除

Object外的对象)
返回值为Object时,把_Nut_after的返回值直接返回即可,当抛出异常等,则返回null
返回值为基本数据类型时,如int/long/double,把_Nut_after的返回值解包,当抛出异常等,则返回0/false
返回值为其他对象时, 把_Nut_after的返回值 check cast一下,进行强转即可.

解包的代码:
if(type.equals(Type.BOOLEAN_TYPE)){
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z");
		}else if(type.equals(Type.BYTE_TYPE)){
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B");
		}else if(type.equals(Type.CHAR_TYPE)){
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C");
		}else if(type.equals(Type.SHORT_TYPE)){
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S");
		}else if(type.equals(Type.INT_TYPE)){
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I");
		}else if(type.equals(Type.LONG_TYPE)){
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J");
		}else if(type.equals(Type.FLOAT_TYPE)){
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F");
		}else if(type.equals(Type.DOUBLE_TYPE)){
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D");
		}


对了对了, 还有一个难点, 处理方法的参数列表,由于_Nut_XXX接受的是Object...args,即一个Object[].
问题就来了, 如果其中某个参数是基本类型呢?恩, 那就得封包了

封包的代码:
if(type.equals(Type.BOOLEAN_TYPE)){
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;");
		}else if(type.equals(Type.BYTE_TYPE)){
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;");
		}else if(type.equals(Type.CHAR_TYPE)){
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;");
		}else if(type.equals(Type.SHORT_TYPE)){
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;");
		}else if(type.equals(Type.INT_TYPE)){
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;");
		}else if(type.equals(Type.LONG_TYPE)){
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;");
		}else if(type.equals(Type.FLOAT_TYPE)){
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;");
		}else if(type.equals(Type.DOUBLE_TYPE)){
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;");
		}


参数转为Object[]的代码:
int index = getArgIndex(0);
		for (int i = 0; i < argumentTypes.length; i++) {
			mv.visitInsn(DUP);
			visitX(i);
			Type t = argumentTypes[i];
	        loadInsn(t, index);
	        index += t.getSize();
	        packagePrivateData(t);
			mv.visitInsn(AASTORE);
		}

其中visitX为
	void visitX(int i){
		if(i < 6){
			mv.visitInsn(i + ICONST_0);
		}else{
			mv.visitIntInsn(BIPUSH, i);
		}
	}

最后,完成!!

附具体实现(基于asm): [url]http://code.google.com/p/nutzlab/source/browse/#svn/trunk/Nutz.Aop-ASM/Nutz.Aop-ASM[/curl
或者参考另外一篇博文: http://wendal.iteye.com/blog/543681

再次,感谢一下Nutz社区和Peter的支持,没有你们就没有这博文了. O(∩_∩)O哈哈~
Nutz: http://code.google.com/p/nutz/