我的JAVA窝! - BlogJava

2008年9月15日

Got Source Code of Kettle

  Using Kettle for more than one year in my project, but still have no time to read the source code of Kettle untill couple of days before (busy or actually lazy?). Yeah, I am a lazy dog...
  Since the source code is not available directly from the Pentato website, I had no choice but to go thru some posts on the Pentaho forum one by one. It didn't take me much effor before I found the relevant post, thx god:) The source code of Kettle now is maintained in SVN of JavaForge, and anybody "can get the latest(subversion trunk) code changes with on this URL: http://svn.javaforge.com/svn/Kettle/trunk", the username is "anonymous" and password is "anon".
  Besides, I had to download a SVN tool. I am using TortoiseSVN in my company, and the ux satisfied me, so I chose it again.
  It taked about 30 minutes to check out all the source code (still very fast I think, my bandwidth is 2M).
  When it done, I imported it as a general project into eclipse, and one thing surprised me a little bit was that, I didn't have to compile the source code or import some jars even than execute a Ant target manually as I built the source code of Spring before. Hah, Kettle is developed by using eclipse?! (you can find some eclipse project files in the trunk).
  Anyway, the work was going on smoothly, no more than 40 minutes. Now I can debug the Kettle, and from my experience, in some scenarios Kettle doesn't work in good performace than I expect, and even sometimes it runs into some bugs when I do the multitudinous insert or update operations. Here I get the opportunity to look into the code and figure out the problem.

  Keep moving forward...

posted @ 2008-09-15 18:24 J2S 阅读(1586) | 评论 (3)编辑 收藏

2007年3月19日

集合框架类笔记的补充

java .util.*定义了一系列的接口和类,告诉我们用什么类NEW出一个对象,可以进行超越数组的操作。
(注:JAVA1.5对JAVA1.4的最大改进就是增加了对范型的支持)

Set接口是数学中集合的概念:其元素无序,且不可重复。(正好与List对应)

!!注意数组和集合的区别:数组中只能存简单数据类型。Collection接口和Map接口只能存对象。

只有List可用get和size。而Set则不可用(因其无序)。

集合中每一个元素都有对象,如有字符串要经过强制类型转换。

Collections是工具类,所有方法均为有用方法,且方法为static。


Collections.Sort()分为两部分,一部分为排序规则;一部分为排序算法。
规则用来判断对象;算法是考虑如何排序。
对于自定义对象,Sort不知道规则,所以无法比较。这种情况下一定要定义排序规则。方式有两种:
java.lang下面有一个接口:Comparable(可比较的)
可以让自定义对象实现一个接口,这个接口只有一个方法comparableTo(Object o)
其规则是当前对象与o对象进行比较,其返回一个int值,系统根据此值来进行排序。
如 当前对象>o对象,则返回值>0;(可将返回值定义为1)
如 当前对象=o对象,则返回值=0;
如 当前对象<o对象,则返回值〈0。(可将返回值定义为-1)
看TestArraylist的java代码。
我们通过返回值1和-1位置的调换来实现升序和降序排列的转换。

java.util下有一个Comparator(比较器)
它拥有compare(),用来比较两个方法。
要生成比较器,则用Sort中Sort(List,List(Compate))
第二种方法更灵活,且在运行的时候不用编译。

注意:要想实现comparTo()就必须在主方法中写上implement comparable.


集合的最大缺点是无法进行类型判定(这个缺点在JAVA1.5中已经解决),这样就可能出现因为类型不同而出现类型错误。
解决的方法是添加类型的判断。

而在LinkedList的底层是一种双向循环链表。在此链表上每一个数据节点都由三部分组成:前指针(指向前面的节点的位置),数据,后指针(指向后面的节点的位置)。最后一个节点的后指针指向第一个节点的前指针,形成一个循环。

LinkedList经常用在增删操作较多而查询操作很少的情况下:队列和堆栈。
队列:先进先出的数据结构。
堆栈:后进先出的数据结构。
注意:使用堆栈的时候一定不能提供方法让不是最后一个元素的元素获得出栈的机会。
LinkedList提供以下方法:(ArrayList无此类方法)
addFirst();
removeFirst();
addLast();
removeLast();


Push用addFirst();pop用removeFirst(),实现后进先出。
用isEmpty()--其父类的方法,来判断栈是否为空。

在队列中,put为入队列操作,get为出队列操作。
Put用addFirst(),get用removeLast()实现队列。

List接口的实现类(Vector)(与ArrayList相似,区别是Vector是重量级的组件,使用使消耗的资源比较多。)
结论:在考虑并发的情况下用Vector(保证线程的安全)。
在不考虑并发的情况下用ArrayList(不能保证线程的安全)。

面试经验(知识点):
java.util.stack(stack即为堆栈)的父类为Vector。可是stack的父类是最不应该为Vector的。因为Vector的底层是数组,且Vector有get方法(意味着它可能访问到并不属于最后一个位置元素的其他元素,很不安全)。
对于堆栈和队列只能用push类和get类。
Stack类以后不要轻易使用。
!!!实现堆栈一定要用LinkedList。


(在JAVA1.5中,collection有queue来实现队列。)








posted @ 2007-03-19 22:04 J2S 阅读(548) | 评论 (1)编辑 收藏

关于集合框架类的学习笔记

集合类

集合类的对象是用来管理其他若干对象的,它类似于C++标准模板库中的容器,不过在JAVA的集合类的对象中可以用来存放多种类型的对象。

接口和类共同构成了一个集合框架,集合的概念,一个对象可以装载多个对象,这个对象就是集合对象。

集合框架

1,接口

Collection 用来管理多个对象,集合中的每个元素都是对象。

Map,Map中没有对象,而是键值对,由Key,value组成的键值对,Key是不可重复的。value是可以相同的,一个Key和一个value一一对应。

集合中用到的类,接口在java.util包中,在使用时注意将其引入import。

Collection 接口(以下介绍其子接口)

1)List 一个List的实现类的对象在管理多个对象时会按顺序组织对象(即按照将对象放入的顺序存储),List实现类的对象是由顺序的。(注意,顺序和排序的区别)

2)Set 一个Set的实现类表示一个数学概念上的集合,Set的实现类的对象中的元素是无顺序的,也就是不会按照输入顺序来存放,Set的实现类对象中的元素是不重复的。

3)SortedSet,他是Set的子接口,他的实现类会对集合中的元素进行排序。但是要指定排序规则,他会按排序规则进行排序。

Map 接口(以下介绍其子接口)

SortedMap,这个接口的实现类同样可以实现,不过是对键值对中的Key进行排序,这个接口的实现类也是要指定排序规
则的。

JDK1.4中的集合是不安全的对象,JDK5.0中解决了这个问题

List接口的实现类

1> ArrayList是接近于功能的集合类,ArryList的实质就是一个会自动增长的数组,ArrayList是用封装的数组来实现的List接口的。

Collection的实现类对象的遍历方式是用迭代来实现的。
在使用迭代器时先要活得一个迭代器的对象,Iterator(迭代器接口)这是一个接口,迭代器是在集合类中实现的,也
就是说,他是一个内部类(匿名内部类)实现的。
Iterator接口中定义的常用方法方法hasNext(),next()。
hasNext(),这个方法会使用一个游标,并通过判断游标指向的位置是否存放有对象。
next()方法也是Iterator接口中定义好的方法,这个方法会使游标指向下一个元素的位置,游标会跳过第一个元素,并
返回其中的内容。

Collections 这是一个工具类,也是java.util包中的,这个类中的sort(list接口的实现类的对象)方法,其参数是一个集合类的对象,这个方法使用来对集合类的对象进行排序的。以后,我将以集合这个名字来称呼集合类的对象。,对于
字符串对象内容的集合来说会按字典顺序排序(升序),对于数字内容的集合排序也会按照升序排序

