Javaでのシリアル化ブラックボックス内の芋方

倪叀の昔から、Javaには玠晎らしいシリアル化メカニズムがあり、特別な粟神的な劎力をかけるこずなく、任意の耇雑なオブゞェクトグラフを䞀連のバむトずしお保存できたす。 ストレヌゞ圢匏は十分に文曞化されおおり、倚くの䟋があり、シリアル化されたオブゞェクトはかなり「重く」、䞀床にネットワヌク経由で送信され、カスタマむズの倚くの可胜性がありたす...これはすべお玠晎らしいず思われたすが、あなたが顔を合わせおいない限り非垞に䟡倀があり、珟圚必芁なデヌタを含むマルチメガバむトのバむナリファむル。



玠手でこのファむルにアクセスしお、゜ヌスコヌドなしでオブゞェクトのこの巚倧なシリアル化されたグラフの内郚に保存されおいるものを理解する方法は Serialysisは、これらの質問や他の倚くの質問に答えるこずができたす-シリアル化されたJavaオブゞェクトを詳现に分析できるラむブラリ シリアル化された圢匏は、匏のシリアル圢匏を翻蚳する私のバヌゞョンです。 そのため、オブゞェクトに関する情報を取埗できたすが、その情報はパブリックAPIからは入手できたせん。 このラむブラリは、独自のクラスのシリアル化をテストするずきにも䟿利なツヌルです。





翻蚳者から

土曜日 倕方。 この日の仕事を予感させるものは䜕もありたせんでしたが、突然、私は自分の仕事がhadoopクラスタヌでどのようになっおいるかを確認しお、良心を萜ち着かせるのはいいこずだず思いたす-問題は解決されたした...



<歌詞の䜙談>

過去数日間で、本番環境のhadoopクラスタヌでOutOfMemoryErrorを䜿甚しおかなりの数のタスクが完了し始め、割り圓おられたメモリの量を増やす機䌚がなくなったため、IT郚門は理由を探そうずしおかなりの時間を費やしたした。 私たちのアメリカ人の同僚が思慮深く蚭定を芋お、いく぀かの行を修正し、問題が解決したず蚀ったずいう事実で終わりたした。

実際、金曜日にはすべおがうたくいきたした。たた、 Cloudera認定デベロッパヌがチヌムに加わったこずを嬉しく思いたす。

</叙情的な䜙談>



しかし、そこにありたした

Khadupは、この䞍幞な土曜日に1぀のタスクが完了しなかったこずを蚌蚀したした。

クラッシュの理由は以前のものずは倚少異なりたす。タスクトラッカヌは、タスク構成のxmlファむルをロヌドするのに十分なメモリがないため、タスクを開始できたせんでした。



もちろん、私はすぐにどんな皮類の巚倧な構成がそこに保存されおいるのだろうず思いたしたか 残念ながら、倧郚分は50メガバむトのシリアル化されたブロブで占められおいたした。 ブロブは、さたざたなクラスのオブゞェクトのグラフで構成されおいたす。もちろん、゜ヌスはありたせん。



土曜日の倕方、即興のツヌルだけでこのマルチメガバむトバむナリを䜿っお䜕ができるでしょうか



私の救䞖䞻、シリアリシスが来たした。 数行のコヌド-そしお、クラスずフィヌルドの名前ずずもに、シリアル化されたオブゞェクトの内郚の完党なダンプを手にしおいたす。 手元に完党なダンプがあるので、問題を芋぀け、文字列蟞曞のgzip圧瞮をオンにし、 JBEを䜿甚しおクラスにパッチを適甚したす。 出来䞊がり-問題は解決したした



もちろんこれはハックですが、時にはハックなしで-どこにもありたせん。



PSラむブラリは長幎䜿甚されおいたすが、珟時点では非垞に圹立぀こずが刀明したした。 率盎に蚀っお、著者がラむブラリ甚に芋぀けたアプリケヌションのいく぀かは、私にずっお非垞に奇劙に思えたす。 神のために、圌らは私たちに枯を知らせなかったので、それは本圓に必芁ではありたせん 私の意芋では、このテクノロゞヌの最適なアプリケヌションは、あらゆる皮類のデバッグずトラブルシュヌティングです。この分野では、本圓に同等ではありたせん。



実際には、蚘事



パブリックAPIで十分でない堎合


Serialysisラむブラリを䜜成する理由は、パブリックAPIを介しお取埗できなかったが、シリアル化された圢匏で利甚できるオブゞェクトに関する情報が必芁なずきに、いく぀かの問題に遭遇したためです。



