简体   繁体   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?

I know this is normally rather stupid, but don't shoot me before reading the question. 我知道这通常是相当愚蠢的,但在阅读这个问题之前不要开枪。 I promise I have a good reason for needing to do this :) 我保证我有充分的理由需要这样做:)

It's possible to modify regular private fields in java using reflection, however Java throws a security exception when trying to do the same for final fields. 可以使用反射修改java中的常规私有字段,但是在尝试对final字段执行相同操作时,Java会引发安全性异常。

I'd assume this is strictly enforced, but figured I'd ask anyway just in case someone had figured out a hack to do this. 我认为这是严格执行的,但无论如何我都会问,以防万一有人想出了一个黑客来做这件事。

Let's just say I have an external library with a class " SomeClass " 我只想说我有一个带有“ SomeClass ”类的外部库

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

  public static SomeClass getInstance(){ 
      return INSTANCE; 
  }

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

I essentially want to Monkey-Patch SomeClass so that I can execute my own version of doSomething() . 我基本上想要Monkey-Patch SomeClass,以便我可以执行自己的doSomething()版本。 Since there isn't (to my knowledge) any way to really do that in java, my only solution here is to alter the value of INSTANCE so it returns my version of the class with the modified method. 由于没有(据我所知)在java中真正做到这一点的任何方法,我这里唯一的解决方案是改变INSTANCE的值,以便它使用修改后的方法返回我的类版本。

Essentially I just want to wrap the call with a security check and then call the original method. 基本上我只想用安全检查包装调用,然后调用原始方法。

The external library always uses getInstance() to get an instance of this class (ie it's a singleton). 外部库总是使用getInstance()来获取此类的实例(即它是一个单例)。

EDIT: Just to clarify, getInstance() is called by the external library, not my code, so just subclassing won't solve the issue. 编辑:只是为了澄清, getInstance()由外部库调用,而不是我的代码,所以只是子类化不会解决问题。

If I can't do that the only other solution I can think of is to copy-paste entire class and modify the method. 如果我不能这样做,我能想到的唯一其他解决方案是复制粘贴整个类并修改方法。 This isn't ideal as I'll have to keep my fork up to date with changes to the library. 这并不理想,因为我必须更新库以更新库。 If someone has something a little more maintainable I'm open to suggestions. 如果某人有更多可维护的东西,我愿意接受建议。

It is possible. 有可能的。 I've used this to monkeypatch naughty threadlocals that were preventing class unloading in webapps. 我已经用它来monkeypatch淘气的threadlocals,它阻止了webapps中的类卸载。 You just need to use reflection to remove the final modifier, then you can modify the field. 您只需要使用反射来删除final修改器,然后您可以修改该字段。

Something like this will do the trick: 像这样的东西可以解决这个问题:

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

There is some caching as well around Field#set , so if some code has run before it might not necessarily work.... Field#set周围也有一些缓存,所以如果某些代码运行之前它可能不一定有效....

Any AOP framework would fit your needs 任何AOP框架都能满足您的需求

It would allow you to define a runtime override for the getInstance method allowing you to return whatever class suits your need. 它允许您为getInstance方法定义运行时覆盖,允许您返回任何适合您需要的类。

Jmockit uses the ASM framework internally to do the same thing. Jmockit在内部使用ASM框架来做同样的事情。

If you really must (though for our problem I'd suggest you use the solution of CaptainAwesomePants) you could have a look at JMockIt . 如果你真的必须(虽然对于我们的问题,我建议你使用CaptainAwesomePants的解决方案),你可以看看JMockIt Although this is intented to be used in unit tests if allows you to redefine arbitrary methods. 虽然这可以用于单元测试,但是如果允许你重新定义任意方法。 This is done by modifying the bytecode at runtime. 这是通过在运行时修改字节码来完成的。

You should be able to change it with JNI... not sure if that is an option for you. 您应该可以使用JNI进行更改...不确定这是否适合您。

EDIT: it is possible, but not a good idea. 编辑:这是可能的,但不是一个好主意。

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

10.9 Violating Access Control Rules 10.9违反访问控制规则

The JNI does not enforce class, field, and method access control restrictions that can be expressed at the Java programming language level through the use of modifiers such as private and final. JNI不强制实施类,字段和方法访问控制限制,这些限制可以通过使用私有和最终修饰符在Java编程语言级别表达。 It is possible to write native code to access or modify fields of an object even though doing so at the Java programming language level would lead to an IllegalAccessException. 可以编写本机代码来访问或修改对象的字段,即使在Java编程语言级别这样做会导致IllegalAccessException。 JNI's permissiveness was a conscious design decision, given that native code can access and modify any memory location in the heap anyway. 鉴于本机代码无论如何都可以访问和修改堆中的任何内存位置,JNI的放纵是一种有意识的设计决策。

Native code that bypasses source-language-level access checks may have undesirable effects on program execution. 绕过源语言级访问检查的本机代码可能会对程序执行产生不良影响。 For example, an inconsistency may be created if a native method modifies a final field after a just-in-time (JIT) compiler has inlined accesses to the field. 例如,如果本机方法在实时(JIT)编译器内联访问字段后修改了最终字段,则可能会产生不一致。 Similarly, native methods should not modify immutable objects such as fields in instances of java.lang.String or java.lang.Integer. 类似地,本机方法不应该修改不可变对象,例如java.lang.String或java.lang.Integer实例中的字段。 Doing so may lead to breakage of invariants in the Java platform implementation. 这样做可能会导致Java平台实现中的不变量破坏。

You can try the following. 您可以尝试以下方法。 Note: It is not at all thread safe and this doesn't work for constant primitives known at compile time (as they are inlined by the compiler) 注意:它根本不是线程安全的,这对编译时已知的常量基元不起作用(因为它们由编译器内联)

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

I will preface this answer by acknowledging that this is not actually an answer to your stated question about modifying a private static final field. 我将通过承认这实际上不是对你所声明的关于修改私有静态最终字段的问题的答案来作为答案的前言。 However, in the specific example code mentioned above, I can in fact make it so that you can override doSomething(). 但是,在上面提到的具体示例代码中,我实际上可以使它可以覆盖doSomething()。 What you can do is to take advantage of the fact that getInstance() is a public method and subclass: 你可以做的是利用getInstance()是一个公共方法和子类的事实:

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

   public SomeClass getInstance() {
        return INSTANCE;
   }

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

Now just invoke MySomeClass.getInstance() instead of SomeClass.getInstance() and you're good to go. 现在只需调用MySomeClass.getInstance()而不是SomeClass.getInstance(),你就可以了。 Of course, this only works if you're the one invoking getInstance() and not some other part of the unmodifiable stuff you're working with. 当然,这只有在你调用getInstance()而不是你正在使用的不可修改的东西的其他部分时才有效。

with mockito is very simple: 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());
    }
}

