Strutsの2つの細かいところ


細かい点1:「Tokenによる重複コミットの解決」の背後にある前提
同期トークン(Token)機構を用いてWebアプリケーションにおいて反復的に送信される問題を解決できることを知っており,Strutsも参照実装を与えている.サーバ側は、到着した要求を処理する前に、要求に含まれるトークン値を現在のユーザセッションに保存されているトークン値と比較し、一致するかどうかを確認する.要求の処理が完了し、応答がクライアントに送信される前に、クライアントに渡されるほか、ユーザセッションに保存されている古いトークンを置き換える新しいトークンが生成される.これにより,ユーザが先ほどのコミットページに戻って再度コミットすると,クライアントから送信されたトークンがサーバ側のトークンと一致せず,重複コミットの発生を効果的に防止できる.この説明に対応して、Actionサブクラスにこのようなコードがある可能性があります.
if (isTokenValid(request, true)) {
	// your code here
return mapping.findForward("success");
} else {
saveToken(request);
return mapping.findForward("submitagain");
}

このうちisTokenValid()もsaveToken()もorg.apache.struts.action.アクションクラスのメソッドである、具体的なToken処理ロジックはorg.apache.struts.util.TokenProcessorクラスにあります.Strutsでは、ユーザセッションIDと現在のシステム時間とに基づいて一意の(各セッションについて)トークンを生成し、具体的なインプリメンテーションは、TokenProcessorクラスのgenerateToken()メソッドを参照することができる.
Strutsはセッションサーバ側ごとに最新のToken値しか保存できないため、セッションサーバ側にTokenを保存する属性です.この点について、同僚は疑問を提起しました.もし私が同じセッションで2つのページを開いたら、後で提出したページはきっと提出できません.例えば、現在、2つの顧客AとBのアドレスをいずれかの値に変更する必要がある場合、ユーザーは同時に2つのページを開き、Aを修正し、Bを修正し、Aを提出し、Bを提出し、Strutsの処理ロジックに従って、Bの修正提出は成功しないに違いないが、この提出操作はユーザーにとって操作が正しくないところはない.
ここで、同じセッションで2つのページを開くことはできませんか?IEブラウザをもう一度開くと、セッションが再開されたのではないでしょうか.いいですね.この場合は2つのセッションで、問題はありません.ただし、メニュー「ファイル」-「新規」-「ウィンドウ」(またはショートカットキーCtrl+N)を使用して現在のウィンドウをコピーすることもできます.この場合、既存のページと同じセッションにあるページが見つかります.実は、この問題を発見できたのは私の同僚のIEに対する習慣的な操作方法のおかげです.
これで私の同僚は満足していません.彼はStrutsの実装方法を修正し始めました.各ページ(少なくともある種類のページ)がサーバ側に唯一のToken値を保存するようにしました.これにより,前述した顧客A,Bが同時に修正する制限は存在しない.しかし間もなく、私の同僚は彼が危険な道に向かっていることに気づき始めた.まず、各ページがサーバ側にToken値を保存すると、サーバ側に保存されるデータ量はますます大きくなります.また、同じセッションで複数のページを開く場合を考えると、まるでパンドラの魔箱を開けたかのように、自分に無限の迷惑をかけることになります.たとえば,まずページP 1を開き,その後Ctrl+Nを用いてページP 2,P 1コミット,P 2コミットを得ることは,これまですべて正常であった.しかし、この場合、P 1,P 2で「戻る」ボタンをクリックして、P 1,P 2を提出したらどうなるのでしょうか.P 2でコミットした後に他の操作を行い、P 1でロールバックしてコミットした場合、どうなりますか?P 1,P 2,P 3があれば、どうなるのでしょうか.複雑すぎる!あなたも私たちと同感だと思います.多くの可能な組み合わせを考えなければなりません.そして、結果はあなたが想像していたほど簡単ではありません.
この道は通じないから、Strutsを見に帰らなければなりません.実は以上の苦労を経て、StrutsのTokenメカニズムの背後には、同じセッションで複数のページを開くことを許さないという前提が隠されていることがわかります.同じセッションであることに注意して、もし2つのIEブラウザを開くならば、あれはすでに2つのセッションで、この制限を受けません.実は、この不合理に見える規定には理屈がある.一つはTokenの実現を極めて簡素化し、二つの制限も大部分の人の使用習慣に合っている.
細かい点2:ページフロー制御における職責の割り当て
Strutsの実行手順は、まず、コントローラがクライアント要求を受信し、これらの要求を対応するActionにマッピングし、Actionのexecuteメソッドを呼び出し、ActionFormの作成と埋め込みにも関連する可能性があることを知っています.Actionのexecuteメソッドの実行が完了すると、コントローラは、ActionForwardオブジェクトに基づいて次のActionまたはJSPにリクエストを転送するActionForwardオブジェクトを返します.最後に、ビュー応答クライアントを生成します.大きなレベルでは、StrutsはMVCというアーキテクチャを採用しており、特別な点はありません.しかし、いくつかの小さな場所から、Craig R.McClanahan兄さんの考えが見えます.Actionとコントローラの間にはActionForwardオブジェクトが渡されています.ActionのexecuteメソッドではActionForwardオブジェクトを返す必要があるため、Actionサブクラスでは次の文がよく見られます.
return (new ActionForward(mapping.getInput()));

