随笔 - 63  文章 - 0  trackbacks - 0
<2009年4月>
2930311234
567891011
12131415161718
19202122232425
262728293012
3456789

常用链接

留言簿(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 阅读(571) | 评论 (0)编辑 收藏
posted @ 2009-05-21 22:13 lanxin1020 阅读(151) | 评论 (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 阅读(240) | 评论 (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 阅读(265) | 评论 (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 阅读(240) | 评论 (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 阅读(172) | 评论 (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 阅读(177) | 评论 (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 阅读(198) | 评论 (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 阅读(161) | 评论 (0)编辑 收藏
Java中的instanceof关键字 收藏
 instanceof是Java的一个二元操作符,和==,>,<是同一类东东。由于它是由字母组成的,所以也是Java的保留关键字。它的作用是测试它左边的对象是否是它右边的类的实例,返回boolean类型的数据。举个例子:

  String s = "I AM an Object!";
  boolean isObject = s instanceof Object;

  我们声明了一个String对象引用,指向一个String对象,然后用instancof来测试它所指向的对象是否是Object类的一个实例,显然,这是真的,所以返回true,也就是isObject的值为True。
  instanceof有一些用处。比如我们写了一个处理账单的系统,其中有这样三个类:

  public class Bill {//省略细节}
  public class PhoneBill extends Bill {//省略细节}
  public class GasBill extends Bill {//省略细节}

  在处理程序里有一个方法,接受一个Bill类型的对象,计算金额。假设两种账单计算方法不同,而传入的Bill对象可能是两种中的任何一种,所以要用instanceof来判断:

  public double calculate(Bill bill) {
  if (bill instanceof PhoneBill) {
  //计算电话账单
  }
  if (bill instanceof GasBill) {
  //计算燃气账单
  }
  ...
  }
  这样就可以用一个方法处理两种子类。

  然而,这种做法通常被认为是没有好好利用面向对象中的多态性。其实上面的功能要求用方法重载完全可以实现,这是面向对象变成应有的做法,避免回到结构化编程模式。只要提供两个名字和返回值都相同,接受参数类型不同的方法就可以了:

  public double calculate(PhoneBill bill) {
  //计算电话账单
  }

  public double calculate(GasBill bill) {
  //计算燃气账单
  }

  所以,使用instanceof在绝大多数情况下并不是推荐的做法,应当好好利用多态。
posted @ 2009-05-07 13:14 lanxin1020 阅读(166) | 评论 (0)编辑 收藏
  1. package cn.edu.tongji.cims.wade.system;   
  2.   
  3. import java.io.*;   
  4.   
  5. public class FileOperate {   
  6.   public FileOperate() {   
  7.   }   
  8.   
  9.   /**  
  10.    * 新建目录  
  11.    * @param folderPath String 如 c:/fqf  
  12.    * @return boolean  
  13.    */  
  14.   public void newFolder(String folderPath) {   
  15.     try {   
  16.       String filePath = folderPath;   
  17.       filePath = filePath.toString();   
  18.       java.io.File myFilePath = new java.io.File(filePath);   
  19.       if (!myFilePath.exists()) {   
  20.         myFilePath.mkdir();   
  21.       }   
  22.     }   
  23.     catch (Exception e) {   
  24.       System.out.println("新建目录操作出错");   
  25.       e.printStackTrace();   
  26.     }   
  27.   }   
  28.   
  29.   /**  
  30.    * 新建文件  
  31.    * @param filePathAndName String 文件路径及名称 如c:/fqf.txt  
  32.    * @param fileContent String 文件内容  
  33.    * @return boolean  
  34.    */  
  35.   public void newFile(String filePathAndName, String fileContent) {   
  36.   
  37.     try {   
  38.       String filePath = filePathAndName;   
  39.       filePath = filePath.toString();   
  40.       File myFilePath = new File(filePath);   
  41.       if (!myFilePath.exists()) {   
  42.         myFilePath.createNewFile();   
  43.       }   
  44.       FileWriter resultFile = new FileWriter(myFilePath);   
  45.       PrintWriter myFile = new PrintWriter(resultFile);   
  46.       String strContent = fileContent;   
  47.       myFile.println(strContent);   
  48.       resultFile.close();   
  49.   
  50.     }   
  51.     catch (Exception e) {   
  52.       System.out.println("新建目录操作出错");   
  53.       e.printStackTrace();   
  54.   
  55.     }   
  56.   
  57.   }   
  58.   
  59.   /**  
  60.    * 删除文件  
  61.    * @param filePathAndName String 文件路径及名称 如c:/fqf.txt  
  62.    * @param fileContent String  
  63.    * @return boolean  
  64.    */  
  65.   public void delFile(String filePathAndName) {   
  66.     try {   
  67.       String filePath = filePathAndName;   
  68.       filePath = filePath.toString();   
  69.       java.io.File myDelFile = new java.io.File(filePath);   
  70.       myDelFile.delete();   
  71.   
  72.     }   
  73.     catch (Exception e) {   
  74.       System.out.println("删除文件操作出错");   
  75.       e.printStackTrace();   
  76.   
  77.     }   
  78.   
  79.   }   
  80.   
  81.   /**  
  82.    * 删除文件夹  
  83.    * @param filePathAndName String 文件夹路径及名称 如c:/fqf  
  84.    * @param fileContent String  
  85.    * @return boolean  
  86.    */  
  87.   public void delFolder(String folderPath) {   
  88.     try {   
  89.       delAllFile(folderPath); //删除完里面所有内容   
  90.       String filePath = folderPath;   
  91.       filePath = filePath.toString();   
  92.       java.io.File myFilePath = new java.io.File(filePath);   
  93.       myFilePath.delete(); //删除空文件夹   
  94.   
  95.     }   
  96.     catch (Exception e) {   
  97.       System.out.println("删除文件夹操作出错");   
  98.       e.printStackTrace();   
  99.   
  100.     }   
  101.   
  102.   }   
  103.   
  104.   /**  
  105.    * 删除文件夹里面的所有文件  
  106.    * @param path String 文件夹路径 如 c:/fqf  
  107.    */  
  108.   public void delAllFile(String path) {   
  109.     File file = new File(path);   
  110.     if (!file.exists()) {   
  111.       return;   
  112.     }   
  113.     if (!file.isDirectory()) {   
  114.       return;   
  115.     }   
  116.     String[] tempList = file.list();   
  117.     File temp = null;   
  118.     for (int i = 0; i < tempList.length; i++) {   
  119.       if (path.endsWith(File.separator)) {   
  120.         temp = new File(path + tempList[i]);   
  121.       }   
  122.       else {   
  123.         temp = new File(path + File.separator + tempList[i]);   
  124.       }   
  125.       if (temp.isFile()) {   
  126.         temp.delete();   
  127.       }   
  128.       if (temp.isDirectory()) {   
  129.         delAllFile(path+"/"+ tempList[i]);//先删除文件夹里面的文件   
  130.         delFolder(path+"/"+ tempList[i]);//再删除空文件夹   
  131.       }   
  132.     }   
  133.   }   
  134.   
  135.   /**  
  136.    * 复制单个文件  
  137.    * @param oldPath String 原文件路径 如:c:/fqf.txt  
  138.    * @param newPath String 复制后路径 如:f:/fqf.txt  
  139.    * @return boolean  
  140.    */  
  141.   public void copyFile(String oldPath, String newPath) {   
  142.     try {   
  143.       int bytesum = 0;   
  144.       int byteread = 0;   
  145.       File oldfile = new File(oldPath);   
  146.       if (oldfile.exists()) { //文件存在时   
  147.         InputStream inStream = new FileInputStream(oldPath); //读入原文件   
  148.         FileOutputStream fs = new FileOutputStream(newPath);   
  149.         byte[] buffer = new byte[1444];   
  150.         int length;   
  151.         while ( (byteread = inStream.read(buffer)) != -1) {   
  152.           bytesum += byteread; //字节数 文件大小   
  153.           System.out.println(bytesum);   
  154.           fs.write(buffer, 0, byteread);   
  155.         }   
  156.         inStream.close();   
  157.       }   
  158.     }   
  159.     catch (Exception e) {   
  160.       System.out.println("复制单个文件操作出错");   
  161.       e.printStackTrace();   
  162.   
  163.     }   
  164.   
  165.   }   
  166.   
  167.   /**  
  168.    * 复制整个文件夹内容  
  169.    * @param oldPath String 原文件路径 如:c:/fqf  
  170.    * @param newPath String 复制后路径 如:f:/fqf/ff  
  171.    * @return boolean  
  172.    */  
  173.   public void copyFolder(String oldPath, String newPath) {   
  174.   
  175.     try {   
  176.       (new File(newPath)).mkdirs(); //如果文件夹不存在 则建立新文件夹   
  177.       File a=new File(oldPath);   
  178.       String[] file=a.list();   
  179.       File temp=null;   
  180.       for (int i = 0; i < file.length; i++) {   
  181.         if(oldPath.endsWith(File.separator)){   
  182.           temp=new File(oldPath+file[i]);   
  183.         }   
  184.         else{   
  185.           temp=new File(oldPath+File.separator+file[i]);   
  186.         }   
  187.   
  188.         if(temp.isFile()){   
  189.           FileInputStream input = new FileInputStream(temp);   
  190.           FileOutputStream output = new FileOutputStream(newPath + "/" +   
  191.               (temp.getName()).toString());   
  192.           byte[] b = new byte[1024 * 5];   
  193.           int len;   
  194.           while ( (len = input.read(b)) != -1) {   
  195.             output.write(b, 0, len);   
  196.           }   
  197.           output.flush();   
  198.           output.close();   
  199.           input.close();   
  200.         }   
  201.         if(temp.isDirectory()){//如果是子文件夹   
  202.           copyFolder(oldPath+"/"+file[i],newPath+"/"+file[i]);   
  203.         }   
  204.       }   
  205.     }   
  206.     catch (Exception e) {   
  207.       System.out.println("复制整个文件夹内容操作出错");   
  208.       e.printStackTrace();   
  209.   
  210.     }   
  211.   
  212.   }   
  213.   
  214.   /**  
  215.    * 移动文件到指定目录  
  216.    * @param oldPath String 如:c:/fqf.txt  
  217.    * @param newPath String 如:d:/fqf.txt  
  218.    */  
  219.   public void moveFile(String oldPath, String newPath) {   
  220.     copyFile(oldPath, newPath);   
  221.     delFile(oldPath);   
  222.   
  223.   }   
  224.   
  225.   /**  
  226.    * 移动文件到指定目录  
  227.    * @param oldPath String 如:c:/fqf.txt  
  228.    * @param newPath String 如:d:/fqf.txt  
  229.    */  
  230.   public void moveFolder(String oldPath, String newPath) {   
  231.     copyFolder(oldPath, newPath);   
  232.     delFolder(oldPath);   
  233.   
  234.   }   
  235. }  
posted @ 2009-05-07 11:16 lanxin1020 阅读(390) | 评论 (0)编辑 收藏

        如果在Java程序中你使用Java Native Interface(JNI) 来调用某个特定平台下的本地库文件,你就会发现这个过程很单调、乏味。Jeff Friesen一直在介绍一个知名度很低的Java开源项目:Java Native Access---它能够避免因使用JNI导致的错误和乏味,同时它还能让你通过编程的方式调用C语言库。

        在Java语言没有提供必要的APIs的情况下,Java程序使用Java Native Interface (JNI)来调用特定平台下的本地库是必要的。例如:在Windows XP平台中,我使用过JNI来调用通用串行总线和基于TWAIN的扫描仪器的库;在更古老的Windows NT平台中,调用过智能卡的库。

        我按照一个基本的、乏味的流程来解决这些问题:首先,我创建一个Java类用来载入JNI-friendly库(这个库能过访问其他的库)并且声明这个类的本地方法。然后,在使用JDK中的javah工具为JNI-friendly库中的函数---函数和这个类中的本地方法一一对应---创建一个代理。最后,我使用C语言写了一个库并用C编译器编译了这些代码。

        尽管完成这些流程并不是很困难,但是写C代码是一个很缓慢的过程---例如: C语言中的字符串处理是通过指针来实现的,这会很复杂的。而且,使用JNI很容易出现错误,导致内存泄漏、很难找到程序崩溃的原因。

        在Java开源系列的第二篇文章中,我要介绍一个更简单、更安全的解决方法:Todd Fast and Timothy Wall的Java Native Access (JNA) 项目。JNA能够让你在Java程序中调用本地方法时避免使用C和Java Native Interface。在这篇文章中,让我以简要的介绍        JNA和运行示例必需的软件来开始下面的内容。然后,向你展示如何使用JNA将3个Windows本地库中的有用代码移植到Java程序中。

Get started with JNA(JNA入门)

Java Native Access 项目 在Java.net上,你可以到这个网站上现在这个项目的代码和在线帮助文档。虽然在下载有5个相关的jar文件,在本文中你仅仅需要下载其中的jna.jar和example.jar。

Jna.jar提供基本的、运行这些示例文件必需的jna运行环境。这个jna.jar文件除了有Unix、Linux、Windows和Mac OS X平台相关的JNT-friendly本地库外,还包含其他几个类包。每一个本地库都是用来访问相对应平台下的本地方法的。

        example.jar包含了不同的示例来表明JNA的用途。其中的一个例子是使用JNA来实现一个在不同平台下的透明视窗技术的API。在文章最后的示例中将要展示如何使用这个API修复上个月的文章关于VerifyAge2应用中辨认透明效果的问题。

获取本地时间(Get local time)

如果你在Java Native Access 首页 看过“JNA如何入门”,你就会知道一个很简单的关于调用Windows 平台下的API函数:GetSystemTime() 的JNA示例。这个不完整的例子只是展示了JNA的基本特点。(在例子的基础上,我做了一个更完整的基于Windows的例子来介绍JNA)我在Windows平台下完善了这个例子来介绍JNA。

第一例子基于Windows GetLocalTime() API函数返回本地当前的时间和日期。和GetSystemTime()不同的是,返回的时间/日期是协调通用时间(UTC)格式的,GetLocalTime()返回的时间/日期信息的格式是根据当前时区来表示。

在一个Java程序中使用JNA调用GetLocalTime,你需要知道这个函数所在的Windows平台下的动态链接库(DLL)的名称(和可能所在的地理区域)。我们发现GetLocalTime()和GetSystemTime在同一个DLL文件中:kernel32.dll。你还需要知道GetLocalTime()在C语言环境中的申明。申明如下Listing 1:

Listing 1. GetLocalTime在C语言中的申明

typedef struct
{
   WORD wYear;
   WORD wMonth;
   WORD wDayOfWeek;
   WORD wDay;
   WORD wHour;
   WORD wMinute;
   WORD wSecond;
   WORD wMilliseconds;
}
SYSTEMTIME, *LPSYSTEMTIME;

VOID GetLocalTime(LPSYSTEMTIME lpst);


这个基于C语言的申明表明传到这个函数的参数数目和类型。在这个例子中,只有一个参数---一个指向Windows SYSTEMTIME结构体的指针。而且,每个结构体成员的类型是16bit长度的无符号整型。根据这些信息,你能够创建一个完全描述GetLocalTime()函数的接口,如Listing 2中所示:

Listing 2. Kernel32.java

// Kernel32.java

import com.sun.jna.*;
import com.sun.jna.win32.*;

public interface Kernel32 extends StdCallLibrary
{
   public static class SYSTEMTIME extends Structure
   {
      public short wYear;
      public short wMonth;
      public short wDayOfWeek;
      public short wDay;
      public short wHour;
      public short wMinute;
      public short wSecond;
      public short wMilliseconds;
   }

   void GetLocalTime (SYSTEMTIME result);
}


Kernel32 接口(The Kernel32 interface)

因为JNA使用通过一个接口来访问某个库中的函数,Listing 2表示了一个描述GetLocalTime()的接口。根据约定,我把接口命名为Kernel32是因为GetLocalTime()在Windows的kernel32.dll库。

这个接口必须继承com.sun..jna.Library接口。因为Windows API函数遵循stdcall调用协议(stdcall calling convention),为Windows API申明的接口也必须继承com.sun.jna.win32. StdCallLibrary接口。因此这个接口共继承了Library 和 com.sun.jna.win32.StdCall两个接口。

在前面,你已经知道了GetLocalTime() 需要一个指向SYSTEMTIME结构体的指针作为它唯一的参数。因为Java不支持指针,JNA是通过申明一个com.sun.jna.Structure的子类来代替的。根据java文档中抽象类的概念,在参数环境中,Structure相当于C语言的struct*。

在SYSTEMTIME类中的字段和C结构体中的相对应的属性字段的顺序是一一对应的。保证字段顺序的一致性是非常重要的。例如,我发现交换wYear和wMonth会导致wYear和wMonth值互换。

每个字段在java中是short integer类型的。按照JNA首页上 “默认类型映射”章节给出的提示,这个short integer分配类型是正确。然而,我们应该知道一个重要的区别:Windows平台下的WORD类型等同于C语言环境中的16-bit的无符号的short integer,而java中short integer是16-bit有符号的short integer。

一个类型映射的问题

通过比较一个API 函数返回的整型值,你会发现Windows/C 语言的无符号整型和Java语言的有符号整型的JNA类型映射是有问题的。在比较的过程中,如果你不细心,那么错误的执行过程可能导致决定性情况。导致这种后果是因为忘记任何数值的符号位的确定是根据:在无符号整型的情况下会被解释为正号,而在有符号整型的进制中被理解为负号的。

通过Kernel32获取本地时间(Access the local time with Kernel32)

JNA首页上的GetSystemTime()示例已经表明必须使用预先申明的接口为本地库分配一个实例对象。你可以通过com.sun.jna.Native类中静态公用方法loadLibrary(String name, Class interfaceClass)来完成上述的目标。Listing 3 所示:

Listing 3. LocalTime.java

// LocalTime.java

import com.sun.jna.*;

public class LocalTime
{
   public static void main (String [] args)
   {
      Kernel32 lib = (Kernel32) Native.loadLibrary ("kernel32",
                                                    Kernel32.class);
      Kernel32.SYSTEMTIME time = new Kernel32.SYSTEMTIME ();
      lib.GetLocalTime (time);
      System.out.println ("Year is "+time.wYear);
      System.out.println ("Month is "+time.wMonth);
      System.out.println ("Day of Week is "+time.wDayOfWeek);
      System.out.println ("Day is "+time.wDay);
      System.out.println ("Hour is "+time.wHour);
      System.out.println ("Minute is "+time.wMinute);
      System.out.println ("Second is "+time.wSecond);
      System.out.println ("Milliseconds are "+time.wMilliseconds);
   }
}


Listing 3 执行Kernel32 lib = (Kernel32) Native.loadLibrary ("kernel32", Kernel32.class);来分配一个Kernel32实例对象并且装载kernel32.dll。因为kernel32.dll是Windows平台下标准的dll文件,所以不要指定访问这个库的路径。然而,如果找不到这个dll文件,loadLibrary()会抛出一个UnsatisfiedLinkError异常。

Kernel32.SYSTEMTIME time = new Kernel32.SYSTEMTIME ();创建了一个SYSTEMTIME结构体的示例。初始化后下面是lib.GetLocalTime (time);,这句话使用本地的时间/日期来给这个实例赋值。几个System.out.println()语句是输出这些值。

编译和运行这个应用(Compile and run the application)

这部分很容易。假设jna.jar、Kernel32.java和LocalTime.java是放在当前文件夹中,调用java –cp jna.jar;. LocalTime.java来编译这个应用的源代码。如果在Windows平台下,调用invoke java –cp jna.jar;. LocalTime 来运行这个应用。你可以得到类似与Listing 4的输出结果:

Listing 4. 从LocalTime.java生成的输出

Year is 2007
Month is 12
Day of Week is 3
Day is 19
Hour is 12
Minute is 35
Second is 13
Milliseconds are 156


获取操纵杆信息(Accessing joystick device info)

上面的例子已经介绍了JNA,但是这个获取本地时间和日期的例子并没有很好的利用这个技术,甚至也没有体现JNI的价值。Java语言中的System.currentTimeMillis()函数已经以毫秒的格式返回了这些信息。因为Java语言没有为游戏控制器提供API,所以获取操纵杆的信息更适合JNA的使用。

例如,你要构建一个平台无关的Java库,而且这些库使用JNA调用Linux, Mac OS X, Windwos和Unix平台中本地的操纵杆API。为了简洁和方便起见,这个例子仅仅是调用Windows平台下的操纵杆API。而且我将重点介绍这个API很小的一部分。

类似GetLocalTime(),第一步是辨别出操作杆API的DLL,这个DLL是winmm.dll,和kernel32.dll在同一个文件夹中,它包含了操作杆的API和其他的多媒体APIs。还需知道要被使用的操作杆函数基于C语言的声明。这些函数声明已经在Listing 5中列出来了。

Listing 5. C-based declarations for some Joystick API functions

#define MAXPNAMELEN 32

typedef struct
{
   WORD  wMid;                  // manufacturer identifier
   WORD  wPid;                  // product identifier
   TCHAR szPname  MAXPNAMELEN ; // product name
   UINT  wXmin;                 // minimum x position
   UINT  wXmax;                 // maximum x position
   UINT  wYmin;                 // minimum y position
   UINT  wYmax;                 // maximum y position
   UINT  wZmin;                 // minimum z position
   UINT  wZmax;                 // maximum z position
   UINT  wNumButtons;           // number of buttons
   UINT  wPeriodMin;            // smallest supported polling interval when captured
   UINT  wPeriodMax;            // largest supported polling interval when captured
}
JOYCAPS, *LPJOYCAPS;

MMRESULT joyGetDevCaps(UINT IDDevice, LPJOYCAPS lpjc, UINT cbjc);

UINT joyGetNumDevs(VOID);


操作杆API的函数(Functions of the Joystick API)

在Windows平台下是通过以joy作为函数名开始的函数以及被各种函数调用的结构体来实现操作杆API的。例如,joyGetNumDevs()返回的是这个平台下支持的操作杆设备最多的数目;joyGetDevCaps()返回的是每个连接上的操纵杆的质量。

joyGetDevCaps()函数需要3个参数:
* 处在0到joyGetNumDevs()-1之间的操作杆ID
* 保存返回的质量信息的JOYCAPS结构体的地址
* JOYCAPS结构体的字节大小
虽然它的结果不同,这个函数返回的是一个32位的无符号整型结果,而且0表示一个已经连接的操纵杆。

JOYCAPS结构体有3种类型。Windows平台下的WORD(16位无符号短整型)类型对应的是Java语言中16位有符号短整型。除此之外,Windows下的UINT(32位无符号整型)类型是和Java语言中32位有符号整型相对应的。而Windows平台上的text character就是TCHAR类型。

微软通过TCHAR类型使开发人员能够从ASCII类型的函数参数平滑的转移到Unicode字符类型的函数参数上。而且,拥有text类型参数的函数的实现是通过宏转变为对应的ASCII或者wide-character的函数。例如,joyGetDevCaps()是一个对应joyGetDevCapsA() 和 joyGetDevCapsW()的宏。

使用TCHAR(Working with TCHAR)

使用TCHAR和将TCHAR转变的宏会导致基于C语言的申明向基于JNA接口的转换
变得有点复杂—你在使用ASCII或者wide-character版本的操纵杆函数吗?两种版本都在如下的接口中展示了:

Listing 6. WinMM.java

// WinMM.java

import com.sun.jna.*;
import com.sun.jna.win32.*;

public interface WinMM extends StdCallLibrary
{
   final static int JOYCAPSA_SIZE = 72;

   public static class JOYCAPSA extends Structure
   {
      public short wMid;
      public short wPid;
      public byte szPname [] = new byte [32];
      public int wXmin;
      public int wXmax;
      public int wYmin;
      public int wYmax;
      public int wZmin;
      public int wZmax;
      public int wNumButtons;
      public int wPeriodMin;
      public int wPeriodMax;
   }

   int joyGetDevCapsA (int id, JOYCAPSA caps, int size);

   final static int JOYCAPSW_SIZE = 104;

   public static class JOYCAPSW extends Structure
   {
      public short wMid;
      public short wPid;
      public char szPname [] = new char [32];
      public int wXmin;
      public int wXmax;
      public int wYmin;
      public int wYmax;
      public int wZmin;
      public int wZmax;
      public int wNumButtons;
      public int wPeriodMin;
      public int wPeriodMax;
   }

   int joyGetDevCapsW (int id, JOYCAPSW caps, int size);

   int joyGetNumDevs ();
}


Listing 6没有介绍JNA的新特性。实际上,JNA强调了对本地库的接口命名规则。同时,还展示了如何将TCHAR映射到Java语言中的byte和char数组。最后,它揭示了以常量方式声明的结构体的大小。Listing 7展示了当调用joyGetDevCapsA() 和 joyGetDevCapsW()时如何使用这些常量。

Listing 7. JoystickInfo.java

// JoystickInfo.java

import com.sun.jna.*;

public class JoystickInfo
{
   public static void main (String [] args)
   {
      WinMM lib = (WinMM) Native.loadLibrary ("winmm", WinMM.class);
      int numDev = lib.joyGetNumDevs ();

      System.out.println ("joyGetDevCapsA() Demo");
      System.out.println ("---------------------\n");

      WinMM.JOYCAPSA caps1 = new WinMM.JOYCAPSA ();
      for (int i = 0; i < numDev; i++)
           if (lib.joyGetDevCapsA (i, caps1, WinMM.JOYCAPSA_SIZE) == 0)
           {
               String pname = new String (caps1.szPname);
               pname = pname.substring (0, pname.indexOf ('\0'));
               System.out.println ("Device #"+i);
               System.out.println ("  wMid = "+caps1.wMid);
               System.out.println ("  wPid = "+caps1.wPid);
               System.out.println ("  szPname = "+pname);
               System.out.println ("  wXmin = "+caps1.wXmin);
               System.out.println ("  wXmax = "+caps1.wXmax);
               System.out.println ("  wYmin = "+caps1.wYmin);
               System.out.println ("  wYmax = "+caps1.wYmax);
               System.out.println ("  wZmin = "+caps1.wZmin);
               System.out.println ("  wZmax = "+caps1.wZmax);
               System.out.println ("  wNumButtons = "+caps1.wNumButtons);
               System.out.println ("  wPeriodMin = "+caps1.wPeriodMin);
               System.out.println ("  wPeriodMax = "+caps1.wPeriodMax);
               System.out.println ();
           }

      System.out.println ("joyGetDevCapsW() Demo");
      System.out.println ("---------------------\n");

      WinMM.JOYCAPSW caps2 = new WinMM.JOYCAPSW ();
      for (int i = 0; i < numDev; i++)
           if (lib.joyGetDevCapsW (i, caps2, WinMM.JOYCAPSW_SIZE) == 0)
           {
               String pname = new String (caps2.szPname);
               pname = pname.substring (0, pname.indexOf ('\0'));
               System.out.println ("Device #"+i);
               System.out.println ("  wMid = "+caps2.wMid);
               System.out.println ("  wPid = "+caps2.wPid);
               System.out.println ("  szPname = "+pname);
               System.out.println ("  wXmin = "+caps2.wXmin);
               System.out.println ("  wXmax = "+caps2.wXmax);
               System.out.println ("  wYmin = "+caps2.wYmin);
               System.out.println ("  wYmax = "+caps2.wYmax);
               System.out.println ("  wZmin = "+caps2.wZmin);
               System.out.println ("  wZmax = "+caps2.wZmax);
               System.out.println ("  wNumButtons = "+caps2.wNumButtons);
               System.out.println ("  wPeriodMin = "+caps2.wPeriodMin);
               System.out.println ("  wPeriodMax = "+caps2.wPeriodMax);
               System.out.println ();
           }
   }
}

尽管和LocalTime这个示例类似,JoystickInfo执行WinMM lib = (WinMM) Native.loadLibrary ("winmm", WinMM.class);这句话来获取一个WinMM的实例,并且载入winmm.dll。它还执行WinMM.JOYCAPSA caps1 = new WinMM.JOYCAPSA (); 和 WinMM.JOYCAPSW caps2 = new WinMM.JOYCAPSW ();初始化必需的结构体实例。

编译和运行这个程序(Compile and run the application)

假如jna.jar,WinMM.java和JoystickInfo.java在同一个文件夹中,调用 javac -cp jna.jar;. JoystickInfo.java 来编译这个应用的源代码。
在windows平台下,调用java -cp jna.jar;. JoystickInfo就可以运行这个应用程序了。如果没有操纵杆设备,你应该得到Listing 8中的输出。

将C语言中的string类型转换为Java语言的String类型

pname = pname.substring (0, pname.indexOf ('\0')); 这段代码将一个C string 转换成了Java string. 如果不使用这个转换,C语言的string结束符’\0’和string后面的无用字符都会成为Java语言中String实例对象的内容。

Listing 8. 输出操纵杆信息(Output of JoystickInfo)

joyGetDevCapsA() Demo
---------------------

joyGetDevCapsW() Demo
---------------------


上面的输出是因为每次调用joyGetDevCap()返回的是一个非空值,这表示没有操纵杆/游戏控制器设备或者是出现错误。为了获取更多有意思的输出,将一个设备连接到你的平台上并且再次运行JoystickInfo。如下,将一个微软SideWinder即插即用游戏触摸板设备联上之后我获取了如下的输出:

Listing 9. 操纵杆连接上之后的运行结果(Output after running JoystickInfo with a joystick attached)

joyGetDevCapsA() Demo
---------------------

Device #0
  wMid = 1118
  wPid = 39
  szPname = Microsoft PC-joystick driver
  wXmin = 0
  wXmax = 65535
  wYmin = 0
  wYmax = 65535
  wZmin = 0
  wZmax = 65535
  wNumButtons = 6
  wPeriodMin = 10
  wPeriodMax = 1000

joyGetDevCapsW() Demo
---------------------

Device #0
  wMid = 1118
  wPid = 39
  szPname = Microsoft PC-joystick driver
  wXmin = 0
  wXmax = 65535
  wYmin = 0
  wYmax = 65535
  wZmin = 0
  wZmax = 65535
  wNumButtons = 6
  wPeriodMin = 10
  wPeriodMax = 1000


窗口透明度(Transparent windows)

在这系列文章中上篇文章是关于Bernhard Pauler's 气泡提示(balloontip)工程的。我构建了一个叫做VerifyAge的、包含有一个气泡提示的GUI应用。Figure 1中显示了这个GUI应用的一个小问题:这个气泡提示的没有经过修饰的对话框部分遮住了应用窗口的一部分边框,导致了无法点击这个边框的最小化和最大化按钮,并且使整个GUI很难看.
image
尽管未修饰部分的对话框不能显示气泡提示的透明度,java语言不支持窗口透明度。幸运的是,我们可以通过使用com.sun.jna.examples.WindowUtils类调用JNA的examples.jar文件来解决这个问题。
WindowUtils提供在Unix,Linux,Mac OS X和Windows平台上使用JNA’s来实现窗口透明的工具方法。例如, public static void setWindowMask(final Window w, Icon mask) 让你根据像素而不是通过预定的掩罩(mask)参数来选取某部分的窗口。这个功能将在Listing 10中展示:

Listing 10. Using JNA to render a window transparent


// Create a mask for this dialog. This mask has the same shape as the
// dialog's rounded balloon tip and ensures that only the balloon tip
// part of the dialog will be visible. All other dialog pixels will
// disappear because they correspond to transparent mask pixels.

// Note: The drawing code is based on the drawing code in
// RoundedBalloonBorder.

Rectangle bounds = getBounds ();
BufferedImage bi = new BufferedImage (bounds.width, bounds.height,
                                      BufferedImage.TYPE_INT_ARGB);
Graphics g = bi.createGraphics ();
g.fillRoundRect (0, 0, bounds.width, bounds.height-VERT_OFFSET,
                 ARC_WIDTH*2, ARC_HEIGHT*2);
g.drawRoundRect (0, 0, bounds.width-1, bounds.height-VERT_OFFSET-1,
                 ARC_WIDTH*2, ARC_HEIGHT*2);
int [] xPoints = { HORZ_OFFSET, HORZ_OFFSET+VERT_OFFSET, HORZ_OFFSET };
int [] yPoints = { bounds.height-VERT_OFFSET-1, bounds.height-VERT_OFFSET
                   -1, bounds.height-1 };
g.fillPolygon (xPoints, yPoints, 3);
g.drawLine (xPoints [0], yPoints [0], xPoints [2], yPoints [2]);
g.drawLine (xPoints [1], yPoints [1], xPoints [2], yPoints [2]);
g.dispose ();
WindowUtils.setWindowMask (this, new ImageIcon (bi));


在Listing 10中的代码段是从本文代码文档(code archive)里的加强版的VerifyAge2 应用中的TipFrame的构造函数结尾部分摘录的。这个构造函数定义了围绕提示气泡的掩罩(mask)的形状,在这个形状范围里描绘不透明的像素。
假如你当前文件夹中有examples.jar, jna.jar, 和 VerifyAge2.java,调用 javac -cp examples.jar;balloontip.jar VerifyAge2.java 来编译源文件.然后调用java -Dsun.java2d.noddraw=true -cp examples.jar;balloontip.jar;. VerifyAge2运行这个应用. Figure 2 展示了透明示例.
image

总结(In conclusion)

JNA项目有很长的历史了(追溯到1999年),但是它第一次发布是在2006年11月。从此以后它慢慢的被需要将本地C代码整合到Java工程中的开发者注意到了。因为JNA能够用来解决JuRuby中常见一个问题:缺乏对POSIX调用的支持(lack of support for POSIX calls),它也在JRuby程序员中掀起些波浪。JNA也同样被作为实现用低级C代码继承Ruby的一种解决方案(extending Ruby with low-level C code)。
我喜欢使用JNA来工作,相信你也会发现它比使用JNI来访问本地代码更简单、更安全。无需多言,JNA还有更多的特性在本文中没有体现出来。查阅它的资源部分:获取这个开源java项目更多的信息(learn more about this open source Java project)。用它做demo,而且在论坛(discussion forum )上共享你的经验。 下一个月我会带着另一个开源项目回来的,这个开源项目会给你每天的java开发带来益处。

附录:WindowUtils.setWindowMask()的替代品

在刚刚写完这篇文章后,我发现java语言支持在6u10版本中支持窗口的透明和形状定制。读完Kirill Grouchnikov的博客后,我用WindowUtils.setWindowMask()的替代品修改了VerifyAge2,如下:
// Create and install a balloon tip shape to ensure that only this part
// of the dialog will be visible.

Rectangle bounds = getBounds ();
GeneralPath gp;
gp = new GeneralPath (new RoundRectangle2D.Double (bounds.x, bounds.y,
                                                   bounds.width,
                                                   bounds.height-
                                                   VERT_OFFSET,
                                                   ARC_WIDTH*2-1,
                                                   ARC_HEIGHT*2-1));
gp.moveTo (HORZ_OFFSET, bounds.height-VERT_OFFSET);
gp.lineTo (HORZ_OFFSET, bounds.height);
gp.lineTo (HORZ_OFFSET+VERT_OFFSET+1, bounds.height-VERT_OFFSET);
AWTUtilities.setWindowShape (this, gp);


这段代码使用新类AWTUtilities(在com.sun.awt包中),而且public void setWindowShape(Window w, Shape s)函数将TipFrame和JDialog窗口设置气泡形状。
posted @ 2009-05-07 11:08 lanxin1020 阅读(806) | 评论 (0)编辑 收藏
Java代码
  1. 简单介绍及应用如下:    
  2.   一、JAVA中所需要做的工作    
  3.   在JAVA程序中,首先需要在类中声明所调用的库名称,如下:    
  4.   
  5. static {    
  6. System.loadLibrary(“goodluck”);    
  7. }   
  8.   
  9.   
  10.   在这里,库的扩展名字可以不用写出来,究竟是DLL还是SO,由系统自己判断。    
  11.   
  12.   还需对将要调用的方法做本地声明,关键字为native。且只需要声明,而不需要具体实现。如下:    
  13.   
  14. public native static void set(int i);    
  15. public native static int get();   
  16.   
  17.   
  18.   然后编译该JAVA程序文件,生成CLASS,再用JAVAH命令,JNI就会生成C/C++的头文件。    
  19.   
  20.   例如程序testdll.java,内容为:    
  21.   
  22. public class testdll    
  23. {    
  24. static    
  25. {    
  26. System.loadLibrary("goodluck");    
  27. }    
  28. public native static int get();    
  29. public native static void set(int i);    
  30. public static void main(String[] args)    
  31. {    
  32. testdll test = new testdll();    
  33. test.set(10);    
  34. System.out.println(test.get());    
  35. }    
  36. }   
  37.   
  38.   
  39.   用javac testdll.java编译它,会生成testdll.class。    
  40.   再用javah testdll,则会在当前目录下生成testdll.h文件,这个文件需要被C/C++程序调用来生成所需的库文件。    
  41.   
  42.   二、C/C++中所需要做的工作    
  43.   对于已生成的.h头文件,C/C++所需要做的,就是把它的各个方法具体的实现。然后编译连接成库文件即可。再把库文件拷贝到JAVA程序的路径下面,就可以用JAVA调用C/C++所实现的功能了。    
  44.   接上例子。我们先看一下testdll.h文件的内容:    
  45.   
  46. /* DO NOT EDIT THIS FILE - it is machine generated */    
  47. #include    
  48. /* Header for class testdll */    
  49. #ifndef _Included_testdll    
  50. #define _Included_testdll    
  51. #ifdef __cplusplus    
  52. extern "C" {    
  53. #endif    
  54. /*   
  55. * Class: testdll   
  56. * Method: get   
  57. * Signature: ()I   
  58. */    
  59. JNIEXPORT jint JNICALL Java_testdll_get (JNIEnv *, jclass);    
  60. /*   
  61. * Class: testdll   
  62. * Method: set   
  63. * Signature: (I)V   
  64. */    
  65. JNIEXPORT void JNICALL Java_testdll_set (JNIEnv *, jclass, jint);    
  66. #ifdef __cplusplus    
  67. }    
  68. #endif    
  69. #endif   
  70.   
  71.   
  72.   在具体实现的时候,我们只关心两个函数原型    
  73.   JNIEXPORT jint JNICALL Java_testdll_get (JNIEnv *, jclass); 和    
  74.   JNIEXPORT void JNICALL Java_testdll_set (JNIEnv *, jclass, jint);   
  75.   
  76.   这里JNIEXPORT和JNICALL都是JNI的关键字,表示此函数是要被JNI调用的。而jint是以JNI为中介使JAVA的int类型与本 地的int沟通的一种类型,我们可以视而不见,就当做int使用。函数的名称是JAVA_再加上java程序的package路径再加函数名组成的。参数 中,我们也只需要关心在JAVA程序中存在的参数,至于JNIEnv*和jclass我们一般没有必要去碰它。    
  77.   
  78.   好,下面我们用testdll.cpp文件具体实现这两个函数:    
  79.   
  80. #include "testdll.h"    
  81. int i = 0;    
  82. JNIEXPORT jint JNICALL Java_testdll_get (JNIEnv *, jclass)    
  83. {    
  84. return i;    
  85. }    
  86. JNIEXPORT void JNICALL Java_testdll_set (JNIEnv *, jclass, jint j)    
  87. {    
  88. i = j;    
  89. }   
  90.   
  91.   
  92.   编译连接成库文件,本例是在WINDOWS下做的,生成的是DLL文件。并且名称要与JAVA中需要调用的一致,这里就是goodluck.dll 。把goodluck.dll拷贝到testdll.class的目录下,java testdll运行它,就可以观察到结果了。    
  93.   我的项目比较复杂,需要调用动态链接库,这样在JNI传送参数到C程序时,需要对参数进行处理转换。才可以被C程序识别。   
  94.   大体程序如下:   
  95.   
  96. public class SendSMS {    
  97. static    
  98. {    
  99.   
  100.   
  101.     
  102. System.out.println(System.getProperty("java.library.path"));    
  103. System.loadLibrary("sms");    
  104. }    
  105. public native static int SmsInit();    
  106. public native static int SmsSend(byte[] mobileNo, byte[] smContent);    
  107. }    
  108.   
  109.   在这里要注意的是,path里一定要包含类库的路径,否则在程序运行时会抛出异常:   
  110.   java.lang.UnsatisfiedLinkError: no sms in java.library.path   
  111.   at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1491)   
  112.   at java.lang.Runtime.loadLibrary0(Runtime.java:788)   
  113.   at java.lang.System.loadLibrary(System.java:834)   
  114.   at com.mobilesoft.sms.mobilesoftinfo.SendSMS.(SendSMS.java:14)   
  115.   at com.mobilesoft.sms.mobilesoftinfo.test.main(test.java:18)   
  116.   Exception in thread "main"  
  117.   
  118.   指引的路径应该到.dll文件的上一级,如果指到.dll,则会报:   
  119.   java.lang.UnsatisfiedLinkError: C:\sms.dll: Can't find dependent libraries   
  120.   at java.lang.ClassLoader$NativeLibrary.load(Native Method)   
  121.   at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1560)   
  122.   at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1485)   
  123.   at java.lang.Runtime.loadLibrary0(Runtime.java:788)   
  124.   at java.lang.System.loadLibrary(System.java:834)   
  125.   at com.mobilesoft.sms.mobilesoftinfo.test.main(test.java:18)   
  126.   Exception in thread "main"  
  127.   
  128.   通过编译,生成com_mobilesoft_sms_mobilesoftinfo_SendSMS.h头文件。(建议使用Jbuilder进行编 译,操作比较简单!)这个头文件就是Java和C之间的纽带。要特别注意的是方法中传递的参数jbyteArray,这在接下来的过程中会重点介绍。   
  129.   
  130. /* DO NOT EDIT THIS FILE - it is machine generated */    
  131. #include    
  132. /* Header for class com_mobilesoft_sms_mobilesoftinfo_SendSMS */    
  133. #ifndef _Included_com_mobilesoft_sms_mobilesoftinfo_SendSMS    
  134. #define _Included_com_mobilesoft_sms_mobilesoftinfo_SendSMS    
  135. #ifdef __cplusplus    
  136. extern "C" {    
  137. #endif    
  138. /*   
  139. * Class: com_mobilesoft_sms_mobilesoftinfo_SendSMS   
  140. * Method: SmsInit   
  141. * Signature: ()I   
  142. */    
  143. JNIEXPORT jint JNICALL Java_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsInit    
  144. (JNIEnv *, jclass);    
  145. /*   
  146. * Class: com_mobilesoft_sms_mobilesoftinfo_SendSMS   
  147. * Method: SmsSend   
  148. * Signature: ([B[B)I   
  149. */    
  150. JNIEXPORT jint JNICALL Java_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsSend    
  151. (JNIEnv *, jclass, jbyteArray, jbyteArray);    
  152. #ifdef __cplusplus    
  153. }    
  154. #endif    
  155. #endif   
  156.   
  157.   
  158.   
  159.   对于我要调用的C程序的动态链接库,C程序也要提供一个头文件,sms.h。这个文件将要调用的方法罗列了出来。   
  160.   
  161. /*   
  162. * SMS API   
  163. * Author: yippit   
  164. * Date: 2004.6.8   
  165. */    
  166. #ifndef MCS_SMS_H    
  167. #define MCS_SMS_H    
  168. #define DLLEXPORT __declspec(dllexport)    
  169. /*sms storage*/    
  170. #define SMS_SIM 0    
  171. #define SMS_MT 1    
  172. /*sms states*/    
  173. #define SMS_UNREAD 0    
  174. #define SMS_READ 1    
  175. /*sms type*/    
  176. #define SMS_NOPARSE -1    
  177. #define SMS_NORMAL 0    
  178. #define SMS_FLASH 1    
  179. #define SMS_MMSNOTI 2    
  180. typedef struct tagSmsEntry {    
  181. int index; /*index, start from 1*/    
  182. int status; /*read, unread*/    
  183. int type; /*-1-can't parser 0-normal, 1-flash, 2-mms*/    
  184. int storage; /*SMS_SIM, SMS_MT*/    
  185. char date[24];    
  186. char number[32];    
  187. char text[144];    
  188. } SmsEntry;    
  189. DLLEXPORT int SmsInit(void);    
  190. DLLEXPORT int SmsSend(char *phonenum, char *content);    
  191. DLLEXPORT int SmsSetSCA(char *sca);    
  192. DLLEXPORT int SmsGetSCA(char *sca);    
  193. DLLEXPORT int SmsSetInd(int ind);    
  194. DLLEXPORT int SmsGetInd(void);    
  195. DLLEXPORT int SmsGetInfo(int storage, int *max, int *used);    
  196. DLLEXPORT int SmsSaveFlash(int flag);    
  197. DLLEXPORT int SmsRead(SmsEntry *entry, int storage, int index);    
  198. DLLEXPORT int SmsDelete(int storage, int index);    
  199. DLLEXPORT int SmsModifyStatus(int storage, int index); /*unread -> read*/    
  200. #endif   
  201.   
  202.   
  203.   在有了这两个头文件之后,就可以进行C程序的编写了。也就是实现对JNI调用的两个方法。在网上的资料中,由于调用的方法实现的都比较简单,(大多是打印字符串等)所以避开了JNI中最麻烦的部分,也是最关键的部分,参数的传递。     
  204. 由于Java和C的编码是不同的,所以传递的参数是要进行再处理,否则C程序是会对参数在编译过程中提出警告,例如;warning C4024: 'SmsSend' : different types for formal and actual parameter 2等。   
  205.   Sms.c的程序如下:   
  206.   
  207. #include "sms.h"    
  208. #include "com_mobilesoft_sms_mobilesoftinfo_SendSMS.h"    
  209. JNIEXPORT jint JNICALL Java_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsInit(JNIEnv * env, jclass jobject)    
  210. {    
  211. return SmsInit();    
  212. }    
  213.   
  214. JNIEXPORT jint JNICALL Java_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsSend(JNIEnv * env, jclass jobject, jbyteArray mobileno, jbyteArray smscontent)    
  215. {    
  216. char * pSmscontent ;    
  217. //jsize theArrayLengthJ = (*env)->GetArrayLength(env,mobileno);    
  218. jbyte * arrayBody = (*env)->GetByteArrayElements(env,mobileno,0);    
  219. char * pMobileNo = (char *)arrayBody;    
  220. printf("[%s]\n ", pMobileNo);    
  221. //jsize size = (*env)->GetArrayLength(env,smscontent);    
  222. arrayBody = (*env)->GetByteArrayElements(env,smscontent,0);    
  223. pSmscontent = (char *)arrayBody;    
  224. printf("<%s>\n", pSmscontent);    
  225. return SmsSend(pMobileNo,pSmscontent);    
  226. }   
  227.   
  228.   
  229.   
  230.   对于C或C++,在程序上是会有稍微的不同,这可以由读者对其进行适当的修改。这里要注意的是GetArrayLength,GetByteArrayElements等这些JNI中已经包含的方法,这些方法是专门对转换参数类型而提供的。具体的方法有很多,在下一篇中会做专门的介绍。   
  231.   在完成了上述的文件后,可以对sms.c进行编译,生成.dll文件(建议在release中编译,这样动态链接库的容积会比较小!)   
  232.   完成.dll文件的编译后,就可以在Java中调用C程序中的方法了。例如文件test.java   
  233.   
  234. public class test {    
  235. public test() {    
  236. }    
  237. public static void main(String[] args) {    
  238. byte[] mobileno = {    
  239. 0x310x330x360x360x310x360x330x300x360x360x370x00};    
  240. String smscontentemp = "早上好";    
  241. byte[] temp = {0};    
  242. try {    
  243. byte[] smscontentdb = smscontentemp.getBytes("gbk");    
  244. byte[] smscontent = new byte[smscontentdb.length + temp.length];    
  245. System.arraycopy(smscontentdb, 0, smscontent, 0, smscontentdb.length);    
  246. System.arraycopy(temp, 0, smscontent, smscontentdb.length, temp.length);    
  247. SendSMS sendSMS = new SendSMS();    
  248. sendSMS.SmsInit();    
  249. if (sendSMS.SmsSend(mobileno, smscontent) >= 0) {    
  250. System.out.println("chenggong !");    
  251. }    
  252. else {    
  253. System.out.println("shibai !");    
  254. }    
  255. }catch (Exception ex) {}    
  256. }    
  257. }   
  258.   
  259.   
  260.   
  261.   在这个文件中要注意的有一点,就是在传递字节数组到C程序中时,最后的结尾一定要以0结束。这是一个偷懒的做法,不过是个有效的做法。因为大多数情况 下,接口是由第三方提供的。所以我们一般是不知道在C的方法里,具体是怎么处理参数的。而C又是要求数组是有长度。所以,在Java中,如果你不想写程序 传数组的长度,那么在数组中以0结尾就是最方便的方法了。当然,如果有更好的方法也希望大家提出。   
  262.   
  263.   到这里,一个完整的Java通过JNI调用动态链接库的程序就完成了。实际上也不是很复杂。只要多注意一下细节,是很容易得出来的。  
posted @ 2009-05-07 11:04 lanxin1020 阅读(161) | 评论 (0)编辑 收藏
Properties 基本知识
如果不熟悉 java.util.Properties 类,那么现在告诉您它是用来在一个文件中存储键-值对的,其中键和值是用等号分隔的,如清单 1 所示。

清单 1. 一组属性示例
foo=bar
fu=baz

将清单 1 装载到 Properties 对象中后,您就可以找到两个键( foo 和 fu )和两个值( foo 的 bar 和 fu 的 baz )了。这个类支持带 \u 的嵌入 Unicode 字符串,但是这里重要的是每一项内容都当作 String 。

清单 2 显示了如何装载属性文件并列出它当前的一组键和值。只需传递这个文件的 InputStream 给 load() 方法,就会将每一个键-值对添加到 Properties 实例中。然后用 list() 列出所有属性或者用 getProperty() 获取单独的属性。
清单 2. 装载属性
Java代码
  1. import java.util.*;   
  2. import java.io.*;   
  3.   
  4. public class LoadSample {   
  5.     public static void main(String args[]) throws Exception {   
  6.       Properties prop = new Properties();   
  7.       FileInputStream fis =    
  8.         new FileInputStream("sample.properties");   
  9.       prop.load(fis);   
  10.       prop.list(System.out);   
  11.       System.out.println("\nThe foo property: " +   
  12.           prop.getProperty("foo"));   
  13.     }   
  14. }  

运行 LoadSample 程序生成如清单 3 所示的输出。注意 list() 方法的输出中键-值对的顺序与它们在输入文件中的顺序不一样。 Properties 类在一个散列表(hashtable,事实上是一个 Hashtable 子类)中储存一组键-值对,所以不能保证顺序。

清单 3. LoadSample 的输出

-- listing properties --
fu=baz
foo=bar

The foo property: bar

XML 属性文件
这里没有什么新内容。 Properties 类总是这样工作的。不过,新的地方是从一个 XML 文件中装载一组属性。它的 DTD 如清单 4 所示。

清单 4. 属性 DTD

dtd 写道
Java代码
  1. <?xml version="1.0" encoding="UTF-8"?>    
  2. <!-- DTD for properties -->    
  3. <!ELEMENT properties ( comment?, entry* ) >    
  4. <!ATTLIST properties version CDATA #FIXED "1.0">    
  5. <!ELEMENT comment (#PCDATA) >    
  6. <!ELEMENT entry (#PCDATA) >    
  7. <!ATTLIST entry key CDATA #REQUIRED>  



如果不想细读 XML DTD,那么可以告诉您它其实就是说在外围 <properties> 标签中包装的是一个 <comment> 标签,后面是任意数量的 <entry> 标签。对每一个 <entry> 标签,有一个键属性,输入的内容就是它的值。清单 5 显示了 清单 1中的属性文件的 XML 版本是什么样子的。

清单 5. XML 版本的属性文件
Java代码
  1. <?xml version="1.0" encoding="UTF-8"?>   
  2. <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">   
  3. <properties>   
  4. <comment>Hi</comment>   
  5. <entry key="foo">bar</entry>   
  6. <entry key="fu">baz</entry>   
  7. </properties>  

如果清单 6 所示,读取 XML 版本的 Properties 文件与读取老格式的文件没什么不同。
清单 6. 读取 XML Properties 文件
Java代码
  1. import java.util.*;   
  2. import java.io.*;   
  3.   
  4. public class LoadSampleXML {   
  5.     public static void main(String args[]) throws Exception {   
  6.       Properties prop = new Properties();   
  7.       FileInputStream fis =   
  8.         new FileInputStream("sampleprops.xml");   
  9.       prop.loadFromXML(fis);   
  10.       prop.list(System.out);   
  11.       System.out.println("\nThe foo property: " +   
  12.           prop.getProperty("foo"));   
  13.     }   
  14. }  

关于资源绑定的说明
虽然 java.util.Properties 类现在除了支持键-值对,还支持属性文件作为 XML 文件,不幸的是,没有内置的选项可以将 ResourceBundle 作为一个 XML 文件处理。是的, PropertyResourceBundle 不使用 Properties 对象来装载绑定,不过装载方法的使用是硬编码到类中的,而不使用较新的 loadFromXML() 方法。

运行清单 6 中的程序产生与原来的程序相同的输出,如 清单 2所示。

保存 XML 属性
新的 Properties 还有一个功能是将属性存储到 XML 格式的文件中。虽然 store() 方法仍然会创建一个类似 清单 1 所示的文件,但是现在可以用新的 storeToXML() 方法创建如 清单 5 所示的文件。只要传递一个 OutputStream 和一个用于注释的 String 就可以了。清单 7 展示了新的 storeToXML() 方法。

清单 7. 将 Properties 存储为 XML 文件
Java代码
  1. import java.util.*;   
  2. import java.io.*;   
  3.   
  4. public class StoreXML {   
  5.     public static void main(String args[]) throws Exception {   
  6.       Properties prop = new Properties();   
  7.       prop.setProperty("one-two""buckle my shoe");   
  8.       prop.setProperty("three-four""shut the door");   
  9.       prop.setProperty("five-six""pick up sticks");   
  10.       prop.setProperty("seven-eight""lay them straight");   
  11.       prop.setProperty("nine-ten""a big, fat hen");   
  12.       FileOutputStream fos =   
  13.         new FileOutputStream("rhyme.xml");   
  14.       prop.storeToXML(fos, "Rhyme");   
  15.       fos.close();   
  16.     }   
  17. }  

运行清单 7 中的程序产生的输出如清单 8 所示。

清单 8. 存储的 XML 文件
Java代码
  1. <?xml version="1.0" encoding="UTF-8"?>   
  2. <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">   
  3. <properties>   
  4. <comment>Rhyme</comment>   
  5. <entry key="seven-eight">lay them straight</entry>   
  6. <entry key="five-six">pick up sticks</entry>   
  7. <entry key="nine-ten">a big, fat hen</entry>   
  8. <entry key="three-four">shut the door</entry>   
  9. <entry key="one-two">buckle my shoe</entry>   
  10. </properties>  

结束语
使用 XML 文件还是使用老式的 a=b 类型的文件完全取决于您自己。老式文件从内存的角度看肯定是轻量级的。不过,由于 XML 的普遍使用,人们会期望 XML 格式流行起来,因为它已经被广泛使用了,只不过没有用到 Properties 对象。选择完全在您。分析软件包 private XMLUtils 类的源代码以获得关于所使用的 XML 解析的更多信息。
Java代码
  1. import java.io.FileInputStream;   
  2. import java.io.IOException;   
  3. import java.io.InputStream;   
  4. import java.util.Properties;   
  5. /**  
  6. * 实现properties文件的读取  
  7. * @author bbflyerwww  
  8. * @date 2006-08-02  
  9. */  
  10. public class PTest {   
  11.       public static void main(String[] args) {   
  12.           try {   
  13.               long start = System.currentTimeMillis();   
  14.               InputStream is = new FileInputStream("conf.properties");   
  15.               Properties p = new Properties();   
  16.               p.load(is);   
  17.               is.close();   
  18.               System.out.println("SIZE : " + p.size());   
  19.               System.out.println("homepage : " + p.getProperty("homepage"));   
  20.               System.out.println("author : " + p.getProperty("author"));   
  21.               System.out.println("school : " + p.getProperty("school"));   
  22.               System.out.println("date : " + p.getProperty("date"));   
  23.               long end = System.currentTimeMillis();    
  24.               System.out.println("Cost : " + (end - start));   
  25.           } catch (IOException ioe) {   
  26.               ioe.printStackTrace();   
  27.           }   
  28.       }   
  29. }  

conf.properties
Java代码
  1. # Configuration fileauthor = bbflyerwww   
  2. school = WuHan University   
  3. date = 2006-08-02  


Result

SIZE:4
author : bbflyerwww
school : WuHan University
date : 2006-08-02
Cost : 0
posted @ 2009-05-06 22:52 lanxin1020 阅读(457) | 评论 (0)编辑 收藏

一、LOG4J组成

    LOG4J主要由三大组件组成:
    . Logger: 决定什么日志信息应该被输出、什么日志信息应该被忽略;
    . Appender: 指定日志信息应该输出到什么地方, 这些地方可以是控制台、文件、网络设备;
    . Layout: 指定日志信息的输出格式;

    一个Logger可以有多个Appender,也就是说日志信息可以同时输出到多个设备上,每个Appender对应
    一种Layout(示例见下图)。

              ↗  Appender1  →  Layout
     /    
    Logger
     ﹨  
              ↘  Appender2  →  Layout


二、Logger组件

    1. Logger组件提供的方法:

       Logger组件是LOG4J的核心组件,它代表了Log4J的日志记录器,它能够对日志信息进行分类筛选。它由org.apache.log4j.Logger类实现,提供了如下方法:

 

java 代码
  1. package org.apache.log4j;   
  2.   
  3. public class Logger {   
  4.   
  5.            // Creation & retrieval methods:   
  6.            public static Logger getRootLogger();   
  7.            public static Logger getLogger(String name);   
  8.   
  9.            // printing methods:   
  10.            public void debug(Object message);   
  11.            public void info(Object message);   
  12.            public void warn(Object message);   
  13.            public void error(Object message);   
  14.            public void fatal(Object message);   
  15.       
  16.            // generic printing method:   
  17.            public void log(Priority p, Object message);   
  18. }   

    2. 在配置文件中配置Logger组件

       可在Log4J配置文件中配置自己的Logger组件,示例:

       log4j.logger.myLogger=WARN

       以上代码定义了一个Logger组件,名称为myLogger,日志级别为WARN。
 
    3. 日志级别种类:

       一共有五种,级别由高到低依次是:fatal、error、warn、info、debug。获得Logger实例后,我们可调用以下方法之一输出日志信息:

       public void debug(Object message);          //输出debug级别的日志信息;
       public void info(Object message);           //输出info级别的日志信息;
       public void warn(Object message);           //输出warn级别的日志信息;
       public void error(Object message);          //输出error级别的日志信息;
       public void fatal(Object message);          //输出fatal级别的日志信息;
       public void log(Priority p, Object message);//输出参数Priority指定级别的日志信息;

       以上方法只有当它的级别大于或等于Logger组件配置的日志级别时才调用。以前面我们配置的myLogger为例,它的日志级别为WARN, 那么在程序中,它的warn()、error()、fatal()方法会被执行。对于log()方法,只有当它的参数Priority指定的日志级别大于或等于WARN时,它才会被执行。

    4. 为什么需要对日志进行分级?
   
       在写程序的时候,为了调试程序,我们会在很多出错的地方输出大量的日志信息。当程序调试完,不需要这些信息时,将程序中这些输出日志信息代码删除吗?这样费时费力,对于大型程序几乎不可行。通过对日志分级,假如不想输出WARN级别的日志信息,则Logger组件的级别调高即可,省时省心。

    5. Logger组件的继承性

       Log4J提供了一个root Logger,它是所有Logger组件的“祖先”,它永远存在,且不能通过名字检索或引用,通过Logger.getRootLogger()方法取得它。配置root Logger代码:

       log4j.rootLogger=INFO,console

       可在配置文件中方便地配置存在继承关系的Logger组件,凡是在符号“.”后面的组件都会成为在符号“.”前面的Logger组件的子类。例如:

       log4j.apache.myLogger=WARN
       log4j.apache.myLogger.mySonLogger=,file

       以上代码中, mySonLogger是myLogger的子类Logger组件。Logger组件的继承关系:
       . 如果子类Logger组件没有定义日志级别,则将继承父类的日志级别;
       . 如果子类Logger组件定义了日志级别,就不会继承父类的日志级别;
       . 黙认情况下,子类Logger组件会继承父类所有的Appender,把它们加入到自己的Appener;
       . 如果把子类Logger组件的additivity标志设为false,那么它就不会继承父类Appender。additivity标志 默认值为false;

       以上配置的三个Logger继承关系示例如图:
    
       root Logger: 日志级别=INFO  appender清单=console
                            ↑
       myLogger: 日志级别=WARN appender清单=null
                            ↑
       mySonLogger: 日志级别=null appender清单=file

       这三个Logger组件实际日志级别和Appender如下表:

       Logger组件          日志级别          Appender清单
       root Logger         INFO              console
       myLogger            WARN              console(继承)
       mySonLogger         WARN(继承)        file,console(继承)
      
三、Appender组件

    Appender组件决定将日志信息输出到什么地方。支持以下目的地:
    . 控制台(Console);
    . 文件(File);
    . GUI组件(GUI component);
    . 套接口服务器(Remote socket server);
    . NT的事件记录器(NT Event Logger);
    . UNIX Syslog守护进程(Remote UNIX Syslog daemon);

    一个Logger可同时对应多个Appender,示例:myLogger配置二个Appender: 一个file, 一个是console:

    log4j.logger.myAppender=WARN,file,console

    log4j.appender.file=org.apache.log4j.RollingFileAppender
    log4j.appender.file.File=log.txt

    log4j.apender.console=org.apache.log4j.ConsoleAppender

四、Layout组件

    Layout组件决定日志输出格式,有以下几种类型:
    . org.apache.log4j.HTMLLayout(以HTML表格形式布局);
    . org.apache.log4j.PatternLayout(可以灵活地指定布局模式);
    . org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串);
    . org.apache.log4j.TTCCLayout(包含日志产生的时间、线程和类别等信息);
   
    为名称为console的Appender配置SimpleLayout,代码如下:

    log4j.appender.console.layout=org.apache.log4j.SimpleLayout

    输出日志格式如下:

    WARN - This is a log message from the myLogger
   
    为名称为file的Appender配置PatternLayout,代码如下:

    log4j.appender.file.layout=org.apache.log4j.PatternLayout
    log4j.appender.file.layout.ConversionPattern=%t %p - %m%n

    输出日志格式如下:

    THREAD-1 WARN - This is a log message from the myLogger

    PatternLayout让开发者依照ConversionPattern定义输出格式。ConversionPattern中一些指定日志内容和格式的预定义符号说明如下:

    符号         描述
    %r           自程序开始后消耗的毫秒数
    %t           表示日志记录请求生成的线程
    %p           表示日专语句的优先级
    %r           与日志请求相关的类别名称
    %c           日志信息所在的类名
    %m%n         表示日志信息的内容

五、Log4J的基本用法

    1. 定义配置文件
       Log4J支持二种配置文件格式:XML和Java属性文件(采用“键=值”形式)。以下为Java属性文件
       格式配置文件:
     
       . 配置Logger组件
        
         配置root Logger语法为:log4j.rootLogger=[priority],appenderName,appenderName,...
         配置自定义Logger组件语法为:log4j.logger.loggerName=[priority],appenderName,appenderName,...

         其中:priority为日志级别,可选值包括FATAL、ERROR、WARN、INFO、DEBUG、ALL;
               appenderName指定Appender组件,可指定多个;        

       . 配置Appender组件

         配置日志信息输出目的地Appender, 语法为:
         log4j.appender.appenderName=fully.ualified.name.of.appender.class
         log4j.appender.appenderName.option1=value1
         ...
         log4j.appender.appenderName.optionN=valueN

         Log4J提供的Appender有以下几种:

         a. org.apache.log4j.ConsoleAppender(控制台);
         b. org.apache.log4j.FileAppender(文件);
         c. org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件);
         d. org.apache.log4j.RollingFileAppender(文件大小到指定尺寸产生一个新的文件);
         e. org.apache.log4j.WriteAppender(将日志信息以流格式发送到任意指定地方);

       . 配置Layout组件

         配置Layout组件语法为:
         log4j.appender.appenderName.layout=fully.ualified.name.of.appender.class
         log4j.appender.appenderName.layout.option1=value1
         ...
         log4j.appender.appenderName.layout.optionN=valueN

         下面为一配置文件示例,文件名为log4j.properties:

         ## LOGGERS ##

         #configure root logger
         log4j.rootLogger=INFO,console
         #define a logger named myLogger
         log4j.logger.myLogger=WARN
         #define a second logger that is a child to myLogger
         log4j.logger.myLogger.mySonLogger=,file

         ## APPENDERS ##

         #define an appender named console, which is set to be a ConsoleAppender
         log4j.appender.console=org.apache.log4j.ConsoleAppender

         # define an appender named file, which is set to be a RollingFileAppender
         log4j.appender.file=org.apache.log4j.FileAppender
         log4j.appender.file.File=log.txt

         ## LAYOUTS ##
         # assian a SimpleLayout to console appender
         log4j.appender.console.layout=org.apache.log4j.SimpleLayout

         # assian a PatternLayout to file appender
         log4j.appender.file.layout=org.apache.log4j.PatternLayout
         log4j.appender.file.layout.ConversionPattern=%t%p-%m%n
        
    2. 程序中使用Log4j

       . 获得日志记录器:

         获得rootLogger:Logger rootLogger=Logger.getRootLogger();
         获得自定义Logger:Logger myLogger = Logger.getLogger("log4j.logger.myLogger");

       . 读取日志记录器,配置Log4J环境;

         a. BasicConfigurator.configure(): 自动快速地使用默认Log4J环境;
         b. Property.configurator.configure(String configFilename): 读取使用Java属性格式的配置文件并配置Log4J环境;
         c. DOMConfigurator.configure(String filename): 读取XML形式的配置文件并配置LOG4J环境;

       . 输出日志信息;

         在程序代码中需要生成日志的地方,调用Logger的各种输出日志方法输出不同级别的日志,例如:
        
         myLogger.debug("Thie is a log message from the " + myLogger.getName());

         下面为一使用Log4J的程序,程序名为Test.java:

java 代码
  1.  import org.apache.log4j.Logger;   
  2.  import org.apache.log4j.PropertyConfigurator;   
  3.     
  4.  public class Test {   
  5.   
  6.    public static void main(String[] args) {   
  7.      //Get an instance of the myLogger   
  8.      Logger myLogger = Logger.getLogger("myLogger");   
  9.       
  10.      //Get an instance of the childLogger   
  11.      Logger mySonLogger = Logger.getLogger("myLogger.mySonLogger");   
  12.      //Load the proerties using the PropertyConfigurator   
  13.      PropertyConfigurator.configure("log4j.properties");   
  14.   
  15.      //Log Messages using the Parent Logger   
  16.      myLogger.debug("Thie is a log message from the " + myLogger.getName());   
  17.      myLogger.info("Thie is a log message from the " + myLogger.getName());   
  18.      myLogger.warn("Thie is a log message from the " +  myLogger.getName());   
  19.      myLogger.error("Thie is a log message from the " + myLogger.getName());   
  20.      myLogger.fatal("Thie is a log message from the " + myLogger.getName());   
  21.   
  22.      mySonLogger.debug("Thie is a log message from the " + mySonLogger.getName());   
  23.      mySonLogger.info("Thie is a log message from the " + mySonLogger.getName());   
  24.      mySonLogger.warn("Thie is a log message from the " +  mySonLogger.getName());   
  25.      mySonLogger.error("Thie is a log message from the " + mySonLogger.getName());   
  26.      mySonLogger.fatal("Thie is a log message from the " + mySonLogger.getName());   
  27.    }   
  28. }   

        程序运行结果为:

        WARN - Thie is a log message from the myLogger
        ERROR - Thie is a log message from the myLogger
        FATAL - Thie is a log message from the myLogger
        WARN - Thie is a log message from the myLogger.mySonLogger
        ERROR - Thie is a log message from the myLogger.mySonLogger
        FATAL - Thie is a log message from the myLogger.mySonLogger

        另在Test.class所在的目录下看到一个log.txt文件,内容如下:

        WARN - Thie is a log message from the myLogger.mySonLogger
        ERROR - Thie is a log message from the myLogger.mySonLogger
        FATAL - Thie is a log message from the myLogger.mySonLogger

        如将配置文件log4j.properties中语句

 log4j.logger.myLogger.mySonLogger=,file

 改为

 log4j.logger.myLogger.mySonLogger=,file,console

 再次运行程序,结果如下:

        WARN - Thie is a log message from the myLogger
        ERROR - Thie is a log message from the myLogger
        FATAL - Thie is a log message from the myLogger
        WARN - Thie is a log message from the myLogger.mySonLogger
        WARN - Thie is a log message from the myLogger.mySonLogger
        ERROR - Thie is a log message from the myLogger.mySonLogger
        ERROR - Thie is a log message from the myLogger.mySonLogger
        FATAL - Thie is a log message from the myLogger.mySonLogger         
        FATAL - Thie is a log message from the myLogger.mySonLogger

        mySonLogger的日志在控制台上输出了二次,这是因为mySonLogger继承了父类console Appender,
        本身又定义了一个console Appender, 因而有二个console Appender。

六、在web应用中使用Log4J

    创建一个Servlet,在它初始化方法中读取Log4J配置文件并配置Log4J环境,这个Servlet在Web应用启
    动时候被加载和初始化,然后就可在其它Web组件中获取Logger对象并输出日志。

    1. 创建用于配置Log4J环境的Servlet

java 代码
  1. import javax.servlet.*;   
  2. import javax.servlet.http.*;   
  3. import java.io.*;   
  4. import java.util.*;   
  5.   
  6. import org.apache.log4j.PropertyConfigurator;   
  7.   
  8. public class Log4JServlet extends HttpServlet {   
  9.       public void init() throws ServletException {   
  10.            String path = getServletContext().getRealPath("/");   
  11.            //getInitParameter("propfile")方法从web.xml文件中读取Log4J配置文件的名字"profile"。   
  12.            String propfile = path + getInitParameter("propfile");   
  13.            PropertyConfigurator.configure(propfile);   
  14.       }   
  15. }   
  16.   

      该Servlet在web.xml中的配置如下:

xml 代码
  1. <servlet>  
  2.   <servlet-name>log4jServlet</servlet-name>  
  3.   <servlet-class>Log4JServlet</servlet-class>  
  4.   <init-param>  
  5.     <param-name>propfile</param-name>  
  6.     <param-value>/WEB-INF/log4j.properties</param-value>  
  7.   </init-param>  
  8.   <load-on-startup>1</load-on-startup>  
  9. </servlet>  

2. 在login.jsp中输出日志
       <%@page import="org.apache.log4j.Logger"%>
       <html>
         <head>
           <title>login</title>
         </head>
         <body>
           <%
             Logger myLogger = Logger.getLogger("myLogger");
             Logger mySonLogger = Logger.getLogger("myLogger.mySonLogger");
             myLogger.debug("Thie is a log message from the " + myLogger.getName());
             myLogger.info("Thie is a log message from the " + myLogger.getName());
             myLogger.warn("Thie is a log message from the " +  myLogger.getName());
             myLogger.error("Thie is a log message from the " + myLogger.getName());
             myLogger.fatal("Thie is a log message from the " + myLogger.getName());

             mySonLogger.debug("Thie is a log message from the " + mySonLogger.getName());
             mySonLogger.info("Thie is a log message from the " + mySonLogger.getName());
             mySonLogger.warn("Thie is a log message from the " +  mySonLogger.getName());
             mySonLogger.error("Thie is a log message from the " + mySonLogger.getName());
             mySonLogger.fatal("Thie is a log message from the " + mySonLogger.getName());
           %>
           <br>
             <form name="loginForm" method="post" action="dispatcher">
             username: <input type="text" name="username">
             <br>
             password: <input type="text" name="password">
             <br>
             <input type="submit" name="submit" value="submit">
           </form>
         </body>
       </html>
              
    3. 发布运行使用Log4J的web应用
       1) 将Log4J的JAR文件拷贝至目录:<WEB应用所在目录>/WEB-INF/lib
       2) 创建Log4J的配置文件log4j.properties, 存放目录为:<WEB应用所在目录>/WEB-INF。内容同前面配置文件示例。
       3) 编译Log4JServlet, 存放至目录: <WEB应用所在目录>/WEB-INF/classes
       4) 修改web.xml文件,加入以下内容:

xml 代码
  1. <servlet>  
  2.   <servlet-name>log4jServlet</servlet-name>  
  3.   <servlet-class>Log4JServlet</servlet-class>  
  4.   <init-param>  
  5.     <param-name>profile</param-name>  
  6.     <param-value>/WEB-INF/log4j.properties</param-value>  
  7.   </init-param>  
  8.   <load-on-startup>1</load-on-startup>  
  9. </servlet>  


       5) 启动服务器,访问login.jsp页面,在服务器控制台上看到如下日志:
          WARN - Thie is a log message from the myLogger
          ERROR - Thie is a log message from the myLogger
          FATAL - Thie is a log message from the myLogger
          WARN - Thie is a log message from the myLogger.mySonLogger
          ERROR - Thie is a log message from the myLogger.mySonLogger
          FATAL - Thie is a log message from the myLogger.mySonLogger

          另在<WEB应用所在目录>/WEB-INF目录下看到一个log.txt文件,内容如下:

          WARN - Thie is a log message from the myLogger.mySonLogger
          ERROR - Thie is a log message from the myLogger.mySonLogger
          FATAL - Thie is a log message from the myLogger.mySonLogger

posted @ 2009-05-04 17:19 lanxin1020 阅读(156) | 评论 (0)编辑 收藏

在实际编程时,要使Log4j真正在系统中运行事先还要对配置文件进行定义。定义步骤就是对Logger、Appender及Layout的分别使用。
  Log4j支持两种配置文件格式,一种是XML格式的文件,一种是java properties(key=value)【Java特性文件(键=值)】。下面我们介绍使用Java特性文件做为配置文件的方法
  具体如下:
  
  1、配置根Logger 其语法为:
  log4j.rootLogger = [ level ] , appenderName1, appenderName2, …
  level : 是日志记录的优先级,分为OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL或者您定义的级别。Log4j建议只使用四个级别,优先级从高到低分别是ERROR、WARN、INFO、DEBUG。通过在这里定义的级别,您可以控制到应用程序中相应级别的日志信息的开关。比如在这里定 义了INFO级别,则应用程序中所有DEBUG级别的日志信息将不被打印出来。
  appenderName:就是指定日志信息输出到哪个地方。您可以同时指定多个输出目的地。
  例如:log4j.rootLogger=info,A1,B2,C3
  
  2、配置日志信息输出目的地 ,其语法为:
  log4j.appender.appenderName = fully.qualified.name.of.appender.class   //
     "fully.qualified.name.of.appender.class" 可以指定下面五个目的地中的一个:
      1.org.apache.log4j.ConsoleAppender(控制台)
      2.org.apache.log4j.FileAppender(文件)
      3.org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件)
      4.org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件)
      5.org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方)
        1.ConsoleAppender选项
              Threshold=WARN:指定日志消息的输出最低层次。
              ImmediateFlush=true:默认值是true,意谓着所有的消息都会被立即输出。
              Target=System.err:默认情况下是:System.out,指定输出控制台
        2.FileAppender 选项
              Threshold=WARN:指定日志消息的输出最低层次。
              ImmediateFlush=true:默认值是true,意谓着所有的消息都会被立即输出。
              File=mylog.txt:指定消息输出到mylog.txt文件。
              Append=false:默认值是true,即将消息增加到指定文件中,false指将消息覆盖指定的文件内容。
        3.DailyRollingFileAppender 选项
              Threshold=WARN:指定日志消息的输出最低层次。
              ImmediateFlush=true:默认值是true,意谓着所有的消息都会被立即输出。
              File=mylog.txt:指定消息输出到mylog.txt文件。
              Append=false:默认值是true,即将消息增加到指定文件中,false指将消息覆盖指定的文件内容。
              DatePattern='.'yyyy-ww:每周滚动一次文件,即每周产生一个新的文件。当然也可以指定按月、周、天、时和分。即对应的格式如下:
              1)'.'yyyy-MM: 每月
              2)'.'yyyy-ww: 每周
              3)'.'yyyy-MM-dd: 每天
              4)'.'yyyy-MM-dd-a: 每天两次
              5)'.'yyyy-MM-dd-HH: 每小时
              6)'.'yyyy-MM-dd-HH-mm: 每分钟
        4.RollingFileAppender 选项
              Threshold=WARN:指定日志消息的输出最低层次。
              ImmediateFlush=true:默认值是true,意谓着所有的消息都会被立即输出。
              File=mylog.txt:指定消息输出到mylog.txt文件。
              Append=false:默认值是true,即将消息增加到指定文件中,false指将消息覆盖指定的文件内容。
              MaxFileSize=100KB: 后缀可以是KB, MB 或者是 GB. 在日志文件到达该大小时,将会自动滚动,即将原来的内容移到mylog.log.1文件。
              MaxBackupIndex=2:指定可以产生的滚动文件的最大数。 实际应用:
  log4j.appender.A1=org.apache.log4j.ConsoleAppender //这里指定了日志输出的第一个位置A1是控制台ConsoleAppender
  
  3、配置日志信息的格式 其语法为:
  A. log4j.appender.appenderName.layout = fully.qualified.name.of.layout.class
          "fully.qualified.name.of.layout.class" 可以指定下面4个格式中的一个:
        1.org.apache.log4j.HTMLLayout(以HTML表格形式布局),
       2.org.apache.log4j.PatternLayout(可以灵活地指定布局模式),
       3.org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串),
       4.org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等等信息)
            1.HTMLLayout 选项
              LocationInfo=true:默认值是false,输出java文件名称和行号
              Title=my app file: 默认值是 Log4J Log Messages.
            2.PatternLayout 选项
              ConversionPattern=%m%n :指定怎样格式化指定的消息。
            3.XMLLayout   选项
              LocationInfo=true:默认值是false,输出java文件和行号
  实际应用:
    log4j.appender.A1.layout=org.apache.log4j.PatternLayout
    B . log4j.appender.A1.layout.ConversionPattern=%-4r %-5p %d{yyyy-MM-dd HH:mm:ssS} %c %m%n
        这里需要说明的就是日志信息格式中几个符号所代表的含义:
         -X号: X信息输出时左对齐;
            %p: 输出日志信息优先级,即DEBUG,INFO,WARN,ERROR,FATAL,
            %d: 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH:mm:ss,SSS},输出类似:2002年10月18日 22:10:28,921
            %r: 输出自应用启动到输出该log信息耗费的毫秒数
            %c: 输出日志信息所属的类目,通常就是所在类的全名
            %t: 输出产生该日志事件的线程名
            %l: 输出日志事件的发生位置,相当于%C.%M(%F:%L)的组合,包括类目名、发生的线程,以及在代码中的行数。举例:Testlog4.main(TestLog4.java:10)
            %x: 输出和当前线程相关联的NDC(嵌套诊断环境),尤其用到像java servlets这样的多客户多线程的应用中。
            %%: 输出一个"%"字符
            %F: 输出日志消息产生时所在的文件名称
            %L: 输出代码中的行号
            %m: 输出代码中指定的消息,产生的日志具体信息
            %n: 输出一个回车换行符,Windows平台为"\r\n",Unix平台为"\n"输出日志信息换行
        可以在%与模式字符之间加上修饰符来控制其最小宽度、最大宽度、和文本的对齐方式。如:
              1)%20c:指定输出category的名称,最小的宽度是20,如果category的名称小于20的话,默认的情况下右对齐。
              2)%-20c:指定输出category的名称,最小的宽度是20,如果category的名称小于20的话,"-"号指定左对齐。
              3)%.30c:指定输出category的名称,最大的宽度是30,如果category的名称大于30的话,就会将左边多出的字符截掉,但小于30的话也不会有空格。
              4)%20.30c:如果category的名称小于20就补空格,并且右对齐,如果其名称长于30字符,就从左边交远销出的字符截掉。
  这里上面三个步骤是对前面Log4j组件说明的一个简化;下面给出一个具体配置例子,在程序中可以参照执行:
  log4j.rootLogger=INFO,A1,B2
  log4j.appender.A1=org.apache.log4j.ConsoleAppender
  log4j.appender.A1.layout=org.apache.log4j.PatternLayout
  log4j.appender.A1.layout.ConversionPattern=%-4r %-5p %d{yyyy-MM-dd HH:mm:ssS} %c %m%n
  根据上面的日志格式,某一个程序的输出结果如下:
  0  INFO 2003-06-13 13:23:46968 ClientWithLog4j Client socket: Socket[addr=localhost/127.0.0.1,port=8002,localport=2014]
  16  DEBUG 2003-06-13 13:23:46984 ClientWithLog4j Server says: 'Java server with log4j, Fri Jun 13 13:23:46 CST 2003'
  16  DEBUG 2003-06-13 13:23:46984 ClientWithLog4j GOOD
  16  DEBUG 2003-06-13 13:23:46984 ClientWithLog4j Server responds: 'Command 'HELLO' not understood.'
  16  DEBUG 2003-06-13 13:23:46984 ClientWithLog4j HELP
  16  DEBUG 2003-06-13 13:23:46984 ClientWithLog4j Server responds: 'Vocabulary: HELP QUIT'
  16  DEBUG 2003-06-13 13:23:46984 ClientWithLog4j QUIT

  4. # 当输出信息于回滚文件时
      log4j.appender.ROLLING_FILE=org.apache.log4j.RollingFileAppender   //指定以文件的方式输出日志
        log4j.appender.ROLLING_FILE.Threshold=ERROR
        log4j.appender.ROLLING_FILE.File=rolling.log   //文件位置,也可以用变量${java.home}、rolling.log
        log4j.appender.ROLLING_FILE.Append=true
        log4j.appender.ROLLING_FILE.MaxFileSize=10KB   //文件最大尺寸
        log4j.appender.ROLLING_FILE.MaxBackupIndex=1   //备份数
        log4j.appender.ROLLING_FILE.layout=org.apache.log4j.PatternLayout
        log4j.appender.ROLLING_FILE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n     
