是否有一种快速的反射场访问方法



我需要一种方法来访问反射性质的字段,而不会受到标准反射的性能影响。我已经找到了如何通过LambdaMetaFactory使用特权查找句柄使用方法/构造函数来实现这一点,然而,我似乎不知道如何获得字段访问权限。

我想我可以通过javaassist之类的东西生成一个内部类,理论上它应该可以访问该字段,但没有成功,抛出了一个IllegalAccessError。

如果我可以重新定义类,那么任务将是琐碎的,因为我可以生成getter/setter方法。然而,对于我正在进行的项目,我无法使用代理,因为它需要在运行时加载,并且我必须从工具中动态导入附加api。

有人能给我指引正确的方向吗?我研究了LambdaMetaFactory是如何生成方法接口的,并试图将其与字段进行镜像,但没有成功。字段和方法是否存在内部差异,导致在不重新定义的情况下无法完成此任务?

在OpendJDK(以及在其上构建的JDK(的情况下,LambdaMetaFactory生成一个非常普通的类文件(只访问另一个类的private成员(,并在sun.misc.Unsafe中使用一种特殊的方法来创建一个匿名类。

创建一个访问字段的类似类文件是直接的,并用它创建一个匿名类是可行的,正如下面的quick&脏程序:

public class Generator {
public static void main(String[] args) throws Throwable {
ToIntFunction<Thread> ft=generateIntFieldAccessor(Thread.class, "threadStatus");
System.out.println(ft.applyAsInt(Thread.currentThread()));
}
private static <X> ToIntFunction<X> generateIntFieldAccessor(
Class<? super X> c, String name) throws Throwable {
byte[] code = Generator.generateIntReaderCode(c.getDeclaredField(name));
Class<?> unsafe = Class.forName("sun.misc.Unsafe");
Field u = unsafe.getDeclaredField("theUnsafe");
u.setAccessible(true);
Object theUnsafe = u.get(null);
Class<ToIntFunction<X>> gen = (Class<ToIntFunction<X>>)
MethodHandles.publicLookup().bind(theUnsafe, "defineAnonymousClass",
MethodType.methodType(
Class.class, Class.class, byte[].class, Object[].class))
.invokeExact(c, code, (Object[])null);
return gen.getConstructor().newInstance();
}
private static final String HEAD = "Êþº¾004247217t7n722"
+ "n26f13ft4bf2320120java/lang/Object140"
+ "java/util/function/ToIntFunction16<init>13()V14Code1n"
+ "applyAsInt125(Ljava/lang/Object;)I11I";
private static final String TAIL = "00112132113f"
+ "1r21115*·5±2116171r"
+ "2412b+À4´7¬";
public static byte[] generateIntReaderCode(Field f) {
return new ByteArrayOutputStream(HEAD.length() + TAIL.length() + 100) {
@SuppressWarnings("deprecation") byte[] get() {
HEAD.getBytes(0, count = HEAD.length(), buf, 0);
try(DataOutputStream dos = new DataOutputStream(this)) {
String decl = f.getDeclaringClass().getName().replace('.', '/');
dos.writeByte(1); dos.writeUTF(decl+"$"+f.getName()+"$access");
dos.writeByte(1); dos.writeUTF(decl);
dos.writeByte(1); dos.writeUTF(f.getName());
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
int dynSize = count;
byte[] result = Arrays.copyOf(buf, dynSize + TAIL.length());
TAIL.getBytes(0, TAIL.length(), result, dynSize);
return result;
}
}.get();
}
}

Ideone 演示

当然,对于生产代码,您应该更好地使用一个常用的代码生成库,以获得可维护的工厂代码。例如,OpenJDK的LambdaMetaFactory在后台使用ASM库。

如果您尝试实现类似的解决方案失败,您必须发布您所尝试的内容,以便我们能够帮助识别问题。但是,也许,知道这在一般情况下是可能的,已经对你有所帮助了。

您可以尝试使用Byte Buddy或Javassist生成运行时代码,但如果您需要多次访问不同对象上的同一字段,这只会提高性能。否则,代码生成的开销可能会高于使用反射的开销。

如果您认为运行时代码生成可能适用于您的情况,请查看https://github.com/raner/projo,特别是projo运行时代码生成/src/main/java/pro/projo/internal/rcg中的代码。请注意,该代码实际上也会生成字段,它不使用现有类的现有字段,因此它不是您所需要的100%,但可能会为您提供一个指向正确方向的指针。

最新更新