Как украсить существующий метод объекта Java?
РЕДАКТИРОВАТЬ: я описал наше решение на /questions/53768355/kak-ukrasit-suschestvuyuschij-metod-obekta-java/53768372#53768372
У меня есть объект java. Это экземпляр одного из многих подклассов, расширяющих абстрактный класс. Я хотел бы изменить один из его методов так, чтобы он запускал дополнительный код перед вызовом исходного метода. Моя цель концептуально такая же, как pointcut в AspectJ.
Это нормально, если я создам некоторую модифицированную версию исходного объекта, а не изменю оригинал. Также нормально, если решение включает манипуляции с байт-кодом.
Предыдущая работа
Я подумал о создании прокси через JavaAssist. Проблема в том, что метод create ProxyFactory ожидает, что я заранее знаю типы ввода конструктора. Я не. Я могу создать свой объект, не вызывая конструктор через Objenesis, но тогда результирующий прокси-объект будет иметь нулевые значения для любых значений, установленных конструктором. Это означает, что мой результирующий объект будет вести себя иначе, чем исходный, всякий раз, когда на значение, установленное конструктором, напрямую ссылается.
Контекст
Мы используем Flink через AWS Kinesis Data Analytics для преобразования некоторых потоковых данных. Мы хотели бы включить некоторый общий код в начало всех методов open() нашего StreamOperator без необходимости изменять каждый оператор. Один из вариантов использования - убедиться, что агент настраиваемых показателей работает на каждом экземпляре, на котором работает оператор.
4 ответа
Ответ от исходного автора вопроса: Мы решили проблему, создав прокси ByteBuddy для StreamExecutionEnvironment, который перехватывает вызовы getStreamGraph и переделывает (с использованием отражения) каждый узел jobVertexClass в класс, который расширил исходный тип класса, но включил нашу настраиваемую логику. Поскольку для разных классов требуются разные параметры, мы создали прокси без вызова конструктора с помощью Objenesis. Чтобы решить проблему с частными полями, которые обычно устанавливаются в конструкторе, оставаясь пустыми, мы использовали отражение, чтобы изменить видимость всех частных полей, а затем скопировали каждое значение поля из исходного объекта в прокси-объект.
Мы не использовали агентное решение, предложенное Рафаэлем Винтерхальтером, потому что оно требует возможности запускать код установки агента на каждом рабочем экземпляре, что аналогично исходной проблеме, связанной с запуском агента метрик на каждой рабочей машине. Хотя я не указывал это в своем исходном вопросе, код, создающий прокси-объекты, происходит на машине управления заданиями Flink, а не на рабочих машинах.
С помощью Byte Buddy вы можете создать оболочку или агент Java, которые оба могут достичь этой цели. Если вы боретесь с вызовом конструктора класса-оболочки, такая же проблема может возникнуть при использовании Byte Buddy, поскольку любая библиотека привязана к ограничениям, заданным JVM.
Чтобы создать агент Java, используйте AgentBuilder
. Затем вы можете указать все типы для перехвата, используяtype
step, например, все типы, реализующие определенный интерфейс или расширяющие класс. Заtransform
, Byte Buddy предлагает API декорации методов, называемый Advice
, он позволяет добавлять дополнительный код, например:
class MyAdvice {
@Advice.OnMethodEnter
static void enter() { System.out.println("Hello"); }
}
по
builder = builder.visit(Advice.to(MyAdvice.class).on(named("foo")));
вы можете, например, напечатать hello world в начале всех методов с именем "foo" для указанных вами типов. Вы можете узнать больше об агентах Java в документации пакета дляjava.instrument
пакет.
Специфичное для Flink решение может заключаться в реализации пользовательских версий используемых вами операторов Flink. Я не уверен, что это приведет вас в хорошее место; просто поделитесь идеей, если это будет полезно.
Документации о том, как реализовать пользовательские операторы, не так много, но по этой теме был разговор в Flink Forward.
Прежде всего, я бы отправил запрос функции в AWS для поддержки вашего варианта использования. Это было бы самое чистое решение.
Во-вторых, я бы не стал искать способ перезаписать open()
. Поскольку вы находитесь в среде, где у вас нет особого контроля, я полагаю, что подходы либо вообще не работают, либо являются хрупкими и ломаются при обновлении среды.
Я бы сделал ленивую инициализацию в соответствующих методах UDF и, конечно, вычеркнул бы это из некоторого общего служебного метода.
private Counter counter;
@Override
public Integer map(String value) {
if (counter == null) {
RuntimeContext ctx = getRuntimeContext();
counter = ctx.getMetricGroup().counter("outputs");
}
counter.inc();
return Integer.parseInt(value);
}