Java
19 January 2010

Java и паттерн Public Morozov

Однажды понадобилось мне переопределить на работающей программе поле, помеченное как private final. Причем останавливать программу было нельзя, ибо сервер. Ну и как маленькое дополнение тип переменной был определен как inner класс. Разумеется тоже private.

К счастью, программа позволяет на ходу подключать модули, содержащие произвольный код. А значит — в нашем распоряжении вся мощь reflection!

Напоминаю, что это proof-of-concept, поэтому для конкретной задачи придется как минимум установить нужный тип для accessor, и вообще при выдирании кода из контекста некоторые блоки могут потерять смысл. Например в приведенном примере всё будет работать и без замены accessor вообще, достаточно будет снять final. Следует учитывать, что это будет гарантированно работать только для объектов. Примитивы обычно инлайнятся компилятором.

package main;

class PublicMorozov
{
  // заготавливаем инстанс, для которого мы будем создавать экземпляр inner класса
  private static final PublicMorozov INSTANCE = new PublicMorozov();
  // и поле, содержимое которого и будем заменять
  private static final java.lang.ref.WeakReference<Inner> targetField = new java.lang.ref.WeakReference<Inner>(null);

  private class Inner
  {}

  public PublicMorozov()
  {}

  public static void makeReplace() throws Exception
  {
    // получаем через рефлект поле, которое предстоит заменить
    java.lang.reflect.Field targetAsField = Class.forName("main.PublicMorozov").getDeclaredField("targetField");
    // снимаем с него private
    targetAsField.setAccessible(true);

    // получаем адрес поля модификаторов в целевом поле
    java.lang.reflect.Field modifiers = Class.forName("java.lang.reflect.Field").getDeclaredField("modifiers");
    // снимаем с него private
    modifiers.setAccessible(true);

    // снимаем с целевого поля private и final, а вместо них ставим public
    modifiers.setInt(targetAsField, targetAsField.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
    modifiers.setInt(targetAsField, targetAsField.getModifiers() & ~java.lang.reflect.Modifier.PRIVATE);
    modifiers.setInt(targetAsField, targetAsField.getModifiers() | java.lang.reflect.Modifier.PUBLIC);

    // но всё не так просто... если попытаться применить изменения то нас может отправить куда подальше с IllegalAccessException
    // поэтому мы заменяем accessor на свой, которому будет пофиг на финал
    // мы используем именно overrideFieldAccessor поскольку поле изначально было private, в противном случае следует использовать fieldAccessor
    java.lang.reflect.Field accessorField = Class.forName("java.lang.reflect.Field").getDeclaredField("overrideFieldAccessor");
    // как обычно снимаем с него private
    accessorField.setAccessible(true);
    // поскольку мы заменяем статический Object нам нужен именно этот тип, их много под разные типы полей и данных
    java.lang.reflect.Constructor accessorConstructor = Class.forName("sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl").getDeclaredConstructor(java.lang.reflect.Field.class, boolean.class);
    // конструктор тоже сокрыт... но разве нас этим испугаешь?
    accessorConstructor.setAccessible(true);
    // вот теперь всё нормально - новый accessor на поле final и смотреть не будет
    accessorField.set(targetAsField, accessorConstructor.newInstance(targetAsField, false));

    // и на десерт - доступ к inner классу
    java.lang.reflect.Constructor innerConstructor = Class.forName("main.PublicMorozov$Inner").getDeclaredConstructor(Class.forName("main.PublicMorozov"));
    innerConstructor.setAccessible(true);

    // заменяем таки содержимое нужного поля
    targetAsField.set(null, new java.lang.ref.WeakReference<Inner>((Inner) innerConstructor.newInstance(INSTANCE)));
  }
}


* This source code was highlighted with Source Code Highlighter.

+37
25.7k 41
Comments 25