Spring Framework 入門記 AOPその14 Metadata-Driven Autoproxying
前回に引き続きautoproxyです.今回は「5.9.2. Using metadata-driven autoproxying」に進みます.
Metadataですよ,メタデータ.C#の,あれ.J2SE1.5(Tiger)で導入される,あれ.あれを先取りしてAOPです.カックイー!
ということで,Core Concernのソースにメタデータを記述してWeavingするAspectを指定するっていうのが今回の学習内容です.個人的には,Crosscutting Concernの適用をCore Concernのソースで指定するのには抵抗を感じないではありません.よく,@remote
みたいなメタデータの例を見かけますが,こんなのをCore Concernで指定するのはちょっと違うと思うわけです.でも,それはたぶん@remote
なんていうのでは抽象度が低すぎるからで,もっと抽象度の高い表現ならいいのかもしれません.具体的にどんなのって言われても困るわけですが,例えば@service
とかって感じ? で,そのサービスメソッドをリモートにするかしないかは,後からどうにでもすればいいわけで.
IoCにAOPだけでもどう設計すればいいかが追いついていない感じの現状ですが,メタデータもまた同じような課題を増やしてくれそうですね.
能書きはこれくらいにして,Springでメタデータをどう扱うのか見ていきましょう.
Springでは今のところ, Apache Jakarta Commons の Attributes を使ってメタデータを扱うようです.ただし,直接 Commons Attributes のAPIを呼び出さなくてもいいように,抽象レイヤが用意されているみたい.
ということで,まずは Commons Attributes に寄り道します.不覚にも触ったことがないもので.ドキドキです.
Commons Attributes では,メタデータの記述は
/** * @@MyAttribute ("constructor arg", property="value") */ public class Foo { ・・・
みたいに記述するようです.'@'
が二つなんですねぇ.
このようにメタデータを記述したソースを,Attribute Compiler というものにかけると,上の例だとFoo$__attributeRepository
というクラスのソースファイルが生成されます.'$'
なんてついていますが,別に内部クラスではありません.そういう名前の普通のクラスです.この生成されたクラスは,元のクラス(Foo
)の中に記述されたメタデータに関する情報を返してくれるようなクラスで,次のようなメソッドを持っています.
Set getClassAttributes()
Map getFieldAttributes()
Map getConstructorAttributes()
Map getMethodAttributes()
なるほどなるほど.それで,こいつらが返してくれるSet
やMap
の要素はどんなものかというと,上の例の場合はMyAttribute
というクラスのインスタンスになります.そういう風に書いたので.MyAttribute
クラスには,上の書き方だと1つのString
を引数として受け取るコンストラクタと,property
という名前でString
型のプロパティのsetterつまりsetProperty(String)
が必要です.これはあくまで上のように書いた場合の話で,実際には引数の数や型は自由自在みたいです.というのも,メタデータでの記述がほとんどそのまま生成されるソースファイルにコピペされるだけなのです.ナイス.なのか?
とまぁ,そんなわけでメタデータのリポジトリとなるクラスのソースが生成されますから,それをコンパイルして使えばいいみたい.使い方は,普通にインスタンスをnew
して前述のメソッドを呼ぶだけ.簡単そうですね.これでひとまず Commons Attributes を終了,Springに戻ります.
ということで,Springに戻ってきました.Springでは,メタデータを扱うためにAttributes
というinterface
を用意しています.こいつは,次のメソッドを持っています.
Collection getAttributes(Class)
Collection getAttributes(Field)
Collection getAttributes(Field, Class)
Collection getAttributes(Method)
Collection getAttributes(Method, Class)
なんとなくイメージは分かりますね.コレクションの要素が何かというのは明記されていない感じなのですが,普通に考えれば先ほどのMyAttribute
みたいなのに決まってます.間違いない.二つ目の引数でClass
を取るものは,その型のメタデータ情報だけを取得するためのものです.
Attributes
はinterface
なのですが,それをimplements
したclass
としてCommonsAttributes
が用意されています.もちろん,Commons Attributes を使った実装です.こいつは普通にBeanとしてインスタンス化して,メソッドを呼び出せばそれでいいみたい.
それで,このAttributes
をどこで使うかというと,もうおなじみの Advisor(Pointcut) で使うようです.具体的には,ClassFilter#matches(Class)
とかMethodMatcher#matches(Method, Class)
の中でAttributes
を呼び出してあげればよさげです.
さぁ,これで材料が揃いました.メタデータは定義できるし,それを取得するべき場所もその方法もわかりました.後は実践あるのみ!
で,恥ずかしながらまたしてもDebugInterceptor
を使います.しょうがないんですよ,日記としては標準出力に跡を残してくれるAspectを使うのが一番なんですから.心より恥じる.
とういわけで,メタデータは @@DebugAttribute
に決定です.となると,必然的にDebugAttribute
クラスを用意しなくてはなりません.でも,トランザクションのAspectみたいにREQUIRED
とか指定する必要はないので,超単純です.
package study; public class DebugAttribute { }
日記に載せるほどでもないですね.いいんです,これも大切な思い出なんです.
で,次にAspectを適用するクラス.今回はメタデータ付.
package study; public class Foo { /** * @@DebugAttribute() */ public void one() { System.out.println("one"); } public void two() { System.out.println("two"); } /** * @@DebugAttribute() */ public void three() { System.out.println("three"); } }
3つの無駄なメソッドのうち二つにはメタデータでデバッグ対象であることを記述しています.
これをAttribute Compilerで処理します.Antのタスクが提供されているので,次のようなbuild.xml
を用意すればOK.
<?xml version="1.0"?> <project name="attribute-compiler" basedir="." default="build"> <target name="build"> <property name="spring.root" value="C:\Program Files\spring\spring-framework-1.0"/> <path id="attribute-compiler-classpath"> <fileset dir="${spring.root}/lib/jakarta-commons"> <include name="commons-attributes-compiler-SNAPSHOT.jar"/> <include name="commons-collections.jar"/> </fileset> <fileset dir="${spring.root}/lib/xdoclet"> <include name="xjavadoc-1.0.jar"/> </fileset> </path> <taskdef resource="org/apache/commons/attributes/anttasks.properties"> <classpath refid="attribute-compiler-classpath"/> </taskdef> <attribute-compiler destdir="${basedir}/generated"> <fileset dir="${basedir}/sources" includes="**/*.java"/> </attribute-compiler> </target> </project>
これをAntで実行すると,study.Foo$__attributeRepository
が生成されます.
次にStaticPointcutなAdvisorを作成します.
package study; import java.lang.reflect.Method; import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor; import org.springframework.metadata.Attributes; public class DebugAttributesAdvisor extends StaticMethodMatcherPointcutAdvisor { private Attributes attributes; public boolean matches(Method method, Class targetClass) { return !attributes.getAttributes(method, DebugAttribute.class).isEmpty(); } public Attributes getAttributes() { return attributes; } public void setAttributes(Attributes attributes) { this.attributes = attributes; } }
このクラスは,プロパティとしてAttributes
を持っており,matches(Method, Class)
ではそれを使って該当のメソッドにDebugAttribute
が存在するかチェックしています.Attributes#getAttributes()
はメタデータがない場合は空のコレクションを返します.null
が返ってこないっていいっすねー.
さ,次は定義ファイルです.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/> <bean class="org.springframework.metadata.commons.CommonsAttributes"/> <bean class="org.springframework.aop.interceptor.DebugInterceptor"/> <bean class="study.DebugAttributesAdvisor" autowire="byType"/> <bean id="foo" class="study.Foo"/> </beans>
えへへ,今回はautowire
を指定して,シンプルな定義ファイルに挑戦してみました.
さて,あとは実行用のクラスです.前回同様,コンテナからfoo
を取り出して,3つのメソッドを呼ぶだけです.
それを実行!!!
Debug interceptor: count=1 invocation=[Invocation: method=[public void study.Foo.one()] args=[Ljava.lang.Object;@289d2e] target is of class study.Foo] one Debug interceptor: next returned two Debug interceptor: count=2 invocation=[Invocation: method=[public void study.Foo.three()] args=[Ljava.lang.Object;@289d2e] target is of class study.Foo] three Debug interceptor: next returned
ばっちグー.
出だしで@remote
は抽象度が低いとか偉そうに書いておきながら,サンプルで使ったのが@@DebugAttribute()
とは... 心より恥じる.