Seasar2.3でアノテーションを使って自動AOP

かなり日記がご無沙汰になってました(汗)
本格的に開発の仕事に入ったのと、現実逃避の為に家で真メガテン3マニアックスにハマっていたので(笑)、結構忙しい日々を送ってました。
さて、どうやらSeasar2.3 RC1がリリースされたみたいですね。今丁度仕事でS2を使用しているので、早速触ってみました。
コンポーネントの自動設定の為に、クラスのパッケージ名を指定するのですが、これは親のパッケージだけ書いてやれば、子のパッケージまでしっかりチェックしてくれるみたいです。業務用に作ったパッケージの親を指定してやれば、まとめてたくさんのクラスを自動的に登録することが出来そうです。これはかなり便利そう。あと、アノテーションにも本格対応したみたいで、コンポーネント定義やアスペクトの定義をメタデータで指定できるようになりました。
留意点としては、JARにまとめたクラスについては動かないことと、アスペクトアノテーションをメソッドレベルで指定できないことでしょうか? 以前自分はアノテーションアスペクトをメソッドレベルで定義する自作クラスを作っていて、それを今仕事でも使用しているのですが・・・何とか自分の自作アノテーションをこのS2.3で動かせないか、考えてみました。
・・・で、いつもの通りソースを・・・

AnnotationInterceptorクラス

以前作ったもの、アノテーションをつけたメソッドやクラスに対してアスペクトをかけるクラス

package test.interceptor;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public abstract class AnnotationInterceptor implements MethodInterceptor {

	private Class<? extends Annotation> annotationClass;

	public Object invoke(MethodInvocation invocation) throws Throwable {
		Method method = invocation.getMethod();
		Class<?> declaringClass = method.getDeclaringClass();
		if (declaringClass.isAnnotationPresent(annotationClass)
				|| method.isAnnotationPresent(annotationClass)) {
			return rootInvoke(invocation);
		}
		return invocation.proceed();
	}

	protected abstract Object rootInvoke(MethodInvocation invocation)
			throws Throwable;

	protected Annotation getAnnotation(MethodInvocation invocation) {
		Annotation annotation = null;
		Method method = invocation.getMethod();
		if (method.isAnnotationPresent(annotationClass)) {
			annotation = method.getAnnotation(annotationClass);
		} else {
			Class<?> targetClass = method.getDeclaringClass();
			annotation = targetClass.getAnnotation(annotationClass);
		}
		return annotation;
	}

	public Class<? extends Annotation> getAnnotationClass() {
		return annotationClass;
	}

	public void setAnnotationClass(Class<? extends Annotation> annotationClass) {
		this.annotationClass = annotationClass;
	}

}
AnnotationMethodInterceptorクラス

あるアノテーションを定義したメソッドやクラスに対して、既存のMethodInterceptorを実行するクラス

package test.interceptor;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class AnnotationMethodInterceptor extends AnnotationInterceptor {

	private MethodInterceptor interceptor;
	
	public void setInterceptor(MethodInterceptor interceptor) {
		this.interceptor = interceptor;
	}

	@Override
	protected Object rootInvoke(MethodInvocation invocation) throws Throwable {
		return interceptor.invoke(invocation);
	}

}
AnnotationAspectAutoRegisterクラス

以前作った、自作のAutoRegisterの一部分のロジックを使って、AspectAutoRegisterを継承してみました

package test.interceptor;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.aopalliance.intercept.MethodInterceptor;
import org.seasar.framework.aop.Pointcut;
import org.seasar.framework.aop.impl.PointcutImpl;
import org.seasar.framework.container.AspectDef;
import org.seasar.framework.container.ComponentDef;
import org.seasar.framework.container.annotation.Binding;
import org.seasar.framework.container.annotation.BindingType;
import org.seasar.framework.container.autoregister.AspectAutoRegister;
import org.seasar.framework.container.impl.AspectDefImpl;

public class AnnotationAspectAutoRegister extends AspectAutoRegister {
	
	private Map<Class<? extends Annotation>, String> interceptorMap;

	public void setInterceptorMap(
			Map<Class<? extends Annotation>, String> interceptorMap) {
		this.interceptorMap = interceptorMap;
	}

	@Override
	@Binding(bindingType=BindingType.NONE)
	public void setInterceptor(MethodInterceptor interceptor) {
		super.setInterceptor(interceptor);
	}

