今週は,今までとは逆にJavaのクラスからXMLのスキーマを生成することを考えてみます。とはいうものの,Javaのソースだけではちょっと難しいのです。
たとえば,次のJavaのクラスをXMLに変換することを考えてみましょう。
public class Name { String first; String last; .... }
今まで,スキーマからJavaのクラスを生成していた経験から,ルートタグがクラス名に相当することは分かります。では,フィールドは?
<name first="Yuichi" last="Sakuraba" />
このように属性として表すのでしょうか,もしくは次のようにタグとして表すのでしょうか。
<name> <first>Yuichi</first> <last>Sakuraba</last> </name>
答えは,どちらでもかまわないです。
図1 JavaとXMLが表せる範囲 |
---|
より正確に言うならば,Javaのソースだけでは,フィールドをどのようにXMLに対応づけさせるかを指定できない,ということです。
Javaはオブジェクトをベースとしたオブジェクトモデルによって表記をおこないます。それに対し,XMLはツリー構造をベースとした文章モデルによって表記を行ないます。この2つのモデルでは表せる範囲が異なるため,変換ができない部分が残ります。
このように表せる範囲が異なるために発生する不整合をインピーダンスミスマッチといいます(図1)。
インピーダンスミスマッチを解消させるためには,Javaのソースコード以外の手段を使用せざるをえません。
そこで登場するのがアノテーションです。
たとえば,フィールドがXMLドキュメントに変換したときに属性になる場合@XmlAttributeで修飾し,タグになる場合@XmlElementで修飾するようにしてみましょう。
public class Name { @XmlAttribute String first; @XmlAttribute String last; .... }
このように@XmlAttributeで修飾されていれば,XMLドキュメントは次のようになると分かります。
<name first="Yuichi" last="Sakuraba" />
図2 アノテーションで表す範囲 |
---|
つまり,Javaでは表現できない領域をアノテーションを用いることでカバーし,インピーダンスミスマッチを解消しているのです(図2)。
ここで,xjcで生成したJavaのクラスを思いだしてください。今まで無視していましたが,生成したクラスにはアノテーションが付加されています。このアノテーションによって,Javaとスキーマの相互変換が可能になるのです。
それでは,JAXBが使用するアノテーションを紹介していきましょう。
JAXBで使用するアノテーションはjavax.xml.bin.annotationパッケージで定義されており,全部で29種類あります。
以下,主なアノテーションを用途ごとに示します。
クラスを修飾するアノテーション
クラスに使用できるAnnoationは表1に示した4種類です。
アノテーション名 | 説明/デフォルト値 |
---|---|
@XmlType | クラスをスキーマの型に対応づける |
@XmlType(
name="##default",
propOrder={""},
namespace="##default",
factoryClass=DEFAULT.class,
factoryMethod=""
) | |
@XmlRootElement | クラスをスキーマの宣言に対応づける |
@XmlRootElement(
name="##default",
namespace="##default"
) | |
@XmlAccessorType | フィールド,プロパティの対応を決定する |
@XmlAccesorType(
namespace="http://www.w3.org/2001/XMLSchema",
type=DEFAULT.class
) | |
@XmlAccessorOrder | フィールド,プロパティの対応順序を決める |
なし |
@XmlType
@XmlTypeをクラスに付加すると,クラスをスキーマの型に対応づけます。
たとえば,次のようなクラスがあるとします。
@XmlType
public class Name {
}
このクラスに対応するXML Schemaを以下に示します(スキーマ定義は省略しています)。
<xs:complexType name="name"> <xs:sequence/> </xs:complexType>
このようにスキーマの型定義だけに変換されました。宣言部分も生成させる場合は,@XmlTypeと一緒に@XmlRootElementを使用します。
@XmlRootElement
@XmlType
public class Name {
}
変換した結果を次に示します。
<xs:element name="name" type="name"/>
<xs:complexType name="name">
<xs:sequence/>
</xs:complexType>
要素の宣言も生成することができました。
@XmlTypeには5つの要素がありますが,よく使われるのはその中の2つです。
- name
- propOrder
name要素は型の名前に対応づけられます。デフォルトではクラス名の先頭文字を小文字にしたものが型名になりますが,それ以外の型名にしたい場合に指定します。次の例ではname要素をnamaeと指定しています。
@XmlRootElement
@XmlType(name="namae")
public class Name {
}
XML Schemaは次のように型名がnamaeになっていることが分かります。
<xs:element name="name" type="namae"/> <xs:complexType name="namae"> <xs:sequence/> </xs:complexType>
name要素を空文字にすると,型を匿名型にします。この場合,必ず@XmlRootElementも一緒に使用します。
@XmlRootElement
@XmlType(name="")
public class Name {
}
生成したXML Schemaは次のようになります。
<xs:element name="name"> <xs:complexType> <xs:sequence/> </xs:complexType> </xs:element>
propOrder要素はプロパティの順序を決めます。また複合型のall,もしくはsequenceのどちらに対応づけるかを決定します。
ここでプロパティといっているのはJava Beansで定義されたプロパティのことです。つまり,外部からアクセスできるフィールド,もしくはセッター,ゲッターでアクセスできるメンバをプロパティとしています。
Nameクラスにはfirstとlastという2つのプロパティが存在し,複合型のsequenceで表します。この時,propOrderの記述順序が,sequenceの順序になります。
@XmlRootElement
@XmlType(propOrder={"last", "first"})
public class Name {
private String first;
private String last;
public void setFirst(String first) {
this.first = first;
}
public String getFirst() {
return first;
}
public void setLast(String last) {
this.last = last;
}
public String getLast() {
return last;
}
}
この例ではlastを先に記述しています。生成したXML Schemaはsequenceで表され,lastが先になっていることが分かります。
<xs:element name="name" type="name"/>
<xs:complexType name="name">
<xs:sequence>
<xs:element name="last" type="xs:string" minOccurs="0"/>
<xs:element name="first" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
複合型でallに対応づける場合,propOrderに空配列を指定します。
@XmlRootElement
@XmlType(propOrder={})
public class Name {
private String first;
private String last;
public void setFirst(String first) {
this.first = first;
}
public String getFirst() {
return first;
}
public void setLast(String last) {
this.last = last;
}
public String getLast() {
return last;
}
}
このクラスをXML Schemaに変換すると,次のようになります。
<xs:element name="name" type="name"/> <xs:complexType name="name"> <xs:all> <xs:element name="first" type="xs:string" minOccurs="0"/> <xs:element name="last" type="xs:string" minOccurs="0"/> </xs:all> </xs:complexType>
name型がcomplextTypeに対応づけられ,プロパティはallで指定されました。
なお,複合型ではなく単純型(simpleType)や単純コンテンツ(simpleContent)で表したい場合は,来週説明する@XmlValueを使用します。
@XmlRootElement
すでに@XmlTypeで紹介したように,@XmlRootElementはクラスをXMLの要素に対応づけます。
@XmlRootElementもname要素を持ち,要素名を指定することができます。デフォルトは@XmlTypeと同様にクラス名の先頭文字を小文字にしたものとなります。
@XmlRootElement(name="name2")
@XmlType
public class Name {
}
生成したXML Schemaでは,要素名がname2になりました。
<xs:element name="name2" type="name"/>
<xs:complexType name="name">
<xs:sequence/>
</xs:complexType>
@XmlAccessorType
@XmlAccessorTypeはフィールドもしくはプロパティのどちらをスキーマに対応づけるかを指定します。
例として,次のクラスを考えてみましょう。
@XmlRootElement
@XmlType
@XmlAccessorType(XmlAccessType.NONE)
public class Name {
private String first;
private String last;
void setFirst(String first) {
this.first = first;
}
String getFirst() {
return first;
}
public void setLast(String last) {
this.last = last;
}
public String getLast() {
return last;
}
}
このクラスは次のようなメンバを持っています。
- フィールド: first, last
- プロパティ: first, last
- publicメンバ: last
ここで,@XmlAccessorTypeの要素を変更して,生成されるXML Schemaを比較してみましょう。@XmlAccessorTypeの要素valueは,列挙型のXmlAccessTypeで表します。XmlAccessTypeは以下の4種類の値をとります。
- NONE
- FIELD
- PUBLIC_MEMBER
- PROPERTY
NONEはフィールドもプロパティもXMLに対応づけません。上記のソースはNONEで記述されています。これに対応するXML Schemaは次のようになります。
<xs:element name="name" type="name"/> <xs:complexType name="name"> <xs:sequence/> </xs:complexType>
子要素は追加されていないことが分かります。
次に,FIELDにしてしてみます。FIELDはフィールドをスキーマに対応づけます。
<xs:element name="name" type="name"/> <xs:complexType name="name"> <xs:sequence> <xs:element name="first" type="xs:string" minOccurs="0"/> <xs:element name="last" type="xs:string" minOccurs="0"/> </xs:sequence> </xs:complexType>
フィールドが子要素として追加されます。Nameクラスの2つのフィールドはprivateですが,正しく対応づけられます。
PUBLIC_MEMBERはフィールドとプロパティのうち,アクセス修飾子がpublicのものだけをスキーマに対応づけます。@XmlAccesorTypeのデフォルトもPUBLIC_MEMBERとなります。
上記のソースでは,lastだけがpublicになっているため,XML Schemaもlastだけが子要素となります。
<xs:element name="name" type="name"/> <xs:complexType name="name"> <xs:sequence> <xs:element name="last" type="xs:string" minOccurs="0"/> </xs:sequence> </xs:complexType>
最後のPROPERTYはプロパティのみスキーマに対応づけます。
<xs:element name="name" type="name"/> <xs:complexType name="name"> <xs:sequence> <xs:element name="first" type="xs:string" minOccurs="0"/> <xs:element name="last" type="xs:string" minOccurs="0"/> </xs:sequence> </xs:complexType>
firstはパッケージプライベートなプロパティですが,スキーマに対応づけられました。
なお,@XmlAccessorTypeはパッケージも修飾することが可能です。パッケージを修飾した場合,そのパッケージに含まれるクラスにもその値が適用されます。
@XmlAccessorOrder
@XmlAccessorOrderはフィールド,プロパティをスキーマに対応づける場合,その順序を規定します。@XmlAccessorOrderの要素valueは,XmlAccessOrder.ALPHABETICALとXmlAccessOrder.UNDEFINEDのいずれかになります。デフォルトはXmlAccessOrder.UNDEFINEDになります。
@XmlAccessorOrderもパッケージを修飾することが可能です。@XmlAccessorTypeと同様,パッケージを修飾した場合,そのパッケージに含まれるクラスにもその値が適用されます。
パッケージを修飾するアノテーション
パッケージを修飾できるアノテーションは表2に示した5種類です。
@XmlAccessorTypeと@XmlAccessorOrderはすでに説明しましたので,@XmlSchemaについて説明します。
パッケージを修飾する場合,アノテーションはpackage-info.javaに記述します。
ここでは,例としてnet.javainthebox.sampleパッケージとしましょう。アノテーションを記述していない状態では,net.javainthebox.sample.package-info.javaは次のようになります。
package net.javainthebox.sample;
つまり,パッケージ宣言だけが記述されたものとなります。
これだけだとスキーマが生成されないので,net.javainthebox.sample.Nameクラスを定義しておきます。
package net.javainthebox.sample; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlType; @XmlRootElement @XmlType(name = "") @XmlAccessorType(XmlAccessType.FIELD) public class Name { private String first; private String last; }
これで準備は整いました。
アノテーション名 | 説明/デフォルト値 |
---|---|
@XmlSchema | パッケージを名前空間に対応づける |
@XmlSchema( xmlns={}, namespace="", elementFormDefault=XmlNsForm.UNSET, attributeFormDefault=XmlNsForm.UNSET ) | |
@XmlAccessorType | フィールド,プロパティの対応を決定する |
@XmlAccesorType( namespace="http://www.w3.org/2001/XMLSchema", type=DEFAULT.class ) | |
@XmlAccessorOrder | フィールド,プロパティの対応順序を決める |
なし | |
@XmlSchemaType | XML Schemaのビルトイン型に対応づける |
@XmlSchemaType( namespace="http://www.w3.org/2001/XMLSchema", type = DEFAULT.class ) | |
@XmlSchemaTypes | 複数の@XmlSchemaTypeをまとめるコンテナアノテーション |
なし |
@XmlSchema
@XmlSchemaはパッケージをXMLの名前空間に対応づけます。
名前空間の指定は@XmlTypeなどでも可能ですが,通常は@XmlSchemaで指定する方が便利です。というのも,@XmlSchemaはパッケージに含まれるすべてのクラスに適用されるためです。
@XmlSchemaの要素は次の4つです。
- namespace
- xmlns
- elementFormDefault
- attributeFormDefault
namespaceで名前空間を指定します。たとえば,次のように名前空間を指定してみます。
@javax.xml.bind.annotation.XmlSchema( namespace = "http://www.javainthebox.net/sample" ) package net.javainthebox.sample;
package文の前にimport文を書くことができないので,ちょっと面倒ですがアノテーションはパッケージを含めて記述します。
赤字の部分が名前空間を指定している部分です。
これに対応するXML Schemaは次に示します。
<xs:schema version="1.0"
targetNamespace="http://www.javainthebox.net/sample"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="name">
<xs:complexType>
<xs:sequence>
<xs:element name="first" type="xs:string" minOccurs="0"/>
<xs:element name="last" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
schemaタグにtargetNamespaceアトリビュートが指定されます。
elementFormDefault要素とattributeFormDefault要素は,<schema>タグのelementFormDefault属性とattributeFormDefault属性に対応します。どちらも,とりうる値は列挙型のXmlNsFormです。
XmlNsFormは次の3つの値をとります。
- XmlNsForm.UNQUALIFIED
- XmlNsForm.QUALIFIED
- XmlNsForm.UNSET
UNQUALIFIEDはunqualified,QUALIFIEDはqualifiedに対応します。UNSETを指定した場合,elementFormDefault属性/attributeFormDefault属性を記述しません。
@javax.xml.bind.annotation.XmlSchema( namespace = "http://www.javainthebox.net/sample", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED ) package net.javainthebox.sample;
対応するXML Schemaは次のようになります。
<xs:schema version="1.0"
elementFormDefault="qualified"
targetNamespace="http://www.javainthebox.net/sample"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="name">
<xs:complexType>
<xs:sequence>
<xs:element name="first" type="xs:string" minOccurs="0"/>
<xs:element name="last" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
かなり長くなってしまったので,列挙型を修飾するアノテーションとフィールドもしくはプロパティを修飾するアノテーションは来週にしましょう。また,実際にJavaのクラスからスキーマを生成する方法も紹介します。