【问题标题】:How to take the exception thrown by a constructor using a ByteBuddy agent?如何使用 ByteBuddy 代理处理构造函数抛出的异常?
【发布时间】:2017-11-12 11:47:36
【问题描述】:

我正在尝试使用 ByteBuddy (v1.7.9) java 代理记录在方法和构造函数中抛出的每个调用、返回的对象和异常,而不会与检测代码的正常运行。

我当前的代理实例是

new AgentBuilder.Default()
                .with(AgentBuilder.Listener.StreamWriting.toSystemOut())
                .type((typeDescription, classLoader, module, classBeingRedefined, protectionDomain) -> 
                     matcher.matchesIncoming(typeDescription.getTypeName()))
                .transform((builder, typeDescription, classLoader, javaModule) -> builder
                        .visit(Advice.to(CustomAdvicer.class).on(ElementMatchers.any())))
                .installOn(inst);

我从最简单的“顾问”开始,

public class CustomAdvicer {
    @Advice.OnMethodEnter
    public static void enter(@Advice.Origin String origin) {
        System.out.println("Entering " + origin);
    }

    @Advice.OnMethodExit(onThrowable = Throwable.class)
    public static void exit(
        @Advice.Return(typing = Assigner.Typing.DYNAMIC) @RuntimeType Object value,
        @Advice.Origin String origin,
        @Advice.Thrown Throwable thrown) {
        System.out.println("Exiting " + origin);
    }
}

但是,当我运行程序时,我从 bytebuddy 收到异常:

[Byte Buddy] ERROR some.pack.Thrower [sun.misc.Launcher$AppClassLoader@18b4aac2, null, loaded=false]
java.lang.IllegalStateException: Cannot catch exception during constructor call for public some.pack.Thrower() throws java.lang.Exception
    at net.bytebuddy.asm.Advice.doWrap(Advice.java:515)
    at net.bytebuddy.asm.Advice.wrap(Advice.java:470)
    at net.bytebuddy.asm.AsmVisitorWrapper$ForDeclaredMethods$Entry.wrap(AsmVisitorWrapper.java:481)
    at net.bytebuddy.asm.AsmVisitorWrapper$ForDeclaredMethods$DispatchingVisitor.visitMethod(AsmVisitorWrapper.java:562)
    at net.bytebuddy.jar.asm.ClassVisitor.visitMethod(ClassVisitor.java:327)
    at net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining$RedefinitionClassVisitor.visitMethod(TypeWriter.java:3801)
    at net.bytebuddy.jar.asm.ClassReader.readMethod(ClassReader.java:1020)
    at net.bytebuddy.jar.asm.ClassReader.accept(ClassReader.java:698)
    at net.bytebuddy.jar.asm.ClassReader.accept(ClassReader.java:500)
    at net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining.create(TypeWriter.java:2941)
    at net.bytebuddy.dynamic.scaffold.TypeWriter$Default.make(TypeWriter.java:1633)
    at net.bytebuddy.dynamic.scaffold.inline.RebaseDynamicTypeBuilder.make(RebaseDynamicTypeBuilder.java:200)
    at net.bytebuddy.agent.builder.AgentBuilder$Default$Transformation$Simple$Resolution.apply(AgentBuilder.java:8905)
    at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.doTransform(AgentBuilder.java:9306)
    at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.transform(AgentBuilder.java:9269)
    at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.access$1300(AgentBuilder.java:9047)
    at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$LegacyVmDispatcher.run(AgentBuilder.java:9625)
    at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$LegacyVmDispatcher.run(AgentBuilder.java:9575)
    at java.security.AccessController.doPrivileged(Native Method)
    at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.transform(AgentBuilder.java:9194)
    at sun.instrument.TransformerManager.transform(Unknown Source)
    at sun.instrument.InstrumentationImpl.transform(Unknown Source)
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(Unknown Source)
    at java.security.SecureClassLoader.defineClass(Unknown Source)
    at java.net.URLClassLoader.defineClass(Unknown Source)
    at java.net.URLClassLoader.access$100(Unknown Source)
    at java.net.URLClassLoader$1.run(Unknown Source)
    at java.net.URLClassLoader$1.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(Unknown Source)
    at java.lang.ClassLoader.loadClass(Unknown Source)
    at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
    at java.lang.ClassLoader.loadClass(Unknown Source)
    at Main.main(Main.java:23)

那么,我应该怎么做才能记录构造函数中抛出的异常,记住它不能干扰原始代码?

顺便说一句,Thrower 类是我为了测试这个案例而写的一个愚蠢的类:

package some.pack;

public class Thrower {
  public Thrower() throws Exception {
    throw new Exception("By courtesy of thrower! ;)");
  }
}

【问题讨论】:

    标签: java profiling java-bytecode-asm javaagents byte-buddy


    【解决方案1】:

    问题是构造函数有一个隐式的第一条指令,它是另一个或超级构造函数的调用。您的 Thrower 课程看起来像这样:

    public class Thrower {
      public Thrower() throws Exception {
        super();
        throw new Exception("By courtesy of thrower! ;)");
      }
    }
    

    如果你想将整个调用包装在一个 try-catch 块中,这将产生:

    public class Thrower {
      public Thrower() throws Exception {
        try {
          super();
          throw new Exception("By courtesy of thrower! ;)");
        } catch (Exception e) {
          ...
        }
      }
    }
    

    但这在 JVM 中是不合法的,因此 Byte Buddy 不允许这样做。排除超级构造函数调用也没有好方法,因为这是第一次调用只是 Java 语言约定,但字节码允许更多任意组合。由于你不知道一个类来自什么语言,Byte Buddy 在这里没有尝试任何技巧,根本不允许这样做。

    【讨论】:

    • 是否有其他方法可以实现记录构造函数抛出的异常这一目标?
    • 其实,它在字节码中合法的(虽然不是在Java编程语言中),只要异常处理程序不尝试正常完成,即它必须做要么,重新抛出异常,抛出另一个异常或进入无限循环。
    • 确实,很高兴知道,也许这允许我将选项添加到必须始终抛出原始异常的 Byte Buddy。我在这里跟踪此功能:github.com/raphw/byte-buddy/issues/375
    • 我想,这对于在重新抛出原始异常之前注入日志操作的典型用例来说是完美的。
    • @kriegaex 不幸的是,HotSpot JVM 经常甚至有时违反规范,就像在这种情况下even deliberately。在我写评论的时候,我已经有了一个工作示例,并且不知道最广泛的 JVM 实现的作者(或其中一位)即将打破它。它在降低类文件版本而不使用堆栈映射时仍然有效,但这不是未来可行的策略……
    猜你喜欢
    • 1970-01-01
    • 2015-08-08
    • 1970-01-01
    • 1970-01-01
    • 2011-10-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多