随笔 - 63  文章 - 0  trackbacks - 0
<2009年5月>
262728293012
3456789
10111213141516
17181920212223
24252627282930
31123456

常用链接

留言簿(2)

随笔分类

随笔档案

搜索

  •  

最新评论

阅读排行榜

评论排行榜

UserType and CompositeUserType为Hibernate中提供的用户类型自定义接口。根据这个接口,可以实现自定义的数据类型。 

最近看<<深入浅出Hibernate>>,作者在书中介绍了Hibernate 提供的用户自定义数据类型,于是效仿书中的方法,在数据库表中添加一个字段用于记录所有好友的userid,每个userid之间用";"加以分隔,同时将该字段映射为一个特殊的List集合,利用UserType interface实现String解析后将各个userid封装在List中,将List中的记录封装成以";"分隔的String。
使用UserType接口,实现了较好的设计风格,以及更好的重用性。
/*
* 用户自定义的数据类型,对应数据库中的一个字段,在该字段中,保存了
* 多个用户需要的信息,之间用";"加以分隔.
* @Author:Paul
* @Date:April 18th,2008
*/
package com.globalhands.hibernate.userTypes;

import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.sql.Types;

import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.usertype.UserType;

public class SpecialList implements UserType {
private List specialList;

private static final char SPLITTER = ';';

private static final int[] TYPES = new int[] { Types.VARCHAR };

public String assemble(Serializable arg0, Object arg1)
throws HibernateException {
return null;
}

/*
* 将List封装为一个String对象
*/
public String assemble(List specialList) throws HibernateException {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < specialList.size() - 1; i++) {
sb.append(specialList.get(i)).append(this.SPLITTER);
}
sb.append(specialList.get(specialList.size() - 1));
return sb.toString();
}

/*
* 创建一个新的List实例,包含原有的List实例中的所有元素.
*/
public Object deepCopy(Object value) throws HibernateException {
List sourceList = (List) value;
List targetList = new ArrayList();
targetList.addAll(sourceList);
return targetList;
}

public Serializable disassemble(Object arg0) throws HibernateException {
return null;
}

/*
* 判断specialList是否发生变化
*/
public boolean equals(Object x, Object y) throws HibernateException {
if (x == y) {
return true;
}
if (x != null && y != null) {
List xList = (List) x;
List yList = (List) y;

if (xList.size() != yList.size()) {
return false;
}

for (int i = 0; i <= xList.size() - 1; i++) {
String str1 = (String) xList.get(i);
String str2 = (String) yList.get(i);
if (!xList.equals(yList)) {
return false;
}
}
return true;
}
return false;
}

public int hashCode(Object arg0) throws HibernateException {
return 0;
}

public boolean isMutable() {
return false;
}

/*
* 从resultset中取出email字段,并将其解析为List类型后返回
*/
public Object nullSafeGet(ResultSet rs, String[] names, Object owner)
throws HibernateException, SQLException {
String value = (String) Hibernate.STRING.nullSafeGet(rs, names[0]);
if (value != null) {
return parse(value);
} else {
return null;
}
}

/*
* 将以";"分隔的字符串解析为一个字符串数组
*/
private List parse(String value) {
String[] strs = value.split(";");
List specialList = new ArrayList();
for (int i = 0; i <= strs.length - 1; i++) {
specialList.add(strs[i]);
}
return specialList;
}

/*
* 将List型的email信息组装成字符串之后保存到email字段
*/
public void nullSafeSet(PreparedStatement st, Object value, int index)
throws HibernateException, SQLException {
if (value != null) {
String str = assemble((List) value);
Hibernate.STRING.nullSafeSet(st, str, index);
} else {
Hibernate.STRING.nullSafeSet(st, value, index);
}
}

public Object replace(Object arg0, Object arg1, Object arg2)
throws HibernateException {
return null;
}

public Class returnedClass() {
return List.class;
}

public int[] sqlTypes() {
return TYPES;
}

}
同时,修改相应的[ormapping_filename].hbm.xml中相应字段的映射信息
<property name="buddy" type="com.globalhands.hibernate.userTypes.SpecialList">
            <column name="buddy" length="2000" not-null="true" />
        </property>
之后,修改POJO类中该字段的返回类型为List。
使用JUnit测试程序。
posted @ 2009-05-24 19:57 lanxin1020 阅读(576) | 评论 (0)编辑 收藏
posted @ 2009-05-21 22:13 lanxin1020 阅读(157) | 评论 (0)编辑 收藏

什么是 ASM?

ASM 是一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class 文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。

与 BCEL 和 SERL 不同,ASM 提供了更为现代的编程模型。对于 ASM 来说,Java class 被描述为一棵树;使用 “Visitor” 模式遍历整个二进制结构;事件驱动的处理方式使得用户只需要关注于对其编程有意义的部分,而不必了解 Java 类文件格式的所有细节:ASM 框架提供了默认的 “response taker”处理这一切。

为什么要动态生成 Java 类?

动态生成 Java 类与 AOP 密切相关的。AOP 的初衷在于软件设计世界中存在这么一类代码,零散而又耦合:零散是由于一些公有的功能(诸如著名的 log 例子)分散在所有模块之中;同时改变 log 功能又会影响到所有的模块。出现这样的缺陷,很大程度上是由于传统的 面向对象编程注重以继承关系为代表的“纵向”关系,而对于拥有相同功能或者说方面 (Aspect)的模块之间的“横向”关系不能很好地表达。例如,目前有一个既有的银行管理系统,包括 Bank、Customer、Account、Invoice 等对象,现在要加入一个安全检查模块, 对已有类的所有操作之前都必须进行一次安全检查。


图 1. ASM – AOP
图 1. ASM – AOP

然而 Bank、Customer、Account、Invoice 是代表不同的事务,派生自不同的父类,很难在高层上加入关于 Security Checker 的共有功能。对于没有多继承的 Java 来说,更是如此。传统的解决方案是使用 Decorator 模式,它可以在一定程度上改善耦合,而功能仍旧是分散的 —— 每个需要 Security Checker 的类都必须要派生一个 Decorator,每个需要 Security Checker 的方法都要被包装(wrap)。下面我们以 Account 类为例看一下 Decorator:

首先,我们有一个 SecurityChecker 类,其静态方法 checkSecurity 执行安全检查功能:

public class SecurityChecker {   public static void checkSecurity() {    System.out.println("SecurityChecker.checkSecurity ...");    //TODO real security check   }   }       

另一个是 Account 类:

public class Account {   public void operation() {    System.out.println("operation...");    //TODO real operation   }  }       

若想对 operation 加入对 SecurityCheck.checkSecurity() 调用,标准的 Decorator 需要先定义一个 Account 类的接口:

public interface Account {   void operation();   }       

然后把原来的 Account 类定义为一个实现类:

public class AccountImpl extends Account{   public void operation() {    System.out.println("operation...");    //TODO real operation   }  }        

定义一个 Account 类的 Decorator,并包装 operation 方法:

public class AccountWithSecurityCheck implements Account {    private  Account account;   public AccountWithSecurityCheck (Account account) {    this.account = account;   }   public void operation() {    SecurityChecker.checkSecurity();    account.operation();   }  }       

在这个简单的例子里,改造一个类的一个方法还好,如果是变动整个模块,Decorator 很快就会演化成另一个噩梦。动态改变 Java 类就是要解决 AOP 的问题,提供一种得到系统支持的可编程的方法,自动化地生成或者增强 Java 代码。这种技术已经广泛应用于最新的 Java 框架内,如 Hibernate,Spring 等。

为什么选择 ASM?

最直接的改造 Java 类的方法莫过于直接改写 class 文件。Java 规范详细说明了class 文件的格式,直接编辑字节码确实可以改变 Java 类的行为。直到今天,还有一些 Java 高手们使用最原始的工具,如 UltraEdit 这样的编辑器对 class 文件动手术。是的,这是最直接的方法,但是要求使用者对 Java class 文件的格式了熟于心:小心地推算出想改造的函数相对文件首部的偏移量,同时重新计算 class 文件的校验码以通过 Java 虚拟机的安全机制。

Java 5 中提供的 Instrument 包也可以提供类似的功能:启动时往 Java 虚拟机中挂上一个用户定义的 hook 程序,可以在装入特定类的时候改变特定类的字节码,从而改变该类的行为。但是其缺点也是明显的:

  • Instrument 包是在整个虚拟机上挂了一个钩子程序,每次装入一个新类的时候,都必须执行一遍这段程序,即使这个类不需要改变。
  • 直接改变字节码事实上类似于直接改写 class 文件,无论是调用 ClassFileTransformer. transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer),还是 Instrument.redefineClasses(ClassDefinition[] definitions),都必须提供新 Java 类的字节码。也就是说,同直接改写 class 文件一样,使用 Instrument 也必须了解想改造的方法相对类首部的偏移量,才能在适当的位置上插入新的代码。

尽管 Instrument 可以改造类,但事实上,Instrument 更适用于监控和控制虚拟机的行为。

一种比较理想且流行的方法是使用 java.lang.ref.proxy。我们仍旧使用上面的例子,给 Account 类加上 checkSecurity 功能:

首先,Proxy 编程是面向接口的。下面我们会看到,Proxy 并不负责实例化对象,和 Decorator 模式一样,要把 Account 定义成一个接口,然后在 AccountImpl 里实现 Account 接口,接着实现一个 InvocationHandler Account 方法被调用的时候,虚拟机都会实际调用这个 InvocationHandlerinvoke 方法:

class SecurityProxyInvocationHandler implements InvocationHandler {   private Object proxyedObject;   public SecurityProxyInvocationHandler(Object o) {    proxyedObject = o;   }       public Object invoke(Object object, Method method, Object[] arguments)    throws Throwable {       if (object instanceof Account && method.getName().equals("opertaion")) {     SecurityChecker.checkSecurity();    }    return method.invoke(proxyedObject, arguments);   }  }    

最后,在应用程序中指定 InvocationHandler 生成代理对象:

public static void main(String[] args) {   Account account = (Account) Proxy.newProxyInstance(    Account.class.getClassLoader(),    new Class[] { Account.class },    new SecurityProxyInvocationHandler(new AccountImpl())   );   account.function();  }   

其不足之处在于:

  • Proxy 是面向接口的,所有使用 Proxy 的对象都必须定义一个接口,而且用这些对象的代码也必须是对接口编程的:Proxy 生成的对象是接口一致的而不是对象一致的:例子中 Proxy.newProxyInstance 生成的是实现 Account 接口的对象而不是 AccountImpl 的子类。这对于软件架构设计,尤其对于既有软件系统是有一定掣肘的。
  • Proxy 毕竟是通过反射实现的,必须在效率上付出代价:有实验数据表明,调用反射比一般的函数开销至少要大 10 倍。而且,从程序实现上可以看出,对 proxy class 的所有方法调用都要通过使用反射的 invoke 方法。因此,对于性能关键的应用,使用 proxy class 是需要精心考虑的,以避免反射成为整个应用的瓶颈。

ASM 能够通过改造既有类,直接生成需要的代码。增强的代码是硬编码在新生成的类文件内部的,没有反射带来性能上的付出。同时,ASM 与 Proxy 编程不同,不需要为增强代码而新定义一个接口,生成的代码可以覆盖原来的类,或者是原始类的子类。它是一个普通的 Java 类而不是 proxy 类,甚至可以在应用程序的类框架中拥有自己的位置,派生自己的子类。

相比于其他流行的 Java 字节码操纵工具,ASM 更小更快。ASM 具有类似于 BCEL 或者 SERP 的功能,而只有 33k 大小,而后者分别有 350k 和 150k。同时,同样类转换的负载,如果 ASM 是 60% 的话,BCEL 需要 700%,而 SERP 需要 1100% 或者更多。

ASM 已经被广泛应用于一系列 Java 项目:AspectWerkz、AspectJ、BEA WebLogic、IBM AUS、OracleBerkleyDB、Oracle TopLink、Terracotta、RIFE、EclipseME、Proactive、Speedo、Fractal、EasyBeans、BeanShell、Groovy、Jamaica、CGLIB、dynaop、Cobertura、JDBCPersistence、JiP、SonarJ、Substance L&F、Retrotranslator 等。Hibernate 和 Spring 也通过 cglib,另一个更高层一些的自动代码生成工具使用了 ASM。





回页首


Java 类文件概述

所谓 Java 类文件,就是通常用 javac 编译器产生的 .class 文件。这些文件具有严格定义的格式。为了更好的理解 ASM,首先对 Java 类文件格式作一点简单的介绍。Java 源文件经过 javac 编译器编译之后,将会生成对应的二进制文件(如下图所示)。每个合法的 Java 类文件都具备精确的定义,而正是这种精确的定义,才使得 Java 虚拟机得以正确读取和解释所有的 Java 类文件。


图 2. ASM – Javac 流程
图 2. ASM – Javac 流程

Java 类文件是 8 位字节的二进制流。数据项按顺序存储在 class 文件中,相邻的项之间没有间隔,这使得 class 文件变得紧凑,减少存储空间。在 Java 类文件中包含了许多大小不同的项,由于每一项的结构都有严格规定,这使得 class 文件能够从头到尾被顺利地解析。下面让我们来看一下 Java 类文件的内部结构,以便对此有个大致的认识。

例如,一个最简单的 Hello World 程序:

public class HelloWorld {   public static void main(String[] args) {    System.out.println("Hello world");   }  }  

经过 javac 编译后,得到的类文件大致是:


图 3. ASM – Java 类文件
图 3. ASM – Java 类文件

从上图中可以看到,一个 Java 类文件大致可以归为 10 个项:

