11

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

可以使用反射修改 Java 中的常规私有字段,但是 Java 在尝试对final字段执行相同操作时会引发安全异常。

我假设这是严格执行的,但我想无论如何我都会问,以防万一有人想出了一个黑客来做到这一点。

假设我有一个带有类“ SomeClass”的外部库

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

  public static SomeClass getInstance(){ 
      return INSTANCE; 
  }

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

我本质上想对 SomeClass 进行猴子补丁,以便我可以执行我自己的doSomething(). 由于(据我所知)没有任何方法可以在 java 中真正做到这一点,因此我唯一的解决方案是更改 的值,INSTANCE以便它使用修改后的方法返回我的类版本。

本质上,我只想用安全检查包装调用,然后调用原始方法。

外部库总是用来getInstance()获取这个类的一个实例(即它是一个单例)。

编辑:只是为了澄清,getInstance()是由外部库调用的,而不是我的代码,所以只是子类化并不能解决问题。

如果我不能这样做,我能想到的唯一其他解决方案是复制粘贴整个类并修改方法。这并不理想,因为我必须让我的 fork 与库的更改保持同步。如果有人有一些更易于维护的东西,我愿意接受建议。

4

9 回答 9

10

有可能的。我用它来猴子补丁阻止 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,所以如果一些代码在它之前运行过,它可能不一定有效....

于 2009-04-20T08:49:33.597 回答
5

任何 AOP 框架都可以满足您的需求

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

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

于 2009-04-20T06:36:23.227 回答
1

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

于 2009-04-20T05:47:17.637 回答
1

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

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

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

10.9 违反访问控制规则

JNI 不强制执行类、字段和方法访问控制限制,这些限制可以通过使用诸如 private 和 final 等修饰符在 Java 编程语言级别表示。可以编写本机代码来访问或修改对象的字段,即使在 Java 编程语言级别这样做会导致 IllegalAccessException。JNI 的许可是一个有意识的设计决定,因为本机代码无论如何都可以访问和修改堆中的任何内存位置。

绕过源语言级别访问检查的本机代码可能会对程序执行产生不良影响。例如,如果在实时 (JIT) 编译器对字段进行内联访问后,本机方法修改了最终字段,则可能会产生不一致。同样,本机方法不应修改不可变对象,例如 java.lang.String 或 java.lang.Integer 实例中的字段。这样做可能会破坏 Java 平台实现中的不变量。

于 2009-04-20T06:29:25.257 回答
1

您可以尝试以下方法。注意:它根本不是线程安全的,这不适用于编译时已知的常量原语(因为它们被编译器内联)

Field field = SomeClass.class.getDeclareField("INSTANCE");
field.setAccessible(true); // what security. ;)
field.set(null, newValue);
于 2009-04-20T06:57:31.243 回答
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() 而不是您正在使用的不可修改内容的其他部分时才有效。

于 2009-04-20T05:41:47.260 回答
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 美分。

于 2009-04-20T17:15:37.107 回答
-1

如果没有可用的外部破解(至少我不知道),我会破解课程本身。通过添加所需的安全检查来更改代码。因此它是一个外部库,您不会定期进行更新,也不会发生太多更新。每当发生这种情况时,我都可以很高兴地重新做,因为这不是一项大任务。

于 2009-04-20T06:45:23.643 回答
-1

在这里,您的问题是古老的依赖注入(又名控制反转)。您的目标应该是注入您的实现SomeClass而不是对其进行修补。是的,这种方法需要对您现有的设计进行一些更改,但出于正确的原因(在此处说出您最喜欢的设计原则)——尤其是同一个对象不应同时负责创建和使用其他对象。

我假设您使用的方式SomeClass有点像这样:

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

    // some more stuff here...
  }
}

相反,您应该首先创建实现相同接口或扩展的类SomeClass,然后将该实例传递给,doEverything()以便您的类对SomeClass. 在这种情况下,调用的代码doEverything负责传入正确的实现——无论是实际的SomeClass还是你的猴子补丁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...
  }
}
于 2009-04-20T16:32:01.503 回答