レベル: 中級 Dennis Sosnoski, President, Sosnoski Software Solutions, Inc.
2002年 4月 01日
JavaのためのXMLデータ・バインディングは、データ・コンテンツに主な重点を置くアプリケーションのための、XML文書モデルに代わる強力な技法です。この記事では、エンタープライズJava専門家のDennis
Sosnoskiがデータ・バインディングを紹介し、この手法の魅力について説明しています。それからJavaデータ・バインディングにオープン・ソースCastorフレームワークを使用して、ますます複雑になる文書を処理する方法を読者に示します。ご使用のアプリケーションが文書としてよりも、データとしてのXMLを重視するものであれば、JavaでXMLを処理する方法として、これから解説される簡単で効率的な方法について造詣を深めたいと思われることでしょう。
アプリケーション内でXML文書を扱うためのアプローチのほとんどは、XMLに重点を置いています。つまり、XMLの観点から文書を処理し、XML要素、属性、および文字データ・コンテンツに関してプログラミングします。ご使用のアプリケーションが文書のXML構造を主に重視するのであれば、この方法は優れています。これに対し、文書そのものよりも文書に含まれているデータを重視する多くのアプリケーションを対象に、データ・バインディング
がよりシンプルなXML処理に関するアプローチを提供しています。
文書モデルvsデータ・バインディング
この記事シリーズの前回に解説した文書モデル (
参考文献
を参照)
は、データ・バインディングの最大の代替方法です。文書モデルとデータ・バインディングはどちらも内部表現と標準であるテキスト形式XMLの間で双方向の変換を行って、メモリーに文書表現を作成します。二者の相違は、文書モデルではXML構造をできるだけそのまま保存するのに対し、データ・バインディングではアプリケーションの使用する文書データのみを取り扱う、ということです。
このことを図で表すため、図1には、単純なXML文書の文書モデル・ビューを示しています。文書コンポーネント
(この場合には要素とテキスト・ノードのみ)
は、オリジナルのXML文書をミラーリングする構造にリンクされます。結果として得られるノードのツリーをオリジナルの文書に関連付けることは簡単ですが、ツリーに存在する実際のデータを解釈することはそれほど簡単ではありません。
図1. 文書の文書モデル・ビュー
あなたのアプリケーション・プログラムがXMLに対して文書モデル・アプローチを使用している場合には、このタイプのツリーで作業することになるでしょう。この場合には、ノード間の親子関係を使用してツリーを上下にナビゲートし、ツリー間のナビゲートには共通の親を持つ子の間の兄弟関係を使用します。ツリー構造はごく詳細なレベルでの操作が可能であり、またツリーをテキストとして直列化すると、生成されるXML文書は、ユーザーが行った変更内容
(コメントの組み込みなど) を反映します。
次は、図1を図2と比較してみましょう。図2は、同じ文書のデータ・バインディング・ビューを表しています。ここではオリジナルのXML文書の構造は変換によってほぼ完全に隠されていますが、実際のデータは、一対のオブジェクトを通じてもっと簡単に見たり、アクセスしたりできます。
図2. 文書のデータ・バインディング・ビュー
このデータ構造の処理は、通常のJavaプログラミングと全く変りありません。XMLについての知識もなんら必要ないのです
(この話はここまでに
しておきましょう。我々専門のコンサルタントにも生活がありますから...)。ただし、プロジェクトにかかわる誰か一人は少なくとも、データ構造とXML文書の間のマッピングがどのようにセットアップされるかを理解しておく必要がありますが、それでも、これは飛躍的に簡単になったといえるでしょう。
データ・バインディングには、プログラミングの簡易さにとどまらない
利点があります。データ・バインディングでは、文書明細の多くが要約されるので、通常は、文書モデル・アプローチよりも必要なメモリーが少なくてすみます。たとえば、先の図に示された2つのデータ構造を考えてみましょう。データ・バインディングでは2個のオブジェクトを使用するのに対し、文書モデル・アプローチでは10個の個別のオブジェクトを使用します。作成するデータ構造もかなり小さいので、文書のデータ・バインディング表現を構成する作業はさらに高速になります。最後に、プログラム内のデータへのアクセスは、データ・バインディング・アプローチを使用した方が、文書モデルを使用した場合よりずっと速くなります。これは、データの表現方法や保管方法をユーザーが制御するからです。これらのポイントについては後で説明します。
データ・バインディングがそれほど優れているのなら、代わりに文書モデルを利用したいのはどんな場合でしょうか?
文書モデルが必要な場合としては、以下の2つのケースが考えられます。
-
アプリケーションが実際に文書構造の詳細を重視する場合。たとえば、XML文書エディターを作成している場合には、データ・バインディングではなく、あえて文書モデルを使用するでしょう。
-
処理する文書が固定構造に従わない場合。たとえば、データ・バインディングは、一般的なXML文書データベースを実装するのに適したアプローチとはいえません。
多くのアプリケーションは、データ転送にXMLを使用しますが、その文書表現の詳細については重視しません。このようなアプリケーションは、データ・バインディングを利用する有力な候補です。お使いのアプリケーションがこのパターンに合うのであれば、この記事を読み進めてください。
Castorフレームワーク
現在、いくつかの各種フレームワークがJava用のXMLデータ・バインディングをサポートしていますが、標準インターフェースはありません。これはゆくゆく確立されるはずです。Java
Community Process (JCP) のJSR-031で標準の定義が進められています (
参考文献
を参照)。ここからは、1つのフレームワークを選んで、そのインターフェースの使用法を学習していきましょう。
今回は、Castorデータ・バインディング・フレームワークを使用することにしました。CastorプロジェクトではBSDスタイルのライセンスを使用しており、あらゆるタイプのアプリケーション
(独自に開発したアプリケーションも含む)
で使用できます。事実、Castorは、SQLバインディングやLDAPバインディングをサポートすることで、単なるXMLデータ・バインディングという以上の働きをしますが、この記事ではこれらの他のフィーチャーは無視することにします。このフレームワークは、2000年初頭から開発されてきたもので、現在はアドバンスト・ベータ状態になっています
(一般的に使用することは可能ですが、バグの修正が必要な場合、ユーザーは現行のCVSバージョンにアップデートすることが必要になります)。詳細情報が必要な場合や、ソフトウェアをダウンロードする場合は、
参考文献
セクションでCastorサイトのリンクを参照してください。
デフォルト・バインディング
CastorのXMLデータ・バインディングを開始するのはとても簡単です。XML文書フォーマットを定義する必要すらありません。ユーザーのデータが、JavaBean類似のオブジェクトで表現されている限り、Castorは、データを表現する文書フォーマットを自動的に生成し、後でその文書からオリジナル・データを再構成することができます。
 |
