Есть ли какой-либо способ изменить значение поля `private static final` в Java извне класса?

StackOverflow https://stackoverflow.com/questions/767008

  •  12-09-2019
  •  | 
  •  

Вопрос

Я знаю, что обычно это довольно глупо, но не стреляйте в меня, прежде чем прочитать вопрос.Я обещаю, что у меня есть веская причина для того, чтобы сделать это :)

В 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() вызывается внешней библиотекой, а не моим кодом, поэтому простое разделение на подклассы не решит проблему.

Если я не могу этого сделать, единственное другое решение, которое я могу придумать, - скопировать-вставить весь класс и изменить метод.Это не идеально, так как мне придется поддерживать свой форк в курсе изменений в библиотеке.Если у кого-то есть что-то более удобное в обслуживании, я открыт для предложений.

Это было полезно?

Решение

Это вполне возможно.Я использовал это, чтобы исправлять непослушные 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 не применяет ограничения контроля доступа к классам, полям и методам , которые могут быть выражены на уровне языка программирования Java посредством использования модификаторов, таких как private и final.Можно написать собственный код для доступа к полям объекта или их изменения, даже если выполнение этого на уровне языка программирования Java привело бы к исключению IllegalAccessException.Вседозволенность JNI была осознанным дизайнерским решением учитывая, что машинный код в любом случае может обращаться к любой памяти и изменять ее расположение в куче.

Машинный код, который обходит проверки доступа на уровне исходного языка может иметь нежелательные последствия для выполнения программы.Например, несоответствие может быть создано, если собственный метод изменяет конечное поле после того, как 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(), а не какую-то другую часть неизменяемого материала, с которым вы работаете.

с мокито это очень просто:

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 вместо того, чтобы обезьянничать.И да, этот подход требует некоторых изменений в вашем существующем дизайне, но по правильным причинам (назовите здесь свой любимый принцип дизайна) - особенно один и тот же объект не должен отвечать как за создание, так и за использование других объектов.

Я предполагаю, что способ, которым вы пользуетесь 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...
  }
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top