  • Magic:该项存放了一个 Java 类文件的魔数(magic number)和版本信息。一个 Java 类文件的前 4 个字节被称为它的魔数。每个正确的 Java 类文件都是以 0xCAFEBABE 开头的,这样保证了 Java 虚拟机能很轻松的分辨出 Java 文件和非 Java 文件。
  • Version:该项存放了 Java 类文件的版本信息,它对于一个 Java 文件具有重要的意义。因为 Java 技术一直在发展,所以类文件的格式也处在不断变化之中。类文件的版本信息让虚拟机知道如何去读取并处理该类文件。
  • Constant Pool:该项存放了类中各种文字字符串、类名、方法名和接口名称、final 变量以及对外部类的引用信息等常量。虚拟机必须为每一个被装载的类维护一个常量池,常量池中存储了相应类型所用到的所有类型、字段和方法的符号引用,因此它在 Java 的动态链接中起到了核心的作用。常量池的大小平均占到了整个类大小的 60% 左右。
  • Access_flag:该项指明了该文件中定义的是类还是接口(一个 class 文件中只能有一个类或接口),同时还指名了类或接口的访问标志,如 public,private, abstract 等信息。
  • This Class:指向表示该类全限定名称的字符串常量的指针。
  • Super Class:指向表示父类全限定名称的字符串常量的指针。
  • Interfaces:一个指针数组,存放了该类或父类实现的所有接口名称的字符串常量的指针。以上三项所指向的常量,特别是前两项,在我们用 ASM 从已有类派生新类时一般需要修改:将类名称改为子类名称;将父类改为派生前的类名称;如果有必要,增加新的实现接口。
  • Fields:该项对类或接口中声明的字段进行了细致的描述。需要注意的是,fields 列表中仅列出了本类或接口中的字段,并不包括从超类和父接口继承而来的字段。
  • Methods:该项对类或接口中声明的方法进行了细致的描述。例如方法的名称、参数和返回值类型等。需要注意的是,methods 列表里仅存放了本类或本接口中的方法,并不包括从超类和父接口继承而来的方法。使用 ASM 进行 AOP 编程,通常是通过调整 Method 中的指令来实现的。
  • Class attributes:该项存放了在该文件中类或接口所定义的属性的基本信息。

事实上,使用 ASM 动态生成类,不需要像早年的 class hacker 一样,熟知 class 文件的每一段,以及它们的功能、长度、偏移量以及编码方式。ASM 会给我们照顾好这一切的,我们只要告诉 ASM 要改动什么就可以了 —— 当然,我们首先得知道要改什么:对类文件格式了解的越多,我们就能更好地使用 ASM 这个利器。





回页首


ASM 3.0 编程框架

ASM 通过树这种数据结构来表示复杂的字节码结构,并利用 Push 模型来对树进行遍历,在遍历过程中对字节码进行修改。所谓的 Push 模型类似于简单的 Visitor 设计模式,因为需要处理字节码结构是固定的,所以不需要专门抽象出一种 Vistable 接口,而只需要提供 Visitor 接口。所谓 Visitor 模式和 Iterator 模式有点类似,它们都被用来遍历一些复杂的数据结构。Visitor 相当于用户派出的代表,深入到算法内部,由算法安排访问行程。Visitor 代表可以更换,但对算法流程无法干涉,因此是被动的,这也是它和 Iterator 模式由用户主动调遣算法方式的最大的区别。

在 ASM 中,提供了一个 ClassReader 类,这个类可以直接由字节数组或由 class 文件间接的获得字节码数据,它能正确的分析字节码,构建出抽象的树在内存中表示字节码。它会调用 accept 方法,这个方法接受一个实现了 ClassVisitor 接口的对象实例作为参数,然后依次调用 ClassVisitor 接口的各个方法。字节码空间上的偏移被转换成 visit 事件时间上调用的先后,所谓 visit 事件是指对各种不同 visit 函数的调用,ClassReader 知道如何调用各种 visit 函数。在这个过程中用户无法对操作进行干涉,所以遍历的算法是确定的,用户可以做的是提供不同的 Visitor 来对字节码树进行不同的修改。ClassVisitor 会产生一些子过程,比如 visitMethod 会返回一个实现 MethordVisitor 接口的实例,visitField 会返回一个实现 FieldVisitor 接口的实例,完成子过程后控制返回到父过程,继续访问下一节点。因此对于 ClassReader 来说,其内部顺序访问是有一定要求的。实际上用户还可以不通过 ClassReader 类,自行手工控制这个流程,只要按照一定的顺序,各个 visit 事件被先后正确的调用,最后就能生成可以被正确加载的字节码。当然获得更大灵活性的同时也加大了调整字节码的复杂度。

各个 ClassVisitor 通过职责链 (Chain-of-responsibility) 模式,可以非常简单的封装对字节码的各种修改,而无须关注字节码的字节偏移,因为这些实现细节对于用户都被隐藏了,用户要做的只是覆写相应的 visit 函数。

ClassAdaptor 类实现了 ClassVisitor 接口所定义的所有函数,当新建一个 ClassAdaptor 对象的时候,需要传入一个实现了 ClassVisitor 接口的对象,作为职责链中的下一个访问者 (Visitor),这些函数的默认实现就是简单的把调用委派给这个对象,然后依次传递下去形成职责链。当用户需要对字节码进行调整时,只需从 ClassAdaptor 类派生出一个子类,覆写需要修改的方法,完成相应功能后再把调用传递下去。这样,用户无需考虑字节偏移,就可以很方便的控制字节码。

每个 ClassAdaptor 类的派生类可以仅封装单一功能,比如删除某函数、修改字段可见性等等,然后再加入到职责链中,这样耦合更小,重用的概率也更大,但代价是产生很多小对象,而且职责链的层次太长的话也会加大系统调用的开销,用户需要在低耦合和高效率之间作出权衡。用户可以通过控制职责链中 visit 事件的过程,对类文件进行如下操作:

  1. 删除类的字段、方法、指令:只需在职责链传递过程中中断委派,不访问相应的 visit 方法即可,比如删除方法时只需直接返回 null,而不是返回由 visitMethod 方法返回的 MethodVisitor 对象。

    class DelLoginClassAdapter extends ClassAdapter {   public DelLoginClassAdapter(ClassVisitor cv) {    super(cv);   }     public MethodVisitor visitMethod(final int access, final String name,    final String desc, final String signature, final String[] exceptions) {    if (name.equals("login")) {     return null;    }    return cv.visitMethod(access, name, desc, signature, exceptions);   }  }           

  2. 修改类、字段、方法的名字或修饰符:在职责链传递过程中替换调用参数。

    class AccessClassAdapter extends ClassAdapter {   public AccessClassAdapter(ClassVisitor cv) {    super(cv);   }     public FieldVisitor visitField(final int access, final String name,          final String desc, final String signature, final Object value) {          int privateAccess = Opcodes.ACC_PRIVATE;          return cv.visitField(privateAccess, name, desc, signature, value);      }  }           

  3. 增加新的类、方法、字段

ASM 的最终的目的是生成可以被正常装载的 class 文件,因此其框架结构为客户提供了一个生成字节码的工具类 —— ClassWriter。它实现了 ClassVisitor 接口,而且含有一个 toByteArray() 函数,返回生成的字节码的字节流,将字节流写回文件即可生产调整后的 class 文件。一般它都作为职责链的终点,把所有 visit 事件的先后调用(时间上的先后),最终转换成字节码的位置的调整(空间上的前后),如下例:

ClassWriter  classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);  ClassAdaptor delLoginClassAdaptor = new DelLoginClassAdapter(classWriter);  ClassAdaptor accessClassAdaptor = new AccessClassAdaptor(delLoginClassAdaptor);     ClassReader classReader = new ClassReader(strFileName);  classReader.accept(classAdapter, ClassReader.SKIP_DEBUG);           

综上所述,ASM 的时序图如下:


图 4. ASM – 时序图
图 4. ASM – 时序图




回页首


使用 ASM3.0 进行 AOP 编程

我们还是用上面的例子,给 Account 类加上 security check 的功能。与 proxy 编程不同,ASM 不需要将 Account 声明成接口,Account 可以仍旧是一个实现类。ASM 将直接在 Account 类上动手术,给 Account 类的 operation 方法首部加上对 SecurityChecker.checkSecurity 的调用。

首先,我们将从 ClassAdapter 继承一个类。ClassAdapter 是 ASM 框架提供的一个默认类,负责沟通 ClassReaderClassWriter。如果想要改变 ClassReader 处读入的类,然后从 ClassWriter 处输出,可以重写相应的 ClassAdapter 函数。这里,为了改变 Account 类的 operation 方法,我们将重写 visitMethdod 方法。

class AddSecurityCheckClassAdapter extends ClassAdapter{     public AddSecurityCheckClassAdapter(ClassVisitor cv) {    //Responsechain 的下一个 ClassVisitor,这里我们将传入 ClassWriter,    //负责改写后代码的输出    super(cv);   }      //重写 visitMethod,访问到 "operation" 方法时,   //给出自定义 MethodVisitor,实际改写方法内容   public MethodVisitor visitMethod(final int access, final String name,    final String desc, final String signature, final String[] exceptions) {    MethodVisitor mv = cv.visitMethod(access, name, desc, signature,exceptions);    MethodVisitor wrappedMv = mv;    if (mv != null) {     //对于 "operation" 方法     if (name.equals("operation")) {       //使用自定义 MethodVisitor,实际改写方法内容      wrappedMv = new AddSecurityCheckMethodAdapter(mv);      }     }    return wrappedMv;   }  }     

下一步就是定义一个继承自 MethodAdapterAddSecurityCheckMethodAdapter,在“operation”方法首部插入对 SecurityChecker.checkSecurity() 的调用。

class AddSecurityCheckMethodAdapter extends MethodAdapter {   public AddSecurityCheckMethodAdapter(MethodVisitor mv) {    super(mv);   }     public void visitCode() {    visitMethodInsn(Opcodes.INVOKESTATIC, "SecurityChecker",     "checkSecurity", "()V");   }  }      

其中,ClassReader 读到每个方法的首部时调用 visitCode(),在这个重写方法里,我们用visitMethodInsn(Opcodes.INVOKESTATIC, "SecurityChecker","checkSecurity", "()V"); 插入了安全检查功能。

最后,我们将集成上面定义的 ClassAdapterClassReaderClassWriter 产生修改后的 Account 类文件:

import java.io.File;  import java.io.FileOutputStream;  import org.objectweb.asm.*;        public class Generator{   public static void main() throws Exception {    ClassReader cr = new ClassReader("Account");    ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);    ClassAdapter classAdapter = new AddSecurityCheckClassAdapter(cw);    cr.accept(classAdapter, ClassReader.SKIP_DEBUG);    byte[] data = cw.toByteArray();    File file = new File("Account.class");    FileOutputStream fout = new FileOutputStream(file);    fout.write(data);    fout.close();   }  }   

执行完这段程序后,我们会得到一个新的 Account.class 文件,如果我们使用下面代码:

public class Main {   public static void main(String[] args) {    Account account = new Account();    account.operation();   }  }   

使用这个 Account,我们会得到下面的输出:

SecurityChecker.checkSecurity ...  operation...   

也就是说,在 Account 原来的 operation 内容执行之前,进行了 SecurityChecker.checkSecurity() 检查。

将动态生成类改造成原始类 Account 的子类

上面给出的例子是直接改造 Account 类本身的,从此 Account 类的 operation 方法必须进行 checkSecurity 检查。但事实上,我们有时仍希望保留原来的 Account 类,因此把生成类定义为原始类的子类是更符合 AOP 原则的做法。下面介绍如何将改造后的类定义为 Account 的子类 Account$EnhancedByASM。其中主要有两项工作:

