我需要一种以反射性方式访问场的方法,而不会受到标准反射的性能影响。我已经想出了如何使用特权查找句柄通过LambdaMetaFactory使用方法/构造函数来执行此操作,但是,我似乎无法弄清楚如何获得字段访问权限。
我以为我可以通过javaassist之类的东西来生成一个内部类,该类在理论上应该可以访问该字段,但没有成功,并抛出IllegalAccessError。
如果我可以重新定义该类,那么该任务将是微不足道的,因为我可以生成getter / setter方法。但是,对于我正在处理的项目,我无法使用代理,因为它将需要在运行时加载,并且必须从工具动态导入attach api。
有人可以在这里指导我正确的方向吗?我研究了LambdaMetaFactory如何为方法生成其接口,并尝试使用字段进行镜像,但没有成功。字段和方法在内部是否存在某些差异,导致无法重新定义此任务而无法完成该任务?
答案 0 :(得分:2)
对于OpendJDK(及其上构建的JDK),LambdaMetaFactory
会生成一个大多数普通的类文件(只需访问另一个类的private
个成员)并在{ {1}},以创建anonymous class。
创建一个类似的访问字段的类文件很简单,并使用它创建一个匿名类也可以正常工作,如以下快速&肮脏程序所示:
sun.misc.Unsafe
当然,对于生产代码,您最好使用一种常用的代码生成库来拥有可维护的工厂代码。例如,OpenJDK的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();
}
}
在后台使用ASM库。
如果您实施类似解决方案的尝试失败,则必须发布您尝试过的内容,以便我们帮助您确定问题所在。但是也许,知道总体上有可能确实对您有所帮助。
答案 1 :(得分:0)
您可以尝试使用Byte Buddy或Javassist生成运行时代码,但这仅在您需要多次访问不同对象上的同一字段时才会提高性能。否则,代码生成的开销可能会比使用反射的开销高。
如果您认为运行时代码生成可能适合您的情况,请查看https://github.com/raner/projo,特别是projo-runtime-code-generation/src/main/java/pro/projo/internal/rcg中的代码。请注意,该代码实际上也会生成字段,它不使用现有类的现有字段,因此它不是100%所需的,但可能会为您提供正确方向的指针。