または
return (mapping.findForward("success"));

実際にはActionForwardオブジェクトが返されます.Actionでは、プログラムの実行状況に応じて、次のページの方向を決定し(入力ページに戻るか次のページに移動するなど)、これらの情報をActionForwardオブジェクトに保存します.次に、コントローラは、ActionForwardオブジェクトを直接利用してページのフローを行うことができます.次はorgです.apache.struts.action.Actionインスタンス呼び出しの後に発生するRequestProcessorクラスのprocessForwardConfig()メソッドの抜粋.
protected void processForwardConfig(HttpServletRequest
                                        request,
                                        HttpServletResponse
                                        response,
                                        ForwardConfig forward)
        throws IOException, ServletException {
        …
        
        String forwardPath = forward.getPath();
        String uri = null;
                
             // paths not starting with / 
                 should be passed through without any  processing
            // (ie. they're absolute)
        if (forwardPath.startsWith("/")) {
            uri = RequestUtils.forwardURL(request, forward);
    // get module relative uri
        } else {
            uri = forwardPath;
        }
                if (forward.getRedirect()) {
            // only prepend context path for relative uri
            if (uri.startsWith("/")) {
                uri = request.getContextPath() + uri;
            }
            response.sendRedirect(response.encodeRedirectURL(uri));
                    }
 else {
            doForward(uri, request, response);
        }
    }

注意:ForwardConfigはActionForwardの親です
この方法はまずForwardConfigのgetPath()メソッドを呼び出して次のフローのパスを取得し,いくつかの条件の下でいくつかの組み立てを行って正しいURIを得る必要があり,最後にこのURIに基づいてページジャンプを行う.ProcessForwardConfig()メソッドでは、ActionForwardに対していくつかの「技術的」な処理が行われているだけで、ビジネスに関連する内容は一切なく、コントローラ(ActionServicelet)とActionを完全に分離し、両者は互いに影響せず、機能モジュール間のばらばらな結合の目的を達成していることがわかる.
モジュール間(システム間)の緩和結合はOO設計が追求してきたが,このような緩和結合を具体的にどのように実現するかはそれほど容易ではない.Strutsの設計は,モジュール間の相互関連影響因子の伝達がオブジェクトの形で包装できることを示唆した.実は、個人的にはStrutsのやり方は少し改善できると思いますが、ActionForwardでgetURI()の方法を提供して最終的なURIを与えたほうがいいのではないでしょうか.
参照先:
1、Beyond MVC: A New Look at the Servlet Infrastructure
2、Allen HolubのBuild user interfaces for object-oriented systemsシリーズの文章は、この文章から多くのオブジェクト向け設計の知識を学ぶことができ、著者はMVCがオブジェクト向けの方法だとは思わないが、私たちMVCの実践者は依然としてオブジェクト向けの知識を学ぶことができる.
3、Struts 1.1の紹介文章:Struts 1.1に深く入り込む
4、Apache Struts Website
5、重複提出問題に関する議論及びそのソリューションについては、『Core J2EE Patterns』(中国語版『J 2 EEコアモデル』)を参考にすることができる.
Deepak Alur,John Crupi,Dan Malks: Core J2EE Patterns-Best Practices and Design Strategies