小菜毛毛技術分享

          與大家共同成長

            BlogJava :: 首頁 :: 聯系 :: 聚合  :: 管理
            164 Posts :: 141 Stories :: 94 Comments :: 0 Trackbacks

          常用鏈接

          留言簿(15)

          我參與的團隊

          搜索

          •  

          最新評論

          閱讀排行榜

          評論排行榜

          #

           摘 要 本文在分析對象池技術基本原理的基礎上,給出了對象池技術的兩種實現方式。還指出了使用對象池技術時所應注意的問題。

            關鍵詞 對象池;對象池技術;Java 對象;性能

            Java對象的生命周期分析

            Java對象的生命周期大致包括三個階段:對象的創建,對象的使用,對象的清除。因此,對象的生命周期長度可用如下的表達式表示:T = T1 + T2 +T3。其中T1表示對象的創建時間,T2表示對象的使用時間,而T3則表示其清除時間。由此,我們可以看出,只有T2是真正有效的時間,而T1、T3則是對象本身的開銷。下面再看看T1、T3在對象的整個生命周期中所占的比例。

            我們知道,Java對象是通過構造函數來創建的,在這一過程中,該構造函數鏈中的所有構造函數也都會被自動調用。另外,默認情況下,調用類的構造函數時,Java會把變量初始化成確定的值:所有的對象被設置成null,整數變量(byte、short、int、long)設置成0,float和double變量設置成0.0,邏輯值設置成false。所以用new關鍵字來新建一個對象的時間開銷是很大的,如表1所示。

            表1 一些操作所耗費時間的對照表

          運算操作 示例 標準化時間
          本地賦值 i = n 1.0
          實例賦值 this.i = n 1.2
          方法調用 Funct() 5.9
          新建對象 New Object() 980
          新建數組 New int[10] 3100

            從表1可以看出,新建一個對象需要980個單位的時間,是本地賦值時間的980倍,是方法調用時間的166倍,而若新建一個數組所花費的時間就更多了。

            再看清除對象的過程。我們知道,Java語言的一個優勢,就是Java程序員勿需再像C/C++程序員那樣,顯式地釋放對象,而由稱為垃圾收集器(Garbage Collector)的自動內存管理系統,定時或在內存凸現出不足時,自動回收垃圾對象所占的內存。凡事有利總也有弊,這雖然為Java程序設計者提供了極大的方便,但同時它也帶來了較大的性能開銷。這種開銷包括兩方面,首先是對象管理開銷,GC為了能夠正確釋放對象,它必須監控每一個對象的運行狀態,包括對象的申請、引用、被引用、賦值等。其次,在GC開始回收“垃圾”對象時,系統會暫停應用程序的執行,而獨自占用CPU。

            因此,如果要改善應用程序的性能,一方面應盡量減少創建新對象的次數;同時,還應盡量減少T1、T3的時間,而這些均可以通過對象池技術來實現。

            對象池技術的基本原理

            對象池技術基本原理的核心有兩點:緩存和共享,即對于那些被頻繁使用的對象,在使用完后,不立即將它們釋放,而是將它們緩存起來,以供后續的應用程序重復使用,從而減少創建對象和釋放對象的次數,進而改善應用程序的性能。事實上,由于對象池技術將對象限制在一定的數量,也有效地減少了應用程序內存上的開銷。

            實現一個對象池,一般會涉及到如下的類:

            1)對象池工廠(ObjectPoolFactory)類

            該類主要用于管理相同類型和設置的對象池(ObjectPool),它一般包含如下兩個方法:

            ·createPool:用于創建特定類型和設置的對象池;

            ·destroyPool:用于釋放指定的對象池;

            同時為保證ObjectPoolFactory的單一實例,可以采用Singleton設計模式,見下述getInstance方法的實現:

          public static ObjectPoolFactory getInstance() {
           if (poolFactory == null) {
            poolFactory = new ObjectPoolFactory();
           }
           return poolFactory;
          }

            2)參數對象(ParameterObject)類

            該類主要用于封裝所創建對象池的一些屬性參數,如池中可存放對象的數目的最大值(maxCount)、最小值(minCount)等。

            3)對象池(ObjectPool)類

            用于管理要被池化對象的借出和歸還,并通知PoolableObjectFactory完成相應的工作。它一般包含如下兩個方法:

             ·getObject:用于從池中借出對象;
             ·returnObject:將池化對象返回到池中,并通知所有處于等待狀態的線程;

            4)池化對象工廠(PoolableObjectFactory)類

            該類主要負責管理池化對象的生命周期,就簡單來說,一般包括對象的創建及銷毀。該類同ObjectPoolFactory一樣,也可將其實現為單實例。
          通用對象池的實現

            對象池的構造和管理可以按照多種方式實現。最靈活的方式是將池化對象的Class類型在對象池之外指定,即在ObjectPoolFactory類創建對象池時,動態指定該對象池所池化對象的Class類型,其實現代碼如下:

          . . .
          public ObjectPool createPool(ParameterObject paraObj,Class clsType) {
           return new ObjectPool(paraObj, clsType);
          }
          . . .

            其中,paraObj參數用于指定對象池的特征屬性,clsType參數則指定了該對象池所存放對象的類型。對象池(ObjectPool)創建以后,下面就是利用它來管理對象了,具體實現如下:

          public class ObjectPool {
           private ParameterObject paraObj;//該對象池的屬性參數對象
           private Class clsType;//該對象池中所存放對象的類型
           private int currentNum = 0; //該對象池當前已創建的對象數目
           private Object currentObj;//該對象池當前可以借出的對象
           private Vector pool;//用于存放對象的池
           public ObjectPool(ParameterObject paraObj, Class clsType) {
            this.paraObj = paraObj;
            this.clsType = clsType;
            pool = new Vector();
           }
           public Object getObject() {
            if (pool.size() <= paraObj.getMinCount()) {
             if (currentNum <= paraObj.getMaxCount()) {
              //如果當前池中無對象可用,而且已創建的對象數目小于所限制的最大值,就利用
              //PoolObjectFactory創建一個新的對象
              PoolableObjectFactory objFactory =PoolableObjectFactory.getInstance();
              currentObj = objFactory.create Object (clsType);
              currentNum++;
             } else {
              //如果當前池中無對象可用,而且所創建的對象數目已達到所限制的最大值,
              //就只能等待其它線程返回對象到池中
              synchronized (this) {
               try {
                wait();
               } catch (InterruptedException e) {
                System.out.println(e.getMessage());
                e.printStackTrace();
               }
               currentObj = pool.firstElement();
              }
             }
            } else {
             //如果當前池中有可用的對象,就直接從池中取出對象
             currentObj = pool.firstElement();
            }
            return currentObj;
          }
            public void returnObject(Object obj) {
             // 確保對象具有正確的類型
             if (obj.isInstance(clsType)) {
              pool.addElement(obj);
              synchronized (this) {
               notifyAll();
              }
             } else {
              throw new IllegalArgumentException("該對象池不能存放指定的對象類型");
             }
            }
          }

            從上述代碼可以看出,ObjectPool利用一個java.util.Vector作為可擴展的對象池,并通過它的構造函數來指定池化對象的Class類型及對象池的一些屬性。在有對象返回到對象池時,它將檢查對象的類型是否正確。當對象池里不再有可用對象時,它或者等待已被使用的池化對象返回池中,或者創建一個新的對象實例。不過,新對象實例的創建并不在ObjectPool類中,而是由PoolableObjectFactory類的createObject方法來完成的,具體實現如下:

          . . .
          public Object createObject(Class clsType) {
           Object obj = null;
           try {
            obj = clsType.newInstance();
           } catch (Exception e) {
            e.printStackTrace();
           }
           return obj;
          }
          . . .

            這樣,通用對象池的實現就算完成了,下面再看看客戶端(Client)如何來使用它,假定池化對象的Class類型為StringBuffer:

          . . .
          //創建對象池工廠
          ObjectPoolFactory poolFactory = ObjectPoolFactory. getInstance ();
          //定義所創建對象池的屬性
          ParameterObject paraObj = new ParameterObject(2,1);
          //利用對象池工廠,創建一個存放StringBuffer類型對象的對象池
          ObjectPool pool = poolFactory.createPool(paraObj,String Buffer.class);
          //從池中取出一個StringBuffer對象
          StringBuffer buffer = (StringBuffer)pool.getObject();
          //使用從池中取出的StringBuffer對象
          buffer.append("hello");
          System.out.println(buffer.toString());
          . . .

            可以看出,通用對象池使用起來還是很方便的,不僅可以方便地避免頻繁創建對象的開銷,而且通用程度高。但遺憾的是,由于需要使用大量的類型定型(cast)操作,再加上一些對Vector類的同步操作,使得它在某些情況下對性能的改進非常有限,尤其對那些創建周期比較短的對象
          專用對象池的實現     

            由于通用對象池的管理開銷比較大,某種程度上抵消了重用對象所帶來的大部分優勢。為解決該問題,可以采用專用對象池的方法。即對象池所池化對象的Class類型不是動態指定的,而是預先就已指定。這樣,它在實現上也會較通用對象池簡單些,可以不要ObjectPoolFactory和PoolableObjectFactory類,而將它們的功能直接融合到ObjectPool類,具體如下(假定被池化對象的Class類型仍為StringBuffer,而用省略號表示的地方,表示代碼同通用對象池的實現):

          public class ObjectPool {
           private ParameterObject paraObj;//該對象池的屬性參數對象
           private int currentNum = 0; //該對象池當前已創建的對象數目
           private StringBuffer currentObj;//該對象池當前可以借出的對象
           private Vector pool;//用于存放對象的池
           public ObjectPool(ParameterObject paraObj) {
            this.paraObj = paraObj;
            pool = new Vector();
           }
           public StringBuffer getObject() {
            if (pool.size() <= paraObj.getMinCount()) {
             if (currentNum <= paraObj.getMaxCount()) {
              currentObj = new StringBuffer();
              currentNum++;
             }
             . . .
            }
            return currentObj;
           }
           public void returnObject(Object obj) {
            // 確保對象具有正確的類型
            if (StringBuffer.isInstance(obj)) {
             . . .
            }
           }

            結束語

            恰當地使用對象池技術,能有效地改善應用程序的性能。目前,對象池技術已得到廣泛的應用,如對于網絡和數據庫連接這類重量級的對象,一般都會采用對象池技術。但在使用對象池技術時也要注意如下問題:

            ·并非任何情況下都適合采用對象池技術。基本上,只在重復生成某種對象的操作成為影響性能的關鍵因素的時候,才適合采用對象池技術。而如果進行池化所能帶來的性能提高并不重要的話,還是不采用對象池化技術為佳,以保持代碼的簡明。

            ·要根據具體情況正確選擇對象池的實現方式。如果是創建一個公用的對象池技術實現包,或需要在程序中動態指定所池化對象的Class類型時,才選擇通用對象池。而大部分情況下,采用專用對象池就可以了。
          posted @ 2010-03-05 15:24 小菜毛毛 閱讀(218) | 評論 (0)編輯 收藏

          AppFuse是一個集成了眾多當前最流行開源框架與工具(包括Hibernate、ibatis、Struts、Spring、DBUnit、Ant、Log4J、Struts Menu、Xdoclet、SiteMesh、OSCache、JUnit、JSTL)于一身的Web開發框架。AppFuse提供了Web系統開發過程中都需要開發的一些功能,如登陸、用戶密碼加密,用戶管理、根據不同的用戶可以展現不同的菜單.J2EE開發者也可以在此基礎上開發加入自己的功能模塊。利用這個框架可以大幅度的提高開發速度。

          轉自:http://blog.chinaunix.net/u/11409/showart_436247.html

          http://blog.csdn.net/changzhang/category/369570.aspx可參考這個網址
          AppFuse是一個集成了當前最流行的Web應用框架的一個更高層次的Web開發框架,也可以說是一個Web開發基礎平臺,它與它所集成的各種框架相比,它提供了一部分所有Web系統開發過程中都需要開發的一些功能,如登陸、用戶密碼加密,用戶管理、根據不同的用戶可以展現不同的菜單,可以自動生成 40%-60%左右的代碼,自帶了默認的一些在CSS中設定的樣式,使用這些樣式能很快的改變整個系統的外觀,還有自動化測試的功能。

          它最大的價值就是為我們提供了一個Web開發的新的方式和思路,盡管這些技術在國外都已進很流行了,但在國內能夠將Hibernate、Struts、 Spring、DBUnit、Ant、Log4J、Struts Menu、Xdoclet、SiteMesh、Velocity、JUnit、JSTL、WebWork這些技術集成到一個框架中的還不多見,所以即使不使用它的全部功能,它也給我們提供了一個很好的借鑒、學習的機會。

          通過關注AppFuse,我們可以看到目前國外的主流開發都使用了哪些技術,開發方式是什么樣的,可能達到什么樣的結果,而在以前,是很少能夠看到這樣完整的例子的。

          AppFuse的另一個啟示是:我們可以依靠開源軟件的功能降低開發成本,而且可以閱讀開源軟件的代碼提高所在團隊的整體實力。

          AppFuse 的作者 Matt Raible是當今開源世界一個比較活躍的開發者,它是AppFuse、Struts Menu的作者,也是XDoclet、DisplayTag等一些著名開源項目的積極參與者,《Hibernate In Action》的作者就在感謝的名單里面提到他,XDoclet的下載版本中所帶的Hibernate標簽部分的例子就是他寫的,他還是2004年 Apache技術年會的主講人之一。

          但是通過2個月的實際學習和使用,我也遇到一系列的問題,因為AppFuse是將其他的一些類庫或者框架集成在一起的,集成的技術眾多,而且有一些技術在國內甚至很少有人知道,資料也比較少,所以雖然作者經過了一些測試,但都是基于英文編碼的,而對于中文編碼來說,還潛在的存在著一些問題,雖然不是AppFuse的問題,但卻降低了開發速度,下面是我在開發過程中遇到過的問題,有些解決了,有些還沒有解決:
          一.Struts
          1. AppFuse中默認的MVC框架是Struts,而且使用的是LookupDispatchAction,并且使用的是按鈕(button),在XP下用IE瀏覽效果還可以,但如果在2000或者98下,就使外觀很難看,而且當時我還遇到一個問題:如果按鈕顯示中文,則在DisplayTag中翻頁失靈,而且報錯,后來我把BaseAction的相關方法改變了,才可以使用,因為國內的客戶都比較重視界面,所以后來我將那些按鈕都改成圖片了,當然也要添加一些方法了,有點麻煩!
          2. Struts中的標簽如今推薦使用的只有html部分的標簽了,其他的標簽或者可以使用JSTL替代,或者已經不推薦使用了,而且AppFuse中推薦使用JSTL,而JSTL和struts的標簽的聯合使用時,需要的不是<html:標簽>,而是<html-el:標簽>,這個問題曾經困擾了我整整2天。
          3. Struts的Validation的校驗規則并不完善,比如如果使用客戶端的javascript校驗,則在郵箱中輸入漢字根本校驗不出來,到了服務器端報錯。
          4.最嚴重的問題是AppFuse生成的Struts的validation.xml文件中有許多多余的“.”,如果你去掉了,常常在執行ant的 deploy任務時又恢復原樣。這樣是提交表單的時候經常會報javascript的腳本錯誤或者缺少對象或者缺少value,所以我會手工的修改這個文件,然后把修改后的文件備份,當重新生成有錯誤的文件時,我會用備份的沒有錯誤的文件去覆蓋。
          5. Struts的validatioin對于使用同一個FormBean的Action的校驗方式比較復雜。(待解決)。
          二.Hibernate
          1. Hibernate是現在受到越來越多的人推崇的一個ORM工具(框架、類庫),它將我們從繁瑣的使用JDBC的開發過程中解放出來,但同時也帶來了新的問題,如學習曲線,執行效率,數據庫設計優化,還有最重要的靈活性。Hibernate不是一個很容易上手的東西,要完全駕馭它還需要讀很多資料,但好的資料卻很少。
          2. 使用Xdoclet可以很方便的生成Hibernate中的持久類的配置文件(*.hbm.xml),但對一些特殊的映射卻無能為力,如使用序列的id生成規則,序列的名字沒有地方寫,所以也只好先利用它生成主要的內容,然后手工修改。
          3. 同樣還是id的生成策略問題,如果使用序列、hilo等需要一些數據庫機制支持的策略時,schemaExport并不能自動生成序列或者保存當前id的表,這項工作仍然要手工解決。
          4. Hibernate中提供了幾種關聯,一對一、一對多、多對多,但對于怎樣調整效率卻沒有一個很明確的提示,還要根據情況判定,這就帶來和一些彈性的設計。
          5. Hibernate中可以選擇的操作數據庫的方式有3種,其中HQL功能最強大,但有些功能使用標準查詢可能會更方便,但會有一些限制,所以雖然它很靈活,但易用性不如JDBC好。
          三.Spring
          在AppFuse的過程中,Spring完全隱藏在幕后,除了一些配置外,幾乎感覺不到它的存在,所以我在使用它的過程中并沒有遇到什么麻煩,這里只是簡單的介紹一下它在AppFuse中起到的作用。
          1. Spring在AppFuse中起到的主要作用是對Hibernate的Session和事務的管理,利用Spring封裝的Hibernate模板類,我們大大地減少了實現DAO的代碼行數。
          2. Spring還起到了連接映射文件和類之間的關聯,及接口和實現類之間的關聯,這些都依賴于Spring的IoC的機制的實現。
          3. 對于字符進行編碼和解碼部分用到了Spring自帶的Filter,只需要在配置文件中配置就好了。

          四.SiteMesh
          SiteMesh是一個基于Decorator模式的技術,它可以修飾返回的網頁文件,它的工作方式受到越來越多的人的推崇,這點從Manning出版的一些技術書籍中可以看出來。
          我在使用SiteMesh的過程中并不順利,我參考了《Java Open Source Programming》,這本書中說SiteMesh在默認的情況下不對下載文件進行裝飾,但我在下載文件時發現,我的文件內容被丟棄了,取而代之的是 SiteMesh的模板的內容,后來我通過修改SiteMesh的配置文件解決了這個問題,但感覺還有一些不太清楚的地方需要學習。

          五.DisplayTag
          DisplayTag 是一個優秀的顯示內容的標簽,從SourceForge的訪問量來看,它是很活躍的項目,僅次于Ant、Hibernate、Xdoclet等幾個著名的項目,我總結,它的主要功能有4項:顯示、分頁、排序、將顯示的數據寫入指定類型的文件中,然后下載。
          1. 據我使用的情況看,我只使用了分頁和顯示的功能,因為當時我沒有很好的解決中文編碼的問題,所以排序會有問題,直到昨天,我在朋友的幫助下解決了這個問題,至此我可以放心使用的功能又增加了排序(我昨天簡單的測試了一下是可以的)。

          2. 但對于將顯示的內容生成到一個指定格式的文件中的功能卻有著很多缺陷,如:
          (1) 生成的文件中只有顯示的數據,那些沒有顯示在界面上的的數據,則不會被寫到文件中。
          (2) 如果修改了DisplayTag的顯示的內容,比如添加一列,在這列中的內容不是字符,而是HTML的標簽,則生成的文件只有這些HTML標簽,而沒有數據。
          (3) 即使DisplayTag中沒有我們定制的HTML腳本,生成的文件偶爾也有問題,比如:它會把“007”生成為“7”,把字符串自動的轉換為整型值。有時候還生成空白內容的文件。
          (4) DisplayTag生成的Excel文件兼容性不好,有時在Excel2003中不能正常打開,或者在XP下打開報錯。
          后來,我看了作者寫的《Spring Live》,書中說如果想實現穩定的Excel,推薦使用POI,于是我使用POI生成Excel,穩定性和兼容性都不錯。

          六.DBUnit
          DBUnit是一個可以被Ant集成的向數據庫中添加數據和備份數據的一個類庫,配置很方便,因為AppFuse已經集成好了,所以使用也很容易。
          但是如果你使用EditPlus之類的工具手工修改了AppFuse生成的內容,則執行Ant的setup、setup-db或者deploy的任務時,常常報錯,說無效的格式,這是因為這個被手工修改的文件再次被AppFuse執行后,它的第一行的文件聲明的前幾個字母是無效的,是因為本地的字符集編碼的原因而引起了亂碼,如果把這幾個無效的字母去掉,問題就解決了。

          七.Struts Menu
          Struts Menu也是AppFuse的作者開發的一個開源軟件,它可以根據配置文件讀取當前用戶可以使用的功能菜單,這個功能是我一直以來都想要的,我也找到了一些代碼,但實現的都不如這個完善,沒什么好說的,使用簡單,配置容易,很好的解決了我的問題。
          問題是我只使用了AppFuse提供的2個角色,對于多個角色的實驗我還沒有做。

          八.XDoclet
          在AppFuse中,使用Xdoclet生成了幾乎一切的配置文件:Struts-config.xml、web.xml、validation.xml、*.hbm.xml等文件,如果使用AppGen的話,還會生成更多的文件,這一切都是使用Xdoclet實現的。
          問題是我在Struts部分提到的,生成的Validation.xml文件中會多生成一個“.”,另外在生成資源文件時也會多生成一個“.”,目前我沒有很好的閱讀這段代碼,不知道是不是Xdoclet的問題。

          九.Ant
          Ant并沒有什么問題,但在執行作者寫的Ant任務的時候,有一些任務不能正常執行,比如,運行模擬對象測試的任務,作者也在1.7版本的修復列表中提到以前版本有些ant任務不能執行,在1.7中修改了一些ant任務,使他們能夠正常的執行了。
          實際上,我們如果使用AppGen進行開發的話,使用的任務一般不超過8個。

          十.JSTL
          JSTL 是個好東西,我常用的有<c:>和<fmt:>部分的標簽,但是如果使用JSTL進行邏輯判斷,我并沒有感覺比使用JSP的代碼塊優雅多少。另外,熟悉JSTL也需要一段時間,我就經歷了面對著JSP頁面不知道該怎么寫JSTL語法的困境。當然,AppFuse中使用的基本都是 JSTL,包括向DisplayTag傳遞顯示的數據,使用的都是JSTL語法,這方面的資料挺多,我參考的是電子工業出版社出的《JSP2.0技術》,說的很詳細。

          十一.Tomcat
          你也許會說:“Tomcat就不用說了吧?”,是的,Tomcat一般都會使用,但是 ―――――――――――――Tomcat5和Tomcat4.X對于中文編碼使用了不同的機制,這個問題困擾了我好久,我解決了頁面上寫入漢字顯示亂碼的問題,我也曾經以為DisplayTag對漢字不能排序,也不能正常分頁是因為DisplayTag的開發者都是老外,是因為他們沒有考慮中文的關系的原因。
          直到昨天,我才知道這一切都是因為Tomcat5對漢字編碼的實現的方式和Tomcat4不一樣的原因,如果感興趣,可以看看這個帖子: http://www.javaworld.com.tw/jute/post/view?bid=9&id=44042&sty=1&tpg=1&age=0

          十二.JavaScript
          JavaScript簡單易學,但想運用自如就不太容易了。AppFuse中嵌入了幾個js文件,里面包含了許多函數,值得我們好好的研究一下,比如,如果有一個必填字段沒有填寫,AppFuse會自動的聚焦在那個input上,類似的小技巧有很多,你可以自己去翻看。
          但AppFuse 自帶的JavaScript腳本有一個Bug,就是當DisplatyTag中沒有可以顯示的數據時,你用鼠標單擊,它會報JavaScript錯誤,你仔細研究一下function highlightTableRows(tableId) 就知道了:我的解決辦法是在location.href = link.getAttribute("href");前面添加一行判斷:if (link != null)。

          十三.資源文件國際化
          對于Struts和DisplayTag都涉及到資源文件國際化AppFuse1.6.1很好的解決了Struts資源映射文件國際化的問題,你只需要在對應本國語言的資源文件中寫入漢字,Ant中有一項執行native2ascii的任務,AppFuse自動的為你進行了資源文件的編碼轉換,而對于 DisplayTag的資源文件問題,還要自己執行native2ascii命令,為了避免每次都輸入一串命令,我用Delphi寫了個小工具,可視化的選擇資源文件,點擊按鈕自動執行該命令,底層依賴于JDK。


          經過2個多月的學習,我感覺這個框架非常不錯,它為我以后的項目開發指出了一個新的方向,但如果想很熟練的使用這個框架進行開發,至少要對以下幾種技術比較熟練:Struts(或者WebWork、Spring及其他的已經整合進來的MVC框架)、Hibernate(或者ibatis)、JSTL,當然其他的技術至少也要知道一點,否則遇到問題都不知道出在哪里。


          目前我還沒有解決的問題有:
          1. 如何在翻頁的時候才讀取下面的數據?
          2. 怎樣對使用同一個FormBean的多個Form進行客戶端校驗?
          3. 怎樣優化Hibernate的效率?《Hibernate In Action》中提供了多種策略,有些時候應該使用lazy,有些時候應該使用outer-join。
          4.在什么時機生成導出文件?目前我是在查詢的Action中同時生成了導出文件,否則,到了下一頁,我就不知道查詢條件了,當然,如果把拼裝后的HQL存儲在Session或者Hidden中也可以解決這個問題,但是這樣就破壞了DAO的封裝,要把DAO封裝后的HQL發送給Action,然后發送的到 Web界面層,所以目前我還在猶豫生成導出文件的時機選擇在哪里?
          5. 什么時候應該自己獲取數據庫連接,執行native SQL?具體需要注意些什么?
          6. SiteMesh的模板優化?
          7. DisplayTag的底層實現?


          每個問題都比較棘手,要一個一個解決!

          這個框架的優點是:如果熟悉了開發流程,可以大幅度的提高開發速度,如果業務不是很復雜,使用AppGen可以生成60%左右的代碼,而且程序可維護性好,因為作者使用了多個設計模式對各個層面進行了封裝,所以不同的模塊代碼風格出奇的一致,有利于開發人員快速上手,也有利于接收其他開發人員遺留的代碼。

          兔八哥
          2004-2-3下午15:51

          ++++++++++++++++++++

          引用:
          有沒有成功的項目同我們分享一下?

           

          我已經使用AppFuse開發了2個項目了,都是教育系統的,系統都不大,我一個人用實際開發一個月,因為是公司的項目,源碼不好外發,但主要的東西都是一樣的,對于AppFuse我也沒有修改多少,否則,往新版本移植就會有問題了。
          我遇到的問題,能想起來的我都寫下來了,如果有其他的問題,我們可以一起討論。
          最近我有個朋友在使用WebWork的AppFuse版本進行開發,他遇到的問題和我基本差不多,有交流才有進步,呵呵!
          Very Happy

          感謝樓上幾位的熱心解答,謝謝!

          +++++++++++++++++++++++

          引用:
          目前我還沒有解決的問題有:
          1. 如何在翻頁的時候才讀取下面的數據?
          2. 怎樣對使用同一個FormBean的多個Form進行客戶端校驗?
          3. 怎樣優化Hibernate的效率?《Hibernate In Action》中提供了多種策略,有些時候應該使用lazy,有些時候應該使用outer-join。
          4.在什么時機生成導出文件?目前我是在查詢的Action中同時生成了導出文件,否則,到了下一頁,我就不知道查詢條件了,當然,如果把拼裝后的HQL存儲在Session或者Hidden中也可以解決這個問題,但是這樣就破壞了DAO的封裝,要把DAO封裝后的HQL發送給Action,然后發送的到 Web界面層,所以目前我還在猶豫生成導出文件的時機選擇在哪里?
          5. 什么時候應該自己獲取數據庫連接,執行native SQL?具體需要注意些什么?
          6. SiteMesh的模板優化?
          7. DisplayTag的底層實現?


          1.關于翻頁的問題,如果你剛開始使用AppFuse開發的話,推薦使用valuelist,它可以和Hibernate很好的集成,我的一個網友用的就是這個東西,雖然界面沒有DisplayTag漂亮,但關于分頁卻不用你操太多的心,
          因為這幾天天天開會,所以也沒有做些技術實驗,另一個朋友告訴我有一個老外把DisplayTag分頁部分修改了,在JIRA上有源碼下載,我下來了,還沒有看,還有一個思路,就是分析DisplayTag的分頁的格式,然后用Filter解析,然后把當前頁號傳入DAO,然后使用標準查詢進行查詢分頁,但要對AppFuse的接口和方法添加參數,正在猶豫中,還有更簡單的方法,直接在Session中放入當前的頁號,每次都刷新,就不用Filter了,然后同樣修改方法和接口。

          2.對于Struts的使用同一個FormBean的多個Form進行客戶端校驗,在孫衛琴的Struts的書中已經提到了,即使她的方法不管用,也可以手寫JavaScript來解決,只是我不愿意而已,如果別無他法,則只能如此了。

          3. 優化Hibernate的效率,其實對我的程序來說問題不大,我的表比較少,基本只有3層的級聯,而且對于數據字典和業務表之間的關聯,我采用的是業務表到數據字典表的many-to-one,這樣的單向關聯比較簡單,也能夠滿足我的要求,性能會好一點點,再加上分頁功能,只查詢當前也內容,然后參考《Hibernate In Action》的第七章的內容,提高接收效率應該是沒有問題的。

          4.關于到處文件的時機,我正在看關于模式的書籍,正在找答案,其實還有個簡單的辦法,就是把生成導出文件單獨的實現,用戶點擊按鈕才生產,當然這樣就要把用戶的查詢條件記下來,當然也涉及到修改接口和方法了,AppFuse中,修改接口和方法很麻煩,如果代碼生成后再修改,要改動很多處,所以前期設計很重要。

          5.關于這一點,我一直在找一個硬性的標準,比如關聯表超過多少個等等條件就應該自己獲取數據庫連接,但現在看來這也不是絕對的,如果能夠大幅度提高效率,或者使用 native SQL可以減少工作量或者節省時間的話,那就可以使用,只是要對這種方式的利弊要有所了解,不幸的是,我還沒有這樣試過,試過的朋友請提供些建議,謝謝!

          6.SiteMesh的優化,我看到一片文章,也是個老外寫的,忘了出處,說SiteMesh對性能影響不大,可以放心使用,那我就暫時不考慮它了,呵呵。

          7.DisplayTag的底層的原理我早就知道,而且它的文檔的流程圖也有,只是我需要知道更詳細的實現細節,最近在讀源碼,應該很快就有結果了,如果我有好消息,會來這里把解決方案貼出來。


          上面的文字只是我目前的一些思路,因為天天開會,也沒有做技術實驗,還不知道可行的程度,但我想應該都可以實現,我只不過是在找更好的辦法,如果你有任何好的思路或者建議,請不吝告知,謝謝,任何建議和意見都是受歡迎的,只是要詳細些,不要拿些空洞的模式來敷衍我,呵呵,目前這樣的“高手”不少,我有些受夠了,呵呵

           

          本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/changzhang/archive/2008/03/12/2172675.aspx



          posted @ 2010-03-05 14:40 小菜毛毛 閱讀(671) | 評論 (0)編輯 收藏

          第 7 章 使用filter過濾請求

          注意

          Filter雖然很常用,但是覆蓋的范圍太廣,這里我們只介紹設置編碼和控制權限的過濾器,其他的使用方式還需要大家自行積累。

          如果你不滿足以下任一條件,請繼續閱讀,否則請跳過此后的部分,進入下一章:第 8 章 配置listener監聽器

          1. 了解Filter的使用。

          7.1. 批量設置請求編碼

          編碼問題會不會成為中國人學java的標志呢?

          通過之前的討論第 2.2.2 節 “POST亂碼”,我們知道為了避免提交數據的亂碼問題,需要在每次使用請求之前設置編碼格式。在你復制粘貼了無數次request.setCharacterEncoding("gb2312");后,有沒有想要一勞永逸的方法呢?能不能一次性修改所有請求的編碼呢?

          用Filter吧,它的名字是過濾器,可以批量攔截修改servlet的請求和響應。

          我們編寫一個EncodingFilter.java,來批量設置請求編碼。

          package anni;
          import java.io.IOException;
          import javax.servlet.Filter;
          import javax.servlet.FilterChain;
          import javax.servlet.FilterConfig;
          import javax.servlet.ServletException;
          import javax.servlet.ServletRequest;
          import javax.servlet.ServletResponse;
          public class EncodingFilter implements Filter {
          public void init(FilterConfig config) throws ServletException {}
          public void destroy() {}
          public void doFilter(ServletRequest request,
          ServletResponse response,
          FilterChain chain)
          throws IOException, ServletException {
          request.setCharacterEncoding("gb2312");
          chain.doFilter(request, response);
          }
          }
          

          在此EncodingFilter實現了Filter接口,Filter接口中定義的三個方法都要在EncodingFilter中實現,其中doFilter()的代碼實現主要的功能:為請求設置gb2312編碼并執行chain.doFilter()繼續下面的操作。

          與servlet相似,為了讓filter發揮作用還需要在web.xml進行配置。

          <filter>
          <filter-name>EncodingFilter</filter-name>
          <filter-class>anni.EncodingFilter</filter-class>
          </filter>
          <filter-mapping>
          <filter-name>EncodingFilter</filter-name>
          <url-pattern>/*</url-pattern>
          </filter-mapping>
          

          filter標簽部分定義使用的過濾器,filter-mapping標簽告訴服務器把哪些請求交給過濾器處理。這里的/*表示所有請求,/表示根路徑,*(星號)代表所有請求,加在一起就變成了根路徑下的所有請求。

          這樣,所有的請求都會先被EncodingFilter攔截,并在請求里設置上指定的gb2312編碼。

          例子在lingo-sample/07-01目錄下,這次我們不需要在test.jsp中為請求設置編碼也可以得到正常的中文參數了,EncodingFilter圓滿的完成了它的工作。

          7.2. 用filter控制用戶訪問權限

          出于信息安全和其他一些原因的考慮,項目中的一些頁面要求用戶滿足了一定條件之后才能訪問。比如,讓用戶輸入帳號和密碼,如果輸入的信息正確就在session里做一個成功登錄的標記,其后在請求保密信息的時候判斷session中是否有已經登錄成功的標記,存在則可以訪問,不存在則禁止訪問。

          如07-02例子中所示,進入首頁看到的就是登錄頁面。

          現在用戶還沒有登錄,如果直接訪問保密信息,就會顯示無法訪問保密信息的頁面,并提醒用戶進行注冊。

          返回登錄頁面后,輸入正確的用戶名和密碼,點擊登錄。

          后臺程序判斷用戶名和密碼正確無誤后,在session中設置已登錄的標記,然后跳轉到保密信息頁面。

          我們要保護的頁面是admin/index.jsp,為此我們在web.xml進行如下配置。

          <filter>
          <filter-name>SecurityFilter</filter-name>
          <filter-class>anni.SecurityFilter</filter-class>
          </filter>
          <filter-mapping>
          <filter-name>SecurityFilter</filter-name>
          <url-pattern>/admin/*</url-pattern>
          </filter-mapping>
          

          定義SecurityFilter過濾器,讓它過濾匹配/admin/*的所有請求,這就是說,對/admin/路徑下的所有請求都會接受SecurityFilter的檢查,那么SecurityFilter里到底做了些什么呢?

          public void doFilter(ServletRequest request,
          ServletResponse response,
          FilterChain chain)
          throws IOException, ServletException {
          HttpServletRequest req = (HttpServletRequest) request;
          HttpServletResponse res = (HttpServletResponse) response;
          HttpSession session = req.getSession();
          if (session.getAttribute("username") != null) {
          chain.doFilter(request, response);
          } else {
          res.sendRedirect("../failure.jsp");
          }
          }
          

          首先要將ServletRequest和ServletResponse轉換成HttpServletRequest和HttpServletResponse,因為Filter本來設計成為多種協議服務,http協議僅僅是其中一部分。不過我們接觸到的也只有http,而且也只有轉換成對應HttpServletRequest和HttpServletResponse才能進行下面的session操作和頁面重定向。

          得到了http請求之后,可以獲得請求對應的session,判斷session中的username變量是否為null,如果不為null,說明用戶已經登錄,就可以調用doFilter繼續請求訪問的資源。如果為null,說明用戶還沒有登錄,禁止用戶訪問,并使用頁面重定向跳轉到failure.jsp頁面顯示提示信息。

          session中的username實在登錄的時候設置進去的,值就是登錄用戶使用的用戶名,詳細代碼可以參考07-02/WEB-INF/src/LoginServlet.java,登錄和注銷都寫成了servlet并映射到/login.do和/logout.do這兩個請求路徑上。源代碼和web.xml配置請自行參考07-02中的例子,這里就不復述了。

          我們再來看看頁面重定向的寫法,res.sendRedirect()中使用的是"../failure.jsp",兩個點(..)代表當前路徑的上一級路徑,這是因為SecurityFilter負責處理的是/admin/下的請求,而/failure.jsp的位置在/admin/目錄的上一級,所以加上兩個點才能正確跳轉到failure.jsp。當然這里使用forward()也可以,但是要注意在不同路徑下做請求轉發會影響頁面中相對路徑的指向。相關討論在:第 3.4.2 節 “forward導致找不到圖片”

          7.3. filter所謂的特性

          7.3.1. 請求映射

          filter-mapping和servlet-mapping都是將對應的filter或servlet映射到某個url-pattern上,當客戶發起某一請求時,服務器先將此請求與web.xml中定義的所有url-pattern進行匹配,然后執行匹配通過的filter和servlet。

          你可以使用三種方式定義url-pattern。

          1. 直接映射一個請求。

            <servlet-mapping>
                <servlet-name>ContactServlet</servlet-name>
                <url-pattern>/contact.do</url-pattern>
                </servlet-mapping>
                

            第 6.3 節 “使用servlet改寫聯系簿”中對servlet的映射,只有當請求是/contact.do的時候才會執行ContactServlet。/contact.do?id=1或/contact.do?method=list&id=1的請求也可以匹配到ContactServlet,這是因為根據http規范,請求的路徑不包含問號以后的部分。

          2. 映射一個路徑下的所有請求。

            <servlet-mapping>
                <servlet-name>EncodingFilter</servlet-name>
                <url-pattern>/*</url-pattern>
                </servlet-mapping>
                

            第 7.1 節 “批量設置請求編碼”中這樣使用星號(*)的形式,可以將某個路徑下的所有請求都映射到EncodingFilter過濾器下,如果這個路徑下還有子路徑,那么子路徑下的請求也會被EncodingFilter過濾。所以 /*這種寫法就會過濾應用下所有的請求。

            如果像第 7.2 節 “用filter控制用戶訪問權限”中那樣把映射配置成/admin/*,就會只處理/admin/路徑下的請求,不會處理根路徑下的/index.jsp和/failure.jsp。

            需要注意的是,這種寫法必須以/開頭,寫成與絕對路徑的形式,即便是映射所有請求也要寫成/*,不能簡化成*。

          3. 映射結尾相同的一類請求。

            <servlet-mapping>
                <servlet-name>ControllerServlet</servlet-name>
                <url-pattern>*.do</url-pattern>
                </servlet-mapping>
                

            具體效果請參考07-03的例子,index.jsp中有四個鏈接,分別指向/a1.do, /a2.do, /xx/b1.do, /xx/yy/c1.do。

            web.xml中的ControllerServlet會接收以.do結尾的請求,并使用forward將請求轉發到/test.jsp。

            點擊/a1.do的情況。

            點擊/xx/yy/c1.do的情況。

            這樣做的一個好處是語義更清楚,只要看到以.do結尾的請求就知道肯定是交給ControllerServlet處理了,不管這個請求是在根路徑還是子路徑下,都會準確無誤的找到對應的servlet。

            缺點就是不同路徑之間進行forward,jsp里就不能再使用相對路徑了,所以我們在test.jsp中使用request.getContextPath()獲得當前應用在服務器中的位置(例子中是/07-03)將相對路徑都組裝成絕對路徑,這種用法在以后也會經常用到。

            <%
                pageContext.setAttribute("ctx", request.getContextPath());
                %>
                <p><a href="${ctx}/index.jsp">返回</a></p>
                

            最后需要注意的是,這種請求映射就不能指定某一路徑了,它必須是以星號(*)開始字母結尾,不能寫成/*.do的形式。

          現在咱們也發現java的請求映射有多傻了,靈活配置根本是不可能的任務。

          想要獲得所有以user開頭.do結尾的請求嗎?user*.do在url-pattern是無法識別的,只能配置成*.do,再去servlet中對請求進行篩選。

          想要讓一個servlet負責多個請求嗎?/user/*,/admin/*,*.do寫在一起url-pattern也不認識,只能配成多個servlet-mapping。

          <servlet-mapping>
          <servlet-name>ControllerServlet</servlet-name>
          <url-pattern>/user/*</url-pattern>
          </servlet-mapping>
          <servlet-mapping>
          <servlet-name>ControllerServlet</servlet-name>
          <url-pattern>/admin/*</url-pattern>
          </servlet-mapping>
          <servlet-mapping>
          <servlet-name>ControllerServlet</servlet-name>
          <url-pattern>*.do</url-pattern>
          </servlet-mapping>
          

          java的復雜性在此處顯露無疑。實際使用時,最好不要依賴web.xml中的配置,在自己的類中實現靈活配置才是正途。

          7.3.2. 過濾鏈

          其實在07-02這個例子里,我們使用了兩個過濾器,EncodingFilter負責設置編碼,SecurityFilter負責控制權限,那這兩個過濾器是怎么起作用的呢?它們兩個同時過濾一個請求時誰先誰后呢?

          下面這個圖會告訴我們答案。

          所有的奧秘就在Filter中的FilterChain中。服務器會按照web.xml中過濾器定義的先后循序組裝成一條鏈,然后一次執行其中的doFilter()方法。執行的順序就如上圖所示,執行第一個過濾器的chain.doFilter()之前的代碼,第二個過濾器的chain.doFilter()之前的代碼,請求的資源,第二個過濾器的chain.doFilter()之后的代碼,第一個過濾器的chain.doFilter()之后的代碼,最后返回響應。

          因此在07-02中執行的代碼順序是:

          1. 執行EncodingFilter.doFilter()中chain.doFilter()之前的部分:request.setCharacterEncoding("gb2312");

          2. 執行SecurityFilter.doFilter()中chain.doFilter()之前的部分:判斷用戶是否已登錄。

            如果用戶已登錄,則訪問請求的資源:/admin/index.jsp。

            如果用戶未登錄,則頁面重定向到:/failure.jsp。

          3. 執行SecurityFilter.doFilter()中chain.doFilter()之后的部分:這里沒有代碼。

          4. 執行EncodingFilter.doFilter()中chain.doFilter()之后的部分:這里也沒有代碼。

          過濾鏈的好處是,執行過程中任何時候都可以打斷,只要不執行chain.doFilter()就不會再執行后面的過濾器和請求的內容。而在實際使用時,就要特別注意過濾鏈的執行順序問題,像EncodingFilter就一定要放在所有Filter之前,這樣才能確保在使用請求中的數據前設置正確的編碼。

          7.4. filter的詳細配置

          我們已經了解了filter的基本用法,還有一些細節配置在特殊情況下起作用。

          在servlet-2.3中,Filter會過濾一切請求,包括服務器內部使用forward轉發請求和<%@ include file="/index.jsp"%>的情況。

          到了servlet-2.4中Filter默認下只攔截外部提交的請求,forward和include這些內部轉發都不會被過濾,但是有時候我們需要forward的時候也用到Filter,這樣就需要如下配置。

          <filter>
          <filter-name>TestFilter</filtername>
          <filter-class>anni.TestFilter</filter-class>
          </filter>
          <filter-mapping>
          <filter-name>TestFilter</filtername>
          <url-pattern>/*</url-pattern>
          <dispatcher>REQUEST</dispatcher>
          <dispatcher>FORWARD</dispatcher>
          <dispatcher>INCLUDE</dispatcher>
          <dispatcher>EXCEPTION</dispatcher>
          </filter-mapping>
          

          這樣TestFilter就會過濾所有狀態下的請求。如果我們沒有進行設置,默認使用的就是REQUEST。而EXCEPTION是在isErrorPage="true"的情況下出現的,這個用處不多,看一下即可。

          這里FORWARD是解決request.getDispatcher("index.jsp").forward(request, response);無法觸發Filter的關鍵,配置上這個以后再進行forward的時候就可以觸發過濾器了。

          Filter還有一個有趣的用法,在filter-mapping中我們可以直接指定servlet-mapping,讓過濾器只處理一個定義在web.xml中的servlet。

          <filter-mapping>
          <filter-name>TestFilter</filter-name>
          <servlet-name>TestServlet</servlet-name>
          </filter-mapping>
          <servlet>
          <servlet-name>TestServlet</servlet-name>
          <servlet-class>anni.TestServlet</servlet-class>
          </servlet>
          <servlet-mapping>
          <servlet-name>TestServlet</servlet-name>
          <url-pattern>/TestServlet</url-pattern>
          </servlet-mapping>
          

          直接指定servlet-name,TestFilter便會引用TestServlet配置的url-pattern,在某些filter與servlet綁定的情況下不失為一個好辦法。

          posted @ 2010-03-05 12:33 小菜毛毛 閱讀(6249) | 評論 (0)編輯 收藏

          1.UTF-8

          原來用慣了GBK,被大家一再慫恿,才下定決心整個項目換用UTF-8編碼。

          編碼問題幾乎與所有國內Java項目相伴而生,不同內核的Linux、英文版日文版的WindowsXP總是讓GBK很頭痛,而改用UTF-8后,忽然就輕爽了,UTF-8,全世界語系連馬爾代夫語都有自己的座位,實在找不到理由為什么還要用只支持大中華的GBK。

          而且Springside也成了一個帶有大量UTF-8中文注釋的項目。

          原GBK項目的轉換方法

            把自己當成車衣廠女工,機械的使用UltraEdit和EditPlus的轉換功能,忙活一兩個小時,怎么都可以轉完所有文件了。

            其中UltraEdit的轉換方法是文件--〉轉換--〉ASCII to UTF-8(Unicode編輯)。最好在Advanced-〉本地代碼頁那里也設一下UTF-8。

            然后就可以用設了UTF-8選項的IDE如IDEA,Eclipse直接編輯了。

          2.國際化 I18N

                后臺的menu.jsp 和 editBook.jsp 還有業務異常類,演示了i18N的應用。

          2.1 i18N基礎      

            1.編寫messages.zh_CN.properties

               用中文寫完后,用java帶的native2ascii.exe或者ant的native任務把它轉成非人類的Unicode編碼。

            2.在純Java API里,國際化是這樣做的:

          ResourceBundle rb = ResourceBundle.getBundle("messages");
          String welcome = rb.getString("welcome");
          String welcomeCalvin = MessageFormat.format(welcome,new String[]{"calvin"});

            第一二句從classpath里找到messages_zh_CN.properties,讀出"歡迎你,{0}"字樣。

            第三局把上文格式化成"歡迎你,calvin" 

           3.在Servlet環境下,國際化是這樣做的   

          51.la 專業、免費、強健的訪問統計 則靠<fmt:message>tag

           

          <%@ taglib uri="
          <fmt:message key="welcome"/>

                可以用context-param來定義默認的properties文件

              <context-param>
                      <param-name>javax.servlet.jsp.jstl.fmt.localizationContext</param-name>
                      <param-value>messages</param-value>
              </context-param>

               不過這樣定義的缺點是只能定義一個文件,如果所有信息都集中在一個文件會好長,而如果有多個properties,就惟有在頁面用<fmt:bundle>綁定了。

          2.2 Spring的messageSource增強

          Spring增加了MessageSource的概念

            一是ApplicationContext將充當一個單例的角色,不再需要每次使用i18時都初始化一次ResourceBundle

            二是可以代表多個Resource Bundle.

            在ApplicationContext的定義文件中,增加如下節點: 

          <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
          
              <property name="basename" value= "messages"/>
          </bean>

          在pure Java中

          context.getMessage("welcome", null, LocaleContextHolder.getLocale())

          而經Spring MVC JSTL ViewResolver調用的JSP,<fmt:message>將繼續發揮它的功效。

          不過這里挺麻煩的,第一如果不是從MVC JSTL轉的,messageSource的定義就會失效。而如果定義了<context-param>,則messageSource的定義又會失效.......

           還有,<spring:message> 雞肋一塊,因為它如果找不到key對應的就會拋異常,好恐怖。

          還還有,spring還有有趣的theme機制,和i18n一樣的原理,解決了"做成圖片的文字"的國際化,讓不同語言的美術字圖片的路徑分別定義在theme_zh_CN.properties和theme_en_US.properties里面。

          posted @ 2010-03-05 11:36 小菜毛毛 閱讀(835) | 評論 (0)編輯 收藏

          一句話概括:==比較的是兩個對象的引用(即內存地址)是否相等,而equals()比較的是兩個對象的值(即內存地址里存放的值)是否相等。當然equals()在個別類中被重寫了那就例外了。

          詳細論述:eqauls 與 = =之異同

          1)比較方式角度:

          = =是面向過程的操作符;equals是面向對象的操作符

          = =不屬于任何類,equals則是任何類(在Java中)的一個方法;

          我們可以1)Primitive1 (基本類型)= = Primitive2(基本類型);

                   2)Object Reference1(對象引用)= = Object Reference2(對象引用)

                   3)Object Reference1 (對象引用) .equals(Object Reference2 (對象引用))

                     這三種比較

                     但卻不能Primitive1 (基本類型).equals( Primitive2(基本類型));

           對于基本類型,沒有面向對象中發送消息一說,自然也不會有

          方法成員。

           

          2)比較目的角度:

          1)    如果要比較兩個基本類型是否相等,請用= =;

          2)    如果要比較兩個對象引用是否相等,請用= =;

          3)    如果要比較兩個對象(邏輯上)是否一致,請用equals;

           

          對兩個對象(邏輯上)是否一致的闡釋:

             有人會問:在C++中, 比較兩個對象相等不是也可以用==嗎?我知道您是指運算符重載,但是很遺憾,Java中不支持運算符重載(java中亦有重載過運算符,他們是“+”,“+=”,不過也僅此兩個,而且是內置實現的);所以,對象的是否相等的比較這份責任就交由  equals()來實現 。   

          這個“邏輯上”其實就取決于人類的看法,實際開發中,就取決于用戶的需求;


          第三節:equals()緣起:

                  equals()是每個對象與生俱來的方法,因為所有類的最終基類就是Object(除去Object本身);而equals()是Object的方法之一。

                  我們不妨觀察一下Object中equals()的source code:

                   public boolean equals(Object obj) {

                                return (this == obj);

                    }

                  注意 “return (this == obj)”

                  this與obj都是對象引用,而不是對象本身。所以equals()的缺省實現就是比較

                  對象引用是否一致;為何要如此實現呢? 前面我們說過:對象是否相等,是由我們的需求決定的,世界上的類千奇百怪(當然,這些類都是我們根據模擬現實世界而創造的),雖然Object是他們共同的祖先,可他又怎能知道他的子孫類比較相等的標準呢?但是他明白,任何一個對象,自己總是等于自己的,何謂“自己總是等于自己”呢,又如何判斷“自己總是等于自己”呢?一個對象在內存中只有一份,但他的引用卻可以有無窮多個,“對象自己的引用1=對象自己的引用2”,不就能判斷“自己總是等于自己”嗎?所以缺省實現實現自然也就是

                  “return (this == obj)”;

                  而到了我們自己編寫的類,對象相等的標準由我們確立,于是就不可避免的要覆寫

                  繼承而來的public boolean equals(Object obj);

                  如果您有過編覆寫過equals()的經驗(沒有過也不要緊),請您思考一個問題:

                   “兩個對象(邏輯上)是否一致”實際上是比較什么?沒錯,或許您已脫口而出:

                 就是對象的屬性(即field,或稱數據成員)的比較。方法是不可比較的哦。(這個問題是不是有些弱智呢?哈哈)

           第四節:對一個推論的思考

          推論如下:一言以蔽之:欲比較棧中數據是否相等,請用= =;

                                欲比較堆中數據是否相等,請用equals;

          因為(根)基本類型,(根)對象引用都在棧中; 而對象本身在堆中;

                   這句話又對又不對,問題出在哪,就是“數據”二字,先看棧中,數據或為基本類型,或為對象引用,用==比較當然沒錯;但是堆中呢?對象不是堆中嗎?不是應該用equals比較嗎?可是,我們比較的是堆中“數據”,堆中有對象,對象由什么構成呢?可能是對象引用,可能是基本類型,或兩者兼而有之。如果我們要比較他們,該用什么呢,用”equals()”?不對吧,只能是”= =”!所以正確的結論是:欲比較棧中數據是否相等,請用= =; 欲比較堆中數據是否相等,請用equals;

          因為(根)基本類型,(根)對象引用都在棧中(所謂“根”,指未被任何其他對象所包含); 而對象本身在堆中。

          文章出處:DIY部落(http://www.diybl.com/course/3_program/java/javajs/2007917/71587.html#)

          posted @ 2010-03-05 10:36 小菜毛毛 閱讀(219) | 評論 (0)編輯 收藏

           1. 棧(stack)與堆(heap)都是Java用來在Ram中存放數據的地方。與C++不同,Java自動管理棧和堆,程序員不能直接地設置棧或堆。

            2. 棧的優勢是,存取速度比堆要快,僅次于直接位于CPU中的寄存器。但缺點是,存在棧中的數據大小與生存期必須是確定的,缺乏靈活性。另外,棧數據可以共享,詳見第3點。堆的優勢是可以動態地分配內存大小,生存期也不必事先告訴編譯器,Java的垃圾收集器會自動收走這些不再使用的數據。但缺點是,由于要在運行時動態分配內存,存取速度較慢。

            3. Java中的數據類型有兩種。

            一種是基本類型(primitive types), 共有8種,即int, short, long, byte, float, double, boolean, char(注意,并沒有string的基本類型)。這種類型的定義是通過諸如int a = 3; long b = 255L;的形式來定義的,稱為自動變量。值得注意的是,自動變量存的是字面值,不是類的實例,即不是類的引用,這里并沒有類的存在。如int a = 3; 這里的a是一個指向int類型的引用,指向3這個字面值。這些字面值的數據,由于大小可知,生存期可知(這些字面值固定定義在某個程序塊里面,程序塊退出后,字段值就消失了),出于追求速度的原因,就存在于棧中。

            另外,棧有一個很重要的特殊性,就是存在棧中的數據可以共享。假設我們同時定義:

          int a = 3;
          int b = 3;

            編譯器先處理int a = 3;首先它會在棧中創建一個變量為a的引用,然后查找有沒有字面值為3的地址,沒找到,就開辟一個存放3這個字面值的地址,然后將a指向3的地址。接著處理int b = 3;在創建完b的引用變量后,由于在棧中已經有3這個字面值,便將b直接指向3的地址。這樣,就出現了a與b同時均指向3的情況。

            特別注意的是,這種字面值的引用與類對象的引用不同。假定兩個類對象的引用同時指向一個對象,如果一個對象引用變量修改了這個對象的內部狀態,那么另一個對象引用變量也即刻反映出這個變化。相反,通過字面值的引用來修改其值,不會導致另一個指向此字面值的引用的值也跟著改變的情況。如上例,我們定義完a與b的值后,再令a=4;那么,b不會等于4,還是等于3。在編譯器內部,遇到a=4;時,它就會重新搜索棧中是否有4的字面值,如果沒有,重新開辟地址存放4的值;如果已經有了,則直接將a指向這個地址。因此a值的改變不會影響到b的值。

            另一種是包裝類數據,如Integer, String, Double等將相應的基本數據類型包裝起來的類。這些類數據全部存在于堆中,Java用new()語句來顯示地告訴編譯器,在運行時才根據需要動態創建,因此比較靈活,但缺點是要占用更多的時間。 4. String是一個特殊的包裝類數據。即可以用String str = new String("abc");的形式來創建,也可以用String str = "abc";的形式來創建(作為對比,在JDK 5.0之前,你從未見過Integer i = 3;的表達式,因為類與字面值是不能通用的,除了String。而在JDK 5.0中,這種表達式是可以的!因為編譯器在后臺進行Integer i = new Integer(3)的轉換)。前者是規范的類的創建過程,即在Java中,一切都是對象,而對象是類的實例,全部通過new()的形式來創建。Java中的有些類,如DateFormat類,可以通過該類的getInstance()方法來返回一個新創建的類,似乎違反了此原則。其實不然。該類運用了單例模式來返回類的實例,只不過這個實例是在該類內部通過new()來創建的,而getInstance()向外部隱藏了此細節。那為什么在String str = "abc";中,并沒有通過new()來創建實例,是不是違反了上述原則?其實沒有。

            5. 關于String str = "abc"的內部工作。Java內部將此語句轉化為以下幾個步驟:

            (1)先定義一個名為str的對String類的對象引用變量:String str;

            (2)在棧中查找有沒有存放值為"abc"的地址,如果沒有,則開辟一個存放字面值為"abc"的地址,接著創建一個新的String類的對象o,并將o的字符串值指向這個地址,而且在棧中這個地址旁邊記下這個引用的對象o。如果已經有了值為"abc"的地址,則查找對象o,并返回o的地址。

            (3)將str指向對象o的地址。

            值得注意的是,一般String類中字符串值都是直接存值的。但像String str = "abc";這種場合下,其字符串值卻是保存了一個指向存在棧中數據的引用!

            為了更好地說明這個問題,我們可以通過以下的幾個代碼進行驗證。

          String str1 = "abc";
          String str2 = "abc";
          System.out.println(str1==str2); //true

            注意,我們這里并不用str1.equals(str2);的方式,因為這將比較兩個字符串的值是否相等。==號,根據JDK的說明,只有在兩個引用都指向了同一個對象時才返回真值。而我們在這里要看的是,str1與str2是否都指向了同一個對象。
          結果說明,JVM創建了兩個引用str1和str2,但只創建了一個對象,而且兩個引用都指向了這個對象。

            我們再來更進一步,將以上代碼改成:

          String str1 = "abc";
          String str2 = "abc";
          str1 = "bcd";
          System.out.println(str1 + "," + str2); //bcd, abc
          System.out.println(str1==str2); //false

            這就是說,賦值的變化導致了類對象引用的變化,str1指向了另外一個新對象!而str2仍舊指向原來的對象。上例中,當我們將str1的值改為"bcd"時,JVM發現在棧中沒有存放該值的地址,便開辟了這個地址,并創建了一個新的對象,其字符串的值指向這個地址。

            事實上,String類被設計成為不可改變(immutable)的類。如果你要改變其值,可以,但JVM在運行時根據新值悄悄創建了一個新對象,然后將這個對象的地址返回給原來類的引用。這個創建過程雖說是完全自動進行的,但它畢竟占用了更多的時間。在對時間要求比較敏感的環境中,會帶有一定的不良影響。

            再修改原來代碼:

          String str1 = "abc";
          String str2 = "abc";

          str1 = "bcd";

          String str3 = str1;
          System.out.println(str3); //bcd

          String str4 = "bcd";
          System.out.println(str1 == str4); //true

            str3這個對象的引用直接指向str1所指向的對象(注意,str3并沒有創建新對象)。當str1改完其值后,再創建一個String的引用str4,并指向因str1修改值而創建的新的對象。可以發現,這回str4也沒有創建新的對象,從而再次實現棧中數據的共享。

            我們再接著看以下的代碼。

          String str1 = new String("abc");
          String str2 = "abc";
          System.out.println(str1==str2); //false

            創建了兩個引用。創建了兩個對象。兩個引用分別指向不同的兩個對象。

          String str1 = "abc";
          String str2 = new String("abc");
          System.out.println(str1==str2); //false

            創建了兩個引用。創建了兩個對象。兩個引用分別指向不同的兩個對象。

            以上兩段代碼說明,只要是用new()來新建對象的,都會在堆中創建,而且其字符串是單獨存值的,即使與棧中的數據相同,也不會與棧中的數據共享。

            6. 數據類型包裝類的值不可修改。不僅僅是String類的值不可修改,所有的數據類型包裝類都不能更改其內部的值。 7. 結論與建議:

            (1)我們在使用諸如String str = "abc";的格式定義類時,總是想當然地認為,我們創建了String類的對象str。擔心陷阱!對象可能并沒有被創建!唯一可以肯定的是,指向String類的引用被創建了。至于這個引用到底是否指向了一個新的對象,必須根據上下文來考慮,除非你通過new()方法來顯要地創建一個新的對象。因此,更為準確的說法是,我們創建了一個指向String類的對象的引用變量str,這個對象引用變量指向了某個值為"abc"的String類。清醒地認識到這一點對排除程序中難以發現的bug是很有幫助的。

            (2)使用String str = "abc";的方式,可以在一定程度上提高程序的運行速度,因為JVM會自動根據棧中數據的實際情況來決定是否有必要創建新對象。而對于String str = new String("abc");的代碼,則一概在堆中創建新對象,而不管其字符串值是否相等,是否有必要創建新對象,從而加重了程序的負擔。這個思想應該是享元模式的思想,但JDK的內部在這里實現是否應用了這個模式,不得而知。

            (3)當比較包裝類里面的數值是否相等時,用equals()方法;當測試兩個包裝類的引用是否指向同一個對象時,用==。

            (4)由于String類的immutable性質,當String變量需要經常變換其值時,應該考慮使用StringBuffer類,以提高程序效率。

          posted @ 2010-03-05 10:35 小菜毛毛 閱讀(218) | 評論 (0)編輯 收藏

           

          JAVA語言中的反射機制:
              在Java 運行時 環境中,對于任意一個類,能否知道這個類有哪些屬性和方法?
              對于任意一個對象,能否調用他的方法?這些答案是肯定的,這種動態獲取類的信息,以及動態調用類的方法的功能來源于JAVA的反射。從而使java具有動態語言的特性。

            JAVA反射機制主要提供了以下功能:
                1.在運行時判斷任意一個對象所屬的類
                2.在運行時構造任意一個類的對象
                3.在運行時判斷任意一個類所具有的成員變量和方法(通過反射甚至可以調用private方法)
                4.在運行時調用任意一個對象的方法(*****注意:前提都是在運行時,而不是在編譯時)

            Java 反射相關的API簡介:
                位于java。lang。reflect包中
                  --Class類:代表一個類
                  --Filed類:代表類的成員變量
                  --Method類:代表類的方法
                  --Constructor類:代表類的構造方法
                  --Array類:提供了動態創建數組,以及訪問數組的元素的靜態方法。該類中的所有方法都是靜態方法


          ----Class類
               在 java 的Object類中的申明了數個應該在所有的java類中被改寫的methods:
          hashCode(), equals(),clone(),toString(),getClass()等,其中的getClass()返回yige
          Class 類型的對象。
               Class類十分的特殊,它和一般的類一樣繼承自Object,其實體用以表達java程序運行
          時的 class和 interface,也用來表達 enum,array,primitive,Java Types 以及關鍵字void
          ,當加載一個類,或者當加載器(class loader)的defineClass()被JVM調用,便產生一個Class
          對象,
               Class是Reflection起源,針對任何你想探勘的class(類),唯有現為他產生一個Class
          的對象,接下來才能經由后者喚起為數十多個的反射API。


               Java允許我們從多種途徑為一個類class生成對應的Class對象。
                    --運用 getClass():Object類中的方法,每個類都擁有此方法
                                          String str="abc";
                                          Class cl=str.getClass();


                   --運用 Class。getSuperclass():Class類中的方法,返回該Class的父類的Class
                   --運用 Class。forName()靜態方法:
                   --運用 ,Class:類名.class
                   --運用primitive wrapper classes的TYPE語法: 基本類型包裝類的TYPE,如:Integer.TYPE
                                注意:TYPE的使用,只適合原生(基本)數據類型

          ----運行時生成instance
               想生成對象的實體,在反射動態機制中有兩種方法,一個針對無變量的構造方法,一個針對帶參數的
          構造方法,,如果想調用帶參數的構造方法,就比較的麻煩,不能直接調用Class類中的newInstance()
          ,而是調用Constructor類中newInstance()方法,首先準備一個Class[]作為Constructor的參數類型。
          然后調用該Class對象的getConstructor()方法獲得一個專屬的Constructor的對象,最后再準備一個
          Object[]作為Constructor對象昂的newInstance()方法的實參。
                在這里需要說明的是 只有兩個類擁有newInstance()方法,分別是Class類和Constructor類
          Class類中的newInstance()方法是不帶參數的,而Constructro類中的newInstance()方法是帶參數的
          需要提供必要的參數。

              例:
                Class c=Class.forName("DynTest");
                Class[] ptype=new Class[]{double.class,int.class};
                Constructor ctor=c.getConstructor(ptypr);
                Object[] obj=new Object[]{new Double(3.1415),new Integer(123)};
                Object object=ctor.newInstance(obj);
                System.out.println(object);

          ----運行時調用Method
              這個動作首先準備一個Class[]{}作為getMethod(String name,Class[])方法的參數類型,接下來準備一個
          Obeject[]放置自變量,然后調用Method對象的invoke(Object obj,Object[])方法。
               注意,在這里調用

          ----運行時調用Field內容
              變更Field不需要參數和自變量,首先調用Class的getField()并指定field名稱,獲得特定的Field對象后
          便可以直接調用Field的 get(Object obj)和set(Object obj,Object value)方法

          java 代碼
          1. package cn.com.reflection;   
          2.   
          3. import java.lang.reflect.Field;   
          4. import java.lang.reflect.InvocationTargetException;   
          5. import java.lang.reflect.Method;   
          6.   
          7. public class ReflectTester {   
          8.   
          9.     /**  
          10.      * 在這個類里面存在有copy()方法,根據指定的方法的參數去 構造一個新的對象的拷貝  
          11.      * 并將他返回  
          12.      * @throws NoSuchMethodException   
          13.      * @throws InvocationTargetException   
          14.      * @throws IllegalAccessException   
          15.      * @throws InstantiationException   
          16.      * @throws SecurityException   
          17.      * @throws IllegalArgumentException   
          18.      */  
          19.     public Object copy(Object obj) throws IllegalArgumentException, SecurityException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException{   
          20.            
          21.         //獲得對象的類型   
          22.         Class classType=obj.getClass();   
          23.         System.out.println("該對象的類型是:"+classType.toString());   
          24.            
          25.         //通過默認構造方法去創建一個新的對象,getConstructor的視其參數決定調用哪個構造方法   
          26.         Object objectCopy=classType.getConstructor(new Class[]{}).newInstance(new Object[]{});   
          27.            
          28.         //獲得對象的所有屬性   
          29.         Field[] fields=classType.getDeclaredFields();   
          30.            
          31.         for(int i=0;i
          32.             //獲取數組中對應的屬性   
          33.             Field field=fields[i];   
          34.                
          35.             String fieldName=field.getName();   
          36.             String stringLetter=fieldName.substring(01).toUpperCase();   
          37.                
          38.             //獲得相應屬性的getXXX和setXXX方法名稱   
          39.             String getName="get"+stringLetter+fieldName.substring(1);   
          40.             String setName="set"+stringLetter+fieldName.substring(1);   
          41.                
          42.             //獲取相應的方法   
          43.             Method getMethod=classType.getMethod(getName, new Class[]{});   
          44.             Method setMethod=classType.getMethod(setName, new Class[]{field.getType()});   
          45.                
          46.             //調用源對象的getXXX()方法   
          47.             Object value=getMethod.invoke(obj, new Object[]{});   
          48.             System.out.println(fieldName+" :"+value);   
          49.                
          50.             //調用拷貝對象的setXXX()方法   
          51.             setMethod.invoke(objectCopy,new Object[]{value});   
          52.                
          53.                
          54.         }   
          55.            
          56.         return objectCopy;   
          57.            
          58.     }   
          59.        
          60.        
          61.     public static void main(String[] args) throws IllegalArgumentException, SecurityException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {   
          62.         Customer customer=new Customer();   
          63.         customer.setName("hejianjie");   
          64.         customer.setId(new Long(1234));   
          65.         customer.setAge(19);   
          66.            
          67.         Customer customer2=null;   
          68.         customer2=(Customer)new ReflectTester().copy(customer);   
          69.         System.out.println(customer.getName()+" "+customer2.getAge()+" "+customer2.getId());   
          70.            
          71.         System.out.println(customer);   
          72.         System.out.println(customer2);   
          73.            
          74.   
          75.     }   
          76.   
          77. }   
          78.   
          79.   
          80. class Customer{   
          81.        
          82.     private Long id;   
          83.        
          84.     private String name;   
          85.        
          86.     private int age;   
          87.        
          88.        
          89.     public Customer(){   
          90.            
          91.     }   
          92.   
          93.     public int getAge() {   
          94.         return age;   
          95.     }   
          96.   
          97.   
          98.     public void setAge(int age) {   
          99.         this.age = age;   
          100.     }   
          101.   
          102.   
          103.     public Long getId() {   
          104.         return id;   
          105.     }   
          106.   
          107.   
          108.     public void setId(Long id) {   
          109.         this.id = id;   
          110.     }   
          111.   
          112.   
          113.     public String getName() {   
          114.         return name;   
          115.     }   
          116.   
          117.   
          118.     public void setName(String name) {   
          119.         this.name = name;   
          120.     }   
          121.        
          122. }  


          java 代碼
          1. package cn.com.reflection;   
          2.   
          3. import java.lang.reflect.Array;   
          4.   
          5. public class ArrayTester1 {   
          6.   
          7.     /**  
          8.      * 此類根據反射來創建  
          9.      * 一個動態的數組   
          10.      */  
          11.     public static void main(String[] args) throws ClassNotFoundException {   
          12.            
          13.         Class classType=Class.forName("java.lang.String");   
          14.            
          15.         Object array= Array.newInstance(classType,10);  //指定數組的類型和大小   
          16.            
          17.          //對索引為5的位置進行賦值   
          18.         Array.set(array, 5"hello");   
          19.            
          20.         String s=(String)Array.get(array, 5);   
          21.            
          22.         System.out.println(s);   
          23.            
          24.            
          25.         //循環遍歷這個動態數組   
          26.         for(int i=0;i<((String[])array).length;i++){   
          27.                
          28.             String str=(String)Array.get(array, i);   
          29.                
          30.             System.out.println(str);   
          31.         }   
          32.   
          33.     }   
          34.   
          35. }   
          posted @ 2010-03-04 16:43 小菜毛毛 閱讀(251) | 評論 (0)編輯 收藏

          看到這個標題大家可能又想:哎,又一個重新發明輪子的人。在這里很想先聲明一下,寫這篇文章只是想讓大家了解一下Spring到底是怎么運行的,并不是想重造輪子噢,希望大家看完這篇文章后能對Spring有更深入的了解,希望這篇文章對你有所幫助喔!好,言歸正傳,讓我們來一起探索吧!
          我們先從最常見的例子開始吧
          Java代碼 復制代碼
          1. public static void main(String[] args) {   
          2.         ApplicationContext context = new FileSystemXmlApplicationContext(   
          3.                 "applicationContext.xml");   
          4.         Animal animal = (Animal) context.getBean("animal");   
          5.         animal.say();   
          6.     }  

          這段代碼你一定很熟悉吧,不過還是讓我們分析一下它吧,首先是applicationContext.xml
          Java代碼 復制代碼
          1. <bean id="animal" class="phz.springframework.test.Cat">   
          2.         <property name="name" value="kitty" />   
          3.     </bean>  

          他有一個類phz.springframework.test.Cat
          Java代碼 復制代碼
          1. public class Cat implements Animal {   
          2.     private String name;   
          3.     public void say() {   
          4.         System.out.println("I am " + name + "!");   
          5.     }   
          6.     public void setName(String name) {   
          7.         this.name = name;   
          8.     }   
          9. }  

          實現了phz.springframework.test.Animal接口
          Java代碼 復制代碼
          1. public interface Animal {   
          2.     public void say();   
          3. }  

          很明顯上面的代碼輸出I am kitty!

          那么到底Spring是如何做到的呢?
          接下來就讓我們自己寫個Spring 來看看Spring 到底是怎么運行的吧!

          首先,我們定義一個Bean類,這個類用來存放一個Bean擁有的屬性
          Java代碼 復制代碼
          1. /* Bean Id */  
          2.     private String id;   
          3.     /* Bean Class */  
          4.     private String type;   
          5.     /* Bean Property */  
          6.     private Map<String, Object> properties = new HashMap<String, Object>();  

          一個Bean包括id,type,和Properties。

          接下來Spring 就開始加載我們的配置文件了,將我們配置的信息保存在一個HashMap中,HashMap的key就是Bean 的 Id ,HasMap 的value是這個Bean,只有這樣我們才能通過context.getBean("animal")這個方法獲得Animal這個類。我們都知道Spirng可以注入基本類型,而且可以注入像List,Map這樣的類型,接下來就讓我們以Map為例看看Spring是怎么保存的吧

          Map配置可以像下面的
          Java代碼 復制代碼
          1. <bean id="test" class="Test">   
          2.         <property name="testMap">   
          3.             <map>   
          4.                 <entry key="a">   
          5.                     <value>1</value>   
          6.                 </entry>   
          7.                 <entry key="b">   
          8.                     <value>2</value>   
          9.                 </entry>   
          10.             </map>   
          11.         </property>   
          12.     </bean>  

          Spring是怎樣保存上面的配置呢?,代碼如下:
          Java代碼 復制代碼
          1. if (beanProperty.element("map") != null) {   
          2.                     Map<String, Object> propertiesMap = new HashMap<String, Object>();   
          3.                     Element propertiesListMap = (Element) beanProperty   
          4.                             .elements().get(0);   
          5.                     Iterator<?> propertiesIterator = propertiesListMap   
          6.                             .elements().iterator();   
          7.                     while (propertiesIterator.hasNext()) {   
          8.                         Element vet = (Element) propertiesIterator.next();   
          9.                         if (vet.getName().equals("entry")) {   
          10.                             String key = vet.attributeValue("key");   
          11.                             Iterator<?> valuesIterator = vet.elements()   
          12.                                     .iterator();   
          13.                             while (valuesIterator.hasNext()) {   
          14.                                 Element value = (Element) valuesIterator.next();   
          15.                                 if (value.getName().equals("value")) {   
          16.                                     propertiesMap.put(key, value.getText());   
          17.                                 }   
          18.                                 if (value.getName().equals("ref")) {   
          19.                                     propertiesMap.put(key, new String[] { value   
          20.                                             .attributeValue("bean") });   
          21.                                 }   
          22.                             }   
          23.                         }   
          24.                     }   
          25.                     bean.getProperties().put(name, propertiesMap);   
          26.                 }  


          接下來就進入最核心部分了,讓我們看看Spring 到底是怎么依賴注入的吧,其實依賴注入的思想也很簡單,它是通過反射機制實現的,在實例化一個類時,它通過反射調用類中set方法將事先保存在HashMap中的類屬性注入到類中。讓我們看看具體它是怎么做的吧。
          首先實例化一個類,像這樣
          Java代碼 復制代碼
          1. public static Object newInstance(String className) {   
          2.         Class<?> cls = null;   
          3.         Object obj = null;   
          4.         try {   
          5.             cls = Class.forName(className);   
          6.             obj = cls.newInstance();   
          7.         } catch (ClassNotFoundException e) {   
          8.             throw new RuntimeException(e);   
          9.         } catch (InstantiationException e) {   
          10.             throw new RuntimeException(e);   
          11.         } catch (IllegalAccessException e) {   
          12.             throw new RuntimeException(e);   
          13.         }   
          14.         return obj;   
          15.     }  

          接著它將這個類的依賴注入進去,像這樣
          Java代碼 復制代碼
          1. public static void setProperty(Object obj, String name, String value) {   
          2.         Class<? extends Object> clazz = obj.getClass();   
          3.         try {   
          4.             String methodName = returnSetMthodName(name);   
          5.             Method[] ms = clazz.getMethods();   
          6.             for (Method m : ms) {   
          7.                 if (m.getName().equals(methodName)) {   
          8.                     if (m.getParameterTypes().length == 1) {   
          9.                         Class<?> clazzParameterType = m.getParameterTypes()[0];   
          10.                         setFieldValue(clazzParameterType.getName(), value, m,   
          11.                                 obj);   
          12.                         break;   
          13.                     }   
          14.                 }   
          15.             }   
          16.         } catch (SecurityException e) {   
          17.             throw new RuntimeException(e);   
          18.         } catch (IllegalArgumentException e) {   
          19.             throw new RuntimeException(e);   
          20.         } catch (IllegalAccessException e) {   
          21.             throw new RuntimeException(e);   
          22.         } catch (InvocationTargetException e) {   
          23.             throw new RuntimeException(e);   
          24.         }   
          25. }  

          最后它將這個類的實例返回給我們,我們就可以用了。我們還是以Map為例看看它是怎么做的,我寫的代碼里面是創建一個HashMap并把該HashMap注入到需要注入的類中,像這樣,
          Java代碼 復制代碼
          1. if (value instanceof Map) {   
          2.                 Iterator<?> entryIterator = ((Map<?, ?>) value).entrySet()   
          3.                         .iterator();   
          4.                 Map<String, Object> map = new HashMap<String, Object>();   
          5.                 while (entryIterator.hasNext()) {   
          6.                     Entry<?, ?> entryMap = (Entry<?, ?>) entryIterator.next();   
          7.                     if (entryMap.getValue() instanceof String[]) {   
          8.                         map.put((String) entryMap.getKey(),   
          9.                                 getBean(((String[]) entryMap.getValue())[0]));   
          10.                     }   
          11.                 }   
          12.                 BeanProcesser.setProperty(obj, property, map);   
          13.             }  

          好了,這樣我們就可以用Spring 給我們創建的類了,是不是也不是很難啊?當然Spring能做到的遠不止這些,這個示例程序僅僅提供了Spring最核心的依賴注入功能中的一部分。
          本文參考了大量文章無法一一感謝,在這一起感謝,如果侵犯了你的版權深表歉意,很希望對大家有幫助!

          附件中包含該山寨Spring的源碼,核心只有五個類,還有一個測試程序,phz.springframework.test.AnimalSayApp,可以直接運行。
          posted @ 2010-03-04 16:28 小菜毛毛 閱讀(2849) | 評論 (1)編輯 收藏

          posted @ 2010-03-04 15:37 小菜毛毛 閱讀(131) | 評論 (0)編輯 收藏

          Struts工作流程

          文章分類:招聘求職
          一個用戶的請求是通ActionServlet來處理和轉發的。那么,ActionServlet如何決定把用戶請求轉發給哪個Action對象呢?這就需要一些描述用戶請求路徑和Action衍射關系的配置信息了。在Struts中,這些配置映射信息都存儲在特定的XML文件Struts- config.xml中。在該配置文件中,每一個Action的映射信息都通過一個<Action>元素來配置。

          這些配置信息在系統啟動的時候被讀入內存,供Struts在運行期間使用。在內存中,每一個<action>元素都對應一個org.apache.struts.action.ActionMapping類的實例。

          對于采用Struts框架的web應用,在web應用啟動時就會加載并初始化ActionServlet,ActionServlet從struts- config.xml文件中讀取配置信息,把它們存放到各個配置對象中,例如Action的映射信息存放在ActionMapping對象中。

          當ActionServlet接收到一個客戶請求時,將執行如下流程:

          1.檢索和用戶請求相匹配的ActionMapping實例,如果不存在,就返回用戶請求路徑無效信息。

          2.如ActionForm實例不存在,就創建一個ActionForm對象,把客戶提交的表單數據保存到ActionForm對象中。

          3.根據配置信息決定是否需要表單驗證。如果需要驗證,就調用ActionForm的Validate()方法。

          4.如果ActionForm的Validate()方法返回null或返回一個不包含ActionMessage的ActionErrors對象,就表示表單驗證成功。

          5.ActionServlet根據ActionMapping實例包含的映射信息決定將請求轉發給哪個Action。如果相應的Action實例不存在,就先創建這個實例,然后調用Action的execute()方法。

          6.Action的execute()方法返回一個ActionForward對象,ActionServlet再把客戶請求轉發給ActionForward對象指向的JSP組件。

          7.ActionForward對象指向的JSP組件生成動態網頁,返回給客戶。

          posted @ 2010-03-04 15:34 小菜毛毛 閱讀(1870) | 評論 (1)編輯 收藏

          僅列出標題
          共17頁: First 上一頁 5 6 7 8 9 10 11 12 13 下一頁 Last 
          主站蜘蛛池模板: 宜良县| 阜阳市| 体育| 镇康县| 当雄县| 佛山市| 漳浦县| 辛集市| 屯留县| 临泽县| 沙坪坝区| 新泰市| 织金县| 南靖县| 唐河县| 平武县| 阿合奇县| 洞头县| 莱芜市| 凤阳县| 社会| 武平县| 兴山县| 邵东县| 社旗县| 河津市| 东乌珠穆沁旗| 福泉市| 周口市| 望奎县| 五台县| 湖南省| 江陵县| 喀喇沁旗| 镇坪县| 阳春市| 靖安县| 岳普湖县| 张家界市| 柏乡县| 常熟市|