AspectJのお勉強

前回のSpring+AspectJの実験でAspectJの威力を思い知って以来、今更ながら勉強を始めました。どちらにせよ今回の仕事でAOP定義の一部はAspectJを利用しなければならない状況です。だったらこのタイミングで基本的なところを学んでしまおうという魂胆です。
方針としては、あくまでLoad Time Weaving環境を前提として。やはりJavaアプリってclassファイルレベルで流通するのが基本だと思うので、ファイルとしてはAspectで弄る前の状態で保持していたいですからね。
まずは最も簡単に動かせる、javaagentを使ったローカルアプリから。今回は敢えてSeasar2を使ってみました。S2ContainerのinjectDependencyメソッドを使って、コンストラクタにかけたAOPからインジェクトを行う実験です。
まずは対象となるJavaクラス

package aspectjsample;


public class HelloWorld {
	
	private String message;
	
	private String message2;
	
	public HelloWorld() {
	}
	
	public HelloWorld(String message2) {
		this();
		this.message2 = message2;
	}

	public void setMessage(String message) {
		this.message = message;
	}

	public void sayHello() {
		System.out.println(message + " " + message2);
	}

}

セッターメソッド、コンストラクタからそれぞれ文字列を受け取り、sayHelloメソッドでコンソールに表示します。
次はアスペクトを定義したクラス

package aspectjsample;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.seasar.framework.container.factory.SingletonS2ContainerFactory;

@Aspect
public class Trace {
	
	@Pointcut("initialization(public aspectjsample.HelloWorld.new(..))")
	public void pointcutConstruct() {}
	
	@Pointcut("execution(public * aspectjsample.*.*(..))")
	public void pointcutTrace() {}
	
	@Before("pointcutTrace()")
	public void before(JoinPoint joinPoint) {
		System.out.println(joinPoint.getTarget().getClass().getName() + ":" + joinPoint.getSignature().getName() + ":[before]");
	}

	@After("pointcutTrace()")
	public void after(JoinPoint joinPoint) {
		System.out.println(joinPoint.getTarget().getClass().getName() + ":" + joinPoint.getSignature().getName() + ":[after]");
	}
	
	@After("pointcutConstruct()")
	public void afterConstruct(JoinPoint joinPoint) {
		System.out.println(joinPoint.getTarget().getClass().getName() + ":[afterconst]");
		SingletonS2ContainerFactory.getContainer().injectDependency(joinPoint.getTarget());
	}

}

pointcutConstructにはコンストラクタ用のpointcutを、pointcutTraceにはメソッド用のpointcutを記述しています。before、afterでメソッド実行前後にログ出力しています。
今回の実験対象はafterConstructメソッド。S2Containerによって、初期化直後のインスタンスに関連オブジェクトをDIしています。
次はAspectJの設定ファイル

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE aspectj PUBLIC
        "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
    <weaver>
        <!-- only weave classes in our application-specific packages -->
        <include within="aspectjsample.HelloWorld"/>
    </weaver>
    
    <aspects>
        <!-- weave in just this aspect -->
        <aspect name="aspectjsample.Trace"/>
    </aspects>

  </aspectj>

weaving対象クラス、aspectクラスをそれぞれ記述しているだけです。
次はSeasar2の設定ファイル

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.4//EN"
	"http://www.seasar.org/dtd/components24.dtd">
<components>
	<component class="aspectjsample.HelloWorld" instance="outer">
		<property name="message">"Hello World!"</property>
	</component>
</components>

HelloWorldクラスの定義を記述。instance属性は「outer」にしています。
最後に実行クラス

package aspectjsample;

import org.seasar.framework.container.factory.SingletonS2ContainerFactory;

public class Main {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		SingletonS2ContainerFactory.init();
		new HelloWorld("こんにちは").sayHello();
	}

}

Seasar2を初期化した後、HelloWorldクラスをパラメータ付きコンストラクタで初期化してsayHelloメソッドを実行しています。
このクラスを実行、実行時にjavaagentパラメータにaspectjweaver.jarを定義しておきます。

2008/05/19 23:51:07 org.seasar.framework.log.Logger info
情報: Running on [ENV]product, [DEPLOY MODE]Normal Mode
aspectjsample.HelloWorld:[afterconst]
aspectjsample.HelloWorld:setMessage:[before]
aspectjsample.HelloWorld:setMessage:[after]
aspectjsample.HelloWorld:sayHello:[before]
Hello World! こんにちは
aspectjsample.HelloWorld:sayHello:[after]

コンストラクタが呼び出され、setMessageメソッドが呼び出され、sayHelloメソッドが実行されています。Seasar2から渡したメッセージと、コンストラクタから渡したメッセージが両方表示されていることが確認できます。コンストラクタのAOP定義に「initialization」を使うことにより、コンストラクタの中で更に他のコンストラクタを呼んでいても、アスペクトの実行回数は一回になります。これが「execution」だと、呼び出したthisコンストラクタの数だけアスペクトも実行されてしまうので注意が必要です。
この方法を応用すれば、S2と様々なFWとの連携方法を極限まで簡素化できそうな気がします。Springと違ってSeasar2のデフォルトのAOP機能には制限事項があまり無いので、基本的には今までの形でComponent定義を作成しておいて、他FWが呼び出すクラスにだけこの方式でDI+AOPをかけてやれば、さまざまな連携が可能になりそうな気が・・・
ただし、この手法をまともに使うには、javaagentを使わない、カスタムクラスローダによる動的weaving環境を構築しなければなりません。JPAのClassTransformerに対応した手法を応用して、更にHot Deploy等に対応出来れば面白そうな気がするのですが・・・