How do you deserialize an object from bytes in osgi

放肆的年华 提交于 2020-01-11 04:45:09

问题


In my osgi application I have three bundles, travel.api, table.api and utils. travel.api depends on table.api which depends on utils. Note that travel.api doesn't directly depend on utils. I use aQute Bnd to generate the manifests and I believe it is working fine. The manifests are displayed below.

There is a class called PageData that has a field of type TableData, which in turn has a field of type TestObject. PageData is located in travel.api, TableData is located in table.api and TestObject is located in utils. This all works fine when the bundles are loaded. The problem comes when I receive an array of bytes representing a PageData object. I have to deserialize it in the travel.api bundle. This shouldn't be a problem as that is where it is defined. I use org.jboss.netty.handler.codec.serialization.ObjectDecoderInputStream and pass in the classloader from the travel.api bundle. The exception shown below is thrown but basically it says:

Caused by: java.lang.ClassNotFoundException: com.openaf.utils.TestObject not 
    found by travel.api [9].

Now this makes sense because if you look at the Import-Package for travel.api you will see that com.openaf.utils (where TestObject is located) isn't listed. If I add this package then it is correctly deserialized. However, this doesn't seem like a good general solution as I would have to go through every field that PageData uses and ensure that they are all imported in this module, and recursively on every field contained by those fields etc.

Am I doing something completely wrong here?

What is the best way to deserialize an object when using OSGi?

If I'm doing it correctly and I have to specify all the "deep" imports, is there a way to get Bnd to do a "deep" generation?

Any help would be greatly appreciated!

I'm using felix v4 as my osgi library.

Manifest-Version: 1
Bnd-LastModified: 1355404320862
Bundle-ManifestVersion: 2
Bundle-Name: travel.api
Bundle-SymbolicName: travel.api
Bundle-Version: 0
Created-By: 1.7.0_07 (Oracle Corporation)
Export-Package: com.openaf.travel.api;uses:="scala.runtime,scala,scala.c
 ollection,com.openaf.pagemanager.api,scala.reflect,com.openaf.table.api
 ";version="0.0.0"
Import-Package: com.openaf.pagemanager.api,com.openaf.table.api,scala,sc
 ala.collection,scala.reflect,scala.runtime
Tool: Bnd-1.44.0

Manifest-Version: 1
Bnd-LastModified: 1355404158858
Bundle-ManifestVersion: 2
Bundle-Name: table.api
Bundle-SymbolicName: table.api
Bundle-Version: 0
Created-By: 1.7.0_07 (Oracle Corporation)
Export-Package: com.openaf.table.api;uses:="scala.runtime,scala,scala.co
 llection,scala.reflect,scala.collection.immutable,scala.collection.gene
 ric,com.openaf.utils";version="0.0.0"
Import-Package: com.openaf.utils,scala,scala.collection,scala.collection
 .generic,scala.collection.immutable,scala.reflect,scala.runtime
Tool: Bnd-1.44.0

Manifest-Version: 1
Bnd-LastModified: 1355404158801
Bundle-ManifestVersion: 2
Bundle-Name: utils
Bundle-SymbolicName: utils
Bundle-Version: 0
Created-By: 1.7.0_07 (Oracle Corporation)
Export-Package: com.openaf.utils;uses:="scala.runtime,scala,scala.collec
 tion,scala.reflect";version="0.0.0"
Import-Package: scala,scala.collection,scala.reflect,scala.runtime
Tool: Bnd-1.44.0