  • 改变 Class Description, 将其命名为 Account$EnhancedByASM,将其父类指定为 Account
  • 改变构造函数,将其中对父类构造函数的调用转换为对 Account 构造函数的调用。

AddSecurityCheckClassAdapter 类中,将重写 visit 方法:

public void visit(final int version, final int access, final String name,    final String signature, final String superName,    final String[] interfaces) {   String enhancedName = name + "$EnhancedByASM";  //改变类命名   enhancedSuperName = name; //改变父类,这里是”Account”   super.visit(version, access, enhancedName, signature,   enhancedSuperName, interfaces);  }   

改进 visitMethod 方法,增加对构造函数的处理:

public MethodVisitor visitMethod(final int access, final String name,   final String desc, final String signature, final String[] exceptions) {   MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);   MethodVisitor wrappedMv = mv;   if (mv != null) {    if (name.equals("operation")) {     wrappedMv = new AddSecurityCheckMethodAdapter(mv);    } else if (name.equals("<init>")) {     wrappedMv = new ChangeToChildConstructorMethodAdapter(mv,      enhancedSuperName);    }   }   return wrappedMv;  }   

这里 ChangeToChildConstructorMethodAdapter 将负责把 Account 的构造函数改造成其子类 Account$EnhancedByASM 的构造函数:

class ChangeToChildConstructorMethodAdapter extends MethodAdapter {   private String superClassName;     public ChangeToChildConstructorMethodAdapter(MethodVisitor mv,    String superClassName) {    super(mv);    this.superClassName = superClassName;   }     public void visitMethodInsn(int opcode, String owner, String name,    String desc) {    //调用父类的构造函数时    if (opcode == Opcodes.INVOKESPECIAL && name.equals("<init>")) {      owner = superClassName;    }    super.visitMethodInsn(opcode, owner, name, desc);//改写父类为superClassName   }  }  

最后演示一下如何在运行时产生并装入产生的 Account$EnhancedByASM。 我们定义一个 Util 类,作为一个类工厂负责产生有安全检查的 Account 类:

public class SecureAccountGenerator {     private static AccountGeneratorClassLoader classLoader =     new AccountGeneratorClassLoade();   private static Class secureAccountClass;     public Account generateSecureAccount() throws ClassFormatError,     InstantiationException, IllegalAccessException {    if (null == secureAccountClass) {                 ClassReader cr = new ClassReader("Account");     ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);     ClassAdapter classAdapter = new AddSecurityCheckClassAdapter(cw);     cr.accept(classAdapter, ClassReader.SKIP_DEBUG);     byte[] data = cw.toByteArray();     secureAccountClass = classLoader.defineClassFromClassFile(      "Account$EnhancedByASM",data);    }    return (Account) secureAccountClass.newInstance();   }     private static class AccountGeneratorClassLoader extends ClassLoader {    public Class defineClassFromClassFile(String className,     byte[] classFile) throws ClassFormatError {     return defineClass("Account$EnhancedByASM", classFile, 0, classFile.length());    }   }  }  

静态方法 SecureAccountGenerator.generateSecureAccount() 在运行时动态生成一个加上了安全检查的 Account 子类。著名的 Hibernate 和 Spring 框架,就是使用这种技术实现了 AOP 的“无损注入”。





回页首


小结

最后,我们比较一下 ASM 和其他实现 AOP 的底层技术:

posted @ 2009-05-12 12:43 lanxin1020 阅读(243) | 评论 (0)编辑 收藏

以下是一份完整的struts-config.xml文件,配置元素的说明详见注释.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
"http://jakarta.apache.org/struts/dtds/struts-config.dtd">
<!-- struts-config.xml中的元素必须按照上述doc指令中的dtd文档定义顺序书写,本例即遵从了dtd定义顺序 -->
<!-- struts-config是整个xml的根元素,其他元素必须被包含其内 -->
<struts-config>
<!--
   名称:data-sources
   描述:data-sources元素定义了web App所需要使用的数据源
   数量:最多一个
   子元素:data-source
-->
<data-sources>
   <!--
    名称:data-source
    描述:data-source元素定义了具体的数据源
    数量:任意多个
    属性:
     @key:当需要配置多个数据源时,相当于数据源的名称,用来数据源彼此间进行区别
     @type:可以使用的数据源实现的类,一般来自如下四个库
      Poolman,开放源代码软件
      Expresso,Jcorporate
      JDBC Pool,开放源代码软件
      DBCP,Jakarta
   -->
   <data-source key="firstOne" type="org.apache.commons.dbcp.BasicDataSource">
    <!--
     名称:set-property
     描述:用来设定数据源的属性
     属性:
      @autoCommit:是否自动提交 可选值:true/false
      @description:数据源描述
      @driverClass:数据源使用的类
      @maxCount:最大数据源连接数
      @minCount:最小数据源连接数
      @user:数据库用户
      @password:数据库密码
      @url:数据库url
    -->
    <set-property property="autoCommit" value="true"/>
    <set-property property="description" value="Hello!"/>
    <set-property property="driverClass" value="com.mysql.jdbc.Driver"/>
    <set-property property="maxCount" value="10"/>
    <set-property property="minCount" value="2"/>
    <set-property property="user" value="root"/>
    <set-property property="password" value=""/>
    <set-property property="url" value="jdbc:mysql://localhost:3306/helloAdmin"/>
   </data-source>
</data-sources>

<!--
   名称:form-beans
   描述:用来配置多个ActionForm Bean
   数量:最多一个
   子元素:form-bean
-->
<form-beans>
   <!--
    名称:form-bean
    描述:用来配置ActionForm Bean
    数量:任意多个
    子元素:form-property
    属性:
     @className:指定与form-bean元素相对应的配置类,一般默认使用org.apaceh.struts.config.FormBeanConfig,如果自定义,则必须继承 FormBeanConfig
     @name:必备属性!为当前form-bean制定一个全局唯一的标识符,使得在整个Struts框架内,可以通过该标识符来引用这个ActionForm Bean。
     @type:必备属性!指明实现当前ActionForm Bean的完整类名。
   -->
   <form-bean name="Hello" type="myPack.Hello">
    <!--
     名称:form-property
     描述:用来设定ActionForm Bean的属性
     数量:根据实际需求而定,例如,ActionForm Bean对应的一个登陆Form中有两个文本框,name和password,ActionForm Bean中也有这两个字段,则此处编写两个form-property来设定属性
     属性:
      @className:指定与form-property相对应的配置类,默认是org.apache.struts.config.FormPropertyConfig,如果自定义,则必须继承FormPropertyConfig类
      @name:所要设定的ActionForm Bean的属性名称
      @type:所要设定的ActionForm Bean的属性值的类
      @initial:当前属性的初值
    -->
    <form-property name="name" type="java.lang.String"/>
    <form-property name="number" type="java.lang.Iteger" initial="18"/>
   </form-bean>
</form-beans>

<!--
   名称:global-exceptions
   描述:处理异常
   数量:最多一个
   子元素:exception
-->
<global-exceptions>
   <!--
    名称:exception
    描述:具体定义一个异常及其处理
    数量:任意多个
    属性:
     @className:指定对应exception的配置类,默认为org.apache.struts.config.ExceptionConfig
     @handler:指定异常处理类,默认为org.apache.struts.action.ExceptionHandler
     @key:指定在Resource Bundle种描述该异常的消息key
     @path:指定当发生异常时,进行转发的路径
     @scope:指定ActionMessage实例存放的范围,默认为request,另外一个可选值是session
     @type:必须要有!指定所需要处理异常类的名字。
     @bundle:指定资源绑定
   -->
   <exception
    key=""hello.error
    path="/error.jsp"
    scope="session"
    type="hello.HandleError"/>
</global-exceptions>

<!--
   名称:global-forwards
   描述:定义全局转发
   数量:最多一个
   子元素:forward
-->
<global-forwards>
   <!--
    名称:forward
    描述:定义一个具体的转发
    数量:任意多个
    属性:
     @className:指定和forward元素对应的配置类,默认为org.apache.struts.action.ActionForward
     @contextRelative:如果为true,则指明使用当前上下文,路径以“/”开头,默认为false
     @name:必须配有!指明转发路径的唯一标识符
     @path:必须配有!指明转发或者重定向的URI。必须以"/"开头。具体配置要与contextRelative相应。
     @redirect:为true时,执行重定向操作,否则执行请求转发。默认为false
   -->
   <forward name="A" path="/a.jsp"/>
   <forward name="B" path="/hello/b.do"/>
</global-forwards>

<!--
   名称:action-mappings
   描述:定义action集合
   数量:最多一个
   子元素:action
-->
<action-mappings>
   <!--
    名称:action
    描述:定义了从特定的请求路径到相应的Action类的映射
    数量:任意多个
    子元素:exception,forward(二者均为局部量)
    属性:
     @attribute:制定与当前Action相关联的ActionForm Bean在request和session范围内的名称(key)
     @className:与Action元素对应的配置类。默认为org.apache.struts.action.ActionMapping
     @forward:指名转发的URL路径
     @include:指名包含的URL路径
     @input:指名包含输入表单的URL路径,表单验证失败时,请求会被转发到该URL中
     @name:指定和当前Acion关联的ActionForm Bean的名字。该名称必须在form-bean元素中定义过。
     @path:指定访问Action的路径,以"/"开头,没有扩展名
     @parameter:为当前的Action配置参数,可以在Action的execute()方法中,通过调用ActionMapping的getParameter()方法来获取参数
     @roles:指定允许调用该Aciton的安全角色。多个角色之间用逗号分割。处理请求时,RequestProcessor会根据该配置项来决定用户是否有调用该Action的权限
     @scope:指定ActionForm Bean的存在范围,可选值为request和session。默认为session
     @type:指定Action类的完整类名
     @unknown:值为true时,表示可以处理用户发出的所有无效的Action URL。默认为false
     @validate:指定是否要先调用ActionForm Bean的validate()方法。默认为true
    注意:如上属性中,forward/include/type三者相斥,即三者在同一Action配置中只能存在一个。
   -->
   <action path="/search"
    type="addressbook.actions.SearchAction"
    name="searchForm"
    scope="request"
    validate="true"
    input="/search.jsp">
    <forward name="success" path="/display.jsp"/>
   </action>  
</action-mappings>

<!--
   名称:controller
   描述:用于配置ActionServlet
   数量:最多一个
   属性:
    @bufferSize:指定上传文件的输入缓冲的大小.默认为4096
    @className:指定当前控制器的配置类.默认为org.apache.struts.config.ControllerConfig
    @contentType:指定相应结果的内容类型和字符编码
    @locale:指定是否把Locale对象保存到当前用户的session中,默认为false
    @processorClass:指定负责处理请求的Java类的完整类名.默认org.apache.struts.action.RequestProcessor
    @tempDir:指定文件上传时的临时工作目录.如果没有设置,将才用Servlet容器为web应用分配的临时工作目录.
    @nochache:true时,在相应结果中加入特定的头参数:Pragma ,Cache-Control,Expires防止页面被存储在可数浏览器的缓存中,默认为false
-->
<controller
   contentType="text/html;charset=UTF-8"
   locale="true"
   processorClass="CustomRequestProcessor">
</controller>
<!--
   名称:message-resources
   描述:配置Resource Bundle.
   数量:任意多个
   属性:
    @className:指定和message-resources对应的配置类.默认为org.apache.struts.config.MessageResourcesConfig
    @factory:指定资源的工厂类,默认为org.apache.struts.util.PropertyMessageResourcesFactory
    @key:
    @null:
    @parameter:
-->
<message-resources
   null="false"
   parameter="defaultResource"/>
<message-resources
   key="images"
   null="false"
   parameter="ImageResources"/>
  
<!--
   名称:plug-in
   描述:用于配置Struts的插件
   数量:任意多个
   子元素:set-property
   属性:
    @className:指定Struts插件类.此类必须实现org.apache.struts.action.PlugIn接口
-->
<plug-in
   className="org.apache.struts.validator.ValidatorPlugIn">
   <!--
    名称:set-property
    描述:配置插件的属性
    数量:任意多个
    属性:
     @property:插件的属性名称
     @value:该名称所配置的值
   -->
   <set-property
    property="pathnames"
    value="/WEB-INF/validator-rules.xml,/WEB-INF/vlaidation.xml"/>
</plug-in>

</struts-config>

posted @ 2009-05-11 16:26 lanxin1020 阅读(268) | 评论 (0)编辑 收藏
 过滤器是一个程序,它先于与之相关的servlet或JSP页面运行在服务器上。过滤器可附加到一个或多个servlet或JSP页面上,并且可以检查进入这些资源的请求信息。在这之后,过滤器可以作如下的选择:

1. 以常规的方式调用资源(即,调用servlet或JSP页面)。个人理解为请求通过过滤执行其他的操作
2.利用修改过的请求信息调用资源。对请求的信息加以修改,然后继续执行
3. 调用资源,但在发送响应到客户机前对其进行修改
4. 阻止该资源调用,代之以转到其他的资源,返回一个特定的状态代码或生成替换输出。个人理解为请求被拦截时强制执行(跳转)的操作

过滤器提供了几个重要好处 :
        首先,它以一种模块化的或可重用的方式封装公共的行为。你有30个不同的serlvet或JSP页面,需要压缩它们的内容以减少下载时间吗?没问题:构造一个压缩过滤器,然后将它应用到30个资源上即可。

       其次,利用它能够将高级访问决策与表现代码相分离。这对于JSP特别有价值,其中一般希望将几乎整个页面集中在表现上,而不是集中在业务逻辑上。例如,希 望阻塞来自某些站点的访问而不用修改各页面(这些页面受到访问限制)吗?没问题:建立一个访问限制过滤器并把它应用到想要限制访问的页面上即可。

       最后,过滤器使你能够对许多不同的资源进行批量性的更改。你有许多现存资源,这些资源除了公司名要更改外其他的保持不变,能办到么?没问题:构造一个串替换过滤器,只要合适就使用它。

