隨筆-67  評論-522  文章-0  trackbacks-0
              這一章,大象將詳細分析web層代碼,以及struts2的注解插件——struts2-convention的用法和其它相關知識。
              第四部分:透析控制層
              上一章對dao、entity、service三層進行了詳細的分析,并對代碼進行了測試。測試結果表明這部分功能沒問題,可以正常使用。本章將對最后一個web層進行詳細說明,盡可能的講明白這些知識要點。
              數據庫
              本例使用MySQL數據庫,只有三張表,一張用于管理表主鍵的generator_table,另外兩張是人員表與角色表。
              
              
              
              這里我有一點需要說明一下,在學習JPA——Hibernate Annotation使用實例
          一文中,我將generator_table設了一個id主鍵字段,其實這個字段是不需要的,直接將g_key設為主鍵。這樣設計更好些,因為表名不可能一樣,所以這個存放各個表主鍵的鍵名也不會一樣。
              userrole這兩張表只設了一個主鍵,沒有建立外鍵關聯,而且大象也很反對建立表之間的外鍵關聯。因為這樣做之后,約束太多,在實際開發中,很容易出問題,這是我親身體會過的。所以我建議只對表設置一個流水號主鍵,其它的都可以根據業務關系來設計字段,這樣會更靈活。
              這里對各個字段都默認將它們設置為null,因為針對不同的表,你都會實現相應的功能,你當然會知道哪些字段是不能為空的,哪些是可以為空的。而且在做數據庫設計的時候,你也不可能在短時間內,面面俱到的把所有問題都考慮進去,根據需求的變化,在開發過程中,也是經常會遇到修改數據庫的情況。如果之前過于強調字段的非空設置,在編寫代碼時,為了減少出錯,腦袋里可能會不停的想,啊,這個字段是非空的嗎?哪個字段不是非空的吧?然后反復對比數據庫進行檢查,會使人束手束腳很不舒服。因為這些全部都可以人為來控制,所以除了主鍵外,將其它字段都設為null有利于開發人員更好的進行工作。
              有人會說了,進行非空設置是一種約束,當程序出錯時,很容易發現問題。當然,這話說得沒錯。大象只是建議,從沒說過一定要這樣做,我只是說下自己的一點經驗總結,僅此而已!想怎么實現都是你的自由。
              struts2-convention
              既然說了是全注解開發,而且我們已經實現了HibernateSpring的注解。同樣的,Struts2也能夠做到用注解來代替配置文件,struts2-convention插件可以幫助我們完成這一功能。它是struts2提供的一個插件,目前網上相關的中文文檔主要是一個叫石太洋的人根據官方文檔翻譯的,很多網站與博客都有轉載。我看了原文與譯文,感覺講的不夠清楚,例子也很簡單。大象根據自己在項目中的實際使用情況,現將個人對這個插件的經驗總結寫出來與各位分享,希望與大家多交流,共同提高。
              官方文檔 https://cwiki.apache.org/WW/convention-plugin.html
              請不要把地址中的兩個大寫W換成小寫,否則是打不開頁面滴!這個插件的使用其實非常簡單,如果光看文檔可能會覺得好像很麻煩。那么大象來告訴你怎樣快速學習這個插件。
              首先你要搞清楚,這個插件它會默認掃描所有包名為strutsstruts2、action、actions下面的類。然后它會對實現了Action接口以及類名以Action結尾的這些類,作為Action來進行處理。
              你可以重新定義按哪種包名進行掃描。比如本例設定,只掃描web包下面的所有類,因為我們將Action類都放在這個包下面。
              那這個插件是怎么實現原來的配置信息的呢?它的映射規則是這樣的,對于以Action結尾的的類,去掉Action,取剩下的部分,將所有的字母轉換為小寫,如果有駝峰式的寫法,則用"-"連接符來連接不同的單詞,這是此插件的默認方式。最終轉換之后的就是請求地址,還是用例子說明。
              com.bolo.examples.web.base.UserAction    
              按照上面的規則,請求地址就應該是UserAction去掉Action后綴,將其余部分轉換為小寫,所以user就是我們的請求地址。不過,這還沒有完,因為這里面還有一個命名空間的路徑,在通常的配置文件中,一般會將不同的功能進行劃分,在package標簽里加上namespace屬性。使用這個插件,它會為你自動配上命名空間,默認的就是前面說到的以那四種名稱為根目錄的命名空間,它們之后的都將成為命名空間的名稱。
              com.bolo.examples.struts.UserAction 映射為 /user.action
              com.bolo.examples.struts.base.UserAction 映射為 /base/user.action

              要是我們不以struts或其它幾種默認值為包名,又該怎么辦呢?沒關系,插件為我們提供了一種自定義根包的配置方式
              <constant name="struts.convention.package.locators" value="web" />
              上面這段配置是寫在struts.xml里面的,它指定web為根,作用就相當于那四種默認值。
              com.bolo.examples.web.base.UserAction映射為 /base/user.action
              
          com.bolo.examples.web.HelloAction 映射為 /hello.action
              com.bolo.examples.web.HelloWorldAction 映射為 /hello-world.action

              請一定注意駝峰寫法的映射方式,假如這里不是HelloWorld,而是Helloworld,那就不會再是hello-world.action,而是helloworld.action了。
              既然已經知道了它的映射方式,接下來再看看這個插件是如何定義結果頁面的。
              convention默認會到/WEB-INF/content文件夾下面查找對應的結果頁面,這個文件夾的名字可以修改,需要在struts.xml中定義
              <constant name="struts.convention.result.path" value="/WEB-INF/jsp" />
              文件夾的名字改成了jsp,這樣定義后,convention就會在這個文件夾下面查找結果頁面。它的查找路徑與映射的命名空間有關。默認規則是,在請求的命名空間下面,根據請求名稱再結合方法返回的字符串生成最終的結果頁面名稱,再配以后綴名。convention支持以jsp、ftl、vm、htmlhtm等五種后綴格式的文件。這里有個比較特殊的是如果方法返回success,那么可以不用將它與請求名稱拼接起來,直接使用請求名稱作為返回頁面的名稱。還是舉例子說明。
              
              比如上面這段代碼,HelloAction處于我們定義的根包(web)下面,因此,它的action請求為hello.action。這時,會默認執行execute()方法,由于返回的是success字符串,所以頁面的名稱可以簡寫為hello.jsp,但是當執行welcome方法時,由于返回的字符串為welcome,這時的頁面名稱則為hello-welcome.jsp。convention就是遵循這樣的規則來進行命名,當然這只是最基本的,我們再來看看稍微復雜點的東東。
              
              這個RoleAction類的外部,加了兩種注解,它們的作用相當于配置文件中的result標簽。Results是一個Result類型的數組注解,里面可以包含多個Result配置。使用Result注解來設置返回類型與返回頁面,是不準備采取默認的定義方式。比如HelloAction就是我們采取的默認方式。另外對于有些特殊的返回類型,也需要顯式的進行定義。
              因為我對RoleAction中的execute()方法返回結果進行了顯式的定義,所以,它將不再返回默認的role.jsp,而是location指定的role-list.jsp,Result注解中的name值要與返回值對應。
              當請求路徑為role!input.action時,會執行input()方法,對于這個方法來說,由于沒有進行顯式的定義,所以它會按照默認的命名規則返回role-input.jsp
              redirectUser方法的返回結果指定了一個typeredirectAction的值,這表示要對Action重定向,在location中也說明了是跳轉到哪個Action。請注意這里指定的是user.action,當程序跳轉到UserAction時,會默認執行execute方法。
              假如說,你想執行其它方法該怎么辦呢?可以在location里面這樣定義,location="user!input.action"。請記住,重定向時,如果是跳轉到其它Action或本Action中的其它方法,type要寫成redirectAction
              更進一步,我還想帶些參數過去,又該如何呢?請添加params屬性,它是一個數組類型??梢赃@樣定義,params={"role_id","${role_id}","role_name","超級管理員"}convention文檔中有說明,里面的參數是一個鍵值對,總是形如key,value,key,value。所以第一個role_id與第三個role_name都叫參數名,二和四則是參數值。另外注意下"${role_id}"的含義,這是使用的OGNL表達式取出存在于值棧中的名叫role_id的值。這是一種動態獲取并賦值的方式,在采用配置文件的方式中,也可以這樣運用,而role_name參數則是一個固定字符串值。需要特別注意的就是,作為參數名的role_idrole_name,一定要在指向的Action中有這兩個同名的屬性,并且還有set方法,這是用來給這兩個屬性賦值。而對于${role_id},則要在當前這個Action中,有它的get方法。用于取值。
              補充說明一下,在Action類中定義的全局變量,不是非得給它都加上set、get方法,這是根據實際情況來設置的。簡單的說get()是獲得值,set()是設置值。比如,你現在要在頁面上顯示username,那么就對這個屬性設置get方法,如果只是對username設置值,從頁面傳值到Action,那只需要對它設置set方法就可以了。除此之外,我們也可以不采用struts2提供的值棧方式得到參數值,而是使用非常熟悉的request. getParameter()方法來獲取參數。至于實際怎么使用,由各位自己決定,不知道我這樣說,大家能不能明白?
              大象根據實際使用情況,發現動態參數的傳遞在struts2.1.6存在BUG,如果需要使用這個功能,請將struts2升級到2.1.8.1版。
              大象根據實際應用,建議大家統一在類名上面定義Results設置,這樣做有利于開發與維護;不建議單獨對方法使用@Action注解來重新定義它的訪問地址與返回結果,因為這樣做有些破壞統一性,不過可以根據實際情況進行處理,但不要過多的使用。
              struts.xml
              
              整個struts.xml的配置文件就這么多,當然你自己還可以擴展,因為采用了注解,所以以前的那些配置就再也看不到了。在這個文件中,package是繼承convention-default,而沒有繼承struts-default,為什么呢?查看conventionstruts-plugin.xml文件,我們可以發現convention-default繼承了struts-default,所以這樣寫是沒錯的。另外的幾個constant配置就是對convention的常量設置,請看注釋。
              關于paramsPrepareParamsStack攔截器棧,我準備在第五篇,對基礎框架進行擴展的時候再詳細的說明。大家如果等不急想學習下,可以在網上查找這方面的資料先看看。
              web
              大象是這樣想的,如果一次講的太多太復雜不利于理解和吸收,所以對于web層,大家從前面也看到了,代碼很簡單,因為本篇主要是講convention插件的知識,然后實現一部分功能用于演示它的效果。下面貼上webWebRoot目錄結構、UserAction的代碼,以及jsp代碼。
              
              
              
              請注意web包下面的層次結構,這與你的請求路徑相關。content文件夾是插件默認指定的名字,你可以修改為別的名字。同樣請注意在這個目錄下面的文件與子文件夾的定義方式是和web層相同的。如果還沒有理解,請再看下我對convention插件的說明。
              web.xml文件中,設置了一個<welcome-file-list>標簽,定義了一個index.jsp,這文件里就一句代碼 <% response.sendRedirect("hello.action"); %> 它會去執行HelloActionexecute()方法,這方法里面什么邏輯都沒有,直接返回結果頁面hello.jsp
              
              ${ctx}是一個EL表達式,設置的是當前項目名稱。我在文件開頭加了一個靜態包含,<%@ include file="/common/taglibs.jsp" %>
              
              不管是user.action還是role.action,它們默認的執行方法都是execute(),點擊這兩個鏈接,返回指定的結果頁面。
              
              
              user.jsp里面,用來循環的list,是根據getList()方法獲取的,struts2會自動的分析出屬性名。想一下,listget方法是不是就是getList()呢?我之前說過,get()是獲得值,set()是設置值。在這里我只是要在列表頁面上得到list集合,沒有其它的需求,所以不用像這樣定義 private List list,再然后給它加上set()、get()方法,因為要得到list集合,所以還要在execute()方法里面寫上list = userManager.getUsers(),這樣做有必要么?我一直都在遵循優雅、高效、簡潔的代碼風格,并且一直都在朝這方面努力,也提倡大家這樣做。編程是門藝術,而不是一種工作,不要把它當工作看,只想著完成任務,拼命的堆代碼。這樣做很難有提高。應該換一種心態去對待它,用藝術的眼光來重新審視你的代碼,你會發現這很有樂趣,也會學到很多。自己的一點淺薄之見,讓各位見笑了。
              這部分的內容就說到這里,下一篇將對paramsPrepareParamsStack攔截器棧進行詳細說明,另外再對框架進行一下擴展,封裝CRUD功能,只要沒有特殊的業務邏輯,在你的實際Action中,再不會看到增刪改查這些基本功能。
              本文為菠蘿大象原創,如要轉載請注明出處。 http://bolo.blogjava.net/
          posted on 2010-05-08 16:07 菠蘿大象 閱讀(13261) 評論(16)  編輯  收藏 所屬分類: Struts2

          評論:
          # re: Struts2.1.6+Spring2.5.6+Hibernate3.3.1全注解實例詳解(四) 2010-05-10 13:17 | 宋曉
          終于等來了大象的文章。間隔時間真長?。俊?nbsp; 回復  更多評論
            
          # re: Struts2.1.6+Spring2.5.6+Hibernate3.3.1全注解實例詳解(四) 2010-05-10 13:42 | 菠蘿大象
          @宋曉
          謝謝支持,我對質量要求比較高,寫得比較慢,寫完后又修改了幾遍,第五篇大概是兩星期后發布了,請到時候再來看吧。  回復  更多評論
            
          # re: Struts2.1.6+Spring2.5.6+Hibernate3.3.1全注解實例詳解(四) 2010-05-14 14:35 | 愛上貓的魚
          呵呵 一直看您的文章,我也正在學習SSH2,收益頗多
          期待第五篇的推出  回復  更多評論
            
          # re: Struts2.1.6+Spring2.5.6+Hibernate3.3.1全注解實例詳解(四) 2010-05-14 14:59 | 菠蘿大象
          @愛上貓的魚
          謝謝支持,能對大家有幫助,我非常開心。  回復  更多評論
            
          # re: Struts2.1.6+Spring2.5.6+Hibernate3.3.1全注解實例詳解(四) 2010-06-19 10:37 | gonglianying
          學習中,文章寫的很好。  回復  更多評論
            
          # re: Struts2.1.6+Spring2.5.6+Hibernate3.3.1全注解實例詳解(四) 2010-06-20 20:49 | 菠蘿大象
          @gonglianying
          謝謝你的支持。  回復  更多評論
            
          # re: Struts2.1.6+Spring2.5.6+Hibernate3.3.1全注解實例詳解(四) 2011-03-07 15:11 | HCH
          牛人,學習了  回復  更多評論
            
          # re: Struts2.1.6+Spring2.5.6+Hibernate3.3.1全注解實例詳解(四) 2011-04-22 17:52 | tsinglongwu
          請問在embedded tomcat5/6中使用struts2 convention要注意什么?我在embedded tomcat5/6使用struts2 convention老是報錯:
          問題代碼如下:
          1.web.xml:
          <web-app>
          <filter>
          <filter-name>struts2</filter-name>
          <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
          </filter>

          <filter-mapping>
          <filter-name>struts2</filter-name>
          <url-pattern>/*</url-pattern>
          </filter-mapping>

          <welcome-file-list>
          <welcome-file>index.jsp</welcome-file>
          </welcome-file-list>

          </web-app>

          2.struts.xml:
          <struts>
          <constant name="struts.convention.result.path" value="/WEB-INF/jsp/" />
          </struts>

          3.HelloAction.java:
          package com.synertone.ssh.paging.action;

          import com.opensymphony.xwork2.ActionSupport;

          public class HelloAction extends ActionSupport {

          private String message;

          public String getMessage() {
          return message;
          }

          @Override
          public String execute() {
          message = "lady gaga!";
          return SUCCESS;
          }
          }

          4.hello.jsp:
          <%@ taglib uri="/struts-tags" prefix="s"%>
          <html>
          <body>
          The message is: ${message}
          </body>
          </html>

          5.tomcat啟動類startup.java:
          Tomcat tomcat = new Tomcat();
          String projectPath = new File("").getAbsolutePath();
          tomcat.setBaseDir(projectPath);
          tomcat.setPort(8080);
          try {
          tomcat.addWebapp("", projectPath + "/webapps/SSH");
          } catch (ServletException e) {
          e.printStackTrace();
          throw e;
          }
          try {
          tomcat.start();
          } catch (LifecycleException e) {
          e.printStackTrace();
          throw e;
          }

          輸入http://localhost:8080/index.jsp,能訪問index.jsp頁面。輸入http://localhost:8080/hello或hello.action則報錯
          There is no Action mapped for namespace / and action name hello. - [unknown location]

          同樣的代碼部署在非embeded tomcat中卻能訪問成功。
          這是為什么呢?望大象指點一下,感激不盡!  回復  更多評論
            
          # re: Struts2.1.6+Spring2.5.6+Hibernate3.3.1全注解實例詳解(四) 2011-06-28 16:40 | vjame
          刪除角色之后頁面要手動刷新下,才能看到新數據,reload怎么無效啊  回復  更多評論
            
          # re: Struts2.1.6+Spring2.5.6+Hibernate3.3.1全注解實例詳解(四)[未登錄] 2011-07-14 16:42 | dudu
          我把源碼下下來,因為用的是spring3+struts2.2.3+hibernate3.6,所以很多jar包不一樣,調試好了后,啟動沒有問題了。
          首頁也出來了,但是到了hello。jsp,點擊 執行UserAction的時候就報錯了。

          Unable to instantiate Action, com.bolo.examples.web.base.UserAction, defined for 'user' in namespace '/base'Error creating bean with name 'com.bolo.examples.web.base.UserAction': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: protected com.bolo.examples.common.orm.hibernate3.HibernateDao com.bolo.examples.common.web.StrutsAction.hibernateDao; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'hibernateDao' defined in file [E:\eclipseworkspace-3.7\.metadata\.plugins\org.eclipse.wst.server.core\tmp3\wtpwebapps\ssh2\WEB-INF\classes\com\bolo\examples\common\orm\hibernate3\HibernateDao.class]: Initialization of bean failed; nested exception is java.lang.NoClassDefFoundError: Could not initialize class net.sf.cglib.proxy.Enhancer
          不曉得是什么原因  回復  更多評論
            
          # re: Struts2.1.6+Spring2.5.6+Hibernate3.3.1全注解實例詳解(四)[未登錄] 2011-07-14 17:24 | dudu
          好了。
          原來是cglib包的版本不一致造成的。
          發現版本后不一致真是要人命啊
            回復  更多評論
            
          # re: Struts2.1.6+Spring2.5.6+Hibernate3.3.1全注解實例詳解(四) 2011-07-15 09:06 | 菠蘿大象
          @dudu
          nested exception is java.lang.NoClassDefFoundError: Could not initialize class net.sf.cglib.proxy.Enhancer
          從這里就可以看出是cblib的問題了,大象我現在改用maven構建項目,這樣對版本的控制會好些  回復  更多評論
            
          # re: Struts2.1.6+Spring2.5.6+Hibernate3.3.1全注解實例詳解(四)[未登錄] 2011-07-15 09:28 | dudu
          @菠蘿大象
          用maven有的時候也很無奈。
          我做一個客戶,用ofc2做了一個折線圖,但是這個jar包,在maven上找不到,
          按說可以自己在服務器上的repository安裝一個jar包,但是
          客戶就讓我必須改為jfreechart做。很是郁悶了半天。
            回復  更多評論
            
          # re: Struts2.1.6+Spring2.5.6+Hibernate3.3.1全注解實例詳解(四) 2011-09-29 13:30 | dup
          感謝樓主,看了好久才有點明白  回復  更多評論
            
          # re: Struts2.1.6+Spring2.5.6+Hibernate3.3.1全注解實例詳解(四)[未登錄] 2013-04-13 20:17 | momo
          為什么我的jsp讀取不到UserAction傳過來的值???  回復  更多評論
            
          # re: Struts2.1.6+Spring2.5.6+Hibernate3.3.1全注解實例詳解(四) 2013-11-13 10:32 |
          剛來看,不錯的。  回復  更多評論
            
          主站蜘蛛池模板: 彰武县| 时尚| 衡阳县| 徐水县| 体育| 社旗县| 东辽县| 电白县| 临泽县| 徐水县| 陈巴尔虎旗| 邵阳市| 鹿邑县| 花垣县| 雅江县| 商南县| 黄龙县| 吉木萨尔县| 南昌市| 来安县| 灌云县| 阿尔山市| 辽阳市| 正蓝旗| 榆中县| 蒙阴县| 建瓯市| 深水埗区| 江油市| 新干县| 通江县| 岢岚县| 千阳县| 屏南县| 阜平县| 海门市| 锡林浩特市| 信阳市| 鲁山县| 罗源县| 珠海市|