有什么方法可以从类外部修改 Java 中“private static final”字段的值吗?

发布于 2024-07-17 13:12:48 字数 941 浏览 9 评论 0原文

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

可以使用反射修改 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 
  }
} 

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

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

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

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

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

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.

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"

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(). 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.

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 (i.e. it's a singleton).

EDIT: Just to clarify, getInstance() is called by the external library, not my code, so just subclassing won't solve the issue.

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

嘿看小鸭子会跑 2024-07-24 13:12:48

有可能的。 我用它来修补顽皮的线程局部变量,这些线程局部变量阻止了 Web 应用程序中的类卸载。 你只需要使用反射去掉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 周围也有一些缓存,所以如果某些代码之前已经运行过,它可能不一定有效......

It is possible. I've used this to monkeypatch naughty threadlocals that were preventing class unloading in webapps. You just need to use reflection to remove the final modifier, then you can modify the field.

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....

倾听心声的旋律 2024-07-24 13:12:48

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

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

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

Any AOP framework would fit your needs

It would allow you to define a runtime override for the getInstance method allowing you to return whatever class suits your need.

Jmockit uses the ASM framework internally to do the same thing.

醉梦枕江山 2024-07-24 13:12:48

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

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

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);
玩世 2024-07-24 13:12:48

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

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

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

10.9 违反访问控制规则

JNI 不强制执行类、字段、
和方法访问控制限制
可以用Java来表示
编程语言级别通过
使用修饰符,例如 private 和
最终的。 原生可以写
访问或修改字段的代码
对象,即使这样做是在
Java 编程语言级别将
导致 IllegalAccessException。
JNI的宽容是有意识的
设计决策,考虑到本机
代码可以访问和修改任何内存
无论如何在堆中的位置。

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

You should be able to change it with JNI... not sure if that is an option for you.

EDIT: it is possible, but not a good idea.

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

10.9 Violating Access Control Rules

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. 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.
JNI's permissiveness was a conscious
design decision, given that native
code can access and modify any memory
location in the heap anyway.

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.
Similarly, native methods should not
modify immutable objects such as
fields in instances of
java.lang.String or java.lang.Integer.
Doing so may lead to breakage of
invariants in the Java platform
implementation.

微凉 2024-07-24 13:12:48

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

If you really must (though for our problem I'd suggest you use the solution of CaptainAwesomePants) you could have a look at 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.

霊感 2024-07-24 13:12:48

我将在这个答案的序言中承认,这实际上并不是您所说的有关修改私有静态最终字段的问题的答案。 然而,在上面提到的具体示例代码中,我实际上可以这样做,以便您可以重写 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() 而不是您正在使用的不可修改内容的其他部分时,这才有效。

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(). What you can do is to take advantage of the fact that getInstance() is a public method and subclass:

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. 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.

霞映澄塘 2024-07-24 13:12:48

使用 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());
    }
}

此代码打印“somethingchange!”; 您可以轻松替换您的单例实例。 我的 0.02 美分。

with mockito is very simple:

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.

明月夜 2024-07-24 13:12:48

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

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.

装迷糊 2024-07-24 13:12:48

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

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. 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:

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. 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.

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...
  }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文