代码之家  ›  专栏  ›  技术社区  ›  James Davies

有没有办法从Java外部修改Java中的“私有静态终结”字段的值?

  •  11
  • James Davies  · 技术社区  · 16 年前

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

    可以使用反射修改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修补,以便执行自己的版本 doSomething() . 因为没有(我的知识)在Java中真正做到这一点,所以我唯一的解决办法是改变价值。 INSTANCE 所以它用修改后的方法返回类的版本。

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

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

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

    如果我不能做到这一点,我唯一能想到的解决方案就是复制粘贴整个类并修改方法。这不太理想,因为我必须使我的fork与库中的更改保持最新。如果有人有更容易维护的东西,我愿意接受建议。

    9 回复  |  直到 10 年前
        1
  •  10
  •   Michael Myers KitsuneYMG    16 年前

    这是可能的。我把它用于monkeypatch恶意的线程局部变量,它们阻止了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 ,因此,如果某些代码在它可能不一定工作之前就已经运行了….

        2
  •  5
  •   Jean    16 年前

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

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

    jmockit在内部使用asm框架来做同样的事情。

        3
  •  1
  •   Peter Lawrey    16 年前

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

    Field field = SomeClass.class.getDeclareField("INSTANCE");
    field.setAccessible(true); // what security. ;)
    field.set(null, newValue);
    
        4
  •  1
  •   TofuBeer    16 年前

    你应该能用JNI改变它…不确定这是否适合你。

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

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

    10.9违反访问控制规则

    JNI不强制类、字段, 和方法访问控制限制 可以用Java来表达 通过编程语言水平 使用诸如private和 决赛。可以写本地的 访问或修改 对象,即使在 Java编程语言级 导致非法访问异常。 JNI的放任是有意识的 设计决策,考虑到本地 代码可以访问和修改任何内存 堆中的位置。

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

        5
  •  1
  •   reevesy onejigtwojig    12 年前

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

        6
  •  0
  •   Brandon Yarbrough    16 年前

    在回答这个问题之前,我承认这并不是你所说的关于修改私有静态最终字段的问题的答案。但是,在上面提到的特定示例代码中,我实际上可以这样做,以便您可以重写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()的人,而不是您正在处理的不可修改内容的其他部分时,这才有效。

        7
  •  0
  •   dfa    16 年前

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

        8
  •  -1
  •   Bhushan Bhangale    16 年前

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

        9
  •  -1
  •   Cem Catikkas    16 年前

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

    我猜你用的方式 身体类 看起来有点像这样:

    public class OtherClass {
      public void doEverything() {
        SomeClass sc = SomeClass.geInstance();
        Object o = sc.doSomething();
    
        // some more stuff here...
      }
    }
    

    相反,您应该首先创建实现相同接口或扩展的类 身体类 然后将该实例传递给 doEverything() 所以您的类对于实现 身体类 . 在这种情况下,调用的代码 doEverything 负责传递正确的执行-是否实际 身体类 或者你的蒙凯补丁 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...
      }
    }