排序可一份为两部分内容,一个是排序的规则,也就是按照什么来进行排序,并且排成什么样的顺序。
第二个就是排序的算法,他决定了排序的效率。

在对自定义的集合内容类型排序时,需要先定义那个类型的排序规则。

Comparable接口,这个接口中只定义了一个compareTo(Object o),方法的返回至类型是整型,如果当前对象大于参数对象就返回正数,当前对象等于参数对象是就返回0,当前对象小于参数对象时就返回负值,这样写就是升序排列,反之则是进行降序排列,在实现这个接口中的方法时,返回值定义方式,只有这两种。

根据指定类型的排序规则实现了Comparable接口,那么就可以对存有这个类型的集合进行整体排序。Comparable接口,
也叫做可比较接口。这个接口在java.lang包下。只要实现了这个接口,就是可排序的

接下来介绍另外一种对自定义类型对象的集合整体排序的方法,也就是实现比较器接口(Comparator),这个接口中定义了一个compare(Object o1,Object o2)方法来比较两个对象,这个方法的返回值定义和上面介绍的那个方法是一样。

注意:在API,帮助文档中以上两个方法的参数类型是T,这代表的模板类型,也就是集合中存放的内容的类型,在JDK1.4中其参数就是Object类型,模板类型的详细内容会在最后的JDK5.0新特性中讲到。

Comparator接口可以在匿名内部类中实现,Collections 中的sort(集合了的对象,比较器)方法,可以对自定义类型内容的集合进行整体排序。

2>LinkedList,它是List接口的实现类,其底层是用双向循环链表来实现的

注意:ArrayList的查询效率比较高,增删动作的效率比较差,适用于查询比较频繁,增删动作较少的元素管理的集合。
     LinkedList的查询效率低,但是增删效率很高。适用于增删动作的比较频繁,查询次数较少的元素管理集合。

ArrayList,LinkedList都是线程不安全的
实现堆栈 1,数组(ArrayList,增删效率比较低,不适合)
        2,LinkedList实现堆栈的好方法
        3,java.util.Stack类,Stack是Vector的子类,Vector类是一个线程安全的(是一个重量级的类),并继承
了Vector的方法,Verctor类和ArrayList的功能近乎相同。(不推荐使用Stack类来实现堆栈)。

Set接口的实现类

HashSet

Set的实现类的集合对象中不能够有重复元素,HashSet也一样他是使用了一种标识来确定元素的不重复,HashSet用一种算法来保证HashSet中的元素是不重复的,HashSet的底层实现还是数组

Object类中的hashCode()的方法是所有子类都会继承这个方法,这个方法会用Hash算法算出一个Hash(哈希)码值返回,HashSet会用Hash码值去和数组长度取模,模(这个模就是对象要存放在数组中的位置)相同时才会判断数组中的元素和要加入的对象的内容是否相同,如果不同才会添加进去。

Hash算法是一种散列算法。

注意:所以要存入HashSet的集合对象中的自定义类必须覆盖hashCode(),equals()两个方法,才能保证集合中元素容不重复。在覆盖和hashCode()方法时,要使相同对象的hashCode()方法返回相同值,覆盖equals()方法再判断其内容。为了保证效率,所以在覆盖hashCode()方法时,也要尽量使不同对象尽量返回不同的Hash码值。

如果数组中的元素和要加入的对象的hashCode()返回了相同的Hash值(相同对象),才会用equals()方法来判断两个对象的内容是否相同。

SortedSet接口是Set的子接口。
TreeSet是SortedSet接口的实现类,他可以对集合中的元素进行排序。
要存放在TreeSet中自定义类的对象,这个类要么是已经实现了Comparable接口,要么是能给出Comparator比较器,TreeSet可以自动过滤掉重复元素所以不用重载hashCode()方法,TreeSet会根据比较规则判断元素内容是否相同,TreeSet会在元素存入世就进行了排序。(在TreeSet给出排序规则时,一定要注意对象内容相等的条件,一定要注意在主观的认为两个对象内容相同时,才可以使用比较少的条件来进行判断)

要排序时才使用TreeSet类(存储效率比较低),HashSet的存储效率比较高,在需要为HashSet的对象排序时,就可以把HashSet中的元素放入TreeSet。

posted @ 2007-03-19 21:55 J2S 阅读(1176) | 评论 (0)编辑 收藏

2007年2月21日

如何最快成为有钱人

通向财富的道路有千万条,但条条道路都惊人地相似。而且令人诧异的是,拼命积累财富的男人也惊人地相似,他们在心理特征上就像是同一个模子压出来的一样。

    百万富翁有诸多特征,绝大多数白手起家的百万富翁都是出身贫穷的人,积累金钱对于那些一贫如洗的人有着不可抗拒的力量。

    这些男人是很有冒险精神的,他们敢于冒那些脚踏实地、像苦工似地挣钱的男人所不敢涉足的风险。他们是精明强干的,他们很有远见卓识;他们的天才在于能知道如何利用别人的主意来赚钱。这是赚钱的真正秘诀——利用别人创造性的思想,并且把它们运用到实际中去。

    这样的男人很容易和别人打成一片。他们很有洞察力,他们会观察别人,知道如何通过与别人打交道来获得他们所需要的东西,也知道别人对他们的反应如何。追求财富的人内心深处有着强烈的孤独感,但他们并不因此而去追求政治上的名望和成就。赚钱是他们绝对全神贯注的追求。这给他们胜于一切的最大满足和快乐。

    1、让金钱成为你的情人。

    别在性爱上浪费时间和精力,你会发现,从长远来看,赚钱比性爱更让人兴奋,这可不是空头支票。寻求需求,满足需求。追求财富者的最大天分是瞄准时机,预测所需。大众牌小汽车,美国快餐食品都是满足人们需要的例子。

    2、谨防从众心理。

    群众心理弊病多,即使它是正确的,追随它一般也没有什么利润。在一般情况下,摘取经济精华的都是带头人而不是追随者。

    3、当雇主而不是雇员。

    那些满足于雇主付给他们高薪的男人并不是真正追求财富的人,他们的目标仅仅是成就感或权力欲。你最好去当老板,即使雇员只有你一个,赚的钱也会比任何一个公司付给你的多。

    4、发展你的支配技巧。

    大多数人认为,支配别人,让他们去做你想让他们干的事情非常恶劣。然而,实际情况是,我们时时都在自己没有意识到的情况下支配着别人。

    5、追求财富的人常常是直觉地支配着别人,而且是个行家里手。
 

posted @ 2007-02-21 15:40 J2S 阅读(227) | 评论 (0)编辑 收藏

2007年1月18日

终于解脱了...

        下午答辩,还算顺利,虽然花了半天时间,没任何技术含量,但是终于解脱了...

         五天来平均每天也就睡了不到两小时,现在真有点顶不住了(意识都模糊了),但是为了把生物钟调整过来,还是撑一会吧。突然闲下来却不知道做点什么,20号有个面试,等会没事把Struts那本书再看看。

         接下来都不知道写些什么东西,就把实践中的一个弄明白的一个小知识记一下:
在JDBC中,对于游标对象ResultSet的getString()方法也是用游标的模式来读取数据的(不知道这样描叙对不对),即,你要取字段1,字段2,就必须先调用rs.getString(1),再调用rs.getString(2),不能反过来,否则不能正确读取。原因还不知道,有空了研究一下。

posted @ 2007-01-18 19:56 J2S 阅读(350) | 评论 (3)编辑 收藏

2007年1月15日

期末实践中......

今天成绩都出来了,结果还是比较理想的,以后会相对轻松些。

更新日志都是半个月前的事了,当时还说以后每天都更新,真是惭愧

