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

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

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

EDIT: просто для пояснения, getInstance() вызывается внешней библиотекой, а не моим кодом, поэтому просто подклассификация не решит проблему.

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

Ответ 1

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

Ответ 2

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

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

Jmockit использует внутреннюю структуру ASM, чтобы сделать то же самое.

Ответ 3

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

Ответ 4

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

EDIT: это возможно, но не очень хорошая идея.

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

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

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

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

Ответ 5

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

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

Ответ 6

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

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

этот код печатает "что-то изменилось!"; вы можете легко заменить свои экземпляры singleton. Мои 0.02 $центов.

Ответ 8

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

Ответ 9

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