<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    zongxing

    沒有邁不過去的坎!

      BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
      14 隨筆 :: 16 文章 :: 33 評論 :: 0 Trackbacks

    2009年1月15日 #

    在word中編輯時,突然無法顯示文檔中的圖片,只能看到圖片的邊框。
    頓時覺得莫名其妙。
    本想找同事問一下。算了,還是自己想辦法解決吧。這么小的事情。
    在網(wǎng)上一查,發(fā)現(xiàn),已經(jīng)有人遇到過這類事情。
    word選項-高級-顯示文檔內(nèi)容-顯示圖片框
    不勾選這個選項就可以啦。
    posted @ 2009-01-15 14:34 zongxing 閱讀(1028) | 評論 (0)編輯 收藏

    2007年12月22日 #

                             mysql數(shù)據(jù)庫在dos命令行下亂碼的全套解決方案!
                                               2007年12月22日   15:01:52
           使用mysql數(shù)據(jù)庫最常見的就是亂碼問題了,提到亂碼,相信搞java的人都是不陌生的,由于公司里統(tǒng)一了mysql數(shù)據(jù)庫,所以各個員工都開始遇見了亂碼問題,于是,筆者就把常見的問題列出來,并一一解決:
    使用mysql可視化編程工具打開顯示為正常編碼,在dos命令行下為亂碼,其實這個也是最主要的,也是首要解決的

          在配置mysql時(剛安裝時首先要配置,以后的時間也可以配置),打開配置界面,一路下一步,到了要選擇編碼的地方,選中那一項,然后選擇默認編碼。

          問題1:  在這里選擇編碼就有學問了,也是dos下亂碼的最佳解決方案。一般在國內(nèi)的開發(fā)者都是要支持中文的,所以建議大家先用gb2312,這樣在建庫的時候就可以使用默認的gb2312編碼了,如果你要用大字符集,比如gbk,utf8之類的,只需要在建庫的時候設置上就可以了。如果按這樣操作,無論是在可視化工具里還是在dos下,都不會出現(xiàn)亂碼。如果看到這里,恭喜你,你已經(jīng)不用再被mysql的亂碼困擾了(與web 服務相關的暫不講述).
          問題2:  如果你把默認的編碼設為gbk了,以后你在dos下如果查看utf8編碼的數(shù)據(jù)庫,恭喜你,你也不會出現(xiàn)亂碼。但是如果你要查看gb2312編碼的數(shù)據(jù)庫,那么,完了,你肯定是亂碼了。如果你不是亂碼,你可以給我發(fā)郵件52000100@qq.com,我和你共同探討原因。當然在可視化工具里都不會出現(xiàn)亂碼。
          問題3:如果你把默認的編碼設為utf8了,你在dos下只能查看utf8編碼的數(shù)據(jù)庫,gb2312和gbk的都會是亂碼,這個也不要問我為什么,在經(jīng)歷了這么長時間亂碼的折磨,我才總結(jié)出這些規(guī)律,具體為什么會這樣,我也不太清楚。如果有興趣,你可以留言或是發(fā)郵件給我。

         相信看完文章,你已經(jīng)搞定mysql中與此相關的亂碼問題了,恭喜你!
       

    posted @ 2007-12-22 14:53 zongxing 閱讀(3985) | 評論 (2)編輯 收藏

    2007年10月20日 #

    本文轉(zhuǎn)自:http://www.dev-share.com/java/99953page2.html

    0:前言
    我們知道了tomcat的整體框架了, 也明白了里面都有些什么組件, 以及各個組件是干什么用的了。

    http://www.csdn.net/Develop/read_article.asp?id=27225

    我想,接下來我們應該去了解一下 tomcat 是如何處理jsp和servlet請求的。

    1.  我們以一個具體的例子,來跟蹤TOMCAT,看看它是如何把Request一層一層地遞交給下一個容器,并最后交給Wrapper來處理的。

    以http://localhost:8080/web/login.jsp為例子

    (以下例子,都是以tomcat4 源碼為參考)

    這篇心得主要分為3個部分: 前期, 中期, 和末期。

     前期:講解了在瀏覽器里面輸入一個URL,是怎么被tomcat抓住的。

    中期:講解了被tomcat抓住后,又是怎么在各個容器里面穿梭, 最后到達最后的處理地點。

    末期:講解到達最后的處理地點后,又是怎么具體處理的。

    2、 前期 Request的born.

        在這里我先簡單講一下request這個東西。

         我們先看著這個URL:http://localhost:8080/web/login.jsp 它是動用了8080端口來進行socket通訊的。

         我們知道, 通過

           InputStream in = socket.getInputStream() 和

           OutputStream out = socket.getOutputStream()

         就可以實現(xiàn)消息的來來往往了。

         但是如果把Stream給應用層看,顯然操作起來不方便。

         所以,在tomcat 的Connector里面, socket被封裝成了Request和Response這兩個對象。

         我們可以簡單地把Request看成管發(fā)到服務器來的數(shù)據(jù),把Response看成想發(fā)出服務器的數(shù)據(jù)。

        

         但是這樣又有其他問題了??? Request這個對象是把socket封裝起來了, 但是他提供的又東西太多了。

         諸如Request.getAuthorization(), Request.getSocket()。 像Authorization這種東西開發(fā)人員拿來基本上用不太著,而像socket這種東西,暴露給開發(fā)人員又有潛在的危險。 而且啊, 在Servlet Specification里面標準的通信類是ServletRequest和HttpServletRequest,而非這個Request類。 So, So, So. Tomcat必須得搗持搗持Request才行。 最后tomcat選擇了使用搗持模式(應該叫適配器模式)來解決這個問題。它把org.apache.catalina.Request 搗持成了 org.apache.coyote.tomcat4.CoyoteRequest。 而CoyoteRequest又實現(xiàn)了ServletRequest和HttpServletRequest 這兩種接口。 這樣就提供給開發(fā)人員需要且剛剛需要的方法了。

     

        ok, 讓我們在 tomcat的頂層容器 - StandardEngin 的invoke()方法這里設置一個斷點, 然后訪問

        http://localhost:8080/web/login.jsp, 我們來看看在前期都會路過哪些地方:

           1. run(): 536, java.lang.Thread, Thread.java

           CurrentThread

          2. run():666, org.apache.tomcat.util.threads.ThreadPool$ControlRunnable, ThreadPool.java

                   ThreadPool

           3. runIt():589, org.apache.tomcat.util.net.TcpWorkerThread, PoolTcpEndpoint.java

             ThreadWorker

    4.        processConnection():  549

    org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler, Http11Protocol.java

                      http protocol parser

          5. Process(): 781, org.apache.coyote.http11.Http11Processor, Http11Processor.java

              http request processor

           6. service(): 193, org.apache.coyote.tomcat4.CoyoteAdapter,CoyoteAdapter.java

             adapter

           7. invoke(): 995, org.apache.catalina.core.ContainerBase, ContainerBase.java

       StandardEngin

        1. 主線程

        2. 啟動線程池.

        3. 調(diào)出線程池里面空閑的工作線程。

        4. 把8080端口傳過來由httpd協(xié)議封裝的數(shù)據(jù),解析成Request和Response對象。

        5. 使用Http11Processor來處理request

        6. 在Http11Processor里面, 又會call CoyoteAdapter來進行適配處理,把Request適配成實現(xiàn)了ServletRequest和HttpServletRequest接口的CoyoteRequest.

    7. 到了這里,前期的去毛拔皮工作就基本上搞定,可以交給StandardEngin 做核心的處理工作了。

    3. 中期。 在各個容器間的穿梭。

        Request在各個容器里面的穿梭大致是這樣一種方式:

        每個容器里面都有一個管道(pipline), 專門用來傳送Request用的。

        管道里面又有好幾個閥門(valve), 專門用來過濾Request用的。

        在管道的低部通常都會放上一個默認的閥們。 這個閥們至少會做一件事情,就是把Request交給子容器。

        讓我們來想象一下:

         當一個Request進入一個容器后, 它就在管道里面流動,波羅~ 波羅~ 波羅~ 地穿過各個閥門。在流到最后一個閥門的時候,吧唧~ 那個該死的閥門就把它扔給了子容器。 然后又開始 波羅~ 波羅~ 波羅~ ... 吧唧~.... 波羅~ 波羅~ 波羅~ ....吧唧~....

        就是通過這種方式, Request 走完了所有的容器。( 感覺有點像消化系統(tǒng),最后一個地方有點像那里~ )

        OK, 讓我們具體看看都有些什么容器, 各個容器里面又都有些什么閥門,這些閥們都對我們的Request做了些什么吧:

    3.1 StandardEngin 的pipeline里面放的是:StandardEnginValve

    在這里,VALVE做了三件事:

    1.   驗證傳遞過來的request是不是httpservletRequest.

    2    驗證傳遞過來的 request 是否攜帶了host header信息.

    3    選擇相應的host去處理它。(一般我們都只有一個host:localhost,也就是127.0.0.1)。

    到了這個地方,我們的request就已經(jīng)完成了在Engin這個部分的歷史使命,通向前途未卜的下一站: host了。

    3.2 StandardHost 的pipline里面放的是: StandardHostValve

    1.   驗證傳遞過來的request是不是httpservletRequest.

    2.   根據(jù)Request來確定哪個Context來處理。

    Context其實就是webapp,比如http://localhost:8080/web/login.jsp

    這里web就是Context羅!

    3.   既然確定了是哪個Context了,那么就應該把那個Context的classloader付給當前線程了。

            Thread.currentThread().setContextClassLoader(context.getLoader().getClassLoader());

       這樣request就只看得見指定的context下面的classes啊, jar啊這些,而看不見tomcat本身的類,什么Engin啊, Valve啊。不然還得了?。?/p>

    4. 既然request到了這里了,看來用戶是準備訪問web這個web app了,咋們得更新一下這個用戶的session不是! Ok , 就由manager更新一下用戶的session信息

    5. 交給具體的Context 容器去繼續(xù)處理Request.

    6. Context處理完畢了,把classloader還回來。

    3.3 StandardContext 的pipline里面放的是: StandardContextValve

    1.   驗證傳遞過來的request是不是httpservletRequest.

    2.   如果request意圖不軌,想要訪問/meta-inf, /web-inf這些目錄下的東西,呵呵,沒有用D!

    3.   這個時候就會根據(jù)Request到底是Servlet,還是jsp,還是靜態(tài)資源來決定到底用哪種Wrapper來處理這個Reqeust了。

    4.   一旦決定了到底用哪種Wrapper,OK,交給那個Wrapper處理。

    4. 末期。 不同的需求是怎么處理的.

    StandardWrapper

    之前對Wrapper沒有做過講解,其實它是這樣一種東西。

    我們在處理Request的時候,可以分成3種。

    處理靜態(tài)的: org.apache.catalina.servlets.DefaultServlet  

    處理jsp的:org.apache.jasper.servlet.JspServlet

    處理servlet的:org.apache.catalina.servlets.InvokerServlet

    不同的request就用這3種不同的servlet去處理。

    Wrapper就是對它們的一種簡單的封裝,有了Wrapper后,我們就可以輕松地攔截每次的Request。也可以容易地調(diào)用servlet的init()和destroy()方法, 便于管理嘛!

    具體情況是這么滴:

       如果request是找jsp文件,StandardWrapper里面就會封裝一個org.apache.jasper.servlet.JspServlet去處理它。

       如果request是找 靜態(tài)資源 ,StandardWrapper里面就會封裝一個org.apache.jasper.servlet.DefaultServlet 去處理它。

       如果request是找servlet ,StandardWrapper里面就會封裝一個org.apache.jasper.servlet.InvokerServlet 去處理它。

    StandardWrapper同樣也是容器,既然是容器, 那么里面一定留了一個管道給request去穿,管道低部肯定也有一個閥門(注1),用來做最后一道攔截工作.

    在這最底部的閥門里,其實就主要做了兩件事:

       一是啟動過濾器,讓request在N個過濾器里面篩一通,如果OK! 那就PASS。 否則就跳到其他地方去了。

       二是servlet.service((HttpServletRequest) request,(HttpServletResponse) response); 這個方法.

         如果是 JspServlet, 那么先把jsp文件編譯成servlet_xxx, 再invoke servlet_xxx的servie()方法。

         如果是 DefaultServlet, 就直接找到靜態(tài)資源,取出內(nèi)容, 發(fā)送出去。

         如果是 InvokerServlet, 就調(diào)用那個具體的servlet的service()方法。

       ok! 完畢。

    注1: StandardWrapper 里面的閥門是最后一道關口了。 如果這個閥門欲意把request交給StandardWrapper 的子容器處理。 對不起, 在設計考慮的時候, Wrapper就被考慮成最末的一個容器, 壓根兒就不會給Wrapper添加子容器的機會! 如果硬是要調(diào)用addChild(), 立馬拋出IllegalArgumentException!

    參考:

         <http://jakarta.apache.org/tomcat/>
       <http://www.onjava.com/pub/a/onjava/2003/05/14/java_webserver.html>

    posted @ 2007-10-20 18:09 zongxing 閱讀(376) | 評論 (0)編輯 收藏

    本文轉(zhuǎn)自:http://blog.csdn.net/qiqijava/articles/210499.aspx
    1.關于Tomcat的基本情況

    眾所周知Tomcat是一個免費的開放源碼的Serlvet容器,它是Apache基金會的Jakarta項目中的一個核心項目,也是sun公司官方推薦的servlet和jsp容器,同時它還獲得過多種榮譽。servlet和jsp的最新規(guī)范都可以在tomcat的新版本中得到實現(xiàn)。Tomcat具有輕量級和靈活嵌入到應用系統(tǒng)中的優(yōu)點,所以得到了廣泛的應用。在Tomcat的發(fā)展中,Sun在1999年六月宣布參與Jakarta項目的Tomcat servlet容器和Jsp引擎的開發(fā),使得Tomcat在3.x和4.x版之間系統(tǒng)設計上發(fā)生了比較大的變化。Tomcat的其他信息我就不多說了。有興趣的朋友可以訪問http://jakarta.apache.org/ 的官方網(wǎng)站得到更多的信息。

    因為工作的原因,我改寫了Tomcat的一些代碼,所以我粗略的研究了一下Tomcat3.3和Tomcat4.0的源碼,深深地被這個開放軟件的設計和實現(xiàn)吸引,感覺到這個軟件中有許多值得我們學習和借鑒的地方。我把自己的理解介紹給大家算是拋磚引玉,不足和偏差還望大家批評指正。下面就來讓我們看看從Tomcat那里我們可以得到什么。

    2.從Tomcat中學習設計模式

    Tomcat的設計和實現(xiàn)處處體現(xiàn)著設計模式的思想,它的基本流程是首先通過解析xml格式的配置文件,獲得系統(tǒng)的配置和應用信息,然后加載定制的組件模塊提供各種系統(tǒng)服務。系統(tǒng)的各部分功能都是通過可以配置的組件模塊來實現(xiàn)的。Tomcat實現(xiàn)中像Observer,F(xiàn)acade,Adapter,Singleton等多種設計模型在Tomcat的源碼中隨處可見,為我們提供了一個很好的學習設計模式的平臺。我主要介紹一下Tomcat中程序流程控制所采用的設計模式,這是一個程序運行的框架。前面提到由于Sun公司的參與,Tomcat雖然基本的流程沒有變化,但是Tomcat3.3和Tomcat4.0版本之間在概念上還是有很大地不同的。Tomcat3.3整體上是模塊化的設計,而Tomcat4.0可以看作是采用面向組件技術進行設計的。組件是比模塊更高級的一個層次。我們可以通過比較它們之間的不同來了解實現(xiàn)一個服務器軟件可以采用的設計模式和實現(xiàn)方式。

    2.1Tomcat3.3的基本結(jié)構(gòu)設計

    Tomcat3.3采用的是一種模塊化的鏈狀的控制結(jié)構(gòu),它的主要設計模式有:

    Chain of responsibility(責任鏈)

    作為一個基于請求響應模式的服務器,在Tomcat3.3中采用一種鏈狀處理的控制模式。請求在鏈上的各個環(huán)節(jié)上傳遞,在任一環(huán)節(jié)上可以存在若干個"監(jiān)聽器"處理它。這樣做的目的是避免請求的發(fā)送者和接受者之間的直接耦合,從而可以為其他的對象提供了參與處理請求的機會。采用這個方式不但可以通過"監(jiān)聽器"實現(xiàn)系統(tǒng)功能,而且可以通過添加新的"監(jiān)聽器"對象實現(xiàn)系統(tǒng)功能的擴展。

    Interceptor(監(jiān)聽器)

    "監(jiān)聽器"是一個過去使用的名稱,它可以看作 "模塊(module)"的同義詞。它是Tomcat功能模塊構(gòu)建和擴展的方式。Tomcat3.3的大部分功能都是通過"監(jiān)聽器"實現(xiàn)的。在Tomcat中提供了一種簡單的鉤子(Hook)機制,監(jiān)聽器對鉤子中感興趣的事件進行注冊,事件發(fā)生后通過鉤子喚醒已注冊的"監(jiān)聽器"對象,"監(jiān)聽器"對象對Tomcat內(nèi)核事件進行處理。這些模塊都是圍繞著"責任鏈"和"策略"的模式進行設計。通過"監(jiān)聽器"你可以監(jiān)聽各種特殊事件,進而控制處理請求的各個步驟---解析,認證,授權,會話,響應提交,緩沖區(qū)提交等等。

    Strategy(策略)

    所謂策略是指"定義一組規(guī)則,按照規(guī)則進行對象封裝,使得他們只在規(guī)則內(nèi)部進行交互"。通過策略模式使得Tomcat作為一個開源項目在開放環(huán)境下的開發(fā)和演變變得更輕松。通過這種模式把復雜的算法分成模塊然后不同的開發(fā)組提供各自的實現(xiàn)。從而實現(xiàn)調(diào)用模塊的代碼和模塊的具體實現(xiàn)代碼的分別開發(fā)。這樣可以使我們專注于問題的重點,并且減少問題之間的依賴性。在Tomcat中大量采用了策略的設計模式,通過這種方式每一種服務都提供了多種的實現(xiàn)(例如Tomcat中有2-3種認證模塊),在代碼完成后可以從穩(wěn)定性和性能表現(xiàn)的考慮選擇更好的實現(xiàn)。策略模式對開放式環(huán)境下的軟件開發(fā)非常有用。

    我們通過簡化的類圖(見圖一)和時序圖(見圖二),描述一下Tomcat3.3的程序流程控制如何通過監(jiān)聽器和責任鏈實現(xiàn)。

    圖一 簡化的類圖
    圖一 簡化的類圖

     

    關于類圖的簡單說明:

    BaseInterceptor:是所有監(jiān)聽器的基類,描述了基本的模塊功能和對各種事件的缺省處理。

    ContextManage:系統(tǒng)的核心控制對象,進行請求處理和系統(tǒng)配置。它維護了全局的屬性、web應用的內(nèi)容和全局模塊等多種信息,責任鏈的鉤子實現(xiàn)也在其中。

    PoolTcpConnector:一個處理TCP連接的連接器對象,從BaseIntercepor派生。它包括一個具體處理socket連接的PoolTcpEndPoint類對象。

    PoolTcpEndPoint:處理實際的tcp連接。它有一個連接池對象ThreadPool和運行在獨立線程中的應用邏輯類TcpWorkThread。

    TcpWorkThead:處理socket連接事務,調(diào)用接口TcpConnectionHandler中的請求處理方法。

    Http10Interceptor:從PoolTcpConnector派生,實現(xiàn)了TcpConnectionHandler接口,是一個真正的監(jiān)聽器對象。它按照Http1.0的協(xié)議標準對tcp連接進行處理,調(diào)用核心對象ContextManage的服務方法。

    圖二 簡化的時序圖
    圖二 簡化的時序圖

     

    關于時序圖中需要說明的地方:

    1. 在contextManager初始化后會根據(jù)配置信息,加載基本的應用模塊和各種監(jiān)聽器對象,創(chuàng)建鉤子(Hook)機制,注冊監(jiān)聽器對象,形成一個責任鏈。然后對各個監(jiān)聽器對象發(fā)出engineInit,engineStart消息。
    2. 一個請求在經(jīng)過http10interceptor基本處理后提交到contextManager處理。
    3. ContextManager的processRequest方法進行請求的處理。按照處理的步驟會順序地發(fā)出H_postReadRequest,H_contextMap, H_requestMap等消息。然后從hook中取得對該消息注冊的監(jiān)聽器對象,調(diào)用他們的處理方法,從而實現(xiàn)責任鏈方式。以下的代碼片斷說明了這種方式:
      BaseInterceptor ri[];//取得注冊對象ri=defaultContainer.getInterceptors(Container.H_postReadRequest);//執(zhí)行注冊對象的對消息的處理方法for( int i=0; i< ri.length; i++ ) { status=ri[i].postReadRequest( req );	......}
    4. 系統(tǒng)退出時contextManager發(fā)出engineStop消息。

    Tomcat3.3的基本程序結(jié)構(gòu)就是采用上面介紹的方式設計的。它給我們的設計和開發(fā)提供了一個很好的思路,通過這種模式可以輕松的實現(xiàn)一個事件驅(qū)動的基于模塊化設計的應用程序。各個功能通過模塊實現(xiàn),通過對責任鏈上的消息和處理步驟的改動或者添加新的監(jiān)聽器對象可以非常方便的擴展Tomcat的功能。所以這是一個非常好的設計。

    2.2Tomcat4.0的基本結(jié)構(gòu)設計

    雖然Tomcat3.x已經(jīng)實現(xiàn)了一個非常好的設計體系,但是在Sun公司加入后, Tomcat4.0中還是引入了不同的實現(xiàn)方式。主要的區(qū)別是Tomcat4.0采用了面向組件的設計方式, Tomcat4.0中的功能是由組件提供的,控制流程通過組件之間的通訊完成。這不同于Tomcat3.3中的基于模塊的鏈式控制結(jié)構(gòu)。

    面向組件的技術(CO)是比面向?qū)ο蟮募夹g(OOP)更高一層的抽象,它融合了面向?qū)ο蟮膬?yōu)點,加入了安全性和可擴展的模塊設計,可以更好的映射問題域空間。采用面向組件的設計會帶來很多好處,可以提高復用性、降低耦合度和通過組裝構(gòu)成系統(tǒng)等等。面向組件編程中有許多概念與原來面向?qū)ο蟮木幊淌遣煌?,例如?/p>

    Message(消息):定義抽象操作; Method(方法):定義具體的操作;
    Interface(接口):一組消息的集合; Implementation(實現(xiàn)):一組方法的集合;
    Module(模塊):靜態(tài)的數(shù)據(jù)結(jié)構(gòu), Type(類型):動態(tài)的數(shù)據(jù)結(jié)構(gòu)。

    軟件組件不同與功能模塊,它具有以下特性:

    • 組件是一個自包容的模塊,具有定義清楚的界線,對外提供它的能力、屬性和事件。
    • 組件自身不保留狀態(tài)。
    • 組件可以是一個類,大部分情況下是一組類。

    在Java 語言中對面向組件編程的支持是通過JavaBeans模型獲得的。JavaBean組件框架提供了對事件和屬性的支持。Tomcat4.0的組件的就是通過JavaBean技術實現(xiàn)的。這是它和Tomcat3.3中最大的不同。下面我們來看一下Tomcat4.0是如何通過面向組件編程來實現(xiàn)程序流程控制的。

    面向組件編程時設計組件是關鍵,從Tomcat4.0中可以看出主要使用了以下的設計模式:

    Separation of Concerns(SOC)

    設計組件時應該從不同的問題領域,站在不同的觀點上分析,把每一種屬性分別考慮。舉一個例子FileLogger組件,它用于把系統(tǒng)日志信息保存到文件系統(tǒng)中。按照這種模式分析,我們從不同的角度看待它:它如何啟動服務、停止服務和進行通訊?它的具體的功能有哪些?別的組件可以發(fā)給它哪些消息?基于這些考慮,F(xiàn)ileLogger組件應該實現(xiàn)兩種接口:Lifecycle(生存期接口)和LoggerBase(功能接口)。

    Inversion of Control(IOC)這個模式定義的是,組件總是通過外部進行管理的。組件需要的信息總是來源于外部,實際上組件在生存期的各個階段都是被創(chuàng)建它的組件管理的。在Tomcat4.0中就是通過這種組件之間的相互控制和調(diào)用實現(xiàn)各個功能的。

    按照這些模式分析得到的Tomcat4.0中的組件是既有共性又有特性。共性是Lifecycle接口,特性是不同的功能接口。其中生存期接口處理組件的整個生存期中的各個階段的事件,功能接口提供給其他的組件使用。

    具體的功能如何實現(xiàn)我在這里不多介紹了,我主要介紹一下Tomcat4.0中組件的Lifecycle接口的設計。Lifecycle接口實現(xiàn)了組件的生存期管理,控制管理和通訊。創(chuàng)建一個軟件組件和創(chuàng)建一個JavaBean對象一樣,可以參考JavaBean進行理解。我通過一個模擬的Lifecycle接口組件的類圖來描述。(見圖三)

    圖三 Lifecycle接口組件類圖
    圖三 Lifecycle接口組件類圖

     

    對模擬的Lifecycle接口組件的類圖的說明

    1. Lifecycle Interface(接口)定義一組組件通訊的Message(消息)。
    2. 組件實現(xiàn)Lifecycle接口,組件內(nèi)部定義一個LifecycleSupport對象。需要和該組件通訊的其他組件必須實現(xiàn)LifecycleListener接口,該組件通過add/removeLifecycleListener方法管理需要通訊的其他組件。
    3. 組件內(nèi)部狀態(tài)改變時如果需要和其他組件通訊時,通過LifecycleSupport對象的fireLifecycleEvent方法通知其他組件。
    4. 其他組件通過lifecycleEvent方法獲得通知的消息。LifecycleEvent對象是從java.util.EventObject派生的。
    5. 當然在組件設計和實現(xiàn)中我們也可以直接使用JavaBeans中已經(jīng)提供的的類如:java.beans.PropertyChangeListener;java.beans.PropertyChangeSupport這樣可以獲得更多的功能特性支持。

    通過上面的分析我們可以看到組件成為Tomcat4.0中的核心概念,系統(tǒng)的功能都是通過組件實現(xiàn)的,組件之間的通訊構(gòu)成了系統(tǒng)的運行控制機制。我們把Tomcat3.3中模塊化的鏈狀控制機制和Tomcat4.0的面向組件的設計進行比較,就會發(fā)現(xiàn)Tomcat4.0在設計思想上軟件組件的概念非常明確。Tomcat4.0和Tomcat3.3最主要的區(qū)別就在于此。至于面向?qū)ο蠛兔嫦蚪M件的關系和區(qū)別,我在這里就不介紹了,有興趣的朋友可以找到很多這方面的資源。

    3.從Tomcat源碼中得到高效的軟件組件

    Tomcat不但為我們提供了設計和實現(xiàn)系統(tǒng)時的新思路,同時因為它是由組件或者模塊構(gòu)成的,所以它還為我們提供了大量可用的高效軟件組件。這些組件都可以在我們的程序開發(fā)中使用。我簡單列舉一些,需要時可以直接從源碼中取得。

    • 一些特殊集合類數(shù)據(jù)結(jié)構(gòu)如池、隊列、緩存等可用于服務端開發(fā)。
      \src\share\org\apache\tomcat\util\collections
    • 一個簡單的鉤子(Hooks)機制的實現(xiàn)。
      src\share\org\apache\tomcat\util\hooks
    • 一個簡單線程池(ThreadPool)的實現(xiàn)。
      src\share\org\apache\tomcat\util\threads
    • 組件Lifecycle接口的設計和實現(xiàn)。
      \src\catalina\src\share\org\apache\Catalina
    • 常用的日志信息的管理(Logger)的實現(xiàn)。
      src\catalina\src\share\org\apache\catalina\logger
    • 對xml格式的配置信息進行處理(XmlMapper)的實現(xiàn)。
      src\catalina\src\share\org\apache\catalina\util\xml
    • 對socket通訊的高級管理和實現(xiàn)(net)。
      \src\catalina\src\share\org\apache\catalina\net

    通過以上對Tomcat的簡單的介紹,我們可以看出,作為一個開放源碼的項目,Tomcat不但為我們提供了一個應用的平臺,同時它還為我們提供了一個學習和研究設計模式、面向組件技術等理論的實踐平臺。

    參考資料

    Tomcat3.3源碼和Tomcat4.0源碼http://jakarta.apache.org/tomcat/index.html
    《設計模式》

    posted @ 2007-10-20 18:04 zongxing 閱讀(363) | 評論 (0)編輯 收藏

    本文轉(zhuǎn)自:http://www.moon-soft.com/doc/18332.htm

    TOMCAT源碼分析(啟動框架)
    前言:
       本文是我閱讀了TOMCAT源碼后的一些心得。 主要是講解TOMCAT的系統(tǒng)框架, 以及啟動流程。若有錯漏之處,敬請批評指教!
    建議:
       畢竟TOMCAT的框架還是比較復雜的, 單是從文字上理解, 是不那么容易掌握TOMCAT的框架的。 所以得實踐、實踐、再實踐。 建議下載一份TOMCAT的源碼, 調(diào)試通過, 然后單步跟蹤其啟動過程。 如果有不明白的地方, 再來查閱本文, 看是否能得到幫助。 我相信這樣效果以及學習速度都會好很多!
      
    1. Tomcat的整體框架結(jié)構(gòu)
       Tomcat的基本框架, 分為4個層次。
       Top Level Elements:
        Server
        Service  
       Connector
        HTTP
        AJP
       Container
       Engine
         Host
       Context
       Component 
        manager
       logger
       loader
       pipeline
       valve
             ...
       站在框架的頂層的是Server和Service
       Server:  其實就是BackGroud程序, 在Tomcat里面的Server的用處是啟動和監(jiān)聽服務端事件(諸如重啟、關閉等命令。 在tomcat的標準配置文件:server.xml里面, 我們可以看到“<Server port="8005" shutdown="SHUTDOWN" debug="0">”這里的"SHUTDOWN"就是server在監(jiān)聽服務端事件的時候所使用的命令字)
       Service: 在tomcat里面, service是指一類問題的解決方案。  通常我們會默認使用tomcat提供的:Tomcat-Standalone 模式的service。 在這種方式下的service既給我們提供解析jsp和servlet的服務, 同時也提供給我們解析靜態(tài)文本的服務。
      
       Connector: Tomcat都是在容器里面處理問題的, 而容器又到哪里去取得輸入信息呢?
    Connector就是專干這個的。 他會把從socket傳遞過來的數(shù)據(jù), 封裝成Request, 傳遞給容器來處理。
       通常我們會用到兩種Connector,一種叫http connectoer, 用來傳遞http需求的。 另一種叫AJP, 在我們整合apache與tomcat工作的時候, apache與tomcat之間就是通過這個協(xié)議來互動的。 (說到apache與tomcat的整合工作, 通常我們的目的是為了讓apache 獲取靜態(tài)資源, 而讓tomcat來解析動態(tài)的jsp或者servlet。)
       Container: 當http connector把需求傳遞給頂級的container: Engin的時候, 我們的視線就應該移動到Container這個層面來了。
       在Container這個層, 我們包含了3種容器: Engin, Host, Context.
       Engin: 收到service傳遞過來的需求, 處理后, 將結(jié)果返回給service( service 是通過 connector 這個媒介來和Engin互動的 ).
       Host: Engin收到service傳遞過來的需求后,不會自己處理, 而是交給合適的Host來處理。
    Host在這里就是虛擬主機的意思, 通常我們都只會使用一個主機,既“localhost”本地機來處理。
       Context: Host接到了從Host傳過來的需求后, 也不會自己處理, 而是交給合適的Context來處理。
       比如: <http://127.0.0.1:8080/foo/index.jsp>
             <http://127.0.1:8080/bar/index.jsp>
       前者交給foo這個Context來處理, 后者交給bar這個Context來處理。
       很明顯吧! context的意思其實就是一個web app的意思。
       我們通常都會在server.xml里面做這樣的配置
       <Context path="/foo" docBase="D:/project/foo/web" />
       這個context容器,就是用來干我們該干的事兒的地方的。
      
       Compenent: 接下來, 我們繼續(xù)講講component是干什么用的。
       我們得先理解一下容器和組件的關系。
       需求被傳遞到了容器里面, 在合適的時候, 會傳遞給下一個容器處理。
       而容器里面又盛裝著各種各樣的組件, 我們可以理解為提供各種各樣的增值服務。
       manager: 當一個容器里面裝了manager組件后,這個容器就支持session管理了, 事實上在tomcat里面的session管理, 就是靠的在context里面裝的manager component.
       logger: 當一個容器里面裝了logger組件后, 這個容器里所發(fā)生的事情, 就被該組件記錄下來啦! 我們通常會在logs/ 這個目錄下看見 catalina_log.time.txt 以及 localhost.time.txt 和localhost_examples_log.time.txt。 這就是因為我們分別為:engin, host以及context(examples)這三個容器安裝了logger組件, 這也是默認安裝, 又叫做標配 :)
       loader: loader這個組件通常只會給我們的context容器使用, loader是用來啟動context以及管理這個context的classloader用的。
        pipline: pipeline是這樣一個東西, 當一個容器決定了要把從上級傳遞過來的需求交給子容器的時候, 他就把這個需求放進容器的管道(pipeline)里面去。 而需求傻呼呼得在管道里面流動的時候, 就會被管道里面的各個閥門攔截下來。 比如管道里面放了兩個閥門。 第一個閥門叫做“access_allow_vavle”, 也就是說需求流過來的時候,它會看這個需求是哪個IP過來的, 如果這個IP已經(jīng)在黑名單里面了, sure, 殺! 第二個閥門叫做“defaul_access_valve”它會做例行的檢查, 如果通過的話,OK, 把需求傳遞給當前容器的子容器。 就是通過這種方式, 需求就在各個容器里面?zhèn)鬟f,流動, 最后抵達目的地的了。
        valve: 就是上面所說的閥門啦。
       Tomcat里面大概就是這么些東西, 我們可以簡單地這么理解tomcat的框架,它是一種自上而下, 容器里又包含子容器的這樣一種結(jié)構(gòu)。
    2. Tomcat的啟動流程
       這篇文章是講tomcat怎么啟動的,既然我們大體上了解了TOMCAT的框架結(jié)構(gòu)了, 那么我們可以望文生意地就猜到tomcat的啟動, 會先啟動父容器,然后逐個啟動里面的子容器。 啟動每一個容器的時候, 都會啟動安插在他身上的組件。 當所有的組件啟動完畢, 所有的容器啟動完畢的時候, tomcat本身也就啟動完畢了。
       順理成章地, 我們同樣可以猜到, tomcat的啟動會分成兩大部分, 第一步是裝配工作。 第二步是啟動工作。
       裝配工作就是為父容器裝上子容器, 為各個容器安插進組件的工作。 這個地方我們會用到digester模式, 至于digester模式什么, 有什么用, 怎么工作的. 請參考 <http://software.ccidnet.com/pub/article/c322_a31671_p2.html>
       啟動工作是在裝配工作之后, 一旦裝配成功了, 我們就只需要點燃最上面的一根導線, 整個tomcat就會被激活起來。 這就好比我們要開一輛已經(jīng)裝配好了的汽車的時候一樣,我們只要把鑰匙插進鑰匙孔,一擰,汽車的引擎就會發(fā)動起來,空調(diào)就會開起來, 安全裝置就會生效, 如此一來,汽車整個就發(fā)動起來了。(這個過程確實和TOMCAT的啟動過程不謀而和, 讓我們不得不懷疑 TOMCAT的設計者是在GE做JAVA開發(fā)的)。
    2.1 一些有意思的名稱:
       Catalina
       Tomcat
       Bootstrap
       Engin
       Host
       Context
       他們的意思很有意思:
       Catalina: 遠程轟炸機
       Tomcat: 熊貓轟炸機 -- 轟炸機的一種(這讓我想起了讓國人引以為豪的熊貓手機,是不是英文可以叫做tomcat??? , 又讓我想起了另一則廣告: 波導-手機中的戰(zhàn)斗機、波音-客機中的戰(zhàn)斗機 )
       Bootstap: 引導
       Engin: 發(fā)動機
       Host: 主機,領土
       Context: 內(nèi)容, 目標, 上下文
      
       ... 在許多許多年后, 現(xiàn)代人類已經(jīng)滅絕。 后現(xiàn)代生物發(fā)現(xiàn)了這些單詞零落零落在一塊。 一個自以為聰明的家伙把這些東西翻譯出來了:
       在地勤人員的引導(bootstrap)下, 一架轟炸架(catalina)騰空躍起, 遠看是熊貓轟炸機(tomcat), 近看還是熊貓轟炸機! 憑借著優(yōu)秀的發(fā)動機技術(engin), 這架熊貓轟炸機飛臨了敵國的領土上空(host), 對準目標(context)投下了毀天滅地的核彈頭,波~ 現(xiàn)代生物就這么隔屁了~
     
       綜上所述, 這又不得不讓人聯(lián)想到GE是不是也參與了軍事設備的生產(chǎn)呢?
       反對美帝國主義! 反對美霸權主義! 和平萬歲! 自由萬歲!
      
    2.2  歷史就是那么驚人的相似! tomcat的啟動就是從org.apache.catalina.startup.Bootstrap這個類悍然啟動的!
       在Bootstrap里做了兩件事:
       1. 指定了3種類型classloader:
          commonLoader: common/classes、common/lib、common/endorsed
          catalinaLoader: server/classes、server/lib、commonLoader
          sharedLoader:  shared/classes、shared/lib、commonLoader
       2. 引導Catalina的啟動。
          用Reflection技術調(diào)用org.apache.catalina.startup.Catalina的process方法, 并傳遞參數(shù)過去。
      
    2.3 Catalina.java
       Catalina完成了幾個重要的任務:
       1. 使用Digester技術裝配tomcat各個容器與組件。
          1.1 裝配工作的主要內(nèi)容是安裝各個大件。 比如server下有什么樣的servcie。 Host會容納多少個context。 Context都會使用到哪些組件等等。
          1.2 同時呢, 在裝配工作這一步, 還完成了mbeans的配置工作。 在這里,我簡單地但不十分精確地描述一下mbean是什么,干什么用的。
              我們自己生成的對象, 自己管理, 天經(jīng)地義! 但是如果我們創(chuàng)建了對象了, 想讓別人來管, 怎么辦呢? 我想至少得告訴別人我們都有什么, 以及通過什么方法可以找到  吧! JMX技術給我們提供了一種手段。 JMX里面主要有3種東西。Mbean, agent, connector.
           Mbean: 用來映射我們的對象。也許mbean就是我們創(chuàng)建的對象, 也許不是, 但有了它, 就可以引用到我們的對象了。
           Agent:  通過它, 就可以找到mbean了。
           Connector: 連接Agent的方式。 可以是http的, 也可以是rmi的,還可以直接通過socket。
          發(fā)生在tomcat 裝配過程中的事情:  GlobalResourcesLifecycleListener 類的初始化會被觸發(fā):
             protected static Registry registry = MBeanUtils.createRegistry();  會運行
             MBeanUtils.createRegistry()  會依據(jù)/org/apache/catalina/mbeans/mbeans-descriptors.xml這個配置文件創(chuàng)建 mbeans. Ok, 外界就有了條途徑訪問tomcat中的各個組件了。(有點像后門兒)
       2. 為top level 的server 做初始化工作。 實際上就是做通常會配置給service的兩條connector.(http, ajp)
       3. 從server這個容器開始啟動, 點燃整個tomcat.
       4. 為server做一個hook程序, 檢測當server shutdown的時候, 關閉tomcat的各個容器用。
       5. 監(jiān)聽8005端口, 如果發(fā)送"SHUTDOWN"(默認培植下字符串)過來, 關閉8005serverSocket。
    2.4 啟動各個容器
       1. Server
          觸發(fā)Server容器啟動前(before_start), 啟動中(start), 啟動后(after_start)3個事件, 并運行相應的事件處理器。
          啟動Server的子容器:Servcie.
       2. Service
          啟動Service的子容器:Engin
          啟動Connector
       3. Engin
          到了Engin這個層次,以及以下級別的容器, Tomcat就使用了比較一致的啟動方式了。
          首先,  運行各個容器自己特有一些任務
          隨后,  觸發(fā)啟動前事件
          立即,  設置標簽,就表示該容器已經(jīng)啟動
          接著,  啟動容器中的各個組件: loader, logger, manager等等
          再接著,啟動mapping組件。(注1)
          緊跟著,啟動子容器。
          接下來,啟動該容器的管道(pipline)
          然后,  觸發(fā)啟動中事件
          最后,  觸發(fā)啟動后事件。
     
          Engin大致會這么做, Host大致也會這么做, Context大致還是會這么做。 那么很顯然地, 我們需要在這里使用到代碼復用的技術。 tomcat在處理這個問題的時候, 漂亮地使用了抽象類來處理。 ContainerBase. 最后使得這部分完成復雜功能的代碼顯得干凈利落, 干練爽快, 實在是令人覺得嘆為觀止, 細細品來, 直覺如享佳珍, 另人齒頰留香, 留戀往返?。?br />      
          Engin的觸發(fā)啟動前事件里, 會激活綁定在Engin上的唯一一個Listener:EnginConfig。
          這個EnginConfig類基本上沒有做什么事情, 就是把EnginConfig的調(diào)試級別設置為和Engin相當。 另外就是輸出幾行文本, 表示Engin已經(jīng)配置完畢, 并沒有做什么實質(zhì)性的工作。
          注1: mapping組件的用處是, 當一個需求將要從父容器傳遞到子容器的時候, 而父容器又有多個子容器的話, 那么應該選擇哪個子容器來處理需求呢? 這個由mapping 組件來定奪。
       
       4. Host
           同Engin一樣, 也是調(diào)用ContainerBase里面的start()方法, 不過之前做了些自個兒的任務,就是往Host這個容器的通道(pipline)里面, 安裝了一個叫做
     “org.apache.catalina.valves.ErrorReportValve”的閥門。
           這個閥門的用處是這樣的:  需求在被Engin傳遞給Host后, 會繼續(xù)傳遞給Context做具體的處理。 這里需求其實就是作為參數(shù)傳遞的Request, Response。 所以在context把需求處理完后, 通常會改動response。 而這個org.apache.catalina.valves.ErrorReportValve的作用就是檢察response是否包含錯誤, 如果有就做相應的處理。
       5. Context
           到了這里, 就終于輪到了tomcat啟動中真正的重頭戲,啟動Context了。
     StandardContext.start() 這個啟動Context容器的方法被StandardHost調(diào)用.
     5.1 webappResources 該context所指向的具體目錄
     5.2 安裝defaultContex, DefaultContext 就是默認Context。 如果我們在一個Host下面安裝了DefaultContext,而且defaultContext里面又安裝了一個數(shù)據(jù)庫連接池資源的話。 那么其他所有的在該Host下的Context, 都可以直接使用這個數(shù)據(jù)庫連接池, 而不用格外做配置了。
      5.3 指定Loader. 通常用默認的org.apache.catalina.loader.WebappLoader這個類。   Loader就是用來指定這個context會用到哪些類啊, 哪些jar包啊這些什么的。
     5.4 指定 Manager. 通常使用默認的org.apache.catalina.session. StandardManager 。 Manager是用來管理session的。
         其實session的管理也很好實現(xiàn)。 以一種簡單的session管理為例。 當需求傳遞過來的時候, 在Request對象里面有一個sessionId 屬性。 OK, 得到這個sessionId后, 我們就可以把它作為map的key,而value我們可以放置一個HashMap. HashMap里邊兒, 再放我們想放的東西。
     5.5 postWorkDirectory (). Tomcat下面有一個work目錄。 我們把臨時文件都扔在那兒去。 這個步驟就是在那里創(chuàng)建一個目錄。 一般說來會在%CATALINA_HOME%/work/Standalone\localhost\ 這個地方生成一個目錄。
    5.6  Binding thread。到了這里, 就應該發(fā)生 class Loader 互換了。 之前是看得見tomcat下面所有的class和lib. 接下來需要看得見當前context下的class。 所以要設置contextClassLoader, 同時還要把舊的ClassLoader記錄下來,因為以后還要用的。
    5.7  啟動 Loader. 指定這個Context具體要使用哪些classes, 用到哪些jar文件。 如果reloadable設置成了true, 就會啟動一個線程來監(jiān)視classes的變化, 如果有變化就重新啟動Context。
    5.8  啟動logger
    5.9  觸發(fā)安裝在它身上的一個監(jiān)聽器。
     lifecycle.fireLifecycleEvent(START_EVENT, null);
     作為監(jiān)聽器之一,ContextConfig會被啟動. ContextConfig就是用來配置web.xml的。 比如這個Context有多少Servlet, 又有多少Filter, 就是在這里給Context裝上去的。
     5.9.1 defaultConfig. 每個context都得配置 tomcat/conf/web.xml 這個文件。
     5.9.2 applicationConfig 配置自己的 WEB-INF/web.xml 文件
    5.9.3 validateSecurityRoles 權限驗證。 通常我們在訪問/admin 或者/manager的時候,需要用戶要么是admin的要么是manager的, 才能訪問。 而且我們還可以限制那些資源可以訪問, 而哪些不能。 都是在這里實現(xiàn)的。
    5.9.4 tldScan: 掃描一下, 需要用到哪些標簽(tag lab)
    5.10 啟動 manager
    5.11 postWelcomeFiles() 我們通常會用到的3個啟動文件的名稱:
    index.html、index.htm、index.jsp 就被默認地綁在了這個context上
     5.12 listenerStart 配置listener
     5.13 filterStart 配置 filter
     5.14 啟動帶有<load-on-startup>1</load-on-startup>的Servlet.
      順序是從小到大: 1,2,3… 最后是0
      默認情況下, 至少會啟動如下3個的Servlet:
      org.apache.catalina.servlets.DefaultServlet  
          處理靜態(tài)資源的Servlet. 什么圖片啊, html啊, css啊, js啊都找他
      org.apache.catalina.servlets.InvokerServlet
          處理沒有做Servlet Mapping的那些Servlet.
      org.apache.jasper.servlet.JspServlet
          處理JSP文件的.
           5.15  標識context已經(jīng)啟動完畢。
     走了多少個步驟啊, Context總算是啟動完畢嘍。
        OK! 走到了這里, 每個容器以及組件都啟動完畢。 Tomcat終于不辭辛勞地為人民服務了!
    3. 參考文獻:
        <http://jakarta.apache.org/tomcat/>
        <http://www.onjava.com/pub/a/onjava/2003/05/14/java_webserver.html>
       
    4. 后記
        這篇文章是講解tomcat啟動框架的,還有篇文章是講解TOMCAT里面的消息處理流程的細節(jié)的。 文章內(nèi)容已經(jīng)寫好了, 現(xiàn)在正在整理階段。 相信很快就可以做出來, 大家共同研究共同進步。
        這篇文章是獨自分析TOMCAT源碼所寫的, 所以一定有地方是帶有個人主觀色彩, 難免會有片面之處。若有不當之處敬請批評指教,這樣不僅可以使剛開始研究TOMCAT的兄弟們少走彎路, 我也可以學到東西。
        email: sojan_java@yahoo.com.cn

    5. tomcat源碼分析(消息處理)

    posted @ 2007-10-20 18:02 zongxing 閱讀(310) | 評論 (0)編輯 收藏

    2007年10月13日 #

    本文轉(zhuǎn)自:http://www.cnblogs.com/lane_cn/archive/2007/01/25/629731.html

    我使用OO技術第一次設計軟件的時候,犯了一個設計者所能犯的所有錯誤。那是一個來自國外的外包項目,外方負責功能設計,我們公司負責程序設計、編碼和測試。

    第一個重要的錯誤是,我沒有認真的把設計說明書看明白。功能點設計確實有一些問題,按照他們的設計,一個重要的流程是無法實現(xiàn)的。于是我在沒有與投資方溝通的情況下,擅自改動了設計,把一個原本在Linux系統(tǒng)上開發(fā)的模塊改到了Windows系統(tǒng)上。結(jié)果流程確實是實現(xiàn)了,但是很不幸,根本不符合他們的需要,比起原先的設計差的更多。在詢問了這個流程的設計意圖之后,我也清楚了這一點。對方的工程師承認了錯誤,但是問題是:“為什么不早說啊,我們都跟領導講過了產(chǎn)品的構(gòu)架,也保證了交貨時間了,現(xiàn)在怎么去說???”。他們設計的是一個蘋果,而我造了一個桔子出來。最后和工程師商議的結(jié)果是:先把桔子改成設計書上的蘋果,按時交貨,然后再悄悄的改成他們真正需要的香蕉。的這時候距離交貨的時間已經(jīng)不足三天了,于是我每天加班工作到天明,把代碼逐行抽出來,用gcc編譯調(diào)試。好在大部分都是體力活,沒有什么技術含量,即使在深夜大腦半休眠的情況下仍然可以接著干。

    項目中出現(xiàn)的另外一個錯誤是:我對工作量的估計非常的不準確。在第一個階段的時候,按照功能設計說明書中的一個流程,我做了一個示例,用上了投資方規(guī)定的所有的技術。當我打開瀏覽器,看到頁面上出現(xiàn)了數(shù)據(jù)庫里的“Tom,Jerry,王小帥”,就愉快的跑到走廊上去呼吸了一口新鮮空氣,然后樂觀的認為:設計書都已經(jīng)寫好了,示例也做出來了,剩下的事情肯定就象砍瓜切菜一樣了。不就是把大家召集起來講講設計書,看看示例,然后撲上去開工,然后大功告成。我為每個畫面分配的編碼工作量是三個工作日。結(jié)果卻是,他們的設計并不完美,我的理解也并不正確,大家的思想也并不一致。于是我天天召集開會,朝令夕改,不斷返工。最后算了一下,實際上寫完一個畫面用的時間在十個工作日以上。編碼占用了太多的時間,測試在匆忙中草草了事,質(zhì)量……能掩蓋的問題也就只好掩蓋一下了,性能更是無暇顧及了。

    還有一個方面的問題是出在技術上的,這方面是我本文要說的重點。按照投資方的方案,系統(tǒng)的主體部分需要使用J2EE框架,選擇的中間件是免費的JBoss。再加上Tomcat作為Web服務器,Struts作為表示層的框架。他們對于這些東西的使用都是有明確目的,但是我并不了解這些技術。新手第一次進行OO設計,加上過多的新式技術,于是出現(xiàn)了一大堆的問題。公司原本安排了一個牛人對我進行指導,他熟悉OO設計,并且熟悉這些開源框架,曾熟讀Tomcat和Struts源代碼。可是他確實太忙,能指導我的時間非常有限。

    投資方發(fā)來設計書以后,很快就派來了兩個工程師對這個說明書進行講解。這是一個功能設計說明書,包括一個數(shù)據(jù)庫設計說明書,和一個功能點設計說明。功能點說明里面敘述了每一個工作流程,畫面設計和數(shù)據(jù)流程。兩位工程師向我們簡單的說明了產(chǎn)品的構(gòu)想,然后花了一個多星期的時間十分詳細的說明了他們的設計,包括數(shù)據(jù)表里每一個字段的含義,畫面上每一個控件的業(yè)務意義。除了這些功能性的需求以外,他們還有一些技術上的要求。

    為了減少客戶的擁有成本,他們不想將產(chǎn)品綁定在特定的數(shù)據(jù)庫和操作系統(tǒng)上,并且希望使用免費的平臺。于是他們選擇了Java作為開發(fā)語言,并且使用了一系列免費的平臺。選用的中間件是JBoss,使用Entity Bean作為數(shù)據(jù)庫訪問的方式。我們對Entity Bean的效率不放心,因為猜測他運用了大量的反射技術。在經(jīng)過一段時間的技術調(diào)查之后,我決定不采用Entity Bean,而是自己寫出一大堆的Value Object,每個Value Object對應一個數(shù)據(jù)庫表,Value Object里面只有一些setter和getter方法,只保存數(shù)據(jù),不做任何事情。Value Object的屬性與數(shù)據(jù)庫里面的字段一一對應。與每個Value Object對應,做一個數(shù)據(jù)表的Gateway,負責把數(shù)據(jù)從數(shù)據(jù)庫里面查出來塞到這些Value Object里面,也負責把Value Object里面的數(shù)據(jù)塞回數(shù)據(jù)庫。

    按照這樣的設計,需要為每一個數(shù)據(jù)表寫一個Gateway和一個Value Object,這個數(shù)量是比較龐大的。因此我們做了一個自動生成代碼的工具,到數(shù)據(jù)庫里面遍歷每一個數(shù)據(jù)表,然后遍歷表里面的每一個字段,把這些代碼自動生成出來。

    這等于自己實現(xiàn)了一個ORM的機制。當時我們做這些事情的時候,ORM還是一個很陌生的名詞,Hibernate這樣的ORM框架還沒聽說過。接著我們還是需要解決系統(tǒng)在多種數(shù)據(jù)庫上運行的問題。Gateway是使用JDBC連接數(shù)據(jù)庫的,用SQL查詢和修改數(shù)據(jù)的。于是問題就是:要解決不同數(shù)據(jù)庫之間SQL的微小差別。我是這樣干的:我做了一個SqlParser接口,這個接口的作用是把ANSI SQL格式的查詢語句轉(zhuǎn)化成各種數(shù)據(jù)庫的查詢語句。當然我沒必要做的很全面,只要支持我在項目中用到的查詢方式和數(shù)據(jù)類型就夠了。然后再開發(fā)幾個具體的Parser來轉(zhuǎn)換不同的數(shù)據(jù)庫SQL格式。

    到這個時候,數(shù)據(jù)庫里面的數(shù)據(jù)成功轉(zhuǎn)化成了程序里面的對象。非常好!按道理說,剩下的OO之路就該順理成章了。但是,很不幸,我不知道該怎樣用這些Value Object,接下來我就懷著困惑的心情把過程式的代碼嫁接在這個OO的基礎上了。

    我為每一個畫面設計出了一個Session Bean,在這個Session Bean里面封裝了畫面所關聯(lián)的一切業(yè)務流程,讓這個Session Bean調(diào)用一大堆Value Object開始干活。在Session Bean和頁面之間,我沒有讓他們直接調(diào)用,因為據(jù)公司的牛人說:“頁面直接調(diào)用業(yè)務代碼不好,耦合性太強。”這句話沒錯,但是我對“業(yè)務代碼”的理解實在有問題,于是就硬生生的造出一個Helper來,阻擋在頁面和Session Bean中間,充當了一個傳聲筒的角色。

    于是在開發(fā)中就出現(xiàn)了下面這副景象:每當設計發(fā)生變更,我就要修改數(shù)據(jù)庫的設計,用代碼生成工具重新生成Value Object,然后重新修改Session Bean里面的業(yè)務流程,按照新的參數(shù)和返回值修改Helper的代碼,最后修改頁面的調(diào)用代碼,修改頁面樣式。

    實際情況比我現(xiàn)在說起來復雜的多。比如Value Object的修改,程序規(guī)模越來越大以后,我為了避免出現(xiàn)內(nèi)存的大量占用和效率的下降,不得不把一些數(shù)據(jù)庫查詢的邏輯寫到了Gateway和Value Object里面,于是在發(fā)生變更的時候,我還要手工修改代碼生成工具生成的Gateway和Value Object。這樣的維護十分麻煩,這使我困惑OO到底有什么好處。我在這個項目中用OO方式解決了很多問題,而這些問題都是由OO本身造成的。

    另一個比較大的問題出在Struts上。投資方為系統(tǒng)設計了很靈活的界面,界面上的所有元素都是可以配置出來,包括位置、數(shù)據(jù)來源、讀寫屬性。并且操作員的權限可以精確到每一個查看、修改的動作,可以控制每一個控件的讀寫操作。于是他們希望使用Struts。Struts框架的每一個Action恰好對應一個操作,只需要自己定義Action和權限角色的關系,就可以實現(xiàn)行為的權限控制。但是我錯誤的理解了Struts的用法,我為每一個頁面設計了一個Action,而不是為每一個行為設計一個Action,這樣根本就無法做到他們想要的權限控制方式。他們很快發(fā)現(xiàn)了我的問題,于是發(fā)來了一個說明書,向我介紹Struts的正確使用方式。說明書打印出來厚厚的一本,我翻了一天,終于知道了錯在什么地方。但是一大半畫面已經(jīng)生米煮成熟飯,再加上我的Session Bean里面的流程又是按畫面來封裝的,于是只能改造小部分能改造的畫面,權限問題另找辦法解決了。

    下面就是這個系統(tǒng)的全貌,場面看上去還是蔚為壯觀的:

    系統(tǒng)經(jīng)歷過數(shù)次較大的修改,這個框架不但沒有減輕變更的壓力,反而使得變更困難加大了。到后來,因為業(yè)務流程的變更的越來越復雜,現(xiàn)有流程無法修改,只得用一些十分曲折的方式來實現(xiàn),運行效率越來越低。由于結(jié)構(gòu)過于復雜,根本沒有辦法進行性能上的優(yōu)化。為了平衡效率的延緩,不得不把越來越多的Value Object放在了內(nèi)存中緩存起來,這又造成了內(nèi)存占用的急劇增加。到后期調(diào)試程序的時候,服務器經(jīng)常出現(xiàn)“Out of memory”異常,各類對象龐大繁多,系統(tǒng)編譯部署一次需要10多分鐘。投資方原先是希望我們使用JUnit來進行單元測試,但是這樣的流程代碼測試起來困難重重,要花費太多的時間和人手,也只得作罷。此外他們設計的很多功能其實都沒有實現(xiàn),并且似乎以后也很難再實現(xiàn)了。設計中預想的很多優(yōu)秀特點在這樣框架中一一消失,大家無奈的接受一個失望的局面。

    在我離開公司兩年以后,這個系統(tǒng)仍然在持續(xù)開發(fā)中。新的模塊不斷的添加,框架上不斷添加新的功能點。有一次遇到仍然在公司工作的同事,他們說:“還是原來那個框架,前臺加上一個個的JSP,然后后臺加上一個個的Value Object,中間的Session Bean封裝越來越多的業(yè)務流程。”

    我的第一個OO系統(tǒng)的設計,表面上使用了OO技術,實際上分析設計還是過程主導的方式。設計的時候過多、過早、過深入的考慮了需要做哪些畫面,畫面上應該有哪些功能點,功能點的數(shù)據(jù)流程。再加上一個復雜的OO框架,名目繁多的對象,不僅無法做到快速的開發(fā),靈活的適應需求的變化,反而使系統(tǒng)變得更加復雜,功能修改更加的麻煩了。

    在面條式代碼的時代,很多人用匯編代碼寫出了一個個優(yōu)秀的程序。他們利用一些工具,或者共同遵守一些特別的規(guī)約,采用一致的變量命名方式,規(guī)范的代碼注釋,可以使一個龐大的開發(fā)團隊運行的井井有條。人如果有了先進的思想,工具在這些人的手中就可以發(fā)揮出超越時代的能量。而我設計的第一個OO系統(tǒng),恰好是一個相反的例子。

    實際上,面向?qū)ο蟮淖瞠毺刂?,在于他分析需求的方式。按照這樣的方式,不要過分的糾纏于程序的畫面、操作的過程,數(shù)據(jù)的流程,而是要更加深入的探索需求中的一些重要概念。下面,我們就通過一個實例看一看,怎樣去抓住需求中的這些重要概念,并且運用OO方法把他融合到程序設計中。也看看OO技術是如何幫助開發(fā)人員控制程序的復雜度,讓大家工作的更加簡單、高效。

    我們來看看一個通信公司的賬務系統(tǒng)的開發(fā)情況。最開始,開發(fā)人員找到電信公司的職員詢問需求的情況。電信公司的職員是這樣說的:

    “賬務系統(tǒng)主要做這樣幾件事情:每個月1日凌晨按照用戶使用情況生成賬單,然后用預存沖銷這個賬單。還要受理用戶的繳費,繳費后可以自動沖銷欠費的賬單,欠費用戶繳清費用之后要發(fā)指令到交換上,開啟他的服務。費用繳清以后可以打印發(fā)票,發(fā)票就是下面這個樣子。”

    經(jīng)過一番調(diào)查,開發(fā)人員設計了下面幾個主要的流程:

    1、 出賬:根據(jù)一個月內(nèi)用戶的消費情況生成賬單;

    2、 銷賬:沖銷用戶賬戶上的余額和賬單;

    3、 繳費:用戶向自己的賬戶上繳費,繳清欠費后打印發(fā)票。

    弄清了流程,接著就設計用戶界面來實現(xiàn)這樣的流程。下面是其中一個數(shù)據(jù)查詢界面,分為兩個部分:上半部分是繳費信息,記錄了用戶的繳費歷史;下半部分是賬單信息,顯示賬單的費用和銷賬情況。

    界面上的數(shù)據(jù)一眼看起來很復雜,其實結(jié)合出賬、繳費、銷賬的流程講解一下,是比較容易理解的。下面簡單說明一下。

    繳費的時候,在繳費信息上添加一條記錄,記錄下繳費金額。然后查找有沒有欠費的賬單,如果有就做銷賬。沖抵欠費的金額記錄在“欠費金額”的位置。如果欠費時間較長,就計算滯納金,記錄在“滯納金”的位置上。沖銷欠費以后,剩余的金額記錄在“預存款”的位置上。“其他費用”這個位置是預留的,目前沒有作用。

    每個月出賬的時候,在賬單信息里面加上一條記錄,記錄下賬單的應收和優(yōu)惠,這兩部分相減就是賬單的總金額。然后檢查一下賬戶上有沒有余額,如果有就做銷賬。銷賬的時候,預存款沖銷的部分記錄在“預存劃撥”的位置,如果不足以沖抵欠費,賬單就暫時處于“未繳”狀態(tài)。等到下次繳費的時候,沖銷的金額再記錄到“新交款”的位置。等到所有費用繳清了,賬單狀態(tài)變成“已繳”。

    銷賬的流程就這樣融合在繳費和出賬的過程中。

    看起來一切成功搞定了,最重要的幾個流程很明確了,剩下的事情無疑就像砍瓜切菜一樣。無非是繞著這幾個流程,設計出其他更多的流程?,F(xiàn)在有個小問題:打印發(fā)票的時候,發(fā)票的右側(cè)需要有上次結(jié)余、本次實繳、本次話費、本次結(jié)余這幾個金額。

    上次結(jié)余:上個月賬單銷賬后剩下來的金額,這個容易理解;

    本次結(jié)余:當前的賬單銷賬后剩下的金額,這個也不難;

    本次話費:這是賬單的費用,還是最后一次完全銷賬時的繳費,應該用哪一個呢?

    本次繳費:這個和本次話費有什么區(qū)別,他在哪里算出來?

    帶著問題,開發(fā)者去問電信公司的職員。開發(fā)者把他們設計的界面指點給用戶看,向他說明了自己的設計的這幾個流程,同時也說出了自己的疑問。用戶沒有直接回答這個疑問,卻提出了另一個問題:

    “繳費打發(fā)票這個流程并不總是這樣的,繳費以后不一定立刻要打印發(fā)票的。我們的用戶可以在銀行、超市這樣的地方繳話費,幾個月以后才來到我們這里打印發(fā)票。并且繳費的時間和銷賬的時間可以相距很長的,可以先繳納一筆話費,后面幾個月的賬單都用這筆錢銷賬;也可以幾個月都不繳費,然后繳納一筆費用沖銷這幾個賬單。你們設計的這個界面不能很好的體現(xiàn)用戶的繳費和消費情況,很難看出來某一次繳費是在什么時候用完的。必須從第一次、或者最后一次繳費余額推算這個歷史,太麻煩了。還有,‘預存劃撥’、‘新交款’這兩個概念我們以前從來沒有見過,對用戶解釋起來肯定是很麻煩的。”

    開發(fā)人員平靜了一下自己沮喪(或憤怒)的心情,仔細想一想,這樣的設計確實很不合理。如果一個會計記出這樣的賬本來,他肯定會被老板開除的。

    看起來流程要改,比先前設計的更加靈活,界面也要改。就好像原先蓋好的一棟房子忽然被捅了幾個窟窿,變得四處透風了。還有,那四個數(shù)值到底應該怎樣計算出來呢?我們先到走廊上去呼吸兩口新鮮空氣,然后再回來想想吧。

    現(xiàn)在,讓我們先忘記這幾個變化多端的流程,花一點時間想一想最基本的幾個概念吧。系統(tǒng)里面最顯而易見的一個概念是什么呢?沒錯,是賬戶(Account)。賬戶可以繳費和消費。每個月消費的情況是記錄在一個賬單(Bill)里面的。賬戶和賬單之間是一對多的關系。此外,賬戶還有另一個重要的相關的概念:繳費(Deposit)。賬戶和繳費之間也是一對多的關系。在我們剛才的設計中,這些對象是這樣的:

    這個設計看來有些問題,使用了一些用戶聞所未聞的概念(預存劃撥,新交款)。并且他分離了繳費和消費,表面上很清楚,實際上使賬單的查詢變得困難了。在實現(xiàn)一些功能的時候確實比較簡單(比如繳費和銷賬),但是另一些功能變得很困難(比如打印發(fā)票)。問題到底在什么地方呢?

    涉及到賬務的行業(yè)有很多,最容易想到的也許就是銀行了。從銀行身上,我們是不是可以學到什么呢?下面是一個銀行的存折,這是一個委托收款的賬號。用戶在賬戶上定期存錢,然后他的消費會自動從這里扣除。這個情景和我們需要實現(xiàn)的需求很相似??梢杂^察一下這個存折,存入和支取都是記錄在同一列上的,在支出或者存入的右側(cè)記錄當時的結(jié)余。

    有兩次賬戶上的金額被扣除到0,這時候金額已經(jīng)被全部扣除了,但是消費還沒有完全沖銷。等到再次存入以后,會繼續(xù)支取。這種記賬的方式就是最基本的流水賬,每一條存入和支出都要記錄為一條賬目(Entry)。程序的設計應該是這樣:

    這個結(jié)構(gòu)看上去和剛才那個似乎沒有什么不同,其實差別是很大的。上面的那個Deposit只是繳費記錄,這里的Entry是賬目,包括繳費、扣費、滯納金……所有的費用。銷賬扣費的過程不應該記錄在賬單中,而是應該以賬目的形式記錄下來。Account的代碼片段如下:

     

    public class Account
    {
        
    public Bill[] GetBills()
        {
            
    //按時間順序返回所有的賬單
        }
        
        
    public Bill GetBill(DateTime month)
        {
            
    //按照月份返回某個賬單
        }
        
        
    public Entry[] GetEntrees()
        {
            
    //按時間順序返回所有賬目
        }
        
        
    public void Pay(float money)
        {
            
    //繳費
            
    //先添加一個賬目,然后沖銷欠費的賬單
        }
        
        
    public Bill GenerateBill(DateTime month)
        {
            
    //出賬
            
    //先添加一個賬單,然后用余額沖銷這個賬單
        }
        
        
    public float GetBalance()
        {
            
    //返回賬戶的結(jié)余
            
    //每一條賬目的金額總和,就是賬戶的結(jié)余
        }
        
        
    public float GetDebt()
        {
            
    //返回賬戶的欠費
            
    //每一個賬單的欠費金額綜合,就是賬戶的欠費
        }
    }

    Entry有很多種類型(存入、支取、滯納金、贈送費),可以考慮可以為每一種類型創(chuàng)建一個子類,就像這樣:

    搞成父子關系看起來很復雜、麻煩,并且目前也看不出將這些類型作為Entry的子類有哪些好處。所以我們決定不這樣做,只是簡單的把這幾種類型作為Entry的一個屬性。Entry的代碼片段如下:

    public class Entry
    {
        
    public DateTime GetTime()
        {
            
    //返回賬目發(fā)生的時間
        }
        
        
    public float GetValue()
        {
            
    //返回賬目的金額
        }
        
        
    public EntryType GetType()
        {
            
    //返回賬目的類型(存入、扣除、贈送、滯納金)
        }
        
        
    public string GetLocation()
        {
            
    //返回賬目發(fā)生的營業(yè)廳
        }
        
        
    public Bill GetBill()
        {
            
    //如果這是一次扣除,這里要返回相關的賬單
        }
    }

    Entry是一個枚舉類型,代碼如下:

    public enum EntryType
    {
        Deposit 
    = 1,
        Withdrawal 
    = 2,
        Penalty 
    = 3,
        Present 
    = 4
    }

    下面的界面顯示的就是剛才那個賬戶的賬目。要顯示這個界面只需要調(diào)用Account的GetEntrees方法,得到所有的賬目,然后按時間順序顯示出來。這個界面上的消費情況就明確多了,用戶很容易弄明白某個繳費是在哪幾個月份被消費掉的。

    并且,發(fā)票上的那幾個一直搞不明白的數(shù)值也有了答案。比如2005年6月份的發(fā)票,我們先看看2005年6月份銷賬的所有賬目(第六行、第八行),這兩次一共扣除73.66元,這個金額就是本次消費;兩次扣除之間存入200元,這個就是本次實繳;第五行的結(jié)余是17.66元,這就是上次結(jié)余;第八行上的結(jié)余是144元,這個就是本次結(jié)余。

    用戶檢查了這個設計,覺得這樣的費用顯示明確多了。盡管一些措辭不符合習慣的業(yè)務詞匯,但是他們的概念都是符合的。并且上次還有一個需求沒有說:有時候需要把多個月份的發(fā)票合在一起打印。按照這樣的賬目表達方式,合并的發(fā)票數(shù)值也比較容易搞清楚了。明確了這樣的對象關系,實現(xiàn)這個需求其實很容易。

    面向?qū)ο蟮脑O計就是要這樣,不要急于確定系統(tǒng)需要做哪些功能點和哪些界面,而是首先要深入的探索需求中出現(xiàn)的概念。在具體的流程不甚清楚的情況下,先把這些概念搞清楚,一個一個的開發(fā)出來。然后只要把這些做好的零件拿過來,千變?nèi)f化的流程其實就變得很簡單了,一番搭積木式的裝配就可以比較輕松的實現(xiàn)。

    另一個重要的類型也漸漸清晰的浮現(xiàn)在我們的眼前:賬單(Bill)。他的代碼片段如下:

    public class Bill
    {
        
    public DateTime GetBeginTime()
        {
            
    //返回賬單周期的起始時間
        }
        
        
    public DateTime GetEndTime()
        {
            
    //返回賬單周期的終止時間
        }
        
        
    public Fee GetFee()
        {
            
    //返回賬單的費用
        }
        
        
    public float GetPenalty()
        {
            
    //返回賬單的滯納金
        }
        
        
    public void CaculatePenalty()
        {
            
    //計算賬單的滯納金
        }
        
        
    public float GetPaid()
        {
            
    //返回已支付金額
        }
        
        
    public float GetDebt()
        {
            
    //返回欠費
            
    //賬單費用加上滯納金,再減去支付金額,就是欠費
            return GetFee().GetValue() + GetPanalty() - GetPaid();
        }
        
        
    public Entry GetEntrees()
        {
            
    //返回相關的存入和支取的賬目
        }
        
        
    public Bill Merge(Bill bill)
        {
            
    //合并兩個賬單,返回合并后的賬單
            
    //合并后的賬單可以打印在一張發(fā)票上
        }
    }

    Bill類有兩個與滯納金有關的方法,這使開發(fā)者想到了原先忽略的一個流程:計算滯納金。經(jīng)過與電信公司的確認,決定每個月進行一次計算滯納金的工作。開發(fā)人員寫了一個腳本,先得到系統(tǒng)中所有的欠費賬單,然后一一調(diào)用他們的CaculatePenalty方法。每個月將這個腳本執(zhí)行一次,就可以完成滯納金的計算工作。

    Bill對象中有賬戶的基本屬性和各級賬目的金額和銷賬的情況,要打印發(fā)票,只有這些數(shù)值是不夠的。還要涉及到上次結(jié)余、本次結(jié)余和本次實繳,這三個數(shù)值是需要從賬目中查到的。并且發(fā)票有嚴格的格式要求,也不需要顯示費用的細節(jié),只要顯示一級和二級的費用類就可以了。應該把這些東西另外封裝成一個類:發(fā)票(Invoice):

    通信公司后來又提出了新的需求:有些賬號和銀行簽訂了托收協(xié)議,每個月通信公司打印出這些賬戶的托收單交給銀行,銀行從個人結(jié)算賬戶上扣除這筆錢,再把一個扣費單交給通信公司。通信公司根據(jù)這個扣費單沖銷用戶的欠費。于是開發(fā)人員可以再做一個托收單(DeputyBill):

    賬單中的GetFee方法的返回值類型是Fee,F(xiàn)ee類型包含了費用的名稱、金額和他包含的其他費用。例如下面的情況:

    我們可以用這樣的一個類來表示費用(Fee),一個費用可以包含其他的費用,他的金額是子費用的金額和。代碼片段如下:

    public class Fee
    {
        
    private float valuee = 0;
        
        
    public string GetName()
        {
            
    //返回費用的名稱
        }
        
        
    public bool HasChildren()
        {
            
    //該費用類型是否有子類型
        }
        
        
    public Fee[] GetChildren()
        {
            
    //返回該費用類型的子類型
        }
        
        
    public float GetValue()
        {
            
    //返回費用的金額
            if (HasChildren())
            {
                
    float f = 0;
                Fee[] children 
    = GetChildren();
                
    for (int i = 0; i < children.Length; i ++)
                {
                    f 
    += children[i].GetValue();
                }
                
    return f;
            }
            
    else
            {
                
    return valuee;
            }
        }
    }

    現(xiàn)在開發(fā)者設計出了這么一堆類,構(gòu)成軟件系統(tǒng)的主要零件就這么制造出來了。下面要做的就是把這些零件串在一起,去實現(xiàn)需要的功能。OO設計的重點就是要找到這些零件。就像是設計一輛汽車,僅僅知道油路、電路、傳動的各項流程是不夠的,重要的是知道造一輛汽車需要先制造哪些零件。要想正確的把這些零件設計出來不是一件容易的事情,很少有開發(fā)者一開始就了解系統(tǒng)的需求,設計出合理的對象關系。根本的原因在于領域知識的貧乏,開發(fā)者和用戶之間也缺乏必要的交流。很多人在軟件開發(fā)的過程中才漸漸意識到原來的設計中存在一些難受的地方,然后探索下去,才知道了正確的方式,這就是業(yè)務知識的一個突破。不幸的是,當這個突破到來的時候,程序員經(jīng)常是已經(jīng)忙得熱火朝天,快把代碼寫完了。要把一切恢復到正常的軌道上,需要勇氣,時間,有遠見的領導者,也需要有運氣。

    posted @ 2007-10-13 22:14 zongxing 閱讀(264) | 評論 (0)編輯 收藏

    剛開始怎么樣都不知道怎么把java取得的反射類實例化
    最初的理解是:
    Class a = Class.for("name");總以為這句話得到的是一個實例,現(xiàn)在發(fā)現(xiàn),原來前面有一個Class,得到的實際是一個類.而類是必須要實例化才能使用的。a.newInstance()是給它實例化的方法
    實例化之后還要做轉(zhuǎn)型才能使用。
    舉個例子:A a = (A) Class.forName("A").newInstance();
    這個才可以使用a對象去調(diào)用他的方法。
    posted @ 2007-10-13 19:41 zongxing 閱讀(276) | 評論 (0)編輯 收藏

    2007年10月12日 #

     
    來源:yjhmily - BlogJava

    一、規(guī)范存在的意義

    應用編碼規(guī)范對于軟件本身和軟件開發(fā)人員而言尤為重要,有以下幾個原因:

    1、好的編碼規(guī)范可以盡可能的減少一個軟件的維護成本 , 并且?guī)缀鯖]有任何一個軟件,在其整個生命周期中,均由最初的開發(fā)人員來維護;

    2、好的編碼規(guī)范可以改善軟件的可讀性,可以讓開發(fā)人員盡快而徹底地理解新的代碼;

    3、好的編碼規(guī)范可以最大限度的提高團隊開發(fā)的合作效率;

    4、長期的規(guī)范性編碼還可以讓開發(fā)人員養(yǎng)成好的編碼習慣,甚至鍛煉出更加嚴謹?shù)乃季S;

    二、命名規(guī)范

    1、一般概念

    1、盡量使用完整的英文描述符

    2、采用適用于相關領域的術語

    3、采用大小寫混合使名字可讀

    4、盡量少用縮寫,但如果用了,必須符合整個工程中的統(tǒng)一定義

    5、避免使用長的名字(小于 15 個字母為正常選擇)

    6、避免使用類似的名字,或者僅僅是大小寫不同的名字

    7、避免使用下劃線(除靜態(tài)常量等)

    2、標識符類型說明

    1、包( Package )的命名

    Package 的名字應該采用完整的英文描述符,都是由一個小寫單詞組成。并且包名的前綴總是一個頂級域名,

    通常是 com、edu、gov、mil、net、org 等;
    如: com.yjhmily.test

    2、類( Class )的命名

    類名應該是個一名詞,采用大小寫混合的方式,每個單詞的首字母大寫。盡量保證類名簡潔而富于描述。

    使用完整單詞,避免縮寫詞 ( 除非工程內(nèi)有統(tǒng)一縮寫規(guī)范或該縮寫詞被更廣泛使用,像 URL , HTML)

    如: FileDescription

    3、接口( Interface )的命名

    基本與 Class 的命名規(guī)范類似。在滿足 Classd 命名規(guī)則的基礎之上,保證開頭第一個字母為 ”I”,

    便于與普通的 Class區(qū)別開。其實現(xiàn)類名稱取接口名的第二個字母到最后,且滿足類名的命名規(guī)范;

    如: IMenuEngine

    4、枚舉( Enum )的命名

    基本與 Class 的命名規(guī)范類似。在滿足 Classd 命名規(guī)則的基礎之上,保證開頭第一個字母為 ”E” ,

    便于與普通的 Class區(qū)別開。
    如: EUserRole

    5、異常( Exception )的命名

    異常( Exception ) 通常采用字母 e 表示異常,對于自定義的異常類,其后綴必須為 Exception

    如: BusinessException

    6、方法( Method )的命名

    方法名是一個動詞,采用大小寫混合的方式,第一個單詞的首字母小寫,其后單詞的首字母大寫。

    方法名盡可能的描述出該方法的動作行為。返回類型為 Boolean 值的方法一般由“ is ”或“ has ”來開頭

    如: getCurrentUser() 、 addUser() 、 hasAuthority()

    7、參數(shù)( Param )的命名

    第一個單詞的首字母小寫,其后單詞的首字母大寫。參數(shù)量名不允許以下劃線或美元符號開頭,

    雖然這在語法上是允許的。參數(shù)名應簡短且富于描述。

    如: public UserContext getLoginUser(String loginName);

    8、常量字段 ( Constants )的命名

    靜態(tài)常量字段( static final ) 全部采用大寫字母,單詞之間用下劃線分隔;

    如: public static final Long FEEDBACK;
    public static Long USER_STATUS;

    三、注釋規(guī)范

    一個很好的可遵循的有關注釋的經(jīng)驗法則是:

    問問你自己,你如果從未見過這段代碼,要在合理的時間內(nèi)有效地明白這段代碼,你需要一些什么信息???

    1、一般概念

    1、注釋應該增加代碼的清晰度

    2、保持注釋的簡潔

    3、在寫代碼之前或同時寫注釋

    4、注釋出為什么做了一些事,而不僅僅是做了什么

    2、注釋哪些部分

    1、Java 文件:必須寫明版權信息以及該文件的創(chuàng)建時間和作者;

    2、類:類的目的、即類所完成的功能,以及該類創(chuàng)建的時間和作者名稱;多人一次編輯或修改同一個類時,

    應在作者名稱處出現(xiàn)多人的名稱;

    3、接口: 在滿足類注釋的基礎之上,接口注釋應該包含設置接口的目的、它應如何被使用以及如何不被使用。

    在接口注釋清楚的前提下對應的實現(xiàn)類可以不加注釋;

    4、方法注釋: 對于設置 (Set 方法 ) 與獲取 (Get 方法 ) 成員的方法,在成員變量已有說明的情況下,

    可以不加注釋;普通成員方法要求說明完成什么功能,參數(shù)含義是什么且返回值什么;另外方法的創(chuàng)建

    時間必須注釋清楚,為將來的維護和閱讀提供寶貴線索;

    5、方法內(nèi)部注釋: 控制結(jié)構(gòu),代碼做了些什么以及為什么這樣做,處理順序等,特別是復雜的邏輯處理部分,

    要盡可能的給出詳細的注釋;

    6、參數(shù): 參數(shù)含義、及其它任何約束或前提條件;

    7、屬性: 字段描述;

    8、局部 ( 中間 ) 變量: 無特別意義的情況下不加注釋;

    3、注釋格式

    遵循工程規(guī)定的統(tǒng)一注釋格式,一般情況下會以 codetemplates.xml 格式的文件導入 IDE(Eclipse)

    或者用Eclipse默認的;

    四、代碼格式規(guī)范

    遵循工程規(guī)定的統(tǒng)一代碼格式,一般情況下直接使用 IDE(Eclipse) 自帶的默認代碼格式對代碼進行格式化;

    五、其他規(guī)范

    JSP 文件命名

    采用完整的英文描述說明 JSP 所完成的功能,盡可能包括一個生動的動詞,第一個字母小寫,

    如: viewMessage.jsp 、editUser.jsp 等。

    六、工程特有命名規(guī)范

    1、持久層

    1、 Hibernate 映射文件及實體

    與數(shù)據(jù)庫表名稱完全對應;
    如: Advertisement.hbm.xml 、 Advertisement.java

    2、數(shù)據(jù)訪問 DAO

    DAO 接口和實現(xiàn)類名稱必須完全符合正常接口和實現(xiàn)類的命名規(guī)則,且最后以 ”DAO” 結(jié)尾

    DAO 內(nèi)的數(shù)據(jù)訪問方法必須足夠抽象的描述出對數(shù)據(jù)庫的基本 CRUD 操作;
    如: ICrossAdDAO( 接口 ) 、 CrossAdDAO( 實現(xiàn)類 )

    3、各種操作數(shù)據(jù)庫的 HQL 配置文件

    HQL 文件的個數(shù)原則上與系統(tǒng)的 Services 層的服務個數(shù)相等,且以服務名稱命名 HQL 文件;

    如: resource.hbm.xml

    2、服務層

    1、服務接口和實現(xiàn)

    服務接口和實現(xiàn)類必須完全符合正常接口和實現(xiàn)類的命名規(guī)則;以工程定義的服務名為主體,

    并統(tǒng)一以 ”Serv” 結(jié)尾
    如: IResourceServ( 服務接口 ) 、 ResourceServ( 接口實現(xiàn)類 )

    2、服務接口方法

    方法名是一個動詞,采用大小寫混合的方式,第一個單詞的首字母小寫,其后單詞的首字母大寫。

    方法名盡可能的描述出該方法的動作行為。

    返回類型為 Boolean 值:用“ is ”或“ has ”來開頭
    得到某數(shù)據(jù): get+ 數(shù)據(jù)描述名詞復數(shù) + 數(shù)據(jù)類型;
    得到所有數(shù)據(jù): get+All+ 數(shù)據(jù)描述名詞復數(shù) + 數(shù)據(jù)類型;
    通過 XXX 得到 / 查詢某數(shù)據(jù): get/query+ 數(shù)據(jù)描述名詞復數(shù) + 數(shù)據(jù)類型 +By+ 條件;
    添加某數(shù)據(jù): save/add+ 數(shù)據(jù)描述名詞 ()
    更新某數(shù)據(jù): save/update+ 數(shù)據(jù)描述名詞;
    刪除某數(shù)據(jù): delete/remove+ 數(shù)據(jù)描述名詞;

    3、業(yè)務對象

    業(yè)務名稱 +BO

    4、查詢參數(shù)對象

    凡是繼承 Abst***QuerySpec 的查詢參數(shù)類全部滿足以下規(guī)則:
    Query+ 所要查詢的數(shù)據(jù)描述名詞 +Spec
    作為參數(shù)傳入時,參數(shù)名必須為:所要查詢的數(shù)據(jù)描述名詞 +Spec
    如: QueryProgramSpec

    3、MVC 層

    1、Action 控制層

    Action 類名:功能模塊名稱 +Action ;
    Actoin 方法名稱盡可能的描述出頁面遷移的去向
    如: LoginAction( 登錄用 action) , toWelcome( 轉(zhuǎn)向歡迎頁的 action 方法 )

    2、資源文件

    系統(tǒng)全局資源文件: globalMessages_+ 字符編碼類型 +.properties
    功能模塊內(nèi)部的資源文件: package.properties

    4、Spring 配置文件

    1、Action 相關配置文件
    文件目錄: WebRoot/WEB-INF/spring/action/ 功能模塊名稱 +_ApplicationContext.xml

    2、Services 相關配置文件

    文件目錄: WebRoot/WEB-INF/spring/services/Services_ApplicationContext.xml

    3、全局性配置文件

    文件目錄: WebRoot/WEB-INF/spring/工程名+_ApplicationContext.xml

    5、JSP 文件

    采用完整的英文描述說明 JSP 所完成的功能,盡可能包括一個生動的動詞,第一個字母小寫,
    如: viewMessage.jsp 、editUser.jsp 等。
    posted @ 2007-10-12 10:08 zongxing 閱讀(255) | 評論 (0)編輯 收藏

    2007年10月10日 #

        最近做的這個房產(chǎn)信息系統(tǒng)把我折騰壞了,那么多的字段,而且頁面也是我一個人做。這兩天急得不得了,因為字段太多,處理起來相當?shù)穆闊?,頁面也得一點一點的做,css也得自己寫,我感覺已經(jīng)控制不了這套東西了。
        實在不行了,我就想給老板說,我做不成了。還好,先給公司里一個老程序員說了下,想問下他會怎么處理。
        還是有經(jīng)驗呀,他告訴我不要急于去處理頁面,先把功能實現(xiàn),如果字段多,可以先寫一部分字段,功能實現(xiàn)之后可以再添加字段,這樣程序的進度就不會被擋住,大概的都實現(xiàn)了,再去調(diào)整那些細節(jié)。
        還是這方法好,這樣,就不會感覺不易于控制。還能先讓老板看到做出的效果。好辦法!

    posted @ 2007-10-10 10:39 zongxing 閱讀(190) | 評論 (0)編輯 收藏

    2007年10月9日 #

    一:數(shù)據(jù)操作層:
    一個DAO,對數(shù)據(jù)表進行基本的操作,所有方法都是通用的。
    二:業(yè)務層:
    1:調(diào)用DAO中的方法,業(yè)務層與數(shù)據(jù)操作層之間是獨立的,可以撤換任何業(yè)務層中的程序,而不對系統(tǒng)產(chǎn)生影響。
    2:我曾經(jīng)在寫業(yè)務方法時,把add()方法的輸入?yún)?shù)寫為FormBean了,這樣就會出現(xiàn)問題,將來要是不用struts了,頁面里的數(shù)據(jù)怎么傳到業(yè)務方法里呢。所以業(yè)務方法里還是不能與FormBean有任何關系的。應該把數(shù)據(jù)傳輸bean作為業(yè)務方法的對象。
    3:Struts里的action負責formbean的裝卸,add數(shù)據(jù)時,把頁面里的字段取過來,然后轉(zhuǎn)換成vo,再調(diào)用業(yè)務方法add(vo);
    當要在頁面展示數(shù)據(jù)時, 調(diào)用業(yè)務方法getSomething()把數(shù)據(jù)從數(shù)據(jù)庫中取出,轉(zhuǎn)換成formbean,然后在頁面里展示。

    三:表示層:
    posted @ 2007-10-09 21:13 zongxing 閱讀(1894) | 評論 (1)編輯 收藏

    僅列出標題  下一頁
    主站蜘蛛池模板: 一级毛片无遮挡免费全部| 国产亚洲精品美女| 在线综合亚洲中文精品| 亚洲а∨精品天堂在线| 五月天婷婷免费视频| 久久国产精品一区免费下载| 亚洲免费综合色在线视频| 国产免费黄色大片| 精品亚洲永久免费精品| 亚洲国产日韩女人aaaaaa毛片在线| 亚洲JLZZJLZZ少妇| 韩国免费A级毛片久久| 18女人腿打开无遮掩免费| 青草草在线视频永久免费| 久久精品夜色噜噜亚洲A∨| 亚洲av无码国产精品夜色午夜| 国产.亚洲.欧洲在线| 人妻18毛片a级毛片免费看| 久久久精品2019免费观看| 日韩免费视频播播| 亚洲精品中文字幕乱码三区| jlzzjlzz亚洲jzjzjz| 四虎一区二区成人免费影院网址| 一级毛片免费播放| 国产三级免费电影| 亚洲精品综合久久中文字幕| 老司机免费午夜精品视频| 99久久免费观看| 亚洲A丁香五香天堂网| 亚洲国产夜色在线观看| 一道本在线免费视频| aⅴ免费在线观看| 国产啪亚洲国产精品无码| 亚洲AV一二三区成人影片| 精品97国产免费人成视频 | 日本黄网站动漫视频免费| 又粗又硬又黄又爽的免费视频 | 久久久亚洲欧洲日产国码二区| 精品亚洲av无码一区二区柚蜜| 久久精品私人影院免费看| 波多野结衣一区二区免费视频|