Возможно ли обезьянить патч на Java? - PullRequest
21 голосов
/ 19 декабря 2008

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

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

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

Что я мог бы сделать в Python, Ruby и т. Д. Я изобрел имя setMethod(). Другие варианты, открытые для меня:

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

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

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

Ответы [ 9 ]

9 голосов
/ 19 декабря 2008

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

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

7 голосов
/ 19 декабря 2008

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

3 голосов
/ 19 декабря 2008

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

Возможно, что с помощью AOP, с помощью метода инженерии байт-кода, для внедрения аспекта в метод calcHeight.

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

2 голосов
/ 01 мая 2017

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

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

2 голосов
/ 19 декабря 2008

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

0 голосов
/ 08 октября 2015

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

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

0 голосов
/ 19 декабря 2008

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

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

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

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

0 голосов
/ 19 декабря 2008

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

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

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

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

0 голосов
/ 19 декабря 2008

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

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 ...

...