我想获得对 java.lang.String 的包私有构造函数的反射访问。
即,这一个:
/*
* Package private constructor which shares value array for speed.
* this constructor is always expected to be called with share==true.
* a separate constructor is needed because we already have a public
* String(char[]) constructor that makes a copy of the given char[].
*/
String(char[] value, boolean share) {
// assert share : "unshared not supported";
this.value = value;
}
为它创建 MethodHandle 非常简单,因此调用它也是如此。 直接使用Reflection也是如此。
但我很好奇是否可以通过功能接口直接调用构造函数。
27602758触及了一个类似的问题,但提供的解决方案在这种情况下似乎不起作用。
下面的测试用例编译没有问题。一切都有效,除了实际的接口调用。
package test;
import java.lang.invoke.CallSite;
import java.lang.invoke.LambdaMetafactory;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;
import java.lang.reflect.Field;
public class Test {
// Creates a new String that shares the supplied char[]
private static interface StringCreator {
public String create(char[] value, boolean shared);
}
// Creates a new conventional String
private static String create(char[] value, boolean shared) {
return String.valueOf(value);
}
public static void main(String[] args) throws Throwable {
// Reflectively generate a TRUSTED Lookup for the calling class
Lookup caller = MethodHandles.lookup();
Field modes = Lookup.class.getDeclaredField("allowedModes");
modes.setAccessible(true);
modes.setInt(caller, -1); // -1 == Lookup.TRUSTED
// create handle for #create()
MethodHandle conventional = caller.findStatic(
Test.class, "create", MethodType.methodType(String.class, char[].class, boolean.class)
);
StringCreator normal = getStringCreator(caller, conventional);
System.out.println(
normal.create("foo".toCharArray(), true)
// prints "foo"
);
// create handle for shared String constructor
MethodHandle constructor = caller.findConstructor(
String.class, MethodType.methodType(void.class, char[].class, boolean.class)
);
// test directly if the construcor is correctly accessed
char[] chars = "foo".toCharArray();
String s = (String) constructor.invokeExact(chars, true);
chars[0] = 'b'; // modify array contents
chars[1] = 'a';
chars[2] = 'r';
System.out.println(
s
// prints "bar"
);
// generate interface for constructor
StringCreator shared = getStringCreator(caller, constructor);
System.out.println(
shared.create("foo".toCharArray(), true)
// throws error
);
}
// returns a StringCreator instance
private static StringCreator getStringCreator(Lookup caller, MethodHandle handle) throws Throwable {
CallSite callSite = LambdaMetafactory.metafactory(
caller,
"create",
MethodType.methodType(StringCreator.class),
handle.type(),
handle,
handle.type()
);
return (StringCreator) callSite.getTarget().invokeExact();
}
}
特别是指令
shared.create("foo".toCharArray(), true)
引发以下错误:
Exception in thread "main" java.lang.IllegalAccessError: tried to access method java.lang.String.<init>([CZ)V from class test.Test$$Lambda$2/989110044 at test.Test.main(Test.java:59)
为什么这个错误仍然被抛出,尽管表面上被授予了访问权限?
任何人都可以解释为什么生成的接口无法访问其所有组件都可以访问的方法吗?
是否存在实际适用于此特定用例的解决方案或可行替代方案,而无需恢复为纯反射或 MethodHandles ?
因为我很难过。
答案 0 :(得分:3)
问题是您覆盖了查询对象以使其受信任,因此它对private
String
方法的访问权限将通过查找过程和lambda元工厂,但它仍然绑定到您的{ {1}}类,因为它是通过Test
创建查找对象的类,生成的类将存在于同一个上下文中。对于这些生成的类,JVM在可访问性方面非常慷慨,但显然,不接受从生活在应用程序类上下文中的类访问引导类MethodHandles.lookup()
的{{1}}成员。 / p>
您可以通过以下方式获取生活在适当上下文中的查找对象。 private
(然后修补它以获得java.lang.String
或“信任”访问权限),但是,您将遇到另一个问题:生活在MethodHandles.lookup() .in(String.class)
(或仅在bootstrap loader的上下文)将无法访问您的自定义private
,并且无法实现它。
唯一的解决方案是使用生成在java.lang.String
上下文中的查找对象,并实现一个现有的通用interface StringCreator
,可从引导类加载器访问:
String