Spring アプリケーションでの AspectJ の使用

この章でこれまで取り上げてきたものはすべて、純粋な Spring AOP です。このセクションでは、Spring AOP 単独で提供される機能を超えるニーズがある場合に、Spring AOP の代わりに、または Spring AOP に加えて AspectJ コンパイラーまたはウィーバーを使用する方法について説明します。

Spring には、ディストリビューションで spring-aspects.jar としてスタンドアロンで使用できる小さな AspectJ アスペクトライブラリが付属しています。アスペクトを使用するには、これをクラスパスに追加する必要があります。AspectJ を使用して Spring でドメインオブジェクトを依存性注入するおよび AspectJ の他の Spring アスペクトは、このライブラリの内容とその使用方法について説明しています。Spring IoC を使用した AspectJ アスペクトの構成は、AspectJ コンパイラーを使用して織り込まれた AspectJ アスペクトを依存性注入する方法について説明します。最後に、Spring Framework の AspectJ を使用したロード時ウィービングは、AspectJ を使用する Spring アプリケーションのロード時ウィービングの概要を提供します。

AspectJ を使用して Spring でドメインオブジェクトを依存性注入する

Spring コンテナーは、アプリケーションコンテキストで定義された Bean をインスタンス化して構成します。適用する構成を含む Bean 定義の名前を指定して、Bean ファクトリに既存のオブジェクトの構成を依頼することもできます。spring-aspects.jar には、この機能を活用して任意のオブジェクトの依存性注入を可能にするアノテーション駆動型の側面が含まれています。このサポートは、コンテナーの制御外で作成されたオブジェクトに使用することを目的としています。ドメインオブジェクトは、多くの場合、new 演算子を使用してプログラムで作成されるか、データベースクエリの結果として ORM ツールによって作成されるため、このカテゴリに分類されます。

@Configurable アノテーションは、クラスを Spring 駆動の構成に適格としてマークします。最も単純な場合、次の例に示すように、純粋にマーカーアノテーションとして使用できます。

  • Java

  • Kotlin

package com.xyz.domain;

import org.springframework.beans.factory.annotation.Configurable;

@Configurable
public class Account {
	// ...
}
package com.xyz.domain

import org.springframework.beans.factory.annotation.Configurable

@Configurable
class Account {
	// ...
}

この方法でマーカーインターフェースとして使用する場合、Spring は、完全修飾型名(com.xyz.domain.Account)と同じ名前の Bean 定義(通常はプロトタイプスコープ)を使用して、アノテーション付き型(この場合は Account)の新しいインスタンスを構成します。Bean のデフォルト名はその型の完全修飾名であるため、プロトタイプ定義を宣言する便利な方法は、次の例に示すように、id 属性を省略することです。

<bean class="com.xyz.domain.Account" scope="prototype">
	<property name="fundsTransferService" ref="fundsTransferService"/>
</bean>

使用するプロトタイプ Bean 定義の名前を明示的に指定する場合は、次の例に示すように、アノテーションで直接指定できます。

  • Java

  • Kotlin

package com.xyz.domain;

import org.springframework.beans.factory.annotation.Configurable;

@Configurable("account")
public class Account {
	// ...
}
package com.xyz.domain

import org.springframework.beans.factory.annotation.Configurable

@Configurable("account")
class Account {
	// ...
}

Spring は、account という名前の Bean 定義を検索し、それを定義として使用して新しい Account インスタンスを構成します。

オートワイヤーを使用して、専用の Bean 定義をまったく指定する必要がないようにすることもできます。Spring にオートワイヤーを適用させるには、@Configurable アノテーションの autowire プロパティを使用します。型ごとまたは名前ごとに、オートワイヤー用に @Configurable(autowire=Autowire.BY_TYPE) または @Configurable(autowire=Autowire.BY_NAME) を指定できます。別の方法として、フィールドまたはメソッドレベルで @Autowired または @Inject を介して @Configurable Bean に明示的なアノテーション駆動の依存性注入を指定することをお勧めします(詳細についてはアノテーションベースのコンテナー構成を参照)。

最後に、dependencyCheck 属性(たとえば、@Configurable(autowire=Autowire.BY_NAME,dependencyCheck=true))を使用して、新しく作成および構成されたオブジェクトのオブジェクト参照の Spring 依存関係チェックを有効にできます。この属性が true に設定されている場合、Spring は、構成後にすべてのプロパティ(プリミティブまたはコレクションではない)が設定されていることを検証します。

