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

问题描述 投票:0回答:2

好的,所以这对你们来说是某种理论问题。

我正在尝试使用cglib Enhancer - 为类创建代理。我的代码在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不能将Parent的加载委托给Parent自己的类加载器?这种机制在cglib中真的不可用吗?为什么? cglib不与OSGi结合使用吗?如果是这样,为什么?

java osgi cglib bytecode-manipulation modelmapper
2个回答
0
投票

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

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

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

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


0
投票

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

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

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

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

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

© www.soinside.com 2019 - 2024. All rights reserved.