我知道这通常是相当愚蠢的,但在阅读这个问题之前不要开枪。我保证我有充分的理由需要这样做:)
可以使用反射修改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
}
}
我本质上想要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框架来做同样的事情。
您可以尝试以下方法。注意:它根本不是线程安全的,这对编译时已知的常量基元不起作用(因为它们由编译器内联)
Field field = SomeClass.class.getDeclareField("INSTANCE");
field.setAccessible(true); // what security. ;)
field.set(null, newValue);
您应该可以使用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平台中的不变量
实现。
如果你真的必须(虽然对于我们的问题,我建议你使用CaptainAwesomePants的解决方案)你可以看看 JMockIt。虽然这有意在单元测试中使用,但是如果允许您重新定义任意方法。这是通过在运行时修改字节码来完成的。
我将通过承认这实际上不是对您所声明的关于修改私有静态最终字段的问题的答案来解释这个答案。但是,在上面提到的具体示例代码中,我实际上可以使它可以覆盖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...
}
}