SAStruts話。
ケータイとパソコンでシステムエラーページを変えたいそうだ。ケータイとPCでアクションは別なのだが、困った事にサービスクラスを共用している。このサービスクラス、内部でS2JDBCがSQLRuntimeExceptionを投げる可能性があるのだが、この例外をアクションでcatchしてエラーページに遷移といった処理を書くのは面倒くさいし、RuntimeExcetionなので記述モレもありえる。そもそも、そんな処理はフレームワークがやるべき仕事だ。
SAStrutsもStrutsなのでglobal-exceptionが使えるが、これは例外の型を見て遷移先を指定するもののようなのでユーザエージェント等の条件ではシステムエラーページを分ける事ができない。ならばアスペクトを使って別の例外に変換すればいいのでは?と思ってやってみたら簡単にできた。
ThrowsInterceptorを使ったケータイとパソコンのシステムエラーページの出し分け。
まずインターセプタのクラス、SMART Deploy規約に従って作るのが無難
public class DeviceThrowsInterceptor extends ThrowsInterceptor { /** インターセプタにもHttpServletRequestをバインディングできる */ public HttpServletRequest request; /** requestでケータイかどうか判別 */ private boolean isMobileAccess() { String ua = request.getHeader("User-Agent"); // ...何か処理、ua等でケータイかどうか判別 return true; } public void handleThrowable(Throwable e, MethodInvocation invocation) throws Throwable { if (this.isMobileAccess()) { throw new MobileException(e); } else { throw new PCException(e); } } }
customizer.diconを修正。作成したインターセプタの設定をactionCustomizerの最初に追加する。
<component name="actionCustomizer" class="org.seasar.framework.container.customizer.CustomizerChain"> <initMethod name="addAspectCustomizer"> <arg>"deviceThrowsInterceptor"</arg> </initMethod> <initMethod name="addAspectCustomizer"> <arg>"aop.traceInterceptor"</arg> </initMethod> ... </component>
struts-config.xmlのglobal-exceptionに遷移先を記述
<global-exceptions> <exception type="jp.paulownia.sastruts.exception.MobileException" path="/error/mobile.jsp" key="mobileException"/> <exception type="jp.paulownia.sastruts.exception.PCException" path="/error/pc.jsp" key="pcException"/> </global-exceptions>
ThrowsInterceptorが投げるMobileExceptionやPCExceptionはRuntimeExceptionの方が良い。アスペクトが検査例外を投げたとき、Actionのメソッドにその例外がthrows宣言されていないとUndeclaredThrowableExceptionが発生してしまう。
追記
Cool Deployの場合、HttpServletRequestがバインディングされない。
http://d.hatena.ne.jp/n-kizashi/20080627
http://s2container.seasar.org/2.4/ja/DIContainer.html#Customizer のインスタンス属性がsingleton以外のインターセプタを利用する を参考にして、useLookupAdapterをtrueにしてください。
これをやらないとダメっぽです
customizer.dicon
<component name="actionCustomizer" class="org.seasar.framework.container.customizer.CustomizerChain"> <initMethod name="addCustomizer"> <arg> <component class="org.seasar.framework.container.customizer.AspectCustomizer"> <property name="useLookupAdapter">true</property> <property name="interceptorName">"deviceThrowsInterceptor"</property> </component> </arg> </initMethod> <initMethod name="addAspectCustomizer"> <arg>"aop.traceInterceptor"</arg> </initMethod> ... </component>
Interceptorのライフサイクルはprototypeにする。SMART Deployの規則に沿えばデフォルトでprototypeになるらしい。
追記
と思ったけど、わざわざstruts-config弄る必要ない…
public class DeviceThrowsInterceptor extends ThrowsInterceptor { public HttpServletRequest request; public HttpServletResponse response; /** requestでケータイかどうか判別 */ private boolean isMobileAccess() { String ua = request.getHeader("User-Agent"); // ...何か処理、ua等でケータイかどうか判別 return true; } public void handleThrowable(Throwable e, MethodInvocation invocation) throws Throwable { if (this.isMobileAccess()) { if (!response.isCommitted()) { response.setStatus(500); request.getRequestDispatcher("/mobile/error.jsp").forward(request,response); } } else { if (!response.isCommitted()) { response.setStatus(500); request.getRequestDispatcher("/pc/error.jsp").forward(request,response); } } // デフォルトの500エラーページ、あるいはweb.xmlで定義した500ページに飛ばす throw e; } }
で、いいじゃんね…