今月から,XMLとWebサービスに関するJava SE 6の新機能を紹介していきます。
今まで,WebサービスはJava EEだけで扱われていました。Java EEではWebサービスを提供する側の機能が中心になっています。しかし,提供するだけでなく,Webサービスを使う側の機能も重要です。
Java SE 6では,この使う側,つまりWebサービスのクライアント機能が取りいれられました。また,それに応じて,XMLを扱う機能も強化されています。
そこで,本連載ではWebサービスの基幹となる,XMLを扱う機能から紹介していくことにしましょう。
DOM,SAX,そしてStAX
Java SE 6では,新しいXMLパーサが仲間入りすることになりました。その名はStreaming API for XML,通称StAXです。
StAXはJCPのJSR 173で標準策定が行なわれました。Java SEに取りいれられたJSRのスペックリードは圧倒的にSun Microsystemsの人が多いのですが,JSR 173は元BEA SystemsのChristopher Fry氏が行なっています。このため,Reference ImplementationもBEAで作成されたという,変り種のJSRです。
StAXは新しい標準であるにも関わらず,すでに多くのプロジェクトで使用されています。たとえば,JAXB 2.0などがStAXを使用しています。
そのStAXとはどのようなパーサなのでしょうか。
今まで,JavaでXMLをパースするには,DOMもしくはSAXが使われていました。
DOMはXMLのツリー構造を,そのままオブジェクト構造であらわすパーサです。一方のSAXはイベント駆動型のパーサになります。
StAXも,SAX同様,イベント駆動型のパーサになります。SAXとStAXは名前も似ていますが,処理手法も似ているのです。
それでは,何が違うかといういうと,SAXがプッシュ型,StAXがプル型という点が異なります。プルとかプッシュとかいってもよく分からないですね。
SAXではイベントリスナに相当するorg.xml.sax.ContentHandlerオブジェクトをパーサに登録します。パーサはXMLをパースしながら,要素が開始された時などに,ContentHandleオブジェクトのstartElementなどのコールバックメソッドをコールします。
つまり,イベントを起こすか起こさないかはパーサに依存しており,アプリケーションはイベントを押しつけられている(つまりプッシュです)だけです。
それに対して,StAXではイベントを投げるのはパーサではありません。アプリケーションがパーサに対してイベントがあるかどうか伺いをたてます。イベントがあれば,それをパーサから取得します。アプリケーションがパーサからイベントを引っ張り上げる(つまりプルです)するのです。
StAXを使った場合,イテレータのようにイベントを扱うことになります。つまり,パースの制御はアプリケーション側にあります。
たとえば,SAXはパースがはじまってしまうと,アプリケーション側から制御することができません。それに対し,StAXではパースの制御ができるので,たとえばパースの途中で中止するなどといったことが容易に実現できるのです。
カーソルAPI
では,さっそくStAXの使い方を見ていきましょう。
StAXには2種類のパース手法があります。一方がカーソルAPI,もう一方がイベントイテレータAPIです。
カーソルAPIもイベントイテレータAPIもイテレータのようにパーサを扱うことは同じです。イベントイテレータAPIの方が,名前が示すとおり,イベントが強調されたAPIになっています。
そこで,より基本的なカーソルAPIを今週,イベントイテレータAPIを来週解説することにします。
まずは単純なサンプルを使って,カーソルAPIの使い方を見ていくことにしましょう。
サンプルのソース | CursorSample1.java |
---|
このサンプルはXMLファイルを読み込み,要素が開始されると,その要素名を標準出力に出力するというプログラムです。
public CursorSample1(String xmlfile) { // 1. パーサ用ファクトリの生成 XMLInputFactory factory = XMLInputFactory.newInstance(); XMLStreamReader reader = null; BufferedInputStream stream = null; try { // 2. 入力に使用するファイルの設定 stream = new BufferedInputStream( new FileInputStream(xmlfile)); // 3. パーサの生成 reader = factory.createXMLStreamReader(stream); // 4. イベントループ while (reader.hasNext()) { // 4.1 次のイベントを取得 int eventType = reader.next(); // 4.2 イベントが要素の開始であれば,名前を出力する if (eventType == XMLStreamReader.START_ELEMENT) { System.out.println("Name: " + reader.getName()); } } } catch (FileNotFoundException ex) { System.err.println(xmlfile + " が見つかりません"); } catch (XMLStreamException ex) { System.err.println(xmlfile + " の読み込みに失敗しました"); } finally { // 5. パーサ,ストリームのクローズ if (reader != null) { try { reader.close(); } catch (XMLStreamException ex) {} } if (stream != null) { try { stream.close(); } catch (IOException ex) {} } } }
StAXを使用するには,まずパーサを生成しなくてはなりません。
カーソルAPIを使用する場合,パーサはjavax.xml.stream.XMLStreamReaderインタフェースであらわされます。また,XMLStreamReaderオブジェクトを生成には,ファクトリクラスであるjavax.xml.stream.XMLInputFactoryクラスが使用されます。
XMLInputFactoryクラスのオブジェクトは上記コードの1に示しているようにgetInstanceメソッドをコールして,取得します。
XMLStreamReaderオブジェクトを生成するには,XMLInputFactoryクラスのcreateXMLStreamReaderメソッドをコールします。このメソッドは引数にストリーム,リーダ,もしくはXSLTで用いるソースを使用できます。
ここでは,2に示したようにストリームを使用し,3でストリームを引数にしてcreateXMLStreamReaderメソッドをコールしています。
これでパーサの準備は完了しました。
4からがXMLのパース処理になります。
前述したようにパースはイテレータを用いた処理と同様に行ないます。
XMLStreamReaderオブジェクトに対し,イベントがあるかどうかを確認するのがhasNextメソッドです。hasNextメソッドの戻り値はイベントがあればtrue,なければfalseになります。
イベントがある場合,4.1に示すように,nextメソッドをコールします。
イテレータではnextメソッドの戻り値として値を取得できますが,XMLStreamReaderクラスではnextメソッドの戻り値はイベントタイプを表すintの値です。
このイベントタイプはjavax.xml.stream.XMLStreamConstatntsインタフェースで定義されています。XMLStreamReaderインタフェースはXMLStreamConstantsインタフェースの派生インタフェースなので,XMLStreamConstantsインタフェースで定義された定数をそのまま使用することができます。
個人的には,このようなインタフェースで定数を定義するよりenumを使って定義してもらいたいところです。とはいうものの,StAXが策定されていた頃は,まだJ2SE 5.0がリリースされる前なのでしかたないのですが。
イベントタイプが取得できたら,そのタイプに応じた処理を行ないます。ここでは4.2に示したように,イベントタイプが要素の開始を示すSTART_ELEMENTの場合,getNameメソッドを使用して要素の名前を出力しています。
最後にXMLStreamReaderオブジェクトとストリームをクローズします。
XMLStreamReaderオブジェクトをクローズすると,ストリームも一緒にクローズするような気がしますが,実際にはストリームのクローズは行ないません。このため,別途ストリームのクローズを行なう必要があります。
これはXMLStreamReaderオブジェクトにリーダやソースを用いたときも同様です。
さて,このサンプルを実行してみましょう。例として,以下の簡単なXMLファイル(names.xml)をパースしてみます。
<?xml version="1.0" encoding="utf-8"?> <names> <name> <first>Bob</first> <last>Dylan<last> </name> <name> <first>Paul</first> <last>Simon</last> </name> <name> <first>Pete</first> <last>Seeger</last> </name> </names>
実行結果は次のようになりました。
C:\stax>java CursorSample1 names.xml Name: names Name: name Name: first Name: last Name: name Name: first Name: last Name: name Name: first Name: last
このXMLファイルは名前空間を設定していないため,単にタグ名だけが出力されました。適切な名前空間が設定されていれば,それも合わせて出力されます。
たとえば,この連載の原稿をパースしてみましょう。本連載の原稿はxhtmlで記述してあるので,パースできるはずです。
C:\stax>java CursorSample1 stax1.xml Name: {http://www.w3.org/1999/xhtml}html Name: {http://www.w3.org/1999/xhtml}head Name: {http://www.w3.org/1999/xhtml}meta Name: {http://www.w3.org/1999/xhtml}title Name: {http://www.w3.org/1999/xhtml}link Name: {http://www.w3.org/1999/xhtml}style Name: {http://www.w3.org/1999/xhtml}body Name: {http://www.w3.org/1999/xhtml}div Name: {http://www.w3.org/1999/xhtml}h1 Name: {http://www.w3.org/1999/xhtml}p Name: {http://www.w3.org/1999/xhtml}h2 Name: {http://www.w3.org/1999/xhtml}p Name: {http://www.w3.org/1999/xhtml}p Name: {http://www.w3.org/1999/xhtml}p Name: {http://www.w3.org/1999/xhtml}p Name: {http://www.w3.org/1999/xhtml}h3 Name: {http://www.w3.org/1999/xhtml}p <<以下,省略>>
このように要素の名前が取得できましたが,これ以外にも取得できる情報があります。