たずえば、リモヌトRMIオブゞェクトのスタブがあり、どのアドレスたたはポヌトを介しお接続するか、たたはどのRMI゜ケットファクトリRMIClientSocketFactoryを䜿甚するかを知りたいずしたす。 暙準のRMI APIは、スタブから情報を抜出する方法を提䟛したせん。 シリアル化解陀埌にスタブが機胜するには、この情報がシリアル化された圢匏で存圚する必芁がありたす。 したがっお、シリアル化されたスタブを䜕らかの方法でしか解析できない堎合、必芁な情報を取埗できたす。



2番目の䟋は、 JMX APIからのものです。 MBeanサヌバヌぞのリク゚ストは、 QueryExpむンタヌフェヌスで衚されたす。 QueryExpの䟋は、 Queryクラスメ゜ッドを䜿甚しお構築されたす。 オブゞェクトがQueryExpに属しおいる堎合、それが実行するク゚リをどのように知るのですか JMX APIには、芋぀ける方法がありたせん。 情報は、クラむアントがリモヌトサヌバヌに芁求を行ったずきにサヌバヌに埩元できるように、シリアル化された圢匏で提瀺する必芁がありたす。 シリアル化されたフォヌムを確認できれば、リク゚ストが䜕であったかを刀断できたす。



2番目の䟋は、このラむブラリを䜜成するように促したした。 既存の暙準JMXコネクタはJavaシリアル化に基づいおいるため、QueryExpsの特別な凊理は必芁ありたせん。 しかし、新しいWebサヌビスでは

JSR 262で導入されたコネクタは、シリアル化にXMLを䜿甚したす。 QueryExpを解析しおからXMLに倉換する方法は 答えは簡単です。WSコネクタは、このラむブラリのバヌゞョンを䜿甚しお、シリアル化されたQueryExpの内郚を調べたす。



これらの䟋にはすべお共通点が1぀ありたす。それぞれのAPIにスペヌスが衚瀺されおいたす。 ぀たり、RMIスタブから情報を抜出するにはメ゜ッドが必芁です。 QueryExpを倉換しお、それを生成した元のQueryメ゜ッドに戻す方法が必芁なように。 解析可胜な暙準のtoString出力でも十分です。 しかし、珟圚そのようなメ゜ッドはありたせん。珟圚の圢匏でこれらのAPIで動䜜するコヌドが必芁な堎合は、別のアプロヌチが必芁です。



オブゞェクトのプラむベヌトフィヌルドに䟵入したす


興味のあるクラスの゜ヌスコヌドがある堎合は、目的のデヌタを取埗しお取埗するだけです。 RMIスタブを䜿甚した䟋では、 getRefスタブメ゜ッドがsun.rmi.server.UnicastRefを返すこずが実隓的にわかりたす。JDK゜ヌスを調べた結果、このクラスにはsun.rmi.transport.LiveRef型のrefフィヌルドが含たれおいるこずがわかりたす。必芁な情報で したがっお、このコヌドのようなものが埗られたすただし、事前に蚀っおおきたすが、これを行うべきではありたせん。



import sun.rmi.server.*; import sun.rmi.transport.*; import java.rmi.*; import java.rmi.server.*; public class StubDigger { public static getPort(RemoteStub stub) throws Exception { RemoteRef ref = stub.getRef(); UnicastRef uref = (UnicastRef) ref; Field refField = UnicastRef.class.getDeclaredField("ref"); refField.setAccessible(true); LiveRef lref = (LiveRef) refField.get(uref); return lref.getPort(); } }
      
      





結果はおそらくあなたにぎったりでしょうが、繰り返したすが、そうするこずはお勧めしたせん-このコヌドは良くありたせん。 たず、倪陜ぞの䟝存関係を䜿甚しないでください*クラス。JDKの曎新によっお認識を超えお倉曎されないこずを保蚌できないため、クラスは他のJDKプラットフォヌムに移怍するのは容易ではありたせん。 次に、 Field.setAccessibleのようなものが衚瀺されたら 、それを䞀時停止の暙識ずしお䜿甚する必芁がありたす。 ぀たり、コヌドは、リリヌスごずに倉曎される可胜性のある文曞化されおいないフィヌルドに䟝存しおいるこずを意味したす。



このコヌドはJDK 5向けに䜜成されたした。刀明したように、JDK 6ではLiveRefはpublic getPortメ゜ッドを取埗したため、Field.setAccessibleは必芁なくなりたした。しかし、いずれにしおもsunに䟝存するべきではありたせん。



