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

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

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

    zongxing

    沒(méi)有邁不過(guò)去的坎!

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

    2009年10月8日 #

    在網(wǎng)上找了一下,基本找不到。只有到諾基亞論壇里去找啦。

    第五版sdk下地址
    http://www.forum.nokia.com/info/sw.nokia.com/id/ec866fab-4b76-49f6-b5a5-af0631419e9c/S60_All_in_One_SDKs.html

    給大家說(shuō)下怎么一步一步的找到下載地址:
    1:進(jìn)入諾基亞論壇:
    http://www.forum.nokia.com
    2:菜單:I want to =>Develop Applications=>Java™ technology=>Download all-in-one S60 SDKs
    這樣一步一步就進(jìn)入了sdk的下載頁(yè)面。

    ps:每一步的鏈接地址都是比較隱蔽的,不太好找,這就要看你有沒(méi)有耐心了。

    3:在Download version for: 下拉框里選擇你想要的版本就可以啦。

    有三個(gè)版本選擇:Nokia N97 (569 MB),5th Edition (622 MB),3rd Edition, FP2 v1.1 (455 MB)

    ps:想找資料,還是推薦直接去官方去找。

    第一、二、三版下載地址
    http://www.forum.nokia.com/info/sw.nokia.com/id/4a7149a5-95a5-4726-913a-3c6f21eb65a5/S60-SDK-0616-3.0-mr.html
    posted @ 2009-10-08 11:36 zongxing 閱讀(3690) | 評(píng)論 (3)編輯 收藏

    2009年7月29日 #

    項(xiàng)目里很多復(fù)雜的業(yè)務(wù)都是由存儲(chǔ)過(guò)程或比較復(fù)雜的sql語(yǔ)句來(lái)實(shí)現(xiàn)的。
    1:酬金計(jì)算
    2:重要數(shù)據(jù)導(dǎo)入
    3:報(bào)表
    這些對(duì)oracle要非常熟悉才能做到游刃有余。而oracle數(shù)據(jù)庫(kù)恰恰是我的軟肋。
    復(fù)雜sql不熟悉,常用函數(shù)不熟悉,存儲(chǔ)過(guò)程不熟悉,等等,很多很多都不熟悉。
    oracle之路……很長(zhǎng)很長(zhǎng)……

    應(yīng)對(duì)之道
    老是不熟悉也不是辦法呀!還要是有解決的辦法。
    1:常用的sql,各種關(guān)聯(lián)用法。
    2:常用函數(shù)
    3:技巧
    4:存儲(chǔ)過(guò)程,包,觸發(fā)器等用戶

    posted @ 2009-07-29 21:44 zongxing 閱讀(151) | 評(píng)論 (0)編輯 收藏

    2009年1月15日 #

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

    2007年12月22日 #

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

          在配置mysql時(shí)(剛安裝時(shí)首先要配置,以后的時(shí)間也可以配置),打開(kāi)配置界面,一路下一步,到了要選擇編碼的地方,選中那一項(xiàng),然后選擇默認(rèn)編碼。

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

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

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

    2007年11月1日 #

    1:兩者在一起使用,就不能再用原來(lái)hibernate生成數(shù)據(jù)庫(kù)表的方法了。
    主外鍵關(guān)系一般是生不成的。
    2:powerdesign12生成的mysql5.0版本的數(shù)據(jù)庫(kù)表也是有問(wèn)題的。主外鍵關(guān)系都是生不成的,還要手工修改方可(還沒(méi)時(shí)間修改)。

    posted @ 2007-11-01 17:09 zongxing 閱讀(220) | 評(píng)論 (0)編輯 收藏

    2007年10月30日 #

    Java運(yùn)行原理:
    Java有一個(gè)垃圾回收機(jī)制,總是在內(nèi)存剩余大概5%才啟動(dòng),因?yàn)樗袛鄼?quán)限最高,它運(yùn)行,其他全部停止,因此,我們不希望垃圾回收機(jī)制頻繁啟動(dòng),那么就要控制內(nèi)存不要觸碰剩余5%底線。

    而在普通JavaBeans系統(tǒng)中,每一次客戶端請(qǐng)求訪問(wèn)時(shí),系統(tǒng)總是new一個(gè)javabeans或Java Class,如果并發(fā)訪問(wèn)量很大,比如并發(fā)10人或100人,再加上你的系統(tǒng)復(fù)雜,有很多JavaBeans,假設(shè)有30個(gè),那么這下子100個(gè)并發(fā)請(qǐng)求來(lái),就有3000個(gè)Java對(duì)象創(chuàng)建,然后下一批有來(lái)一次100個(gè)請(qǐng)求,這象潮水一樣。

    每次請(qǐng)求產(chǎn)生的3000個(gè)對(duì)象會(huì)繼續(xù)占用內(nèi)存,不會(huì)被垃圾回收機(jī)制回收,因?yàn)槔厥諜C(jī)制只有等到內(nèi)存剩余5%才啟動(dòng),這樣,你的內(nèi)存無(wú)論多大,取決于訪問(wèn)量,總會(huì)被耗光,最后垃圾回收出來(lái)收拾殘局,你的業(yè)務(wù)系統(tǒng)被暫停甚至緩慢。

    所以,這里需要有資源控制,將內(nèi)存能夠控制住,不要被無(wú)限消耗,最后導(dǎo)致垃圾回收啟動(dòng),造成系統(tǒng)好像死機(jī)。


    控制資源就是使用Pool或Cache來(lái)控制,Spring/JdonFramework下可自行加入; EJB已經(jīng)默認(rèn)加入了。

    這也是我一直反對(duì)使用Jsp+JavaBeans來(lái)寫復(fù)雜或大訪問(wèn)量的系統(tǒng),至于如何控制服務(wù)器資源,只有數(shù)據(jù)庫(kù)連接池是不夠的,因?yàn)锽ean才是真正的資源消耗重點(diǎn)。

    如果你理論上屬于無(wú)知,又狂熱追求Spring這些新玩藝(當(dāng)初),那么,即使你使用Spring,性能還是和Jsp+JavaBeans一樣,在大訪問(wèn)量情況下經(jīng)常死機(jī),因?yàn)镾pring里面需要手工配置Pool或Cache這些資源控制機(jī)制。
    如果說(shuō)Java比C方便,因?yàn)閷?duì)象使用之后不需要清理,那么有了Ioc/DI依賴注射以后,Java中對(duì)象使用之前也不需要?jiǎng)?chuàng)建了。
    spring 的好處,不用創(chuàng)建javabean對(duì)象了。
    posted @ 2007-10-30 13:53 zongxing 閱讀(703) | 評(píng)論 (0)編輯 收藏

    2007年10月20日 #

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

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

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

    我想,接下來(lái)我們應(yīng)該去了解一下 tomcat 是如何處理jsp和servlet請(qǐng)求的。

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

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

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

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

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

    中期:講解了被tomcat抓住后,又是怎么在各個(gè)容器里面穿梭, 最后到達(dá)最后的處理地點(diǎn)。

    末期:講解到達(dá)最后的處理地點(diǎn)后,又是怎么具體處理的。

    2、 前期 Request的born.

        在這里我先簡(jiǎn)單講一下request這個(gè)東西。

         我們先看著這個(gè)URL:http://localhost:8080/web/login.jsp 它是動(dòng)用了8080端口來(lái)進(jìn)行socket通訊的。

         我們知道, 通過(guò)

           InputStream in = socket.getInputStream() 和

           OutputStream out = socket.getOutputStream()

         就可以實(shí)現(xiàn)消息的來(lái)來(lái)往往了。

         但是如果把Stream給應(yīng)用層看,顯然操作起來(lái)不方便。

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

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

        

         但是這樣又有其他問(wèn)題了??? Request這個(gè)對(duì)象是把socket封裝起來(lái)了, 但是他提供的又東西太多了。

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

     

        ok, 讓我們?cè)?tomcat的頂層容器 - StandardEngin 的invoke()方法這里設(shè)置一個(gè)斷點(diǎn), 然后訪問(wèn)

        http://localhost:8080/web/login.jsp, 我們來(lái)看看在前期都會(huì)路過(guò)哪些地方:

           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. 啟動(dòng)線程池.

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

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

        5. 使用Http11Processor來(lái)處理request

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

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

    3. 中期。 在各個(gè)容器間的穿梭。

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

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

        管道里面又有好幾個(gè)閥門(valve), 專門用來(lái)過(guò)濾Request用的。

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

        讓我們來(lái)想象一下:

         當(dāng)一個(gè)Request進(jìn)入一個(gè)容器后, 它就在管道里面流動(dòng),波羅~ 波羅~ 波羅~ 地穿過(guò)各個(gè)閥門。在流到最后一個(gè)閥門的時(shí)候,吧唧~ 那個(gè)該死的閥門就把它扔給了子容器。 然后又開(kāi)始 波羅~ 波羅~ 波羅~ ... 吧唧~.... 波羅~ 波羅~ 波羅~ ....吧唧~....

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

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

    3.1 StandardEngin 的pipeline里面放的是:StandardEnginValve

    在這里,VALVE做了三件事:

    1.   驗(yàn)證傳遞過(guò)來(lái)的request是不是httpservletRequest.

    2    驗(yàn)證傳遞過(guò)來(lái)的 request 是否攜帶了host header信息.

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

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

    3.2 StandardHost 的pipline里面放的是: StandardHostValve

    1.   驗(yàn)證傳遞過(guò)來(lái)的request是不是httpservletRequest.

    2.   根據(jù)Request來(lái)確定哪個(gè)Context來(lái)處理。

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

    這里web就是Context羅!

    3.   既然確定了是哪個(gè)Context了,那么就應(yīng)該把那個(gè)Context的classloader付給當(dāng)前線程了。

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

       這樣request就只看得見(jiàn)指定的context下面的classes啊, jar啊這些,而看不見(jiàn)tomcat本身的類,什么Engin啊, Valve啊。不然還得了??!

    4. 既然request到了這里了,看來(lái)用戶是準(zhǔn)備訪問(wèn)web這個(gè)web app了,咋們得更新一下這個(gè)用戶的session不是! Ok , 就由manager更新一下用戶的session信息

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

    6. Context處理完畢了,把classloader還回來(lái)。

    3.3 StandardContext 的pipline里面放的是: StandardContextValve

    1.   驗(yàn)證傳遞過(guò)來(lái)的request是不是httpservletRequest.

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

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

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

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

    StandardWrapper

    之前對(duì)Wrapper沒(méi)有做過(guò)講解,其實(shí)它是這樣一種東西。

    我們?cè)谔幚鞷equest的時(shí)候,可以分成3種。

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

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

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

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

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

    具體情況是這么滴:

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

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

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

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

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

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

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

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

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

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

       ok! 完畢。

    注1: StandardWrapper 里面的閥門是最后一道關(guān)口了。 如果這個(gè)閥門欲意把request交給StandardWrapper 的子容器處理。 對(duì)不起, 在設(shè)計(jì)考慮的時(shí)候, Wrapper就被考慮成最末的一個(gè)容器, 壓根兒就不會(huì)給Wrapper添加子容器的機(jī)會(huì)! 如果硬是要調(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) | 評(píng)論 (0)編輯 收藏

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

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

    因?yàn)楣ぷ鞯脑?,我改寫了Tomcat的一些代碼,所以我粗略的研究了一下Tomcat3.3和Tomcat4.0的源碼,深深地被這個(gè)開(kāi)放軟件的設(shè)計(jì)和實(shí)現(xiàn)吸引,感覺(jué)到這個(gè)軟件中有許多值得我們學(xué)習(xí)和借鑒的地方。我把自己的理解介紹給大家算是拋磚引玉,不足和偏差還望大家批評(píng)指正。下面就來(lái)讓我們看看從Tomcat那里我們可以得到什么。

    2.從Tomcat中學(xué)習(xí)設(shè)計(jì)模式

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

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

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

    Chain of responsibility(責(zé)任鏈)

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

    Interceptor(監(jiān)聽(tīng)器)

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

    Strategy(策略)

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

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

    圖一 簡(jiǎn)化的類圖
    圖一 簡(jiǎn)化的類圖

     

    關(guān)于類圖的簡(jiǎn)單說(shuō)明:

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

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

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

    PoolTcpEndPoint:處理實(shí)際的tcp連接。它有一個(gè)連接池對(duì)象ThreadPool和運(yùn)行在獨(dú)立線程中的應(yīng)用邏輯類TcpWorkThread。

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

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

    圖二 簡(jiǎn)化的時(shí)序圖
    圖二 簡(jiǎn)化的時(shí)序圖

     

    關(guān)于時(shí)序圖中需要說(shuō)明的地方:

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

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

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

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

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

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

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

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

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

    面向組件編程時(shí)設(shè)計(jì)組件是關(guān)鍵,從Tomcat4.0中可以看出主要使用了以下的設(shè)計(jì)模式:

    Separation of Concerns(SOC)

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

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

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

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

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

     

    對(duì)模擬的Lifecycle接口組件的類圖的說(shuō)明

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

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

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

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

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

    通過(guò)以上對(duì)Tomcat的簡(jiǎn)單的介紹,我們可以看出,作為一個(gè)開(kāi)放源碼的項(xiàng)目,Tomcat不但為我們提供了一個(gè)應(yīng)用的平臺(tái),同時(shí)它還為我們提供了一個(gè)學(xué)習(xí)和研究設(shè)計(jì)模式、面向組件技術(shù)等理論的實(shí)踐平臺(tái)。

    參考資料

    Tomcat3.3源碼和Tomcat4.0源碼http://jakarta.apache.org/tomcat/index.html
    《設(shè)計(jì)模式》

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

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

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

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

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

    2007年10月13日 #

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

    我使用OO技術(shù)第一次設(shè)計(jì)軟件的時(shí)候,犯了一個(gè)設(shè)計(jì)者所能犯的所有錯(cuò)誤。那是一個(gè)來(lái)自國(guó)外的外包項(xiàng)目,外方負(fù)責(zé)功能設(shè)計(jì),我們公司負(fù)責(zé)程序設(shè)計(jì)、編碼和測(cè)試。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    繳費(fèi)的時(shí)候,在繳費(fèi)信息上添加一條記錄,記錄下繳費(fèi)金額。然后查找有沒(méi)有欠費(fèi)的賬單,如果有就做銷賬。沖抵欠費(fèi)的金額記錄在“欠費(fèi)金額”的位置。如果欠費(fèi)時(shí)間較長(zhǎng),就計(jì)算滯納金,記錄在“滯納金”的位置上。沖銷欠費(fèi)以后,剩余的金額記錄在“預(yù)存款”的位置上。“其他費(fèi)用”這個(gè)位置是預(yù)留的,目前沒(méi)有作用。

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

    銷賬的流程就這樣融合在繳費(fèi)和出賬的過(guò)程中。

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

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

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

    本次話費(fèi):這是賬單的費(fèi)用,還是最后一次完全銷賬時(shí)的繳費(fèi),應(yīng)該用哪一個(gè)呢?

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

    帶著問(wèn)題,開(kāi)發(fā)者去問(wèn)電信公司的職員。開(kāi)發(fā)者把他們?cè)O(shè)計(jì)的界面指點(diǎn)給用戶看,向他說(shuō)明了自己的設(shè)計(jì)的這幾個(gè)流程,同時(shí)也說(shuō)出了自己的疑問(wèn)。用戶沒(méi)有直接回答這個(gè)疑問(wèn),卻提出了另一個(gè)問(wèn)題:

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

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

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

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

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

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

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

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

     

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

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

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

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

    Entry是一個(gè)枚舉類型,代碼如下:

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

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

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

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

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

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

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

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

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

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

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

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

    public class Fee
    {
        
    private float valuee = 0;
        
        
    public string GetName()
        {
            
    //返回費(fèi)用的名稱
        }
        
        
    public bool HasChildren()
        {
            
    //該費(fèi)用類型是否有子類型
        }
        
        
    public Fee[] GetChildren()
        {
            
    //返回該費(fèi)用類型的子類型
        }
        
        
    public float GetValue()
        {
            
    //返回費(fèi)用的金額
            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)在開(kāi)發(fā)者設(shè)計(jì)出了這么一堆類,構(gòu)成軟件系統(tǒng)的主要零件就這么制造出來(lái)了。下面要做的就是把這些零件串在一起,去實(shí)現(xiàn)需要的功能。OO設(shè)計(jì)的重點(diǎn)就是要找到這些零件。就像是設(shè)計(jì)一輛汽車,僅僅知道油路、電路、傳動(dòng)的各項(xiàng)流程是不夠的,重要的是知道造一輛汽車需要先制造哪些零件。要想正確的把這些零件設(shè)計(jì)出來(lái)不是一件容易的事情,很少有開(kāi)發(fā)者一開(kāi)始就了解系統(tǒng)的需求,設(shè)計(jì)出合理的對(duì)象關(guān)系。根本的原因在于領(lǐng)域知識(shí)的貧乏,開(kāi)發(fā)者和用戶之間也缺乏必要的交流。很多人在軟件開(kāi)發(fā)的過(guò)程中才漸漸意識(shí)到原來(lái)的設(shè)計(jì)中存在一些難受的地方,然后探索下去,才知道了正確的方式,這就是業(yè)務(wù)知識(shí)的一個(gè)突破。不幸的是,當(dāng)這個(gè)突破到來(lái)的時(shí)候,程序員經(jīng)常是已經(jīng)忙得熱火朝天,快把代碼寫完了。要把一切恢復(fù)到正常的軌道上,需要勇氣,時(shí)間,有遠(yuǎn)見(jiàn)的領(lǐng)導(dǎo)者,也需要有運(yùn)氣。

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

    僅列出標(biāo)題  下一頁(yè)
    主站蜘蛛池模板: 亚洲韩国精品无码一区二区三区| 国产成A人亚洲精V品无码性色| 亚洲精品狼友在线播放| 亚洲一区二区三区91| 曰批免费视频播放免费| 18级成人毛片免费观看| 亚洲VA综合VA国产产VA中| 亚洲综合激情九月婷婷| 精品视频免费在线| 中文字幕在线观看免费视频| 免费欧洲美女牲交视频| 久久久无码精品亚洲日韩按摩 | 亚洲av乱码一区二区三区按摩 | 久久久久亚洲AV成人片| 黄色网址在线免费观看| 亚洲高清视频免费| 中文字幕亚洲综合久久男男| 亚洲综合激情五月丁香六月 | 国产一区二区三区免费观在线| 精品国产无限资源免费观看| 中文亚洲成a人片在线观看| 亚洲一区二区无码偷拍| 无码日韩精品一区二区三区免费| 亚洲国产精品成人AV无码久久综合影院 | 男女男精品网站免费观看| 免费能直接在线观看黄的视频 | 国内精品免费久久影院| 国产男女猛烈无遮挡免费视频网站 | 老司机精品免费视频| 女人18一级毛片免费观看| 亚洲宅男永久在线| 国产精品免费在线播放| 国产成人aaa在线视频免费观看 | 免费国产黄网站在线看| 成人免费视频观看无遮挡| 亚洲AV福利天堂一区二区三| 人妻仑乱A级毛片免费看| 国产精品免费看久久久无码| 亚洲18在线天美| 最近中文字幕高清免费中文字幕mv| 亚洲午夜久久久久妓女影院|