××××××××××××××××××××××××××××××××××××××××××××××××

附:Log4j比较全面的配置
LOG4J的配置之简单使它遍及于越来越多的应用中了:Log4J配置文件实现了输出到控制台、文件、回滚文件、发送日志邮件、输出到数据库日志表、自定义标签等全套功能。择其一二使用就够用了,
log4j.rootLogger=DEBUG,CONSOLE,A1,im
log4j.addivity.org.apache=true
# 应用于控制台
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.Threshold=DEBUG
log4j.appender.CONSOLE.Target=System.out
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n
#log4j.appender.CONSOLE.layout.ConversionPattern=[start]%d{DATE}[DATE]%n%p[PRIORITY]%n%x[NDC]%n%t[THREAD] n%c[CATEGORY]%n%m[MESSAGE]%n%n
#应用于文件
log4j.appender.FILE=org.apache.log4j.FileAppender
log4j.appender.FILE.File=file.log
log4j.appender.FILE.Append=false
log4j.appender.FILE.layout=org.apache.log4j.PatternLayout
log4j.appender.FILE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n
# Use this layout for LogFactor 5 analysis
# 应用于文件回滚
log4j.appender.ROLLING_FILE=org.apache.log4j.RollingFileAppender
log4j.appender.ROLLING_FILE.Threshold=ERROR
log4j.appender.ROLLING_FILE.File=rolling.log   //文件位置,也可以用变量${java.home}、rolling.log
log4j.appender.ROLLING_FILE.Append=true     //true:添加   false:覆盖
log4j.appender.ROLLING_FILE.MaxFileSize=10KB   //文件最大尺寸
log4j.appender.ROLLING_FILE.MaxBackupIndex=1   //备份数
log4j.appender.ROLLING_FILE.layout=org.apache.log4j.PatternLayout
log4j.appender.ROLLING_FILE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n

