ロギングをAOPとして設定する
リスト1は、Springを用いてコンポーネントとサービスを結び付ける構成ファイルです。
<bean id="maintenanceServiceBean" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="proxyInterfaces"> <value>IMaintenanceService</value> </property> <property name="target"> <ref bean="maintenanceServiceImpl"/> </property> <property name="interceptorNames"> <list> <value>loggingInterceptor</value> </list> </property> </bean> <bean id=" maintenanceServiceImpl " class="MaintenanceService"> <bean id=" loggingInterceptor" class="LoggingInterceptor"/>
SpringにビルトインされたAOPインフラストラクチャは、「org.springframework.aop.*」パッケージで定義されています。SpringはFactoryBeanという概念をサポートします。これは、与えられたクラス上でのプレーンなnewInstance()
呼び出しというよりも、ファクトリの結果として返される特別なタイプのBeanです。つまり、リスト1の例でProxyFactoryBeanのインスタンスは生成されず、SpringがProxyFactoryBean
オブジェクトと相談してmaintenanceServiceBean
オブジェクトを要求します。これにどんな意味があるのでしょう。このFactoryBeanという概念は、SpringがBeanをラップし、(動的プロキシやCGLIBなどの内部ツールを用いて)そのBeanのためのプロキシを生成し、それがメソッド呼び出しに対してアドバイスを実行するというものです。アドバイスの実行は、当該メソッドがジョインポイントであるとポイントカットが表明したときに行われます(ポイントカットが定義されている場合)。ジョインポイントとは、メソッド呼び出しや例外スローなどのプログラム実行ポイントのことです。今回のサンプルに即して、一連の流れを説明しておきましょう。maintenanceServiceBean
オブジェクトが要求されると、コンテナは、ターゲットのmaintenanceServiceImpl
に対する呼び出しをインターセプトするオブジェクトを返します。これにより、メソッド呼び出しを先に処理するチャンスがLoggingInterceptor
に与えられます。LoggingInterceptor
の処理が完了すると、制御はmaintenanceServiceImpl
に戻り、元のタスクが実行されます。
interceptorNames
プロパティは、プロキシファクトリBeanがプロキシ対象のBeanにどのアドバイザ(またはアドバイスオブジェクト)を適用するかを定義するものです。このプロパティのリストでは順番に意味があります。このリスト内での順番によって、Beanの開始時および終了時に呼び出されるアドバイザまたはアドバイスオブジェクトの順番が決まります。リスト1のアドバイスオブジェクトはloggingInterceptor
です。これをプロキシ対象のmaintenanceServiceBean
に適用する必要があります。
リスト2は、LoggingInterceptor
クラスのコードです。
import java.lang.reflect.Method; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.aop.AfterReturningAdvice; import org.springframework.aop.MethodBeforeAdvice; import org.springframework.aop.ThrowsAdvice; public class LoggingInterceptor implements MethodBeforeAdvice, AfterReturningAdvice, ThrowsAdvice{ private static Log log = null; public LoggingInterceptor(){ } public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable { log = LogFactory.getLog(arg2.getClass()); log.info("Beginning method: "+arg0.getName()); } public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable { log = LogFactory.getLog(arg3.getClass()); log.info("Ending method: "+arg1.getName()); } public void afterThrowing(Method m, Object[] args, Object target, Throwable ex) { log = LogFactory.getLog(target.getClass()); log.info("Exception in method: "+m.getName()+ " Exception is: "+ex.getMessage()); } }
LoggingInterceptor
クラスは、SpringのAOPフレームワークの3つのインターフェイス、すなわちMethodBeforeAdvice
、AfterReturningAdvice
、ThrowsAdvice
を実装します。MethodBeforeAdvice
インターフェイスは、ターゲットメソッドの起動前に呼び出すアドバイスオブジェクトで実装します。その際に、このインターフェイスに用意されているvoid before (Method arg0, Object [] arg1, Object arg2) throws Throwable
メソッドを実装しなければなりません。before
メソッド内部のコードはターゲットメソッドよりも先に実行されます。そのため、リスト2にはメソッド呼び出しのロギングに必要なコードが含まれています。
同様にAfterReturningAdvice
インターフェイスは、ターゲットメソッドの後で呼び出すアドバイスオブジェクトで実装します。このインターフェイスのvoid afterReturning (Object arg0, Method arg1, Object [] arg2, Object arg3) throws Throwable
メソッドは、ターゲットメソッドの後で実行されます。
最後のThrowsAdvice
インターフェイスは、ターゲットメソッドが例外をスローしたときに呼び出すアドバイスオブジェクトで実装します。このインターフェイスのvoid afterThrowing(Method m, Object[] args, Object target, Throwable ex)
メソッドは、ターゲットメソッドが例外をスローしたときに実行されます。
一般的なロギング機能では、メソッドの入り口と出口、それに例外をトレースする必要があるので、LoggingInterceptor
クラスは上記の3つのインターフェイスを実装して、これらのタスクを遂行します。
選択的なロギングのための設定
リスト1は、ターゲットのBeanのすべてのメソッド呼び出し(Bean
インターフェイスの中で定義されているもの全部)にロギングを編み込む場合の設定を示しています。では、特定のメソッド呼び出しだけを選択的にトレースするにはどうすればよいでしょうか。このシナリオは、セキュリティやトランザクション、監査証跡など、横断的関心事を選択的に適用する場面に向いています。
リスト3に、選択的なロギングのための設定を示します。
<bean id="maintenanceServiceBean" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="proxyInterfaces"> <value>IMaintenanceService</value> </property> <property name="target"> <ref bean="maintenanceServiceImpl"/> </property> <property name="interceptorNames"> <list> <value> theLogger </value> </list> </property> </bean> <bean id="theLogger" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <property name="advice"> <ref local=" loggingInterceptor "/> </property> <property name="patterns"> <value>.*getList.*,.*getCounts.*</value> </property> </bean> <bean id=" maintenanceServiceImpl " class="MaintenanceService"> <bean id=" loggingInterceptor" class="LoggingInterceptor"/>
リスト1とリスト3の設定の違いは、リスト3ではロギングインターセプタ(theLogger
)がRegexpMethodPointcutAdvisor
というSpringクラスをポイントしている点にあります(リスト1ではその実装クラスをポイントしています)。これはつまり、theLogger
は正規表現によるポイントカットを行ってアドバイス(今回のサンプルでは、実際のロギング機構をアドバイスとして実装しているloggingInterceptor
)を適用するクラスであり、pattern
およびpatterns
というプロパティを用いて正規表現パターンを設定できることを意味しています。これらのプロパティによって実際にアドバイスを特定のパブリックメソッド、またはインターセプトされたBeanの各パブリックメソッドに適用することができます。
pattern
プロパティは、「.*getList.*」のようなメソッド名のパターンを1つ取ります。このパターンは完全修飾メソッド名でなければなりません。*を指定すると、インターセプトされたBeanのすべてのパブリックメソッドにアドバイスが適用されます。patterns
プロパティは、実際はパターンの配列です(例えば、「.*getList.*,.*getCounts.*」のように指定します)。パターンの間はコンマで区切ってください。
強力で柔軟性の高い概念
SpringのAOPフレームワークを用いてロギングをアスペクトとして実装するというのは強力で柔軟性の高い考え方です。Springは軽量なので、ロギングの実装は単なるPOJOとなり、どのフレームワークとも密に結合されません。そのため、コンテナベースであれ別の軽量フレームワークであれ、どんなシステムでも再利用が可能となります。
ロギングと同じようなやり方で、その他の横断的関心事も実装できます。例えば、データベースを呼び出す際に監査証跡コンポーネントを起動するといった監査証跡の実装例が考えられます。このような単純な目的なら、監査証跡サービスはSpringのMethodBeforeAdvice
インターフェイスを実装し、before()
メソッドに必要なコードを記述するだけで済みます。