5

我需要一种方法来访问具有反射性质的字段,而不会影响标准反射的性能。我已经想出了如何使用特权查找句柄通过 LambdaMetaFactory 使用方法/构造函数来做到这一点,但是,我似乎无法弄清楚如何获得字段访问权限。

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

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

有人可以在这里指导我正确的方向吗?我研究了 LambdaMetaFactory 如何为方法生成它的接口,并试图用没有成功的字段来镜像它。字段和方法的内部是否存在一些不同之处,使得如果不重新定义就无法完成这项任务?

4

2 回答 2

2

如果是 OpendJDK(以及在其上构建的 JDK),它会LambdaMetaFactory生成一个大部分普通的类文件(只是访问private另一个类的成员)并使用一个特殊的方法sun.misc.Unsafe来创建一个匿名类

创建一个访问字段的类似类文件很简单,并用它创建一个匿名类确实有效,可以使用以下快速&脏程序演示:

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 = "Êþº¾\0\0\0004\0\24\7\0\21\7\0\t\7\0\n\7\0\22"
        + "\n\0\2\0\6\f\0\13\0\f\t\0\4\0\b\f\0\23\0\20\1\0\20java/lang/Object\1\0\40"
        + "java/util/function/ToIntFunction\1\0\6<init>\1\0\3()V\1\0\4Code\1\0\n"
        + "applyAsInt\1\0\25(Ljava/lang/Object;)I\1\0\1I";
    private static final String TAIL = "\0001\0\1\0\2\0\1\0\3\0\0\0\2\0\1\0\13\0\f\0"
        + "\1\0\r\0\0\0\21\0\1\0\1\0\0\0\5*·\0\5±\0\0\0\0\0\21\0\16\0\17\0\1\0\r\0\0"
        + "\0\24\0\1\0\2\0\0\0\b+À\0\4´\0\7¬\0\0\0\0\0\0";

    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 库。

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

于 2020-08-25T13:22:28.203 回答
0

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

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

于 2020-08-14T20:41:18.597 回答