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.
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 25

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

      tagline: кармапоклонники — это безобидный коммент… в карму можете не смотреть :)
        +3
        что все так трясутся, жополизы.
          –6
          Вы мои комменты изучите, а потом подумайте жополиз ли я? :)

          tagline: кармадрочеры старайтесь лучше "-10" за сегодня, темпы падения кармы падают… :)
            –2
            Думаю пора опубликовать предварительные итоги эксперимента с таглайнами:
            Карма: -20,00
            52 голоса
            Сила: 26,50

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

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

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

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

              Поверте, все ваши минусы заслужены, ну по крайней мере те, что были поставленны с начала «эксперемента».
                +3
                лично выбирался из -15 точно :)
          • UFO just landed and posted this here
              0
              Инопланетяне для этого слишком разумны.

              Имхо это дело парсера. Вспоминается эпическое «Я идиот! Убейте меня!»
            0
            Я бы хотел уточнить насколько временное это решение?
            На мой взгляд, лучше просто заменить код на правильный (без хаков) во время плановой остановки сервера на обслуживание. Или с помощью средств виртуализации, если таковые используются.
              +1
              Код писался для разового использования, ибо останавливать сервер было нельзя, но и работать дальше без исправления было невозможно. Естественно, при плановой остановке было проведено нормальное обновление.

              А еще таким способом можно работать с либами, для которых нет исходников. Изжоп, но всякое случается.
              0
              Да, в Delphi всё проще реализуется — взял и сдал предков. Тут уже не Павлик, тут уже шпион какой-нибудь.
                0
                Защита от дурака, однако.
              • UFO just landed and posted this here
                  +1
                  Замотался, забыл :)

                  Блин, 2 месяца прошло…
                  +1
                  А можно уточнить зачем потребовалось менять аттрубут который разве что не заминирован, настолько его автор был против смены?
                    0
                    Потому что модуль заглючил. Я уже сам забыл детали. А private final это вполне обычное состояние для внутренних объектов. ООП же, видимость ставится минимально возможной, во избежание. Иногда это выходит боком.
                    0
                    Главное потом не забыть связаться с автором, уговорить его сделать публичный метод, а потом не забыть убрать этот антипаттерн, а то ведь в один прекрасный день ружье возьмет да и выстрелит
                      +1
                      #define private public
                        +3
                        А теперь сделай это на работающем приложении без его остановки.
                          –1
                          Ну кто же спорит. Раз уж вспомнили Паблика Морозова, то как это не вспомнить:)
                          0
                          Тогда уж #define true false
                          0
                          Если вы знали заранее, что сервер нельзя ни в коем случае останавливать, а фиксить придётся, то Java — не лучший выбор. Конечно, я не могу знать всех причин, но Common Lisp или (если у вас панический страх перед скобочками) erlang был бы лучшим выбором.
                            +5
                            Причин много, и все веские. Пожалуй, самая веская та, что переписывать уже имеющегося монстра из >200000 строк кода на совершенно другой язык силами трех человек, ни один из которых этот язык не знает — дохлое дело.
                            0
                            Ваш пример тяжело назвать паттерном программирования, это просто ваше решение нетривиальной задачи, коих слишком много.
                              0
                              Согласен. Просто это устоявшееся название для задач на разрушение инкапсуляции.

                            Only users with full accounts can post comments. Log in, please.