#应用于socket
log4j.appender.SOCKET=org.apache.log4j.RollingFileAppender
log4j.appender.SOCKET.RemoteHost=localhost
log4j.appender.SOCKET.Port=5001
log4j.appender.SOCKET.LocationInfo=true
# Set up for Log Facter 5
log4j.appender.SOCKET.layout=org.apache.log4j.PatternLayout
log4j.appender.SOCET.layout.ConversionPattern=[start]%d{DATE}[DATE]%n%p[PRIORITY]%n%x[NDC]%n%t[THREAD]%n%c[CATEGORY]%n%m[MESSAGE]%n%n

# Log Factor 5 Appender
log4j.appender.LF5_APPENDER=org.apache.log4j.lf5.LF5Appender
log4j.appender.LF5_APPENDER.MaxNumberOfRecords=2000
# 发送日志给邮件
log4j.appender.MAIL=org.apache.log4j.net.SMTPAppender
log4j.appender.MAIL.Threshold=FATAL
log4j.appender.MAIL.BufferSize=10
log4j.appender.MAIL.From=web@www.wuset.com
log4j.appender.MAIL.SMTPHost=www.wusetu.com
log4j.appender.MAIL.Subject=Log4J Message
log4j.appender.MAIL.To=web@www.wusetu.com
log4j.appender.MAIL.layout=org.apache.log4j.PatternLayout
log4j.appender.MAIL.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n
# 用于数据库
log4j.appender.DATABASE=org.apache.log4j.jdbc.JDBCAppender
log4j.appender.DATABASE.URL=jdbc:mysql://localhost:3306/test
log4j.appender.DATABASE.driver=com.mysql.jdbc.Driver
log4j.appender.DATABASE.user=root
log4j.appender.DATABASE.password=
log4j.appender.DATABASE.sql=INSERT INTO LOG4J (Message) VALUES ('[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n')
log4j.appender.DATABASE.layout=org.apache.log4j.PatternLayout
log4j.appender.DATABASE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n

