首页 > 解决方案 > 我的 ByteBuddy 生成的类互相看不到

问题描述

我尝试基本上实现类的深度模拟图(我不会在野外控制)以记录被调用的内容。简单地使用 Mockito 的快速而肮脏的版本令人惊讶地健壮,但我遇到了一个试图“正确”实现它的墙(并且没有人问有趣的问题,为什么他们需要在运行时类路径中使用 mockito)。因为这是 scala,默认构造函数是闻所未闻的,空值很少被优雅地传递,并且深度嵌套的成员类是一种规范,因此当我需要检测一个类时,我需要为构造函数提供实际参数,这需要我检测那些反过来。我的代码中的相关片段:

    private def createTracerClass(tpe :Type, clazz :Class[_]) :Class[_] = {
    val name = clazz.getName + TracerClassNameSuffix + tpe.typeSymbol.fullName.replace('.', '_')
    val builder =
        if (clazz.isInterface) //todo: implement abstract methods!!!
            ByteBuddy.subclass(clazz, new ForDefaultConstructor).name(name)
        else {
            val constructors = clazz.getDeclaredConstructors
                .filter { c => (c.getModifiers & (PUBLIC | PROTECTED)) != 0 }.sortBy(_.getParameterCount)
            if (constructors.isEmpty)
                throw new PropertyReflectionException(
                    s"Can't instrument a tracer for class ${clazz.getName} as it has no accessible constructor."
                )
            val best = constructors.head

            new ByteBuddy().subclass(clazz, NO_CONSTRUCTORS).name(name)
                .defineConstructor(PUBLIC).intercept(
                    invoke(best).onSuper.`with`(best.getParameterTypes.map(createInstance):_*)
                )
        }
    println("instrumenting " + name + "; class loader: "+clazz.getClassLoader)
    val mockClass = builder
        .method(not(isConstructor[MethodDescription]())).intercept(to(new MethodInterceptor()))
        .defineMethod(TypeMethodName, classOf[AnyRef], PUBLIC).intercept(FixedValue.value(tpe))
        .defineField(TraceFieldName, classOf[Trace], PUBLIC)
        .make().load(getClassLoader, ClassLoadingStrategy.Default.WRAPPER_PERSISTENT).getLoaded
    println("created class " + mockClass +"with classloader: " + mockClass.getClassLoader)
    mockClass
}


private def instrumentedInstance(clazz :Class[_], modifiers :Int = PUBLIC | PROTECTED) :AnyRef =
    if ((clazz.getModifiers & FINAL) != 0)
        null
    else {
        val mockClass = MockCache.getOrElse(clazz,
            clazz.synchronized {
                MockCache.getOrElse(clazz, {
                    println("creating mock class for "+clazz.getName)
                    clazz.getDeclaredConstructors.filter { c => (c.getModifiers & modifiers) != 0 }
                        .sortBy(_.getParameterCount).headOption.map { cons =>
                            val subclass = ByteBuddy.subclass(clazz, NO_CONSTRUCTORS)
                                .name(clazz.getName + MockClassNameSuffix)
                                .defineConstructor(PUBLIC).intercept(
                                    invoke(cons).onSuper.`with`(cons.getParameterTypes.map(createInstance) :_*)
                                ).make().load(getClassLoader, ClassLoadingStrategy.Default.WRAPPER_PERSISTENT).getLoaded
                            MockCache.put(clazz, subclass)
                        subclass
                    }.orNull
                })
            }
        )
        println("creating a mock for " + clazz.getName + "; class loader: " + mockClass.getClassLoader)
        mockClass.getConstructor().newInstance().asInstanceOf[AnyRef]
    }

问题出在第一个方法底部的构造函数生成器中,该方法createInstance用于创建。该方法又回退到instrumentInstance.

结果是我得到了一个NoClassDefFoundErrorduring load( LoadedTypeInitializer$ForStaticField.onLoad()) 因为每个类都加载了自己的类加载器。不幸的是,尽管原因很明显,但它并没有帮助我尝试让 ByteBuddy 共享一个类加载器或以其他方式使这些类可用。我使用了所有提供的参数makeload但无济于事;让所有调用共享一个TypePool不同的类型解析策略 - 除了我不想使用的 INJECTION ClassLoaderStrategy ,因为它依赖于私有 API,这不会使我的精力投入到这个战略上。

这似乎是一个非常基本的问题并且很容易解决,但是我浏览了其他项目的许多代码示例,我看不出他们做的任何不同的事情应该有什么不同。想法?

标签: classloaderbyte-buddy

解决方案


这很可能与您使用 ClassLoadingStrategy.Default.WRAPPER_PERSISTENT 有关。使用这种策略,类被加载到一个隔离的类加载器中,这使得类对任何不从该类加载器继承的人都是不可见的。

为了加载一组类,您可能希望将所有未加载的类 (.merge) 组合在一起并将它们一起加载到单个类加载器中。

请注意,您也可以自己创建一个 ByteArrayClassLoader 并将其保持打开状态以供注入。这样可以使用注入类加载策略添加以后的类。


推荐阅读