Spring Framework 入門記 AOPその16 Advice Type

OO Enkaiに備えて仕事をお休みしたため,いつもより早い時間に入門記です.自宅なので前傾姿勢です.無念だ.
今回は「5.11. Defining new Advice types」です.なんかもう,第5章も終わりですねぇ.感慨深い.
Springには,Around advice,Before advice,Throws advice,After returning adviceと,豊富なAdviceの種類がありますが,実はこれ,拡張できるそうです.Springって本当にこういうの好きですねぇ.
それでその,どうやってカスタマイズするのかというと,なんかソース読めっぽいことしか書いてないし.ぶつぶつ.しょうがない,そんなときはソースを読めるのがオープンソースのいいところ.ごそごそ(ソース調査中)...
なるほど.Adviceをカスタマイズするということは,新しい種類のAdviceを作るわけですが,それには,AOP AllicanceのAdviceというinterfaceextendsした独自のinterfaceを作ります.
しかし,SpringのAOPフレームワークは,その新しいAdviceを知りませんから,どう扱っていいか分からないはず.どうするのでしょうか? ごそごそ(ソース調査中)...
なるほど.そのために用意するのが,AdvisorAdapterというinterfaceimplementsしたクラスですか.このinterfaceは,次のメソッドをもっています.

  • boolean supportsAdvice(Advice advice)
  • Interceptor getInterceptor(Advisor advisor)

ふむ.もし,ある種類のAdviceをサポートするなら,supportsAdvice(Advice)trueを返せばいいわけですね.よしよし.
では,getInterceptor(Advice)が返すInterceptorって? ごそごそ(ソース調査中)...
なるほど.実はSpringでは,全ての種類のAdviceをAOP AllianceのInterceptorとして扱うんですね.とはいえ,Interceptorは単なるマーカインタフェースです.こういう場合,実質的にはMethodInterceptorimplementsしなくてはならないようです.そのMethodInterceptorには,次のメソッドがあります.

  • Object invoke(MethodInvocation methodInvocation)

これは「AOPその6 Interception Around Advice」で見たものですね.そういえば,SpringではAround AdviceはMethodInterceptorimplementsすればよくて,別途Around Advice用のinterfaceが用意されているわけではありませんでした.どうやら,AdviceMethodInterceptorのように扱うアダプタを用意するというのが,カスタムなAdviceを組み込む手段のようです.AdvisorAdapter#getInterceptor(Advisor)が返すInterceptorは,AdviceAdaptorとでもいうべきオブジェクトで,それを提供するのがAdvisorAdaptorということですね.よしよし.
それでは,そうやって作ったAdvisorAdaptorをどうやってSpringのAOPフレームワークに組み込むのでしょうか? ごそごそ(ソース調査中)...
なるほど.AdvisorAdapterRegistrationManagerなるclassを発見! こいつは,BeanPostProcessorimplementsしていて,Beanのインスタンスが作られるたびにコンテナからコールバックされます.そのBeanがもしAdvisorAdaptorなら,レジストリに登録してくれるようです.ということは,定義ファイルにBeanとして書けばそれでいいって事ですね.よしよし.
それで,そのレジストリは? というと,GlobalAdvisorAdapterRegistryというまさにシングルトンなクラスが用意されていました.staticフィールドで唯一のインスタンスを保持する,あれです.ふーん,Springでもそういうことするんだー.シングルトンは邪悪なのにぃー って,あれは私の誤読でしたか.心より恥じる.
そのGlobalAdvisorAdapter,実質的な実装は親クラスであるDefaultAdvisorAdapterRegistryから提供されます.その実装を眺めてみると,AdvisorAdapterLinkedListで保持されています.そしてデフォルトで,BeforeAdviceAdapterAfterReturningAdviceAdapterThrowsAdviceAdapterが組み込まれるようになっています.Springが標準で用意するAdviceをサポートするAdvisorAdapterです.しかし,interfaceAdvisorAdapterなのに,実装クラスはAdviceAdapterですか.ちゃんちゃらおかしいね.
新しいAdvisorAdapterは,LinkedListの後ろに追加されます.検索は先頭から始められます.ということは,Spring組み込みのAdvisorAdapterを上書きすることはできないのですね.それができれば例の使えないThrowsAdviceをどうにかできたのに.無念だ.
ところで,そのLinkedListからAdviceAdapterを検索するコードなんですが...

for (int i = 0; i < this.adapters.size(); i++) {
    AdvisorAdapter adapter = (AdvisorAdapter) this.adapters.get(i);
    if (adapter.supportsAdvice(advice)) {
        return adapter.getInterceptor(advisor);
    }
}

をいをい,adaptersLinkedListだぞ.Iterator使えよ.
ま,見なかったことにしますか.


さて,仕込みは完了かな.お試しコーナー!
今回のネタは,当然ながら使えないThrowsAdviceにかわるAdviceです.ひがさんが作ってくれたThrowsInterceptorを,実装継承なしで使えるようにしてみましょう.
まずはAdviceextendsしたinterfaceを作ります.

