web-dev-qa-db-ja.com

JAXBがマーシャリングのために引数なしのコンストラクタを必要とするのはなぜですか?

次のように、引数のないコンストラクタを持たない複合型を参照するクラスをマーシャリングしようとすると、

import Java.sql.Date;

@XmlRootElement(name = "Foo")
@XmlAccessorType(XmlAccessType.FIELD)
public class Foo {
    int i;
    Date d; //Java.sql.Date does not have a no-arg constructor
}

次のように、Javaの一部であるJAXB実装を使用します。

    Foo foo = new Foo();
    JAXBContext jc = JAXBContext.newInstance(Foo.class);
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    Marshaller marshaller = jc.createMarshaller();
    marshaller.marshal(foo, baos);

JAXBは

com.Sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions Java.sql.Date does not have a no-arg default constructor

今、JAXBはオブジェクトをインスタンス化する必要があるため、アンマーシャリングで引数なしのコンストラクタが必要な理由を理解しています。しかし、なぜJAXBはマーシャリング中に引数なしのコンストラクタを必要とするのですか?

また、フィールドがnullの場合にJavaのJAXB実装が例外をスローし、とにかくマーシャリングされないのはなぜですか?

JavaのJAXB実装では、何か不足しているのですか、それとも実装の選択肢が悪いだけですか?

44
rouble

JAXB(JSR-222) 実装がメタデータを初期化すると、マーシャリングとアンマーシャリングの両方をサポートできるようになります。

引数なしのコンストラクタを持たないPOJOクラスの場合、タイプレベルXmlAdapterを使用して処理できます。

_Java.sql.Date_はデフォルトではサポートされていません(ただし、 EclipseLink JAXB(MOXy) はサポートされています)。これは、フィールド、プロパティ、またはパッケージレベルで_@XmlJavaTypeAdapter_を介して指定されたXmlAdapterを使用して処理することもできます。


また、フィールドがnullの場合、JavaのJAXB実装が例外をスローし、とにかくマーシャリングされないのはなぜですか?

どのような例外が発生していますか?通常、フィールドがnullの場合、@XmlElement(nillable=true)アノテーションが付けられていない限り、XML結果には含まれません。その場合、要素には_xsi:nil="true"_が含まれます。


[〜#〜] update [〜#〜]

次のことができます。

SqlDateAdapter

以下はXmlAdapterで、これはJAXB実装が_Java.sql.Date_を処理する方法を知らない_Java.util.Date_から変換します:

_package forum9268074;

import javax.xml.bind.annotation.adapters.*;

public class SqlDateAdapter extends XmlAdapter<Java.util.Date, Java.sql.Date> {

    @Override
    public Java.util.Date marshal(Java.sql.Date sqlDate) throws Exception {
        if(null == sqlDate) {
            return null;
        }
        return new Java.util.Date(sqlDate.getTime());
    }

    @Override
    public Java.sql.Date unmarshal(Java.util.Date utilDate) throws Exception {
        if(null == utilDate) {
            return null;
        }
        return new Java.sql.Date(utilDate.getTime());
    }

}
_

Foo

XmlAdapterは_@XmlJavaTypeAdapter_アノテーションを介して登録されます:

_package forum9268074;

import Java.sql.Date;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlRootElement(name = "Foo")
@XmlAccessorType(XmlAccessType.FIELD)
public class Foo {
    int i;

    @XmlJavaTypeAdapter(SqlDateAdapter.class)
    Date d; //Java.sql.Date does not have a no-arg constructor
}
_
24
bdoughan

あなたの質問に答えるために:これはJAXB(またはおそらくJAXB実装)の設計が不十分だと思います。引数なしのコンストラクタの存在は、JAXBContextの作成中に検証されるため、マーシャリングまたはアンマーシャリングにJAXBを使用するかどうかに関係なく適用されます。 JAXBがこのタイプのチェックをJAXBContext.createUnmarshaller()に延期すれば素晴らしいと思います。この設計が実際に仕様で義務付けられているのか、それがJAXB-RIの実装設計であるのかを掘り下げることは興味深いと思います。

しかし、実際には回避策があります。

JAXBは、マーシャリングに引数なしのコンストラクターを実際に必要としません。以下では、アンマーシャリングではなく、マーシャリング専用にJAXBを使用していると仮定します。また、変更できるようにマーシャリングする不変オブジェクトを制御できると仮定します。そうでない場合は、他の回答で説明されているように、唯一の方法は XmlAdapter です。

不変オブジェクトであるクラスCustomerがあるとします。インスタンス化は、Builderパターンまたは静的メソッドを介して行われます。

public class Customer {

    private final String firstName;
    private final String lastName;

    private Customer(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    // Object created via builder pattern
    public static CustomerBuilder createBuilder() {
        ...
    }

    // getters here ...
}

確かに、デフォルトではJAXBにそのようなオブジェクトをアンマーシャリングさせることはできません。エラー「.... Customer has no no arg default constructor」が表示されます。

これを解決するには、少なくとも2つの方法があります。どちらも、JAXBのイントロスペクションを満足させるためだけに、メソッドまたはコンストラクターを配置することに依存しています。

ソリューション1

このメソッドでは、クラスのインスタンスをインスタンス化するために使用できる静的ファクトリーメソッドがあることをJAXBに伝えます。私たちは知っていますが、JAXBはそうではありません。実際、これは決して使用されません。トリックは @XmlType注釈にfactoryMethod パラメーターを付けます。方法は次のとおりです。

@XmlType(factoryMethod="createInstanceJAXB")
public class Customer {
    ...

    private static CustomerImpl createInstanceJAXB() {  // makes JAXB happy, will never be invoked
        return null;  // ...therefore it doesn't matter what it returns
    }

    ...
}

例のようにメソッドがプライベートであるかどうかは関係ありません。 JAXBは引き続き受け入れます。あなたのIDEは、メソッドをプライベートにするとメソッドに未使用のフラグを立てますが、私はまだプライベートを好みます。

ソリューション2

このソリューションでは、実際のコンストラクタにnullを渡すだけのプライベートな引数なしコンストラクタを追加します。

public class Customer {
    ...
    private Customer() {  // makes JAXB happy, will never be invoked
        this(null, null);   // ...therefore it doesn't matter what it creates
    }

    ...
}

例のように、コンストラクターがプライベートであるかどうかは関係ありません。 JAXBは引き続き受け入れます。

概要

どちらのソリューションも、引数なしのインスタンス化に対するJAXBの要望を満たします。マーシャリングを解除するのではなく、マーシャリングするだけでよいことを自分で知っている場合、これを行う必要があるのは残念です。

これがJAXB-RIでのみ機能し、たとえばEclipseLink MOXyでは機能しないハックであるかどうかはわかりません。 JAXB-RIで確実に機能します。

4
peterh

JAXBイントロスペクションコードには、初期化のためのアクション固有のパスがあるという印象を受けているようです。もしそうなら、それは多くの重複コードをもたらし、実装の質が悪いでしょう。 JAXBコードには、最初に必要なときにモデルクラスを調べ、必要なすべての規則に従っていることを検証する共通のルーチンがあると想像できます。この状況では、メンバーの1つに必要な引数なしのコンストラクタがないため、失敗しています。初期化ロジックは、おそらくマーシャル/アンマーシャル固有ではなく、現在のオブジェクトインスタンスを考慮する可能性が非常に低いです。

0
jtahlborn