有没有办法从类外部修改Java中的`private static final`字段的值?

时间:2009-04-20 05:27:04

标签: java monkeypatching

我知道这通常是相当愚蠢的,但在阅读这个问题之前不要开枪。我保证我有充分的理由需要这样做:)

可以使用反射修改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()是由外部库调用的,而不是我的代码,所以只是子类化不会解决问题。

如果我不能这样做,我能想到的唯一其他解决方案是复制粘贴整个类并修改方法。这并不理想,因为我必须更新库以更新库。如果某人有更多可维护的东西,我愿意接受建议。

9 个答案:

答案 0 :(得分:10)

有可能。我已经用它来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也有一些缓存,所以如果某些代码运行之前它可能不一定有效....

答案 1 :(得分:5)

任何AOP框架都符合您的需求

它允许您为getInstance方法定义运行时覆盖,允许您返回任何适合您需要的类。

Jmockit在内部使用ASM框架来做同样的事情。

答案 2 :(得分:1)

如果你真的必须(虽然对于我们的问题,我建议你使用CaptainAwesomePants的解决方案),你可以看一下JMockIt。虽然这可以用于单元测试,但是如果允许你重新定义任意方法。这是通过在运行时修改字节码来完成的。

答案 3 :(得分:1)

您应该可以使用JNI进行更改...不确定这是否适合您。

编辑:这是可能的,但不是一个好主意。

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

  

10.9违反访问控制规则

     

JNI不强制执行class,field,   和方法访问控制限制   可以在Java上表达   编程语言水平通过   使用修饰符如private和   最后。可以写原生的   用于访问或修改字段的代码   即使这样做也是对象   Java编程语言水平会   导致IllegalAccessException。   JNI的放纵是有意识的   设计决定,鉴于原生   代码可以访问和修改任何内存   无论如何,在堆中的位置。

     

绕过的本机代码   源语言级访问检查   可能会产生不良影响   程序执行。例如,一个   如果a,可能会产生不一致   native方法修改final字段   在即时(JIT)编译器之后   已经内联访问该字段。   同样,本机方法不应该   修改不可变对象,如   实例中的字段   java.lang.String或java.lang.Integer。   这样做可能会导致破损   Java平台中的不变量   实施

答案 4 :(得分:1)

您可以尝试以下操作。注意:它根本不是线程安全的,这对编译时已知的常量基元不起作用(因为它们由编译器内联)

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

答案 5 :(得分:0)

我将通过承认这实际上不是对您所声明的关于修改私有静态最终字段的问题的答案来解释这个答案。但是,在上面提到的具体示例代码中,我实际上可以使它可以覆盖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()而不是你正在使用的不可修改的东西的其他部分时才有效。

答案 6 :(得分:0)

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美分。

答案 7 :(得分:-1)

如果没有可用的外部黑客(至少我不知道),我会破解这个类本身。通过添加所需的安全检查来更改代码。因此它是一个外部库,你不会定期更新,也不会发生很多更新。无论何时发生这种情况,我都可以愉快地重新做这件事,因为无论如何这不是一件大事。

答案 8 :(得分:-1)

在这里,你的问题是古老的依赖注入(又名反转控制)。您的目标应该是注入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...
  }
}