package study;
import org.aopalliance.aop.Advice;

public interface UsefulThrowsAdvice extends Advice {
}

ThrowsAdvice同様,マーカインタフェースです.
次に,このUsefulThrowsAdviceをサポートするInterceptorを作ります.

package study;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.AopConfigException;

public class UsefulThrowsAdviceInterceptor implements MethodInterceptor {
    public static final String METHOD_NAME = "afterThrowing";
    private Map exceptionHandlerHash = new HashMap();
    private Object advice;

    public UsefulThrowsAdviceInterceptor(Object advice) {
        this.advice = advice;

        Method methods = advice.getClass().getMethods();
        for (int i = 0; i < methods.length; ++i) {
            Method method = methods[i];
            if (isHandleThrowable(method)) {
                Class parameterTypes = method.getParameterTypes();
                exceptionHandlerHash.put(parameterTypes[1], method);
            }
        }
        if (exceptionHandlerHash.size() == 0) {
            throw new AopConfigException("At least one handler method must be found in class " + advice.getClass());
        }
    }

    private boolean isHandleThrowable(Method method) {
        Class parameterTypes = method.getParameterTypes();
        return METHOD_NAME.equals(method.getName())
            && parameterTypes.length == 2
            && MethodInvocation.class.equals(parameterTypes[0])
            && Throwable.class.isAssignableFrom(parameterTypes[1]);
    }

    public Object invoke(MethodInvocation invocation) throws Throwable {
        try {
            return invocation.proceed();
        }
        catch (Throwable t) {
            Method handler = getExceptionHandler(t);
            if (handler != null) {
                try {
                    return handler.invoke(advice, new Object {invocation, t});
                }
                catch (InvocationTargetException e) {
                    throw e.getTargetException();
                }
            }
            throw t;
        }
    }

    private Method getExceptionHandler(Throwable t) {
        Class exceptionClass = t.getClass();
        Method handler = (Method) exceptionHandlerHash.get(exceptionClass);
        while (handler == null && !exceptionClass.equals(Throwable.class)) {
            exceptionClass = exceptionClass.getSuperclass();
            handler = (Method) this.exceptionHandlerHash.get(exceptionClass);
        }
        return handler;
    }
}

SpringのThrowsAdviceInterceptorとひがさんのThrowsInterceptorのハイブリッドです.
そして,これを使うAdvisorAdapter

package study;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.Interceptor;
import org.springframework.aop.Advisor;
import org.springframework.aop.framework.adapter.AdvisorAdapter;

public class UsefulThrowsAdviceAdapter implements AdvisorAdapter {
    public boolean supportsAdvice(Advice advice) {
        return advice instanceof UsefulThrowsAdvice;
    }
    public Interceptor getInterceptor(Advisor advisor) {
        return new UsefulThrowsAdviceInterceptor(advisor.getAdvice());
    }
}

さぁ,これで下ごしらえができました.
次は,UsefulThrowsAdviceimplementsしたクラスを作成します.

package study;
import org.aopalliance.intercept.MethodInvocation;

public class IgnoreExceptionAdvice implements UsefulThrowsAdvice {
    public void afterThrowing(MethodInvocation invocation, Exception e) {
    }
}

例によって,例外を無視します.
後は定義ファイルです.

<bean class="org.springframework.aop.framework.adapter.AdvisorAdapterRegistrationManager"/>
<bean class="study.UsefulThrowsAdviceAdapter"/>
<bean id="advice" class="study.IgnoreExceptionAdvice"/>

<bean id="foo" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target">
        <bean class="study.Foo"/>
    </property>
    <property name="interceptorNames"><value>advisor</value></property>
</bean>

さて,これを「AOPその8 Throws Advice」のFooMainを使って実行します.

こんな仕事やってられるか!
こんな仕事やってられるか!
こんな仕事やってられるか!

ということで,見事にやる気Nullな例外を握りつぶして,イヤな仕事をきっちり3回もやらせることに成功しました.
これで,任意の種類のAdviceを作れます.そんなに必要だとは思えませんけどね.Around Adviceがあれば十分じゃないかと.あって困ることはないので問題ないですが.


さて,今回の「AOPその16」で,長く続いたAOP編も終了です.\(^o^)/
Chapter 5. Spring AOP: Aspect Oriented Programming with Spring」には,まだ2つの節が残っているのですが,「5.12. Further reading and resources」は「AspectJ in Action(isbn:1930110936)」を読めとか(積んでます.心より恥じる),サンプルを見ろとか書いてある程度で,「5.13. Roadmap」はまだまだ続くよ開発は,なんてことが書いてある程度.なので,これで5章は終わりです.
ふー,挫折することなく,どうにかここまでこれたよ.よかったよかった.日記じゃなかったら放り出していたかも.はてなや,はてなを始めるきっかけをくれた人,コメントくれた人,読んでくれてる人,皆さんに感謝します.
次は「Chapter 6. Transaction management」ですか.たぶん,休み明けの火曜日くらいから再開します.