アノテーションを単独で使用しても何も起こらないことに注意してください。アノテーションの存在に作用するのは、spring-aspects.jar 内の AnnotationBeanConfigurerAspect です。本質的に、アスペクトは、「 @Configurable でアノテーションが付けられた型の新しいオブジェクトの初期化から戻った後、アノテーションのプロパティに従って Spring を使用して新しく作成されたオブジェクトを構成する」と述べています。このコンテキストでは、「初期化」とは、新しくインスタンス化されたオブジェクト (たとえば、new オペレーターでインスタンス化されたオブジェクト) と、(たとえば、readResolve() (標準 Javadoc) を介して) 直列化解除中の Serializable オブジェクトを指します。

上記の段落のキーフレーズの 1 つは、「本質的に」です。ほとんどの場合、「新しいオブジェクトの初期化から戻った後」の正確なセマンティクスは問題ありません。このコンテキストでは、「初期化後」とは、オブジェクトが構築された後に依存関係が注入されることを意味します。これは、クラスのコンストラクター本体で依存関係を使用できないことを意味します。コンストラクターの本体が実行される前に依存関係を注入して、コンストラクターの本体で使用できるようにする場合は、次のように @Configurable 宣言でこれを定義する必要があります。

  • Java

  • Kotlin

@Configurable(preConstruction = true)
@Configurable(preConstruction = true)

AspectJ プログラミングガイド (英語) この付録 (英語) では、AspectJ のさまざまなポイントカット型の言語セマンティクスに関する詳細情報を見つけることができます。

これが機能するためには、アノテーション付きの型を AspectJ ウィーバーで織り込む必要があります。ビルド時の Ant または Maven タスクを使用してこれを行うことができます(たとえば、AspectJ 開発環境ガイド (英語) を参照)か、ロード時のウィービング(Spring Framework の AspectJ を使用したロード時ウィービングを参照)。AnnotationBeanConfigurerAspect 自体は、Spring によって構成する必要があります(新しいオブジェクトの構成に使用される Bean ファクトリへの参照を取得するため)。Java ベースの構成を使用する場合、次のように、@EnableSpringConfigured を @Configuration クラスに追加できます。

  • Java

  • Kotlin

@Configuration
@EnableSpringConfigured
public class AppConfig {
}
@Configuration
@EnableSpringConfigured
class AppConfig {
}

XML ベースの構成を好む場合、Spring context 名前空間は便利な context:spring-configured 要素を定義し、次のように使用できます。

<context:spring-configured/>

アスペクトが構成される前に作成された @Configurable オブジェクトのインスタンスは、デバッグログにメッセージが発行され、オブジェクトの構成は行われません。例は、Spring によって初期化されたときにドメインオブジェクトを作成する Spring 構成の Bean です。この場合、depends-on Bean 属性を使用して、Bean が構成の側面に依存することを手動で指定できます。次の例は、depends-on 属性の使用メソッドを示しています。

<bean id="myService"
		class="com.xyz.service.MyService"
		depends-on="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect">

	<!-- ... -->

</bean>
実行時にセマンティクスに本当に依存するつもりでない限り、Bean 構成機能アスペクトを介して @Configurable 処理をアクティブにしないでください。特に、通常の Spring Bean としてコンテナーに登録されている Bean クラスで @Configurable を使用しないようにしてください。これを行うと、コンテナーとアスペクトを一度ずつ通る二重の初期化が行われます。

@Configurable オブジェクトの単体テスト

@Configurable サポートのゴールの 1 つは、ハードコードされたルックアップに関連する困難なしに、ドメインオブジェクトの独立した単体テストを有効にすることです。@Configurable 型が AspectJ で編まれていない場合、単体テスト中にアノテーションは影響しません。テスト対象のオブジェクトにモックまたはスタブプロパティ参照を設定し、通常どおり続行できます。@Configurable 型が AspectJ によって織り込まれている場合、通常どおりコンテナーの外部で単体テストを実行できますが、Spring によって構成されていないことを示す @Configurable オブジェクトを作成するたびに警告メッセージが表示されます。

複数のアプリケーションコンテキストの操作