这几天都是忙考试,弄得心力憔悴,今天晚上终于闲下来,继续写实践的程序。

刚刚解决了前几个小时的困惑,现在对JAVA的内部类机制又有了更新的认识(兴奋中......),废话少说,来看代码:

package tanzhang;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.TabFolder;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import com.swtdesigner.SWTResourceManager;
import org.eclipse.swt.widgets.TabItem;
public class tanzhang {

 /**
  * Launch the application
  * @param args
  */
 private static Table table;
 private static TableItem newItemTableItem;

 
 public static void main(String[] args) {
  final Display display = Display.getDefault();
  final Shell shell = new Shell();
  shell.setImage(SWTResourceManager.getImage(tanzhang.class, "/org/eclipse/ui/internal/forms/widgets/progress.gif"));
  shell.setSize(774, 514);
  shell.setText("宣城供电局消弧线圈台帐");
............
............
............
  final Combo combo_1 = new Combo(composite, SWT.READ_ONLY);
  combo_1.addSelectionListener(new SelectionAdapter() {
   public void widgetSelected(SelectionEvent arg0) {
    String str=combo_1.getText();
    try{ 
     String dbUrl = "jdbc:odbc:test";
     Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
     Connection conn = DriverManager.getConnection(dbUrl, "", "");
     Statement stmt=conn.createStatement();
     String sql="SELECT * from test where 单元名称="+str;
     ResultSet rs=stmt.executeQuery(sql);
     
     if(rs.next()){
     for(int i=0;i<=4;i++){         
       newItemTableItem.setText(i,rs.getString(i+1));  
       }
     
     rs.close();
     stmt.close();
     conn.close();
     }
    }catch(Exception e){}
    
   }
  });
............
............
............

  table = new Table(composite_1, SWT.VIRTUAL | SWT.FULL_SELECTION | SWT.BORDER | SWT.HIDE_SELECTION);

  table.setLinesVisible(true);
  table.setHeaderVisible(true);
  table.setBounds(0, 0, 678, 80);
............
............
............

   刚开始是用final修饰table和newItemTableItem,但是“newItemTableItem.setText(i,rs.getString(i+1));”这行(代码中兰色那句)会报错,编译器提示“无法解析newItemTableItem”。我的第一反应是监听器的位置放得不对。翻了一下书,说SWT/JFace事件代码中要访问类中变量有三种方法:(1)加final;(2)将变量变为类的实例变量;(3)将事件代码写成命名内部类,然后通过构造函数的参数来传入。
   想了想好象只有第三种方法值得试试,但是后来想起来newItemTableItem是个对象,传入以后却不知道怎么转换类型,呆了...
   最后做了个新的程序试了试,在新程序里是成功的,就是要把newItemTableItem在main函数外申明其为private static,但是在旧的程序中,编译器是通过了,但是进行连接数据库查询的时候,出现“Fatal Exception...”的错误,当时一下就蒙掉了!!完全一样的,怎么会不成功。
   百般无奈以后,----当然之前肯定是深思熟虑----终于脑海中闪出了传说中的“灵感”!于是马上把table也定义为private static......OK!   胜利总是来得那么突然,那瞬间的感觉就象90分钟打进的金球...

   当然到现在,我还没找到确切的原因。只能用自己的话总结一下:(1)匿名内部类中的方法是不能访问其他方法中定义的变量的(包括实例对象),要访问必须在类中将其定义为static类型。(2)TableItem是Table的子控件,要定义TableItem为static也要同时把Table定义为static类型。

   给自己一个任务:研究JAVA的内部类机制;研究SWT中父子控件关系的机制!

   看看《Thinking in JAVA》或许会是个不错的选择,当然还有《The Definitive Guide to SWT and JFace》。

 

posted @ 2007-01-15 02:41 J2S 阅读(296) | 评论 (0)编辑 收藏

2007年1月1日

考完试了!

昨天终于考完了!
都大四了,还用最变态的老师(三大魔头)来折磨我们,对于这学校真无语了......

现在终于可以继续我的自由学习之旅,以后一定每天上来更新blog。
时间不多了,既然已经饱受过往昏昏惑惑之疾、因循苟且之痛,那么现在就要珍惜分分秒秒,弥补差距。

今天下午花了两个小时把XML SPY教程学了一遍(没想到这么快能搞定),我用的是2007,学的是2005,但基本上没区别,进度还是蛮快的。最后估计那个xml schema文件没写好,最后用xslt转换没成功。不过晚上准备再试一次,权当做复习。
以前都用记事本写xml,现在用了xml spy感觉真是太好用了,尤其是xml数据与数据库的互相转换。而且还可以用工程来管理(还没试呢),对于开发大一点的程序无疑方便了许多。

晚上再准备把struts那本书看看。

对了,今天元旦,祝大家新年快乐,祈祷家人在新的一年里平平安安!

posted @ 2007-01-01 16:42 J2S 阅读(209) | 评论 (0)编辑 收藏

2006年12月19日

12月19日

         昨天搞了一个下午加晚上,配置eclipse3.2及其插件,最后太晚了没上来更新blog。有几点需要记一下:

      (1)其实在links目录里设置插件路径的时候“//”也是可以用的,网上一般说用“/”或“\\”。以后只要记住一点:“\”(windows里的路径格式)不能用就行了。

      (2)myeclipse5.0以上必须配eclipse3.2;myeclipse4.0配eclipse3.0; 而myeclipse4.1才能配eclipse3.1。 昨天刚开始没搞明白,给我的eclipse3.1.2下了一个myeclipse4.0和一个myeclipse5.1结果都不行,最后索性下了个最新的eclipse3.2.1,心想反正早晚都要升级干脆现在提前搞定算了。

      (3)关于eclipse3.2.1的lomboz插件问题,在网上看了很多帖子(其实源帖就两篇),总结如此:a) all-in-one版本里其实已经包含了eclipse3.2了;b)两种选择:直接下载org.objectweb.lomboz-all-in-one-win32解压缩即可;据说为了能支持GEF要分别下载这两个插件lomboz-S-3.2RC2-200608081203lomboz-and-prereqs-S-3.2RC2-200608081203进行安装。  我最后还是选择了第二种方法。
        其实有了myeclipse,lomboz可以不用装了,我想我只是为了多一种选择吧,不知道这是不是我骨子里已经开始倾向于开源了:)

      (4)最后下了一个javasript的eclipse小插件,但是安装不成功,不知道什么原因,反正以后再试吧...


      最后,想说一点,其实昨天一天的工作是毫无技术含量的 ,而且现在时间这么紧,我应该把一些更重要或更有价值的事情提到议程之前来,而不是跟自己过不去死钻牛角尖。我知道这是性格做怂,但是希望以后自己更理智点更“聪明”点。时间对于我们这样的人是最宝贵的!

posted @ 2006-12-19 13:05 J2S 阅读(329) | 评论 (0)编辑 收藏

2006年12月18日

初涉AJAX

多数 Web 应用程序都使用请求/响应模型从服务器上获得完整的 HTML 页面。常常是点击一个按钮,等待服务器响应,再点击另一个按钮,然后再等待,这样一个反复的过程。有了 Ajax 和 XMLHttpRequest 对象,就可以使用不必让用户等待服务器响应的请求/响应模型了。本文中,Brett McLaughlin 介绍了如何创建能够适应不同浏览器的 XMLHttpRequest 实例,建立和发送请求,并响应服务器。

本系列的上一期文章(请参阅 参考资料 中的链接),我们介绍了 Ajax 应用程序,考察了推动 Ajax 应用程序的基本概念。其中的核心是很多您可能已经了解的技术:JavaScript、HTML 和 XHTML、一点动态 HTML 以及 DOM(文档对象模型)。本文将放大其中的一点,把目光放到具体的 Ajax 细节上。

