Можно ли патчить обезьяну на Java?

Я не хочу обсуждать достоинства этого подхода, просто если это возможно. Я считаю, что ответ - «нет». Но может кто меня удивит!

Представьте, что у вас есть базовый класс виджетов. У него есть метод calculateHeight(), который возвращает высоту. Высота слишком велика - в результате кнопки (скажем) становятся слишком большими. Вы можете расширить DefaultWidget, чтобы создать свой собственный NiceWidget, и реализовать свой собственный calculateHeight(), чтобы вернуть более удобный размер.

Теперь класс библиотеки WindowDisplayFactory создает экземпляр DefaultWidget довольно сложным методом. Вы хотите, чтобы он использовал ваш NiceWidget. Метод фабричного класса выглядит примерно так:

public IWidget createView(Component parent) {
    DefaultWidget widget = new DefaultWidget(CONSTS.BLUE, CONSTS.SIZE_STUPIDLY);

    // bunch of ifs ...
    SomeOtherWidget bla = new SomeOtherWidget(widget);
    SomeResultWidget result = new SomeResultWidget(parent);
    SomeListener listener = new SomeListener(parent, widget, flags);

    // more widget creation and voodoo here

    return result;
}

Вот в чем дело. В результате DefaultWidget находится глубоко внутри иерархии других объектов. Вопрос - как заставить этот заводской метод использовать мой собственный NiceWidget? Или, по крайней мере, поставьте туда мой собственный calculateHeight(). В идеале я хотел бы иметь возможность обезьяны исправлять DefaultWidget, чтобы его calculateHeight работал правильно ...

public class MyWindowDisplayFactory {
    public IWidget createView(Component parent) {
        DefaultWidget.class.setMethod("calculateHeight", myCalculateHeight);
        return super.createView(parent);
    }
}

Это то, что я мог сделать в Python, Ruby и т. д. Я придумал имя setMethod(). Мне доступны следующие другие варианты:

  • Копирование и вставка кода метода createView() в мой собственный класс, наследуемый от фабричного класса
  • Жизнь со слишком большими виджетами

Класс фабрики не может быть изменен - ​​он является частью API базовой платформы. Я попытался отразить возвращенный результат, чтобы добраться до виджета, который (в конце концов) был добавлен, но он находится на нескольких уровнях виджетов и где-то он используется для инициализации других вещей, вызывая странные побочные эффекты.

Любые идеи? Моим решением пока является копирование и вставка, но это отстой, требующий отслеживания изменений в родительском фабричном классе при обновлении до более новых версий платформы, и мне было бы интересно услышать другие варианты.

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
22
0
8 433
9
Перейти к ответу Данный вопрос помечен как решенный

Ответы 9

Одно уродливое решение - разместить собственную реализацию DefaultWidget (с тем же FQCN) раньше в пути к классам, чем обычная реализация. Это ужасный взлом, но все остальные подходы, которые я могу придумать, еще хуже.

К сожалению, я бы хотел использовать 99% виджета настоящий DefaultWidget без возможности доступа к нему.

richq 19.12.2008 22:02

Объектно-ориентированный способ сделать это - создать оболочку, реализующую IWidget, делегируя все вызовы фактическому виджету, кроме calculateHeight, примерно так:

class MyWidget implements IWidget {
    private IWidget delegate;
    public MyWidget(IWidget d) {
        this.delegate = d;
    }
    public int calculateHeight() {
        // my implementation of calculate height
    }
    // for all other methods: {
    public Object foo(Object bar) {
        return delegate.foo(bar);
    }
}

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

Это также зависит от того, что ни один клиент не пытается вернуть IWidget обратно в DefaultWidget ...

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

e-satis 19.12.2008 19:01

Безусловно, я просто публикую это как возможный вариант. Здесь есть множество альтернатив, которые лучше всего подходят в зависимости от фактического сценария: возможности расширения рассматриваемого API, гибкость, платформа, необходимость быть прямой / обратной совместимостью и т. д.

Rolf Rander 20.12.2008 00:26