java.io.InvalidClassException: failed to read class descriptor
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1585)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1514)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1750)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1347)
at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:1964)
at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1888)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1771)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1347)
at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:1964)
at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1888)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1771)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1347)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:369)
at org.jboss.netty.handler.codec.serialization.ObjectDecoderInputStream.readObject(ObjectDecoderInputStream.java:115)
at com.openaf.rmi.common.DefaultObjectEncoder$.decode(RMICommon.scala:33)
at com.openaf.rmi.client.ClientHandler.messageReceived(ClientPipelineFactory.scala:43)
at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:296)
at org.jboss.netty.handler.codec.frame.FrameDecoder.unfoldAndFireMessageReceived(FrameDecoder.java:363)
at org.jboss.netty.handler.codec.frame.FrameDecoder.callDecode(FrameDecoder.java:345)
at org.jboss.netty.handler.codec.frame.FrameDecoder.messageReceived(FrameDecoder.java:211)
at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:268)
at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:255)
at org.jboss.netty.channel.socket.nio.NioWorker.read(NioWorker.java:94)
at org.jboss.netty.channel.socket.nio.AbstractNioWorker.processSelectedKeys(AbstractNioWorker.java:372)
at org.jboss.netty.channel.socket.nio.AbstractNioWorker.run(AbstractNioWorker.java:246)
at org.jboss.netty.channel.socket.nio.NioWorker.run(NioWorker.java:38)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
at java.lang.Thread.run(Thread.java:722)
Caused by: java.lang.ClassNotFoundException: com.openaf.utils.TestObject not found by travel.api [9]
at org.apache.felix.framework.BundleWiringImpl.findClassOrResourceByDelegation(BundleWiringImpl.java:1460)
at org.apache.felix.framework.BundleWiringImpl.access$400(BundleWiringImpl.java:72)
at org.apache.felix.framework.BundleWiringImpl$BundleClassLoader.loadClass(BundleWiringImpl.java:1843)
at java.lang.ClassLoader.loadClass(ClassLoader.java:356)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:264)
at org.jboss.netty.handler.codec.serialization.ClassLoaderClassResolver.resolve(ClassLoaderClassResolver.java:30)
at org.jboss.netty.handler.codec.serialization.CachingClassResolver.resolve(CachingClassResolver.java:39)
at org.jboss.netty.handler.codec.serialization.CompactObjectInputStream.readClassDescriptor(CompactObjectInputStream.java:55)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1583)
... 28 more

Thanks, Nick.


回答1:


This actually sounds like a serious shortcoming in deserialization? A decent deserializer should use the class loader of the class that causes the load. The given class loader should only be used for the top level object since the there is no parent object yet.

So in this case the given class loader is used to load PageData. PageData's loader is used to load TableData, and TableData's loader must be used to load TestObject. There is no logical reason why this should fail unless the deserializer you use is really brain damaged since this is the model the VM uses to load classes. I am surprised that the Java deserializer does this, I consider this behavior a serious error since it uses different rules than the VM.

Serialization is a problem in OSGi because modularity is about hiding implementation classes; deserialization has a tendency to want to access these private classes, the antithesis of modularity. However, there are very good solutions to this (which does not include Dynamic-ImportPackage, that is reverting to JAR hell in a more complicated and expensive way than just using plain Java). The basic trick is to have a root object from a public API that has access to the private/transiently needed classes. Hmm, doesn't this sound like a service?

Solution

Looking at how negative people are about this, a small example how you can solve the problem with Java Serialization (that is, ObjectInputStream, and ObjectOutputStream). In your question you mention ObjectDecoderInputStream, a class I am not familiar with.

The setup is:

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

So lets first serialize an object:

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

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

Now the hard part. We override the resolveObject method, this gives us a chance to to actually do proper class loading ...

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();

Please not that this only works as long as the transient graph is properly exported. I.e. if you can do new TableData then this should also work. An example that does not work is if you for example get an implementation from an interface. The interface class is not connected to the impl. class. I.e. if you had a TableDataImpl that extended TableData you would be screwed. In those cases you need some service to find the "domain" of the implementation.

Good luck.




回答2:


There is no other way how to do that AFAIK.

You have to explicitly state all dependencies the deserialized object tree contains in that bundle where you are trying to do that.

You could try to put all domain object into one bundle, let's say model and then let all other bundles depend on it.




回答3:


Yes, this is a tricky one. In many cases the problem is even worse, it might not even be known which bundles will be required to deserialize a stream. For these, the compile time dependencies are just not the same as runtime dependencies.

To tackle these situations, I've used either DynamicImports-Package or use the BundleWiring API. Both worked out pretty well, Dynamic imports are easier though.

I'd say isolate the part that needs this class loading as much as you can in a separate bundle, and have that bundle use a DynamicImport.

Good luck, Frank



来源:https://stackoverflow.com/questions/13861342/how-do-you-deserialize-an-object-from-bytes-in-osgi

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!