本文中,您将开始接触最基本和基础性的有关 Ajax 的全部对象和编程方法:XMLHttpRequest 对象。该对象实际上仅仅是一个跨越所有 Ajax 应用程序的公共线程,您可能已经预料到,只有彻底理解该对象才能充分发挥编程的潜力。事实上,有时您会发现,要正确地使用 XMLHttpRequest,显然不能 使用 XMLHttpRequest。这到底是怎么回事呢?

Web 2.0 一瞥

在深入研究代码之前首先看看最近的观点 —— 一定要十分清楚 Web 2.0 这个概念。听到 Web 2.0 这个词的时候,应该首先问一问 “Web 1.0 是什么?” 虽然很少听人提到 Web 1.0,实际上它指的就是具有完全不同的请求和响应模型的传统 Web。比如,到 Amazon.com 网站上点击一个按钮或者输入搜索项。就会对服务器发送一个请求,然后响应再返回到浏览器。该请求不仅仅是图书和书目列表,而是另一个完整的 HTML 页面。因此当 Web 浏览器用新的 HTML 页面重绘时,可能会看到闪烁或抖动。事实上,通过看到的每个新页面可以清晰地看到请求和响应。

Web 2.0(在很大程度上)消除了这种看得见的往复交互。比如访问 Google Maps 或 Flickr 这样的站点(到这些支持 Web 2.0 和 Ajax 站点的链接请参阅 参考资料)。比如在 Google Maps 上,您可以拖动地图,放大和缩小,只有很少的重绘操作。当然这里仍然有请求和响应,只不过都藏到了幕后。作为用户,体验更加舒适,感觉很像桌面应用程序。这种新的感受和范型就是当有人提到 Web 2.0 时您所体会到的。

需要关心的是如何使这些新的交互成为可能。显然,仍然需要发出请求和接收响应,但正是针对每次请求/响应交互的 HTML 重绘造成了缓慢、笨拙的 Web 交互的感受。因此很清楚,我们需要一种方法使发送的请求和接收的响应只 包含需要的数据而不是整个 HTML 页面。惟一需要获得整个新 HTML 页面的时候就是希望用户看到 新页面的时候。

但多数交互都是在已有页面上增加细节、修改主体文本或者覆盖原有数据。这些情况下,Ajax 和 Web 2.0 方法允许在不 更新整个 HTML 页面的情况下发送和接收数据。对于那些经常上网的人,这种能力可以让您的应用程序感觉更快、响应更及时,让他们不时地光顾您的网站。







XMLHttpRequest 简介

要真正实现这种绚丽的奇迹,必须非常熟悉一个 JavaScript 对象,即 XMLHttpRequest。这个小小的对象实际上已经在几种浏览器中存在一段时间了,它是本专栏今后几个月中要介绍的 Web 2.0、Ajax 和大部分其他内容的核心。为了让您快速地大体了解它,下面给出将要用于该对象的很少的几个 方法和属性。

  • open():建立到服务器的新请求。
  • send():向服务器发送请求。
  • abort():退出当前请求。
  • readyState:提供当前 HTML 的就绪状态。
  • responseText:服务器返回的请求响应文本。

如果不了解这些(或者其中的任何 一个),您也不用担心,后面几篇文章中我们将介绍每个方法和属性。现在应该 了解的是,明确用 XMLHttpRequest 做什么。要注意这些方法和属性都与发送请求及处理响应有关。事实上,如果看到 XMLHttpRequest 的所有方法和属性,就会发现它们都 与非常简单的请求/响应模型有关。显然,我们不会遇到特别新的 GUI 对象或者创建用户交互的某种超极神秘的方法,我们将使用非常简单的请求和非常简单的响应。听起来似乎没有多少吸引力,但是用好该对象可以彻底改变您的应用程序。

简单的 new

首先需要创建一个新变量并赋给它一个 XMLHttpRequest 对象实例。这在 JavaScript 中很简单,只要对该对象名使用 new 关键字即可,如 清单 1 所示。



清单 1. 创建新的 XMLHttpRequest 对象
<script language="javascript" type="text/javascript">
                        var request = new XMLHttpRequest();
                        </script>
                        

不难吧?记住,JavaScript 不要求指定变量类型,因此不需要像 清单 2 那样做(在 Java 语言中可能需要这样)。



清单 2. 创建 XMLHttpRequest 的 Java 伪代码
XMLHttpRequest request = new XMLHttpRequest();
                        

因此在 JavaScript 中用 var 创建一个变量,给它一个名字(如 “request”),然后赋给它一个新的 XMLHttpRequest 实例。此后就可以在函数中使用该对象了。

错误处理

在实际上各种事情都可能出错,而上面的代码没有提供任何错误处理。较好的办法是创建该对象,并在出现问题时优雅地退出。比如,任何较早的浏览器(不论您是否相信,仍然有人在使用老版本的 Netscape Navigator)都不支持 XMLHttpRequest,您需要让这些用户知道有些地方出了问题。清单 3 说明如何创建该对象,以便在出现问题的时候发出 JavaScript 警告。



清单 3. 创建具有错误处理能力的 XMLHttpRequest
<script language="javascript" type="text/javascript">
                        var request = false;
                        try {
                        request = new XMLHttpRequest();
                        } catch (failed) {
                        request = false;
                        }
                        if (!request)
                        alert("Error initializing XMLHttpRequest!");
                        </script>
                        

一定要理解这些步骤:

  1. 创建一个新变量 request 并赋值 false。后面将使用 false 作为判定条件,它表示还没有创建 XMLHttpRequest 对象。
  2. 增加 try/catch 块:
    1. 尝试创建 XMLHttpRequest 对象。
    2. 如果失败(catch (failed))则保证 request 的值仍然为 false。
  3. 检查 request 是否仍为 false(如果一切正常就不会是 false)。
  4. 如果出现问题(request 是 false)则使用 JavaScript 警告通知用户出现了问题。

代码非常简单,对大多数 JavaScript 和 Web 开发人员来说,真正理解它要比读写代码花更长的时间。现在已经得到了一段带有错误检查的 XMLHttpRequest 对象创建代码,还可以告诉您哪儿出了问题。

应付 Microsoft

看起来似乎一切良好,至少在用 Internet Explorer 试验这些代码之前是这样的。如果这样试验的话,就会看到 图 1 所示的糟糕情形。



图 1. Internet Explorer 报告错误
Internet Explorer 报告错误
Microsoft 参与了吗?
关于 Ajax 和 Microsoft 对该领域不断增长的兴趣和参与已经有很多文章进行了介绍。事实上,据说 Microsoft 最新版本的 Internet Explorer —— version 7.0,将在 2006 年下半年推出 —— 将开始直接支持 XMLHttpRequest,让您使用 new 关键字代替所有的 Msxml2.XMLHTTP 创建代码。但不要太激动,仍然需要支持旧的浏览器,因此跨浏览器代码不会很快消失。

显然有什么地方不对劲,而 Internet Explorer 很难说是一种过时的浏览器,因为全世界有 70% 在使用 Internet Explorer。换句话说,如果不支持 Microsoft 和 Internet Explorer 就不会受到 Web 世界的欢迎!因此我们需要采用不同的方法处理 Microsoft 浏览器。

经验证发现 Microsoft 支持 Ajax,但是其 XMLHttpRequest 版本有不同的称呼。事实上,它将其称为几种 不同的东西。如果使用较新版本的 Internet Explorer,则需要使用对象 Msxml2.XMLHTTP,而较老版本的 Internet Explorer 则使用 Microsoft.XMLHTTP。我们需要支持这两种对象类型(同时还要支持非 Microsoft 浏览器)。请看看 清单 4,它在前述代码的基础上增加了对 Microsoft 的支持。



