基本
次のサイトで使い方の基本はひととおりわかる
考え方
(たぶん)
XML Schema <---> Java クラス ↓ ↓ ↓ ↓ XML文書 <---> オブジェクトツリー
XML Schema は、
- elementの内容(属性含む)を型として定義
- 型は名前付きの場合も、名前なしの場合もある
- elementの出現順や名前を定義し、elementに型を割り当て
- 名前付きの型は、複数のelementに割り当てることができる。
elementの名前は型と独立であるに注意。
このため、XMLにおいてelementの名前が直接的にその内容(型)を規定している、elementは型の名前である、という発想で考えている場合には、JAXBをどう使うのか(Java側をどう記述したらよいのか)わからなくなってくる。
JAXBは、
- XML Schemaの型をJavaクラスにマッピング
- elementや属性を、Javaのプロパティ(またはインスタンス変数)にマッピング
- element - 型 の対応関係は、プロパティ-そのクラス の対応関係にマッピング
例えば、以下のクラス定義を考えると、
public class Feed {
@XmlElement
private String title;
@XmlElement
private Person author;
@XmlElement
private Person editor;
}
対応するXMLは、
<????>
<title>テキスト</title>
<author>
Personクラスで定義される内容
</author>
<editor>
Personクラスで定義される内容
</editor>
</????>
- Feedクラスにより、内容の構成は定義される。Feedに対応するelementの名前は決まらない。elementは、Feedを使う側によって決まる。
- 同様に、author elementとeditor elementの内容はPersonクラスによって定義されるが、elementの名前は、Personを利用している側、つまりFeedクラス側によって(この場合はインスタンス変数名authro, editorによって)決まる。
@XmlRootElement
@XmlRootElementは、クラスで定義した(XMLの)型にelementの名前を対応付ける。
@XmlRootElement
public class Feed {
...
}
この例では、Feed型にfeedというelementが対応付けられる。@XmlRootElementにname= を指定すれば、クラス名とは別の名前のelementを対応付けられる。
この情報は、型からelement名を求めなければならない状況で利用されるようだ。たとえば、FeedクラスをXMLに変換するときは、@XmlRootElementがなければ、XMLのルートelementの名前が決まらない。
フィールドとプロパティのどちらをelementやattributeにマッピングするか
@XmlElementや@XmlAttributeはフィールド(インスタンス変数)とプロパティのどちらにつけてもよい。
プロパティの場合はgetterにつける(setterでもよいのか?)
デフォルトのルールの選択
これらのアノテーションをつけなくても、デフォルトのルールを適用して、自動でマッピングすることもできる。
デフォルトのルールは@XmlAccessTypeで、クラスごと、または、パッケージごとに選択する。
パッケージに対して指定する場合は、パッケージのディレクトリにpackage-info.javaを作成し、その中で指定する。
@XmlAccessorType(XmlAccessType.NONE) // Beanのプロパティは、明示的に指定された場合のみXMLにマッピングする
package my.sample;
JAXBContext.newInstance(Class...)
この引数にどのクラスを渡せばよいのか。
MOXyのマニュアルから理解したところでは
- XMLの型と対応付けられるすべての型をJAXBに認識させる必要がある
- newInstanceにクラスを渡すと、次のものはJAXBがたどって自動的に認識する
- そのスーパークラス
- そのクラスから参照しているクラス
このため、基本的にはXMLのルートelementに対応するクラスを渡せばよさそう。
複数のクラスを渡す必要があるのは、たとえば、
- ルートelementが何種類かある場合
- ルートから再帰的に参照されているクラスの、子孫クラスもJAXBに認識させる必要がある場合。たとえば、次のクラスが該当する。
- @XmlElementRefを使ってクラスを切り替える場合の、切り替え先のクラス。
- MOXy独自の機能で @XmlDiscriminatorNode / @XmlDiscriminatorValue を使ってクラスを切り替える場合の、切り替え先のクラス。
サブクラスを切り替えて使いたい場合
elementの名前で切り替える
@XmlElementsを使う
@XmlElementsの中で@XmlElementを使って、element名とJavaクラスの対応を明示的に指定する。
public class Feed {
@XmlElements(
value={
@XmlElement(name="tw", type=Twitter.class),
@XmlElement(name="fb", type=Facebook.class)
}
)
private Sns sns;
}
public class Twitter extends Sns {
@XmlElement
private String username;
}
public class Facebook extends Sns {
@XmlElement
private String realname;
}
この定義により、以下のどちらのXMLも解析できる。Feed.snsには、elementがtwかfbかで代入されるクラスが異なる。
<feed>
<tw>
<username>@twittername</username>
</tw>
</feed>
<feed>
<fb>
<realname>foo bar</realname>
</fb>
</feed>
@XmlElementRefを使う
@XmlElementsの場合は、element名 - 型 の対応関係を、その型を参照する側で定義した。@XmlElementRefの場合は、参照される側で定義する。
その点を除けば、@XmlElementsと同じ (@XmlElementsとの機能的な差異はあるのだろうか??)
public class Feed {
@XmlElementRef
private Contact contact;
}
@XmlRootElement
public class Phone extends Contact {
@XmlElement
private String number;
}
@XmlRootElement
public class Mail extends Contact {
@XmlElement
private String address;
}
Phone, Mailクラスに@XmlRootElementをつけることで、element名を対応付けている。@XmlRootElementにname=を指定すれば、クラス名(phone, mail)とは別のelement名を対応付けることができる(はず)。
この方法の場合は、JAXBContext.newInstanceを実行する際、引数に Mail.class, Phone.classも渡す必要がある。
attributeの値で切り替える
以下が詳しい。
JAXBニッチ技特集 XMLを属性に基づいて特定のサブクラスに非整列化(unmarshal)する
標準のJAXB 2の範囲では実現できず、EclipseLink MOXy の独自機能を利用する。
例として、mediaというelementのtype attributeの値によって、クラスを切り替えたいとする。
XMLの例
<feed>
<media type="book">
<publisher>foo出版</publisher>
</media>
</feed>
Java側の記述
public class Feed {
@XmlElement
private Media media;
}
import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorNode;
@XmlDiscriminatorNode("@type")
class Media {
}
import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue;
@XmlDiscriminatorValue("book")
public class Book extends Media {
@XmlElement
private String publisher;
}
@XmlDiscriminatorValue("web")
public class Web extends Media {
@XmlElement
private String url;
}
- @XmlDiscriminatorNode で、どの値を参照するか、を指定する。XPathで指定する。@typeは、XPathによる表記で、typeというattributeを指す。
- @XmlDiscriminatorValue で、値を指定する。参照した値がこの値に一致した場合に、このクラスが利用される。
先の例のXMLを解析すると、Feedクラスのmediaには、Bookのオブジェクトが代入される。
なお、JAXBContext.newInstanceの実行時は、引数に Book.classとWeb.classも渡す必要がある。
名前空間も利用する場合
@XmlDiscriminatorNode で指定するXPathに、名前空間付きのアトリビュートを指定したい場合は、
名前空間のprefixを@XmlSchemaであらかじめ定義しておき、XPath中ではそのprefixを使って名前空間を指定する。
例えば、XML側が <media foo:type="book"> となっており、my.sample.Mediaクラスに @XmlDiscriminatorNode("@foo:type") をつけたとする。また、fooの名前空間は、"http://foo.bar/baz" とする。この場合、my.sampleパッケージのpackage-info.javaは、
@XmlSchema(
xmlns = {
@XmlNs(prefix = "foo", namespaceURI = "http://foo.bar/baz")
}
)
package my.sample;
名前空間
以下のサイトを参照。
XmlAdapter
JAXBでXMLとJavaを直接マッピングできない場合は、マッピング可能なJavaを経由してマッピングさせる。
XML <--> JAXBでマッピング可能なJavaクラス <-- XmlAdapter --> 希望のJavaクラス
このため、希望のJavaクラスのほかに、「JAXBでマッピング可能なJavaクラス」も作成が必要となる場合がある。「JAXBでマッピング可能なJavaクラス」がStringなどの場合は、作成は不要。
以下は、XML側の "on" , "off" という文字列を、Java側のbooleanとマッピングさせる例である。ここではunmarshalの処理だけ用意している。
public class OnOffAdapter extends XmlAdapter<String, Boolean>{
@Override
public Boolean unmarshal(String v) throws Exception {
return "on".equalsIgnoreCase(v); // "on"以外はすべて"off"とみなす。
}
@Override
public String marshal(Boolean v) throws Exception {
throw new UnsupportedOperationException("Not supported yet.");
}
}
public class Feed {
@XmlElement
@XmlJavaTypeAdapter(OnOffAdapter.class)
private boolean available;
}
JAXBは element "available" の型として、String(に対応するXMLの)型を割り当てて解析を行うと思われる。JAXBは変数availableに割り当てられたXmlAdapterのValueTypeを取得し (XmlAdapter<String, Boolean>のStringがValueType) それを element "available"の型として扱うだろう。
XML側が"on"/"off"のような単純な文字列ではなく、子要素を持つような複合型の場合は、この例のStringの部分がFooBeanのようなBeanになるだろう。その場合、FooBeanは、JAXBの範疇でXMLとマッピング可能な型でなければならない。FooBeanから、希望する型(この例でのboolean)への変換方法は、XmlAdapterによって自由に定義できる。
繰り返し
XML側は典型的には2パターンありそうだ。
<feed>
<entry>...</entry>
<entry>...</entry>
<entry>...</entry>
</feed>
<feed>
<list>
<entry>...</entry>
<entry>...</entry>
<entry>...</entry>
</list>
</feed>
上記のどちらの場合も、Java側は以下の構造にマッピングできる。
class Feed {
private List<Entry> entries;
}
前者のXMLの場合、Java側で使うアノテーションは次のようになる。
class Feed {
@XmlElement(name = "entry")
private List<Entry> entries;
}
element名と変数名が異なるのでname=を指定したが、同じなら省略可能。
後者のXMLの場合、
class Feed {
@XmlElementWrapper(name = "list")
@XmlElement(name = "entry")
private List<Entry> entries;
}
その他
自分の子孫のelementの内容やattributeを自分のプロパティ(や変数)にマッピング
MOXyの独自機能で@XmlPathを利用する。
@XmlPath("nameKana/@disclose") // element "nameKana"の "disclose" attribute
private String nameKanaDisclosed;
プロパティや変数として定義していないその他のelementやattributeを拾う
@XmlAnyElementや@XmlAnyAttribute を使うらしい。
コンテントで、テキストと要素が混在するような要素を扱う
XML Schema では、complexTypeにmixed="true"を指定することで表現される。
JAXBでは、@XmlMixedを使うらしい。Java側はListとなり、Listの要素は、テキストはString、要素はJAXBElementまたはElementとなる。
6.2.7.8 Annotations for Mixed Content: XmlElementRef, XmlMixed
simple typeの要素であっても、要素を1つのクラスに対応付けて、要素のコンテントをプロパティにセットしたい
@XmlValueを使う。
要素のクラスを用意し、フィールドを1つだけ持たせ、そこに@XmlValueを付ける。
@XmlRootElement(name = "p")
public class Parameter {
@XmlValue
String text;
}
同じXML Schemaに対し、処理目的別に、異なるJavaクラスを用意する
これ自体は可能で、unmarshalするときに使うJavaクラスの使い分けをすることができる。
使い分けは、JAXBContext.newInstanceに指定するクラスを切り替えることで実現できる。
以下のケースはうまくいかない。
- ある処理用(JAXBでの解析)にクラスAを作成。これは、XMLの型aに対応するものとする。
- 別の処理用(JAXBでの解析)にクラスAを継承したBを作成。これも、XMLの型aに対応する。
JAXBContext.newInstance(B.class)をにより、Bを利用しようとすると、Bの親クラスであるAもJAXBに認識されるが、AもBも同じXML型に対応するのでエラーとなる。
また、このエラーが回避できたとしても(クラスAまたはBに@XmlType(name="...")を指定して、XML側での型名がAとBで異なるようにすれば回避できる)、次の課題が生じる。
Aの変数 X foo; により、element "foo"の型がXであると認識される。Xは「ある処理」用のクラスであり、「別の処理」のときに、Xとは異なるクラスを割り当てたくても、そうすることはできない。クラスBにY foo;を定義し、Y fooに@XmlElementなどをつけて親クラスの定義を上書きしようとしても、無視された。(MOXyで確認)
enum
EclipseLink MOXy 利用時の留意点
MOXyを有効にするには、jaxb.propertiesというファイルを作り、以下の内容とする。
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
jaxb.propertiesは、クラスsome.package.AでMOXy固有機能を使っているなら、some.packageのディレクトリに置く。
maven利用時は置く場所に注意。src/main/java/<パッケージ名>ではなく、src/main/resources/<パッケージ> に置く。たとえば、src/main/resources/some/package に置く。
MOXyが利用されているか確認するには、
JAXBContext.newInstance(Class...)実行の結果取得されたJAXBContextの実装クラスが org.eclipse.persistence.jaxb.JAXBContext であることを確認する。
利用されていない場合には、JAXBが、some.package.Aを処理対象として認識していない可能性がある(このページの上のほうに記載のとおり)。newInstanceの引数にAを加えるのがより確実。
JAXB.unmarshalでは、うまく認識されない場合があった。想像でしかないが、JAXBContext.newInstanceでルートelementのクラス以外も指定しなければならない状況では、JAXB.unmarshalは動作しないのではないか。