データ・バインディングに関する用語の辞書
この記事で使用する用語のミニ辞書は以下のとおりです。
マーシャル
とは、メモリー内のオブジェクトからXML表現を生成するプロセスです。Javaの直列化と全く同様に、この表現にはすべての依存オブジェクトを組み込むことが必要です。つまり、メイン・オブジェクトで参照されるオブジェクト、それらのオブジェクトで参照されるオブジェクト、などを組み込みます。
アンマーシャル はこの逆のプロセスであり、XML表現からメモリー内にオブジェクト
(および依存オブジェクト) を作成します。
マッピング
は、マーシャルとアンマーシャルに使用されるルールのセットです。Castorには、この記事のこのセクションで説明するデフォルト・マッピングを定義する組み込みルールがあります。また、以下のセクションで説明しているように、個別のマッピング・ファイルを使用することもできます。
|
|
"JavaBean類似" とはどういう意味でしょうか?
実際のJavaBeanは、GUIレイアウトで使用するために開発環境内で構成可能な、ビジュアル・コンポーネントです。実際のJavaBeanを起源とするいくつかの慣例、特にデータ・クラスは、Javaコミュニティーで広く普及しました。このような慣例に従うクラスを、"JavaBean類似"
と呼んでいます。
- クラスはパブリック
- パブリック・デフォルト (引数なし) コンストラクターを定義する
-
プロパティー (データ) 値へのアクセスに、パブリックの
getX
および
setX
メソッドを定義する
テクニカルの定義が終わったので、今後このようなJavaBean類似のクラスを参照するときは、単に
"Bean" クラスと呼ぶことにします。
この記事全体にわたり、コード例として航空会社のフライト時刻表を利用することにします。これがどのように動作するか示すため、特定のフライトを表す単純なBeanクラスから始めることにしましょう。このBeanには以下の4つの項目の情報が組み込まれています。
- キャリア (航空会社) 識別子
- フライト番号
- 出発時刻
- 到着時刻
下記のリスト1は、フライト情報を処理するコードを示しています。
リスト1. フライト情報Bean
public class FlightBean
{
private String m_carrier;
private int m_number;
private String m_departure;
private String m_arrival;
public FlightBean() {}
public void setCarrier(String carrier) {
m_carrier = carrier;
}
public String getCarrier() {
return m_carrier;
}
public void setNumber(int number) {
m_number = number;
}
public int getNumber() {
return m_number;
}
public void setDepartureTime(String time) {
m_departure = time;
}
public String getDepartureTime() {
return m_departure;
}
public void setArrivalTime(String time) {
m_arrival = time;
}
public String getArrivalTime() {
return m_arrival;
}
}
|
ご覧のとおり、このBeanはそれだけではかなり面白味に欠けますので、リスト2に示すように、クラスを追加して、それをデフォルトXMLバインディングで使用することにします。
リスト2. デフォルトのデータ・バインディング・テスト
import java.io.*;
import org.exolab.castor.xml.*;
public class Test
{
public static void main(String[] argv) {
// build a test bean
FlightBean bean = new FlightBean();
bean.setCarrier("AR");
bean.setNumber(426);
bean.setDepartureTime("6:23a");
bean.setArrivalTime("8:42a");
try {
// write it out as XML
File file = new File("test.xml");
Writer writer = new FileWriter(file);
Marshaller.marshal(bean, writer);
// now restore the value and list what we get
Reader reader = new FileReader(file);
FlightBean read = (FlightBean)
Unmarshaller.unmarshal(FlightBean.class, reader);
System.out.println("Flight " + read.getCarrier() +
read.getNumber() + " departing at " +
read.getDepartureTime() +
" and arriving at " + read.getArrivalTime());
} catch (IOException ex) {
ex.printStackTrace(System.err);
} catch (MarshalException ex) {
ex.printStackTrace(System.err);
} catch (ValidationException ex) {
ex.printStackTrace(System.err);
}
}
}
|
 |