log4j.appender.A1=org.apache.log4j.DailyRollingFileAppender
log4j.appender.A1.File=SampleMessages.log4j
log4j.appender.A1.DatePattern=yyyyMMdd-HH'.log4j'
log4j.appender.A1.layout=org.apache.log4j.xml.XMLLayout
#自定义Appender
log4j.appender.im = net.cybercorlin.util.logger.appender.IMAppender
log4j.appender.im.host = mail.cybercorlin.net
log4j.appender.im.username = username
log4j.appender.im.password = password
log4j.appender.im.recipient = corlin@cybercorlin.net
log4j.appender.im.layout=org.apache.log4j.PatternLayout
log4j.appender.im.layout.ConversionPattern =[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n

posted @ 2009-05-04 14:40 lanxin1020 阅读(114) | 评论 (0)编辑 收藏
Log4j下载
在apache网站:jakarta.apache.org/log4j 可以免费下载到Log4j最新版本的软件包。

Log4j使用
Log4j的包下载完成后,解压,将其中打包好的的log4j-1.x.x.jar导入你的工程LIB中。
Log4j之所以受欢迎的原因之一是它的灵活性。Log4j提供了灵活的配置方法,默认是调用BasicConfigurator.configure()来进行配置,但如果只是简单的调用BasicConfigurator.configure()来进行配置工作,那么所有的配置都是固定的,不方便以后修改配置。另一种是动态配置,Log4j提供了PropertyConfigurator.configure(……)来动态配置,参数可以是一个properties文件所在路径的String对象,可以是一个properties文件所在路径的URL对象,也可以是一个properties对象。如果要用XML文件来配置信息,则可用类型的DOMConfigurator()函数来从一个XML文件中加载配置信息。这种方式更方便修改配置。

动态配置

Java代码
  1. package http;       
  2.       
  3. import org.apache.log4j.BasicConfigurator;       
  4. import org.apache.log4j.Logger;       
  5. import org.apache.log4j.PropertyConfigurator;       
  6. import org.apache.log4j.xml.DOMConfigurator;       
  7.       
  8. public class Log4jDemo {       
  9.     static Logger log = Logger.getLogger(Log4jDemo.class.getClass());       
  10.     /**     
  11.      * main     
  12.      * @param args     
  13.      */      
  14.     public static void main(String[] args) {       
  15.         BasicConfigurator.configure();//默认配置       
  16.         PropertyConfigurator.configure("c:/log4j.properties");       
  17.         //动态配置,参数可以是一个properties文件所在路径的String对象       
  18.         //可以是一个properties文件所在路径的URL对象,也可以是一个properties对象       
  19.                
  20.         DOMConfigurator.configure("c:/log4j.xml");//XML配置文件       
  21.       
  22.         //PropertyConfigurator.configure()的参数还可以是XML、Properties对象       
  23.                
  24.         //下面就可使用log4j       
  25.         log.info("info");       
  26.         log.debug("debug");       
  27.         log.error("error");       
  28.         log.warn("warn");       
  29.     }       
  30.       
  31. }      


J2EE应用log4j
上面代码描述了Log4j的简单应用,其实使用Log4j也就是这样简单方便。当然除了上面的配置方法,还有其它,比如做一个J2EE应用,在J2EE应用使用Log4j,必须先在启动服务时加载Log4j的配置文件进行初始化,可以在web.xml中进行。


java 代码
Java代码
  1. import java.io.IOException;       
  2. import java.io.PrintWriter;       
  3.       
  4. import javax.servlet.ServletException;       
  5. import javax.servlet.http.HttpServlet;       
  6. import javax.servlet.http.HttpServletRequest;       
  7. import javax.servlet.http.HttpServletResponse;       
  8.       
  9.       
  10.       
  11. public class J2eeLog4jDemo extends HttpServlet {       
  12.       
  13.     public void destroy() {       
  14.         super.destroy();        
  15.     }          
  16.     public void doGet(HttpServletRequest request, HttpServletResponse response)       
  17.             throws ServletException, IOException {       
  18.     }       
  19.     public void doPost(HttpServletRequest request, HttpServletResponse response)       
  20.             throws ServletException, IOException {             
  21.     }       
  22.     public void init() throws ServletException {       
  23.         //通过web.xml来动态取得配置文件       
  24.         String prefix = getServletContext().getRealPath("/");       
  25.         String file = getInitParameter("log4j");       
  26.       
  27.         //如果没有给出相应的配置文件,则不进行初始化       
  28.         if(file != null)        
  29.         {       
  30.             PropertyConfigurator.configure(prefix+file);       
  31.         }       
  32.     }       
  33.       
  34. }      

Web.xml 代码
Java代码
  1. <servlet>      
  2. <servlet-name>j2eelog4jdemoservlet-name>      
  3. <servlet-class>J2eeLog4jDemoservlet-class>      
  4. <init-param>      
  5. <param-name>log4jparam-name>      
  6. <param-value>log4j.propertiesparam-value>      
  7. init-param>      
  8. <load-on-startup>1load-on-startup>  //设为1时,Web容器启动即进行加载    
  9. servlet  


Spring配置Log4j
Spring中配置Log4j只要配置applicationContext.xml文件,Log4j的配置文件放在Web工程的根目录下,默认是objectname/root下,可以在web.xml中设置工程根目录.

设置根目录


web.xml 代码
Java代码
  1. <!--不定义webAppRootKey参数,webAppRootKey就是缺省的"webapp.root"-->      
  2.  <context-param>      
  3.   <param-name>webAppRootKeyparam-name>      
  4.   <param-value>webapp.rootparam-value>      
  5.  context-param>  


配置applicationContext.xml


applicationContext.xml 代码
Java代码
  1. <!--由Sprng载入的Log4j配置文件位置-->      
  2. <context-param>      
  3.  <param-name>log4jConfigLocationparam-name>      
  4.  <param-value>/WEB-INF/log4j.propertiesparam-value>      
  5.  <!--在这里定位配置文件,需要的是从root开始的绝对路径-->      
  6. context-param>      
  7.       
  8.       
  9.       
  10. <!--Spring默认刷新Log4j配置文件的间隔,单位为millisecond-->      
  11. <context-param>      
  12.  <param-name>log4jRefreshIntervalparam-name>      
  13.  <param-value>60000param-value>      
  14. context-param>      
  15.       
  16. <!--Spring log4j 监听器-->      
  17. <listener>      
  18.  <listener-class>org.springframework.web.util.Log4jConfigListenerlistener-class>      
  19. listener>     


同时使用commons-logging和Log4j


1)首先在classpath下寻找自己的配置文件commons-logging.properties,如果找到,则使用其中定义的Log实现类
2)如果找不到commons-logging.properties文件,则在查找是否已定义系统环境变量org.apache.commons.logging.Log,找到则使用其定义的Log实现类
3)否则,查看classpath中是否有Log4j的包,如果发现,则自动使用Log4j作为日志实现类
4)否则,使用JDK自身的日志实现类(JDK1.4以后才有日志实现类)
5)否则,使用commons-logging自己提供的一个简单的日志实现类SimpleLog


