Есть ли способ изменить значение поля "private static final" в Java вне класса? - PullRequest
11 голосов
/ 20 апреля 2009

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

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

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

Ответы [ 9 ]

10 голосов
/ 20 апреля 2009

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

5 голосов
/ 20 апреля 2009

Любой AOP-фреймворк будет соответствовать вашим потребностям

Это позволит вам определить переопределение времени выполнения для метода getInstance, позволяющего вам возвращать любой класс, который вам подходит.

Jmockit использует внутреннюю среду ASM для того же.

1 голос
/ 20 апреля 2009

Вы можете попробовать следующее. Примечание: это не совсем потокобезопасно, и это не работает для константных примитивов, известных во время компиляции (поскольку они встроены компилятором)

Field field = SomeClass.class.getDeclareField("INSTANCE");
field.setAccessible(true); // what security. ;)
field.set(null, newValue);
1 голос
/ 20 апреля 2009

Вы должны иметь возможность изменить его с помощью JNI ... не уверен, что это вариант для вас.

РЕДАКТИРОВАТЬ: это возможно, но не очень хорошая идея.

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

10,9 Нарушение правил контроля доступа

JNI не применяет класс, поле, и метод контроля доступа ограничения что можно выразить на Яве уровень языка программирования через использование модификаторов, таких как частные и окончательный. Можно написать родной код для доступа или изменения полей возражать, хотя делает это на Уровень языка программирования Java будет привести к IllegalAccessException. Вседозволенность JNI была сознательной дизайнерское решение, учитывая, что родной код может получить доступ и изменить любую память расположение в куче в любом случае.

Собственный код, который обходит проверки доступа на уровне исходного языка может иметь нежелательные последствия для выполнение программы. Например, несоответствие может быть создано, если нативный метод изменяет конечное поле после JIT-компилятора имеет встроенный доступ к полю. Аналогично, нативные методы не должны изменить неизменяемые объекты, такие как поля в случаях java.lang.String или java.lang.Integer. Это может привести к поломке инварианты в платформе Java осуществление.

1 голос
/ 20 апреля 2009

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

0 голосов
/ 20 апреля 2009

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

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 $ центов.

0 голосов
/ 20 апреля 2009

Здесь ваша проблема - это старый добрый Dependency Injection (он же Inversion of Control). Ваша цель должна заключаться в том, чтобы внедрить вашу реализацию 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...
  }
}
0 голосов
/ 20 апреля 2009

Если бы не было внешнего хака, доступного (по крайней мере, я не знаю), я бы взломал сам класс. Измените код, добавив необходимую проверку безопасности. Так как это внешняя библиотека, вы не будете регулярно получать обновления, также не так много обновлений в любом случае. Всякий раз, когда это происходит, я с радостью могу сделать это заново, так как в любом случае это не большая задача.

0 голосов
/ 20 апреля 2009

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

...