AOPで内部クラスのコンストラクタをいぢる

なんか日記を付けてなかった&ネタがないんで昔のブログから。
向こうのはGoogleにも引っかからないんでこんな情報が欲しい人は困ると勝手に思って投稿。

話はAOPの内部クラスのいじり方についてです。

なんか半分趣味でeclipse+AspectJ (つまりAJDT)で AOPを始めました。AOPについては下記のリンクを参照。

http://netail.net/aosdwiki/index.php?FrontPage

んで、ちょい必要があったのでコンストラクタをいじるコードを書いたわけです。普通のクラスなら以下のコードでいける。

/**
 * OuterClass.java
 * 内部クラスを持つクラス(まだ内部クラスは無い)
 * これだけを走らすと”外”と出て終わる
 */
 package org.sww.aopinnerclasstest;
 public class Outer {
	private String msg;
	public static void main(String[] args) {
		Outer out = new Outer("外");
		out.print();
	}
	public Outer(String s){
		this.msg = s;
	}
	public void print(){
		System.out.println(msg);
	}
}
/**
 * OuterTrace.aj
 * {@link Outer}クラスを改変するアスペクト
 * 適用後は”外”だったのが”改変:外”になる
 */
package org.sww.aopinnerclasstest;
public aspect OuterTrace {
	/**
	 * {@link Outer#Outer(String)}コンストラクタを改変する
	 */
	void around(String s, Outer out)
	: execution(Outer.new(String))
		& args(s)
		& this(out)
	{
		s = "改変:外";
		proceed(s, out); // Outer(String)コンストラクタを実行
	}
}

上記2コードは

  • Outer.java だけをコンパイルして実行->コンソールに”外”と表示
  • OuterTrace.aj を Outer.java に Weaving してコンパイル&実行 -> コンソールに”改変:外”と表示

となります。void around(String s, Outer out) : execution(Outer.new(String)) & args(s) & this(out) は、「Outer(String)コンストラクタをフックして、そのコンストラクタを実行せずにここに書かれた内容を実行する」という意味。
proceed(s, out) で本来実行されるはずだったコンストラクタの内容を実行してます(つまり、new Outer("改変:外") が呼ばれる)。
とまぁこれなら何も問題は無いわけですが、同様の操作を内部クラスに行っても内部クラスのコンストラクタをフックしてくれません。

/**
 * Outer.java
 * 内部クラスを持つクラス
 */
package org.sww.aopinnerclasstest;
public class Outer {
	private String msg;
	public static void main(String[] args) {
		Outer out = new Outer("外");
		out.print();
	}
	public Outer(String s){
		this.msg = s;
		Inner in = new Inner("内");
		in.print();
	}
	public void print(){
		System.out.println(msg);
	}
	/**
	 * 内部クラス
	 */
	class Inner{
		String _msg;
		public Inner(String s){
			this._msg = s;
		}
		public void print(){
			System.out.println(_msg);
		}
	}
}
/**
 * OuterTrace.aj
 * {@link Outer}クラスを改変するアスペクト
 * 適用後は”外”だったのが”改変:外”になる
 */
package org.sww.aopinnerclasstest;
public aspect OuterTrace {
	/**
	 * コンストラクタ:{@link Outer#Outer(String)}のコンストラクタを改変する
	 */
	void around(String s, Outer out)
	: execution(Outer.new(String))
		& args(s)
		& this(out)
	{
		s = "改変:外";
		proceed(s, out);
	}
	/**
	 * コンストラクタ:{@link Outer.Inner#Inner(String)}のコンストラクタを改変する
	 */
	void around(String s, Outer.Inner in)
	: execution(Outer.Inner.new(String))
		& args(s)
		& this(in)
		{
		s = "改変:内";
		proceed(s, in);
	}
}

上記を Weaving しても、"内 改変:外"と出るだけで、"改変:内"とは出てくれません。
なぜなのか?というのを色々パラメータをいじくって試してみたところ、以下の結論に。

アスペクトで内部クラスのコンストラクタをフックすると、第一引数はOuterクラスオブジェクトとなる

アドバイス:void around(String s, Outer.Inner in) : execution(Outer.Inner.new(String)) & args(s) & this(in) を以下のようにすると動きます。

/**
 * コンストラクタ:{@link Outer.Inner#Inner(String)}のコンストラクタを改変する
 */
void around(Outer o, String s, Outer.Inner in)
: execution(Outer.Inner.new(Outer, String, ..))
	& args(o, s, ..)
	& this(in)
{
	proceed(o, "改変:内", in);
}

どうも eclipse のデバッガで、引数を見てみると以下のようになってる模様。

スレッド [main] (中断中)
		Outer$Inner.init$_aroundBody1$advice(Outer$Inner, Outer, String, OuterTrace, Outer, String, Outer$Inner, AroundClosure) 行: 134
		<不明な受取型>(Outer$Inner).<init>(Outer, String) 行: 40
		Outer.init$_aroundBody0(Outer, String) 行: 23
		Outer.init$_aroundBody1$advice(Outer, String, OuterTrace, String, Outer, AroundClosure) 行: 122
		<不明な受取型>(Outer).<init>(String) 行: 21
		Outer.main(String[]) 行: 17

なんでこんな事になっているかと思い、Outer.Inner クラスを javap コマンドで覗いてみると以下のようになってる模様

class org.sww.aopinnerclasstest.Outer$Inner extends java.lang.Object{
  java.lang.String _msg;
  final org.sww.aopinnerclasstest.Outer this$0;
  public org.sww.aopinnerclasstest.Outer$Inner(org.sww.aopinnerclasstest.Outer, java.lang.String);
  public void print();
}

つまり、クラスファイル仕様としては、ソースには書かれてないけど暗黙に第一引数があり、それは親クラスとなるわけです。そりゃ Outer.Inner(String) じゃ引っかからないわな。
ちなみに、さらに Outer.Inner クラスのさらなる内部クラス Outer.Inner.MoreInner クラスを作ってみて今度も javap でクラスの中を見てみると。

/** 更に内部(Outer.Inner の内部クラス) */
class MoreInner{
	String __msg;
	public MoreInner(String s){
		this.__msg = s;
	}
	public void print(){
		System.out.println(__msg);
	}
}
class org.sww.aopinnerclasstest.Outer$Inner$MoreInner extends java.lang.Object{
  java.lang.String __msg;
  final org.sww.aopinnerclasstest.Outer$Inner this$1;
  public org.sww.aopinnerclasstest.Outer$Inner$MoreInner(org.sww.aopinnerclasstest.Outer$Inner, java.lang.String);
  public void print();
}

というわけで、Outerクラスがさらに引数に渡っていて、引数が(Outer, Outer.Inner, String) とかになっているわけではない模様。ちゃんと調べるなら Java のクラスファイル仕様を見た方がいいんだけどもう時間が時間なので今日はストップ。
この問題で4〜6時間ほどかかってしまったよ〜〜 orz
ちなみに Outer の javap 結果はこんな感じ

public class org.sww.aopinnerclasstest.Outer extends java.lang.Object{
  public static void main(java.lang.String[]);
  public org.sww.aopinnerclasstest.Outer(java.lang.String);
  public void print();
}