将commons-logging和Log4j的jar包都放置到classpath下,同时也将Log4j的配置文件放到classpath中,两者就可以很好的合作,实现如下:
Java代码
  1. package com.doctorcom.model;       
  2.       
  3. import org.apache.commons.logging.Log;       
  4.       
  5. public class LogFactorySupport {           
  6.           
  7.     public Log getLog(){       
  8.         Log log = org.apache.commons.logging.LogFactory.getLog(LogFactorySupport.class);       
  9.         log.info("");       
  10.         log.debug("");       
  11.     }       
  12.            
  13. }   


java 代码

Log4j配置内容
看一个简单的java属性配置文件log4j.properties:

properties 代码
Java代码
  1. #指定根Logger,及日志输出级别,大于等于该级别的日志将被输出( DEBUG < INFO < WARN < ERROR < FATAL ) 设为OFF可以关闭日志       
  2. log4j.rootLogger=DEBUG, A1,A2       
  3. #指定log输出目的,这里设为输出日志到指定目录的文件my.log中       
  4. log4j.appender.A1=org.apache.log4j.FileAppender       
  5. log4j.appender.A1.File=\\logs\\my.log   #当前根目录下    
  6. #指定日志信息的格式       
  7. log4j.appender.A1.layout=org.apache.log4j.PatternLayout        
  8. log4j.appender.A1.layout.ConversionPattern=%r %d{yyyy-MM-dd HH:mm:ss} %c %p -%m%n       
  9.       
  10. #把A2输出到控制台       
  11. log4j.appender.A2=org.apache.log4j.ConsoleAppender       
  12. log4j.appender.A2.layout=org.apache.log4j.SimpleLayout        
  13.       
  14. #还可以单独指定输出某个包的日志级别       
  15. #log4j.logger.com.study.HelloLog4j=INFO   