清单 4. 增加对 Microsoft 浏览器的支持
<script language="javascript" type="text/javascript">
                        var request = false;
                        try {
                        request = new XMLHttpRequest();
                        } catch (trymicrosoft) {
                        try {
                        request = new ActiveXObject("Msxml2.XMLHTTP");
                        } catch (othermicrosoft) {
                        try {
                        request = new ActiveXObject("Microsoft.XMLHTTP");
                        } catch (failed) {
                        request = false;
                        }
                        }
                        }
                        if (!request)
                        alert("Error initializing XMLHttpRequest!");
                        </script>
                        

很容易被这些花括号迷住了眼睛,因此下面分别介绍每一步:

  1. 创建一个新变量 request 并赋值 false。使用 false 作为判断条件,它表示还没有创建 XMLHttpRequest 对象。
  2. 增加 try/catch 块:
    1. 尝试创建 XMLHttpRequest 对象。
    2. 如果失败(catch (trymicrosoft)):
      1. 尝试使用较新版本的 Microsoft 浏览器创建 Microsoft 兼容的对象(Msxml2.XMLHTTP)。
      2. 如果失败(catch (othermicrosoft))尝试使用较老版本的 Microsoft 浏览器创建 Microsoft 兼容的对象(Microsoft.XMLHTTP)。
    3. 如果失败(catch (failed))则保证 request 的值仍然为 false。
  3. 检查 request 是否仍然为 false(如果一切顺利就不会是 false)。
  4. 如果出现问题(request 是 false)则使用 JavaScript 警告通知用户出现了问题。

这样修改代码之后再使用 Internet Explorer 试验,就应该看到已经创建的表单(没有错误消息)。我实验的结果如 图 2 所示。



图 2. Internet Explorer 正常工作
Internet Explorer 正常工作

静态与动态

再看一看清单 134,注意,所有这些代码都直接嵌套在 script 标记中。像这种不放到方法或函数体中的 JavaScript 代码称为静态 JavaScript。就是说代码是在页面显示给用户之前的某个时候运行。(虽然根据规范不能完全精确地 知道这些代码何时运行对浏览器有什么影响,但是可以保证这些代码在用户能够与页面交互之前运行。)这也是多数 Ajax 程序员创建 XMLHttpRequest 对象的一般方式。

就是说,也可以像 清单 5 那样将这些代码放在一个方法中。



清单 5. 将 XMLHttpRequest 创建代码移动到方法中
<script language="javascript" type="text/javascript">
                        var request;
                        function createRequest() {
                        try {
                        request = new XMLHttpRequest();
                        } catch (trymicrosoft) {
                        try {
                        request = new ActiveXObject("Msxml2.XMLHTTP");
                        } catch (othermicrosoft) {
                        try {
                        request = new ActiveXObject("Microsoft.XMLHTTP");
                        } catch (failed) {
                        request = false;
                        }
                        }
                        }
                        if (!request)
                        alert("Error initializing XMLHttpRequest!");
                        }
                        </script>
                        

如果按照这种方式编写代码,那么在处理 Ajax 之前需要调用该方法。因此还需要 清单 6 这样的代码。



清单 6. 使用 XMLHttpRequest 的创建方法
<script language="javascript" type="text/javascript">
                        var request;
                        function createRequest() {
                        try {
                        request = new XMLHttpRequest();
                        } catch (trymicrosoft) {
                        try {
                        request = new ActiveXObject("Msxml2.XMLHTTP");
                        } catch (othermicrosoft) {
                        try {
                        request = new ActiveXObject("Microsoft.XMLHTTP");
                        } catch (failed) {
                        request = false;
                        }
                        }
                        }
                        if (!request)
                        alert("Error initializing XMLHttpRequest!");
                        }
                        function getCustomerInfo() {
                        createRequest();
                        // Do something with the request variable
                        }
                        </script>
                        

此代码惟一的问题是推迟了错误通知,这也是多数 Ajax 程序员不采用这一方法的原因。假设一个复杂的表单有 10 或 15 个字段、选择框等,当用户在第 14 个字段(按照表单顺序从上到下)输入文本时要激活某些 Ajax 代码。这时候运行 getCustomerInfo() 尝试创建一个 XMLHttpRequest 对象,但(对于本例来说)失败了。然后向用户显示一条警告,明确地告诉他们不能使用该应用程序。但用户已经花费了很多时间在表单中输入数据!这是非常令人讨厌的,而讨厌显然不会吸引用户再次访问您的网站。

如果使用静态 JavaScript,用户在点击页面的时候很快就会看到错误信息。这样也很烦人,是不是?可能令用户错误地认为您的 Web 应用程序不能在他的浏览器上运行。不过,当然要比他们花费了 10 分钟输入信息之后再显示同样的错误要好。因此,我建议编写静态的代码,让用户尽可能早地发现问题。







用 XMLHttpRequest 发送请求

得到请求对象之后就可以进入请求/响应循环了。记住,XMLHttpRequest 惟一的目的是让您发送请求和接收响应。其他一切都是 JavaScript、CSS 或页面中其他代码的工作:改变用户界面、切换图像、解释服务器返回的数据。准备好 XMLHttpRequest 之后,就可以向服务器发送请求了。

欢迎使用沙箱

Ajax 采用一种沙箱安全模型。因此,Ajax 代码(具体来说就是 XMLHttpRequest 对象)只能对所在的同一个域发送请求。以后的文章中将进一步介绍安全和 Ajax,现在只要知道在本地机器上运行的代码只能对本地机器上的服务器端脚本发送请求。如果让 Ajax 代码在 www.breakneckpizza.com 上运行,则必须 www.breakneck.com 中运行的脚本发送请求。

设置服务器 URL

首先要确定连接的服务器的 URL。这并不是 Ajax 的特殊要求,但仍然是建立连接所必需的,显然现在您应该知道如何构造 URL 了。多数应用程序中都会结合一些静态数据和用户处理的表单中的数据来构造该 URL。比如,清单 7 中的 JavaScript 代码获取电话号码字段的值并用其构造 URL。



清单 7. 建立请求 URL
<script language="javascript" type="text/javascript">
                        var request = false;
                        try {
                        request = new XMLHttpRequest();
                        } catch (trymicrosoft) {
                        try {
                        request = new ActiveXObject("Msxml2.XMLHTTP");
                        } catch (othermicrosoft) {
                        try {
                        request = new ActiveXObject("Microsoft.XMLHTTP");
                        } catch (failed) {
                        request = false;
                        }
                        }
                        }
                        if (!request)
                        alert("Error initializing XMLHttpRequest!");
                        function getCustomerInfo() {
                        var phone = document.getElementById("phone").value;
                        var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);
                        }
                        </script>
                        

这里没有难懂的地方。首先,代码创建了一个新变量 phone,并把 ID 为 “phone” 的表单字段的值赋给它。清单 8 展示了这个表单的 XHTML,其中可以看到 phone 字段及其 id 属性。



清单 8. Break Neck Pizza 表单
 <body>
                        <p><img src="breakneck-logo_4c.gif" alt="Break Neck Pizza" /></p>
                        <form action="POST">
                        <p>Enter your phone number:
                        <input type="text" size="14" name="phone" id="phone"
                        onChange="getCustomerInfo();" />
                        </p>
                        <p>Your order will be delivered to:</p>
                        <div id="address"></div>
                        <p>Type your order in here:</p>
                        <p><textarea name="order" rows="6" cols="50" id="order"></textarea></p>
                        <p><input type="submit" value="Order Pizza" id="submit" /></p>
                        </form>
                        </body>
                        

