AspectJはJavaにおける強力なAOP(アスペクト指向プログラミング)フレームワークです。本記事では、Spring AOPとの違いや織り込み方式の詳細、実際のコード例を通じて、その活用方法を解説します。
Spring AOPとの本質的な違い
両フレームワークの設計思想と実装アプローチには明確な差異があります。
自立性の違い
AspectJは完全に独立したAOP専用フレームワークで、Springなどのコンテナに依存しません。一方、Spring AOPはSpring Frameworkの一部として動作し、IoCコンテナと深く連携しています。
織り込みタイミングの違い
AspectJはコンパイル時(CTW)とクラスロード時(LTW)の両方でバイトコードに直接アスペクトを織り込むことが可能です。Spring AOPは実行時に動的プロキシを生成する方式のため、パフォーマンス面で若干のオーバーヘッドが発生します。
インターセプト可能な対象の違い
AspectJはフィールドアクセス、コンストラクタ呼び出し、アノテーションといった多様な JoinPoint に対応します。Spring AOPはメソッド実行のみを対象とするため、カバー範囲が限定的です。
コンパイル時織り込み(CTW)の詳細
CTWは開発フェーズでアスペクトをクラスファイルに埋め込む手法です。実行時の処理が不要なため、高速な動作が期待できます。
CTWの利点
- 実行性能: 動的プロキシやリフレクションを使用しないため、オーバーヘッドが極めて小さい
- 型安全性: コンパイル段階で型チェックが行われるため、実行時エラーを事前に防げる
CTWの実行フロー
- アスペクト定義を含むソースコードを作成
- AspectJコンパイラ(ajc)でコンパイルし、ターゲットクラスのバイトコードにアスペクトを統合
- アスペクトロジックを内包したクラスファイルが生成される
MavenでのCTW設定例
pom.xmlに以下のプラグイン設定を追加することで、ビルド時に自動的にCTWが実行されます。
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.14.0</version>
<configuration>
<source>11</source>
<target>11</target>
<complianceLevel>11</complianceLevel>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
クラスロード時織り込み(LTW)の詳細
LTWはJVMがクラスをロードするタイミングでバイトコードを改変する手法です。既存のバイナリに対しても適用可能な柔軟性が魅力です。
LTWの利点
- 非侵襲性: ソースコードやコンパイル済みクラスを変更する必要がない
- 動的設定: 実行時にどのクラスにどのアスペクトを適用するかを選択可能
- 適用範囲の広さ: サードパーティライブラリなど、ソースが利用できないクラスにも適用できる
LTWの実行フロー
- アプリケーション起動時にJavaエージェントをJVMに登録
- クラスローダーがクラスを読み込む際に、LTWフレームワークが介入
- 設定ファイルに基づいて、バイトコードにアスペクトを動的に注入
JVMエージェントの設定方法
Eclipseでの設定
実行設定の「VM引数」に以下を追加:
-javaagent:${project_loc}/lib/aspectjweaver.jar
Tomcatでの設定
catalina.bat(またはcatalina.sh)に以下を記述:
set JAVA_OPTS=-javaagent:C:/tomcat/lib/aspectjweaver.jar %JAVA_OPTS%
実装例:実行時間計測アスペクト
以下に、メソッドの実行時間を計測する実用的なアスペクト実装を示します。
有効化アノテーション
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(MonitoringConfig.class)
public @interface EnablePerformanceTracking {
}
このアノテーションをアプリケーションクラスに付与することで、パフォーマンス監視機能が有効になります。
計測対象マーカーアノテーション
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TrackExecution {
String endpoint() default "";
}
監視対象メソッドに付与します。エンドポイント名を指定することで、識別しやすくなります。
アスペクト実装
@Aspect
public class PerformanceMonitor {
@Pointcut("@annotation(com.example.TrackExecution)")
public void monitorTarget() {}
@Around("monitorTarget()")
public Object measureExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long beginTime = System.nanoTime();
try {
Object result = joinPoint.proceed();
long finishTime = System.nanoTime();
long durationMs = TimeUnit.NANOSECONDS.toMillis(finishTime - beginTime);
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
TrackExecution annotation = signature.getMethod().getAnnotation(TrackExecution.class);
String endpointName = annotation.endpoint();
if (endpointName.isEmpty()) {
endpointName = signature.getName();
} else if (endpointName.startsWith("/")) {
endpointName = endpointName.substring(1);
}
String className = joinPoint.getTarget().getClass().getSimpleName();
String methodName = signature.getName();
Logger.getLogger(className).log(Level.INFO,
"{0}.{1} execution time: {2}ms",
new Object[]{className, methodName, durationMs});
monitoringService.recordMetrics(endpointName, durationMs, true);
return result;
} catch (Exception ex) {
monitoringService.recordMetrics(endpointName, 0, false);
throw ex;
}
}
@Autowired
private MetricsService monitoringService;
}
このアスペクトは、@TrackExecutionが付与されたメソッドの実行時間を自動的に計測し、ログ出力とメトリクス収集を行います。