Spring Framework 入門記 AOPその10 Introduction Advice

おぉ,AOP編も10回目ですか.ほとんど毎日やっているのにあまり進んでいる感じがしませんね.この調子じゃAOP編卒業はGWとか? いつまでたっても先行している皆様方に追いつけなさそうです.無念だ.
さて,今回はIntroduction adviceです.このAdviceは,これまで学習してきたAdviceのようにメソッドJoinpointに適用されるものではなく,クラスに適用されるようなAdviceです.クラスに何をしてくれるかというと,interfaceとその実装を追加してくれるのです.これはなかなか.なんですが,またしてもちょっと複雑っぽいです.ま,Springはそういうものだということであきらめてがんばりましょう.
Introduction adviceを実装するにはIntroductionInterceptorIntroductionAdvisorという2つのinterfaceを実装します.例によって柔軟性のために分かれている? のでしょうか?
まずIntroductionInterceptorですが,これはMethodInterceptorextendsしたもので,あわせて次の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()はターゲットに追加するinterfaceClass[]で返します.この配列の要素が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クラスはどうでもいい感じなのでさくっと省略.あ,ComparableOrderedも何も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にターゲットを保持するプロパティを持つことになるでしょう.なんか,あまりスマートじゃない感じですね.無念だ.