1、配置根Logger,其语法为:
log4j.rootLogger = [ level ] , appenderName, appenderName2
level:日志的级别,指定这条日志信息的重要性。分为ALL < DEBUG < INFO < WARN <error fatal=""></error>一般常用的为 DEBUG , INFO ,WARN ,ERROR四种,分别对应Logger类的四种方法
debug(Object message ) ;
info(Object message ) ;
warn(Object message ) ;
error(Object message ) ;
如果设置级别为INFO,则优先级大于等于INFO级别(如:INFO、WARN、ERROR)的日志信息将可以被输出,小于该级别的如:DEBUG将不会被输出
appenderName :就是指定日志信息输出目的地,比如(打印到控制台,输出到文件等)。同一条日志信息可以配置多个输出目的地。

2、配置log输出目的地
Log4j提供以下几种:
org.apache.log4j.ConsoleAppender(控制台)
org.apache.log4j.FileAppender(文件)
org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件)
org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件)
org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方)
3、log信息的格式
org.apache.log4j.HTMLLayout(HTML表格形式)
org.apache.log4j.SimpleLayout(简单格式的日志,只包括日志信息的级别和指定的信息字符串 ,如:DEBUG - Hello)
org.apache.log4j.TTCCLayout(日志的格式包括日志产生的时间、线程、类别等等信息)
org.apache.log4j.PatternLayout(灵活地自定义日志格式)