@Configurable サポートの実装に使用される AnnotationBeanConfigurerAspect は、AspectJ シングルトンアスペクトです。シングルトンアスペクトのスコープは、static メンバーのスコープと同じです。型を定義する ClassLoader ごとに 1 つのアスペクトインスタンスがあります。つまり、同じ ClassLoader 階層内で複数のアプリケーションコンテキストを定義する場合、@EnableSpringConfigured Bean を定義する場所と、クラスパスのどこに spring-aspects.jar を配置するかを考慮する必要があります。

共通のビジネスサービスを定義する共有の親アプリケーションコンテキスト、それらのサービスをサポートするために必要なすべてのもの、および各サーブレット (そのサーブレットに固有の定義を含む) の 1 つの子アプリケーションコンテキストを持つ典型的な Spring Web アプリケーション構成を考えてみましょう。これらのコンテキストはすべて同じ ClassLoader 階層内に共存するため、AnnotationBeanConfigurerAspect はそのうちの 1 つのみへの参照を保持できます。この場合、共有 (親) アプリケーションコンテキストで @EnableSpringConfigured Bean を定義することをお勧めします。これにより、ドメインオブジェクトに注入する可能性が高いサービスが定義されます。その結果、@Configurable メカニズムを使用して、子 (サーブレット固有) コンテキストで定義された Bean への参照を使用してドメインオブジェクトを構成することはできません (これはおそらくやりたいことではありません)。

同じコンテナー内に複数の Web アプリケーションをデプロイする場合は、各 Web アプリケーションが独自の ClassLoader を使用して (たとえば、spring-aspects.jar を WEB-INF/lib に配置することによって) spring-aspects.jar に型をロードすることを確認してください。spring-aspects.jar がコンテナー全体のクラスパスにのみ追加された場合 (したがって、共有の親 ClassLoader によってロードされた場合)、すべての Web アプリケーションが同じアスペクトインスタンスを共有します (これはおそらく望むものではありません)。

AspectJ の他の Spring アスペクト

@Configurable アスペクトに加えて、spring-aspects.jar には AspectJ アスペクトが含まれており、これを使用して @Transactional アノテーションが付けられた型とメソッドの Spring のトランザクション管理を駆動できます。これは主に、Spring Framework のトランザクションサポートを Spring コンテナーの外部で使用したいユーザーを対象としています。

@Transactional アノテーションを解釈するアスペクトは AnnotationTransactionAspect です。このアスペクトを使用する場合、クラスが実装するインターフェース(存在する場合)ではなく、実装クラス(またはそのクラス内のメソッド、あるいはその両方)にアノテーションを付ける必要があります。AspectJ は、インターフェースのアノテーションが継承されないという Java のルールに従います。

クラスの @Transactional アノテーションは、クラス内のすべてのパブリック操作の実行に対するデフォルトのトランザクションセマンティクスを指定します。

クラス内のメソッドの @Transactional アノテーションは、クラスアノテーション(存在する場合)によって指定されたデフォルトのトランザクションセマンティクスをオーバーライドします。プライベートメソッドを含む、任意の可視性のメソッドにアノテーションを付けることができます。非 public メソッドに直接アノテーションを付けることは、そのようなメソッドの実行のためにトランザクション境界を取得する唯一の方法です。

Spring Framework 4.2 以降、spring-aspects は、標準の jakarta.transaction.Transactional アノテーションとまったく同じ機能を提供する同様の側面を提供します。詳細については、JtaAnnotationTransactionAspect を確認してください。

Spring 構成およびトランザクション管理サポートを使用したいが、アノテーションを使用したくない(または使用できない)AspectJ プログラマー向けに、spring-aspects.jar には、独自のポイントカット定義を提供するために拡張できる abstract アスペクトも含まれています。詳細については、AbstractBeanConfigurerAspect および AbstractTransactionAspect のアスペクトのソースを参照してください。例として、次の抜粋は、完全修飾クラス名に一致するプロトタイプ Bean 定義を使用して、ドメインモデルで定義されたオブジェクトのすべてのインスタンスを構成するアスペクトを作成する方法を示しています。

public aspect DomainObjectConfiguration extends AbstractBeanConfigurerAspect {

	public DomainObjectConfiguration() {
		setBeanWiringInfoResolver(new ClassNameBeanWiringInfoResolver());
	}

	// the creation of a new bean (any object in the domain model)
	protected pointcut beanCreation(Object beanInstance) :
		initialization(new(..)) &&
		CommonPointcuts.inDomainModel() &&
		this(beanInstance);
}

