Декорирование методов ClassLoader с помощью ByteBuddy - PullRequest
1 голос
/ 23 марта 2019

У меня есть проект, который позволяет регистрировать классы, когда они загружаются JVM (https://github.com/jdeppe-pivotal/classload-tracer).. Ограничение состоит в том, что класс должен быть успешно загружен для регистрации.

Теперь я хотел бы попытаться расширить это и использовать различные ClassLoader методы для регистрации классов, когда они были загружены (независимо от успеха). Я надеюсь использовать ByteBuddy для этого. К сожалению, мне не очень повезло.

Вот код для агента:

    public class ScratchAgent {

      private static final PrintWriter out;
      public static final ByteArrayOutputStream baos;

      static {
        baos = new ByteArrayOutputStream(10);
        out = new PrintWriter(baos);
      }

      public static void premain(String arg, Instrumentation inst) throws Exception {
        File temp = Files.createTempDirectory("tmp").toFile();

        ClassInjector.UsingInstrumentation
            .of(temp, ClassInjector.UsingInstrumentation.Target.BOOTSTRAP, inst)
            .inject(Collections.singletonMap(
                new TypeDescription.ForLoadedType(MyInterceptor.class),
                ClassFileLocator.ForClassLoader.read(MyInterceptor.class)));

        new AgentBuilder.Default()
            .disableClassFormatChanges()
            .with(AgentBuilder.Listener.StreamWriting.toSystemOut().withTransformationsOnly())
            .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
            .with(AgentBuilder.TypeStrategy.Default.REDEFINE)
            .with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE)
            .ignore(ElementMatchers.nameStartsWith("net.bytebuddy."))
            .enableBootstrapInjection(inst, temp)

            .type(is(TestClass.class))
            .or(ElementMatchers.isSubTypeOf(ClassLoader.class)
                .or(ElementMatchers.nameContainsIgnoreCase("classloader"))
            )

            .transform((builder, type, classLoader, module) -> builder
                .visit(Advice.to(MyInterceptor.class)
                    .on(hasMethodName("fooMethod")))
                .visit(Advice.to(MyInterceptor.class)
                    .on(hasMethodName("loadClass"))))
            .installOn(inst);
      }

      public static class MyInterceptor {
        @Advice.OnMethodEnter
        public static void decorate(
            @Advice.Argument(0) String arg,
            @Advice.This Object thisThis,
            @Advice.Origin Method method,
            @Advice.Origin Class<?> clazz) {
          System.out.println("--->>> OK " + method.getName()
              + "(" + arg + ") " + thisThis);
          out.println("--->>> OK " + arg);
          out.flush();
        }
      }
    }

А вот и тест:

    @Test
    public void sanity() throws Exception {
      ScratchAgent.premain(null, ByteBuddyAgent.install());

      String result = new TestClass().fooMethod("world");
      assertThat(result).isEqualTo("Hello world");
      assertThat(ScratchAgent.baos.toString()).isEqualTo("--->>> OK world\n");
    }

Но это приводит к следующей ошибке:

[Byte Buddy] TRANSFORM io.pivotal.test.TestClass [sun.misc.Launcher$AppClassLoader@18b4aac2, null, loaded=true]
[Byte Buddy] TRANSFORM java.net.URLClassLoader$2 [null, null, loaded=true]
[Byte Buddy] TRANSFORM java.net.URLClassLoader$3$1 [null, null, loaded=true]
[Byte Buddy] TRANSFORM java.net.URLClassLoader$3 [null, null, loaded=true]
[Byte Buddy] TRANSFORM java.lang.ClassLoader$2 [null, null, loaded=true]
[Byte Buddy] TRANSFORM java.net.URLClassLoader$1 [null, null, loaded=true]
[Byte Buddy] TRANSFORM java.lang.SystemClassLoaderAction [null, null, loaded=true]
[Byte Buddy] TRANSFORM sun.misc.Launcher$AppClassLoader$1 [null, null, loaded=true]
[Byte Buddy] TRANSFORM sun.misc.Launcher$ExtClassLoader$1 [null, null, loaded=true]
[Byte Buddy] TRANSFORM java.net.URLClassLoader$7 [null, null, loaded=true]
[Byte Buddy] TRANSFORM java.lang.ClassLoader$ParallelLoaders [null, null, loaded=true]
[Byte Buddy] TRANSFORM java.lang.ClassLoader$NativeLibrary [null, null, loaded=true]
[Byte Buddy] TRANSFORM java.lang.ClassLoader$3 [null, null, loaded=true]
[Byte Buddy] TRANSFORM sun.misc.Launcher$ExtClassLoader [null, null, loaded=true]
[Byte Buddy] TRANSFORM sun.misc.Launcher$AppClassLoader [null, null, loaded=true]
[Byte Buddy] TRANSFORM java.net.URLClassLoader [null, null, loaded=true]
[Byte Buddy] TRANSFORM sun.reflect.DelegatingClassLoader [null, null, loaded=true]
[Byte Buddy] TRANSFORM java.security.SecureClassLoader [null, null, loaded=true]
[Byte Buddy] TRANSFORM java.lang.ClassLoader [null, null, loaded=true]
--->>> OK fooMethod(world) io.pivotal.test.TestClass@2ca26d77
--->>> OK loadClass(org.assertj.core.api.Assertions) sun.misc.Launcher$AppClassLoader@18b4aac2
--->>> OK loadClass(org.junit.runners.model.MultipleFailureException) sun.misc.Launcher$AppClassLoader@18b4aac2
--->>> OK loadClass(org.junit.runner.notification.RunNotifier$7) sun.misc.Launcher$AppClassLoader@18b4aac2
--->>> OK loadClass(net.bytebuddy.pool.TypePool$Default$WithLazyResolution$LazyResolution) sun.misc.Launcher$AppClassLoader@18b4aac2
[Byte Buddy] ERROR java.lang.Throwable$WrappedPrintStream [null, null, loaded=false]
[Byte Buddy] ERROR java.lang.Throwable$PrintStreamOrWriter [null, null, loaded=false]
[Byte Buddy] ERROR java.util.IdentityHashMap$KeySet [null, null, loaded=false]
Exception in thread "main" java.lang.NoClassDefFoundError: org/junit/runners/model/MultipleFailureException
        at org.junit.internal.runners.model.EachTestNotifier.addFailure(EachTestNotifier.java:20)
        at org.junit.runners.ParentRunner.run(ParentRunner.java:369)
        at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
        at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
        at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
        at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
        at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
