JAXB その1
4月にはJava SE 6におけるXMLに関する新機能ということで,StAXを紹介しました。今月もXMLに関する新機能であるJava Architecture for XML Binding,通称JAXBを紹介します。
JAXBはJava SE 6より一足早く,Java EE 5で採用されていたので,ご存じの方も多いはずです。
JAXBを説明する前に,XMLを用いてデータ交換を行なう場合について考えてみましょう。
通常,JavaのアプリケーションでXMLドキュメントを扱う場合,XMLドキュメントをパースし,パースした内容をアプリケーションで扱いやすいオブジェクトに変換します。
たとえば,次のような名前を表すXMLがあったとします。
<name first="Yuichi" last="Sakuraba" />
これをアプリケーションでは次のように表すのではないでしょうか。
public class Name { private String first; private String last; <<以下,省略>> } or String first = ...; String last = ...;
最初の例は名前を表すクラスを作成して使用します。後の例は単に変数に名と姓を代入するだけのものです。
もちろん,これは例であって,それ以外の表し方があるかもしれません。
重要なことはXMLドキュメントをアプリケーションで扱うには,XMLをパースして,アプリケーションで扱えるように何らかの形式に変換する必要があるということです。
とはいうものの,アプリケーションごとにXMLドキュメントから,何らかの形式に変換することは,なかなか大変です。
そこで,登場するのがJAXBです。
JAXBはXMLドキュメントを読み込み,対応するJavaオブジェクトを生成します。また,JavaオブジェクトからXMLドキュメントを作成することも可能です。
ただし,オブジェクトを生成するには,クラスの定義が必要です。Javaのオブジェクトに対するクラスの関係は,XMLドキュメントに対するスキーマと同じになります。つまり,XMLドキュメントの中でどのようなタグを使用しているか,属性は何かなどを定義しているのがスキーマです。
スキーマにはXML Schemaが多く使われますが,その他にもDTDやRelax NGなどがあります。
JAXBは,このスキーマからJavaのクラスを生成します。また,Javaのクラスからスキーマを生成することも可能です。
これがJAXBの名前になっているバインディングです。
XMLからJavaオブジェクトを生成するのはアンマーシャリング(Unmarshalling),JavaオブジェクトからXMLドキュメントを生成するのはマーシャリング(Marshalling)と呼びます。
この関係をまとめたのが図1です。
|
図1 JAXBバインディングプロセス |
---|
スキーマからJavaクラスへのバインド
それでは,さっそくスキーマからJavaのクラスを生成してみましょう。ここでは,スキーマとしてXLM Schemaを使用します。本記事では特にXML Schemaの解説はしませんが,必要であればW3CのXML Schema Part 0: Primerなどを参照してください。
スキーマからJavaクラスを生成するためには,スキーマコンパイラxjcを使用します。
例として,次に示す簡単なXML Schemaで表したスキーマを用いてみます。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="person"> <xs:complexType> <xs:attribute name="name" type="xs:string"/> <xs:attribute name="age" type="xs:int"/> </xs:complexType> </xs:element> </xs:schema>
このXML Schemaは,personタグを定義しています。personタグにはnameとageという属性があり,それぞれ型が文字列と整数だということを表しています。
XML Schemaで定義されている型についてはXMLSchema Part2: Datatypesを参照してください。
このスキーマを,xjcを用いてJavaのクラスに変換してみましょう。スキーマのファイル名はperson.xsdで,c:\jaxbに存在するとします。ここでは,Windows Vistaを使用しました。
C:\jaxb>xjc person.xsd parsing a schema... compiling a schema... generated\ObjectFactory.java generated\Person.java
すると,直下にgeneratedディレクトリが作成され,ObjectFactory.javaとPerson.javaの二つのファイルが作成されました。
まず,ObjectFactory.javaファイルです。名前からして,ファクトリのクラスだということは明白です。
生成されたクラスにはコメントやアノテーションが挿入されていますが,ここでは省略します。アノテーションについては来週以降,別途説明します。
package generated; public class ObjectFactory { public ObjectFactory() { } public Person createPerson() { return new Person(); } }
ObjectFactoryクラスでは,Personオブジェクトを生成するcreatePersonメソッドが定義されています。
では,そのPersonクラスを見てみましょう。ここでも,コメントとアノテーションを省略してあります。
package generated; public class Person { protected String name; protected Integer age; public String getName() { return name; } public void setName(String value) { this.name = value; } public Integer getAge() { return age; } public void setAge(Integer value) { this.age = value; } }
作成されたPersonクラスは,nameとageの二つのフィールドを持ちます。nameの型はStringクラス,ageの型はIntegerクラスになっています。
それ以外の部分は,nameとageに対するゲッター,セッターのみです。いわゆる,Beanと呼ばれるクラスですね。
このPersonクラスを見ると,スキーマで定義された属性がフィールドに対応することがお分かりのはずです。
ちょっと腑に落ちないのがgeneratedというパッケージです。パッケージを指定するには,xjcの-pオプションを使用するか,スキーマで名前空間を指定します。
ここでは,スキーマに記述してみることにします。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0"
targetNamespace="http://xml.javainthebox.net"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:complexType name="person">
<xs:attribute name="name" type="xs:string" />
<xs:attribute name="age" type="xs:int" />
</xs:complexType>
</xs:schema>
赤字の部分が名前空間を表しています。パッケージ名はドメインの逆順ですが,targetNamespaceはURLで指定します。
さて,これでもう一度クラス生成をしてみましょう。
C:\jaxb>xjc person.xsd parsing a schema... compiling a schema... net\javainthebox\xml\ObjectFactory.java net\javainthebox\xml\Person.java net\javainthebox\xml\package-info.java
今度はパッケージを表すnet\javainthebox\xmlディレクトリが作成されました。また,前回は生成されたクラスが二つだけだったのに対し,今回はpackage-info.javaを加えた三つになっています。
package-info.javaは,単にパッケージ宣言がされているだけのクラスです。
では,Personクラスを見てみましょう。
package net.javainthebox.xml; public class Person { protected String name; protected Integer age; public String getName() { return name; } public void setName(String value) { this.name = value; } public Integer getAge() { return age; } public void setAge(Integer value) { this.age = value; } }
正しくパッケージ文が記述されました。
次にスキーマとJavaの型に注目してみます。
ここで使用したスキーマではname属性はtype="xs:string"で定義されていました。文字列なので,JavaのStringクラスに対応づけられています。
一方のage属性はtype="xs:int"で定義されています。整数(正確には32bitの整数)ですから,Javaのintに対応づけられるところですが,Integerクラスに対応づけられています。
このようにスキーマとJavaのクラスをバインディングするには,スキーマの型とJavaの型を対応づける必要があります。JAXBでは,表1のように型の対応を決めています。
XML Schema Type | Java Data Type |
---|---|
xsd:string | java.lang.String |
xsd:integer | java.math.BigInteger |
xsd:int | int |
xsd.long | long |
xsd:short | short |
xsd:decimal | java.math.BigDecimal |
xsd:float | float |
xsd:double | double |
xsd:boolean | boolean |
xsd:byte | byte |
xsd:QName | javax.xml.namespace.QName |
xsd:dateTime | javax.xml.datatype.XMLGregorianCalendar |
xsd:base64Binary | byte[] |
xsd:hexBinary | byte[] |
xsd:unsignedInt | long |
xsd:unsignedShort | int |
xsd:unsignedByte | short |
xsd:time | javax.xml.datatype.XMLGregorianCalendar |
xsd:date | javax.xml.datatype.XMLGregorianCalendar |
xsd:g | javax.xml.datatype.XMLGregorianCalendar |
xsd:anySimpleType | java.lang.Object |
xsd:anySimpleType | java.lang.String |
xsd:duration | javax.xml.datatype.Duration |
xsd:NOTATION | javax.xml.namespace.QName |
では,次のようなスキーマはどのようにJavaに変換されるでしょうか。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0"
targetNamespace="http://xml.javainthebox.net"
xmlns:tns="http://xml.javainthebox.net"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="person">
<xs:complexType>
<xs:attribute name="name" type="xs:string"/>
<xs:attribute name="age" type="xs:int"/>
<xs:attribute name="sex" type="tns:sex"/>
</xs:complexType>
</xs:element>
<xs:simpleType name="sex">
<xs:restriction base="xs:string">
<xs:enumeration value="MALE"/>
<xs:enumeration value="FEMALE"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>
このスキーマはpersonタグに性別を表すsex属性を付け加えています。sex属性の定義が赤字の部分です。sex属性はMALEもしくはFEMALEのいずれかをとる文字列です。
このスキーマからJavaのクラスを生成してみましょう。
C:\jaxb>xjc person2.xsd parsing a schema... compiling a schema... net\javainthebox\xml\ObjectFactory.java net\javainthebox\xml\Person.java net\javainthebox\xml\Sex.java net\javainthebox\xml\package-info.java
今までとは異なり,Sex.javaファイルが生成されました。Sex.javaファイルを次に示します。
package net.javainthebox.xml; public enum Sex { MALE, FEMALE; public String value() { return name(); } public static Sex fromValue(String v) { return valueOf(v); } }
驚いたことに,値が限定されている性別はenumで定義されました。確かにenumを使用すると,値が限定されることが一目瞭然です。
Personクラスは次のように変更されました。
package net.javainthebox.xml; public class Person { protected String name; protected Integer age; protected Sex sex; public String getName() { return name; } public void setName(String value) { this.name = value; } public Integer getAge() { return age; } public void setAge(Integer value) { this.age = value; } public Sex getSex() { return sex; } public void setSex(Sex value) { this.sex = value; } }
フィールドにsexが追加され,sexに対するゲッター,セッターも追加されました。
最後にもう一つ試してみましょう。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0"
targetNamespace="http://xml.javainthebox.net"
xmlns:tns="http://xml.javainthebox.net"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="persons">
<xs:complexType>
<xs:sequence>
<xs:element ref="tns:person"
minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="person">
<xs:complexType>
<xs:attribute name="name" type="xs:string"/>
<xs:attribute name="age" type="xs:int"/>
<xs:attribute name="sex" type="tns:sex"/>
</xs:complexType>
</xs:element>
<xs:simpleType name="sex">
<xs:restriction base="xs:string">
<xs:enumeration value="MALE"/>
<xs:enumeration value="FEMALE"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>
赤字の部分が追加した部分です。
これはpersonタグを複数要素として持つpersonsタグを定義しています。つまり,次に示すようなXMLドキュメントを定義しています。
<persons> <person name="Neil Young" age="62" sex="MALE" /> <person name="Bob Dylan" age="67" sex="MALE" /> <person name="Joni Mitchell" age="65" sex="FEMALE" /> </persons>
それでは,Javaのクラスを生成します。
C:\jaxb>xjc persons.xsd parsing a schema... compiling a schema... net\javainthebox\xml\ObjectFactory.java net\javainthebox\xml\Person.java net\javainthebox\xml\Persons.java net\javainthebox\xml\Sex.java net\javainthebox\xml\package-info.java
予想通り,Personsクラスが追加されました。
package net.javainthebox.xml; public class Persons { protected List<Person> person; public List<Person> getPerson() { if (person == null) { person = new ArrayList<Person>(); } return this.person; } }
Personクラスを要素として持つListオブジェクトをフィールドで持つようになっています。
このように,JAXBではスキーマがあればJavaのクラスを簡単に生成できます。
XMLドキュメントのアンマーシャリング
スキーマからJavaのクラスが生成できたので,XMLドキュメントを読み込んでみましょう。
XMLドキュメントをJavaのオブジェクトに変換するのはjavax.xml.bind.Unmarshallerインタフェースです。そして,Unmashallerオブジェクトを生成するにはjavax.xml.bind.JAXBContextクラスを使用します。
サンプルのソース | UnmarshallerSample.java |
---|
このサンプルはpersons.xsdで定義されたXMLドキュメントを読み込み,Personsオブジェクトを生成します。
public class UnmarshallerSample { public UnmarshallerSample() { try { // 1. JAXBContextオブジェクトの生成 // 引数はパッケージもしくはクラス JAXBContext context = JAXBContext.newInstance("net.javainthebox.xml"); // 2. Unmarsallerオブジェクトの取得 Unmarshaller unmarshaller = context.createUnmarshaller(); File file = new File("artists.xml"); // 3. アンマーシャリング // 戻り値はルートタグに相当するクラス Object obj = unmarshaller.unmarshal(file); Persons artists = (Persons)obj; // 4. 変換されたオブジェクトにアクセス java.util.List<Person> p = artists.getPerson(); for (Person person: artists.getPerson()) { System.out.printf("%s\tAge: %2d Sex: %s%n", person.getName(), person.getAge(), person.getSex()); } } catch (JAXBException ex) { // 例外処理 } } public static void main(String[] args) { new UnmarshallerSample(); } }
はじめに行なうのが,JAXBContextオブジェクトの生成です。これには,JAXBContextクラスのstaticメソッドであるnewInstanceを使用します。
newInstanceメソッドの引数は,文字列か,Classクラスです。
文字列の場合,xjcで生成されたObjectFactoryクラスのパッケージを指定します。ObjectFactoryクラスが複数のパッケージにある場合は,パッケージを:(コロン)で区切って記述します。
Classクラスを引数にとる場合,xjcで生成されたXMLドキュメントに対応するクラスを記述します。一般的には,ルートタグに相当するクラスを引数にとるだけで十分です。
次に,2で行なっているようにcreateUnmarshallerメソッドをコールして,Unmarshallerオブジェクトを取得します。
Unmarshallerオブジェクトを取得できたら,アンマーシャルする対象のXMLドキュメントを準備します。ここでは,ファイルから読み込ませるので,Fileオブジェクトを生成しておきます。
アンマーシャルは3に示したように,Fileオブジェクトを引数にして,unmarshalメソッドをコールします。
unmarshalメソッドの引数には次のような手段を選べます。
- ファイル
- ストリーム/リーダー
- URL
- XSLTのソース
- DOMのノード
- StAXのXMLStreamReader/XMLEventReader
unmarshalメソッドの引数がファイルやストリームなどの場合,戻り値はルートタグに相当するオブジェクトになります。しかし,型はObjectクラスなので,キャストが必要です。
DOMのノードやStAXのXMLStreamReaderなどの場合,パースの途中からアンマーシャルすることがあるため,直接タグに相当するオブジェクトに変換できません。その代わり,javax.xml.bind.JAXBElementクラスが戻り値の型になります。JAXBElementクラスは,XML要素をラップします。
ここで示したサンプルはファイルを扱っているので,ルートタグに対応するPersonsクラスが戻り値の型になります。
後は,PersonsクラスやPersonクラスに定義されたゲッターを使用して,XMLに記述された内容にアクセスします。
UnmarshallerSampleクラスはPersonsクラス,Personクラスへの参照を持つので,UnmershallerSampleクラスをコンパイルするとこれらのクラスも一緒にコンパイルします。しかし,ObujectFactoryクラスには直接参照がないので,コンパイルしません。別途,コンパイルするようにしてください。
では,実際に実行してみましょう。
XMLドキュメントは次に示すものを使用しました。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <persons> <person name="Neil Young" age="62" sex="MALE" /> <person name="Bob Dylan" age="67" sex="MALE" /> <person name="Joni Mitchell" age="65" sex="FEMALE" /> </persons>
このファイルを読み込んで実行した結果を次に示します。
C:\jaxb>java UnmashallerSample Neil Young Age: 62 Sex: MALE Bob Dylan Age: 67 Sex: MALE Joni Mitchell Age: 65 Sex: FEMALE
正しく読み込めたようです。
パーサーを使ってXMLドキュメントをパースするのに比べると,格段に簡単です。スキーマさえあれば,クラス生成ができ,読み込む処理も10行もありません。ほんとに便利ですね。
今週は長くなってしまったので,マーシャリングは来週解説することにします。お楽しみに。