this code prints "something changed!"; 这段代码打印出“改变了一些东西!”; you can easily replace your singleton instances. 您可以轻松替换您的单例实例。 My 0.02$ cents. 我0.02美分。

If there is no external hack available (at least I am not aware of) I would have hacked the class itself. 如果没有可用的外部黑客(至少我不知道),我会破解这个类本身。 Change the code by adding the security check you want. 通过添加所需的安全检查来更改代码。 As such its an external library, you won't be taking the updates regularly, also not many update happens anyway. 因此它是一个外部库,你不会定期更新,也不会发生很多更新。 Whenever that happens I can happily re-do it as it is not a big task anyway. 无论什么时候发生这种情况,我都可以愉快地重新做到这一点,因为无论如何这不是一件大事。

Here, your problem is good-old Dependency Injection (aka Inversion of Control). 在这里,你的问题是很好的依赖注入(又名反转控制)。 Your goal should be to inject your implementation of SomeClass instead of monkeypatching it. 你的目标应该是注入SomeClass的实现而不是monkeypatching它。 And yes, this approach requires some changes to your existing design but for the right reasons (name your favorite design principle here) - especially the same object should not be responsible for both creating and using other objects. 是的,这种方法需要对现有设计进行一些更改,但出于正确的原因(在此处列出您最喜欢的设计原则) - 尤其是同一个对象不应该负责创建和使用其他对象。

I assume the way you're using SomeClass looks somewhat like this: 我假设您使用SomeClass的方式看起来有点像这样:

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

    // some more stuff here...
  }
}

Instead, what you should do is first create your class that implements the same interface or extends SomeClass and then pass that instance to doEverything() so your class becomes agnostic to implementation of SomeClass . 相反,你应该做的是首先创建实现相同接口的类或扩展SomeClass ,然后将该实例传递给doEverything()这样你的类就变得与SomeClass实现无关。 In this case the code that calls doEverything is responsible for passing in the correct implementation - whether be the actual SomeClass or your monkeypatched MySomeClass . 在这种情况下,调用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