量産メモ帳

忘れっぽいのでメモを残しています。浅く適当に書きます。

JAXBで生成するオブジェクトに Serializable を実装させる。

そもそも何でこんな問題で悩んでるかと言うと、開発に当たって採用しているJAXBで生成したオブジェクト(ビーン)は、デフォルトでは Serializable を実装していないからだ。
Serializable がデフォルトで実装できない理由はこういった点にあるのだろうか?
http://www.itarchitect.jp/technology_and_programming/-/27074.html

※2 ただし、以下の方法により、異なるバージョンでも復元できる場合がある。

  • メソッドwriteObject/readObjectを実装し、バージョン間で整合性を保証したカスタム・シリアライズを実現する。両メソッドはprivateメソッドであり、またインタフェースSerializableはメソッドが用意されていないマーカ・インタフェースだが、同インタフェースをimplementsしたときにだけ、両メソッドを特別に定義できる仕組みになっている
  • シリアル・バージョンUIDをカスタム定義する。このシリアル・バージョンUIDは、「private static final long serialVersionUID」という名前で宣言する必要がある。また、ほかのクラスのシリアル・バージョンUIDと重複しないようにしなければならない

でも、JAXBで使用するXMLスキーマファイルに記述を追加すれば、Serializable が実装できることが分かったので、備忘録的に書いておく。
っていうか、以下のページの通りにやればできる。
JAXB RI 1.0.5 -- Vendor Customizations


    
        
            
                
            

        

    

:

また xjc タスクは以下の様な感じでantのビルドファイルで定義。(ちょっと端折ってます。)

  
    
  

  
    
  

  

しかし xjc:serializable 要素の uid 属性値を動的に変更できるのかどうかまでは分からず、また例えば"1"で統一してしまってもいいのかどうか判断できなかったため、最終的に開発の方針としてこの方法は採用されなかった。


で、結局、ゴリゴリ書く羽目に。。。

PropertyUtilsを利用したディープコピー。

とりあえずその場しのぎで実装してみたのが、これ。
orig が org.apache.commons.beanutils.DynaBean のケースは考慮してない。

import java.beans.PropertyDescriptor;
import java.lang.reflect.Array;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.collections.Factory;
import org.apache.commons.collections.FactoryUtils;

/** ディープコピー。 */
public static T deepCopy(T orig)
        throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
    if (orig == null) {
        return null;
    }

    Class origClass = orig.getClass();
    if (origClass.isPrimitive()
            || String.class.equals(origClass)
            || Boolean.class.equals(origClass)
            || Number.class.isAssignableFrom(origClass)) {
        return orig;
    } else if (origClass.isArray()) {
        Object[ ] origArray = (Object[ ]) orig;
        Class origComponentType = origClass.getComponentType();
        int origSize = origArray.length;

        Object[ ] destArray = (Object[ ]) Array.newInstance(origComponentType, origSize);
        for (int i = 0; i < origSize; i++) {
            destArray[i] = deepCopy(origArray[i]);
        }

        return (T) destArray;
    }

    Factory factory = FactoryUtils.instantiateFactory(origClass);
    T dest = (T) factory.create();

    if (dest instanceof List) {
        List destList = (List) dest;
        List origList = (List) orig;
        int origSize = origList.size();
        for (int i = 0; i < origSize; i++) {
            Object origElement = origList.get(i);
            Object destElement = deepCopy(origElement);
            destList.add(destElement);
        }
    } else if (dest instanceof Map) {
        Map destMap = (Map) dest;
        Map origMap = (Map) orig;
        Set origKeySet = origMap.keySet();
        Iterator origKeyIter = origKeySet.iterator();
        while (origKeyIter.hasNext()) {
            Object origKey = (Object) origKeyIter.next();
            Object origValue = origMap.get(origKey);
            Object destValue = deepCopy(origValue);
            destMap.put(origKey, destValue);
        }
    } else {
        PropertyDescriptor origDescriptors[ ] = PropertyUtils.getPropertyDescriptors(orig);
        for (int i = 0; i < origDescriptors.length; i++) {
            String propName = origDescriptors[i].getName();
            if (PropertyUtils.isReadable(orig, propName)) {
                if (PropertyUtils.isWriteable(dest, propName)) {
                    Object origValue = PropertyUtils.getSimpleProperty(orig, propName);
                    Object destValue = deepCopy(origValue);
                    PropertyUtils.setSimpleProperty(dest, propName, destValue);
                }
            }
        }
    }

    return dest;
}

大したテストはしてないので、取り扱い注意w

readResolve メソッドについて。