      但要注意,过滤器只在与servlet规范2.3版兼容的服务器上有作用。如果你的Web应用需要支持旧版服务器,就不能使用过滤器。

1.   建立基本过滤器

建立一个过滤器涉及下列五个步骤:
1)建立一个实现Filter接口的类。这个类需要三个方法,分别是:doFilter、init和destroy
       doFilter方法包含主要的过滤代码(见第2步),init方法建立设置操作,而destroy方法进行清楚。

2)在doFilter方法中放入过滤行为。doFilter方法的第一个参数为ServletRequest对象。此对象给过滤器提供了对进入的信息 (包括表单数据、cookie和HTTP请求头)的完全访问。第二个参数为ServletResponse,通常在简单的过滤器中忽略此参数。最后一个参 数为FilterChain,如下一步所述,此参数用来调用servlet或JSP页。

3)调用FilterChain对象的doFilter方法。Filter接口的doFilter方法取一个FilterChain对象作为它的一个参 数。在调用此对象的doFilter方法时,激活下一个相关的过滤器。如果没有另一个过滤器与servlet或JSP页面关联,则servlet或JSP 页面被激活。

4)对相应的servlet和JSP页面注册过滤器。在部署描述符文件(web.xml)中使用filter和filter-mapping元素。

5)禁用激活器servlet。防止用户利用缺省servlet URL绕过过滤器设置。

1.1   建立一个实现Filter接口的类
      所有过滤器都必须实现javax.servlet.Filter。这个接口包含三个方法,分别为doFilter、init和destroy。

public void doFilter(ServletRequset request,
                     ServletResponse response,
                     FilterChain chain)
     thows ServletException, IOException

每当调用一个过滤器(即,每次请求与此过滤器相关的servlet或JSP页面)时,就执行其doFilter方法。正是这个方法包含了大部分过滤逻辑。 第一个参数为与传入请求有关的ServletRequest。对于简单的过滤器,大多数过滤逻辑是基于这个对象的。如果处理HTTP请求,并且需要访问诸 如getHeader或getCookies等在ServletRequest中无法得到的方法,就要把此对象构造成 HttpServletRequest。

第二个参数为ServletResponse。除了在两个情形下要使用它以外,通常忽略这个参数。首先,如果希望完全阻塞对相关servlet或JSP页 面的访问。可调用response.getWriter并直接发送一个响应到客户机。其次,如果希望修改相关的servlet或JSP页面的输出,可把响 应包含在一个收集所有发送到它的输出的对象中。然后,在调用serlvet或JSP页面后,过滤器可检查输出,如果合适就修改它,之后发送到客户机。

DoFilter的最后一个参数为FilterChain对象。对此对象调用doFilter以激活与servlet或JSP页面相关的下一个过滤器。如果没有另一个相关的过滤器,则对doFilter的调用激活servlet或JSP本身。

public void init(FilterConfig config)   thows ServletException

init方法只在此过滤器第一次初始化时执行,不是每次调用过滤器都执行它。对于简单的过滤器,可提供此方法的一个空体,但有两个原因需要使用init。 首先,FilterConfig对象提供对servlet环境及web.xml文件中指派的过滤器名的访问。因此,普遍的办法是利用init将 FilterConfig对象存放在一个字段中,以便doFilter方法能够访问servlet环境或过滤器名.其次,FilterConfig对象具 有一个getInitParameter方法,它能够访问部署描述符文件(web.xml)中分配的过滤器初始化参数。

public void destroy( )
     大多数过滤器简单地为此方法提供一个空体,不过,可利用它来完成诸如关闭过滤器使用的文件或数据库连接池等清除任务。

1.2   将过滤行为放入doFilter方法
     doFilter方法为大多数过滤器地关键部分。每当调用一个过滤器时,都要执行doFilter。对于大多数过滤器来说,doFilter执行的步骤是 基于传入的信息的。因此,可能要利用作为doFilter的第一个参数提供的ServletRequest。这个对象常常构造为 HttpServletRequest类型,以提供对该类的更特殊方法的访问。

1.3   调用FilterChain对象的doFilter方法
     Filter接口的doFilter方法以一个FilterChain对象作为它的第三个参数。在调用该对象的doFilter方法时,激活下一个相关的 过滤器。这个过程一般持续到链中最后一个过滤器为止。在最后一个过滤器调用其FilterChain对象的doFilter方法时,激活servlet或 页面自身。
但是,链中的任意过滤器都可以通过不调用其FilterChain的doFilter方法中断这个过程。在这样的情况下,不再调用JSP页面的serlvet,并且中断此调用过程的过滤器负责将输出提供给客户机。

1.4   对适当的servlet和JSP页面注册过滤器
     部署描述符文件的2.3版本引入了两个用于过滤器的元素,分别是:filter和filter-mapping。filter元素向系统注册一个过滤对象,filter-mapping元素指定该过滤对象所应用的URL。

1.filter元素
filter元素位于部署描述符文件(web.xml)的前部,所有filter-mapping、servlet或servlet-mapping元素之前。filter元素具有如下六个可能的子元素:

1、 icon   这是一个可选的元素,它声明IDE能够使用的一个图象文件。
2、filter-name   这是一个必需的元素,它给过滤器分配一个选定的名字。
3、display-name   这是一个可选的元素,它给出IDE使用的短名称。
4、 description   这也是一个可选的元素,它给出IDE的信息,提供文本文档。
5、 filter-class   这是一个必需的元素,它指定过滤器实现类的完全限定名。
6、 init-param   这是一个可选的元素,它定义可利用FilterConfig的getInitParameter方法读取的初始化参数。单个过滤器元素可包含多个init-param元素。

请注意,过滤是在serlvet规范2.3版中初次引入的。因此,web.xml文件必须使用DTD的2.3版本。下面介绍一个简单的例子:

<xml version="1.0" encoding="ISO-8859-1"?>
    DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
    <web-app>
     <filter>
        <filter-name>MyFilterfilter-name>
        <filter-class>myPackage.FilterClassfilter-class>
      filter>
     
      <filter-mapping>...filter-mapping>
     <web-app>


2.filter-mapping元素
    filter-mapping元素位于web.xml文件中filter元素之后serlvet元素之前。它包含如下三个可能的子元素:

1、 filter-name   这个必需的元素必须与用filter元素声明时给予过滤器的名称相匹配。

2、 url-pattern   此元素声明一个以斜杠(/)开始的模式,它指定过滤器应用的URL。所有filter-mapping元素中必须提供url-pattern或 servlet-name。但不能对单个filter-mapping元素提供多个url-pattern元素项。如果希望过滤器适用于多个模式,可重复 整个filter-mapping元素。

3、 servlet-name   此元素给出一个名称,此名称必须与利用servlet元素给予servlet或JSP页面的名称相匹配。不能给单个filter-mapping元素提供 多个servlet-name元素项。如果希望过滤器适合于多个servlet名,可重复这个filter-mapping元素。
下面举一个例子:

xml version="1.0" encoding="ISO-8859-1"?>
    DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd">
    <web-app>
      <filter>
        <filter-name>MyFilterfilter-name>
        <filter-class>myPackage.FilterClassfilter-class>
      filter>
     
      <filter-mapping>
        <filter-name>MyFilterfilter-name>
        <url-pattern>/someDirectory/SomePage.jspurl-pattern>
      filter-mapping>
    web-app>

 

1.5   禁用激活器servlet
     在对资源应用过滤器时,可通过指定要应用过滤器的URL模式或servlet名来完成。如果提供servlet名,则此名称必须与web.xml的 servlet元素中给出的名称相匹配。如果使用应用到一个serlvet的URL模式,则此模式必须与利用web.xml的元素servlet- mapping指定的模式相匹配。但是,多数服务器使用“激活器servlet”为servlet体统一个缺省的URL:http: //host/WebAppPrefix/servlet/ServletName。需要保证用户不利用这个URL访问servlet(这样会绕过过滤器 设置)。
例如,假如利用filter和filter-mapping指示名为SomeFilter的过滤器应用到名为SomeServlet的servlet,则如下:

<filter>
      <filter-name>SomeFilterfilter-name>
      <filter-class>somePackage.SomeFilterClassfilter-class>
   <filter>
   
    <filter-mapping>
      <filter-name>SomeFilterfilter-name>
      <servlet-name>SomeServletservlet-name>
     <filter-mapping>

 


接着,用servlet和servlet-mapping规定URL   http://host/webAppPrefix/Blah 应该调用SomeSerlvet,如下所示:

<filter>
      <filter-name>SomeFilterfilter-name>
      <filter-class>somePackage.SomeFilterClassfilter-class>
    filter>
   
    <filter-mapping>
      <filter-name>SomeFilterfilter-name>
      <servlet-name>/Blahservlet-name>
     <filter-mapping>

 


现在,在客户机使用URL   http://host/webAppPrefix/Blah 时就会调用过滤器。过滤器不应用到
http://host/webAppPrefix/servlet/SomePackage.SomeServletClass
尽管有关闭激活器的服务器专用方法。但是,可移植最强的方法时重新映射Web应用钟的/servlet模式,这样使所有包含此模式的请求被送到相同的 servlet中。为了重新映射此模式,首先应该建立一个简单的servlet,它打印一条错误消息,或重定向用户到顶层页。然后,使用servlet和 servlet-mapping元素发送包含/servlet模式的请求到该servlet。程序清单9-1给出了一个简短的例子。

程序清单9-1 web.xml(重定向缺省servlet URL的摘录)

xml version="1.0" encoding="ISO-8859-1"?>
    DOCTYPE web-app PUBLIC
         "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
         "http://java.sun.com/dtd/web-app_2_3.dtd">
    <web-app>
   
    <servlet>
      <servlet-name>Errorservlet-name>
      <servlet-class>somePackage.ErrorServletservlet-class>
    servlet>
   