还要注意,当用户输入电话号码或者改变电话号码时,将触发 清单 8 所示的 getCustomerInfo() 方法。该方法取得电话号码并构造存储在 url 变量中的 URL 字符串。记住,由于 Ajax 代码是沙箱型的,因而只能连接到同一个域,实际上 URL 中不需要域名。该例中的脚本名为 /cgi-local/lookupCustomer.php。最后,电话号码作为 GET 参数附加到该脚本中:"phone=" + escape(phone)。

如果以前没用见过 escape() 方法,它用于转义不能用明文正确发送的任何字符。比如,电话号码中的空格将被转换成字符 %20,从而能够在 URL 中传递这些字符。

可以根据需要添加任意多个参数。比如,如果需要增加另一个参数,只需要将其附加到 URL 中并用 “与”(&)字符分开 [第一个参数用问号(?)和脚本名分开]。

打开请求

open() 是打开吗?
Internet 开发人员对 open() 方法到底做什么没有达成一致。但它实际上并不是 打开一个请求。如果监控 XHTML/Ajax 页面及其连接脚本之间的网络和数据传递,当调用 open() 方法时将看不到任何通信。不清楚为何选用了这个名字,但显然不是一个好的选择。

有了要连接的 URL 后就可以配置请求了。可以用 XMLHttpRequest 对象的 open() 方法来完成。该方法有五个参数:

  • request-type:发送请求的类型。典型的值是 GET 或 POST,但也可以发送 HEAD 请求。
  • url:要连接的 URL。
  • asynch:如果希望使用异步连接则为 true,否则为 false。该参数是可选的,默认为 true。
  • username:如果需要身份验证,则可以在此指定用户名。该可选参数没有默认值。
  • password:如果需要身份验证,则可以在此指定口令。该可选参数没有默认值。

通常使用其中的前三个参数。事实上,即使需要异步连接,也应该指定第三个参数为 “true”。这是默认值,但坚持明确指定请求是异步的还是同步的更容易理解。

将这些结合起来,通常会得到 清单 9 所示的一行代码。



清单 9. 打开请求
   function getCustomerInfo() {
                        var phone = document.getElementById("phone").value;
                        var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);
                        request.open("GET", url, true);
                        }
                        

一旦设置好了 URL,其他就简单了。多数请求使用 GET 就够了(后面的文章中将看到需要使用 POST 的情况),再加上 URL,这就是使用 open() 方法需要的全部内容了。

挑战异步性

本系列的后面一篇文章中,我将用很多时间编写和使用异步代码,但是您应该明白为什么 open() 的最后一个参数这么重要。在一般的请求/响应模型中,比如 Web 1.0,客户机(浏览器或者本地机器上运行的代码)向服务器发出请求。该请求是同步的,换句话说,客户机等待服务器的响应。当客户机等待的时候,至少会用某种形式通知您在等待:

  • 沙漏(特别是 Windows 上)。
  • 旋转的皮球(通常在 Mac 机器上)。
  • 应用程序基本上冻结了,然后过一段时间光标变化了。

这正是 Web 应用程序让人感到笨拙或缓慢的原因 —— 缺乏真正的交互性。按下按钮时,应用程序实际上变得不能使用,直到刚刚触发的请求得到响应。如果请求需要大量服务器处理,那么等待的时间可能很长(至少在这个多处理器、DSL 没有等待的世界中是如此)。

而异步请求不 等待服务器响应。发送请求后应用程序继续运行。用户仍然可以在 Web 表单中输入数据,甚至离开表单。没有旋转的皮球或者沙漏,应用程序也没有明显的冻结。服务器悄悄地响应请求,完成后告诉原来的请求者工作已经结束(具体的办法很快就会看到)。结果是,应用程序感觉不 那么迟钝或者缓慢,而是响应迅速、交互性强,感觉快多了。这仅仅是 Web 2.0 的一部分,但它是很重要的一部分。所有老套的 GUI 组件和 Web 设计范型都不能克服缓慢、同步的请求/响应模型。

发送请求

一旦用 open() 配置好之后,就可以发送请求了。幸运的是,发送请求的方法的名称要比 open() 适当,它就是 send()。

send() 只有一个参数,就是要发送的内容。但是在考虑这个方法之前,回想一下前面已经通过 URL 本身发送过数据了:

var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);
                        

虽然可以使用 send() 发送数据,但也能通过 URL 本身发送数据。事实上,GET 请求(在典型的 Ajax 应用中大约占 80%)中,用 URL 发送数据要容易得多。如果需要发送安全信息或 XML,可能要考虑使用 send() 发送内容(本系列的后续文章中将讨论安全数据和 XML 消息)。如果不需要通过 send() 传递数据,则只要传递 null 作为该方法的参数即可。因此您会发现在本文中的例子中只需要这样发送请求(参见 清单 10)。



清单 10. 发送请求
   function getCustomerInfo() {
                        var phone = document.getElementById("phone").value;
                        var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);
                        request.open("GET", url, true);
                        request.send(null);
                        }
                        

指定回调方法

现在我们所做的只有很少一点是新的、革命性的或异步的。必须承认,open() 方法中 “true” 这个小小的关键字建立了异步请求。但是除此之外,这些代码与用 Java servlet 及 JSP、PHP 或 Perl 编程没有什么两样。那么 Ajax 和 Web 2.0 最大的秘密是什么呢?秘密就在于 XMLHttpRequest 的一个简单属性 onreadystatechange。

首先一定要理解这些代码中的流程(如果需要请回顾 清单 10)。建立其请求然后发出请求。此外,因为是异步请求,所以 JavaScript 方法(例子中的 getCustomerInfo())不会等待服务器。因此代码将继续执行,就是说,将退出该方法而把控制返回给表单。用户可以继续输入信息,应用程序不会等待服务器。

这就提出了一个有趣的问题:服务器完成了请求之后会发生什么?答案是什么也不发生,至少对现在的代码而言如此!显然这样不行,因此服务器在完成通过 XMLHttpRequest 发送给它的请求处理之后需要某种指示说明怎么做。

在 JavaScript 中引用函数
JavaScript 是一种弱类型的语言,可以用变量引用任何东西。因此如果声明了一个函数 updatePage(),JavaScript 也将该函数名看作是一个变量。换句话说,可用变量名 updatePage 在代码中引用函数。

现在 onreadystatechange 属性该登场了。该属性允许指定一个回调函数。回调允许服务器(猜得到吗?)反向调用 Web 页面中的代码。它也给了服务器一定程度的控制权,当服务器完成请求之后,会查看 XMLHttpRequest 对象,特别是 onreadystatechange 属性。然后调用该属性指定的任何方法。之所以称为回调是因为服务器向网页发起调用,无论网页本身在做什么。比方说,可能在用户坐在椅子上手没有碰键盘的时候调用该方法,但是也可能在用户输入、移动鼠标、滚动屏幕或者点击按钮时调用该方法。它并不关心用户在做什么。

这就是称之为异步的原因:用户在一层上操作表单,而在另一层上服务器响应请求并触发 onreadystatechange 属性指定的回调方法。因此需要像 清单 11 一样在代码中指定该方法。



清单 11. 设置回调方法
   function getCustomerInfo() {
                        var phone = document.getElementById("phone").value;
                        var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);
                        request.open("GET", url, true);
                        request.onreadystatechange = updatePage;
                        request.send(null);
                        }
                        

需要特别注意的是该属性在代码中设置的位置 —— 它是在调用 send() 之前 设置的。发送请求之前必须设置该属性,这样服务器在回答完成请求之后才能查看该属性。现在剩下的就只有编写 updatePage() 方法了,这是本文最后一节要讨论的重点。







处理服务器响应