CastorによるBean以外の能力
Castorは実際には、この記事で解説したJavaBean類似のクラスばかりでなく、それ以外も処理します。また、パブリック・メンバー変数を指定した単純データ・オブジェクト・クラスから情報にアクセスすることもできます。たとえば、Testクラスに若干の変更を加えて、フライト・データに以下の定義を使用し、なおかつ最終的に同じXMLフォーマットにすることができます。
public class FlightData
{
public String carrier;
public int number;
public String departure;
public String arrival;
} |
クラスはなんらかの方法でCastorに正しく処理される必要があります。そのクラスでなんらかの
getX
または
setX
メソッドを定義すると、CastorはそれをBeanと見なし、それらのメソッドだけをマーシャルとアンマーシャルに使用します。
|
|
このコードでは、最初に
FlightBean
Beanを構成し、なんらかのあらかじめ用意した値でそのBeanを初期設定します。次に、CastorのそのBean用のデフォルトXMLマッピングを使用して、そのBeanを出力ファイルに書き込みます。最後に、生成されたXMLを読み取って、同じデフォルト・マッピングを使用してBeanを再構成し、そのBeanから情報を印刷します。結果は次のとおりです。
Flight AR426 departing at 6:23a and arriving at
8:42a
この出力は、フライト情報を正常に往復したことを表しています
(メソッド呼び出しを2つだけ使ったにしては悪くないでしょう)。次は、コンソール出力よりも少しだけ深く検討しましょう。
舞台裏情報
この例で何が行われているのかをもっと詳しく知るため、Marshaller.marshal()呼び出しによって生成されたXMLを見てみましょう。文書は次のとおりです。
<?xml version="1.0"?>
<flight-bean number="426">
<arrival-time>8:42a</arrival-time>
<departure-time>6:23a</departure-time>
<carrier>AR</carrier>
</flight-bean> |
Castorは、Javaイントロスぺクションを使用して
Marshaller.marshal()
呼び出しにユーザーが渡したオブジェクトを調べます。この場合、ユーザーが定義した4つのプロパティー値が検出されます。Castorは、そのオブジェクト全体を表すため、出力XMLに1つの要素
(文書のルート要素) を作成します。この要素名はオブジェクト・クラス名から派生します。この場合、
flight-bean
です。Castorは次に、このオブジェクトのプロパティー値を2とおりの方法のいずれかで組み込みます。以下が作成されます。
-
プリミティブと評価された各プロパティーの要素の属性 (このケースでは、
getNumber()
メソッドによって公開された
int
値の
number
プロパティーのみ)
-
オブジェクトと評価された各プロパティーのルート要素の子要素
(文字列であるため、ここでは上記以外のすべて)。
この結果が、上記のXML文書です。
XMLフォーマットの変更
Castorのデフォルト・マッピング・フォーマットがお好みでなければ、簡単にマッピングを変更できます。このフライト情報の例で、たとえば、もっとコンパクトなデータ表現が必要であるとしましょう。子要素の代わりに属性を使用すれば、よりコンパクトな表現が可能になります。また、デフォルトよりも短い名前を使用したいこともあります。このような要件を満たしてくれるのが、以下のような文書です。
<?xml version="1.0"?>
<flight carrier="AR" depart="6:23a" arrive="8:42a" number="426"/> |
マッピングの定義
デフォルトではなくこのフォーマットをCastorに使用させるには、最初に、このフォーマットを記述するマッピングを定義する必要があります。そのマッピング記述そのものが
(驚くなかれ)
XML文書なのです。リスト3は、Beanを、先に示したフォーマットにマーシャルするためのマッピングを表しています。
リスト3. コンパクト・フォーマットのためのマッピング
<!DOCTYPE databases PUBLIC
"-//EXOLAB/Castor Mapping DTD Version 1.0//EN"
"http://castor.exolab.org/mapping.dtd">
<mapping>
<description>Basic mapping example</description>
<class name="FlightBean" auto-complete="true">
<map-to xml="flight"/>
<field name="carrier">
<bind-xml name="carrier" node="attribute"/>
</field>
<field name="departureTime">
<bind-xml name="depart" node="attribute"/>
</field>
<field name="arrivalTime">
<bind-xml name="arrive" node="attribute"/>
</field>
</class>
</mapping> |
class
要素は、名前付きクラス、このケースでは
FlightBean
のためのマッピングを定義します。要素の本体内に特にリストされていないクラスのどのプロパティーにもデフォルト・マッピングを使用するように、Castorに指示することがきます。そのためには、この要素に
true
の値を指定してオプションの
auto-complete
属性を組み込みます。
number
プロパティーは、すでに好みの方法で処理されているので、これは便利です。
子の
map-to
要素は、XML文書で
FlightBean
クラスのインスタンスを
flight
要素にマップするように、Castorに指示します。
舞台裏情報
のデフォルトのマッピング出力の例で見られる
flight-bean
のデフォルト要素名を引き続き使用する場合、この要素はオプションです。
最後に、デフォルトとは別の方法で処理したい各プロパティーに、子の
field
要素を組み込むことができます。これらはすべて、同じパターンに従います。すなわち、
name
属性はプロパティー名を指定し、子の
bind-xml
要素はそのプロパティーをマップする方法をCastorに指示します。このケースでは、各プロパティーを、指定した名前を持つ属性にマップするように指示します。
マッピングの使用
マッピングの定義が完了したので、次はCastorフレームワークに、データのマーシャルおよびアンマーシャルの際にそのマッピングを使用するように指示する必要があります。リスト4は、これを実現するために、前のコードに加えなければならない変更を示しています。
リスト4. マッピングによるマーシャルおよびアンマーシャル
...
// write it out as XML (if not already present)
Mapping map = new Mapping();
map.loadMapping("mapping.xml");
File file = new File("test.xml");
Writer writer = new FileWriter(file);
Marshaller marshaller = new Marshaller(writer);
marshaller.setMapping(map);
marshaller.marshal(bean);
// now restore the value and list what we get
Reader reader = new FileReader(file);
Unmarshaller unmarshaller = new Unmarshaller(map);
FlightBean read = (FlightBean)unmarshaller.unmarshal(reader);
...
} catch (MappingException ex) {
ex.printStackTrace(System.err);
... |
このコードは、
リスト2
で示した、デフォルト・マッピングを使用するコードよりも、少し複雑です。その他の操作を行う前に、
Mapping
オブジェクトを作成し、作成したマッピング定義をロードしてください。実際のマーシャルとアンマーシャルも異なります。このマッピングを使用するには、
Marshaller
オブジェクトと
Unmarshaller
オブジェクトを作成し、それらをこのマッピングによって構成し、これらのオブジェクトで、最初の例のstaticメソッドではないメソッドを呼び出す必要があります。最後に、マッピング・エラーで生成される別の例外タイプの処理を用意します。
これらの変更を完了したら、テスト・プログラムをもう一度試行できます。コンソール出力は、最初の例 (
リスト2
に示されたもの) と同じですが、XML文書は、希望した以下のように見えるようになります。
<?xml version="1.0"?>
<flight carrier="AR" depart="6:23a" arrive="8:42a" number="426"/>
|
コレクションの処理
個々のフライト・データが好みの形式になったので、より上位レベルの構造、経路データを定義してみましょう。この構造には、その経路のフライトのコレクションと共に、発着空港の識別子も組み込みます。リスト5は、この情報を保持するBeanクラスの例を示しています。
リスト5. 経路情報Bean
import java.util.ArrayList;
public class RouteBean
{
private String m_from;
private String m_to;
private ArrayList m_flights;
public RouteBean() {
m_flights = new ArrayList();
}
public void setFrom(String from) {
m_from = from;
}
public String getFrom() {
return m_from;
}
public void setTo(String to) {
m_to = to;
}
public String getTo() {
return m_to;
}
public ArrayList getFlights() {
return m_flights;
}
public void addFlight(FlightBean flight) {
m_flights.add(flight);
}
}
|
このコードでは、経路のフライトを一度に1つずつ設定するために使用する、
addFlight()
メソッドを定義しました。これはテスト・プログラムでデータ構造を作成する場合の簡便なアプローチですが、皆さんの予想に反し、Castorは、アンマーシャル時の経路へのフライトの追加にこのメソッドを使用しません。代わりに、
getFlights()
メソッドを使用して、フライトのコレクションにアクセスし、そのコレクションに直接追加します。
マッピングでのフライトのコレクションの処理では、最後の例 (
リスト3
で示したもの) で使用した
field
要素を変形したものを利用します。リスト6は、その変更されたマッピング・ファイルです。
リスト6. フライト・コレクションの経路へのマッピング
<!DOCTYPE databases PUBLIC
"-//EXOLAB/Castor Mapping DTD Version 1.0//EN"
"http://castor.exolab.org/mapping.dtd">
<mapping>
<description>Collection mapping example</description>
<class name="RouteBean">
<map-to xml="route"/>
<field name="from">
<bind-xml name="from" node="attribute"/>
</field>
<field name="to">
<bind-xml name="to" node="attribute"/>
</field>
<field name="flights" collection="collection" type="FlightBean">
<bind-xml name="flight"/>
</field>
</class>
<class name="FlightBean" auto-complete="true">
<field name="carrier">
<bind-xml name="carrier" node="attribute"/>
</field>
<field name="departureTime">
<bind-xml name="depart" node="attribute"/>
</field>
<field name="arrivalTime">
<bind-xml name="arrive" node="attribute"/>
</field>
</class>
</mapping> |
これは、最後のマッピング (
リスト3
で示したもの) とほとんど変りありませんが、
field
要素は、
RouteBean
の
flights
プロパティーを定義しています。このマッピングでは、以前は必要でなかった一対の属性を使用しています。値
collection
を指定した
collection
属性は、このプロパティーをjava.util.Collectionと定義します
(他の値では配列、java.util.Vectorsなどを定義します)。
type
プロパティーは、値として完全なクラス名を指定して、コレクションに組み込まれるオブジェクトの型を定義します。私はクラスにパッケージを使用しなかったので、この例での値は
FlightBean
だけです。
その他の相違は、バインディング用の要素名を定義するのに、
FlightBean
クラス要素内で
map-to
子要素を使用する必要がなくなったことです。
RouteBean
の
flights
プロパティーを定義する
field
要素は、その子の
bind-xml
要素によってこの定義を行います。このプロパティーを利用するのが、
FlightBean
オブジェクトをマーシャルまたはアンマーシャルする唯一の方法であるため、この
bind-xml
要素によって設定された名前が常に使用されます。
この例の場合のデータ・バインディング部分は、最後の例と同じなので、この例のためのテスト・プログラムはあえて表示しません。サンプル・データの生成済みXML文書は以下のようになります。
<?xml version="1.0"?>
<route from="SEA" to="LAX">
<flight carrier="AR" depart="6:23a" arrive="8:42a"
number="426"/>
<flight carrier="CA" depart="8:10a" arrive="10:52a"
number="833"/>
<flight carrier="AR" depart="9:00a" arrive="11:36a"
number="433"/>
</route>
|
オブジェクト参照
これで、完全なフライト時刻表に挑戦する準備ができました。このためにさらに以下の3つのBeanをセットに追加します。
-
AirportBean
、空港情報用
-
CarrierBean
、航空会社情報用
-
TimeTableBean
、すべてをまとめて収めます
作業が退屈にならないよう、最後の例 (
コレクションの処理
で示したもの) で使用した
RouteBean
と
FlightBean
の間の所有権関係と、さらにはBean間のリンケージを追加することにしましょう。
Beanのリンク
最初に追加した関係について、単にコードを使用してキャリアを識別するのでなく、キャリア情報を直接参照するように
FlightBean
を変更しましょう。
FlightBean
への変更は以下のとおりです。
public class FlightBean
{
private CarrierBean m_carrier;
...
public void setCarrier(CarrierBean carrier) {
m_carrier = carrier;
}
public CarrierBean getCarrier() {
return m_carrier;
}
...
}
|
今度は、空港情報を参照する
RouteBean
について同じことをやってみましょう。
{
private AirportBean m_from;
private AirportBean m_to;
...
public void setFrom(AirportBean from) {
m_from = from;
}
public AirportBean getFrom() {
return m_from;
}
public void setTo(AirportBean to) {
m_to = to;
}
public AirportBean getTo() {
return m_to;
}
...
}
|
追加したBeanそのもののコードは組み込みません
(それらのBeanは、以前の実行内容以上のことはなにも示さないからです)。code.jarダウンロード・ファイル
(
参考文献
を参照) で、すべての例の完全なコードをダウンロードできます。
マッピング参照
マーシャルおよびアンマーシャルするオブジェクト間の参照をサポートするため、マッピング文書のその他のフィーチャーを使用する必要があります。リスト7に完全なマッピングを示しています。
リスト7. 完全な時刻表のマッピング
<!DOCTYPE databases PUBLIC
"-//EXOLAB/Castor Mapping DTD Version 1.0//EN"
"http://castor.exolab.org/mapping.dtd">
<mapping>
<description>Reference mapping example</description>
<class name="TimeTableBean">
<map-to xml="timetable"/>
<field name="carriers" type="CarrierBean" collection="collection">
<bind-xml name="carrier"/>
</field>
<field name="airports" type="AirportBean" collection="collection">
<bind-xml name="airport"/>
</field>
<field name="routes" type="RouteBean" collection="collection">
<bind-xml name="route"/>
</field>
</class>
<class name="CarrierBean" identity="ident" auto-complete="true">
<field name="ident">
<bind-xml name="ident" node="attribute"/>
</field>
</class>
<class name="AirportBean" identity="ident" auto-complete="true">
<field name="ident">
<bind-xml name="ident" node="attribute"/>
</field>
</class>
<class name="RouteBean">
<field name="from" type="AirportBean">
<bind-xml name="from" node="attribute" reference="true"/>
</field>
<field name="to" type="AirportBean">
<bind-xml name="to" node="attribute" reference="true"/>
</field>
<field name="flights" type="FlightBean" collection="collection">
<bind-xml name="flight"/>
</field>
</class>
<class name="FlightBean" auto-complete="true">
<field name="carrier">
<bind-xml name="carrier" node="attribute" reference="true"/>
</field>
<field name="departureTime">
<bind-xml name="depart" node="attribute"/>
</field>
<field name="arrivalTime">
<bind-xml name="arrive" node="attribute"/>
</field>
</class>
</mapping> |
追加したBeanのほか、ここでの重要な変更点は、
identity
属性と
reference
属性の追加です。
class
要素の
identity
属性は、名前付きプロパティーが、そのクラスのインスタンスの固有な識別子であることをCastorに示します。
CarrierBean
と
AirportBean
の両方で、識別子としてそれらの
ident
プロパティーを定義しています。
bind-xml
要素の
reference
属性は、Castorがマッピングのために必要とするリンケージ情報のその他の部分を提供します。
true
に設定される
reference
によるマッピングでは、オブジェクトそのもののコピーではなく、参照されたオブジェクトの識別子をマーシャルするようにCastorに指示されます。
RouteBean
から経路の2つの端のリンクされた
AirportBean
への参照と、
FlightBean
からリンクされた
CarrierBean
への参照で、これを使用しました。
Castorは、このタイプのマッピングを使用してデータをアンマーシャルするとき、オブジェクト識別子を自動的に実際のオブジェクトへの参照に変換します。識別子の値が異なるタイプのオブジェクト間でも、ほんとうに固有であることを確認する必要があります。この例のデータでは、これは問題ありません。キャリアの識別子は2文字で、空港の識別子は3文字なので、同じになることはないからです。矛盾が生じる可能性がある
ケースでも、各識別子に、それが表すオブジェクトのタイプごとに固有なコードをプレフィックスとして指定すれば、この問題は簡単に回避できます。
マーシャルされた時刻表
サンプル・データにさらに多くのセットアップがあるほかは、この例のテスト・コードに新しいものはなにも含まれていません。リスト8は、マーシャルによって生成されたXML文書を表しています。
リスト8. マーシャルされた時刻表
<?xml version="1.0"?>
<timetable>
<carrier ident="AR" rating="9">
<URL>http://www.arcticairlines.com</URL>
<name>Arctic Airlines</name>
</carrier>
<carrier ident="CA" rating="7">
<URL>http://www.combinedlines.com</URL>
<name>Combined Airlines</name>
</carrier>
<airport ident="SEA">
<location>Seattle, WA</location>
<name>Seattle-Tacoma International Airport</name>
</airport>
<airport ident="LAX">
<location>Los Angeles, CA</location>
<name>Los Angeles International Airport</name>
</airport>
<route from="SEA" to="LAX">
<flight carrier="AR" depart="6:23a" arrive="8:42a" number="426"/>
<flight carrier="CA" depart="8:10a" arrive="10:52a" number="833"/>
<flight carrier="AR" depart="9:00a" arrive="11:36a" number="433"/>
</route>
<route from="LAX" to="SEA">
<flight carrier="CA" depart="7:45a" arrive="10:20a" number="311"/>
<flight carrier="AR" depart="9:27a" arrive="12:04p" number="593"/>
<flight carrier="AR" depart="12:30p" arrive="3:07p" number="102"/>
</route>
</timetable>
|
データの作業
時刻表のすべてのデータがついにセットアップ完了しました。プログラムでそのデータをどのように操作することができるかを簡単に見ておきましょう。データ・バインディングを使用して、いくつかのタイプのBeanから成る時刻表のデータ構造を構成しました。そのデータで動作するアプリケーション・コードは、これらのBeanを直接使用できます。
たとえば、SeattleとLos
Angeles間の往復フライトの選択項目を、指定した最低限の品質評価を持つキャリアでのみ、検索したいとしましょう。リスト9は、データ・バインディングBean構造を使用してこの情報を得るための基本コーディングを示しています
(完全な詳細は、
参考文献
のソース・ダウンロードを参照してください)。
リスト9. フライト検索プログラム・コード
private static void listFlights(TimeTableBean top, String from,
String to, int rating) {
// find the routes for outbound and inbound flights
Iterator r_iter = top.getRoutes().iterator();
RouteBean in = null;
RouteBean out = null;
while (r_iter.hasNext()) {
RouteBean route = (RouteBean)r_iter.next();
if (route.getFrom().getIdent().equals(from) &&
route.getTo().getIdent().equals(to)) {
out = route;
} else if (route.getFrom().getIdent().equals(to) &&
route.getTo().getIdent().equals(from)) {
in = route;
}
}
// make sure we found the routes
if (in != null && out != null) {
// find outbound flights meeting carrier rating requirement
Iterator o_iter = out.getFlights().iterator();
while (o_iter.hasNext()) {
FlightBean o_flight = (FlightBean)o_iter.next();
if (o_flight.getCarrier().getRating() >= rating) {
// find inbound flights meeting carrier rating
// requirement, and leaving after outbound arrives
int time = timeToMinute(o_flight.getArrivalTime());
Iterator i_iter = in.getFlights().iterator();
while (i_iter.hasNext()) {
FlightBean i_flight = (FlightBean)i_iter.next();
if (i_flight.getCarrier().getRating() >= rating
&&
timeToMinute(i_flight.getDepartureTime())
> time) {
// list the flight combination
printFlights(o_flight, i_flight, from, to);
}
}
}
}
}
} |
先に
リスト8
で示したサンプル・データを使用して、これを試行できます。ランクが8以上のキャリアでSeattle (SEA)
からLos Angeles (LAX) へのフライトを問い合わせると、その結果は以下のようになります。
Leave SEA on Arctic Airlines 426 at 6:23a
return from LAX on Arctic Airlines 593 at 9:27a
Leave SEA on Arctic Airlines 426 at 6:23a
return from LAX on Arctic Airlines 102 at 12:30p
Leave SEA on Arctic Airlines 433 at 9:00a
return from LAX on Arctic Airlines 102 at 12:30p |
文書モデルの比較
ここでは、XML文書モデルの1つを使用した同等のコードについては検討しません。検討しようとすれば、また別の記事が書けるほど複雑だからです。この問題にアプローチする最も簡単な方法はおそらく、最初に
carrier
要素を構文解析し、対応する要素に各識別子コードをリンクするマップを作成することです。それから、
リスト9
のコード例のようなロジックを使用します。このコードは、実際のデータ値ではなく、XMLコンポーネントに対して動作するため、各ステップは、Bean例よりも複雑です。またパフォーマンスもずっと悪くなりますが、このデータを使って少しの操作を行う場合は、問題にはなりません。ただし、アプリケーションの重要部分である場合は、大きな問題になります。
BeanとXML間のマッピングで使用するデータ型変換が多くなるほど、(コードの複雑さとパフォーマンスの両方の意味での)
相違は大きくなります。たとえば、フライト時刻に関する作業が多い場合は、おそらく、テキスト時刻をよりよい内部表現
(
リスト9
に示しているような分の数値など) に変換したいでしょう。テキストVS内部形式のために代替の
get
メソッドと
set
メソッドを定義するか (テキスト形式だけを使用するようにマッピングを設定)、あるいはカスタムの
org.exolab.castor.mapping.FieldHandler
インプリメンテーションを定義して、Castorがこれらの値を使用するようにします。時刻値を内部形式に保つとリスト9でフライトの突き合わせを試みるときに変換をスキップすることができ、処理はさらに高速になります。
Castorには、この記事で説明したもののほかにも、カスタマイズのためのフックがたくさん用意されています。特別な
FieldHandler
はほんの一例です。このサンプル・コードと解説から、読者にこのフレームワークの能力と柔軟性を感じていただけたら幸いです。それぞれ独自に、Castorを使ってみることをお勧めします。Castorの便利さ
(と面白さ) を実感されるものと信じます。
結論
データ・バインディングは、データ交換にXMLを使用するアプリケーションにおいて文書モデルに代わる優れた技法です。プログラミングは、XMLの観点から考える必要がなくなるため、単純化されます。アプリケーションで使用されるデータの意味を表すオブジェクトで、直接作業することができます。また、文書モデルよりも優れたメモリーと処理プログラムのパフォーマンスの可能性も提供されます。
この記事では、Castorフレームワークを使用したデータ・バインディングの、段階的に複雑になる一連の例を検討しました。これらのすべての例で、私が直接
データ・バインディングと呼ぶ技法を使用しています。開発者は、データを基にしてクラスを定義し、そのデータをXML文書構造にマップすることになります。次回の記事では別のアプローチを説明します。そのアプローチはスキーマ
・データ・バインディングであり、文書スキーマ (DTD、XMLスキーマ、または別のタイプ)
をとり、そのスキーマに対応するコードを生成します。
Castorでは、この記事で説明した直接バインディングだけでなくスキーマ・バインディングもサポートされるので、次の記事でさらにCastorの能力を理解いただけるものと思います。また、Javaデータ・バインディング標準のためのJSR-031の進行状況について検討し、各アプローチのパフォーマンスを比較します。JavaにおけるXMLデータ・バインディングについての次回の記事にご注目ください。IBMdeveloperWorks
でまもなく発表されるはずです。
ダウンロード | 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|
| Example programs and libraries for this article | x-bindcastor/code.jar.zip | 1360KB | HTTP |
|---|
参考文献
著者について  | 
|  | Dennis Sosnoskiはシアトル地域にあるJava技術のコンサルティング会社、Sosnoski Software Solutions, Inc.の創立者で、主席コンサルタントでもあり、またXMLやWebサービスに関するトレーニングやコンサルティングの専門家でもあります。彼のプロとしてのソフトウェア開発経験は30年以上に渡り、ここ数年はサーバー側のXML技術やJava技術に注力しています。Dennisは、全米各地で行われる会議で頻繁に講演を行っています。また、Javaクラスワーキング技術を基に構築された、オープンソースのJiBX XML Data Bindingフレームワークの中心開発者でもあります。
|
記事の評価
|