    <servlet-mapping>
      <servlet-name>Errorservlet-name>
      <url-pattern>/servlet/*url-pattern>
    servlet-mapping>
   
    <web-app>

本文参考:http://www.javaeye.com/topic/140553

posted @ 2009-05-10 09:33 lanxin1020 阅读(245) | 评论 (0)编辑 收藏

struts的validator的客户端验证,不能进行多表单页面的验证,原因是由<html:script>标签生成的javascipt是根据每个表单,生成一段代码。例如:

<html:javascript formName="searchSgbySjForm" dynamicJavascript="true" staticJavascript="false"/>
生成  :
             var bCancel = false; 
             function validateSearchSgbySjForm(form)
            {                                                                         
                if (bCancel)       return true;        
                else        return validateRequired(form) && validateDate(form);   
            }
            function required ()
           {     
               this.aa = new Array("sgfssjq", "事故发生时间起 不可为空.", new Function ("varName", "this.datePatternStrict='yyy-MM-dd'; return thisvarName];"));
               this.ab = new Array("sgfssjz", "事故发生时间止 不可为空.", new Function ("varName", "this.datePatternStrict='yyy-MM-dd';  return this[varName];"));   
           }

           function DateValidations ()
          {
              this.aa = new Array("sgfssjq", "事故发生时间起 不是有效的日期类型.", new Function ("varName", "this.datePatternStrict='yyy-MM-dd';  return this  [varName];"));
             this.ab = new Array("sgfssjz", "事故发生时间止 不是有效的日期类型.", new Function ("varName", "this.datePatternStrict='yyy-MM-dd';  return this[varName];"));   
         }

如果有多个的话required和DateValidations 都会重复的,而javascript是只认最后一个函数的。所以,会导致验证出错。

再写一个标签 ,主要根据原来的代码修改,代码如下:

package com.tmri.acd.tag;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.BodyTagSupport;

import org.apache.commons.validator.Field;
import org.apache.commons.validator.Form;
import org.apache.commons.validator.ValidatorAction;
import org.apache.commons.validator.ValidatorResources;
import org.apache.commons.validator.util.ValidatorUtils;
import org.apache.commons.validator.Var;
import org.apache.struts.Globals;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.config.ModuleConfig;
import com.tmri.acd.tag.TagUtils;
import org.apache.struts.util.MessageResources;
import org.apache.struts.validator.Resources;
import org.apache.struts.validator.ValidatorPlugIn;
import java.util.StringTokenizer;

public class JavascriptValidatorTag extends BodyTagSupport
{
 private static final Comparator actionComparator = new Comparator()
{       
            public int compare(Object o1, Object o2)
           {
                 ValidatorAction va1 = (ValidatorAction) o1;           
                 ValidatorAction va2 = (ValidatorAction) o2;
                 if ((va1.getDepends() == null || va1.getDepends().length() == 0) && (va2.getDepends() == null || va2.getDepends().length() == 0))
                {               
                    return 0;
                }
                else if (  (va1.getDepends() != null && va1.getDepends().length() > 0)  && (va2.getDepends() == null || va2.getDepends().length() == 0))
               {  
                   return 1;
               }
}

 

posted @ 2009-05-09 22:59 lanxin1020 阅读(175) | 评论 (0)编辑 收藏
HTML 注释 

在客户端显示一个注释. 

JSP 语法
<!-- comment [ <%= expression %> ] --> 
例子 1
<!-- This file displays the user login screen --> 
在客户端的HTML源代码中产生和上面一样的数据: 

<!-- This file displays the user login screen --> 
例子 2
<!-- This page was loaded on <%= (new java.util.Date()).toLocaleString() %> --> 
在客户端的HTML源代码中显示为: 

<!-- This page was loaded on January 1, 2000 --> 
描述
这种注释和HTML中很像,也就是它可以在"查看源代码"中看到. 

唯一有些不同的就是,你可以在这个注释中用表达式(例子2所示).这个表达示是不定的,由页面不同而不同,你能够使用各种表达式,只要是合法的就行。更多的请看表达式



隐藏注释 


写在JSP程序中,但不是发给客户。 

JSP 语法
<%-- comment --%> 
例子:

<%@ page language="java" %> <html> <head><title>A Comment Test</title></head> <body> <h2>A Test of Comments</h2> <%-- This comment will not be visible in the page source --%> </body> </html> 

描述
用隐藏注释标记的字符会在JSP编译时被忽略掉。这个注释在你希望隐藏或注释你的JSP程序时是很有用的。JSP编译器不是会对<%--and--%>之间的语句进行编译的,它不会显示在客户的浏览器中,也不会在源代码中看到 

声明 


在JSP程序中声明合法的变量和方法 

JSP 语法
<%! declaration; [ declaration; ]+ ... %> 
例子
<%! int i = 0; %> 
<%! int a, b, c; %> 
<%! Circle a = new Circle(2.0); %> 
描述
声明你将要在JSP程序中用到的变量和方法。你也必须这样做,不然会出错. 

你可以一次性声明多个变量和方法,只要以";"结尾就行,当然这些声明在Java中要是合法的。 

当你声明方法或变量时,请注意以下的一些规则: 

声明必须以";"结尾(Scriptlet有同样的规则,但是 表达式就不同了). 
你可以直接使用在<% @ page %>中被包含进来的已经声明的变量和方法,不需要对它们重新进行声明. 
一个声明仅在一个页面中有效。如果你想每个页面都用到一些声明,最好把它们写成一个单独的文件,然后用<%@ include %>或<jsp:include >元素包含进来。 


表达式 


包含一个符合JSP语法的表达式 

JSP 语法
<%= expression %> 
例子
<font color="blue"><%= map.size() %></font> 
<b><%= numguess.getHint() %></b>. 
描述
表达式元素表示的是一个在脚本语言中被定义的表达式,在运行后被自动转化为字符串,然后插入到这个表达示在JSP文件的位置显示。因为这个表达式的值已经被转化为字符串,所以你能在一行文本中插入这个表达式(形式和ASP完全一样). 

当你在JSP中使用表达式时请记住以下几点: 

你不能用一个分号(";")来作为表达式的结束符.但是同样的表达式用在scriptlet中就需要以分号来结尾了!查看Scriptlet 
这个表达式元素能够包括任何在Java Language Specification中有效的表达式. 
有时候表达式也能做为其它JSP元素的属性值.一个表达式能够变得很复杂,它可能由一个或多个表达式组成,这些表达式的顺序是从左到右。 


[/b]Scriptlet [/b]


包含一个有效的程序段. 

JSP 语法
<% code fragment %> 
例子
<% 
String name = null; 
if (request.getParameter("name") == null) { 
%> 
<%@ include file="error.html" %> 
<% 
} else { 
foo.setName(request.getParameter("name")); 
if (foo.getName().equalsIgnoreCase("integra")) 
name = "acura"; 
if (name.equalsIgnoreCase( "acura" )) { 
%> 
描述
一个scriptlet能够包含多个jsp语句,方法,变量,表达式 

因为scriptlet,我们便能做以下的事: 

声明将要用到的变量或方法(参考 声明). 
编写JSP表达式(参考 表达式). 
使用任何隐含的对象和任何用<jsp:useBean>声明过的对象 
编写JSP语句 (如果你在使用Java语言,这些语句必须遵从Java Language Specification,). 
任何文本,HTML标记,JSP元素必须在scriptlet之外 
当JSP收到客户的请求时,scriptlet就会被执行,如果scriptlet有显示的内容,这些显示的内容就被存在out对象中。 


Include 指令


在JSP中包含一个静态的文件,同时解析这个文件中的JSP语句. 

JSP 语法
<%@ include file="relativeURL" %> 
例子
include.jsp: 

<html> <head><title>An Include Test</title></head> <body bgcolor="white"> <font color="blue"> The current date and time are <%@ include file="date.jsp" %> </font> </body> </html> 

date.jsp:

 <%@ page import="java.util.*" %> <%= (new java.util.Date() ).toLocaleString() %> 

Displays in the page: 
The current date and time are 
Aug 30, 1999 2:38:40 

描述
<%@include %>指令将会在JSP编译时插入一个包含文本或代码的文件,当你使用<%@ include %>指命时,这个包含的过程就当是静态的。静态的包含就是指这个被包含的文件将会被插入到JSP文件中去,这个包含的文件可以是JSP文件,HTML文件,文本文件。如果包含的是JSP文件,这个包含的JSP的文件中代码将会被执行。 

如果你仅仅只是用include 来包含一个静态文件。那么这个包含的文件所执行的结果将会插入到JSP文件中放<% @ include %>的地方。一旦包含文件被执行,那么主JSP文件的过程将会被恢复,继续执行下一行. 

这个被包含文件可以是html文件,jsp文件,文本文件,或者只是一段Java代码,但是你得注意在这个包含文件中不能使用<html>,</html>,<body>,</body>标记,因为这将会影响在原JSP文件中同样的标记 ,这样做有时会导致错误. 

有一些<%@ include %>指命的行为是以特殊的JSP编译条件为基础,比如: 

这个被包含的文件必须对所有客户都有开放且必须f有效,或者它有安全限制 
如果这个包含文件被改变,包含此文件的JSP文件将被重新编译 
属性:
file="relativeURL"
这个包含文件的路径名一般来说是指相对路径,不需要什么端口,协议,和域名,如下: 

"error.jsp""templates/onlinestore.html""/beans/calendar.jsp" 

如果这个路径以"/"开头,那么这个路径主要是参照JSP应用的上下关系路径,如果路径是以文件名或目录名开头,那么这个路径就是正在使用的JSP文件的当前路径.


Page 指令


定义JSP文件中的全局属性. 

JSP 语法
<%@ page 
[ language="java" ] 
[ extends="package.class" ] 
[ import="{package.class | package.*}, ..." ] 
[ session="true | false" ] 
[ buffer="none | 8kb | sizekb" ] 
[ autoFlush="true | false" ] 
[ isThreadSafe="true | false" ] 
[ info="text" ] 
[ errorPage="relativeURL" ] 
[ contentType="mimeType [ ;charset=characterSet ]" | "text/html ; charset=ISO-8859-1" ] 
[ isErrorPage="true | false" ] 
%> 
例子
<%@ page import="java.util.*, java.lang.*" %> 
<%@ page buffer="5kb" autoFlush="false" %> 
<%@ page errorPage="error.jsp" %> 
描述
<%@ page %>指令作用于整个JSP页面,同样包括静态的包含文件。但是<% @ page %>指令不能作用于动态的包含文件,比如 <jsp:include> 

你可以在一个页面中用上多个<% @ page %>指令,但是其中的属性只能用一次,不过也有个例外,那就是import属性。因为import属性和Java中的import语句差不多(参照Java Language),所以你就能多用此属性几次了. 

无论你把<% @ page %>指令放在JSP的文件的哪个地方,它的作用范围都是整个JSP页面。不过,为了JSP程序的可读性,以及好的编程习惯,最好还是把它放在JSP文件的顶部. 

属性
language="java"
声明脚本语言的种类,暂时只能用"java" 

extends="package.class"
标明JSP编译时需要加入的Java Class的全名,但是得慎重的使用它,它会限制JSP的编译能力. 

import="{package.class | package.* }, ..."
需要导入的Java包的列表,这些包就作用于程序段,表达式,以及声明。 

下面的包在JSP编译时已经导入了,所以你就不需要再指明了: 

java.lang.* 
javax.servlet.* 
javax.servlet.jsp.* 
javax.servlet.http.* 

session="true | false"
设定客户是否需要HTTP Session.(学过ASP的人,应该对它不陌生)如果它为true,那么Session是有用的。 

如果它有false,那么你就不能使用session对象,以及定义了scope=session的<jsp:useBean>元素。这样的使用会导致错误. 

缺省值是true. 

buffer="none | 8kb | sizekb"
buffer的大小被out对象用于处理执行后的JSP对客户浏览器的输出。缺省值是8kb 

autoFlush="true | false"
设置如果buffer溢出,是否需要强制输出,如果其值被定义为true(缺省值),输出正常,如果它被设置为false,如果这个buffer溢出,就会导致一个意外错误的发生.如果你把buffer设置为none,那么你就不能把autoFlush设置为false. 

isThreadSafe="true | false"
设置Jsp文件是否能多线程使用。缺省值是true,也就是说,JSP能够同时处理多个用户的请求,如果设置为false,一个jsp只能一次处理一个请求 

info="text"
一个文本在执行JSP将会被逐字加入JSP中,你能够使用Servlet.getServletInfo方法取回。 

errorPage="relativeURL"
设置处理异常事件的JSP文件。 

isErrorPage="true | false"
设置此页是否为出错页,如果被设置为true,你就能使用exception对象. 

contentType="mimeType [ ;charset=characterSet ]" | "text/html;charset=ISO-8859-1"
设置MIME类型 。缺省MIME 类型是: text/html, 缺省字符集为 ISO-8859-1.


<jsp:forward>


重定向一个HTML文件,JSP文件,或者是一个程序段. 

JSP 语法
<jsp:forward page={"relativeURL" | "<%= expression %>"} /> 
or 
<jsp:forward page={"relativeURL" | "<%= expression %>"} > 
<jsp:param name="parameterName" 
value="{parameterValue | <%= expression %>}" />+ 
</jsp:forward> 
例子
<jsp:forward page="/servlet/login" /> 
<jsp:forward page="/servlet/login"> 
<jsp:param name="username" value="jsmith" /> 
</jsp:forward> 
描述
<jsp:forward>标签从一个JSP文件向另一个文件传递一个包含用户请求的request对象.<jsp:forward>标签以下的代码,将不能执行. 

你能够向目标文件传送参数和值,在这个例子中我们传递的参数名为username,值为scott,如果你使用了<jsp:param>标签的话,目标文件必须是一个动态的文件,能够处理参数. 

如果你使用了非缓冲输出的话,那么使用<jsp:forward>时就要小心。如果在你使用<jsp:forward>之前,jsp文件已经有了数据,那么文件执行就会出错. 

属性
page="{relativeURL | <%= expression %>}"
这里是一个表达式或是一个字符串用于说明你将要定向的文件或URL.这个文件可以是JSP,程序段,或者其它能够处理request对象的文件(如asp,cgi,php). 

<jsp:param name="parameterName" value="{parameterValue | <%= expression %>}" />+
向一个动态文件发送一个或多个参数,这个文件一定是动态文件. 

如果你想传递多个参数,你可以在一个JSP文件中使用多个<jsp:param>。name指定参数名,value指定参数值.


<jsp:getProperty>


获取Bean的属性值,用于显示在页面中 

JSP 语法
<jsp:getProperty name="beanInstanceName" property="propertyName" /> 
例子
<jsp:useBean id="calendar" scope="page" class="employee.Calendar" /> 
<h2> 
Calendar of <jsp:getProperty name="calendar" property="username" /> 
</h2> 
描述
这个<jsp:getProperty>元素将获得Bean的属性值,并可以将其使用或显示在JSP页面中.在你使用<jsp:getProperty>之前,你必须用<jsp:useBean>创建它. 

<jsp:getProperty>元素有一些限制: 

你不能使用<jsp:getProperty>来检索一个已经被索引了的属性 
你能够和JavaBeans组件一起使用<jsp:getProperty>,但是不能与Enterprise Bean一起使用。 
属性
name="beanInstanceName"
bean的名字,由<jsp:useBean>指定 

property="propertyName"
所指定的Bean的属性名。 

技巧:
在sun的JSP参考中提到,如果你使用<jsp:getProperty>来检索的值是空值,那么NullPointerException将会出现,同时如果使用程序段或表达式来检索其值,那么在浏览器上出现的是null(空).


<jsp:include>


包含一个静态或动态文件. 

JSP 语法
<jsp:include page="{relativeURL | <%= expression%>}" flush="true" /> 
or 
<jsp:include page="{relativeURL | <%= expression %>}" flush="true" > 
<jsp:param name="parameterName" value="{parameterValue | <%= expression %>}" />+ 
</jsp:include> 
Examples
<jsp:include page="scripts/login.jsp" /> 
<jsp:include page="copyright.html" /> 
<jsp:include page="/index.html" /> 
<jsp:include page="scripts/login.jsp"> 
<jsp:param name="username" value="jsmith" /> 
</jsp:include> 

描述
<jsp:include>元素允许你包含动态文件和静态,这两种包含文件的结果是不同的。如果文件仅是静态文件,那么这种包含仅仅是把包含文件的内容加到jsp文件中去,而如果这个文件动态的,那么这个被包含文件也会被Jsp编译器执行(这一切与asp相似) 

你不能从文件名上判断一个文件是动态的还是静态的,比如aspcn.asp 就有可能只是包含一些信息而已,而不需要执行。<jsp:include>能够同时处理这两种文件,因此你就不需要包含时还要判断此文件是动态的还是静态的. 

如果这个包含文件是动态的,那么你还可以用<jsp:param>还传递参数名和参数值。 

属性
page="{relativeURL | <%= expression %>}"
参数为一相对路径,或者是代表相对路径的表达式. 

flush="true"
这里你必须使用flush="true",你不能使用false值.缺省值为false 

<jsp:param name="parameterName" value="{parameterValue | <%= expression %> }" />+
<jsp:param>子句能让你传递一个或多个参数给动态文件 

你能在一个页面中使用多个<jsp:param>来传递多个参数,


<jsp:plugin>


执行一个applet或Bean,有可能的话还要下载一个Java插件用于执行它. 

JSP 语法
<jsp:plugin 
type="bean | applet" 
code="classFileName" 
codebase="classFileDirectoryName" 
[ name="instanceName" ] 
[ archive="URIToArchive, ..." ] 
[ align="bottom | top | middle | left | right" ] 
[ height="displayPixels" ] 
[ width="displayPixels" ] 
[ hspace="leftRightPixels" ] 
[ vspace="topBottomPixels" ] 
[ jreversion="JREVersionNumber | 1.1" ] 
[ nspluginurl="URLToPlugin" ] 
[ iepluginurl="URLToPlugin" ] > 
[ <jsp:params> 
[ <jsp:param name="parameterName" value="{parameterValue | <%= expression %>}" /> ]+ 
</jsp:params> ] 

[ <jsp:fallback> text message for user </jsp:fallback> ] 

</jsp:plugin> 

例子
<jsp:plugin type=applet code="Molecule.class" codebase="/html"> 
<jsp:params> 
<jsp:param name="molecule" value="molecules/benzene.mol" /> 
</jsp:params> 
<jsp:fallback> 
<p>Unable to load applet</p> 
</jsp:fallback> 
</jsp:plugin> 
描述
<jsp:plugin>元素用于在浏览器中播放或显示一个对象(典型的就是applet和Bean),而这种显示需要在浏览器的java插件。 

当Jsp文件被编译,送往浏览器时,<jsp:plugin>元素将会根据浏览器的版本替换成<object>或者<embed>元素。注意,<object>用于HTML 4.0 ,<embed>用于HTML 3.2. 

一般来说,<jsp:plugin>元素会指定对象是Applet还是Bean,同样也会指定class的名字,还有位置,另外还会指定将从哪里下载这个Java插件。具体如下: 

属性
type="bean | applet"
.将被执行的插件对象的类型,你必须得指定这个是Bean还是applet,因为这个属性没有缺省值. 

code="classFileName"
将会被Java插件执行的Java Class的名字,必须以.class结尾。这个文件必须存在于codebase属性指定的目录中. 

codebase="classFileDirectoryName"
将会被执行的Java Class文件的目录(或者是路径),如果你没有提供此属性,那么使用<jsp:plugin>的jsp文件的目录将会被使用. 

name="instanceName"
这个Bean或applet实例的名字,它将会在Jsp其它的地方调用. 

archive="URIToArchive, ..."
一些由逗号分开的路径名,这些路径名用于预装一些将要使用的class,这会提高applet的性能. 

align="bottom | top | middle | left | right"
图形,对象,Applet的位置,有以下值: 

bottom 
top 
middle 
left 
right 
height="displayPixels" width="displayPixels"
Applet或Bean将要显示的长宽的值,此值为数字,单位为象素. 

hspace="leftRightPixels" vspace="topBottomPixels"
Applet或Bean显示时在屏幕左右,上下所需留下的空间,单位为象素. 

jreversion="JREVersionNumber | 1.1" 
Applet或Bean运行所需的Java Runtime Environment (JRE) 的版本. 缺省值是 1.1. 

nspluginurl="URLToPlugin" 
Netscape Navigator用户能够使用的JRE的下载地址,此值为一个标准的URL,如http://www.aspcn.com/jsp 

iepluginurl="URLToPlugin"
IE用户能够使用的JRE的下载地址,此值为一个标准的URL,如http://www.aspcn.com/jsp 

<jsp:params> [ <jsp:param name="parameterName" value="{parameterValue | <%= expression %>}" /> ]+ </jsp:params>
你需要向applet或Bean传送的参数或参数值。 

<jsp:fallback> text message for user </jsp:fallback>
一段文字用于Java 插件不能启动时显示给用户的,如果插件能够启动而applet或Bean不能,那么浏览器会有一个出错信息弹出.


<jsp:setProperty>


设置Bean中的属性值. 

JSP语法
<jsp:setProperty 
name="beanInstanceName" 

property= "*" | 
property="propertyName" [ param="parameterName" ] | 
property="propertyName" value="{string | <%= expression %>}" 

/> 
例子
<jsp:setProperty name="mybean" property="*" /> 
<jsp:setProperty name="mybean" property="username" /> 
<jsp:setProperty name="mybean" property="username" value="Steve" /> 
描述
<jsp:setProperty>元素使用Bean给定的setter方法,在Bean中设置一个或多个属性值。你在使用这个元素之前必须得使用<jsp:useBean>声明此Bean.因为,<jsp:useBean>和<jsp:setProperty>是联系在一起的,同时这他们使用的Bean实例的名字也应当相匹配(就是说,在<jsp:setProperty>中的name的值应当和<jsp:useBean>中id的值相同) 

你能使用多种方法利用<jsp:setProperty>来设定属性值 : 

通过用户输入的所有值(被做为参数储存中request对象中)来匹配Bean中的属性 
通过用户输入的指定的值来匹配Bean中指定的属性 
在运行时使用一个表达式来匹配Bean的属性 
每一种设定属性值的方法都有其特定的语法,下面我们会来讲解 

属性及其用法
name="beanInstanceName"
表示已经在<jsp:useBean>中创建的Bean实例的名字. 

property="*"
储存用户在Jsp输入的所有值,用于匹配Bean中的属性。在Bean中的属性的名字必须和request对象中的参数名一致. 

从客户传到服器上的参数值一般都是字符类型 ,这些字符串为了能够在Bean中匹配就必须转换成其它的类型,下面的表中列出了Bean属性的类型以及他们的转换方法.

把字符串转化为其它类型的方法. Property 类型 
方法 
boolean or Boolean 
java.lang.Boolean.valueOf(String) 
byte or Byte 
java.lang.Byte.valueOf(String) 
char or Character 
java.lang.Character.valueOf(String) 
double or Double 
java.lang.Double.valueOf(String) 
integer or Integer 
java.lang.Integer.valueOf(String) 
float or Float 
java.lang.Float.valueOf(String) 
long or Long 
java.lang.Long.valueOf(String) 

如果request对象的参数值中有空值,那么对应的Bean属性将不会设定任何值。同样的,如果Bean中有一个属性没有与之对应的Request参数值,那么这个属性同样也不会设定. 

property="propertyName" [ param="parameterName" ]
使用request中的一个参数值来指定Bean中的一个属性值。在这个语法中,property指定Bean的属性名,param指定request中的参数名. 

如果bean属性和request参数的名字不同,那么你就必须得指定property和param ,如果他们同名,那么你就只需要指明property就行了. 

如查参数值为空(或未初始化),对应的Bean属性不被设定. 

property="propertyName" value="{string | <%= expression %>}"
使用指定的值来设定Bean属性。这个值可以是字符串,也可以是表达式。如果这个字符串,那么它就会被转换成Bean属性的类型(查看上面的表).如果它是一个表达式,那么它类型就必须和它将要设定的属性值的类型一致。 

如果参数值为空,那么对应的属性值也不会被设定。另外,你不能在一个<jsp:setProperty>中同时使用param和value 

技巧
如果你使用了property="*",那么Bean的属性没有必要按Html表单中的顺序排序 


<jsp:useBean>


创建一个Bean实例并指定它的名字和作用范围. 

JSP 语法
<jsp:useBean 
id="beanInstanceName" 
scope="page | request | session | application" 

class="package.class" | 
type="package.class" | 
class="package.class" type="package.class" | 
beanName="{package.class | <%= expression %>}" type="package.class" 


/> | 
> other elements </jsp:useBean> 

例子
<jsp:useBean id="cart" scope="session" class="session.Carts" /> 
<jsp:setProperty name="cart" property="*" /> 
<jsp:useBean id="checking" scope="session" class="bank.Checking" > 
<jsp:setProperty name="checking" property="balance" value="0.0" /> 
</jsp:useBean> 

描述
<jsp:useBean>用于定位或示例一个JavaBeans组件。<jsp:useBean>首先会试图定位一个Bean实例,如果这个Bean不存在,那么<jsp:useBean>就会从一个class或模版中进行示例。 

为了定位或示例一个Bean,<jsp:useBean>会进行以下步聚,顺序如下: 

通过给定名字和范围试图定位一个Bean. 
对这个Bean对象引用变量以你指定的名字命名. 
如果发现了这个Bean,将会在这个变量中储存这个引用。如果你也指定了类型,那么这个Bean也设置为相应的类型. 
如果没有发现这个Bean,将会从你指定的class中示例,并将此引用储存到一个新的变量中去。如果这个class的名字代表的是一个模版,那么这个Bean被java.beans.Beans.instantiate示例. 
如果<jsp:useBean>已经示例(不是定位)了Bean,同时<jsp:useBean>和</jsp:useBean>中有元素,那么将会执行其中的代码. 
<jsp:useBean>元素的主体通常包含有<jsp:setProperty>元素,用于设置Bean的属性值。正如上面第五步所说的,<jsp:useBean>的主体仅仅只有在<jsp:useBean>示例Bean时才会被执行,如果这个Bean已经存在,<jsp:useBean>能够定位它,那么主体中的内容将不会起作用 

属性以及用法
id="beanInstanceName"
在你所定义的范围中确认Bean的变量,你能在后面的程序中使用此变量名来分辨不同的Bean 

这个变量名对大小写敏感,必须符合你所使用的脚本语言的规定,在Java Programming Language中,这个规定在Java Language 规范已经写明。如果这个Bean已经在别的<jsp:useBean>中创建,那么这个id的值必须与原来的那个id值一致. 

scope="page | request | session | application"
Bean存在的范围以及id变量名的有效范围。缺省值是page,以下是详细说明: 

page - 你能在包含<jsp:useBean>元素的JSP文件以及此文件中的所有静态包含文件中使用Bean,直到页面执行完毕向客户端发回响应或转到另一个文件为止。
   
request - 你在任何执行相同请求的Jsp文件中使用Bean,直到页面执行完毕向客户端发回响应或转到另一个文件为止。你能够使用Request对象访问Bean,比如request.getAttribute(beanInstanceName)

session - 从创建Bean开始,你就能在任何使用相同session的Jsp文件中使用Bean.这个Bean存在于整个Session生存周期内,任何在分享此Session的Jsp文件都能使用同一Bean.注意在你创建Bean的Jsp文件中<% @ page %>指令中必须指定session=true

application - 从创建Bean开始,你就能在任何使用相同application的Jsp文件中使用Bean.这个Bean存在于整个application生存周期内,任何在分享此application的Jsp文件都能使用同一Bean. 
class="package.class"
使用new关键字以及class构造器从一个class中示例一个bean.这个class不能是抽象的,必须有一个公用的,没有参数的构造器.这个package的名字区别大小写。 

type="package.class"
如果这个Bean已经在指定的范围中存在,那么写这个Bean一个新的数据库类型 。如果你没有使用class或beanName指定type,Bean将不会被示例.package和class的名字,区分大小写. 

beanName="{package.class | <%= expression %>}" type="package.class"
使用java.beans.Beans.instantiate方法来从一个class或连续模版中示例一个Bean,同时指定Bean的类型。 

beanName可以是package和class也可以是表达式,它的值会传给Beans.instantiate.tupe的值可以和Bean相同。 

package 和 class 名字区分大小写.
posted @ 2009-05-09 11:58 lanxin1020 阅读(180) | 评论 (0)编辑 收藏

1 引子
try…catch…finally恐怕是大家再熟悉不过的语句了,而且感觉用起来也是很简单,逻辑上似乎也是很容易理解。不过,我亲自体验的“教训”告诉我,这个东西可不是想象中的那么简单、听话。不信?那你看看下面的代码,“猜猜”它执行后的结果会是什么?不要往后看答案、也不许执行代码看真正答案哦。如果你的答案是正确,那么这篇文章你就不用浪费时间看啦。
public class TestException
{
    public TestException()
    {
    }
    boolean testEx() throws Exception
    {
        boolean ret = true;
        try
        {
            ret = testEx1();
        }
        catch (Exception e)
        {
            System.out.println("testEx, catch exception");
            ret = false;
            throw e;
        }
        finally
        {
            System.out.println("testEx, finally; return value=" + ret);
            return ret;
        }
    }
    boolean testEx1() throws Exception
    {
        boolean ret = true;
        try
        {
            ret = testEx2();
            if (!ret)
            {
                return false;
            }
            System.out.println("testEx1, at the end of try");
            return ret;
        }
        catch (Exception e)
        {
            System.out.println("testEx1, catch exception");
            ret = false;
            throw e;
        }
        finally
        {
            System.out.println("testEx1, finally; return value=" + ret);
            return ret;
        }
    }
    boolean testEx2() throws Exception
    {
        boolean ret = true;
        try
        {
            int b = 12;
            int c;
            for (int i = 2; i >= -2; i--)
            {
                c = b / i;
                System.out.println("i=" + i);
            }
            return true;
        }
        catch (Exception e)
        {
            System.out.println("testEx2, catch exception");
            ret = false;
            throw e;
        }
        finally
        {
            System.out.println("testEx2, finally; return value=" + ret);
            return ret;
        }
    }
    public static void main(String[] args)
    {
        TestException testException1 = new TestException();
        try
        {
            testException1.testEx();
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}
你的答案是什么?是下面的答案吗?
i=2
i=1
testEx2, catch exception
testEx2, finally; return value=false
testEx1, catch exception
testEx1, finally; return value=false
testEx, catch exception
testEx, finally; return value=false
如果你的答案真的如上面所说,那么你错啦。^_^,那就建议你仔细看一看这篇文章或者拿上面的代码按各种不同的情况修改、执行、测试,你会发现有很多事情不是原来想象中的那么简单的。
现在公布正确答案:
i=2
i=1
testEx2, catch exception
testEx2, finally; return value=false
testEx1, finally; return value=false
testEx, finally; return value=false

2 基础知识

2.1 相关概念
例外是在程序运行过程中发生的异常事件,比如除0溢出、数组越界、文件找不到等,这些事件的发生将阻止程序的正常运行。为了加强程序的鲁棒性,程序设计时,必须考虑到可能发生的异常事件并做出相应的处理。C语言中,通过使用if语句来判断是否出现了例外,同时,调用函数通过被调用函数的返回值感知在被调用函数中产生的例外事件并进行处理。全程变量ErroNo常常用来反映一个异常事件的类型。但是,这种错误处理机制会导致不少问题。
Java通过面向对象的方法来处理例外。在一个方法的运行过程中,如果发生了例外,则这个方法生成代表该例外的一个对象,并把它交给运行时系统,运行时系统寻找相应的代码来处理这一例外。我们把生成例外对象并把它提交给运行时系统的过程称为抛弃(throw)一个例外。运行时系统在方法的调用栈中查找,从生成例外的方法开始进行回朔,直到找到包含相应例外处理的方法为止,这一个过程称为捕获(catch)一个例外。
2.2 Throwable类及其子类
 用面向对象的方法处理例外,就必须建立类的层次。类 Throwable位于这一类层次的最顶层,只有它的后代才可以做为一个例外被抛弃。图1表示了例外处理的类层次。
从图中可以看出,类Throwable有两个直接子类:Error和Exception。Error类对象(如动态连接错误等),由Java虚拟机生成并抛弃(通常,Java程序不对这类例外进行处理);Exception类对象是Java程序处理或抛弃的对象。它有各种不同的子类分别对应于不同类型的例外。其中类RuntimeException代表运行时由Java虚拟机生成的例外,如算术运算例外ArithmeticException(由除0错等导致)、数组越界例外ArrayIndexOutOfBoundsException等;其它则为非运行时例外,如输入输出例外IOException等。Java编译器要求Java程序必须捕获或声明所有的非运行时例外,但对运行时例外可以不做处理。
 

2.3  异常处理关键字
Java的异常处理是通过5个关键字来实现的:try,catch,throw,throws,finally。JB的在线帮助中对这几个关键字是这样解释的:
Throws:  Lists the exceptions a method could throw.
Throw:   Transfers control of the method to the exception handler.
Try:    Opening exception-handling statement.
Catch:  Captures the exception.
Finally: Runs its code before terminating the program.
2.3.1 try语句 
try语句用大括号{}指定了一段代码,该段代码可能会抛弃一个或多个例外。
2.3.2 catch语句 
catch语句的参数类似于方法的声明,包括一个例外类型和一个例外对象。例外类型必须为Throwable类的子类,它指明了catch语句所处理的例外类型,例外对象则由运行时系统在try所指定的代码块中生成并被捕获,大括号中包含对象的处理,其中可以调用对象的方法。
catch语句可以有多个,分别处理不同类的例外。Java运行时系统从上到下分别对每个catch语句处理的例外类型进行检测,直到找到类型相匹配的catch语句为止。这里,类型匹配指catch所处理的例外类型与生成的例外对象的类型完全一致或者是它的父类,因此,catch语句的排列顺序应该是从特殊到一般。
也可以用一个catch语句处理多个例外类型,这时它的例外类型参数应该是这多个例外类型的父类,程序设计中要根据具体的情况来选择catch语句的例外处理类型。 
2.3.3 finally语句 
try所限定的代码中,当抛弃一个例外时,其后的代码不会被执行。通过finally语句可以指定一块代码。无论try所指定的程序块中抛弃或不抛弃例外,也无论catch语句的例外类型是否与所抛弃的例外的类型一致,finally所指定的代码都要被执行,它提供了统一的出口。通常在finally语句中可以进行资源的清除工作。如关闭打开的文件等。
2.3.4 throws语句 
throws总是出现在一个函数头中,用来标明该成员函数可能抛出的各种异常。对大多数Exception子类来说,Java 编译器会强迫你声明在一个成员函数中抛出的异常的类型。如果异常的类型是Error或 RuntimeException, 或它们的子类,这个规则不起作用, 因为这在程序的正常部分中是不期待出现的。 如果你想明确地抛出一个RuntimeException,你必须用throws语句来声明它的类型。
2.3.5 throw语句 
throw总是出现在函数体中,用来抛出一个异常。程序会在throw语句后立即终止,它后面的语句执行不到,然后在包含它的所有try块中(可能在上层调用函数中)从里向外寻找含有与其匹配的catch子句的try块。

3 关键字及其中语句流程详解

3.1 try的嵌套
你可以在一个成员函数调用的外面写一个try语句,在这个成员函数内部,写另一个try语句保护其他代码。每当遇到一个try语句,异常的框架就放到堆栈上面,直到所有的try语句都完成。如果下一级的try语句没有对某种异常进行处理,堆栈就会展开,直到遇到有处理这种异常的try语句。下面是一个try语句嵌套的例子。
class MultiNest {
    static void procedure() {
        try {
            int a = 0;
            int b = 42/a;
        } catch(java.lang.ArithmeticException e) {
            System.out.println("in procedure, catch ArithmeticException: " + e);
        }
    }
    public static void main(String args[]) {
        try {
            procedure();
        } catch(java.lang. Exception e) {
            System.out.println("in main, catch Exception: " + e);
        }
    }
}
这个例子执行的结果为:
in procedure, catch ArithmeticException: java.lang.ArithmeticException: / by zero
成员函数procedure里有自己的try/catch控制,所以main不用去处理 ArrayIndexOutOfBoundsException;当然如果如同最开始我们做测试的例子一样,在procedure中catch到异常时使用throw e;语句将异常抛出,那么main当然还是能够捕捉并处理这个procedure抛出来的异常。例如在procedure函数的catch中的System.out语句后面增加throw e;语句之后,执行结果就变为:
in procedure, catch ArithmeticException: java.lang.ArithmeticException: / by zero
in main, catch Exception: java.lang.ArithmeticException: / by zero

3.2 try-catch程序块的执行流程以及执行结果
相对于try-catch-finally程序块而言,try-catch的执行流程以及执行结果还是比较简单的。
首先执行的是try语句块中的语句,这时可能会有以下三种情况:
    1.如果try块中所有语句正常执行完毕,那么就不会有其他的“动做”被执行,整个try-catch程序块正常完成。
    2.如果try语句块在执行过程中碰到异常V,这时又分为两种情况进行处理:
-->如果异常V能够被与try相应的catch块catch到,那么第一个catch到这个异常的catch块(也是离try最近的一个与异常V匹配的catch块)将被执行;如果catch块执行正常,那么try-catch程序块的结果就是“正常完成”;如果该catch块由于原因R突然中止,那么try-catch程序块的结果就是“由于原因R突然中止(completes abruptly)”。
-->如果异常V没有catch块与之匹配,那么这个try-catch程序块的结果就是“由于抛出异常V而突然中止(completes abruptly)”。
    3. 如果try由于其他原因R突然中止(completes abruptly),那么这个try-catch程序块的结果就是“由于原因R突然中止(completes abruptly)”。

3.3 try-catch-finally程序块的执行流程以及执行结果
try-catch-finally程序块的执行流程以及执行结果比较复杂。
首先执行的是try语句块中的语句,这时可能会有以下三种情况:
1.如果try块中所有语句正常执行完毕,那么finally块的居于就会被执行,这时分为以下两种情况:
-->如果finally块执行顺利,那么整个try-catch-finally程序块正常完成。
-->如果finally块由于原因R突然中止,那么try-catch-finally程序块的结局是“由于原因R突然中止(completes abruptly)”
2.如果try语句块在执行过程中碰到异常V,这时又分为两种情况进行处理:
-->如果异常V能够被与try相应的catch块catch到,那么第一个catch到这个异常的catch块(也是离try最近的一个与异常V匹配的catch块)将被执行;这时就会有两种执行结果:
-->如果catch块执行正常,那么finally块将会被执行,这时分为两种情况:
-->如果finally块执行顺利,那么整个try-catch-finally程序块正常完成。
-->如果finally块由于原因R突然中止,那么try-catch-finally程序块的结局是“由于原因R突然中止(completes abruptly)”
-->如果catch块由于原因R突然中止,那么finally模块将被执行,分为两种情况:
-->如果如果finally块执行顺利,那么整个try-catch-finally程序块的结局是“由于原因R突然中止(completes abruptly)”。
-->如果finally块由于原因S突然中止,那么整个try-catch-finally程序块的结局是“由于原因S突然中止(completes abruptly)”,原因R将被抛弃。
(注意,这里就正好和我们的例子相符合,虽然我们在testEx2中使用throw e抛出了异常,但是由于testEx2中有finally块,而finally块的执行结果是complete abruptly的(别小看这个用得最多的return,它也是一种导致complete abruptly的原因之一啊——后文中有关于导致complete abruptly的原因分析),所以整个try-catch-finally程序块的结果是“complete abruptly”,所以在testEx1中调用testEx2时是捕捉不到testEx1中抛出的那个异常的,而只能将finally中的return结果获取到。
如果在你的代码中期望通过捕捉被调用的下级函数的异常来给定返回值,那么一定要注意你所调用的下级函数中的finally语句,它有可能会使你throw出来的异常并不能真正被上级调用函数可见的。当然这种情况是可以避免的,以testEx2为例:如果你一定要使用finally而且又要将catch中throw的e在testEx1中被捕获到,那么你去掉testEx2中的finally中的return就可以了。
这个事情已经在OMC2.0的MIB中出现过啦:服务器的异常不能完全被反馈到客户端。)
-->如果异常V没有catch块与之匹配,那么finally模块将被执行,分为两种情况:
-->如果finally块执行顺利,那么整个try-catch-finally程序块的结局就是“由于抛出异常V而突然中止(completes abruptly)”。
-->如果finally块由于原因S突然中止,那么整个try-catch-finally程序块的结局是“由于原因S突然中止(completes abruptly)”,异常V将被抛弃。
3.如果try由于其他原因R突然中止(completes abruptly),那么finally块被执行,分为两种情况:
-->如果finally块执行顺利,那么整个try-catch-finally程序块的结局是“由于原因R突然中止(completes abruptly)”。
-->如果finally块由于原因S突然中止,那么整个try-catch-finally程序块的结局是“由于原因S突然中止(completes abruptly)”,原因R将被抛弃。
3.4 try-catch-finally程序块中的return
从上面的try-catch-finally程序块的执行流程以及执行结果一节中可以看出无论try或catch中发生了什么情况,finally都是会被执行的,那么写在try或者catch中的return语句也就不会真正的从该函数中跳出了,它的作用在这种情况下就变成了将控制权(语句流程)转到finally块中;这种情况下一定要注意返回值的处理。
例如,在try或者catch中return false了,而在finally中又return true,那么这种情况下不要期待你的try或者catch中的return false的返回值false被上级调用函数获取到,上级调用函数能够获取到的只是finally中的返回值,因为try或者catch中的return语句只是转移控制权的作用。
3.5 如何抛出异常
如果你知道你写的某个函数有可能抛出异常,而你又不想在这个函数中对异常进行处理,只是想把它抛出去让调用这个函数的上级调用函数进行处理,那么有两种方式可供选择:
第一种方式:直接在函数头中throws SomeException,函数体中不需要try/catch。比如将最开始的例子中的testEx2改为下面的方式,那么testEx1就能捕捉到testEx2抛出的异常了。
    boolean testEx2() throws Exception{
        boolean ret = true;
        int b=12;
        int c;
        for (int i=2;i>=-2;i--){
            c=b/i;
            System.out.println("i="+i);
        }
        return true;   
}
第二种方式:使用try/catch,在catch中进行一定的处理之后(如果有必要的话)抛出某种异常。例如上面的testEx2改为下面的方式,testEx1也能捕获到它抛出的异常:
    boolean testEx2() throws Exception{
        boolean ret = true;
        try{
            int b=12;
            int c;
            for (int i=2;i>=-2;i--){
                c=b/i;
                System.out.println("i="+i);
            }
            return true;
        }catch (Exception e){
            System.out.println("testEx2, catch exception");
            Throw e;
        }
    }
第三种方法:使用try/catch/finally,在catch中进行一定的处理之后(如果有必要的话)抛出某种异常。例如上面的testEx2改为下面的方式,testEx1也能捕获到它抛出的异常:
    boolean testEx2() throws Exception{
        boolean ret = true;
        try{
            int b=12;
            int c;
            for (int i=2;i>=-2;i--){
                c=b/i;
                System.out.println("i="+i);
                throw new Exception("aaa");
            }
            return true;
        }catch (java.lang.ArithmeticException e){
            System.out.println("testEx2, catch exception");
            ret = false;
            throw new Exception("aaa");
        }finally{
            System.out.println("testEx2, finally; return value="+ret);
        }
    }
4  关于abrupt completion
前面提到了complete abruptly(暂且理解为“突然中止”或者“异常结束”吧),它主要包含了两种大的情形:abrupt completion of expressions and statements,下面就分两种情况进行解释。
4.1 Normal and Abrupt Completion of Evaluation
每一个表达式(expression)都有一种使得其包含的计算得以一步步进行的正常模式,如果每一步计算都被执行且没有异常抛出,那么就称这个表达式“正常结束(complete normally)”;如果这个表达式的计算抛出了异常,就称为“异常结束(complete abruptly)”。异常结束通常有一个相关联的原因(associated reason),通常也就是抛出一个异常V。
与表达式、操作符相关的运行期异常有:
-->A class instance creation expression, array creation expression , or string concatenation operatior expression throws an OutOfMemoryError if there is insufficient memory available.
-->An array creation expression throws a NegativeArraySizeException if the value of any dimension expression is less than zero.
-->A field access throws a NullPointerException if the value of the object reference  expression is null.
-->A method invocation expression that invokes an instance method throws a NullPointerException if the target reference is null.
-->An array access throws a NullPointerException if the value of the array reference  expression is null.
-->An array access throws an ArrayIndexOutOfBoundsException if the value of the array index expression is negative or greater than or equal to the length of the array.
-->A cast throws a ClassCastException if a cast is found to be impermissible at run time.
-->An integer division or integer remainder operator throws an ArithmeticException if the value of the right-hand operand expression is zero.
-->An assignment to an array component of reference type throws an ArrayStoreException when the value to be assigned is not compatible with the component type of the array.
4.2 Normal and Abrupt Completion of Statements
正常情况我们就不多说了,在这里主要是列出了abrupt completion的几种情况:
-->break, continue, and return 语句将导致控制权的转换,从而使得statements不能正常地、完整地执行。
-->某些表达式的计算也可能从java虚拟机抛出异常,这些表达式在上一小节中已经总结过了;一个显式的的throw语句也将导致异常的抛出。抛出异常也是导致控制权的转换的原因(或者说是阻止statement正常结束的原因)。
如果上述事件发生了,那么这些statement就有可能使得其正常情况下应该都执行的语句不能完全被执行到,那么这些statement也就是被称为是complete abruptly.
导致abrupt completion的几种原因:
-->A break with no label
-->A break with a given label
-->A continue with no label
-->A continue with a given label
-->A return with no value
-->A return with a given value A
-->throw with a given value, including exceptions thrown by the Java virtual machine
5 关于我们的编程的一点建议
弄清楚try-catch-finally的执行情况后我们才能正确使用它。
如果我们使用的是try-catch-finally语句块,而我们又需要保证有异常时能够抛出异常,那么在finally语句中就不要使用return语句了(finally语句块的最重要的作用应该是释放申请的资源),因为finally中的return语句会导致我们的throw e被抛弃,在这个try-catch-finally的外面将只能看到finally中的返回值(除非在finally中抛出异常)。(我们需要记住:不仅throw语句是abrupt completion 的原因,return、break、continue等这些看起来很正常的语句也是导致abrupt completion的原因。)

posted @ 2009-05-08 15:23 lanxin1020 阅读(200) | 评论 (0)编辑 收藏
 
    1.详解

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

    2)SAX

    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解析器的时候编码工作会比较困难,而且很难同时访问同一个文档中的多处不同数据。

    3)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许可证变体下发布的开放源码。

    4)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.

    2。比较

    1)DOM4J性能最好,连Sun的JAXM也在用DOM4J.目前许多开源项目中大量采用DOM4J,例如大名鼎鼎的Hibernate也用DOM4J来读取XML配置文件。如果不考虑可移植性,那就采用DOM4J.

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

    3)SAX表现较好,这要依赖于它特定的解析方式-事件驱动。一个SAX检测即将到来的XML流,但并没有载入到内存(当然当XML流被读入时,会有部分文档暂时隐藏在内存中)。
   
    3. 四种xml操作方式的基本使用方法
    xml文件:
Xml代码
  1. <?xml version="1.0" encoding="gbk"?><list><node><name>weidewei</name><space>http://wishlife.javaeye.com</space></node><node><name>flying</name><space>http://user.qzone.qq.com/94611981</space></node></list>  

   
     程序代码:
Java代码
  1. import java.io.File;   
  2. import java.util.Iterator;   
  3. import java.util.List;   
  4.   
  5. import javax.xml.parsers.DocumentBuilder;   
  6. import javax.xml.parsers.DocumentBuilderFactory;   
  7. import javax.xml.parsers.SAXParser;   
  8. import javax.xml.parsers.SAXParserFactory;   
  9.   
  10. import org.dom4j.io.SAXReader;   
  11. import org.jdom.Element;   
  12. import org.jdom.input.SAXBuilder;   
  13. import org.w3c.dom.Document;   
  14. import org.w3c.dom.NodeList;   
  15. import org.xml.sax.Attributes;   
  16. import org.xml.sax.InputSource;   
  17. import org.xml.sax.SAXException;   
  18. import org.xml.sax.helpers.DefaultHandler;   
  19.   
  20. public class MyXMLReader extends DefaultHandler {   
  21.   
  22.     java.util.Stack tags = new java.util.Stack();   
  23.     public MyXMLReader() {   
  24.         super();   
  25.     }   
  26.     /**  
  27.      * DOM方式  
  28.     * @since V2.0  
  29.     * @author David.Wei  
  30.     * @date 2008-4-11  
  31.     * @return void  
  32.      */  
  33.     public void DOM() {   
  34.         long lasting = System.currentTimeMillis();   
  35.   
  36.         try {   
  37.             File f = new File("F:/xmltest.xml");   
  38.             DocumentBuilderFactory factory = DocumentBuilderFactory   
  39.                     .newInstance();   
  40.             DocumentBuilder builder = factory.newDocumentBuilder();   
  41.             Document doc = builder.parse(f);   
  42.             NodeList nl = doc.getElementsByTagName("node");   
  43.             for (int i = 0; i < nl.getLength(); i++) {   
  44.                 System.out.println("|| Name:  |"  
  45.                         + doc.getElementsByTagName("name").item(i)   
  46.                                 .getFirstChild().getNodeValue());   
  47.                 System.out.println("||Space:  |"  
  48.                         + doc.getElementsByTagName("space").item(i)   
  49.                                 .getFirstChild().getNodeValue());   
  50.                 System.out.println("-------------------------------------------------");            }   
  51.         } catch (Exception e) {   
  52.             e.printStackTrace();   
  53.         }   
  54.         System.out.println("DOM RUNTIME:"  
  55.                 + (System.currentTimeMillis() - lasting) + " MS");   
  56.     }   
  57.   
  58.        
  59.   
  60.     /**  
  61.      * SAX方式  
  62.     * @since V2.0  
  63.     * @author David.Wei  
  64.     * @date 2008-4-11  
  65.     * @return void  
  66.      */  
  67.     public void SAX() {   
  68.   
  69.         long lasting = System.currentTimeMillis();   
  70.         try {   
  71.             SAXParserFactory sf = SAXParserFactory.newInstance();   
  72.             SAXParser sp = sf.newSAXParser();   
  73.             MyXMLReader reader = new MyXMLReader();   
  74.             sp.parse(new InputSource("F:/xmltest.xml"), reader);   
  75.         } catch (Exception e) {   
  76.             e.printStackTrace();   
  77.         }   
  78.         System.out.println("SAX RUNTIME:"  
  79.                 + (System.currentTimeMillis() - lasting) + " MS");   
  80.     }   
  81.   
  82.     public void startElement(String uri, String localName, String qName,   
  83.             Attributes attrs) {   
  84.         tags.push(qName);   
  85.     }   
  86.   
  87.     public void characters(char ch[], int start, int length)   
  88.             throws SAXException {   
  89.         String tag = (String) tags.peek();   
  90.         if (tag.equals("name")) {   
  91.             System.out.println("|| Name:  |" + new String(ch, start, length));   
  92.         }   
  93.         if (tag.equals("space")) {   
  94.             System.out.println("||Space:  |" + new String(ch, start, length));   
  95.         }   
  96.         System.out.println("-------------------------------------------------");   
  97.     }   
  98.   
  99.     /**  
  100.      * JDOM方式  
  101.     * @since V2.0  
  102.     * @author David.Wei  
  103.     * @date 2008-4-11  
  104.     * @return void  
  105.      */  
  106.     public void JDOM() {   
  107.         long lasting = System.currentTimeMillis();   
  108.         try {   
  109.             SAXBuilder builder = new SAXBuilder();   
  110.             org.jdom.Document doc = builder.build(new File("F:/xmltest.xml"));   
  111.             Element foo = doc.getRootElement();   
  112.             List allChildren = foo.getChildren();   
  113.             for (int i = 0; i < allChildren.size(); i++) {   
  114.                 System.out.println("|| Name:  |"  
  115.                         + ((Element) allChildren.get(i)).getChild("name")   
  116.                                 .getText());   
  117.                 System.out.println("||Space:  |"  
  118.                         + ((Element) allChildren.get(i)).getChild("space")   
  119.                                 .getText());   
  120.                 System.out.println("-------------------------------------------------");            }   
  121.         } catch (Exception e) {   
  122.             e.printStackTrace();   
  123.         }   
  124.         System.out.println("JDOM RUNTIME:"  
  125.                 + (System.currentTimeMillis() - lasting) + " MS");   
  126.     }   
  127.   
  128.     /**  
  129.      * DOM4J方式  
  130.     * @since V2.0  
  131.     * @author David.Wei  
  132.     * @date 2008-4-11  
  133.     * @return void  
  134.      */  
  135.     public void DOM4J() {   
  136.         long lasting = System.currentTimeMillis();   
  137.         try {   
  138.             File f = new File("F:/xmltest.xml");   
  139.             SAXReader reader = new SAXReader();   
  140.             org.dom4j.Document doc = reader.read(f);   
  141.             org.dom4j.Element root = doc.getRootElement();   
  142.             org.dom4j.Element foo;   
  143.             for (Iterator i = root.elementIterator("node"); i.hasNext();) {   
  144.                 foo = (org.dom4j.Element) i.next();   
  145.                 System.out.println("|| Name:  |" + foo.elementText("name"));   
  146.                 System.out.println("||Space:  |" + foo.elementText("space"));   
  147.                 System.out.println("-------------------------------------------------");   
  148.             }   
  149.         } catch (Exception e) {   
  150.             e.printStackTrace();   
  151.         }   
  152.         System.out.println("DOM4J RUNTIME:"  
  153.                 + (System.currentTimeMillis() - lasting) + " MS");   
  154.     }   
  155.   
  156.     public static void main(String arge[]) {   
  157.         MyXMLReader myXML = new MyXMLReader();   
  158.         System.out.println("=====================DOM=========================");   
  159.         myXML.DOM();   
  160.         System.out.println("=====================SAX=========================");   
  161.         myXML.SAX();   
  162.         System.out.println("=====================JDOM========================");   
  163.         myXML.JDOM();   
  164.         System.out.println("=====================DOM4J=======================");   
  165.         myXML.DOM4J();   
  166.         System.out.println("=================================================");   
  167.     }   
  168. }  


