DynamicProxyにおける、コンストラクタの互換性問題とその解決方法

昨日までの作業で、Castle.DynamicProxyによる拡張された型をDIContainerAOP機能に組み込む際の問題点は大体解決できる目処が経った。実はもうひとつ大きな問題があって、これは過去に自ら試作したDynamicProxyによる型の拡張で明らかになった問題である。

インスタンスを生成する際にAOP拡張型をあたかもコンポーネントの型のように見せる必要もあります。これは例えば元の型と全く同じシグネチャを持つコンストラクタを拡張した型の実装にも定義しておく必要があるということです。
[2005-08-20 IL EmissionによるAOP (4)]

今ならもっと判りやすく書ける。これは、プロキシで拡張された型と、元に型で使用するコンストラクタに互換性が無い、ということだ。例えば、今回使用しているDynamicProxyで動的に拡張された型のコンストラクタは、元々の型のコンストラクタのパラメタの前にCastle.IInterceptor型のパラメタをこっそり挿入する。

//元の型のコンストラクタ
public HogeImpl(string hoge)
{}
                                                                                                                                      • -
//DynamicProxyのクラスプロキシにより拡張されたコンストラクタ public CProxyHogeImpl0(Castle.IInterceptor interceptor, string hoge) :base(hoge) {}

この操作は型を拡張する際に行われるので、この型をコンテナ経由で使用するユーザは気がつかない。従って、以下のようなコンストラクタインジェクションはパラメタの並びが違うので、全て失敗してしまう。


  "hoge"

従って、理想を言うなら、プロキシにより拡張された型のコンストラクタは元の型のコンストラクタが使えるふりを、つまり互換であるように振舞って欲しいのであるが、DynamicProxyは当然のことながら、そんな親切なことはしてくれないようだ。
幸いこれを回避する方法はある。現在s2dotnetの実装などのように対象となる型のインスタンスをプロキシによる拡張の前に生成してしまい、その後プロキシにより型を拡張してアスペクトを織り込めばよいのだ。
例) s2dotnetのAutoConstructorAssemblerクラスのAssembleメソッドより抜粋

//元々の型でインスタンス生成
ConstructorInfo constructor =this.GetSuitableConstructor();
object[] args = this.GetArgs(
    ParameterUtil.GetParameterTypes(constructor.GetParameters()));
obj = ConstructorUtil.NewInstance(constructor,args);
//必要ならアスペクトを織り込む
if(this.ComponentDef.AspectDefSize > 0)
{
    AopProxyUtil.AspectWeaver(ref obj,this.ComponentDef);
}

本文とはぜんぜん関係ないけど、"AspectWeaver"メソッドは"WeaveAspect"のほうが、よくなくないw?

ちなみに、本家JavaのSesar2は型の拡張をJavassistを利用して行っているが、ぱっと見では型の拡張に関してのコンストラクタ互換性問題は発生していないようだ。それは、CompponentDefImplクラスにConcreateClassを直接取得するプロパティがあることからも判る。

public synchronized final Class getConcreteClass() {
    〜中略〜
    concreteClass = AopProxyUtil.getConcreteClass(this);
    return concreteClass;
}

このように拡張された型を直接返して、これをインジェクションに使用しても問題が無いのである。