2007年01月11日

原文地址:http://www.jdon.com/idea/j2eebasic.htm

J2EE学习者越来越多,J2EE本身技术不断在发展,涌现出各种概念,本文章试图从一种容易理解的角度对这些概念向初学者进行解释,以便掌握学习J2EE学习方向。

  首先我们需要知道Java和J2EE是两个不同概念,Java不只是指一种语言,已经代表与微软不同的另外一个巨大阵营,所以Java有时是指一种软件系统的流派,当然目前主要是.NET和Java两大主流体系。

  J2EE可以说指Java在数据库信息系统上实现,数据库信息系统从早期的dBase、到Delphi/VB等C/S结构,发展到B/S(Browser浏览器/Server服务器)结构,而J2EE主要是指B/S结构的实现。

  J2EE又是一种框架和标准,框架类似API、库的概念,但是要超出它们。如果需要详细了解框架,可先从设计模式开始学习。

  J2EE是一个虚的大的概念,J2EE标准主要有三种子技术标准:WEB技术、EJB技术和JMS,谈到J2EE应该说最终要落实到这三个子概念上。

  这三种技术的每个技术在应用时都涉及两个部分:容器部分和应用部分,Web容器也是指Jsp/Servlet容器,你如果要开发一个Web应用,无论是编译或运行,都必须要有Jsp/Servlet库或API支持(除了JDK/J2SE以外)。

  Web技术中除了Jsp/Servlet技术外,还需要JavaBeans或Java Class实现一些功能或者包装携带数据,所以Web技术最初裸体简称为Jsp/Servlet+JavaBeans系统。

  谈到JavaBeans技术,就涉及到组件构件技术(component),这是Java的核心基础部分,很多软件设计概念(设计模式)都是通过JavaBeans实现的。

  JavaBeans不属于J2EE概念范畴中,如果一个JavaBeans对象被Web技术(也就是Jsp/Servlet)调用,那么JavaBeans就运行在J2EE的Web容器中;如果它被EJB调用,它就运行在EJB容器中。

  EJB(企业JavaBeans)是普通JavaBeans的一种提升和规范,因为企业信息系统开发中需要一个可伸缩的性能和事务、安全机制,这样能保证企业系统平滑发展,而不是发展到一种规模重新更换一套软件系统。

  至此,JavaBeans组件发展到EJB后,并不是说以前的那种JavaBeans形式就消失了,这就自然形成了两种JavaBeans技术:EJB 和POJO,POJO完全不同于EJB概念,指的是普通JavaBeans,而且这个JavaBeans不依附某种框架,或者干脆可以说:这个 JavaBeans是你为这个应用程序单独开发创建的。

  J2EE应用系统开发工具有很多:如 JBuilder、Eclipse等,这些IDE首先是Java开发工具,也就是说,它们首要基本功能是可以开发出JavaBeans或Java class,但是如果要开发出J2EE系统,就要落实到要么是Web技术或EJB技术,那么就有可能要一些专门模块功能(如eclipse需要 lomboz插件),最重要的是,因为J2EE系统区分为容器和应用两个部分,所以,在任何开发工具中开发J2EE都需要指定J2EE容器。

  J2EE容器分为WEB容器和EJB容器,Tomcat/Resin是Web容器;JBoss是EJB容器+Web容器等,其中Web容器直接使用 Tomcat实现的。所以你开发的Web应用程序可以在上面两种容器运行,而你开发的Web+EJB应用则只可以在JBoss服务器上运行,商业产品 Websphere/Weblogic等和JBoss属于同一种性质。

  J2EE容器也称为J2EE服务器,大部分时它们概念是一致的。

  如果你的J2EE应用系统的数据库连接是通过JNDI获得,也就是说是从容器中获得,那么你的J2EE应用系统基本与数据库无关,如果你在你的J2EE 应用系统耦合了数据库JDBC驱动的配置,那么你的J2EE应用系统就有数据库概念色彩,作为一个成熟需要推广的J2EE应用系统,不推荐和具体数据库耦合,当然这其中如何保证J2EE应用系统运行性能又是体现你的设计水平了。

  衡量J2EE应用系统设计开发水平高低的标准就是:解耦性;你的应用系统各个功能是否能够彻底脱离?是否不相互依赖,也只有这样,才能体现可维护性、可拓展性的软件设计目标。

  为了达到这个目的,诞生各种框架概念,J2EE框架标准将一个系统划分为WEB和EJB主要部分,当然我们有时不是以这个具体技术区分,而是从设计上抽象为表现层、服务层和持久层,这三个层次从一个高度将J2EE分离开来,实现解耦目的。

  因此,我们实际编程中,也要将自己的功能向这三个层次上靠,做到大方向清楚,泾渭分明,但是没有技术上约束限制要做到这点是很不容易的,因此我们还是必须借助J2EE具体技术来实现,这时,你可以使用EJB规范实现服务层和持久层,Web技术实现表现层;

  EJB为什么能将服务层从Jsp/Servlet手中分离出来,因为它对JavaBeans编码有强制的约束,现在有一种对JavaBeans弱约束,使用Ioc模式实现的(当然EJB 3.0也采取这种方式),在Ioc模式诞生前,一般都是通过工厂模式来对JavaBeans约束,形成一个服务层,这也是是Jive这样开源论坛设计原理之一。

  由此,将服务层从表现层中分离出来目前有两种可选架构选择:管理普通JavaBeans(POJO)框架(如Spring、JdonFramework)以及管理EJB的EJB框架,因为EJB不只是框架,还是标准,而标准可以扩展发展,所以,这两种区别将来是可能模糊,被纳入同一个标准了。 但是,个人认为:标准制定是为某个目的服务的,总要牺牲一些换取另外一些,所以,这两种架构会长时间并存。

  这两种架构分歧也曾经诞生一个新名词:完全POJO的系统也称为轻量级系统(lightweight),其实这个名词本身就没有一个严格定义,更多是一个吸引人的招牌,轻量是指容易学习容易使用吗?按照这个定义,其实轻量Spring等系统并不容易学习;而且EJB 3.0(依然叫EJB)以后的系统是否可称为轻量级了呢?

  前面谈了服务层框架,使用服务层框架可以将JavaBeans从Jsp/Servlet中分离出来,而使用表现层框架则可以将Jsp中剩余的JavaBeans完全分离,这部分 JavaBeans主要负责显示相关,一般是通过标签库(taglib)实现,不同框架有不同自己的标签库,Struts是应用比较广泛的一种表现层框架。

  这样,表现层和服务层的分离是通过两种框架达到目的,剩余的就是持久层框架了,通过持久层的框架将数据库存储从服务层中分离出来是其目的,持久层框架有两种方向:直接自己编写JDBC等SQL语句(如iBatis);使用O/R Mapping技术实现的Hibernate和JDO技术;当然还有EJB中的实体Bean技术。

  持久层框架目前呈现百花齐放,各有优缺点的现状,所以正如表现层框架一样,目前没有一个框架被指定为标准框架,当然,表现层框架现在又出来了一个JSF,它代表的页面组件概念是一个新的发展方向,但是复杂的实现让人有些忘而却步。

  在所有这些J2EE技术中,虽然SUN公司发挥了很大的作用,不过总体来说:网络上有这样一个评价:SUN的理论天下无敌;SUN的产品用起来撞墙;对于初学者,特别是那些试图通过或已经通过SUN认证的初学者,赶快摆脱SUN的阴影,立即开溜,使用开源领域的产品来实现自己的应用系统。

  最后,你的J2EE应用系统如果采取上面提到的表现层、服务层和持久层的框架实现,基本你也可以在无需深刻掌握设计模式的情况下开发出一个高质量的应用系统了。

  还要注意的是: 开发出一个高质量的J2EE系统还需要正确的业务需求理解,那么域建模提供了一种比较切实可行的正确理解业务需求的方法,相关详细知识可从UML角度结合理解。

  当然,如果你想设计自己的行业框架,那么第一步从设计模式开始吧,因为设计模式提供你一个实现JavaBeans或类之间解耦参考实现方法,当你学会了系统基本单元JavaBean或类之间解耦时,那么系统模块之间的解耦你就可能掌握,进而你就可以实现行业框架的提炼了,这又是另外一个发展方向了。

  以上理念可以总结为一句话:
J2EE开发三件宝: Domain Model(域建模)、patterns(模式)和framework(框架)。

  推荐一套高质量的J2EE开源系统: JPestore
  
  如果初学者没有理解,欢迎继续讨论,大胆提出你心中的疑问。

参考文章:Java企业系统架构选择考量

Jdon Framework的服务调用命令模式实现

2006年12月19日

