前回の記事の続きで『2.クライアント端末の種類で画面を振り分け』の実装方法についての記事になります。
環境は大雑把に以下
言語:Java
フレームワーク:Struts2
Struts2での実装を図に表すと下のようになります。
処理の要となるクラスはActionで、通常は各画面毎にActionクラスを作成し、その画面特有のロジックを組み込みます。例えば掲示板の画面を開く時はそれ用に作成したBbsActionクラスが実行される等。
そして、Actionの前後にInterceptorというクラスが呼び出されるようになっており、その処理の中ではStruts2が提供するデフォルトの機能群(例外処理や2重押下防止に使えるToken、クッキー制御用のクラスなど)が動いています。
スマホ対応の処理切り分けロジックを埋め込むのは、このインターセプタ群にスマホ用のインターセプタを新規作成して追加するのが妥当でしょう。
スマホ用のインターセプタを作成して追加したイメージは下記
アクションクラスの処理結果が正常である場合、通常は”SUCCESS”を文字列として返却します。そしてその文字列によってコントローラで処理が分かれ、JSPの処理へ移行したり、別のアクションにチェインしたりします。
ここでは、新規追加したスマホ用インターセプタがアクション処理後に動くようにし、クライアントがスマホである場合は、処理結果の文字列”SUCCESS”の頭に固定で”S_”を連結するようにしています。
インターセプタの処理結果として”S_SUCCESS”が返ってきた場合は、スマホ用のJSPへと処理が流れるようにして対応しています。
スマホ用のインターセプタでは、クライアントのリクエストのヘッダ情報”User-Agent”(ユーザエージェント、通称UA)を見て、スマホかどうか判定を行います。
このインターセプタクラスの参考ソースは以下
package xxx.interceptor; import org.apache.struts2.ServletActionContext; import com.opensymphony.xwork2.ActionInvocation; import com.opensymphony.xwork2.interceptor.AbstractInterceptor; import com.opensymphony.xwork2.interceptor.PreResultListener; /** * クライアント端末がスマートフォンの場合、アクションresult文字列の先頭に"S_"を付与するインターセプタークラス */ public class SmartphoneInterceptor extends AbstractInterceptor { private static final long serialVersionUID = 1L; /** * インターセプト */ @Override public String intercept(ActionInvocation invocation) throws Exception { //アクション実行後のインターセプト処理 invocation.addPreResultListener( new PreResultListener() { public void beforeResult(ActionInvocation actioninvocation, String resultCode) { // User-Agent(ユーザエージェント、通称UA)文字列取得 String ua = ServletActionContext.getRequest().getHeader("User-Agent"); // [iPhone][iPod][Android][Windows Phone][BlackBerry]が含まれている場合のみ、スマホ用JSPを呼び出すように対応する。 // ※Androidの場合は[Mobile]が含まれている場合のみ。 int iphone = ua.indexOf("iPhone"); int ipod = ua.indexOf("iPod"); int win = ua.indexOf("Windows Phone"); int bb = ua.indexOf("BlackBerry"); int android = ua.indexOf("Android"); if(-1 < android) { android = ua.indexOf("Mobile"); } if(-1 < iphone || -1 < ipod || -1 < win || -1 < bb || -1 < android) { // スマホは"S_"付与 actioninvocation.setResultCode("S_" + actioninvocation.getResultCode()); } } } ); //次のインターセプタ処理 String result_code = invocation.invoke(); return result_code; } }
ゼロコンフィグレーションでアクションクラス内にアノテーション付与で実装している場合は別ですが、アクション結果文字列の処理振り分け設定はstruts.xmlに記述します。
個人的にstruts.xmlを記述するのが好みです。この1ファイルを見れば全てのアクション処理が網羅されており、システムの全体マップとなりますので。
(※ゼロコンフィグレーションの思想でアクション内に設定を記述してしまっては、システム全体像の把握が困難になります。開発から数か月も経ってしまえば忘れてしまうでしょう。それを把握する為にstruts.xmlに相当するドキュメントを作成すればいいのかもしれませんが、実装とドキュメントを完全に同期させておかなければ意味がなく、1つでも反映漏れが有ればそこからバグが生まれます。そうなるとグダグダになるので、結局はドキュメントを信用できなくなり、ソース(アクションクラス)を覗かなければ確証が持てないとなり、ドキュメントは混乱を招くために存在している物のようになり、現場はカオスに。)
<!-- トップページ --> <package name="top" namespace="/" extends="struts-default"> <interceptors> <!-- interceptorの定義 --> <interceptor name="smartphone" class="xxx.interceptor.SmartphoneInterceptor"/><!-- スマートフォンインターセプタ --> <!-- interceptorをグルーピング --> <interceptor-stack name="myInterceptorStack" > <interceptor-ref name="smartphone" /> <interceptor-ref name="defaultStack"/> </interceptor-stack> </interceptors> <default-action-ref name="top"/> <action name="top" class="xxx.TopAction"> <interceptor-ref name="myInterceptorStack"/> <result name="success">/WEB-INF/jsp/top.jsp</result> <!--スマホでない場合は通常JSPへ。--> <result name="S_success">/WEB-INF/jsp/smartPhone/smp_top.jsp</result> <!--スマホの場合は専用JSPへ。--> </action> </package>
以上でスマホのルート切り分けとJSPの定義まで完了しています。
あとはスマホ用のJSPを実装し、CSSも必要により作ればよいでしょう。元々のJSPやCSSには影響が無いのが嬉しい実装です。