Вопрос по java, bnd, apache-felix, osgi – Как десериализовать объект из байтов в OSGI

9

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

Есть класс под названиемPageData имеет поле типаTableData, который в свою очередь имеет поле типа.TestObjectPageData находится в ,travel.apiTableData находится в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

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

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

Ваш Ответ

3   ответа
6

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

Так что в этом случае данный загрузчик классов используется для загрузки PageData. PageData»Загрузчик s используется для загрузки TableData, а TableData 'Загрузчик s должен использоваться для загрузки 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();

Теперь самая сложная часть. Мы переопределяем метод resolObject, это дает нам возможность действительно выполнить правильную загрузку класса ...

ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bous.toByteArray())) {
    Set    lhs = new LinkedHashSet();
    {
        // 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, вы бы облажались. В этих случаях вам нужен какой-то сервис, чтобы найтидомен" реализации.

Удачи.

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

Нет другого способа, как это сделать AFAIK.

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

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

Если вы десериализуете объект, тоs использовал фактический загрузчик классов, и все классы в десериализованном дереве объектов (включая сам объект) должны быть доступны этому загрузчику классов, не так ли?не так ли? Если это так, то в этом случае все классы недоступны, так как он вызывает исключение ClassNotFoundException. Behnil
Посмотрите около 600 пикселей на вашем экране ... :-) Peter Kriens
Нет, это должно быть достигнуто через классЗагрузчик классов. Так что графики загрузчиков классов работают нормально, если вы можете переходить из класса в класс. То есть A -> B -> С в порядке, когда вы десериализуете А. Так как А 'Загрузчик класса s должен видеть B, а B 'Загрузчик класса s должен видеть C. Для того, чтобы видеть C, нет необходимости. Peter Kriens
У вас был бы источник, где я мог бы прочитать больше об этом? Behnil
Не обязательно ... в этом случае все классы достижимы из корня, поэтому нет необходимости выполнять тяжелую работу! Peter Kriens
2

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

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

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

Удачи Фрэнк

а) в этом случае нет личных полей б) как я уже говорил в другом вопросе, для API -> осущ. десериализации есть хорошие решения. Peter Kriens

Похожие вопросы