JAXB継承、マーシャリングクラスのサブクラスへの非整列化

StackOverflow https://stackoverflow.com/questions/619761

  •  03-07-2019
  •  | 
  •  

質問

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つは、機能することです。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top