Однажды понадобилось мне переопределить на работающей программе поле, помеченное как 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.

Комментарии (25)


  1. SerGold
    19.01.2010 04:28

    Павлик Морозов — это не паттерн, а Антипаттерн!

    tagline: кармапоклонники — это безобидный коммент… в карму можете не смотреть :)


    1. SwampRunner
      19.01.2010 04:28

      что все так трясутся, жополизы.


      1. SerGold
        19.01.2010 04:28

        Вы мои комменты изучите, а потом подумайте жополиз ли я? :)

        tagline: кармадрочеры старайтесь лучше "-10" за сегодня, темпы падения кармы падают… :)


        1. SerGold
          19.01.2010 04:28

          Думаю пора опубликовать предварительные итоги эксперимента с таглайнами:
          Карма: -20,00
          52 голоса
          Сила: 26,50

          выводы:
          1. адекватных людей с кармой позволяющей ставить "+" только комментам, на хабре большинство;
          2. не раз наблюдал «задротов» пробегающихся не только по карме, но и по всем комментам...;
          3. выбраться из минуса невозможно!..

          ну и главный вывод: система кармы прогнила!

          Всем спасибо, если у кого смелости хватит — буду благодарен за размещение этого в виде топика!


          1. SilenceAndy
            19.01.2010 04:28

            Вы бредите. Хабр это сообщество и как в любом сообществе есть абсолютно разные люди. Кому-то ваш комментарий нравится, кому-то нет, кого-то он вообще бесит и он ставит минус в карму. Конечно есть опредленный процент людей которые в любом случае «срут» в карму, но их не так много.

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

            Поверте, все ваши минусы заслужены, ну по крайней мере те, что были поставленны с начала «эксперемента».


          1. corristo
            19.01.2010 04:28

            лично выбирался из -15 точно :)


      1. maeris
        19.01.2010 04:28

        Инопланетяне для этого слишком разумны.

        Имхо это дело парсера. Вспоминается эпическое «Я идиот! Убейте меня!»


  1. anonymous
    19.01.2010 04:28


  1. wwarlock
    19.01.2010 04:28

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


    1. Oblitus Автор
      19.01.2010 04:28

      Код писался для разового использования, ибо останавливать сервер было нельзя, но и работать дальше без исправления было невозможно. Естественно, при плановой остановке было проведено нормальное обновление.

      А еще таким способом можно работать с либами, для которых нет исходников. Изжоп, но всякое случается.


  1. web4_0
    19.01.2010 04:28

    Да, в Delphi всё проще реализуется — взял и сдал предков. Тут уже не Павлик, тут уже шпион какой-нибудь.


    1. Oblitus Автор
      19.01.2010 04:28

      Защита от дурака, однако.


  1. anonymous
    19.01.2010 04:28


    1. Oblitus Автор
      19.01.2010 04:28

      Замотался, забыл :)

      Блин, 2 месяца прошло…


  1. kivsiak
    19.01.2010 04:28

    А можно уточнить зачем потребовалось менять аттрубут который разве что не заминирован, настолько его автор был против смены?


    1. Oblitus Автор
      19.01.2010 04:28

      Потому что модуль заглючил. Я уже сам забыл детали. А private final это вполне обычное состояние для внутренних объектов. ООП же, видимость ставится минимально возможной, во избежание. Иногда это выходит боком.


  1. Gorthauer87
    19.01.2010 04:28

    Главное потом не забыть связаться с автором, уговорить его сделать публичный метод, а потом не забыть убрать этот антипаттерн, а то ведь в один прекрасный день ружье возьмет да и выстрелит


  1. yeroo
    19.01.2010 04:28

    #define private public


    1. Oblitus Автор
      19.01.2010 04:28

      А теперь сделай это на работающем приложении без его остановки.


      1. yeroo
        19.01.2010 04:28

        Ну кто же спорит. Раз уж вспомнили Паблика Морозова, то как это не вспомнить:)


  1. anonymous
    19.01.2010 04:28


  1. naryl
    19.01.2010 04:28

    Если вы знали заранее, что сервер нельзя ни в коем случае останавливать, а фиксить придётся, то Java — не лучший выбор. Конечно, я не могу знать всех причин, но Common Lisp или (если у вас панический страх перед скобочками) erlang был бы лучшим выбором.


    1. Oblitus Автор
      19.01.2010 04:28

      Причин много, и все веские. Пожалуй, самая веская та, что переписывать уже имеющегося монстра из >200000 строк кода на совершенно другой язык силами трех человек, ни один из которых этот язык не знает — дохлое дело.


  1. AndryX
    19.01.2010 04:28

    Ваш пример тяжело назвать паттерном программирования, это просто ваше решение нетривиальной задачи, коих слишком много.


    1. Oblitus Автор
      19.01.2010 04:28

      Согласен. Просто это устоявшееся название для задач на разрушение инкапсуляции.