Google Guice で AOP (アスペクト指向プログラミング)

プログラムのあっちゃこっちゃで頻繁に登場するような処理などを上手く取り扱うために AOP (Aspect Oriented Programming) という技法があるみたいです。このエントリは Google Guice という Java の DIコンテナ を使って AOP を試した際のメモです。

今回のサンプルは、メソッドの実行時間を計測するものです。アプリケーションのスループットを向上させるにはベンチマークが必要不可欠ですが、手動で実行時間を測定するためのコードをあっちゃこっちゃに仕込む、なんてのはありえないですよね。サンプルでは、あらかじめ適用するメソッドのルールを設定するだけで、測定できるようにします。

まずは Mainクラス です。

package test.guice.aop;

import com.google.inject.Guice;
import com.google.inject.Injector;

public class Main {
	public static void main(String[] args) {
		Injector injector = Guice.createInjector(new MyGuiceModule());
		Service service = injector.getInstance(Service.class);

		service.execute();
	}
}

Guice の基本的な使い方は DI (Dependency Injectio) です。Injectorインタフェース のインスタンスを作成した上で、そこから依存性が注入されたインスタンスを得ます。GuiceAOP では、依存性を注入したインスタンスに対して必要な処理を織り込めます。Injectorインタフェース は Guice.createInjector() メソッドで得られます。引数の MyGuiceModule は Guice の設定です。

Guice の設定となる MyGuiceModuleクラス です。AbstractModuleクラス を継承して作ります。

package test.guice.aop;

import com.google.inject.AbstractModule;
import com.google.inject.matcher.Matchers;

public class MyGuiceModule extends AbstractModule {

	@Override
	protected void configure() {
		this.bind(Service.class).to(ServiceImpl.class);
		this.bindInterceptor(Matchers.any(), Matchers.any(), new BenchmarkInterceptor());
	}
	
}

AbstractModule#bind(), AbstractModule#to() メソッドでは DI について設定しています。今回は AOP に使うインスタンスを得るためです。Serviceインタフェース を指定して Guice からインスタンスを得るときに、実装はServiceImplクラスのインスタンスになるよう設定しています。

AbstractModule#bindInterceptor() メソッドが AOP の設定です。第一引数には AOP を適用するクラスにマッチするルールを指定します。第二引数は同メソッドの指定です。第三引数は、マッチした際に適用する処理を実装した MethodInterceptor を指定します。

マッチした際の処理を記述した BenchmarkInterceptorクラス です。

package test.guice.aop;

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

public class BenchmarkInterceptor implements MethodInterceptor {

	public Object invoke(MethodInvocation mi) throws Throwable {
		String methodName = mi.getMethod().getName();
		
		long start = System.currentTimeMillis();
		Object result = mi.proceed();
		long end = System.currentTimeMillis();
		
		System.out.println(methodName + ": " + (end - start) + "ms");
		
		return result;
	}
	
}

メソッドの実行の前後で時間を測定して、メソッド名と実行時間を標準出力しています。本来のメソッドの呼び出しを横取りしているイメージです。

以下は処理の織り込み対象のインタフェースとクラスです。

package test.guice.aop;

public interface Service {
	
	public void execute();
}
package test.guice.aop;

public class ServiceImpl implements Service {
	
	public void execute() {
		System.out.println("execute!!");
	}
	
}

今回使ったライブラリは Guice だけですが一応 pom.xml を貼っておきます。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>test.guice.aop</groupId>
	<artifactId>test-guice-aop</artifactId>
	<version>1.0-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>test-guice-aop</name>
	<url>http://maven.apache.org</url>
	
	<build>
		<plugins>
			<plugin>
				<groupId>org.codehaus.mojo</groupId>
				<artifactId>exec-maven-plugin</artifactId>
				<version>1.2.1</version>
				<configuration>
					<mainClass>test.guice.aop.Main</mainClass>
				</configuration>
			</plugin>
		</plugins>
	</build>
	
	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	</properties>

	<dependencies>
		<dependency>
			<groupId>com.google.inject</groupId>
			<artifactId>guice</artifactId>
			<version>3.0</version>
		</dependency>
	</dependencies>
</project>

Mainクラス 実行すると以下のような結果が得られます。

execute!!
execute: 3ms

ServiceImpl#execute() メソッドの実行に 3ms かかったことがわかりました。

AOP はプログラムで頻繁に登場する処理や、モックデータへの入れ替えなどに使うと便利そうですね。有効に扱うには、プログラム中で使うインスタンスのほとんどを依存性が注入されたもので構成する必要があるでしょうか。