posts - 35,  comments - 6,  trackbacks - 0

          關鍵字: Struts       

          進入新的項目組,
          checkout項目下來,
          看了一下其整體結構與代碼,哎,比較亂。
          經過我的建議,部門經理讓我對該項目進行全面重構。

          首先,此項目是以AppFuse作為基礎的,還記得robin說它就一toy
          選擇的模板是iBatis + Spring + Struts
          我的第一步工作是要隔離Struts。

          Struts是老牌的MVC實現,那個年代IoC和輕量級還沒現在流行,框架侵入性也沒有得到重視。
          所以Struts的實現讓應用程序嚴重依賴它:
          1.所有控制器都必需繼承Action類
          2.所有數據封裝類必需繼承ActionForm
          3.控制器方法execute必需返回ActionForward,與Struts藕合
          4.控制器方法execute的參數ActionMapping,與Struts藕合
          5.控制器方法execute的參數HttpServletRequest,HttpServletResponse,與Servlet容器藕合
          6.由于Action類是Struts管理的,不支持Service類的IoC注入,除非將控制權委托給IoC容器,再配一遍(如:org.springframework.web.struts.DelegatingActionProxy)。

          目標:
          1.控制器不繼承任何類,也不實現任何接口
          2.數據封裝Form類應為簡單的POJO,不要繼承ActionForm
          3.execute返回值可以是任意對象(包括基本類型和void),
          標準返回String,即forward的索引值,
          如果返回其它類型對象就調用其toString。
          如果返回類型為void或返回值為null,forward到默認的"success"
          4和5.execute只傳入POJO的Form,
          如果該動作不需要Form數據,也可以保持空的參數列表。
          如果有多個參數,第一個參數為Form(作為傳入,也作為傳出,這個是struts已經實現的規則),后面的都為傳出對象,必需保證為POJO,傳出對象會根據struts的action配置的scope,放入相應域。
          6.支持IoC注入Service,即然IoC,當然不能依賴某個具體IoC容器,沒有Spring一樣運行。要不然會被ajoo一頓臭罵,什么什么? IoC還:容器類.getXXX()?
          7.當然,還要實現一個線程安全的容器類,持有與Servlet相關的信息,
          這樣,若有特殊要求需要訪問HttpServletRequest,HttpServletResponse
          則可以通過:容器類.get當前線程容器().getRequest()方式獲取。

          最后類應該像這樣:

          java 代碼
          1. // Action無任何要求(哦,不對,要求有一個沒有參數的構造函數,不算太高吧^_^)   
          2. public class ItemAction {   
          3.        
          4.     private ItemService itemService;   
          5.        
          6.     // IoC注入   
          7.     public void setItemService(ItemService itemService) {   
          8.         this.itemService = itemService;   
          9.     }   
          10.        
          11.     // 只有一個forward "success" 時,也可以用void   
          12.     // ItemForm寫實際類型,不要用Object,然后在函數內強制轉型,多麻煩   
          13.     // 建議參數加final   
          14.     public String viewAllItems(final ItemForm itemForm) {   
          15.         itemForm.setItems(itemService.getItems());   
          16.         return "success";   
          17.     }   
          18.        
          19.     //多個跳轉,返回String識別   
          20.     public String saveItem(final ItemForm itemForm) {   
          21.         return itemService.saveItem(itemForm.getItem()) ? "success" : "failure";   
          22.     }   
          23. }   

           

          不用說,這樣的類是易于測試的。
          例如:

          java 代碼
          1. public void testRightAllViewItems() {   
          2.     ItemAction itemAction = new ItemAction();   
          3.     ItemService itemService = new ItemServiceMock();   
          4.     itemAction.setItemService(itemService);   
          5.   
          6.     ItemsForm itemsForm = new ItemsForm();   
          7.     String forward = itemAction.viewAllItems(itemsForm);   
          8.   
          9.     assertEquals("沒有正確跳轉!""success", forward);   
          10.     assertNotNull("沒有向itemsForm寫入數據!", itemsForm.getItems());   
          11.   
          12.     // 下面這些斷言是判斷和ItemServiceMock中寫硬編碼的值是否一樣   
          13.     assertEquals("取得的items大小與ItemServiceMock中的不一致!"1, itemsForm.getItems().size());   
          14.     Item item = (Item) itemsForm.getItems().iterator().next();   
          15.     assertEquals("獲取的item的Id不對!"new Long(5), item.getId());   
          16.     assertEquals("獲取的item的CategoryId不對!"new Long(2), item.getCategoryId());   
          17.     assertEquals("獲取的item的Name不對!""aaa", item.getName());   
          18.     assertEquals("獲取的item的Price不對!"new Float(1.2), item.getPrice());   
          19. }   

          當然還有測試傳入一個null的ItemsForm等的其它測試,這里就不例舉了。

           

          好,明確目標后,開始重構,重構后要先保證以前的代碼也能運行。

          由于要使Action徹底獨立,我暫時想到的辦法是反射回調。
          我先寫一個通用的Action,然后回調具體的控制器類。
          實現如下:
          通用Action

          java 代碼
          1. package com.ynmp.webapp.frame;   
          2.   
          3. import java.lang.reflect.Method;   
          4. import java.util.Map;   
          5.   
          6. import javax.servlet.http.HttpServletRequest;   
          7. import javax.servlet.http.HttpServletResponse;   
          8.   
          9. import org.apache.commons.logging.Log;   
          10. import org.apache.commons.logging.LogFactory;   
          11. import org.apache.struts.action.Action;   
          12. import org.apache.struts.action.ActionForm;   
          13. import org.apache.struts.action.ActionForward;   
          14. import org.apache.struts.action.ActionMapping;   
          15.   
          16. import com.ynmp.webapp.frame.provider.ServiceProvider;   
          17. import com.ynmp.webapp.frame.provider.SpringServiceProvider;   
          18. import com.ynmp.webapp.frame.util.ClassUtils;   
          19.   
          20. public class BaseAction extends Action {   
          21.        
          22.     private static final Log log = LogFactory.getLog(BaseAction.class);   
          23.        
          24.     private static final String UNCALL_METHOD = "*";   
          25.        
          26.     private static final String SUCCESS_FORWARD = "success";   
          27.        
          28.     private static final String ERROR_FORWARD = "error";   
          29.        
          30.     public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {   
          31.         String forward;   
          32.         try {   
          33.             ActionContext.initCurrentContext(request, response);   
          34.             forward = getActionForward(mapping.getScope(), mapping.getParameter(), form);   
          35.         } catch (Exception e) {   
          36.             e.printStackTrace();   
          37.             log.warn(e);   
          38.             forward = ERROR_FORWARD;   
          39.             request.setAttribute("BeanActionException", e);   
          40.         }   
          41.         return mapping.findForward(forward);   
          42.     }   
          43.        
          44.     // 處理forward   
          45.     private String getActionForward(String scope, String config, Object model) throws Exception { // TODO Exception處理待重構   
          46.         String forward = SUCCESS_FORWARD;   
          47.         ActionConfig actionConfig = new ActionConfig(config);   
          48.         Object actionObject = populateActionClass(actionConfig.getClassName());   
          49.         Object returnObject = callActionMethod(actionObject, actionConfig.getMethodName(), model, scope);   
          50.         if (returnObject!= null && String.valueOf(returnObject) != null) {   
          51.             forward = String.valueOf(returnObject);   
          52.         }   
          53.         return forward;   
          54.     }   
          55.        
          56.     // 處理action類   
          57.     private Object populateActionClass(String className) throws Exception {   
          58.         Class actionClass = Class.forName(className);   
          59.         Object action = actionClass.newInstance();   
          60.         injectService(action);   
          61.         return action;   
          62.     }   
          63.        
          64.     // 簡單實現IoC   
          65.     private void injectService(Object action) throws Exception {   
          66.         ServiceProvider serviceProvider = new SpringServiceProvider(getServlet()); // TODO 待重構為策略   
          67.         Method[] methods = action.getClass().getMethods();   
          68.         for (int i = 0; i < methods.length; i ++) {   
          69.             if (methods[i].getName().startsWith("set")) {   
          70.                 Class[] parameters = methods[i].getParameterTypes();   
          71.                 if (parameters != null && parameters.length == 1) {   
          72.                     String methodName = methods[i].getName();   
          73.                     String serviceName = methodName.substring(3,4).toLowerCase() + methodName.substring(4);   
          74.                     methods[i].invoke(action, new Object[]{serviceProvider.getService(serviceName, parameters[0])});   
          75.                 }   
          76.             }   
          77.         }   
          78.     }   
          79.        
          80.     // 處理action方法   
          81.     private Object callActionMethod(Object action, String methodName, Object model, String scope) throws Exception {   
          82.         if (UNCALL_METHOD.equals(methodName)) return null;   
          83.         Method actionMethod = ClassUtils.findMethodByName(action.getClass(), methodName);   
          84.         Object[] parameters = initParameters(actionMethod, model);   
          85.         Object returnObject = actionMethod.invoke(action, parameters);   
          86.         outParameters(getScopeMap(scope), parameters);   
          87.         return returnObject;   
          88.     }   
          89.        
          90.     // 組裝action方法的參數列表   
          91.     private Object[] initParameters(Method actionMethod, Object model) throws Exception {   
          92.         Class[] parameterTypes = actionMethod.getParameterTypes();   
          93.         int parameterSize = parameterTypes.length;   
          94.         if (parameterSize == 0) {   
          95.             return new Object[0];   
          96.         } else if (parameterSize == 1) {   
          97.             return new Object[] {model};   
          98.         } else {   
          99.             Object[] parameters = new Object[parameterSize];   
          100.             parameters[0] = model;   
          101.             for (int i = 1; i < parameterSize; i ++) {   
          102.                 parameters[i] = parameterTypes[i].newInstance();   
          103.             }   
          104.             return parameters;   
          105.         }   
          106.     }   
          107.        
          108.     // 向指定范圍域輸出參數   
          109.     private void outParameters(Map scopeMap, Object[] parameters) throws Exception {   
          110.         if (parameters.length < 2return ;   
          111.         for (int i = 1; i < parameters.length; i ++) {   
          112.             String name = ClassUtils.getLowerClassName(parameters[i].getClass());   
          113.             scopeMap.put(name, parameters[i]);   
          114.         }   
          115.     }   
          116.        
          117.     // 通過scope配置找到相應域Map   
          118.     private Map getScopeMap(String scope) {   
          119.         if ("request".equals(scope)) {   
          120.             return ActionContext.getActionContext().getRequestMap();   
          121.         } else if ("session".equals(scope)) {   
          122.             return ActionContext.getActionContext().getSessionMap();   
          123.         } else if ("application".equals(scope)) {   
          124.             return ActionContext.getActionContext().getApplicationMap();   
          125.         }   
          126.         throw new RuntimeException("不合法的scope:" + scope + ",scope必需為request,session,application中的一個!");   
          127.     }   
          128. }   

           

          IoC的Service供給接口

          java 代碼
          1. package com.ynmp.webapp.frame.provider;   
          2.   
          3. public interface ServiceProvider {   
          4.   
          5.     public Object getService(String serviceName, Class serviceClass) throws Exception;   
          6.   
          7. }   

           

          Spring的Service供給實現,依賴Spring,作為一種策略應該不成問題。

          java 代碼
          1. package com.ynmp.webapp.frame.provider;   
          2.   
          3. import javax.servlet.http.HttpServlet;   
          4.   
          5. import org.springframework.context.ApplicationContext;   
          6. import org.springframework.web.context.support.WebApplicationContextUtils;   
          7.   
          8. public class SpringServiceProvider implements ServiceProvider {   
          9.        
          10.     private HttpServlet servlet;   
          11.        
          12.     public SpringServiceProvider(HttpServlet servlet) {   
          13.         this.servlet = servlet;   
          14.     }   
          15.   
          16.     public Object getService(String serviceName, Class serviceClass) throws Exception {   
          17.         ApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(servlet.getServletContext());   
          18.         Object serviceObject = ctx.getBean(serviceName);   
          19.         if (serviceObject == null) {   
          20.             throw new RuntimeException("在IoC容器中未找到引用:" + serviceName);   
          21.         }   
          22.         return serviceObject;   
          23.     }   
          24.   
          25. }   

           

          線程安全的Servlet相關信息持有類,
          還有幾個Map的封裝,不貼也應該猜得到,
          就是map.put時調用request,session,cookie,application相應的setAttribute等,get也類似

          代碼
          1. package com.ynmp.webapp.frame;   
          2.   
          3. import java.util.HashMap;   
          4. import java.util.Map;   
          5.   
          6. import javax.servlet.http.HttpServletRequest;   
          7. import javax.servlet.http.HttpServletResponse;   
          8.   
          9. import com.ynmp.webapp.frame.map.ApplicationMap;   
          10. import com.ynmp.webapp.frame.map.CookieMap;   
          11. import com.ynmp.webapp.frame.map.ParameterMap;   
          12. import com.ynmp.webapp.frame.map.RequestMap;   
          13. import com.ynmp.webapp.frame.map.SessionMap;   
          14.   
          15. public class ActionContext {   
          16.   
          17.     private static final ThreadLocal localContext = new ThreadLocal();   
          18.   
          19.     private HttpServletRequest request;   
          20.   
          21.     private HttpServletResponse response;   
          22.   
          23.     private Map cookieMap;   
          24.   
          25.     private Map parameterMap;   
          26.   
          27.     private Map requestMap;   
          28.   
          29.     private Map sessionMap;   
          30.   
          31.     private Map applicationMap;   
          32.   
          33.     private ActionContext() {   
          34.         cookieMap = new HashMap();   
          35.         parameterMap = new HashMap();   
          36.         requestMap = new HashMap();   
          37.         sessionMap = new HashMap();   
          38.         applicationMap = new HashMap();   
          39.     }   
          40.   
          41.     static void initCurrentContext(HttpServletRequest request,   
          42.             HttpServletResponse response) {   
          43.         ActionContext ctx = getActionContext();   
          44.         ctx.request = request;   
          45.         ctx.response = response;   
          46.         ctx.cookieMap = null;   
          47.         ctx.parameterMap = null;   
          48.         ctx.requestMap = null;   
          49.         ctx.sessionMap = null;   
          50.         ctx.applicationMap = null;   
          51.     }   
          52.   
          53.     public static ActionContext getActionContext() {   
          54.         ActionContext ctx = (ActionContext) localContext.get();   
          55.         if (ctx == null) {   
          56.             ctx = new ActionContext();   
          57.             localContext.set(ctx);   
          58.         }   
          59.         return ctx;   
          60.     }   
          61.   
          62.     public Map getCookieMap() {   
          63.         if (cookieMap == null) {   
          64.             cookieMap = new CookieMap(request, response);   
          65.         }   
          66.         return cookieMap;   
          67.     }   
          68.   
          69.     public Map getParameterMap() {   
          70.         if (parameterMap == null) {   
          71.             parameterMap = new ParameterMap(request);   
          72.         }   
          73.         return parameterMap;   
          74.     }   
          75.   
          76.     public Map getRequestMap() {   
          77.         if (requestMap == null) {   
          78.             requestMap = new RequestMap(request);   
          79.         }   
          80.         return requestMap;   
          81.     }   
          82.   
          83.     public Map getSessionMap() {   
          84.         if (sessionMap == null) {   
          85.             sessionMap = new SessionMap(request);   
          86.         }   
          87.         return sessionMap;   
          88.     }   
          89.   
          90.     public Map getApplicationMap() {   
          91.         if (applicationMap == null) {   
          92.             applicationMap = new ApplicationMap(request);   
          93.         }   
          94.         return applicationMap;   
          95.     }   
          96.   
          97.     public HttpServletRequest getRequest() {   
          98.         return request;   
          99.     }   
          100.   
          101.     public HttpServletResponse getResponse() {   
          102.         return response;   
          103.     }   
          104.   
          105.     public String getAppURL() {   
          106.         StringBuffer url = new StringBuffer();   
          107.         int port = request.getServerPort();   
          108.         if (port < 0) {   
          109.             port = 80;   
          110.         }   
          111.         String scheme = request.getScheme();   
          112.         url.append(scheme);   
          113.         url.append("://");   
          114.         url.append(request.getServerName());   
          115.         if ((scheme.equals("http") && (port != 80))   
          116.                 || (scheme.equals("https") && (port != 443))) {   
          117.             url.append(':');   
          118.             url.append(port);   
          119.         }   
          120.         url.append(request.getContextPath());   
          121.         return url.toString();   
          122.     }   
          123.   
          124. }   

           

          Struts配置中,parameter屬性是作為擴展用的,所以我們可以利用它。
          改動:
          parameter指定控制器類的類名和方法名,格式為:包名.類名:函數名
          type固定為com.ynmp.webapp.frame.BaseAction
          如:

          xml 代碼
          1. <action path="/item_list" parameter="com.ynmp.webapp.action.ItemAction:viewAllItems" name="itemsForm" type="com.ynmp.webapp.frame.BaseAction">  
          2.     <forward name="success" path="/item_list.jsp" />  
          3. </action>  

           

          配置管理類:

          java 代碼
          1. package com.ynmp.webapp.frame;   
          2.   
          3. public class ActionConfig {   
          4.        
          5.     private static final String ACTION_CONFIG_REGEX = "^([A-Z|a-z|_]+\\.)+[A-Z|a-z|_]+\\:(([A-Z|a-z|_]+)|\\*)$";   
          6.        
          7.     private String className;   
          8.        
          9.     private String methodName;   
          10.        
          11.     public ActionConfig(String config) {   
          12.         if (config == null    
          13.                 || config.length() == 0    
          14.                 || ! config.replaceAll(" """).matches(ACTION_CONFIG_REGEX)) {   
          15.             throw new RuntimeException("Parameter=\"" + config + "\" 格式不合法!應為:包名.類名:方法名,如:com.company.UserAction:login");   
          16.         }   
          17.         int index = config.indexOf(":");   
          18.         className = config.substring(0, index).trim();   
          19.         methodName = config.substring(index + 1).trim();   
          20.     }   
          21.        
          22.     public ActionConfig(String className, String methodName) {   
          23.         this.className = className;   
          24.         this.methodName = methodName;   
          25.     }   
          26.        
          27.     public String getClassName() {   
          28.         return className;   
          29.     }   
          30.   
          31.     public String getMethodName() {   
          32.         return methodName;   
          33.     }   
          34.   
          35.     public void setClassName(String className) {   
          36.         this.className = className;   
          37.     }   
          38.   
          39.     public void setMethodName(String methodName) {   
          40.         this.methodName = methodName;   
          41.     }   
          42.        
          43. }   

           

          Class輔助工具類

          java 代碼
          1. package com.ynmp.webapp.frame.util;   
          2.   
          3. import java.lang.reflect.Method;   
          4.   
          5. public class ClassUtils {   
          6.        
          7.     public static Method findMethodByName(Class clazz, String methodName) {   
          8.         int count = 0;   
          9.         Method actionMethod = null;   
          10.         Method[] methods = clazz.getMethods();   
          11.         for (int i = 0; i < methods.length; i ++) {   
          12.             if (methods[i].getName().equals(methodName)) {   
          13.                 // 其實也可以不檢查函數是否重載,直接返回第一個定義的,   
          14.                 // 但這樣有可能會使程序員迷惑,還是檢查一下重載。   
          15.                 count ++;   
          16.                 if (count > 1) {   
          17.                     throw new RuntimeException(clazz + " 類中有重載的同名方法: " + methodName + ",無法判定使用哪一個!");   
          18.                 }   
          19.                 actionMethod = methods[i];   
          20.             }   
          21.         }   
          22.         if (count == 0 || actionMethod == null) {   
          23.             throw new RuntimeException(clazz + " 類中找到不方法: " + methodName);   
          24.         }   
          25.         return actionMethod;   
          26.     }   
          27.        
          28.     public static String getLowerClassName(Class clazz) {   
          29.         String className = clazz.getName();   
          30.         int index = className.lastIndexOf(".");   
          31.         if (index != -1) {   
          32.             className = className.substring(index + 1);   
          33.         }   
          34.         return className.substring(0,1).toLowerCase() + className.substring(1);   
          35.     }   
          36.        
          37. }   

           

          其它JUnit的測試類就不貼了。

          現在Action解放了,Form對象還沒解放
          還是必需繼承ActionForm,
          因為<form-bean配置會檢查該對象是否繼承于ActionForm,否則報錯。
          驗證框架和ActionForm也有點藕合。
          我現在還沒有想到好的辦法,先搞個權宜之計,
          寫了一個BaseForm,繼承于ActionForm,然后將從ActionForm中繼承來的方法全給final掉
          其它Form都繼承于BaseForm,這樣先保證Form不會重寫ActionForm中的方法,
          看起來像個POJO,若以后能去掉ActionForm,就只要改BaseForm

          posted on 2007-04-23 07:09 NG 閱讀(282) 評論(0)  編輯  收藏

          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          <2007年4月>
          25262728293031
          1234567
          891011121314
          15161718192021
          22232425262728
          293012345

          常用鏈接

          留言簿(1)

          隨筆檔案(35)

          文章分類(5)

          文章檔案(2)

          新聞檔案(5)

          java link

          搜索

          •  

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 凌海市| 许昌县| 海晏县| 铜山县| 上饶县| 鄂尔多斯市| 潍坊市| 甘德县| 高唐县| 射阳县| 桃源县| 青海省| 太白县| 英吉沙县| 如皋市| 南通市| 平遥县| 平顺县| 柘荣县| 太仆寺旗| 双柏县| 团风县| 原阳县| 开平市| 永康市| 达孜县| 扶沟县| 博湖县| 永定县| 高台县| 上蔡县| 岚皋县| 沈阳市| 清丰县| 宁强县| 西宁市| 会宁县| 东乡| 太仆寺旗| 南昌县| 凭祥市|