Spring IoC を使用した AspectJ アスペクトの構成

Spring アプリケーションで AspectJ アスペクトを使用する場合、そのようなアスペクトを Spring で構成できることを望み、期待することは当然です。AspectJ ランタイム自体がアスペクトの作成を担当し、Spring を介して AspectJ で作成されたアスペクトを構成する手段は、アスペクトで使用される AspectJ インスタンス化モデル(per-xxx 句)に依存します。

AspectJ アスペクトの大部分はシングルトンアスペクトです。これらのアスペクトの構成は簡単です。通常どおりアスペクト型を参照し、factory-method="aspectOf" Bean 属性を含む Bean 定義を作成できます。これにより、Spring は、インスタンス自体を作成しようとするのではなく、AspectJ に要求してアスペクトインスタンスを取得します。次の例は、factory-method="aspectOf" 属性の使用方法を示しています。

<bean id="profiler" class="com.xyz.profiler.Profiler"
		factory-method="aspectOf"> (1)

	<property name="profilingStrategy" ref="jamonProfilingStrategy"/>
</bean>
1factory-method="aspectOf" 属性に注意してください

非シングルトンのアスペクトは構成が困難です。ただし、プロトタイプ Bean 定義を作成し、spring-aspects.jar の @Configurable サポートを使用して、AspectJ ランタイムによって Bean が作成されると、アスペクトインスタンスを構成することにより、これを行うことができます。

AspectJ で織り込む @AspectJ アスペクト(ドメインモデル型のロード時ウィービングなど)や Spring AOP で使用する他の @AspectJ アスペクトがあり、これらのアスペクトはすべて Spring で構成されている場合、Spring AOP @AspectJ 自動プロキシサポートに、構成で定義された @AspectJ アスペクトの正確なサブセットを自動プロキシに使用するよう指示する必要があります。これを行うには、<aop:aspectj-autoproxy/> 宣言内で 1 つ以上の <include/> エレメントを使用します。各 <include/> エレメントは名前パターンを指定し、Spring AOP 自動プロキシ構成には、少なくとも 1 つのパターンと一致する名前を持つ Bean のみが使用されます。次の例は、<include/> 要素の使用メソッドを示しています。

<aop:aspectj-autoproxy>
	<aop:include name="thisBean"/>
	<aop:include name="thatBean"/>
</aop:aspectj-autoproxy>
<aop:aspectj-autoproxy/> 要素の名前に惑わされないでください。これを使用すると、Spring AOP プロキシが作成されます。ここでは @AspectJ スタイルのアスペクト宣言が使用されていますが、AspectJ ランタイムは関係していません。

Spring Framework の AspectJ を使用したロード時ウィービング

ロードタイムウィービング(LTW)は、Java 仮想マシン(JVM)にロードされるときに、AspectJ アスペクトをアプリケーションのクラスファイルにウィービングするプロセスを指します。このセクションの焦点は、Spring Framework の特定のコンテキストで LTW を構成および使用することです。このセクションは、LTW の一般的な導入ではありません。LTW の詳細および AspectJ のみで Spring を使用しない LTW の構成の詳細については、AspectJ 開発環境ガイドの LTW セクション (英語) を参照してください。

Spring Framework が AspectJ LTW にもたらす価値は、ウィービングプロセスをよりきめ細かく制御できることです。「バニラ」AspectJ LTW は、JVM の起動時に VM 引数を指定することによりオンに切り替えられる Java(5+)エージェントを使用することにより影響を受けます。JVM 全体の設定であり、状況によっては問題ない場合もありますが、多くの場合少し粗すぎます。Spring 対応の LTW を使用すると、ClassLoader 単位で LTW を切り替えることができます。これは、よりきめ細かく、「単一 JVM 複数アプリケーション」環境(一般的なアプリケーションサーバーなど)でより意味があります。環境)。

さらに、特定の環境では、このサポートにより、-javaagent:path/to/aspectjweaver.jar または(このセクションで後述する) -javaagent:path/to/spring-instrument.jar を追加するために必要なアプリケーションサーバーの起動スクリプトを変更することなく、ロード時のウィービングが可能になります。開発者は、起動スクリプトなどのデプロイ構成を通常担当する管理者に依存する代わりに、アプリケーションコンテキストを構成してロード時のウィービングを有効にします。

