5

Could someone explain why the following code fails? I have the following five classes:

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);
    }
}

What the code is trying to do is to use reflection to replace the LOG variable in the ClassWithFinalFields class. As it stands, the class throws an IllegalAccessException when it tries to set the field at the end of TestReplaceLogger.

However, if I replace

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

with

Logger oldLogger = classWithFinalFields.LOG;

then the code runs without problems and prints log "MockLogger: hello" as expected.

So the question is, why does reading the final field through reflection stop the program working? It looks like the final modifier can no longer be removed so you get an IllegalAccessException but I have no idea why. I can speculate that may be it is something to do with the compiler optimisation or classloader ordering but, despite having had a look at the byte code, I've no real idea what is going on.

If people are wondering why I would want to do this at all, it started off as looking for a way to mock out some awkward logging during unit tests whilst we were upgrading some software. Now I'm just curious as to what on earth is going on under the covers.

If anyone want to see it, the stack trace is

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())。修飾子を変更しましたが、そのキャッシュを無効にしていないため、古いフィールド アクセサーが使用され、フィールドが final であるとまだ認識されています。

于 2013-09-27T07:48:41.020 に答える
0

フィールドの修飾子は、フィールドに対する操作の前に変更する必要があります。そうしないと、最初の操作時にフィールドのメタデータが JVM によってキャッシュされます (@meriton および @oleg.lukyrych で言及されているように)。

于 2016-06-11T16:48:51.200 に答える