レベル: 上級 Brett D. McLaughlin, Sr. (brett@newInstance.com), Author and Editor, O'Reilly Media, Inc.
2007年 12月 10日 このシリーズの前回の記事では、Cator をダウンロードしてインストールし、セットアップしました。今回の記事では、Java™ クラスを XML に変換し、そしてその XML を Java コードに逆変換する方法について学びます。また Castor がどのように動作するのかについて、そして Castor の API でうまく動作するようにクラスを設計する方法についても説明します。
前提条件
先に進む前に、この記事のための前提条件が揃っていることを確認する必要があります。これらの要件を確認する最も簡単な方法は、このシリーズの第 1 回で概要を説明したステップに従うことです (リンクは毎回おなじみの「参考文献」にあります)。第 1 回の記事では、Castor をダウンロードし、インストールして構成し、そして非常に単純なクラスに対して API をテストする方法を順番に説明しました。
また、皆さんが現在作業しているプロジェクトから、XML に変換したり XML から逆変換したりするためのクラスをいくつか用意しておくと便利です。今回の記事 (そして前回の記事) にはサンプルのクラスが用意されていますが、Castor を使いこなすためには、ここで説明した内容を皆さん自身のプロジェクトに応用してみることが一番です。まず、人間や CD、本、あるいはその他の具象オブジェクトを表す、いくつかの基本的なオブジェクト・クラスから始めます。次に、この記事でマッピングを説明するセクションに入ったら、さらに複雑なものを追加します。
マーシャリングの基本
Castor における最も基本的な操作は、Java クラスのインスタンスを XML にマーシャリングすることです。マーシャリングでは、クラスそのものを用意したら、そのクラスを最上位レベルのコンテナー要素として使います。従って Book クラスであれば、ほぼ確実に、root 要素として「book」という要素を持つ XML 文書になります。
そして、その XML 文書の中で、このクラスの個々のプロパティーが表現されます。従って「Power Play」という値を持つ title プロパティーがあると、下記のような XML が生成されます。
<title>Power Play</title>
|
これを見ると、単純な Java クラスから生成される XML 文書の残りの部分をとても容易に推測することができます。
クラスのインスタンスをマーシャリングする
コードをマーシャリングする前に、いくつかの注意点があります。第 1 に、マーシャリングは必ずクラスのインスタンスに対して行うのであり、クラスそのものをマーシャリングするわけではない、という点です。クラスは構造であり、DTD や XML Schema などの、XML の制約モデルに等しいものと考えるのが適切です。クラス自体はデータを持たず、保存されるデータの構造と、そのデータへのアクセス方法を定義しているにすぎません。
そのクラスをインスタンス化することで (または、ファクトリーや他のインスタンス生成機構から取得することで)、そのクラスに特定の形式を与えます。そして次に、そのインスタンスのフィールドに実際のデータを追加します。このインスタンスは固有のものです。つまり同じクラスの他のすべてのインスタンスと同じ構造を持ちますが、データは異なります。図 1 はこの概念を視覚的に表現しています。
図 1. クラスは構造を提供し、インスタンスはそのデータである
従って、インスタンスをマーシャリングするのです。後のセクションで、制約モデルとマッピング・ファイルを使って XML の構造を変更する方法を検証しますが、ここでは XML の構造 (要素と属性) を Java の構造 (プロパティー) に対応させることにします。
基本的なマーシャリング
リスト 1 は、この記事で使用する単純な Book クラスです。
リスト 1. Book クラス
package ibm.xml.castor;
public class Book {
/** The book's ISBN */
private String isbn;
/** The book's title */
private String title;
/** The author's name */
private String authorName;
public Book(String isbn, String title, String authorName) {
this.isbn = isbn;
this.title = title;
this.authorName = authorName;
}
public String getIsbn() {
return isbn;
}
public void setTitle(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
public void setAuthorName(String authorName) {
this.authorName = authorName;
}
public String getAuthorName() {
return authorName;
}
}
|
このコードをコンパイルすると Book.class ファイルが得られます。このクラスは非常に単純であり、ISBN、タイトル、そして著者名という 3 つのプロパティーしか持っていません (これにはいくつかの問題がありますが、今はそれを気にしないことにします)。この Book クラスのインスタンスを XML 文書に変換する場合、Castor に必要なのはこのファイルの内容と、あと数行のコードだけです。リスト 2 は、Castor を使って Book の新しいインスタンスを作成し、それを XML に書き出す単純なプログラムです。
リスト 2. Book をマーシャリングするクラス
package ibm.xml.castor;
import java.io.FileWriter;
import org.exolab.castor.xml.Marshaller;
public class BookMarshaller {
public static void main(String[] args) {
try {
Book book = new Book("9780312347482", "Power Play", "Joseph Finder");
FileWriter writer = new FileWriter("book.xml");
Marshaller.marshal(book, writer);
} catch (Exception e) {
System.err.println(e.getMessage());
e.printStackTrace(System.err);
}
}
}
|
このプログラムをコンパイルして実行します。すると新しいファイル book.xml が作成されます。このファイルを開くと、以下のような記述が見つかるはずです。
リスト 3. コンパイルされたプログラムによって作成された XML
<?xml version="1.0" encoding="UTF-8"?>
<book><author-name>Joseph Finder</author-name>
<isbn>9780312347482</isbn><title>Power Play</title></book> |
ここでは画面上に表示するために改行を追加してあります。実際に生成される XML 文書には、author-name 要素の終了タグと isbn 要素の開始タグの間に改行はありません。
Castor が処理しないもの
このサンプルを改善する前に (実際、少し改善が必要です)、少し時間を取って、Castor が XML の中に保存しないものに注目してみましょう。
-
Java クラスのパッケージ。 Java パッケージはクラスの構造の一部ではありません。そのこと自体は実際には、Java の名前空間との関係において、クラスとパッケージという言葉が示す内容の問題です。従って、パッケージとは無関係に、この XML 文書を、(同じ 3 つのプロパティーを持つ) 任意の
Book インスタンスにアンマーシャル (XML から Java コードに変換) することができます。
-
フィールドの順序。 順序は XML では重要ですが、Java プログラミングでは重要ではありません。従って、ソース・ファイルでは、ある順序でフィールドがリストされていましたが、XML 文書は別の順序にしました。皆さんの XML でもフィールドの順序は重要ですが、
Book クラスの宣言では順序は関係ありません。
-
メソッド。 メソッドはパッケージ宣言と同じく、データの構造とは何の関係もありません。従って XML 文書はメソッドに関しては何もせず、メソッドは無視されます。
おそらく皆さんは、「だからどうした」と思われるのではないでしょうか。こうした詳細が、生成された XML では失われたとしても、その詳細が重要でなければ、誰がそれを気にするのでしょう。しかしこの詳細は重要なのです。この詳細によって期待していた以上の柔軟性が得られる、という意味で詳細が重要なのです。この XML は、以下の必要最低限の要件を満たすクラスへとアンマーシャルすることができます。
- そのクラスの名前が「Book」であること (マッピング・ファイルを使えば対応できますが、それは後にします)
- そのクラスには
authorName、title、isbn というフィールドがあること。
これだけです。この 2 つの要件を満たす一方、そのクラスが持つ他のフィールドやパッケージ宣言、あるいはメソッド等々を変化させても、クラスをいくつか作成できるかどうか試してみてください。このおかげで Castor がどれほど柔軟になっているか、すぐに理解することができるでしょう。
より複雑な型を追加する
Book クラスの最大の欠陥は複数の著者を保存できないことです。複数の著者を処理できるようにクラスを変更するのは簡単です。
リスト 4. 複数の著者を保存する Book クラス
package ibm.xml.castor;
import java.util.LinkedList;
import java.util.List;
public class Book {
/** The book's ISBN */
private String isbn;
/** The book's title */
private String title;
/** The authors' names */
private List authorNames;
public Book(String isbn, String title, List authorNames) {
this.isbn = isbn;
this.title = title;
this.authorNames = authorNames;
}
public Book(String isbn, String title, String authorName) {
this.isbn = isbn;
this.title = title;
this.authorNames = new LinkedList();
authorNames.add(authorName);
}
public String getIsbn() {
return isbn;
}
public void setTitle(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
public void setAuthorNames(List authorNames) {
this.authorNames = authorNames;
}
public List getAuthorNames() {
return authorNames;
}
public void addAuthorName(String authorName) {
authorNames.add(authorName);
}
} |
Java 5 または 6 の技術を使っている人は、「未チェックまたは安全ではない操作 (unchecked or unsafe operation)」の警告が出るかもしれません。それは、このコードがパラメーター・リストを使っていないためです。もし必要であれば、そのコードを追加することができます。
これは単純な変更であり、マーシャリング・コードを変更する必要はありません。しかし、複数の著者による本を使って、Castor のマーシャリング・プロセスがどのようにコレクションを処理するかを調べてみる価値はあります。そこで BookMarshaller クラスに対して、以下のような変更を加えます。
リスト 5. コレクションを処理する Book クラス
package ibm.xml.castor;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.List;
import org.exolab.castor.xml.Marshaller;
public class BookMarshaller {
public static void main(String[] args) {
try {
Book book = new Book("9780312347482", "Power Play", "Joseph Finder");
FileWriter writer = new FileWriter("book.xml");
Marshaller.marshal(book, writer);
List book2Authors = new ArrayList();
book2Authors.add("Douglas Preston");
book2Authors.add("Lincoln Child");
Book book2 = new Book("9780446618502", "The Book of the Dead",
book2Authors);
writer = new FileWriter("book2.xml");
Marshaller.marshal(book2, writer);
} catch (Exception e) {
System.err.println(e.getMessage());
e.printStackTrace(System.err);
}
}
} |
最初の本は同じ方法で処理されます。つまり book.xml を再度開くと、先ほどと同じものが出力されます。また book2.xml を開くと、Castor がコレクションをどう処理したかを見ることができます。
リスト 6. コレクターを使った XML の結果
<?xml version="1.0" encoding="UTF-8"?>
<book><isbn>9780446618502</isbn><title>The Book of the Dead</title>
<author-names xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:type="java:java.lang.String">Douglas Preston</author-names>
<author-names xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:type="java:java.lang.String">Lincoln Child</author-names>
</book> |
当然ですが、Castor は、著者名のリストを問題なく処理しました。もっと重要なこととして、Castor は著者名の全体を格納するためのコンテナーを作成する以上のことをしています。Castor のフレームワークは実際にリストの内部を調べ、そしてその内容がストリングであることを認識しています (これはパラメーター・リストではなく、リストの各メンバーの型を取得するという大変な作業を Castor が行っていたことを思い出してください)。そのため、XML の中では非常に具体的な型定義が行われます。これは、特に XML を Java コードに戻す前に XML を処理する場合には便利な機能です。
カスタム・クラスを追加する
現実的なアプリケーションに向かって、もう 1 歩進めましょう。著者名のストリングを保存すると、必然的に重複したデータが作られてしまいます (ほとんどの著者は複数の本を執筆しています)。リスト 7 は、これまでのものに加えて新しいクラス Author を追加します。
リスト 7. Author クラス
package ibm.xml.castor;
public class Author {
private String firstName, lastName;
private int totalSales;
public Author(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public void setTotalSales(int totalSales) {
this.totalSales = totalSales;
}
public void addToSales(int additionalSales) {
this.totalSales += additionalSales;
}
public int getTotalSales() {
return totalSales;
}
} |
非常に単純だと思われたのではないでしょうか。この新しい Author クラスを使う前に、Book クラスに以下の変更を加えます。
リスト 8. カスタムの author クラスを使う Book クラス
package ibm.xml.castor;
import java.util.LinkedList;
import java.util.List;
public class Book {
/** The book's ISBN */
private String isbn;
/** The book's title */
private String title;
/** The authors' names */
private List authors;
public Book(String isbn, String title, List authors) {
this.isbn = isbn;
this.title = title;
this.authors = authors;
}
public Book(String isbn, String title, Author author) {
this.isbn = isbn;
this.title = title;
this.authors = new LinkedList();
authors.add(author);
}
public String getIsbn() {
return isbn;
}
public void setTitle(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
public void setAuthors(List authors) {
this.authors = authors;
}
public List getAuthors() {
return authors;
}
public void addAuthor(Author author) {
authors.add(author);
}
} |
BookMarshaller をさらに少し変更すると以下のコードが得られます。
リスト 9. 著者情報を追加した BookMarshaller クラス
package ibm.xml.castor;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.List;
import org.exolab.castor.xml.Marshaller;
public class BookMarshaller {
public static void main(String[] args) {
try {
Author finder = new Author("Joseph", "Finder");
Book book = new Book("9780312347482", "Power Play", finder);
FileWriter writer = new FileWriter("book.xml");
Marshaller.marshal(book, writer);
List book2Authors = new ArrayList();
book2Authors.add(new Author("Douglas", "Preston"));
book2Authors.add(new Author("Lincoln", "Child"));
Book book2 = new Book("9780446618502", "The Book of the Dead",
book2Authors);
writer = new FileWriter("book2.xml");
Marshaller.marshal(book2, writer);
} catch (Exception e) {
System.err.println(e.getMessage());
e.printStackTrace(System.err);
}
}
} |
これで終わりです。すべてをコンパイルして、マーシャラーを実行します。両方のファイルをチェックしてください。ここでは、関心のある方の book2.xml のみを示してあります。
リスト 10. authors と book に関する XML の結果
<?xml version="1.0" encoding="UTF-8"?>
<book><authors xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
total-sales="0" xsi:type="java:ibm.xml.castor.Author"><last-name>Preston</last-name>
<first-name>Douglas</first-name></authors><authors
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" total-sales="0"
xsi:type="java:ibm.xml.castor.Author"><last-name>Child</last-name>
<first-name>Lincoln</first-name></authors><isbn>9780446618502</isbn>
<title>The Book of the Dead</title></book>
|
この場合も、Castor は問題なく処理を行っています。Castor は、Book クラスだけではなく Author クラスもマーシャリングする方法を見つけ出しています (この場合も先ほどと同じですが、authors リストのイントロスペクションを行っています)。さらに Castor は、Author の totalSales プロパティーまで追加しています。そのため、totalSales プロパティーを設定して XML の中に反映させることもできます。
Castor は Generics の処理を行えます
こうなってくると終夜放送しているショップ・チャンネルのように思えるかもしれませんが、Castor は Generics とパラメーター・リストを処理することができます。そのため、Java 5 または Java 6 を想定した場合、Book を以下のように変更することができます。
リスト 11. Generics とパラメーター・リストを処理する Book クラス
package ibm.xml.castor;
import java.util.LinkedList;
import java.util.List;
public class Book {
/** The book's ISBN */
private String isbn;
/** The book's title */
private String title;
/** The authors' names */
private List<Author> authors;
public Book(String isbn, String title, List<Author> authors) {
this.isbn = isbn;
this.title = title;
this.authors = authors;
}
public Book(String isbn, String title, Author author) {
this.isbn = isbn;
this.title = title;
this.authors = new LinkedList<Author>();
authors.add(author);
}
public String getIsbn() {
return isbn;
}
public void setTitle(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
public void setAuthors(List<Author> authors) {
this.authors = authors;
}
public List<Author> getAuthors() {
return authors;
}
public void addAuthor(Author author) {
authors.add(author);
}
} |
これで、著者リストは Author インスタンスしか受け付けないようになりました。これはかなり大きな改善です。同様の変更を BookMarshaller に対しても行い、再コンパイルすると、同じ XML 出力が得られます。つまり、Castor で使用するための非常に堅牢なクラスを作成できるのです。
クラスは変わりますが Castor は変わりません
いよいよアンマーシャリングに移る前に、重要なことに注目する必要があります。ここまでのセクションで、クラスに対してさまざまな調整や変更を行いましたが、マーシャリング・コードを変更する必要はありませんでした。Generics やカスタム・クラス、コレクション等々を追加しましたが、Castor の API によって、単純な呼び出しを 1 つ行うだけで XML にマーシャリングできたのです。これは非常に驚くべきことです。
アンマーシャリング
マーシャリングに関して十分に時間をかけて、詳細を説明したので、アンマーシャリングは非常に簡単です。XML 文書を選び、その XML 文書のデータに対応する Java クラスがあることを確認し、作業を Castor に任せます。先ほど生成した 2 つの XML 文書をアンマーシャリングしてみましょう。リスト 12 はその作業を行うコードです。
リスト 12. book をアンマーシャリングする
package ibm.xml.castor;
import java.io.FileReader;
import java.util.Iterator;
import java.util.List;
import org.exolab.castor.xml.Unmarshaller;
public class BookUnmarshaller {
public static void main(String[] args) {
try {
FileReader reader = new FileReader("book.xml");
Book book = (Book)Unmarshaller.unmarshal(Book.class, reader);
System.out.println("Book ISBN: " + book.getIsbn());
System.out.println("Book Title: " + book.getTitle());
List authors = book.getAuthors();
for (Iterator i = authors.iterator(); i.hasNext(); ) {
Author author = (Author)i.next();
System.out.println("Author: " + author.getFirstName() + " " +
author.getLastName());
}
System.out.println();
reader = new FileReader("book2.xml");
book = (Book)Unmarshaller.unmarshal(Book.class, reader);
System.out.println("Book ISBN: " + book.getIsbn());
System.out.println("Book Title: " + book.getTitle());
authors = book.getAuthors();
for (Iterator i = authors.iterator(); i.hasNext(); ) {
Author author = (Author)i.next();
System.out.println("Author: " + author.getFirstName() + " " +
author.getLastName());
}
} catch (Exception e) {
System.err.println(e.getMessage());
e.printStackTrace(System.err);
}
}
} |
このコードをコンパイルして実行します。おそらく予想外の結果が得られるはずです。以下の出力は私のエラーとスタック・トレースです。
リスト 13. アンマーシャリングのエラーとスタック・トレース
[bmclaugh:~/Documents/developerworks/castor-2]
java ibm.xml.castor.BookUnmarshaller
ibm.xml.castor.Book
org.exolab.castor.xml.MarshalException: ibm.xml.castor.Book{File:
[not available]; line: 2; column: 7}
at org.exolab.castor.xml.Unmarshaller.
convertSAXExceptionToMarshalException(Unmarshaller.java:755)
at org.exolab.castor.xml.Unmarshaller.unmarshal
(Unmarshaller.java:721)
at org.exolab.castor.xml.Unmarshaller.unmarshal
(Unmarshaller.java:610)
at org.exolab.castor.xml.Unmarshaller.unmarshal
(Unmarshaller.java:812)
at ibm.xml.castor.BookUnmarshaller.main
(BookUnmarshaller.java:14)
Caused by: java.lang.InstantiationException: ibm.xml.castor.Book
at java.lang.Class.newInstance0(Class.java:335)
at java.lang.Class.newInstance(Class.java:303)
at org.exolab.castor.util.DefaultObjectFactory.createInstance(
DefaultObjectFactory.java:107)
at org.exolab.castor.xml.UnmarshalHandler.createInstance(
UnmarshalHandler.java:2489)
at org.exolab.castor.xml.UnmarshalHandler.startElement(
UnmarshalHandler.java:1622)
at org.exolab.castor.xml.UnmarshalHandler.startElement(
UnmarshalHandler.java:1353)
at org.apache.xerces.parsers.AbstractSAXParser.startElement
(Unknown Source)
at org.apache.xerces.impl.dtd.XMLDTDValidator.startElement
(Unknown Source)
at
org.apache.xerces.impl.XMLDocumentFragmentScannerImpl.scanStartElement(
Unknown Source)
at org.apache.xerces.impl.XMLDocumentScannerImpl
$ContentDispatcher.
scanRootElementHook(Unknown Source)
at org.apache.xerces.impl.
XMLDocumentFragmentScannerImpl
$FragmentContentDispatcher.dispatch(
Unknown Source)
at
org.apache.xerces.impl.XMLDocumentFragmentScannerImpl.scanDocument(
Unknown Source)
at org.apache.xerces.parsers.XML11Configuration.parse
(Unknown Source)
at org.apache.xerces.parsers.XML11Configuration.parse
(Unknown Source)
at org.apache.xerces.parsers.XMLParser.parse(Unknown Source)
at org.apache.xerces.parsers.AbstractSAXParser.parse(Unknown
Source)
at org.exolab.castor.xml.Unmarshaller.unmarshal
(Unmarshaller.java:709)
... 3 more
Caused by: java.lang.InstantiationException: ibm.xml.castor.Book
at java.lang.Class.newInstance0(Class.java:335)
at java.lang.Class.newInstance(Class.java:303)
at org.exolab.castor.util.DefaultObjectFactory.createInstance(
DefaultObjectFactory.java:107)
at org.exolab.castor.xml.UnmarshalHandler.createInstance(
UnmarshalHandler.java:2489)
at org.exolab.castor.xml.UnmarshalHandler.startElement
(UnmarshalHandler.java:1622)
at org.exolab.castor.xml.UnmarshalHandler.startElement
(UnmarshalHandler.java:1353)
at org.apache.xerces.parsers.AbstractSAXParser.startElement
(Unknown Source)
at org.apache.xerces.impl.dtd.XMLDTDValidator.startElement
(Unknown Source)
at
org.apache.xerces.impl.XMLDocumentFragmentScannerImpl.scanStartElement(
Unknown Source)
at org.apache.xerces.impl.XMLDocumentScannerImpl
$ContentDispatcher.
scanRootElementHook(Unknown Source)
at org.apache.xerces.impl.
XMLDocumentFragmentScannerImpl
$FragmentContentDispatcher.dispatch(
Unknown Source)
at
org.apache.xerces.impl.XMLDocumentFragmentScannerImpl.scanDocument(
Unknown Source)
at org.apache.xerces.parsers.XML11Configuration.parse
(Unknown Source)
at org.apache.xerces.parsers.XML11Configuration.parse
(Unknown Source)
at org.apache.xerces.parsers.XMLParser.parse(Unknown Source)
at org.apache.xerces.parsers.AbstractSAXParser.parse(Unknown
Source)
at org.exolab.castor.xml.Unmarshaller.unmarshal
(Unmarshaller.java:709)
at org.exolab.castor.xml.Unmarshaller.unmarshal
(Unmarshaller.java:610)
at org.exolab.castor.xml.Unmarshaller.unmarshal
(Unmarshaller.java:812)
at ibm.xml.castor.BookUnmarshaller.main
(BookUnmarshaller.java:14) |
さて、何が起きているのでしょう。Castor にまったく手を加えずそのままデータ・バインディング用に使うためには、いくつか妥協しなければならないことがあります。そうした最初の妥協点に突き当たっているのです。
Castor では引数を持たないコンストラクターが必要
Castor は主に、リフレクションと、Class.forName(ここにクラス名が入ります).newInstance() のような呼び出しによってアンマーシャリングを処理します。これによって Castor は、クラスに関する情報がほとんどなくても、クラスをインスタンス化することができます。しかし、Castor では、引数を持たないコンストラクターによってクラスをインスタンス化できることも必要なのです。
幸いなことに、Book クラスと Author クラスに簡単な変更を加えることで、このエラーを解決することができます。その変更とは、引数を持たないコンストラクターを両方 (public Book() { } と public Author() { }) に追加することのみです。変更したコードを再コンパイルして、再度実行します。
これが何を意味するのかを考えてみてください。引数を持たないコンストラクターによって、Castor だけではなく、任意のクラスまたはプログラムが、クラスの新しいインスタンスを作成できるのです。本や著者の場合、ISBN やタイトル (title)、著者 (author) の情報のない本や、苗字 (last name) あるいは名前 (first name) を持たない著者を作成できてしまいます。これは明らかに問題です。この問題には、Castor の持つ、より高度な機能を利用しない限り対処することができません (これについては次回の記事で説明します)。そのため、クラスの使い方には十分な注意が必要です。また、NullPointerException を発生させる可能性のあるすべてのデータ型に対して、実際の値を設定することも考える必要があります (ストリングは、空のストリングまたはデフォルト値に設定し、オブジェクトは初期化する必要がある、など)。
2 回目の実行: ヌルに関する他の問題
Book クラスと Author クラスに関して、引数を持たないコンストラクターの問題に対応したら、テスト用のアンマーシャラーを再実行します。すると以下のような出力が得られるはずです。
リスト 14. 引数を持たないコンストラクターを使ってアンマーシャリングした結果
[bmclaugh:~/Documents/developerworks/castor-2] java ibm.xml.castor.BookUnmarshaller
Book ISBN: null
Book Title: Power Play
Author: null null
Book ISBN: null
Book Title: The Book of the Dead
Author: null null
Author: null null |
どうやら、まだ処理が必要な問題があるようです (アンマーシャリングは簡単なものだったはずです。しかし、少し待ってください。それについてはこのすぐ後に説明します)。明らかにわかるように、いくつかのフィールドはヌルです。これらのフィールドを XML と比較すると、XML 文書の中ではこれらのフィールドが適切に表現されていることがわかります。では何が悪いのでしょう。
何が起きているかを知るために、本のタイトルが正しく設定されていることに注目してください。また、『The Book of the Dead』というタイトルの 2 番目の本をよく見てください。ちょっと見ると両方の本の著者が設定されていないように見えるかもしれませんが、そんなことはありません。実際、この 2 番目の本は、Authors オブジェクトに対する 2 つの参照を適切に持っています。つまりこの本は両方のオブジェクトを参照しています。しかしこれらのオブジェクト自体には苗字と名前の値が設定されていません。そのため、本のタイトルは設定され、また著者も設定されますが、ISBN はヌルであり、また著者の苗字と名前もヌルです。ここで、もう 1 つヒントがあります。ある著者の合計販売額 (total sales) を表示すると、これらの値はヌルではありません。
理解できたでしょうか。ヌル値のフィールドはどれも、セッター・メソッド (またはミューテーター・メソッド) を持っていないのです。setIsbn() や setFirstName() 等はありません。その理由は、Book クラスや Author クラスは、クラスの作成時にそうした情報が構造の中にあるように要求する設計になっているためです (ある本の ISBN を設定したら、その ISBN は後から変更できてはならないものです。要するに、新しい Book クラスを作成する場合、新しい ISBN を持つ新しい Book クラスを作成/インスタンス化するように要求するのが適切であるということです)。
ただし Castor がリフレクションを使うことを覚えているでしょうか。引数のないコンストラクターを持たない限りオブジェクトを作成できないのと同じように、setFieldName() メソッドを持たないフィールドの値を設定することはできません。そのため、このようなセッター・メソッドを Book クラスと Author クラスに追加する必要があります。クラスに追加する必要があるのは、下記のメソッドです (実装は非常に簡単なので皆さんにお任せします)。
-
setIsbn(String isbn) (Book クラス)
-
setFirstName(String firstName) (Author クラス)
-
setLastName(String lastName) (Author クラス)
これらのメソッドを追加し、再コンパイルすれば、準備は完了です。
3 回目の実行: アンマーシャリングの成功
再度アンマーシャラーを実行してみると、以下のような出力が得られるはずです。
リスト 15. 著者名と ISBN のセッター・メソッドがある場合のアンマーシャリングの結果
[bmclaugh:~/Documents/developerworks/castor-2] java ibm.xml.castor.BookUnmarshaller
Book ISBN: 9780312347482
Book Title: Power Play
Author: Joseph Finder
Book ISBN: 9780446618502
Book Title: The Book of the Dead
Author: Douglas Preston
Author: Lincoln Child |
これこそ、期待していたものです。クラスに対して、いくつかの変更を加える必要がありました。しかし私が先ほど、アンマーシャリングは非常に簡単だと言ったことを覚えているでしょうか。ここで説明したことと、その発言は一致しているでしょうか。アンマーシャリングは、クラスにさまざまな変更を加えたことで動作するようになったでしょうか。
実際、アンマーシャリングは動作しています。変更したのはクラスであり、Castor のアンマーシャリング・プロセスを変更したのではありません。Castor の使い方は非常に簡単ですが、そのためには Castor が想定するとおりにクラスが作成されている必要があります (つまり各クラスは引数のないコンストラクターを持ち、また各フィールドに対して get/set メソッドを持っている必要があります)。
Castor を利用するための作業に見合った価値
こうしてできあがったクラスは、元々のクラスと大幅に異なるものではありません。Book と Author の内容はちょっとみればわかるようなものであり、機能的にも大幅に異なるわけではありません (実際、変更作業を行う前後の機能にまったく違いがありません)。しかし設計には、少し疑問があります。本は (少なくとも私の世界では) ISBN を持っている必要があります。そのため、ISBN を持たない新しい本を作成できてしまうこと (あの、引数を持たないコンストラクターの件) は、私にとっては大いに気になります。さらに、誰かが本の ISBN を変更できてしまうことも気に入りません。これでは、このオブジェクトがどんなものかを正しく表現したことにはなりません。
一方、著者に対してなされた変更はそれほど問題になるものではありません。その理由は、このオブジェクトに対する識別子として、おそらく名前はあまり適切ではないからです。苗字は変更されることがあり、最近では名前までも変更されることがあります。また、2 人の著者が同じ名前だと、両者を区別するための方法は現在のところないかもしれません。しかし、たとえもっと適切な一意に決まる識別子 (社会保障番号や免許証番号、何らかの任意の識別子など) を追加したとしても、引数を持たないコンストラクターと、そのフィールドのためのセッターが相変わらず必要です。そのため、問題は残ります。
この問題は結局、以下に示す価値と管理の問題になります。
- Castor は (単純なデータ・バインディングを許可することで)、設計に関して妥協しなければならないほどの価値を提供しているでしょうか。
- Castor で動作するように追加したメソッドが誤った使い方をされないようにするためには、保護機構を組み込む必要がありますが、そのためのコード・ベースを十分に管理できるでしょうか。
皆さんの組織に関して、これらの質問に答えられるのは皆さんのみです。大部分の開発者にとっては、利害得失を考えると Castor を使った方が得策であり、そのため設計に関して妥協する必要があります。一方、設計上の制約、またアプリケーションの特定の要件による制約のため、こうした妥協ができない少数の人達にとっては、独自の単純な XML シリアライゼーションを作成した方がおそらく得策でしょう。そのどちらであったとしても、皆さんのすぐに使えるツールとして Castor を持っている必要があります。現在のプロジェクトには適切ではなくても、今後のプロジェクトにはきっと役立つはずです。
次回は
Castor に関するこれまでの説明の中で、大きく欠けているものがマッピング・ファイルです。ここまでは、Java コードから XML に、細部にわたって直接変換することで処理してきました。しかしこれは、以下のようないくつかのことを前提にしています。
- Java のクラスのすべてのフィールドを XML の中に永続させる
- クラスとフィールドの名前を XML の中に保持する
- XML 文書の中のデータに対応する、デシリアライズが必要なクラス・モデルがある
これらの前提はどれも、エンタープライズ・プログラミングではあまり安全な前提ではありません。もし、フィールド名とクラス名を XML 文書に保存することがセキュリティー・リスクと見なされ、アプリケーションの構造が見えすぎてしまうとしたら、どうすればよいのでしょう。もし誰か他の人から XML 文書を渡され、それを Java コードに変換しようとする際に、他のメソッド名やフィールド名を使いたい場合にはどうすればよいのでしょう。もし、クラスの属性のうち、いくつかのみを XML に保存したい時にはどうすればよいのでしょう。
これらの質問のどれに対しても、答えは同じです。Java コードから XML へのマッピングの場合も、そしてその逆のマッピングの場合も、Castor によってより多くのことを制御できるようになります。次回の記事では、マッピングについて詳しく説明します。それまでの間、少し時間を取ってマーシャリングとアンマーシャリングを試すだけではなく、Castor に本来の動作をさせるために必要な設計の変更とはどんな変更なのか、十分に考えてみてください。そしてそれを考えながら、次回の記事が公開されていないかどうか、頻繁にチェックしてください。ではまた次回、オンラインの記事で会いましょう。
ダウンロード | 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|
| Java source code from this article | x-xjavacastor2/castor2_source_code.zip | 2KB | HTTP |
|---|
| Java compiled code from this article | x-xjavacastor2/castor2_compiled_code.zip | 4KB | HTTP |
|---|
参考文献 学ぶために
製品や技術を入手するために
議論するために
著者について  | 
|  | Brett McLaughlin はノンフィクション作家としてベストセラーを書き、またいくつかの賞を受賞しています。彼が執筆したコンピューター・プログラミングやホーム・シアター、分析や設計に関する本は、これまで 10 万部以上売れています。彼は 10 年近く技術的な本を執筆し、編集し、そして製作してきており、ワープロの前に座っていることが好きですが、ギターを弾いたり、家で 2 人の息子を追いかけたり、彼の妻と Arrested Development (訳注: 米国で作成、放映されていたテレビ番組) の再放送を見て笑い転げることも好きです。彼の最新の本『Head First Object Oriented Analysis and Design』は 2007年の Jolt Technical Book award を受賞しています。彼の古典作『Java & XML』は、Java 言語での XML 技術の使い方に関する決定作の地位を保っています。 |
記事の評価
|