紹介が終わったため、まず Spring を使用する AspectJ LTW の簡単な例を見てみましょう。次に、例で紹介した要素に関する詳細を説明します。完全な例については、Petclinic サンプルアプリケーション [GitHub] (英語) を参照してください。

最初の例

システムのパフォーマンスの問題の原因の診断を担当しているアプリケーション開発者であると仮定します。プロファイリングツールをブレークアウトするのではなく、パフォーマンスメトリクスをすばやく取得できる単純なプロファイリングアスペクトに切り替えます。その後、すぐにその特定の領域に詳細なプロファイリングツールを適用できます。

ここに示す例では、XML 構成を使用しています。Java 構成で @AspectJ を構成して使用することもできます。具体的には、@EnableLoadTimeWeaving アノテーションを <context:load-time-weaver/> の代替として使用できます(詳細については以下を参照)。

次の例はプロファイリングのアスペクトを示していますが、これは派手ではありません。@AspectJ スタイルのアスペクト宣言を使用する時間ベースのプロファイラーです。

  • Java

  • Kotlin

package com.xyz;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.util.StopWatch;
import org.springframework.core.annotation.Order;

@Aspect
public class ProfilingAspect {

	@Around("methodsToBeProfiled()")
	public Object profile(ProceedingJoinPoint pjp) throws Throwable {
		StopWatch sw = new StopWatch(getClass().getSimpleName());
		try {
			sw.start(pjp.getSignature().getName());
			return pjp.proceed();
		} finally {
			sw.stop();
			System.out.println(sw.prettyPrint());
		}
	}

	@Pointcut("execution(public * com.xyz..*.*(..))")
	public void methodsToBeProfiled(){}
}
package com.xyz

import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.annotation.Pointcut
import org.springframework.util.StopWatch
import org.springframework.core.annotation.Order

@Aspect
class ProfilingAspect {

	@Around("methodsToBeProfiled()")
	fun profile(pjp: ProceedingJoinPoint): Any? {
		val sw = StopWatch(javaClass.simpleName)
		try {
			sw.start(pjp.getSignature().getName())
			return pjp.proceed()
		} finally {
			sw.stop()
			println(sw.prettyPrint())
		}
	}

	@Pointcut("execution(public * com.xyz..*.*(..))")
	fun methodsToBeProfiled() {
	}
}

また、ProfilingAspect をクラスに織り込むことを AspectJ ウィーバーに通知するために、META-INF/aop.xml ファイルを作成する必要があります。このファイル規則、つまり、META-INF/aop.xml と呼ばれる Java クラスパス上のファイルの存在は、標準の AspectJ です。次の例は、aop.xml ファイルを示しています。

<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "https://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>

	<weaver>
		<!-- only weave classes in our application-specific packages and sub-packages -->
		<include within="com.xyz..*"/>
	</weaver>

	<aspects>
		<!-- weave in just this aspect -->
		<aspect name="com.xyz.ProfilingAspect"/>
	</aspects>

</aspectj>
AspectJ ダンプファイルや警告などの副作用を避けるために、特定のクラス (通常、上記の aop.xml の例に示すように、アプリケーションパッケージ内のクラス) のみをウィービングすることをお勧めします。これは効率の観点からもベストプラクティスです。

これで、構成の Spring 固有の部分に進むことができます。LoadTimeWeaver を構成する必要があります(後で説明します)。このロード時ウィーバーは、1 つ以上の META-INF/aop.xml ファイルのアスペクト構成をアプリケーションのクラスに織り込む重要なコンポーネントです。良い点は、次の例に示すように、多くの構成を必要としないことです(指定できるオプションがいくつかありますが、これらについては後で詳しく説明します)。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="
		http://www.springframework.org/schema/beans
		https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context
		https://www.springframework.org/schema/context/spring-context.xsd">

	<!-- a service object; we will be profiling its methods -->
	<bean id="entitlementCalculationService"
			class="com.xyz.StubEntitlementCalculationService"/>

	<!-- this switches on the load-time weaving -->
	<context:load-time-weaver/>
</beans>

必要なすべてのアーティファクト(アスペクト、META-INF/aop.xml ファイル、Spring 構成)が配置されたため、LTP の実際の動作を示すために、main(..) メソッドを使用して次のドライバークラスを作成できます。

  • Java

  • Kotlin

