kagamihogeの日記

kagamihogeの日記です。

Spring Framework Reference Documentation 4.1.xのIII. Core Technologies 9. Aspect Oriented Programming with Springの9.6以降をテキトーに訳した

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オブジェクトを設定するのにビーンファクトリを参照可能です*1spring-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インスタンスを設定するための定義として使います。

また、個別のビーン定義指定を避けるためにオートワイヤを使用可能です。そのためには@Configurableautowireプロパティでオートワイヤ設定を適用します。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 Guidein 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の実装に使われるAnnotationBeanConfigurerAspectAspectJのシングルトンアスペクトです。シングルトンアスペクトのスコープは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はクラスレベルのアノテーション(もしあれば)で指定されるデフォルトトランザクションセマンティクスをオーバーライドします。publicprotected・デフォルトの可視性のメソッドはすべてアノテーションの付与が可能です。デフォルトとprotectedメソッドに直接アノテーションを付与することは、そうしたメソッドの実行でトランザクション境界を得る唯一の方法です。

Springコンフィグレーションとトランザクション管理を使いたいがアノテーションの使用を望まないAspectJプログラマの場合、spring-aspects.jarには自前のポイントカット定義を拡張可能なabstractアスペクトがあります。詳細についてはAbstractBeanConfigurerAspectAbstractTransactionAspectのソースを参照してください。例として、以下の引用は、完全修飾クラス名にマッチするプロトタイプビーン定義のドメインモデルに定義したオブジェクトの全インスタンスを設定するアスペクトの書き方、について示しています。

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 FrameworkAspectJ 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

-javaagentJVMで動作するプログラムを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固有のLoadTimeWeaverAspectJWeavingEnablerなどのインフラビーンがいくつか定義および設定されます。デフォルトのLoadTimeWeaverDefaultContextLoadTimeWeaverで、このクラスは自動検出されたLoadTimeWeaverのデコレートを試行します。自動検出されるLoadTimeWeaverの実際の型は実行環境に依存します(以下の表を参照)。

Table 9.1. DefaultContextLoadTimeWeaver LoadTimeWeavers

|Runtime Environment|LoadTimeWeaverimplementation| |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を行う実際のClassFileTransformerClassPreProcessorAgentAdapterorg.aspectj.weaver.loadtimeパっケージに存在)です。詳細についてはClassPreProcessorAgentAdapterのクラスレベルのjavadocを参照してください。ウィービングが実際にどう行われるかの詳細についてはこのセクションの範囲外です。

最後に、もう一つの設定の属性、aspectjWeavingXMLでは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はそのための拡張実装を提供しています。TomcatInstrumentableClassLoaderTomcat 5.0以上で動作するローダーで、以下のようにすることでwebアプリケーション毎に個別に登録が可能です。

  • Tomcat 6.0.x 以上
  • org.springframework.instrument.tomcat.jar$CATALINA_HOME/libにコピーする($CATALINA_HOMETomcatインストールの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.が原文。