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
) の新しいインスタンスを構成します。XML で定義された 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 つは、「本質的に」です。ほとんどの場合、「新しいオブジェクトの初期化から戻った後」の正確なセマンティクスは問題ありません。このコンテキストでは、「初期化後」とは、オブジェクトが構築された後に依存関係が注入されることを意味します。これは、クラスのコンストラクター本体で依存関係を使用できないことを意味します。コンストラクターの本体が実行される前に依存関係を注入して、コンストラクターの本体で使用できるようにする場合は、次のように
AspectJ プログラミングガイド (英語) のこの付録 (英語) では、AspectJ のさまざまなポイントカット型の言語セマンティクスに関する詳細情報を見つけることができます。 |
これを機能させるには、アノテーション付き型を AspectJ ウィーバーでウィービングする必要があります。これを行うには、ビルド時の Ant または Maven タスクを使用するか (たとえば、AspectJ 開発環境ガイド (英語) を参照)、ロード時のウィービング (Spring Framework の AspectJ を使用したロード時ウィービングを参照) のいずれかを使用できます。AnnotationBeanConfigurerAspect
自体は、Spring によって構成する必要があります (新しいオブジェクトを構成するために使用される Bean ファクトリへの参照を取得するため)。関連する構成は次のように定義できます。
Java
Kotlin
XML
@Configuration
@EnableSpringConfigured
public class ApplicationConfiguration {
}
@Configuration
@EnableSpringConfigured
class ApplicationConfiguration
<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:spring-configured />
</beans>
アスペクトが構成される前に作成された @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>
1 | factory-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 の簡単な例を見てみましょう。次に、例で紹介した要素に関する詳細を説明します。完全な例については、Spring Framework に基づく 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 提供のエージェントを使用する場合は、次のものも必要です。
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
を次のように設定する必要があります。
Java
Kotlin
XML
@Configuration
@EnableLoadTimeWeaving
public class ApplicationConfiguration {
}
@Configuration
@EnableLoadTimeWeaving
class ApplicationConfiguration
<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
実装をまとめたものです。
ランタイム環境 | LoadTimeWeaver の実装 |
---|---|
| |
GlassFish (英語) で実行 (EAR デプロイに限定) |
|
Red Hat の JBoss AS (英語) または WildFly (英語) で実行 |
|
Spring |
|
フォールバック、基礎となる ClassLoader が一般的な規則に従うことを期待 (すなわち、 |
|
この表には、DefaultContextLoadTimeWeaver
の使用時に自動検出される LoadTimeWeavers
のみがリストされていることに注意してください。使用する LoadTimeWeaver
実装を正確に指定できます。
特定の LoadTimeWeaver
を構成するには、LoadTimeWeavingConfigurer
インターフェースを実装し、getLoadTimeWeaver()
メソッドをオーバーライドします (または XML の同等のものを使用します)。次の例では、ReflectiveLoadTimeWeaver
を指定します。
Java
Kotlin
XML
@Configuration
@EnableLoadTimeWeaving
public class CustomWeaverConfiguration implements LoadTimeWeavingConfigurer {
@Override
public LoadTimeWeaver getLoadTimeWeaver() {
return new ReflectiveLoadTimeWeaver();
}
}
@Configuration
@EnableLoadTimeWeaving
class CustomWeaverConfiguration : LoadTimeWeavingConfigurer {
override fun getLoadTimeWeaver(): LoadTimeWeaver {
return ReflectiveLoadTimeWeaver()
}
}
<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 つの可能な値をまとめたものです。
アノテーション値 | XML 値 | 説明 |
---|---|---|
|
| AspectJ ウィービングはオンであり、必要に応じてロード時にアスペクトが織り込まれます。 |
|
| LTW はオフです。ロード時にアスペクトは織り込まれません。 |
|
| Spring LTW インフラストラクチャが少なくとも 1 つの |
環境固有の構成
この最後のセクションには、アプリケーションサーバーや 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 セットアップ全体を制御します。