1

我正在使用 BCEL 转换方法字节码来实现匿名内部类风格的方法拦截器,在拦截方法的同时,我需要对被拦截的方法进行一些注解。我使用 BCEL 来拦截除 java 反射之外的方法访问。

现在我的代码可以很好地与没有原始类型的方法一起使用。由于我不知道如何将 Class.getDeclaredMethod 与原始参数类型列表一起使用,因为 getDeclaredMethod 接受 methodName 和 Class[] 数组作为参数。

所以第一个问题是如何做到这一点。

然后我发现在JDK7中,可以通过java类文件中的ldc_w字节码通过CONSTANT_MethodHandle直接获取MethodHandle引用。就像使用 ldc 引用 Java 类一样,如果我可以直接用 ldc_w 引用 java.lang.reflection.Method ,那么我将节省我做反射的时间,并且不会被上面第一个问题中提到的原始类型所困扰. 我试过了,但我没有做到这一点。

所以第二个问题是我可以使用 ldc_w 来引用 java.lang.reflection.Method 吗?

第三个问题是我可以将 MethodHandle 转换为 java.lang.reflection.Method 或相应方法上的注释吗?

谢谢霍尔格,您的回答我几乎完全清楚,但以下问题。我可能会误解您的回答,现在我在运行时遇到异常:

Exception in thread "main" java.lang.NoClassDefFoundError: long
at net.madz.lifecycle.demo.standalone.ServiceOrder.allocateResources(ServiceOrder.java)
at net.madz.lifecycle.demo.standalone.Main2.main(Main2.java:18)
Caused by: java.lang.ClassNotFoundException: long
at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 2 more

代码如下 //2.5 为数组中的每个元素赋值

    // Step 2. final InterceptContext<Void> context = new
    // InterceptContext<Void>(getClass(), this, "allocateResources",
    // new Class[] { Long.class, Long.class, Long.class });
    // 2.1 getClass()
    ilist.append(ifact.createNew(InterceptContext.class.getName()));
    ilist.append(InstructionFactory.DUP);
    ilist.append(new LDC(cgen.getConstantPool().lookupClass(interceptingClass)));
    // 2.2 load this
    ilist.append(InstructionFactory.createLoad(new ObjectType(interceptingClass), 0));// this
    // 2.3 load intercepting method
    int methodNameIndex = cgen.getConstantPool().lookupString(interceptingMethod);
    if ( -1 >= methodNameIndex ) {
        methodNameIndex = cgen.getConstantPool().addString(interceptingMethod);
    }
    ilist.append(new LDC(methodNameIndex));// methodName
    // 2.4 calculate argument size and allocate an array with same size
    ilist.append(new ICONST(types.length));
    ilist.append(ifact.createNewArray(new ObjectType("java.lang.Class"), (short) 1));
    // 2.5 assign value for each element in array
    for ( int i = 0; i < types.length; i++ ) {
        ilist.append(InstructionFactory.DUP);
        ilist.append(new ICONST(i));
        String className = convertType2ClassName(types[i]);
        int argumentClassIndex = cgen.getConstantPool().lookupClass(className); // ?
        if ( -1 >= argumentClassIndex ) {
            argumentClassIndex = cgen.getConstantPool().addClass(className);
        }
        if ( types[i].getSize() > 4 ) {
            ilist.append(new LDC_W(argumentClassIndex));
        } else {
            ilist.append(new LDC(argumentClassIndex));
        }
        ilist.append(InstructionConstants.AASTORE);
    }
    // 2.6 new InterceptContext<Void>(...
    final Type[] interceptor_method_arg_types = new Type[4];
    interceptor_method_arg_types[0] = new ObjectType("java.lang.Class");
    interceptor_method_arg_types[1] = new ObjectType("java.lang.Object");
    interceptor_method_arg_types[2] = new ObjectType("java.lang.String");
    interceptor_method_arg_types[3] = new ArrayType("java.lang.Class", 1);
    ilist.append(ifact.createInvoke(InterceptContext.class.getName(), "<init>", Type.VOID,
            interceptor_method_arg_types, Constants.INVOKESPECIAL));

