web-dev-qa-db-ja.com

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

私はJAXBを使用してXMLを読み書きしています。私が欲しいのは、マーシャリングにベース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() ...
}
_

マーシャリングは期待どおりに機能します。問題は非整列化にあり、JAXBContextがサブクラス化されたPersonのパッケージ名を使用しているにもかかわらず、依然としてReceiverPersonに非整列化されます。

_JAXBContext jaxbContext = JAXBContext.newInstance(package name of ReceiverPerson);
_

私が望むのは、ReceiverPersonにアンマーシャルすることです。これを行うことができた唯一の方法は、Personから_@XmlRootElement_を削除することです。残念ながら、これを行うとPersonがマーシャリングされなくなります。まるでJAXBが基本クラスから開始し、適切な名前を持つ最初の_@XmlRootElement_を見つけるまで下降するかのようです。 ReceiverPersonObjectFactoryに返すcreatePerson()メソッドを追加しようとしましたが、それは役に立ちません。

42
Steve Kuo

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
}

この概念を使用することで、自分でマーシャリング/アンマーシャリングプロセスを制御できます(構築する正しい[サブ|スーパー]タイプの選択を含む)。

次のスニペットは、緑色のライトを使用した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_が得られます(したがって、必須はこの順序で渡されます)。

20
Pascal Thivent

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注釈がある場合、注意する問題を回避します。

これはおそらく、より適切でより多くのOOアプローチです。これは、共通のデータモデルを分離するため、「回避策」ではないためです。

12
13ren

カスタム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();
    }
}
7
vocaro

なぜあなたはこれをしたいのか分かりません...それは私にとってそれほど安全ではないようです。

ReceiverPersonに追加のインスタンス変数があるとどうなるか考えてみてください...その後、変数がnull、0、またはfalseであると推測します... ?

おそらくあなたがやりたいことは、Personで読み取られ、それから新しいReceiverPersonを構築することだと思います(おそらくPersonを受け取るコンストラクタを提供します)。

3
TofuBeer

重要な点は、「アンマーシャリング時、JAXBが基本クラスで開始し、その方法を実行する」ことです。ただし、アンマーシャリング時はReceiverPersonを指定しますが、@XmlRootElementアノテーションが付いたクラスPersonがあります。 Personに非整列化します。

そのため、クラスPerson@XmlRootElementを削除する必要がありますが、これを行うとPersonがマーシャリングされなくなります。

解決策は、Personを拡張し、@XmlRootElementで注釈を付けるDummyPersonを作成することです。これにより、DummyPersonReceiverPersonが同じレベルになります。 DummyPersonの代わりにPersonをマーシャリングし、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の継承サポート

0
Jason Law