Redirect After Post 是 web application 常用的技巧。簡單講就是 user submit form、做完 transaction、然後 "redirect" 到成功網頁。使用 http redirect 可以防止 browser refresh button 所引起的二次 submit (如果你不知道這是什麼,可以自己試一遍 submit form 後,再按 refresh 看看)
上面附的連結裡,他使用的是 Post-Redirect-Get pattern -- post 完之後,接著 redirect,然後成功網頁再去 Get 資料(也許是從資料庫),而且他建議所有的網頁都這樣寫。個人認為這樣太過火了,因為真正會出現 二次 submit 問題多半是 "最後一個 submit網頁" (例如結算購物車的網頁),在這裡使用 Redirect After Post 就夠了,其他的網頁就算出現二次 submit,由於不會動到資料庫,頂多像是購物車的商品數量會加 1 而已,最後結算前 user 還可以再更改,所以這並不構成問題。
好,回到正題。在 Struts 裡做 RedirectAfterPost 很簡單:
public class SaveAccountAction { public ActionForward execute(ActionMapping mapping...) { Account account = (Account)request.getAttribute("account") ; getAccountService().saveAccount(account) ; return mapping.findForward("success") ; } } in struts-config.xml: <action path="/account/save" type="mycom.SaveAccountAction" > <!-- 設定 redirect="true" 即可 --> <forward redirect="true" name="success" path="/accout/success.jsp" > </action>user submit /account/save.do 後,便 redirect 到 /account/success.jsp。這大家應該沒啥問題... 不過這帶出另一個問題:如何在 /account/success.jsp 裡面顯示訊息-- 已建立 XXX 帳號。在還未使用 redirect 之前,我們可以將 ActionMessage 儲在 request 裡:
public ActionForward execute(ActionMapping mapping...) { Account account = (Account)request.getAttribute("account") ; getAccountService().saveAccount(account) ; //create messages ActionMessages messages = new ActionMessages() ; messages.add(ActionMessages.GLOBAL_MESSAGE , new ActionMessage("account.save.success"), account.getName()); //save ActionMessage in request, 不過在 redirect 時不適用 saveMessages(request, messges) ; return mapping.findForward("success") ; } in /account/success.jsp: <html:messages id="msg" message="true"> ${msg} </html:messages>
由於 redirect 會產生第二個 request,所以 saveMessages(request, messages) 是沒有作用的。為解決這個問題,Struts 1.2 追加一個 method:
HttpSession session = request.getSession(false) ; saveMessages(session, messges) ; //將 ActionMessage 儲存到 session 裡
有了這個之後,redirect 之後也可以看到訊息了。聰明的看官應該可以發現這種做法有個問題 (第三個問題了...) 就是什麼時候該移除掉這個 ActionMessage ? 難道一直放在 session 裡嗎?對於這個問題,Struts 有個解法 -- 當你下次進入 Struts 時 (也就是經由 *.do 的path進入ActionServlet),Struts 會將 session 裡所有的 ActionMessage 移除掉,比方說我們的 success.jsp 裡這樣寫:
in /account/success.jsp <!-- show message here --> <html:messages id="msg" message="true"> ${msg} </html:messages> <!-- 使用者若點了下面的 link,ActionMessage 便會自動清除。 tag 展開後為:<a href="/yourContext/account/home.do" >Back to Top</a> --> <html:link action="/account/home">Back to Top</a> in struts-config.xml: <!-- 替 /account/home.jsp 做一個 logical path --> <action path="/account/home" forward="/account/home.jsp" />
上面寫的 Back to Top 只是個範例,只要使用者離開 success.jsp 網頁時,是經由 *.do 的 path ,ActionServlet 便會移除掉 ActionMessages... 所以... 這又帶出了下個問題 (第四個了 -_-;;) -- 不要讓 page 裡出現 *.jsp 的連結:
<!-- 錯誤範例 1,直接提供 .jsp link --> <a href="/yourContext/account/home.jsp" >Back to Top</a> <!-- 錯誤範例 2,直接forward 到 .jsp link --> in struts-config.xml: <forward name="home" path="/account/home.jsp" /> in /account/home.jsp: <html:link foward="home">Back to Top</a> <!-- 注意上面 tag 展開後為: <a href="/yourContext/account/home.jsp" >Back to Top</a> -->
上述的兩種情形,第一種應該不常見,第二種就要小心了,因為雖然沒在 page 裡直接寫 .jsp 的URI,但是 <html:link foward="home" 展開後卻會變成 .jsp。所以最好將所有要在網頁上提供連結的 .jsp 都 mapping 成 action。(其實這是 Struts 的基本精神,它希望所有的真實 URI 都 mapping 成 logical 的 path)
OK,總結一下:
- 使用 RedirectAfterPost 解決二次 submit 問題
- 使用 saveMessages(session, messages) 將 ActionMessage 儲存在 session
- 使用 <html:messages> 在 jsp 裡顯示訊息
- 不要讓 *.jsp 這種網址出現在 page 上,全部都要使用 *.do (使用 tag-- <html:link action="/foo/bar" />