使用java.lang.invoke.MethodHandle调用私有方法

14

我该如何使用方法句柄调用私有方法呢?

据我所知,只有两种公共可访问的Lookup实例:

  • MethodHandles.lookup()
  • MethodHandles.publicLookup()

但是它们都不允许无限制的私有访问。

有一个非公共的Lookup.IMPL_LOOKUP可以满足我的需求。是否有一些公共的方法可以获取它(假设安全管理器允许)?


我遇到了类似的问题(对于 getters/setters),如果您能分享一下您如何调用私有方法,那将非常棒! - bachr
请参见下面的答案中的示例。 - Marcin Wisnicki
它使用反射在调用invoke之前设置可访问性,我想知道为什么要这样做,因为lookup()应该根据文档中指定的方式允许调用者访问直接/私有字段和方法。 - bachr
只有使用 setAccessible 来获取特殊的特权实现 Lookup,才能调用私有成员,而不管它们是否对调用者可访问。 - Marcin Wisnicki
3个回答

17

通过Lookup#unreflect(Method)并临时使方法可访问(可能会在程序初始化期间引入小的安全问题),原来的方法是可以实现的。

这里是Thorben答案中修改后的主方法:

public static void main(String[] args) {

    Lookup lookup = MethodHandles.lookup();
    NestedTestClass ntc = new Program().new NestedTestClass();

    try {
        // Grab method using normal reflection and make it accessible
        Method pm = NestedTestClass.class.getDeclaredMethod("gimmeTheAnswer");
        pm.setAccessible(true);

        // Now convert reflected method into method handle
        MethodHandle pmh = lookup.unreflect(pm);
        System.out.println("reflection:" + pm.invoke(ntc));

        // We can now revoke access to original method
        pm.setAccessible(false);

        // And yet the method handle still works!
        System.out.println("handle:" + pmh.invoke(ntc));

        // While reflection is now denied again (throws exception)
        System.out.println("reflection:" + pm.invoke(ntc));

    } catch (Throwable e) {
        e.printStackTrace();
    }

}

5
更好的选择是使用Java SE 9中添加的MethodHandles.privateLookupIn方法。 - Alan Bateman

3

我不知道这是否是你真正想要的。也许你可以提供更多关于你想要实现什么的信息。 但如果你想访问Lookup.IMPL_LOOKUP,你可以像这个代码示例一样做:

public class Main {

public static void main(String[] args) {

    Lookup myLookup = MethodHandles.lookup(); // the Lookup which should be trusted
    NestedTestClass ntc = new Main().new NestedTestClass(); // test class instance

    try {
        Field impl_lookup = Lookup.class.getDeclaredField("IMPL_LOOKUP"); // get the required field via reflections
        impl_lookup.setAccessible(true); // set it accessible
        Lookup lutrusted = (Lookup) impl_lookup.get(myLookup); // get the value of IMPL_LOOKUP from the Lookup instance and save it in a new Lookup object

        // test the trusted Lookup
        MethodHandle pmh = lutrusted.findVirtual(NestedTestClass.class, "gimmeTheAnswer", MethodType.methodType(int.class));
        System.out.println(pmh.invoke(ntc));

    } catch (Throwable e) {
        e.printStackTrace();
    }

}

// nested class with private method for testing
class NestedTestClass{

    @SuppressWarnings("unused")
    private int gimmeTheAnswer(){

        return 42;
    }
}

}

它可以在JDK 7中运行,但可能会在JDK 8中出现问题。请小心!当我执行它时,我的杀毒软件发出了警报。

我遇到过类似的问题,并最终找到了解决方法:从JDK(7)访问非公共(java-native)类


我知道如何做到这一点,但我已经要求公开获取它的方法 - Marcin Wisnicki
我所说的“公共方式”是指使用公共API。 - Marcin Wisnicki
我认为使用公共API不可能实现这一点。通过公共方法直接访问私有字段或方法将会破坏Java的安全系统。 - Thorben
1
不会。你已经可以通过 java.lang.reflect.* 使用公共 API 尝试访问私有成员。我正在询问是否可以使用 java.lang.invoke.* 实现相同的功能。 - Marcin Wisnicki
好的,现在我明白你想要什么了。是的,反射可以做到这一点,但缺点是需要进行巨大的安全检查。因此,它们是一种特殊情况。InvokeDynamic主要是为速度而构建的,据我所知,与反射不同,没有这些安全检查。如果我错了,请纠正我。尽管如此,我认为必要的安全检查会破坏InvokeDynamic的性能目标。 - Thorben
显示剩余2条评论

-3

这里有一个类似的解决方案,其中包括私有函数中的参数(我碰巧从以前的项目中找到了代码):

类名: InspectionTree.java

函数签名: private String getSamePackagePathAndName(String className, String classPath)

String firstName = "John";
String lastName = "Smith";

//call the class's constructor to set up the instance, before calling the private function
InspectionTree inspectionTree = new InspectionTree(firstName, lastName);

String privateMethodName ="getSamePackagePathAndName";        
Class[] privateMethodArgClasses = new Class[] { String.class, String.class };

Method method = 
         inspectionTree.getClass().getDeclaredMethod(privateMethodName, privateArgClasses);

method.setAccessible(true);

String className = "Person";
String classPath = "C:\\workspace";

Object[] params = new Object[]{className, classPath};        

//note the return type of function 'getSamePackagePathAndName' is a String, so we cast
//the return type here as a string
String answer=  (String)method.invoke(inspectionTree, params);

method.setAccessible(false);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接