Как вы десериализуете объект из байтов в osgi

В моем приложении osgi у меня есть три пакета: travel.api, table.api и utils. travel.api зависит от table.api, который зависит от utils. Обратите внимание, что travel.api напрямую не зависит от utils. Я использую aQute Bnd для создания манифестов, и я считаю, что он работает нормально. Манифесты показаны ниже.

Существует класс с именем PageData, который имеет поле типа TableData, которое, в свою очередь, имеет поле типа TestObject. PageData находится в travel.api, TableData находится в table.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].

Теперь это имеет смысл, потому что если вы посмотрите на Import-Package вместо travel.api, вы увидите, что com.openaf.utils (где находится TestObject) не указано. Если я добавлю этот пакет, он будет правильно десериализован. Однако это не похоже на хорошее общее решение, так как мне придется просмотреть каждое поле, которое использует PageData, и убедиться, что все они импортированы в этот модуль, а также рекурсивно для каждого поля, содержащегося в этих полях и т. д.

Я делаю что-то совершенно неправильно здесь?

Как лучше всего десериализовать объект при использовании OSGi?

Если я делаю это правильно и мне нужно указать весь «глубокий» импорт, есть ли способ заставить Bnd выполнять «глубокую» генерацию?

Любая помощь будет принята с благодарностью!

Я использую felix v4 в качестве своей библиотеки osgi.

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

Спасибо, Ник.


person Boomah    schedule 13.12.2012    source источник
comment
В некоторых сценариях можно использовать пакеты узлов и фрагментов. Таким образом, у вас будет только один загрузчик классов, и проблема будет устранена. Как я уже сказал, он охватывает лишь небольшой набор сценариев.   -  person pillingworth    schedule 13.12.2012
comment
Мы думаем, что сталкиваемся с той же проблемой в нашем приложении, но не получаем исключение ClassNotFoundException. Вместо этого мы получаем ошибку java.io.StreamCorruptedException: invalid type code: 00. Как вы это исправили в итоге?   -  person DuncanKinnear    schedule 20.07.2016


Ответы (3)


Это на самом деле звучит как серьезный недостаток десериализации? Достойный десериализатор должен использовать загрузчик класса, вызывающего загрузку. Данный загрузчик классов следует использовать только для объекта верхнего уровня, поскольку родительского объекта еще нет.

Таким образом, в этом случае данный загрузчик классов используется для загрузки PageData. Загрузчик PageData используется для загрузки TableData, а загрузчик TableData должен использоваться для загрузки TestObject. Нет логической причины, почему это должно потерпеть неудачу, если только используемый вами десериализатор действительно не поврежден, поскольку это модель, которую виртуальная машина использует для загрузки классов. Я удивлен, что десериализатор Java делает это, я считаю это поведение серьезной ошибкой, поскольку он использует другие правила, чем виртуальная машина.

Сериализация является проблемой в OSGi, потому что модульность заключается в сокрытии классов реализации; десериализация имеет тенденцию хотеть получить доступ к этим закрытым классам, что является антитезой модульности. Однако для этого есть очень хорошие решения (которые не включают Dynamic-ImportPackage, который возвращается к аду JAR более сложным и дорогим способом, чем просто использование Java). Основная хитрость заключается в том, чтобы иметь корневой объект из общедоступного API, который имеет доступ к закрытым/временно необходимым классам. Хм, разве это не похоже на услугу?

Решение

Глядя на то, как негативно люди относятся к этому, небольшой пример, как можно решить проблему с сериализацией Java (то есть 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, то это тоже должно работать. Пример, который не работает, - это если вы, например, получаете реализацию из интерфейса. Класс интерфейса не подключен к реализации. сорт. т.е. если бы у вас был TableDataImpl, который расширял бы TableData, вы бы облажались. В этих случаях вам нужна служба для поиска «домена» реализации.

Удачи.

person Peter Kriens    schedule 14.12.2012
comment
Спасибо за ответы. Питер, есть ли у Bnd опция, позволяющая находить временно необходимые классы? - person Boomah; 14.12.2012
comment
Нет, потому что это нарушило бы закон Деметры. Модуль зависит от своих соседей, но он должен зависеть от соседей своего соседа. Опять же, это проблема с вашим поставщиком сериализации. Используйте другой механизм сериализации (JSON намного удобнее). Очень интересный сериализатор, который правильно справляется с этой проблемой, находится в bnd: aQute.lib.json. - person Peter Kriens; 14.12.2012
comment
Хорошо, я попробую. Спасибо за информацию. - person Boomah; 14.12.2012
comment
Я должен был упомянуть, что десериализатор, который я использую, — это org.jboss.netty.handler.codec.serialization.ObjectDecoderInputStream, поскольку я получаю объекты по сети. Теперь я понимаю, что могу использовать ваш пример при использовании ObjectDecoderInputStream. Спасибо. - person Boomah; 14.12.2012

Другого способа сделать это AFAIK нет.

Вы должны явно указать все зависимости, которые десериализованное дерево объектов содержит в том пакете, где вы пытаетесь это сделать.

Вы можете попытаться поместить все объекты предметной области в один пакет, скажем, model, а затем позволить всем остальным пакетам зависеть от него.

person Behnil    schedule 13.12.2012
comment
Не обязательно ... в этом случае все классы доступны из корня, поэтому НЕТ необходимости выполнять тяжелую работу! - person Peter Kriens; 14.12.2012
comment
Если вы десериализуете объект, то он использует фактический загрузчик классов, и все классы в десериализованном дереве объектов (включая сам объект) должны быть доступны для этого загрузчика классов, не так ли? Если это так, то в этом случае все классы недоступны, так как генерируется ClassNotFoundException. - person Behnil; 14.12.2012
comment
Нет, он должен быть доступен через загрузчик классов класса. Таким образом, графики загрузчиков классов работают нормально, если вы можете переходить от класса к классу. т.е. A -> B -> C нормально, когда вы десериализуете A. Поскольку загрузчик класса A должен видеть B, а загрузчик класса B должен видеть C. Нет необходимости, чтобы A видел C. - person Peter Kriens; 14.12.2012
comment
Посмотрите около 600 пикселей на экране ... :-) - person Peter Kriens; 14.12.2012

Да, это непростой вопрос. Во многих случаях проблема еще хуже, может быть даже неизвестно, какие пакеты потребуются для десериализации потока. Для них зависимости времени компиляции не совпадают с зависимостями времени выполнения.

Чтобы справиться с такими ситуациями, я использовал либо DynamicImports-Package, либо использовал API BundleWiring. Оба сработали довольно хорошо, хотя динамический импорт проще.

Я бы сказал, изолируйте часть, которая требует загрузки этого класса, насколько это возможно, в отдельный пакет, и пусть этот пакет использует DynamicImport.

Удачи, Фрэнк

person Frank Lee    schedule 13.12.2012
comment
а) в данном случае нет приватных полей б) как я утверждал в другом вопросе, для API -> импл. десериализации есть хорошие решения. - person Peter Kriens; 14.12.2012