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 阅读(5759) | 评论 (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 阅读(1224) | 评论 (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 阅读(715) | 评论 (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 阅读(943) | 评论 (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 阅读(613) | 评论 (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 阅读(1140) | 评论 (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 阅读(1012) | 评论 (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 阅读(9748) | 评论 (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 阅读(899) | 评论 (0)编辑 收藏

     摘要: 原文:http://www.blogjava.net/eamoi/archive/2005/11/07/18566.html (续前一篇)AJAX开发简略 在前一篇文章《AJAX开发简略》中,我们讲述了如何用AJAX来改进设计的用户体验。接下来,我们将讲述如何用AJAX来更新文档,以及处理服务器返回的XML文档。我们的最终目的是接收服务器的返回信息,修改当前文档的内容。是时候让...  阅读全文
posted @ 2005-11-23 20:28 Dion 阅读(1211) | 评论 (0)编辑 收藏

仅列出标题
共5页: 上一页 1 2 3 4 5 下一页