convertType2ClassName 如下:

    private static String convertType2ClassName(Type type) {
    if ( Type.BOOLEAN.equals(type) ) {
        return boolean.class.getName();
    } else if ( Type.BYTE.equals(type) ) {
        return byte.class.getName();
    } else if ( Type.CHAR.equals(type) ) {
        return char.class.getName();
    } else if ( Type.DOUBLE.equals(type) ) {
        return double.class.getName();
    } else if ( Type.FLOAT.equals(type) ) {
        return float.class.getName();
    } else if ( Type.INT.equals(type) ) {
        return int.class.getName();
    } else if ( Type.LONG.equals(type) ) {
        return long.class.getName();
    } else if ( Type.SHORT.equals(type) ) {
        return short.class.getName();
    } else if ( type instanceof ObjectType ) {
        String signature = type.getSignature();
        if ( signature.startsWith("L") ) {
            signature = signature.substring(1);
        }
        int leftArrow = signature.indexOf("<");
        if ( -1 < leftArrow ) {
            signature = signature.substring(0, leftArrow);
        }
        if ( signature.endsWith(";") ) {
            signature = signature.substring(0, signature.length() - 1);
        }
        return signature;
    } else if ( type instanceof ArrayType ) {
        //unsupport for now
    }
    //wrong return
    return type.getSignature();
}
4

2 回答 2

4

Holger 的回答涵盖了您的第一个问题(带有原始参数的 getDeclaredMethod)。您的第二个问题(用于ldc获取 java.lang.reflect.Method)的答案仍然是no。但是您的第三个问题(将方法句柄转换为 java.lang.reflect.Method)的答案随着 Java 8 的发布而改变了——现在的答案有时是,包括在您关心的情况下(转换您刚刚的句柄ldc) .


从 Java 8 开始,可以“破解”直接方法句柄以获取 Method、Constructor 或 Field 对象。直接方法句柄定义为由ldcCONSTANT_MethodHandle 常量或MethodHandles.Lookup 中的find*和方法产生的句柄,没有进一步的转换(没有绑定参数等)。unreflect*破解直接方法句柄有两种途径:

  • 调用MethodHandles.reflectAs,传递预期的类(方法、构造函数或字段)和要破解的句柄。此方法需要ReflectPermission("suppressAccessChecks"),因此如果您需要在安全管理器下运行可能不合适。
  • 获取 MethodHandles.Lookup “相当于创建目标方法句柄的方法,或者具有足够访问权限来重新创建等效方法句柄的方法”(根据 MethodHandleInfo 文档),然后调用Lookup.revealDirect传递句柄进行破解以获取 MethodHandleInfo ,然后调用MethodHandleInfo.reflectAs传递预期的类(方法、构造函数或字段)和查找。为了维护安全性,此路径通过检查查找类来特别处理调用者敏感方法。

第一种途径更方便,但如果您需要在安全管理器下运行,则需要在需要破解方法句柄的每个类中创建一个 Lookup(存储在初始化的新静态字段中<clinit>)并使用该 Lookup使手柄断裂。

于 2014-06-29T04:53:23.880 回答
1

你不能得到一个java.lang.reflection.Methodwith ldc。对 a做ldcaCONSTANT_MethodHandle会产生 a MethodHandle

这些方法句柄可用于执行相关代码(尽管它的名称,但不限于方法),您可以查询其参数类型。但是您不能将其转换为 Reflection Method。虽然你可以做相反的事情,但将 a 转换java.lang.reflection.Method为 a java.lang.invoke.MethodHandle

由于java.lang.invoke.MethodHandle可以更快地调用方法并且没有自动装箱的开销并且无需将所有参数放入数组中,因此您可能无论如何都不想使用它。


顺便说一句,使用原始类型的反射非常容易,例如

obj.getClass()
   .getDeclareMethodMethod("foo", int.class, String.class)
   .invoke(obj, 42, "blah");

它将包装int成一个Integer并创建一个包含两个参数s的临时Object[]数组。Object但是对于方法的查找,您必须指定正确的原始类型。


从 Java 8 开始,有一种将直接转换为MethodHandleaMethod的方法,因此您可以使用ldc指令后跟转换,但您需要获取一个MethodHandles.Lookup实例并使用它两次,因此字节码如下所示:

ldc             class java/lang/reflect/Method
ldc             method handle <your desired method>
invokestatic    java/lang/invoke/MethodHandles.reflectAs:(Ljava/lang/Class;Ljava/lang/invoke/MethodHandle;)Ljava/lang/reflect/Member;
checkcast       java/lang/reflect/Method

略短于,例如

ldc             class <declaring class of your desired method>
ldc             string <name of your desired method>
ldc             method type <type signature of your desired method>
invokevirtual   java/lang/invoke/MethodType.parameterArray:()[Ljava/lang/Class;
invokevirtual   java/lang/Class.getDeclaredMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;

请记住,在字节码级别,没有强制执行异常处理

于 2013-10-23T18:21:52.173 回答