发送请求,用户高兴地使用 Web 表单(同时服务器在处理请求),而现在服务器完成了请求处理。服务器查看 onreadystatechange 属性确定要调用的方法。除此以外,可以将您的应用程序看作其他应用程序一样,无论是否异步。换句话说,不一定要采取特殊的动作编写响应服务器的方法,只需要改变表单,让用户访问另一个 URL 或者做响应服务器需要的任何事情。这一节我们重点讨论对服务器的响应和一种典型的动作 —— 即时改变用户看到的表单中的一部分。

回调和 Ajax

现在我们已经看到如何告诉服务器完成后应该做什么:将 XMLHttpRequest 对象的 onreadystatechange 属性设置为要运行的函数名。这样,当服务器处理完请求后就会自动调用该函数。也不需要担心该函数的任何参数。我们从一个简单的方法开始,如 清单 12 所示。



清单 12. 回调方法的代码
<script language="javascript" type="text/javascript">
                        var request = false;
                        try {
                        request = new XMLHttpRequest();
                        } catch (trymicrosoft) {
                        try {
                        request = new ActiveXObject("Msxml2.XMLHTTP");
                        } catch (othermicrosoft) {
                        try {
                        request = new ActiveXObject("Microsoft.XMLHTTP");
                        } catch (failed) {
                        request = false;
                        }
                        }
                        }
                        if (!request)
                        alert("Error initializing XMLHttpRequest!");
                        function getCustomerInfo() {
                        var phone = document.getElementById("phone").value;
                        var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);
                        request.open("GET", url, true);
                        request.onreadystatechange = updatePage;
                        request.send(null);
                        }
                        function updatePage() {
                        alert("Server is done!");
                        }
                        </script>
                        

它仅仅发出一些简单的警告,告诉您服务器什么时候完成了任务。在自己的网页中试验这些代码,然后在浏览器中打开(如果希望查看该例中的 XHTML,请参阅 清单 8)。输入电话号码然后离开该字段,将看到一个弹出的警告窗口(如 图 3 所示),但是点击 OK 又出现了……



图 3. 弹出警告的 Ajax 代码
弹出警告的 Ajax 代码

根据浏览器的不同,在表单停止弹出警告之前会看到两次、三次甚至四次警告。这是怎么回事呢?原来我们还没有考虑 HTTP 就绪状态,这是请求/响应循环中的一个重要部分。

HTTP 就绪状态

前面提到,服务器在完成请求之后会在 XMLHttpRequest 的 onreadystatechange 属性中查找要调用的方法。这是真的,但还不完整。事实上,每当 HTTP 就绪状态改变时它都会调用该方法。这意味着什么呢?首先必须理解 HTTP 就绪状态。

HTTP 就绪状态表示请求的状态或情形。它用于确定该请求是否已经开始、是否得到了响应或者请求/响应模型是否已经完成。它还可以帮助确定读取服务器提供的响应文本或数据是否安全。在 Ajax 应用程序中需要了解五种就绪状态:

  • 0:请求没有发出(在调用 open() 之前)。
  • 1:请求已经建立但还没有发出(调用 send() 之前)。
  • 2:请求已经发出正在处理之中(这里通常可以从响应得到内容头部)。
  • 3:请求已经处理,响应中通常有部分数据可用,但是服务器还没有完成响应。
  • 4:响应已完成,可以访问服务器响应并使用它。

与大多数跨浏览器问题一样,这些就绪状态的使用也不尽一致。您也许期望任务就绪状态从 0 到 1、2、3 再到 4,但实际上很少是这种情况。一些浏览器从不报告 0 或 1 而直接从 2 开始,然后是 3 和 4。其他浏览器则报告所有的状态。还有一些则多次报告就绪状态 1。在上一节中看到,服务器多次调用 updatePage(),每次调用都会弹出警告框 —— 可能和预期的不同!

对于 Ajax 编程,需要直接处理的惟一状态就是就绪状态 4,它表示服务器响应已经完成,可以安全地使用响应数据了。基于此,回调方法中的第一行应该如 清单 13 所示。



清单 13. 检查就绪状态
   function updatePage() {
                        if (request.readyState == 4)
                        alert("Server is done!");
                        }
                        

修改后就可以保证服务器的处理已经完成。尝试运行新版本的 Ajax 代码,现在就会看到与预期的一样,只显示一次警告信息了。

HTTP 状态码

虽然 清单 13 中的代码看起来似乎不错,但是还有一个问题 —— 如果服务器响应请求并完成了处理但是报告了一个错误怎么办?要知道,服务器端代码应该明白它是由 Ajax、JSP、普通 HTML 表单或其他类型的代码调用的,但只能使用传统的 Web 专用方法报告信息。而在 Web 世界中,HTTP 代码可以处理请求中可能发生的各种问题。

比方说,您肯定遇到过输入了错误的 URL 请求而得到 404 错误码的情形,它表示该页面不存在。这仅仅是 HTTP 请求能够收到的众多错误码中的一种(完整的状态码列表请参阅 参考资料 中的链接)。表示所访问数据受到保护或者禁止访问的 403 和 401 也很常见。无论哪种情况,这些错误码都是从完成的响应 得到的。换句话说,服务器履行了请求(即 HTTP 就绪状态是 4)但是没有返回客户机预期的数据。

因此除了就绪状态外,还需要检查 HTTP 状态。我们期望的状态码是 200,它表示一切顺利。如果就绪状态是 4 而且状态码是 200,就可以处理服务器的数据了,而且这些数据应该就是要求的数据(而不是错误或者其他有问题的信息)。因此还要在回调方法中增加状态检查,如 清单 14 所示。



清单 14. 检查 HTTP 状态码
   function updatePage() {
                        if (request.readyState == 4)
                        if (request.status == 200)
                        alert("Server is done!");
                        }
                        

为了增加更健壮的错误处理并尽量避免过于复杂,可以增加一两个状态码检查,请看一看 清单 15 中修改后的 updatePage() 版本。



清单 15. 增加一点错误检查
   function updatePage() {
                        if (request.readyState == 4)
                        if (request.status == 200)
                        alert("Server is done!");
                        else if (request.status == 404)
                        alert("Request URL does not exist");
                        else
                        alert("Error: status code is " + request.status);
                        }
                        

现在将 getCustomerInfo() 中的 URL 改为不存在的 URL 看看会发生什么。应该会看到警告信息说明要求的 URL 不存在 —— 好极了!很难处理所有的错误条件,但是这一小小的改变能够涵盖典型 Web 应用程序中 80% 的问题。

读取响应文本

现在可以确保请求已经处理完成(通过就绪状态),服务器给出了正常的响应(通过状态码),最后我们可以处理服务器返回的数据了。返回的数据保存在 XMLHttpRequest 对象的 responseText 属性中。

关于 responseText 中的文本内容,比如格式和长度,有意保持含糊。这样服务器就可以将文本设置成任何内容。比方说,一种脚本可能返回逗号分隔的值,另一种则使用管道符(即 | 字符)分隔的值,还有一种则返回长文本字符串。何去何从由服务器决定。

在本文使用的例子中,服务器返回客户的上一个订单和客户地址,中间用管道符分开。然后使用订单和地址设置表单中的元素值,清单 16 给出了更新显示内容的代码。



