Делегирование метода Byte Buddy приводит к StackOverflowError

Я пытаюсь создать агент, который проксирует методы, чтобы выполнять некоторую логику до и после метода. Для этого я использую Byte Buddy с делегированием метода.

Агент:

//Agent code before...
private static void instrument(String agentOps, Instrumentation inst) {
    new AgentBuilder.Default().with(new Eager())
            .ignore(ElementMatchers.nameContains("com.dvelopp.agenttest"))
            .or(ElementMatchers.hasAnnotation(ElementMatchers.annotationType(ElementMatchers.nameContains("SpringBootApplication"))))
            .type((ElementMatchers.any()))
            .transform((builder, typeDescription, classLoader, module) -> builder.method(ElementMatchers.any())
                    .intercept(MethodDelegation.to(Interceptor.class)))
            .installOn(inst);
}
//Agent code after...

Перехватчик:

public static class Interceptor {

    @RuntimeType
    public static Object intercept(@SuperCall Callable<?> superCall, @SuperMethod Method superMethod, @Origin Method currentMethod,
                                   @AllArguments Object[] args, @This(optional = true) Object me) throws Exception {
        //... logic
        Object call = superCall.call();
        //... logic
        return call;
    }
}

Он отлично работает в простом консольном приложении. Но когда у меня есть несколько общих библиотек в моем пути к классам (например, простой проект Spring Boot), он работает не так, как ожидалось. Он вызывает разные ошибки, но большинство из них связано с неожиданным отражением, например:

Exception in thread "main" java.lang.NoClassDefFoundError: sun/reflect/GeneratedMethodAccessor24
at sun.reflect.GeneratedMethodAccessor24.<clinit>(Unknown Source)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at java.lang.Class.newInstance(Class.java:442)
at sun.reflect.MethodAccessorGenerator$1.run(MethodAccessorGenerator.java:403)
at sun.reflect.MethodAccessorGenerator$1.run(MethodAccessorGenerator.java:394)
at java.security.AccessController.doPrivileged(Native Method)
at sun.reflect.MethodAccessorGenerator.generate(MethodAccessorGenerator.java:393)
at sun.reflect.MethodAccessorGenerator.generateMethod(MethodAccessorGenerator.java:75)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:53)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.apache.logging.log4j.status.StatusLogger.<clinit>(StatusLogger.java:85)
at org.apache.logging.log4j.LogManager.<clinit>(LogManager.java:60)
at org.apache.commons.logging.LogAdapter$Log4jLog.<clinit>(LogAdapter.java:135)
at org.apache.commons.logging.LogAdapter$Log4jAdapter.createLog$original$snrPR67N(LogAdapter.java:102)
at org.apache.commons.logging.LogAdapter$Log4jAdapter.createLog$original$snrPR67N$accessor$5P1QZaof(LogAdapter.java)
at org.apache.commons.logging.LogAdapter$Log4jAdapter$auxiliary$pVmPc64S.call(Unknown Source)
at com.dvelopp.agenttest.Main$Interceptor.intercept(Main.java:126)
at org.apache.commons.logging.LogAdapter$Log4jAdapter.createLog(LogAdapter.java)
at org.apache.commons.logging.LogAdapter.createLog$original$4Ty3vM8s(LogAdapter.java:79)
at org.apache.commons.logging.LogAdapter.createLog$original$4Ty3vM8s$accessor$z3gv7aJK(LogAdapter.java)
at org.apache.commons.logging.LogAdapter$auxiliary$6cBoraQE.call(Unknown Source)
at com.dvelopp.agenttest.Main$Interceptor.intercept(Main.java:126)
at org.apache.commons.logging.LogAdapter.createLog(LogAdapter.java)
at org.apache.commons.logging.LogFactory.getLog$original$MoDHp2B7(LogFactory.java:67)
at org.apache.commons.logging.LogFactory.getLog$original$MoDHp2B7$accessor$VdtqA6Wx(LogFactory.java)
at org.apache.commons.logging.LogFactory$auxiliary$ak9XEBl0.call(Unknown Source)
at com.dvelopp.agenttest.Main$Interceptor.intercept(Main.java:126)
at org.apache.commons.logging.LogFactory.getLog(LogFactory.java)
at org.apache.commons.logging.LogFactory.getLog$original$MoDHp2B7(LogFactory.java:59)
at org.apache.commons.logging.LogFactory.getLog$original$MoDHp2B7$accessor$VdtqA6Wx(LogFactory.java)
at org.apache.commons.logging.LogFactory$auxiliary$8JhBdK8k.call(Unknown Source)
at com.dvelopp.agenttest.Main$Interceptor.intercept(Main.java:126)
at org.apache.commons.logging.LogFactory.getLog(LogFactory.java)
at org.springframework.boot.SpringApplication.<clinit>(SpringApplication.java:194)
at hello.Application.main(Application.java:15)
Caused by: java.lang.ClassNotFoundException: sun.reflect.GeneratedMethodAccessor24
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 39 more

Как я вижу, эти ошибки происходят с объектами, которые являются прокси:

e.g. org.apache.commons.logging.LogFactory.getLog$original$MoDHp2B7

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

@SuperMethod Method superMethod, @Origin Method currentMethod

Есть ли способ заставить работать делегирование с такими объектами?

Добавлен:

В этом примере Spring Boot 2 (https://spring.io/guides/gs/spring-boot/) есть явная ошибка StackOverflowError:

java.lang.StackOverflowError: null
at com.dvelopp.agenttest.MethodCallContext.getCaller(MethodCallContext.java:18)
at com.dvelopp.agenttest.Main$Interceptor.intercept(Main.java:108)
at org.springframework.core.env.SystemEnvironmentPropertySource.getSource(SystemEnvironmentPropertySource.java)
at org.springframework.core.env.MapPropertySource.getSource(MapPropertySource.java)
at org.springframework.core.env.SystemEnvironmentPropertySource.getSource$accessor$d92Pg8OK(SystemEnvironmentPropertySource.java)
at org.springframework.core.env.SystemEnvironmentPropertySource$auxiliary$ATPz5tSr.call(Unknown Source)
at com.dvelopp.agenttest.Main$Interceptor.intercept(Main.java:122)
at org.springframework.core.env.SystemEnvironmentPropertySource.getSource(SystemEnvironmentPropertySource.java)
at org.springframework.core.env.MapPropertySource.getSource(MapPropertySource.java)
at org.springframework.core.env.SystemEnvironmentPropertySource.getSource$accessor$d92Pg8OK(SystemEnvironmentPropertySource.java)
at org.springframework.core.env.SystemEnvironmentPropertySource$auxiliary$ATPz5tSr.call(Unknown Source)
at com.dvelopp.agenttest.Main$Interceptor.intercept(Main.java:122)
at org.springframework.core.env.SystemEnvironmentPropertySource.getSource(SystemEnvironmentPropertySource.java)
at org.springframework.core.env.MapPropertySource.getSource(MapPropertySource.java)
at org.springframework.core.env.SystemEnvironmentPropertySource.getSource$accessor$d92Pg8OK(SystemEnvironmentPropertySource.java)
at org.springframework.core.env.SystemEnvironmentPropertySource$auxiliary$ATPz5tSr.call(Unknown Source)
at com.dvelopp.agenttest.Main$Interceptor.intercept(Main.java:122)

Лук связано ли это с ошибкой StackOverflowError?

Rafael Winterhalter 12.01.2019 08:15

Да, ошибки бывают разные. Ошибка при попытке найти их в Google приводит к проблеме StackOverflowError. Я попытался не инструментировать этот пакет, а потом получил нечто подобное в пакетах Spring.

dvelopp 12.01.2019 10:00

Добавлен вопрос StackOverflowError, который возникает, когда я тестирую его с помощью проекта Spring.

dvelopp 12.01.2019 10:08

Для меня это похоже на то, что Byte Buddy правильно вызывает супер-метод SystemEnvironmentPropertySource, который находится в MapPropertySource. Однако этот метод вызывает сам себя, что вызывает рекурсию.

Rafael Winterhalter 12.01.2019 11:45

Если я исключу классы из инструментария. Например. измените any () на .type ((ElementMatchers.is (Main.class))), тогда все работает. Вот почему я думаю, что байтовый приятель конфликтует с весенней загрузкой

dvelopp 12.01.2019 19:04

Проверил то же самое, но с советами - Advice.OnMethodExit Advice.OnMethodEnter и получил ту же проблему

dvelopp 12.01.2019 19:04

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

Rafael Winterhalter 13.01.2019 14:37

Рафаэль, я почти уверен, что это связано с инструментированным байт-кодом. Потому что, когда я исключаю этот и другие классы, все работает нормально. Но я должен исключить множество классов из Spring, потому что у многих из них есть эта проблема.

dvelopp 13.01.2019 20:08

не могли бы вы создать репродукцию? Я уверен, что есть простое объяснение.

Rafael Winterhalter 13.01.2019 20:30

Рафаэль, ты имеешь в виду небольшой проект, которым можно поделиться?

dvelopp 13.01.2019 20:35

Да, какой-то код, который я могу запустить, воспроизводит вашу проблему. Отправьте его на GitHub в средство отслеживания проблем.

Rafael Winterhalter 13.01.2019 20:45

Да, конечно: github.com/raphw/byte-buddy/issues/585

dvelopp 13.01.2019 21:11

@dvelopp у вас была возможность проверить ответ от автор байта, Рафаэля Винтерхальтера? Вроде описана похожая проблема.

Sergii 16.01.2019 13:46

Этот случай позволяет вам выполнять / не выполнять ваш код в методе рекурсии, но он не контролирует сам поток. Вы все равно вызываете супер метод.

dvelopp 16.01.2019 16:35
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
14
287
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Похоже, вы используете неправильные аннотации или неправильные функции.

Я бы посоветовал проверить более простой случай. Например, замените вашу реализацию:

    @RuntimeType
    public static Object intercept(@SuperCall Callable<?> superCall, @SuperMethod Method superMethod, @Origin Method currentMethod,
                                   @AllArguments Object[] args, @This(optional = true) Object me) throws Exception {
        return superCall.call();
    }

со следующим:

    @RuntimeType
    public static Object intercept(@RuntimeType Object value) throws Exception {
        return value;
    }

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

Возможно, стоит обратить внимание и на концептуальный вопрос. Вы знаете, что разработчики java, использующие объект Callable, никогда не должны использовать call() напрямую.

Следуя описанию API для @RuntimeType, он используется для сопоставления одного объекта с другим. Возникает новый вопрос: зачем вам конвертировать объект callable? В какой тип он будет преобразован (вы уверены, что без Future wrapper у вас будет объект. Возможно, null приведет к вашему исключению, не так ли?)?

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