当使用org.apache.log4j.PatternLayout来自定义信息格式时,可以使用
log4j.appender.A1.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %p -%m%n 来格式化信息
%c    输出所属类的全名,可写为 %c{Num} ,Num类名输出的范围  如:"com.sun.aaa.classB", %C{2}将使日志输出输出范围为:aaa.classB
%d    输出日志时间其格式为 可指定格式 如 %d{HH:mm:ss}等
%l    输出日志事件发生位置,包括类目名、发生线程,在代码中的行数
%n    换行符
%m    输出代码指定信息,如info(“message”),输出message
%p    输出日志的优先级,即 FATAL ,ERROR 等
%r    输出从启动到显示该条日志信息所耗费的时间(毫秒数)
%t    输出产生该日志事件的线程名
posted @ 2009-05-04 14:37 lanxin1020 阅读(151) | 评论 (0)编辑 收藏

HashMap 与 TreeMap的区别

HashMap通过hashcode对其内容进行快速查找,而TreeMap中所有的元素都保持着某种固定的顺序,如果你需要得到一个有序的结果你就应该使用TreeMap(HashMap中元素的排列顺序是不固定的)。

集合框架”提供两种常规的Map实现:HashMapTreeMap (TreeMap实现SortedMap接口)。在Map 中插入、删除和定位元素,HashMap 是最好的选择。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。使用HashMap要求添加的键类明确定义了hashCode()equals()的实现。  这个TreeMap没有调优选项,因为该树总处于平衡状态。