package com.xyz;

// imports

public class Main {

	public static void main(String[] args) {
		ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");

		EntitlementCalculationService service =
				ctx.getBean(EntitlementCalculationService.class);

		// the profiling aspect is 'woven' around this method execution
		service.calculateEntitlement();
	}
}
package com.xyz

// imports

fun main() {
	val ctx = ClassPathXmlApplicationContext("beans.xml")

	val service = ctx.getBean(EntitlementCalculationService.class)

	// the profiling aspect is 'woven' around this method execution
	service.calculateEntitlement()
}

最後にやることがあります。このセクションの導入では、Spring を使用して ClassLoader ごとに LTW を選択的にオンにできると述べていますが、これは事実です。ただし、この例では、Java エージェント(Spring で提供)を使用して LTW をオンにします。次のコマンドを使用して、前述の Main クラスを実行します。

java -javaagent:C:/projects/xyz/lib/spring-instrument.jar com.xyz.Main

-javaagent は、エージェントが JVM 上で実行されるプログラムをインスツルメント (標準 Javadoc) できるように指定および有効化する (標準 Javadoc) ためのフラグです。Spring Framework には、このようなエージェント InstrumentationSavingAgent が付属しています。これは、前の例で -javaagent 引数の値として提供された spring-instrument.jar にパッケージ化されています。

Main プログラムの実行からの出力は、次の例のようになります。(Thread.sleep(..) ステートメントを calculateEntitlement() 実装に導入して、プロファイラーが実際に 0 ミリ秒以外をキャプチャーするようにしました(01234 ミリ秒は AOP によってもたらされるオーバーヘッドではありません)。以下のリストは、プロファイラーを実行したときに得られる出力を示しています。

Calculating entitlement

StopWatch 'ProfilingAspect': running time (millis) = 1234
------ ----- ----------------------------
ms     %     Task name
------ ----- ----------------------------
01234  100%  calculateEntitlement

この LTW は本格的な AspectJ を使用して行われるため、Spring Bean のアドバイスのみに限定されません。Main プログラムの次のわずかなバリエーションでも同じ結果が得られます。

  • Java

  • Kotlin

package com.xyz;

// imports

public class Main {

	public static void main(String[] args) {
		new ClassPathXmlApplicationContext("beans.xml");

		EntitlementCalculationService service =
				new StubEntitlementCalculationService();

		// the profiling aspect will be 'woven' around this method execution
		service.calculateEntitlement();
	}
}
package com.xyz

// imports

fun main(args: Array<String>) {
	ClassPathXmlApplicationContext("beans.xml")

	val service = StubEntitlementCalculationService()

	// the profiling aspect will be 'woven' around this method execution
	service.calculateEntitlement()
}

上記のプログラムで、Spring コンテナーをブートストラップし、Spring のコンテキストの完全に外側で StubEntitlementCalculationService の新しいインスタンスを作成する方法に注目してください。プロファイリングのアドバイスはまだ織り込まれています。

確かに、この例は単純化されています。ただし、Spring での LTW サポートの基本はすべて前の例で紹介されています。このセクションの残りの部分では、構成と使用箇所の各ビットの背後にある「理由」を詳細に説明します。

この例で使用される ProfilingAspect は基本的なものですが、非常に便利です。これは、開発者が開発中に使用できる開発時のアスペクトの良い例であり、UAT または本番にデプロイされるアプリケーションのビルドから簡単に除外できます。

アスペクト

LTW で使用するアスペクトは、AspectJ アスペクトでなければなりません。AspectJ 言語自体で記述することも、@AspectJ スタイルでアスペクトを記述することもできます。アスペクトは有効な AspectJ と Spring AOP アスペクトの両方です。さらに、コンパイルされたアスペクトクラスは、クラスパスで利用可能である必要があります。

META-INF/aop.xml

AspectJ LTW インフラストラクチャは、Java クラスパス上にある 1 つ以上の META-INF/aop.xml ファイル (直接、またはより一般的には jar ファイル内) を使用して構成されます。例:

<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "https://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>

	<weaver>
		<!-- only weave classes in our application-specific packages and sub-packages -->
		<include within="com.xyz..*"/>
	</weaver>

</aspectj>
AspectJ ダンプファイルや警告などの副作用を避けるために、特定のクラス (通常、上記の aop.xml の例に示すように、アプリケーションパッケージ内のクラス) のみをウィービングすることをお勧めします。これは効率の観点からもベストプラクティスです。

このファイルの構造と内容は、AspectJ リファレンスドキュメント (英語) の LTW 部分で詳しく説明されています。aop.xml ファイルは 100% AspectJ であるため、ここではこれ以上説明しません。

必要なライブラリ (JARS)

AspectJ LTW の Spring Framework のサポートを使用するには、少なくとも次のライブラリが必要です。

  • spring-aop.jar

  • aspectjweaver.jar

  • spring-instrument.jar

Spring の設定

Spring の LTW サポートの重要なコンポーネントは、LoadTimeWeaver インターフェース(org.springframework.instrument.classloading パッケージ内)と、Spring ディストリビューションに同梱される多数の実装です。LoadTimeWeaver は、実行時に 1 つ以上の java.lang.instrument.ClassFileTransformers を ClassLoader に追加するロールを果たします。これにより、あらゆる種類の興味深いアプリケーションへの扉が開かれます。

未知でランタイムクラスファイルの変換を考えている場合は、続行する前に java.lang.instrument パッケージの javadoc API ドキュメントを参照してください。このドキュメントは包括的なものではありませんが、少なくとも、主要なインターフェースとクラスを確認できます(このセクションを読む際に参照してください)。

特定の ApplicationContext 用に LoadTimeWeaver を構成するのは、1 行追加するのと同じくらい簡単です。(Spring コンテナーとして ApplicationContext を使用する必要があることはほぼ確実であることに注意してください。通常、LTP サポートは BeanFactoryPostProcessors を使用するため、BeanFactory では十分ではありません。)

Spring Framework の LTW サポートを有効にするには、LoadTimeWeaver を構成する必要があります。これは、通常、次のように @EnableLoadTimeWeaving アノテーションを使用して行われます。

  • Java

  • Kotlin

@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}
@Configuration
@EnableLoadTimeWeaving
class AppConfig {
}