おまけ。
readResolve メソッドというのは、Serializable インタフェースを実装しているクラスであれば、そのメソッドを定義することによって、もしそのクラスのインスタンスが直列化されている場合、それを復元する直前に呼び出されるメソッドのようだ。
readResolve メソッドの修飾子は、上述の小野氏のブログやITアーキテクトの記事では private だが、

private Object readResolve() throws ObjectStreamException {
    // ・・・中略
}

以下の Sun のサイトによれば、何でも良いっぽい。
http://sdc.sun.co.jp/java/docs/j2se/1.4/ja/docs/ja/guide/serialization/spec/input.doc7.html

Serializable クラスと Externalizable クラスの場合、クラスは readResolve メソッドを使うことによって、呼び出し側に返される前に、ストリームから読み込んだオブジェクトを置換または解釈処理できます。readResolve メソッドを実装することによって、クラスは、クラス自体の直列化復元されているインスタンスの型およびインスタンスを直接制御できます。このメソッドは、次のように定義します。

    ANY-ACCESS-MODIFIER Object readResolve()
        throws ObjectStreamException;))

・・・って書いてみたけど、やっぱ private にしてるのって、オーバーライドを防ぐためなのかな?

java.io.Serializableインタフェースを実装していないビーンに対して、動的プロキシを使ってディープコピーを試みる。→無茶でした。すみません。

まずは参考サイト。小野和俊氏のブログより。
Java Programming Tips:シリアライズを利用したディープコピー

import java.io.*;

public class CopyUtil {
    public static Object deepCopy(Serializable obj)
            throws IOException, ClassNotFoundException {
        if (obj == null) return null;
        ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(byteOut);
        out.writeObject(obj);
        ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
        ObjectInputStream in = new ObjectInputStream(byteIn);
        return in.readObject();
    }
}

以下のメソッドも同じようなことをしてますね。
SerializationUtils#clone(java.io.Serializable)
っていうか、ソースを見たら殆ど一緒でした。

ただし今回は Serializable を実装していないオブジェクトのディープコピーが欲しいので、少し試行錯誤。
で、思いついたのがオブジェクトをプロキシで被せる方法。
(・・・が、直列化を利用したディープコピーが何なのか分かってる人にとっては、これが無駄な作業だったことは明白で、自分の無知ぶりを曝け出してしまって何とも恥ずかしいが、こうなったらとりあえずそういうのも含めて残しておくことにした。)

まず、リフレクション・ハンドラを以下の様な感じで作成。

Serializable の実装

  • ディープ・コピーしたいオブジェクト(ビーン)の Getter、Setter を用意。
  • readResolve メソッドを定義し、その戻り値として自分自身を返すように実装。

/** リフレクション・ハンドラ */
public class BeanInvocationHandler implements InvocationHandler, Serializable {
    /** ビーン */
    private T bean;
    /** デフォルトコンストラクタ */
    public BeanInvocationHandler() {
    }
    /** ビーンのGetter */
    public T getBean() {
        return this.bean;
    }
    /** ビーンのSetter */
    public setBean(T bean) {
        this.bean = bean;
    }
    /** 直列化したオブジェクトを復元する直前に呼び出されるメソッド */
    public Object readResolve() throws ObjectStreamException {
        return this;
    }
}

それからプロキシ・ファクトリも作成。

/** プロキシ・ファクトリ */
public abstract class ProxyFactory {
    /** プロキシを生成するメソッド */
    public static <P> P createProxy(Class<P> proxyClass, InvocationHandler invocationHandler) {
        ClassLoader classLoader = proxyClass.getClassLoader();
        Class[ ] proxyClasses = new Class[ ] {proxyClass};
        Object proxy = Proxy.newProxyInstance(classLoader, proxyClasses, invocationHandler);
        return proxyClass.cast(proxy);
    }
}

で、リフレクション・ハンドラにビーンを渡してから、このハンドラをプロキシ・ファクトリに渡して、プロキシ・オブジェクトを生成。

そしてこのプロキシ・オブジェクトを渡したところ、、、ビーンが直列化できません的なエラーが発生。。。
つまりプロキシで被せた意味なし。
上述の deepCopy メソッドを使ったディープ・コピーの場合、メンバーに一つでも直列化できない(つまり Serializable インタフェースを実装していない)クラスのインスタンスがあると、例外が発生してしまうのだ。*1

自分の無知にちょっと凹んだ。。。


けれども仕方ないので次の方法。

*1:ただし、メンバーのアクセス修飾子に transient を指定しておけば、例外は発生しないものと思われる。