簡體   English   中英

有沒有辦法從類外部修改Java中的`private static final`字段的值?

[英]Is there any way to modify the value of a `private static final` field in Java from outside the class?

我知道這通常是相當愚蠢的,但在閱讀這個問題之前不要開槍。 我保證我有充分的理由需要這樣做:)

可以使用反射修改java中的常規私有字段,但是在嘗試對final字段執行相同操作時,Java會引發安全性異常。

我認為這是嚴格執行的,但無論如何我都會問,以防萬一有人想出了一個黑客來做這件事。

我只想說我有一個帶有“ SomeClass ”類的外部庫

public class SomeClass 
{
  private static final SomeClass INSTANCE = new SomeClass()

  public static SomeClass getInstance(){ 
      return INSTANCE; 
  }

  public Object doSomething(){
    // Do some stuff here 
  }
} 

我基本上想要Monkey-Patch SomeClass,以便我可以執行自己的doSomething()版本。 由於沒有(據我所知)在java中真正做到這一點的任何方法,我這里唯一的解決方案是改變INSTANCE的值,以便它使用修改后的方法返回我的類版本。

基本上我只想用安全檢查包裝調用,然后調用原始方法。

外部庫總是使用getInstance()來獲取此類的實例(即它是一個單例)。

編輯:只是為了澄清, getInstance()由外部庫調用,而不是我的代碼,所以只是子類化不會解決問題。

如果我不能這樣做,我能想到的唯一其他解決方案是復制粘貼整個類並修改方法。 這並不理想,因為我必須更新庫以更新庫。 如果某人有更多可維護的東西,我願意接受建議。

有可能的。 我已經用它來monkeypatch淘氣的threadlocals,它阻止了webapps中的類卸載。 您只需要使用反射來刪除final修改器,然后您可以修改該字段。

像這樣的東西可以解決這個問題:

private void killThreadLocal(String klazzName, String fieldName) {
    Field field = Class.forName(klazzName).getDeclaredField(fieldName);
    field.setAccessible(true);  
    Field modifiersField = Field.class.getDeclaredField("modifiers");
    modifiersField.setAccessible(true);
    int modifiers = modifiersField.getInt(field);
    modifiers &= ~Modifier.FINAL;
    modifiersField.setInt(field, modifiers);
    field.set(null, null);
}

Field#set周圍也有一些緩存,所以如果某些代碼運行之前它可能不一定有效....

任何AOP框架都能滿足您的需求

它允許您為getInstance方法定義運行時覆蓋,允許您返回任何適合您需要的類。

Jmockit在內部使用ASM框架來做同樣的事情。

如果你真的必須(雖然對於我們的問題,我建議你使用CaptainAwesomePants的解決方案),你可以看看JMockIt 雖然這可以用於單元測試,但是如果允許你重新定義任意方法。 這是通過在運行時修改字節碼來完成的。

您應該可以使用JNI進行更改...不確定這是否適合您。

編輯:這是可能的,但不是一個好主意。

http://java.sun.com/docs/books/jni/html/pitfalls.html

10.9違反訪問控制規則

JNI不強制實施類,字段和方法訪問控制限制,這些限制可以通過使用私有和最終修飾符在Java編程語言級別表達。 可以編寫本機代碼來訪問或修改對象的字段,即使在Java編程語言級別這樣做會導致IllegalAccessException。 鑒於本機代碼無論如何都可以訪問和修改堆中的任何內存位置,JNI的放縱是一種有意識的設計決策。

繞過源語言級訪問檢查的本機代碼可能會對程序執行產生不良影響。 例如,如果本機方法在實時(JIT)編譯器內聯訪問字段后修改了最終字段,則可能會產生不一致。 類似地,本機方法不應該修改不可變對象,例如java.lang.String或java.lang.Integer實例中的字段。 這樣做可能會導致Java平台實現中的不變量破壞。

您可以嘗試以下方法。 注意:它根本不是線程安全的,這對編譯時已知的常量基元不起作用(因為它們由編譯器內聯)

Field field = SomeClass.class.getDeclareField("INSTANCE");
field.setAccessible(true); // what security. ;)
field.set(null, newValue);

我將通過承認這實際上不是對你所聲明的關於修改私有靜態最終字段的問題的答案來作為答案的前言。 但是,在上面提到的具體示例代碼中,我實際上可以使它可以覆蓋doSomething()。 你可以做的是利用getInstance()是一個公共方法和子類的事實:

public class MySomeClass extends SomeClass
{
   private static final INSTANCE = new MySomeClass();

   public SomeClass getInstance() {
        return INSTANCE;
   }

   public Object doSomething() {
      //Override behavior here!
   }
}

現在只需調用MySomeClass.getInstance()而不是SomeClass.getInstance(),你就可以了。 當然,這只有在你調用getInstance()而不是你正在使用的不可修改的東西的其他部分時才有效。

mockito很簡單:

import static org.mockito.Mockito.*;

public class SomeClass {

    private static final SomeClass INSTANCE = new SomeClass();

    public static SomeClass getInstance() {
        return INSTANCE;
    }

    public Object doSomething() {
        return "done!";
    }

    public static void main(String[] args) {
        SomeClass someClass = mock(SomeClass.getInstance().getClass());
        when(someClass.doSomething()).thenReturn("something changed!");
        System.out.println(someClass.doSomething());
    }
}

這段代碼打印出“改變了一些東西!”; 您可以輕松替換您的單例實例。 我0.02美分。

如果沒有可用的外部黑客(至少我不知道),我會破解這個類本身。 通過添加所需的安全檢查來更改代碼。 因此它是一個外部庫,你不會定期更新,也不會發生很多更新。 無論什么時候發生這種情況,我都可以愉快地重新做到這一點,因為無論如何這不是一件大事。

在這里,你的問題是很好的依賴注入(又名反轉控制)。 你的目標應該是注入SomeClass的實現而不是monkeypatching它。 是的,這種方法需要對現有設計進行一些更改,但出於正確的原因(在此處列出您最喜歡的設計原則) - 尤其是同一個對象不應該負責創建和使用其他對象。

我假設您使用SomeClass的方式看起來有點像這樣:

public class OtherClass {
  public void doEverything() {
    SomeClass sc = SomeClass.geInstance();
    Object o = sc.doSomething();

    // some more stuff here...
  }
}

相反,你應該做的是首先創建實現相同接口的類或擴展SomeClass ,然后將該實例傳遞給doEverything()這樣你的類就變得與SomeClass實現無關。 在這種情況下,調用doEverything的代碼負責傳遞正確的實現 - 無論是實際的SomeClass還是monkeypatched MySomeClass

public class MySomeClass() extends SomeClass {
  public Object doSomething() {
    // your monkeypatched implementation goes here
  }
}

public class OtherClass {
  public void doEveryting(SomeClass sc) {
    Object o = sc.doSomething();

    // some more stuff here...
  }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM