http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/ をテキトーに訳した。Spring Framework Reference Documentation 4.1.xのIII. Core Technologies 9. Aspect Oriented Programming with Springの9.5までをテキトーに訳した - kagamihogeの日記の続き。
では、どうすればよいのでしょうか? ベストなアプローチ(ここでいう「ベスト」は割とアバウトな感じ)はコードをリファクタリングして自己呼び出しが発生しないようにします。確かに、これは幾ばくかの作業を伴うものの、ベストであり、非侵襲的なアプローチです。他のやり方としては、極めてアレなモノがあるにはありますが、アレすぎて出来れば解説したくないです。これをやるとクラス内にSpring AOPとロジックが密結合してしまいます。
public class SimplePojo implements Pojo { public void foo() { // 動くけどマジでアレ ((Pojo) AopContext.currentProxy()).bar(); } public void bar() { // some logic... } }
上記のコードはSpring AOPと完全に密結合しており、更に、クラス自身がAOPコンテキストで使われることを知っている状態になります。また、プロキシ生成時に追加設定が必要となります。
public class Main { public static void main(String[] args) { ProxyFactory factory = new ProxyFactory(new SimplePojo()); factory.adddInterface(Pojo.class); factory.addAdvice(new RetryAdvice()); factory.setExposeProxy(true); Pojo pojo = (Pojo) factory.getProxy(); // this is a method call on the proxy! pojo.foo(); } }
最後に、AspectJは非プロキシベースのAOPフレームワークなので自己呼び出しの問題は無い点には注意が必要です。
9.7 Programmatic creation of @AspectJ Proxies
<aop:config>
か<aop:aspectj-autoproxy>
のどちらかでアスペクトを宣言する方法に加えて、ターゲットオブジェクトをアドバイスするプロキシをプログラム的に生成することも可能です。Spring AOP AOPの完全な詳細については、次チャプターを参照してください。ここでは@AspectJアスペクトのプロキシをプログラム的に生成する機能についての解説をするだけに止めます。
一つ以上の@AspectJアスペクトでアドバイスされるターゲットオブジェクトのプロキシを、org.springframework.aop.aspectj.annotation.AspectJProxyFactory
を使うことで、生成できます。このクラスの基本的な使い方は非常にシンプルで、以下に例を示します。すべての情報についてはjavadocを参照してください。
// 指定のターゲットオブジェクトのプロキシを生成可能なファクトリを生成 AspectJProxyFactory factory = new AspectJProxyFactory(targetObject); // アスペクトを追加。アスペクトのクラスは@AspectJアスペクトが必須。 // 別のアスペクトで必要であればこのクラスを呼んでも構わない。 factory.addAspect(SecurityManager.class); // you can also add existing aspect instances, the type of the object supplied must be an @AspectJ aspect // 既存のアスペクトインスタンスを追加することも可能。 // 与えるオブジェクトの型は@AspectJアスペクトが必須。 factory.addAspect(usageTracker); // プロキシオブジェクトを取得。 MyInterfaceType proxy = factory.getProxy();
9.8 Using AspectJ with Spring applications
このチャプターではこれまでのところSpring AOPについてだけ解説してきました。このセクションでは、代わりにAspectJコンパイラ/ウィーバーの使用法について解説し、あるいは、Spring AOP単独で提供する機能の範囲を超える必要がある場合についても触れていきます。
Springは小さなAspectJアスペクトライブラリを搭載しており、これの実体はspring-aspects.jar
として配布されておりスタンドアローンで利用可能です。アスペクトを使うにはクラスパスにこのライブラリを追加します。Section 9.8.1, “Using AspectJ to dependency inject domain objects with Spring”とSection 9.8.2, “Other Spring aspects for AspectJ”ではこのライブラリの中身についてと使用法について解説します。Section 9.8.3, “Configuring AspectJ aspects using Spring IoC”ではAspectJコンパイラでウィーブしたAspectJアスペクトをDIする方法について解説します。最後に、Section 9.8.4, “Load-time weaving with AspectJ in the Spring Framework”ではAspectJでSpringアプリケーションのロード時ウィービング(loadtime weaving)について触れます。
9.8.1 Using AspectJ to dependency inject domain objects with Spring
Springコンテナはアプリケーションコンテキストで定義したビーンのインスタンス化と設定を行います。また、適用した設定を含むビーン定義の指定名でpre-existingオブジェクトを設定するのにビーンファクトリを参照可能です*1。spring-aspects.jar
のアノテーション駆動アスペクトはこの機能を活かして任意のオブジェクト(any object)のDIを可能にします。この機能は、コンテナの制御外(outside of the control of any container)で生成されたオブジェクトで使うことを意図しています。ドメインオブジェクトはこのカテゴリに当てはまる事があり、その理由はnew
演算子でプログラム的に生成したり、DBクエリの結果としてORMツールが生成したりするためです。
@Configurable
アノテーションは、あるクラスにSpringの設定資格を与えます。最も単純なケースでは単なるマーカーアノテーションとして使われます。
package com.xyz.myapp.domain; import org.springframework.beans.factory.annotation.Configurable; @Configurable public class Account { // ... }
この方法でマーカーインタフェースとして使う場合、Springは、完全修飾クラス名(com.xyz.myapp.domain.Account
)と同じ名前のビーン定義(通常プロトタイプスコープ)でアノテーションを付与した型(上記の場合Account
)の新規インスタンスを設定します。ビーンのデフォルト名はその型の完全修飾クラス名なので、プロトタイプ定義を宣言する簡易な方法はid
属性を削除します。
<bean class="com.xyz.myapp.domain.Account" scope="prototype"> <property name="fundsTransferService" ref="fundsTransferService"/> </bean>
プロトタイプビーン定義の名前を明示的に指定したい場合、アノテーションに直接書きます。
package com.xyz.myapp.domain; import org.springframework.beans.factory.annotation.Configurable; @Configurable("account") public class Account { // ... }
これでSpringは名前付きビーン定義"account
"を認識し、新規のAccount
インスタンスを設定するための定義として使います。
また、個別のビーン定義指定を避けるためにオートワイヤを使用可能です。そのためには@Configurable
のautowire
プロパティでオートワイヤ設定を適用します。by typeかby nameのオートワイヤにそれぞれ@Configurable(autowire=Autowire.BY_TYPE)
か@Configurable(autowire=Autowire.BY_NAME
を指定します。もしくは、Spring 2.5以降ではフィールドやメソッドレベルに@Autowired
か@Inject
を使用して@Configurable
ビーンをアノテーションで明示的にDIすることを推奨します(詳細についてはSection 5.9, “Annotation-based container configuration”を参照して下さい)。
dependencyCheck
属性により新規生成と設定をしたオブジェクト参照の依存性チェックをSpringでは可能です(例:@Configurable(autowire=Autowire.BY_NAME,dependencyCheck=true)
)。この属性をtrueにすると、Springは設定後に(プリミティブかコレクションでは無い)全プロパティが設定されていることを検証します。
Using the annotation on its own does nothing of course. It is the AnnotationBeanConfigurerAspect in spring-aspects.jar that acts on the presence of the annotation.本質的にはアスペクトは、"@Configurable
を付与した型の新規オブジェクト初期化がreturnした後、Springを使用してアノテーションのプロパティに従いながら新規生成オブジェクトを設定する"、というものです。このコンテキストにおける、初期化(initialization)とは、新規にインスタンス化されたオブジェクト(例えばnew
演算子でインスタンス化したオブジェクト)、同様にデシリアライズされるSerializable
オブジェクト(readResolveなど)、を指します。
上記パラグラフのキーフレーズの一つはin essenceです。ほとんどの場合で、新規オブジェクト初期化からreturnした後(after returning from the initialization of a new object)は問題無く動作します。このコンテキストにおける、初期化後(after initializationとはオブジェクトが生成された後に依存性が注入されることを意味します。つまり、クラスのコンストラクタでは依存性は利用不可能、ということです。もしコンストラクタ実行前に依存性を注入したい場合、@Configurable
宣言を@Configurable(preConstruction=true)
のように定義します。AspectJの各種のポイントカットタイプの言語セマンティクスについてはAspectJ Programming Guideのin this appendixを参照してください。
アノテーションを付与した型を動作させるにはAspectJウィーバーを使うことが必須です。ビルド時AntやMavenタスク(AspectJ Development Environment Guideの例を参照)やロード時ウィービング(Section 9.8.4, “Load-time weaving with AspectJ in the Spring Framework”)を参照)を使用可能です。AnnotationBeanConfigurerAspect
自体はSpringで設定する必要があります(新規オブジェクト設定に使われるビーンファクトリーの参照を取得するため)。Javaコンフィグレーションを使う場合には単に@Configuration
に@EnableSpringConfigured
を追加します。
@Configuration @EnableSpringConfigured public class AppConfig { }
XMLコンフィグレーションの場合、Springcontext
名前空間がcontext:spring-configured
要素を定義しています。
<context:spring-configured/>
アスペクト設定前に生成される@Configurable
オブジェクトのインスタンスはデバッグログに問題発生のメッセージを出力してオブジェクトの設定は行いません。
例としては、Springによる初期化時にドメインオブジェクトを生成するSpringコンフィグレーションのビーンがそうなる可能性があります。この場合では、ビーンがアスペクトに依存することをマニュアルで指定するのに"depends-on"
ビーン属性を使用可能です。
<bean id="myService" class="com.xzy.myapp.service.MyService" depends-on="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect"> <!-- ... --> </bean>
bean configurer aspect経由で@Configurable
処理をアクティベートしてはいけません。特に、コンテナに通常のSpringビーンとして登録したビーンクラスに@Configurable
を使っていないことを確認してください。でないと、コンテナで一度、アスペクトで一度の二重初期化となります。
Unit testing @Configurable objects
@Configurable
の目的の一つはハードコード参照の関連付け無しにドメインオブジェクトのユニットテストを独立させることです。AspectJが@Configurable
をウィーブしていない場合、アノテーションはユニットテスト時には影響を及ぼさず、テスト対象オブジェクトのプロパティ参照にモックやスタブをセットして普通のクラスのように実行できます。AspectJが@Configurable
をウィーブしている場合でも、普通のクラスのようにコンテナ外でユニットテストは可能ですが、Springが設定していない@Configurable
を生成する場合は警告メッセージを出力します。
Working with multiple application contexts
@Configurable
の実装に使われるAnnotationBeanConfigurerAspect
はAspectJのシングルトンアスペクトです。シングルトンアスペクトのスコープはstatic
メンバのスコープと同じで、型定義を行うクラスローダーごとに一つのアスペクトインスタンスがある、という事です。つまり、同一クラスローダー階層内に複数のアプリケーションコンテキストを定義する場合、@EnableSpringConfigured
ビーンを定義する場所とクラスパス上のspring-aspects.jar
の配置場所を考慮する必要があります。
共通のビジネスサービスが定義してある親アプリケーションコンテキストを共有する一般的なSpring Webアプリケーションの設定を考えます。これは、固有の定義を持つサーブレットごとに一つの子アプリケーションコンテキストとなります。こうしたすべてのコンテキストは同一クラスローダー階層内に共存するので、AnnotationBeanConfigurerAspect
はその複数コンテキストの中から一つの参照だけを保持可能です。この場合、推奨するのは共有(親の)アプリケーションコンテキストに@EnableSpringConfigured
ビーンを定義することです。ここにはドメインオブジェクトにインジェクトしたいサービスを定義します。結果として、@Configurableで子コンテキストに定義したビーン参照でドメインオブジェクトを設定することは出来ません(そう滅多にやってみたいと思うことは無いでしょうが*2)。
同一コンテナ内に複数のwebアプリケーションをデプロイする場合、各webアプリケーションが個々のクラスローダーでspring-aspects.jar
をロードすることを確認してください(例えば、'WEB-INF/lib'
にspring-aspects.jar
を配置する)。もしspring-aspects.jar
がコンテナの広いクラスパスにだけ追加されている場合(つまり共有親クラスローダーによるロード)、全webアプリケーションが同一のアスペクトインスタンスを共有することになり、恐らくそうしたいと望むユーザはいないでしょう。
9.8.2 Other Spring aspects for AspectJ
@Configurable
アスペクトの他に、spring-aspects.jar
は、型レベルと@Transactional
アノテーションを付与するメソッドレベルで、Springトランザクション管理を行うのに使われるAspectJアスペクトを持ちます。これは主として、Springコンテナ外でSpring Frameworkのトランザクションサポートを使いたいユーザ向けのものです。
@Transactional
をインターセプトするアスペクトはAnnotationTransactionAspect
です。このアスペクトを使う場合、実装クラス(および/またはクラス内のメソッド)にアノテーション付与が必須であり、クラスが実装するインタフェースにではありません。Javaのルールであるインタフェースのアノテーションは継承されない、にAspectJは従います。
クラスレベルの@Transactional
は、そのクラスのpublicなオペレーション実行のデフォルトトランザクションセマンティクスを指定します。
クラス内のメソッドレベルの@Transactional
はクラスレベルのアノテーション(もしあれば)で指定されるデフォルトトランザクションセマンティクスをオーバーライドします。public
・protected
・デフォルトの可視性のメソッドはすべてアノテーションの付与が可能です。デフォルトとprotected
メソッドに直接アノテーションを付与することは、そうしたメソッドの実行でトランザクション境界を得る唯一の方法です。
Springコンフィグレーションとトランザクション管理を使いたいがアノテーションの使用を望まないAspectJプログラマの場合、spring-aspects.jar
には自前のポイントカット定義を拡張可能なabstract
アスペクトがあります。詳細についてはAbstractBeanConfigurerAspect
とAbstractTransactionAspect
のソースを参照してください。例として、以下の引用は、完全修飾クラス名にマッチするプロトタイプビーン定義のドメインモデルに定義したオブジェクトの全インスタンスを設定するアスペクトの書き方、について示しています。
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(..)) && SystemArchitecture.inDomainModel() && this(beanInstance); }
9.8.3 Configuring AspectJ aspects using Spring IoC
SpringアプリケーションでAspectJのアスペクトを使う場合、Springでそうしたアスペクトの設定が可能だろうと期待や推測するのは自然なことです。AspectJランタイム自身にはアスペクト生成の役割があり、Spring経由でAspectJが生成したアスペクトの設定方法はアスペクトが使うAspectJのインスタンス化モデル(per-xxx
節)に依存します。
AspectJアスペクトの大半はsingletonアスペクトです。このアスペクトの設定はとても簡単です。アスペクト型を参照するビーン定義をいつも通りに作成し、'factory-method="aspectOf"'
ビーン属性を含めます。これにより、インスタンス生成を試行するのではなく、AspectJに問い合わせることでアスペクトのインスタンスをSpringが取得するようになります。
<bean id="profiler" class="com.xyz.profiler.Profiler" factory-method="aspectOf"> <property name="profilingStrategy" ref="jamonProfilingStrategy"/> </bean>
非シングルトンアスペクトの設定は困難です。ただし、アスペクトインスタンスの設定にspring-aspects.jar
の@Configurable
を使用してAspectJランタイムが一度でもアスペクトインスタンスを生成し、プロトタイプビーン定義を生成するのであれば可能です。もし、AspectJでウィーブしたい@AspectJがあり、また、Spring AOPを使いたい@AspectJアスペクトが他にあり、それらのアスペクトをすべてSpringで設定している場合、設定で定義する@AspectJアスペクトのサブセットである、Spring AOP @AspectJ autoproxyingが、autoproxyingを使うよう設定する必要があります*3。これには<aop:aspectj-autoproxy/>
に一つ以上の<include/>
要素を含めます。個々の<include/>
にはnameパターンを指定し、パターンの少なくとも一つにマッチする名前のビーンのみがSpring AOP autoproxy設定に使われます。
<aop:aspectj-autoproxy> <aop:include name="thisBean"/> <aop:include name="thatBean"/> </aop:aspectj-autoproxy>
<aop:aspectj-autoproxy/>
要素の名称に惑わされないようにして下さい。これを使うことでSpring AOPのプロキシが生成されるようになります。アスペクト宣言に@AspectJスタイルがここでは使われていますが、AspectJランタイムは全くの無関係です。
9.8.4 Load-time weaving with AspectJ in the Spring Framework
ロード時ウィービング(Load-time weaving (LTW))は、JVMへのクラスロード時に行われるので、AspectJアスペクトをアプリケーションクラスファイルにウィービングする処理に関係します。このセクションでは、設定と、Spring Frameworkの特定のコンテキストでのLTW使用について解説します。AspectJを使うLTWの設定(Springは無関係)、LTWの仕様の詳細については、LTW section of the AspectJ Development Environment Guideを参照してください。
Spring FrameworkがAspectJ LTWにもたらす価値とは、ウィービング処理に細かい制御が可能になることです。素のAspectJ LTWはJava (5+)エージェントを使用しており、これはJVM開始時にVM引数を指定することで有効になります。JVMという広いレベルの設定は大抵の場合で有用ですが、粗すぎる場合もあります。Springが有効化するLTWは、LTWをクラスローダーベースで設定可能であり、これは明らかにより細粒度の設定で、単一JVMの複数アプリケーション(single-JVM-multiple-application)環境(一般的なアプリケーションサーバ環境など)で有用です。
また、特定の環境では、これによりロード時ウィービングがアプリケーションサーバの起動スクリプトに一切の変更を入れることなく可能となります。これには-javaagent:path/to/aspectjweaver.jar
もしくは(このセクションで後述する)-javaagent:path/to/org.springframework.instrument-{version}.jar
(以前の名称はspring-agent.jar
)を追加する必要があります。起動スクリプトなど一般的には環境設定に責任を持つ管理者に依頼する代わりに、開発者はロード時ウィービングを有効化するためアプリケーションコンテキストを構成するいくつかのファイルを修正するだけです。
売り文句はこれくらいにして、Springを使用したAspectJ LTWのクイックスタートに入ります。それに続いて、サンプルで使われいる要素の詳細について解説します。すべてのサンプルコードについてはPetclinic sample applicationを参照してください。
A first example
サンプルの仮定として、あるアプリケーション開発者がシステムのパフォーマンス問題の原因調査のタスクを振られた、という状況を置きます。いきなりプロファイリングツールを入れるよりも、シンプルなプロファイリングのアスペクトを有効化するのが良いでしょう。このアスペクトにより手早くパフォーマンスのメトリクスを得られるので、そのあと、より小回りの利くプロファイリングツールを特定エリアに適用します。
ここに示す例はXMLスタイルの設定ですが、Javaコンフィグレーションで@AspectJを使い設定することも可能です。具体的には、<context:load-time-weaver/>
の代わりに@EnableLoadTimeWeaving
を使います(詳細は以下を参照)。
以下がプロファイリングのアスペクトです。何てことはなく、極めてテキトーに時間を計測するだけのプロファイラで、アスペクト宣言に@AspectJスタイルを使用しています。
package foo; 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 * foo..*.*(..))") public void methodsToBeProfiled(){} }
META-INF/aop.xml
ファイルを作成する必要があり、上記のProfilingAspect
をクラスにウィーブすることをAspectJウィーバーに指定します。ファイルは慣習的に、META-INF/aop.xml
という名前でJavaクラスパスに配置するのがAspectJスタンダードです。
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd"> <aspectj> <weaver> <!-- アプリケーション固有のパッケージのクラスにのみウィーブする --> <include within="foo.*"/> </weaver> <aspects> <!-- 以下のアスペクトのみウィーブする --> <aspect name="foo.ProfilingAspect"/> </aspects> </aspectj>
次にSpring固有の設定に移ります。まずLoadTimeWeaver
を設定する必要があります(詳細は後述)。ロード時ウィーバーは、本質的には、アプリケーションのクラスに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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- プロファイルしたいメソッドを持つサービスオブジェクト --> <bean id="entitlementCalculationService" class="foo.StubEntitlementCalculationService"/> <!-- ロード時ウィービングの有効化 --> <context:load-time-weaver/> </beans>
これで必要なすべてのアーティファクトは揃いました。アスペクトはMETA-INF/aop.xml
にあるので、あとはSpringの設定です。LTWの動作をmain(..)
で動かす簡単なコードを書いてみます。
package foo; import org.springframework.context.support.ClassPathXmlApplicationContext; public final class Main { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml", Main.class); EntitlementCalculationService entitlementCalculationService = (EntitlementCalculationService) ctx.getBean("entitlementCalculationService"); // 以下のメソッド実行のaroundにプロファイリングのアスペクトがウィーブされる entitlementCalculationService.calculateEntitlement(); } }
もう一つ最後にやることがあります。この章の導入部で、SpringではClassLoader
ごとに選択的にLTWを有効化可能である、と述べました。それは真実です。しかし、このサンプルでは、LTWの有効化に(Spring付属の)Javaエージェントを使います。上記のMain
クラスを実行するコマンドラインは以下になります。
java -javaagent:C:/projects/foo/lib/global/spring-instrument.jar foo.Main
-javaagent
はJVMで動作するプログラムをinstrumentするためのエージェントの指定と有効化のためのフラグです。Spring Frameworkにはそうしたエージェントが含まれており、spring-instrument.jar
パッケージ内のInstrumentationSavingAgent
は、上述のサンプルでは-javaagent
引数の値として渡しています。
Main
の出力結果は以下のようになります。(calculateEntitlement()
の実装にThread.sleep(..)
を入れ込んでいるので、プロファイラは0ミリ秒ではない何らかの適当な値をキャプチャしています。01234
ミリ秒はAOPを導入したことによるオーバーヘッドではないです(迫真))
Calculating entitlement StopWatch ProfilingAspect: running time (millis) = 1234 ------ ----- ---------------------------- ms % Task name ------ ----- ---------------------------- 01234 100% calculateEntitlement
LTWはAspectJの影響下にあるため、Springビーンのアドバイスに制限はありません。サンプルのMain
を少々変えた以下のコードは同じ結果となります。
package foo; import org.springframework.context.support.ClassPathXmlApplicationContext; public final class Main { public static void main(String[] args) { new ClassPathXmlApplicationContext("beans.xml", Main.class); EntitlementCalculationService entitlementCalculationService = new StubEntitlementCalculationService(); // 以下のメソッド実行のaroundにプロファイリングのアスペクトがウィーブされる entitlementCalculationService.calculateEntitlement(); } }
上記のプログラムではSpringコンテナはただ単に起動しているだけの点に注意してください。この状態でStubEntitlementCalculationService
の新規インスタンスを生成することは完全にSpringコンテキスト外の出来事ですが、プロファイリングのアドバイスは依然としてウィーブされます。
この例はさすがに単純化が過ぎます。ただし、SpringのLTWサポートの基礎部分は上記のサンプルにすべて含まれており、このセクションの続きでは設定の裏側と使用法の詳細について解説します。
このサンプルのProfilingAspect
は基本的ではありますが極めて有用でもあります。開発者が開発時に使用可能なアスペクトの例として優れており、UATや本番環境にデプロイするアプリケーションのビルドと簡単に切り離せます。
Aspects
LTWで使うアスペクトはAspectJアスペクトで作成してください。AspectJ言語もしくは@AspectJスタイルで作成します。つまり、アスペクトはAspectJかつSpring AOPアスペクトの両方で妥当です。また、コンパイル済みのアスペクトクラスはクラスパス上で利用可能となっている必要があります。
META-INF/aop.xml
AspectJ LTWのインフラは一つ以上のMETA-INF/aop.xml
ファイルで設定され、それらのファイルはJavaクラスパス上に配置します(直接配置するか、通常はjarファイル中に配置)。
ファイルの構造と内容の詳細についてはAspectJ reference documentationにあり、 興味のある方はこちらを参照してください。(このセクションは「さわり」に過ぎず、aop.xml
は100% AspectJ由来のものです。これの適用に関してはSpring固有の情報やセマンティクスは何もなく、よって、これ以上付け加えて言うことはありません。AspectJの開発者のドキュメントを焼き直すのもどうかと思うので、ここでは単にそちらへのリンクを張るだけとします。)
Required libraries (JARS)
最小構成でAspectJ LTWをSpring Frameworkでサポートするには以下のライブラリが必要です。
spring-aop.jar
(version 2.5 or later, plus all mandatory dependencies)aspectjweaver.jar
(version 1.6.8 or later)
Spring-provided agent to enable instrumentationを使う場合は以下も必要です。
spring-instrument.jar
Spring configuration
Spring LTWの中核コンポーネントはLoadTimeWeaver
インタフェース(org.springframework.instrument.classloading
パッケージに在る)で、これの実装が多数Springに含まれています。LoadTimeWeaver
は実行時にClassLoader
に一つ以上のjava.lang.instrument.ClassFileTransformers
を追加する役割を持ちます。ClassFileTransformersは対象アプリケーションのクラスファイル変換を行い、そのうちの一つがアスペクトのLTWになります*4。
もしruntime class file transformationに馴染みが無ければ、本ドキュメントを読み進める前にjava.lang.instrument
パッケージのjavadoc APIドキュメントを読むことを推奨します。さほど大きいドキュメントでは無いので、大した手間になりません。中核となるインタフェースとクラスは、少なくともこのセクションを通じて解説します。
特定のApplicationContext
用にLoadTimeWeaver
を設定するのは簡単です(SpringコンテナにはApplicationContext
を必ず使うように注意してください。LTWはBeanFactoryPostProcessors
を使うのでBeanFactory
では不十分です。)。
Spring FrameworkのLTWサポートを有効化するには、LoadTimeWeaver
を設定します。これには@EnableLoadTimeWeaving
を通常は使います。
@Configuration @EnableLoadTimeWeaving public class AppConfig { }
もしくは、XMLコンフィグレーションの場合、<context:load-time-weaver/>
要素を使います。この要素はcontext
名前空間に定義されています。
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:load-time-weaver/> </beans>
上記の設定により、自動的にLTW固有のLoadTimeWeaver
やAspectJWeavingEnabler
などのインフラビーンがいくつか定義および設定されます。デフォルトのLoadTimeWeaver
はDefaultContextLoadTimeWeaver
で、このクラスは自動検出されたLoadTimeWeaver
のデコレートを試行します。自動検出されるLoadTimeWeaver
の実際の型は実行環境に依存します(以下の表を参照)。
Table 9.1. DefaultContextLoadTimeWeaver LoadTimeWeavers
|Runtime Environment|LoadTimeWeaver
implementation|
|Running in BEA’s Weblogic 10|WebLogicLoadTimeWeaver
|
|Running in IBM WebSphere Application Server 7|WebSphereLoadTimeWeaver
|
|Running in GlassFish|GlassFishLoadTimeWeaver
|
|Running in JBoss AS|JBossLoadTimeWeaver
|
|SpringのInstrumentationSavingAgent
(java -javaagent:path/to/spring-instrument.jar)で開始した場合|InstrumentationLoadTimeWeaver
|
|Fallback, expecting the underlying ClassLoader to follow common conventions (e.g. applicable to TomcatInstrumentableClassLoader
and Resin)|ReflectiveLoadTimeWeaver
|
DefaultContextLoadTimeWeaver
を使う場合にはLoadTimeWeavers
が自動検出されることに注意してください。なお、特定のLoadTimeWeaver
実装を指定することも可能です。
特定のLoadTimeWeaver
を指定するにはLoadTimeWeavingConfigurer
インタフェースをJavaコンフィグレーションで実装してgetLoadTimeWeaver()
をオーバーライドします。
@Configuration @EnableLoadTimeWeaving public class AppConfig implements LoadTimeWeavingConfigurer { @Override public LoadTimeWeaver getLoadTimeWeaver() { return new ReflectiveLoadTimeWeaver(); } }
XMLコンフィグレーションの場合は完全修飾クラス名を<context:load-time-weaver/>
要素のweaver-class
属性に指定します。
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:load-time-weaver weaver-class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/> </beans>
設定で定義と登録したLoadTimeWeaver
はwell-known名loadTimeWeaver
でSpringコンテナから参照可能となります。先の解説で、LoadTimeWeaver
は一つ以上のClassFileTransformers
を追加するためのSpring LTWインフラ用のメカニズムとして存在する、と述べました。LTWを行う実際のClassFileTransformer
はClassPreProcessorAgentAdapter
(org.aspectj.weaver.loadtime
パっケージに存在)です。詳細についてはClassPreProcessorAgentAdapter
のクラスレベルのjavadocを参照してください。ウィービングが実際にどう行われるかの詳細についてはこのセクションの範囲外です。
最後に、もう一つの設定の属性、aspectjWeaving
(XMLではaspectj-weaving
)、について解説します。この属性はただ単にLTWが有効か無効化を制御します。三種類の値が指定可能で、以下の通りです。属性が指定されていない場合のデフォルト値はautodetect
です。
Table 9.2. AspectJ weaving attribute values
|Annotation Value|XML Value|Explanation|
|ENABLED
|on
|AspectJウィービング有効化。アスペクトは必要に応じてロード時にウィーブされる|
|DISABLED
|off
|LTW無効化。アスペクトはロードにウィーブされない。|
|AUTODETECT
|autodetect
|Spring LTWインフラが少なくとも一つのMETA-INF/aop.xml
を参照可能な場合、AspectJウィービングが有効化。そうでない場合無効化。デフォルト値|
Environment-specific configuration
最後のセクションではアプリケーションサーバやwebコンテナなどの各環境でSpring LTWサポートを使用する場合に必要となる設定について解説します。
Tomcat
Apache Tomcatのデフォルトのクラスローダはclass transformationをサポートしていないため、Springはそのための拡張実装を提供しています。TomcatInstrumentableClassLoader
はTomcat 5.0以上で動作するローダーで、以下のようにすることでwebアプリケーション毎に個別に登録が可能です。
- Tomcat 6.0.x 以上
org.springframework.instrument.tomcat.jar
を$CATALINA_HOME/libにコピーする($CATALINA_HOMEはTomcatインストールのroot)。- Tomcatに(デフォルトではない)カスタムクラスローダーを使うよう指定するにはwebアプリケーションコンテキストファイルを編集します。
<Context path="/myWebApp" docBase="/my/webApp/location"> <Loader loaderClass="org.springframework.instrument.classloading.tomcat.TomcatInstrumentableClassLoader"/> </Context>
Apache Tomcat (6.0+)は複数のコンテキストロケーションをサポートします。
- server configuration file - $CATALINA_HOME/conf/server.xml
- default context configuration - $CATALINA_HOME/conf/context.xml - デプロイされたすべてのwebアプリケーションに影響を及ぼす。
- webアプリケーションごとの設定は、サーバーサイド$CATALINA_HOME/conf/[enginename]/[hostname]/[webapp]-context.xmlもしくはMETA-INF/context.xmlのweb-appアーカイブ内に組み込みます。
組み込みweb-app別設定(embedded per-web-app configuration)スタイルを推奨します。そのアプリケーションだけがカスタムクラスローダーを使い、サーバーコンフィグレーションの変更が必要ないためです。利用可能なコンテキストロケーションについての詳細はTomcat 6.0.xdocumentationを参照してください。
または、Springが提供する汎用VMエージェントをTomcatの起動スクリプトに指定することも考慮に入れて下さい。これによりデプロイされたすべてのwebアプリケーションでinstrumentationが利用可能となり、ClassLoaderに関わらず動作します。
WebLogic, WebSphere, Resin, GlassFish, JBoss
WebLogic Server (version 10以上)の最新バージョン、IBM WebSphere Application Server (version 7以上)、Resin (3.1以上) 、JBoss (6.xもしくはそれ以上)は、local instrumentation機能を持つClassLoaderを提供しています。SpringのネイティブLTWはAspectJ のウィービングをするのにそうしたClassLoaderを使用します。前述したようなロード時ウィービングを有効化することでLTWが使用可能となります。具体的には、起動スクリプトに-javaagent:path/to/spring-instrument.jar
を追加する必要はありません。
なお、GlassFish instrumentation-capable ClassLoaderはEAR環境でのみ利用可能です。GlassFish webアプリケーションでは、上述したTomcatのセットアップに従ってください。
Note that on JBoss 6.x, the app server scanning needs to be disabled to prevent it from loading the classes before the application actually starts. A quick workaround is to add to your artifact a file named WEB-INF/jboss-scanning.xml with the following content:
<scanning xmlns="urn:jboss:scanning:1.0"/>
Generic Java applications
既存のLoadTimeWeaver
がサポートしないか、環境のclass instrumentationがサポートしない場合、JDKエージェントが唯一の解決策となります。そうした場合には、Springは、org.springframework.instrument-{version}.jar
(以前の名称はspring-agent.jar
)でSpring固有(固有だが汎用的)のVMエージェントを要求するInstrumentationLoadTimeWeaver
を提供しています。
これを使うには、以下のようなJVMオプションを指定することで、SpringエージェントでVMを開始することが必須となります。
-javaagent:/path/to/org.springframework.instrument-{version}.jar
注意点として、上記はVM起動スクリプトの修正が必要となり、アプリケーションサーバ環境での使用は動作を阻害する可能性があります(オペレーションポリシー依存)。また、JDKエージェントはVM全体をinstrumentするので高コストになる可能性があります。
パフォーマンス上の理由により、上記の設定が推奨されるのは、ターゲット環境(Jettyなど)が固有のLTWを持たない(もしくはサポートしない)場合に限ります。
9.9 Further Resources
AspectJの情報はAspectJ websiteにあります。
Adrian Colyerらの著作Eclipse AspectJ(Addison-Wesley, 2005)では、包括的な解説とAspectJ言語のリファレンスが書かれています。
Ramnivas Laddadの著作 AspectJ in Action(Manning, 2003)は強くオススメする書籍で、AspectJの本ではあるものの、汎用的なAOPについても詳しい解説がされています。
*1:It is also possible to ask a bean factory to configure a pre-existing object given the name of a bean definition containing the configuration to be applied. が原文。微妙な訳
*2:probably not something you want to do anyway!が原文。
*3:誤訳してるかも…
*4:which opens the door to all manner of interesting applications, one of which happens to be the LTW of aspects.が原文。