Spring Framework 入門記 AOPその10 Introduction Advice
おぉ,AOP編も10回目ですか.ほとんど毎日やっているのにあまり進んでいる感じがしませんね.この調子じゃAOP編卒業はGWとか? いつまでたっても先行している皆様方に追いつけなさそうです.無念だ.
さて,今回はIntroduction adviceです.このAdviceは,これまで学習してきたAdviceのようにメソッドJoinpointに適用されるものではなく,クラスに適用されるようなAdviceです.クラスに何をしてくれるかというと,interface
とその実装を追加してくれるのです.これはなかなか.なんですが,またしてもちょっと複雑っぽいです.ま,Springはそういうものだということであきらめてがんばりましょう.
Introduction adviceを実装するにはIntroductionInterceptor
とIntroductionAdvisor
という2つのinterface
を実装します.例によって柔軟性のために分かれている? のでしょうか?
まずIntroductionInterceptor
ですが,これはMethodInterceptor
をextends
したもので,あわせて次の2つのメソッドが宣言されています.
implementsInterface(Class)
invoke(MethodInvocation)
最初のメソッドはIntroductionInterceptor
のもので,引数のinterface
をターゲットに追加するかどうかをチェックします.後者はすでにAround adviceで学習しましたね.そうです,このAdviceが実装するメソッドは,invoke(MethodInvocation)
を通じて呼び出されるわけです.それほど難しくない感じですね.ところでコンテナはimplementsInterface(Class)
に渡すinterface
をどこから持ってくるのでしょうか? それを提供するのがIntroductionAdvisor
です.Pointcutで学習したあのAdvisorです.こちらには,次のメソッドが宣言されています.
getClassFilter()
getInterfaces()
validateInterfaces()
isPerInstance()
getAdvice()
Pointcut
同様getClassFilter()
がありますが,こちらにはgetMethodMatcher()
はありません.メソッド単位に適用されるものではないからですね.2番目のメソッドgetInterfaces()
はターゲットに追加するinterface
をClass[]
で返します.この配列の要素がIntroductionInterceptor#implementsInterface(Class)
に渡されるのでしょう.たぶん.validateInterfaces()
は,interface
をうまく実装できない場合に例外をスローする機会を提供してくれるものらしい? たぶん.最後の2つはAdvisor
で宣言されているメソッドです.たぶん.
なんだかごちゃごちゃした感じですが,とにかくIntroductionAdvisor
を実装して,そこからIntroductionInterceptor
をコンテナに渡してあげればいいらしいです.ってことでやってみましょう.この一年間,できるだけのことをやってみましょう.
そうはいってもやっぱりわからないことが.無念だ.Introduction adviceを適用するターゲットはどうやって入手するのでしょうか? IntroductionInterceptor#invoke(MethodInvocation)
が呼び出されたときに,ターゲットにアクセスするにはどうするのでしょうか? Introduction adviceの場合,Around adviceと異なり,proceed()
を呼べばいいとは限りません.異なったメソッドを呼び出したい場合もあるはずです.というか,もともとターゲットがimplements
していないinterface
のメソッドのはずなのですから,proceed()
ですむはずがないのです.
しょうがないのでSpringが提供しているIntroduction Adviceの実装クラスを見たところ,コンストラクタで受け取ったオブジェクトを大切に抱え込んでいる模様.なんですとぉ? それじゃあ,ターゲットごとにAdviceのインスタンス作るわけ? そのためのIntroductionAdvisor#isPerInstance()
なのでしょうけれど,なんだかいまいちです.というか,かなりいまいちな気がします.いいや,Springってのはそういうものなのだということで,あきらめましょう.無念だ.
ともあれ(JavaWorld風),何か作ってみることにします.でも何を作りましょう? アイディアが出てこない... 心より恥じる.
そんなときはJava標準のinterface
に限ります.Runnable
とか最高です(何が?).いいんですよ,役に立たなくても.いつものことだし(開き直り).
ということで,Comparable
をIntroductionしてみましょう.SpringにはOrdered
というinterface
があって,「順序付けられる」を表しているようです.順序付けができるなら,比較もできますね.こいつらをまとめてIntroduce!
めんどうなので必要そうなinterface
まとめて実装しちゃいました.心より恥じる.
package study; import org.aopalliance.aop.Advice; import org.aopalliance.intercept.MethodInvocation; import org.springframework.aop.ClassFilter; import org.springframework.aop.IntroductionAdvisor; import org.springframework.aop.IntroductionInterceptor; import org.springframework.aop.framework.AopConfigException; import org.springframework.core.Ordered; public class ComparableIntroductionInterceptor implements IntroductionInterceptor, IntroductionAdvisor, ClassFilter, Comparable, Ordered { private int order; //IntroductionInterceptor public boolean implementsInterface(Class intf) { return intf.equals(Comparable.class) || intf.equals(Ordered.class); } public Object invoke(MethodInvocation methodInvocation) throws Throwable { return methodInvocation.getMethod().invoke(this, methodInvocation.getArguments()); } //IntroductionAdvisor public ClassFilter getClassFilter() { return this; } public Class getInterfaces() { return new Class { Comparable.class, Ordered.class }; } public void validateInterfaces() throws AopConfigException { } public Advice getAdvice() { return this; } public boolean isPerInstance() { return true; } //ClassFilter public boolean matches(Class clazz) { return true; } //Comparable public int compareTo(Object rhs) { return order - ((Ordered) rhs).getOrder(); } //Ordered public int getOrder() { return order; } public void setOrder(int order) { this.order = order; } }
すげー手抜きでinvoke(MethodInvocation)
でもmatches(Class>
でも,何もチェックしていません.m(__)m
(ピタッと頭を止めて) 頭を下げるつもりは,ございません!
さ,次次.定義ファイルです.<bean>
要素以下のみ.
<bean id="one" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target"> <bean class="study.Foo"> </bean> </property> <property name="interceptorNames"><value>oneComparableInterceptor</value></property> </bean> <bean id="oneComparableInterceptor" class="study.ComparableIntroductionInterceptor"> <property name="order"><value>1</value></property> </bean> <bean id="two" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target"> <bean class="study.Foo"> </bean> </property> <property name="interceptorNames"><value>twoComparableInterceptor</value></property> </bean> <bean id="twoComparableInterceptor" class="study.ComparableIntroductionInterceptor"> <property name="order"><value>2</value></property> </bean>
今回のFooクラスはどうでもいい感じなのでさくっと省略.あ,Comparable
もOrdered
も何もimplements
していません.
で,実行用のクラス.中ほどのみ.
Comparable one = (Comparable) context.getBean("one"); Comparable two = (Comparable) context.getBean("two"); System.out.println(one.compareTo(two));
これを実行すると...
-1
なんともつまらない実行結果ですね.心より恥じる.ともかく,外付けでFoo
に順番をつけて比較することが出来ました.役に立つようなAdviceにしようと思ったら,せめてターゲット(この場合はFoo
)のプロパティでも参照して順序付けしないといけなさそう.その場合はIntroduction adviceにターゲットを保持するプロパティを持つことになるでしょう.なんか,あまりスマートじゃない感じですね.無念だ.