1、Action中User注入問題css
Action中可能會常常用到已經登錄的User對象,若是每次都從Session中拿會顯得很是繁瑣。能夠想一種方法,當Action想要獲取User對象的時候直接使用,這種方法仍是得須要藉助攔截器的力量,直接在登陸攔截器中實現便可,可是登錄攔截器怎麼知道該Action想要獲取User對象呢?這就須要給Action加上一個接口,若是該Action是該接口的實現類,則表示該Action想要獲取User對象。接口仿照HttpRequestAware接口的形式,名字爲用戶關注接口:html
1 package com.kdyzm.struts.action.aware; 2 3 import com.kdyzm.domain.User; 4 /** 5 * 用戶關注接口,用於Action獲取Session中的User對象 6 * @author kdyzm 7 * 8 */ 9 public interface UserAware { 10 public void setUser(User user); 11 }
這樣在登錄攔截器中直接進行判斷便可,若是用戶已經登錄了,並且請求的Action是UserAware的實現類,那麼就經過setUser方法將User注入到Action中。這樣登陸攔截器中的intercept方法就變成了以下的形式:前端
1 public String intercept(ActionInvocation invocation) throws Exception { 2 System.out.println("被登陸攔截器攔截!"); 3 Action action=(Action) invocation.getAction(); 4 if(action instanceof LoginAction ||action instanceof RegisterAction){ 5 System.out.println("即將進行登陸或者註冊,直接放行!"); 6 return invocation.invoke(); 7 } 8 HttpServletRequest request=ServletActionContext.getRequest(); 9 HttpSession session=request.getSession(); 10 User user=(User) session.getAttribute("user"); 11 if(user==null){ 12 System.out.println("用戶未登陸,必須先登陸再訪問其餘資源!即將跳轉到登錄界面!"); 13 return "toLoginPage"; 14 }else{ 15 System.out.println("用戶已經登錄,登陸攔截器已經放行!"); 16 //若是用戶名不爲空,並且實現了UserAware接口,就須要調用該接口中的相應方法給類中的成員變量賦值 17 //TODO 給Action中User動態賦值的方法 18 if(action instanceof UserAware){ 19 ((UserAware)action).setUser(user); 20 } 21 return invocation.invoke(); 22 } 23 }
2、設計調查頁面,設計調查頁面幾乎是該項目中最複雜的一個頁面了在「個人調查」中的其中一個調查欄目中直接單擊「設計調查」超連接,就直接跳轉到設計調查頁面,固然須要在Action將調查對象(Survey對象)先查詢出來。完整的頁面設計代碼以下這個頁面設計起來很是困難,我也是寫了好幾個小時才完成的,由於須要考慮到不少因素,須要一步一步的進行調試才行。java
完整代碼是:數據庫
1 <%@ page language="java" pageEncoding="utf-8"%> 2 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> 3 <html> 4 <head> 5 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> 6 <link rel="stylesheet" href="${pageContext.servletContext.contextPath}/css/header.css" type="text/css"> 7 <style type="text/css"> 8 table{ 9 border: 1px solid black; 10 border-collapse: collapse; 11 width: 100%; 12 } 13 table tr{ 14 border-collapse: collapse; 15 } 16 tr td{ 17 border:1px solid black; 18 border-collapse: collapse; 19 } 20 a{ 21 color: gray; 22 text-decoration: none; 23 } 24 a:HOVER { 25 color: red; 26 } 27 .tdHL{ 28 text-align: left; 29 border-width: 0px; 30 } 31 .tdHR{ 32 text-align: right; 33 border-width: 0px; 34 } 35 .pageTitle{ 36 background-color: #CCF; 37 } 38 .questionTitle{ 39 background-color: #CCC; 40 } 41 </style> 42 <title>Insert title here</title> 43 </head> 44 <body> 45 <%@ include file="/header.jsp" %> 46 <div> 47 <!-- 先設計一個變量保存住surveyid --> 48 <s:set value="%{surveyId}" name="id"/> 49 <table> 50 <tr> 51 <td colspan="2">設計調查</td> 52 </tr> 53 <tr> 54 <td class="tdHL"> 55 <!-- 這裏使用sturs2標籤直接判斷圖片是否存在! --> 56 <!-- 在這裏加上一個logo標識 --> 57 <s:if test="isLogoImageExists()"> 58 <img width="40px" alt="這是logo標識" src="<s:url value='%{logoPath}'/>"/> 59 </s:if> 60 <s:else> 61 <!-- 若是圖片不存在,則什麼都不顯示 --> 62 </s:else> 63 <s:property value="title"/> 64 </td> 65 <td class="tdHR"> 66 <s:a action="SurveyAction_toUploadLogoPage.action" namespace="/"> 67 <s:param name="surveyId" value="%{#id}"></s:param> 68 增長Logo 69 </s:a> 70 71 <s:a action="SurveyAction_toEditSurveyPage.action" namespace="/"> 72 <s:param name="surveyId" value="%{#id}"></s:param> 73 編輯調查</s:a> 74 <s:a action="PageAction_toAddPagePage.action" namespace="/"> 75 <s:param value="%{#id}" name="surveyId"></s:param> 76 增長頁</s:a> 77 </td> 78 </tr> 79 <tr> 80 <td colspan="2"> 81 <!-- 主幹內容開始 --> 82 <table> 83 <tr> 84 <td width="20px"></td> 85 <td width="*"> 86 <!-- 迭代頁面集合 --> 87 <table> 88 <s:iterator value="%{pages}" var="page"> 89 <s:set var="pId" value="%{#page.pageId}"></s:set> 90 <tr> 91 <td> 92 <table> 93 <tr class="pageTitle"> 94 <!-- 頁面標題 --> 95 <td width="40%" class="tdHL"> 96 <s:property value="%{#page.title}"/> 97 </td> 98 <td width="60%" class="tdHR"> 99 <s:a action="PageAction_toEditPageTitlePage.action" namespace="/"> 100 <s:param name="pageId" value="%{#pId}"></s:param> 101 <s:param name="surveyId" value="%{#id}"></s:param> 102 編輯頁面標題</s:a> 103 <s:a action="PageAction_toSelectTargetPage.action" namespace="/"> 104 <s:param name="pageId" value="%{#pId}"></s:param> 105 <s:param name="surveyId" value="%{#id}"></s:param> 106 移動/複製頁 107 </s:a> 108 109 <s:a action="QuestionAction_toSelectQuestionTypePage.action" namespace="/"> 110 <s:param name="pageId" value="%{#pId}"></s:param> 111 <s:param name="surveyId" value="%{#id}"></s:param> 112 增長問題</s:a> 113 <s:a action="PageAction_deletePage.action" namespace="/"> 114 <s:param name="pageId" value="%{#pId}"></s:param> 115 <s:param name="surveyId" value="%{#id}"></s:param> 116 刪除頁</s:a> 117 </td> 118 </tr> 119 <tr> 120 <td colspan="2"> 121 <table> 122 <tr> 123 <td width="20px"></td> 124 <td> 125 <!-- 迭代問題的集合 --> 126 <table> 127 <s:iterator value="%{#page.questions}" var="question"> 128 <s:set var="qid" value="%{#question.questionId}"></s:set> 129 <!-- 問題題幹 --> 130 <tr class="questionTitle"> 131 <td class="tdHL"><s:property value="%{#question.title}"/></td> 132 <td class="tdHR"> 133 <s:a action="QuestionAction_editQuestion.action" namespace="/"> 134 <s:param name="pageId" value="%{#pId}"></s:param> 135 <s:param name="surveyId" value="%{#id}"></s:param> 136 <s:param name="questionId" value="%{#qid}"></s:param> 137 編輯問題</s:a> 138 <s:a action="QuestionAction_deleteQuestion.action" namespace="/"> 139 <s:param name="pageId" value="%{#pId}"></s:param> 140 <s:param name="surveyId" value="%{#id}"></s:param> 141 <s:param name="questionId" value="%{#qid}"></s:param> 142 刪除問題</s:a> 143 </td> 144 </tr> 145 <!-- 問題主體,主要涉及的問題就是問題的分類 --> 146 <tr> 147 <td colspan="2"> 148 <!-- 定義變量,爲當前類型的題型 --> 149 <s:set value="%{#question.questionType}" var="qt"></s:set> 150 <!-- 第一種題型:帶有單選框或者複選框的 151 題目標識就是題號小於4,0-3 152 --> 153 <s:if test="#qt lt 4"> 154 <s:iterator value="#question.optionTextArr"> 155 <input type='<s:property value="#qt<2?'radio':'checkbox'"/>'> 156 <s:property/> 157 <s:if test="#qt==1 || #qt==3"> 158 <br/> 159 </s:if> 160 </s:iterator> 161 <!-- 處理other的狀況 --> 162 <s:if test="#question.other"> 163 <input type='<s:property value="#qt<2?'radio':'checkbox'"/>'>其它 164 <s:if test="#question.otherType==1"> 165 <input type="text"> 166 </s:if> 167 <s:elseif test="#question.otherType==2"> 168 <s:select list="#question.otherSelectOptionArr"> 169 </s:select> 170 </s:elseif> 171 </s:if> 172 </s:if> 173 <!-- 第二種題型,是下拉列表類型的題型 --> 174 <s:elseif test="#qt==4"> 175 <s:select list="#question.optionTextArr"></s:select> 176 </s:elseif> 177 <s:elseif test="#qt==5"> 178 <s:textfield></s:textfield> 179 </s:elseif> 180 <!-- 第三種題型,矩陣問題,6,7,8 --> 181 <s:else> 182 <table> 183 <!-- 列頭 --> 184 <tr> 185 <td></td> 186 <s:iterator value="#question.matrixColTitleArr"> 187 <td><s:property/></td> 188 </s:iterator> 189 </tr> 190 <!-- 輸出N多行 --> 191 <s:iterator value="#question.matrixRowTitleArr"> 192 <tr> 193 <td><s:property/></td> 194 <s:iterator value="#question.matrixColTitleArr"> 195 <td> 196 <s:if test="#qt==6"> 197 <input type="radio"> 198 </s:if> 199 <s:elseif test="#qt==7"> 200 <input type="checkbox"> 201 </s:elseif> 202 <s:elseif test="#qt==8"> 203 <select> 204 <s:iterator value="#question.matrixSelectOptionArr"> 205 <option> 206 <s:property/> 207 </option> 208 </s:iterator> 209 </select> 210 </s:elseif> 211 </td> 212 </s:iterator> 213 </tr> 214 </s:iterator> 215 </table> 216 </s:else> 217 </td> 218 </tr> 219 </s:iterator> 220 </table> 221 </td> 222 </tr> 223 </table> 224 </td> 225 </tr> 226 </table> 227 </td> 228 </tr> 229 </s:iterator> 230 </table> 231 </td> 232 </tr> 233 </table> 234 </td> 235 </tr> 236 </table> 237 </div> 238 </body> 239 </html>
最核心的代碼是對問題種類的判斷:session
1 <s:set value="%{#question.questionType}" var="qt"></s:set> 2 <!-- 第一種題型:帶有單選框或者複選框的 3 題目標識就是題號小於4,0-3 4 --> 5 <s:if test="#qt lt 4"> 6 <s:iterator value="#question.optionTextArr"> 7 <input type='<s:property value="#qt<2?'radio':'checkbox'"/>'> 8 <s:property/> 9 <s:if test="#qt==1 || #qt==3"> 10 <br/> 11 </s:if> 12 </s:iterator> 13 <!-- 處理other的狀況 --> 14 <s:if test="#question.other"> 15 <input type='<s:property value="#qt<2?'radio':'checkbox'"/>'>其它 16 <s:if test="#question.otherType==1"> 17 <input type="text"> 18 </s:if> 19 <s:elseif test="#question.otherType==2"> 20 <s:select list="#question.otherSelectOptionArr"> 21 </s:select> 22 </s:elseif> 23 </s:if> 24 </s:if> 25 <!-- 第二種題型,是下拉列表類型的題型 --> 26 <s:elseif test="#qt==4"> 27 <s:select list="#question.optionTextArr"></s:select> 28 </s:elseif> 29 <s:elseif test="#qt==5"> 30 <s:textfield></s:textfield> 31 </s:elseif> 32 <!-- 第三種題型,矩陣問題,6,7,8 --> 33 <s:else> 34 <table> 35 <!-- 列頭 --> 36 <tr> 37 <td></td> 38 <s:iterator value="#question.matrixColTitleArr"> 39 <td><s:property/></td> 40 </s:iterator> 41 </tr> 42 <!-- 輸出N多行 --> 43 <s:iterator value="#question.matrixRowTitleArr"> 44 <tr> 45 <td><s:property/></td> 46 <s:iterator value="#question.matrixColTitleArr"> 47 <td> 48 <s:if test="#qt==6"> 49 <input type="radio"> 50 </s:if> 51 <s:elseif test="#qt==7"> 52 <input type="checkbox"> 53 </s:elseif> 54 <s:elseif test="#qt==8"> 55 <select> 56 <s:iterator value="#question.matrixSelectOptionArr"> 57 <option> 58 <s:property/> 59 </option> 60 </s:iterator> 61 </select> 62 </s:elseif> 63 </td> 64 </s:iterator> 65 </tr> 66 </s:iterator> 67 </table> 68 </s:else>
以前就說過,每一個問題的位置不能改變,這是由於將會使用該問題的下標獲得該問題是什麼種類的問題,一種有九種類型的問題,每一種問題都對應一種獨一無二的問題類型。app
最終設計效果以下圖所示:固然若是隻是當前階段的話是沒有這種效果的,必須結合以後的添加問題的功能才行。整個頁面都是用表格標籤進行了嵌套,因此顯得比較難看,可是也沒有好的方法,若是有時間的話就會對其進行優化。dom
3、Action中模型賦值問題。(重點)jsp
每個Action基本上都是BaseAction的子類,繼承BaseAction的優勢就是不須要每次都實現模型驅動接口而且重寫getModel方法了,模型的賦值過程將會在父類中實現,這裏固然也會用到泛型。ide
可是實現了模型驅動接口須要注意一點事項:可能會有數據庫中的信息和前端頁面顯示的信息不一致的狀況發生。
首先實現編輯調查功能,小功能很是小,因此略過不提。可是有必要說一下這個過程,由於這是引起該問題的關鍵
在設計調查頁面單擊「編輯調查」->請求SurveyAction.toEditSurveyPage()方法,該方法查詢數據庫,賦值到model->跳轉到editSurveyPage.jsp頁面回顯,這時候詭異的事情就發生了,回顯的時候有異常的狀況發生。回顯的調查標題是「未命名」,可是該調查原來明明有標題是「居民生活水平調查」。爲何會發生這種狀況呢?
緣由分析:棧頂指針未改變致使的。看一下在編輯調查方法中的代碼:
1 //跳轉到編輯調查的頁面上去 2 public String toEditSurveyPage() throws Exception{ 3 this.model=this.surveyService.getModelById(this.model.getSurveyId()); 4 return "toEditSurveyPage"; 5 }
實際上該方法中只有一句代碼而已。就是將數據庫中查到的對象賦值給model對象。好像這樣就將模型給「刷新」了,可是這也只是「好像」而已,實際上並無刷新model對象。
在棧頂中存放的是舊model的引用地址,說到底model只是一個變量而已,若是改變了model的值,只是將model的指針指向了新的地址,可是棧頂的model對象中的值並無被改變。只是沒法再經過model對象訪問到而已。因此若是直接給model對象賦值可是不作其餘修改是沒有任何意義的。
解決方法:
方法1.再次壓棧,示例代碼以下:
ActionContext.getContext().getValueStack().push(model);
固然實際上值棧中就會有兩份model對象了,這樣作的好處就是解決了model賦值的問題,可是也有弊端,每次都須要這麼寫不嫌麻煩麼?並且這麼作一點都不「優雅」。
方法2.經過prepare攔截器加上paramsPrepareStack攔截器棧組合完成該項任務。
具體作法是首先將BaseAction實現Prepare接口,而後在Action中重寫接口中的方法。僞代碼以下:
public void prepareDesignSurvey(){ this.model = xxx ; }
固然最重要的仍是須要改變默認棧爲parameterPrepareStack,不然仍是沒有任何效果。
Action中的目標方法DesignSurvey中直接跳轉頁面便可,不須要再對model進行修改。
爲何使用這種方法可以解決問題:實際上引起該問題的緣由是模型賦值在模型驅動攔截器獲取model以後完成的,這樣就致使了即便改變model對象也不會改變棧頂指針,若是將二者順序顛倒一下,即先給模型賦值,而後模型驅動攔截器再取值,這樣就沒問題了,關鍵是在合適的位置實現模型賦值,首先肯定的是必定是在模型驅動攔截器以前,合適的位置就是prepareInterceptor攔截器,因此實現Prepare接口而後重寫方法便可;可是僅僅這麼作仍是不夠的,由於parametersInterceptor在默認攔截器棧中的順序是在模型驅動攔截器以後,因此在prepareInterceptor攔截器中獲取不到Action中的相關參數,必定會引起相似於id can't load的異常 ,解決方法就是使用paramsPreparedStack攔截器棧,該攔截器棧和默認的攔截器棧的惟一區別就是在prepare攔截器以前增長了一個parameter攔截器,正好解決了Action中屬性賦值的問題。
下面是兩個攔截器棧的定義:
1 <!-- An example of the paramsPrepareParams trick. This stack is exactly 2 the same as the defaultStack, except that it includes one extra interceptor 3 before the prepare interceptor: the params interceptor. This is useful for 4 when you wish to apply parameters directly to an object that you wish to 5 load externally (such as a DAO or database or service layer), but can't load 6 that object until at least the ID parameter has been loaded. By loading the 7 parameters twice, you can retrieve the object in the prepare() method, allowing 8 the second params interceptor to apply the values on the object. --> 9 <interceptor-stack name="paramsPrepareParamsStack"> 10 <interceptor-ref name="exception" /> 11 <interceptor-ref name="alias" /> 12 <interceptor-ref name="i18n" /> 13 <interceptor-ref name="checkbox" /> 14 <interceptor-ref name="multiselect" /> 15 <interceptor-ref name="params"> 16 <param name="excludeParams">dojo\..*,^struts\..*</param> 17 </interceptor-ref> 18 <interceptor-ref name="servletConfig" /> 19 <interceptor-ref name="prepare" /> 20 <interceptor-ref name="chain" /> 21 <interceptor-ref name="modelDriven" /> 22 <interceptor-ref name="fileUpload" /> 23 <interceptor-ref name="staticParams" /> 24 <interceptor-ref name="actionMappingParams" /> 25 <interceptor-ref name="params"> 26 <param name="excludeParams">dojo\..*,^struts\..*</param> 27 </interceptor-ref> 28 <interceptor-ref name="conversionError" /> 29 <interceptor-ref name="validation"> 30 <param name="excludeMethods">input,back,cancel,browse</param> 31 </interceptor-ref> 32 <interceptor-ref name="workflow"> 33 <param name="excludeMethods">input,back,cancel,browse</param> 34 </interceptor-ref> 35 </interceptor-stack>
方法3.修改模型驅動攔截器,設置刷新model標識爲true
模型驅動攔截器代碼很短,看看它到底幹了什麼事:
1 public class ModelDrivenInterceptor extends AbstractInterceptor { 2 3 protected boolean refreshModelBeforeResult = false; 4 5 public void setRefreshModelBeforeResult(boolean val) { 6 this.refreshModelBeforeResult = val; 7 } 8 9 @Override 10 public String intercept(ActionInvocation invocation) throws Exception { 11 Object action = invocation.getAction(); 12 13 if (action instanceof ModelDriven) { 14 ModelDriven modelDriven = (ModelDriven) action; 15 ValueStack stack = invocation.getStack(); 16 Object model = modelDriven.getModel(); 17 if (model != null) { 18 stack.push(model); 19 } 20 if (refreshModelBeforeResult) { 21 invocation.addPreResultListener(new RefreshModelBeforeResult(modelDriven, model)); 22 } 23 } 24 return invocation.invoke(); 25 } 26 27 /** 28 * Refreshes the model instance on the value stack, if it has changed 29 */ 30 protected static class RefreshModelBeforeResult implements PreResultListener { 31 private Object originalModel = null; 32 protected ModelDriven action; 33 34 35 public RefreshModelBeforeResult(ModelDriven action, Object model) { 36 this.originalModel = model; 37 this.action = action; 38 } 39 40 public void beforeResult(ActionInvocation invocation, String resultCode) { 41 ValueStack stack = invocation.getStack(); 42 CompoundRoot root = stack.getRoot(); 43 44 boolean needsRefresh = true; 45 Object newModel = action.getModel(); 46 47 // Check to see if the new model instance is already on the stack 48 for (Object item : root) { 49 if (item.equals(newModel)) { 50 needsRefresh = false; 51 break; 52 } 53 } 54 55 // Add the new model on the stack 56 if (needsRefresh) { 57 58 // Clear off the old model instance 59 if (originalModel != null) { 60 root.remove(originalModel); 61 } 62 if (newModel != null) { 63 stack.push(newModel); 64 } 65 } 66 } 67 } 68 }
在模型驅動攔截器中有個很是重要的屬性:refreshModelBeforeResult,該屬性是一個標識字段的屬性,用於標識是否須要刷新model,什麼是刷新model,就是從新獲取model的值並壓棧,這樣最終就可以實現棧頂中的model對象是最新的model對象。
實現原理:執行模型驅動攔截器的時候,會判斷refreshModelBeforeResult的值是否爲true,若是爲true,則給invocation對象添加一個PreResultListener監聽器,模型驅動攔截器中有一個靜態類RefreshModelBeforeResult實現了該監聽器的接口,觀察該實現類的類名,翻譯成中文就是「在執行Result以前刷新Model對象」,真是一個直白的方法名,咱們知道執行結果集是在最後完成的,那麼在執行結果集以前刷新Model對象的話就不會出現上述問題了。它實現刷新的原理十分簡單,就是先讓老的Model對象彈棧,再獲取新的model對象並將新的model對象壓棧,OK,前端頁面獲取的必定就是最新的Model對象了。
方法4:使用BeanUtils中的copy屬性的方法直接給model對象中的全部屬性從新賦值,使用這種方式的好處就是不須要考慮model對象是新的對象仍是老的對象,可是有一點很差之處就是該方法底層使用反射技術,效率比較低,並且每次都要寫該方法實際上和方法一沒有什麼差異了,都比較麻煩,並且顯得十分的「不優雅」。
4、編輯調查,略。