如何从osgi中的字节反序列化对象

时间:2012-12-13 13:55:57

标签: java osgi apache-felix bnd

在我的osgi应用程序中,我有三个包,travel.apitable.apiutilstravel.api取决于取决于table.api的{​​{1}}。请注意,utils并不直接依赖travel.api。我使用aQute Bnd来生成清单,我相信它工作正常。清单如下所示。

有一个名为utils的类,其字段为PageData,其字段为TableDataTestObject位于PageDatatravel.api位于TableDatatable.api位于TestObject。加载包时,这一切都正常。当我收到表示utils对象的字节数组时,问题出现了。我必须在PageData包中反序列化它。这应该不是问题,因为它是定义的地方。我使用travel.api并从org.jboss.netty.handler.codec.serialization.ObjectDecoderInputStream包中传入类加载器。抛出如下所示的异常,但基本上它说:

travel.api

现在这是有道理的,因为如果您查看Caused by: java.lang.ClassNotFoundException: com.openaf.utils.TestObject not found by travel.api [9]. 的{​​{1}},您会看到Import-Packagetravel.api所在的位置)未列出。如果我添加此包,则正确反序列化。但是,这似乎不是一个好的通用解决方案,因为我必须遍历com.openaf.utils使用的每个字段并确保它们都在此模块中导入,并递归地在这些字段包含的每个字段等。

我在这里做错了吗?

使用OSGi时反序列化对象的最佳方法是什么?

如果我正确地做了并且我必须指定所有“深度”导入,有没有办法让Bnd进行“深度”生成?

非常感谢任何帮助!

我正在使用felix v4作为我的osgi库。

TestObject

谢谢,尼克。

3 个答案:

答案 0 :(得分:6)

这实际上听起来像反序列化的一个严重缺点?一个不错的反序列化器应该使用导致负载的类的类加载器。给定的类加载器应该只用于顶级对象,因为还没有父对象。

因此在这种情况下,给定的类加载器用于加载PageData。 PageData的加载器用于加载TableData,而TableData的加载器必须用于加载TestObject。除非你使用的解串器确实是脑损坏,否则没有合理的理由,因为这是VM用来加载类的模型。我很惊讶Java解串器这样做,我认为这种行为是一个严重的错误,因为它使用的规则不同于VM。

序列化是OSGi中的一个问题,因为模块化是关于隐藏实现类;反序列化倾向于想要访问这些私有类,这是模块化的对立面。但是,有很好的解决方案(不包括Dynamic-ImportPackage,它以比使用普通Java更复杂和更昂贵的方式恢复到JAR地狱)。基本技巧是从公共API获得一个root对象,该对象可以访问私有/瞬时需要的类。嗯,这听起来不像是服务吗?

解决方案

观察人们对此的看法,一个小例子如何解决Java Serialization(即ObjectInputStream和ObjectOutputStream)的问题。在你的问题中,你提到了ObjectDecoderInputStream,这是一个我不熟悉的类。

设置为:

Bundle A:    class a.A { B b; }   (import b)
Bundle B:    class b.B { C c; }   (import c)
Bundle C:    class c.C { }

所以先让序列化一个对象:

ByteArrayOutputStream bous = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bous);

oos.writeObject(this);
oos.close();

现在困难的部分。我们覆盖resolveObject方法,这使我们有机会实际进行适当的类加载......

ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bous.toByteArray())) {
    Set<ClassLoader>    lhs = new LinkedHashSet<ClassLoader>();
    {
        // Keep a set if discovered class loaders
        lhs.add(getClass().getClassLoader());
    }

    @Override
    protected Class< ? > resolveClass(ObjectStreamClass desc) 
         throws ClassNotFoundException, IOException {

         for (ClassLoader cl : lhs) try {
             Class< ? > c = cl.loadClass(name);

             // we found the class, so we can use its class loader,
             // it is in the proper class space  if the uses constraints 
             // are set properly (and you're using bnd so you should be ok)

             lhs.add(c.getClassLoader());

             // The paranoid among us would check
             // the serial uuid here ...
             // long uuid = desc.getSerialVersionUID();
             // Field field = c.getField("serialVersionUID");
             // assert uuid == field.get(null)

             return c;
         } catch (Exception e) {
           // Ignore
         }

         // Fallback (for void and primitives)
         return super.resolveClass(desc);
     }
 };

 // And now we've successfully read the object ...

 A clone = (A) in.readObject();

请注意,只有正确导出瞬态图表才能使用此功能。即如果你可以做new TableData那么这也应该有效。一个不起作用的例子是你从一个接口获得一个实现。接口类未连接到impl。类。即如果您有一个扩展TableData的TableDataImpl,那么您将被搞砸。在这些情况下,您需要一些服务才能找到实施的“域”。

祝你好运。

答案 1 :(得分:2)

没有其他方法可以做到这一点AFAIK。

您必须显式声明反序列化对象树包含在您尝试执行此操作的捆绑包中的所有依赖项。

您可以尝试将所有域对象放入一个捆绑包中,假设 model ,然后让所有其他捆绑包依赖它。

答案 2 :(得分:2)

是的,这是一个棘手的问题。在许多情况下,问题更加严重,甚至可能不知道将反序列化流需要哪些捆绑包。对于这些,编译时依赖性与运行时依赖性不同。

为解决这些问题,我使用了DynamicImports-Package或使用BundleWiring API。两者都很顺利,但动态导入更容易。

我会说在一个单独的包中尽可能多地隔离需要加载此类的部分,并让该包使用DynamicImport。

好运,弗兰克