Только предложения, о которых я могу думать:

  1. Изучите API библиотеки, чтобы увидеть, есть ли способ переопределить значения по умолчанию и размер. Размер может сбивать с толку (по крайней мере, для меня), setMinimum, setMaximum, setdefault, setDefaultOnThursday, .... Возможно, есть способ. Если вы сможете связаться с дизайнером (-ами) библиотеки, вы можете найти ответ, который избавит вас от неприятного взлома.

  2. Возможно, расширить фабрику, заменив только некоторые параметры размера по умолчанию? зависит от завода, но это возможно.

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

Я продолжаю искать метод "setMinimumNoReallyIMeanItStopIgnoringMe".

Paul Tomblin 19.12.2008 18:27

Хех - на самом деле это намного хуже - рассматриваемый код даже не Swing. Глядя на API, он требует внедрения зависимостей, но я понимаю, почему у него этого нет (для простоты). Хо хм!

richq 19.12.2008 22:01
Ответ принят как подходящий

Возможно, вы могли бы использовать аспектно-ориентированное программирование, чтобы перехватывать вызовы этой функции и вместо этого возвращать вашу собственную версию?

Spring предлагает некоторую функциональность АОП, но есть и другие библиотеки, которые это делают.

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

richq 19.12.2008 21:54

Вам не нужен Spring для АОП, просто скачайте AspectJ. Также есть плагин eclipse: eclipse.org/aspectj

Jason Day 19.12.2008 22:31

Просто моя концептуальная идея,

Возможно использование АОП с использованием метода байт-кода для добавления аспекта в метод calculateHeight.

Затем вы можете включить патч с помощью ThreadLocal или другой переменной.

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

Лучшее решение, которое я могу придумать, - создать подкласс WindowDisplayFactory, затем в методе подкласса createView () сначала вызвать super.createView (), затем изменить возвращенный объект, чтобы полностью выбросить виджет и заменить его экземпляром подкласса, который делает что хочешь. Но виджет используется для инициализации, поэтому вам придется все это изменить.

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

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

Да, в дополнительных классах new'd в createView сделано изрядное количество вещей - в конце концов, отражение + исправления, вероятно, были бы более хрупкими (труднее поддерживать с новыми версиями WidgetFactory), чем просто копирование и вставка .

richq 19.12.2008 21:47

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

Другие инструменты для управления байтовым кодом: jakarta.apache.org/bcelasm.objectweb.org

Rolf Rander 20.12.2008 00:31

Вы можете попробовать использовать такие инструменты, как PowerMock / Mockito. Если вы можете издеваться в тестах, вы также можете имитировать в продакшене.

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

Вполне возможно выполнить monkeypatch на Java, используя Unsafe.putObject и средство поиска классов. Написал здесь сообщение в блоге:

https://tersesystems.com/blog/2014/03/02/monkeypatching-java-classes/

Это не совсем обезьяна. Это (просто) изменение значения поля путем взлома абстракции. Теперь, если вы добавляли поля / добавляли методы / изменяли методы во время выполнения (не время загрузки) ... это обезьяна исправления.

Stephen C 19.04.2019 10:40

Из Википедии: «Патч обезьяны - это способ программы локально расширить или изменить поддерживающее системное программное обеспечение (затрагивая только работающий экземпляр программы)». JVM запускается при изменении методов, так что это обезьяна.

Will Sargent 21.04.2019 07:27

Согласно вашей интерпретации этого определения, выполнение любого оператора присваивания также будет обезьяньим исправлением. А также "хитрые" вещи, такие как изменение неизменяемой строки. В любом случае, большинство программистов на Java не согласятся с вашей интерпретацией. Вот почему все остальные предложения относятся к таким вещам, как AOP, разработка байт-кода и фиктивные фреймворки.

Stephen C 21.04.2019 07:37

> А также "хитрые" вещи, такие как изменение неизменяемой строки. Да, по определению. Из вашего связанного URL: «Нет, это не похоже ни на что из этого. Это просто динамическая замена атрибутов во время выполнения». Атрибуты не ограничиваются методами. Такие вещи, как tersesystems.com/blog/2016/01/19/…, конечно же, тоже обезьяны.

Will Sargent 21.04.2019 23:35

Другие вопросы по теме