wicket-guiceでAOP

wicket-guiceでDIのサンプルはよく見るのですが、AOPをやってるサンプルはあまり見たことないのでやってみた。

このエントリーを参考にやらせていただきました。ありがとうございます。

下準備

wicket-guideでただ「HelloWorld!」を表示するだけのアプリです。これをもとにAOPの設定をしたいと思います。

public class WicketApplication extends WebApplication {
	public WicketApplication() {
	}
	public Class getHomePage() {
		return HomePage.class;
	}
	@Override
	protected void init() {
		addComponentInstantiationListener(new GuiceComponentInjector(this));
	}
}
public class HomePage extends WebPage {
	@Inject
	private Service service;
	public HomePage(final PageParameters parameters) {
		add(new Label("message", service.getMessage()));
	}
}
@ImplementedBy(ServiceImpl.class)
public interface Service {
	public String getMessage();
}
@Singleton
public class ServiceImpl implements Service {
	public String getMessage() {
		return "Hello World!";
	}
}

アノテーションでやる方法

まず、アノテーションを作る。

@Retention(RetentionPolicy.RUNTIME)
@Target( { ElementType.METHOD })
@BindingAnnotation
public @interface CalledLogging {
	// NOB.
}

AOPアライアンスのMethodInterceptorをimplしてメソッドインターセプターを作る。

public class AroundLoggingInterceptor implements MethodInterceptor {
	public Object invoke(MethodInvocation invocation) throws Throwable {
		String methodName = invocation.getMethod().getDeclaringClass()
				.getName()
				+ "#" + invocation.getMethod().getName();
		System.out.println("start: " + methodName);
		// メソッド実行
		Object returnObj = invocation.proceed();
		// 終了ログ
		System.out.println("end: " + methodName);
		return returnObj;
	}
}

ちなみにAOPアライアンスはmaven2から使う場合はpom.xmlに以下のように書いて依存関係を解決しとく。

		<!-- AOPALLIANCE DEPENDENCIES -->
		<dependency>
			<groupId>aopalliance</groupId>
			<artifactId>aopalliance</artifactId>
			<version>1.0</version>
		</dependency>

つぎにバインディング用のモジュールクラスを作って設定してあげます。わたしはWebApplicationのインナークラスで定義すれば良いかなと思います。

public class WicketApplication extends WebApplication {

	public WicketApplication() {
	}
	public Class<? extends WebPage> getHomePage() {
		return HomePage.class;
	}
	@Override
	protected void init() {
		addComponentInstantiationListener(new GuiceComponentInjector(this,
				getModule()));
	}
	protected Module getModule() {
		return new MyModule();
	}
	static class MyModule extends AbstractModule {
		@Override
		protected void configure() {
			AroundLoggingInterceptor interceptor = new AroundLoggingInterceptor();
			bindInterceptor(Matchers.any(), Matchers
					.annotatedWith(CalledLogging.class), interceptor);
		}
	}
}

any()と、annotatedWith()が最初どこにあるか解らなかったのですが、com.google.inject.matcher.Matchersにあります。これはstatic importにしても良いと思います。

これで、あとはAOPしたいところに@CalledLoggingのアノテーションをつけていくだけです。

@Singleton
public class ServiceImpl implements Service {
	@CalledLogging
	public String getMessage() {
		return "Hello World!";
	}
}

こんなのが出るとちゃんと動いている。

start: org.yamkazu.wicket.guice.service.ServiceImpl#getMessage
end: org.yamkazu.wicket.guice.service.ServiceImpl#getMessage

アノテーションを使わずに、メソッド名とかで一括設定

AOP対象となるクラスの条件とAOP対象となるメソッドの条件を設定するクラスを作る。

public class ServiceClassMatcher extends AbstractMatcher<Class> {
	public boolean matches(Class clazz) {
		if (clazz.getName().endsWith("ServiceImpl")) {
			return true;
		}
		return false;
	}
}
public class ServiceMethodMatcher extends AbstractMatcher<Method> {
	public boolean matches(Method t) {
		if (t.getName().endsWith("getMessage")) {
			return true;
		}
		return false;
	}
}

こんどはWicketApplicationで以下のように設定してあげます。AroundLoggingInterceptorはアノテーションの設定で使ったものと同じものです。

public class WicketApplication extends WebApplication {
	public WicketApplication() {
	}
	public Class<? extends WebPage> getHomePage() {
		return HomePage.class;
	}
	@Override
	protected void init() {
		addComponentInstantiationListener(new GuiceComponentInjector(this,
				getModule()));
	}
	protected Module getModule() {
		return new MyModule();
	}
	static class MyModule extends AbstractModule {
		@Override
		protected void configure() {
			bindInterceptor(new ServiceClassMatcher(),
			new ServiceMethodMatcher(), new AroundLoggingInterceptor());
		}
	}
}

このように設定してあげると今度は、ServiceImplでアノテーションを設定しなくても良くなります。

@Singleton
public class ServiceImpl implements Service {
	public String getMessage() {
		return "Hello World!";
	}
}

ちゃんと動けば、さっきと同じようにこんなのが出ます。

start: org.yamkazu.wicket.guice.service.ServiceImpl#getMessage
end: org.yamkazu.wicket.guice.service.ServiceImpl#getMessage

あー、あとguiceトランザクション使えれば完璧なんだが。