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

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

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

    Java Votary

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

    #

    解決JAVA服務(wù)器性能問題

    通過負(fù)載測試和分析來改善JAVA服務(wù)器應(yīng)用的性能

    作者:Ivan Small

    譯者:xMatrix





    版權(quán)聲明:任何獲得Matrix授權(quán)的網(wǎng)站,轉(zhuǎn)載時(shí)請務(wù)必以超鏈接形式標(biāo)明文章原始出處和作者信息及本聲明
    作者:Ivan Small;xMatrix
    原文地址:http://www.javaworld.com/javaworld/jw-02-2005/jw-0207-server-p3.html
    中文地址:http://www.matrix.org.cn/resource/article/43/43998_server_capacity.html
    關(guān)鍵詞: server capacity

    摘要

    改善JAVA服務(wù)器的性能需要模擬負(fù)載下的服務(wù)器。創(chuàng)建一個(gè)模擬環(huán)境、搜集數(shù)據(jù)并且分析結(jié)果可能是對許多開發(fā)人員的挑戰(zhàn)。這篇文章中的示例介紹了JAVA服務(wù)器性能分析的概念和工具。作者使用這個(gè)示例來研究超額請求次數(shù)下內(nèi)存使用和同步竟?fàn)幍挠绊憽?br>作者Ivan Small

    項(xiàng)目團(tuán)隊(duì)已經(jīng)很熟悉如何組織一些具體的任務(wù)并完成他們。簡單的性能問題很容易由一個(gè)開發(fā)人員分離并解決。然而大的性能問題,通常在系統(tǒng)處于高負(fù)載情況下發(fā)生,就不是這么簡單能處理的了。這些問題需要一個(gè)獨(dú)立的測試環(huán)境、一個(gè)模擬的負(fù)載,并且需要仔細(xì)地分析和跟蹤。

    在這篇文章中,我使用比較通用的工具和設(shè)備創(chuàng)建了一個(gè)測試環(huán)境。我會(huì)專注于兩個(gè)性能問題,內(nèi)存和同步,他們很難用簡單的分析得到。通過一個(gè)具體的例子,我希望比較容易地解決復(fù)雜的性能問題而且可以提供處理問題過程中的細(xì)節(jié)。

    改善服務(wù)器的性能

    服 務(wù)器的性能改善是依賴于數(shù)據(jù)的。沒有可靠的數(shù)據(jù)基礎(chǔ)而更改應(yīng)用或環(huán)境會(huì)導(dǎo)致更差的結(jié)果。分析器提供有用的JAVA服務(wù)器應(yīng)用信息,但由于從單用戶負(fù)載下的 數(shù)據(jù)與多用戶負(fù)載下得到的數(shù)據(jù)是完全不同的,這導(dǎo)致分析器的數(shù)據(jù)并不精確。在開發(fā)階段使用分析器來優(yōu)化應(yīng)用的性能是一個(gè)好的方式,但在高負(fù)載下的應(yīng)用分析 可以取到更好的效果。


    在負(fù)載下分析服務(wù)器應(yīng)用的性能需要一些基本的元素:
            1、可控的進(jìn)行應(yīng)用負(fù)載測試的環(huán)境。
            2、可控的人造負(fù)載使得應(yīng)用滿負(fù)荷運(yùn)行。
            3、來自監(jiān)視器、應(yīng)用和負(fù)載測試工具自身的數(shù)據(jù)搜集。
            4、性能改變的跟蹤。

    不要低估最后一個(gè)需求(性能跟蹤)的重要性因?yàn)槿绻荒芨櫺阅苣憔筒荒軐?shí)際的管理項(xiàng)目。性能上10-20%的改善對單用戶環(huán)境來說并沒有什么不同,但對支持人員來說就不一樣了。20%的改善是非常大的,而且通過跟蹤性能的改善,你可以提供重要的反饋和持續(xù)跟蹤。
    雖然性能跟蹤很重要,但有時(shí)為了使后續(xù)的測試更加精確而不得不拋棄先前的測試結(jié)果。在性能測試中,改善負(fù)載測試的精確性可能需要修改模擬環(huán)境,而這些變化是必須的,通過變化前后的負(fù)載測試你可以觀察到其中的轉(zhuǎn)變。


    可控的環(huán)境        
    可 控的環(huán)境最少也需要兩臺(tái)獨(dú)立的機(jī)器和第三臺(tái)控制的機(jī)器。其中一臺(tái)用來生成負(fù)載,另一臺(tái)作為控制機(jī)與前一臺(tái)建立測試應(yīng)用并接受反饋,第三臺(tái)機(jī)器運(yùn)行應(yīng)用。此 外,負(fù)載和應(yīng)用機(jī)器間的網(wǎng)絡(luò)應(yīng)該與局域網(wǎng)分開。控制機(jī)接受運(yùn)行應(yīng)用機(jī)器的反饋如操作系統(tǒng)、硬件使用率、應(yīng)用(特別是VM)的狀態(tài)。

    負(fù)載模擬
    最精確的模擬通常用實(shí)際的用戶數(shù)據(jù)和WEB服務(wù)器端的訪問日志。如果你還沒有實(shí)際布署或者缺少實(shí)際的用戶數(shù)據(jù),你可以通過構(gòu)造類似的場景或詢問銷售和產(chǎn)品管理團(tuán)隊(duì)或做一些有依據(jù)的猜想。協(xié)調(diào)負(fù)載測試和實(shí)際用戶體驗(yàn)是一個(gè)持續(xù)的過程。

    在 模擬中一些用戶場景是必須的。如在一個(gè)通用地址薄應(yīng)用中,你應(yīng)該區(qū)分更新和查詢操作。在我的測試應(yīng)用中GrinderServlet類只有一個(gè)場景。單用 戶連接10次訪問這個(gè)servlet(在每一次訪問間有一段暫停)。雖然這個(gè)應(yīng)用很小,我認(rèn)為這可以重復(fù)一些常見的東西。用戶通常不會(huì)連接給服務(wù)器請求而 沒有間斷。如果沒有間斷,我們可能不能得到更精確的實(shí)際用戶上限。

    串行10個(gè)請求的另一個(gè)原因是實(shí)際應(yīng)用中不會(huì)只有一個(gè)HTTP請求。單一而又分離的請求可以影響環(huán)境中的許多因素。對Tomcat來說,會(huì)為每一個(gè)請求創(chuàng)建一個(gè)會(huì)話,并且HTTP協(xié)議允許不同的請求重用連接。我會(huì)修改一下負(fù)載測試來避免混洧。

    GrinderServlet類不會(huì)執(zhí)行任何排序操作,但這個(gè)需求在大部分應(yīng)用中都很普通。在這些應(yīng)用中,你需要?jiǎng)?chuàng)建模擬的數(shù)據(jù)集并且用他們來構(gòu)造相關(guān)用例的負(fù)載測試。

    例如,如果用例涉及到用戶登錄一個(gè)WEB應(yīng)用,從可能的用戶列表中選取隨機(jī)的用戶會(huì)只使用一個(gè)用戶更精確。否則,你可能不經(jīng)意地使用了系統(tǒng)緩存或其他的優(yōu)化或一些微妙的東西,而這會(huì)使得結(jié)果不正確。

    負(fù)載測試軟件
    負(fù) 載測試軟件可以構(gòu)造測試場景并且對服務(wù)進(jìn)行負(fù)載測試。我會(huì)在下面的示例中使用OpenSTA測試軟件。這軟件簡單易學(xué),結(jié)果也很容易導(dǎo)出,并且支持參數(shù)化 腳本,還可以監(jiān)視信息的變化,他的主要缺點(diǎn)是基于Windows,但在這兒不是個(gè)問題。當(dāng)然還有很多可選項(xiàng)如Apache的JMeter和Mercury 的LoadRunner。

    The GrinderServlet

    列表1中顯示了GrinderServlet類,列表2中顯示了Grinder類
    Listing 1

    package pub.capart;

    import java.io.*;
    import java.util.*;
    import javax.servlet.*;
    import javax.servlet.http.*;

    public class GrindServlet extends HttpServlet {
       protected void doGet(HttpServletRequest req, HttpServletResponse res)
             throws ServletException, IOException {
          Grinderv1 grinder = Grinderv1.getGrinder();
          long t1 = System.currentTimeMillis();
          grinder.grindCPU(13);
          long t2 = System.currentTimeMillis();

          PrintWriter pw = res.getWriter();
          pw.print("<html>\n< body> \n");
          pw.print("Grind Time = "+(t2-t1));
          pw.print("< body> \n< /html> \n");
       }
    }


    Listing 2

    package pub.capart;

    /**
    * This is a simple class designed to simulate an application consuming
    * CPU, memory, and contending for a synchronization lock.
    */
    public class Grinderv1 {
       private static Grinderv1 singleton = new Grinderv1();
       private static final String randstr =
          "this is just a random string that I'm going to add up many many times";

       public static Grinderv1 getGrinder() {
          return singleton;
       }
       public synchronized void grindCPU(int level) {
          StringBuffer sb = new StringBuffer();
          String s = randstr;
          for (int i=0;i<level;++i) {
             sb.append(s);
             s = getReverse(sb.toString());
          }
       }
       public String getReverse(String s) {
          StringBuffer sb = new StringBuffer(s);
          sb = sb.reverse();
          return sb.toString();
       }
    }


    類 很簡單,但他們會(huì)產(chǎn)生兩個(gè)很常見的問題。咋一看瓶頸可能由grindCPU()方法的同步修飾符引起,但實(shí)際上內(nèi)存消耗才是真正的問題所在。如圖1,我的 第一個(gè)負(fù)載測試顯示了常見的負(fù)載變化。在這里負(fù)載變化很重要因?yàn)槟阏谀M一個(gè)高的負(fù)載。這種熱身的方式也更精確因?yàn)楸苊饬薐SP編譯引起的問題。我通常 習(xí)慣于在進(jìn)行負(fù)載測試前先進(jìn)行單用戶模擬。

    image
    Figure 1        

    我 在這篇文章中會(huì)使用相同的容量小結(jié)圖。在執(zhí)行負(fù)載測試時(shí)還有更多的可用信息,但這里只用了有用的部分。最上面的面板包含每秒完成的請求數(shù)和請求時(shí)間信息。 第二個(gè)面板包含活動(dòng)用戶數(shù)和失敗率,我將超時(shí)、不正確的服務(wù)器應(yīng)答和長于5秒的請求認(rèn)為是失敗的。第三個(gè)面板包含JVM內(nèi)存統(tǒng)計(jì)和CPU使用率。CPU值 是所有處理器的用戶時(shí)間的平均值,這里所有的測試機(jī)器都是雙CPU的。內(nèi)存統(tǒng)計(jì)圖包含垃圾回收表和每秒垃圾回收數(shù)。

    圖1中兩個(gè)最明顯的數(shù)據(jù)是50%的CPU使用率和大量內(nèi)存使用和釋放。從列表2中可以看出這個(gè)原因。同步修飾符導(dǎo)致所有進(jìn)程串行處理,就好像只用了一個(gè)CPU,而算法導(dǎo)致大量內(nèi)存消耗在局部變量上。

    通過CPU是個(gè)受限的資源,如果在這個(gè)測試中我可以完全利用到兩個(gè)CPU的話就可以提高一倍的性能。垃圾回收器運(yùn)行得如此頻繁以致于不能忽略。在測試中每秒釋放的內(nèi)存達(dá)到100M,很顯然這是個(gè)限制因素。失敗數(shù)這么大明顯這個(gè)應(yīng)用是不可用的。

    監(jiān)視

    在生成合理的用戶負(fù)載后,監(jiān)視工具需要收集進(jìn)程的運(yùn)行狀況。在我的測試環(huán)境中可以收集到各種有用的信息:

    1、        所有計(jì)算機(jī)、網(wǎng)絡(luò)設(shè)備
    2、        等等的使用率
    3、        JVM的統(tǒng)計(jì)數(shù)據(jù)。
    4、        個(gè)別JAVA方法所花費(fèi)的時(shí)間。
    5、        數(shù)據(jù)庫性能信息,6、        包括SQL查詢的統(tǒng)計(jì)。
    7、        其他應(yīng)用相關(guān)的信息

    當(dāng) 然這些監(jiān)視也會(huì)影響負(fù)載測試,但如果影響比較小也可以忽略。基本上如果我們想獲取所有上面的信息,肯定會(huì)影響測試的性能。但如果不是一次獲取所有信息還是 有可能保證負(fù)載測試的有效性。僅對特定的方法設(shè)置定時(shí)器,僅獲取低負(fù)載的硬件信息和低頻率地獲取樣例數(shù)據(jù)。當(dāng)然不加載監(jiān)視器來做測試是最好的,然后和加載 監(jiān)視器的測試來做比較。雖然有時(shí)候侵入式監(jiān)視是個(gè)好主意,但就不可能有監(jiān)視結(jié)果了。


    獲取所有監(jiān)視數(shù)據(jù)到一個(gè)中央控制器來做分析是 最好的,但使用動(dòng)態(tài)運(yùn)行時(shí)工具也可以提供有用的信息。例如,命令行工具如PS、TOP、VMSTAT可以提供UNIX機(jī)器的信息;性能監(jiān)視器工具可以提供 WINDOWS機(jī)器的信息;而TeamQuest, BMC Patrol, SGI's Performance Co-Pilot, and ISM's PerfMan這樣的工具會(huì)在所有的測試環(huán)境中的機(jī)器安裝代理并且將需要的信息傳回中央控制機(jī),這樣就可以提供文本或可視化的信息。在本文中,我使用開源 的Performance Co-Pilot作為測試統(tǒng)計(jì)的工具。我發(fā)現(xiàn)他對測試環(huán)境的影響最小,并且以相對直接的方式來提供數(shù)據(jù)。

    JAVA 分析器提供很多信息,但通常對負(fù)載測試來說影響太大而沒有太多的用處。工具甚至可以讓你在負(fù)載服務(wù)器上做一些分析,但這也很容易便測試無效。在這些測試 中,我激活了詳細(xì)的垃圾收集器來收集內(nèi)存信息。我也使用jconsole 和jstack工具(包含在J2SE 1.5中)來檢查高負(fù)載下的VM。我沒有保留這些測試用例中負(fù)載測試的結(jié)果因?yàn)槲艺J(rèn)為這些數(shù)據(jù)不是很正確。


    同步瓶頸

    在 診斷服務(wù)器問題時(shí)線程的信息是非常有用的,特別是對同步之類的問題。jstack工具可以連接到運(yùn)行的進(jìn)程并且保存每一個(gè)線程的堆棧信息。在UNIX系統(tǒng) 可以用信號量3來保存線程的堆棧信息,在WINDOWS系統(tǒng)的控制臺(tái)中可以用Ctrl-Break。在第一項(xiàng)測試中,jstack指出許多線程在 grindCPU()方法中被阻塞。

    你可以已經(jīng)注意到列表2中g(shù)rindCPU()方法的同步修飾符實(shí)際上并不必須。我在后一項(xiàng)測試中刪除了他,如圖2顯示

    image
    Figure 2        

    在圖2中,你會(huì)注意到性能下降了。雖然我使用了更多的CPU,但吞吐量和失敗數(shù)都更差了。雖然垃圾回收周期變了,但每秒依然需要回收100M。顯然我們還沒有找到主要的瓶頸。
    非 竟?fàn)幍耐较鄬τ诤唵蔚暮瘮?shù)調(diào)用還是很費(fèi)時(shí)的。竟?fàn)幮缘耐骄透M(fèi)時(shí)了,因?yàn)槌藘?nèi)存需要同步外,VM還需要維護(hù)等待的線程。在這種狀況下,這些代價(jià)實(shí)際 上要小于內(nèi)存瓶頸。實(shí)際上,通過消除了同步瓶頸,VM內(nèi)存系統(tǒng)承擔(dān)了更多的壓力最后導(dǎo)致更差的吞吐量,即使我使用了更多的CPU。顯然最好的方式是從最大 的瓶頸開始,但有時(shí)這也不是很容易確定的。當(dāng)然,確保VM的內(nèi)存處理足夠正常也是一個(gè)好的開始方向。

    內(nèi)存瓶頸

    現(xiàn)在我會(huì)首先也定位內(nèi)存問題。列表3是GrinderServlet的重構(gòu)版本,使用了StringBuffer實(shí)例。圖3顯示了測試結(jié)果。

    Listing 3

    package pub.capart;

    /**
    * This is a simple class designed to simulate an application consuming
    * CPU, memory, and contending for a synchronization lock.
    */
    public class Grinderv2 {
       private static Grinderv2 singleton = new Grinderv2();
       private static final String randstr =
          "this is just a random string that I'm going to add up many many times";
       private StringBuffer sbuf = new StringBuffer();
       private StringBuffer sbufrev = new StringBuffer();

       public static Grinderv2 getGrinder() {
          return singleton;
       }
       public synchronized void grindCPU(int level) {
          sbufrev.setLength(0);
          sbufrev.append(randstr);
          sbuf.setLength(0);
          for (int i=0;i<level;++i) {
             sbuf.append(sbufrev);
             reverse();
          }
          return sbuf.toString();
       }

       public String getReverse(String s) {
          StringBuffer sb = new StringBuffer(s);
          sb = sb.reverse();
          return sb.toString();
       }
    }


    image
    Figure 3        

    通 常重用StringBuffer并不是一個(gè)好主意,但這里我只是為了重現(xiàn)一些常見的問題,而不量提供解決方案。內(nèi)存數(shù)據(jù)已經(jīng)從圖上消失了因?yàn)闇y試中沒有垃 圾回收器運(yùn)行。吞吐量戲劇性的增加而CPU使用率又回到了50%。列表3不只是優(yōu)化了內(nèi)存,但我認(rèn)為主要了改善了過度的內(nèi)存消耗。

    檢視同步瓶頸

    列表4另一個(gè)GrinderServlet類的重構(gòu)版本,實(shí)現(xiàn)了一個(gè)小的資源池。圖4顯示了測試結(jié)果。
    Listing 4

    package pub.capart;

    /**

    * This is just a dummy class designed to simulate a process consuming
    * CPU, memory, and contending for a synchronization lock.
    */
    public class Grinderv3 {
       private static Grinderv3 grinders[];
       private static int grinderRoundRobin = 0;
       private static final String randstr =
          "this is just a random string that I'm going to add up many many times";
       private StringBuffer sbuf = new StringBuffer();
       private StringBuffer sbufrev = new StringBuffer();

       static {
          grinders = new Grinderv3[10];
          for (int i=0;i<grinders.length;++i) {
             grinders[i] = new Grinderv3();
          }
       }
       public synchronized static Grinderv3 getGrinder() {
          Grinderv3 g = grinders[grinderRoundRobin];
          grinderRoundRobin = (grinderRoundRobin +1) % grinders.length;
          return g;
       }
       public synchronized void grindCPU(int level) {
          sbufrev.setLength(0);
          sbufrev.append(randstr);
          sbuf.setLength(0);
          for (int i=0;i<level;++i) {
             sbuf.append(sbufrev);
             reverse();
          }
          return sbuf.toString();
       }
       public String getReverse(String s) {
          StringBuffer sb = new StringBuffer(s);
          sb = sb.reverse();
          return sb.toString();
       }
    }
      


    image
    Figure 4        


    吞吐量有一定的增加,而且使用更少的CPU資源。竟?fàn)幒头蔷範(fàn)幮酝蕉际琴M(fèi)時(shí)的,但通常最大的同步消耗是減少了系統(tǒng)的可伸縮性。我的負(fù)載測試不再滿足系統(tǒng)的需求了,因此我增加了虛擬的用戶數(shù),如圖5 所示。

    image
    Figure 5        


    在圖5 中吞吐量在負(fù)載達(dá)到飽和時(shí)下降了一些然后在負(fù)載減少時(shí)又提高了。此外注意到測試使得CPU使用率達(dá)到100%,這意味著測試超過了系統(tǒng)的最佳吞吐量。負(fù)載測試的一個(gè)產(chǎn)出是性能計(jì)劃,當(dāng)應(yīng)用的負(fù)載超過他的容量時(shí)會(huì)產(chǎn)生更低的吞吐量。


    水平可伸縮性

    水平伸縮允許更大的性能,但并不一定是費(fèi)用相關(guān)的。運(yùn)行在多個(gè)服務(wù)器上的應(yīng)用通常比較運(yùn)行在單個(gè)VM上的應(yīng)用復(fù)雜。但水平伸縮支持在性能上的最大增加。

    圖6是我的最后一項(xiàng)測試的結(jié)果。我已經(jīng)在三臺(tái)基本一致的機(jī)器上使用了負(fù)載平衡,只是在內(nèi)存和CPU速度上稍有不同。總的吞吐量要高于三倍的單機(jī)結(jié)果,而且CPU從來沒有完全利用。在圖6中我只顯示了一臺(tái)機(jī)器上的CPU結(jié)果,其他的是一樣的。

    image
    Figure 6        


    小結(jié)

    我曾經(jīng)花了9個(gè)月來布署一個(gè)復(fù)雜的JAVA應(yīng)用,但卻沒有時(shí)間來做性能計(jì)劃。但差勁的性能使得用戶合約幾乎中止。開發(fā)人員使用分析器花了很長時(shí)間找到幾個(gè)小問題但沒有解決根本的瓶頸,而且被后續(xù)的問題完全迷惑了。最后通過負(fù)載測試找到解決方法,但你可以想到其中的處境。

    又一次我碰得更難的問題,應(yīng)用只能達(dá)到所預(yù)期性能的1/100。但通過前期檢測到的問題和認(rèn)識(shí)到負(fù)載測試的必要性,這個(gè)問題很快被解決了。負(fù)載測試相對于整個(gè)軟件開發(fā)的花費(fèi)并不多,但其所歸避的風(fēng)險(xiǎn)就高多了。

    關(guān)于作者
    Ivan Small擁有14年的軟件開發(fā)經(jīng)驗(yàn)。他在LBNL從開發(fā)Supernovae Cosmology Project開始他的職業(yè)生涯。這個(gè)項(xiàng)目是導(dǎo)致反重力和無限擴(kuò)展宇宙理論被發(fā)現(xiàn)的兩個(gè)項(xiàng)目之一。他從此工作于數(shù)據(jù)挖掘和企業(yè)級JAVA應(yīng)用。現(xiàn)在他是 nnovative Interfaces公司的首席軟件工程師。

    資源
    ·javaworld.com:javaworld.com
    ·Matrix-Java開發(fā)者社區(qū):http://www.matrix.org.cn/
    ·JAVA性能調(diào)優(yōu)第二版:http://www.amazon.com/exec/obidos/ASIN/0596003773/javaworld
    ·并發(fā)編程技術(shù):JAVA并發(fā)編程第二版:http://www.amazon.com/exec/obidos/ASIN/0201310090/javaworld
    ·JAVA網(wǎng)站分析:JAVA網(wǎng)站的性能分析:http://www.amazon.com/exec/obidos/ASIN/0201844540/javaworld
    ·JAVA性能:高性能JAVA平臺(tái)計(jì)算:http://www.amazon.com/exec/obidos/ASIN/0130161640/javaworld
    ·JAVA2性能和術(shù)語指南:http://www.amazon.com/exec/obidos/ASIN/0130142603/javaworld
    ·BEA WebLogic服務(wù)器性能調(diào)優(yōu),包含有用的一般信息:BEA WebLogic服務(wù)器上J2EE應(yīng)用性能測試:http://www.amazon.com/exec/obidos/ASIN/1904284000/javaworld
    ·JAVA性能調(diào)優(yōu):http://www.javaperformancetuning.com
    ·過度的JAVA同步:“輕量級線程”:http://www-106.ibm.com/developerworks/java/library/j-threads1.html
    ·負(fù)載和性能測試工具:http://www.softwareqatest.com/qatweb1.html#LOAD
    posted @ 2005-12-02 21:59 Dion 閱讀(5781) | 評論 (0)編輯 收藏

    Quartz - Quartz 1 - CronTriggers Tutorial

    Some of the content in this tutorial is taken from the Quartz 1.4.2 javadocs for CronTrigger.

    Introduction

    cron is a UNIX tool that has been around for a long time, so its scheduling capabilities are powerful and proven. The CronTrigger class is based on the scheduling capabilities of cron.

    CronTrigger uses "cron expressions", which are able to create firing schedules such as: "At 8:00am every Monday through Friday" or "At 1:30am every last Friday of the month".

    Cron expressions are powerful, but can be pretty confusing. This tutorial aims to take some of the mystery out of creating a cron expression, giving users a resource which they can visit before having to ask in a forum or mailing list.

    Format

    A cron expression is a string comprised of 6 or 7 fields separated by white space. Fields can contain any of the allowed values, along with various combinations of the allowed special characters for that field. The fields are as follows:

    Field Name Mandatory? Allowed Values Allowed Special Characters
    Seconds YES 0-59 , - * /
    Minutes YES 0-59 , - * /
    Hours YES 0-23 , - * /
    Day of month YES 1-31 , - * ? / L W C
    Month YES 1-12 or JAN-DEC , - * /
    Day of week YES 1-7 or SUN-SAT , - * ? / L C #
    Year NO empty, 1970-2099 , - * /

    So cron expressions can be as simple as this: * * * * ? *
    or more complex, like this: 0 0/5 14,18,3-39,52 ? JAN,MAR,SEP MON-FRI 2002-2010

    Special characters

    • * ("all values") - used to select all values within a field. For example, "*" in the minute field means "every minute".
    • ? ("no specific value") - useful when you need to specify something in one of the two fields in which the character is allowed, but not the other. For example, if I want my trigger to fire on a particular day of the month (say, the 10th), but don't care what day of the week that happens to be, I would put "10" in the day-of-month field, and "?" in the day-of-week field. See the examples below for clarification.
    • - - used to specify ranges. For example, "10-12" in the hour field means "the hours 10, 11 and 12".
    • , - used to specify additional values. For example, "MON,WED,FRI" in the day-of-week field means "the days Monday, Wednesday, and Friday".
    • / - used to specify increments. For example, "0/15" in the seconds field means "the seconds 0, 15, 30, and 45". And "5/15" in the seconds field means "the seconds 5, 20, 35, and 50". You can also specify '/' after the '*' character - in this case '*' is equivalent to having '0' before the '/'. '1/3' in the day-of-month field means "fire every 3 days starting on the first day of the month".
    • L ("last") - has different meaning in each of the two fields in which it is allowed. For example, the value "L" in the day-of-month field means "the last day of the month" - day 31 for January, day 28 for February on non-leap years. If used in the day-of-week field by itself, it simply means "7" or "SAT". But if used in the day-of-week field after another value, it means "the last xxx day of the month" - for example "6L" means "the last friday of the month". When using the 'L' option, it is important not to specify lists, or ranges of values, as you'll get confusing results.
    • W ("weekday") - used to specify the weekday (Monday-Friday) nearest the given day. As an example, if you were to specify "15W" as the value for the day-of-month field, the meaning is: "the nearest weekday to the 15th of the month". So if the 15th is a Saturday, the trigger will fire on Friday the 14th. If the 15th is a Sunday, the trigger will fire on Monday the 16th. If the 15th is a Tuesday, then it will fire on Tuesday the 15th. However if you specify "1W" as the value for day-of-month, and the 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not 'jump' over the boundary of a month's days. The 'W' character can only be specified when the day-of-month is a single day, not a range or list of days.

    The 'L' and 'W' characters can also be combined in the day-of-month field to yield 'LW', which translates to "last weekday of the month".

    • # - used to specify "the nth" XXX day of the month. For example, the value of "6#3" in the day-of-week field means "the third Friday of the month" (day 6 = Friday and "#3" = the 3rd one in the month). Other examples: "2#1" = the first Monday of the month and "4#5" = the fifth Wednesday of the month. Note that if you specify "#5" and there is not 5 of the given day-of-week in the month, then no firing will occur that month.
    • C ("calendar") - this means values are calculated against the associated calendar, if any. If no calendar is associated, then it is equivalent to having an all-inclusive calendar. A value of "5C" in the day-of-month field means "the first day included by the calendar on or after the 5th". A value of "1C" in the day-of-week field means "the first day included by the calendar on or after Sunday".

    The legal characters and the names of months and days of the week are not case sensitive. MON is the same as mon.

    Examples

    Here are some full examples:

    Expression Meaning
    0 0 12 * * ? Fire at 12pm (noon) every day
    0 15 10 ? * * Fire at 10:15am every day
    0 15 10 * * ? Fire at 10:15am every day
    0 15 10 * * ? * Fire at 10:15am every day
    0 15 10 * * ? 2005 Fire at 10:15am every day during the year 2005
    0 * 14 * * ? Fire every minute starting at 2pm and ending at 2:59pm, every day
    0 0/5 14 * * ? Fire every 5 minutes starting at 2pm and ending at 2:55pm, every day
    0 0/5 14,18 * * ? Fire every 5 minutes starting at 2pm and ending at 2:55pm, AND fire every 5 minutes starting at 6pm and ending at 6:55pm, every day
    0 0-5 14 * * ? Fire every minute starting at 2pm and ending at 2:05pm, every day
    0 10,44 14 ? 3 WED Fire at 2:10pm and at 2:44pm every Wednesday in the month of March.
    0 15 10 ? * MON-FRI Fire at 10:15am every Monday, Tuesday, Wednesday, Thursday and Friday
    0 15 10 15 * ? Fire at 10:15am on the 15th day of every month
    0 15 10 L * ? Fire at 10:15am on the last day of every month
    0 15 10 ? * 6L Fire at 10:15am on the last Friday of every month
    0 15 10 ? * 6L Fire at 10:15am on the last Friday of every month
    0 15 10 ? * 6L 2002-2005 Fire at 10:15am on every last friday of every month during the years 2002, 2003, 2004 and 2005
    0 15 10 ? * 6#3 Fire at 10:15am on the third Friday of every month
    0 0 12 1/5 * ? Fire at 12pm (noon) every 5 days every month, starting on the first day of the month.
    0 11 11 11 11 ? Fire every November 11th at 11:11am.

    Pay attention to the effects of '?' and '*' in the day-of-week and day-of-month fields!

    posted @ 2005-11-25 23:13 Dion 閱讀(1231) | 評論 (1)編輯 收藏

    架構(gòu)設(shè)計(jì)師與 SOA , 第 2 部分

    developerWorks
    文檔選項(xiàng)
    將此頁作為電子郵件發(fā)送

    將此頁作為電子郵件發(fā)送

    未顯示需要 JavaScript 的文檔選項(xiàng)


    對此頁的評價(jià)

    幫助我們改進(jìn)這些內(nèi)容


    王 強(qiáng) , IBM中國軟件開發(fā)實(shí)驗(yàn)室 - SOA Design Center

    2005 年 11 月 24 日

    本系列的第 1 部分 介紹了有關(guān)架構(gòu)設(shè)計(jì)師以及 SOA 架構(gòu)的知識(shí),分析了 SOA 架構(gòu)師在設(shè)計(jì) SOA 系統(tǒng)架構(gòu)時(shí)有哪些應(yīng)該特別注意的地方。本文將延續(xù)第一部分的內(nèi)容,向您介紹了 SOA 為企業(yè)級架構(gòu)設(shè)計(jì)帶來的影響,以及在構(gòu)建基于 SOA 架構(gòu)的企業(yè)系統(tǒng)時(shí)應(yīng)該怎樣保證所構(gòu)建的系統(tǒng)架構(gòu)能夠滿足系統(tǒng)中不同的服務(wù)級別需求。

    1. SOA 為企業(yè)級架構(gòu)設(shè)計(jì)帶來的影響

    1.1 SOA 的特點(diǎn)及其使用范圍

    SOA 既不是一種語言,也不是一種具體的技術(shù),它是一種新的軟件系統(tǒng)架構(gòu)模型。 SOA 最主要的應(yīng)用場合在于解決在Internet環(huán)境下的不同商業(yè)應(yīng)用之間的業(yè)務(wù)集成問題。Internet環(huán)境區(qū)別于Intranet環(huán)境的幾個(gè)特點(diǎn)主要是:

    (a)大量異構(gòu)系統(tǒng)并存,不同計(jì)算機(jī)硬件工作方式不同,操作系統(tǒng)不同、編程語言也不同;

    (b)大量、頻繁的數(shù)據(jù)傳輸?shù)乃俣热匀幌鄬^緩慢并且不穩(wěn)定;

    (c)無法完成服務(wù)(service)的版本升級,甚至根本就無法知道互聯(lián)網(wǎng)上有哪些機(jī)器直接或者間接的使用某個(gè)服務(wù)。

    SOA 架構(gòu)具有一些典型特性,主要包括松耦合性,位置透明性以及協(xié)議無關(guān)性。松耦合性要求 SOA 架構(gòu)中的不同服務(wù)之間應(yīng)該保持一種松耦合的關(guān)系,也就是應(yīng)該保持一種相對獨(dú)立無依賴的關(guān)系;位置透明性要求 SOA 系統(tǒng)中的所有服務(wù)對于他們的調(diào)用者來說都是位置透明的,也就是說每個(gè)服務(wù)的調(diào)用者只需要知道他們調(diào)用的是哪一個(gè)服務(wù),但并不需要知道所調(diào)用服務(wù)的物理位置 在哪里;而協(xié)議無關(guān)性要求每一個(gè)服務(wù)都可以通過不同的協(xié)議來調(diào)用。通過這些 SOA 架構(gòu)所具有的特性我們可以看到,SOA 架構(gòu)的出現(xiàn)為企業(yè)系統(tǒng)架構(gòu)提供了更加靈活的構(gòu)建方式,如果企業(yè)架構(gòu)設(shè)計(jì)師基于 SOA 來構(gòu)建系統(tǒng)架構(gòu),就可以從底層架構(gòu)的級別來保證整個(gè)系統(tǒng)的松耦合性以及靈活性,這都為未來企業(yè)業(yè)務(wù)邏輯的擴(kuò)展打好了基礎(chǔ)。

    1.2 SOA 架構(gòu)的分層模型

    接下來簡要介紹一下 SOA 系統(tǒng)中的分層模型,整個(gè) SOA 架構(gòu)的分層模型如圖2所示。




    在 SOA 系統(tǒng)中不同的功能模塊可以被分為7層:第一層就是系統(tǒng)已經(jīng)存在的程序資源,例如ERP或者CRM系統(tǒng)等。第2層就是組件層,在這一層中我們用不同的組件把 底層系統(tǒng)的功能封裝起來。第3層就是 SOA 系統(tǒng)中最重要的服務(wù)層,在這層中我們要用底層功能組件來構(gòu)建我們所需要的不同功能的服務(wù)。總的來說,SOA 中的服務(wù)可以被映射成具體系統(tǒng)中的任何功能模塊,但是從功能性方面可以大致劃分為以下三種類型:(1)商業(yè)服務(wù)(business service) 或者是商業(yè)過程(business process)。這一類的服務(wù)是一個(gè)企業(yè)可以暴露給外部用戶或者合作伙伴使用的服務(wù)。比如說提交貸款申請,用戶信用檢查,貸款信用查詢。(2)商業(yè)功能 服務(wù)(business function service), 這類服務(wù)會(huì)完成一些具體的商業(yè)操作,也會(huì)被更上層的商業(yè)服務(wù)調(diào)用,不過大多數(shù)情況下這類服務(wù)不會(huì)暴露給外部用戶直接調(diào)用,比如說檢索用戶帳戶信息,存儲(chǔ)用 戶信息等。(3)技術(shù)功能服務(wù)(technical function service),這類服務(wù)主要完成一些底層的技術(shù)功能,比如說日志服務(wù)以及安全服務(wù)等。在服務(wù)層之上的第4層就是商業(yè)流程層,在這一層中我們利用已經(jīng)封 裝好的各種服務(wù)來構(gòu)建商業(yè)系統(tǒng)中的商業(yè)流程。在商業(yè)流程層之上的就是第5層表示層了,我們利用表示層來向用戶提供用戶接口服務(wù),這一層可以用基于 portal的系統(tǒng)來構(gòu)建。以上這5層都需要有一個(gè)集成的環(huán)境來支持它們的運(yùn)行,第6層中的企業(yè)服務(wù)總線(ESB)提供了這個(gè)功能。第7層主要為整個(gè) SOA 系統(tǒng)提供一些輔助的功能,例如服務(wù)質(zhì)量管理,安全管理這一類的輔助功能。



    回頁首


    2. SOA 架構(gòu)中的非功能性服務(wù)級別(service-level)需求

    除 了系統(tǒng)的業(yè)務(wù)需求,架構(gòu)設(shè)計(jì)師還必須要保證構(gòu)建出來的系統(tǒng)架構(gòu)能夠滿足系統(tǒng)中的非功能性服務(wù)級別(service-level)需求以及服務(wù)質(zhì)量 (QoS)方面的需求。在項(xiàng)目初始及細(xì)化階段,架構(gòu)設(shè)計(jì)師應(yīng)該與系統(tǒng)所有涉及方(Stakeholders)一起,為每一個(gè)服務(wù)級別需求定義其相關(guān)的衡量 標(biāo)準(zhǔn)。構(gòu)建出的系統(tǒng)架構(gòu)必須要能滿足以下幾方面的服務(wù)水準(zhǔn)要求:性能、可升級性、可靠性、可用性、可擴(kuò)展性、可維護(hù)性、易管理性以及安全性。架構(gòu)設(shè)計(jì)師在 設(shè)計(jì)架構(gòu)過程中需要平衡所有的這些服務(wù)級別需求。例如,如果服務(wù)級別需求中最重要的是系統(tǒng)性能,架構(gòu)設(shè)計(jì)師很有可能不得不在一定程度上犧牲系統(tǒng)的可維護(hù)性 及可擴(kuò)展性,以確保滿足系統(tǒng)性能上的要求。隨著互聯(lián)網(wǎng)的發(fā)展,新構(gòu)建的系統(tǒng)對于服務(wù)級別需求也變得日益重要,現(xiàn)在基于互聯(lián)網(wǎng)的企業(yè)系統(tǒng)的用戶已經(jīng)不僅僅局 限于是本企業(yè)的雇員,企業(yè)的外部客戶也會(huì)成為企業(yè)系統(tǒng)的主要用戶。

    架構(gòu)設(shè)計(jì)師的職責(zé)之一就是要盡可能地為提高系統(tǒng)設(shè)計(jì)人員和系統(tǒng)開發(fā)人 員的工作效率考慮。在構(gòu)建整個(gè)企業(yè)系統(tǒng)架構(gòu)的過程中,需要充分重視各種服務(wù)級別需求,從而避免在系統(tǒng)開發(fā)和運(yùn)行的時(shí)候出現(xiàn)重大問題。一個(gè)企業(yè)級系統(tǒng)中的服 務(wù)級別需求往往是十分錯(cuò)綜復(fù)雜的, SOA 架構(gòu)設(shè)計(jì)師需要憑借豐富的專業(yè)經(jīng)驗(yàn)和扎實(shí)的理論知識(shí)來分離和抽象系統(tǒng)中不同的服務(wù)級別需求,圖3展示了這種分析的過程。


    圖3
    圖3

    經(jīng)過 SOA 架構(gòu)設(shè)計(jì)師分析和抽象的服務(wù)級別需求主要分為以下幾類:

    • 性能是指系統(tǒng)提供的服務(wù)要滿足一定的性能衡量標(biāo)準(zhǔn),這些標(biāo)準(zhǔn)可能包括系統(tǒng)反應(yīng)時(shí)間以及處理交易量的能力等;
    • 可升級性是指當(dāng)系統(tǒng)負(fù)荷加大時(shí),能夠確保所需的服務(wù)質(zhì)量,而不需要更改整個(gè)系統(tǒng)的架構(gòu);
    • 可靠性是指確保各應(yīng)用及其相關(guān)的所有交易的完整性和一致性的能力;
    • 可用性是指一個(gè)系統(tǒng)應(yīng)確保一項(xiàng)服務(wù)或者資源永遠(yuǎn)都可以被訪問到;
    • 可擴(kuò)展性是指在不影響現(xiàn)有系統(tǒng)功能的基礎(chǔ)上,為系統(tǒng)填加新的功能或修改現(xiàn)有功能的能力;
    • 可維護(hù)性是指在不影響系統(tǒng)其他部分的情況下修正現(xiàn)有功能中問題或缺陷,并對整個(gè)系統(tǒng)進(jìn)行維護(hù)的能力;
    • 可管理性是指管理系統(tǒng)以確保系統(tǒng)的可升級性、可靠性、可用性、性能和安全性的能力;
    • 安全性是指確保系統(tǒng)安全不會(huì)被危及的能力。

    1) 性能

    我 們通常可以根據(jù)每個(gè)用戶訪問的系統(tǒng)響應(yīng)時(shí)間來衡量系統(tǒng)的整體性能;另外,我們也可以通過系統(tǒng)能夠處理的交易量(每秒)來衡量系統(tǒng)的性能。對于架構(gòu)設(shè)計(jì)師來 說,無論采取哪種衡量系統(tǒng)性能的方法來構(gòu)建系統(tǒng)架構(gòu),這些對于性能的考慮對系統(tǒng)設(shè)計(jì)開發(fā)人員來說都應(yīng)該是透明的,也就是說對于系統(tǒng)整體架構(gòu)性能的考慮應(yīng)該 是架構(gòu)設(shè)計(jì)師的工作,而不是系統(tǒng)設(shè)計(jì)開發(fā)人員應(yīng)該關(guān)注的事情。在較傳統(tǒng)的基于EJB或者XML-RPC的分布式計(jì)算模型中,它們的服務(wù)提供都是通過函數(shù)調(diào) 用的方式進(jìn)行的,一個(gè)功能的完成往往需要通過客戶端和服務(wù)器來回很多次的遠(yuǎn)程函數(shù)調(diào)用才能完成。在Intranet的環(huán)境下,這些調(diào)用給系統(tǒng)的響應(yīng)速度和 穩(wěn)定性帶來的影響都可以忽略不計(jì),但如果我們在基于 SOA 的架構(gòu)中使用了很多Web Service來作為服務(wù)提供點(diǎn)的話,我們就需要考慮性能的影響,尤其是在Internet環(huán)境下,這些往往是決定整個(gè)系統(tǒng)是否能正常工作的一個(gè)關(guān)鍵決定 因素。因此在基于 SOA 的系統(tǒng)中,推薦采用大數(shù)據(jù)量低頻率訪問模式,也就是以大數(shù)據(jù)量的方式一次性進(jìn)行信息交換。這樣做可以在一定程度上提高系統(tǒng)的整體性能。

    2) 可升級性

    可 升級性是指當(dāng)系統(tǒng)負(fù)荷加大時(shí),仍能夠確保所需的服務(wù)質(zhì)量,而不需要更改整個(gè)系統(tǒng)的架構(gòu)。當(dāng)基于 SOA 的系統(tǒng)中負(fù)荷增大時(shí),如果系統(tǒng)的響應(yīng)時(shí)間仍能夠在可接受的限度內(nèi),那么我們就可以認(rèn)為這個(gè)系統(tǒng)是具有可升級性的。要想理解可升級性,我們必須首先了解系統(tǒng) 容量或系統(tǒng)的承受能力,也就是一個(gè)系統(tǒng)在保證正常運(yùn)行質(zhì)量的同時(shí),所能夠處理的最大進(jìn)程數(shù)量或所能支持的最大用戶數(shù)量。如果系統(tǒng)運(yùn)轉(zhuǎn)時(shí)已經(jīng)不能在可接受時(shí) 間范圍內(nèi)反應(yīng),那么這個(gè)系統(tǒng)已經(jīng)到達(dá)了它的最大可升級狀態(tài)。要想升級已達(dá)到最大負(fù)載能力的系統(tǒng),你必須增加新的硬件。新添加的硬件可以以垂直或水平的方式 加入。垂直升級包括為現(xiàn)在的機(jī)器增加處理器、內(nèi)存或硬盤。水平升級包括在環(huán)境中添置新的機(jī)器,從而增加系統(tǒng)的整體處理能力。作為一個(gè)系統(tǒng)架構(gòu)設(shè)計(jì)師所設(shè)計(jì) 出來的架構(gòu)必須能夠處理對硬件的垂直或者水平升級。基于 SOA 的系統(tǒng)架構(gòu)可以很好地保證整體系統(tǒng)的可升級性,這主要是因?yàn)橄到y(tǒng)中的功能模塊已經(jīng)被抽象成不同的服務(wù),所有的硬件以及底層平臺(tái)的信息都被屏蔽在服務(wù)之下, 因此不管是對已有系統(tǒng)的水平升級還是垂直升級,都不會(huì)影響到系統(tǒng)整體的架構(gòu)。

    3) 可靠性

    可靠性是指確保各應(yīng) 用及其相關(guān)的所有交易的完整性和一致性的能力。當(dāng)系統(tǒng)負(fù)荷增加時(shí),你的系統(tǒng)必須能夠持續(xù)處理需求訪問,并確保系統(tǒng)能夠象負(fù)荷未增加以前一樣正確地處理各個(gè) 進(jìn)程。可靠性可能會(huì)在一定程度上限制系統(tǒng)的可升級性。如果系統(tǒng)負(fù)荷增加時(shí),不能維持它的可靠性,那么實(shí)際上這個(gè)系統(tǒng)也并不具備可升級性。因此,一個(gè)真正可 升級的系統(tǒng)必須是可靠的系統(tǒng)。在基于 SOA 來構(gòu)建系統(tǒng)架構(gòu)的時(shí)候,可靠性也是必須要著重考慮的問題。要在基于 SOA 架構(gòu)的系統(tǒng)中保證一定的系統(tǒng)可靠性,就必須要首先保證分布在系統(tǒng)中的不同服務(wù)的可靠性。而不同服務(wù)的可靠性一般可以由其部署的應(yīng)用服務(wù)器或Web服務(wù)器來 保證。只有確保每一個(gè) SOA 系統(tǒng)中的服務(wù)都具有較高的可靠性,我們才能保證系統(tǒng)整體的可靠性能夠得以保障。

    4) 可用性

    可 用性是指一個(gè)系統(tǒng)應(yīng)確保一項(xiàng)服務(wù)或者資源應(yīng)該總是可被訪問到的。可靠性可以增加系統(tǒng)的整體可用性,但即使系統(tǒng)部件出錯(cuò),有時(shí)卻并不一定會(huì)影響系統(tǒng)的可用 性。通過在環(huán)境中設(shè)置冗余組件和錯(cuò)誤恢復(fù)機(jī)制,雖然一個(gè)單獨(dú)的組件的錯(cuò)誤會(huì)對系統(tǒng)的可靠性產(chǎn)生不良的影響,但由于系統(tǒng)冗余的存在,使得整個(gè)系統(tǒng)服務(wù)仍然可 用。在基于 SOA 來構(gòu)建系統(tǒng)架構(gòu)的時(shí)候,對于關(guān)鍵性的服務(wù)需要更多地考慮其可用性需求,這可以由兩個(gè)層次的技術(shù)實(shí)現(xiàn)來支持,第一種是利用不同服務(wù)的具體內(nèi)部實(shí)現(xiàn)內(nèi)部所基于 的框架的容錯(cuò)或者冗余機(jī)制來實(shí)現(xiàn)對服務(wù)可用性的支持;第二種是通過UDDI等動(dòng)態(tài)查找匹配方式來支持系統(tǒng)整體的高可用性。在 SOA 架構(gòu)設(shè)計(jì)師構(gòu)建企業(yè)系統(tǒng)架構(gòu)的時(shí)候,應(yīng)該綜合考慮這兩個(gè)方面的內(nèi)容,盡量保證所構(gòu)建的 SOA 系統(tǒng)架構(gòu)中的關(guān)鍵性業(yè)務(wù)能具有較高的可用性。

    5) 可擴(kuò)展性

    可 擴(kuò)展性是指在不影響現(xiàn)有系統(tǒng)功能的基礎(chǔ)上,為系統(tǒng)添加新的功能或修改現(xiàn)有功能的能力。當(dāng)系統(tǒng)剛配置好的時(shí)候,你很難衡量它的可擴(kuò)展性,直到第一次你必須去 擴(kuò)展系統(tǒng)已有功能的時(shí)候,你才能真正去衡量和檢測整個(gè)系統(tǒng)的可擴(kuò)展性。任何一個(gè)架構(gòu)設(shè)計(jì)師在構(gòu)建系統(tǒng)架構(gòu)時(shí),為了確保架構(gòu)設(shè)計(jì)的可擴(kuò)展性,都應(yīng)該考慮下面 幾個(gè)要素:低耦合,界面(interfaces)以及封裝。當(dāng)架構(gòu)設(shè)計(jì)師基于 SOA 來構(gòu)建企業(yè)系統(tǒng)架構(gòu)時(shí),就已經(jīng)隱含地解決了這幾個(gè)可擴(kuò)展性方面的要素。這是因?yàn)?SOA 架構(gòu)中的不同服務(wù)之間本身就保持了一種無依賴的低耦合關(guān)系;服務(wù)本身是通過統(tǒng)一的接口定義(可以是WSDL)語言來描述具體的服務(wù)內(nèi)容,并且很好地封裝了 底層的具體實(shí)現(xiàn)。在這里我們也可以從一個(gè)方面看到基于 SOA 來構(gòu)架企業(yè)系統(tǒng)能為我們帶來的好處。

    6) 可維護(hù)性

    可 維護(hù)性是指在不影響系統(tǒng)其他部分的情況下修改現(xiàn)有系統(tǒng)功能中問題或缺陷的能力。同系統(tǒng)的可擴(kuò)展性相同,當(dāng)系統(tǒng)剛被部署時(shí),你很難判斷一個(gè)系統(tǒng)是否已經(jīng)具備 了很好的可維護(hù)性。當(dāng)創(chuàng)建和設(shè)計(jì)系統(tǒng)架構(gòu)時(shí),要想提高系統(tǒng)的可維護(hù)性,你必須考慮下面幾個(gè)要素:低耦合、模塊性以及系統(tǒng)文檔記錄。在企業(yè)系統(tǒng)可擴(kuò)展性中我 們已經(jīng)提到了 SOA 架構(gòu)能為系統(tǒng)中暴露出來的各個(gè)子功能模塊也就是服務(wù)帶來低耦合性和很好的模塊性。關(guān)于系統(tǒng)文檔紀(jì)錄,除了底層子系統(tǒng)的相關(guān)文檔外,基于 SOA 的系統(tǒng)還會(huì)引用到許多系統(tǒng)外部的由第三方提供的服務(wù),因此如果人力資源準(zhǔn)許的話,應(yīng)該增加專職的文檔管理員來專門負(fù)責(zé)有關(guān)整個(gè)企業(yè)系統(tǒng)所涉及的所有外部服 務(wù)相關(guān)文檔的收集、歸類和整理,這些相關(guān)的文檔可能涉及到第三方服務(wù)的接口(可以是WSDL)、服務(wù)的質(zhì)量和級別、具體性能測試結(jié)果等各種相關(guān)文檔。基于 這些文檔,就可以為 SOA 架構(gòu)設(shè)計(jì)師構(gòu)建企業(yè) SOA 架構(gòu)提供很好的文檔參考和支持。

    7) 可管理性

    可 管理性是指管理系統(tǒng)以確保整個(gè)系統(tǒng)的可升級性、可靠性、可用性、性能和安全性的能力。具有可管理性的系統(tǒng),應(yīng)具備對服務(wù)質(zhì)量需求(QoS)的系統(tǒng)監(jiān)控能 力,通過改變系統(tǒng)的配置從而可以動(dòng)態(tài)地改善服務(wù)質(zhì)量,而不用改變整體系統(tǒng)架構(gòu)。一個(gè)好的系統(tǒng)架構(gòu)必須能夠監(jiān)控整個(gè)系統(tǒng)的運(yùn)行情況并具備動(dòng)態(tài)系統(tǒng)配置管理的 功能。在對復(fù)雜系統(tǒng)進(jìn)行系統(tǒng)架構(gòu)建模時(shí), SOA 架構(gòu)設(shè)計(jì)師應(yīng)該盡量考慮利用將系統(tǒng)整體架構(gòu)構(gòu)建在已有的成熟的底層系統(tǒng)框架(Framework)上。對于 SOA 架構(gòu)設(shè)計(jì)師來說,可以選擇的底層系統(tǒng)框架有很多,可以選用基于MQ, MessageBorker,WebSphere Application Server等產(chǎn)品來構(gòu)建企業(yè)服務(wù)總線(Enterprise Service Bus)以支持企業(yè)的 SOA 系統(tǒng)架構(gòu),也可以選用較新的基于WebSphere Application Server 6中內(nèi)嵌的Sibus來構(gòu)建企業(yè)的ESB以支持 SOA 系統(tǒng)架構(gòu)。具體選擇哪種底層框架來實(shí)施 SOA 系統(tǒng)架構(gòu)要根據(jù)每個(gè)系統(tǒng)各自的特點(diǎn)來決定,但這些底層的框架都已經(jīng)提供了較高的系統(tǒng)可管理性。因此,分析并選擇不同的產(chǎn)品或底層框架來實(shí)現(xiàn)企業(yè)系統(tǒng)架構(gòu)也 是架構(gòu)設(shè)計(jì)師的主要職責(zé)之一。有關(guān)于如何利用已有底層架構(gòu)來構(gòu)建 SOA 系統(tǒng),中國 SOA 設(shè)計(jì)中心已經(jīng)發(fā)表了一系列相關(guān)的文章,大家可以在DeveloperWorks中的 SOA 專欄看到它們。

    8) 安全性

    安 全性是指確保系統(tǒng)安全不會(huì)被危及的能力。目前,安全性應(yīng)該說是最困難的系統(tǒng)質(zhì)量控制點(diǎn)。這是因?yàn)榘踩圆粌H要求確保系統(tǒng)的保密和完整性,而且還要防止影響 可用性的服務(wù)拒絕(Denial-of-Service)攻擊。這就要求當(dāng) SOA 架構(gòu)設(shè)計(jì)師在構(gòu)建一個(gè)架構(gòu)時(shí),應(yīng)該把整體系統(tǒng)架構(gòu)盡可能地分割成各個(gè)子功能模塊,在將一些子功能模塊暴露為外部用戶可見的服務(wù)的時(shí)候,要圍繞各個(gè)子模塊構(gòu) 建各自的安全區(qū),這樣更便于保證整體系統(tǒng)架構(gòu)的安全。如果一個(gè)子模塊受到了安全攻擊,也可以保證其他模塊相對安全。如果企業(yè) SOA 架構(gòu)中的一些服務(wù)是由Web Service實(shí)現(xiàn)的,在考慮這些服務(wù)安全性的時(shí)候也要同時(shí)考慮效率的問題,因?yàn)閃S-Security會(huì)為Web Service帶來一定的執(zhí)行效率損耗。



    回頁首


    3.結(jié)束語

    本 系列兩部分介紹了有關(guān)架構(gòu)設(shè)計(jì)師以及 SOA 架構(gòu)的知識(shí),分析了 SOA 架構(gòu)師在設(shè)計(jì) SOA 系統(tǒng)架構(gòu)時(shí)有哪些應(yīng)該特別注意的地方并在最后簡要介紹了在構(gòu)建基于 SOA 架構(gòu)的企業(yè)系統(tǒng)時(shí)應(yīng)該怎樣保證所構(gòu)建的系統(tǒng)架構(gòu)能夠滿足系統(tǒng)中不同的服務(wù)級別需求。從架構(gòu)設(shè)計(jì)師的角度, SOA 是一種新的設(shè)計(jì)模式,方法學(xué)。因此, SOA 本身涵蓋了很多的內(nèi)容,也觸及到了系統(tǒng)整體架構(gòu)設(shè)計(jì)、實(shí)現(xiàn)、維護(hù)等各個(gè)方面。本文的內(nèi)容只是涉及到了有關(guān)于架構(gòu)方面的一部分內(nèi)容,希望能對廣大的 SOA 系統(tǒng)開發(fā)設(shè)計(jì)人員起到一定的幫助作用。



    回頁首


    參考資料

    1. Patterns: Service-oriented Architecture and Web Services紅皮書,SG24-6303-00,2004 年 4 月,作者M(jìn)ark Endrei 等。
    2. DeveloperWorks 的 SOA 和 Web 服務(wù)專區(qū)有大量專題文章以及關(guān)于開發(fā) Web 服務(wù)應(yīng)用程序的入門級、中級和高級教程。
    3. 有關(guān)隨需應(yīng)變商務(wù)的更多信息,請參閱 Developer resources for an on demand world Web site
    4. Web 服務(wù)項(xiàng)目角色 描述了 Web 服務(wù)開發(fā)項(xiàng)目中所涉及到的各種不同的工作角色,包括各自的目標(biāo),任務(wù)以及彼此之間是如何協(xié)作的。
    5. 面向服務(wù)的分析與設(shè)計(jì)原理一文研究了OOAD、EA 和 BPM 中的適當(dāng)原理。
    6. 企業(yè)服務(wù)總線解決方案剖析,第 1 部分 介紹了面向服務(wù)的體系結(jié)構(gòu)(service-oriented architecture, SOA )和企業(yè)服務(wù)總線(Enterprise Service Bus,ESB)的基本知識(shí),ESB的技術(shù)沿革,以及ESB與 SOA 之間的關(guān)系。


    回頁首


    關(guān)于作者


    王 強(qiáng),IBM軟件工程師,工作在IBM中國軟件開發(fā)實(shí)驗(yàn)室 - SOA Design Center,從事Incubator及 SOA ,Grid項(xiàng)目的工作,對J2EE架構(gòu)、 SOA 架構(gòu)、MDA/MDD以及網(wǎng)格計(jì)算等技術(shù)有較深入的研究。 聯(lián)系方式:wangq@cn.ibm.com

    posted @ 2005-11-25 22:35 Dion 閱讀(736) | 評論 (0)編輯 收藏

    架構(gòu)設(shè)計(jì)師與SOA, 第 1 部分

    developerWorks
    文檔選項(xiàng)
    將此頁作為電子郵件發(fā)送

    將此頁作為電子郵件發(fā)送

    未顯示需要 JavaScript 的文檔選項(xiàng)


    對此頁的評價(jià)

    幫助我們改進(jìn)這些內(nèi)容


    王 強(qiáng) , IBM中國軟件開發(fā)實(shí)驗(yàn)室 - SOA Design Center

    2005 年 11 月 17 日

    SOA(Service-Oriented Architecture),即面向服務(wù)的架構(gòu),這是最近一兩年出現(xiàn)在各種技術(shù)期刊上最多的詞匯了。現(xiàn)在有很多架構(gòu)設(shè)計(jì)師和設(shè)計(jì)開發(fā)人員簡單的把SOA和 Web Services技術(shù)等同起來,認(rèn)為SOA就是Web Service的一種實(shí)現(xiàn)。本質(zhì)上來說,SOA體現(xiàn)的是一種新的系統(tǒng)架構(gòu),SOA的出現(xiàn),將為整個(gè)企業(yè)級軟件架構(gòu)設(shè)計(jì)帶來巨大的影響。本系列兩部分文章將 根據(jù)作者自己的理解來幫助大家分析和了解什么是SOA架構(gòu),SOA將怎樣對企業(yè)系統(tǒng)架構(gòu)設(shè)計(jì)帶來積極的影響,什么是SOA架構(gòu)設(shè)計(jì)師的角色,以及SOA架 構(gòu)師在設(shè)計(jì)SOA系統(tǒng)架構(gòu)時(shí)有哪些應(yīng)該特別注意的地方。

    1. 什么是架構(gòu)?什么是基于SOA的架構(gòu)?

    1.1 什么是架構(gòu)

    從架構(gòu)設(shè)計(jì)師的角度來看,架構(gòu)就是一套構(gòu)建系統(tǒng)的準(zhǔn)則。通過這套準(zhǔn)則,我們可以把一個(gè)復(fù)雜的系統(tǒng)劃分為一套更簡單的子系統(tǒng)的集合,這些子系統(tǒng)之間應(yīng)該保持相互獨(dú)立,并與整個(gè)系統(tǒng)保持一致。而且每一個(gè)子系統(tǒng)還可以繼續(xù)細(xì)分下去,從而構(gòu)成一個(gè)復(fù)雜的企業(yè)級架構(gòu)。

    當(dāng) 一名架構(gòu)設(shè)計(jì)師在構(gòu)建某個(gè)企業(yè)級的軟件系統(tǒng)時(shí),除了要考慮這個(gè)系統(tǒng)的架構(gòu)以及其應(yīng)具有的功能行為以外,還要關(guān)注整個(gè)架構(gòu)的可用性,性能問題,容錯(cuò)能力,可 重用性,安全性,擴(kuò)展性,可管理維護(hù)性,可靠性等各個(gè)相關(guān)方面。有的時(shí)候一名好的架構(gòu)設(shè)計(jì)師甚至還需要考慮所構(gòu)建的系統(tǒng)架構(gòu)是否合乎美學(xué)要求。由此我們可 以看到,我們衡量一個(gè)好的架構(gòu)設(shè)計(jì)并不能只從功能角度出發(fā),還要考慮很多其他的因素,對任何一個(gè)方面的欠缺考慮都有可能為整個(gè)系統(tǒng)的構(gòu)建埋下隱患。

    1.2 什么是基于SOA的架構(gòu)

    SOA 本身就是一種面向企業(yè)級服務(wù)的系統(tǒng)架構(gòu),簡單來說,SOA就是一種進(jìn)行系統(tǒng)開發(fā)的新的體系架構(gòu),在基于SOA架構(gòu)的系統(tǒng)中,具體應(yīng)用程序的功能是由一些松 耦合并且具有統(tǒng)一接口定義方式的組件(也就是service)組合構(gòu)建起來的。因此,基于SOA的架構(gòu)也一定是從企業(yè)的具體需求開始構(gòu)建的。但是,SOA 和其它企業(yè)架構(gòu)的不同之處就在于SOA提供的業(yè)務(wù)靈活性。業(yè)務(wù)靈活性是指企業(yè)能對業(yè)務(wù)變更快速和有效地進(jìn)行響應(yīng)、并且利用業(yè)務(wù)變更來得到競爭優(yōu)勢的能力。 對企業(yè)級架構(gòu)設(shè)計(jì)師來說,創(chuàng)建一個(gè)業(yè)務(wù)靈活的架構(gòu)意味著創(chuàng)建一個(gè)可以滿足當(dāng)前還未知的業(yè)務(wù)需求的IT架構(gòu)。

    利用基于SOA的系統(tǒng)構(gòu)建方法,如圖1中所示的一樣,一個(gè)基于SOA架構(gòu)的系統(tǒng)中的所有的程序功能都被封裝在一些功能模塊中,我們就是利用這些已經(jīng)封裝好的功能模塊組裝構(gòu)建我們所需要的程序或者系統(tǒng),而這些功能模塊就是SOA架構(gòu)中的不同的服務(wù)(services)。


    圖1
    圖1

    因此,SOA架構(gòu)本質(zhì)上來說體現(xiàn)了一種復(fù)合的概念:它不僅為一個(gè)企業(yè)中商業(yè)流程的組織和實(shí)現(xiàn)提供了一種指導(dǎo)模式,同時(shí)也為具體的底層service開發(fā)提供了指導(dǎo)。



    回頁首


    2. SOA架構(gòu)設(shè)計(jì)師的角色

    2.1 SOA架構(gòu)設(shè)計(jì)師應(yīng)該具備什么?

    談 到SOA架構(gòu)設(shè)計(jì)師的角色,我們首先要了解架構(gòu)設(shè)計(jì)師應(yīng)具有的能力。總體上來說,一個(gè)好的架構(gòu)設(shè)計(jì)師不僅應(yīng)該是一個(gè)成熟的,具有實(shí)際經(jīng)驗(yàn)的并具有快速學(xué)習(xí) 能力的人,而且他還應(yīng)該具有良好的管理能力和溝通能力。只有具備了必需的能力,架構(gòu)設(shè)計(jì)師才能在關(guān)鍵的時(shí)刻作出困難的決定,這就是一名架構(gòu)設(shè)計(jì)師應(yīng)該承擔(dān) 的責(zé)任。從角色上來看,SOA 架構(gòu)師不僅會(huì)負(fù)責(zé)端到端的服務(wù)請求者和提供者的設(shè)計(jì),并且會(huì)負(fù)責(zé)對系統(tǒng)中非功能服務(wù)請求的調(diào)研和表述。

    對 于任何一名經(jīng)驗(yàn)豐富的架構(gòu)設(shè)計(jì)師來說,不論他是采用基于傳統(tǒng)的架構(gòu)設(shè)計(jì)方法(基于J2EE架構(gòu)或者.NET架構(gòu))還是采用基于SOA的架構(gòu)設(shè)計(jì)方法來構(gòu)建 一個(gè)企業(yè)級的系統(tǒng)架構(gòu),具有相關(guān)商業(yè)領(lǐng)域的知識(shí)對于架構(gòu)設(shè)計(jì)師來說都是必不可少的,架構(gòu)設(shè)計(jì)師往往可以通過實(shí)際的工作經(jīng)驗(yàn)積累以及接受相關(guān)的專項(xiàng)培訓(xùn)來獲 得這些商業(yè)領(lǐng)域的知識(shí)。除了具有相關(guān)商業(yè)領(lǐng)域的知識(shí)以外,一名合格的架構(gòu)設(shè)計(jì)師必須具有較廣泛的技術(shù)背景,這可能包括軟硬件,通信,安全等各個(gè)方面的知 識(shí)。但這并不是意味著要成為一名架構(gòu)設(shè)計(jì)師就必須熟悉每一門具體技術(shù)的細(xì)節(jié),架構(gòu)設(shè)計(jì)師必須至少能對各種技術(shù)有一個(gè)整體上的了解,能夠熟知每種技術(shù)的特點(diǎn) 以及優(yōu)缺點(diǎn),只有這樣架構(gòu)設(shè)計(jì)師才能在特定的應(yīng)用場景下正確地選擇各種技術(shù)來設(shè)計(jì)企業(yè)整體架構(gòu)。

    2.2 什么是SOA架構(gòu)設(shè)計(jì)師的職責(zé)?

    那 什么是企業(yè)級SOA架構(gòu)設(shè)計(jì)師的具體角色呢?什么是SOA架構(gòu)設(shè)計(jì)師與設(shè)計(jì)和開發(fā)人員之間的差別呢?相信這些都是使大家最容易產(chǎn)生迷惑的問題。舉個(gè)實(shí)際的 例子來說,當(dāng)構(gòu)建一個(gè)基于SOA架構(gòu)的系統(tǒng)的時(shí)候,針對一個(gè)具體的 service,系統(tǒng)設(shè)計(jì)人員主要應(yīng)該關(guān)注的是這個(gè)service能夠?yàn)橥獠坑脩籼峁┦裁礃拥姆?wù),也就是說系統(tǒng)設(shè)計(jì)人員關(guān)注的是這個(gè)service所提 供的功能。而對于SOA架構(gòu)設(shè)計(jì)師來說,他們更關(guān)心的可能是當(dāng)有一千個(gè)用戶同時(shí)調(diào)用這個(gè) service的時(shí)候,什么會(huì)發(fā)生?也就是說架構(gòu)設(shè)計(jì)師關(guān)注的應(yīng)該是一些商業(yè)需求和服務(wù)級別(service-level)需求。所有的架構(gòu)設(shè)計(jì)師的角色 都包含了在構(gòu)建一個(gè)系統(tǒng)的一開始就應(yīng)該盡量減少可能存在的技術(shù)風(fēng)險(xiǎn)。而技術(shù)風(fēng)險(xiǎn)一般指的是一切未知的、未經(jīng)證明的或未經(jīng)測試所帶來的風(fēng)險(xiǎn)。這些風(fēng)險(xiǎn)通常與 服務(wù)級別(service-level)需求相關(guān),偶爾也會(huì)與企業(yè)具體的業(yè)務(wù)需求相關(guān)。無論是哪種類型的風(fēng)險(xiǎn),在項(xiàng)目初期設(shè)計(jì)整體系統(tǒng)架構(gòu)的過程中更易于 發(fā)掘這些風(fēng)險(xiǎn),如果等到架構(gòu)實(shí)施時(shí)再發(fā)覺這些風(fēng)險(xiǎn),那么很可能會(huì)致使大量的開發(fā)人員等在那里,直到這些風(fēng)險(xiǎn)被妥善解決。如果進(jìn)一步的細(xì)化,我們可以看到 SOA架構(gòu)設(shè)計(jì)師的主要任務(wù)包括對整個(gè)系統(tǒng)解決方案輪廓的構(gòu)建,需求分析,對體系結(jié)構(gòu)的整體決策,相關(guān)組件建模,相關(guān)操作建模,系統(tǒng)組件的邏輯和物理布局 設(shè)計(jì)。

    作為SOA架構(gòu)設(shè)計(jì)師必須要能夠領(lǐng)導(dǎo)整個(gè)開發(fā)團(tuán)隊(duì),這樣才能保證設(shè)計(jì)和開發(fā)人員是按照構(gòu)建好的系統(tǒng)架構(gòu)來開發(fā)整個(gè)系統(tǒng)的,這一點(diǎn)十分 的重要。這就要求一名架構(gòu)設(shè)計(jì)師不僅要有很好的技術(shù)洞察力,同時(shí)還要具有一定的項(xiàng)目管理和項(xiàng)目實(shí)施的能力。在系統(tǒng)開發(fā)的過程中,架構(gòu)設(shè)計(jì)師必須要有良好的 溝通和表達(dá)能力,這就體現(xiàn)在由架構(gòu)設(shè)計(jì)師構(gòu)建的系統(tǒng)模型是否具有很好的可讀性和易理解性。如果由架構(gòu)設(shè)計(jì)師構(gòu)造出的系統(tǒng)模型不是很清晰的話,就可能會(huì)影響 設(shè)計(jì)和開發(fā)人員對于整個(gè)系統(tǒng)架構(gòu)的理解。為了避免這種情況的出現(xiàn),定期由架構(gòu)設(shè)計(jì)師主持的開發(fā)團(tuán)隊(duì)內(nèi)部討論是十分重要的。



    回頁首


    3. 構(gòu)建SOA架構(gòu)時(shí)應(yīng)該注意的問題

    3.1 原有系統(tǒng)架構(gòu)中的集成需求

    當(dāng) 架構(gòu)師基于SOA來構(gòu)建一個(gè)企業(yè)級的系統(tǒng)架構(gòu)的時(shí)候,一定要注意對原有系統(tǒng)架構(gòu)中的集成需求進(jìn)行細(xì)致的分析和整理。我們都知道,面向服務(wù)的體系結(jié)構(gòu)是當(dāng)前 及未來應(yīng)用程序系統(tǒng)開發(fā)的重點(diǎn),面向服務(wù)的體系結(jié)構(gòu)本質(zhì)上來說是一種具有特殊性質(zhì)的體系結(jié)構(gòu),它由具有互操作性和位置透明的組件集成構(gòu)建并互連而成。基于 SOA的企業(yè)系統(tǒng)架構(gòu)通常都是在現(xiàn)有系統(tǒng)架構(gòu)投資的基礎(chǔ)上發(fā)展起來的,我們并不需要徹底重新開發(fā)全部的子系統(tǒng);SOA可以通過利用當(dāng)前系統(tǒng)已有的資源(開 發(fā)人員、軟件語言、硬件平臺(tái)、數(shù)據(jù)庫和應(yīng)用程序)來重復(fù)利用系統(tǒng)中現(xiàn)有的系統(tǒng)和資源。SOA是一種可適應(yīng)的、靈活的體系結(jié)構(gòu)類型,基于SOA構(gòu)建的系統(tǒng)架 構(gòu)可以在系統(tǒng)的開發(fā)和維護(hù)中縮短產(chǎn)品上市時(shí)間,因而可以降低企業(yè)系統(tǒng)開發(fā)的成本和風(fēng)險(xiǎn)。因此,當(dāng)SOA架構(gòu)師遇到一個(gè)十分復(fù)雜的企業(yè)系統(tǒng)時(shí),首先考慮的應(yīng) 該是如何重用已有的投資而不是替換遺留系統(tǒng),因?yàn)槿绻紤]到有限的預(yù)算,整體系統(tǒng)替換的成本是十分高昂的。

    當(dāng)SOA架構(gòu)師分析原有系統(tǒng)中的 集成需求的時(shí)候,不應(yīng)該只限定為基于組件構(gòu)建的已有應(yīng)用程序的集成,真正的集成比這要寬泛得多。在分析和評估一個(gè)已有系統(tǒng)體系結(jié)構(gòu)的集成需求時(shí),我們必須 考慮一些更加具體的集成的類型,這主要包括以下幾個(gè)方面:應(yīng)用程序集成的需求,終端用戶界面集成的需求,流程集成的需求以及已有系統(tǒng)信息集成的需求。當(dāng) SOA架構(gòu)師分析和評估現(xiàn)有系統(tǒng)中所有可能的集成需求的時(shí)候,我們可以發(fā)現(xiàn)實(shí)際上所有集成方式在任何種類的企業(yè)中都有一定程度的體現(xiàn)。針對不同的企業(yè)類 型,這些集成方式可能是簡化的,或者沒有明確地進(jìn)行定義的。因而,SOA架構(gòu)師在著手設(shè)計(jì)新的體系結(jié)構(gòu)框架時(shí),必須要全面的考慮所有可能的集成需求。例 如,在一些類型的企業(yè)系統(tǒng)環(huán)境中可能只有很少的數(shù)據(jù)源類型,因此,系統(tǒng)中對消息集成的需求就可能會(huì)很簡單,但在一些特定的系統(tǒng)中,例如航運(yùn)系統(tǒng)中的EDI (Electronic Data Interchange 電子數(shù)據(jù)交換)系統(tǒng),會(huì)有大量的電子數(shù)據(jù)交換處理的需求,因此也就會(huì)存在很多不同的數(shù)據(jù)源類型,在這種情況下整個(gè)系統(tǒng)對于消息數(shù)據(jù)的集成需求就會(huì)比較復(fù) 雜。因此,如果SOA架構(gòu)師希望所構(gòu)建的系統(tǒng)架構(gòu)能夠隨著企業(yè)的成長和變化成功地繼續(xù)得以保持,則整個(gè)系統(tǒng)構(gòu)架中的集成功能就應(yīng)該由服務(wù)提供,而不是由特 定的應(yīng)用程序來完成。

    3.2 服務(wù)粒度的控制以及無狀態(tài)服務(wù)的設(shè)計(jì)

    當(dāng)SOA架構(gòu)師構(gòu)建一個(gè)企業(yè)級的SOA系統(tǒng)架構(gòu)的時(shí)候,關(guān)于系統(tǒng)中最重要的元素,也就是SOA系統(tǒng)中的服務(wù)的構(gòu)建有兩點(diǎn)需要特別注意的地方:首先是對于服務(wù)粒度的控制,另外就是對于無狀態(tài)服務(wù)的設(shè)計(jì)。

    服務(wù)粒度的控制

    SOA 系統(tǒng)中的服務(wù)粒度的控制是一項(xiàng)十分重要的設(shè)計(jì)任務(wù)。通常來說,對于將暴露在整個(gè)系統(tǒng)外部的服務(wù)推薦使用粗粒度的接口,而相對較細(xì)粒度的服務(wù)接口通常用于企 業(yè)系統(tǒng)架構(gòu)的內(nèi)部。從技術(shù)上講,粗粒度的服務(wù)接口可能是一個(gè)特定服務(wù)的完整執(zhí)行,而細(xì)粒度的服務(wù)接口可能是實(shí)現(xiàn)這個(gè)粗粒度服務(wù)接口的具體的內(nèi)部操作。 舉個(gè)例子來說,對于一個(gè)基于SOA架構(gòu)的網(wǎng)上商店來說,粗粒度的服務(wù)可能就是暴露給外部用戶使用的提交購買表單的操作,而系統(tǒng)內(nèi)部的細(xì)粒度的服務(wù)可能就是 實(shí)現(xiàn)這個(gè)提交購買表單服務(wù)的一系列的內(nèi)部服務(wù),比如說創(chuàng)建購買記錄,設(shè)置客戶地址,更新數(shù)據(jù)庫等一系列的操作。雖然細(xì)粒度的接口能為服務(wù)請求者提供了更加 細(xì)化和更多的靈活性,但同時(shí)也意味著引入較難控制的交互模式易變性,也就是說服務(wù)的交互模式可能隨著不同的服務(wù)請求者而不同。如果我們暴露這些易于變化的 服務(wù)接口給系統(tǒng)的外部用戶,就可能造成外部服務(wù)請求者難于支持不斷變化的服務(wù)提供者所暴露的細(xì)粒度服務(wù)接口。而粗粒度服務(wù)接口保證了服務(wù)請求者將以一致的 方式使用系統(tǒng)中所暴露出的服務(wù)。雖然面向服務(wù)的體系結(jié)構(gòu)(SOA)并不強(qiáng)制要求一定要使用粗粒度的服務(wù)接口,但是建議使用它們作為外部集成的接口。通常架 構(gòu)設(shè)計(jì)師可以使用BPEL來創(chuàng)建由細(xì)粒度操作組成的業(yè)務(wù)流程的粗粒度的服務(wù)接口。

    無狀態(tài)服務(wù)的設(shè)計(jì)

    SOA系統(tǒng) 架構(gòu)中的具體服務(wù)應(yīng)該都是獨(dú)立的、自包含的請求,在實(shí)現(xiàn)這些服務(wù)的時(shí)候不需要前一個(gè)請求的狀態(tài),也就是說服務(wù)不應(yīng)該依賴于其他服務(wù)的上下文和狀態(tài),即 SOA架構(gòu)中的服務(wù)應(yīng)該是無狀態(tài)的服務(wù)。當(dāng)某一個(gè)服務(wù)需要依賴時(shí),我們最好把它定義成具體的業(yè)務(wù)流程(BPEL)。在服務(wù)的具體實(shí)現(xiàn)機(jī)制上,我們可以通過 使用 EJB 組件來實(shí)現(xiàn)粗粒度的服務(wù)。我們通常會(huì)利用無狀態(tài)的Session Bean來實(shí)現(xiàn)具體的服務(wù),如果基于Web Service技術(shù),我們就可以將無狀態(tài)的Session Bean暴露為外部用戶可以調(diào)用的到的Web服務(wù),也就是把傳統(tǒng)的Session Facade模型轉(zhuǎn)化為了 EJB 的Web服務(wù)端點(diǎn),這樣,我們就可以向 Web 服務(wù)客戶提供粗粒度的服務(wù)。

    如果我們要在 J2EE的環(huán)境下(基于WebSphere)構(gòu)建Web服務(wù),Web 服務(wù)客戶可以通過兩種方式訪問 J2EE 應(yīng)用程序。客戶可以訪問用 JAX-RPC API 創(chuàng)建的 Web 服務(wù)(使用 Servlet 來實(shí)現(xiàn));Web 服務(wù)客戶也可以通過 EJB的服務(wù)端點(diǎn)接口訪問無狀態(tài)的Session Bean,但Web 服務(wù)客戶不能訪問其他類型的企業(yè)Bean,如有狀態(tài)的Session Bean,實(shí)體Bean和消息驅(qū)動(dòng)Bean。后一種選擇(公開無狀態(tài) EJB 組件作為 Web 服務(wù))有很多優(yōu)勢,基于已有的EJB組件,我們可以利用現(xiàn)有的業(yè)務(wù)邏輯和流程。在許多企業(yè)中,現(xiàn)有的業(yè)務(wù)邏輯可能已經(jīng)使用 EJB 組件編寫,通過 Web 服務(wù)公開它可能是實(shí)現(xiàn)從外界訪問這些服務(wù)的最佳選擇。EJB 端點(diǎn)是一種很好的選擇,因?yàn)樗箻I(yè)務(wù)邏輯和端點(diǎn)位于同一層上。另外EJB容器會(huì)自動(dòng)提供對并發(fā)的支持,作為無狀態(tài)Session Bean實(shí)現(xiàn)的 EJB 服務(wù)端點(diǎn)不必?fù)?dān)心多線程訪問,因?yàn)?EJB 容器必須串行化對無狀態(tài)會(huì)話 bean 任何特定實(shí)例的請求。 由于EJB容器都會(huì)提供對于Security和Transaction的支持,因此Bean的開發(fā)人員可以不需要編寫安全代碼以及事務(wù)處理代碼。 性能問題對于Web服務(wù)來說一直都是一個(gè)問題,由于幾乎所有 EJB 容器都提供了對無狀態(tài)會(huì)話 Bean 群集的支持以及對無狀態(tài)Session Bean 池與資源管理的支持,因此當(dāng)負(fù)載增加時(shí),可以向群集中增加機(jī)器,Web 服務(wù)請求可以定向到這些不同的服務(wù)器,同時(shí)由于無狀態(tài)Session Bean 池改進(jìn)了資源利用和內(nèi)存管理,使 Web 服務(wù)能夠有效地響應(yīng)多個(gè)客戶請求。由此我們可以看到,通過把 Web 服務(wù)模型化為 EJB 端點(diǎn),可以使服務(wù)具有更強(qiáng)的可伸縮性,并增強(qiáng)了系統(tǒng)整體的可靠性。



    回頁首


    4. 結(jié)束語

    本文簡要介紹了有關(guān)架構(gòu)設(shè)計(jì)師以及SOA架構(gòu)的知識(shí),分析了SOA架構(gòu)師在設(shè)計(jì)SOA系統(tǒng)架構(gòu)時(shí)有哪些應(yīng)該特別注意的地方。

    本 文的第二部分將向您介紹在構(gòu)建基于SOA架構(gòu)的企業(yè)系統(tǒng)時(shí)應(yīng)該怎樣保證所構(gòu)建的系統(tǒng)架構(gòu)能夠滿足系統(tǒng)中不同的服務(wù)級別需求。從架構(gòu)設(shè)計(jì)師的角度,SOA是 一種新的設(shè)計(jì)模式,方法學(xué)。因此,SOA本身涵蓋了很多的內(nèi)容,也觸及到了系統(tǒng)整體架構(gòu)設(shè)計(jì)、實(shí)現(xiàn)、維護(hù)等各個(gè)方面。本文的內(nèi)容只是涉及到了有關(guān)于架構(gòu)方 面的一部分內(nèi)容,希望能對廣大的SOA系統(tǒng)開發(fā)設(shè)計(jì)人員起到一定的幫助作用。



    回頁首


    參考資料

    1. Patterns: Service-oriented Architecture and Web Services紅皮書,SG24-6303-00,2004 年 4 月,作者M(jìn)ark Endrei 等。
    2. DeveloperWorks 的SOA 和 Web 服務(wù)專區(qū)有大量專題文章以及關(guān)于開發(fā) Web 服務(wù)應(yīng)用程序的入門級、中級和高級教程。
    3. 有關(guān)隨需應(yīng)變商務(wù)的更多信息,請參閱 Developer resources for an on demand world Web site
    4. Web 服務(wù)項(xiàng)目角色 描述了 Web 服務(wù)開發(fā)項(xiàng)目中所涉及到的各種不同的工作角色,包括各自的目標(biāo),任務(wù)以及彼此之間是如何協(xié)作的。
    5. 面向服務(wù)的分析與設(shè)計(jì)原理一文研究了OOAD、EA 和 BPM 中的適當(dāng)原理。
    6. 企業(yè)服務(wù)總線解決方案剖析,第 1 部分 介紹了面向服務(wù)的體系結(jié)構(gòu)(service-oriented architecture,SOA)和企業(yè)服務(wù)總線(Enterprise Service Bus,ESB)的基本知識(shí),ESB的技術(shù)沿革,以及ESB與SOA之間的關(guān)系。


    回頁首


    關(guān)于作者


    王 強(qiáng),IBM軟件工程師,工作在IBM中國軟件開發(fā)實(shí)驗(yàn)室 - SOA Design Center,從事Incubator及SOA,Grid項(xiàng)目的工作,對J2EE架構(gòu)、SOA架構(gòu)、MDA/MDD以及網(wǎng)格計(jì)算等技術(shù)有較深入的研究。 聯(lián)系方式:wangq@cn.ibm.com

    posted @ 2005-11-25 22:34 Dion 閱讀(955) | 評論 (0)編輯 收藏

    J2EE中軟件基礎(chǔ)結(jié)構(gòu)的瓶頸

    作者:Deepak Goel

    譯者:xMatrix


    版權(quán)聲明:任何獲得Matrix授權(quán)的網(wǎng)站,轉(zhuǎn)載時(shí)請務(wù)必以超鏈接形式標(biāo)明文章原始出處和作者信息及本聲明
    作者:Deepak Goel;xMatrix
    原文地址:http://www.onjava.com/pub/a/onjava/2005/01/19/j2ee-bottlenecks.html
    中文地址:http://www.matrix.org.cn/resource/article/43/43964_J2EE_bottlenecks.html
    關(guān)鍵詞: bottlenecks J2EE


    可擴(kuò)展性是系統(tǒng)中的一個(gè)非常重要的非功能性需求。但系統(tǒng)中可能有多個(gè)瓶頸會(huì)阻礙系統(tǒng)的擴(kuò)展性。在這篇文章中,我們嘗試分析軟件基礎(chǔ)結(jié)構(gòu)成為瓶頸的案例,而不考慮硬件資源上的限制(如CPU、磁盤空間、網(wǎng)絡(luò)速度等)。下面我們將探討一下這個(gè)問題。

    下面是一些整篇文章中用到的一些術(shù)語:
    ·吞吐量:系統(tǒng)支持的每秒能夠處理的事務(wù)個(gè)數(shù)。
    ·服務(wù)請求:每個(gè)事務(wù)中特定硬件的使用率,等于硬件使用率除以吞吐量。
    ·硬件資源:指處理器、內(nèi)存、磁盤和網(wǎng)絡(luò)。
    ·軟件資源:指WEB線程、執(zhí)行線程、BEAN池及數(shù)據(jù)庫連接池等。
    ·預(yù)計(jì)時(shí)間:用戶預(yù)計(jì)兩次并發(fā)提交請求之間的時(shí)間。
    ·短時(shí)間法則:一個(gè)驗(yàn)證測試及確信測試環(huán)境不是瓶頸的法則。
    ·響應(yīng)時(shí)間:用戶等待他提交的請求返回響應(yīng)的時(shí)間。

    理論基礎(chǔ)

    任何J2EE應(yīng)用通常都有下面幾個(gè)層次,如圖1:
    1、硬件基礎(chǔ)結(jié)構(gòu)資源(處理器、內(nèi)存、磁盤和網(wǎng)絡(luò))
    2、軟件基礎(chǔ)結(jié)構(gòu)資源(JVM,WEB服務(wù)器、應(yīng)用服務(wù)器、數(shù)據(jù)庫服務(wù)器)
    3、軟件應(yīng)用(J2EE應(yīng)用)

    imageFigure 1. Snapshot of a J2EE system

    這兒有兩個(gè)可能性導(dǎo)致瓶頸:硬件成為主要瓶頸或者軟件成為主要瓶頸。在第一種情況,硬件資源不足夠而軟件資源很充分,如圖2。隨著負(fù)載的增加,硬件資源成為瓶頸,而軟件可以繼續(xù)擴(kuò)展。減輕這個(gè)瓶頸的方案通常是擴(kuò)大或者增加硬件。

    image
    Figure 2. The hardware pipe becomes a bottleneck


    在第二種情況,硬件資源是足夠的而軟件資源相對有限。隨著負(fù)載的增加,軟件資源成為瓶頸,如圖3。減輕這種瓶頸的方案通常是使用軟件群集或優(yōu)化軟件。

    image
    Figure 3. Software pipe becomes a bottleneck

    應(yīng)用服務(wù)器如何工作?

    來 考慮一下應(yīng)用服務(wù)器的內(nèi)部機(jī)制。應(yīng)用服務(wù)器的基本功能包括事務(wù)管理、數(shù)據(jù)持久、對象池、SOCKET處理和請求處理。這些功能的流程如圖4。有各種組件來 處理這些功能。這些組件需要同步應(yīng)用服務(wù)器中的線程來維護(hù)數(shù)據(jù)的一致性并操作數(shù)據(jù)。雖然這種同步對應(yīng)用服務(wù)器的功能正確性是必須而且有用的,但是也成為高 負(fù)載的限制,即使有足夠的硬件資源。

    image
    Figure 4. Internals of an application server

    實(shí)驗(yàn)

    為 了理解瓶頸的狀況,我們在基于Windows/Intel平臺(tái)用流行的J2EE應(yīng)用服務(wù)器來測試一下JAVA PETSTORE應(yīng)用。一些測試用例如PetStore應(yīng)用的瀏覽和購買周期來測試擴(kuò)展性。我們確信整個(gè)測試環(huán)境(包括操作系統(tǒng)、JVM、應(yīng)用服務(wù)器和應(yīng) 用自身)已經(jīng)盡可能優(yōu)化了,而且J2EE應(yīng)用沒有任何瓶頸或同步問題。我們使用了多用戶負(fù)載測試并觀察了響應(yīng)時(shí)間、吞吐量、資源利用率等指標(biāo)。

    環(huán)境如下:
    1、        J2EE PetStore應(yīng)用
    2、        J2EE應(yīng)用服務(wù)器
    3、        Sun JVM 1.3
    4、        Windows 2000高級服務(wù)器
    5、        Intel Dell PowerEdge 8450 (8Intel至強(qiáng)800MHz處理器, 4GB RAM)
    6、        100Mbps Cisco dedicated network
    7、        負(fù)載測試工具WebLoad

    image




    在這個(gè)測試中我們看到即使有足夠的硬件資源,應(yīng)用服務(wù)器的實(shí)例個(gè)數(shù)限制了擴(kuò)展的能力。在這里軟件資源(如執(zhí)行線程、BEAN池大小、數(shù)據(jù)庫池和其他應(yīng)用服務(wù)器參數(shù))優(yōu)化后即使這些資源不足也不會(huì)影響系統(tǒng)的擴(kuò)展。下面我們來研究一下減輕這種問題的方案。
    注:Sun J2EE PetStore可以被更多地優(yōu)化來改善性能和可擴(kuò)展性。


    解決方案

    同一機(jī)器上的群集

    當(dāng)吞吐量滿載了應(yīng)用服務(wù)器的一個(gè)實(shí)例時(shí),需要增加一個(gè)實(shí)例來減輕這種問題。這個(gè)方案如圖5。

    image
    Figure 5. Instance clusters on the same hardware box

    當(dāng)前機(jī)器的CPU使用率只有40%因而有足夠空間來增加一個(gè)實(shí)例。我們可以發(fā)現(xiàn)在增加了實(shí)例后,吞吐量也增加了50%,如表2

    image

    在不同機(jī)器的群集
    當(dāng)吞吐量滿載了應(yīng)用服務(wù)器的一個(gè)實(shí)例時(shí),機(jī)器的CPU使用率只有40%。因?yàn)?CPU的機(jī)器未完全利用,所以我們測試一下更低配置的機(jī)器。現(xiàn)在我們使用兩個(gè)4CPU的機(jī)器,如圖6。

    image
    Figure 6. Instance clusters on different hardware boxes


    我們發(fā)現(xiàn)4CPU機(jī)器的CPU使用率已達(dá)到80%,再增加實(shí)例也沒有什么用處了。因此我們又增加一臺(tái)4CPU的機(jī)器來運(yùn)行應(yīng)用的實(shí)例。在增加機(jī)器后,吞吐量幾乎翻了一倍。在這里我們確信數(shù)據(jù)庫服務(wù)器不會(huì)成為瓶頸。

    注:在上面的兩臺(tái)機(jī)器的測試中,負(fù)載平衡是通過一種不增加應(yīng)用服務(wù)器實(shí)例功能負(fù)載的方式來處理的。但是在實(shí)際的生產(chǎn)環(huán)境中很難做到。

    因?yàn)槲覀冇^察到8CPU的機(jī)器被沒有被完全利用,所以我們使用4CPU的機(jī)器重新測試了一遍。測試的結(jié)果可以在下表中看到,分別對應(yīng)配置1和2。4CPU的配置幾乎被完全利用了,這意味著使用4CPU的配置比8CPU的配置更實(shí)際,因?yàn)榍罢呋ㄙM(fèi)更少。

    image

    小結(jié)

    這 些實(shí)驗(yàn)指出了軟件基礎(chǔ)結(jié)構(gòu)如應(yīng)用服務(wù)器實(shí)例可能成為瓶頸,并給出了一些解決方案來減輕這種問題(包括在相同或不同機(jī)器上的群集)。這個(gè)問題需要在J2EE 應(yīng)用的負(fù)載計(jì)劃或大小確定時(shí)優(yōu)先考慮,因?yàn)檫@直接影響到應(yīng)用的擴(kuò)展性。這種想法很重要,下面我通過一個(gè)情景對話來表達(dá)。

    項(xiàng)目經(jīng)理:你的意思是應(yīng)用服務(wù)器(代表軟件基礎(chǔ)結(jié)構(gòu))會(huì)成為系統(tǒng)的瓶頸。
    性能架構(gòu)師:是的
    項(xiàng)目經(jīng)理:那為什么這種情況不是經(jīng)常發(fā)生。
    性能架構(gòu)師:是的,因?yàn)橛袝r(shí)候瓶頸首先發(fā)生在硬件或應(yīng)用本身。這時(shí)候應(yīng)用服務(wù)器還沒有使用到所有的擴(kuò)展性。
    項(xiàng)目經(jīng)理:這是事實(shí)。那么解決這種瓶頸的方法就是使用群集。如果有足夠的硬件資源就在同一臺(tái)機(jī)器上跑群集否則在多臺(tái)機(jī)器中配置。
    性能架構(gòu)師:是的。這也是在這篇文章中所得到的。

    資源
    ·onjava.com:onjava.com
    ·Matrix-Java開發(fā)者社區(qū):http://www.matrix.org.cn/


    Deepak Goel是Infosys技術(shù)有限公司軟件工程實(shí)驗(yàn)室的技術(shù)架構(gòu)師。

    posted @ 2005-11-25 10:35 Dion 閱讀(620) | 評論 (0)編輯 收藏

    Quartz從入門到進(jìn)階

    作者:Cavaness

    譯者:David_w_johnson


    版權(quán)聲明:任何獲得Matrix授權(quán)的網(wǎng)站,轉(zhuǎn)載時(shí)請務(wù)必以超鏈接形式標(biāo)明文章原始出處和作者信息及本聲明
    作者:Cavaness;David_w_johnson
    原文地址:http://www.onjava.com/pub/a/onjava/2005/09/28/what-is-quartz.html
    中文地址:http://www.matrix.org.cn/resource/article/43/43968_Quartz.html
    關(guān)鍵詞: Quartz


    Quartz

    Quartz 是一個(gè)開源的作業(yè)調(diào)度框架,它完全由java寫成,并設(shè)計(jì)用于J2SE和J2EE應(yīng)用中。它提供了巨大的靈活性而不犧牲簡單性。你能夠用它來為執(zhí)行一個(gè)作 業(yè)而創(chuàng)建簡單的或復(fù)雜的調(diào)度。它有很多特征,如:數(shù)據(jù)庫支持,集群,插件,EJB作業(yè)預(yù)構(gòu)建,JavaMail及其它,支持cron-like表達(dá)式等 等。

    本文內(nèi)容
    1.        Quartz讓任務(wù)調(diào)度簡單
    2.        Quartz的發(fā)展史
    3.        上手Quartz
    4.        Quartz內(nèi)部架構(gòu)
    5.        作業(yè)
    6.        作業(yè)管理和存儲(chǔ)
    7.        有效作業(yè)存儲(chǔ)
    8.        作業(yè)和觸發(fā)器
    9.        調(diào)度一個(gè)作業(yè)
    10.        用調(diào)度器(Scheduler)調(diào)用你的作業(yè)
    11.        編程調(diào)度同聲明性調(diào)度
    12.        有狀態(tài)和無狀態(tài)作業(yè)
    13.        Quartz框架的其他特征
    14.        Quartz下一步計(jì)劃
    15.        了解更多Quartz特征


    你 曾經(jīng)需要應(yīng)用執(zhí)行一個(gè)任務(wù)嗎?這個(gè)任務(wù)每天或每周星期二晚上11:30,或許僅僅每個(gè)月的最后一天執(zhí)行。一個(gè)自動(dòng)執(zhí)行而無須干預(yù)的任務(wù)在執(zhí)行過程中如果發(fā) 生一個(gè)嚴(yán)重錯(cuò)誤,應(yīng)用能夠知到其執(zhí)行失敗并嘗試重新執(zhí)行嗎?你和你的團(tuán)隊(duì)是用java編程嗎?如果這些問題中任何一個(gè)你回答是,那么你應(yīng)該使用 Quartz調(diào)度器。

    旁注:Matrix目前就大量使用到了Quartz。比如,排名統(tǒng)計(jì)功能的實(shí)現(xiàn),在Jmatrix里通過Quartz定義了一個(gè)定時(shí)調(diào)度作業(yè),在每天凌晨一點(diǎn),作業(yè)開始工作,重新統(tǒng)計(jì)大家的Karma和排名等。
    還有,RSS文件的生成,也是通過Quartz定義作業(yè),每隔半個(gè)小時(shí)生成一次RSS XML文件。
    所以Quartz使用的地方很多,本文無疑是一篇很好的入門和進(jìn)階的文章,在此,感謝David w Johnson的努力!


    Quartz讓作業(yè)調(diào)度簡單

    Quartz 是一個(gè)完全由java編寫的開源作業(yè)調(diào)度框架。不要讓作業(yè)調(diào)度這個(gè)術(shù)語嚇著你。盡管Quartz框架整合了許多額外功能, 但就其簡易形式看,你會(huì)發(fā)現(xiàn)它易用得簡直讓人受不了!。簡單地創(chuàng)建一個(gè)實(shí)現(xiàn)org.quartz.Job接口的java類。Job接口包含唯一的方法:

    public void execute(JobExecutionContext context) 
         throws JobExecutionException;


    在 你的Job接口實(shí)現(xiàn)類里面,添加一些邏輯到execute()方法。一旦你配置好Job實(shí)現(xiàn)類并設(shè)定好調(diào)度時(shí)間表,Quartz將密切注意剩余時(shí)間。當(dāng)調(diào) 度程序確定該是通知你的作業(yè)的時(shí)候,Quartz框架將調(diào)用你Job實(shí)現(xiàn)類(作業(yè)類)上的execute()方法并允許做它該做的事情。無需報(bào)告任何東西 給調(diào)度器或調(diào)用任何特定的東西。僅僅執(zhí)行任務(wù)和結(jié)束任務(wù)即可。如果配置你的作業(yè)在隨后再次被調(diào)用,Quartz框架將在恰當(dāng)?shù)臅r(shí)間再次調(diào)用它。

    如 果你使用了其它流行的開源框架象struts,你會(huì)對Quartz的設(shè)計(jì)和部件感到舒適。雖然兩個(gè)開源工程是解決完全不同的問題,還是有很多相似的之處, 就是開源軟件用戶每天感覺很舒適。Quartz能用在單機(jī)J2SE應(yīng)用中,作為一個(gè)RMI服務(wù)器,也可以用在web應(yīng)用中,甚至也可以用在J2EE應(yīng)用服 務(wù)器中。

    Quartz的發(fā)展史

    盡 管Quartz今年開始受到人們注意,但還是暫時(shí)流行。Quartz由James House創(chuàng)建并最初于2001年春天被加入sourceforge工程。接下來的幾年里,有許多新特征和版本出現(xiàn),但是直到項(xiàng)目遷移到新的站點(diǎn)并成為 OpenSymphony項(xiàng)目家族的一員,才開始真正啟動(dòng)并受到應(yīng)有的關(guān)注。
    James House仍然和幾個(gè)協(xié)助他的業(yè)余開發(fā)者參與大量開發(fā)工作。Quartz開發(fā)團(tuán)隊(duì)今年能發(fā)布幾個(gè)新版本,包括當(dāng)前正處在候選發(fā)布階段的1.5版。

    上手Quartz
    Quartz工程駐留在OpenSymphony站點(diǎn)上。在Quartz站點(diǎn)上可以找到許多有用的資源:JavaDocs,包含指南的文檔,CVS訪問,用戶和開發(fā)者論壇的連接,當(dāng)然也有下載。
    從 下載連接取得Quartz的發(fā)布版本,并且解壓到到本地目錄。這個(gè)下載文件包含了一個(gè)預(yù)先構(gòu)建好的Quartz二進(jìn)制文件(quartz.jar),你可 以將它放進(jìn)自己的應(yīng)用中。Quartz框架只需要少數(shù)的第三方庫,并且這些三方庫是必需的,你很可能已經(jīng)在使用這些庫了。

    你要把 Quartz的安裝目錄的<quartz- install>/lib/core 和 <quartz-install>/lib/optional目錄中的第三方庫加進(jìn)你自己的工程中。大多數(shù)第三方庫是我們所熟知和喜歡的標(biāo)準(zhǔn) Jakarta Commons庫,像Commons Logging, Commons BeantUtils等等。
        
    quartz.properties文件
    Quartz 有一個(gè)叫做quartz.properties的配置文件,它允許你修改框架運(yùn)行時(shí)環(huán)境。缺省是使用Quartz.jar里面的 quartz.properties文件。當(dāng)然,你應(yīng)該創(chuàng)建一個(gè)quartz.properties文件的副本并且把它放入你工程的classes目錄中 以便類裝載器找到它。quartz.properties樣本文件如例1所示。

    例1.quartz.properties文件允許修改Quartz運(yùn)行環(huán)境:

    #===============================================================
    # Configure Main Scheduler Properties  
    #===============================================================

    org.quartz.scheduler.instanceName = QuartzScheduler
    org.quartz.scheduler.instanceId = AUTO

    #===============================================================
    # Configure ThreadPool  
    #===============================================================

    org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
    org.quartz.threadPool.threadCount =  5
    org.quartz.threadPool.threadPriority = 5

    #===============================================================
    # Configure JobStore  
    #===============================================================

    org.quartz.jobStore.misfireThreshold = 60000
    org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore


    一旦將Quartz.jar文件和第三方庫加到自己的工程里面并且quartz.properties文件在工程的classes目錄中,就可以創(chuàng)建作業(yè)了。然而,在做這之前,我們暫且回避一下先簡短討論一下Quartz架構(gòu)。

    Quartz內(nèi)部架構(gòu)
    在 規(guī)模方面,Quartz跟大多數(shù)開源框架類似。大約有300個(gè)java類和接口,并被組織到12個(gè)包中。這可以和Apache Struts把大約325個(gè)類和接口以及組織到11個(gè)包中相比。盡管規(guī)模幾乎不會(huì)用來作為衡量框架質(zhì)量的一個(gè)特性,但這里的關(guān)鍵是quarts內(nèi)含很多功 能,這些功能和特性集是否成為、或者應(yīng)該成為評判一個(gè)開源或非開源框架質(zhì)量的因素。

    Quartz調(diào)度器
    Quartz 框架的核心是調(diào)度器。調(diào)度器負(fù)責(zé)管理Quartz應(yīng)用運(yùn)行時(shí)環(huán)境。調(diào)度器不是靠自己做所有的工作,而是依賴框架內(nèi)一些非常重要的部件。Quartz不僅僅 是線程和線程管理。為確保可伸縮性,Quartz采用了基于多線程的架構(gòu)。啟動(dòng)時(shí),框架初始化一套worker線程,這套線程被調(diào)度器用來執(zhí)行預(yù)定的作 業(yè)。這就是Quartz怎樣能并發(fā)運(yùn)行多個(gè)作業(yè)的原理。Quartz依賴一套松耦合的線程池管理部件來管理線程環(huán)境。本片文障中,我們會(huì)多次提到線程池管 理,但Quartz里面的每個(gè)對象是可配置的或者是可定制的。所以,例如,如果你想要插進(jìn)自己線程池管理設(shè)施,我猜你一定能!

    作業(yè)
    用Quartz 的行話講,作業(yè)是一個(gè)執(zhí)行任務(wù)的簡單java類。任務(wù)可以是任何java代碼。只需你實(shí)現(xiàn)org.quartz.Job接口并且在出現(xiàn)嚴(yán)重錯(cuò)誤情況下拋出 JobExecutionException異常即可。Job接口包含唯一的一個(gè)方法execute(),作業(yè)從這里開始執(zhí)行。一旦實(shí)現(xiàn)了Job接口和 execute()方法,當(dāng)Quartz確定該是作業(yè)運(yùn)行的時(shí)候,它將調(diào)用你的作業(yè)。Execute()方法內(nèi)就完全是你要做的事情。下面有一些你要在作 業(yè)里面做事情的例子:
    ·        用JavaMail(或者用其他的像Commons Net一樣的郵件框架)發(fā)送郵件
    ·        創(chuàng)建遠(yuǎn)程接口并且調(diào)用在EJB上的方法
    ·        獲取Hibernate Session,查詢和更新關(guān)系數(shù)據(jù)庫里的數(shù)據(jù)
    ·        使用OSWorkflow并且從作業(yè)調(diào)用一個(gè)工作流
    ·        使用FTP和到處移動(dòng)文件
    ·        調(diào)用Ant構(gòu)建腳本開始預(yù)定構(gòu)建

    這種可能性是無窮的,正事這種無限可能性使得框架功能如此強(qiáng)大。Quartz給你提供了一個(gè)機(jī)制來建立具有不同粒度的、可重復(fù)的調(diào)度表,于是,你只需創(chuàng)建一個(gè)java類,這個(gè)類被調(diào)用而執(zhí)行任務(wù)。

    作業(yè)管理和存儲(chǔ)
    作 業(yè)一旦被調(diào)度,調(diào)度器需要記住并且跟蹤作業(yè)和它們的執(zhí)行次數(shù)。如果你的作業(yè)是30分鐘后或每30秒調(diào)用,這不是很有用。事實(shí)上,作業(yè)執(zhí)行需要非常準(zhǔn)確和即 時(shí)調(diào)用在被調(diào)度作業(yè)上的execute()方法。Quartz通過一個(gè)稱之為作業(yè)存儲(chǔ)(JobStore)的概念來做作業(yè)存儲(chǔ)和管理。


    有效作業(yè)存儲(chǔ)

    Quartz 提供兩種基本作業(yè)存儲(chǔ)類型。第一種類型叫做RAMJobStore,它利用通常的內(nèi)存來持久化調(diào)度程序信息。這種作業(yè)存儲(chǔ)類型最容易配置、構(gòu)造和運(yùn)行。對 許多應(yīng)用來說,這種作業(yè)存儲(chǔ)已經(jīng)足夠了。然而,因?yàn)檎{(diào)度程序信息是存儲(chǔ)在被分配給JVM的內(nèi)存里面,所以,當(dāng)應(yīng)用程序停止運(yùn)行時(shí),所有調(diào)度信息將被丟失。 如果你需要在重新啟動(dòng)之間持久化調(diào)度信息,則將需要第二種類型的作業(yè)存儲(chǔ)。

    第二種類型的作業(yè)存儲(chǔ)實(shí)際上提供兩種不同的實(shí)現(xiàn),但兩種實(shí)現(xiàn)一 般都稱為JDBC作業(yè)存儲(chǔ)。兩種JDBC作業(yè)存儲(chǔ)都需要JDBC驅(qū)動(dòng)程序和后臺(tái)數(shù)據(jù)庫來持久化調(diào)度程序信息。這兩種類型的不同在于你是否想要控制數(shù)據(jù)庫事 務(wù)或這釋放控制給應(yīng)用服務(wù)器例如BEA's WebLogic或Jboss。(這類似于J2EE領(lǐng)域中,Bean管理的事務(wù)和和容器管理事務(wù)之間的區(qū)別)
    這兩種JDBC作業(yè)存儲(chǔ)是:

    ·        JobStoreTX:當(dāng)你想要控制事務(wù)或工作在非應(yīng)用服務(wù)器環(huán)境中是使用
    ·        JobStoreCMT:當(dāng)你工作在應(yīng)用服務(wù)器環(huán)境中和想要容器控制事務(wù)時(shí)使用。

    JDBC作業(yè)存儲(chǔ)為需要調(diào)度程序維護(hù)調(diào)度信息的用戶而設(shè)計(jì)。

    作業(yè)和觸發(fā)器

    Quartz 設(shè)計(jì)者做了一個(gè)設(shè)計(jì)選擇來從調(diào)度分離開作業(yè)。Quartz中的觸發(fā)器用來告訴調(diào)度程序作業(yè)什么時(shí)候觸發(fā)。框架提供了一把觸發(fā)器類型,但兩個(gè)最常用的是 SimpleTrigger和CronTrigger。SimpleTrigger為需要簡單打火調(diào)度而設(shè)計(jì)。典型地,如果你需要在給定的時(shí)間和重復(fù)次數(shù) 或者兩次打火之間等待的秒數(shù)打火一個(gè)作業(yè),那么SimpleTrigger適合你。另一方面,如果你有許多復(fù)雜的作業(yè)調(diào)度,那么或許需要 CronTrigger。

    CronTrigger是基于Calendar-like調(diào)度的。當(dāng)你需要在除星期六和星期天外的每天上午10點(diǎn)半執(zhí)行作業(yè)時(shí),那么應(yīng)該使用CronTrigger。正如它的名字所暗示的那樣,CronTrigger是基于Unix克隆表達(dá)式的。

    作為一個(gè)例子,下面的Quartz克隆表達(dá)式將在星期一到星期五的每天上午10點(diǎn)15分執(zhí)行一個(gè)作業(yè)。
    0 15 10 ? * MON-FRI

    下面的表達(dá)式
    0 15 10 ? * 6L 2002-2005
    將在2002年到2005年的每個(gè)月的最后一個(gè)星期五上午10點(diǎn)15分執(zhí)行作業(yè)。

    你不可能用SimpleTrigger來做這些事情。你可以用兩者之中的任何一個(gè),但哪個(gè)跟合適則取決于你的調(diào)度需要。


    調(diào)度一個(gè)作業(yè)

    讓 我們通過看一個(gè)例子來進(jìn)入實(shí)際討論。現(xiàn)假定你管理一個(gè)部門,無論何時(shí)候客戶在它的FTP服務(wù)器上存儲(chǔ)一個(gè)文件,都得用電子郵件通知它。我們的作業(yè)將用 FTP登陸到遠(yuǎn)程服務(wù)器并下載所有找到的文件。然后,它將發(fā)送一封含有找到和下載的文件數(shù)量的電子郵件。這個(gè)作業(yè)很容易就幫助人們整天從手工執(zhí)行這個(gè)任務(wù) 中解脫出來,甚至連晚上都無須考慮。我們可以設(shè)置作業(yè)循環(huán)不斷地每60秒檢查一次,而且工作在7×24模式下。這就是Quartz框架完全的用途。

    首先創(chuàng)建一個(gè)Job類,將執(zhí)行FTP和Email邏輯。下例展示了Quartz的Job類,它實(shí)現(xiàn)了org.quartz.Job接口。

    例2.從FTP站點(diǎn)下載文件和發(fā)送email的Quartz作業(yè)

    public class ScanFTPSiteJob implements Job {
        private static Log logger = LogFactory.getLog(ScanFTPSiteJob.class);

        /*
         * Called the scheduler framework at the right time
         */
        public void execute(JobExecutionContext context)
                throws JobExecutionException {

            JobDataMap jobDataMap = context.getJobDataMap();

            try {
                // Check the ftp site for files
                File[] files = JobUtil.checkForFiles(jobDataMap);

                JobUtil.sendEmail(jobDataMap, files);
            } catch (Exception ex) {
                throw new JobExecutionException(ex.getMessage());
            }
        }
    }


    我 們故意讓ScanFTPSiteJob保持很簡單。我們?yōu)檫@個(gè)例子創(chuàng)建了一個(gè)叫做JobUtil的實(shí)用類。它不是Quartz的組成部分,但對構(gòu)建各種作 業(yè)能重用的實(shí)用程序庫來說是有意義的。我們可以輕易將那種代碼組織進(jìn)作業(yè)類中,quarts 調(diào)度器一樣好用,因?yàn)槲覀円恢痹谑褂胵uarts,所以那些代碼可繼續(xù)重用。

    JobUtil.checkForFiles() and JobUtil.sendEmail()方法使用的參數(shù)是Quartz創(chuàng)建的JobDataMap的實(shí)例。實(shí)例為每個(gè)作業(yè)的執(zhí)行而創(chuàng)建,它是向作業(yè)類傳遞配置參數(shù)的方法。
    這里并沒有展示JobUtil的實(shí)現(xiàn),但我們能用Jakarta上的Commons Net輕易地實(shí)現(xiàn)FTP和Email功能。

    用調(diào)度器調(diào)用作業(yè)

    首先創(chuàng)建一個(gè)作業(yè),但為使作業(yè)能被調(diào)度器調(diào)用,你得向調(diào)度程序說明你的作業(yè)的調(diào)用時(shí)間和頻率。這個(gè)事情由與作業(yè)相關(guān)的觸發(fā)器來完成。因?yàn)槲覀儍H僅對大約每60秒循環(huán)調(diào)用作業(yè)感興趣,所以打算使用SimpleTrigger。

    作業(yè)和觸發(fā)器通過Quartz調(diào)度器接口而被調(diào)度。我們需要從調(diào)度器工廠類取得一個(gè)調(diào)度器的實(shí)例。最容易的辦法是調(diào)用StdSchedulerFactory這個(gè)類上的靜態(tài)方法getDefaultScheduler()。
    使用Quartz框架,你需要調(diào)用start()方法來啟動(dòng)調(diào)度器。例3的代碼遵循了大多數(shù)Quartz應(yīng)用的一般模式:創(chuàng)建一個(gè)或多個(gè)作業(yè),創(chuàng)建和設(shè)置觸發(fā)器,用調(diào)度器調(diào)度作業(yè)和觸發(fā)器,啟動(dòng)調(diào)度器。

    例3.Quartz作業(yè)通過Quartz調(diào)度器而被調(diào)度

    public class MyQuartzServer {

        public static void main(String[] args) {
            MyQuartzServer server = new MyQuartzServer();

            try {
                server.startScheduler();
            } catch (SchedulerException ex) {
                ex.printStackTrace();
            }
        }

        protected void startScheduler() throws SchedulerException {

            // Use the factory to create a Scheduler instance
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

            // JobDetail holds the definition for Jobs
            JobDetail jobDetail =
    new JobDetail("ScanFTPJob", Scheduler.DEFAULT_GROUP,
                    ScanFTPSiteJob.class);

    // Store job parameters to be used within execute()
    jobDetail.getJobDataMap().put(
    "FTP_HOST",
    "\\home\\cavaness\\inbound");

            // Other neccessary Job parameters here

            // Create a Trigger that fires every 60 seconds
            Trigger trigger = TriggerUtils.makeSecondlyTrigger(60);
            
            // Setup the Job and Trigger with the Scheduler
            scheduler.scheduleJob(jobDetail, trigger );
            
            // Start the Scheduler running
            scheduler.start();
        }
    }



    編程調(diào)度同聲明性調(diào)度

    例3中,我們通過編程的方法調(diào)度我們的ScanFTPSiteJob作業(yè)。就是說,我們用java代碼來設(shè)置作業(yè)和觸發(fā)器。Quartz框架也支持在xml文件里面申明性的設(shè)置作業(yè)調(diào)度。申明性方法允許我們更快速地修改哪個(gè)作業(yè)什么時(shí)候被執(zhí)行。

    Quartz 框架有一個(gè)插件,這個(gè)插件負(fù)責(zé)讀取xml配置文件。xml配置文件包含了關(guān)于啟動(dòng)Quartz應(yīng)用的作業(yè)和觸發(fā)器信息。所有xml文件中的作業(yè)連同相關(guān)的 觸發(fā)器都被加進(jìn)調(diào)度器。你仍然需要編寫作業(yè)類,但配置那些作業(yè)類的調(diào)度器則非常動(dòng)態(tài)化。例4展示了一個(gè)用申明性方式執(zhí)行與例3代碼相同的邏輯的xml配置 文件。

    例4.能使用xml文件調(diào)度的作業(yè)

    <?xml version='1.0' encoding='utf-8'?>
    <quartz>
        <job>
            <job-detail>
                <name>ScanFTPSiteJob</name>
                <group>DEFAULT</group>
                <description>
                    A job that scans an ftp site for files
                </description>
                <job-class>ScanFTPSiteJob</job-class>

                <job-data-map allows-transient-data="true">
                    <entry>
                        <key>FTP_HOST</key>
                        <value>\home\cavaness\inbound</value>
                    </entry>
                    
                    <!--  Other neccessary Job parameters here -->

                </job-data-map>
            </job-detail>

            <trigger>
                <simple>
                    <name>ScanFTPSiteJobTrigger</name>
                    <group>DEFAULT</group>
                    <job-name>ScanFTPSiteJob</job-name>
                    <job-group>DEFAULT</job-group>
                    <start-time>2005-09-11 6:10:00 PM</start-time>
                    <!-- repeat indefinitely every 60 seconds -->
                    <repeat-count>-1</repeat-count>
                    <repeat-interval>60000</repeat-interval>
                </simple>
            </trigger>

        </job>
    </quartz>


    你可以將xml文件中的元素跟例3代碼作個(gè)比較,它們從概念上來看是相同的。使用例4式的申明性方法的好處是維護(hù)變得極其簡單,只需改變xml配置文件和重新啟動(dòng)Quartz應(yīng)用即可。無須修改代碼,無須重新編譯,無須重新部署。

    有狀態(tài)和無狀態(tài)作業(yè)

    在本文中你所看到的作業(yè)到是無狀態(tài)的。這意味著在兩次作業(yè)執(zhí)行之間,不會(huì)去維護(hù)作業(yè)執(zhí)行時(shí)JobDataMap的狀態(tài)改變。如果你需要能增、刪,改JobDataMap的值,而且能讓作業(yè)在下次執(zhí)行時(shí)能看到這個(gè)狀態(tài)改變,則需要用Quartz有狀態(tài)作業(yè)。
    如 果你是一個(gè)有經(jīng)驗(yàn)的EJB開發(fā)者的話,深信你會(huì)立即退縮,因?yàn)橛袪顟B(tài)帶有負(fù)面含義。這主要是由于EJB帶來的伸縮性問題。Quartz有狀態(tài)作業(yè)實(shí)現(xiàn)了 org.quartz.StatefulJob接口。無狀態(tài)和有狀態(tài)作業(yè)的關(guān)鍵不同是有狀態(tài)作業(yè)在每次執(zhí)行時(shí)只有一個(gè)實(shí)例。大多數(shù)情況下,有狀態(tài)的作業(yè)不 回帶來大的問題。然而,如果你有一個(gè)需要頻繁執(zhí)行的作業(yè)或者需要很長時(shí)間才能完成的作業(yè),那么有狀態(tài)作業(yè)可能給你帶來伸縮性問題。

    Quartz框架的其他特征

    Quartz框架有一個(gè)豐富的特征集。事實(shí)上,quarts有太多特性以致不能在一種情況中全部領(lǐng)會(huì),下面列出了一些有意思的特征,但沒時(shí)間在此詳細(xì)討論。

    監(jiān)聽器和插件

    每 個(gè)人都喜歡監(jiān)聽和插件。今天,幾乎下載任何開源框架,你必定會(huì)發(fā)現(xiàn)支持這兩個(gè)概念。監(jiān)聽是你創(chuàng)建的java類,當(dāng)關(guān)鍵事件發(fā)生時(shí)會(huì)收到框架的回調(diào)。例如, 當(dāng)一個(gè)作業(yè)被調(diào)度、沒有調(diào)度或觸發(fā)器終止和不再打火時(shí),這些都可以通過設(shè)置來來通知你的監(jiān)聽器。Quartz框架包含了調(diào)度器監(jiān)聽、作業(yè)和觸發(fā)器監(jiān)聽。你 可以配置作業(yè)和觸發(fā)器監(jiān)聽為全局監(jiān)聽或者是特定于作業(yè)和觸發(fā)器的監(jiān)聽。

    一旦你的一個(gè)具體監(jiān)聽被調(diào)用,你就能使用這個(gè)技術(shù)來做一些你想要在 監(jiān)聽類里面做的事情。例如,你如果想要在每次作業(yè)完成時(shí)發(fā)送一個(gè)電子郵件,你可以將這個(gè)邏輯寫進(jìn)作業(yè)里面,也可以JobListener里面。寫進(jìn) JobListener的方式強(qiáng)制使用松耦合有利于設(shè)計(jì)上做到更好。

    Quartz插件是一個(gè)新的功能特性,無須修改Quartz源碼便可 被創(chuàng)建和添加進(jìn)Quartz框架。他為想要擴(kuò)展Quartz框架又沒有時(shí)間提交改變給Quartz開發(fā)團(tuán)隊(duì)和等待新版本的開發(fā)人員而設(shè)計(jì)。如果你熟悉 Struts插件的話,那么完全可以理解Quartz插件的使用。

    與其Quartz提供一個(gè)不能滿足你需要的有限擴(kuò)展點(diǎn),還不如通過使用插件來擁有可修整的擴(kuò)展點(diǎn)。

    集群Quartz應(yīng)用
    Quartz應(yīng)用能被集群,是水平集群還是垂直集群取決于你自己的需要。集群提供以下好處:
    ·        伸縮性
    ·        搞可用性
    ·        負(fù)載均衡
    目前,Quartz只能借助關(guān)系數(shù)據(jù)庫和JDBC作業(yè)存儲(chǔ)支持集群。將來的版本這個(gè)制約將消失并且用RAMJobStore集群將是可能的而且將不需要數(shù)據(jù)庫的支持。

    Quartz web應(yīng)用
    使 用框架幾個(gè)星期或幾個(gè)月后,Quartz用戶所顯示的需求之一是需要集成Quartz到圖形用戶界面中。目前Quartz框架已經(jīng)有一些工具允許你使用 Java servlet來初始化和啟動(dòng)Quartz。一旦你可以訪問調(diào)度器實(shí)例,你就可以把它存儲(chǔ)在web容器的servlet上下文中 (ServletContext中)并且可以通過調(diào)度器接口管理調(diào)度環(huán)境。

    幸運(yùn)的是一些開發(fā)者已正影響著單機(jī)Quartz web應(yīng)用,它用來更好地管理調(diào)度器環(huán)境。構(gòu)建在若干個(gè)流行開源框架如Struts和Spring之上的圖形用戶界面支持很多功能,這些功能都被包裝進(jìn)一個(gè)簡單接口。GUI的一個(gè)畫面如圖1所示:


    image
    圖1.Quartz Web應(yīng)用允許比較容易地管理Quartz環(huán)境。

    Quartz的下一步計(jì)劃

    Quartz是一個(gè)活動(dòng)中的工程。Quartz開發(fā)團(tuán)隊(duì)明確表示不會(huì)停留在已有的榮譽(yù)上。Quartz下一個(gè)主要版本已經(jīng)在啟動(dòng)中。你可以在OpenSymphony的 wiki上體驗(yàn)一下Quartz 2.0的設(shè)計(jì)和特征。
    總之,Quartz用戶每天都自由地添加特性建議和設(shè)計(jì)創(chuàng)意以便能被核心框架考慮(看重)。

    了解更多Quartz特征

    當(dāng)你開始使用Quartz框架的更多特性時(shí),User and Developer Forum論壇變成一個(gè)回答問題和跟其他Quartz用戶溝通的極其有用的資源。經(jīng)常去逛逛這個(gè)論壇時(shí)很有好處的,你也可以依靠James House來共享與你的需要相關(guān)的知識(shí)和意見。

    這個(gè)論壇時(shí)免費(fèi)的,你不必登陸便可以查找和查看歸檔文件。然而,如果你覺得這個(gè)論壇比較好而且需要向某人回復(fù)問題時(shí),你必須得申請一個(gè)免費(fèi)帳號并用該帳號登陸。

    資源
    ·onjava.com:onjava.com
    ·Matrix-Java開發(fā)者社區(qū):http://www.matrix.org.cn/

    Chuck Cavaness畢業(yè)于Georgia Tech并獲得計(jì)算機(jī)科學(xué)與技術(shù)學(xué)位。他在健康,銀行,B2B領(lǐng)域開發(fā)過基于java的企業(yè)系統(tǒng)。同時(shí)也是O'Reilly出版的兩本書 Programming Jakarta Struts 和 Jakarta Struts Pocket Reference的作者。

    posted @ 2005-11-25 10:34 Dion 閱讀(1151) | 評論 (0)編輯 收藏

    作者:Brad Neuberg

    譯者:boool


    版權(quán)聲明:任何獲得Matrix授權(quán)的網(wǎng)站,轉(zhuǎn)載時(shí)請務(wù)必以超鏈接形式標(biāo)明文章原始出處和作者信息及本聲明
    作者:Brad Neuberg;boool
    原文地址:http://www.onjava.com/pub/a/onjava/2005/10/26/ajax-handling-bookmarks-and-back-button.html
    中文地址:http://www.matrix.org.cn/resource/article/43/43972_AJAX.html
    關(guān)鍵詞: ajax;bookmarks;back button


    這篇文章描述了一個(gè)支持AJAX應(yīng)用書簽和回退按鈕的開源的javascript庫。在這個(gè)指南的最后,開發(fā)者將會(huì)得出一個(gè)甚至不是Google Maps 或者 Gmail那樣處理的AJAX的解決方案:健壯的,可用的書簽和向前向后的動(dòng)作能夠象其他的web頁面一樣正確的工作。

    AJAX:怎樣去控制書簽和回退按鈕 這篇文章說明了一個(gè)重要的成果,AJAX應(yīng)用目前面對著書簽和回退按鈕的應(yīng)用,描述了非常簡單的歷史庫(Really Simple History),一個(gè)開源的解決這類問題的框架,并提供了一些能夠運(yùn)行的例子。

    這 篇文章描述的主要問題是雙重的,一是一個(gè)隱藏的html 表單被用作一個(gè)大而短生命周期的客戶端信息的session緩存,這個(gè)緩存對在這個(gè)頁面上前進(jìn)回退是強(qiáng)壯的。二是一個(gè)錨連接和隱藏的iframes的組合 用來截取和記錄瀏覽器的歷史事件,來實(shí)現(xiàn)前進(jìn)和回退的按鈕。這兩個(gè)技術(shù)都被用一個(gè)簡單的javascript庫來封裝,以利于開發(fā)者的使用。

    存在的問題
    書簽和回退按鈕在傳統(tǒng)的多頁面的web應(yīng)用上能順利的運(yùn)行。當(dāng)用戶在網(wǎng)站上沖浪時(shí),他們的瀏覽器地址欄能更新URL,這些URL可以被粘貼到的email或者添加到書簽以備以后的使用。回退和前進(jìn)按鈕也可以正常運(yùn)行,這可以使用戶在他們訪問的頁面間移動(dòng)。

    AJAX應(yīng)用是與眾不同的,然而,他也是在單一web頁面上成熟的程序。瀏覽器不是為AJAX而做的—AJAX他捕獲過去的事件,當(dāng)web應(yīng)用在每個(gè)鼠標(biāo)點(diǎn)擊時(shí)刷新頁面。

    在象Gmail那樣的AJAX軟件里,瀏覽器的地址欄正確的停留就象用戶在選擇和改變應(yīng)用的狀態(tài)時(shí),這使得作書簽到特定的應(yīng)用視圖里變得不可能。此外,如果用戶按下了他們的回退按鈕去返回上一個(gè)操作,他們會(huì)驚奇的發(fā)現(xiàn)瀏覽器將完全離開原來他所在的應(yīng)用的web頁面。

    解決方案
    開 源的Really Simply History(RSH)框架解決了這些問題,他帶來了AJAX應(yīng)用的作書簽和控制前進(jìn)后退按鈕的功能。RSH目前還是beta版,在 Firefox1.0上,Netscape7及以上,和IE6及以上運(yùn)行。Safari現(xiàn)在還不支持(要得到更詳細(xì)的說明,請看我的weblog中的文章Coding in Paradise: Safari: No DHTML History Possible).

    目前存在的幾個(gè)AJAX框架可以幫助我們做書簽和發(fā)布?xì)v史,然而所有的框架都因?yàn)樗麄兊膶?shí)現(xiàn)而被幾個(gè)重要的bug困擾(請看Coding in Paradise: AJAX History Libraries 得知詳情)。此外,許多AJAX歷史框架集成綁定到較大的庫上,比如Backbase 和 Dojo,這些框架提供了與傳統(tǒng)AJAX應(yīng)用不同的編程模型,強(qiáng)迫開發(fā)者去采用一整套全新的方式去獲得瀏覽器的歷史相關(guān)的功能。

    相應(yīng)的,RSH是一個(gè)簡單的模型,能被包含在已經(jīng)存在的AJAX系統(tǒng)中。而且,Really Simple History庫使用了一些技巧去避免影響到其他歷史框架的bug.

    Really Simple History框架由2個(gè)javascript類庫組成,分別叫DhtmlHistory 和 HistoryStorage.

    DhtmlHistory 類提供了一個(gè)對AJAX應(yīng)用提取歷史的功能。.AJAX頁面add() 歷史事件到瀏覽器里,指定新的地址和關(guān)聯(lián)歷史數(shù)據(jù)。DhtmlHistory 類用一個(gè)錨的hash表更新瀏覽器現(xiàn)在的URL,比如#new-location ,然后用這個(gè)新的URL關(guān)聯(lián)歷史數(shù)據(jù)。AJAX應(yīng)用注冊他們自己到歷史監(jiān)聽器里,然后當(dāng)用戶用前進(jìn)和后退按鈕導(dǎo)航的時(shí)候,歷史事件被激發(fā),提供給瀏覽器新 的地址和調(diào)用add()持續(xù)保留數(shù)據(jù)。

    第二個(gè)類HistoryStorage,允許開發(fā)者存儲(chǔ)任意大小的歷史數(shù)據(jù)。一般的頁面,當(dāng)一個(gè)用 戶導(dǎo)航到一個(gè)新的網(wǎng)站,瀏覽器會(huì)卸載和清除所有這個(gè)頁面的應(yīng)用和javascript狀態(tài)信息。如果用戶用回退按鈕返回過來了,所有的數(shù)據(jù)已經(jīng)丟失了。 HistoryStorage 類解決了這個(gè)問題,他有一個(gè)api 包含簡單的hashtable方法比如put(),get(),hasKey()。這些方法允許開發(fā)者在離開web頁面時(shí)存儲(chǔ)任意大小的數(shù)據(jù),當(dāng)用戶點(diǎn)了 回退按鈕返回時(shí),數(shù)據(jù)可以通過HistoryStorage 類被訪問。我們通過一個(gè)隱藏的表單域(a hidden form field),利用瀏覽器即使在用戶離開web頁面也會(huì)自動(dòng)保存表單域值的這個(gè)特性,完成這個(gè)功能。

    讓我們立即進(jìn)入一個(gè)簡單的例子吧。

    示例1
    首先,任何一個(gè)想使用Really Simple History框架的頁面必須包含(include)dhtmlHistory.js 腳本。

    <!-- Load the Really Simple 
         History framework -->
    <script type="text/javascript"
            src="../../framework/dhtmlHistory.js">
    </script>


    DHTML History 應(yīng)用也必須在和AJAX web頁面相同的目錄下包含一個(gè)叫blank.html 的指定文件,這個(gè)文件被Really Simple History框架綁定而且對IE來說是必需的。另一方面,RSH使用一個(gè)hidden iframe 來追蹤和加入IE歷史的改變,為了正確的執(zhí)行功能,這個(gè)iframe需要指向一個(gè)真正的地址,不需要blank.html。

    RSH框架創(chuàng)建了一個(gè)叫dhtmlHistory 的全局對象,作為操作瀏覽器歷史的入口。使用dhtmlHistory 的第一步需要在頁面加載后初始化這個(gè)對象。

    window.onload = initialize;
        
    function initialize() {
      // initialize the DHTML History
      // framework
      dhtmlHistory.initialize();


    然后,開發(fā)者使用dhtmlHistory.addListener()方法去訂閱歷史改變事件。這個(gè)方法獲取一個(gè)javascript回調(diào)方法,當(dāng)一個(gè)DHTML歷史改變事件發(fā)生時(shí)他將收到2個(gè)自變量,新的頁面地址,和任何可選的而且可以被關(guān)聯(lián)到這個(gè)事件的歷史數(shù)據(jù)。

    indow.onload = initialize;
        
    function initialize() {
      // initialize the DHTML History
      // framework
      dhtmlHistory.initialize();
      
      // subscribe to DHTML history change
      // events
      dhtmlHistory.addListener(historyChange);


    historyChange()方法是簡單易懂得,它是由一個(gè)用戶導(dǎo)航到一個(gè)新地址后收到的新地址(newLocation)和一個(gè)關(guān)聯(lián)到事件的可選的歷史數(shù)據(jù)historyData 構(gòu)成的。

    /** Our callback to receive history change
         events. */
    function historyChange(newLocation,
                           historyData) {
      debug("A history change has occurred: "
            + "newLocation="+newLocation
            + ", historyData="+historyData,
            true);
    }


    上面用到的debug()方法是例子代碼中定義的一個(gè)工具函數(shù),在完整的下載例子里有。debug()方法簡單的在web頁面上打一條消息,第2個(gè)Boolean變量,在代碼里是true,控制一個(gè)新的debug消息打印前是否要清除以前存在的所有消息。

    一個(gè)開發(fā)者使用add()方法加入歷史事件。加入一個(gè)歷史事件包括根據(jù)歷史的改變指定一個(gè)新的地址,就像"edit:SomePage"標(biāo)記, 還提供一個(gè)事件發(fā)生時(shí)可選的會(huì)被存儲(chǔ)到歷史數(shù)據(jù)historyData值.

    window.onload = initialize;
        
    function initialize() {
      // initialize the DHTML History
      // framework
      dhtmlHistory.initialize();
      
      // subscribe to DHTML history change
      // events
      dhtmlHistory.addListener(historyChange);
          
      // if this is the first time we have
      // loaded the page...
      if (dhtmlHistory.isFirstLoad()) {
        debug("Adding values to browser "
              + "history", false);
        // start adding history
        dhtmlHistory.add("helloworld",
                         "Hello World Data");
        dhtmlHistory.add("foobar", 33);
        dhtmlHistory.add("boobah", true);
          
        var complexObject = new Object();
        complexObject.value1 =
                      "This is the first value";
        complexObject.value2 =
                      "This is the second data";
        complexObject.value3 = new Array();
        complexObject.value3[0] = "array 1";
        complexObject.value3[1] = "array 2";
          
        dhtmlHistory.add("complexObject",
                         complexObject);


    在add ()方法被調(diào)用后,新地址立刻被作為一個(gè)錨值顯示在用戶的瀏覽器的URL欄里。例如,一個(gè)AJAX web頁面停留在http://codinginparadise.org/my_ajax_app,調(diào)用了dhtmlHistory.add ("helloworld", "Hello World Data" 后,用戶將在瀏覽器的URL欄里看到下面的地址
    http://codinginparadise.org/my_ajax_app#helloworld

    然 后他們可以把這個(gè)頁面做成書簽,如果他們使用這個(gè)書簽,你的AJAX應(yīng)用可以讀出#helloworld值然后使用她去初始化web頁面。Hash里的地 址值被Really Simple History  框架顯式的編碼和解碼(URL encoded and decoded) (這是為了解決字符的編碼問題)

    對當(dāng)AJAX地址改變時(shí)保存更多的復(fù)雜的狀態(tài)來說,historyData  比一個(gè)更容易的匹配一個(gè) URL的東西更有用。他是一個(gè)可選的值,可以是任何javascript類型,比如Number, String, 或者 Object 類型。有一個(gè)例子是用這個(gè)在一個(gè)多文本編輯器(rich text editor)保存所有的文本,例如,如果用戶從這個(gè)頁面漂移(或者說從這個(gè)頁面導(dǎo)航到其他頁面,離開了這個(gè)頁面)走。當(dāng)一個(gè)用戶再回到這個(gè)地址,瀏覽器 會(huì)把這個(gè)對象返回給歷史改變偵聽器(history change listener)。

    開發(fā)者可以提供一個(gè)完全的 historyData 的javascript對象,用嵌套的對象objects和排列arrays來描繪復(fù)雜的狀態(tài)。只要是JSON (JavaScript Object Notation)  允許的那么在歷史數(shù)據(jù)里就是允許的,包括簡單數(shù)據(jù)類型和null型。DOM的對象和可編程的瀏覽器對象比如 XMLHttpRequest ,不會(huì)被保存。注意historyData 不會(huì)被書簽持久化,如果瀏覽器關(guān)掉,或者瀏覽器的緩存被清空,或者用戶清除歷史的時(shí)候,會(huì)消失掉。

    使用dhtmlHistory 最后一步,是isFirstLoad() 方法。如果你導(dǎo)航到一個(gè)web頁面,再跳到一個(gè)不同的頁面,然后按下回退按鈕返回起始的網(wǎng)站,第一頁將完全重新裝載,并激發(fā)onload事件。這樣能產(chǎn)生 破壞性,當(dāng)代碼在第一次裝載時(shí)想要用某種方式初始化頁面的時(shí)候,不會(huì)再刷新頁面。isFirstLoad() 方法讓區(qū)別是最開始第一次裝載頁面,還是相對的,在用戶導(dǎo)航回到他自己的瀏覽器歷史中記錄的網(wǎng)頁時(shí)激發(fā)load事件,成為可能。

    在例子代碼中,我們只想在第一次頁面裝載的時(shí)候加入歷史事件,如果用戶在第一次裝載后,按回退按鈕返回頁面,我們就不想重新加入任何歷史事件。

    window.onload = initialize;
        
    function initialize() {
      // initialize the DHTML History
      // framework
      dhtmlHistory.initialize();
      
      // subscribe to DHTML history change
      // events
      dhtmlHistory.addListener(historyChange);
          
      // if this is the first time we have
      // loaded the page...
      if (dhtmlHistory.isFirstLoad()) {
        debug("Adding values to browser "
              + "history", false);
        // start adding history
        dhtmlHistory.add("helloworld",
                         "Hello World Data");
        dhtmlHistory.add("foobar", 33);
        dhtmlHistory.add("boobah", true);
          
        var complexObject = new Object();
        complexObject.value1 =
                      "This is the first value";
        complexObject.value2 =
                      "This is the second data";
        complexObject.value3 = new Array();
        complexObject.value3[0] = "array 1";
        complexObject.value3[1] = "array 2";
          
        dhtmlHistory.add("complexObject",
                         complexObject);


    讓 我們繼續(xù)使用historyStorage 類。類似dhtmlHistory ,historyStorage通過一個(gè)叫historyStorage的單一全局對象來顯示他的功能,這個(gè)對象有幾個(gè)方法來偽裝成一個(gè)hash table, 象put(keyName, keyValue), get(keyName), and hasKey(keyName).鍵名必須是字符,同時(shí)鍵值可以是復(fù)雜的javascript對象或者甚至是xml格式的字符。在我們源碼source code的例子中,我們put() 簡單的XML  到historyStorage 在頁面第一次裝載時(shí)。

    window.onload = initialize;
        
    function initialize() {
      // initialize the DHTML History
      // framework
      dhtmlHistory.initialize();
      
      // subscribe to DHTML history change
      // events
      dhtmlHistory.addListener(historyChange);
          
      // if this is the first time we have
      // loaded the page...
      if (dhtmlHistory.isFirstLoad()) {
        debug("Adding values to browser "
              + "history", false);
        // start adding history
        dhtmlHistory.add("helloworld",
                         "Hello World Data");
        dhtmlHistory.add("foobar", 33);
        dhtmlHistory.add("boobah", true);
          
        var complexObject = new Object();
        complexObject.value1 =
                      "This is the first value";
        complexObject.value2 =
                      "This is the second data";
        complexObject.value3 = new Array();
        complexObject.value3[0] = "array 1";
        complexObject.value3[1] = "array 2";
          
        dhtmlHistory.add("complexObject",
                         complexObject);
                        
        // cache some values in the history
        // storage
        debug("Storing key 'fakeXML' into "
              + "history storage", false);
        var fakeXML =
          '<?xml version="1.0" '
          +      'encoding="ISO-8859-1"?>'
          +      '<foobar>'
          +         '<foo-entry/>'
          +      '</foobar>';
        historyStorage.put("fakeXML", fakeXML);
      }


    然后,如果用戶從這個(gè)頁面漂移走(導(dǎo)航走)又通過返回按鈕返回了,我們可以用get()提出我們存儲(chǔ)的值或者用haskey()檢查他是否存在。

    window.onload = initialize;
        
    function initialize() {
      // initialize the DHTML History
      // framework
      dhtmlHistory.initialize();
      
      // subscribe to DHTML history change
      // events
      dhtmlHistory.addListener(historyChange);
          
      // if this is the first time we have
      // loaded the page...
      if (dhtmlHistory.isFirstLoad()) {
        debug("Adding values to browser "
              + "history", false);
        // start adding history
        dhtmlHistory.add("helloworld",
                         "Hello World Data");
        dhtmlHistory.add("foobar", 33);
        dhtmlHistory.add("boobah", true);
          
        var complexObject = new Object();
        complexObject.value1 =
                      "This is the first value";
        complexObject.value2 =
                      "This is the second data";
        complexObject.value3 = new Array();
        complexObject.value3[0] = "array 1";
        complexObject.value3[1] = "array 2";
          
        dhtmlHistory.add("complexObject",
                         complexObject);
                        
        // cache some values in the history
        // storage
        debug("Storing key 'fakeXML' into "
              + "history storage", false);
        var fakeXML =
          '<?xml version="1.0" '
          +      'encoding="ISO-8859-1"?>'
          +      '<foobar>'
          +         '<foo-entry/>'
          +      '</foobar>';
        historyStorage.put("fakeXML", fakeXML);
      }
      
      // retrieve our values from the history
      // storage
      var savedXML =
                  historyStorage.get("fakeXML");
      savedXML = prettyPrintXml(savedXML);
      var hasKey =
               historyStorage.hasKey("fakeXML");
      var message =
        "historyStorage.hasKey('fakeXML')="
        + hasKey + "<br>"
        + "historyStorage.get('fakeXML')=<br>"
        + savedXML;
      debug(message, false);
    }


    prettyPrintXml() 是一個(gè)第一在例子源碼full example source code中的工具方法。這個(gè)方法準(zhǔn)備簡單的xml顯示在web page ,方便調(diào)試。

    注 意數(shù)據(jù)只是在使用頁面的歷史時(shí)被持久化,如果瀏覽器關(guān)閉了,或者用戶打開一個(gè)新的窗口又再次鍵入了ajax應(yīng)用的地址,歷史數(shù)據(jù)對這些新的web頁面是不 可用的。歷史數(shù)據(jù)只有在用前進(jìn)或回退按鈕時(shí)才被持久化,而且在用戶關(guān)閉瀏覽器或清空緩存的時(shí)候會(huì)消失掉。想真正的長時(shí)間的持久化,請看Ajax MAssive Storage System (AMASS).
    我們的簡單示例已經(jīng)完成。演示他(Demo it)或者下載全部的源代碼(download the full source code.)

    示例2
    我 們的第2個(gè)例子是一個(gè)簡單的模擬ajax email  應(yīng)用的示例,叫O'Reilly Mail,類似Gmail. O'Reilly Mail描述了怎樣使用dhtmlHistory類去控制瀏覽器的歷史,和怎樣使用historyStorage對象去緩存歷史數(shù)據(jù)。

    O'Reilly Mail 用戶接口(user interface)有兩部分。在頁面的左邊是一個(gè)有不同email文件夾和選項(xiàng)的菜單,例如 收件箱,草稿,等等。當(dāng)一個(gè)用戶選擇了一個(gè)菜單項(xiàng),比如收件箱,我們用這個(gè)菜單項(xiàng)的內(nèi)容更新右邊的頁面。在一個(gè)實(shí)際應(yīng)用中,我們會(huì)遠(yuǎn)程取得和顯示選擇的信 箱內(nèi)容,不過在O'Reilly Mail里,我們簡單的顯示選擇的選項(xiàng)。

    O'Reilly Mail使用Really Simple History 框架向?yàn)g覽器歷史里加入菜單變化和更新地址欄,允許用戶利用瀏覽器的回退和前進(jìn)按鈕對應(yīng)用做書簽和跳到上一個(gè)變化的菜單。

    我 們加入一個(gè)特別的菜單項(xiàng),地址簿,來描繪historyStorage 能夠怎樣被使用。地址簿是一個(gè)由聯(lián)系的名字電子郵件和地址組成的javascript數(shù)組,在一個(gè)真實(shí)的應(yīng)用里我們會(huì)取得他從一個(gè)遠(yuǎn)程的服務(wù)器。不過,在 O'Reilly Mail里,我們在本地創(chuàng)建這個(gè)數(shù)組,加入幾個(gè)名字電子郵件和地址,然后把他們存儲(chǔ)在historyStorage 對象里。如果用戶離開了這個(gè)web頁面以后又返回的話,O'Reilly Mail應(yīng)用重新從緩存里得到地址簿,勝過(不得不)再次訪問遠(yuǎn)程服務(wù)器。

    地址簿是在我們的初始化initialize()方法里存儲(chǔ)和重新取得的

    /** Our function that initializes when the page
        is finished loading. */
    function initialize() {
       // initialize the DHTML History framework
       dhtmlHistory.initialize();
      
       // add ourselves as a DHTML History listener
       dhtmlHistory.addListener(handleHistoryChange);

       // if we haven't retrieved the address book
       // yet, grab it and then cache it into our
       // history storage
       if (window.addressBook == undefined) {
          // Store the address book as a global
          // object.
          // In a real application we would remotely
          // fetch this from a server in the
          // background.
          window.addressBook =
             ["Brad Neuberg 'bkn3@columbia.edu'",
              "John Doe 'johndoe@example.com'",
              "Deanna Neuberg 'mom@mom.com'"];
              
          // cache the address book so it exists
          // even if the user leaves the page and
          // then returns with the back button
          historyStorage.put("addressBook",
                             addressBook);
       }
       else {
          // fetch the cached address book from
          // the history storage
          window.addressBook =
                   historyStorage.get("addressBook");
       }


    處 理歷史變化的代碼是簡單的。在下面的代碼中,當(dāng)用戶不論按下回退還是前進(jìn)按鈕handleHistoryChange 都被調(diào)用。我們得到新的地址(newLocation) 使用他更新我們的用戶接口來改變狀態(tài),通過使用一個(gè)叫displayLocation的O'Reilly Mail的工具方法。

    /** Handles history change events. */
    function handleHistoryChange(newLocation,
                                 historyData) {
       // if there is no location then display
       // the default, which is the inbox
       if (newLocation == "") {
          newLocation = "section:inbox";
       }
      
       // extract the section to display from
       // the location change; newLocation will
       // begin with the word "section:"
       newLocation =
             newLocation.replace(/section\:/, "");
      
       // update the browser to respond to this
       // DHTML history change
       displayLocation(newLocation, historyData);
    }

    /** Displays the given location in the
        right-hand side content area. */
    function displayLocation(newLocation,
                             sectionData) {
       // get the menu element that was selected
       var selectedElement =
                document.getElementById(newLocation);
                
       // clear out the old selected menu item
       var menu = document.getElementById("menu");
       for (var i = 0; i < menu.childNodes.length;
                                              i++) {
          var currentElement = menu.childNodes[i];
          // see if this is a DOM Element node
          if (currentElement.nodeType == 1) {
             // clear any class name
             currentElement.className = "";
          }                                      
       }
      
       // cause the new selected menu item to
       // appear differently in the UI
       selectedElement.className = "selected";
      
       // display the new section in the right-hand
       // side of the screen; determine what
       // our sectionData is
      
       // display the address book differently by
       // using our local address data we cached
       // earlier
       if (newLocation == "addressbook") {
          // format and display the address book
          sectionData = "<p>Your addressbook:</p>";
          sectionData += "<ul>";
          
          // fetch the address book from the cache
          // if we don't have it yet
          if (window.addressBook == undefined) {
             window.addressBook =
                   historyStorage.get("addressBook");
          }
          
          // format the address book for display
          for (var i = 0;
                   i < window.addressBook.length;
                         i++) {
             sectionData += "<li>"
                            + window.addressBook[i]
                            + "</li>";                  
          }
          
          sectionData += "</ul>";
       }
      
       // If there is no sectionData, then
       // remotely retrieve it; in this example
       // we use fake data for everything but the
       // address book
       if (sectionData == null) {
          // in a real application we would remotely
          // fetch this section's content
          sectionData = "<p>This is section: "
             + selectedElement.innerHTML + "</p>";  
       }
      
       // update the content's title and main text
       var contentTitle =
             document.getElementById("content-title");
       var contentValue =
             document.getElementById("content-value");
       contentTitle.innerHTML =
                            selectedElement.innerHTML;
       contentValue.innerHTML = sectionData;
    }


    演示(Demo)O'Reilly Mail或者下載(download)O'Reilly Mail的源代碼。

    結(jié)束語
    你現(xiàn)在已經(jīng)學(xué)習(xí)了使用Really Simple History API 讓你的AJAX應(yīng)用響應(yīng)書簽和前進(jìn)回退按鈕,而且有代碼可以作為創(chuàng)建你自己的應(yīng)用的素材。我熱切地期待你利用書簽和歷史的支持完成你的AJAX創(chuàng)造。

    資源
    ·onjava.com:onjava.com
    ·Matrix-Java開發(fā)者社區(qū):http://www.matrix.org.cn/
    ·Download all sample code for this article.
    ·Download the Really Simple History framework.
    ·Demo O'Reilly Mail or download the O'Reilly Mail source code. The full example download also includes more examples for you to play with.
    ·Coding in Paradise: The author's weblog, covering AJAX, DHTML, and Java techniques and new developments in collaborative technologies, such as WikiWikis.


    感謝
    特別的要感謝每個(gè)檢閱這篇文章的the Really Simple History框架的人:
    Michael Eakes, Jeremy Sevareid, David Barrett, Brendon Wilson, Dylan Parker, Erik Arvidsson, Alex Russell, Adam Fisk, Alex Lynch, Joseph Hoang Do, Richard MacManus, Garret Wilson, Ray Baxter, Chris Messina, and David Weekly.
    posted @ 2005-11-25 10:33 Dion 閱讀(1037) | 評論 (0)編輯 收藏

    方法一:

    調(diào)用Windows的DOS命令,從輸出結(jié)果中讀取MAC地址:

    public static String getMACAddress() {

    String address = "";
    String os = System.getProperty("os.name");
    if ( os != null && os.startsWith("Windows")) {
    try {
    String command = "cmd.exe /c ipconfig /all";
    Process p = Runtime.getRuntime().exec(command);
    BufferedReader br =
    new BufferedReader(
    new InputStreamReader(p.getInputStream()));
    String line;
    while ((line = br.readLine()) != null) {
    if (line.indexOf("Physical Address") > 0) {
    int index = line.indexOf(":");
    index += 2;
    address = line.substring(index);
    break;
    }
    }
    br.close();
    return address.trim();
    }
    catch (IOException e) { }
    }
    return address;
    }

    We can replace the "ipconfig" to "ping x.x.x.x" and "arp -a"...We can get the mac list...haha!!

    缺點(diǎn):只能取得服務(wù)器端MAC地址.如果要取得客戶端的MAC地址,需用Applet.只針對MS-WIN系統(tǒng).

     

    方法二:

    可以用JS或vbscript來調(diào)用WMI接口來獲取Client端的MAC地址.


     
     
     
     
     
     
     
      
      

      
      

      


      

       
       
       

      
     

    忘了附上原文的出處了:
    How to get IP address of the browser when its operating behind a proxy/firewall? (applets...activex....??)
    http://www.faqts.com/knowledge_base/view.phtml/aid/9005/fid/125

    關(guān)于WMI的詳細(xì)信息可以參看MSDN:
    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wmisdk/wmi/wmi_tasks_for_scripts_and_applications.asp

    平心而論,WMI的很強(qiáng)大的。原先需要?jiǎng)佑弥亓考壘幊坦ぞ卟拍茏龅降氖拢F(xiàn)在用js/vbscript就可以做了。

    獲取多塊網(wǎng)卡的MAC地址:

    if(objObject.MACAddress != null && objObject.MACAddress != "undefined"){
                             MACAddr = objObject.MACAddress;
                             alert( MACAddr );
                       }

    缺點(diǎn):需要ActiveX支持.對MS-WIN系統(tǒng)有效.

    方法三:

    想137口發(fā)送UDP查詢:

    WINDOWS平臺(tái)的客戶端(當(dāng)獲取時(shí)它轉(zhuǎn)換為服務(wù)端角色),NETBIOS協(xié)議在137口上,我們只要向它的137口發(fā)送UDP查詢,獲取它的返回值就可以獲取到它所有的網(wǎng)卡地址.

    以上內(nèi)容來自dev2dev的討論帖:

    http://dev2dev.bea.com.cn/bbs/thread.jspa?forumID=121&threadID=12941&tstart=0

     

    posted on 2005-01-20 08:50 eamoi 閱讀(636) 評論(1)  編輯 收藏 收藏至365Key 所屬分類: Java

    評論: # Java系統(tǒng)如何獲取客戶端的MAC地址? [TrackBack] 2005-01-20 11:01 | eamoi
    Ping Back來自:blog.csdn.net
    eamoi引用了該文章,地址:http://blog.csdn.net/eamoi/archive/2005/01/20/260611.aspx
    posted @ 2005-11-23 20:33 Dion 閱讀(9761) | 評論 (3)編輯 收藏

    一個(gè)用于J2EE應(yīng)用程序的Backbase Ajax前端

    時(shí)間:2005-11-03
    作者:Mark Schiefelbein
    瀏覽次數(shù): 628
    本文關(guān)鍵字:AjaxRIARich Internet ApplicationbackbaseDev Toolbox Eclipse WebLogic
    文章工具
    推薦給朋友 推薦給朋友
    打印文章 打印文章

      動(dòng)態(tài)HTML技術(shù)已經(jīng)出現(xiàn)了多年。最近,Google的最新Web應(yīng)用程序GMail、Google Suggests和Google Maps,在前端頁面中重新引入了基于標(biāo)準(zhǔn)的DHTML開發(fā)模型。Google證明了,DHTML開發(fā)模型能夠讓開發(fā)人員創(chuàng)建具有可視化吸引力和高度交互 式的Rich Internet Application(豐富網(wǎng)絡(luò)應(yīng)用程序,RIA)。

      Adaptive Path公司的Jesse James Garrett為這個(gè)基于標(biāo)準(zhǔn)的RIA開發(fā)模型創(chuàng)造了術(shù)語Ajax (Asynchronous JavaScript + XML)。與傳統(tǒng)的基于頁面的Web應(yīng)用程序模型相比,Ajax有3點(diǎn)不同之處:

    • 有一個(gè)客戶端引擎擔(dān)任用戶界面(UI)和服務(wù)器之間的中介。
    • 用戶行為由客戶端引擎處理,而不是生成發(fā)往服務(wù)器的頁面請求。
    • XML數(shù)據(jù)在客戶端引擎和服務(wù)器之間傳輸。

      換言之,Ajax解決方案包括一個(gè)客戶端引擎,它用于呈現(xiàn)用戶界面,并使用XML格式與服務(wù)器通信。這個(gè)引擎由很多JavaScript函數(shù)組成,位于Web瀏覽器中,它不需要插件,也不需要用戶安裝。

      基于Ajax的RIA正在迅速成為Web應(yīng)用程序前端的基準(zhǔn),因?yàn)樗梢酝瑫r(shí)提供二者的優(yōu)點(diǎn):豐富性和可達(dá)性。Ajax應(yīng)用程序和桌面應(yīng)用程序 一樣豐富,響應(yīng)高度靈敏,并且可以在一個(gè)頁面上提供所有數(shù)據(jù),無需刷新頁面。它們還擁有基于標(biāo)準(zhǔn)的瀏覽器應(yīng)用程序的可達(dá)性特點(diǎn),這類應(yīng)用程序可以在不具備 瀏覽器插件或客戶端applet的情況下進(jìn)行部署。

      Backbase所提供的Ajax軟件具有以下特點(diǎn):基于標(biāo)準(zhǔn)、功能全面且易于使用。Backbase Presentation Client (BPC)基于Ajax技術(shù),它使用稱為Backbase XML (BXML)的附加標(biāo)簽擴(kuò)展了DHTML。Backbase XML Server Edition for J2EE (BXS)包含了一些服務(wù)器端的組件,利用這些組件,J2EE開發(fā)人員可以快速開發(fā)J2EE應(yīng)用程序的Ajax前端。

      在本文中,我使用Backbase為Java Pet Store開發(fā)了一個(gè)基于Ajax的前端。該案例分析說明了如何使用Backbase技術(shù)作為J2EE應(yīng)用程序的Ajax表示層。您可以查看文中所描述的應(yīng)用程序的在線演示,網(wǎng)址是http://www.backbase.com/xmlserver

    Backbase Ajax表示層

      Web開發(fā)人員應(yīng)該能夠輕松創(chuàng)建具有以下特點(diǎn)的Rich Internet Application (RIA):完全基于HTML標(biāo)準(zhǔn)(W3C),不需要最終用戶安裝插件,速度超快,能夠在所有瀏覽器上進(jìn)行操作,并與J2EE運(yùn)行時(shí)和開發(fā)環(huán)境完全集成。 RIA利用客戶端(Web瀏覽器)資源創(chuàng)建和管理用戶界面,從而為最終用戶提供一個(gè)響應(yīng)靈敏而且具有應(yīng)用程序風(fēng)格的用戶界面。

      這種方法最近被稱為Ajax。Ajax這個(gè)術(shù)語的靈感來源于Gmail、Google Maps和Google Suggests這類應(yīng)用程序,它把現(xiàn)有的瀏覽器技術(shù)提高到了一個(gè)新的水平上。RIA從根本上改進(jìn)了在線應(yīng)用程序的可用性和有效性。Ajax RIA只使用標(biāo)準(zhǔn)的瀏覽器技術(shù)(如JavaScript、XHTML和XMLHttpRequest對象)就做到了這一點(diǎn)。通過使用 XMLHttpRequest,在將數(shù)據(jù)異步加載到界面中時(shí)就無需刷新頁面。

      Backbase在J2EE架構(gòu)中提供一個(gè)Ajax表示層,它結(jié)合了目前的J2EE服務(wù)器和先進(jìn)的富客戶端技術(shù)的優(yōu)點(diǎn)。Backbase表示層 控制了富用戶界面的每個(gè)方面:與最終用戶的交互模型,與后端系統(tǒng)的集成,以及整個(gè)客戶端-服務(wù)器通信。Backbase直接提供了用于聚合來自任意位置的 XML的下一個(gè)范型,將數(shù)據(jù)綁定到先進(jìn)的富用戶界面控件,并在一個(gè)統(tǒng)一的富用戶界面中交付組合應(yīng)用程序。

      Backbase表示層由一個(gè)客戶機(jī)和一個(gè)服務(wù)器組成。Backbase Presentation Client (BPC)是一個(gè)基于Ajax的GUI引擎,它允許開發(fā)人員以聲明性的方式快速構(gòu)建RIA。Backbase XML(BXML)是對XHTML的擴(kuò)展。它為開發(fā)人員提供了交付富前端功能的附加標(biāo)簽(B tag)。Backbase XML Server (BXS)提供一種XML流水線架構(gòu),利用它可以從Web服務(wù)、數(shù)據(jù)庫或Java對象獲取數(shù)據(jù),可以聚合和轉(zhuǎn)換這些數(shù)據(jù),并將其綁定到BPC中的UI元 素。BPC和BXS相結(jié)合,可以在Web瀏覽器和應(yīng)用服務(wù)器之間搭建一座功能強(qiáng)大的橋梁,并提供一個(gè)分布在客戶端和服務(wù)器上的完整的富Internet表 示層。

      圖1說明了在邏輯和物理應(yīng)用程序架構(gòu)中,Backbase所處的位置。應(yīng)用程序由一個(gè)J2EE后端和一個(gè)基于Ajax的RIA前端組成。從邏輯 上說,Backbase提供了表示層,而J2EE提供了業(yè)務(wù)邏輯和數(shù)據(jù)層。從物理上說,表示層分布在客戶端和服務(wù)器上。在客戶端上,Backbase使用 BPC擴(kuò)展了瀏覽器。在服務(wù)器上,Backbase使用BXS擴(kuò)展了應(yīng)用服務(wù)器。

    圖1. Backbase富Internet表示層

    Pet Store案例分析

      我們將使用Java Pet Store作為案例來分析如何為J2EE應(yīng)用程序添加Backbase RIA前端。Java Pet Store Demo是Sun Microsystems提供的一個(gè)示例應(yīng)用程序,其目的是為了演示如何使用Java 2 Platform, Enterprise Edition(J2EE)構(gòu)建Web應(yīng)用程序(詳情請參見http://java.sun.com/developer/releases/petstore)。

      Java Pet Store是業(yè)內(nèi)一個(gè)著名的參考應(yīng)用程序(pet store還有.NET和Flash版本)。由于以下兩個(gè)原因,它成為為J2EE應(yīng)用程序添加基于Ajax的RIA前端的完美案例:

    • Java Pet Store是一個(gè)完整的Web應(yīng)用程序。
    • Sun設(shè)計(jì)Pet Store的目的是演示所有常見的Web應(yīng)用程序功能。通過使用Pet Store作為案例,我可以說明為J2EE應(yīng)用程序添加RIA層的所有方面。

      作為一個(gè)典型的在線商店,它包含以下功能:

      • 瀏覽產(chǎn)品類別。
      • 在購物車中添加和刪除物品。
      • 填寫訂單表單。
      • 提交訂單。
    • Java Pet Store有一個(gè)傳統(tǒng)的HTML前端。
    • 使用RIA前端的目的是提供更簡單和響應(yīng)更靈敏的GUI,以及通常更為豐富的Web用戶體驗(yàn)。我將說明,如何通過Backbase RIA技術(shù)極大地改進(jìn)應(yīng)用程序的前端,同時(shí)無需對后端和總體系統(tǒng)需求做任何修改。

      Pet Store的RIA前端將通過以下方式改善可用性:

    • 把前端變?yōu)橐粋€(gè)單頁面的界面(SPI)。
    • 提供更先進(jìn)的UI控件(如模態(tài)彈出式菜單)。
    • 使用可視化效果(例如,把寵物放入購物車)。
    • 更加有效地利用電腦屏幕的操作區(qū)域。

    RIA Pet Store前端

      在這一節(jié)中,我將討論經(jīng)過改進(jìn)的新Pet Store RIA前端。

      下面的兩個(gè)屏幕快照演示了前端的改進(jìn)。要獲得對Backbase RIA前端更直觀的感受,請?jiān)L問http://www.backbase.com/xmlserver上的在線演示,或者到http://www.backbase.com/download下載Backbase社區(qū)版本。

    下面兩個(gè)圖對兩個(gè)前端進(jìn)行了可視化的比較。圖2顯示的是原來靜態(tài)的多頁面HTML前端。圖3顯示的是新的Backbase SPI前端:

    圖2. 原始HTML前端

    圖3. 新Backbase前端

      Backbase為創(chuàng)建豐富的單頁面Web界面提供了許多可能性。下面列出了一些Pet Store所使用的例子。

    • 選項(xiàng)卡式的單頁面瀏覽
    • 在Web界面上,不同的動(dòng)物種類(狗、貓等等)被表示為不同的選項(xiàng)卡。點(diǎn)擊一個(gè)選項(xiàng)卡就會(huì)打開相應(yīng)的類別,顯示可供出售的寵物。

      在Backbase SPI中,無需刷新頁面就可以打開選項(xiàng)卡。BPC只從服務(wù)器請求所需的數(shù)據(jù),然后更新客戶端的視圖。SPI機(jī)制可以極大地縮短響應(yīng)時(shí)間,讓客戶隨心所欲地在類別之間來回穿梭。

    • 活動(dòng)的多功能界面
    • 界面有三個(gè)主要功能——類別瀏覽、購物車和頁面引導(dǎo)歷史記錄,它們在界面上都是一直可見的。因此,購物者總是能夠查看購物車的當(dāng)前內(nèi)容或最近看過的寵物的記錄。

      這些功能是高度同步的:瀏覽一個(gè)寵物時(shí),歷史記錄將自動(dòng)更新為在記錄中顯示該寵物。定購一個(gè)寵物時(shí),它將被添加到購物車中。上述一切都發(fā)生在客戶端的一個(gè)頁面上(例如,無需重新加載頁面就可以更新界面的各個(gè)部分)。

    • 界面變化的流暢可視化效果
    • 進(jìn)行瀏覽時(shí),客戶將會(huì)看到不斷變化的界面視圖。例如,他可以按照價(jià)格和名稱對寵物進(jìn)行排序。界面需要根據(jù)新的排列順序顯示更新以后的寵物清單。

      在Backbase RIA前端中,以前的視圖被使用可視化效果的新視圖所代替,新視圖向最終用戶顯示什么正在改變。圖4說明了如何通過流暢的定位效果,把按名稱排列的順序轉(zhuǎn)變?yōu)榘磧r(jià)格排列的順序:

      圖4.類別視圖的排列順序轉(zhuǎn)換

    • 用于提高轉(zhuǎn)換速度的信息欄驗(yàn)證

      為了執(zhí)行購買,購買者必須在一份表單中填入個(gè)人詳細(xì)信息。Backbase極大地簡化了這個(gè)購買過程,通過客戶端的信息欄驗(yàn)證提供即時(shí)的反饋,并在提供所有數(shù)據(jù)的過程中提供逐步的指南和概述。

      圖5顯示了在填寫表單的第一個(gè)步驟中,對于e-mail地址信息欄的驗(yàn)證。當(dāng)購買者填寫下一欄時(shí),就會(huì)提供即時(shí)的反饋。

    圖5. 信息欄驗(yàn)證—e-mail欄

    Backbase RIA Pet Store的架構(gòu)

      增強(qiáng)Pet Store(或其他任何Web應(yīng)用程序)的前端時(shí),我們將繼續(xù)依賴于以下兩條架構(gòu)基本原則:

    • 最終用戶仍然使用標(biāo)準(zhǔn)的Web瀏覽器訪問Pet Store,無需添加任何插件。
    • 由J2EE業(yè)務(wù)邏輯和數(shù)據(jù)組成的整個(gè)后端保持不變。

      現(xiàn)有的后端在開發(fā)期間是完全孤立的,而且不會(huì)改變,這個(gè)事實(shí)對于架構(gòu)師和IT管理人員十分有利。通過一個(gè)規(guī)整的、模塊化的架構(gòu),他們將能夠控制風(fēng)險(xiǎn)和成本,同時(shí)顯著提高Web應(yīng)用程序的用戶友好性。

      Backbase的富表示層技術(shù)由兩個(gè)模塊組成,它們將被加入到架構(gòu)中。在客戶端,BPC管理著SPI,并通過異步響應(yīng)事件來處理與最終用戶之 間的交互。在服務(wù)器端,Backbase XML Server這個(gè)靈活的XML管道可以連接到任意服務(wù)器端的數(shù)據(jù)源,包括Web服務(wù)、文件、數(shù)據(jù)庫或本地Java對象。圖6說明了BPC和BXS如何共同 為RIA提供一個(gè)聲明式的、基于XML的端到端表示層。

    圖6. 聲明式的端到端表示層

    Backbase表示客戶端

      BPC是一個(gè)基于Ajax的GUI引擎,它運(yùn)行在標(biāo)準(zhǔn)的Web瀏覽器中。運(yùn)行時(shí),BPC被加載到瀏覽器中,然后它會(huì)接收BXML代碼,構(gòu)造對應(yīng)的B樹,并不斷地把這種表示轉(zhuǎn)換為瀏覽器所呈現(xiàn)的DOM樹。圖7說明了運(yùn)行時(shí)轉(zhuǎn)換過程。

    圖7. BPC運(yùn)行時(shí)

    Backbase XML

      Backbase XML (BXML)是XHTML的擴(kuò)展。開發(fā)人員通過創(chuàng)建BXML應(yīng)用程序來開發(fā)富前端,包括BXML標(biāo)簽、標(biāo)準(zhǔn)的XHTML和CSS。BXML是一種聲明性語言,它包含了XHTML中所沒有的標(biāo)簽(B標(biāo)簽)

      BXML包含用于下列用途的標(biāo)簽:

    • 定義屏幕分區(qū)(<b:panel>)
    • 交互式客戶端控制(<b:menu>)
    • 處理標(biāo)準(zhǔn)的用戶交互事件(onClick)
    • 處理高級的用戶交互事件(拖放和調(diào)整大小)
    • 管理客戶端狀態(tài)
    • 處理可視化效果(使修改任意CSS屬性的過程動(dòng)畫化)
    • 數(shù)據(jù)綁定
    • 使用XSLT的一個(gè)子集進(jìn)行客戶端轉(zhuǎn)換

    用于J2EE的Backbase XML Server

      Backbase XML Server (BXS)是一個(gè)服務(wù)器端的引擎,用于把BPC鏈接到任意J2EE后端。和BPC一樣,BXS是完全基于XML的,其編程是聲明性的。它使用一種XML管道架構(gòu),提供功能強(qiáng)大的服務(wù)器端轉(zhuǎn)換和聚合。

      BXS附帶一些用于訪問最常用的數(shù)據(jù)源(包括Web服務(wù)、數(shù)據(jù)庫、文件系統(tǒng)和本地Java對象)的開箱即用任務(wù)。我們使用Backbase標(biāo)簽對從這些源獲得的數(shù)據(jù)進(jìn)行聚合,然后使用XSLT進(jìn)行轉(zhuǎn)換。結(jié)果以無格式XML數(shù)據(jù)或BXML表示代碼的形式返回給BPC。

      BXS還提供一些應(yīng)用服務(wù),包括身份驗(yàn)證、授權(quán)、日志記錄和用戶跟蹤。圖8顯示了BXS的總體架構(gòu)。

    圖8. BXS架構(gòu)

    Eclipse開發(fā)工具

      為了讓J2EE開發(fā)人員可以只使用一種開發(fā)工具就能創(chuàng)建完整的Web應(yīng)用程序,包括富前端,Backbase提供了一個(gè)Eclipse插件。如圖9所示,該插件提供了在Eclipse中突出顯示語法和Backbase標(biāo)簽代碼自動(dòng)完成的功能。

    圖9. Backbase Eclipse插件

      注意:Eclipse的可視化拖放開發(fā)插件還處在開發(fā)階段。

    部署到BEA WebLogic

      BXS是一個(gè)與標(biāo)準(zhǔn)兼容的J2EE應(yīng)用程序,可以將其部署到任何J2EE應(yīng)用服務(wù)器上。圖10顯示了如何使用WebLogic控制臺(tái)把BXS部署到BEA WebLogic Server

    圖10. 把BXS部署到BEA WebLogic

    實(shí)現(xiàn)Backbase RIA Pet Store

      下面的順序圖包括更多詳細(xì)信息,可以幫助您更好地理解如何實(shí)現(xiàn)Backbase pet store。該順序圖顯示了在應(yīng)用程序的初始化加載期間BPC與BXS之間的交互,如圖11所示,它包括以下4個(gè)步驟:

    • 初始化:用戶在瀏覽器中輸入寵物商店的URL;對BPC進(jìn)行初始化。
    • 應(yīng)用程序布局:觸發(fā)正在構(gòu)造的事件;BPC構(gòu)建整體應(yīng)用程序布局;寵物類別被加載并顯示在選項(xiàng)卡中。
    • 默認(rèn)數(shù)據(jù):默認(rèn)情況下加載狗的類別;最初顯示8張狗的圖片,并帶有向前/向后和排序功能。

      用戶交互:用戶點(diǎn)擊Next按鈕便可顯示編號從9到16的狗圖片。

    圖11.順序圖:富商店前端

    • 初始化
    • 從用戶在瀏覽器中輸入寵物商店的URL開始,這將導(dǎo)致從Web服務(wù)器請求一個(gè)索引頁面。

      索引頁面包含用于實(shí)例化BPC的代碼。索引頁面是XHTML和BXML標(biāo)簽的結(jié)合,包含負(fù)責(zé)啟動(dòng)富前端的初始化事件處理程序。

      BPC初始化代碼:

      <...><body onload="bpc.boot('/Backbase/')">

      <...>

      <xmp b:backbase="true"

      style="display:none;height:100%;">

      <s:loading>

      <div style="position:absolute;width:20%;

      top: 50px;left: 35%;">

      <center>Please wait while loading...

      </center>

      </div>

      </s:loading>

      <...>

      <!-- Include petshop specific behaviors -->

      <s:include b:url="petshop.xml"/>
    • 應(yīng)用程序布局
    • 加載頁面之后,BPC就會(huì)處理正在構(gòu)造的事件,以便開始構(gòu)建總體的應(yīng)用程序布局。

      應(yīng)用程序布局由幾個(gè)面板組成,它們將屏幕劃分為幾個(gè)部分。頂行有一個(gè)固定高度的寵物商店徽標(biāo),接下來的主行是實(shí)際的商店,大小可以調(diào)整。主行分為兩列,左邊一列是產(chǎn)品類別,右邊一列是購物車和歷史記錄。

      產(chǎn)品類別使用選項(xiàng)卡式的導(dǎo)航,每個(gè)寵物類別一個(gè)選項(xiàng)卡。這些選項(xiàng)卡是動(dòng)態(tài)構(gòu)造的,具體過程是通過BXS從一個(gè)XML文件加載類別,然后通過一個(gè)客戶端模板把這些類別轉(zhuǎn)換為選項(xiàng)卡,該轉(zhuǎn)換模板的BPC代碼如下:
      <s:task b:action="transform"

      b:stylesheet="b:xml('categories')"

      b:xmldatasource="b:url('categories.xml')"

      b:destination="id('main-content')"

      b:mode="aslastchild" />

      下面是用于從文件系統(tǒng)把類別加載為XML的BXS代碼:

      <bsd:pipeline equals="categories.xml"

      access="public">

      <bsd:readxml input="file:/categories.xml"/>

      </bsd:pipeline>

      下面是用于創(chuàng)建選項(xiàng)卡式導(dǎo)航的BPC客戶端模板:

      <b:tabrow>

      <s:for-each b:select="categories/category">

      <b:tab>

      <s:attribute b:name="b:followstate">

      id('<s:value-of b:select="name"/>')

      </s:attribute>

      <s:value-of b:select="name"/>

      </b:tab>

      </s:for-each>

      </b:tabrow>

      所有BPC代碼(用藍(lán)色表示)都在客戶端執(zhí)行,而所有BXS代碼(用紅色表示)都在服務(wù)器端執(zhí)行。注意,在本例中,我選擇了在客戶端進(jìn)行轉(zhuǎn)換,因?yàn)? 數(shù)據(jù)集很小。下面我會(huì)給出一個(gè)在服務(wù)器端轉(zhuǎn)換的例子。兩種轉(zhuǎn)換都要用到XSLT語法。Backbase的一個(gè)強(qiáng)大功能就是,前端開發(fā)人員可以根據(jù)情況選擇 在客戶端還是服務(wù)器端處理表示邏輯。語法似乎允許輕松地把代碼從客戶端移到服務(wù)器端,或者反之。

      以上的代碼示例應(yīng)該可以使您了解到,借助于Backbase,Ajax編程變得多么輕松。結(jié)合了DHTML的聲明性方法則更容易上手。使用附加的B 標(biāo)簽不僅可以使界面更加豐富,而且可以使開發(fā)人員的效率更高。諸如<b:tab>之類的單個(gè)標(biāo)簽可以代替多行HTML和JavaScript 代碼,而且保證可以用于各種瀏覽器。

    • 默認(rèn)數(shù)據(jù)
    • 顯示商店前端時(shí),默認(rèn)情況下顯示的是狗的類別。對于本案例,BXS負(fù)責(zé)此項(xiàng)操作。BXS從一個(gè)Web服務(wù)獲得數(shù)據(jù),將其放入緩存,然后生成BXML 表示代碼,再把這些表示代碼發(fā)回給BPC。服務(wù)器還通過一項(xiàng)配置設(shè)置確定一個(gè)頁面上可以顯示的動(dòng)物數(shù)量,并根據(jù)需要加入了Next和Previous按 鈕。最后,服務(wù)器還提供了按照名稱或價(jià)格進(jìn)行排序的功能。

      下面的代碼片斷演示了服務(wù)器功能。外部管道products-overview.xml首先調(diào)用catalog.xml子管道。該子管道要么返回緩 存中的寵物信息,要么調(diào)用另一個(gè)子管道catalog.ws。在緩存沒有命中的情況下,內(nèi)部管道catalog.ws會(huì)從Web服務(wù)獲取寵物信息。

      外部管道獲得寵物信息,然后進(jìn)行XSLT轉(zhuǎn)換,從而以4x2表格顯示這些信息,并帶有Next和Previouse按鈕,然后把BXML格式的代碼發(fā)回給BPC。BPC呈現(xiàn)它接收到的BXML。

      有3個(gè)嵌套的BXS管道分別用于從Web服務(wù)獲取數(shù)據(jù)、將其放入緩存,以及通過XSLT轉(zhuǎn)換創(chuàng)建BXML輸出:

      <bsd:pipeline equals="products-overview.xml"

      access="public"/>

      <bsd:callpipe pipe="catalog.xml"/>
      <bsd:pipeline equals="catalog.xml" access="private">

      <bsd:exist field="{global:petstore-catalog}">

      <bsd:readxml>{global:petstore-catalog}

      </bsd:readxml>

      <bsd:otherwise>

      <bsd:callpipe pipe="catalog.ws"/>
      <bsd:pipeline equals="catalog.ws"

      access="private">

      <bsd:try>

      <bsd:callws wsdl="PetstoreCatalog.wsdl"

      method="getAll"/>

      <bsd:callpipe pipe="strip-root-ns"/>

      <bsd:catch>

      <bsd:xslt xslt="error.xslt">

      <bsd:param name="errormsg">{error:message}

      </bsd:param>

      <bsd:param name="errorsrc">{error:source}

      </bsd:param>

      </bsd:xslt>

      </bsd:catch>

      </bsd:try>

      </bsd:pipeline>
      <bsd:writexml>{global:petstore-catalog}

      </bsd:writexml>

      </bsd:otherwise>

      </bsd:exist>

      </bsd:pipeline>
      <bsd:extractfilter xpath=

      "category[name/text()='{requestparam:category}']"/>

      <bsd:xslt xslt="products/products-overview.xslt">

      <bsd:param name="category">

      {requestparam:category}

      </bsd:param>

      <bsd:param name="stepsize">

      {global:stepsize}

      </bsd:param>

      <bsd:param name="sortorder">

      {requestparam:sortorder}

      </bsd:param>

      <bsd:param name="sortfield">

      {requestparam:sortfield}

      </bsd:param>

      </bsd:xslt>

      </bsd:pipeline>

      代碼示例再次清楚地說明了,借助于Backbase,以聲明性的方式創(chuàng)建Ajax前端是多么容易的事情。例如,只要使用帶有一個(gè)WSDL引用作為屬性的<bsd:callws>標(biāo)簽,就可以調(diào)用一個(gè)Web服務(wù)。

    • 用戶交互
    • 現(xiàn)在,最終用戶可以與寵物商店類別進(jìn)行交互。可以使用Next或Previous按鈕或者排序功能在動(dòng)物類別中進(jìn)行瀏覽。或者,只要點(diǎn)擊一下相應(yīng)的選項(xiàng)卡,就可以轉(zhuǎn)到另一個(gè)類別中。

      BPC和BXS對這種交互進(jìn)行了無縫處理。顯示已經(jīng)在客戶端上的數(shù)據(jù)時(shí),無需與服務(wù)器進(jìn)行任何通信。例如,購物者已經(jīng)從狗類別轉(zhuǎn)到了貓類別,然后再 回到狗類別。客戶端仍然擁有狗類別的數(shù)據(jù),所以可以馬上顯示出來,這使得購物體驗(yàn)變得更完美。其他的類別需要從BXS獲取。BXS要么立即從其緩存返回它 們,要們訪問Web服務(wù)來獲得新數(shù)據(jù)。

      為了詳細(xì)說明Backbase Ajax寵物商店的實(shí)現(xiàn),我把重點(diǎn)放在了初始化的步驟上。完整的寵物商店(可以從http://www.backbase.com/xmlserver下載)還包括以下功能:

      • 商店前端
        • 初始化。
        • 使用從文件加載的寵物類別創(chuàng)建選項(xiàng)卡。
        • 默認(rèn)情況下從Web服務(wù)加載Dog選項(xiàng)卡。
        • 通過緩存瀏覽Dog并對其進(jìn)行排序。
      • 寵物詳細(xì)情況
        • 使用跟蹤聚合來自緩存和數(shù)據(jù)庫的寵物詳細(xì)情況。
        • 創(chuàng)建可視化歷史記錄。
      • 購物車
        • 使用跟蹤添加到購物車。
      • 登錄
        • 登錄和身份驗(yàn)證。
      • 退出
        • 退出和授權(quán)。
        • 確認(rèn)。

    結(jié)束語

      最近有很多人都在研究Ajax。Ajax的優(yōu)點(diǎn)已經(jīng)在實(shí)踐中得到了證明。定制Ajax的缺點(diǎn)在于它的復(fù)雜性和不兼容性。大量客戶端 JavaScript的出現(xiàn)意味著開發(fā)人員很可能陷入到瀏覽器實(shí)現(xiàn)差別的泥潭中去。另外,JavaScript這種語言不適用于復(fù)雜的應(yīng)用程序。

      為了開發(fā)易于管理的、可伸縮的和適應(yīng)未來變化的Ajax解決方案,開發(fā)人員所需使用的工具應(yīng)該具有比定制部件開發(fā)更多的功能。Backbase Ajax軟件提供了一個(gè)功能全面的客戶端GUI管理引擎(Backbase Presentation Client)、一個(gè)靈活的服務(wù)器端XML管道(Backbase XML Server)和一種聲明性的基于標(biāo)簽的UI語言,BXML(Backbase eXtensible Markup Language)。該方法具有幾個(gè)優(yōu)點(diǎn)。

      首先,Backbae易于使用。它的聲明性語言水平地?cái)U(kuò)展了DHTML;它完全對開發(fā)人員隱藏了瀏覽器兼容性的問題;而且它帶有一套開發(fā)和調(diào)試工具。

      其次,Backbase是一個(gè)功能全面的Ajax GUI管理系統(tǒng)。Backbase的先進(jìn)性大大超過了其他Ajax框架,它完全把重點(diǎn)放在提供一個(gè)部件庫或客戶端-服務(wù)器通信(如DWR)上。在控件和客 戶端-服務(wù)器通信的基礎(chǔ)上,Backbase提供了用于如下用途的標(biāo)簽:提供電影效果,隨需應(yīng)變的數(shù)據(jù)加載,數(shù)據(jù)綁定和客戶端的數(shù)據(jù)轉(zhuǎn)換,對于Back和 Forward按鈕的支持,完善的GUI狀態(tài)管理,等等。所有這些功能對于目前的Ajax Web應(yīng)用程序來說都是必需的。

      最后,Backbase是以兼容的方式提供所有客戶端和服務(wù)器端的功能。用戶可以使用富Ajax前端擴(kuò)展現(xiàn)有的應(yīng)用程序,同時(shí)無需修改后端。對于整個(gè)表示層來說,它的架構(gòu)是時(shí)新的、模塊化的,而且它基于XML。

    參考資料

    原文出處

    A Backbase Ajax Front-end for J2EE Applications

    http://dev2dev.bea.com/pub/a/2005/08/backbase_ajax.html

     作者簡介

    Mark Schiefelbein自2005年2月以來一直擔(dān)任Backbase的產(chǎn)品管理主管。Mark極大地推動(dòng)了Backbase Rich Internet Application的全球推廣。
    posted @ 2005-11-23 20:31 Dion 閱讀(913) | 評論 (0)編輯 收藏

         摘要: 原文:http://m.tkk7.com/eamoi/archive/2005/11/07/18566.html (續(xù)前一篇)AJAX開發(fā)簡略 在前一篇文章《AJAX開發(fā)簡略》中,我們講述了如何用AJAX來改進(jìn)設(shè)計(jì)的用戶體驗(yàn)。接下來,我們將講述如何用AJAX來更新文檔,以及處理服務(wù)器返回的XML文檔。我們的最終目的是接收服務(wù)器的返回信息,修改當(dāng)前文檔的內(nèi)容。是時(shí)候讓...  閱讀全文
    posted @ 2005-11-23 20:28 Dion 閱讀(1261) | 評論 (0)編輯 收藏

    僅列出標(biāo)題
    共5頁: 上一頁 1 2 3 4 5 下一頁 
    主站蜘蛛池模板: 久久亚洲中文字幕精品有坂深雪| 4480yy私人影院亚洲| 亚洲乱码在线卡一卡二卡新区| 亚洲中文字幕无码av永久| 亚洲免费福利视频| 亚洲网站在线播放| 亚洲精品免费网站| 亚洲熟妇中文字幕五十中出| 国产特黄特色的大片观看免费视频| 亚洲视频免费播放| 亚洲国产精品综合一区在线| 2021久久精品免费观看| 亚洲免费观看视频| 免费人成在线观看网站| 亚洲成AⅤ人影院在线观看| 鲁啊鲁在线视频免费播放| 亚洲欧洲日本在线| 免费毛片a线观看| 亚洲欧洲日本国产| 日本视频免费在线| 亚洲成人高清在线观看| 国产精品久久久久免费a∨| 亚洲熟女www一区二区三区| 国产一级淫片视频免费看| 亚欧洲精品在线视频免费观看| 韩国欧洲一级毛片免费| 亚洲色图.com| 成人激情免费视频| 亚洲AV成人影视在线观看| 国产精品免费一级在线观看| 中文毛片无遮挡高清免费| 精品无码一区二区三区亚洲桃色| 18禁超污无遮挡无码免费网站国产| 99久久亚洲精品无码毛片| 无码国产精品一区二区免费I6| 老妇激情毛片免费| 久久精品亚洲中文字幕无码网站| 91精品免费国产高清在线| 黄页网址大全免费观看12网站| 亚洲avav天堂av在线不卡| 日本免费一区二区三区最新|