CLOVER🍀

That was when it all began.

JAXB RIで、アンマーシャル時のオブジェクトの生成をカスタマイズする

引き続き、JAXBネタです。アンマーシャルの際には、Constructor#newInstance(要はデフォルトコンストラクタ)が呼び出されているようですが、これをちょっと手を加えることでカスタマイズすることができます。

対象はjava.xml.bind.Unmarshallerで、Unmarshaller#setPropertyでこれをカスタマイズすることができます。プロパティ名が「com.sun」で始まるので、まずSun(Oracle?)の実装でしか使えないと思った方がいいんでしょうね。

なお、前の名前空間の接頭辞の場合と違い、今回はJAXB RIそのものに依存関係の定義をしなくても使用することができます。

以下が、サンプルになります。

    public static <T> T unmarshal(String inputString, Class<T> requiredType) throws JAXBException {
        JAXBContext context = JAXBContext.newInstance(requiredType);
        Unmarshaller unmarshaller = context.createUnmarshaller();
        unmarshaller.setProperty("com.sun.xml.internal.bind.ObjectFactory", new jaxb.entity.ObjectFactory());

        return requiredType.cast(unmarshaller.unmarshal(new StringReader(inputString)));
    }

requiredTypeは、アンマーシャル後のトップレベルのクラスになります。

注目点は以下の箇所です。

        unmarshaller.setProperty("com.sun.xml.internal.bind.ObjectFactory", new jaxb.entity.ObjectFactory());

今回はJAXBっぽくObjectFactoryって名前を使ってみましたが、名前は何でもいいんじゃないかと思います。このObjectFactoryクラスに定義されたオブジェクトのファクトリメソッドを使用して、インスタンスの生成を行うことになります。

ここでのObjectFactoryの実装は、以下になります。
ObjectFactory.java

package jaxb.entity;

public class ObjectFactory {
    public Data createData() {
        System.out.println("Called Create Data");
        return new Data();
    }

    public Person createPerson() {
        System.out.println("Called Create Person");
        return new Person();
    }
}

何の変哲もないクラスですが、以下の事項を守らなくてはいけません。

  • ファクトリメソッドは、publicであること
  • ファクトリメソッドは、「create」で始まること
  • ファクトリメソッドは、引数を持たないこと
  • ファクトリメソッドの戻り値は、生成する型の最も具体的な型とすること

どれも重要なのですが、メソッド名だけ変えて戻り値を共通の抽象クラスとするような宣言はできません。ここで返されるクラスのClassクラスが、JAXB内部で使用しているMapのキーになりますので…。

ファクトリクラスが持つメソッドで、先の3つのルールを守っていなかったものは単純にスキップされるだけなので、別に他のメソッドを持っていてはいけないわけではありません。

では、実行してみましょう。
*サンプルコード自体は、前のエントリを参照してください…

Called Create Data
Called Create Person
Called Create Person
Result = [title = [名簿], persons = [!!id = [1], firstName = [Taro], lastName = [Tanaka], age = [17]!!, !!id = [2], firstName = [Hanako], lastName = [Suzuki], age = [15]!!]]

オブジェクト生成の際に、設定したファクトリメソッドが呼び出されていることが確認できます。実際、戻ってくるオブジェクトもファクトリメソッドが生成したオブジェクトとなります。

なお、JAXB RIを明示的に使用する場合は、プロパティの設定が変わります。

        unmarshaller.setProperty("com.sun.xml.bind.ObjectFactory", new jaxb.entity.ObjectFactory());

なぜか、com.sun.xml.internal.bindがcom.sun.xml.bindになります…。
もちろん、依存関係の定義も忘れずに。

libraryDependencies += "com.sun.xml.bind" % "jaxb-impl" % "2.2.6-b35"

ちなみに、これを使って生成するクラスのサブクラスを返すこともできますが、@XmlRootElementなどのアノテーションを付与するのを忘れたりすると、場合によってはコケたりするので注意しましょう。