RMI,远程方法调用(Remote Method Invocation)是Enterprise JavaBeans的支柱,是建立分布式Java应用程序的方便途径。RMI是非常容易使用的,但是它非常的强大。
  RMI的基础是接口,RMI构架基于一个重要的原理:定义接口和定义接口的具体实现是分开的。下面我们通过具体的例子,建立一个简单的远程计算服务和使用它的客户程序

  一个正常工作的RMI系统由下面几个部分组成: 

  • 远程服务的接口定义
  • 远程服务接口的具体实现
  • Stub 和 Skeleton 文件
  • 一个运行远程服务的服务器
  • 一个RMI命名服务,它允许客户端去发现这个远程服务
  • 类文件的提供者(一个HTTP或者FTP服务器)
  • 一个需要这个远程服务的客户端程序

      下面我们一步一步建立一个简单的RMI系统。首先在你的机器里建立一个新的文件夹,以便放置我们创建的文件,为了简单起见,我们只使用一个文件夹存放客户端和服务端代码,并且在同一个目录下运行服务端和客户端。

      如果所有的RMI文件都已经设计好了,那么你需要下面的几个步骤去生成你的系统:

      1、  编写并且编译接口的Java代码
      2、  编写并且编译接口实现的Java代码
      3、  从接口实现类中生成 Stub 和 Skeleton 类文件
      4、  编写远程服务的主运行程序
      5、  编写RMI的客户端程序
      6、  安装并且运行RMI系统

      1、接口

      第一步就是建立和编译服务接口的Java代码。这个接口定义了所有的提供远程服务的功能,下面是源程序:

    1. //Calculator.java
    2. //define the interface
    3. import java.rmi.Remote;
    4.  
    5. public interface Calculator extends Remote
    6. {
    7.     public long add(long a, long b) 
    8.         throws java.rmi.RemoteException
    9.  
    10.     public long sub(long a, long b) 
    11.         throws java.rmi.RemoteException
    12.  
    13.     public long mul(long a, long b) 
    14.         throws java.rmi.RemoteException
    15.  
    16.     public long div(long a, long b) 
    17.         throws java.rmi.RemoteException

      注意,这个接口继承自Remote,每一个定义的方法都必须抛出一个RemoteException异常对象。

      建立这个文件,把它存放在刚才的目录下,并且编译。

      >javac Calculator.java

      2、接口的具体实现

      下一步,我们就要写远程服务的具体实现,这是一个CalculatorImpl类文件:

    1. //CalculatorImpl.java
    2. //Implementation
    3. import java.rmi.server.UnicastRemoteObject;
    4.  
    5. public class CalculatorImpl extends UnicastRemoteObject implements Calculator 
    6.  
    7.     // 这个实现必须有一个显式的构造函数,并且要抛出一个RemoteException异常 
    8.     public CalculatorImpl() 
    9.         throws java.rmi.RemoteException { 
    10.         super(); 
    11.     } 
    12.  
    13.     public long add(long a, long b) 
    14.         throws java.rmi.RemoteException { 
    15.         return a + b; 
    16.     } 
    17.  
    18.     public long sub(long a, long b) 
    19.         throws java.rmi.RemoteException { 
    20.         return a - b; 
    21.     } 
    22.  
    23.     public long mul(long a, long b) 
    24.         throws java.rmi.RemoteException { 
    25.         return a * b; 
    26.     } 
    27.  
    28.     public long div(long a, long b) 
    29.         throws java.rmi.RemoteException { 
    30.         return a / b; 
    31.     } 

      同样的,把这个文件保存在你的目录里然后编译他。

      这个实现类使用了UnicastRemoteObject去联接RMI系统。在我们的例子中,我们是直接的从UnicastRemoteObject这个类上继承的,事实上并不一定要这样做,如果一个类不是从UnicastRmeoteObject上继承,那必须使用它的exportObject()方法去联接到RMI。

      如果一个类继承自UnicastRemoteObject,那么它必须提供一个构造函数并且声明抛出一个RemoteException对象。当这个构造函数调用了super(),它久激活UnicastRemoteObject中的代码完成RMI的连接和远程对象的初始化。

      3、Stubs 和Skeletons

      下一步就是要使用RMI编译器rmic来生成桩和框架文件,这个编译运行在远程服务实现类文件上。

      >rmic CalculatorImpl

      在你的目录下运行上面的命令,成功执行完上面的命令你可以发现一个Calculator_stub.class文件,如果你是使用的Java2SDK,那么你还可以发现Calculator_Skel.class文件。

      4、主机服务器

      远程RMI服务必须是在一个服务器中运行的。CalculatorServer类是一个非常简单的服务器。

    1. //CalculatorServer.java
    2. import java.rmi.Naming;
    3.  
    4. public class CalculatorServer {
    5.  
    6.    public CalculatorServer() {
    7.      try {
    8.        Calculator c = new CalculatorImpl();
    9.        Naming.rebind("rmi://localhost:1099/CalculatorService", c);
    10.      } catch (Exception e) {
    11.        System.out.println("Trouble: " + e);
    12.      }
    13.    }
    14.  
    15.    public static void main(String args[]) {
    16.      new CalculatorServer();
    17.    }
    18. }

      建立这个服务器程序,然后保存到你的目录下,并且编译它。

      5、客户端

      客户端源代码如下:

    1. //CalculatorClient.java
    2.  
    3. import java.rmi.Naming
    4. import java.rmi.RemoteException
    5. import java.net.MalformedURLException
    6. import java.rmi.NotBoundException
    7.  
    8. public class CalculatorClient { 
    9.  
    10.     public static void main(String[] args) { 
    11.         try { 
    12.             Calculator c = (Calculator)
    13.                            Naming.lookup(
    14.                  "rmi://localhost
    15.                         /CalculatorService"); 
    16.             System.out.println( c.sub(4, 3) ); 
    17.             System.out.println( c.add(4, 5) ); 
    18.             System.out.println( c.mul(3, 6) ); 
    19.             System.out.println( c.div(9, 3) ); 
    20.         } 
    21.         catch (MalformedURLException murle) { 
    22.             System.out.println(); 
    23.             System.out.println(
    24.               "MalformedURLException"); 
    25.             System.out.println(murle); 
    26.         } 
    27.         catch (RemoteException re) { 
    28.             System.out.println(); 
    29.             System.out.println(
    30.                         "RemoteException"); 
    31.             System.out.println(re); 
    32.         } 
    33.         catch (NotBoundException nbe) { 
    34.             System.out.println(); 
    35.             System.out.println(
    36.                        "NotBoundException"); 
    37.             System.out.println(nbe); 
    38.         } 
    39.         catch (
    40.             java.lang.ArithmeticException
    41.                                       ae) { 
    42.             System.out.println(); 
    43.             System.out.println(
    44.              "java.lang.ArithmeticException"); 
    45.             System.out.println(ae); 
    46.         } 
    47.     } 

      保存这个客户端程序到你的目录下(注意这个目录是一开始建立那个,所有的我们的文件都在那个目录下),并且编译他。

      6、运行RMI系统

      现在我们建立了所有运行这个简单RMI系统所需的文件,现在我们终于可以运行这个RMI系统啦!来享受吧。

      我们是在命令控制台下运行这个系统的,你必须开启三个控制台窗口,一个运行服务器,一个运行客户端,还有一个运行RMIRegistry。

      首先运行注册程序RMIRegistry,你必须在包含你刚写的类的那么目录下运行这个注册程序。

      >rmiregistry

      好,这个命令成功的话,注册程序已经开始运行了,不要管他,现在切换到另外一个控制台,在第二个控制台里,我们运行服务器CalculatorService,因为RMI的安全机制将在服务端发生作用,所以你必须增加一条安全策略。以下是对应安全策略的例子 
    grant {
    permission java.security.AllPermission "", "";
    };

      注意:这是一条最简单的安全策略,它允许任何人做任何事,对于你的更加关键性的应用,你必须指定更加详细安全策略。

      现在为了运行服务端,你需要除客户类(CalculatorClient.class)之外的所有的类文件。确认安全策略在policy.txt文件之后,使用如下命令来运行服务器。

      > java -Djava.security.policy=policy.txt CalculatorServer

      这个服务器就开始工作了,把接口的实现加载到内存等待客户端的联接。好现在切换到第三个控制台,启动我们的客户端。

      为了在其他的机器运行客户端程序你需要一个远程接口(Calculator.class) 和一个stub(CalculatorImpl_Stub.class)。 使用如下命令运行客户端

       > java -Djava.security.policy=policy.txt CalculatorClient

      如果所有的这些都成功运行,你应该看到下面的输出:
      1
      9
      18
      3

      如果你看到了上面的输出,恭喜你,你成功了,你已经成功的创建了一个RMI系统,并且使他正确工作了。即使你运行在同一个计算机上,RMI还是使用了你的网络堆栈和TCP/IP去进行通讯,并且是运行在三个不同的Java虚拟机上。这已经是一个完整的RMI系统。

  • 2006年12月13日

    什么应用适合Web Services

    Web Services这么多的缺点是不是让你很泄气?其实,已经有很多成功的Web Services的应用和越来越多的开发商的加盟,证明Web Services一定会成为新一代WEB信息通讯的主流。经过不断的发展,Web Services一定能克服自身的弱点,得到更广泛的应用。但就目前来说,Web Services比较适合用于下列形式的应用:

    • 基于WANInternet的应用

    要在Internet上创建基于二进制协议的RMI/IIOP的应用,一般都会遇上一个大麻烦防火墙。客户端浏览器极大可能在ISP防火墙后,大多数防火墙都只能允许和外部的HTTP连接,因此想要ISP防火墙后的客户端能和防火墙外的RMI/IIOP的应用端口进行连接的话,就要改变ISP的安全策略,让客户端能够连接除了80以外的其他端口。可是当运行RMI/IIOP的应用的服务器为了安全也在防火墙之后的DMZ中的话,那这个连接就更加复杂了,要跨越两个防火墙。
    Web Services由于使用的是HTTP协议,传递的是纯文本的XML数据,因此拥有穿透防火墙的良好性能。

    • 基于异构平台的应用

    XML语言本身就是跨平台、跨语言的数据表示方法,在加上通用的HTTP等协议,使得Web Services天生就适用于基于异构平台的应用。如果你的客户端包含了各种不同的平台,例如,你希望你的服务即可以被JAVA程序所调用,又可以由VBCOM程序所调用。你有两种选择:一种是为不同的平台提供相应的API,还要为不同的语言提供API;如果提供Web Services,所有平台和语言都可以调用了!

    • 需要强安全特性的应用

    很多人都认为,安全性是Web Services的弱项。其实不然,经过不断的完善和各种新的协议的出台,Web Services完全可以用于安全性很强的应用环境下。并且,由于Web Services使用HTTP协议进行传输,所以可以和容易就使用已经很成熟的基于HTTP的各种安全技术。

    • EAI(企业应用集成)
      这是目前Web Services应用最看好的方向之一。大多数企业内部都有着各种各样的应用系统,它们是在不同的领导在任期间,由不同的软件开发商开发,因此运行在不同的平台和系统上,系统的开发语言也各不相同。由于现代企业信息自动化要求的提高,各个系统之间的互动和相互通讯便提到日程上。因此,保护原有投资,重用遗留系统是当前很多中大型企业的重要任务。
      由于遗留系统的运行平台是异构环境,因此企业应用集成的代价一般来说是很高的。但如果使用Web Services作为应用集成的手段,将会大大降低集成的消耗。Web Services与平台和语言无关的特性,以及各种平台和环境下的开发工具都是企业应用集成的利器。
      另外,在开发新的应用系统的时候,仍然需要考虑和其他系统的集成,需要考虑调用其他系统的功能,和被其他系统所调用。使用Web Services作为系统与外部交流的接口,能够使新的系统和别的系统之间保持松耦合的关系,保持较高的可扩展性。

    • 行业内部B2B应用
      行业内部的应用是Web Services的另外一个方向。因为在一个行业中,商业业务是很相似的,因此在行业内部很容易形成服务的标准,使所有的业内企业共同遵守;但怎样实现服务和使用什么样的系统,决定权在于各个企业自己。例如,电信运营商之间的结算服务,银行之间的转帐服务等都可以形成行业标准,以WSDL的形式公布出来。各个企业之间可以选择不同的平台进行服务的实现。

    提高Web Services的性能

    要想提高Web Services应用的性能,需要对整个系统做全盘的考虑。一般来说,有以下几点需要注意:

    1. Web Services的颗粒度
      选择Web Services的颗粒度是提高Web Services应用的性能的主要手段。因为Web Services使用的传输协议为HTTPSMTP等,这些协议都是面向无状态的连接协议,每一个请求都要建立一个新的连接。因此Web Services的调用不能象数据库JDBCODBC)接口一样可以进行精细而复杂的方法调用(例如,先获得Connection,再获得结果集,然后一行一行获取结果)。Web Services比较适用于大颗粒度的应用,在一个调用中便获得所有的信息(比如说银行之间的转帐,在一次调用中就将包括金额和认证等所有的信息都传输过去)。

    2. 谨慎使用XML接口
      系统之间的接口可以使用XML,这样可以增加系统的灵活性;但不要使用XML作为系统内部的接口,因为这不会带来任何好处,尽量使用二进制作为系统内部的接口,避免不必要的XML文档的解析和效验;在处理XML的时候,尽快将XML转换成内部对象,XML的传递只会增加系统的开销。

    3. 最大可能性使用CACHE
      当有些信息是只读的,或者在一段时间内保持不变,就可以使用CACHE。无论是客户端的CACHE还是服务器端的CACHE,都能大大提高系统的性能

    总结

    一旦Web Services得到更加广泛的应用,使得各种服务可以动态查找和定位,这样就提供了不同设备之间各种各样的信息交互方式,将会大大改变商业运做的模式和信息交流的风格。

    你可以使用别人已经成熟的功能来使自己提供更好的服务,例如google,它的搜索引擎可以通过Web Services来访问。这就意味着在你的系统中可以方便的嵌入使用google的强大搜索功能,而不论你的系统是运行在什么平台上,使google的搜索引擎成为你系统的一部分,(请参考http://www.google.com/apis/)。站在别人的肩膀上,毕竟要看得远些!

    面对Web Services你现在可以不行动,但你一定要准备好!

    http://java.ccidnet.com/art/3755/20060508/547547_1.html

    无论是在计算机杂志还是在Internet上,目前最热门的话题莫过于“Web Services”。各个平台之间的锋争,各个新产品的发布,众多新标准的制订,大都和Web Services有关。

    我的一些朋友是这样的一些人,他们总是用着最新的平台,尝试着最新的技术,他们喜欢变化,喜欢流行,用他们自己的话说,新技术创造新生活!可是,当我的一个朋友,带领他们一个部门的开发人员,花了两个月的,将他们内部的管理系统用Web Services重新设计和实现了一遍,却发现在实际使用的情况下,系统性能非常糟糕。他提出了这样一个问题:是不是Web Services现在还处于实验和市场炒作时期,根本没有进入实用的阶段?简单的回答是:Web Services不是万能的,它有它的应用范围和优势劣势。

    我的一些朋友是这样的一些人,他们总是用着最新的平台,尝试着最新的技术,他们喜欢变化,喜欢流行,用他们自己的话说,新技术创造新生活!可是,当我的一个朋友,带领他们一个部门的开发人员,花了两个月的,将他们内部的管理系统用Web Services重新设计和实现了一遍,却发现在实际使用的情况下,系统性能非常糟糕。他提出了这样一个问题:是不是Web Services现在还处于实验和市场炒作时期,根本没有进入实用的阶段?简单的回答是:Web Services不是万能的,它有它的应用范围和优势劣势。

    Web Services的起源

    Web应用的巨大成功和不断发展,使其渗透到商业领域和个人生活的各个方面。人们只要使用浏览器,就可以享受到各种各样的Web服务,例如网上购物,网上交易,网络游戏,预定车票,网上聊天和交友等等。与此同时,由于Web技术所带来的优势(统一的客户端和较好的维护性),使一些传统的应用纷纷转型到BS结构上。

    然而,在发展中,逐步暴露了一些问题。所有这些Web页面都是为人准备的,是让人去阅读,去输入,去判断。因此各种反映视觉效果的内容占用了大量的网络带宽,例如各种图片,字体信息,文字排版样式等。而真正含有高价值的一些信息,被深深埋在这些显示信息中,很难被其他应用和程序所使用。更重要的是,各种web服务之间缺少交互和通讯的机制。

    程序之间的互相通讯很重要吗?简单举一个例子。

    假设你经常去国外出差,在你回国以后,第一件事就是费用报销了。而你们公司有这样的财务规定,所有的报销款,都按报销当天的外汇比价进行结算。因此在你填写报销单的时候必须先填写每一笔在各个国家的花消,然后上网查出当天的外汇比价,填写到报销单上。剩下的事情也许不用你做了,你的报销单填写工具会自动进行换算和统计。

    觉得有什么不妥吗?作为IT公司的员工,也许都有一个特点,计算机能做的事情,尽量要计算机去做。外汇比价的查询可以让计算机自动去做嘛!然而,让你的程序自动去网页上查找指定的外汇比价可不是一件容易的事。因为这些网页是给人阅读的,人眼和大脑的反应速度有多快,它们可以从一整页信息中快速定位到你所要的内容,而且无论网页怎样变化和改版都不会带来太大的影响。而应用程序想要做同样的事就差得太远了。因此,现在需要的是专门为应用程序制定的Web服务。

    随着应用程序之间通讯的需求越来越大,这就需要制定统一的标准和协议。HP公司是最先提出这个观点的公司,他们制定了有关“e-Speak”的标准来保证应用程序之间的交互,并声称将成为下一代Internet信息交互的标准。而随后,MicroSoft意识到此计划的美好前景,便推出了.Net战略;IBM很快就发布了Web Services Toolkit(WSTK),和Web Services Development Environment(WSDE),申明对Web Services的全力支持。与此同时,Oracle也开发出自己的Dynamic Services,并和Oracle 8i Release 2集成在一起。在这以后,W3C统一制定了Web Services的各种标准。而SUN公司在宣布了自己的Web Services的框架以后,将Web Services的标准溶入J2EE的环境,使Web Services有了广泛支持的基础和平台。

    Web Services的基本原理

    Web Services 是通过一系列标准和协议来保证程序之间的动态连接。其中最基本的协议包括:SOAP, WSDL, UDDI

    • SOAP: 是“Simple Object Access Protocol”的缩写,SOAP是消息传递的协议,它规定了Web Services之间是怎样传递信息的。简单的说,SOAP规定了:
      1.
      传递信息的格式为XML。这就使Web Services能够在任何平台上,用任何语言进行实现。
      2.
      远程对象方法调用的格式。规定了怎样表示被调用对象以及调用的方法名称和参数类型等。
      3.
      参数类型和XML格式之间的映射。这是因为,被调用的方法有时候需要传递一个复杂的参数,例如,一个Person对象。怎样用XML来表示一个对象参数,也是SOAP所定义的范围。
      4.
      异常处理以及其他的相关信息.

    • WSDL:是“Web Services Description Language”的缩写.意如其名,WSDLWeb Services的定义语言。当你实现了某种服务的时候(,股票查询服务),为了让别的程序调用,你必须告诉大家你的服务的接口.例如,服务名称,服务所在的机器名称,监听端口号,传递参数的类型,个数和顺序,返回结果的类型等等.这样别的应用程序才能调用你的服务。WSDL协议就是规定了有关Web Services描述的标准。

    • UDDI: Universal Description, Discovery, and Integration的缩写。简单说,UDDI用于集中存放和查找WSDL描述文件,起着目录服务器的作用。

     

    如上图,一个Web Services的生命周期是:

    1. 实现一个Web Services,使其能够接受和响应SOAP消息(现在有很多工具都可以帮助实现)。

    2. 撰写一个WSDL文件用于描述此Web Services。(现在有很多工具可以自动生成WSDL文件)。

    3. 将此WSDL发布到UDDI上。

    4. 其他的应用程序(客户端)从UDDI上搜索到你的WSDL

    5. 根据你的WSDL,客户端可以编写程序(现在有很多工具可以自动生成调用程序)调用你的Web Services

    Web Services的缺点

    由于是基于XML的应用,Web Services与生俱来地在拥有XML带来的一切优势的同时,不可避免地继承了XML所带来的一些限制。

    • Web Services通常需要大量的CPU资源。因为XML数据要经过多步处理才能被系统使用。首先是效验(validate),检查它的格式是否符合XML的规范,以及根据应用程序定义(DTDSchema)检查是否符合语义上的规范;然后还要进行解析(parse),从XML文档分解出单个的元素;最后还要转换成应用程序所需要的二进制表达(例如,把“12” 转换成整型12的二进制表示)。

    • Web Services还意味着占用较多的内存资源。在进行XML解析的时候,会产生大量的临时内存对象。特别是在处理DOM对象的时候。这些大量的临时对象对于象JAVA这类自动回收内存的语言和系统其实是一种负担,大量的临时对象将会使系统每隔一段时间就会进行内存回收,从而降低系统的性能。当然,现在有的Web Services的产品(如axis)采用了SAX技术,大大减少了内存的占用量。详细信息请参考:(http://xml.apache.org/axis/index.html)。

    • 网络资源的消耗也是Web Services应用的一些限制。因为基于XML数据的传递通常数据量要比二进制的协议(如RMI/IIOP)要大的多。这种额外的消耗在网络资源比较紧张或网络传输比较频繁的应用中会产生一定的影响。

    除了XML带来的限制,Web Services本身也具有一些缺点:

    • 到目前为止,Web Services还可以说是一种无状态(stateless)的服务。
      所谓stateless就意味着不保存客户端服务调用者的任何信息。这是由Web Services的本质所决定的。Web Services在本质上是要为应用程序之间提供数据通讯的标准,为企业应用之间动态地提供大颗粒度的服务,所以Web Services并不适合于非常精细的基于会话的方法调用以及复杂的事务(transaction)处理之中。
      也许有人会对我这点提出异议!因为,现在有很多Web Services的产品(如WASD),不但可以保存session的信息,使服务成为有状态(stateful)的服务,而且还实现了remote interface,可以在Web Services的会话中传递远程对象的句柄,让客户端可以操纵递远程对象(详细信息请参考:http://www.systinet.com )。原理上说,这并不难实现,因为在XML数据中,可以互相传送任何数据,包括sessionIDtransactionID,有了这些ID,从技术角度上说,实现有状态(stateful)的服务和事务处理并不复杂。但是,这样功能缺少标准的支持,当前版本的WSDL还无法表示这些复杂的服务。在企业内部,你可以任意使用这些特殊的功能,可以自己定义会话状态的交互协议,因为服务者和服务调用者之间的通讯都在你的控制之中;然而要将这些服务发布到Internet上,其他的应用程序是无法根据标准去识别这些特殊功能。

    • 数据绑定也存在一些不足。
      因为所有的数据传递都用XML格式,因此,需要在二进制数据和XML数据之间有个转换。但是,并不是所有的二进制数据都能方便地用XML来表示,并不是所有的JAVA对象都能被XML所表示。因此,经常在转换过程中会出现语义丢失的情况。

    • 技术要求高,学习曲线较长。
      每一个Web Services的产品,都有丰富的工具,能够根据Web Services的定义(如WSDL文件)方便地生成客户端的程序;能够将一般的服务程序,很容易就包装成Web Services服务。因此,各个Web Services的产品都声称自己的平台容易使用,根本不需要了解XML,也不需要了解什么WSDLUDDISOAP就能使用发布Web Services。特别是一个朋友告诉我,他在什么都不了解的情况下,用.NET花了15分钟就发布了一个Web Services
      千万不要醉心于这种简便,这对于简单的Demo也许是对的,可是对于真正意义上严肃的应用,一定要了解Web Services的各个方面,设计整体结构和解决方案,还要根据具体的应用调整性能。所有这些都需要对Web Services知识的全面掌握。

    2006年09月22日

    1.如何获得当前文件路径

    常用:

    字符串类型:System.getProperty("user.dir");

    综合:

    package com.zcjl.test.base;
    import java.io.File;
    public class Test {
        public static void main(String[] args) throws Exception {
            System.out.println(
                Thread.currentThread().getContextClassLoader().getResource(""));
            System.out.println(Test.class.getClassLoader().getResource(""));
            System.out.println(ClassLoader.getSystemResource(""));
            System.out.println(Test.class.getResource(""));
            System.out.println(Test.class.getResource("/"));
            System.out.println(new File("").getAbsolutePath());
            System.out.println(System.getProperty("user.dir"));

        }
    }

    2.Web服务中

    (1).Weblogic

    WebApplication的系统文件根目录是你的weblogic安装所在根目录。
    例如:如果你的weblogic安装在c:\bea\weblogic700…..
    那么,你的文件根路径就是c:\.
    所以,有两种方式能够让你访问你的服务器端的文件:
    a.使用绝对路径:
    比如将你的参数文件放在c:\yourconfig\yourconf.properties,
    直接使用 new FileInputStream("yourconfig/yourconf.properties");
    b.使用相对路径:
    相对路径的根目录就是你的webapplication的根路径,即WEB-INF的上一级目录,将你的参数文件放在yourwebapp\yourconfig\yourconf.properties,
    这样使用:
    new FileInputStream("./yourconfig/yourconf.properties");
    这两种方式均可,自己选择。

    (2).Tomcat

    在类中输出System.getProperty("user.dir");显示的是%Tomcat_Home%/bin

    (3).Resin

    不是你的JSP放的相对路径,是JSP引擎执行这个JSP编译成SERVLET
    的路径为根.比如用新建文件法测试File f = new File("a.htm");
    这个a.htm在resin的安装目录下

    (4).如何读相对路径哪?

    在Java文件中getResource或getResourceAsStream均可

    例:getClass().getResourceAsStream(filePath);//filePath可以是"/filename",这里的/代表web发布根路径下WEB-INF/classes

    (5).获得文件真实路径

    string  file_real_path=request.getRealPath("mypath/filename"); 

    通常使用request.getRealPath("/"); 

    3.文件操作的类

    import java.io.*;
    import java.net.*;
    import java.util.*;
    //import javax.swing.filechooser.*;
    //import org.jr.swing.filter.*;

    /**
    * 此类中封装一些常用的文件操作。
    * 所有方法都是静态方法,不需要生成此类的实例,
    * 为避免生成此类的实例,构造方法被申明为private类型的。
    * @since  0.1
    */

    public class FileUtil {
      /**
       * 私有构造方法,防止类的实例化,因为工具类不需要实例化。
       */
      private FileUtil() {

      }

      /**
       * 修改文件的最后访问时间。
       * 如果文件不存在则创建该文件。
       * <b>目前这个方法的行为方式还不稳定,主要是方法有些信息输出,这些信息输出是否保留还在考

    虑中。</b>
       * @param file 需要修改最后访问时间的文件。
       * @since  0.1
       */
      public static void touch(File file) {
        long currentTime = System.currentTimeMillis();
        if (!file.exists()) {
          System.err.println("file not found:" + file.getName());
          System.err.println("Create a new file:" + file.getName());
          try {
            if (file.createNewFile()) {
            //  System.out.println("Succeeded!");
            }
            else {
            //  System.err.println("Create file failed!");
            }
          }
          catch (IOException e) {
          //  System.err.println("Create file failed!");
            e.printStackTrace();
          }
        }
        boolean result = file.setLastModified(currentTime);
        if (!result) {
        //  System.err.println("touch failed: " + file.getName());
        }
      }

      /**
       * 修改文件的最后访问时间。
       * 如果文件不存在则创建该文件。
       * <b>目前这个方法的行为方式还不稳定,主要是方法有些信息输出,这些信息输出是否保留还在考

    虑中。</b>
       * @param fileName 需要修改最后访问时间的文件的文件名。
       * @since  0.1
       */
      public static void touch(String fileName) {
        File file = new File(fileName);
        touch(file);
      }

      /**
       * 修改文件的最后访问时间。
       * 如果文件不存在则创建该文件。
       * <b>目前这个方法的行为方式还不稳定,主要是方法有些信息输出,这些信息输出是否保留还在考

    虑中。</b>
       * @param files 需要修改最后访问时间的文件数组。
       * @since  0.1
       */
      public static void touch(File[] files) {
        for (int i = 0; i < files.length; i++) {
          touch(files);
        }
      }

      /**
       * 修改文件的最后访问时间。
       * 如果文件不存在则创建该文件。
       * <b>目前这个方法的行为方式还不稳定,主要是方法有些信息输出,这些信息输出是否保留还在考

    虑中。</b>
       * @param fileNames 需要修改最后访问时间的文件名数组。
       * @since  0.1
       */
      public static void touch(String[] fileNames) {
        File[] files = new File[fileNames.length];
        for (int i = 0; i < fileNames.length; i++) {
          files = new File(fileNames);
        }
        touch(files);
      }

      /**
       * 判断指定的文件是否存在。
       * @param fileName 要判断的文件的文件名
       * @return 存在时返回true,否则返回false。
       * @since  0.1
       */
      public static boolean isFileExist(String fileName) {
        return new File(fileName).isFile();
      }

      /**
       * 创建指定的目录。
       * 如果指定的目录的父目录不存在则创建其目录书上所有需要的父目录。
       * <b>注意:可能会在返回false的时候创建部分父目录。</b>
       * @param file 要创建的目录
       * @return 完全创建成功时返回true,否则返回false。
       * @since  0.1
       */
      public static boolean makeDirectory(File file) {
        File parent = file.getParentFile();
        if (parent != null) {
          return parent.mkdirs();
        }
        return false;
      }

      /**
       * 创建指定的目录。
       * 如果指定的目录的父目录不存在则创建其目录书上所有需要的父目录。
       * <b>注意:可能会在返回false的时候创建部分父目录。</b>
       * @param fileName 要创建的目录的目录名
       * @return 完全创建成功时返回true,否则返回false。
       * @since  0.1
       */
      public static boolean makeDirectory(String fileName) {
        File file = new File(fileName);
        return makeDirectory(file);
      }

      /**
       * 清空指定目录中的文件。
       * 这个方法将尽可能删除所有的文件,但是只要有一个文件没有被删除都会返回false。
       * 另外这个方法不会迭代删除,即不会删除子目录及其内容。
       * @param directory 要清空的目录
       * @return 目录下的所有文件都被成功删除时返回true,否则返回false.
       * @since  0.1
       */
      public static boolean emptyDirectory(File directory) {
        boolean result = false;
        File[] entries = directory.listFiles();
        for (int i = 0; i < entries.length; i++) {
          if (!entries.delete()) {
            result = false;
          }
        }
        return true;
      }

      /**
       * 清空指定目录中的文件。
       * 这个方法将尽可能删除所有的文件,但是只要有一个文件没有被删除都会返回false。
       * 另外这个方法不会迭代删除,即不会删除子目录及其内容。
       * @param directoryName 要清空的目录的目录名
       * @return 目录下的所有文件都被成功删除时返回true,否则返回false。
       * @since  0.1
       */
      public static boolean emptyDirectory(String directoryName) {
        File dir = new File(directoryName);
        return emptyDirectory(dir);
      }

      /**
       * 删除指定目录及其中的所有内容。
       * @param dirName 要删除的目录的目录名
       * @return 删除成功时返回true,否则返回false。
       * @since  0.1
       */
      public static boolean deleteDirectory(String dirName) {
        return deleteDirectory(new File(dirName));
      }

      /**
       * 删除指定目录及其中的所有内容。
       * @param dir 要删除的目录
       * @return 删除成功时返回true,否则返回false。
       * @since  0.1
       */
      public static boolean deleteDirectory(File dir) {
        if ( (dir == null) || !dir.isDirectory()) {
          throw new IllegalArgumentException("Argument " + dir +
                                             " is not a directory. ");
        }

        File[] entries = dir.listFiles();
        int sz = entries.length;

        for (int i = 0; i < sz; i++) {
          if (entries.isDirectory()) {
            if (!deleteDirectory(entries)) {
              return false;
            }
          }
          else {
            if (!entries.delete()) {
              return false;
            }
          }
        }

        if (!dir.delete()) {
          return false;
        }
        return true;
      }

      /**
       * 返回文件的URL地址。
       * @param file 文件
       * @return 文件对应的的URL地址
       * @throws MalformedURLException
       * @since  0.4
       * @deprecated 在实现的时候没有注意到File类本身带一个toURL方法将文件路径转换为URL。
       *             请使用File.toURL方法。
       */
      public static URL getURL(File file) throws MalformedURLException {
        String fileURL = "file:/" + file.getAbsolutePath();
        URL url = new URL(fileURL);
        return url;
      }

      /**
       * 从文件路径得到文件名。
       * @param filePath 文件的路径,可以是相对路径也可以是绝对路径
       * @return 对应的文件名
       * @since  0.4
       */
      public static String getFileName(String filePath) {
        File file = new File(filePath);
        return file.getName();
      }

      /**
       * 从文件名得到文件绝对路径。
       * @param fileName 文件名
       * @return 对应的文件路径
       * @since  0.4
       */
      public static String getFilePath(String fileName) {
        File file = new File(fileName);
        return file.getAbsolutePath();
      }

      /**
       * 将DOS/Windows格式的路径转换为UNIX/Linux格式的路径。
       * 其实就是将路径中的"\"全部换为"/",因为在某些情况下我们转换为这种方式比较方便,
       * 某中程度上说"/"比"\"更适合作为路径分隔符,而且DOS/Windows也将它当作路径分隔符。
       * @param filePath 转换前的路径
       * @return 转换后的路径
       * @since  0.4
       */
      public static String toUNIXpath(String filePath) {
        return filePath.replace(‘\\’, ‘/’);
      }

      /**
       * 从文件名得到UNIX风格的文件绝对路径。
       * @param fileName 文件名
       * @return 对应的UNIX风格的文件路径
       * @since  0.4
       * @see #toUNIXpath(String filePath) toUNIXpath
       */
      public static String getUNIXfilePath(String fileName) {
        File file = new File(fileName);
        return toUNIXpath(file.getAbsolutePath());
      }

      /**
       * 得到文件的类型。
       * 实际上就是得到文件名中最后一个“.”后面的部分。
       * @param fileName 文件名
       * @return 文件名中的类型部分
       * @since  0.5
       */
      public static String getTypePart(String fileName) {
        int point = fileName.lastIndexOf(‘.’);
        int length = fileName.length();
        if (point == -1 || point == length – 1) {
          return "";
        }
        else {
          return fileName.substring(point + 1, length);
        }
      }

      /**
       * 得到文件的类型。
       * 实际上就是得到文件名中最后一个“.”后面的部分。
       * @param file 文件
       * @return 文件名中的类型部分
       * @since  0.5
       */
      public static String getFileType(File file) {
        return getTypePart(file.getName());
      }

      /**
       * 得到文件的名字部分。
       * 实际上就是路径中的最后一个路径分隔符后的部分。
       * @param fileName 文件名
       * @return 文件名中的名字部分
       * @since  0.5
       */
      public static String getNamePart(String fileName) {
        int point = getPathLsatIndex(fileName);
        int length = fileName.length();
        if (point == -1) {
          return fileName;
        }
        else if (point == length – 1) {
          int secondPoint = getPathLsatIndex(fileName, point – 1);
          if (secondPoint == -1) {
            if (length == 1) {
              return fileName;
            }
            else {
              return fileName.substring(0, point);
            }
          }
          else {
            return fileName.substring(secondPoint + 1, point);
          }
        }
        else {
          return fileName.substring(point + 1);
        }
      }

      /**
       * 得到文件名中的父路径部分。
       * 对两种路径分隔符都有效。
       * 不存在时返回""。
       * 如果文件名是以路径分隔符结尾的则不考虑该分隔符,例如"/path/"返回""。
       * @param fileName 文件名
       * @return 父路径,不存在或者已经是父目录时返回""
       * @since  0.5
       */
      public static String getPathPart(String fileName) {
        int point = getPathLsatIndex(fileName);
        int length = fileName.length();
        if (point == -1) {
          return "";
        }
        else if (point == length – 1) {
          int secondPoint = getPathLsatIndex(fileName, point – 1);
          if (secondPoint == -1) {
            return "";
          }
          else {
            return fileName.substring(0, secondPoint);
          }
        }
        else {
          return fileName.substring(0, point);
        }
      }

      /**
       * 得到路径分隔符在文件路径中首次出现的位置。
       * 对于DOS或者UNIX风格的分隔符都可以。
       * @param fileName 文件路径
       * @return 路径分隔符在路径中首次出现的位置,没有出现时返回-1。
       * @since  0.5
       */
      public static int getPathIndex(String fileName) {
        int point = fileName.indexOf(‘/’);
        if (point == -1) {
          point = fileName.indexOf(‘\\’);
        }
        return point;
      }

      /**
       * 得到路径分隔符在文件路径中指定位置后首次出现的位置。
       * 对于DOS或者UNIX风格的分隔符都可以。
       * @param fileName 文件路径
       * @param fromIndex 开始查找的位置
       * @return 路径分隔符在路径中指定位置后首次出现的位置,没有出现时返回-1。
       * @since  0.5
       */
      public static int getPathIndex(String fileName, int fromIndex) {
        int point = fileName.indexOf(‘/’, fromIndex);
        if (point == -1) {
          point = fileName.indexOf(‘\\’, fromIndex);
        }
        return point;
      }

      /**
       * 得到路径分隔符在文件路径中最后出现的位置。
       * 对于DOS或者UNIX风格的分隔符都可以。
       * @param fileName 文件路径
       * @return 路径分隔符在路径中最后出现的位置,没有出现时返回-1。
       * @since  0.5
       */
      public static int getPathLsatIndex(String fileName) {
        int point = fileName.lastIndexOf(‘/’);
        if (point == -1) {
          point = fileName.lastIndexOf(‘\\’);
        }
        return point;
      }

      /**
       * 得到路径分隔符在文件路径中指定位置前最后出现的位置。
       * 对于DOS或者UNIX风格的分隔符都可以。
       * @param fileName 文件路径
       * @param fromIndex 开始查找的位置
       * @return 路径分隔符在路径中指定位置前最后出现的位置,没有出现时返回-1。
       * @since  0.5
       */
      public static int getPathLsatIndex(String fileName, int fromIndex) {
        int point = fileName.lastIndexOf(‘/’, fromIndex);
        if (point == -1) {
          point = fileName.lastIndexOf(‘\\’, fromIndex);
        }
        return point;
      }

      /**
       * 将文件名中的类型部分去掉。
       * @param filename 文件名
       * @return 去掉类型部分的结果
       * @since  0.5
       */
      public static String trimType(String filename) {
        int index = filename.lastIndexOf(".");
        if (index != -1) {
          return filename.substring(0, index);
        }
        else {
          return filename;
        }
      }
      /**
       * 得到相对路径。
       * 文件名不是目录名的子节点时返回文件名。
       * @param pathName 目录名
       * @param fileName 文件名
       * @return 得到文件名相对于目录名的相对路径,目录下不存在该文件时返回文件名
       * @since  0.5
       */
      public static String getSubpath(String pathName,String fileName) {
        int index = fileName.indexOf(pathName);
        if (index != -1) {
          return fileName.substring(index + pathName.length() + 1);
        }
        else {
          return fileName;
        }
      }

    }
     4.遗留问题

    目前new FileInputStream()只会使用绝对路径,相对没用过,因为要相对于web服务器地址,比较麻烦

    还不如写个配置文件来的快哪

    5.按Java文件类型分类读取配置文件

    配置文件是应用系统中不可缺少的,可以增加程序的灵活性。java.util.Properties是从jdk1.2就有的类,一直到现在都支持load ()方法,jdk1.4以后save(output,string) ->store(output,string)。如果只是单纯的读,根本不存在烦恼的问题。web层可以通过 Thread.currentThread().getContextClassLoader().
    getResourceAsStream("xx.properties") 获取;Application可以通过new FileInputStream("xx.properties");直接在classes一级获取。关键是有时我们需要通过web修改配置文件,我们不能将路径写死了。经过测试觉得有以下心得:

    1.servlet中读写。如果运用Struts 或者Servlet可以直接在初始化参数中配置,调用时根据servlet的getRealPath("/")获取真实路径,再根据String file = this.servlet.getInitParameter("abc");获取相对的WEB-INF的相对路径。
    例:
    InputStream input = Thread.currentThread().getContextClassLoader().
    getResourceAsStream("abc.properties");
    Properties prop = new Properties();
    prop.load(input);
    input.close();
    OutputStream out = new FileOutputStream(path);
    prop.setProperty("abc", “test");
    prop.store(out, “–test–");
    out.close();

    2.直接在jsp中操作,通过jsp内置对象获取可操作的绝对地址。
    例:
    // jsp页面
    String path = pageContext.getServletContext().getRealPath("/");
    String realPath = path+"/WEB-INF/classes/abc.properties";

    //java 程序
    InputStream in = getClass().getClassLoader().getResourceAsStream("abc.properties"); // abc.properties放在webroot/WEB-INF/classes/目录下
    prop.load(in);
    in.close();

    OutputStream out = new FileOutputStream(path); // path为通过页面传入的路径
    prop.setProperty("abc", “abcccccc");
    prop.store(out, “–test–");
    out.close();

    3.只通过Java程序操作资源文件
    InputStream in = new FileInputStream("abc.properties"); // 放在classes同级

    OutputStream out = new FileOutputStream("abc.properties");

    2006年09月20日

    最近有机会仔细研究Servlet规范,原文的英语虽然都很平实,但对于我这个并不喜欢英语的人来说,要完全理解透彻还是有难度,所以一直在犹豫,是否要将笔记发布出来让大家耻笑。所谓灯不点不亮,问题不争就不明,放弃就意味着拥有谬误,而最不能容忍的就是容忍错误,希望发现错误的同仁不要吝啬你的板砖,我将在最快的时间内改正。

    另外,就着tomcat源码看规范是一件很惬意的事情,就像蘸着番茄酱吃薯条一样,为了大家方便,我将一个tomcat5.0.28源码的eclipse项目放在网上,大家可以直接下载。

    http://freehost07.websamba.com/GPS921/download.htm

    [后记]v2.3的规范的确有些老,在完成v2.3后,我会将v2.4的内容补上。
    ____________________________________________________________________________

    Java Servlet Specification  Version 2.3

    第一章 概要

    1.1 什么是servlet
            servlet是一种基于web组件的java技术,由容器管理,产生动态内容。象其他基于java的组件一样,servlet是不依赖平台的的java类,被编译为中间字节码,可被动态装载运行于支持java的web服务器上。这里说的容器,有时也称它为servlet引擎,提供Servlet功能的web服务器扩展,servelt通过一种由servlet容器实现的request/response范式(paradigm)与web客户机交互。

    1.2什么是servlet容器
            servlet容器是web服务器或应用服务器的一部分,它们提供处理处理request并发送response的网络服务,解析基于MIME的request,准备基于MIME的response。servlet容器包含并管理着servlet对象的一生。
            servlet容器可以被嵌入web服务器主机,或者通过服务器本地扩展API安装为web服务器的一个组件。同时,它也可以被嵌入到支持web功能的应用服务器中。
            所有的servlet容器必须支持以HTTP作为request和response的协议,但额外的协议如HTTPS可以支持。应支持的HTTP最低版本为HTTP/1.0,强烈议也支持HTTP1.1规范。
            servlet容器可以在其执行环境中放置安全制约。Java2平台,标准1.2或者J2EE1.3环境,应当使用由java2平台定义的许可架构下的约束。例如,高端应用服务器可以限制线程对象的创建,确保容器内的其他组件免收影响。J2SE1.2是构建servlet容器的最低版本。
            
    1.3 例子
    以下是一系列典型的场景序列:
            1.客户机(即,浏览器)访问web服务器,并发送HTTP请求。
            2.web服务器接收到了这个请求,并向下传递给了servlet容器。servlet容器可以与web服务器运行在同一进程里,也可以在同一主机上的不同进程里,或者运行于不同主机上。
            3.servlet容器使用servlet配置信息而决定调用哪一个servlet,同时向其传递request和response对象参数。
            4.servlet利用request对象得到远端用户信息,HTTP POST参数是什么,以及其他相关数据。servlet执行其编码逻辑,并产生数据,通过response对象送回客户机。
            5.一旦servlet完成处理request,容器应确保response对象被flush,并将控制权返回web服务器。

    1.4 和其他技术的比较
            功能上,servlet居于CGI和服务器扩展(如Netscape Server API或 Apache modules)之间。
            但servlet还具有一些其他服务器扩展技术没有的先进性:
            .它们通常比CGI脚本运行快。
            .它们使用很多web服务器都支持的标准API。
            .它们具有所有java编程语言具有的优点,包括开发简单平台独立等。
            .它们可以访问java平台大量的API。

    1.5与J2EE的关系
            Servlet API v2.3 是JavaTM 2 平台, 企业版 v1.31的必需的API 。servlet容器和发布servlet必须满足其他的需求在J2EE规范中都有描述。

    第二章 Servlet接口

            servlet接口是servlet主要抽象的API。所有servlet都需要直接实现这一接口或者继承实现了该接口的类。servlet API中有两个类实现了Servlet接口,GenericServlet和HttpServlet。大多数情况下,开发人员只需要在这两个类的基础上扩展来实现他们自己的Servlet。

    2.1 request处理方法
            servlet接口定义了service方法来处理客户机的请求。当容器将每个请求传递给servlet实例处理时都会调用该方法。为应付同时到达的请求,通常要求web开发人员编写的service方法可以多线程执行。
            开发人员在不同线程内并发执行service方法来处理同时到达同一servlet的多个请求。

    2.1.1 HTTP规范request处理方法
            HttpServlet,实现了Servlet接口的抽象类,添加了一些附加的方法处理HTTP请求,由service方法自动调用。这些方法是:
            .doGet处理HTTP GET request
            .doPost处理HTTP POST request
            .doPut处理HTTP PUT request
            .doDelete处理HTTP DELETE request
            .doHead处理HTTP HEAD request
            .doOption处理HTTP OPTION request
            .doTrace处理HTTP TRACE request
            
            在开发HTTP Servlet的时候,开发人员一般关注doGet和doPost方法.其他方法在HTTP开发时很少用.

    2.1.2 附加方法
            doGet和doPost方法允许开发人员支持HTTP1.1特性.doHead()方法实际是一种特殊的doGet方法,它只返回由doGet产生的头信息.doOption
    方法返回servlet支持的所有HTTP方法的信息.doTrace方法产生的响应包含TRACE请求发送的所有头实例.
            对于仅支持HTTP/1.0的容器,只需支持doGet,doPost,doHead方法.因为HTTP/1.0没有定义PUT,DELETE,OPTIONS,TRACE方法.

    2.1.3 条件Get支持
            HttpServlet接口定义了getLastModified方法,目的是支持有条件的GET操作.一个有条件的GET操作所请求的资源只有在指定时间内被修改了的情况下才需要发送.在某些情形下,实现该方法可以更加有效地利用网络资源.

    2.2 实例的数量
            servlet声明包含在web应用配置描述内,控制着容器如何提供servlet实例。因为servlet不运行于分布式环境(缺省),容器为每个Servlet声明维护一个实例.然而,当servlet实现SingleThreadModel接口时,容器可以持有多个servlet实例来应付繁重的请求,但要保证一个实例每次只处理一个请求.

            当servlet被发布到分布式应用时,容器可以在每个虚拟机中为每个servlet声明持有一个实例.如果实现SingleThreadModel,可以在每个虚拟机中为每个声明持有多个servlet实例.

    2.2.1 注意单线程模式
            SingleThreadModel只允许某一时刻只能有一个线程执行指定的servlet实例的service方法.应该注意到,此保证只针对servlet实例而言,因为容器可以选择池化这些对象.多个servlet实例可以同时访问的对象,例如HttpSession对象,在任意时候对多个servlet都是可用的,哪怕这些servlet实现了singleThreadModel.

    2.3 servlet生命周期
            servlet的生命周期定义了装载,实例化,初始化,处理客户机请求,卸载等等。用API表示生命周期就是init,service,和destroy方法.

    2.3.1 装载和实例化
            容器负责servlet的装载和实例化.它们可以发生在容器启动时,也可以延迟到容器认为需要该servlet处理request时.
            当容器启动时,容器必须可以定位所需要的servlet类,并使用Java类装载机构装载servlet类.从本地文件系统中,或从远程文件系统中或者从其他网络服务中都可以装载servlet类.
            装载完成后,容器实例化该类.
            servlet对象被实例化后,容器必须在使用它处理客户机的请求之前初始化这个servlet实例.初始化的意义是srvlet对象可以读取一些持久性的配置数据,或者初始化某些重量级型资源(比如JDBC连接),或是执行某些一次性的行为.容器使用init方法并传递一个单例SrvletConf ig对象参数(每个servlet声明只有一个).该配置对象允许servlet访问应用配置信息中的初始化参数,同时提供了一个实现了ServletContext接口的对象来访问servlet运行环境的信息.

    2.3.2.1 初始化的错误条件
            在初始化期间,servlet实例可以抛出UnavailabelException或者ServletException异常.发生异常时不能将servlet放进可用服务中,容器必须释放它,因为未被成功初始化,所以无需调用destroy方法.
            初始化失败后,应重新创建一个新的实例并初始化它.规则是,当UnavailabelException指示了最小不可用时间,容器必须等待这个最小时间才创建并初始化新的实例.

    2.3.2.2 Tool Considerations
            工具装载并内省web应用而触发类的静态初始化方法有别于调用servlet的init方法.在没有调用init方法前,开发人员不应假定sevlet是在一个活跃的servlet容器运行环境中。也就是说,servlet不应在静态初始化方法里试图建立数据库连接等.

    2.3.3处理请求
            当servlet被正确初始化后,容器就可以用它处理客户机的请求了.请求用ServletRequest类型的对象表示,servlet通过调用ServletResponse的对象上的方法填充该请求的响应。这些对象由service方法作为参数传递进来.
            在HTTP请求中,容器提供的这些对象类型为HttpServletRequest和HttpServletResponse.
            一个被容器放进服务序列的servlet实例也有可能在整个生命周期中都不处理请求.

    2.3.3.1多线程
            servlet容器可以向servlet的service方法发送并发请求.为了处理这些请求,开发人员必须在他们的service方法中遵守有关的多线程并发处理规则.
            否则,一个可替代的办法是实现SingleThreadModel接口,它需要容器保证在service方法一次只有处理一个请求.容器通过序列化一个servlet上的请求来满足这个需求,还可以通过持有一个servlet实例池的方式.若servlet属于分布式应用的一部分,容器可以在应用分布的每个虚拟机上持有一个servlet实例池.
            若servlet没有实现SingleThreadModel,而是使用synchronized关键字定义了service方法(或doGet,doPost),容器就不会使用实例池,但必须序列式地处理到达此servlet的请求,强烈建议开发人员不要同步service方法,否则会严重影响性能.
            
    2.3.3.2 处理请求期间的异常
            servlet在处理request期间可以抛出ServletException或UnavailableException异常. ServletException表明在处理request时发生了一些错误,此时容器应使用适当的方式清除此请求.
            UnavailableException显示该servlet不能处理请求,有可能是暂时的,也有可能是永久性的.
            如果是永久性的不可用,容器必须从服务中移除该servlet实例,并调用它的destroy()方法,然后释放实例.
            如果是暂时性的不可用,容器可以在不可用期间不发送任何请求到该servlet处理.
            在此期间被容器拒绝的请求,必须以SERVICE_UNAVAILABLE (503)状态码返回,同时在HTTP响应头Retry-After中指明何时服务可用.
            容器也可以选择忽略这两种可用性的区别,将所有UnavailableExceptions当作永久性的,而直接移除该servlet实例.

    2.3.3.3 线程安全
            request和response对象不是线程安全的,这意味着它们应该只能在处理该请求南叱棠诓渴褂?
            在其他线程中使用被引用的request和response对象将引起不可预期的后果.

    2.3.4 终止服务
            容器并不需要永久持有servlet. servlet实例在容器中可能只存在数毫秒,也可能在整个容器的生命周期中都存在(可能是几天,个月,几年)
            当容器决定应该从服务中移除servlet时,它调用servlet接口上的destroy()方法,这样,servlet可以释放它所使用的资源,或者存储任何持久性的状态.
            在容器调用的destroy()方法之前.必须允许正在运行service方法的线程完成执行,或者延迟运行一段服务器定义的时限.
            一旦servlet实例的destroy()方法开始被调用,容器不用再发送其他请求到该servlet实例处理.即使容器需要重新激活该servlet,它也必须重新创建一个实例。
            当destroy()方法调用完成后,容器必须释放该实例,以便它可以被垃圾收集.

    第三章 Servlet Context

    3.1 介绍ServletContext接口
            ServletContext接口定义了web应用的servlet视图.容器提供者有责任提供对ServletContext接口的实现.通过ServletContext对象,servlet可以记录日志,获得资源的URL引用,存储其他servlet也可以访问的属性.
            ServletContext以Web服务器中一条已知的路径为根,例如,servlet context可以位于http://www.mycorp.com/catalog,所有以/catalog开始的请求被发送到与此ServletContext相关联的web应用.

    3.2 ServletContext接口的范围
            每个应用都会有一个ServletContext对象与之关联,当容器分布在在多个虚拟机上时,web应用在所分布的每个虚拟机上都拥有一个ServletContext实例.缺省情况下,ServletContext不是分布式的,并且只存在于一个虚拟机上。
            
    3.3 初始化参数
            以下ServletContext接口中的方法允许servlet访问开发人员在配置描述中定义的web应用初始化参数:
            . getInitParameter
            . getInitParameterNames
            应用开发人员使用初始化参数传递构建信息,例如管理员的邮件地址等.
            
    3.4 context属性
            可以在context中用名称邦定一个对象属性.context中的任何属性对于同一web应用下的其他servlet都是可用的.下列方法完成了这样的功能:

            . setAttribute
            . getAttribute
            . getAttributeNames
            . removeAttribute

    3.4.1 在分布式容器中的context属性
            对于创建它们的虚拟机来说context属性是本地的. 这使得ServletContext属性不能以共享内存的方式存储在分布式的容器中.如果有些信息需要在不同的机器上运行的servlet之间共享的话,它们应该被存储在session中,数据库中,或EJB组件中.

    3.5 资源
            使用ServletContext接口可以直接访问web应用中的静态内容文档结构.包括HTML,GIF和JPEG文件.如以下方法:
            .getResource
            .getResourceAsStream
            这两个方法的参数都是以"/"开头的字符串,表示资源相对于context根的相对路径.文档结构可以存在于服务器文件系统,或是war包中,或是在远程服务器上,抑或其他位置.
            以上方法不可以用来获得动态资源,比如,getResource("/index.jsp"),这个方法将返回该jsp文件的源码,而不是动态页面.可以用"Dispatching Requests"获得动态内容.
            列出web应用中可被访问的资源,可以使用getResourcePaths(String path)方法。
            
    3.6多主机和servlet context
            web服务器支持在一台机器上共享一个IP的多个逻辑主机,这种能力被称为"虚拟主机",每个逻辑主机都拥有它自己的servlet context。servlet context不能跨虚拟主机共享.

    3.7 重新装载
             虽然容器提供重新装载的实现对于开发有益,但不是必须的。这些实现必须确保所有的servlet、和它们所使用的类在单独的类装载器范围内被装载。此机制保证了应用正常运行。
             Previous generations of containers created new class loaders to load a servlet,distinct from class loaders used to load other servlets or classes used in the servlet context. This could cause object references within a servlet context to point at unexpected classes or objects, and cause unexpected behavior. The requirement is needed to prevent problems caused by demand generation of new class loaders.
             (不确定的参考译文:
             容器用来装载servlet的类装载器和ServletContext用来装载servlet和其他类的装载器不同,这导致在servlet上下文之内的对象引用指向不可预期的类或对象, 和不可预期的行为。需要防止由新的类装载器产生的问题.)

    3.8 临时工作目录
            每个servlet context都有它自己的临时存储目录,容器必须为每个servlet conetxt提供一个私有的临时目录,存储于context属性javax.servlet.context.tempdir.该对象必须是java.io.File类型.容器启动时,不需要维护该临时目录的内容,但必须确保一个servlet context临时目录的内容对其他web应用(servlet context)是不可见的.

    第四章 Request

    4.1 HTTP协议参数
            servlet的请求参数是客户机发送给servlet容器的字符串,它是请求的一部分.当请求是一个HttpServletRequest对象时,且条件设置合适时,容器从URI请求字符串和POST数据中组装参数.
            参数的存储为名称-值成对形式,对于同一个名称可能存在多参数值.以下ServletRequest接口方法可以访问参数:
            .getParameter
            .getParameterNames
            .getParameterValues
            getParameterValues方法返回一个字符串对象数组,包含有和指定名称关联的所有参数值.getParameter方法返回的是该数组的第一个参数值.
            查询字符串和post body 包含的数据被组装到request参数集合中,query string中的数据顺序要优先于post body中的数据.比如:
    如果一个请求使用查询字符串 a=hello 和post body a=goodbye&a=world,最后的参数集顺序是a=(hello,goodbye,world).
            路径参数需要使用getRequestURI和getPathInfo方法获得.

    4.1.1 什么时候参数可用
            POST form 中的数据被放进参数集之前,必须符合以下条件:
            1.必须是HTTP或HTTPS请求
            2.HTTP方法是POST
            3.内容类型是application/x-www-form-urlencoded
            4.servlet 调用了request对象上的getParameter之类与参数相关的方法.
            如上述条件不满足的话,被POST的form中的数据不会包含在参数集中,但数据仍可以通过request对象的输入流得到。否则数据不能通过request对象的输入流得到。

    4.2 属性
            属性是和request关联的对象。它一般被容器用作存储不能通过API获得的信息,或者是用作与其他servlet的通讯信息(通过RequestDispatcher)。属性可以用以下方法访问:
            .getAttribute
            .getAttributeNames
            .setAttribute
            一个属性名只能和一个属性值关联。
            以“java.”,"javax."开始的属性名是本规范的保留定义,同样,以"sun.","com.sun."开头的属性名是Sun公司的保留定义。建议所有的属性名以包命名规范。

    4.3 头(Headers)
            servlet通过以下方法访问HTTP request的头信息。
            .getHeader
            .getHeaders
            .getHeaderNames
            .getHeader方法返回与头名称对应的头信息。同一个名称可能有多个头信息对应。比如:Cache-Control头。如果这样,该方法应该                返回第一个。要取得所有信息,则使用getHeaders方法。
            头信息也许是某些整型或日期型,可以直接使用下面方法读取:
            .getIntHeader
            .getDateHeader
            如果getIntHeader方法不能将值转换为整数,将抛出NumberFormatException异常。如果.getDateHeader方法不能将值转换为日期对象,将抛出IllegalArgumentException异常。

    4.4 请求路径
            请求路径由许多重要的部分组成。下列元素从请求URI获得,并通过request对象暴露。

            .Context Path:和此servlet所在的ServletContext关联的路径前缀,如果context是"default" context,那么该路径为"",另外,如果该context不是root,那么路径以"/"开头,但不能以"/"结束。
            .servlet path:此部分和请求的映射相关,以"/"开始,但如果该映射是"/*",那么servlet路径此时为""
            .PathInfo:此部分是context path 和servlet path 的一部分。如没有额外路径它为null,否则是以"/"开始的字符串。

            HttpServletRequest接口中的以下方法可以访问这些信息:
            .getContextPath
            .getServletPath
            .getPathInfo
            
            值得注意的是,URI和路径之间除了URL编码区别外,以下等式是成立的:
            requestURI = contextPath + servletPath + pathInfo
            例子:
            Context Path         /catalog
            Servlet Mapping         Pattern: /lawn/*
                            Servlet: LawnServlet

            Servlet Mapping         Pattern: /garden/*
                            Servlet: GardenServlet

            Servlet Mapping         Pattern: *.jsp
                            Servlet: JSPServlet

            我们发现:
            Request Path                         Path Elements

            /catalog/lawn/index.html                 ContextPath: /catalog
                                            ServletPath: /lawn
                                            PathInfo: /index.html

            /catalog/garden/implements/                 ContextPath: /catalog
                                            ServletPath: /garden
                                            PathInfo: /implements/

            /catalog/help/feedback.jsp                 ContextPath: /catalog
                                            ServletPath: /help/feedback.jsp
                                            PathInfo: null

    4.5 路径转换方法
            API中有两个方便的方法可以让开发人员获得和某个路径相关的文件系统路径:
            .ServletContext.getRealPath
            .HttpServletRequest.getPathTranslated
            getRealPath接受一个字符串参数,并返回和该路径相关联的文件系统路径。getPathTranslated方法则可以得到该request的PathInfo对应的真实路径。下列情况下,servlet容器不能判别合法的文件路径,如,web应用以war包形式运行,或者不是本地文件路径,或者在数据库中,这些方法将返回NULL.

    4.6 cookie
            HttpServletRequest接口提供getCookies方法获得该request中的cookies数组。这些cookies是客户机每次发送给服务器请求中的数据,其形式为cookie名和cookie值。当cookie被送回给客户机时也可设置其他属性,比如注释,但一般是不会送回服务器的。

    4.7 SSL属性
            如果请求是通过安全协议传输的,如:HTTPS,那么ServletRequest接口的isScure方法可以得到。

    4.8 国际化
            客户机可以向web服务器提示它以何种语言接受服务器的响应.HTTP1.1规范中指出在头信息Accept-Language表示。以下方法可以得到发送者的地域信息:
            .getLocale
            .getLocales

            如果客户机没有指定地域信息,servlet容器将会使用默认的地域信息设定。

    4.9 请求数据编码
            目前,许多浏览器在Content-Type头中不会指定字符编码方式,那么容器就会使用"ISO-8859-1"方式解析POST数据,而此时,为了向开发人员提示字符编码方式未指定,容器将会在getCharacterEncoding返回null.
            如果客户机没有设置字符编码信息,但是request数据又以和缺省编码方式不同的方式编码,就会发生数据破坏。setCharacterEncoding(String enc)方法可以防止这种状况发生,但是必须在解析数据或从request中读取数据之前调用。否则调用该方法不会有任何效果。

    4.10 request对象的生命周期
            request对象只在servlet的service方法范围内有效。或者是在filter的doFilter方法内,开发人员必须确信在范围之外引用request对象行为不会导致不可预计的后果。

    第五章 Response

            response对象包装了所有从服务器发送到客户机的的信息,在HTTP协议中,这些信息被分为HTTP头和消息体的两部分。
            
    5.1 缓冲
            servlet容器被允许但不被要求,缓冲发送到客户机的输出.一般情况下容器都会做缓冲,并且允许servlet定制缓冲参数。下列方法和缓冲相关。
            .getBufferSize
            .setBufferSize
            .isCommitted
            .reset
            .resetBuffer
            .flushBuffer
            这些方法对ServletOutputStream和Writer有效。
            getBufferSize返回正在使用的缓冲大小,如果没有使用缓冲,返回整数0。可以通过setBufferSize设定缓冲大小,缓冲分配的ad校可以与servlet请求的不同,但至少应不比所请求的小。此方法应在调用ServletOutputStream或Writer向客户机发送数据之前调用。如果有任何数据已经被发送,此方法将抛出IllegalStateException异常.
            isCommitted方法返回一个boolean值显示是否有任何response字节被发送回客户机。flushBuffer强制将buffer中的内容发送给客户机。reset方法在response还没有被commit以前将buffer的内容清空,头信息和状态码也被清除。resetBuffer则只清除buffer内容。
            如果response已经被commit,调用reset和resetBuffer将抛出IllegalStateException异常,同时response对象不会发生改变。
            一旦数据被开始发送给客户机,response对象就被认为是committed状态。

    5.2 头
            servlet用下面的方法设置HTTP头信息。
            .setHeader
            .addHeader        
            setHeader方法会替换已经被设置的值,而addHeader则会添加一个值到同名头的值集合中。
            为保证成功地将头信息传送到客户机,必须在response对象被提交以前,设置HTTP头信息。
            servlet开发人员有责任确保Content-type头被正确设置,HTTP1.1规范并不要求HTTP response中设置该头。当开发人员没有设置该值,        容器也不要设置缺省值。

    5.3 方便的方法
            .sendRedirect
            .sendError
            sendRedirect方法会自动设置合适的头与内容,引导客户机跳转到另一个URL去。 使用相对路径作为参数也是合法的,但容器必须将相对路径转换为绝对路径送回客户机。如果由于某些原因不能转换,必须抛出IllegalArgumentException异常.
            sendError方法自动设置返回客户机的头信息和错误信息,一个可选的字符串类型的变量,可用来显示错误信息的内容部分。
            这两个方法对于response的commit都有影响,如response还没被提交,将终止提交。这些方法后调用的输出都将被忽略。
            若数据被写进缓冲,但还没有发送到客户机,这些数据将被这两个方法发送的数据代替,如果response已经被提交,将抛出                        IllegalStateException异常.

    5.4 国际化
            setLocale方法可以设置response对象的语言属性,此方法必须在开发人员调用getWriter之前使用。
            注意。setContentType方法将覆盖setLocale设定的值。

    5.5 response对象的关闭
            当response被关闭时,容器必须立即发送所有缓冲中的数据到客户机,下列情形显示response对象被关闭了。
            .servlet的service方法结束。
            .当发送完由setContentLength设定的长度的内容后。
            .调用了sendError方法。
            .调用了sendRedirect方法。

    5.6 response对象的生命周期
            每个response对象只在service方法范围内可用,或者是filter的doFilter方法内。容器通常会回收response对象以避免频繁创建response对象造成的性能过载。开发人员在可用范围外引用response对象时,必须确保不会引起不可预知的行为。

    第六章  filter

            Filter是规范2.3新提供的特性,它使得请求与响应过程的信息转换很敏捷。

    6.1 什么是Filter
            Filter是一段可重用的,转换HTTP请求,响应内容和头信息的代码。Filter通常不象servlet那样会创建response或对请求作出响应。它们只是修改对资源的请求或对资源的响应。
            Filter对静态和动态资源都可以起作用。filter的一般作用:
            .在request到达它调用的资源之前访问该资源。
            .在request到达它调用的资源之前处理该资源。
            .修改request头和数据,通过重新包装request对象。
            .修改response头和数据,通过重新包装response对象。
            .拦截监听资源的调用。
            .以一定顺序对一个或一组servlet或者静态资源作用。

    6.1.1 Filter的例子
            .认证filter
            .日志与审核filter
            .图像转换filter
            .数据压缩filter
            .加密Filter
            .令牌filter
            .出发资源访问事件的filter
            .转换XML文档的XSLT filter
            .MIME-type 链filter
            .缓存filter

    6.2 主要概念
            开发人员实现javax.servlet.Filter接口来创建filter,同时提供一个公共无参数的构造函数。该类应该被包含在web应用中。在发布文件中,以“filter”,"filter-mapping"元素声明配置。

    6.2.1 filter 生命周期
            web应用发布完成之后,在request访问web资源之前,容器必须定位需要应用到该资源上的filter列表。容器需要确保实例化列表中的每个filter并调用它的init方法。如不能正确执行,filter可以抛出异常。
    配置描述文件中每个filter的声明在每个虚拟机中仅仅只有一个实例。容器提供filter配置秘书中声明的config对象, 它包含了该web应用的ServletContext引用,和filter初始参数。

            当容器接收到request,将取得列表中的第一个filter实例,并调用它的doFilter方法。将传入ServletRequest,ServletResponse和filterchain的引用。

            doFilter方法遵循以下模式:
            1.该方法检测request的头信息
            2.可能会用一个定制的ServletRequest或HttpServletRequest包装request对象。
            3.可能会用一个定制的ServletResponse或HttpServletResponse包装response对象。
            4.filter可以调用filter链中的下一个入口,可能是一个filter,也可能是目的web资源。
            5.调用链中的下一个filter之后,filter可以检测响应头信息。
            6.处理过程中,filter可能抛出异常。如果在doFilter中抛出UnavailableException,容器不要继续filter链的处理,如飞永久性异        常,它可以选择在稍晚的时候重新执行整个filter链。
            
    6.2.2 包装request和response
            filter的核心概念就是包装request和response对象,以便于执行filter任务时,覆盖其行为。在此模型中,开发人员不仅能够覆盖request和response对象上已有的方法,还可以为某个特定的过滤任务在整个链上提供新的API。例如,开发人员可能想在response对象上使用比out流和writer更高级的输出对象,例如,允许DOM对象写回客户机的API。
            为了支持这种类型的filter,容器必须满足下列需求。当一个filter调用filter chain的doFilter方法时,容器必须确保传到下一Filter或web资源入口的request和response对象,和调用filter的doFilter方法传入的是同一个对象。
            同样的,被包装的request、response对象和传入的也必须是一致的。

    6.2.3 filter环境
            配置描述中可以使用一系列参数初始化filter,描述符为"init-param",filter在运行时可以使用FilterConfig对象上的getInitParameter和getInitParameterNames方法得到参数名和值。FilterConfig也提供对ServletContext的访问。

    6.2.4 在web应用中配置filter
            在配置描述中,filter以filter元素定义。
            filter-name:用来映射该filter到一个servlet或者URL
            filter-class:容器用来识别filter类型
            init-params:初始化参数
            图标,文本描述,显示名称都是可选项描述,主要给县骨干工具软件使用。对于每个filter声明,容器必须正确地实例化一个filter对象,如果对于一个filter类定义了多个声明,容器将会生成多个实例。
    例子:
            filter声明:
            <filter>
                    <filter-name>Image Filter</filter-name>
                    <filter-class>com.acme.ImageServlet</filter-class>
            </filter>
            filter声明后,应使用filter-mapping元素定义该filter要应用的servlet或是静态资源。
            <filter-mapping>
                    <filter-name>Image Filter</filter-name>
                    <servlet-name>ImageServlet</servlet-name>
            </filter-mapping>

            filter也可以使用URL匹配模式和一组servlet或静态资源关联起来。

            <filter-mapping>
                    <filter-name>Logging Filter</filter-name>
                    <url-pattern>/*</url-pattern>
            </filter-mapping>
            
            此处,Logging filter被应用到applicationlide所有servlet和静态资源上了,因为所有请求URI都匹配"/*"。
            当用url-pattern处理filter-mapping时,容器必须用路径映射规则判断所请求的URI是否匹url-pattern。
            容器针对某个被请求的URI构建的filter链的顺序为:
            1.首先是url-pattern匹配的filter,如有多个,其顺序是配置描述的顺序。
            2.然后是servlet-name匹配的filter,如有多个,其顺序是配置描述的顺序。
            
            以上两点意味着容器:
            .根据匹配规则11.2确定被访问的目标Web资源
            .如果通过servlet名称匹配到了filter,且web资源拥有servlet名称,容器按照配置描述的顺序构建filter链。链的底部就是最后一个通过servlet名称匹配的filter,并且由它调用目的web资源。
            .如果有url-pattern匹配的request URI的filter,容器以配置描述中的顺序构建url-pattern匹配的filter链.链的底部是最后一个url-pattern匹配到的filter,并且该filter将调用第一个servlet-name匹配的filter,如没有则直接调用目的web资源.
            若希望提高容器性能,最好缓存filter链,以免每次请求都要重新计算.

    第七章 会话 sessions
            超文本传输协议(HTTP)被设计成无状态的协议.为了构建有效率的web应用,将从某个特定客户机上发送来的request互相关联起来是很必要的,会话跟踪的技术已经发展了很长时间,但对于开发人员直接使用来说都是很困难的.
            本规范定义了HttpSession接口,允许servlet容器使用好几种方式跟踪用户会话,而开发人员不会陷入不同方式的细节.

    7.1  会话跟踪机制
            通过HTTP cookie跟踪会话时比较常用的办法,所有servlet容器都要求实现这一技术.
            容器发送cookie到客户机,客户机在接下来的请求中都将该cookie返回给容器.这样就很清楚地将请求和会话关联起来,用于会话跟踪的cookie名称一定是JSESSIONID.

    7.1.2 SSL Session

    7.1.3 URL重写
            URL重写式会话跟踪的最低公分母?,当客户机不接受cookie时,server就使用URL重写作为会话跟踪的基本方式.URL重写,添加了附加数据(会话ID)到请求的URL路径上.
            会话ID必须被编码作为该URL字符串中的路径参数。该参数的名称为jsessionid,例如:        
            http://www.myserver.com/catalog/index.html;jsessionid=1234

    7.1.4会话完整性
            Web容器必须能支持HTTP会话,当处理不支持cookie使用的客户机发送来的HTTP请求时,web容器通常使用URL重写机制。

    7.2 创建会话
            一个会话被认为是新的,只有当它是唯一一个预期会话并还没没有被建立。因为,HTTP是基于request-response的协议,HTTP会话被认为是新的,直到有客户机加入它.当会话跟踪信息返回服务器时,意味着一个会话已经被建立了,此时,客户机加入了会话.直到客户机加入会话为止,它都不能假定下一个来自该客户机的请求隶属于某个会话的一部分.只要下列条件成立,会话被认为是新的:
            .客户机还不知道该会话
            .客户机选择不加入会话
            
            这些情况定义了servlet容器没有机制将一个请求和上一个请求关联起来.
            servlet开发人员必须将它的应用设计为可以处理客户机没有,不能,不会加入会话的情形.

    7.3 会话范围
            HttpSession对象在应用级别(servlet context)范畴.
            下列机制,例如cookie,对于不同context可以是相同的,但该对象引用,包扩属性,容器必须按context区别开来.
            用一个例子来说明:如果一个servlet使用RequestDispatcher调用属于另一个web应用的servlet,任何对于执行调用的servlet创建的,可        见的会话信息,必须不同于被调用的servlet.

    7.4 在会话中绑定属性
            servlet可以通过名称绑定对象属性到HttpSession的实现中.任何绑定对象对于同一servlet context下在同一会话中处理请求的其他servlet都是可用的.
            会话中的一些对象被放入或是移出时,可能需要获得通知,这可以通过实现HttpSessionBindingListener接口的对象获得.该接口定义了下列方法,当对象被绑定或解除绑定时,发出信号.

            .valueBound
            .valueUnbound

            valueBound方法必须在该对象通过执行HttpSession的setAttribute方法而可用之前被调用valueUnbound方法必须在该对象通过执行HttpSession的setAttribute方法而不再可用之前被调用.

    7.5 会话超时
            在HTTP协议中,客户机不再活跃时没有明确的终止信号.这意味着只有超时才是标识客户机不再活跃的唯一机制.
            缺省的会话超时由servlet容器定义,在HttpSession接口上调用getMaxInactiveIntervalfangaf得到该值.开发人员可以使用HttpSession接口上的setMaxInactiveInterval方法改变该值.时间单位是秒.若设置为-1,表示该会话永不过期.

    7.6最后访问时间
            HttpSession接口上的getLastAccessedTime方法允许servlet判定该会话在本次请求前被访问的最后时间.当容器第一次处理隶属于该会话的请求时,认为该会话被访问.
            
    7.7 重要的会话语义
    7.7.1 线程
            多个servlet执行请求线程也许同时使用一个会话对象,开发人员有责任同步对会话资源的访问.

    7.7.2 分布式环境
            在分布式应用中,所有隶属于某个会话的请求在某一时间必须同一个虚拟机处理,容器必须能适当地处理使用setAttribute或putValue方法放进HttpSession实例的所有对象.下列约束是强制的:
            .接受的对象应实现Serializable接口
            .容器可以选择支持在HttpSession中存储其他指定的对象,例如:EJB和事务
            .移动会话将由容器具体设施处理

            若放进会话的对象未实现Serializable接口,或对该类对象的支持还不可用,servlet容器可以抛出IllegalArgumentException异常,只有在容器不能支持移动会话时抛出该异常.
            这些约束表明开发人员被保证没有发生在非分布式容器之外的并发问题.

            通过拥有移动会话对象的能力,从分布式系统中的任一个活跃节点移动到系统中的另一个节点,容器提供者能保证服务的可伸缩性和品质,负载均衡和故障转移等特性.
            如果分布式容器坚持或移动会话来提供服务特性的品质,就不强求在序列化HttpSession和它们的属性时使用本地虚拟机的序列化机制.容器不会保证调用会话属性上的readObject和writeObject方法.但是保证它们的属性可序列化关闭将被保存.

            在移动会话期间,容器必须通知实现了HttpSessionActivationListener接口的会话属性.通知钝化监听器必须优先于序列化监听器,活化则要迟于会话反序列化.

            应用开发人员编写分布式应用,应该明白因为允许容器运行在多个java虚拟机上,他就不能依赖静态变量来存储应用程序状态.应使用EJB或数据库.

    7.7.3 客户机语义
            实际上cookie和SSL认证都是由web浏览器控制的,而不是和浏览器的某个特定窗口相关联,也许从所有窗口发送到servlet容器的请求是属于同一个会话.为了尽量方便,开发人员总是假定客户机的所有窗口参加的是同一个会话.

    八 转发请求
            使用RequestDispatcher接口,向另一个servlet转发请求,或者在response中包含另一个servlet的输出.

    8.1 获得RequestDispatcher
            从ServletContext上可以获得RequestDispatcher的实现对象:
            .getRequestDispatcher
            .getNamedDispatcher
            getRequestDispatcher方法的参数为一个字符串变量,描述在ServletContext范围内的路径.该路径必须相对于ServletContext的根.并且以"/"开始.该方法使用此路径寻找到servlet后,用一个RequestDispatcher对象包装返回.如果在该路径上没有找到对应的servelt,一个包装了该路径内容的RequestDispatcher被返回.
            
            getNamedDispatcher方法参数为一个字符串变量,它表示ServletContext内已知servlet的名称,如找到该servelt,用RequestDispatcher对象包装后返回,如未找到,返回null。
            
            使用相对于当前请求的相对路径也可以获得RequestDispatcher对象,以下方法在ServletRequest接口提供:
            .getRequestDispatcher
            该方法的行为类似于ServletContext中同名函数,servlet容器使用request对象中的信息转换给定的相对路径为完整的路径。例如:
    在context“/”和一个到“/garden/tools.html”的请求,此时,调用request上的getRequestDispatcher("header.html")方法效果等同于ServletContext.getRequestDispatcher("/garden/header.html")。

    8.1.1 转发请求路径里的查询字符串
            ServletContext和ServletRequest里使用路径参数获得RequestDispatcher的方法,允许添加查询字符串到该路径上。例如,开发人员用以下代码:
            String path = “/raisons.jsp?orderno=5”;
            RequestDispatcher rd = context.getRequestDispatcher(path);
            rd.include(request, response);
            查询字符串里指定的参数,顺序要优先于同名的其他被传进的参数。该参数与RequestDispatcher相关联,只在include或forward调用中有效。

    8.2 使用request Dispatcher

            使用request Dispatcher,调用该接口上的include或forward方法。参数可以是通过service方法传入的request,response对象,也可以是它们被包装后的对象。
            容器提供者应确保将请求分发到目的servlet必须发生在原始请求所在的虚拟机上。

    8.3 include方法
            该方法必须可以在任意时刻调用,目标servlet的include方法可以五约束地访问request对象,但在访问response对象上有一些限制。它只能向response对象的ServletOutputStream或Writer写入信息,在response的缓冲写完后提交response,或直接调用flushBuffer方法提交response,但不能设置头信息,或者调用任何可能改变头信息的方法。任何此类操作将被忽略。

    8.3.1 Included参数
            除了使用getNamedDispatcher获得的servlet外,a servlet being used from within an include has access to the path by which it was invoked.
            以下request属性被设置:
            javax.servlet.include.request_uri
            javax.servlet.include.context_path
            javax.servlet.include.servlet_path
            javax.servlet.include.path_info
            javax.servlet.include.query_string        
            被包含的servlet可以调用getAttribute访问这些属性。
            以getNamedDispatcher方式获得的被包含servlet不能得到这些属性。
            
    8.4 Forward方法
            在负责调用的servlet还没有向客户机提交输出的时候,才可以调用RequestDispatcher上的Forward方法。在目标servlet的service方法调用前,如果response缓冲中存在还没提交的数据,这些缓冲内容必须被清空,如果response已经提交,将抛出IllegalStateException异常。request对象暴露给目的servlet的路径元素必须反射用于获得RequestDispatcher路径。唯一的例外是,如果RequestDispatcher是通过
    getNamedDispatcher方法获得的。        
            在forward方法返回前,response内容必须被发送和提交,并被容器关闭。
            
    8.4.1query string
            在forward或include请求的时候,请求转发机制负责组合查询字符串。

    8.5 错误处理
            如果request dispatcher的目标servlet扔出运行时错误或一个被检查类型的意外ServletException或IOException,它应当被传播到调用它的servlet。所有其他异常则应被包装为ServletException,被传播以前,异常引起的最初原因应被设置到原始异常。

    第九章 web 应用
            web应用是web server中某个特定的路径。例如,一个catalog应用可能在http://www.mycorp.com/catalog.所有以此路径开始的请求都被发送到catalog应用处理。
            servlet容器能建立自动产生web应用的规则,例如,~user/ 可以映射到基于/home/user/public_html/的一个web应用。
            默认情形下,在任意时间内,一个web应用的实例必须运行在一个虚拟机上。但如果是分布式应用,这是可以改变的,分布式应用需要遵循一套更严格的规则,这些规则不在本规范内。

    9.2 和ServletContext的关系
            servlet 容器在web应用和Servlet context之间必须强制一对一的对应关系。ServletContext对象相servelt提供了整个应用视图。

    9.3 web应用的元素
            web应用可以由以下元素构成:
            .servlet
            .JSP页面
            .工具类(Utility class)
            .静态文档(html,image,sound 等)
            .客户端java applet,beans,和类
            .组合所有以上元素的描述元信息

    9.4发布层次
            本规范定义了一个层次结构,用于发布和打包,可以文件系统形式存在,也可以以压缩包文件形式。建议servlet容器支持此结构为运行时表示。

    9.5 目录结构
            web应用以结构化层次目录形式存在。该层次结构的根为文档的根。例如,context路径为/catalog的web应用,应用根路径下的index.html文件可以用/catalog/index.html请求来访问。映射的规则在第11章有叙述。因为应用的context路径决定了内容的URL名字空间,web容器必须拒绝可能引起URL名字空间潜在冲突的context定义。这有可能发生,例如试图以相同的context路径发布另一个web应用,或者其中一个context路径是另一个context的一部份。匹配是大小写敏感的,所以,冲突检测也是一样的。

            应用层次中有个特殊的目录叫“WEB-INF”,此目录包含了不在应用根文档的所有东西。WEB-INF节点不是应用文档树的公开部分,不能被客户机直接访问。但该目录的内容对于servlet代码是可见的,可使用ServletContext接口上的getResource或getResourceAsStream方法访问。因此,开发人员如果需要在servlet代码访问,又不希望暴露给客户机的信息,他可以放置在此目录。WEB-INF目录的内容有:
            ./WEB-INF/web.xml
            ./WEB-INF/classes/目录存储servlet和应用类,这些类必须可以对application class loader是可用的。
            ./WEB-INF/lib/*.jar存储java存档文件,这些文件包含servlet,bean,和其他可用类。application class loader必须也能装载这些存档文件中的类。
            装载次序是先WEB-INF/classes目录,再WEB-INF/lib目录。

    9.6 web应用存档文件
            web应用可以使用标准java存档工具打包为war文件.

    9.7 web 应用配置描述
            应该包含以下内容:
            .ServletContext 初始化参数
            .会话配置
            .Servlet / JSP定义
            .Servlet / JSP映射
            .MIME类型映射
            .Welcome文件列表
            .Error页面
            .Security

    9.7.1 扩展依赖
            当大量的应用使用相同的代码和资源,它们在容器中一般被安装为库文件。这些文件通常是通用或标准的API.
            应用开发人员需要知道web容器上安装了什么扩展部件,容器则需要知道servlet依赖什么样的库。web容器应有一种机制,让web应用可以知道JAR文件包含的资源与代码是可用的,并使它们为应用所用。容器应当提供一种简便的过程可以编辑配置库文件或是扩展部分。
            推荐开发人员在war文件中使用META-INF/MANIFEST.MF列出扩展部分。若web容器不能满足这种方式声明的依赖,它必须拒绝应用程序,并给出错误信息。

    9.7.2 web应用classloader
            容器用来装载WAR中的servlet的Classloader必须允许开发人员以J2SE语义使用getResource装载任何包含在war中JAR库内的资源。必须防止war覆盖J2SE或java servlet API类。该classloader应该不允许servlet访问web容器的实现类。推荐让应用类装载器优先装载WAR中的类和资源,再装载JAR中的类和资源。

    9.8 替换web应用
            服务器应该能够在不重新启动容器的情况下替换一个web应用。当应用被替换时,容器应当保持会话数据。

    9.9 错误处理
    9.9.1 request属性
            web应用必须能够指定错误发生时应用使用的其他资源,这些资源的规范在配置描述中叙述。
            如果错误定位为servlet或JSP页面,下列request属性必须设定:

            Request Attributes                                                                                 Type
            javax.servlet.error.status_code                         java.lang.Integer
            javax.servlet.error.exception_type                 java.lang.Class
            javax.servlet.error.message                                         java.lang.String
            javax.servlet.error.exception                                 java.lang.Throwable
            javax.servlet.error.request_uri                         java.lang.String
            javax.servlet.error.servlet_name                         java.lang.String

            这些属性允许servlet产生一个描述详细错误信息的页面。

    9.9.2 错误页面        
            当sertvlet产生错误时,为了允许开发人员定制返回客户机的页面,配置描述文件中定义了一系列错误页面的描述。该语法允许容器返回配置的资源,或者当servlet设置状态码来表明response的错误,或者servlet产生传播到容器的异常和错误.

            若在response上设置了表明错误的状态码,容器参考错误页面声明的列表,试图取得一个匹配的结果.
            servlet处理请求时可以抛出下列异常:
            
            .runtime exceptions or errors
            .ServletExceptions or subclasses thereof
            .IOExceptions or subclasses thereof
            
            web应用也可以使用exception-type元素声明错误页面.容器通过比较所抛出的异常和错误页面中exception-type元素的定义,返回匹配的资源.在类层次最接近的将获得匹配.如果得不到匹配,将抛出ServletException或subclass thereof,容器将展开被包装的异常.A second pass is made over the errorpage declarations, again attempting the match against the error page declarations,but using the wrapped exception instead.
            exception-type元素必须是exception-type的class名称唯一.类似的,status-code元素的状套码也必须是唯一的.
            错误页面机制不回干扰使用RequestDispatcher调用的servlet发生错误.
            如果servlet产生的错误没有错误页处理,容器将会产生状态码为500的response.

    9.10 WelCome file
            开发人员可以在配置描述文件中定义一个welcaome文件的顺序列表.本机制的目的是允许开发人员制定目录入口的缺省资源,如目录缺省资源未定义,那么到目录入口的请求将返回一个404的response.

    9.11 web应用环境
            J2EE定义了命名环境,允许应用很容易地访问资源和外部信息,webu需要显式了解外部信息具体是如何命名和组织的.
            正如servlet是J2EE技术的一个组件,也制定允许servlet获得资源和EJB的引用的规范,这些元素是:

            .env-entry
            .ejb-ref
            .ejb-local-ref
            .resource-ref
            .resource-env-ref

            ( 本节略)

    第十章 应用生命周期事件

    10.1 介绍
            本规范支持应用级别的事件.这些应用事件给了开发人员更大的控制能力,比使用ServletContext和HttpSession对象.

    10.2 事件监听器
            应用事件监听器是实现了一个或多个和servlet 事件监听接口的类,在web应用被发布的时候被实例化并在web容器中注册。它们应由开发人员提供。
            Servlet事件监听器支持当ServletContext和HttpSession对象的状态改变时,进行事件通知。servlet context监听器用来管理应用程序的虚拟机级的资源和状态。HTTP会话监听器用来管理应用程序中与某个客户机或用户关联的一些列请求的资源与状态。
            对于每种事件类型,可能有多个监听器监听它们。开发人员也可以指定容器调用监听器的顺序。
            
    10.3 事件类型与监听器接口

    事件类型                 描述                                 监听器接口
    __________________________________________________________________________________________
    Servlet Context
    事件

    生命周期                 servlet context 刚刚被创建         javax.servlet.ServletContextListener
                                                    它准备好处理它的第一个请求
                                                    或者context即将被关闭                        

    属性改变                 servlet        context上的属性已经        javax.servlet.ServletContextAttributesListener
                                                    被添加,删除,替换
    ____________________________________________________________________________________

    HTTP会话事件

    生命周期                HttpSession对象已经被创建,           javax.servlet.http.HttpSessionListener                
                                                    实效,或过期

    属性改变                HttpSession对象上的属性已经           javax.servlet.HttpSessionAttributesListener        
                                                    被添加、删除或替换

    10.2.2 使用监听器的例子
            为举例说明事件的使用,我们假定一个简单的web应用,包含很多用到数据库的servlet。开发人员已提供了一个servlet context监听器管理数据库联接。

            1.当应用启动,监听器类被通知,应用登陆数据库,并将联接存储在servlet context内。
            2.web应用中的servlet可以在需要的时候访问该联接。
            3.当web服务器关闭,或者web应用被删除,该监听器被通知,接着数据库联接被关闭。

    10.3 监听器类配置

    10.3.1 提供监听器类
            应用开发人员听实现以上四个接口的监听器类,每个类必须有不带参数的公共构造函数。该类被打包进WAR中,也可以放在WEB-INF/classes目录下或者是WEB-INF/lib下的JAR中。

    10.3.2 发布声明
            在web应用配置描述文件中,使用listener元素声明监听器类。以类名按照调用的顺序被列出。

    10.3.3 注册监听器
            web容器在应用处理第一个请求前,创建每个监听器的实例并注册它。容器根据监听器实现的接口以及在配置描述文件中的顺序注册实例。容器以监听器注册的顺序执行它们。

    10.3.4 关闭时通知
            当应用被关闭时,监听器以声明时相反的顺序得到通知,,先处理会话监听,然后是context监听.

    10.4 Example
            下面例子是关于如何注册两个servlet context 监听器和一个 会话监听器的语法.
            假定com.acme.MyConnectionManager和com.acme.MyLoggingModule都实现了javax.servlet.ServletContextListener,并且com.acme.MyLoggingModule
            还实现了javax.servlet.HttpSessionListener,以下是配置描述:
            <web-app>
            <display-name>MyListeningApplication</display-name>
                    <listener>
                            <listener-class>com.acme.MyConnectionManager</listenerclass>
                    </listener>
                    <listener>
                            <listener-class>com.acme.MyLoggingModule</listener-class>
                    </listener>
                    
                    <servlet>
                            <display-name>RegistrationServlet</display-name>
                    </servlet>
    </web-app>
            
    10.5 监听器实例与线程
            容器需要在应用程序处理第一个请求之前,完成实例化监听器类.在完成最后一个服务请求之前,容器必须维护每个监听器实例的引用.
            ServletContext和HttpSession属性的改变是可以并发的.对属性监听器通知,容器不需要同步.维持状态的监听器类有责任保持数据的完整性,并应当明确地处理这种情况.

    10.6 分布式容器
            在分布式web容器中,HttpSession实例存在于处理该会话的请求的特定虚拟机范围,ServletContext对象的范围则在web容器所在的虚拟机内.分布式容器不需要将servlet context和session上的        事件传播到其他虚拟机.监听器实例范围则在每个虚拟机上的每个部署声明对应一个.
            
    10.7 会话事件
            开发人员使用监听器类跟踪应用会话.它的作用很大,想知道哪个会话因容器使会话超时,或是因为被调用了invalidate方法而失效了。区别在于是直接使用HTTPSession的API还是间接使用监听器。

    2006年09月15日

    引用地址:
    http://www2.matrix.org.cn/resource/article/44/44550.html
    英文原版:
    http://www.onjava.com/pub/a/onjava/2004/09/22/sitemesh.html

    假设你打算结合多种技术来构建一个企业级web站点。比如,你准备采用J2EE技术往你的web站点里添加新内容,而这个系统的其他部分是用CGI或者微软的IIS Server搭建的。

    在这种情况下,怎样让你的应用系统从外观和感受(look and feel)上保持一致呢?一种办案就是采用J2EE技术全部重写,然后选用一种框架,比如Struts-Tiles,但这种办案的开发成本太高,不太现实。另一种可选方案是在你的应用系统的各个部分采用相同的Look and Feel。但这种方案会使维护站点变成噩梦,因为每当一个应用系统里面的Look and Feel需要改变的时候,你就需要让系统里的其他web应用保持同样的改变。

    大多数用于解决这种商务需求的可用框架都有一个共同的缺点,他们不是平台相关就是框架相关。当你决定采用Tiles作为struts修饰器的时候,需要创建tiles定义文件tiles-defs.xml,然后在struts-config.xml里面声明forwards,引用这些tiles以修饰原始的JSP。

    最简单的一种可能的解决方案是,全部采用纯html方式来生成你的web应用,每一个html页面都不需要知道自己将会被如何修饰,而是在外部采用某种机制来选择合适的修饰器修饰它们。这就是SiteMesh的功能。

    SiteMesh是基于Java、J2EE和XML的开源框架,依赖于从Servlet 2.3版本里引入的新功能——过滤器(Filters)

    安装和设置

    按照以往的经验,学习任何新技术或新框架最好的办法,就是使用它来创建一个简单的应用程序。所以,我们将使用SiteMesh来创建一个简单的Struts应用程序。我们的应用程序包括三个页面:

    •一个登录页面
    •一个帮助页面,包括页头和页脚
    •一个主页面,包括页头、页脚和页边菜单

    下面是创建这个简单web应用程序的步骤:

    1.SiteMesh基于过滤器,所以我们需要把SiteMesh过滤器通知给我们的web应用程序。在web.xml文件里加入如下几行:

    <filter>
      <filter-name>sitemesh</filter-name>
      <filter-class>
          com.opensymphony.module.sitemesh.filter.PageFilter
      </filter-class>
      <init-param>
        <param-name>debug.pagewriter</param-name>
        <param-value>true</param-value>
      </init-param>
    </filter>
    <filter-mapping>
      <filter-name>sitemesh</filter-name>
      <url-pattern>/*</url-pattern>
    </filter-mapping>

    这几行是告诉web容器,所有对web应用的请求都会经由PageFilter“过滤”一下。PageFilter是sitemesh-2.1.jar里的一个类,你可以从http://www.opensymphony.com/sitemesh/download.html下载该jar包。

    2.在WEB-INF目录下生成一个decorators.xml文件,内容如下:

    <decorators defaultdir="/decorators">
      <!— 给需要页边菜单的页面配置页边菜单修饰器 –>
      <decorator name="sidemenu" page="sidemenu.jsp">
        <pattern>/home.jsp</pattern>
      </decorator>
      <!— 给需要页头和页脚的页面配置页头页脚修饰器 –>
      <decorator name="headerfooter" page="headerfooter.jsp">
        <pattern>/help.jsp</pattern>
      </decorator>
    </decorators>

    decorators.xml文件用来在你的应用程序里定义修饰器(decorators)。在这个文件里,每个<decorator>元素定义一个修饰器,name指定修饰器名,page指定修饰器所使用的JSP页面。<pattern>子元素指定这些修饰器如何应用到实际的页面上去。

    在我们的示例web应用里,定义了两个修饰器:追加页头和页脚的headerfooter.jsp和追加页边菜单的sidemenu.jsp。我们想修饰help页面追加页头和页脚,所以我们追加了一个/help.jsp路径子元素给headerfooter.jsp修饰器。

    3.在WebContent/decorators目录下创建headerfooter.jsp:

    <%@ taglib
        uri="http://www.opensymphony.com/sitemesh/decorator"
        prefix="decorator" %>
    <html>
      <head>
        <title>
          My Site –
          <decorator:title default="Welcome!" />
        </title>
        <decorator:head />
      </head>
      <body>
        <table>
          <tr>
            <td>
              <H1>
                SiteMesh Corporation
              <H1>
            </td>
          </tr>
          <tr>
            <td><decorator:body /></td>
          </tr>
          <tr>
            <td> SiteMesh copyright</td>
          </tr>
        </table>
      </body>
    </html>

    一个SiteMesh修饰器其实就是一个使用SiteMesh自定义标签的JSP页面。在我们的web应用里,当用户请求help页面的时候,SiteMesh会拦截这个请求,然后再把它发送给web应用。而当应用返回响应的时候,SiteMesh会结合headerfooter.jsp文件解析这个响应,遇到<decorator:head/>就插入响应文件的<head>,遇到<decorator:body/>就插入响应文件的<body>。最后,被headerfooter.jsp修饰过的文件会被返回给客户端。

    4.在WebContent目录下创建help.jsp:

    <HTML>
    <HEAD>
    <%@ page
    language="java"
    contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"
    %>
    <TITLE>Help Page</TITLE>
    </HEAD>
    <BODY>
      Help Page
    </BODY>
    </HTML>

    这是一个在web应用里很常见的help页面。

    5.在浏览器里请求help.jsp页面,测试SiteMesh安装是否正常。浏览器将会返回一个包含页头和页脚的help页面。

    SiteMesh架构

    SiteMesh架构基于PageFilter——一个Servlet过滤器。容器接收到页面请求时,会把请求传递给PageFilter,PageFilter收集应用程序的响应细节,生成自定义的响应对象,然后连同请求一起传递给web应用程序。web应用程序把响应资源写入到自定义响应对象里,再返回给PageFilter。

    1.解析阶段
    当控制返回给PageFilter的时候,它会检查web应用生成响应的内容类型(content type),然后基于响应类型,生成不同的解析器来解析响应。比如,如果应用返回text/html类型的内容,SiteMesh会生成一个FastPageParser实例,并把web应用生成的页面传递给它。FastPageParser会解析这个页面,提取出这个页面的header、footer、title 等内容。

    2.修饰阶段

    解析结束后,SiteMesh开始修饰页面。这一阶段分成两部分:

    a.决定如何修饰

    SiteMesh有一个概念,叫做修饰器映射,实现这个概念的接口是DecoratorMapper(有init()和getDecorator()方法)。映射器在sitemesh.xml里声明。在sitemesh.xml文件里,每一个映射器都是它上一个映射器的父映射。当SiteMesh需要一个修饰器来修饰页面的时候,会在sitemesh.xml里查找映射器,生成找到的第一个映射器的实例并调用getDecorator()方法,在这个方法里尝试查找针对那个页面的修饰器。如果找到了就返回;否则,调用父映射器的getDecorator()方法,反复进行这个过程,直到找到正确的修饰器。

    b.应用修饰

    找到修饰器后,SiteMesh会把请求分发给它。修饰器JSP页面会访问在前阶段里解析出来的页面信息。使用各种SiteMesh自定义标签来提取页面信息不同的部分(比如header、footer和title)并把它们插入到输出文件合适的位置上去。

    你可以在sitemesh.xml文件里自定义使用哪个页面解析器来解析指定的内容类型或者使用哪种修饰器映射方案,比如:

    <?xml version="1.0" encoding="UTF-8"?>
    <sitemesh>
      <property name="decorators-file" value="/WEB-INF/decorators.xml"/>
      <excludes file="${decorators-file}"/>
      <page-parsers>
        <parser content-type="text/html"
            class="com.opensymphony.module.sitemesh.parser.FastPageParser" />
      </page-parsers>
      <decorator-mappers>
        <mapper
            class="com.opensymphony.module.sitemesh.mapper.PageDecoratorMapper">
          <param name="property.1" value="meta.decorator" />
          <param name="property.2" value="decorator" />
        </mapper>
        <!– Mapper for localization –>
        <mapper
            class="com.opensymphony.module.sitemesh.mapper.LanguageDecoratorMapper">
          <param name="match.en" value="en" />
          <param name="match.zh" value="zh" />
        </mapper>
        <!– Mapper for browser compatibility –>
        <mapper
            class="com.opensymphony.module.sitemesh.mapper.AgentDecoratorMapper">
          <param name="match.MSIE" value="ie" />
          <param name="match.Mozilla/" value="ns" />
        </mapper>
        <mapper
            class="com.opensymphony.module.sitemesh.mapper.PrintableDecoratorMapper">
          <param name="decorator" value="printable" />
          <param name="parameter.name" value="printable" />
          <param name="parameter.value" value="true" />
        </mapper>
        <mapper
            class="com.opensymphony.module.sitemesh.mapper.ParameterDecoratorMapper">
          <param name="decorator.parameter" value="decorator" />
          <param name="parameter.name" value="confirm" />
          <param name="parameter.value" value="true" />
        </mapper>
        <mapper
            class="com.opensymphony.module.sitemesh.mapper.ConfigDecoratorMapper">
          <param name="config" value="${decorators-file}" />
        </mapper>
      </decorator-mappers>
    </sitemesh>

    在这个列表里,<property name="decorators-file">指定了用于定义修饰器的文件。<page-parsers>定义了SiteMesh可以处理的内容类型。每一个<parser>子元素指定哪一个解析器解析哪一种特定的内容类型。在我们的示例sitemesh.xml文件里,我们告诉SiteMesh使用FastPageParser解析text/html类型的内容。默认地,SiteMesh只可以处理HTML,但我们可以创建自己的解析器来处理其他的内容类型。

    <decorator-mappers>子元素定义了映射方案,SiteMesh使用这个映射方案来查找修饰指定页面的修饰器。你可以使用<param>子元素来配置每一个映射器。SiteMesh会把这些配置信息包装成java.util.Properties对象传递给映射器的init()方法。

    区域相关的修饰器

    在我们的示例sitemesh.xml文件里,有下面几行标签:

    <mapper class="com.opensymphony.module.sitemesh.mapper.LanguageDecoratorMapper">
      <param name="match.en" value="en" />
      <param name="match.zh" value="zh" />
    </mapper>

    当查找一个应用于页面的修饰器时,SiteMesh会首先读取请求头部的Accept-Language信息。如果匹配en区域,SiteMesh会在修饰器JSP文件名末尾追加-en。在我们的例子里,如果请求定义了修饰器headerfooter.jsp的help.jsp页面,并且使用的是区域是英国,SiteMesh会首先查找并应用headerfooter-en.jsp修饰器,如果找不到再去应用headerfooter.jsp。

    浏览器相关的修饰器

    可以使用AgentDecoratorMapper来保证浏览器的兼容性:

    <mapper
    class="com.opensymphony.module.sitemesh.mapper.AgentDecoratorMapper">
      <param name="match.MSIE" value="ie" />
      <param name="match.Mozilla/" value="ns" />
    </mapper>

    这意味着当SiteMesh查找一个修饰器来修饰页面的时候,会首先提取出请求头部的User-Agent信息。如果是IE,就加上-ie到修饰器的文件名末尾,并查找和应用这个修饰器。如果找不到这样的修饰器,则继续应用headerfooter.jsp。

    高级SiteMesh

    SiteMesh提供映射器,让每一个页面参与到寻找自己修饰器的过程中去。

    PrintableDecoratorMapper

    大多数的web站点都提供了一个获得可打印版本页面的功能。所谓可打印版本,一般是指去除了页头、页尾和页边菜单,并使用了另一套样式表的页面。在SiteMesh里,我们可以使用PrintableDecoratorMapper来提供这个功能。要使用这个映射器,需要在sitemesh.xml里追加如下几行:

    <mapper
    class= "com.opensymphony.module.sitemesh.mapper.PrintableDecoratorMapper">
      <param name="decorator" value="printable" />
      <param name="parameter.name" value="printable" />
      <param name="parameter.value" value="true" />
    </mapper>

    传递给PrintableDecoratorMapper的三个配置参数会被包装成java.util.Properties对象传递给init()方法。

    •decorator
      用来生成可打印版本页面的修饰器名。

    •parameter.name
      用来通知SiteMesh我们需要一个可打印版本的请求参数名。比如在我们的例子里,通过在查询字符串里追加printable=true参数传递

    •parameter.value
      设置可打印参数为何值时SiteMesh提供可打印版本的页面。

    PageDecoratorMapper

    页面可以通过定义META属性来重载指定修饰自己的修饰器名。

    要使用这个映射器,需要在sitemesh.xml文件里加入如下几行:

    <mapper
    class="com.opensymphony.module.sitemesh.mapper.PageDecoratorMapper">
      <param name="property.1" value="meta.decorator" />
    </mapper>

    PageDecoratorMapper可以获取一个参数列表。在我们的例子里,提供了一个参数名,指定了通过META属性来取得修饰器名。所以如果我们希望使用test修饰器来修饰页面,则在该页头部加入:

    <META name="decorator" content="test">

    PageDecoratorMapper提供了一种静态的方法来让页面选择自己想要使用的修饰器。另外,页面还可以通过使用ParameterDecoratorMapper在运行时指定要使用的修饰器。

    ParameterDecoratorMapper

    要使用ParameterDecoratorMapper,在sitemesh.xml里追加如下几行:

    <mapper
    class= "com.opensymphony.module.sitemesh.mapper.ParameterDecoratorMapper">
      <param name="decorator.parameter" value="decorator" />
      <param name="parameter.name" value="confirm" />
      <param name="parameter.value" value="true" />
    </mapper>

    三个参数的意义分别如下:

    •decorator.parameter
      指定修饰器所使用的请求参数名。

    •parameter.name
      确定使用请求修饰器的确认参数名。

    •parameter.value
      确定使用请求修饰器的确认参数值。

    比如,如果你想使用test修饰器来修饰help.jsp,可以像下面这样访问help.jsp

    help.jsp?decorator=test&confirm=true

    除了以上这些映射器以外,SiteMesh还提供了更多有用的映射器,比如:

    •FrameSetDecoratorMapper
      当页面是Frame的时候使用。

    •CookieDecoratorMapper
      可以通过cookie来指定想要使用的修饰器。

    •RobotDecoratorMapper
      当请求者被确人为robot的时候使用指定的修饰器。你可以手动的在请求头部追加robot关键字,或者通过修饰器来做。

    Velocity 和 Freemarker 修饰器

    SiteMesh并没有限制你只能修饰JSP页面。你可以自由的选择想要修饰的对象,比如Velocity或者Freemarker。Velocity和Freemarker是一种可被用于生成web页面的模板语言。这些语言比JSP更加的简单易用,但在可编程性方面不如JSP灵活。

    SiteMesh通过两个servlet支持这两种模板语言,这两个servlet也被定义在SiteMesh.jar文件里。我们可以像这样在web.xml里声明这两个servlet:

    <servlet>
      <servlet-name>sitemesh-velocity</servlet-name>
      <servlet-class>
        com.opensymphony.module.sitemesh.velocity.VelocityDecoratorServlet
      </servlet-class>
    </servlet>
    <!–Declare servlet for handling freemarker requests –>
    <servlet>
      <servlet-name>sitemesh-freemarker</servlet-name>
      <servlet-class>
        com.opensymphony.module.sitemesh.freemarker.FreemarkerDecoratorServlet
      </servlet-class>
      <init-param>
        <param-name>TemplatePath</param-name>
        <param-value>/</param-value>
      </init-param>
      <init-param>
        <param-name>default_encoding</param-name>
        <param-value>ISO-8859-1</param-value>
      </init-param>
    </servlet>
    <!– Velocity servlet should serve all requests with .vm extension–>
    <servlet-mapping>
      <servlet-name>sitemesh-velocity</servlet-name>
      <url-pattern>*.vm</url-pattern>
    </servlet-mapping>
    <!– FreeMarker servlet should serve all requests with .dec extension–>
    <servlet-mapping>
      <servlet-name>sitemesh-freemarker</servlet-name>
      <url-pattern>*.dec</url-pattern>
    </servlet-mapping>

    当然,我们还需要在lib文件夹里引入freemarker.jar、velocity-dep.jar和velocity-tools-view.jar。这些jar文件已经包含在SiteMesh的发布包里了。下面让我们修改第一个示例应用,使用Velocity和Freemarker修饰器来取代JSP。在我们第一个示例应用里定义了两个修饰器:headerfooter和sidemenu。下面我们创建一个headerfooter.dec:

    <html>
      <head>
        <title>My Site – $Advanced SiteMesh</title>
          ${head}
      </head>
      <body>
        <table border="1">
          <tr>
            <td>SiteMesh Corporation</td>
          </tr>
          <tr>
            <td>${body}</td>
          </tr>
          <tr>
            <td>SiteMesh copyright</td>
          </tr>
        </table>
      </body>
    </html>

    在这个页面里,我们使用Freemarker模板来请求header、footer和title,而不是使用JSP自定义标签,但页面布局是一样的。当容器接收到一个.dec扩展名的页面请求时,会把这个请求传递给FreemarkerDecoratorServlet,后者将会调用FreemarkerDecorator修饰生成的HTML页面。我们使用$Advanced SiteMesh模板来访问应用生成的web页面的title,${head}访问head,${body}访问body。Freemarker提供了非常丰富的模板,想深入研究的话可以参考http://www.javaworld.com/jw-01-2001/jw-0119-freemarker.html。

    相似的,在decorators目录下创建sidemenu.vm文件,这是Velocity修饰器文件:

    <html>
      <head>
        <title>My Site – $title</title>
          $head
      </head>
      <body>
        <table border="1">
          <tr>
            <td> SiteMesh Header </td>
          </tr>
          <tr>
            <td> Sidemenu </td>
            <td> $body </td>
          </tr>
          <tr>
            <td> SiteMesh Footer </td>
          </tr>
        </table>
      </body>
    </html>

    使用$title模板取代<decorator:title/>,使用$head和$body Velocity模板来取代相应的JSP自定义标签。

    结论

    基于过滤器的SiteMesh是一个非常灵活和简单易用的修饰器框架。但它还是存在着一些问题。首先,从Servlet 2.3版本才开始支持过滤器,所以一些早期版本的应用服务器无法支持SiteMesh。在使用SiteMesh之前请先检查一下您想使用的应用服务器是否支持过滤器。

    另外,过滤器只有在使用浏览器请求一个页面的时候才能生效。所以,如果你通过浏览器访问home.jsp,它将被修饰,但如果你使用Servlet的RequestDispatcher.include()或者forward()来控制home.jsp,修饰器就不起作用了。但是不用担心,从Servlet 2.4版本开始,你可以配置过滤器适用的环境,包括forward和include的情况下都可以使用了。
    的情况下都可以使用了。

    2006年09月14日

     

    原文地址:http://www-128.ibm.com/developerworks/cn/xml/x-dom4j.html

    使用 domj4 API 创建与修改 XML 文档

    developerWorks
    文档选项


    将此页作为电子邮件发送

    将此页作为电子邮件发送

    最新推荐

    Java 应用开发源动力 - 下载免费软件,快速启动开发

    级别: 初级

    Deepak Vohra, Web 开发人员

    2004 年 4 月 01 日

    dom4j 是一种解析 XML 文档的开放源代码 XML 框架。本文介绍如何使用包含在 dom4j 中的解析器创建并修改 XML 文档。

    dom4j API 包含一个解析 XML 文档的工具。本文中将使用这个解析器创建一个示例 XML 文档。清单 1 显示了这个示例 XML 文档,catalog.xml。

    清单 1. 示例 XML 文档(catalog.xml)

    
    <?xml version="1.0" encoding="UTF-8"?>
    <catalog>
    <!--An XML Catalog-->
    <?target instruction?>
      <journal title="XML Zone"
                      publisher="IBM developerWorks"> 
    
    <article level="Intermediate" date="December-2001">
     <title>Java configuration with XML Schema</title>
     <author>
         <firstname>Marcello</firstname>
         <lastname>Vitaletti</lastname>
     </author>
      </article>
      </journal>
    </catalog>
    

    然后使用同一个解析器修改 catalog.xml,清单 2 是修改后的 XML 文档,catalog-modified.xml。

    清单 2. 修改后的 XML 文档(catalog-modified.xml)

    
    <?xml version="1.0" encoding="UTF-8"?>
    <catalog>
    <!--An XML catalog-->
    <?target instruction?>
      <journal title="XML Zone"
                       publisher="IBM developerWorks"> 
    
    <article level="Introductory" date="October-2002">
     <title>Create flexible and extensible XML schemas</title>
     <author>
         <firstname>Ayesha</firstname>
         <lastname>Malik</lastname>
     </author>
      </article>
      </journal>
    </catalog>
    

    与 W3C DOM API 相比,使用 dom4j 所包含的解析器的好处是 dom4j 拥有本地的 XPath 支持。DOM 解析器不支持使用 XPath 选择节点。

    本文包括以下几个部分:

    • 预先设置
    • 创建文档
    • 修改文档

    预先设置

    这个解析器可以从 http://dom4j.org 获取。通过设置使 dom4j-1.4/dom4j-full.jar 能够在 classpath 中访问,该文件中包括 dom4j 类、XPath 引擎以及 SAX 和 DOM 接口。如果已经使用了 JAXP 解析器中包含的 SAX 和 DOM 接口,向 classpath 中增加 dom4j-1.4/dom4j.jardom4j.jar 包括 dom4j 类和 XPath 引擎,但是不含 SAX 与 DOM 接口。


    回页首

    创建文档

    本节讨论使用 dom4j API 创建 XML 文档的过程,并创建示例 XML 文档 catalog.xml。

    使用 import 语句导入 dom4j API 类:

    
    import org.dom4j.Document;
    import org.dom4j.DocumentHelper;
    import org.dom4j.Element;
    

    使用 DocumentHelper 类创建一个文档实例。 DocumentHelper 是生成 XML 文档节点的 dom4j API 工厂类。

     Document document = DocumentHelper.createDocument();

    使用 addElement() 方法创建根元素 catalogaddElement() 用于向 XML 文档中增加元素。

    Element catalogElement = document.addElement("catalog");

    catalog 元素中使用 addComment() 方法添加注释“An XML catalog”。

     catalogElement.addComment("An XML catalog");

    catalog 元素中使用 addProcessingInstruction() 方法增加一个处理指令。

    catalogElement.addProcessingInstruction("target","text");

    catalog 元素中使用 addElement() 方法增加 journal 元素。

    Element journalElement =  catalogElement.addElement("journal");

    使用 addAttribute() 方法向 journal 元素添加 titlepublisher 属性。

    journalElement.addAttribute("title", "XML Zone");
             journalElement.addAttribute("publisher", "IBM developerWorks");

    article 元素中添加 journal 元素。

    Element articleElement=journalElement.addElement("article");

    article 元素增加 leveldate 属性。

    articleElement.addAttribute("level", "Intermediate");
          articleElement.addAttribute("date", "December-2001");

    article 元素中增加 title 元素。

    Element titleElement=articleElement.addElement("title");

    使用 setText() 方法设置 article 元素的文本。

    titleElement.setText("Java configuration with XML Schema");

    article 元素中增加 author 元素。

    Element authorElement=articleElement.addElement("author");

    author 元素中增加 firstname 元素并设置该元素的文本。

    Element  firstNameElement=authorElement.addElement("firstname");
         firstNameElement.setText("Marcello");

    author 元素中增加 lastname 元素并设置该元素的文本。

    Element lastNameElement=authorElement.addElement("lastname");
         lastNameElement.setText("Vitaletti");

    可以使用 addDocType() 方法添加文档类型说明。

    document.addDocType("catalog", null,"file://c:/Dtds/catalog.dtd");

    这样就向 XML 文档中增加文档类型说明:

    <!DOCTYPE catalog SYSTEM "file://c:/Dtds/catalog.dtd">

    如果文档要使用文档类型定义(DTD)文档验证则必须有 Doctype。

    XML 声明 <?xml version="1.0" encoding="UTF-8"?> 自动添加到 XML 文档中。

    清单 3 所示的例子程序 XmlDom4J.java 用于创建 XML 文档 catalog.xml。

    清单 3. 生成 XML 文档 catalog.xml 的程序(XmlDom4J.java)

    
    import org.dom4j.Document;
    import org.dom4j.DocumentHelper;
    import org.dom4j.Element;
    import org.dom4j.io.XMLWriter;
    import java.io.*;
    
    public class XmlDom4J{
    
    public void generateDocument(){
    Document document = DocumentHelper.createDocument();
         Element catalogElement = document.addElement("catalog");
         catalogElement.addComment("An XML Catalog");
         catalogElement.addProcessingInstruction("target","text");
         Element journalElement =  catalogElement.addElement("journal");
         journalElement.addAttribute("title", "XML Zone");
         journalElement.addAttribute("publisher", "IBM developerWorks");
    
         Element articleElement=journalElement.addElement("article");
         articleElement.addAttribute("level", "Intermediate");
         articleElement.addAttribute("date", "December-2001");
         Element  titleElement=articleElement.addElement("title");
         titleElement.setText("Java configuration with XML Schema");
         Element authorElement=articleElement.addElement("author");
         Element  firstNameElement=authorElement.addElement("firstname");
         firstNameElement.setText("Marcello");
         Element lastNameElement=authorElement.addElement("lastname");
         lastNameElement.setText("Vitaletti");
    
         document.addDocType("catalog",
                               null,"file://c:/Dtds/catalog.dtd");
        try{
        XMLWriter output = new XMLWriter(
                new FileWriter( new File("c:/catalog/catalog.xml") ));
            output.write( document );
            output.close();
            }
         catch(IOException e){System.out.println(e.getMessage());}
    }
    
    public static void main(String[] argv){
    XmlDom4J dom4j=new XmlDom4J();
    dom4j.generateDocument();
    }}
    

    这一节讨论了创建 XML 文档的过程,下一节将介绍使用 dom4j API 修改这里创建的 XML 文档。


    回页首

    修改文档

    这一节说明如何使用 dom4j API 修改示例 XML 文档 catalog.xml。

    使用 SAXReader 解析 XML 文档 catalog.xml:

    SAXReader saxReader = new SAXReader();
     Document document = saxReader.read(inputXml);

    SAXReader 包含在 org.dom4j.io 包中。

    inputXml 是从 c:/catalog/catalog.xml 创建的 java.io.File。使用 XPath 表达式从 article 元素中获得 level 节点列表。如果 level 属性值是“Intermediate”则改为“Introductory”。

    List list = document.selectNodes("//article/@level" );
          Iterator iter=list.iterator();
            while(iter.hasNext()){
                Attribute attribute=(Attribute)iter.next();
                   if(attribute.getValue().equals("Intermediate"))
                   attribute.setValue("Introductory");
           }

    获取 article 元素列表,从 article 元素中的 title 元素得到一个迭代器,并修改 title 元素的文本。

    list = document.selectNodes("//article" );
         iter=list.iterator();
       while(iter.hasNext()){
           Element element=(Element)iter.next();
          Iterator iterator=element.elementIterator("title");
       while(iterator.hasNext()){
       Element titleElement=(Element)iterator.next();
       if(titleElement.getText().equals("Java configuration with XML Schema"))
         titleElement.setText("Create flexible and extensible XML schema");
    
        }}

    通过和 title 元素类似的过程修改 author 元素。

    清单 4 所示的示例程序 Dom4JParser.java 用于把 catalog.xml 文档修改成 catalog-modified.xml 文档。

    清单 4. 用于修改 catalog.xml 的程序(Dom4Jparser.java)

    
    import org.dom4j.Document;
    import org.dom4j.Element;
    import org.dom4j.Attribute;
    import java.util.List;
    import java.util.Iterator;
    import org.dom4j.io.XMLWriter;
    import java.io.*;
    import org.dom4j.DocumentException;
    import org.dom4j.io.SAXReader; 
    
    public class Dom4JParser{
    
     public void modifyDocument(File inputXml){
    
      try{
       SAXReader saxReader = new SAXReader();
       Document document = saxReader.read(inputXml);
    
       List list = document.selectNodes("//article/@level" );
       Iterator iter=list.iterator();
       while(iter.hasNext()){
        Attribute attribute=(Attribute)iter.next();
        if(attribute.getValue().equals("Intermediate"))
          attribute.setValue("Introductory"); 
    
           }
    
       list = document.selectNodes("//article/@date" );
       iter=list.iterator();
       while(iter.hasNext()){
        Attribute attribute=(Attribute)iter.next();
        if(attribute.getValue().equals("December-2001"))
          attribute.setValue("October-2002");
    
           }
    
       list = document.selectNodes("//article" );
       iter=list.iterator();
       while(iter.hasNext()){
        Element element=(Element)iter.next();
        Iterator iterator=element.elementIterator("title");
          while(iterator.hasNext()){
            Element titleElement=(Element)iterator.next();
            if(titleElement.getText().equals("Java configuration with XML
    
          Schema"))
            titleElement.setText("Create flexible and extensible XML schema");
    
                                              }
    
                                    }
    
        list = document.selectNodes("//article/author" );
        iter=list.iterator();
         while(iter.hasNext()){
         Element element=(Element)iter.next();
         Iterator iterator=element.elementIterator("firstname");
         while(iterator.hasNext()){
          Element firstNameElement=(Element)iterator.next();
          if(firstNameElement.getText().equals("Marcello"))
          firstNameElement.setText("Ayesha");
                                         }
    
                                  }
    
        list = document.selectNodes("//article/author" );
        iter=list.iterator();
         while(iter.hasNext()){
          Element element=(Element)iter.next();
          Iterator iterator=element.elementIterator("lastname");
         while(iterator.hasNext()){
          Element lastNameElement=(Element)iterator.next();
          if(lastNameElement.getText().equals("Vitaletti"))
          lastNameElement.setText("Malik");
    
                                      }
    
                                   }
         XMLWriter output = new XMLWriter(
          new FileWriter( new File("c:/catalog/catalog-modified.xml") ));
         output.write( document );
         output.close();
       }
    
      catch(DocumentException e)
                     {
                      System.out.println(e.getMessage());
                                }
    
      catch(IOException e){
                           System.out.println(e.getMessage());
                        }
     }
    
     public static void main(String[] argv){
    
      Dom4JParser dom4jParser=new Dom4JParser();
      dom4jParser.modifyDocument(new File("c:/catalog/catalog.xml"));
    
                                            }
    
       }
    

    这一节说明了如何使用 dom4j 中的解析器修改示例 XML 文档。这个解析器不使用 DTD 或者模式验证 XML 文档。如果 XML 文档需要验证,可以解释用 dom4j 与 JAXP SAX 解析器。


    回页首

    结束语

    包含在 dom4j 中的解析器是一种用于解析 XML 文档的非验证性工具,可以与JAXP、Crimson 或 Xerces 集成。本文说明了如何使用该解析器创建和修改 XML 文档。

    Parsing XML

        或许你想要做的第一件事情就是解析一个某种类型的XML文档,用dom4j很容易做到。请看下面的示范代码:

    import java.net.URL;

    import org.dom4j.Document;
    import org.dom4j.DocumentException;
    import org.dom4j.io.SAXReader;

    public class Foo {

        public Document parse(URL url) throws DocumentException {
            SAXReader reader = new SAXReader();
            Document document = reader.read(url);
            return document;
        }
    }

    使用迭代器(Iterators)

        我们可以通过多种方法来操作XML文档,这些方法返回java里标准的迭代器(Iterators)。例如:

    public void bar(Document document) throws DocumentException {
            Element root = document.getRootElement();
            //迭代根元素下面的所有子元素
            for ( Iterator i = root.elementIterator(); i.hasNext(); ) {
                Element element = (Element) i.next();
                //处理代码
            }

            //迭代根元素下面名称为"foo"的子元素
            for ( Iterator i = root.elementIterator( "foo" ); i.hasNext(); ) {
                Element foo = (Element) i.next();
                //处理代码
            }

            // 迭代根元素的属性attributes)元素
            for ( Iterator i = root.attributeIterator(); i.hasNext(); ) {
                Attribute attribute = (Attribute) i.next();
                // do something
            }
         }

    强大的XPath导航

        在dom4j中XPath可以表示出在XML树状结构中的Document或者任意的节点(Node)(例如:Attribute,Element 或者ProcessingInstruction等)。它可以使在文档中复杂的操作仅通过一行代码就可以完成。例如:

    public void bar(Document document) {
            List list = document.selectNodes( "//foo/bar" );

            Node node = document.selectSingleNode( "//foo/bar/author" );

            String name = node.valueOf( "@name" );
        }

        如果你想得到一个XHTML文档中的所有超文本链接(hypertext links)你可以使用下面的代码:

        public void findLinks(Document document) throws DocumentException {

            List list = document.selectNodes( "//a/@href" );

            for (Iterator iter = list.iterator(); iter.hasNext(); ) {
                Attribute attribute = (Attribute) iter.next();
                String url = attribute.getValue();
            }
        }

        如果你需要关于XPath语言的任何帮助,我们强烈推荐这个站点Zvon tutorial他会通过一个一个的例子引导你学习。

    快速遍历(Fast Looping)

    如果你不得不遍历一个非常大的XML文档,然后才去执行,我们建议你使用快速遍历方法(fast looping method),它可以避免为每一个循环的节点创建一个迭代器对象,如下所示:

    public void treeWalk(Document document) {
            treeWalk( document.getRootElement() );
        }

        public void treeWalk(Element element) {
            for ( int i = 0, size = element.nodeCount(); i < size; i++ ) {
                Node node = element.node(i);
                if ( node instanceof Element ) {
                    treeWalk( (Element) node );
                }
                else {
                    // do something….
                }
            }
        }

    生成一个新的XML文档对象

        在dom4j中你可能常常希望用程序生成一个XML文档对象,下面的程序为你进行了示范:

    import org.dom4j.Document;
    import org.dom4j.DocumentHelper;
    import org.dom4j.Element;

    public class Foo {

        public Document createDocument() {
            Document document = DocumentHelper.createDocument();
            Element root = document.addElement( "root" );

            Element author1 = root.addElement( "author" )
                .addAttribute( "name", "James" )
                .addAttribute( "location", "UK" )
                .addText( "James Strachan" );
            
            Element author2 = root.addElement( "author" )
                .addAttribute( "name", "Bob" )
                .addAttribute( "location", "US" )
                .addText( "Bob McWhirter" );

            return document;
        }
    }

    将一个文档对象写入文件中

        将一个文档对象写入Writer对象的一个简单快速的途径是通过write()方法。

            FileWriter out = new FileWriter( "foo.xml" );
            document.write( out );

    如果你想改变输出文件的排版格式,比如你想要一个漂亮的格式或者是一个紧凑的格式,或者你想用Writer 对象或者OutputStream 对象来操作,那么你可以使用XMLWriter 类。

    import org.dom4j.Document;
    import org.dom4j.io.OutputFormat;
    import org.dom4j.io.XMLWriter;

    public class Foo {

        public void write(Document document) throws IOException {

            // 写入文件
            XMLWriter writer = new XMLWriter(
                new FileWriter( "output.xml" )
            );
            writer.write( document );
            writer.close();

            // 以一种优雅的格式写入System.out对象
            OutputFormat format = OutputFormat.createPrettyPrint();
            writer = new XMLWriter( System.out, format );
            writer.write( document );

            // 以一种紧凑的格式写入System.out对象
            format = OutputFormat.createCompactFormat();
            writer = new XMLWriter( System.out, format );
            writer.write( document );
        }
    }

    转化为字符串,或者从字符串转化

        如果你有一个文档(Document)对象或者任何一个节点(Node)对象的引用(reference),象属性(Attribute)或者元素(Element),你可以通过asXML()方法把它转化为一个默认的XML字符串:

            Document document = …;
            String text = document.asXML();

    如果你有一些XML内容的字符串表示,你可以通过DocumentHelper.parseText()方法将它重新转化为文档(Document)对象:

            String text = "<person> <name>James</name> </person>";
            Document document = DocumentHelper.parseText(text);

    通过XSLT样式化文档(Document)

        使用Sun公司提供的JAXP API将XSLT 应用到文档(Document)上是很简单的。它允许你使用任何的XSLT引擎(例如:Xalan或SAXON等)来开发。下面是一个使用JAXP创建一个转化器(transformer),然后将它应用到文档(Document)上的例子:

    import javax.xml.transform.Transformer;
    import javax.xml.transform.TransformerFactory;

    import org.dom4j.Document;
    import org.dom4j.io.DocumentResult;
    import org.dom4j.io.DocumentSource;

    public class Foo {

        public Document styleDocument(
            Document document, 
            String stylesheet
        ) throws Exception {

            // 使用 JAXP 加载转化器
            TransformerFactory factory = TransformerFactory.newInstance();
            Transformer transformer = factory.newTransformer( 
                new StreamSource( stylesheet ) 
            );

            // 现在来样式化一个文档(Document)
            DocumentSource source = new DocumentSource( document );
            DocumentResult result = new DocumentResult();
            transformer.transform( source, result );

            // 返回经过样式化的文档(Document)
            Document transformedDoc = result.getDocument();
            return transformedDoc;
        }
    }

    原文地址

    2006年09月13日
    2005-08-09   来源:CSDN  作者:Ginger547
    在平时工作中,难免会遇到把XML作为数据存储格式。面对目前种类繁多的解决方案,哪个最适合我们呢?在这篇文章中,我对这四种主流方案做一个不完全评测,仅仅针对遍历XML这块来测试,因为遍历XML是工作中使用最多的(至少我认为)。

      预备

      测试环境:

      AMD毒龙1.4G OC 1.5G、256M DDR333、Windows2000 Server SP4、Sun JDK 1.4.1+Eclipse 2.1+Resin 2.1.8,在Debug模式下测试。

      XML文件格式如下:

      <?xml version="1.0" encoding="GB2312"?><RESULT><VALUE>

      <NO>A1234</NO>

      <ADDR>四川省XX县XX镇XX路X段XX号</ADDR></VALUE><VALUE>

      <NO>B1234</NO>

      <ADDR>四川省XX市XX乡XX村XX组</ADDR></VALUE></RESULT>

      测试方法:

      采用JSP端调用Bean(至于为什么采用JSP来调用,请参考:http://blog.csdn.net/rosen/archive/2004/10/15/138324.aspx),让每一种方案分别解析10K、100K、1000K、10000K的XML文件,计算其消耗时间(单位:毫秒)。

      JSP文件:

      <%@ page contentType="text/html; charset=gb2312" %><%@ page import="com.test.*"%>

      <html><body><%String args[]={""};MyXMLReader.main(args);%></body></html>

      测试

      首先出场的是DOM(JAXP Crimson解析器)

      DOM是用与平台和语言无关的方式表示XML文档的官方W3C标准。DOM是以层次结构组织的节点或信息片断的集合。这个层次结构允许开发人员在树中寻找特定信息。分析该结构通常需要加载整个文档和构造层次结构,然后才能做任何工作。由于它是基于信息层次的,因而DOM被认为是基于树或基于对象的。DOM以及广义的基于树的处理具有几个优点。首先,由于树在内存中是持久的,因此可以修改它以便应用程序能对数据和结构作出更改。它还可以在任何时候在树中上下导航,而不是像SAX那样是一次性的处理。DOM使用起来也要简单得多。

      另一方面,对于特别大的文档,解析和加载整个文档可能很慢且很耗资源,因此使用其他手段来处理这样的数据会更好。这些基于事件的模型,比如SAX。

      Bean文件:

      package com.test;

      import java.io.*;import java.util.*;import org.w3c.dom.*;import javax.xml.parsers.*;

      public class MyXMLReader{

      public static void main(String arge[]){

      long lasting =System.currentTimeMillis();

      try{ 

       File f=new File("data_10k.xml");

       DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance();

       DocumentBuilder builder=factory.newDocumentBuilder();

       Document doc = builder.parse(f);

       NodeList nl = doc.getElementsByTagName("VALUE");

       for (int i=0;i<nl.getLength();i++){

        System.out.print("车牌号码:" + doc.getElementsByTagName("NO").item(i).getFirstChild().getNodeValue());

        System.out.println("车主地址:" + doc.getElementsByTagName("ADDR").item(i).getFirstChild().getNodeValue());

      }

      }catch(Exception e){

       e.printStackTrace();

      }

      System.out.println("运行时间:"+(System.currentTimeMillis() - lasting)+"毫秒");}} 

      10k消耗时间:265 203 219 172

      100k消耗时间:9172 9016 8891 9000

      1000k消耗时间:691719 675407 708375 739656

      10000k消耗时间:OutOfMemoryError

      接着是SAX

      这种处理的优点非常类似于流媒体的优点。分析能够立即开始,而不是等待所有的数据被处理。而且,由于应用程序只是在读取数据时检查数据,因此不需要将数据存储在内存中。这对于大型文档来说是个巨大的优点。事实上,应用程序甚至不必解析整个文档;它可以在某个条件得到满足时停止解析。一般来说,SAX还比它的替代者DOM快许多。
       选择DOM还是选择SAX?

      对于需要自己编写代码来处理XML文档的开发人员来说, 

      选择DOM还是SAX解析模型是一个非常重要的设计决策。

      DOM采用建立树形结构的方式访问XML文档,而SAX采用的事件模型。

      DOM解析器把XML文档转化为一个包含其内容的树,并可以对树进行遍历。用DOM解析模型的优点是编程容易,开发人员只需要调用建树的指令,然后利用navigation APIs访问所需的树节点来完成任务。可以很容易的添加和修改树中的元素。然而由于使用DOM解析器的时候需要处理整个XML文档,所以对性能和内存的要求比较高,尤其是遇到很大的XML文件的时候。由于它的遍历能力,DOM解析器常用于XML文档需要频繁的改变的服务中。

      SAX解析器采用了基于事件的模型,它在解析XML文档的时候可以触发一系列的事件,当发现给定的tag的时候,它可以激活一个回调方法,告诉该方法制定的标签已经找到。SAX对内存的要求通常会比较低,因为它让开发人员自己来决定所要处理的tag。特别是当开发人员只需要处理文档中所包含的部分数据时,SAX这种扩展能力得到了更好的体现。但用SAX解析器的时候编码工作会比较困难,而且很难同时访问同一个文档中的多处不同数据。

      Bean文件:

      package com.test;import org.xml.sax.*;import org.xml.sax.helpers.*;import javax.xml.parsers.*;

      public class MyXMLReader extends DefaultHandler {

      java.util.Stack tags = new java.util.Stack();

      public MyXMLReader() {

      super();}

      public static void main(String args[]) {

      long lasting = System.currentTimeMillis();

      try {

       SAXParserFactory sf = SAXParserFactory.newInstance();

       SAXParser sp = sf.newSAXParser();

       MyXMLReader reader = new MyXMLReader();

       sp.parse(new InputSource("data_10k.xml"), reader);

      } catch (Exception e) {

       e.printStackTrace();

      }

      System.out.println("运行时间:" + (System.currentTimeMillis() - lasting) + "毫秒");}

      public void characters(char ch[], int start, int length) throws SAXException {

      String tag = (String) tags.peek();

      if (tag.equals("NO")) { 

       System.out.print("车牌号码:" + new String(ch, start, length));}if (tag.equals("ADDR")) {

      System.out.println("地址:" + new String(ch, start, length));}}

      public void startElement(String uri,String localName,String qName,Attributes attrs) {

      tags.push(qName);}} 

      10k消耗时间:110 47 109 78

      100k消耗时间:344 406 375 422

      1000k消耗时间:3234 3281 3688 3312

      10000k消耗时间:32578 34313 31797 31890 30328

      然后是JDOM http://www.jdom.org/

      JDOM的目的是成为Java特定文档模型,它简化与XML的交互并且比使用DOM实现更快。由于是第一个Java特定模型,JDOM一直得到大力推广和促进。正在考虑通过“Java规范请求JSR-102”将它最终用作“Java标准扩展”。从2000年初就已经开始了JDOM开发。

      JDOM与DOM主要有两方面不同。首先,JDOM仅使用具体类而不使用接口。这在某些方面简化了API,但是也限制了灵活性。第二,API大量使用了Collections类,简化了那些已经熟悉这些类的Java开发者的使用。

      JDOM文档声明其目的是“使用20%(或更少)的精力解决80%(或更多)Java/XML问题”(根据学习曲线假定为20%)。JDOM对于大多数Java/XML应用程序来说当然是有用的,并且大多数开发者发现API比DOM容易理解得多。JDOM还包括对程序行为的相当广泛检查以防止用户做任何在XML中无意义的事。然而,它仍需要您充分理解XML以便做一些超出基本的工作(或者甚至理解某些情况下的错误)。这也许是比学习DOM或JDOM接口都更有意义的工作。

      JDOM自身不包含解析器。它通常使用SAX2解析器来解析和验证输入XML文档(尽管它还可以将以前构造的DOM表示作为输入)。它包含一些转换器以将JDOM表示输出成SAX2事件流、DOM模型或XML文本文档。JDOM是在Apache许可证变体下发布的开放源码。

      Bean文件:

      package com.test;

      import java.io.*;import java.util.*;import org.jdom.*;import org.jdom.input.*;

      public class MyXMLReader {

      public static void main(String arge[]) {

      long lasting = System.currentTimeMillis();

      try {

       SAXBuilder builder = new SAXBuilder(); 

       Document doc = builder.build(new File("data_10k.xml")); 

       Element foo = doc.getRootElement(); 

       List allChildren = foo.getChildren(); 

       for(int i=0;i<allChildren.size();i++) { 

        System.out.print("车牌号码:" + ((Element)allChildren.get(i)).getChild("NO").getText());

        System.out.println("车主地址:" + ((Element)allChildren.get(i)).getChild("ADDR").getText());

       }

      } catch (Exception e) {

       e.printStackTrace();

      }

      System.out.println("运行时间:" + (System.currentTimeMillis() - lasting) + "毫秒");}} 

      10k消耗时间:125 62 187 94

      100k消耗时间:704 625 640 766

      1000k消耗时间:27984 30750 27859 30656

      10000k消耗时间:OutOfMemoryError

      最后是DOM4J http://dom4j.sourceforge.net/

      虽然DOM4J代表了完全独立的开发结果,但最初,它是JDOM的一种智能分支。它合并了许多超出基本XML文档表示的功能,包括集成的XPath支持、XML Schema支持以及用于大文档或流化文档的基于事件的处理。它还提供了构建文档表示的选项,它通过DOM4J API和标准DOM接口具有并行访问功能。从2000下半年开始,它就一直处于开发之中。

      为支持所有这些功能,DOM4J使用接口和抽象基本类方法。DOM4J大量使用了API中的Collections类,但是在许多情况下,它还提供一些替代方法以允许更好的性能或更直接的编码方法。直接好处是,虽然DOM4J付出了更复杂的API的代价,但是它提供了比JDOM大得多的灵活性。

      在添加灵活性、XPath集成和对大文档处理的目标时,DOM4J的目标与JDOM是一样的:针对Java开发者的易用性和直观操作。它还致力于成为比JDOM更完整的解决方案,实现在本质上处理所有Java/XML问题的目标。在完成该目标时,它比JDOM更少强调防止不正确的应用程序行为。

      DOM4J是一个非常非常优秀的Java XML API,具有性能优异、功能强大和极端易用使用的特点,同时它也是一个开放源代码的软件。如今你可以看到越来越多的Java软件都在使用DOM4J来读写XML,特别值得一提的是连Sun的JAXM也在用DOM4J。

      Bean文件:

      package com.test;

      import java.io.*;import java.util.*;import org.dom4j.*;import org.dom4j.io.*;

      public class MyXMLReader {

      public static void main(String arge[]) {

      long lasting = System.currentTimeMillis();

      try {

       File f = new File("data_10k.xml");

       SAXReader reader = new SAXReader();

       Document doc = reader.read(f);

       Element root = doc.getRootElement();

       Element foo;

       for (Iterator i = root.elementIterator("VALUE"); i.hasNext();) {

        foo = (Element) i.next();

        System.out.print("车牌号码:" + foo.elementText("NO"));

        System.out.println("车主地址:" + foo.elementText("ADDR"));

       }

      } catch (Exception e) {

       e.printStackTrace();

      }

      System.out.println("运行时间:" + (System.currentTimeMillis() - lasting) + "毫秒");}} 

      10k消耗时间:109 78 109 31

      100k消耗时间:297 359 172 312

      1000k消耗时间:2281 2359 2344 2469

      10000k消耗时间:20938 19922 20031 21078

      JDOM和DOM在性能测试时表现不佳,在测试10M文档时内存溢出。在小文档情况下还值得考虑使用DOM和JDOM。虽然JDOM的开发者已经说明他们期望在正式发行版前专注性能问题,但是从性能观点来看,它确实没有值得推荐之处。另外,DOM仍是一个非常好的选择。DOM实现广泛应用于多种编程语言。它还是许多其它与XML相关的标准的基础,因为它正式获得W3C推荐(与基于非标准的Java模型相对),所以在某些类型的项目中可能也需要它(如在JavaScript中使用DOM)。

      SAX表现较好,这要依赖于它特定的解析方式。一个SAX检测即将到来的XML流,但并没有载入到内存(当然当XML流被读入时,会有部分文档暂时隐藏在内存中)。

      无疑,DOM4J是这场测试的获胜者,目前许多开源项目中大量采用DOM4J,例如大名鼎鼎的Hibernate也用DOM4J来读取XML配置文件。如果不考虑可移植性,那就采用DOM4J吧!(