質問
XMLの読み取りと書き込みにJAXBを使用しています。私が欲しいのは、マーシャリングにベースJAXBクラスを使用し、アンマーシャリングに継承されたJAXBクラスを使用することです。これは、送信側JavaアプリケーションがXMLを別の受信側Javaアプリケーションに送信できるようにするためです。送信者と受信者は共通のJAXBライブラリを共有します。レシーバーは、汎用JAXBクラスを拡張するレシーバー固有のJAXBクラスにXMLを非整列化します。
例:
これは、送信者が使用する一般的なJAXBクラスです。
@XmlRootElement(name="person")
public class Person {
public String name;
public int age;
}
これは、XMLを非整列化するときに使用されるレシーバ固有のJAXBクラスです。レシーバークラスには、レシーバーアプリケーションに固有のロジックがあります。
@XmlRootElement(name="person")
public class ReceiverPerson extends Person {
public doReceiverSpecificStuff() ...
}
マーシャリングは期待どおりに機能します。問題はアンマーシャリングにあり、サブクラス化された ReceiverPerson
のパッケージ名を使用するJAXBContextにもかかわらず、 Person
へのマーシャリングは解除されます。
JAXBContext jaxbContext = JAXBContext.newInstance(package name of ReceiverPerson);
欲しいのは、 ReceiverPerson
へのマーシャリング解除です。これを行うことができた唯一の方法は、 Person
から @XmlRootElement
を削除することです。残念ながらこれを行うと、 Person
がマーシャリングされなくなります。まるでJAXBが基本クラスから開始し、適切な名前を持つ最初の @XmlRootElement
を見つけるまで下降するかのようです。 ReceiverPerson
を ObjectFactory
に返す createPerson()
メソッドを追加しようとしましたが、役に立ちません。
解決
JAXB 2.0を使用していますか? (JDK6以降)
クラスがあります:
javax.xml.bind.annotation.adapters.XmlAdapter<ValueType,BoundType>
サブクラス化でき、次のメソッドをオーバーライドできます:
public abstract BoundType unmarshal(ValueType v) throws Exception;
public abstract ValueType marshal(BoundType v) throws Exception;
例:
public class YourNiceAdapter
extends XmlAdapter<ReceiverPerson,Person>{
@Override public Person unmarshal(ReceiverPerson v){
return v;
}
@Override public ReceiverPerson marshal(Person v){
return new ReceiverPerson(v); // you must provide such c-tor
}
}
使用方法は次のとおりです。
@Your_favorite_JAXB_Annotations_Go_Here
class SomeClass{
@XmlJavaTypeAdapter(YourNiceAdapter.class)
Person hello; // field to unmarshal
}
この概念を使用することで、自分でマーシャリング/アンマーシャリングプロセスを制御できる(構築する正しい[sub | super] typeの選択を含む)ことを確信しています。
他のヒント
次のスニペットは、緑色のライトを使用したJunit 4テストの方法です。
@Test
public void testUnmarshallFromParentToChild() throws JAXBException {
Person person = new Person();
int age = 30;
String name = "Foo";
person.name = name;
person.age= age;
// Marshalling
JAXBContext context = JAXBContext.newInstance(person.getClass());
Marshaller marshaller = context.createMarshaller();
StringWriter writer = new StringWriter();
marshaller.marshal(person, writer);
String outString = writer.toString();
assertTrue(outString.contains("</person"));
// Unmarshalling
context = JAXBContext.newInstance(Person.class, RecieverPerson.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
StringReader reader = new StringReader(outString);
RecieverPerson reciever = (RecieverPerson)unmarshaller.unmarshal(reader);
assertEquals(name, reciever.name);
assertEquals(age, reciever.age);
}
重要な部分は、アンマーシャリングコンテキストに対する JAXBContext.newInstance(Class ... classesToBeBound)
メソッドの使用です:
context = JAXBContext.newInstance(Person.class, RecieverPerson.class);
この呼び出しにより、JAXBは指定されたクラスの参照クロージャを計算し、 RecieverPerson
を認識します。テストに合格しました。また、パラメーターの順序を変更すると、 java.lang.ClassCastException
が発生します(したがって、この順序で渡す必要があります)。
サブクラスPersonを2回、1回は受信者用、1回は送信者用として、これらのサブクラスにXmlRootElementのみを配置します(XmlRootElementなしでスーパークラス Person
を残します)。送信者と受信者の両方が同じJAXB基本クラスを共有していることに注意してください。
@XmlRootElement(name="person")
public class ReceiverPerson extends Person {
// receiver specific code
}
@XmlRootElement(name="person")
public class SenderPerson extends Person {
// sender specific code (if any)
}
// note: no @XmlRootElement here
public class Person {
// data model + jaxb annotations here
}
[JAXBでの動作がテストおよび確認された]。継承階層内の複数のクラスにXmlRootElement注釈がある場合に注意する問題を回避します。
これはおそらく、より適切でオブジェクト指向のアプローチでもあります。これは、共通のデータモデルを分離するためです。したがって、「回避策」ではありません。まったく。
カスタムObjectFactoryを作成して、アンマーシャリング中に目的のクラスをインスタンス化します。例:
JAXBContext context = JAXBContext.newInstance("com.whatever.mypackage");
Unmarshaller unmarshaller = context.createUnmarshaller();
unmarshaller.setProperty("com.sun.xml.internal.bind.ObjectFactory", new ReceiverPersonObjectFactory());
return unmarshaller;
public class ReceiverPersonObjectFactory extends ObjectFactory {
public Person createPerson() {
return new ReceiverPerson();
}
}
なぜあなたがこれをしたいのか分かりません...それは私にとってそれほど安全ではないようです。
ReceiverPersonで何が起こるかを考慮し、追加のインスタンス変数がある場合...それらの変数がnull、0、またはfalseであると推測します... 0より大きい?
おそらくやりたいことは、Personで読み取り、それから新しいReceiverPersonを構築することだと思います(おそらくPersonを取るコンストラクタを提供します)。
重要な点は、「アンマーシャリングの場合、JAXBは基本クラスから開始し、下に向かって動作します」」ですが、アンマーシャリングの際に ReceiverPerson
を指定すると、クラスは Person
になります @XmlRootElement
の注釈が付けられているため、 Person
への非整列化が行われます。
クラス Person
の @XmlRootElement
を削除する必要がありますが、これを行うと Person
がマーシャリングされなくなります。
解決策は、Personを拡張する DummyPerson
を作成し、 @XmlRootElement
で注釈を付けることです。これにより、 DummyPerson
および同じレベルのReceiverPerson
、 Person
の代わりに DummyPerson
をマーシャリングし、xmlStringを ReceiverPerson
にマーシャリングできます。
@XmlAccessorType(XmlAccessType.FIELD)
public class Person {
public String name;
public int age;
}
@XmlRootElement(name = "person")
public class DummyPerson extends Person {
}
@XmlRootElement(name = "person")
public class ReceiverPerson extends Person {
public doReceiverSpecificStuff();
}
参照:
JAXBの継承サポート
実際には2つの別々のアプリがあるので、クラス&quot; Person&quot;の異なるバージョンでそれらをコンパイルします。 -受信者アプリの @XmlRootElement(name =&quot; person&quot;)
が Person
にありません。これは見苦しいだけでなく、送信者と受信者の両方に同じ定義のPersonを使用することで望んでいた保守性を損ないます。その償還機能の1つは、機能することです。