[[Spring-MVC/ステップ・バイ・ステップ]]

2008/04/16からのアクセス回数 &counter;

#contents

** AopNameSpaceとは何か [#z558866b]
Spring 2.0の特徴は、
- NameSpaceを使ってAOPやトランザクションの記述が簡単にできるようになった
- AspectJをサポートした

ことです。

AopNameSpaceでは、普通のオブジェクト(以下POJOオブジェクトと呼びます)をAspectの
Adviceとして使用することができます。

''AopNameSpaceを使用するとAutoProxyCreatorは使用できないことに注意してください。''

** 準備 [#c1b26fc9]
AopNameSpaceを使用するには、
- spring.jar(version 2.5以降)
- aspectjrt.jar(version 1.5以降)
- aspectjweaver.jar(version 1.5以降)

が必要です。

いつものようにMVN Repositoryで検索すると、以下のようなdependecyタグが見つかりました。

#pre{{
	<dependency>
	  <groupId>org.aspectj</groupId>
	  <artifactId>aspectjrt</artifactId>
	  <version>1.5.4</version>
	</dependency>
	<dependency>
	  <groupId>org.aspectj</groupId>
	  <artifactId>aspectjweaver</artifactId>
	  <version>1.5.4</version>
	</dependency>
}}

これをpom.xmlに追加して、以下のコマンドを実行してください。

#pre{{
$ rm .project .classpath
$ mvn eclipse:eclipse -DdownloadSources=true
}}

** AopNameSpaceの例題 [#uee1662d]
AopNameSpaceを使った例を順を追って作成していきましょう。

*** POJOオブジェクト [#g5e1af53]
Adviceとして使用するPOJOオブジェクトクラス(POJOAdvice)を以下のように定義します。

#pre{{
package org.springframework.showcase.aop;

public class POJOAdvice {
	public void a() {
		System.out.println("a called");
	}
	public void b(Long id) {
		System.out.println("b(" + id +") called");
	}
}
}}

- aは単に”a called”と出力します
- bは、引数にidを持ち、"b(id) called"と出力します

*** AOP定義ファイル [#m435ea61]
AopNameSpaceを使った定義ファイルは他のBean定義ファイルと別ファイルにすると
機能が切り分けられます。

aop-def.xmlは、以下のようになります。

#pre{{
<beans xmlns="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:aop="http://www.springframework.org/schema/aop" 
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
		http://www.springframework.org/schema/beans/spring-beans-2.0.xsd 
		http://www.springframework.org/schema/aop 
		http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">

    <bean id="pojoAdvice" class="org.springframework.showcase.aop.POJOAdvice" />
	
    <aop:config>
        <aop:aspect ref="pojoAdvice">			
            <aop:before
                method="a"
                pointcut="execution(* *.findAll(..))" />
            <aop:before
                method="b"
                pointcut="execution(* org.springframework.showcase.coverc.service.GenericHibernateDao.findById(..))
                    and args(id)"
             />
        </aop:aspect>
    </aop:config>
</beans>
}}

- beans定義では、AopNameSpaceを使用するためにNameSpaceを定義します
- pojoAdviceがPOJOAdviceのBeanです
- <aop:config>がAOPの定義を示します
- <aop:aspect>のrefでpojoAdviceを指定します
- <aop:before>でpojoAdviceの呼び出すメソッド名とpointcutを指定します

'<aop:config>では、<aop:before>の他に以下の要素(タグ)が使用できます。

| 要素名 | 目的 |
| <aop:advisor> | AOP Advisorを定義します |
| <aop:after> | AOP after advice を定義します(正常・異常にかかわらずメソッドがreturnしたときに適応)|
| <aop:after-returning> | AOP after-return adivice を定義します |
| <aop:after-throwing> | AOP after-throwing advice を定義します |
| <aop:around> | AOP around advice を定義します |
| <aop:aspect> | aspectを定義します|
| <aop:before> | AOP before advice を定義します |
| <aop:pointcut> | pointcut を定義します |


'<aop:before>に戻って
- method: aというメソッドを呼び出すことを指定
- pointcut: AspectJの記述形式でpointcutを指定

します。
AspectJのpointcutは、
 execution ( <戻り値のタイプ> <クラスパス>.<メソッド名>( [ <引数のタイプ> ] ) )
の形式で記述します。

aの場合のpointcutを見てみると
#pre{{
pointcut="execution(* *.findAll(..))"
}}
とありますが、型、クラスパスに関係なくfindAllという名前のメソッドにpointcutを設定する
指定です。

次にbの場合のpointcutを見ると
#pre{{
pointcut="execution(* org.springframework.showcase.coverc.service.GenericHibernateDao.findById(..))
		 and args(id)"
}}

and args(id)でfindByIdの引数idをbの呼び出しに渡すことを指定します。

*** web.xmlの変更 [#j0994e68]
aop-def.xmlを追加するために、web.xmlに以下のcontext-paramタグを追加します。

#pre{{
	<context-param> 
	  <param-name>contextConfigLocation</param-name> 
	  <param-value> 
		/WEB-INF/applicationContext.xml
		/WEB-INF/aop-def.xml
	  </param-value> 
	</context-param> 
}}

