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

时间:2017-12-17 10:12:15

标签: java osgi cglib bytecode-manipulation modelmapper

好的,这对你们来说是一个理论上的问题。

我正在尝试使用cglib的Enchancer - 为类创建代理。 我的代码在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功能齐全。 包导入/导出是生成的。虽然BundleA没有显式导入javax.xml.datatype包 - 我可以创建Child的实例而没有任何问题。 到目前为止一切都很好。

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

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

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

我想知道 - 不是这样的"委托"适用于cglib的用例? 请注意,我不知道有关字节代码操作的任何内容,这对某些人来说可能听起来像是一个非常愚蠢的问题。 但是我真的不明白 - 为什么cglib不能将父级的加载委托给父母自己的类加载器? 这种机制在cglib中真的不可用吗?为什么? cglib不与OSGi结合使用吗?如果是这样,为什么呢?

2 个答案:

答案 0 :(得分:0)

只要Child类不访问javax.xml.datatype字段而您只是以正常方式使用javax.xml.datatype.XMLGregorianCalendar类,Child类就不需要导入javax.xml.datatype.XMLGregorianCalendar 。但是,为了生成代理类,CGLib需要具有完整继承层次结构内部的可见性,包括Child,以便为新类型生成字节码。因此,需要导入包裹。

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

一般来说,从另一个包中导入的类继承是一个坏主意。 Java继承创建了从子类到超类的非常紧密的耦合,这意味着您将暴露给超类的内部。

关于你的上一个问题:CGLib在OSGi中被广泛用于测试期间模拟对象之类的东西。它在生产中使用较少,因为几乎总是比字节码生成更好的解决方案,例如正确使用服务注册表。

答案 1 :(得分:0)

我尝试结合这里描述的OSGi类加载器桥的想法: https://www.infoq.com/articles/code-generation-with-osgi ...解决了OSGi中运行的代码生成框架的类似问题,最近又出现了另一个想法。

我们的想法是跟踪在用户类型的父类型层次结构中找到的类类型的类加载器。我们以后可以使用这些类加载器作为后备来加载Bundle用户类型的类加载器所不知道的类型。

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

这个想法在这里介绍: https://github.com/modelmapper/modelmapper/pull/294

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