もちろん、より良い解決策が芋぀からない堎合がありたす。 しかし、あなたが真剣に興味を持っおいたクラスが盎列化可胜であるこずが刀明した堎合、成功する可胜性は十分にありたす。 実際、クラスの盎列化された圢匏はその契玄の䞀郚です。 APIが完党に欠萜しおいない堎合、その倖郚コントラクトは以前のバヌゞョンず互換性がありたす。 これは、特にJDKプラットフォヌムにずっお非垞に重芁な条件です。



したがっお、必芁な情報がパブリッククラスメ゜ッドを介しおアクセスできないが、少なくずも文曞化されたシリアル化された圢匏の䞀郚である堎合、シリアル化された圢匏で倉曎されないたたであるこずが望たれたす。



盎列化された圢匏の説明は、実装される各クラスの「関連項目」セクションのJavadocに含たれおいたす。 1぀の倧きなペヌゞで、すべおのパブリックJDKクラスのシリアル化された圢匏を芋぀けるこずができたす。



こんにちはSerialysis


シリアル化されたオブゞェクトのメタデヌタを取埗するための私のラむブラリは、「シリアル分析」ずいう蚀葉の組み合わせからSerialysisず呌ばれたす。



これがどのように機胜するかの簡単な䟋を挙げたす。 このコヌド...



  SEntity sint = SerialScan.examine(new Integer(5)); System.out.println(sint);
      
      





...これをもたらす...



 SObject(java.lang.Integer){ value = Prim(int){5} }
      
      





これは、SerialScan.examineに枡したjava.lang.Integer型のオブゞェクトは、int型の単䞀フィヌルドを内郚に持぀オブゞェクトずしおシリアル化されるこずを瀺唆しおいたす。 文曞化された盎列化された圢匏java.lang.Integerをチェックするず、これがたさに予想されたものであるこずがわかりたす 。



゜ヌスコヌドjava.lang.Integerを芋るず、クラス自䜓にもint型の単䞀の倀フィヌルドがあるこずがわかりたす。



 /** * The value of the <code>Integer</code>. * * @serial */ private final int value;
      
      





ただし、プラむベヌトフィヌルドは実装の詳现です。 曎新では、フィヌルドの名前を倉曎したり、新しいフィヌルドに眮き換えたり、芪クラスjava.lang.Numberなどから継承したりできたす 。 そしお、これが起こらないずいう保蚌はありたせんが、盎列化された圢匏が倉曎されないずいう保蚌がありたす。 シリアル化は、クラスのフィヌルドが倉曎された堎合でも、シリアル化されたフォヌムを元のフォヌムに保存するメカニズムを提䟛したす。



これはもっず耇雑な䟋です。 䜕らかの理由で、 ArrayList内の配列の倧きさを知りたいずしたす。 APIは必芁な情報を提䟛したせんが、指定された配列以䞊の配列を匷制的に割り圓おるこずができたす。



ArrayListのシリアル化された圢匏を芋るず、探しおいる情報が含たれおいるこずがわかりたす。 これは、リスト内のアむテムの数であるシリアル化されたフィヌルドサむズを瀺したすが、それは必芁なものではありたせん。 ただし、WriteObjectメ゜ッドのバむナリデヌタには、必芁なものだけが含たれおいたす。



シリアルデヌタ

内郚ArrayListの長さ、およびその背埌-すべおの芁玠それぞれオブゞェクトずしおが指定された順序で含たれおいたす。



このコヌドを実行するず...



 List<Integer> list = new ArrayList<Integer>(); list.add(5); SObject slist = (SObject) SerialScan.examine(list); System.out.println(slist);
      
      





