cglib - 在 OSGi 中创建 class 代理导致 NoClassDefFoundError

cglib - creating class proxy in OSGi results in NoClassDefFoundError

好的,这是给你们的一些理论问题。

我正在试验 cglib 的 Enchancer - 为 class 创建代理。 我的代码在 Felix OSGi 容器中 运行。

层次结构看起来有点类似于:

// Bundle A;
// Imports-Package: javax.xml.datatype
// Exports-Package: a.foo

package a.foo;
public class Parent {
   protected javax.xml.datatype.XMLGregorianCalendar foo;

   ... -> getter/setter;
}

// Bundle B
// Imports-Package: a.foo
// DOES NOT IMPORT PACKAGE javax.xml.datatype !!!

package b.bar;

import a.foo.Parent;
public class Child extends Parent {
   protected String bar;

   ... -> getter/setter;
}

// Bundle B

// Code extracted from https://github.com/modelmapper/modelmapper/blob/master/core/src/main/java/org/modelmapper/internal/ProxyFactory.java#L59
    public Child enchance() {
         Enhancer enhancer = new Enhancer();
         enhancer.setSuperclass(Child.class);
         enhancer.setUseFactory(true);
         enhancer.setUseCache(true);
         enhancer.setNamingPolicy(NAMING_POLICY);
         enhancer.setCallbackFilter(METHOD_FILTER);
         enhancer.setCallbackTypes(new Class[] { MethodInterceptor.class, NoOp.class });

         try {
             return enhancer.createClass();
         } catch (Throwable t) {
             t.printStackTrace();
     }
 } 

从 OSGi 的角度来看 - 这两个包 - Bundle A 和 Bundle B 功能齐全。 包 imports/exports 是 bnd 生成的。尽管 BundleA 没有显式导入 javax.xml.datatype 包 - 我可以毫无问题地创建 Child 的实例。 到目前为止一切顺利。

但是当我尝试调用 enchance() 方法并创建一个 Child 代理时 - cglib 抛出一个 NoClassDefFoundError: javax.xml.datatype.XMLGregorianCalendar

好的,我明白了 - BundleB 的 classloader 确实无法加载这个 class 事实上 - cglib 的 Enchancer 似乎正在使用 BundleB 的 classloader(Child的 class 类型 classloader) 以创建代理。

另一方面 - 为了处理模块化,OSGi 容器正在执行所谓的 class 加载委托 - 而不是 BundleB 的 classloader,OSGi 运行时委托加载 parent class Parent 到 BundleA 的 classloader,它知道如何加载它的所有字段。 这就是为什么 BundleB 不需要显式导入 javax.xml.datatype 包并且不需要知道如何加载 XMLGregorianCalendar class 并且仍然能够使用 Child objects .

我想知道 - 这种 "delegating" 方法不也适用于 cglib 的用例吗? 请注意,我对字节码操作一无所知,这对某些人来说可能听起来像是一个非常愚蠢的问题。 但我真的不明白 - 为什么 cglib 不能将 Parent 的加载委托给 Parent 自己的 classloader? cglib 中真的没有这种机制吗?为什么? cglib 不与 OSGi 结合使用吗?如果是,那为什么?

Child class 不需要导入 javax.xml.datatype 只要它不访问 javax.xml.datatype.XMLGregorianCalendar 字段并且您只是使用 Child class以正常方式。然而,为了生成代理 class,CGLib 需要了解包括 javax.xml.datatype.XMLGregorianCalendar 在内的完整继承层次结构的内部结构,以便为新类型生成字节码。因此需要导入包。

不幸的是,bnd 无法预测您将在 Child class 上生成字节码,因此它不会添加 javax.xml.datatype 的导入 - 它只会添加正常所需的导入用法。

一般来说,继承从另一个包导入的 class 是一个 坏主意 。 Java 继承创建了从 subclass 到 superclass 的非常紧密的耦合,这意味着您会接触到 superclass.

的内部结构

关于你的最后一个问题:CGLib 在 OSGi 中相当广泛地用于测试期间模拟对象等事情。它在生产中使用较少,因为几乎总是有比字节码生成更好的解决方案,例如正确使用服务注册表。

我尝试结合此处描述的 OSGi Class Loader Bridge 想法: https://www.infoq.com/articles/code-generation-with-osgi ...解决了 OSGi 中代码生成框架 运行 的类似问题,还有我最近想到的另一个想法。

想法是跟踪在用户类型的父类型层次结构中找到的 class 类型的 class 加载程序。我们稍后可以使用这些 class 加载器作为加载类型的后备,否则用户类型的 Bundle 的 class 加载器是未知的。

然后我们可以告诉 CGLIB 的增强器使用这个新的 class 加载器来解析。

这里提出了这个想法: https://github.com/modelmapper/modelmapper/pull/294

不过,我很想听听经验丰富的 OSGi 专家对此的意见。 但到目前为止,这似乎有效。 在证明错误之前,我接受我自己的回答。