JAXB(Java Architecture for XML Binding)の使い方メモ。
JAXB とは、 XML と Java オブジェクトを相互変換するための API 仕様のこと。
Java SE6 からは標準ライブラリに組み込まれているので、特に jar を追加することなく使える。
Java 11 より、 JAXB は標準ライブラリから削除されました(Java EE には引き続き含まれています)。
Java SE 11 以降の環境で JAXB を使う
JAXB はもともと Java EE の一部だった。
一時は SE に入れられたが、 Java 11 で EE 系のクラスが SE から削除されたときに、一緒に SE から削除された。
SE 11 以降の環境で JAXB を使用したい場合は、ライブラリとして追加する必要がある。
2019 年現在の、JAXB の参照実装は たぶんこれ。
ここのドキュメント を見た感じ、次のアーティファクトを依存関係に入れれば利用できる。
dependencies {
implementation "javax.xml.bind:jaxb-api:2.3.1"
implementation "org.glassfish.jaxb:jaxb-runtime:2.3.1"
}
javax.xml.bind:jaxb-api
が、 JAXB の API を含むアーティファクトで、
org.glassfish.jaxb:jaxb-runtime
が JAXB の実装になる。
Jakarta EE での利用
Java EE から Jakarta EE に変わったことで、ライブラリやパッケージ名が変わった。
Jakarta EE を使う場合は次のアーティファクトを指定する。
dependencies {
implementation "jakarta.xml.bind:jakarta.xml.bind-api:4.0.0"
implementation "com.sun.xml.bind:jaxb-impl:4.0.3"
}
ランタイムには Eclipse から提供されている互換実装(Compatible Implementation)を指定している。
参考:https://eclipse-ee4j.github.io/jaxb-ri/
本記事のコード例は、すべて Java EE のパッケージ名 (javax.xml.bind...
) で記載している。
Jakarta EE 版を利用する場合は、適宜 jakarta.xml.bind...
パッケージに読み替えること。
オブジェクト → XML
public class Hoge {
private int id;
private String value;
// Getter, Setter は省略
}
import javax.xml.bind.JAXB;
public class JAXBSample {
public static void main(String[] args) {
Hoge hoge = new Hoge();
hoge.setId(10);
hoge.setValue("hoge");
JAXB.marshal(hoge, System.out);
}
}
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<hoge>
<id>10</id>
<value>hoge</value>
</hoge>
XML → オブジェクト
public static void main(String[] args) {
String xml = "<?xml version=\"1.0\"?>"
+ "<hoge>"
+ " <id>20</id>"
+ " <value>hogehoge</value>"
+ "</hoge>";
Hoge hoge = JAXB.unmarshal(new StringReader(xml), Hoge.class);
System.out.println("id=" + hoge.getId() + ", value=" + hoge.getValue());
}
id=20, value=hogehoge
アノテーションを使って調整する
フィールドをタグの属性と紐付ける
import javax.xml.bind.annotation.XmlAttribute;
public class Hoge {
private int id;
private String value;
@XmlAttribute
public int getId() {
return id;
}
// 以下 Getter, Setter 省略
}
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<hoge id="10">
<value>hoge</value>
</hoge>
XmlAttribute
アノテーションを、属性と紐付けたいフィールドの getterメソッド に付与する。
タグと属性の名前を任意の値に変更する
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
public class Hoge {
private int id;
private String value;
@XmlAttribute(name="hoge-id")
public int getId() {
return id;
}
@XmlElement(name="hoge-value")
public String getValue() {
return value;
}
// Setter メソッドは省略
}
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<hoge hoge-id="10">
<hoge-value>hoge</hoge-value>
</hoge>
タグ名の場合は XmlElement
アノテーションの name
に、
属性名の場合は XmlAttribute
アノテーションの name
にそれぞれ設定したい名前を指定する。
要素の順序の決定
順序を決定するためには、 XmlType
アノテーションの propOrder
を付与する。
@XmlType(propOrder={"one", "two", "three"})
public class Hoge {
private String one = "1";
private String three = "3";
private String two = "2";
// Getter, Setter は省略
}
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<hoge>
<one>1</one>
<two>2</two>
<three>3</three>
</hoge>
※ toenobu さんに追記していただきました。ありがとうございます。
ルートタグの名前を変更する
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name="hoge-tag")
public class Hoge {
private int id;
private String value;
// Getter, Setter 省略
}
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<hoge-tag>
<id>10</id>
<value>hoge</value>
</hoge-tag>
XML のルートタグとなるクラスに XmlRootElement
アノテーションを付与し、 name
に設定したいタグの名前を指定する。
Listの変換をいい感じにする
List<String>
のような List
のフィールドを XML に変換すると、デフォルトだと以下のようになる。
public class Hoge {
private List<String> listValue;
// Getter, Setter は省略
}
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<hoge>
<listValue>aaa</listValue>
<listValue>bbb</listValue>
<listValue>ccc</listValue>
</hoge>
List
の中身が同じ高さで出力されて格好悪い。
できれば以下のようにしたい。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<hoge>
<list>
<value>aaa</value>
<value>bbb</value>
<value>ccc</value>
</list>
</hoge>
上記のように変換するためには、次のように Java クラスでアノテーションを設定する。
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
public class Hoge {
private List<String> listValue;
@XmlElementWrapper(name="list")
@XmlElement(name="value")
public List<String> getListValue() {
return listValue;
}
// Setter 省略
}
XmlElementWrapper
アノテーションの name
に List
の各要素タグをラップするタグの名前を、
XmlElement
アノテーションの name
に要素タグの名前を設定する。
特定のフィールドを変換対象外にする
import javax.xml.bind.annotation.XmlTransient;
public class Hoge {
private int id;
private String value;
@XmlTransient
public String getValue() {
return value;
}
// Getter, Setter 省略
}
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<hoge>
<id>10</id>
</hoge>
XmlTransient
アノテーションを付与したフィールドは XML 変換対象外になる。
enum をマッピングする
package jaxb;
public class MyClass {
private MyEnum hoge = MyEnum.HOGE;
private MyEnum fuga = MyEnum.FUGA;
private MyEnum piyo = MyEnum.PIYO;
// getter, setter 省略
}
package jaxb;
import javax.xml.bind.annotation.XmlEnumValue;
public enum MyEnum {
HOGE,
@XmlEnumValue("fuga") FUGA,
@XmlEnumValue("Piyo") PIYO,
}
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<myClass>
<fuga>fuga</fuga>
<hoge>HOGE</hoge>
<piyo>Piyo</piyo>
</myClass>
enum は、デフォルトでは列挙子の名前を文字列にした値とマッピングされる(HOGE)。
任意の値とマッピングさせたい場合は、 enum の列挙子に対して @XmlEnumValue
を付与し、 value にマッピングしたい文字列を指定する(fuga, Piyo)。
1つのインターフェースフィールドに対して、複数の実装クラスをマッピングする
package jaxb;
public interface MyInterface {
void hello();
}
package jaxb;
import java.util.List;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlElements;
public class Hoge {
private List<MyInterface> list;
private MyInterface any;
@XmlElementWrapper(name="list")
@XmlElements({
@XmlElement(name="fuga", type=Fuga.class),
@XmlElement(name="piyo", type=Piyo.class)
})
public List<MyInterface> getList() {
return this.list;
}
@XmlElements({
@XmlElement(name="fuga", type=Fuga.class),
@XmlElement(name="piyo", type=Piyo.class)
})
public MyInterface getAny() {
return this.any;
}
// setter 省略
}
package jaxb;
public class Fuga implements MyInterface {
private int num;
@Override
public void hello() {
System.out.println("fuga : num = " + this.num);
}
// setter, getter 省略
}
package jaxb;
import javax.xml.bind.annotation.XmlAttribute;
public class Piyo implements MyInterface {
private String value;
@Override
public void hello() {
System.out.println("hoge : value = " + this.value);
}
@XmlAttribute
public String getValue() {
return this.value;
}
// setter 省略
}
MyInterface
インターフェースを実装した Fuga
クラスと Piyo
クラスがある。
Hoge
クラスでは MyInterface
型でフィールドを定義して、 @XmlElements({@XmlElement(...), ...})
という形でタグ名と実装クラスのマッピングを定義している。
上記クラスは、↓のような XML に変換できる。
<?xml version="1.0" encoding="UTF-8"?>
<hoge>
<fuga>
<num>123</num>
</fuga>
<list>
<fuga>
<num>543</num>
</fuga>
<piyo value="piyopiyo" />
</list>
</hoge>
↑の XML を読み込んで、 MyInterface#hello()
を実行すると、↓のようになる。
package jaxb;
import java.io.File;
import javax.xml.bind.JAXB;
public class JaxbMain {
public static void main(String[] args) {
Hoge hoge = JAXB.unmarshal(new File("test.xml"), Hoge.class);
hoge.getAny().hello();
System.out.println();
for (MyInterface any : hoge.getList()) {
any.hello();
}
}
}
fuga : num = 123
fuga : num = 543
hoge : value = piyopiyo
名前空間が宣言されている XML をマッピングする
次のように名前空間が宣言されている XML をマッピングする。
<?xml version="1.0" encoding="UTF-8"?>
<entity xmlns="hoge-namespace" xmlns:fuga="fuga-namespace">
<id>20</id>
<fuga:value>VALUE</fuga:value>
</entity>
フィールドごとに名前空間を設定する
package sample.jaxb;
import javax.xml.bind.annotation.XmlElement;
public class Entity {
private int id;
private String value;
@XmlElement(namespace="hoge-namespace")
public int getId() {
return this.id;
}
@XmlElement(namespace="fuga-namespace")
public String getValue() {
return this.value;
}
// setter 省略
}
@XmlElement
アノテーションの namespace
属性で名前空間を指定する。
※namespace
属性は、 @XmlAttribute
アノテーションにもある。
package-info.java
で共通の名前空間を設定する
package-info.java
に @XmlSchema
アノテーションを付けることで、パッケージ内に存在する全てのクラスに対して名前空間を一括で設定できる。
@XmlSchema(
namespace="hoge-namespace",
elementFormDefault=XmlNsForm.QUALIFIED
)
package sample.jaxb;
import javax.xml.bind.annotation.XmlSchema;
import javax.xml.bind.annotation.XmlNsForm;
package sample.jaxb;
import javax.xml.bind.annotation.XmlElement;
public class Entity {
private int id;
private String value;
@XmlElement(namespace="fuga-namespace")
public String getValue() {
return this.value;
}
// getter, setter 省略
}
hoge-namespace
は package-info.java
の @XmlSchema
アノテーションで指定しているので、 Entity.java
では名前空間の指定を省略できる。
ある名前空間に属するタグが複数存在する場合は、この方法を使うと設定が1カ所にまとめられて楽になる。
ただし、 package-info.java
で指定した設定が適用されるのは、そのパッケージ直下のクラスだけなので注意。
Java オブジェクトから XML に変換するときの QName のプレフィックスを指定する
名前空間が設定された状態で Java オブジェクトを XML に変換すると、以下のように QName (qualified name=修飾された名前)のプレフィックスに適当な値が使用される。
package sample.jaxb;
import javax.xml.bind.annotation.XmlElement;
public class Entity {
private int id;
private String value;
@XmlElement(namespace="hoge-namespace")
public int getId() {
return this.id;
}
@XmlElement(namespace="fuga-namespace")
public String getValue() {
return this.value;
}
// setter 省略
}
package sample.jaxb;
import javax.xml.bind.JAXB;
public class JaxbMain {
public static void main(String[] args) {
Entity entity = new Entity();
entity.setId(12);
entity.setValue("ENTITY");
JAXB.marshal(entity, System.out);
}
}
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<entity xmlns:ns2="fuga-namespace" xmlns:ns3="hoge-namespace">
<ns3:id>12</ns3:id>
<ns2:value>ENTITY</ns2:value>
</entity>
ns2
、 ns3
という適当なプレフィックスが設定されている。
プレフィックスを任意の値にしたい場合は、 package-info.java
の @XmlSchema
アノテーションで以下のように設定する。
@XmlSchema(
xmlns={
@XmlNs(prefix="hoge", namespaceURI="hoge-namespace"),
@XmlNs(prefix="fuga", namespaceURI="fuga-namespace")
}
)
package sample.jaxb;
import javax.xml.bind.annotation.XmlSchema;
import javax.xml.bind.annotation.XmlNs;
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<entity xmlns:fuga="fuga-namespace" xmlns:hoge="hoge-namespace">
<hoge:id>12</hoge:id>
<fuga:value>ENTITY</fuga:value>
</entity>
プレフィックスが hoge
と fuga
に変わっている。