运行结果:
Html代码
  1. =====================DOM=========================   
  2. || Name:  |weidewei   
  3. ||Space:  |http://wishlife.javaeye.com   
  4. -------------------------------------------------   
  5. || Name:  |flying   
  6. ||Space:  |http://user.qzone.qq.com/94611981   
  7. -------------------------------------------------   
  8. DOM RUNTIME:62 MS  
  9. =====================SAX=========================   
  10. || Name:  |weidewei   
  11. -------------------------------------------------   
  12. ||Space:  |http://wishlife.javaeye.com   
  13. -------------------------------------------------   
  14. || Name:  |flying   
  15. -------------------------------------------------   
  16. ||Space:  |http://user.qzone.qq.com/94611981   
  17. -------------------------------------------------   
  18. SAX RUNTIME:16 MS  
  19. =====================JDOM========================   
  20. || Name:  |weidewei   
  21. ||Space:  |http://wishlife.javaeye.com   
  22. -------------------------------------------------   
  23. || Name:  |flying   
  24. ||Space:  |http://user.qzone.qq.com/94611981   
  25. -------------------------------------------------   
  26. JDOM RUNTIME:78 MS  
  27. =====================DOM4J=======================   
  28. || Name:  |weidewei   
  29. ||Space:  |http://wishlife.javaeye.com   
  30. -------------------------------------------------   
  31. || Name:  |flying   
  32. ||Space:  |http://user.qzone.qq.com/94611981   
  33. -------------------------------------------------   
  34. DOM4J RUNTIME:78 MS  
  35. =================================================  
posted @ 2009-05-08 10:52 lanxin1020 阅读(166) | 评论 (0)编辑 收藏