【问题标题】:Mocking a class whose static initializer threw ExceptionInInitializerError模拟一个静态初始化器抛出 ExceptionInInitializerError 的类
【发布时间】:2021-02-18 19:24:46
【问题描述】:

我有一个场景,我需要从未知来源加载类并实例化它们以进行模拟 - 我不需要运行代码,但方法和属性必须在生成的实例中。我还需要类的名称保持不变,因此可以将其实例分配给该类型的其他已加载类的字段。 有时,由于ExceptionInInitializerError,类实例化失败,使类处于无法恢复的无效状态。我不知道哪个类会提前失败。

考虑一下:

class A {
    static {
        // Throws exception, resulting in 'A' changing to an error state
    }
}
    
class B {
    // In case 'A' could not be instantiated properly, I wish to mock
    // it so it can be assigned to this field
    private A someField;
}

以下是我想出的:

  1. 使用 ByteBuddy 创建失败类的子类 - 失败并显示 NoClassDefFoundError,可能是因为超类处于错误状态。
  2. 修改类的静态初始化程序并在使用 ByteBuddy 代理加载时将其包装在 try-catch 语句中 - 以可移植方式完成这似乎相当复杂。
  3. 在单独的临时类加载器中加载一个类并识别初始化失败;如果已抛出 ExceptionInInitializerError,请重新定义该类并删除其静态初始化程序。这似乎也很难实现,并导致各种链接和循环错误。

我错过了什么吗?有没有更简单的方法来实现我正在寻找的东西?


编辑:

我最终得到了某种解决方案,但是我无法让 Byte Buddy 在没有静态初始化程序的情况下过滤掉类(它将计算每个类的帧)

agentBuilder = agentBuilder.type(ElementMatchers.any())
    .transform((builder, type, classLoader, module) -> builder
        .visit(new AsmVisitorWrapper.AbstractBase() {
            @Override
            public int mergeWriter(int flags) {
                return flags | ClassWriter.COMPUTE_FRAMES;
            }

            @Override
            public ClassVisitor wrap(TypeDescription instrumentedType, ClassVisitor classVisitor,
                                     Implementation.Context implementationContext, TypePool typePool,
                                     FieldList<FieldDescription.InDefinedShape> fields,
                                     MethodList<?> methods, int writerFlags, int readerFlags) {
                if (methods.stream().noneMatch(MethodDescription::isTypeInitializer)) {
                    return classVisitor;
                }
                return new ClassVisitor(Opcodes.ASM9, classVisitor) {
                    @Override
                    public MethodVisitor visitMethod(int access, String name, String descriptor,
                                                     String signature, String[] exceptions) {
                        MethodVisitor methodVisitor = super.visitMethod(access, name, descriptor,
                                signature, exceptions);

                        methodVisitor = new JSRInlinerAdapter(methodVisitor, access,
                                name, descriptor,
                                signature, exceptions);

                        if (name.equals("<clinit>")) {
                            methodVisitor = new TryCatchBlockSorter(methodVisitor, access, name,
                                    descriptor, signature, exceptions);

                            methodVisitor = new MethodVisitor(Opcodes.ASM7, methodVisitor) {
                                private final Label start = new Label();
                                private final Label end = new Label();
                                private final Label handler = new Label();

                                @Override
                                public void visitCode() {
                                    super.visitCode();
                                    visitTryCatchBlock(start,
                                            end,
                                            handler,
                                            "java/lang/RuntimeException");
                                    visitLabel(start);
                                }

                                @Override
                                public void visitMaxs(int maxStack, int maxLocals) {
                                    visitJumpInsn(Opcodes.GOTO, end);
                                    visitLabel(handler);
                                    visitInsn(Opcodes.RETURN);
                                    visitLabel(end);
                                    super.visitMaxs(maxStack, maxLocals);
                                }
                            };
                        }
                        return methodVisitor;
                    }
                };
            }
        }));

此外,我在某些类上遇到了以下异常,可能是 Byte Buddy 错误?

[Byte Buddy] ERROR ch.qos.logback.core.util.COWArrayList [ClassLoader@3b00856b, unnamed module @4189d70b, loaded=false]
java.lang.NullPointerException
    at net.bytebuddy.description.type.TypeDescription$Generic$Visitor$Reducing.onGenericArray(TypeDescription.java:2326)
    at net.bytebuddy.description.type.TypeDescription$Generic$Visitor$Reducing.onGenericArray(TypeDescription.java:2281)
    at net.bytebuddy.description.type.TypeDescription$Generic$OfGenericArray.accept(TypeDescription.java:4415)
    at net.bytebuddy.description.method.MethodDescription$Token.asSignatureToken(MethodDescription.java:1915)
    at net.bytebuddy.description.method.MethodList$AbstractBase.asSignatureTokenList(MethodList.java:109)
    at net.bytebuddy.dynamic.scaffold.inline.RebaseDynamicTypeBuilder.make(RebaseDynamicTypeBuilder.java:227)
    at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.doTransform(AgentBuilder.java:10438)
    at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.transform(AgentBuilder.java:10374)
    at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.access$1600(AgentBuilder.java:10140)
    at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$Java9CapableVmDispatcher.run(AgentBuilder.java:10833)
    at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$Java9CapableVmDispatcher.run(AgentBuilder.java:10771)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:391)
    at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.transform(AgentBuilder.java:10330)
    at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$ByteBuddy$ModuleSupport.transform(Unknown Source)
    at java.instrument/sun.instrument.TransformerManager.transform(TransformerManager.java:188)
    at java.instrument/sun.instrument.InstrumentationImpl.transform(InstrumentationImpl.java:563)

【问题讨论】:

  • “从未知来源加载类”似乎很冒险。您是在某种沙盒中执行此操作吗?

标签: java reflection classloader byte-buddy


【解决方案1】:

如果一个类无法加载,它将永远被破坏。您可以做的是,在加载类之前通过操作它们的字节码来检测传入的类。例如,您可以使用 Byte Buddy 重新定义类以完全删除静态初始化程序。如果您还使用 Mockito 模拟这些类,如果您选择模拟它们,它们将无法执行任何代码。

您可以通过添加 Java 代理或实现自定义 ClassLoader 来检测类,您可以在定义之前显式转换任何类。

【讨论】:

  • 在这种情况下,我认为选项 #2 是最可行的。我关注this 并使用Byte Buddy 的AsmVisitorWrapper 实现了它,但是我在来自Byte Buddy 的ClassReader 的一些类上遇到了java.lang.IllegalArgumentException: JSR/RET are not supported with computeFrames option。你知道如何处理这个问题吗?
  • 您是否需要计算法语?您有设置示例吗?
  • 如果我不计算帧,我会收到错误消息。我将我的设置添加到问题中。
  • 可能是这样,你能复制一个让我看看吗?
猜你喜欢
  • 2020-11-20
  • 1970-01-01
  • 1970-01-01
  • 2021-12-01
  • 1970-01-01
  • 2020-04-14
  • 2011-03-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多