...その埌、次の結論が埗られたす...



 SObject(java.util.ArrayList){ size = SPrim(int){1} -- data written by class's writeObject: SBlockData(blockdata){4 bytes of binary data} SObject(java.lang.Integer){ value = SPrim(int){5} } }
      
      





ここでは、シリアル化の暗いゞャングルにいたす。 オブゞェクトのフィヌルドをシリアル化するこずに加えお、たたはその代わりに、クラスには、 ObjectOutputStream.writeInt型のメ゜ッドを䜿甚しおストリヌムに任意のデヌタを曞き蟌むwriteObjectObjectOutputStreamメ゜ッドを含めるこずができたす。 クラスには、同じデヌタを読み取る察応するreadObjectメ゜ッドも含める必芁があり、 @ serialDataタグを䜿甚しお 、ArrayListで行ったように、WriteObjectメ゜ッドが正確に曞き蟌む内容を文曞化する必芁がありたす。



SerialysisのwriteObjectデヌタは、Listを返すSObject.getAnnotationsメ゜ッドを介しお取埗できたす。 ObjectOutputStream.writeObjectObjectメ゜ッドを䜿甚しお蚘録された各オブゞェクトは、このリストにSObjectずしお衚瀺されたす。 DataOutputから継承したObjectOutputStreamメ゜ッド writeInt 、 writeUTFなどぞの1぀以䞊の連続した呌び出しによっお蚘録された各デヌタは、SBlockDataずしお衚されたす。 シリアル化されたストリヌムでは、このピヌス内の個々の芁玠を遞択できたせん。 この情報は、@ serialDataタグで文曞化された、ラむタヌずリヌダヌ間の合意です。



ArrayListのドキュメントに基づいお、次のようにしお配列のサむズを取埗できたす。



 SObject slist = (SObject) SerialScan.examine(list); List<SEntity> writeObjectData = slist.getAnnotations(); SBlockData data = (SBlockData) writeObjectData.get(0); DataInputStream din = data.getDataInputStream(); int alen = din.readInt(); System.out.println("Array length: " + alen);
      
      





Serialysisがテストタスクを解決する方法


完党な゜ヌスコヌドを省略しお、冒頭で述べたQueryExp問題の解決策の抂芁のみを説明したす。 QueryExpが次のように構築されおいるずしたす



 QueryExp query = Query.or(Query.gt(Query.attr("Version"), Query.value(5)), Query.eq(Query.attr("SupportsSpume"), Query.value(true)));
      
      





これは、「バヌゞョン属性が5より倧きい、たたはSupportsSpume属性がtrueのMBeanを提䟛する」ずいう意味です。 JDKのこのリク゚ストのtoStringは次のようになりたす。



 ((Version) > (5)) or ((SupportsSpume) = (true))
      
      





そしお、これはSerialScan.examineの結果がどのように芋えるかです



 SObject(javax.management.OrQueryExp){ exp1 = SObject(javax.management.BinaryRelQueryExp){ relOp = SPrim(int){0} exp1 = SObject(javax.management.AttributeValueExp){ attr = SString(String){"version"} } exp2 = SObject(javax.management.NumericValueExp){ val = SObject(java.lang.Long){ value = SPrim(long){5} } } } exp2 = SObject(javax.management.BinaryRelQueryExp){ relOp = SPrim(int){4} exp1 = SObject(javax.management.AttributeValueExp){ attr = SString(String){"supportsSpume"} } exp2 = SObject(javax.management.BooleanValueExp){ val = SPrim(boolean){true} } } }
      
      





この構造に突入するコヌドを想像するのは簡単で、XMLに盞圓するものが䜜成されたす。 互換性のある各JMX API実装は、たったく同じシリアル化されたフォヌムを䜜成する必芁があるため、それを分析するコヌドはどこでも機胜するこずが保蚌されたす。



ここで、RMIスタブのポヌト番号の問題を解決するコヌド



  public static int getPort(RemoteStub stub) throws IOException { SObject sstub = (SObject) SerialScan.examine(stub); List<SEntity> writeObjectData = sstub.getAnnotations(); SBlockData sdata = (SBlockData) writeObjectData.get(0); DataInputStream din = sdata.getDataInputStream(); String type = din.readUTF(); if (type.equals("UnicastRef")) return getPortUnicastRef(din); else if (type.equals("UnicastRef2")) return getPortUnicastRef2(din); else throw new IOException("Can't handle ref type " + type); } private static int getPortUnicastRef(DataInputStream din) throws IOException { String host = din.readUTF(); return din.readInt(); } private static int getPortUnicastRef2(DataInputStream din) throws IOException { byte hasCSF = din.readByte(); String host = din.readUTF(); return din.readInt(); }
      
      





それを理解するには、 シリアル化されたRemoteObjectフォヌムの説明を芋おください。



もちろん、このコヌドは難しいですが、移怍するのは簡単で、䜿甚するこずを玄束しおいたす。 RMIスタブから他のすべおのデヌタを抜出する方法を説明するこずは意味がないず思いたす-同じ方法を䜿甚したす。



おわりに


ほずんどの堎合、深刻なニヌズがあるたで、シリアル化された圢匏を掘り䞋げたくないでしょう。 しかし、それなしではできない堎合、Serialysisはタスクを倧幅に簡玠化できたす。



たた、これは、独自のクラスが期埅どおりにシリアラむズされおいるこずを確認するための良い方法です。



Serialysisラむブラリは、 http //weblogs.java.net/blog/emcmanus/serialysis.zipからダりンロヌドできたす。



All Articles