5

有人可以解释为什么以下代码会失败吗?我有以下五个课程:

public class TestReplaceLogger {
    public static void main(String[] arv) throws Exception {
        ClassWithFinalFields classWithFinalFields = new ClassWithFinalFields();
        Field field = ClassWithFinalFields.class.getDeclaredField("LOG");

        // Comment this line and uncomment out the next line causes program work
        Logger oldLogger = (Logger)field.get(null);
        //Logger oldLogger = classWithFinalFields.LOG;


        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

        field.set(null, new MockLogger());

        classWithFinalFields.log();
    }
}

public class ClassWithFinalFields {
    public static final Logger LOG = new RealLogger();

    public void log() {
        LOG.log("hello");
    }
}

public interface Logger {
    public void log(String msg);
}

public class RealLogger implements Logger{
    public void log(String msg) {
        System.out.println("RealLogger: " + msg);
    }
}

public class MockLogger implements Logger {
    public void log(String msg) {
        System.out.println("MockLogger: " + msg);
    }
}

代码试图做的是使用反射来替换 ClassWithFinalFields 类中的 LOG 变量。就目前而言,该类IllegalAccessException在尝试将字段设置在TestReplaceLogger.

但是,如果我更换

Logger oldLogger = (Logger)field.get(null);

Logger oldLogger = classWithFinalFields.LOG;

然后代码运行没有问题并按预期打印日志“MockLogger:hello”。

所以问题是,为什么通过反射读取 final 字段会使程序停止工作?看起来 final 修饰符不能再被删除,所以你得到一个 IllegalAccessException 但我不知道为什么。我可以推测这可能与编译器优化或类加载器排序有关,但是尽管查看了字节码,但我并不真正知道发生了什么。

如果人们想知道我为什么要这样做,它开始是在我们升级一些软件时寻找一种方法来模拟单元测试期间一些尴尬的日志记录。现在我只是很好奇幕后到底发生了什么。

如果有人想看到它,堆栈跟踪是

Exception in thread "main" java.lang.IllegalAccessException: Can not set static final org.matthelliwell.reflection.Logger field org.matthelliwell.reflection.ClassWithFinalFields.LOG to org.matthelliwell.reflection.MockLogger
at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:73)
at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:77)
at sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl.set(UnsafeQualifiedStaticObjectFieldAccessorImpl.java:77)
at java.lang.reflect.Field.set(Field.java:741)
at org.matthelliwell.reflection.TestReplaceLogger.main(TestReplaceLogger.java:23)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
4

4 回答 4

5

您正在绕过其公共 api 访问字段对象。当然,如果你这样做,任何事情都可能发生。特别是,Java API 的不同实现可能表现不同。

在 Oracle JDK 中,Field 假定修饰符是 final,因此缓存 fieldAccessor(请参阅 参考资料Field.getFieldAccessor())。您更改了修饰符,但没有使缓存无效,导致使用旧的字段访问器,它仍然认为该字段是最终的。

于 2013-09-27T07:48:41.020 回答
3

只需移动这些行:

Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

在这之前 :

Logger oldLogger = (Logger)field.get(null);
于 2013-09-27T07:55:21.697 回答
1

有一个类似的问题,它几乎让我把自己的头发扯掉了。
这段代码(简化)可以完美运行的地方......

        Field field = Long.class.getDeclaredField("MIN_VALUE");
        field.get(null);

        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
        field.get(null);

...这个会抛出“IllegalAccessException”

        Field field = Long.class.getDeclaredField("MIN_VALUE");
        field.get(null);

        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
        field.set(null, 1000l);

(出于上述原因)

于 2017-09-14T09:40:45.737 回答
0

字段的修饰符必须在对字段进行任何操作之前更改,否则字段元数据将在第一次操作时由 JVM 缓存(如 @meriton 和 @oleg.lukyrych 所述)。

于 2016-06-11T16:48:51.200 回答