Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does reflection fail to update a static field?

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)
like image 328
matt helliwell Avatar asked Sep 27 '13 07:09

matt helliwell


People also ask

Why is reflection not good in Java?

It's very bad because it ties your UI to your method names, which should be completely unrelated. Making an seemingly innocent change later on can have unexpected disastrous consequences. Using reflection is not a bad practice. Doing this sort of thing is a bad practice.

How do you set a private static field in Junit?

field. set(null, p_fieldValue); This will let you set the static field.

How do I change my public static final?

FALSE. Reflection is used to change the public static final Boolean.

How to get methods of a class in java using reflection?

Reflection of Java MethodsMethod[] methods = obj. getDeclaredMethod(); Here, the getDeclaredMethod() returns all the methods present inside the class.


1 Answers

You are accessing the field object bypassing its public api. Of course, anything may happen if you do that. In particular, different implementations of the Java API may behave differently.

In the Oracle JDK, Field assumes the modifiers to be final, and therefore caches the fieldAccessor (see Field.getFieldAccessor()). You have changed the modifiers, but not invalidated that cache, causing the old field accessor to be used, which still believes the field to be final.

like image 70
meriton Avatar answered Sep 20 '22 03:09

meriton