または、XML ベースの構成が必要な場合は、<context:load-time-weaver/> 要素を使用します。要素は context 名前空間で定義されていることに注意してください。次の例は、<context:load-time-weaver/> の使用方法を示しています。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="
		http://www.springframework.org/schema/beans
		https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context
		https://www.springframework.org/schema/context/spring-context.xsd">

	<context:load-time-weaver/>

</beans>

前述の構成により、LoadTimeWeaver や AspectJWeavingEnabler などの多数の LTW 固有のインフラストラクチャ Bean が自動的に定義および登録されます。デフォルトの LoadTimeWeaver は DefaultContextLoadTimeWeaver クラスで、自動的に検出された LoadTimeWeaver をデコレートしようとします。「自動的に検出」される LoadTimeWeaver の正確な型は、ランタイム環境によって異なります。次の表は、さまざまな LoadTimeWeaver 実装をまとめたものです。

表 1: DefaultContextLoadTimeWeaver LoadTimeWeavers
ランタイム環境 LoadTimeWeaver の実装

Apache Tomcat (英語) で実行

TomcatLoadTimeWeaver

GlassFish (英語) で実行 (EAR デプロイに限定)

GlassFishLoadTimeWeaver

Red Hat の JBoss AS (英語) または WildFly (英語) で実行

JBossLoadTimeWeaver

Spring InstrumentationSavingAgent で開始した JVM (java -javaagent:path/to/spring-instrument.jar)

InstrumentationLoadTimeWeaver

フォールバック、基礎となる ClassLoader が一般的な規則に従うことを期待 (すなわち、addTransformer およびオプションで getThrowawayClassLoader メソッド)

ReflectiveLoadTimeWeaver

この表には、DefaultContextLoadTimeWeaver の使用時に自動検出される LoadTimeWeavers のみがリストされていることに注意してください。使用する LoadTimeWeaver 実装を正確に指定できます。

Java 構成で特定の LoadTimeWeaver を指定するには、LoadTimeWeavingConfigurer インターフェースを実装し、getLoadTimeWeaver() メソッドをオーバーライドします。次の例では、ReflectiveLoadTimeWeaver を指定しています。

  • Java

  • Kotlin

@Configuration
@EnableLoadTimeWeaving
public class AppConfig implements LoadTimeWeavingConfigurer {