[Byte Buddy] ERROR java.lang.Shutdown [null, null, loaded=false]
java.lang.NoClassDefFoundError: net/bytebuddy/pool/TypePool$Default$WithLazyResolution$LazyResolution
        at net.bytebuddy.pool.TypePool$Default$WithLazyResolution.doDescribe(TypePool.java:1319)
        at net.bytebuddy.pool.TypePool$AbstractBase.describe(TypePool.java:408)
        at net.bytebuddy.pool.TypePool$AbstractBase$Hierarchical.describe(TypePool.java:471)
        at net.bytebuddy.agent.builder.AgentBuilder$DescriptionStrategy$Default$1.apply(AgentBuilder.java:3373)
        at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.resolve(AgentBuilder.java:10499)
        at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.doTransform(AgentBuilder.java:10469)
        at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.transform(AgentBuilder.java:10432)
        at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.access$1500(AgentBuilder.java:10198)
        at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$LegacyVmDispatcher.run(AgentBuilder.java:10807)
        at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$LegacyVmDispatcher.run(AgentBuilder.java:10754)
        at java.security.AccessController.doPrivileged(Native Method)
        at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.transform(AgentBuilder.java:10355)
        at sun.instrument.TransformerManager.transform(TransformerManager.java:188)
        at sun.instrument.InstrumentationImpl.transform(InstrumentationImpl.java:428)
[Byte Buddy] ERROR java.lang.Shutdown$Lock [null, null, loaded=false]
java.lang.NoClassDefFoundError: net/bytebuddy/pool/TypePool$Default$WithLazyResolution$LazyResolution
        at net.bytebuddy.pool.TypePool$Default$WithLazyResolution.doDescribe(TypePool.java:1319)
        at net.bytebuddy.pool.TypePool$AbstractBase.describe(TypePool.java:408)
        at net.bytebuddy.pool.TypePool$AbstractBase$Hierarchical.describe(TypePool.java:471)
        at net.bytebuddy.agent.builder.AgentBuilder$DescriptionStrategy$Default$1.apply(AgentBuilder.java:3373)
        at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.resolve(AgentBuilder.java:10499)
        at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.doTransform(AgentBuilder.java:10469)
        at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.transform(AgentBuilder.java:10432)
        at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.access$1500(AgentBuilder.java:10198)
        at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$LegacyVmDispatcher.run(AgentBuilder.java:10807)
        at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$LegacyVmDispatcher.run(AgentBuilder.java:10754)
        at java.security.AccessController.doPrivileged(Native Method)
        at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.transform(AgentBuilder.java:10355)
        at sun.instrument.TransformerManager.transform(TransformerManager.java:188)
        at sun.instrument.InstrumentationImpl.transform(InstrumentationImpl.java:428)
        at java.lang.Shutdown.<clinit>(Shutdown.java:61)

Process finished with exit code 1

Я понимаю, что мой тест ничего не делает для проверки декорации ClassLoader, но я просто перебираю сначала «нормальный» класс и медленно добавляю необходимые биты в классы инструмента ClassLoader.

Что я делаю не так? Это реально даже для инструмента ClassLoader с? Спасибо!

1 Ответ

0 голосов
/ 24 марта 2019

Когда вы применяете класс, во время инструментария может быть загружен дополнительный класс.Если это инициирует загрузку классов, на которые вы полагаетесь, например, классов вашего собственного агента, который не находится в пространстве имен Byte Buddy, или классов загрузчика, это может вызвать циклическую обработку инструментария и, следовательно, ошибку no def def found, что выопыт работы.

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

...