*** applicationContext.xmlの変更 [#m6eb002c]
前回のDefaultAdvisorAutoProxyCreatorを使ったAOPと共存できないので、applicationContext.xmlを
以下のように変更します。

#pre{{
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN"
        "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
	<bean id="recipeManager" class="org.springframework.showcase.coverc.service.StubRecipeDaoManager">
		<property name="sessionFactory" ref="sessionFactory" /> 
	</bean>

	<bean id="dataSource"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName">
			<value>org.postgresql.Driver</value>
		</property>
		<property name="url">
			<value>jdbc:postgresql://localhost/springdb</value>
		</property>
		<property name="username">
			<value>spring</value>
		</property>
		<property name="password">
			<value>spring</value>
		</property>
	</bean>
	<bean id="sessionFactory" 
		class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
		<property name="hibernateProperties">
			<props>
			<prop key="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</prop>
			</props>
		</property>
		<property name="mappingDirectoryLocations">
			<list>
				<value>classpath:/org/springframework/showcase/coverc/domain</value>
			</list>
		</property>
		<property name="dataSource">
			<ref bean="dataSource"/>
		</property>
	</bean>	
</beans>
}}

** 例題の実行 [#mb38416e]
maven jettyプラグインを使って例題を実行します。

#pre{{
$ mvn jetty:run
}}

次にブラウザーで
 http://localhost:8080/mvc-convention/
と入力すると

#pre{{
a called
a called
}}
と表示されます。これは、RecipeManagerのfindAllとGenericHibernateDaoのfindAllの2カ所に
pointcutが設定されたからです。

次に、ブラウザのaddリンクをクリックして「test」を入力した後、Save Changeボタンを押してください。
testがリストに追加されましたが、testのdeleteリンクをクリックしてください。

#pre{{
b(4) called
a called
a called
}}

とb(4) calledが表示されます。bは、
 org.springframework.showcase.coverc.service.GenericHibernateDao.findById
とクラスパスを指定しているので1回だけ表示されます。

** ログ出力との併用 [#ne4e3c45]
AopNameSpaceを使ったAOPでは、引数を明示的に指定する必要があり、ログ出力のような
チェックプリントのメソッドを実行することができません。

チェックプリントは、デバッガでは追えない並行処理のデバッグに有効な手段です。

ここでは、ログ出力とAopNameSpaceの併用の方法について説明します。

チェックプリントを出力したいBeanをProxyFactoryBeanでラップすることで
指定したBeanにログ出力機能を加えることができます。

- logBaseでinterceptorNames属性を定義します
- recipeManagerをStubRecipeDaoManagerからProxyFactoryBeanに代えます
- ProxyFactoryBeanのtargetにStubRecipeDaoManagerのBeanを定義します

#pre{{
	<bean id="enterMethodLogAdvice" class="org.springframework.showcase.aop.EnterMethodLogAdvice"/>
	<bean id="leaveMethodLogAdvice" class="org.springframework.showcase.aop.LeaveMethodLogAdvice"/>
	<bean id="logBase" abstract="true">
		<property name="interceptorNames">
			<list>
				<value>enterMethodLogAdvice</value>
				<value>leaveMethodLogAdvice</value>
			</list>
		</property>		
	</bean>
	
	<bean id="recipeManager" class="org.springframework.aop.framework.ProxyFactoryBean" 
		parent="logBase">
		<property name="target">
			<bean class="org.springframework.showcase.coverc.service.StubRecipeDaoManager">
				<property name="sessionFactory" ref="sessionFactory" /> 
			</bean>
		</property>
	</bean>
}}

*** 実行例 [#w5dd437d]
先ほどと同様に実行すると、

#pre{{
a called
enter findAll args=()
a called
leave findAll return=[org.springframework.showcase.coverc.domain.Recipe@9ab468, 
org.springframework.showcase.coverc.domain.Recipe@a06db5, 
org.springframework.showcase.coverc.domain.Recipe@82aacf]

-- 途中省略
enter save args=(org.springframework.showcase.coverc.domain.Recipe@fbe09b)
leave save return=null
a called
enter findAll args=()
a called
leave findAll return=[org.springframework.showcase.coverc.domain.Recipe@82c6dc, 
org.springframework.showcase.coverc.domain.Recipe@cdb92b, 
org.springframework.showcase.coverc.domain.Recipe@37bc9e, 
org.springframework.showcase.coverc.domain.Recipe@403477]
enter findById args=(4)
b(4) called
leave findById return=org.springframework.showcase.coverc.domain.Recipe@108172
enter delete args=(org.springframework.showcase.coverc.domain.Recipe@108172)
leave delete return=null
a called
enter findAll args=()
a called
leave findAll return=[org.springframework.showcase.coverc.domain.Recipe@6b2d99, 
org.springframework.showcase.coverc.domain.Recipe@7ec7b9, 
org.springframework.showcase.coverc.domain.Recipe@6a56f0]
}}

のようにログ出力とAopNameSpaceの出力の両方が出ています。

[[Spring-MVC/ステップ・バイ・ステップ/AOPの追加]]の

今回使用したファイルは、以下にあります。
#ref(aop-def.xml);
#ref(applicationContext.xml);
#ref(web.xml);
#ref(POJOAdvice.java);

** コメント [#n9849984]
この記事は、

#vote(おもしろかった,そうでもない,わかりずらい)

皆様のご意見、ご希望をお待ちしております。

#comment



トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
SmartDoc