2、两个对象值相同(x.equals(y) == true),但却可有不同的hash code,这句话对不对?hash code是什么意思

 hashcode是给一系列hash算法用的,比如hashtable。不同的对象应该有不同的hashcode,同一个对象应该有同样的hashcode

更正,不是同一个对象,而是相等的对象,应该有相同的hashcode

hash算法是什么啊,作用? hash算法基本就是为了将一个对象和一个整数对应起来,不同的对象对应不同的整数。
(x.equals(y) == true)那这个的话就是去比较它们所对应的整数?
不是。有一个equals()函数,和一个hashcode()函数

3、String a="abc";String b=new String("abc");String c="abc";

System.out.println(a==b);f
System.out.println(a==c);t
System.out.println(b==c);f
System.out.println(a.equals(b));
输出结果是什么?
为什么?

4、a=0;b=0;
if((a=3)>0|(b=3)>0){}
if((a=3)>0||(b=3)>0){}分别说出a,b的值

posted @ 2009-04-18 09:42 lanxin1020 阅读(202) | 评论 (0)编辑 收藏
     摘要: TREEMAP的排序机制   1package com.sf;  2  3import java.text.CollationKey;  4import java.text.Collator;  5import java.util.Comparator;  6import ...  阅读全文
posted @ 2009-04-18 09:36 lanxin1020 阅读(1076) | 评论 (0)编辑 收藏

内部类:定义在其他类里面的类。
使用内部类的理由:
1.内部类方法能够访问外部类的任何数据成员包括私有成员。
2.对同一个包的其他类,内部类是不可见的。
3.匿名内部类能够方便的定义回调而不用写太多方法。

非静态内部类没有默认的构造函数,非静态内部类的构造函数都有一个外围类对象的引用。
内部类的特殊语法规则:
1.相对内部类,引用其外部类隐式对象的形式:OuterClass.this
2.调用内部类的构造函数:outerObject.new InnerClass(construction parameters);
3.外部类外面引用内部类:OuterClass.InnerClass

内部类是一种编译器现象与虚拟机无关。编译器将内部类翻译为用$分隔外部类名和内部类名的常规类文件,虚拟机对此并无所知。
使用javap -private OuterClass$InnerClass。javap这个工具确实挺不错的,对分析字节码和源码都有很大的帮助。
可以看出详细的内部类源码清单,其中包括了编译器自动添加的部分:
public class Outer
{
 public class Inner
 {
 }
}
当内部类是非静态内部类时相应的内部类的详细源码如下:
Compiled from "Outer.java"
public class Outer$Inner extends java.lang.Object{
    final Outer this$0;  //编译器自动在内部类里面添加了指向外部类对象的引用
    public Outer$Inner(Outer);  //内部类的构造函数默认有一个外部类对象作为参数。
}

当内部类是静态内部类时:
Compiled from "Outer.java"
public class Outer$Inner extends java.lang.Object{
    public Outer$Inner(); //没有了对外部类对象的引用
}


如下代码模拟了上面内部类的情形唯一不同的是这里的Inner没有访问Outer私有数据的权限:
class Outer{
   Inner in = new Inner(this);
}

class Inner{
   public Inner(Outer outer){
      this.outer = outer;
   }
  
   private Outer outer;
}

 

//那么权限是如何控制的呢?当内部类中的方法访问到外部类的私有数据时(注意如果内部类没有方法去访问外部类的私有数据不会生成该静态方法static int access$000(Outer);)
public class Outer
{
 private int i;
 public void methodOne()
 {
 }

 class Inner
 {
  public void print(){
   System.out.println(Outer.this.i);
  }
 }
}

相应的外部类详细源码如下:
public class Outer
{
   public Outer();

   public void methodOne();
   static int access$000(Outer); //由编译器合成的用于内部类对外部类进行特殊访问权限的控制:这也是
                                //为什么内部类能够访问外部类中的私有数据的原因。
   private int i;
}

内部类访问外部类的private数据的这种方式很可能导致危险,虽然access$000不是一个合法的Java方法名,但是熟悉class文件结构的黑客可以使用十六进制编辑器轻松创建一个用虚拟机指令调用那个方法的类文件。由于隐秘的访问方法需要拥有包可见性,所以攻击代码需要与被攻击类放在同一个包中。总之,如果内部类访问了外部类的私有数据域,就有可能通过附加在外部类所在包中的其他类访问私有数据。

局部内部类:定义在方法之中,因此局部类没有public和private修饰符。对外界是完全隐藏的。
局部类不仅能够访问外部类,还能访问方法中的局部变量(或方法的参数)。不过那些局部变量要声明为final的。


匿名内部类:用外部类+$+数字的形式表示。一个外部类中有多个匿名内部类时,其生成的class文件对应有Outer$(1、2、3)的形式表示。注意到该匿名内部类是final的。
final class Outer$1
{
   Outer$1(Outer);

   public void method();

   final Outer this$0;
}

嵌套类:当内部类不需要访问外部类的数据成员时应该使用嵌套类。注意只有内部类可以声明为static的其他不行。
在接口中声明的内部类自动转换为public static的,在接口中定义的成员自动都是public static的。

内部类构造函数的可见性与其类的可见性相同。

 

posted @ 2009-04-16 14:06 lanxin1020 阅读(137) | 评论 (0)编辑 收藏