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
というinterface
をextends
した独自のinterface
を作ります.
しかし,SpringのAOPフレームワークは,その新しいAdviceを知りませんから,どう扱っていいか分からないはず.どうするのでしょうか? ごそごそ(ソース調査中)...
なるほど.そのために用意するのが,AdvisorAdapter
というinterface
をimplements
したクラスですか.このinterface
は,次のメソッドをもっています.
boolean supportsAdvice(Advice advice)
Interceptor getInterceptor(Advisor advisor)
ふむ.もし,ある種類のAdvice
をサポートするなら,supportsAdvice(Advice)
でtrue
を返せばいいわけですね.よしよし.
では,getInterceptor(Advice)
が返すInterceptor
って? ごそごそ(ソース調査中)...
なるほど.実はSpringでは,全ての種類のAdviceをAOP AllianceのInterceptor
として扱うんですね.とはいえ,Interceptor
は単なるマーカインタフェースです.こういう場合,実質的にはMethodInterceptor
をimplements
しなくてはならないようです.そのMethodInterceptor
には,次のメソッドがあります.
Object invoke(MethodInvocation methodInvocation)
これは「AOPその6 Interception Around Advice」で見たものですね.そういえば,SpringではAround AdviceはMethodInterceptor
をimplements
すればよくて,別途Around Advice用のinterface
が用意されているわけではありませんでした.どうやら,Advice
をMethodInterceptor
のように扱うアダプタを用意するというのが,カスタムなAdviceを組み込む手段のようです.AdvisorAdapter#getInterceptor(Advisor)
が返すInterceptor
は,AdviceAdaptorとでもいうべきオブジェクトで,それを提供するのがAdvisorAdaptor
ということですね.よしよし.
それでは,そうやって作ったAdvisorAdaptor
をどうやってSpringのAOPフレームワークに組み込むのでしょうか? ごそごそ(ソース調査中)...
なるほど.AdvisorAdapterRegistrationManager
なるclass
を発見! こいつは,BeanPostProcessor
をimplements
していて,Beanのインスタンスが作られるたびにコンテナからコールバックされます.そのBeanがもしAdvisorAdaptor
なら,レジストリに登録してくれるようです.ということは,定義ファイルにBeanとして書けばそれでいいって事ですね.よしよし.
それで,そのレジストリは? というと,GlobalAdvisorAdapterRegistry
というまさにシングルトンなクラスが用意されていました.staticフィールドで唯一のインスタンスを保持する,あれです.ふーん,Springでもそういうことするんだー.シングルトンは邪悪なのにぃー って,あれは私の誤読でしたか.心より恥じる.
そのGlobalAdvisorAdapter
,実質的な実装は親クラスであるDefaultAdvisorAdapterRegistry
から提供されます.その実装を眺めてみると,AdvisorAdapter
はLinkedList
で保持されています.そしてデフォルトで,BeforeAdviceAdapter
,AfterReturningAdviceAdapter
,ThrowsAdviceAdapter
が組み込まれるようになっています.Springが標準で用意するAdviceをサポートするAdvisorAdapter
です.しかし,interface
はAdvisorAdapter
なのに,実装クラスは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); } }
をいをい,adapters
はLinkedList
だぞ.Iterator
使えよ.
ま,見なかったことにしますか.
さて,仕込みは完了かな.お試しコーナー!
今回のネタは,当然ながら使えないThrowsAdviceにかわるAdviceです.ひがさんが作ってくれたThrowsInterceptor
を,実装継承なしで使えるようにしてみましょう.
まずはAdvice
をextends
した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()); } }
さぁ,これで下ごしらえができました.
次は,UsefulThrowsAdvice
をimplements
したクラスを作成します.
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」のFoo
とMain
を使って実行します.
こんな仕事やってられるか! こんな仕事やってられるか! こんな仕事やってられるか!
ということで,見事にやる気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」ですか.たぶん,休み明けの火曜日くらいから再開します.