MethodHandles.Lookup返回的实例
MethodHandles.lookup()封装了调用者的上下文,即创建新类加载器的类的上下文。如异常所示,该类型
Formatter在此上下文中不可见。您可以将其看作是模仿操作的编译时语义的尝试。如果将语句放置
Formatter.formatSource(sourceText)在代码中,由于该类型不在范围内,那么它将无法正常工作。
您可以使用更改查找对象的上下文类
in(Class),但是使用时
MethodHandles.lookup().in(formatterClass),您会遇到其他问题。更改查找对象的上下文类将降低访问级别,以使其与Java访问规则保持一致,即,您只能访问
public该类的成员
Formatter。但是,
Lambdametafactory唯一接受有权
private访问其查找类的查找对象,即由调用者本身直接生成的查找对象。唯一的例外是在嵌套类之间进行更改。
因此,在中使用
MethodHandles.lookup().in(formatterClass)results
Invalid caller:com.google.googlejavaformat.java.Formatter,因为您(调用方)不是
Formatter该类。或者从技术上讲,查找对象没有
private访问模式。
Java API不提供任何(简单)方式来使查找对象位于不同的类加载上下文中并具有
private访问权限(在Java
9之前)。所有常规机制都将涉及驻留在该上下文中的代码的合作。这就是开发人员经常采用带有访问覆盖的反射功能来操纵查找对象以具有所需属性的方法。不幸的是,新的模块系统有望在未来变得更加严格,可能会破坏这些解决方案。
Java
9提供了一种获取此类查找对象的方法,
privateLookupIn该方法要求目标类位于同一模块中,或者将其模块开放给调用者的模块以允许这种访问。
由于创建的是new
ClassLoader,因此可以使用类加载上下文。因此,解决问题的一种方法是向其添加另一个类,该类创建查找对象并允许您的调用代码检索它:
try (URLClassLoader cl = new URLClassLoader(urls.toArray(new URL[0])) { { byte[] pre = gimmeLookupClassDef(); defineClass("GimmeLookup", pre, 0, pre.length); } }) { MethodHandles.Lookup lookup = (MethodHandles.Lookup) cl.loadClass("GimmeLookup").getField("lookup").get(null); Class<?> formatterClass = cl.loadClass("com.google.googlejavaformat.java.Formatter"); Object formatInstance = formatterClass.getConstructor().newInstance(); Method method = formatterClass.getMethod("formatSource", String.class); MethodHandle methodHandle = lookup.unreflect(method); MethodType type = methodHandle.type(); MethodType factoryType = MethodType.methodType(FormatInvoker.class, type.parameterType(0)); type = type.dropParameterTypes(0, 1); FormatInvoker formatInvoker = (FormatInvoker) Lambdametafactory.metafactory( lookup, "invoke", factoryType, type, methodHandle, type) .getTarget().invoke(formatInstance); String text = (String) formatInvoker.invoke(sourceText); System.out.println(text); }static byte[] gimmeLookupClassDef() { return ( "u00CAu00FEu00BAu00BE 001 211 13GimmeLookup7 11 20" +"java/lang/Object7 31 10<clinit>1 3()V1 4Code1 6lookup1 'Ljav" +"a/lang/invoke/MethodHandles$Lookup;14 10 1111 2 121 )()Ljava/lang" +"/invoke/MethodHandles$Lookup;1 36java/lang/invoke/MethodHandles7 1514 " +"10 1412 16 17261 2 4 12031 10 11 12011 5 " +"6 1 7 23 3 3 7u00B8 20u00B3 13u00B1 " ) .getBytes(StandardCharsets.ISO_8859_1);}该子类在构造函数中
URLClassLoader调用
defineClass一次,以添加一个等效于
public interface GimmeLookup { MethodHandles.Lookup lookup = MethodHandles.lookup();}然后,代码
lookup通过反射读取该字段。查找对象封装的上下文中
GimmeLookup,这是内新定义的
URLClassLoader,并且足以以访问
public方法
formatSource的
public
com.google.googlejavaformat.java.Formatter。
该接口
FormatInvoker可用于该上下文,因为您代码的类加载器将成为created的父级
URLClassLoader。
一些附加说明:
当然,如果您
FormatInvoker
足够频繁地使用生成的实例来补偿创建它的成本,那么它只能比任何其他反射式访问更有效。我删除了该
Thread.currentThread().setContextClassLoader(cl);
语句,因为它在此操作中没有任何意义,但是实际上,由于您没有将其重新设置,因此非常危险,因此该线程URLClassLoader
此后一直引用关闭的内容。我简化了对的
toArray
呼叫urls.toArray(new URL[0])
。本文提供了一个非常有趣的观点,说明了为数组指定集合大小的有用性。



