2013-09-27 37 views
4

有人可以解釋爲什麼下面的代碼失敗?我有以下五類:爲什麼反射無法更新靜態字段?

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類。就目前而言,當它試圖在TestReplaceLogger的末尾設置字段時,該類會拋出IllegalAccessException

但是,如果我更換

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

Logger oldLogger = classWithFinalFields.LOG; 

然後代碼運行沒有問題,而且打印日誌 「MockLogger:你好」 如預期。

所以問題是,爲什麼通過反射來讀取最終字段會停止程序的工作?它看起來像最後一個修飾符不能再被刪除,所以你得到一個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) 
+1

你能張貼堆棧跟蹤? – Fildor

+0

我已將堆棧跟蹤添加到問題 –

回答

4

您正在訪問的領域對象繞過它的公共API。當然,如果你這樣做,任何事情都可能發生。特別是,Java API的不同實現可能會有不同的表現。

在Oracle JDK中,Field假定修飾符是最終的,因此會緩存字段訪問者(請參閱Field.getFieldAccessor())。您已經更改了修改器,但未使該緩存失效,導致使用舊的字段存取器,但仍認爲該字段是最終的。

+0

謝謝,高速緩存將解釋問題。 (在我的防守中,我知道任何事情都可能發生,我只是好奇底下發生了什麼) –

3

只是將這些行:

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

在此之前:

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

場修飾語必須改變之前場上否則他字段元數據將被緩存任何操作第一次操作時的JVM(如@meriton和@ oleg.lukyrych所述)。

0

有一個類似的問題,它幾乎讓我撕下我自己的頭髮。
如果這個代碼(簡化)將很好地工作......

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

(由於上述原因解釋)

相關問題