	@Override
	protected void registInterceptor(ComponentDef componentDef) {
		Class<?> componentClass = componentDef.getComponentClass();
		for (Class<? extends Annotation> annotationClass : interceptorMap.keySet()) {
			Pointcut pointcut = null;
			if (componentClass.isAnnotationPresent(annotationClass)) {
				pointcut = new PointcutImpl(componentClass);
			}
			if (pointcut == null) {
				Set<String> set = new HashSet<String>();
				for (Method method : componentClass.getMethods()) {
					if (method.isAnnotationPresent(annotationClass)) {
						set.add(method.getName());
					}
				}
				if (set.size() >= 1) {
					pointcut = new PointcutImpl(set.toArray(new String[set.size()]));
				}
			}
			if (pointcut != null) {
				AspectDef aspectDef = new AspectDefImpl(pointcut);
				aspectDef.setExpression(interceptorMap.get(annotationClass));
				componentDef.addAspectDef(aspectDef);
			}
		}
	}

	

}

・・・んで、テスト用クラス

package test.inject;

public interface Test {

	String getMessage();

}
package test.inject.impl;

import test.annotation.Trace;
import test.annotation.Transaction;
import test.inject.Test;

public class TestImpl implements Test {

	@Transaction
	public String getMessage() {
		return getMessage2();
	}

	@Trace
	public String getMessage2() {
		return "こんにちは";
	}

}
package test.client;

public interface TestClient {

	void execute();
}
package test.client.impl;

import test.client.TestClient;
import test.inject.Test;

public class TestClientImpl implements TestClient {
	
	private Test test;

	public TestClientImpl(Test test) {
		this.test = test;
	}

	public void execute() {
		System.out.println(test.getMessage());

	}

}
package test.annotation;

import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface Trace {

}
package test.annotation;

import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface Transaction {

}
package test.main;

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

import test.client.TestClient;

public class Main {
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		SingletonS2ContainerFactory.init();
		S2Container container = SingletonS2ContainerFactory.getContainer();
		
		TestClient client = (TestClient) container.getComponent(TestClient.class);
		client.execute();

	}

}

で、最後にapp.dicon

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC
    "-//SEASAR//DTD S2Container 2.3//EN"
    "http://www.seasar.org/dtd/components23.dtd">

<components>
	<include path="aop.dicon"/>
	<include path="j2ee.dicon"/>

	<component
  		class="org.seasar.framework.container.autoregister.FileSystemComponentAutoRegister">
    	<property name="autoNaming">
    		<component class="org.seasar.framework.container.autoregister.DefaultAutoNaming"/>
    	</property>
    	<initMethod name="addClassPattern">
    		<arg>"test"</arg>
    		<arg>".*Impl"</arg>
    	</initMethod>
    	<initMethod name="registAll"/>
    </component>
    
    <component
      class="test.interceptor.AnnotationAspectAutoRegister">
        <property name="interceptorMap">
        	#{@test.annotation.Trace@class : "annotationTraceInterceptor",
        	@test.annotation.Transaction@class : "annotationTransactionInterceptor"}
        </property>
    	<initMethod name="addClassPattern">
    		<arg>"test"</arg>
    		<arg>".*Impl"</arg>
    	</initMethod>
        <initMethod name="registAll"/>
    </component>

    <component name="annotationTraceInterceptor"
    	class="test.interceptor.AnnotationMethodInterceptor">
    	<property name="interceptor">aop.traceInterceptor</property>
    	<property name="annotationClass">@test.annotation.Trace@class</property>
    </component>
    
    <component name="annotationTransactionInterceptor"
    	class="test.interceptor.AnnotationMethodInterceptor">
    	<property name="interceptor">j2ee.requiredTx</property>
    	<property name="annotationClass">@test.annotation.Transaction@class</property>
    </component>
    
</components>

これで実行してみると・・・

DEBUG 2005-10-13 04:47:20,488 [main] トランザクションを開始しました
DEBUG 2005-10-13 04:47:20,504 [main] BEGIN test.inject.impl.TestImpl#getMessage2()
DEBUG 2005-10-13 04:47:20,504 [main] END test.inject.impl.TestImpl#getMessage2() : こんにちは
DEBUG 2005-10-13 04:47:20,504 [main] トランザクションをコミットしました
こんにちは

・・・アスペクトをかける順番とか気になりますが、とりあえず上手く動いたっぽい。どうしよう・・・2.3に上げてみるべきか・・・設定ファイルが消えるのは凄い魅力的だし・・・