清单 16. 处理服务器响应
   function updatePage() {
                        if (request.readyState == 4) {
                        if (request.status == 200) {
                        var response = request.responseText.split("|");
                        document.getElementById("order").value = response[0];
                        document.getElementById("address").innerHTML =
                        response[1].replace(/\n/g, "
"); } else alert("status is " + request.status); } }

首先,得到 responseText 并使用 JavaScript split() 方法从管道符分开。得到的数组放到 response 中。数组中的第一个值 —— 上一个订单 —— 用 response[0] 访问,被设置为 ID 为 “order” 的字段的值。第二个值 response[1],即客户地址,则需要更多一点处理。因为地址中的行用一般的行分隔符(“\n”字符)分隔,代码中需要用 XHTML 风格的行分隔符 <br /> 来代替。替换过程使用 replace() 函数和正则表达式完成。最后,修改后的文本作为 HTML 表单 div 中的内部 HTML。结果就是表单突然用客户信息更新了,如图 4 所示。



图 4. 收到客户数据后的 Break Neck 表单
收到客户数据后的 Break Neck 表单

结束本文之前,我还要介绍 XMLHttpRequest 的另一个重要属性 responseXML。如果服务器选择使用 XML 响应则该属性包含(也许您已经猜到)XML 响应。处理 XML 响应和处理普通文本有很大不同,涉及到解析、文档对象模型(DOM)和其他一些问题。后面的文章中将进一步介绍 XML。但是因为 responseXML 通常和 responseText 一起讨论,这里有必要提一提。对于很多简单的 Ajax 应用程序 responseText 就够了,但是您很快就会看到通过 Ajax 应用程序也能很好地处理 XML。







结束语

您可能对 XMLHttpRequest 感到有点厌倦了,我很少看到一整篇文章讨论一个对象,特别是这种简单的对象。但是您将在使用 Ajax 编写的每个页面和应用程序中反复使用该对象。坦白地说,关于 XMLHttpRequest 还真有一些可说的内容。下一期文章中将介绍如何在请求中使用 POST 及 GET,来设置请求中的内容头部和从服务器响应读取内容头部,理解如何在请求/响应模型中编码请求和处理 XML。

再往后我们将介绍常见 Ajax 工具箱。这些工具箱实际上隐藏了本文所述的很多细节,使得 Ajax 编程更容易。您也许会想,既然有这么多工具箱为何还要对底层的细节编码。答案是,如果不知道应用程序在做什么,就很难发现应用程序中的问题。

因此不要忽略这些细节或者简单地浏览一下,如果便捷华丽的工具箱出现了错误,您就不必挠头或者发送邮件请求支持了。如果了解如何直接使用 XMLHttpRequest,就会发现很容易调试和解决最奇怪的问题。只有让其解决您的问题,工具箱才是好东西。

因此请熟悉 XMLHttpRequest 吧。事实上,如果您有使用工具箱的 Ajax 代码,可以尝试使用 XMLHttpRequest 对象及其属性和方法重新改写。这是一种不错的练习,可以帮助您更好地理解其中的原理。

posted @ 2006-12-18 00:32 J2S 阅读(269) | 评论 (0)编辑 收藏

SWT/JFace开发入门指南(十一)〈转帖〉

JFace中的工具条和菜单

前一节中我们简单介绍了一下 Action 。其实所谓的 Action 就是一个最常用的事件,举个例子来说,对于一个按钮来说它可以有多个事件,比如按键,焦点,鼠标,等等等等吧,但是实际上在使用程序的时候,我们最关心的,就是按下去这个按钮会发生什么,这个其实就是所谓的 Action 。如果大家以前做过 swing/awt 变成的话,应该对 Action 不会陌生。

JFace 里面,一个 Action 可以对应多个 GUI 对象,这些对象就是所谓的 Contribution Item 。比如我们在一般程序里面很常见的“文件”菜单,下面都会有“新建”,“保存”等等。同时我们可以在工具条上放置相应的按钮,那么这些都是有相同的功能,在 JFace 里面我们可以只写一个 Action ,然后把它映射到不同的 ContributionItem 去,而不必为每个部件都写一串处理事件。

我们下面还是通过一个简单的例子来说明,在 JFace 中怎么使用菜单和工具条这两种最基本也是最有用的 Contribution Item

我们这个程序写得很傻,就是一个光秃秃的窗口上做了一个菜单和工具条按钮,功能也只有一个,就是每次点一下,就弹出一个输入框来问你名字是什么,然后显示一个 Hello, xxx 之类的。

首先我们还是来写一个 Action 类:

 1 
 2 public class SayHiAction extends Action {
 3     private Shell shell;
 4 
 5     public SayHiAction(Shell shell) {
 6         super();
 7         this.shell = shell;
 8         this.setText("Say&Hi@Ctrl+H");
 9     }
10 
11     @Override
12     public void run() {
13         InputDialog input = new InputDialog(shell, "Input your name",
14                 "Please input your name here:"nullnull);
15         if (input.open() == Window.OK) {
16             MessageDialog.openInformation(shell, "Hello""Hello, "
17                     + input.getValue() + "!");
18         }
19 
20     }
21 
22 }
23 



代码段 17

这只是一个很简单的 Action 类,没有太多可说的。

然后我们创建一个 ApplicationWindow 类:

 1 
 2 public class Hiyou extends ApplicationWindow {
 3 
 4     private SayHiAction hiaction;
 5     public Hiyou(Shell parentShell) {
 6         super(parentShell);
 7         hiaction=new SayHiAction(getShell());
 8         addMenuBar();
 9         addToolBar(SWT.FLAT | SWT.WRAP);
10     }
11 
12     @Override
13     protected ToolBarManager createToolBarManager(int style) {
14         ToolBarManager toolbar=new ToolBarManager();
15         toolbar.add(hiaction);
16         return toolbar;
17     }
18 
19     @Override
20     protected MenuManager createMenuManager() {
21         MenuManager menubar=new MenuManager();
22         MenuManager fileMenu=new MenuManager("&File");
23         fileMenu.add(hiaction);
24         menubar.add(fileMenu);
25         return menubar;
26     }
27 
28     /**
29      * @param args
30      */
31     public static void main(String[] args) {
32         Hiyou window=new Hiyou(null);
33         window.setBlockOnOpen(true);
34         window.open();
35         Display.getCurrent().dispose();
36     }
37 
38 }
39 

 

代码段 18

大家可能已经注意到了,在这里面我们重载了 createMenuManager createToolBarManager 两个方法,它们的用途就和名字一样,一个是用来创建菜单的,一个是用来创建工具条的。重载了这两个方法以后,通过在构造函数中调用 addMenuBar addToolBar 让工具条和菜单显示出来。

这里值得一提的是 MenuManager ToolBarManager 类,如果大家翻一下 API 文档的话会发现它们都是所谓的 contribution manager (实现了 IConntributionManager 接口),你可以通过这些 contribution manager 来实现对特定组件的管理(添加删除等等)。

具体到菜单的创建,看了我们上面的代码就很明白了,就直接调用相应 MenuManager add 方法把 action 添加上就可以了。 JFace 会自动找到这个 Action getText 方法设置菜单的文字。如果是有好几层菜单,那么只要在重新 new 一个 MenuManager 添加到已有的 MenuManager 里面就可以了。就象前面代码中的:

menubar.add(fileMenu);



至于工具条就更简单了,创建一个 ToolBarManager 然后直接 add 对应的 Action 就可以了。

添加图标

如果菜单只是文字还没有什么,如果你的工具条都是文字是不是会显得干巴巴的?其实只要我们为 Action 设置 ImageDescriptor 就可以了,比如你可以自己画一个图标保存到 Action 的包下面(我画了一个 hi.gif ),然后把 Action 的构造函数改写成这样:

public  SayHiAction(Shell shell) {
        
super ();
        
this .shell  =  shell;
        
this .setText( " Say&Hi@Ctrl+H " );
        
this .setImageDescriptor(ImageDescriptor.createFromFile( this .getClass(),
                
" hi.gif " ));
    }



大家注意最后一句话,就是为 action 设置图标的。然后再运行一下就会发现菜单和工具栏都有图标了。

posted @ 2006-12-18 00:26 J2S 阅读(327) | 评论 (0)编辑 收藏

仅列出标题  下一页
<2024年4月>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

导航

统计

常用链接

留言簿(2)

随笔档案

最新随笔

搜索

积分与排名

最新评论

阅读排行榜

评论排行榜