Java Votary

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

          #

          解決JAVA服務器性能問題

          通過負載測試和分析來改善JAVA服務器應用的性能

          作者:Ivan Small

          譯者:xMatrix





          版權聲明:任何獲得Matrix授權的網站,轉載時請務必以超鏈接形式標明文章原始出處和作者信息及本聲明
          作者: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
          關鍵詞: server capacity

          摘要

          改善JAVA服務器的性能需要模擬負載下的服務器。創建一個模擬環境、搜集數據并且分析結果可能是對許多開發人員的挑戰。這篇文章中的示例介紹了JAVA服務器性能分析的概念和工具。作者使用這個示例來研究超額請求次數下內存使用和同步竟爭的影響。
          作者Ivan Small

          項目團隊已經很熟悉如何組織一些具體的任務并完成他們。簡單的性能問題很容易由一個開發人員分離并解決。然而大的性能問題,通常在系統處于高負載情況下發生,就不是這么簡單能處理的了。這些問題需要一個獨立的測試環境、一個模擬的負載,并且需要仔細地分析和跟蹤。

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

          改善服務器的性能

          服 務器的性能改善是依賴于數據的。沒有可靠的數據基礎而更改應用或環境會導致更差的結果。分析器提供有用的JAVA服務器應用信息,但由于從單用戶負載下的 數據與多用戶負載下得到的數據是完全不同的,這導致分析器的數據并不精確。在開發階段使用分析器來優化應用的性能是一個好的方式,但在高負載下的應用分析 可以取到更好的效果。


          在負載下分析服務器應用的性能需要一些基本的元素:
                  1、可控的進行應用負載測試的環境。
                  2、可控的人造負載使得應用滿負荷運行。
                  3、來自監視器、應用和負載測試工具自身的數據搜集。
                  4、性能改變的跟蹤。

          不要低估最后一個需求(性能跟蹤)的重要性因為如果不能跟蹤性能你就不能實際的管理項目。性能上10-20%的改善對單用戶環境來說并沒有什么不同,但對支持人員來說就不一樣了。20%的改善是非常大的,而且通過跟蹤性能的改善,你可以提供重要的反饋和持續跟蹤。
          雖然性能跟蹤很重要,但有時為了使后續的測試更加精確而不得不拋棄先前的測試結果。在性能測試中,改善負載測試的精確性可能需要修改模擬環境,而這些變化是必須的,通過變化前后的負載測試你可以觀察到其中的轉變。


          可控的環境        
          可 控的環境最少也需要兩臺獨立的機器和第三臺控制的機器。其中一臺用來生成負載,另一臺作為控制機與前一臺建立測試應用并接受反饋,第三臺機器運行應用。此 外,負載和應用機器間的網絡應該與局域網分開。控制機接受運行應用機器的反饋如操作系統、硬件使用率、應用(特別是VM)的狀態。

          負載模擬
          最精確的模擬通常用實際的用戶數據和WEB服務器端的訪問日志。如果你還沒有實際布署或者缺少實際的用戶數據,你可以通過構造類似的場景或詢問銷售和產品管理團隊或做一些有依據的猜想。協調負載測試和實際用戶體驗是一個持續的過程。

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

          串行10個請求的另一個原因是實際應用中不會只有一個HTTP請求。單一而又分離的請求可以影響環境中的許多因素。對Tomcat來說,會為每一個請求創建一個會話,并且HTTP協議允許不同的請求重用連接。我會修改一下負載測試來避免混洧。

          GrinderServlet類不會執行任何排序操作,但這個需求在大部分應用中都很普通。在這些應用中,你需要創建模擬的數據集并且用他們來構造相關用例的負載測試。

          例如,如果用例涉及到用戶登錄一個WEB應用,從可能的用戶列表中選取隨機的用戶會只使用一個用戶更精確。否則,你可能不經意地使用了系統緩存或其他的優化或一些微妙的東西,而這會使得結果不正確。

          負載測試軟件
          負 載測試軟件可以構造測試場景并且對服務進行負載測試。我會在下面的示例中使用OpenSTA測試軟件。這軟件簡單易學,結果也很容易導出,并且支持參數化 腳本,還可以監視信息的變化,他的主要缺點是基于Windows,但在這兒不是個問題。當然還有很多可選項如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();
             }
          }


          類 很簡單,但他們會產生兩個很常見的問題。咋一看瓶頸可能由grindCPU()方法的同步修飾符引起,但實際上內存消耗才是真正的問題所在。如圖1,我的 第一個負載測試顯示了常見的負載變化。在這里負載變化很重要因為你正在模擬一個高的負載。這種熱身的方式也更精確因為避免了JSP編譯引起的問題。我通常 習慣于在進行負載測試前先進行單用戶模擬。

          image
          Figure 1        

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

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

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

          監視

          在生成合理的用戶負載后,監視工具需要收集進程的運行狀況。在我的測試環境中可以收集到各種有用的信息:

          1、        所有計算機、網絡設備
          2、        等等的使用率
          3、        JVM的統計數據。
          4、        個別JAVA方法所花費的時間。
          5、        數據庫性能信息,6、        包括SQL查詢的統計。
          7、        其他應用相關的信息

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


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

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


          同步瓶頸

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

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

          image
          Figure 2        

          在圖2中,你會注意到性能下降了。雖然我使用了更多的CPU,但吞吐量和失敗數都更差了。雖然垃圾回收周期變了,但每秒依然需要回收100M。顯然我們還沒有找到主要的瓶頸。
          非 竟爭的同步相對于簡單的函數調用還是很費時的。竟爭性的同步就更費時了,因為除了內存需要同步外,VM還需要維護等待的線程。在這種狀況下,這些代價實際 上要小于內存瓶頸。實際上,通過消除了同步瓶頸,VM內存系統承擔了更多的壓力最后導致更差的吞吐量,即使我使用了更多的CPU。顯然最好的方式是從最大 的瓶頸開始,但有時這也不是很容易確定的。當然,確保VM的內存處理足夠正常也是一個好的開始方向。

          內存瓶頸

          現在我會首先也定位內存問題。列表3是GrinderServlet的重構版本,使用了StringBuffer實例。圖3顯示了測試結果。

          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并不是一個好主意,但這里我只是為了重現一些常見的問題,而不量提供解決方案。內存數據已經從圖上消失了因為測試中沒有垃 圾回收器運行。吞吐量戲劇性的增加而CPU使用率又回到了50%。列表3不只是優化了內存,但我認為主要了改善了過度的內存消耗。

          檢視同步瓶頸

          列表4另一個GrinderServlet類的重構版本,實現了一個小的資源池。圖4顯示了測試結果。
          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資源。竟爭和非竟爭性同步都是費時的,但通常最大的同步消耗是減少了系統的可伸縮性。我的負載測試不再滿足系統的需求了,因此我增加了虛擬的用戶數,如圖5 所示。

          image
          Figure 5        


          在圖5 中吞吐量在負載達到飽和時下降了一些然后在負載減少時又提高了。此外注意到測試使得CPU使用率達到100%,這意味著測試超過了系統的最佳吞吐量。負載測試的一個產出是性能計劃,當應用的負載超過他的容量時會產生更低的吞吐量。


          水平可伸縮性

          水平伸縮允許更大的性能,但并不一定是費用相關的。運行在多個服務器上的應用通常比較運行在單個VM上的應用復雜。但水平伸縮支持在性能上的最大增加。

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

          image
          Figure 6        


          小結

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

          又一次我碰得更難的問題,應用只能達到所預期性能的1/100。但通過前期檢測到的問題和認識到負載測試的必要性,這個問題很快被解決了。負載測試相對于整個軟件開發的花費并不多,但其所歸避的風險就高多了。

          關于作者
          Ivan Small擁有14年的軟件開發經驗。他在LBNL從開發Supernovae Cosmology Project開始他的職業生涯。這個項目是導致反重力和無限擴展宇宙理論被發現的兩個項目之一。他從此工作于數據挖掘和企業級JAVA應用。現在他是 nnovative Interfaces公司的首席軟件工程師。

          資源
          ·javaworld.com:javaworld.com
          ·Matrix-Java開發者社區:http://www.matrix.org.cn/
          ·JAVA性能調優第二版:http://www.amazon.com/exec/obidos/ASIN/0596003773/javaworld
          ·并發編程技術:JAVA并發編程第二版:http://www.amazon.com/exec/obidos/ASIN/0201310090/javaworld
          ·JAVA網站分析:JAVA網站的性能分析:http://www.amazon.com/exec/obidos/ASIN/0201844540/javaworld
          ·JAVA性能:高性能JAVA平臺計算:http://www.amazon.com/exec/obidos/ASIN/0130161640/javaworld
          ·JAVA2性能和術語指南:http://www.amazon.com/exec/obidos/ASIN/0130142603/javaworld
          ·BEA WebLogic服務器性能調優,包含有用的一般信息:BEA WebLogic服務器上J2EE應用性能測試:http://www.amazon.com/exec/obidos/ASIN/1904284000/javaworld
          ·JAVA性能調優:http://www.javaperformancetuning.com
          ·過度的JAVA同步:“輕量級線程”:http://www-106.ibm.com/developerworks/java/library/j-threads1.html
          ·負載和性能測試工具:http://www.softwareqatest.com/qatweb1.html#LOAD
          posted @ 2005-12-02 21:59 Dion 閱讀(5792) | 評論 (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 閱讀(1238) | 評論 (1)編輯 收藏

          架構設計師與 SOA , 第 2 部分

          developerWorks
          文檔選項
          將此頁作為電子郵件發送

          將此頁作為電子郵件發送

          未顯示需要 JavaScript 的文檔選項


          對此頁的評價

          幫助我們改進這些內容


          王 強 , IBM中國軟件開發實驗室 - SOA Design Center

          2005 年 11 月 24 日

          本系列的第 1 部分 介紹了有關架構設計師以及 SOA 架構的知識,分析了 SOA 架構師在設計 SOA 系統架構時有哪些應該特別注意的地方。本文將延續第一部分的內容,向您介紹了 SOA 為企業級架構設計帶來的影響,以及在構建基于 SOA 架構的企業系統時應該怎樣保證所構建的系統架構能夠滿足系統中不同的服務級別需求。

          1. SOA 為企業級架構設計帶來的影響

          1.1 SOA 的特點及其使用范圍

          SOA 既不是一種語言,也不是一種具體的技術,它是一種新的軟件系統架構模型。 SOA 最主要的應用場合在于解決在Internet環境下的不同商業應用之間的業務集成問題。Internet環境區別于Intranet環境的幾個特點主要是:

          (a)大量異構系統并存,不同計算機硬件工作方式不同,操作系統不同、編程語言也不同;

          (b)大量、頻繁的數據傳輸的速度仍然相對較緩慢并且不穩定;

          (c)無法完成服務(service)的版本升級,甚至根本就無法知道互聯網上有哪些機器直接或者間接的使用某個服務。

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

          1.2 SOA 架構的分層模型

          接下來簡要介紹一下 SOA 系統中的分層模型,整個 SOA 架構的分層模型如圖2所示。




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



          回頁首


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

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

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


          圖3
          圖3

          經過 SOA 架構設計師分析和抽象的服務級別需求主要分為以下幾類:

          • 性能是指系統提供的服務要滿足一定的性能衡量標準,這些標準可能包括系統反應時間以及處理交易量的能力等;
          • 可升級性是指當系統負荷加大時,能夠確保所需的服務質量,而不需要更改整個系統的架構;
          • 可靠性是指確保各應用及其相關的所有交易的完整性和一致性的能力;
          • 可用性是指一個系統應確保一項服務或者資源永遠都可以被訪問到;
          • 可擴展性是指在不影響現有系統功能的基礎上,為系統填加新的功能或修改現有功能的能力;
          • 可維護性是指在不影響系統其他部分的情況下修正現有功能中問題或缺陷,并對整個系統進行維護的能力;
          • 可管理性是指管理系統以確保系統的可升級性、可靠性、可用性、性能和安全性的能力;
          • 安全性是指確保系統安全不會被危及的能力。

          1) 性能

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

          2) 可升級性

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

          3) 可靠性

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

          4) 可用性

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

          5) 可擴展性

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

          6) 可維護性

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

          7) 可管理性

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

          8) 安全性

          安 全性是指確保系統安全不會被危及的能力。目前,安全性應該說是最困難的系統質量控制點。這是因為安全性不僅要求確保系統的保密和完整性,而且還要防止影響 可用性的服務拒絕(Denial-of-Service)攻擊。這就要求當 SOA 架構設計師在構建一個架構時,應該把整體系統架構盡可能地分割成各個子功能模塊,在將一些子功能模塊暴露為外部用戶可見的服務的時候,要圍繞各個子模塊構 建各自的安全區,這樣更便于保證整體系統架構的安全。如果一個子模塊受到了安全攻擊,也可以保證其他模塊相對安全。如果企業 SOA 架構中的一些服務是由Web Service實現的,在考慮這些服務安全性的時候也要同時考慮效率的問題,因為WS-Security會為Web Service帶來一定的執行效率損耗。



          回頁首


          3.結束語

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



          回頁首


          參考資料

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


          回頁首


          關于作者


          王 強,IBM軟件工程師,工作在IBM中國軟件開發實驗室 - SOA Design Center,從事Incubator及 SOA ,Grid項目的工作,對J2EE架構、 SOA 架構、MDA/MDD以及網格計算等技術有較深入的研究。 聯系方式:wangq@cn.ibm.com

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

          架構設計師與SOA, 第 1 部分

          developerWorks
          文檔選項
          將此頁作為電子郵件發送

          將此頁作為電子郵件發送

          未顯示需要 JavaScript 的文檔選項


          對此頁的評價

          幫助我們改進這些內容


          王 強 , IBM中國軟件開發實驗室 - SOA Design Center

          2005 年 11 月 17 日

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

          1. 什么是架構?什么是基于SOA的架構?

          1.1 什么是架構

          從架構設計師的角度來看,架構就是一套構建系統的準則。通過這套準則,我們可以把一個復雜的系統劃分為一套更簡單的子系統的集合,這些子系統之間應該保持相互獨立,并與整個系統保持一致。而且每一個子系統還可以繼續細分下去,從而構成一個復雜的企業級架構。

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

          1.2 什么是基于SOA的架構

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

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


          圖1
          圖1

          因此,SOA架構本質上來說體現了一種復合的概念:它不僅為一個企業中商業流程的組織和實現提供了一種指導模式,同時也為具體的底層service開發提供了指導。



          回頁首


          2. SOA架構設計師的角色

          2.1 SOA架構設計師應該具備什么?

          談 到SOA架構設計師的角色,我們首先要了解架構設計師應具有的能力。總體上來說,一個好的架構設計師不僅應該是一個成熟的,具有實際經驗的并具有快速學習 能力的人,而且他還應該具有良好的管理能力和溝通能力。只有具備了必需的能力,架構設計師才能在關鍵的時刻作出困難的決定,這就是一名架構設計師應該承擔 的責任。從角色上來看,SOA 架構師不僅會負責端到端的服務請求者和提供者的設計,并且會負責對系統中非功能服務請求的調研和表述。

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

          2.2 什么是SOA架構設計師的職責?

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

          作為SOA架構設計師必須要能夠領導整個開發團隊,這樣才能保證設計和開發人員是按照構建好的系統架構來開發整個系統的,這一點十分 的重要。這就要求一名架構設計師不僅要有很好的技術洞察力,同時還要具有一定的項目管理和項目實施的能力。在系統開發的過程中,架構設計師必須要有良好的 溝通和表達能力,這就體現在由架構設計師構建的系統模型是否具有很好的可讀性和易理解性。如果由架構設計師構造出的系統模型不是很清晰的話,就可能會影響 設計和開發人員對于整個系統架構的理解。為了避免這種情況的出現,定期由架構設計師主持的開發團隊內部討論是十分重要的。



          回頁首


          3. 構建SOA架構時應該注意的問題

          3.1 原有系統架構中的集成需求

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

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

          3.2 服務粒度的控制以及無狀態服務的設計

          當SOA架構師構建一個企業級的SOA系統架構的時候,關于系統中最重要的元素,也就是SOA系統中的服務的構建有兩點需要特別注意的地方:首先是對于服務粒度的控制,另外就是對于無狀態服務的設計。

          服務粒度的控制

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

          無狀態服務的設計

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

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



          回頁首


          4. 結束語

          本文簡要介紹了有關架構設計師以及SOA架構的知識,分析了SOA架構師在設計SOA系統架構時有哪些應該特別注意的地方。

          本 文的第二部分將向您介紹在構建基于SOA架構的企業系統時應該怎樣保證所構建的系統架構能夠滿足系統中不同的服務級別需求。從架構設計師的角度,SOA是 一種新的設計模式,方法學。因此,SOA本身涵蓋了很多的內容,也觸及到了系統整體架構設計、實現、維護等各個方面。本文的內容只是涉及到了有關于架構方 面的一部分內容,希望能對廣大的SOA系統開發設計人員起到一定的幫助作用。



          回頁首


          參考資料

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


          回頁首


          關于作者


          王 強,IBM軟件工程師,工作在IBM中國軟件開發實驗室 - SOA Design Center,從事Incubator及SOA,Grid項目的工作,對J2EE架構、SOA架構、MDA/MDD以及網格計算等技術有較深入的研究。 聯系方式:wangq@cn.ibm.com

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

          J2EE中軟件基礎結構的瓶頸

          作者:Deepak Goel

          譯者:xMatrix


          版權聲明:任何獲得Matrix授權的網站,轉載時請務必以超鏈接形式標明文章原始出處和作者信息及本聲明
          作者: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
          關鍵詞: bottlenecks J2EE


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

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

          理論基礎

          任何J2EE應用通常都有下面幾個層次,如圖1:
          1、硬件基礎結構資源(處理器、內存、磁盤和網絡)
          2、軟件基礎結構資源(JVM,WEB服務器、應用服務器、數據庫服務器)
          3、軟件應用(J2EE應用)

          imageFigure 1. Snapshot of a J2EE system

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

          image
          Figure 2. The hardware pipe becomes a bottleneck


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

          image
          Figure 3. Software pipe becomes a bottleneck

          應用服務器如何工作?

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

          image
          Figure 4. Internals of an application server

          實驗

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

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

          image




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


          解決方案

          同一機器上的群集

          當吞吐量滿載了應用服務器的一個實例時,需要增加一個實例來減輕這種問題。這個方案如圖5。

          image
          Figure 5. Instance clusters on the same hardware box

          當前機器的CPU使用率只有40%因而有足夠空間來增加一個實例。我們可以發現在增加了實例后,吞吐量也增加了50%,如表2

          image

          在不同機器的群集
          當吞吐量滿載了應用服務器的一個實例時,機器的CPU使用率只有40%。因為8CPU的機器未完全利用,所以我們測試一下更低配置的機器。現在我們使用兩個4CPU的機器,如圖6。

          image
          Figure 6. Instance clusters on different hardware boxes


          我們發現4CPU機器的CPU使用率已達到80%,再增加實例也沒有什么用處了。因此我們又增加一臺4CPU的機器來運行應用的實例。在增加機器后,吞吐量幾乎翻了一倍。在這里我們確信數據庫服務器不會成為瓶頸。

          注:在上面的兩臺機器的測試中,負載平衡是通過一種不增加應用服務器實例功能負載的方式來處理的。但是在實際的生產環境中很難做到。

          因為我們觀察到8CPU的機器被沒有被完全利用,所以我們使用4CPU的機器重新測試了一遍。測試的結果可以在下表中看到,分別對應配置1和2。4CPU的配置幾乎被完全利用了,這意味著使用4CPU的配置比8CPU的配置更實際,因為前者花費更少。

          image

          小結

          這 些實驗指出了軟件基礎結構如應用服務器實例可能成為瓶頸,并給出了一些解決方案來減輕這種問題(包括在相同或不同機器上的群集)。這個問題需要在J2EE 應用的負載計劃或大小確定時優先考慮,因為這直接影響到應用的擴展性。這種想法很重要,下面我通過一個情景對話來表達。

          項目經理:你的意思是應用服務器(代表軟件基礎結構)會成為系統的瓶頸。
          性能架構師:是的
          項目經理:那為什么這種情況不是經常發生。
          性能架構師:是的,因為有時候瓶頸首先發生在硬件或應用本身。這時候應用服務器還沒有使用到所有的擴展性。
          項目經理:這是事實。那么解決這種瓶頸的方法就是使用群集。如果有足夠的硬件資源就在同一臺機器上跑群集否則在多臺機器中配置。
          性能架構師:是的。這也是在這篇文章中所得到的。

          資源
          ·onjava.com:onjava.com
          ·Matrix-Java開發者社區:http://www.matrix.org.cn/


          Deepak Goel是Infosys技術有限公司軟件工程實驗室的技術架構師。

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

          Quartz從入門到進階

          作者:Cavaness

          譯者:David_w_johnson


          版權聲明:任何獲得Matrix授權的網站,轉載時請務必以超鏈接形式標明文章原始出處和作者信息及本聲明
          作者: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
          關鍵詞: Quartz


          Quartz

          Quartz 是一個開源的作業調度框架,它完全由java寫成,并設計用于J2SE和J2EE應用中。它提供了巨大的靈活性而不犧牲簡單性。你能夠用它來為執行一個作 業而創建簡單的或復雜的調度。它有很多特征,如:數據庫支持,集群,插件,EJB作業預構建,JavaMail及其它,支持cron-like表達式等 等。

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


          你 曾經需要應用執行一個任務嗎?這個任務每天或每周星期二晚上11:30,或許僅僅每個月的最后一天執行。一個自動執行而無須干預的任務在執行過程中如果發 生一個嚴重錯誤,應用能夠知到其執行失敗并嘗試重新執行嗎?你和你的團隊是用java編程嗎?如果這些問題中任何一個你回答是,那么你應該使用 Quartz調度器。

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


          Quartz讓作業調度簡單

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

          public void execute(JobExecutionContext context) 
               throws JobExecutionException;


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

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

          Quartz的發展史

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

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

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

          例1.quartz.properties文件允許修改Quartz運行環境:

          #===============================================================
          # 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目錄中,就可以創建作業了。然而,在做這之前,我們暫且回避一下先簡短討論一下Quartz架構。

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

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

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

          這種可能性是無窮的,正事這種無限可能性使得框架功能如此強大。Quartz給你提供了一個機制來建立具有不同粒度的、可重復的調度表,于是,你只需創建一個java類,這個類被調用而執行任務。

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


          有效作業存儲

          Quartz 提供兩種基本作業存儲類型。第一種類型叫做RAMJobStore,它利用通常的內存來持久化調度程序信息。這種作業存儲類型最容易配置、構造和運行。對 許多應用來說,這種作業存儲已經足夠了。然而,因為調度程序信息是存儲在被分配給JVM的內存里面,所以,當應用程序停止運行時,所有調度信息將被丟失。 如果你需要在重新啟動之間持久化調度信息,則將需要第二種類型的作業存儲。

          第二種類型的作業存儲實際上提供兩種不同的實現,但兩種實現一 般都稱為JDBC作業存儲。兩種JDBC作業存儲都需要JDBC驅動程序和后臺數據庫來持久化調度程序信息。這兩種類型的不同在于你是否想要控制數據庫事 務或這釋放控制給應用服務器例如BEA's WebLogic或Jboss。(這類似于J2EE領域中,Bean管理的事務和和容器管理事務之間的區別)
          這兩種JDBC作業存儲是:

          ·        JobStoreTX:當你想要控制事務或工作在非應用服務器環境中是使用
          ·        JobStoreCMT:當你工作在應用服務器環境中和想要容器控制事務時使用。

          JDBC作業存儲為需要調度程序維護調度信息的用戶而設計。

          作業和觸發器

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

          CronTrigger是基于Calendar-like調度的。當你需要在除星期六和星期天外的每天上午10點半執行作業時,那么應該使用CronTrigger。正如它的名字所暗示的那樣,CronTrigger是基于Unix克隆表達式的。

          作為一個例子,下面的Quartz克隆表達式將在星期一到星期五的每天上午10點15分執行一個作業。
          0 15 10 ? * MON-FRI

          下面的表達式
          0 15 10 ? * 6L 2002-2005
          將在2002年到2005年的每個月的最后一個星期五上午10點15分執行作業。

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


          調度一個作業

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

          首先創建一個Job類,將執行FTP和Email邏輯。下例展示了Quartz的Job類,它實現了org.quartz.Job接口。

          例2.從FTP站點下載文件和發送email的Quartz作業

          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保持很簡單。我們為這個例子創建了一個叫做JobUtil的實用類。它不是Quartz的組成部分,但對構建各種作 業能重用的實用程序庫來說是有意義的。我們可以輕易將那種代碼組織進作業類中,quarts 調度器一樣好用,因為我們一直在使用quarts,所以那些代碼可繼續重用。

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

          用調度器調用作業

          首先創建一個作業,但為使作業能被調度器調用,你得向調度程序說明你的作業的調用時間和頻率。這個事情由與作業相關的觸發器來完成。因為我們僅僅對大約每60秒循環調用作業感興趣,所以打算使用SimpleTrigger。

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

          例3.Quartz作業通過Quartz調度器而被調度

          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();
              }
          }



          編程調度同聲明性調度

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

          Quartz 框架有一個插件,這個插件負責讀取xml配置文件。xml配置文件包含了關于啟動Quartz應用的作業和觸發器信息。所有xml文件中的作業連同相關的 觸發器都被加進調度器。你仍然需要編寫作業類,但配置那些作業類的調度器則非常動態化。例4展示了一個用申明性方式執行與例3代碼相同的邏輯的xml配置 文件。

          例4.能使用xml文件調度的作業

          <?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代碼作個比較,它們從概念上來看是相同的。使用例4式的申明性方法的好處是維護變得極其簡單,只需改變xml配置文件和重新啟動Quartz應用即可。無須修改代碼,無須重新編譯,無須重新部署。

          有狀態和無狀態作業

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

          Quartz框架的其他特征

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

          監聽器和插件

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

          一旦你的一個具體監聽被調用,你就能使用這個技術來做一些你想要在 監聽類里面做的事情。例如,你如果想要在每次作業完成時發送一個電子郵件,你可以將這個邏輯寫進作業里面,也可以JobListener里面。寫進 JobListener的方式強制使用松耦合有利于設計上做到更好。

          Quartz插件是一個新的功能特性,無須修改Quartz源碼便可 被創建和添加進Quartz框架。他為想要擴展Quartz框架又沒有時間提交改變給Quartz開發團隊和等待新版本的開發人員而設計。如果你熟悉 Struts插件的話,那么完全可以理解Quartz插件的使用。

          與其Quartz提供一個不能滿足你需要的有限擴展點,還不如通過使用插件來擁有可修整的擴展點。

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

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

          幸運的是一些開發者已正影響著單機Quartz web應用,它用來更好地管理調度器環境。構建在若干個流行開源框架如Struts和Spring之上的圖形用戶界面支持很多功能,這些功能都被包裝進一個簡單接口。GUI的一個畫面如圖1所示:


          image
          圖1.Quartz Web應用允許比較容易地管理Quartz環境。

          Quartz的下一步計劃

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

          了解更多Quartz特征

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

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

          資源
          ·onjava.com:onjava.com
          ·Matrix-Java開發者社區:http://www.matrix.org.cn/

          Chuck Cavaness畢業于Georgia Tech并獲得計算機科學與技術學位。他在健康,銀行,B2B領域開發過基于java的企業系統。同時也是O'Reilly出版的兩本書 Programming Jakarta Struts 和 Jakarta Struts Pocket Reference的作者。

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

          作者:Brad Neuberg

          譯者:boool


          版權聲明:任何獲得Matrix授權的網站,轉載時請務必以超鏈接形式標明文章原始出處和作者信息及本聲明
          作者: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
          關鍵詞: ajax;bookmarks;back button


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

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

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

          存在的問題
          書簽和回退按鈕在傳統的多頁面的web應用上能順利的運行。當用戶在網站上沖浪時,他們的瀏覽器地址欄能更新URL,這些URL可以被粘貼到的email或者添加到書簽以備以后的使用。回退和前進按鈕也可以正常運行,這可以使用戶在他們訪問的頁面間移動。

          AJAX應用是與眾不同的,然而,他也是在單一web頁面上成熟的程序。瀏覽器不是為AJAX而做的—AJAX他捕獲過去的事件,當web應用在每個鼠標點擊時刷新頁面。

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

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

          目前存在的幾個AJAX框架可以幫助我們做書簽和發布歷史,然而所有的框架都因為他們的實現而被幾個重要的bug困擾(請看Coding in Paradise: AJAX History Libraries 得知詳情)。此外,許多AJAX歷史框架集成綁定到較大的庫上,比如Backbase 和 Dojo,這些框架提供了與傳統AJAX應用不同的編程模型,強迫開發者去采用一整套全新的方式去獲得瀏覽器的歷史相關的功能。

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

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

          DhtmlHistory 類提供了一個對AJAX應用提取歷史的功能。.AJAX頁面add() 歷史事件到瀏覽器里,指定新的地址和關聯歷史數據。DhtmlHistory 類用一個錨的hash表更新瀏覽器現在的URL,比如#new-location ,然后用這個新的URL關聯歷史數據。AJAX應用注冊他們自己到歷史監聽器里,然后當用戶用前進和后退按鈕導航的時候,歷史事件被激發,提供給瀏覽器新 的地址和調用add()持續保留數據。

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

          讓我們立即進入一個簡單的例子吧。

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

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


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

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

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


          然后,開發者使用dhtmlHistory.addListener()方法去訂閱歷史改變事件。這個方法獲取一個javascript回調方法,當一個DHTML歷史改變事件發生時他將收到2個自變量,新的頁面地址,和任何可選的而且可以被關聯到這個事件的歷史數據。

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


          historyChange()方法是簡單易懂得,它是由一個用戶導航到一個新地址后收到的新地址(newLocation)和一個關聯到事件的可選的歷史數據historyData 構成的。

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


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

          一個開發者使用add()方法加入歷史事件。加入一個歷史事件包括根據歷史的改變指定一個新的地址,就像"edit:SomePage"標記, 還提供一個事件發生時可選的會被存儲到歷史數據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 ()方法被調用后,新地址立刻被作為一個錨值顯示在用戶的瀏覽器的URL欄里。例如,一個AJAX web頁面停留在http://codinginparadise.org/my_ajax_app,調用了dhtmlHistory.add ("helloworld", "Hello World Data" 后,用戶將在瀏覽器的URL欄里看到下面的地址
          http://codinginparadise.org/my_ajax_app#helloworld

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

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

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

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

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

          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);


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

          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);
            }


          然后,如果用戶從這個頁面漂移走(導航走)又通過返回按鈕返回了,我們可以用get()提出我們存儲的值或者用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() 是一個第一在例子源碼full example source code中的工具方法。這個方法準備簡單的xml顯示在web page ,方便調試。

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

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

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

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

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

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

          /** 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");
             }


          處 理歷史變化的代碼是簡單的。在下面的代碼中,當用戶不論按下回退還是前進按鈕handleHistoryChange 都被調用。我們得到新的地址(newLocation) 使用他更新我們的用戶接口來改變狀態,通過使用一個叫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的源代碼。

          結束語
          你現在已經學習了使用Really Simple History API 讓你的AJAX應用響應書簽和前進回退按鈕,而且有代碼可以作為創建你自己的應用的素材。我熱切地期待你利用書簽和歷史的支持完成你的AJAX創造。

          資源
          ·onjava.com:onjava.com
          ·Matrix-Java開發者社區: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.


          感謝
          特別的要感謝每個檢閱這篇文章的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 閱讀(1045) | 評論 (0)編輯 收藏

          方法一:

          調用Windows的DOS命令,從輸出結果中讀取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!!

          缺點:只能取得服務器端MAC地址.如果要取得客戶端的MAC地址,需用Applet.只針對MS-WIN系統.

           

          方法二:

          可以用JS或vbscript來調用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

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

          平心而論,WMI的很強大的。原先需要動用重量級編程工具才能做到的事,現在用js/vbscript就可以做了。

          獲取多塊網卡的MAC地址:

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

          缺點:需要ActiveX支持.對MS-WIN系統有效.

          方法三:

          想137口發送UDP查詢:

          WINDOWS平臺的客戶端(當獲取時它轉換為服務端角色),NETBIOS協議在137口上,我們只要向它的137口發送UDP查詢,獲取它的返回值就可以獲取到它所有的網卡地址.

          以上內容來自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系統如何獲取客戶端的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 閱讀(9769) | 評論 (3)編輯 收藏

          一個用于J2EE應用程序的Backbase Ajax前端

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

            動態HTML技術已經出現了多年。最近,Google的最新Web應用程序GMail、Google Suggests和Google Maps,在前端頁面中重新引入了基于標準的DHTML開發模型。Google證明了,DHTML開發模型能夠讓開發人員創建具有可視化吸引力和高度交互 式的Rich Internet Application(豐富網絡應用程序,RIA)。

            Adaptive Path公司的Jesse James Garrett為這個基于標準的RIA開發模型創造了術語Ajax (Asynchronous JavaScript + XML)。與傳統的基于頁面的Web應用程序模型相比,Ajax有3點不同之處:

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

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

            基于Ajax的RIA正在迅速成為Web應用程序前端的基準,因為它可以同時提供二者的優點:豐富性和可達性。Ajax應用程序和桌面應用程序 一樣豐富,響應高度靈敏,并且可以在一個頁面上提供所有數據,無需刷新頁面。它們還擁有基于標準的瀏覽器應用程序的可達性特點,這類應用程序可以在不具備 瀏覽器插件或客戶端applet的情況下進行部署。

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

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

          Backbase Ajax表示層

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

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

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

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

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

          圖1. Backbase富Internet表示層

          Pet Store案例分析

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

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

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

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

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

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

          • 把前端變為一個單頁面的界面(SPI)。
          • 提供更先進的UI控件(如模態彈出式菜單)。
          • 使用可視化效果(例如,把寵物放入購物車)。
          • 更加有效地利用電腦屏幕的操作區域。

          RIA Pet Store前端

            在這一節中,我將討論經過改進的新Pet Store RIA前端。

            下面的兩個屏幕快照演示了前端的改進。要獲得對Backbase RIA前端更直觀的感受,請訪問http://www.backbase.com/xmlserver上的在線演示,或者到http://www.backbase.com/download下載Backbase社區版本。

          下面兩個圖對兩個前端進行了可視化的比較。圖2顯示的是原來靜態的多頁面HTML前端。圖3顯示的是新的Backbase SPI前端:

          圖2. 原始HTML前端

          圖3. 新Backbase前端

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

          • 選項卡式的單頁面瀏覽
          • 在Web界面上,不同的動物種類(狗、貓等等)被表示為不同的選項卡。點擊一個選項卡就會打開相應的類別,顯示可供出售的寵物。

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

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

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

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

            在Backbase RIA前端中,以前的視圖被使用可視化效果的新視圖所代替,新視圖向最終用戶顯示什么正在改變。圖4說明了如何通過流暢的定位效果,把按名稱排列的順序轉變為按價格排列的順序:

            圖4.類別視圖的排列順序轉換

          • 用于提高轉換速度的信息欄驗證

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

            圖5顯示了在填寫表單的第一個步驟中,對于e-mail地址信息欄的驗證。當購買者填寫下一欄時,就會提供即時的反饋。

          圖5. 信息欄驗證—e-mail欄

          Backbase RIA Pet Store的架構

            增強Pet Store(或其他任何Web應用程序)的前端時,我們將繼續依賴于以下兩條架構基本原則:

          • 最終用戶仍然使用標準的Web瀏覽器訪問Pet Store,無需添加任何插件。
          • 由J2EE業務邏輯和數據組成的整個后端保持不變。

            現有的后端在開發期間是完全孤立的,而且不會改變,這個事實對于架構師和IT管理人員十分有利。通過一個規整的、模塊化的架構,他們將能夠控制風險和成本,同時顯著提高Web應用程序的用戶友好性。

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

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

          Backbase表示客戶端

            BPC是一個基于Ajax的GUI引擎,它運行在標準的Web瀏覽器中。運行時,BPC被加載到瀏覽器中,然后它會接收BXML代碼,構造對應的B樹,并不斷地把這種表示轉換為瀏覽器所呈現的DOM樹。圖7說明了運行時轉換過程。

          圖7. BPC運行時

          Backbase XML

            Backbase XML (BXML)是XHTML的擴展。開發人員通過創建BXML應用程序來開發富前端,包括BXML標簽、標準的XHTML和CSS。BXML是一種聲明性語言,它包含了XHTML中所沒有的標簽(B標簽)

            BXML包含用于下列用途的標簽:

          • 定義屏幕分區(<b:panel>)
          • 交互式客戶端控制(<b:menu>)
          • 處理標準的用戶交互事件(onClick)
          • 處理高級的用戶交互事件(拖放和調整大小)
          • 管理客戶端狀態
          • 處理可視化效果(使修改任意CSS屬性的過程動畫化)
          • 數據綁定
          • 使用XSLT的一個子集進行客戶端轉換

          用于J2EE的Backbase XML Server

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

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

            BXS還提供一些應用服務,包括身份驗證、授權、日志記錄和用戶跟蹤。圖8顯示了BXS的總體架構。

          圖8. BXS架構

          Eclipse開發工具

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

          圖9. Backbase Eclipse插件

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

          部署到BEA WebLogic

            BXS是一個與標準兼容的J2EE應用程序,可以將其部署到任何J2EE應用服務器上。圖10顯示了如何使用WebLogic控制臺把BXS部署到BEA WebLogic Server

          圖10. 把BXS部署到BEA WebLogic

          實現Backbase RIA Pet Store

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

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

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

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

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

            索引頁面包含用于實例化BPC的代碼。索引頁面是XHTML和BXML標簽的結合,包含負責啟動富前端的初始化事件處理程序。

            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"/>
          • 應用程序布局
          • 加載頁面之后,BPC就會處理正在構造的事件,以便開始構建總體的應用程序布局。

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

            產品類別使用選項卡式的導航,每個寵物類別一個選項卡。這些選項卡是動態構造的,具體過程是通過BXS從一個XML文件加載類別,然后通過一個客戶端模板把這些類別轉換為選項卡,該轉換模板的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" />

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

            <bsd:pipeline equals="categories.xml"

            access="public">

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

            </bsd:pipeline>

            下面是用于創建選項卡式導航的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代碼(用藍色表示)都在客戶端執行,而所有BXS代碼(用紅色表示)都在服務器端執行。注意,在本例中,我選擇了在客戶端進行轉換,因為 數據集很小。下面我會給出一個在服務器端轉換的例子。兩種轉換都要用到XSLT語法。Backbase的一個強大功能就是,前端開發人員可以根據情況選擇 在客戶端還是服務器端處理表示邏輯。語法似乎允許輕松地把代碼從客戶端移到服務器端,或者反之。

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

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

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

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

            有3個嵌套的BXS管道分別用于從Web服務獲取數據、將其放入緩存,以及通過XSLT轉換創建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,以聲明性的方式創建Ajax前端是多么容易的事情。例如,只要使用帶有一個WSDL引用作為屬性的<bsd:callws>標簽,就可以調用一個Web服務。

          • 用戶交互
          • 現在,最終用戶可以與寵物商店類別進行交互。可以使用Next或Previous按鈕或者排序功能在動物類別中進行瀏覽。或者,只要點擊一下相應的選項卡,就可以轉到另一個類別中。

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

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

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

          結束語

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

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

            首先,Backbae易于使用。它的聲明性語言水平地擴展了DHTML;它完全對開發人員隱藏了瀏覽器兼容性的問題;而且它帶有一套開發和調試工具。

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

            最后,Backbase是以兼容的方式提供所有客戶端和服務器端的功能。用戶可以使用富Ajax前端擴展現有的應用程序,同時無需修改后端。對于整個表示層來說,它的架構是時新的、模塊化的,而且它基于XML。

          參考資料

          原文出處

          A Backbase Ajax Front-end for J2EE Applications

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

           作者簡介

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

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

          僅列出標題
          共5頁: 上一頁 1 2 3 4 5 下一頁 
          主站蜘蛛池模板: 四平市| 宽甸| 贵南县| 保山市| 襄汾县| 平乡县| 富宁县| 连平县| 江源县| 台北市| 平原县| 青神县| 山阳县| 满城县| 龙胜| 隆昌县| 合川市| 卫辉市| 商河县| 淮北市| 东乌珠穆沁旗| 德钦县| 安达市| 津南区| 永兴县| 芜湖市| 大姚县| 怀宁县| 兴文县| 方山县| 青岛市| 施秉县| 延庆县| 东乌珠穆沁旗| 裕民县| 高阳县| 海安县| 滕州市| 乐陵市| 紫金县| 公安县|