	@Override
	public LoadTimeWeaver getLoadTimeWeaver() {
		return new ReflectiveLoadTimeWeaver();
	}
}
@Configuration
@EnableLoadTimeWeaving
class AppConfig : LoadTimeWeavingConfigurer {

	override fun getLoadTimeWeaver(): LoadTimeWeaver {
		return ReflectiveLoadTimeWeaver()
	}
}

XML ベースの構成を使用する場合、完全修飾クラス名を <context:load-time-weaver/> エレメントの weaver-class 属性の値として指定できます。繰り返しますが、次の例では ReflectiveLoadTimeWeaver を指定しています。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="
		http://www.springframework.org/schema/beans
		https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context
		https://www.springframework.org/schema/context/spring-context.xsd">

	<context:load-time-weaver
			weaver-class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/>

</beans>

構成によって定義および登録された LoadTimeWeaver は、既知の名前 loadTimeWeaver を使用して Spring コンテナーから後で取得できます。LoadTimeWeaver は、Spring の LTW インフラストラクチャが 1 つ以上の ClassFileTransformers を追加するためのメカニズムとしてのみ存在することに注意してください。LTW を実行する実際の ClassFileTransformer は、ClassPreProcessorAgentAdapter (org.aspectj.weaver.loadtime パッケージから)クラスです。詳細については、ClassPreProcessorAgentAdapter クラスのクラスレベルの javadoc を参照してください。ウィービングが実際にどのように行われるかの詳細は、このドキュメントの範囲外です。

議論の余地がある設定の最後の属性が 1 つあります。aspectjWeaving 属性(XML を使用する場合は aspectj-weaving)です。この属性は、LTW を有効にするかどうかを制御します。3 つの可能な値のいずれかを受け入れます。属性が存在しない場合、デフォルト値は autodetect です。次の表は、3 つの可能な値をまとめたものです。

表 2: AspectJ ウィービング属性値
アノテーション値 XML 値 説明

ENABLED

on

AspectJ ウィービングはオンであり、必要に応じてロード時にアスペクトが織り込まれます。

DISABLED

off

LTW はオフです。ロード時にアスペクトは織り込まれません。

AUTODETECT

autodetect

Spring LTW インフラストラクチャが少なくとも 1 つの META-INF/aop.xml ファイルを検出できる場合、AspectJ ウィービングはオンになっています。それ以外の場合はオフです。これがデフォルト値です。

環境固有の構成

この最後のセクションには、アプリケーションサーバーや Web コンテナーなどの環境で Spring の LTW サポートを使用するときに必要な追加の設定と構成が含まれています。

トムキャット、JBoss、WildFly

Tomcat および JBoss/WildFly は、ローカルインストルメンテーションが可能な汎用アプリ ClassLoader を提供します。Spring のネイティブ LTW は、これらの ClassLoader 実装を利用して AspectJ ウィービングを提供できます。前述のように、ロード時ウィービングを有効にするだけで済みます。具体的には、-javaagent:path/to/spring-instrument.jar を追加するために JVM 起動スクリプトを変更する必要はありません。

JBoss では、アプリケーションが実際に起動する前にクラスをロードしないように、アプリケーションサーバーのスキャンを無効にする必要がある場合があることに注意してください。簡単な回避策は、次の内容の WEB-INF/jboss-scanning.xml という名前のファイルをアーティファクトに追加することです。

<scanning xmlns="urn:jboss:scanning:1.0"/>

汎用 Java アプリケーション

特定の LoadTimeWeaver 実装でサポートされていない環境でクラスインスツルメンテーションが必要な場合、JVM エージェントが一般的なソリューションです。そのような場合、Spring は InstrumentationLoadTimeWeaver を提供し、一般的な @EnableLoadTimeWeaving および <context:load-time-weaver/> セットアップによって自動検出される Spring 固有の(ただし非常に一般的な)JVM エージェント spring-instrument.jar を必要とします。

これを使用するには、次の JVM オプションを指定して、Spring エージェントで仮想マシンを起動する必要があります。

-javaagent:/path/to/spring-instrument.jar

これには、JVM 起動スクリプトの変更が必要であり、アプリケーションサーバー環境でこれを使用できない場合があることに注意してください(サーバーと操作ポリシーによって異なります)。ただし、スタンドアロン Spring Boot アプリケーションなど、JVM ごとに 1 つのアプリケーションデプロイの場合、通常、いずれの場合でも JVM セットアップ全体を制御します。