Мои сгенерированные ByteBuddy классы не могут видеть друг друга - PullRequest
1 голос
/ 29 февраля 2020

Я пытаюсь в основном реализовать графы глубокой насмешки классов (которые я не буду контролировать в дикой природе) для записи того, что вызывается. Быстрая и грязная версия, использующая просто Mockito, была на удивление надежной, но я наткнулся на стену, пытаясь реализовать ее «правильно» (и не заставлять людей задавать смешные вопросы, зачем им mockito в classpath времени выполнения). Поскольку это 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.

. В результате я получаю NoClassDefFoundError во время load (LoadedTypeInitializer$ForStaticField.onLoad()), поскольку каждый класс загружается со своим собственным загрузчиком классов. К сожалению, хотя причина была сразу очевидна, это не помогло мне попытаться заставить ByteBuddy совместно использовать загрузчик классов или каким-либо другим образом сделать эти классы доступными. Я играл со всеми предоставленными аргументами make, load, но безрезультатно; все вызовы имеют общую стратегию разрешения типов TypePool - ничего, кроме INJECTION ClassLoaderStrategy, которую я не хочу использовать из-за ее зависимости от частных API-интерфейсов, которые не позволили бы инвестировать мои усилия в эту стратегию.

Кажется, это очень простая c проблема, которую легко решить, но я просмотрел множество примеров кода из других проектов и не вижу ничего, что они делают по-другому, что должно иметь какое-то значение. Идеи?

1 Ответ

1 голос
/ 29 февраля 2020

Скорее всего, это связано с использованием вами ClassLoadingStrategy.Default.WRAPPER_PERSISTENT. Используя эту стратегию, классы загружаются в изолированный загрузчик классов, который делает классы невидимыми для всех, кто не наследует этот загрузчик классов.

Для загрузки группы классов вы, вероятно, захотите объединить все незагруженные классы (.merge) и загрузить их все вместе в одном загрузчике классов.

Обратите внимание, что вы также можете создать ByteArrayClassLoader самостоятельно и оставить его открытым для инъекции. Таким образом, более поздние классы могут быть добавлены с использованием стратегии загрузки классов инъекций.

...