UserType and CompositeUserType为
Hibernate中提供的用户类型自定义接口。根据这个接口,可以实现自定义的数据类型。
最近看<<深入浅出
Hibernate>>,作者在书中介绍了
Hibernate 提供的用户自定义数据类型,于是效仿书中的方法,在数据库表中添加一个字段用于记录所有好友的userid,每个userid之间用";"加以分隔,同时将该字段映射为一个特殊的List集合,利用UserType interface实现String解析后将各个userid封装在List中,将List中的记录封装成以";"分隔的String。
使用UserType接口,实现了较好的设计风格,以及更好的重用性。
/*
* 用户自定义的数据类型,对应数据库中的一个字段,在该字段中,保存了
* 多个用户需要的信息,之间用";"加以分隔.
* @Author:Paul
* @Date:April 18th,2008
*/
package com.globalhands.
hibernate.userTypes;
import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.sql.Types;
import org.
hibernate.
Hibernate;
import org.
hibernate.HibernateException;
import org.
hibernate.usertype.UserType;
public class SpecialList implements UserType {
private List specialList;
private static final char SPLITTER = ';';
private static final int[] TYPES = new int[] { Types.VARCHAR };
public String assemble(Serializable arg0, Object arg1)
throws HibernateException {
return null;
}
/*
* 将List封装为一个String对象
*/
public String assemble(List specialList) throws HibernateException {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < specialList.size() - 1; i++) {
sb.append(specialList.get(i)).append(this.SPLITTER);
}
sb.append(specialList.get(specialList.size() - 1));
return sb.toString();
}
/*
* 创建一个新的List实例,包含原有的List实例中的所有元素.
*/
public Object deepCopy(Object value) throws HibernateException {
List sourceList = (List) value;
List targetList = new ArrayList();
targetList.addAll(sourceList);
return targetList;
}
public Serializable disassemble(Object arg0) throws HibernateException {
return null;
}
/*
* 判断specialList是否发生变化
*/
public boolean equals(Object x, Object y) throws HibernateException {
if (x == y) {
return true;
}
if (x != null && y != null) {
List xList = (List) x;
List yList = (List) y;
if (xList.size() != yList.size()) {
return false;
}
for (int i = 0; i <= xList.size() - 1; i++) {
String str1 = (String) xList.get(i);
String str2 = (String) yList.get(i);
if (!xList.equals(yList)) {
return false;
}
}
return true;
}
return false;
}
public int hashCode(Object arg0) throws HibernateException {
return 0;
}
public boolean isMutable() {
return false;
}
/*
* 从resultset中取出email字段,并将其解析为List类型后返回
*/
public Object nullSafeGet(ResultSet rs, String[] names, Object owner)
throws HibernateException, SQLException {
String value = (String)
Hibernate.STRING.nullSafeGet(rs, names[0]);
if (value != null) {
return parse(value);
} else {
return null;
}
}
/*
* 将以";"分隔的字符串解析为一个字符串数组
*/
private List parse(String value) {
String[] strs = value.split(";");
List specialList = new ArrayList();
for (int i = 0; i <= strs.length - 1; i++) {
specialList.add(strs[i]);
}
return specialList;
}
/*
* 将List型的email信息组装成字符串之后保存到email字段
*/
public void nullSafeSet(PreparedStatement st, Object value, int index)
throws HibernateException, SQLException {
if (value != null) {
String str = assemble((List) value);
Hibernate.STRING.nullSafeSet(st, str, index);
} else {
Hibernate.STRING.nullSafeSet(st, value, index);
}
}
public Object replace(Object arg0, Object arg1, Object arg2)
throws HibernateException {
return null;
}
public Class returnedClass() {
return List.class;
}
public int[] sqlTypes() {
return TYPES;
}
}
同时,修改相应的[ormapping_filename].hbm.xml中相应字段的映射信息
<property name="buddy"
type="com.globalhands.hibernate.userTypes.SpecialList">
<column name="buddy" length="2000" not-null="true" />
</property>
之后,修改POJO类中该字段的返回类型为List。
使用JUnit测试程序。
posted @
2009-05-24 19:57 lanxin1020 阅读(576) |
评论 (0) |
编辑 收藏
posted @
2009-05-21 22:13 lanxin1020 阅读(157) |
评论 (0) |
编辑 收藏
什么是 ASM?
ASM 是一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class 文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。
与 BCEL 和 SERL 不同,ASM 提供了更为现代的编程模型。对于 ASM 来说,Java class 被描述为一棵树;使用 “Visitor” 模式遍历整个二进制结构;事件驱动的处理方式使得用户只需要关注于对其编程有意义的部分,而不必了解 Java 类文件格式的所有细节:ASM 框架提供了默认的 “response taker”处理这一切。
为什么要动态生成 Java 类?
动态生成 Java 类与 AOP 密切相关的。AOP 的初衷在于软件设计世界中存在这么一类代码,零散而又耦合:零散是由于一些公有的功能(诸如著名的 log 例子)分散在所有模块之中;同时改变 log 功能又会影响到所有的模块。出现这样的缺陷,很大程度上是由于传统的 面向对象编程注重以继承关系为代表的“纵向”关系,而对于拥有相同功能或者说方面 (Aspect)的模块之间的“横向”关系不能很好地表达。例如,目前有一个既有的银行管理系统,包括 Bank、Customer、Account、Invoice 等对象,现在要加入一个安全检查模块, 对已有类的所有操作之前都必须进行一次安全检查。
图 1. ASM – AOP
然而 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
方法被调用的时候,虚拟机都会实际调用这个 InvocationHandler
的 invoke
方法:
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 流程
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 类文件
从上图中可以看到,一个 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 事件的过程,对类文件进行如下操作:
-
删除类的字段、方法、指令:只需在职责链传递过程中中断委派,不访问相应的 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); } }
|
-
修改类、字段、方法的名字或修饰符:在职责链传递过程中替换调用参数。
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); } }
|
-
增加新的类、方法、字段
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 – 时序图
使用 ASM3.0 进行 AOP 编程
我们还是用上面的例子,给 Account
类加上 security check 的功能。与 proxy 编程不同,ASM 不需要将 Account
声明成接口,Account
可以仍旧是一个实现类。ASM 将直接在 Account
类上动手术,给 Account
类的 operation
方法首部加上对 SecurityChecker.checkSecurity
的调用。
首先,我们将从 ClassAdapter
继承一个类。ClassAdapter
是 ASM 框架提供的一个默认类,负责沟通 ClassReader
和 ClassWriter
。如果想要改变 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; } }
|
下一步就是定义一个继承自 MethodAdapter
的 AddSecurityCheckMethodAdapter
,在“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");
插入了安全检查功能。
最后,我们将集成上面定义的 ClassAdapter
,ClassReader
和ClassWriter
产生修改后的 Account
类文件:
import java.io.File; import java.io.FileOutputStream; import org.objectweb.asm.*; public class Generator{ public static void main() throws Exception { ClassReader cr = new ClassReader("Account"); ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); ClassAdapter classAdapter = new AddSecurityCheckClassAdapter(cw); cr.accept(classAdapter, ClassReader.SKIP_DEBUG); byte[] data = cw.toByteArray(); File file = new File("Account.class"); FileOutputStream fout = new FileOutputStream(file); fout.write(data); fout.close(); } }
|
执行完这段程序后,我们会得到一个新的 Account.class 文件,如果我们使用下面代码:
public class Main { public static void main(String[] args) { Account account = new Account(); account.operation(); } }
|
使用这个 Account,我们会得到下面的输出:
SecurityChecker.checkSecurity ... operation...
|
也就是说,在 Account
原来的 operation
内容执行之前,进行了 SecurityChecker.checkSecurity()
检查。
将动态生成类改造成原始类 Account 的子类
上面给出的例子是直接改造 Account
类本身的,从此 Account
类的 operation
方法必须进行 checkSecurity 检查。但事实上,我们有时仍希望保留原来的 Account
类,因此把生成类定义为原始类的子类是更符合 AOP 原则的做法。下面介绍如何将改造后的类定义为 Account
的子类 Account$EnhancedByASM
。其中主要有两项工作:
- 改变 Class Description, 将其命名为
Account$EnhancedByASM
,将其父类指定为 Account
。
- 改变构造函数,将其中对父类构造函数的调用转换为对
Account
构造函数的调用。
在 AddSecurityCheckClassAdapter
类中,将重写 visit
方法:
public void visit(final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces) { String enhancedName = name + "$EnhancedByASM"; //改变类命名 enhancedSuperName = name; //改变父类,这里是”Account” super.visit(version, access, enhancedName, signature, enhancedSuperName, interfaces); }
|
改进 visitMethod
方法,增加对构造函数的处理:
public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) { MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions); MethodVisitor wrappedMv = mv; if (mv != null) { if (name.equals("operation")) { wrappedMv = new AddSecurityCheckMethodAdapter(mv); } else if (name.equals("<init>")) { wrappedMv = new ChangeToChildConstructorMethodAdapter(mv, enhancedSuperName); } } return wrappedMv; }
|
这里 ChangeToChildConstructorMethodAdapter
将负责把 Account
的构造函数改造成其子类 Account$EnhancedByASM
的构造函数:
class ChangeToChildConstructorMethodAdapter extends MethodAdapter { private String superClassName; public ChangeToChildConstructorMethodAdapter(MethodVisitor mv, String superClassName) { super(mv); this.superClassName = superClassName; } public void visitMethodInsn(int opcode, String owner, String name, String desc) { //调用父类的构造函数时 if (opcode == Opcodes.INVOKESPECIAL && name.equals("<init>")) { owner = superClassName; } super.visitMethodInsn(opcode, owner, name, desc);//改写父类为superClassName } }
|
最后演示一下如何在运行时产生并装入产生的 Account$EnhancedByASM
。 我们定义一个 Util
类,作为一个类工厂负责产生有安全检查的 Account
类:
public class SecureAccountGenerator { private static AccountGeneratorClassLoader classLoader = new AccountGeneratorClassLoade(); private static Class secureAccountClass; public Account generateSecureAccount() throws ClassFormatError, InstantiationException, IllegalAccessException { if (null == secureAccountClass) { ClassReader cr = new ClassReader("Account"); ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); ClassAdapter classAdapter = new AddSecurityCheckClassAdapter(cw); cr.accept(classAdapter, ClassReader.SKIP_DEBUG); byte[] data = cw.toByteArray(); secureAccountClass = classLoader.defineClassFromClassFile( "Account$EnhancedByASM",data); } return (Account) secureAccountClass.newInstance(); } private static class AccountGeneratorClassLoader extends ClassLoader { public Class defineClassFromClassFile(String className, byte[] classFile) throws ClassFormatError { return defineClass("Account$EnhancedByASM", classFile, 0, classFile.length()); } } }
|
静态方法 SecureAccountGenerator.generateSecureAccount()
在运行时动态生成一个加上了安全检查的 Account
子类。著名的 Hibernate 和 Spring 框架,就是使用这种技术实现了 AOP 的“无损注入”。
小结
最后,我们比较一下 ASM 和其他实现 AOP 的底层技术:
posted @
2009-05-12 12:43 lanxin1020 阅读(243) |
评论 (0) |
编辑 收藏
以下是一份完整的struts-config.xml文件,配置元素的说明详见注释.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
"http://jakarta.apache.org/struts/dtds/struts-config.dtd">
<!-- struts-config.xml中的元素必须按照上述doc指令中的dtd文档定义顺序书写,本例即遵从了dtd定义顺序 -->
<!-- struts-config是整个xml的根元素,其他元素必须被包含其内 -->
<struts-config>
<!--
名称:data-sources
描述:data-sources元素定义了web App所需要使用的数据源
数量:最多一个
子元素:data-source
-->
<data-sources>
<!--
名称:data-source
描述:data-source元素定义了具体的数据源
数量:任意多个
属性:
@key:当需要配置多个数据源时,相当于数据源的名称,用来数据源彼此间进行区别
@type:可以使用的数据源实现的类,一般来自如下四个库
Poolman,开放源代码软件
Expresso,Jcorporate
JDBC Pool,开放源代码软件
DBCP,Jakarta
-->
<data-source key="firstOne" type="org.apache.commons.dbcp.BasicDataSource">
<!--
名称:set-property
描述:用来设定数据源的属性
属性:
@autoCommit:是否自动提交 可选值:true/false
@description:数据源描述
@driverClass:数据源使用的类
@maxCount:最大数据源连接数
@minCount:最小数据源连接数
@user:数据库用户
@password:数据库密码
@url:数据库url
-->
<set-property property="autoCommit" value="true"/>
<set-property property="description" value="Hello!"/>
<set-property property="driverClass" value="com.mysql.jdbc.Driver"/>
<set-property property="maxCount" value="10"/>
<set-property property="minCount" value="2"/>
<set-property property="user" value="root"/>
<set-property property="password" value=""/>
<set-property property="url" value="jdbc:mysql://localhost:3306/helloAdmin"/>
</data-source>
</data-sources>
<!--
名称:form-beans
描述:用来配置多个ActionForm Bean
数量:最多一个
子元素:form-bean
-->
<form-beans>
<!--
名称:form-bean
描述:用来配置ActionForm Bean
数量:任意多个
子元素:form-property
属性:
@className:指定与form-bean元素相对应的配置类,一般默认使用org.apaceh.struts.config.FormBeanConfig,如果自定义,则必须继承 FormBeanConfig
@name:必备属性!为当前form-bean制定一个全局唯一的标识符,使得在整个Struts框架内,可以通过该标识符来引用这个ActionForm Bean。
@type:必备属性!指明实现当前ActionForm Bean的完整类名。
-->
<form-bean name="Hello" type="myPack.Hello">
<!--
名称:form-property
描述:用来设定ActionForm Bean的属性
数量:根据实际需求而定,例如,ActionForm Bean对应的一个登陆Form中有两个文本框,name和password,ActionForm Bean中也有这两个字段,则此处编写两个form-property来设定属性
属性:
@className:指定与form-property相对应的配置类,默认是org.apache.struts.config.FormPropertyConfig,如果自定义,则必须继承FormPropertyConfig类
@name:所要设定的ActionForm Bean的属性名称
@type:所要设定的ActionForm Bean的属性值的类
@initial:当前属性的初值
-->
<form-property name="name" type="java.lang.String"/>
<form-property name="number" type="java.lang.Iteger" initial="18"/>
</form-bean>
</form-beans>
<!--
名称:global-exceptions
描述:处理异常
数量:最多一个
子元素:exception
-->
<global-exceptions>
<!--
名称:exception
描述:具体定义一个异常及其处理
数量:任意多个
属性:
@className:指定对应exception的配置类,默认为org.apache.struts.config.ExceptionConfig
@handler:指定异常处理类,默认为org.apache.struts.action.ExceptionHandler
@key:指定在Resource Bundle种描述该异常的消息key
@path:指定当发生异常时,进行转发的路径
@scope:指定ActionMessage实例存放的范围,默认为request,另外一个可选值是session
@type:必须要有!指定所需要处理异常类的名字。
@bundle:指定资源绑定
-->
<exception
key=""hello.error
path="/error.jsp"
scope="session"
type="hello.HandleError"/>
</global-exceptions>
<!--
名称:global-forwards
描述:定义全局转发
数量:最多一个
子元素:forward
-->
<global-forwards>
<!--
名称:forward
描述:定义一个具体的转发
数量:任意多个
属性:
@className:指定和forward元素对应的配置类,默认为org.apache.struts.action.ActionForward
@contextRelative:如果为true,则指明使用当前上下文,路径以“/”开头,默认为false
@name:必须配有!指明转发路径的唯一标识符
@path:必须配有!指明转发或者重定向的URI。必须以"/"开头。具体配置要与contextRelative相应。
@redirect:为true时,执行重定向操作,否则执行请求转发。默认为false
-->
<forward name="A" path="/a.jsp"/>
<forward name="B" path="/hello/b.do"/>
</global-forwards>
<!--
名称:action-mappings
描述:定义action集合
数量:最多一个
子元素:action
-->
<action-mappings>
<!--
名称:action
描述:定义了从特定的请求路径到相应的Action类的映射
数量:任意多个
子元素:exception,forward(二者均为局部量)
属性:
@attribute:制定与当前Action相关联的ActionForm Bean在request和session范围内的名称(key)
@className:与Action元素对应的配置类。默认为org.apache.struts.action.ActionMapping
@forward:指名转发的URL路径
@include:指名包含的URL路径
@input:指名包含输入表单的URL路径,表单验证失败时,请求会被转发到该URL中
@name:指定和当前Acion关联的ActionForm Bean的名字。该名称必须在form-bean元素中定义过。
@path:指定访问Action的路径,以"/"开头,没有扩展名
@parameter:为当前的Action配置参数,可以在Action的execute()方法中,通过调用ActionMapping的getParameter()方法来获取参数
@roles:指定允许调用该Aciton的安全角色。多个角色之间用逗号分割。处理请求时,RequestProcessor会根据该配置项来决定用户是否有调用该Action的权限
@scope:指定ActionForm Bean的存在范围,可选值为request和session。默认为session
@type:指定Action类的完整类名
@unknown:值为true时,表示可以处理用户发出的所有无效的Action URL。默认为false
@validate:指定是否要先调用ActionForm Bean的validate()方法。默认为true
注意:如上属性中,forward/include/type三者相斥,即三者在同一Action配置中只能存在一个。
-->
<action path="/search"
type="addressbook.actions.SearchAction"
name="searchForm"
scope="request"
validate="true"
input="/search.jsp">
<forward name="success" path="/display.jsp"/>
</action>
</action-mappings>
<!--
名称:controller
描述:用于配置ActionServlet
数量:最多一个
属性:
@bufferSize:指定上传文件的输入缓冲的大小.默认为4096
@className:指定当前控制器的配置类.默认为org.apache.struts.config.ControllerConfig
@contentType:指定相应结果的内容类型和字符编码
@locale:指定是否把Locale对象保存到当前用户的session中,默认为false
@processorClass:指定负责处理请求的Java类的完整类名.默认org.apache.struts.action.RequestProcessor
@tempDir:指定文件上传时的临时工作目录.如果没有设置,将才用Servlet容器为web应用分配的临时工作目录.
@nochache:true时,在相应结果中加入特定的头参数:Pragma ,Cache-Control,Expires防止页面被存储在可数浏览器的缓存中,默认为false
-->
<controller
contentType="text/html;charset=UTF-8"
locale="true"
processorClass="CustomRequestProcessor">
</controller>
<!--
名称:message-resources
描述:配置Resource Bundle.
数量:任意多个
属性:
@className:指定和message-resources对应的配置类.默认为org.apache.struts.config.MessageResourcesConfig
@factory:指定资源的工厂类,默认为org.apache.struts.util.PropertyMessageResourcesFactory
@key:
@null:
@parameter:
-->
<message-resources
null="false"
parameter="defaultResource"/>
<message-resources
key="images"
null="false"
parameter="ImageResources"/>
<!--
名称:plug-in
描述:用于配置Struts的插件
数量:任意多个
子元素:set-property
属性:
@className:指定Struts插件类.此类必须实现org.apache.struts.action.PlugIn接口
-->
<plug-in
className="org.apache.struts.validator.ValidatorPlugIn">
<!--
名称:set-property
描述:配置插件的属性
数量:任意多个
属性:
@property:插件的属性名称
@value:该名称所配置的值
-->
<set-property
property="pathnames"
value="/WEB-INF/validator-rules.xml,/WEB-INF/vlaidation.xml"/>
</plug-in>
</struts-config>
posted @
2009-05-11 16:26 lanxin1020 阅读(268) |
评论 (0) |
编辑 收藏
过滤器是一个程序,它先于与之相关的servlet或JSP页面运行在服务器上。过滤器可附加到一个或多个servlet或JSP页面上,并且可以检查进入这些资源的请求信息。在这之后,过滤器可以作如下的选择:
1. 以常规的方式调用资源(即,调用servlet或JSP页面)。个人理解为请求通过过滤执行其他的操作
2.利用修改过的请求信息调用资源。对请求的信息加以修改,然后继续执行
3. 调用资源,但在发送响应到客户机前对其进行修改
4. 阻止该资源调用,代之以转到其他的资源,返回一个特定的状态代码或生成替换输出。个人理解为请求被拦截时强制执行(跳转)的操作
过滤器提供了几个重要好处 :
首先,它以一种模块化的或可重用的方式封装公共的行为。你有30个不同的serlvet或JSP页面,需要压缩它们的内容以减少下载时间吗?没问题:构造一个压缩过滤器,然后将它应用到30个资源上即可。
其次,利用它能够将高级访问决策与表现代码相分离。这对于JSP特别有价值,其中一般希望将几乎整个页面集中在表现上,而不是集中在业务逻辑上。例如,希 望阻塞来自某些站点的访问而不用修改各页面(这些页面受到访问限制)吗?没问题:建立一个访问限制过滤器并把它应用到想要限制访问的页面上即可。
最后,过滤器使你能够对许多不同的资源进行批量性的更改。你有许多现存资源,这些资源除了公司名要更改外其他的保持不变,能办到么?没问题:构造一个串替换过滤器,只要合适就使用它。
但要注意,过滤器只在与servlet规范2.3版兼容的服务器上有作用。如果你的Web应用需要支持旧版服务器,就不能使用过滤器。
1. 建立基本过滤器
建立一个过滤器涉及下列五个步骤:
1)建立一个实现Filter接口的类。这个类需要三个方法,分别是:doFilter、init和destroy。
doFilter方法包含主要的过滤代码(见第2步),init方法建立设置操作,而destroy方法进行清楚。
2)在doFilter方法中放入过滤行为。doFilter方法的第一个参数为ServletRequest对象。此对象给过滤器提供了对进入的信息 (包括表单数据、cookie和HTTP请求头)的完全访问。第二个参数为ServletResponse,通常在简单的过滤器中忽略此参数。最后一个参 数为FilterChain,如下一步所述,此参数用来调用servlet或JSP页。
3)调用FilterChain对象的doFilter方法。Filter接口的doFilter方法取一个FilterChain对象作为它的一个参 数。在调用此对象的doFilter方法时,激活下一个相关的过滤器。如果没有另一个过滤器与servlet或JSP页面关联,则servlet或JSP 页面被激活。
4)对相应的servlet和JSP页面注册过滤器。在部署描述符文件(web.xml)中使用filter和filter-mapping元素。
5)禁用激活器servlet。防止用户利用缺省servlet URL绕过过滤器设置。
1.1 建立一个实现Filter接口的类
所有过滤器都必须实现javax.servlet.Filter。这个接口包含三个方法,分别为doFilter、init和destroy。
public void doFilter(ServletRequset request,
ServletResponse response,
FilterChain chain)
thows ServletException, IOException
每当调用一个过滤器(即,每次请求与此过滤器相关的servlet或JSP页面)时,就执行其doFilter方法。正是这个方法包含了大部分过滤逻辑。 第一个参数为与传入请求有关的ServletRequest。对于简单的过滤器,大多数过滤逻辑是基于这个对象的。如果处理HTTP请求,并且需要访问诸 如getHeader或getCookies等在ServletRequest中无法得到的方法,就要把此对象构造成 HttpServletRequest。
第二个参数为ServletResponse。除了在两个情形下要使用它以外,通常忽略这个参数。首先,如果希望完全阻塞对相关servlet或JSP页 面的访问。可调用response.getWriter并直接发送一个响应到客户机。其次,如果希望修改相关的servlet或JSP页面的输出,可把响 应包含在一个收集所有发送到它的输出的对象中。然后,在调用serlvet或JSP页面后,过滤器可检查输出,如果合适就修改它,之后发送到客户机。
DoFilter的最后一个参数为FilterChain对象。对此对象调用doFilter以激活与servlet或JSP页面相关的下一个过滤器。如果没有另一个相关的过滤器,则对doFilter的调用激活servlet或JSP本身。
public void init(FilterConfig config) thows ServletException
init方法只在此过滤器第一次初始化时执行,不是每次调用过滤器都执行它。对于简单的过滤器,可提供此方法的一个空体,但有两个原因需要使用init。 首先,FilterConfig对象提供对servlet环境及web.xml文件中指派的过滤器名的访问。因此,普遍的办法是利用init将 FilterConfig对象存放在一个字段中,以便doFilter方法能够访问servlet环境或过滤器名.其次,FilterConfig对象具 有一个getInitParameter方法,它能够访问部署描述符文件(web.xml)中分配的过滤器初始化参数。
public void destroy( )
大多数过滤器简单地为此方法提供一个空体,不过,可利用它来完成诸如关闭过滤器使用的文件或数据库连接池等清除任务。
1.2 将过滤行为放入doFilter方法
doFilter方法为大多数过滤器地关键部分。每当调用一个过滤器时,都要执行doFilter。对于大多数过滤器来说,doFilter执行的步骤是 基于传入的信息的。因此,可能要利用作为doFilter的第一个参数提供的ServletRequest。这个对象常常构造为 HttpServletRequest类型,以提供对该类的更特殊方法的访问。
1.3 调用FilterChain对象的doFilter方法
Filter接口的doFilter方法以一个FilterChain对象作为它的第三个参数。在调用该对象的doFilter方法时,激活下一个相关的 过滤器。这个过程一般持续到链中最后一个过滤器为止。在最后一个过滤器调用其FilterChain对象的doFilter方法时,激活servlet或 页面自身。
但是,链中的任意过滤器都可以通过不调用其FilterChain的doFilter方法中断这个过程。在这样的情况下,不再调用JSP页面的serlvet,并且中断此调用过程的过滤器负责将输出提供给客户机。
1.4 对适当的servlet和JSP页面注册过滤器
部署描述符文件的2.3版本引入了两个用于过滤器的元素,分别是:filter和filter-mapping。filter元素向系统注册一个过滤对象,filter-mapping元素指定该过滤对象所应用的URL。
1.filter元素
filter元素位于部署描述符文件(web.xml)的前部,所有filter-mapping、servlet或servlet-mapping元素之前。filter元素具有如下六个可能的子元素:
1、 icon 这是一个可选的元素,它声明IDE能够使用的一个图象文件。
2、filter-name 这是一个必需的元素,它给过滤器分配一个选定的名字。
3、display-name 这是一个可选的元素,它给出IDE使用的短名称。
4、 description 这也是一个可选的元素,它给出IDE的信息,提供文本文档。
5、 filter-class 这是一个必需的元素,它指定过滤器实现类的完全限定名。
6、 init-param 这是一个可选的元素,它定义可利用FilterConfig的getInitParameter方法读取的初始化参数。单个过滤器元素可包含多个init-param元素。
请注意,过滤是在serlvet规范2.3版中初次引入的。因此,web.xml文件必须使用DTD的2.3版本。下面介绍一个简单的例子:
<xml version="1.0" encoding="ISO-8859-1"?>
DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<filter>
<filter-name>MyFilterfilter-name>
<filter-class>myPackage.FilterClassfilter-class>
filter>
<filter-mapping>...filter-mapping>
<web-app>
2.filter-mapping元素
filter-mapping元素位于web.xml文件中filter元素之后serlvet元素之前。它包含如下三个可能的子元素:
1、 filter-name 这个必需的元素必须与用filter元素声明时给予过滤器的名称相匹配。
2、 url-pattern 此元素声明一个以斜杠(/)开始的模式,它指定过滤器应用的URL。所有filter-mapping元素中必须提供url-pattern或 servlet-name。但不能对单个filter-mapping元素提供多个url-pattern元素项。如果希望过滤器适用于多个模式,可重复 整个filter-mapping元素。
3、 servlet-name 此元素给出一个名称,此名称必须与利用servlet元素给予servlet或JSP页面的名称相匹配。不能给单个filter-mapping元素提供 多个servlet-name元素项。如果希望过滤器适合于多个servlet名,可重复这个filter-mapping元素。
下面举一个例子:
xml version="1.0" encoding="ISO-8859-1"?>
DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<filter>
<filter-name>MyFilterfilter-name>
<filter-class>myPackage.FilterClassfilter-class>
filter>
<filter-mapping>
<filter-name>MyFilterfilter-name>
<url-pattern>/someDirectory/SomePage.jspurl-pattern>
filter-mapping>
web-app>
1.5 禁用激活器servlet
在对资源应用过滤器时,可通过指定要应用过滤器的URL模式或servlet名来完成。如果提供servlet名,则此名称必须与web.xml的 servlet元素中给出的名称相匹配。如果使用应用到一个serlvet的URL模式,则此模式必须与利用web.xml的元素servlet- mapping指定的模式相匹配。但是,多数服务器使用“激活器servlet”为servlet体统一个缺省的URL:http: //host/WebAppPrefix/servlet/ServletName。需要保证用户不利用这个URL访问servlet(这样会绕过过滤器 设置)。
例如,假如利用filter和filter-mapping指示名为SomeFilter的过滤器应用到名为SomeServlet的servlet,则如下:
<filter>
<filter-name>SomeFilterfilter-name>
<filter-class>somePackage.SomeFilterClassfilter-class>
<filter>
<filter-mapping>
<filter-name>SomeFilterfilter-name>
<servlet-name>SomeServletservlet-name>
<filter-mapping>
接着,用servlet和servlet-mapping规定URL http://host/webAppPrefix/Blah 应该调用SomeSerlvet,如下所示:
<filter>
<filter-name>SomeFilterfilter-name>
<filter-class>somePackage.SomeFilterClassfilter-class>
filter>
<filter-mapping>
<filter-name>SomeFilterfilter-name>
<servlet-name>/Blahservlet-name>
<filter-mapping>
现在,在客户机使用URL http://host/webAppPrefix/Blah 时就会调用过滤器。过滤器不应用到
http://host/webAppPrefix/servlet/SomePackage.SomeServletClass。
尽管有关闭激活器的服务器专用方法。但是,可移植最强的方法时重新映射Web应用钟的/servlet模式,这样使所有包含此模式的请求被送到相同的 servlet中。为了重新映射此模式,首先应该建立一个简单的servlet,它打印一条错误消息,或重定向用户到顶层页。然后,使用servlet和 servlet-mapping元素发送包含/servlet模式的请求到该servlet。程序清单9-1给出了一个简短的例子。
程序清单9-1 web.xml(重定向缺省servlet URL的摘录)
xml version="1.0" encoding="ISO-8859-1"?>
DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<servlet>
<servlet-name>Errorservlet-name>
<servlet-class>somePackage.ErrorServletservlet-class>
servlet>
<servlet-mapping>
<servlet-name>Errorservlet-name>
<url-pattern>/servlet/*url-pattern>
servlet-mapping>
<web-app>
本文参考:http://www.javaeye.com/topic/140553
posted @
2009-05-10 09:33 lanxin1020 阅读(245) |
评论 (0) |
编辑 收藏
struts的validator的客户端验证,不能进行多表单页面的验证,原因是由<html:script>标签生成的javascipt是根据每个表单,生成一段代码。例如:
<html:javascript formName="searchSgbySjForm" dynamicJavascript="true" staticJavascript="false"/>
生成 :
var bCancel = false;
function validateSearchSgbySjForm(form)
{
if (bCancel) return true;
else return validateRequired(form) && validateDate(form);
}
function required ()
{
this.aa = new Array("sgfssjq", "事故发生时间起 不可为空.", new Function ("varName", "this.datePatternStrict='yyy-MM-dd'; return thisvarName];"));
this.ab = new Array("sgfssjz", "事故发生时间止 不可为空.", new Function ("varName", "this.datePatternStrict='yyy-MM-dd'; return this[varName];"));
}
function DateValidations ()
{
this.aa = new Array("sgfssjq", "事故发生时间起 不是有效的日期类型.", new Function ("varName", "this.datePatternStrict='yyy-MM-dd'; return this [varName];"));
this.ab = new Array("sgfssjz", "事故发生时间止 不是有效的日期类型.", new Function ("varName", "this.datePatternStrict='yyy-MM-dd'; return this[varName];"));
}
如果有多个的话required和DateValidations 都会重复的,而javascript是只认最后一个函数的。所以,会导致验证出错。
再写一个标签 ,主要根据原来的代码修改,代码如下:
package com.tmri.acd.tag;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.BodyTagSupport;
import org.apache.commons.validator.Field;
import org.apache.commons.validator.Form;
import org.apache.commons.validator.ValidatorAction;
import org.apache.commons.validator.ValidatorResources;
import org.apache.commons.validator.util.ValidatorUtils;
import org.apache.commons.validator.Var;
import org.apache.struts.Globals;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.config.ModuleConfig;
import com.tmri.acd.tag.TagUtils;
import org.apache.struts.util.MessageResources;
import org.apache.struts.validator.Resources;
import org.apache.struts.validator.ValidatorPlugIn;
import java.util.StringTokenizer;
public class JavascriptValidatorTag extends BodyTagSupport
{
private static final Comparator actionComparator = new Comparator()
{
public int compare(Object o1, Object o2)
{
ValidatorAction va1 = (ValidatorAction) o1;
ValidatorAction va2 = (ValidatorAction) o2;
if ((va1.getDepends() == null || va1.getDepends().length() == 0) && (va2.getDepends() == null || va2.getDepends().length() == 0))
{
return 0;
}
else if ( (va1.getDepends() != null && va1.getDepends().length() > 0) && (va2.getDepends() == null || va2.getDepends().length() == 0))
{
return 1;
}
}
posted @
2009-05-09 22:59 lanxin1020 阅读(175) |
评论 (0) |
编辑 收藏
HTML 注释
在客户端显示一个注释.
JSP 语法
<!-- comment [ <%= expression %> ] -->
例子 1
<!-- This file displays the user login screen -->
在客户端的HTML源代码中产生和上面一样的数据:
<!-- This file displays the user login screen -->
例子 2
<!-- This page was loaded on <%= (new java.util.Date()).toLocaleString() %> -->
在客户端的HTML源代码中显示为:
<!-- This page was loaded on January 1, 2000 -->
描述
这种注释和HTML中很像,也就是它可以在"查看源代码"中看到.
唯一有些不同的就是,你可以在这个注释中用表达式(例子2所示).这个表达示是不定的,由页面不同而不同,你能够使用各种表达式,只要是合法的就行。更多的请看表达式
隐藏注释
写在JSP程序中,但不是发给客户。
JSP 语法
<%-- comment --%>
例子:
<%@ page language="java" %> <html> <head><title>A Comment Test</title></head> <body> <h2>A Test of Comments</h2> <%-- This comment will not be visible in the page source --%> </body> </html>
描述
用隐藏注释标记的字符会在JSP编译时被忽略掉。这个注释在你希望隐藏或注释你的JSP程序时是很有用的。JSP编译器不是会对<%--and--%>之间的语句进行编译的,它不会显示在客户的浏览器中,也不会在源代码中看到
声明
在JSP程序中声明合法的变量和方法
JSP 语法
<%! declaration; [ declaration; ]+ ... %>
例子
<%! int i = 0; %>
<%! int a, b, c; %>
<%! Circle a = new Circle(2.0); %>
描述
声明你将要在JSP程序中用到的变量和方法。你也必须这样做,不然会出错.
你可以一次性声明多个变量和方法,只要以";"结尾就行,当然这些声明在Java中要是合法的。
当你声明方法或变量时,请注意以下的一些规则:
声明必须以";"结尾(Scriptlet有同样的规则,但是 表达式就不同了).
你可以直接使用在<% @ page %>中被包含进来的已经声明的变量和方法,不需要对它们重新进行声明.
一个声明仅在一个页面中有效。如果你想每个页面都用到一些声明,最好把它们写成一个单独的文件,然后用<%@ include %>或<jsp:include >元素包含进来。
表达式
包含一个符合JSP语法的表达式
JSP 语法
<%= expression %>
例子
<font color="blue"><%= map.size() %></font>
<b><%= numguess.getHint() %></b>.
描述
表达式元素表示的是一个在脚本语言中被定义的表达式,在运行后被自动转化为字符串,然后插入到这个表达示在JSP文件的位置显示。因为这个表达式的值已经被转化为字符串,所以你能在一行文本中插入这个表达式(形式和ASP完全一样).
当你在JSP中使用表达式时请记住以下几点:
你不能用一个分号(";")来作为表达式的结束符.但是同样的表达式用在scriptlet中就需要以分号来结尾了!查看Scriptlet
这个表达式元素能够包括任何在Java Language Specification中有效的表达式.
有时候表达式也能做为其它JSP元素的属性值.一个表达式能够变得很复杂,它可能由一个或多个表达式组成,这些表达式的顺序是从左到右。
[/b]Scriptlet [/b]
包含一个有效的程序段.
JSP 语法
<% code fragment %>
例子
<%
String name = null;
if (request.getParameter("name") == null) {
%>
<%@ include file="error.html" %>
<%
} else {
foo.setName(request.getParameter("name"));
if (foo.getName().equalsIgnoreCase("integra"))
name = "acura";
if (name.equalsIgnoreCase( "acura" )) {
%>
描述
一个scriptlet能够包含多个jsp语句,方法,变量,表达式
因为scriptlet,我们便能做以下的事:
声明将要用到的变量或方法(参考 声明).
编写JSP表达式(参考 表达式).
使用任何隐含的对象和任何用<jsp:useBean>声明过的对象
编写JSP语句 (如果你在使用Java语言,这些语句必须遵从Java Language Specification,).
任何文本,HTML标记,JSP元素必须在scriptlet之外
当JSP收到客户的请求时,scriptlet就会被执行,如果scriptlet有显示的内容,这些显示的内容就被存在out对象中。
Include 指令
在JSP中包含一个静态的文件,同时解析这个文件中的JSP语句.
JSP 语法
<%@ include file="relativeURL" %>
例子
include.jsp:
<html> <head><title>An Include Test</title></head> <body bgcolor="white"> <font color="blue"> The current date and time are <%@ include file="date.jsp" %> </font> </body> </html>
date.jsp:
<%@ page import="java.util.*" %> <%= (new java.util.Date() ).toLocaleString() %>
Displays in the page:
The current date and time are
Aug 30, 1999 2:38:40
描述
<%@include %>指令将会在JSP编译时插入一个包含文本或代码的文件,当你使用<%@ include %>指命时,这个包含的过程就当是静态的。静态的包含就是指这个被包含的文件将会被插入到JSP文件中去,这个包含的文件可以是JSP文件,HTML文件,文本文件。如果包含的是JSP文件,这个包含的JSP的文件中代码将会被执行。
如果你仅仅只是用include 来包含一个静态文件。那么这个包含的文件所执行的结果将会插入到JSP文件中放<% @ include %>的地方。一旦包含文件被执行,那么主JSP文件的过程将会被恢复,继续执行下一行.
这个被包含文件可以是html文件,jsp文件,文本文件,或者只是一段Java代码,但是你得注意在这个包含文件中不能使用<html>,</html>,<body>,</body>标记,因为这将会影响在原JSP文件中同样的标记 ,这样做有时会导致错误.
有一些<%@ include %>指命的行为是以特殊的JSP编译条件为基础,比如:
这个被包含的文件必须对所有客户都有开放且必须f有效,或者它有安全限制
如果这个包含文件被改变,包含此文件的JSP文件将被重新编译
属性:
file="relativeURL"
这个包含文件的路径名一般来说是指相对路径,不需要什么端口,协议,和域名,如下:
"error.jsp""templates/onlinestore.html""/beans/calendar.jsp"
如果这个路径以"/"开头,那么这个路径主要是参照JSP应用的上下关系路径,如果路径是以文件名或目录名开头,那么这个路径就是正在使用的JSP文件的当前路径.
Page 指令
定义JSP文件中的全局属性.
JSP 语法
<%@ page
[ language="java" ]
[ extends="package.class" ]
[ import="{package.class | package.*}, ..." ]
[ session="true | false" ]
[ buffer="none | 8kb | sizekb" ]
[ autoFlush="true | false" ]
[ isThreadSafe="true | false" ]
[ info="text" ]
[ errorPage="relativeURL" ]
[ contentType="mimeType [ ;charset=characterSet ]" | "text/html ; charset=ISO-8859-1" ]
[ isErrorPage="true | false" ]
%>
例子
<%@ page import="java.util.*, java.lang.*" %>
<%@ page buffer="5kb" autoFlush="false" %>
<%@ page errorPage="error.jsp" %>
描述
<%@ page %>指令作用于整个JSP页面,同样包括静态的包含文件。但是<% @ page %>指令不能作用于动态的包含文件,比如 <jsp:include>
你可以在一个页面中用上多个<% @ page %>指令,但是其中的属性只能用一次,不过也有个例外,那就是import属性。因为import属性和Java中的import语句差不多(参照Java Language),所以你就能多用此属性几次了.
无论你把<% @ page %>指令放在JSP的文件的哪个地方,它的作用范围都是整个JSP页面。不过,为了JSP程序的可读性,以及好的编程习惯,最好还是把它放在JSP文件的顶部.
属性
language="java"
声明脚本语言的种类,暂时只能用"java"
extends="package.class"
标明JSP编译时需要加入的Java Class的全名,但是得慎重的使用它,它会限制JSP的编译能力.
import="{package.class | package.* }, ..."
需要导入的Java包的列表,这些包就作用于程序段,表达式,以及声明。
下面的包在JSP编译时已经导入了,所以你就不需要再指明了:
java.lang.*
javax.servlet.*
javax.servlet.jsp.*
javax.servlet.http.*
session="true | false"
设定客户是否需要HTTP Session.(学过ASP的人,应该对它不陌生)如果它为true,那么Session是有用的。
如果它有false,那么你就不能使用session对象,以及定义了scope=session的<jsp:useBean>元素。这样的使用会导致错误.
缺省值是true.
buffer="none | 8kb | sizekb"
buffer的大小被out对象用于处理执行后的JSP对客户浏览器的输出。缺省值是8kb
autoFlush="true | false"
设置如果buffer溢出,是否需要强制输出,如果其值被定义为true(缺省值),输出正常,如果它被设置为false,如果这个buffer溢出,就会导致一个意外错误的发生.如果你把buffer设置为none,那么你就不能把autoFlush设置为false.
isThreadSafe="true | false"
设置Jsp文件是否能多线程使用。缺省值是true,也就是说,JSP能够同时处理多个用户的请求,如果设置为false,一个jsp只能一次处理一个请求
info="text"
一个文本在执行JSP将会被逐字加入JSP中,你能够使用Servlet.getServletInfo方法取回。
errorPage="relativeURL"
设置处理异常事件的JSP文件。
isErrorPage="true | false"
设置此页是否为出错页,如果被设置为true,你就能使用exception对象.
contentType="mimeType [ ;charset=characterSet ]" | "text/html;charset=ISO-8859-1"
设置MIME类型 。缺省MIME 类型是: text/html, 缺省字符集为 ISO-8859-1.
<jsp:forward>
重定向一个HTML文件,JSP文件,或者是一个程序段.
JSP 语法
<jsp:forward page={"relativeURL" | "<%= expression %>"} />
or
<jsp:forward page={"relativeURL" | "<%= expression %>"} >
<jsp:param name="parameterName"
value="{parameterValue | <%= expression %>}" />+
</jsp:forward>
例子
<jsp:forward page="/servlet/login" />
<jsp:forward page="/servlet/login">
<jsp:param name="username" value="jsmith" />
</jsp:forward>
描述
<jsp:forward>标签从一个JSP文件向另一个文件传递一个包含用户请求的request对象.<jsp:forward>标签以下的代码,将不能执行.
你能够向目标文件传送参数和值,在这个例子中我们传递的参数名为username,值为scott,如果你使用了<jsp:param>标签的话,目标文件必须是一个动态的文件,能够处理参数.
如果你使用了非缓冲输出的话,那么使用<jsp:forward>时就要小心。如果在你使用<jsp:forward>之前,jsp文件已经有了数据,那么文件执行就会出错.
属性
page="{relativeURL | <%= expression %>}"
这里是一个表达式或是一个字符串用于说明你将要定向的文件或URL.这个文件可以是JSP,程序段,或者其它能够处理request对象的文件(如asp,cgi,php).
<jsp:param name="parameterName" value="{parameterValue | <%= expression %>}" />+
向一个动态文件发送一个或多个参数,这个文件一定是动态文件.
如果你想传递多个参数,你可以在一个JSP文件中使用多个<jsp:param>。name指定参数名,value指定参数值.
<jsp:getProperty>
获取Bean的属性值,用于显示在页面中
JSP 语法
<jsp:getProperty name="beanInstanceName" property="propertyName" />
例子
<jsp:useBean id="calendar" scope="page" class="employee.Calendar" />
<h2>
Calendar of <jsp:getProperty name="calendar" property="username" />
</h2>
描述
这个<jsp:getProperty>元素将获得Bean的属性值,并可以将其使用或显示在JSP页面中.在你使用<jsp:getProperty>之前,你必须用<jsp:useBean>创建它.
<jsp:getProperty>元素有一些限制:
你不能使用<jsp:getProperty>来检索一个已经被索引了的属性
你能够和JavaBeans组件一起使用<jsp:getProperty>,但是不能与Enterprise Bean一起使用。
属性
name="beanInstanceName"
bean的名字,由<jsp:useBean>指定
property="propertyName"
所指定的Bean的属性名。
技巧:
在sun的JSP参考中提到,如果你使用<jsp:getProperty>来检索的值是空值,那么NullPointerException将会出现,同时如果使用程序段或表达式来检索其值,那么在浏览器上出现的是null(空).
<jsp:include>
包含一个静态或动态文件.
JSP 语法
<jsp:include page="{relativeURL | <%= expression%>}" flush="true" />
or
<jsp:include page="{relativeURL | <%= expression %>}" flush="true" >
<jsp:param name="parameterName" value="{parameterValue | <%= expression %>}" />+
</jsp:include>
Examples
<jsp:include page="scripts/login.jsp" />
<jsp:include page="copyright.html" />
<jsp:include page="/index.html" />
<jsp:include page="scripts/login.jsp">
<jsp:param name="username" value="jsmith" />
</jsp:include>
描述
<jsp:include>元素允许你包含动态文件和静态,这两种包含文件的结果是不同的。如果文件仅是静态文件,那么这种包含仅仅是把包含文件的内容加到jsp文件中去,而如果这个文件动态的,那么这个被包含文件也会被Jsp编译器执行(这一切与asp相似)
你不能从文件名上判断一个文件是动态的还是静态的,比如aspcn.asp 就有可能只是包含一些信息而已,而不需要执行。<jsp:include>能够同时处理这两种文件,因此你就不需要包含时还要判断此文件是动态的还是静态的.
如果这个包含文件是动态的,那么你还可以用<jsp:param>还传递参数名和参数值。
属性
page="{relativeURL | <%= expression %>}"
参数为一相对路径,或者是代表相对路径的表达式.
flush="true"
这里你必须使用flush="true",你不能使用false值.缺省值为false
<jsp:param name="parameterName" value="{parameterValue | <%= expression %> }" />+
<jsp:param>子句能让你传递一个或多个参数给动态文件
你能在一个页面中使用多个<jsp:param>来传递多个参数,
<jsp:plugin>
执行一个applet或Bean,有可能的话还要下载一个Java插件用于执行它.
JSP 语法
<jsp:plugin
type="bean | applet"
code="classFileName"
codebase="classFileDirectoryName"
[ name="instanceName" ]
[ archive="URIToArchive, ..." ]
[ align="bottom | top | middle | left | right" ]
[ height="displayPixels" ]
[ width="displayPixels" ]
[ hspace="leftRightPixels" ]
[ vspace="topBottomPixels" ]
[ jreversion="JREVersionNumber | 1.1" ]
[ nspluginurl="URLToPlugin" ]
[ iepluginurl="URLToPlugin" ] >
[ <jsp:params>
[ <jsp:param name="parameterName" value="{parameterValue | <%= expression %>}" /> ]+
</jsp:params> ]
[ <jsp:fallback> text message for user </jsp:fallback> ]
</jsp:plugin>
例子
<jsp:plugin type=applet code="Molecule.class" codebase="/html">
<jsp:params>
<jsp:param name="molecule" value="molecules/benzene.mol" />
</jsp:params>
<jsp:fallback>
<p>Unable to load applet</p>
</jsp:fallback>
</jsp:plugin>
描述
<jsp:plugin>元素用于在浏览器中播放或显示一个对象(典型的就是applet和Bean),而这种显示需要在浏览器的java插件。
当Jsp文件被编译,送往浏览器时,<jsp:plugin>元素将会根据浏览器的版本替换成<object>或者<embed>元素。注意,<object>用于HTML 4.0 ,<embed>用于HTML 3.2.
一般来说,<jsp:plugin>元素会指定对象是Applet还是Bean,同样也会指定class的名字,还有位置,另外还会指定将从哪里下载这个Java插件。具体如下:
属性
type="bean | applet"
.将被执行的插件对象的类型,你必须得指定这个是Bean还是applet,因为这个属性没有缺省值.
code="classFileName"
将会被Java插件执行的Java Class的名字,必须以.class结尾。这个文件必须存在于codebase属性指定的目录中.
codebase="classFileDirectoryName"
将会被执行的Java Class文件的目录(或者是路径),如果你没有提供此属性,那么使用<jsp:plugin>的jsp文件的目录将会被使用.
name="instanceName"
这个Bean或applet实例的名字,它将会在Jsp其它的地方调用.
archive="URIToArchive, ..."
一些由逗号分开的路径名,这些路径名用于预装一些将要使用的class,这会提高applet的性能.
align="bottom | top | middle | left | right"
图形,对象,Applet的位置,有以下值:
bottom
top
middle
left
right
height="displayPixels" width="displayPixels"
Applet或Bean将要显示的长宽的值,此值为数字,单位为象素.
hspace="leftRightPixels" vspace="topBottomPixels"
Applet或Bean显示时在屏幕左右,上下所需留下的空间,单位为象素.
jreversion="JREVersionNumber | 1.1"
Applet或Bean运行所需的Java Runtime Environment (JRE) 的版本. 缺省值是 1.1.
nspluginurl="URLToPlugin"
Netscape Navigator用户能够使用的JRE的下载地址,此值为一个标准的URL,如http://www.aspcn.com/jsp
iepluginurl="URLToPlugin"
IE用户能够使用的JRE的下载地址,此值为一个标准的URL,如http://www.aspcn.com/jsp
<jsp:params> [ <jsp:param name="parameterName" value="{parameterValue | <%= expression %>}" /> ]+ </jsp:params>
你需要向applet或Bean传送的参数或参数值。
<jsp:fallback> text message for user </jsp:fallback>
一段文字用于Java 插件不能启动时显示给用户的,如果插件能够启动而applet或Bean不能,那么浏览器会有一个出错信息弹出.
<jsp:setProperty>
设置Bean中的属性值.
JSP语法
<jsp:setProperty
name="beanInstanceName"
{
property= "*" |
property="propertyName" [ param="parameterName" ] |
property="propertyName" value="{string | <%= expression %>}"
}
/>
例子
<jsp:setProperty name="mybean" property="*" />
<jsp:setProperty name="mybean" property="username" />
<jsp:setProperty name="mybean" property="username" value="Steve" />
描述
<jsp:setProperty>元素使用Bean给定的setter方法,在Bean中设置一个或多个属性值。你在使用这个元素之前必须得使用<jsp:useBean>声明此Bean.因为,<jsp:useBean>和<jsp:setProperty>是联系在一起的,同时这他们使用的Bean实例的名字也应当相匹配(就是说,在<jsp:setProperty>中的name的值应当和<jsp:useBean>中id的值相同)
你能使用多种方法利用<jsp:setProperty>来设定属性值 :
通过用户输入的所有值(被做为参数储存中request对象中)来匹配Bean中的属性
通过用户输入的指定的值来匹配Bean中指定的属性
在运行时使用一个表达式来匹配Bean的属性
每一种设定属性值的方法都有其特定的语法,下面我们会来讲解
属性及其用法
name="beanInstanceName"
表示已经在<jsp:useBean>中创建的Bean实例的名字.
property="*"
储存用户在Jsp输入的所有值,用于匹配Bean中的属性。在Bean中的属性的名字必须和request对象中的参数名一致.
从客户传到服器上的参数值一般都是字符类型 ,这些字符串为了能够在Bean中匹配就必须转换成其它的类型,下面的表中列出了Bean属性的类型以及他们的转换方法.
把字符串转化为其它类型的方法. Property 类型
方法
boolean or Boolean
java.lang.Boolean.valueOf(String)
byte or Byte
java.lang.Byte.valueOf(String)
char or Character
java.lang.Character.valueOf(String)
double or Double
java.lang.Double.valueOf(String)
integer or Integer
java.lang.Integer.valueOf(String)
float or Float
java.lang.Float.valueOf(String)
long or Long
java.lang.Long.valueOf(String)
如果request对象的参数值中有空值,那么对应的Bean属性将不会设定任何值。同样的,如果Bean中有一个属性没有与之对应的Request参数值,那么这个属性同样也不会设定.
property="propertyName" [ param="parameterName" ]
使用request中的一个参数值来指定Bean中的一个属性值。在这个语法中,property指定Bean的属性名,param指定request中的参数名.
如果bean属性和request参数的名字不同,那么你就必须得指定property和param ,如果他们同名,那么你就只需要指明property就行了.
如查参数值为空(或未初始化),对应的Bean属性不被设定.
property="propertyName" value="{string | <%= expression %>}"
使用指定的值来设定Bean属性。这个值可以是字符串,也可以是表达式。如果这个字符串,那么它就会被转换成Bean属性的类型(查看上面的表).如果它是一个表达式,那么它类型就必须和它将要设定的属性值的类型一致。
如果参数值为空,那么对应的属性值也不会被设定。另外,你不能在一个<jsp:setProperty>中同时使用param和value
技巧
如果你使用了property="*",那么Bean的属性没有必要按Html表单中的顺序排序
<jsp:useBean>
创建一个Bean实例并指定它的名字和作用范围.
JSP 语法
<jsp:useBean
id="beanInstanceName"
scope="page | request | session | application"
{
class="package.class" |
type="package.class" |
class="package.class" type="package.class" |
beanName="{package.class | <%= expression %>}" type="package.class"
}
{
/> |
> other elements </jsp:useBean>
}
例子
<jsp:useBean id="cart" scope="session" class="session.Carts" />
<jsp:setProperty name="cart" property="*" />
<jsp:useBean id="checking" scope="session" class="bank.Checking" >
<jsp:setProperty name="checking" property="balance" value="0.0" />
</jsp:useBean>
描述
<jsp:useBean>用于定位或示例一个JavaBeans组件。<jsp:useBean>首先会试图定位一个Bean实例,如果这个Bean不存在,那么<jsp:useBean>就会从一个class或模版中进行示例。
为了定位或示例一个Bean,<jsp:useBean>会进行以下步聚,顺序如下:
通过给定名字和范围试图定位一个Bean.
对这个Bean对象引用变量以你指定的名字命名.
如果发现了这个Bean,将会在这个变量中储存这个引用。如果你也指定了类型,那么这个Bean也设置为相应的类型.
如果没有发现这个Bean,将会从你指定的class中示例,并将此引用储存到一个新的变量中去。如果这个class的名字代表的是一个模版,那么这个Bean被java.beans.Beans.instantiate示例.
如果<jsp:useBean>已经示例(不是定位)了Bean,同时<jsp:useBean>和</jsp:useBean>中有元素,那么将会执行其中的代码.
<jsp:useBean>元素的主体通常包含有<jsp:setProperty>元素,用于设置Bean的属性值。正如上面第五步所说的,<jsp:useBean>的主体仅仅只有在<jsp:useBean>示例Bean时才会被执行,如果这个Bean已经存在,<jsp:useBean>能够定位它,那么主体中的内容将不会起作用
属性以及用法
id="beanInstanceName"
在你所定义的范围中确认Bean的变量,你能在后面的程序中使用此变量名来分辨不同的Bean
这个变量名对大小写敏感,必须符合你所使用的脚本语言的规定,在Java Programming Language中,这个规定在Java Language 规范已经写明。如果这个Bean已经在别的<jsp:useBean>中创建,那么这个id的值必须与原来的那个id值一致.
scope="page | request | session | application"
Bean存在的范围以及id变量名的有效范围。缺省值是page,以下是详细说明:
page - 你能在包含<jsp:useBean>元素的JSP文件以及此文件中的所有静态包含文件中使用Bean,直到页面执行完毕向客户端发回响应或转到另一个文件为止。
request - 你在任何执行相同请求的Jsp文件中使用Bean,直到页面执行完毕向客户端发回响应或转到另一个文件为止。你能够使用Request对象访问Bean,比如request.getAttribute(beanInstanceName)
session - 从创建Bean开始,你就能在任何使用相同session的Jsp文件中使用Bean.这个Bean存在于整个Session生存周期内,任何在分享此Session的Jsp文件都能使用同一Bean.注意在你创建Bean的Jsp文件中<% @ page %>指令中必须指定session=true
application - 从创建Bean开始,你就能在任何使用相同application的Jsp文件中使用Bean.这个Bean存在于整个application生存周期内,任何在分享此application的Jsp文件都能使用同一Bean.
class="package.class"
使用new关键字以及class构造器从一个class中示例一个bean.这个class不能是抽象的,必须有一个公用的,没有参数的构造器.这个package的名字区别大小写。
type="package.class"
如果这个Bean已经在指定的范围中存在,那么写这个Bean一个新的数据库类型 。如果你没有使用class或beanName指定type,Bean将不会被示例.package和class的名字,区分大小写.
beanName="{package.class | <%= expression %>}" type="package.class"
使用java.beans.Beans.instantiate方法来从一个class或连续模版中示例一个Bean,同时指定Bean的类型。
beanName可以是package和class也可以是表达式,它的值会传给Beans.instantiate.tupe的值可以和Bean相同。
package 和 class 名字区分大小写.
posted @
2009-05-09 11:58 lanxin1020 阅读(180) |
评论 (0) |
编辑 收藏
1 引子
try…catch…finally恐怕是大家再熟悉不过的语句了,而且感觉用起来也是很简单,逻辑上似乎也是很容易理解。不过,我亲自体验的“教训”告诉我,这个东西可不是想象中的那么简单、听话。不信?那你看看下面的代码,“猜猜”它执行后的结果会是什么?不要往后看答案、也不许执行代码看真正答案哦。如果你的答案是正确,那么这篇文章你就不用浪费时间看啦。
public class TestException
{
public TestException()
{
}
boolean testEx() throws Exception
{
boolean ret = true;
try
{
ret = testEx1();
}
catch (Exception e)
{
System.out.println("testEx, catch exception");
ret = false;
throw e;
}
finally
{
System.out.println("testEx, finally; return value=" + ret);
return ret;
}
}
boolean testEx1() throws Exception
{
boolean ret = true;
try
{
ret = testEx2();
if (!ret)
{
return false;
}
System.out.println("testEx1, at the end of try");
return ret;
}
catch (Exception e)
{
System.out.println("testEx1, catch exception");
ret = false;
throw e;
}
finally
{
System.out.println("testEx1, finally; return value=" + ret);
return ret;
}
}
boolean testEx2() throws Exception
{
boolean ret = true;
try
{
int b = 12;
int c;
for (int i = 2; i >= -2; i--)
{
c = b / i;
System.out.println("i=" + i);
}
return true;
}
catch (Exception e)
{
System.out.println("testEx2, catch exception");
ret = false;
throw e;
}
finally
{
System.out.println("testEx2, finally; return value=" + ret);
return ret;
}
}
public static void main(String[] args)
{
TestException testException1 = new TestException();
try
{
testException1.testEx();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
你的答案是什么?是下面的答案吗?
i=2
i=1
testEx2, catch exception
testEx2, finally; return value=false
testEx1, catch exception
testEx1, finally; return value=false
testEx, catch exception
testEx, finally; return value=false
如果你的答案真的如上面所说,那么你错啦。^_^,那就建议你仔细看一看这篇文章或者拿上面的代码按各种不同的情况修改、执行、测试,你会发现有很多事情不是原来想象中的那么简单的。
现在公布正确答案:
i=2
i=1
testEx2, catch exception
testEx2, finally; return value=false
testEx1, finally; return value=false
testEx, finally; return value=false
2 基础知识
2.1 相关概念
例外是在程序运行过程中发生的异常事件,比如除0溢出、数组越界、文件找不到等,这些事件的发生将阻止程序的正常运行。为了加强程序的鲁棒性,程序设计时,必须考虑到可能发生的异常事件并做出相应的处理。C语言中,通过使用if语句来判断是否出现了例外,同时,调用函数通过被调用函数的返回值感知在被调用函数中产生的例外事件并进行处理。全程变量ErroNo常常用来反映一个异常事件的类型。但是,这种错误处理机制会导致不少问题。
Java通过面向对象的方法来处理例外。在一个方法的运行过程中,如果发生了例外,则这个方法生成代表该例外的一个对象,并把它交给运行时系统,运行时系统寻找相应的代码来处理这一例外。我们把生成例外对象并把它提交给运行时系统的过程称为抛弃(throw)一个例外。运行时系统在方法的调用栈中查找,从生成例外的方法开始进行回朔,直到找到包含相应例外处理的方法为止,这一个过程称为捕获(catch)一个例外。
2.2 Throwable类及其子类
用面向对象的方法处理例外,就必须建立类的层次。类 Throwable位于这一类层次的最顶层,只有它的后代才可以做为一个例外被抛弃。图1表示了例外处理的类层次。
从图中可以看出,类Throwable有两个直接子类:Error和Exception。Error类对象(如动态连接错误等),由Java虚拟机生成并抛弃(通常,Java程序不对这类例外进行处理);Exception类对象是Java程序处理或抛弃的对象。它有各种不同的子类分别对应于不同类型的例外。其中类RuntimeException代表运行时由Java虚拟机生成的例外,如算术运算例外ArithmeticException(由除0错等导致)、数组越界例外ArrayIndexOutOfBoundsException等;其它则为非运行时例外,如输入输出例外IOException等。Java编译器要求Java程序必须捕获或声明所有的非运行时例外,但对运行时例外可以不做处理。
2.3 异常处理关键字
Java的异常处理是通过5个关键字来实现的:try,catch,throw,throws,finally。JB的在线帮助中对这几个关键字是这样解释的:
Throws: Lists the exceptions a method could throw.
Throw: Transfers control of the method to the exception handler.
Try: Opening exception-handling statement.
Catch: Captures the exception.
Finally: Runs its code before terminating the program.
2.3.1 try语句
try语句用大括号{}指定了一段代码,该段代码可能会抛弃一个或多个例外。
2.3.2 catch语句
catch语句的参数类似于方法的声明,包括一个例外类型和一个例外对象。例外类型必须为Throwable类的子类,它指明了catch语句所处理的例外类型,例外对象则由运行时系统在try所指定的代码块中生成并被捕获,大括号中包含对象的处理,其中可以调用对象的方法。
catch语句可以有多个,分别处理不同类的例外。Java运行时系统从上到下分别对每个catch语句处理的例外类型进行检测,直到找到类型相匹配的catch语句为止。这里,类型匹配指catch所处理的例外类型与生成的例外对象的类型完全一致或者是它的父类,因此,catch语句的排列顺序应该是从特殊到一般。
也可以用一个catch语句处理多个例外类型,这时它的例外类型参数应该是这多个例外类型的父类,程序设计中要根据具体的情况来选择catch语句的例外处理类型。
2.3.3 finally语句
try所限定的代码中,当抛弃一个例外时,其后的代码不会被执行。通过finally语句可以指定一块代码。无论try所指定的程序块中抛弃或不抛弃例外,也无论catch语句的例外类型是否与所抛弃的例外的类型一致,finally所指定的代码都要被执行,它提供了统一的出口。通常在finally语句中可以进行资源的清除工作。如关闭打开的文件等。
2.3.4 throws语句
throws总是出现在一个函数头中,用来标明该成员函数可能抛出的各种异常。对大多数Exception子类来说,Java 编译器会强迫你声明在一个成员函数中抛出的异常的类型。如果异常的类型是Error或 RuntimeException, 或它们的子类,这个规则不起作用, 因为这在程序的正常部分中是不期待出现的。 如果你想明确地抛出一个RuntimeException,你必须用throws语句来声明它的类型。
2.3.5 throw语句
throw总是出现在函数体中,用来抛出一个异常。程序会在throw语句后立即终止,它后面的语句执行不到,然后在包含它的所有try块中(可能在上层调用函数中)从里向外寻找含有与其匹配的catch子句的try块。
3 关键字及其中语句流程详解
3.1 try的嵌套
你可以在一个成员函数调用的外面写一个try语句,在这个成员函数内部,写另一个try语句保护其他代码。每当遇到一个try语句,异常的框架就放到堆栈上面,直到所有的try语句都完成。如果下一级的try语句没有对某种异常进行处理,堆栈就会展开,直到遇到有处理这种异常的try语句。下面是一个try语句嵌套的例子。
class MultiNest {
static void procedure() {
try {
int a = 0;
int b = 42/a;
} catch(java.lang.ArithmeticException e) {
System.out.println("in procedure, catch ArithmeticException: " + e);
}
}
public static void main(String args[]) {
try {
procedure();
} catch(java.lang. Exception e) {
System.out.println("in main, catch Exception: " + e);
}
}
}
这个例子执行的结果为:
in procedure, catch ArithmeticException: java.lang.ArithmeticException: / by zero
成员函数procedure里有自己的try/catch控制,所以main不用去处理 ArrayIndexOutOfBoundsException;当然如果如同最开始我们做测试的例子一样,在procedure中catch到异常时使用throw e;语句将异常抛出,那么main当然还是能够捕捉并处理这个procedure抛出来的异常。例如在procedure函数的catch中的System.out语句后面增加throw e;语句之后,执行结果就变为:
in procedure, catch ArithmeticException: java.lang.ArithmeticException: / by zero
in main, catch Exception: java.lang.ArithmeticException: / by zero
3.2 try-catch程序块的执行流程以及执行结果
相对于try-catch-finally程序块而言,try-catch的执行流程以及执行结果还是比较简单的。
首先执行的是try语句块中的语句,这时可能会有以下三种情况:
1.如果try块中所有语句正常执行完毕,那么就不会有其他的“动做”被执行,整个try-catch程序块正常完成。
2.如果try语句块在执行过程中碰到异常V,这时又分为两种情况进行处理:
-->如果异常V能够被与try相应的catch块catch到,那么第一个catch到这个异常的catch块(也是离try最近的一个与异常V匹配的catch块)将被执行;如果catch块执行正常,那么try-catch程序块的结果就是“正常完成”;如果该catch块由于原因R突然中止,那么try-catch程序块的结果就是“由于原因R突然中止(completes abruptly)”。
-->如果异常V没有catch块与之匹配,那么这个try-catch程序块的结果就是“由于抛出异常V而突然中止(completes abruptly)”。
3. 如果try由于其他原因R突然中止(completes abruptly),那么这个try-catch程序块的结果就是“由于原因R突然中止(completes abruptly)”。
3.3 try-catch-finally程序块的执行流程以及执行结果
try-catch-finally程序块的执行流程以及执行结果比较复杂。
首先执行的是try语句块中的语句,这时可能会有以下三种情况:
1.如果try块中所有语句正常执行完毕,那么finally块的居于就会被执行,这时分为以下两种情况:
-->如果finally块执行顺利,那么整个try-catch-finally程序块正常完成。
-->如果finally块由于原因R突然中止,那么try-catch-finally程序块的结局是“由于原因R突然中止(completes abruptly)”
2.如果try语句块在执行过程中碰到异常V,这时又分为两种情况进行处理:
-->如果异常V能够被与try相应的catch块catch到,那么第一个catch到这个异常的catch块(也是离try最近的一个与异常V匹配的catch块)将被执行;这时就会有两种执行结果:
-->如果catch块执行正常,那么finally块将会被执行,这时分为两种情况:
-->如果finally块执行顺利,那么整个try-catch-finally程序块正常完成。
-->如果finally块由于原因R突然中止,那么try-catch-finally程序块的结局是“由于原因R突然中止(completes abruptly)”
-->如果catch块由于原因R突然中止,那么finally模块将被执行,分为两种情况:
-->如果如果finally块执行顺利,那么整个try-catch-finally程序块的结局是“由于原因R突然中止(completes abruptly)”。
-->如果finally块由于原因S突然中止,那么整个try-catch-finally程序块的结局是“由于原因S突然中止(completes abruptly)”,原因R将被抛弃。
(注意,这里就正好和我们的例子相符合,虽然我们在testEx2中使用throw e抛出了异常,但是由于testEx2中有finally块,而finally块的执行结果是complete abruptly的(别小看这个用得最多的return,它也是一种导致complete abruptly的原因之一啊——后文中有关于导致complete abruptly的原因分析),所以整个try-catch-finally程序块的结果是“complete abruptly”,所以在testEx1中调用testEx2时是捕捉不到testEx1中抛出的那个异常的,而只能将finally中的return结果获取到。
如果在你的代码中期望通过捕捉被调用的下级函数的异常来给定返回值,那么一定要注意你所调用的下级函数中的finally语句,它有可能会使你throw出来的异常并不能真正被上级调用函数可见的。当然这种情况是可以避免的,以testEx2为例:如果你一定要使用finally而且又要将catch中throw的e在testEx1中被捕获到,那么你去掉testEx2中的finally中的return就可以了。
这个事情已经在OMC2.0的MIB中出现过啦:服务器的异常不能完全被反馈到客户端。)
-->如果异常V没有catch块与之匹配,那么finally模块将被执行,分为两种情况:
-->如果finally块执行顺利,那么整个try-catch-finally程序块的结局就是“由于抛出异常V而突然中止(completes abruptly)”。
-->如果finally块由于原因S突然中止,那么整个try-catch-finally程序块的结局是“由于原因S突然中止(completes abruptly)”,异常V将被抛弃。
3.如果try由于其他原因R突然中止(completes abruptly),那么finally块被执行,分为两种情况:
-->如果finally块执行顺利,那么整个try-catch-finally程序块的结局是“由于原因R突然中止(completes abruptly)”。
-->如果finally块由于原因S突然中止,那么整个try-catch-finally程序块的结局是“由于原因S突然中止(completes abruptly)”,原因R将被抛弃。
3.4 try-catch-finally程序块中的return
从上面的try-catch-finally程序块的执行流程以及执行结果一节中可以看出无论try或catch中发生了什么情况,finally都是会被执行的,那么写在try或者catch中的return语句也就不会真正的从该函数中跳出了,它的作用在这种情况下就变成了将控制权(语句流程)转到finally块中;这种情况下一定要注意返回值的处理。
例如,在try或者catch中return false了,而在finally中又return true,那么这种情况下不要期待你的try或者catch中的return false的返回值false被上级调用函数获取到,上级调用函数能够获取到的只是finally中的返回值,因为try或者catch中的return语句只是转移控制权的作用。
3.5 如何抛出异常
如果你知道你写的某个函数有可能抛出异常,而你又不想在这个函数中对异常进行处理,只是想把它抛出去让调用这个函数的上级调用函数进行处理,那么有两种方式可供选择:
第一种方式:直接在函数头中throws SomeException,函数体中不需要try/catch。比如将最开始的例子中的testEx2改为下面的方式,那么testEx1就能捕捉到testEx2抛出的异常了。
boolean testEx2() throws Exception{
boolean ret = true;
int b=12;
int c;
for (int i=2;i>=-2;i--){
c=b/i;
System.out.println("i="+i);
}
return true;
}
第二种方式:使用try/catch,在catch中进行一定的处理之后(如果有必要的话)抛出某种异常。例如上面的testEx2改为下面的方式,testEx1也能捕获到它抛出的异常:
boolean testEx2() throws Exception{
boolean ret = true;
try{
int b=12;
int c;
for (int i=2;i>=-2;i--){
c=b/i;
System.out.println("i="+i);
}
return true;
}catch (Exception e){
System.out.println("testEx2, catch exception");
Throw e;
}
}
第三种方法:使用try/catch/finally,在catch中进行一定的处理之后(如果有必要的话)抛出某种异常。例如上面的testEx2改为下面的方式,testEx1也能捕获到它抛出的异常:
boolean testEx2() throws Exception{
boolean ret = true;
try{
int b=12;
int c;
for (int i=2;i>=-2;i--){
c=b/i;
System.out.println("i="+i);
throw new Exception("aaa");
}
return true;
}catch (java.lang.ArithmeticException e){
System.out.println("testEx2, catch exception");
ret = false;
throw new Exception("aaa");
}finally{
System.out.println("testEx2, finally; return value="+ret);
}
}
4 关于abrupt completion
前面提到了complete abruptly(暂且理解为“突然中止”或者“异常结束”吧),它主要包含了两种大的情形:abrupt completion of expressions and statements,下面就分两种情况进行解释。
4.1 Normal and Abrupt Completion of Evaluation
每一个表达式(expression)都有一种使得其包含的计算得以一步步进行的正常模式,如果每一步计算都被执行且没有异常抛出,那么就称这个表达式“正常结束(complete normally)”;如果这个表达式的计算抛出了异常,就称为“异常结束(complete abruptly)”。异常结束通常有一个相关联的原因(associated reason),通常也就是抛出一个异常V。
与表达式、操作符相关的运行期异常有:
-->A class instance creation expression, array creation expression , or string concatenation operatior expression throws an OutOfMemoryError if there is insufficient memory available.
-->An array creation expression throws a NegativeArraySizeException if the value of any dimension expression is less than zero.
-->A field access throws a NullPointerException if the value of the object reference expression is null.
-->A method invocation expression that invokes an instance method throws a NullPointerException if the target reference is null.
-->An array access throws a NullPointerException if the value of the array reference expression is null.
-->An array access throws an ArrayIndexOutOfBoundsException if the value of the array index expression is negative or greater than or equal to the length of the array.
-->A cast throws a ClassCastException if a cast is found to be impermissible at run time.
-->An integer division or integer remainder operator throws an ArithmeticException if the value of the right-hand operand expression is zero.
-->An assignment to an array component of reference type throws an ArrayStoreException when the value to be assigned is not compatible with the component type of the array.
4.2 Normal and Abrupt Completion of Statements
正常情况我们就不多说了,在这里主要是列出了abrupt completion的几种情况:
-->break, continue, and return 语句将导致控制权的转换,从而使得statements不能正常地、完整地执行。
-->某些表达式的计算也可能从java虚拟机抛出异常,这些表达式在上一小节中已经总结过了;一个显式的的throw语句也将导致异常的抛出。抛出异常也是导致控制权的转换的原因(或者说是阻止statement正常结束的原因)。
如果上述事件发生了,那么这些statement就有可能使得其正常情况下应该都执行的语句不能完全被执行到,那么这些statement也就是被称为是complete abruptly.
导致abrupt completion的几种原因:
-->A break with no label
-->A break with a given label
-->A continue with no label
-->A continue with a given label
-->A return with no value
-->A return with a given value A
-->throw with a given value, including exceptions thrown by the Java virtual machine
5 关于我们的编程的一点建议
弄清楚try-catch-finally的执行情况后我们才能正确使用它。
如果我们使用的是try-catch-finally语句块,而我们又需要保证有异常时能够抛出异常,那么在finally语句中就不要使用return语句了(finally语句块的最重要的作用应该是释放申请的资源),因为finally中的return语句会导致我们的throw e被抛弃,在这个try-catch-finally的外面将只能看到finally中的返回值(除非在finally中抛出异常)。(我们需要记住:不仅throw语句是abrupt completion 的原因,return、break、continue等这些看起来很正常的语句也是导致abrupt completion的原因。)
posted @
2009-05-08 15:23 lanxin1020 阅读(200) |
评论 (0) |
编辑 收藏
1.详解
1)DOM(JAXP Crimson解析器)
DOM是用与平台和语言无关的方式表示XML文档的官方W3C标准。DOM是以层次结构组织的节点或信息片断的集合。这个层次结构允许开发人员在树中寻找特定信息。分析该结构通常需要加载整个文档和构造层次结构,然后才能做任何工作。由于它是基于信息层次的,因而DOM被认为是基于树或基于对象的。DOM以及广义的基于树的处理具有几个优点。首先,由于树在内存中是持久的,因此可以修改它以便应用程序能对数据和结构作出更改。它还可以在任何时候在树中上下导航,而不是像SAX那样是一次性的处理。DOM使用起来也要简单得多。
2)SAX
SAX处理的优点非常类似于流媒体的优点。分析能够立即开始,而不是等待所有的数据被处理。而且,由于应用程序只是在读取数据时检查数据,因此不需要将数据存储在内存中。这对于大型文档来说是个巨大的优点。事实上,应用程序甚至不必解析整个文档;它可以在某个条件得到满足时停止解析。一般来说,SAX还比它的替代者DOM快许多。
选择DOM还是选择SAX? 对于需要自己编写代码来处理XML文档的开发人员来说, 选择DOM还是SAX解析模型是一个非常重要的设计决策。 DOM采用建立树形结构的方式访问XML文档,而SAX采用的事件模型。
DOM解析器把XML文档转化为一个包含其内容的树,并可以对树进行遍历。用DOM解析模型的优点是编程容易,开发人员只需要调用建树的指令,然后利用navigation APIs访问所需的树节点来完成任务。可以很容易的添加和修改树中的元素。然而由于使用DOM解析器的时候需要处理整个XML文档,所以对性能和内存的要求比较高,尤其是遇到很大的XML文件的时候。由于它的遍历能力,DOM解析器常用于XML文档需要频繁的改变的服务中。
SAX解析器采用了基于事件的模型,它在解析XML文档的时候可以触发一系列的事件,当发现给定的tag的时候,它可以激活一个回调方法,告诉该方法制定的标签已经找到。SAX对内存的要求通常会比较低,因为它让开发人员自己来决定所要处理的tag.特别是当开发人员只需要处理文档中所包含的部分数据时,SAX这种扩展能力得到了更好的体现。但用SAX解析器的时候编码工作会比较困难,而且很难同时访问同一个文档中的多处不同数据。
3)JDOM http://www.jdom.org
JDOM的目的是成为Java特定文档模型,它简化与XML的交互并且比使用DOM实现更快。由于是第一个Java特定模型,JDOM一直得到大力推广和促进。正在考虑通过“Java规范请求JSR-102”将它最终用作“Java标准扩展”。从2000年初就已经开始了JDOM开发。
JDOM与DOM主要有两方面不同。首先,JDOM仅使用具体类而不使用接口。这在某些方面简化了API,但是也限制了灵活性。第二,API大量使用了Collections类,简化了那些已经熟悉这些类的Java开发者的使用。
JDOM文档声明其目的是“使用20%(或更少)的精力解决80%(或更多)Java/XML问题”(根据学习曲线假定为20%)。JDOM对于大多数Java/XML应用程序来说当然是有用的,并且大多数开发者发现API比DOM容易理解得多。JDOM还包括对程序行为的相当广泛检查以防止用户做任何在XML中无意义的事。然而,它仍需要您充分理解XML以便做一些超出基本的工作(或者甚至理解某些情况下的错误)。这也许是比学习DOM或JDOM接口都更有意义的工作。
JDOM自身不包含解析器。它通常使用SAX2解析器来解析和验证输入XML文档(尽管它还可以将以前构造的DOM表示作为输入)。它包含一些转换器以将JDOM表示输出成SAX2事件流、DOM模型或XML文本文档。JDOM是在Apache许可证变体下发布的开放源码。
4)DOM4J http://dom4j.sourceforge.net
虽然DOM4J代表了完全独立的开发结果,但最初,它是JDOM的一种智能分支。它合并了许多超出基本XML文档表示的功能,包括集成的XPath支持、XML Schema支持以及用于大文档或流化文档的基于事件的处理。它还提供了构建文档表示的选项,它通过DOM4J API和标准DOM接口具有并行访问功能。从2000下半年开始,它就一直处于开发之中。
为支持所有这些功能,DOM4J使用接口和抽象基本类方法。DOM4J大量使用了API中的Collections类,但是在许多情况下,它还提供一些替代方法以允许更好的性能或更直接的编码方法。直接好处是,虽然DOM4J付出了更复杂的API的代价,但是它提供了比JDOM大得多的灵活性。
在添加灵活性、XPath集成和对大文档处理的目标时,DOM4J的目标与JDOM是一样的:针对Java开发者的易用性和直观操作。它还致力于成为比JDOM更完整的解决方案,实现在本质上处理所有Java/XML问题的目标。在完成该目标时,它比JDOM更少强调防止不正确的应用程序行为。
DOM4J是一个非常非常优秀的Java XML API,具有性能优异、功能强大和极端易用使用的特点,同时它也是一个开放源代码的软件。如今你可以看到越来越多的Java软件都在使用DOM4J来读写XML,特别值得一提的是连Sun的JAXM也在用DOM4J.
2。比较
1)DOM4J性能最好,连Sun的JAXM也在用DOM4J.目前许多开源项目中大量采用DOM4J,例如大名鼎鼎的Hibernate也用DOM4J来读取XML配置文件。如果不考虑可移植性,那就采用DOM4J.
2)JDOM和DOM在性能测试时表现不佳,在测试10M文档时内存溢出。在小文档情况下还值得考虑使用DOM和JDOM.虽然JDOM的开发者已经说明他们期望在正式发行版前专注性能问题,但是从性能观点来看,它确实没有值得推荐之处。另外,DOM仍是一个非常好的选择。DOM实现广泛应用于多种编程语言。它还是许多其它与XML相关的标准的基础,因为它正式获得W3C推荐(与基于非标准的Java模型相对),所以在某些类型的项目中可能也需要它(如在JavaScript中使用DOM)。
3)SAX表现较好,这要依赖于它特定的解析方式-事件驱动。一个SAX检测即将到来的XML流,但并没有载入到内存(当然当XML流被读入时,会有部分文档暂时隐藏在内存中)。
3. 四种xml操作方式的基本使用方法
xml文件:
- <?xml 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>
<?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>
程序代码:
- import java.io.File;
- import java.util.Iterator;
- import java.util.List;
-
- import javax.xml.parsers.DocumentBuilder;
- import javax.xml.parsers.DocumentBuilderFactory;
- import javax.xml.parsers.SAXParser;
- import javax.xml.parsers.SAXParserFactory;
-
- import org.dom4j.io.SAXReader;
- import org.jdom.Element;
- import org.jdom.input.SAXBuilder;
- import org.w3c.dom.Document;
- import org.w3c.dom.NodeList;
- import org.xml.sax.Attributes;
- import org.xml.sax.InputSource;
- import org.xml.sax.SAXException;
- import org.xml.sax.helpers.DefaultHandler;
-
- public class MyXMLReader extends DefaultHandler {
-
- java.util.Stack tags = new java.util.Stack();
- public MyXMLReader() {
- super();
- }
-
-
-
-
-
-
-
- public void DOM() {
- long lasting = System.currentTimeMillis();
-
- try {
- File f = new File("F:/xmltest.xml");
- DocumentBuilderFactory factory = DocumentBuilderFactory
- .newInstance();
- DocumentBuilder builder = factory.newDocumentBuilder();
- Document doc = builder.parse(f);
- NodeList nl = doc.getElementsByTagName("node");
- for (int i = 0; i < nl.getLength(); i++) {
- System.out.println("|| Name: |"
- + doc.getElementsByTagName("name").item(i)
- .getFirstChild().getNodeValue());
- System.out.println("||Space: |"
- + doc.getElementsByTagName("space").item(i)
- .getFirstChild().getNodeValue());
- System.out.println("-------------------------------------------------"); }
- } catch (Exception e) {
- e.printStackTrace();
- }
- System.out.println("DOM RUNTIME:"
- + (System.currentTimeMillis() - lasting) + " MS");
- }
-
-
-
-
-
-
-
-
-
-
- public void SAX() {
-
- long lasting = System.currentTimeMillis();
- try {
- SAXParserFactory sf = SAXParserFactory.newInstance();
- SAXParser sp = sf.newSAXParser();
- MyXMLReader reader = new MyXMLReader();
- sp.parse(new InputSource("F:/xmltest.xml"), reader);
- } catch (Exception e) {
- e.printStackTrace();
- }
- System.out.println("SAX RUNTIME:"
- + (System.currentTimeMillis() - lasting) + " MS");
- }
-
- public void startElement(String uri, String localName, String qName,
- Attributes attrs) {
- tags.push(qName);
- }
-
- public void characters(char ch[], int start, int length)
- throws SAXException {
- String tag = (String) tags.peek();
- if (tag.equals("name")) {
- System.out.println("|| Name: |" + new String(ch, start, length));
- }
- if (tag.equals("space")) {
- System.out.println("||Space: |" + new String(ch, start, length));
- }
- System.out.println("-------------------------------------------------");
- }
-
-
-
-
-
-
-
-
- public void JDOM() {
- long lasting = System.currentTimeMillis();
- try {
- SAXBuilder builder = new SAXBuilder();
- org.jdom.Document doc = builder.build(new File("F:/xmltest.xml"));
- Element foo = doc.getRootElement();
- List allChildren = foo.getChildren();
- for (int i = 0; i < allChildren.size(); i++) {
- System.out.println("|| Name: |"
- + ((Element) allChildren.get(i)).getChild("name")
- .getText());
- System.out.println("||Space: |"
- + ((Element) allChildren.get(i)).getChild("space")
- .getText());
- System.out.println("-------------------------------------------------"); }
- } catch (Exception e) {
- e.printStackTrace();
- }
- System.out.println("JDOM RUNTIME:"
- + (System.currentTimeMillis() - lasting) + " MS");
- }
-
-
-
-
-
-
-
-
- public void DOM4J() {
- long lasting = System.currentTimeMillis();
- try {
- File f = new File("F:/xmltest.xml");
- SAXReader reader = new SAXReader();
- org.dom4j.Document doc = reader.read(f);
- org.dom4j.Element root = doc.getRootElement();
- org.dom4j.Element foo;
- for (Iterator i = root.elementIterator("node"); i.hasNext();) {
- foo = (org.dom4j.Element) i.next();
- System.out.println("|| Name: |" + foo.elementText("name"));
- System.out.println("||Space: |" + foo.elementText("space"));
- System.out.println("-------------------------------------------------");
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- System.out.println("DOM4J RUNTIME:"
- + (System.currentTimeMillis() - lasting) + " MS");
- }
-
- public static void main(String arge[]) {
- MyXMLReader myXML = new MyXMLReader();
- System.out.println("=====================DOM=========================");
- myXML.DOM();
- System.out.println("=====================SAX=========================");
- myXML.SAX();
- System.out.println("=====================JDOM========================");
- myXML.JDOM();
- System.out.println("=====================DOM4J=======================");
- myXML.DOM4J();
- System.out.println("=================================================");
- }
- }
import java.io.File;
import java.util.Iterator;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.dom4j.io.SAXReader;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
public class MyXMLReader extends DefaultHandler {
java.util.Stack tags = new java.util.Stack();
public MyXMLReader() {
super();
}
/**
* DOM方式
* @since V2.0
* @author David.Wei
* @date 2008-4-11
* @return void
*/
public void DOM() {
long lasting = System.currentTimeMillis();
try {
File f = new File("F:/xmltest.xml");
DocumentBuilderFactory factory = DocumentBuilderFactory
.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(f);
NodeList nl = doc.getElementsByTagName("node");
for (int i = 0; i < nl.getLength(); i++) {
System.out.println("|| Name: |"
+ doc.getElementsByTagName("name").item(i)
.getFirstChild().getNodeValue());
System.out.println("||Space: |"
+ doc.getElementsByTagName("space").item(i)
.getFirstChild().getNodeValue());
System.out.println("-------------------------------------------------"); }
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("DOM RUNTIME:"
+ (System.currentTimeMillis() - lasting) + " MS");
}
/**
* SAX方式
* @since V2.0
* @author David.Wei
* @date 2008-4-11
* @return void
*/
public void SAX() {
long lasting = System.currentTimeMillis();
try {
SAXParserFactory sf = SAXParserFactory.newInstance();
SAXParser sp = sf.newSAXParser();
MyXMLReader reader = new MyXMLReader();
sp.parse(new InputSource("F:/xmltest.xml"), reader);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("SAX RUNTIME:"
+ (System.currentTimeMillis() - lasting) + " MS");
}
public void startElement(String uri, String localName, String qName,
Attributes attrs) {
tags.push(qName);
}
public void characters(char ch[], int start, int length)
throws SAXException {
String tag = (String) tags.peek();
if (tag.equals("name")) {
System.out.println("|| Name: |" + new String(ch, start, length));
}
if (tag.equals("space")) {
System.out.println("||Space: |" + new String(ch, start, length));
}
System.out.println("-------------------------------------------------");
}
/**
* JDOM方式
* @since V2.0
* @author David.Wei
* @date 2008-4-11
* @return void
*/
public void JDOM() {
long lasting = System.currentTimeMillis();
try {
SAXBuilder builder = new SAXBuilder();
org.jdom.Document doc = builder.build(new File("F:/xmltest.xml"));
Element foo = doc.getRootElement();
List allChildren = foo.getChildren();
for (int i = 0; i < allChildren.size(); i++) {
System.out.println("|| Name: |"
+ ((Element) allChildren.get(i)).getChild("name")
.getText());
System.out.println("||Space: |"
+ ((Element) allChildren.get(i)).getChild("space")
.getText());
System.out.println("-------------------------------------------------"); }
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("JDOM RUNTIME:"
+ (System.currentTimeMillis() - lasting) + " MS");
}
/**
* DOM4J方式
* @since V2.0
* @author David.Wei
* @date 2008-4-11
* @return void
*/
public void DOM4J() {
long lasting = System.currentTimeMillis();
try {
File f = new File("F:/xmltest.xml");
SAXReader reader = new SAXReader();
org.dom4j.Document doc = reader.read(f);
org.dom4j.Element root = doc.getRootElement();
org.dom4j.Element foo;
for (Iterator i = root.elementIterator("node"); i.hasNext();) {
foo = (org.dom4j.Element) i.next();
System.out.println("|| Name: |" + foo.elementText("name"));
System.out.println("||Space: |" + foo.elementText("space"));
System.out.println("-------------------------------------------------");
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("DOM4J RUNTIME:"
+ (System.currentTimeMillis() - lasting) + " MS");
}
public static void main(String arge[]) {
MyXMLReader myXML = new MyXMLReader();
System.out.println("=====================DOM=========================");
myXML.DOM();
System.out.println("=====================SAX=========================");
myXML.SAX();
System.out.println("=====================JDOM========================");
myXML.JDOM();
System.out.println("=====================DOM4J=======================");
myXML.DOM4J();
System.out.println("=================================================");
}
}
运行结果:
- =====================DOM=========================
- || Name: |weidewei
- ||Space: |http://wishlife.javaeye.com
- -------------------------------------------------
- || Name: |flying
- ||Space: |http://user.qzone.qq.com/94611981
- -------------------------------------------------
- DOM RUNTIME:62 MS
- =====================SAX=========================
- || Name: |weidewei
- -------------------------------------------------
- ||Space: |http://wishlife.javaeye.com
- -------------------------------------------------
- || Name: |flying
- -------------------------------------------------
- ||Space: |http://user.qzone.qq.com/94611981
- -------------------------------------------------
- SAX RUNTIME:16 MS
- =====================JDOM========================
- || Name: |weidewei
- ||Space: |http://wishlife.javaeye.com
- -------------------------------------------------
- || Name: |flying
- ||Space: |http://user.qzone.qq.com/94611981
- -------------------------------------------------
- JDOM RUNTIME:78 MS
- =====================DOM4J=======================
- || Name: |weidewei
- ||Space: |http://wishlife.javaeye.com
- -------------------------------------------------
- || Name: |flying
- ||Space: |http://user.qzone.qq.com/94611981
- -------------------------------------------------
- DOM4J RUNTIME:78 MS
- =================================================
posted @
2009-05-08 10:52 lanxin1020 阅读(166) |
评论 (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 阅读(169) |
评论 (0) |
编辑 收藏
- package cn.edu.tongji.cims.wade.system;
-
- import java.io.*;
-
- public class FileOperate {
- public FileOperate() {
- }
-
-
-
-
-
-
- public void newFolder(String folderPath) {
- try {
- String filePath = folderPath;
- filePath = filePath.toString();
- java.io.File myFilePath = new java.io.File(filePath);
- if (!myFilePath.exists()) {
- myFilePath.mkdir();
- }
- }
- catch (Exception e) {
- System.out.println("新建目录操作出错");
- e.printStackTrace();
- }
- }
-
-
-
-
-
-
-
- public void newFile(String filePathAndName, String fileContent) {
-
- try {
- String filePath = filePathAndName;
- filePath = filePath.toString();
- File myFilePath = new File(filePath);
- if (!myFilePath.exists()) {
- myFilePath.createNewFile();
- }
- FileWriter resultFile = new FileWriter(myFilePath);
- PrintWriter myFile = new PrintWriter(resultFile);
- String strContent = fileContent;
- myFile.println(strContent);
- resultFile.close();
-
- }
- catch (Exception e) {
- System.out.println("新建目录操作出错");
- e.printStackTrace();
-
- }
-
- }
-
-
-
-
-
-
-
- public void delFile(String filePathAndName) {
- try {
- String filePath = filePathAndName;
- filePath = filePath.toString();
- java.io.File myDelFile = new java.io.File(filePath);
- myDelFile.delete();
-
- }
- catch (Exception e) {
- System.out.println("删除文件操作出错");
- e.printStackTrace();
-
- }
-
- }
-
-
-
-
-
-
-
- public void delFolder(String folderPath) {
- try {
- delAllFile(folderPath);
- String filePath = folderPath;
- filePath = filePath.toString();
- java.io.File myFilePath = new java.io.File(filePath);
- myFilePath.delete();
-
- }
- catch (Exception e) {
- System.out.println("删除文件夹操作出错");
- e.printStackTrace();
-
- }
-
- }
-
-
-
-
-
- public void delAllFile(String path) {
- File file = new File(path);
- if (!file.exists()) {
- return;
- }
- if (!file.isDirectory()) {
- return;
- }
- String[] tempList = file.list();
- File temp = null;
- for (int i = 0; i < tempList.length; i++) {
- if (path.endsWith(File.separator)) {
- temp = new File(path + tempList[i]);
- }
- else {
- temp = new File(path + File.separator + tempList[i]);
- }
- if (temp.isFile()) {
- temp.delete();
- }
- if (temp.isDirectory()) {
- delAllFile(path+"/"+ tempList[i]);
- delFolder(path+"/"+ tempList[i]);
- }
- }
- }
-
-
-
-
-
-
-
- public void copyFile(String oldPath, String newPath) {
- try {
- int bytesum = 0;
- int byteread = 0;
- File oldfile = new File(oldPath);
- if (oldfile.exists()) {
- InputStream inStream = new FileInputStream(oldPath);
- FileOutputStream fs = new FileOutputStream(newPath);
- byte[] buffer = new byte[1444];
- int length;
- while ( (byteread = inStream.read(buffer)) != -1) {
- bytesum += byteread;
- System.out.println(bytesum);
- fs.write(buffer, 0, byteread);
- }
- inStream.close();
- }
- }
- catch (Exception e) {
- System.out.println("复制单个文件操作出错");
- e.printStackTrace();
-
- }
-
- }
-
-
-
-
-
-
-
- public void copyFolder(String oldPath, String newPath) {
-
- try {
- (new File(newPath)).mkdirs();
- File a=new File(oldPath);
- String[] file=a.list();
- File temp=null;
- for (int i = 0; i < file.length; i++) {
- if(oldPath.endsWith(File.separator)){
- temp=new File(oldPath+file[i]);
- }
- else{
- temp=new File(oldPath+File.separator+file[i]);
- }
-
- if(temp.isFile()){
- FileInputStream input = new FileInputStream(temp);
- FileOutputStream output = new FileOutputStream(newPath + "/" +
- (temp.getName()).toString());
- byte[] b = new byte[1024 * 5];
- int len;
- while ( (len = input.read(b)) != -1) {
- output.write(b, 0, len);
- }
- output.flush();
- output.close();
- input.close();
- }
- if(temp.isDirectory()){
- copyFolder(oldPath+"/"+file[i],newPath+"/"+file[i]);
- }
- }
- }
- catch (Exception e) {
- System.out.println("复制整个文件夹内容操作出错");
- e.printStackTrace();
-
- }
-
- }
-
-
-
-
-
-
- public void moveFile(String oldPath, String newPath) {
- copyFile(oldPath, newPath);
- delFile(oldPath);
-
- }
-
-
-
-
-
-
- public void moveFolder(String oldPath, String newPath) {
- copyFolder(oldPath, newPath);
- delFolder(oldPath);
-
- }
- }
posted @
2009-05-07 11:16 lanxin1020 阅读(395) |
评论 (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很难看.
尽管未修饰部分的对话框不能显示气泡提示的透明度,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 展示了透明示例.
总结(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 阅读(812) |
评论 (0) |
编辑 收藏
- 简单介绍及应用如下:
- 一、JAVA中所需要做的工作
- 在JAVA程序中,首先需要在类中声明所调用的库名称,如下:
-
- static {
- System.loadLibrary(“goodluck”);
- }
-
-
- 在这里,库的扩展名字可以不用写出来,究竟是DLL还是SO,由系统自己判断。
-
- 还需对将要调用的方法做本地声明,关键字为native。且只需要声明,而不需要具体实现。如下:
-
- public native static void set(int i);
- public native static int get();
-
-
- 然后编译该JAVA程序文件,生成CLASS,再用JAVAH命令,JNI就会生成C/C++的头文件。
-
- 例如程序testdll.java,内容为:
-
- public class testdll
- {
- static
- {
- System.loadLibrary("goodluck");
- }
- public native static int get();
- public native static void set(int i);
- public static void main(String[] args)
- {
- testdll test = new testdll();
- test.set(10);
- System.out.println(test.get());
- }
- }
-
-
- 用javac testdll.java编译它,会生成testdll.class。
- 再用javah testdll,则会在当前目录下生成testdll.h文件,这个文件需要被C/C++程序调用来生成所需的库文件。
-
- 二、C/C++中所需要做的工作
- 对于已生成的.h头文件,C/C++所需要做的,就是把它的各个方法具体的实现。然后编译连接成库文件即可。再把库文件拷贝到JAVA程序的路径下面,就可以用JAVA调用C/C++所实现的功能了。
- 接上例子。我们先看一下testdll.h文件的内容:
-
-
- #include
-
- #ifndef _Included_testdll
- #define _Included_testdll
- #ifdef __cplusplus
- extern "C" {
- #endif
-
-
-
-
-
- JNIEXPORT jint JNICALL Java_testdll_get (JNIEnv *, jclass);
-
-
-
-
-
- JNIEXPORT void JNICALL Java_testdll_set (JNIEnv *, jclass, jint);
- #ifdef __cplusplus
- }
- #endif
- #endif
-
-
- 在具体实现的时候,我们只关心两个函数原型
- JNIEXPORT jint JNICALL Java_testdll_get (JNIEnv *, jclass); 和
- JNIEXPORT void JNICALL Java_testdll_set (JNIEnv *, jclass, jint);
-
- 这里JNIEXPORT和JNICALL都是JNI的关键字,表示此函数是要被JNI调用的。而jint是以JNI为中介使JAVA的int类型与本 地的int沟通的一种类型,我们可以视而不见,就当做int使用。函数的名称是JAVA_再加上java程序的package路径再加函数名组成的。参数 中,我们也只需要关心在JAVA程序中存在的参数,至于JNIEnv*和jclass我们一般没有必要去碰它。
-
- 好,下面我们用testdll.cpp文件具体实现这两个函数:
-
- #include "testdll.h"
- int i = 0;
- JNIEXPORT jint JNICALL Java_testdll_get (JNIEnv *, jclass)
- {
- return i;
- }
- JNIEXPORT void JNICALL Java_testdll_set (JNIEnv *, jclass, jint j)
- {
- i = j;
- }
-
-
- 编译连接成库文件,本例是在WINDOWS下做的,生成的是DLL文件。并且名称要与JAVA中需要调用的一致,这里就是goodluck.dll 。把goodluck.dll拷贝到testdll.class的目录下,java testdll运行它,就可以观察到结果了。
- 我的项目比较复杂,需要调用动态链接库,这样在JNI传送参数到C程序时,需要对参数进行处理转换。才可以被C程序识别。
- 大体程序如下:
-
- public class SendSMS {
- static
- {
-
-
-
- System.out.println(System.getProperty("java.library.path"));
- System.loadLibrary("sms");
- }
- public native static int SmsInit();
- public native static int SmsSend(byte[] mobileNo, byte[] smContent);
- }
-
- 在这里要注意的是,path里一定要包含类库的路径,否则在程序运行时会抛出异常:
- java.lang.UnsatisfiedLinkError: no sms in java.library.path
- at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1491)
- at java.lang.Runtime.loadLibrary0(Runtime.java:788)
- at java.lang.System.loadLibrary(System.java:834)
- at com.mobilesoft.sms.mobilesoftinfo.SendSMS.(SendSMS.java:14)
- at com.mobilesoft.sms.mobilesoftinfo.test.main(test.java:18)
- Exception in thread "main"
-
- 指引的路径应该到.dll文件的上一级,如果指到.dll,则会报:
- java.lang.UnsatisfiedLinkError: C:\sms.dll: Can't find dependent libraries
- at java.lang.ClassLoader$NativeLibrary.load(Native Method)
- at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1560)
- at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1485)
- at java.lang.Runtime.loadLibrary0(Runtime.java:788)
- at java.lang.System.loadLibrary(System.java:834)
- at com.mobilesoft.sms.mobilesoftinfo.test.main(test.java:18)
- Exception in thread "main"
-
- 通过编译,生成com_mobilesoft_sms_mobilesoftinfo_SendSMS.h头文件。(建议使用Jbuilder进行编 译,操作比较简单!)这个头文件就是Java和C之间的纽带。要特别注意的是方法中传递的参数jbyteArray,这在接下来的过程中会重点介绍。
-
-
- #include
-
- #ifndef _Included_com_mobilesoft_sms_mobilesoftinfo_SendSMS
- #define _Included_com_mobilesoft_sms_mobilesoftinfo_SendSMS
- #ifdef __cplusplus
- extern "C" {
- #endif
-
-
-
-
-
- JNIEXPORT jint JNICALL Java_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsInit
- (JNIEnv *, jclass);
-
-
-
-
-
- JNIEXPORT jint JNICALL Java_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsSend
- (JNIEnv *, jclass, jbyteArray, jbyteArray);
- #ifdef __cplusplus
- }
- #endif
- #endif
-
-
-
- 对于我要调用的C程序的动态链接库,C程序也要提供一个头文件,sms.h。这个文件将要调用的方法罗列了出来。
-
-
-
-
-
-
- #ifndef MCS_SMS_H
- #define MCS_SMS_H
- #define DLLEXPORT __declspec(dllexport)
-
- #define SMS_SIM 0
- #define SMS_MT 1
-
- #define SMS_UNREAD 0
- #define SMS_READ 1
-
- #define SMS_NOPARSE -1
- #define SMS_NORMAL 0
- #define SMS_FLASH 1
- #define SMS_MMSNOTI 2
- typedef struct tagSmsEntry {
- int index;
- int status;
- int type;
- int storage;
- char date[24];
- char number[32];
- char text[144];
- } SmsEntry;
- DLLEXPORT int SmsInit(void);
- DLLEXPORT int SmsSend(char *phonenum, char *content);
- DLLEXPORT int SmsSetSCA(char *sca);
- DLLEXPORT int SmsGetSCA(char *sca);
- DLLEXPORT int SmsSetInd(int ind);
- DLLEXPORT int SmsGetInd(void);
- DLLEXPORT int SmsGetInfo(int storage, int *max, int *used);
- DLLEXPORT int SmsSaveFlash(int flag);
- DLLEXPORT int SmsRead(SmsEntry *entry, int storage, int index);
- DLLEXPORT int SmsDelete(int storage, int index);
- DLLEXPORT int SmsModifyStatus(int storage, int index);
- #endif
-
-
- 在有了这两个头文件之后,就可以进行C程序的编写了。也就是实现对JNI调用的两个方法。在网上的资料中,由于调用的方法实现的都比较简单,(大多是打印字符串等)所以避开了JNI中最麻烦的部分,也是最关键的部分,参数的传递。
- 由于Java和C的编码是不同的,所以传递的参数是要进行再处理,否则C程序是会对参数在编译过程中提出警告,例如;warning C4024: 'SmsSend' : different types for formal and actual parameter 2等。
- Sms.c的程序如下:
-
- #include "sms.h"
- #include "com_mobilesoft_sms_mobilesoftinfo_SendSMS.h"
- JNIEXPORT jint JNICALL Java_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsInit(JNIEnv * env, jclass jobject)
- {
- return SmsInit();
- }
-
- JNIEXPORT jint JNICALL Java_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsSend(JNIEnv * env, jclass jobject, jbyteArray mobileno, jbyteArray smscontent)
- {
- char * pSmscontent ;
-
- jbyte * arrayBody = (*env)->GetByteArrayElements(env,mobileno,0);
- char * pMobileNo = (char *)arrayBody;
- printf("[%s]\n ", pMobileNo);
-
- arrayBody = (*env)->GetByteArrayElements(env,smscontent,0);
- pSmscontent = (char *)arrayBody;
- printf("<%s>\n", pSmscontent);
- return SmsSend(pMobileNo,pSmscontent);
- }
-
-
-
- 对于C或C++,在程序上是会有稍微的不同,这可以由读者对其进行适当的修改。这里要注意的是GetArrayLength,GetByteArrayElements等这些JNI中已经包含的方法,这些方法是专门对转换参数类型而提供的。具体的方法有很多,在下一篇中会做专门的介绍。
- 在完成了上述的文件后,可以对sms.c进行编译,生成.dll文件(建议在release中编译,这样动态链接库的容积会比较小!)
- 完成.dll文件的编译后,就可以在Java中调用C程序中的方法了。例如文件test.java
-
- public class test {
- public test() {
- }
- public static void main(String[] args) {
- byte[] mobileno = {
- 0x31, 0x33, 0x36, 0x36, 0x31, 0x36, 0x33, 0x30, 0x36, 0x36, 0x37, 0x00};
- String smscontentemp = "早上好";
- byte[] temp = {0};
- try {
- byte[] smscontentdb = smscontentemp.getBytes("gbk");
- byte[] smscontent = new byte[smscontentdb.length + temp.length];
- System.arraycopy(smscontentdb, 0, smscontent, 0, smscontentdb.length);
- System.arraycopy(temp, 0, smscontent, smscontentdb.length, temp.length);
- SendSMS sendSMS = new SendSMS();
- sendSMS.SmsInit();
- if (sendSMS.SmsSend(mobileno, smscontent) >= 0) {
- System.out.println("chenggong !");
- }
- else {
- System.out.println("shibai !");
- }
- }catch (Exception ex) {}
- }
- }
-
-
-
- 在这个文件中要注意的有一点,就是在传递字节数组到C程序中时,最后的结尾一定要以0结束。这是一个偷懒的做法,不过是个有效的做法。因为大多数情况 下,接口是由第三方提供的。所以我们一般是不知道在C的方法里,具体是怎么处理参数的。而C又是要求数组是有长度。所以,在Java中,如果你不想写程序 传数组的长度,那么在数组中以0结尾就是最方便的方法了。当然,如果有更好的方法也希望大家提出。
-
- 到这里,一个完整的Java通过JNI调用动态链接库的程序就完成了。实际上也不是很复杂。只要多注意一下细节,是很容易得出来的。
posted @
2009-05-07 11:04 lanxin1020 阅读(167) |
评论 (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. 装载属性
- import java.util.*;
- import java.io.*;
-
- public class LoadSample {
- public static void main(String args[]) throws Exception {
- Properties prop = new Properties();
- FileInputStream fis =
- new FileInputStream("sample.properties");
- prop.load(fis);
- prop.list(System.out);
- System.out.println("\nThe foo property: " +
- prop.getProperty("foo"));
- }
- }
import java.util.*;
import java.io.*;
public class LoadSample {
public static void main(String args[]) throws Exception {
Properties prop = new Properties();
FileInputStream fis =
new FileInputStream("sample.properties");
prop.load(fis);
prop.list(System.out);
System.out.println("\nThe foo property: " +
prop.getProperty("foo"));
}
}
运行 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 写道
- <?xml version="1.0" encoding="UTF-8"?>
- <!-- DTD for properties -->
- <!ELEMENT properties ( comment?, entry* ) >
- <!ATTLIST properties version CDATA #FIXED "1.0">
- <!ELEMENT comment (#PCDATA) >
- <!ELEMENT entry (#PCDATA) >
- <!ATTLIST entry key CDATA #REQUIRED>
<?xml version="1.0" encoding="UTF-8"?>
<!-- DTD for properties -->
<!ELEMENT properties ( comment?, entry* ) >
<!ATTLIST properties version CDATA #FIXED "1.0">
<!ELEMENT comment (#PCDATA) >
<!ELEMENT entry (#PCDATA) >
<!ATTLIST entry key CDATA #REQUIRED>
如果不想细读 XML DTD,那么可以告诉您它其实就是说在外围 <properties> 标签中包装的是一个 <comment> 标签,后面是任意数量的 <entry> 标签。对每一个 <entry> 标签,有一个键属性,输入的内容就是它的值。清单 5 显示了 清单 1中的属性文件的 XML 版本是什么样子的。
清单 5. XML 版本的属性文件
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
- <properties>
- <comment>Hi</comment>
- <entry key="foo">bar</entry>
- <entry key="fu">baz</entry>
- </properties>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>Hi</comment>
<entry key="foo">bar</entry>
<entry key="fu">baz</entry>
</properties>
如果清单 6 所示,读取 XML 版本的 Properties 文件与读取老格式的文件没什么不同。
清单 6. 读取 XML Properties 文件
- import java.util.*;
- import java.io.*;
-
- public class LoadSampleXML {
- public static void main(String args[]) throws Exception {
- Properties prop = new Properties();
- FileInputStream fis =
- new FileInputStream("sampleprops.xml");
- prop.loadFromXML(fis);
- prop.list(System.out);
- System.out.println("\nThe foo property: " +
- prop.getProperty("foo"));
- }
- }
import java.util.*;
import java.io.*;
public class LoadSampleXML {
public static void main(String args[]) throws Exception {
Properties prop = new Properties();
FileInputStream fis =
new FileInputStream("sampleprops.xml");
prop.loadFromXML(fis);
prop.list(System.out);
System.out.println("\nThe foo property: " +
prop.getProperty("foo"));
}
}
关于资源绑定的说明
虽然
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 文件
- import java.util.*;
- import java.io.*;
-
- public class StoreXML {
- public static void main(String args[]) throws Exception {
- Properties prop = new Properties();
- prop.setProperty("one-two", "buckle my shoe");
- prop.setProperty("three-four", "shut the door");
- prop.setProperty("five-six", "pick up sticks");
- prop.setProperty("seven-eight", "lay them straight");
- prop.setProperty("nine-ten", "a big, fat hen");
- FileOutputStream fos =
- new FileOutputStream("rhyme.xml");
- prop.storeToXML(fos, "Rhyme");
- fos.close();
- }
- }
import java.util.*;
import java.io.*;
public class StoreXML {
public static void main(String args[]) throws Exception {
Properties prop = new Properties();
prop.setProperty("one-two", "buckle my shoe");
prop.setProperty("three-four", "shut the door");
prop.setProperty("five-six", "pick up sticks");
prop.setProperty("seven-eight", "lay them straight");
prop.setProperty("nine-ten", "a big, fat hen");
FileOutputStream fos =
new FileOutputStream("rhyme.xml");
prop.storeToXML(fos, "Rhyme");
fos.close();
}
}
运行清单 7 中的程序产生的输出如清单 8 所示。
清单 8. 存储的 XML 文件
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
- <properties>
- <comment>Rhyme</comment>
- <entry key="seven-eight">lay them straight</entry>
- <entry key="five-six">pick up sticks</entry>
- <entry key="nine-ten">a big, fat hen</entry>
- <entry key="three-four">shut the door</entry>
- <entry key="one-two">buckle my shoe</entry>
- </properties>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>Rhyme</comment>
<entry key="seven-eight">lay them straight</entry>
<entry key="five-six">pick up sticks</entry>
<entry key="nine-ten">a big, fat hen</entry>
<entry key="three-four">shut the door</entry>
<entry key="one-two">buckle my shoe</entry>
</properties>
结束语
使用 XML 文件还是使用老式的 a=b 类型的文件完全取决于您自己。老式文件从内存的角度看肯定是轻量级的。不过,由于 XML 的普遍使用,人们会期望 XML 格式流行起来,因为它已经被广泛使用了,只不过没有用到 Properties 对象。选择完全在您。分析软件包 private XMLUtils 类的源代码以获得关于所使用的 XML 解析的更多信息。
- import java.io.FileInputStream;
- import java.io.IOException;
- import java.io.InputStream;
- import java.util.Properties;
-
-
-
-
-
- public class PTest {
- public static void main(String[] args) {
- try {
- long start = System.currentTimeMillis();
- InputStream is = new FileInputStream("conf.properties");
- Properties p = new Properties();
- p.load(is);
- is.close();
- System.out.println("SIZE : " + p.size());
- System.out.println("homepage : " + p.getProperty("homepage"));
- System.out.println("author : " + p.getProperty("author"));
- System.out.println("school : " + p.getProperty("school"));
- System.out.println("date : " + p.getProperty("date"));
- long end = System.currentTimeMillis();
- System.out.println("Cost : " + (end - start));
- } catch (IOException ioe) {
- ioe.printStackTrace();
- }
- }
- }
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
* 实现properties文件的读取
* @author bbflyerwww
* @date 2006-08-02
*/
public class PTest {
public static void main(String[] args) {
try {
long start = System.currentTimeMillis();
InputStream is = new FileInputStream("conf.properties");
Properties p = new Properties();
p.load(is);
is.close();
System.out.println("SIZE : " + p.size());
System.out.println("homepage : " + p.getProperty("homepage"));
System.out.println("author : " + p.getProperty("author"));
System.out.println("school : " + p.getProperty("school"));
System.out.println("date : " + p.getProperty("date"));
long end = System.currentTimeMillis();
System.out.println("Cost : " + (end - start));
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
conf.properties
- # Configuration fileauthor = bbflyerwww
- school = WuHan University
- date = 2006-08-02
# Configuration fileauthor = bbflyerwww
school = WuHan University
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 阅读(463) |
评论 (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 代码
- package org.apache.log4j;
-
- public class Logger {
-
-
- public static Logger getRootLogger();
- public static Logger getLogger(String name);
-
-
- public void debug(Object message);
- public void info(Object message);
- public void warn(Object message);
- public void error(Object message);
- public void fatal(Object message);
-
-
- public void log(Priority p, Object message);
- }
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 代码
- import org.apache.log4j.Logger;
- import org.apache.log4j.PropertyConfigurator;
-
- public class Test {
-
- public static void main(String[] args) {
-
- Logger myLogger = Logger.getLogger("myLogger");
-
-
- Logger mySonLogger = Logger.getLogger("myLogger.mySonLogger");
-
- PropertyConfigurator.configure("log4j.properties");
-
-
- 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());
- }
- }
程序运行结果为:
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 代码
- import javax.servlet.*;
- import javax.servlet.http.*;
- import java.io.*;
- import java.util.*;
-
- import org.apache.log4j.PropertyConfigurator;
-
- public class Log4JServlet extends HttpServlet {
- public void init() throws ServletException {
- String path = getServletContext().getRealPath("/");
-
- String propfile = path + getInitParameter("propfile");
- PropertyConfigurator.configure(propfile);
- }
- }
-
该Servlet在web.xml中的配置如下:
xml 代码
- <servlet>
- <servlet-name>log4jServlet</servlet-name>
- <servlet-class>Log4JServlet</servlet-class>
- <init-param>
- <param-name>propfile</param-name>
- <param-value>/WEB-INF/log4j.properties</param-value>
- </init-param>
- <load-on-startup>1</load-on-startup>
- </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 代码
- <servlet>
- <servlet-name>log4jServlet</servlet-name>
- <servlet-class>Log4JServlet</servlet-class>
- <init-param>
- <param-name>profile</param-name>
- <param-value>/WEB-INF/log4j.properties</param-value>
- </init-param>
- <load-on-startup>1</load-on-startup>
- </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 阅读(160) |
评论 (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 阅读(121) |
评论 (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文件中加载配置信息。这种方式更方便修改配置。
动态配置
- package http;
-
- import org.apache.log4j.BasicConfigurator;
- import org.apache.log4j.Logger;
- import org.apache.log4j.PropertyConfigurator;
- import org.apache.log4j.xml.DOMConfigurator;
-
- public class Log4jDemo {
- static Logger log = Logger.getLogger(Log4jDemo.class.getClass());
-
-
-
-
- public static void main(String[] args) {
- BasicConfigurator.configure();
- PropertyConfigurator.configure("c:/log4j.properties");
-
-
-
- DOMConfigurator.configure("c:/log4j.xml");
-
-
-
-
- log.info("info");
- log.debug("debug");
- log.error("error");
- log.warn("warn");
- }
-
- }
package http;
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import org.apache.log4j.xml.DOMConfigurator;
public class Log4jDemo {
static Logger log = Logger.getLogger(Log4jDemo.class.getClass());
/**
* main
* @param args
*/
public static void main(String[] args) {
BasicConfigurator.configure();//默认配置
PropertyConfigurator.configure("c:/log4j.properties");
//动态配置,参数可以是一个properties文件所在路径的String对象
//可以是一个properties文件所在路径的URL对象,也可以是一个properties对象
DOMConfigurator.configure("c:/log4j.xml");//XML配置文件
//PropertyConfigurator.configure()的参数还可以是XML、Properties对象
//下面就可使用log4j
log.info("info");
log.debug("debug");
log.error("error");
log.warn("warn");
}
}
J2EE应用
log4j
上面代码描述了
Log4j的简单应用,其实使用
Log4j也就是这样简单方便。当然除了上面的配置方法,还有其它,比如做一个J2EE应用,在J2EE应用使用
Log4j,必须先在启动服务时加载
Log4j的配置文件进行初始化,可以在web.xml中进行。
java 代码
- import java.io.IOException;
- import java.io.PrintWriter;
-
- import javax.servlet.ServletException;
- import javax.servlet.http.HttpServlet;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
-
-
-
- public class J2eeLog4jDemo extends HttpServlet {
-
- public void destroy() {
- super.destroy();
- }
- public void doGet(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- }
- public void doPost(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- }
- public void init() throws ServletException {
-
- String prefix = getServletContext().getRealPath("/");
- String file = getInitParameter("log4j");
-
-
- if(file != null)
- {
- PropertyConfigurator.configure(prefix+file);
- }
- }
-
- }
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class J2eeLog4jDemo extends HttpServlet {
public void destroy() {
super.destroy();
}
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
}
public void init() throws ServletException {
//通过web.xml来动态取得配置文件
String prefix = getServletContext().getRealPath("/");
String file = getInitParameter("log4j");
//如果没有给出相应的配置文件,则不进行初始化
if(file != null)
{
PropertyConfigurator.configure(prefix+file);
}
}
}
Web.xml 代码
- <servlet>
- <servlet-name>j2eelog4jdemoservlet-name>
- <servlet-class>J2eeLog4jDemoservlet-class>
- <init-param>
- <param-name>log4jparam-name>
- <param-value>log4j.propertiesparam-value>
- init-param>
- <load-on-startup>1load-on-startup>
- servlet
<servlet>
<servlet-name>j2eelog4jdemoservlet-name>
<servlet-class>J2eeLog4jDemoservlet-class>
<init-param>
<param-name>log4jparam-name>
<param-value>log4j.propertiesparam-value>
init-param>
<load-on-startup>1load-on-startup> //设为1时,Web容器启动即进行加载
servlet
Spring
配置Log4j
Spring中配置
Log4j只要配置applicationContext.xml文件,
Log4j的配置文件放在Web工程的根目录下,默认是objectname/root下,可以在web.xml中设置工程根目录.
设置根目录
web.xml 代码
- <!--不定义webAppRootKey参数,webAppRootKey就是缺省的"webapp.root"-->
- <context-param>
- <param-name>webAppRootKeyparam-name>
- <param-value>webapp.rootparam-value>
- context-param>
<!--不定义webAppRootKey参数,webAppRootKey就是缺省的"webapp.root"-->
<context-param>
<param-name>webAppRootKeyparam-name>
<param-value>webapp.rootparam-value>
context-param>
配置applicationContext.xml
applicationContext.xml 代码
- <!--由Sprng载入的Log4j配置文件位置-->
- <context-param>
- <param-name>log4jConfigLocationparam-name>
- <param-value>/WEB-INF/log4j.propertiesparam-value>
- <!--在这里定位配置文件,需要的是从root开始的绝对路径-->
- context-param>
-
-
-
- <!--Spring默认刷新Log4j配置文件的间隔,单位为millisecond-->
- <context-param>
- <param-name>log4jRefreshIntervalparam-name>
- <param-value>60000param-value>
- context-param>
-
- <!--Spring log4j 监听器-->
- <listener>
- <listener-class>org.springframework.web.util.Log4jConfigListenerlistener-class>
- listener>
<!--由Sprng载入的Log4j配置文件位置-->
<context-param>
<param-name>log4jConfigLocationparam-name>
<param-value>/WEB-INF/log4j.propertiesparam-value>
<!--在这里定位配置文件,需要的是从root开始的绝对路径-->
context-param>
<!--Spring默认刷新Log4j配置文件的间隔,单位为millisecond-->
<context-param>
<param-name>log4jRefreshIntervalparam-name>
<param-value>60000param-value>
context-param>
<!--Spring log4j 监听器-->
<listener>
<listener-class>org.springframework.web.util.Log4jConfigListenerlistener-class>
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中,两者就可以很好的合作,实现如下:
- package com.doctorcom.model;
-
- import org.apache.commons.logging.Log;
-
- public class LogFactorySupport {
-
- public Log getLog(){
- Log log = org.apache.commons.logging.LogFactory.getLog(LogFactorySupport.class);
- log.info("");
- log.debug("");
- }
-
- }
package com.doctorcom.model;
import org.apache.commons.logging.Log;
public class LogFactorySupport {
public Log getLog(){
Log log = org.apache.commons.logging.LogFactory.getLog(LogFactorySupport.class);
log.info("");
log.debug("");
}
}
java 代码
Log4j配置内容
看一个简单的java属性配置文件
log4j.properties:
properties 代码
- #指定根Logger,及日志输出级别,大于等于该级别的日志将被输出( DEBUG < INFO < WARN < ERROR < FATAL ) 设为OFF可以关闭日志
- log4j.rootLogger=DEBUG, A1,A2
- #指定log输出目的,这里设为输出日志到指定目录的文件my.log中
- log4j.appender.A1=org.apache.log4j.FileAppender
- log4j.appender.A1.File=\\logs\\my.log #当前根目录下
- #指定日志信息的格式
- log4j.appender.A1.layout=org.apache.log4j.PatternLayout
- log4j.appender.A1.layout.ConversionPattern=%r %d{yyyy-MM-dd HH:mm:ss} %c %p -%m%n
-
- #把A2输出到控制台
- log4j.appender.A2=org.apache.log4j.ConsoleAppender
- log4j.appender.A2.layout=org.apache.log4j.SimpleLayout
-
- #还可以单独指定输出某个包的日志级别
- #log4j.logger.com.study.HelloLog4j=INFO
#指定根Logger,及日志输出级别,大于等于该级别的日志将被输出( DEBUG < INFO < WARN < ERROR < FATAL ) 设为OFF可以关闭日志
log4j.rootLogger=DEBUG, A1,A2
#指定log输出目的,这里设为输出日志到指定目录的文件my.log中
log4j.appender.A1=org.apache.log4j.FileAppender
log4j.appender.A1.File=\\logs\\my.log #当前根目录下
#指定日志信息的格式
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%r %d{yyyy-MM-dd HH:mm:ss} %c %p -%m%n
#把A2输出到控制台
log4j.appender.A2=org.apache.log4j.ConsoleAppender
log4j.appender.A2.layout=org.apache.log4j.SimpleLayout
#还可以单独指定输出某个包的日志级别
#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 阅读(155) |
评论 (0) |
编辑 收藏
HashMap通过hashcode对其内容进行快速查找,而TreeMap中所有的元素都保持着某种固定的顺序,如果你需要得到一个有序的结果你就应该使用TreeMap(HashMap中元素的排列顺序是不固定的)。
集合框架”提供两种常规的Map实现:HashMap和TreeMap (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 阅读(205) |
评论 (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 阅读(1082) |
评论 (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 阅读(140) |
评论 (0) |
编辑 收藏
1.Struts 2的基本流程
Struts 2框架由3个部分组成:核心控制器FilterDispatcher、业务控制器和用户实现的业务逻辑组件。在这3个部分里,Struts 2框架提供了核心控制器FilterDispatcher,而用户需要实现业务控制器和业务逻辑组件。
2.核心控制器:FilterDispatcher
FilterDispatcher是Struts 2框架的核心控制器,该控制器作为一个Filter运行在Web应用中,它负责拦截所有的用户请求,当用户请求到达时,该Filter会过滤用户请求。如果用户请求以action结尾,该请求将被转入Struts 2框架处理。
Struts 2框架获得了*.action请求后,将根据*.action请求的前面部分决定调用哪个业务逻辑组件,例如,对于login.action请求,Struts 2调用名为login的Action来处理该请求。
Struts 2应用中的Action都被定义在struts.xml文件中,在该文件中定义Action时,定义了该Action的name属性和class属性,其中name属性决定了该Action处理哪个用户请求,而class属性决定了该Action的实现类。
Struts 2用于处理用户请求的Action实例,并不是用户实现的业务控制器,而是Action代理——因为用户实现的业务控制器并没有与Servlet API耦合,显然无法处理用户请求。而Struts 2框架提供了系列拦截器,该系列拦截器负责将HttpServletRequest请求中的请求参数解析出来,传入到Action中,并回调Action 的execute方法来处理用户请求。
显然,上面的处理过程是典型的AOP(面向切面编程)处理方式。图3.19显示了这种处理模型。
图3.19 Struts 2的拦截器和Action
从图3.19中可以看出,用户实现的Action类仅仅是Struts 2的Action代理的代理目标。用户实现的业务控制器(Action)则包含了对用户请求的处理。用户的请求数据包含在 HttpServletRequest对象里,而用户的Action类无需访问HttpServletRequest对象。拦截器负责将 HttpServletRequest里的请求数据解析出来,并传给业务逻辑组件Action实例。
3.业务控制器
正如从图3.19所看到的,业务控制器组件就是用户实现Action类的实例,Action类里通常包含了一个execute方法,该方法返回一个字符串——该字符串就是一个逻辑视图名,当业务控制器处理完用户请求后,根据处理结果不同,execute方法返回不同字符串 ——每个字符串对应一个视图名。
程序员开发出系统所需要的业务控制器后,还需要配置Struts 2的Action,即需要配置Action的如下三个部分定义:
— Action所处理的URL。
— Action组件所对应的实现类。
— Action里包含的逻辑视图和物理资源之间的对应关系。
每个Action都要处理一个用户请求,而用户请求总是包含了指定URL。当Filter Dispatcher拦截到用户请求后,根据请求的URL和Action处理URL之间的对应关系来处理转发。
4.Struts 2的模型组件
实际上,模型组件已经超出了MVC框架的覆盖范围。对于Struts 2框架而言,通常没有为模型组件的实现提供太多的帮助。
文本框: 图3.20 控制器调用模型组件Java EE应用里的模型组件,通常指系统的业务逻辑组件。而隐藏在系统的业务逻辑组件下面的,可能还包含了DAO、领域对象等组件。
通常,MVC框架里的业务控制器会调用模型组件的方法来处理用户请求。也就是说,业务逻辑控制器不会对用户请求进行任何实际处理,用户请求最终由模型组件负责处理。业务控制器只是中间负责调度的调度器,这也是称Action为控制器的原因。
图3.20显示了这种处理流程。
提示 在图3.20中看到Action调用业务逻辑组件的方法。当控制器需要获得业务逻辑组件实例时,通常并不会直接获取业务逻辑组件实例,而是通过工厂模式来获得业务逻辑组件的实例;或者利用其他IoC容器(如Spring容器)来管理业务逻辑组件的实例。
5.Struts 2的视图组件
Struts 2已经改变了Struts 1只能使用JSP作为视图技术的现状,Struts 2允许使用其他的模板技术,如FreeMarker、Velocity作为视图技术。
当Struts 2的控制器返回逻辑视图名时,逻辑视图并未与任何的视图技术关联,仅仅是返回一个字符串,该字符串作为逻辑视图名。
当我们在struts.xml文件中配置 Action时,不仅需要指定Action的name属性和class属性,还要为Action元素指定系列result子元素,每个result子元素定义一个逻辑视图和物理视图之间的映射。前面所介绍的应用都使用了JSP技术作为视图,故配置result子元素时没有指定type属性,默认使用JSP 作为视图资源。
如果需要在Struts 2中使用其他视图技术,则可以在配置result子元素时,指定相应的type属性即可。例如,如果需要使用FreeMarker,则为result指定值为freemarker的type属性;如果想使用Velocity模板技术作为视图资源,则为result指定值为velocity的type属性……
6.Struts 2的运行流程
经过上面介绍,我们发现Struts 2框架的运行流程非常类似于
WebWork框架的流程。
提示 在Struts 2的官方站点,我们可以找到如下说法:Essentially,Struts 2.0 is the technical equivalent of
WebWork 2.3。Aside from the package and property renaming,it isn't much different than,say,migrating from
WebWork 2.1 to 2.2——意思是说:Struts 2.0技术等同于
WebWork 2.3框架,除了包和属性被改名外。从
WebWork 2.2迁移到Struts 2不会比从
WebWork 2.1迁移到
WebWork 2.2更复杂。
这里我们可以看到,Struts 2其实就是
WebWork 2.2的升级版,这也就不难理解:为什么
WebWork和Struts 2如此相似!
因此,Struts 2的运行流程与
WebWork的运行流程完全相同,读者可以参看图1.8来了解Struts 2的运行流程。
posted @
2009-04-15 14:03 lanxin1020 阅读(1434) |
评论 (0) |
编辑 收藏
摘要: 问题:
struts2 使用jakarta 上传文件时,如果上传文件的大小超出commons fileupload(jakarta上传文件还是依赖commons-fileupload)设置的大小就会在进入action以前抛出异常.
&nb...
阅读全文
posted @
2009-04-14 12:50 lanxin1020 阅读(2216) |
评论 (0) |
编辑 收藏
下文书中包的版本:commons-fileupload-1.2.1.jar、struts2-core-2.1.2.jar
孙鑫的书《Struts2 深入详解》509页是关于限制上传文件的最大长度的内容。
其中谈到fileUpload拦截器只是当文件上传到服务器上之后,才进行的文件类型和大小判断。
Struts2框架底层默认用的是apache的commons-fileupload组件对上传文件进行接受处理。
通过struts.multipart.maxSize属性来对文件大小进行限定时,将直接影响到commons-fileupload组件的文件大小设定,默认是2M。当上传文件超过了这个尺寸时,将从commons-fileupload组件中抛出SizeLimitExceededException异常。上传文件拦截器捕获到这个异常后,将直接把该异常信息设置为Action级别的错误信息。
经过我的测试和对源代码的Debug,发现确实如孙鑫书中所言,如果上传文件大于2M时,在页面上就出现了一堆英文的错误信息,大致是:the request was rejected because its size....exceeds the configured maximum...并且在fieUpload中将来自MultiPartRequestWrapper型request对象的错误信息给加到了Action的错误中。
这时候,你在ApplicationResources.properties中自定义的上传文件过大的错误信息根本不起作用。原因就如书上所言,在底层commons-fileupload组件中就把异常给抛出来了文件根本没被上传,所以到了fileUpload拦截器时,根据取不到文件,当然也就没法对文件的类型和大小进行判断了。
然而,这个异常直接带来两个问题:
1、在页面上显示了英文的错误信息。这样的信息显然不是我们想要的。
2、由于错误的产生,原来页面上输入的其他文本内容也都不见了,也就是说params注入失败。
带着这两个问题,我们来探寻一下Struts2对于请求的处理过程。
注:这并不是一篇关于Struts2请求过程的介绍,主要是为了解决以上两个问题,才引起的简单分析。
首先当然我们要拿FilterDispatcher开刀。
在doFilter方法中调用了prepareDispatcherAndWrapRequest方法,为了包装出Struts2自己的request对象,在prepareDispatcherAndWrapRequest方法中调用Dispatcher类的wrapRequest方法,在这个方法里,会根据请求内容的类型(提交的是文本的,还是multipart/form-data格式),决定是使用tomcat的HttpServletRequestWrapper类分离出请求中的数据,还是使用Struts2的MultiPartRequestWrapper来分离请求中的数据。
注:向服务器请求时,数据是以流的形式向服务器提交,内容是一些有规则东东,我们平时在jsp中用request内置对象取parameter时,实际上是由tomcat的HttpServletRequestWrapper类分解好了的,无需我们再分解这些东西了。
当然,在这里,我们研究的是上传文件的情况,所以,由于form中设定的提交内容是媒体格式的,所以,Dispatcher类的wrapRequest方法会将请求交由MultiPartRequestWrapper类来处理。
MultiPartRequestWrapper这个类是Struts2的类,并且继承了tomcat的HttpServletRequestWrapper类,也是我们将用来代替HttpServletRequest这个类的类,看名字也知道,是对多媒体请求的包装类。
Struts2本身当然不会再造个轮子,来解析请求,而是交由Apache的commons-fileupload组件来解析了。
在MultiPartRequestWrapper的构造方法中,会调用MultiPartRequest(默认为JakartaMultiPartRequest类)的parse方法来解析请求。
在Struts2的JakartaMultiPartRequest类的parse方法中才会真正来调用commons-fileupload组件的ServletFileUpload类对请求进行解析,至此,Struts2已经实现了将请求转交commons-fileupload组件对请求解析的全过程。剩下的就是等commons-fileupload组件对请求解析完毕后,拿到分解后的数据,根据field名,依次将分解后的field名和值放到params(HashMap类型)里,同时JakartaMultiPartRequest类重置了HttpServletRequest的好多方法,比如熟知的getParameter、getParameterNames、getParameterValues,实际上都是从解析后得到的那个params对象里拿数据,在这个过程,commons-fileupload组件也乖乖的把上传的文件分析好了,JakartaMultiPartRequest也毫不客气的把分解后的文件一个一个的放到了files(HashMap类型)中,实际上此时,commons-fileupload组件已经所有要上传的文件上传完了。至此,Struts2实现了对HttpServletRequest类的包装,当回到MultiPartRequestWrapper类后,再取一下上述解析过程中发生的错误,然后把错误加到了自己的errors列表中了。同样我们会发现在MultiPartRequestWrapper类中,也把HttpServletRequest类的好多方法重载了,毕竟是个包装类嘛,实际上对于上传文件的请求,在Struts2后期的处理中用到的request都是MultiPartRequestWrapper类对象,比如我们调用getParameter时,直接调用的是MultiPartRequestWrapper的getParameter方法,间接调的是JakartaMultiPartRequest类对象的getParameter方法。
注:从这里,我们就可以看出,JakartaMultiPartRequest是完全设计成可以替换的类了。
然后继续向回返,到了Dispatcher类的wrapRequest方法,直接把MultiPartRequestWrapper对象返回了,我们就终于回到了FilterDispatcher类的prepareDispatcherAndWrapRequest方法,此时,我们拿到了完全解析好了的request对象(MultiPartRequestWrapper类),该对象又进一步被返回到了FilterDispatcher类的doFilter方法,也就是回到了出发点,至此,doFilter中拿到的request对象就是一个将请求中的数据分解好的了HttpServletRequest对象,我们完全可以用getParameter方法取其中的数据了,同时,我们也可以用getFiles得到文件数组了。
doFilter方法中,会进一步调用actionMapper的getMapping方法对url进行解析,找出命名空间和action名等,以备后面根据配置文件调用相应的拦截器和action使用。
关于doFilter方法中下一步对Dispatcher类的serviceAction方法的调用,不再描述,总之在action被调用之前,会首先走到fileUpload拦截器(对应的是FileUploadInterceptor类),在这个拦截器中,会先看一下request是不是 MultiPartRequestWrapper,如果不是,就说明不是上传文件用的request,fildUpload拦截器会直接将控制权交给下一个拦截器;如果是,就会把request对象强转为MultiPartRequestWrapper对象,然后调用hasErrors方法,看看有没有上传时候产生的错误,有的话,就直接加到了Action的错误(Action级别的)中了。另外,在fileUpload拦截器中会将MultiPartRequestWrapper对象中放置的文件全取出来,把文件、文件名、文件类型取出来,放到request的parameters中,这样到了params拦截器时,就可以轻松的将这些内容注入到Action中了,这也就是为什么fileUpload拦截器需要放在params拦截器前面的理由。在文件都放到request的parameters对象里之后,fileUpload拦截器会继续调用其他拦截器直到Action等执行完毕,他还要做一个扫尾的工作:把临时文件夹中的文件删除(这些文件是由commons-fileupload组件上传的,供你在自己的Action中将文件copy到指定的目录下,当action执行完了后,这些临时文件当然就没用了)。
你好,你还在看吗?呵呵,是不是太多了,也太乱了,没办法,Struts2就是这样的调用的。也不知道Struts2有没有公开其Sequence图,我是想画一个,不过,太懒,还是看着代码说说吧。
如果上面看烦了,也完全可以不看了,直接看下面的。
在上面一番分析之后,文件上传的全过程就结束了。
我们回到我们的问题上来。
先看第一个:
1、在页面上显示了英文的错误信息。这显然不是我们想要的。
没办法了,commons-fileupload组件没想到国际化,在FileUploadInterceptor拦载器中,也没想着国际化,直接放到Action的错误中了,就没他事了,三种做法:
(1)在错误显示之前,把这条错误给换掉,应该难度不大,我没做留给你做了。
(2)或者重写一下JakartaMultiPartRequest这个类,把捕捉到的异常信息换成自己的,然后,通过Struts2的配置文件,把我们重写的这个parser换上去用。
(3)直接改commons-fileupload组件的类,换成中文的。
我具体说一下第(3)种做法:找到FileUploadBase类,把902行~908行改一下。
FileUploadException ex =
new SizeLimitExceededException(
"the request was rejected because"
+ " its size (" + pCount
+ ") exceeds the configured maximum"
+ " (" + pSizeMax + ")",
pCount, pSizeMax);
=>
FileUploadException ex = new SizeLimitExceededException(
"服务器拒绝了您的请求,原因可能是向服务器提交的数据发生了丢失。", pCount, pSizeMax);
把914行~918行改一下。
throw new SizeLimitExceededException(
"the request was rejected because its size ("
+ requestSize
+ ") exceeds the configured maximum ("
+ sizeMax + ")",
=>
throw new SizeLimitExceededException("服务器拒绝了您的请求,原因是提交数据量过大(通常是由于上传文件过大),请返回上页重试。"
+ " (最大字节数:" + sizeMax / 1024
+ "K)", requestSize, sizeMax);
再看一下第二个问题。
2、由于错误的产生,原来页面上输入的内容也全部不见了,也就是说params注入失败。
关于这个问题我在javaeye上搜索到一篇文章(使用的commons-fileupload组件的jar包似乎比较老)。
http://www.javaeye.com/topic/197345
虽然按照此文,当上传失败时,能够将其他输入内容显示出来,但是这样做的结果是全部的文件肯定会上传到服务器上,也就是说,虽然是页面上报了文件因为太大,请求被拒绝的错,但是文件依然会被上传到服务器上,commons-fileupload组件根本没会去拦文件的上传。
在这里要说明一下,如果你不抛出这个异常,请求的流会继续向服务器上传,只有当整个流上传完了之后,commons-fileupload组件才能正确的分析出文件部分、文本部分。所以,在这里抛出异常是不得已的作法,如果不抛异常,后果是虽然页面报错,但文件还是会被传到服务器的上,这一步根本没挡住输入流的上传,如果没挡住的话,大家想想会有什么后果?
所以,综上所述,对于第二个问题,如果出现了这个异常,我们根本无法让原来输入的内容还显示出来的,因为commons-fileupload组件并没有解析全部的输入内容,直接给出异常了,到了params拦截器中,request里就是空的,根本取不到parameter,所以也就无法注入到Action中了。这种情况下,只能显示一个告知用户由于提交数据量过大,服务器拒绝了请求的错误信息,比较好的方法是,直接跳到一个专门的页面,提示用户,然后让用户点返回来再次输入,否则用户会感觉上传文件大就大吧,怎么连我输入的其他一些内容也没给保存住。当然,如果能用Ajax来上传文件,对客户的操作体验可能是最好的,但是,这样可能会导致服务器上有些挂空的文件(上传后从来没被用过),需要想法清除的。
整个分析下来,我们说第二个问题基本上是无法避免的。
posted @
2009-04-14 12:46 lanxin1020 阅读(365) |
评论 (0) |
编辑 收藏
摘要: Web Service概述
Web Service的定义
W3C组织对其的定义如下,它是一个软件系统,为了支持跨网络的机器间相互操作交互而设计。Web Service服务通常被定义为一组模块化的API,它们可以通过网络进行调用,来执行远程系统的请求服务。
这里我们从一个程序员的视角来观察web service。在传统的程序编码中,存在这各种的函数方法调用。通常,我们知道一个程序...
阅读全文
posted @
2009-04-13 18:37 lanxin1020 阅读(228) |
评论 (0) |
编辑 收藏
技术要点
本节代码详细说明文件上传功能的开发流程,介绍知识点如下:
文件上传页面和显示上传成功页面代码内容。
UploadAction类中实现上传功能方法和上传文件属性介绍。
struts.xml中UploadAction配置,以及字符编码、文件临时存放路径配置。
上传后所处路径和最终上传成功后效果展示。
演示代码
上传文件页面,这里笔者定义的是多个文件上传。
- <!---------------------文件名:upload.jsp----------------->
- <%@taglib prefix="s" uri="/struts-tags"%>
- <html>
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=gb2312">
- <title>上传文件</title>
- </head>
- <body>
- <!-- 上传文件表单定义 -->
- <s:form action="upload" method="post" enctype="multipart/form-data">
- <tr>
- <!-- 上传文件标签定义 -->
- <td>上传文件:<s:file name="file"></s:file></td>
- </tr>
- <tr>
- <td>再次上传文件:<s:file name="file"></s:file></td>
- </tr>
- <tr>
- <td align="left"><s:submit name="submit" value="提交"></s:submit></td>
- </tr>
- </s:form>
- </body>
- </html>
<!---------------------文件名:upload.jsp----------------->
<%@taglib prefix="s" uri="/struts-tags"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>上传文件</title>
</head>
<body>
<!-- 上传文件表单定义 -->
<s:form action="upload" method="post" enctype="multipart/form-data">
<tr>
<!-- 上传文件标签定义 -->
<td>上传文件:<s:file name="file"></s:file></td>
</tr>
<tr>
<td>再次上传文件:<s:file name="file"></s:file></td>
</tr>
<tr>
<td align="left"><s:submit name="submit" value="提交"></s:submit></td>
</tr>
</s:form>
</body>
</html>
上传文件成功后结果页面
- <!-------------------文件名:result.jsp ----------------->
- <%@taglib prefix="s" uri="/struts-tags"%>
- <html>
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=gb2312">
- <title>上传结果</title>
- </head>
- <body>
- 上传文件:
- <!-- 显示上传成功文件名 -->
- <s:property value="fileFileName" />
- </body>
- </html>
<!-------------------文件名:result.jsp ----------------->
<%@taglib prefix="s" uri="/struts-tags"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>上传结果</title>
</head>
<body>
上传文件:
<!-- 显示上传成功文件名 -->
<s:property value="fileFileName" />
</body>
</html>
UploadAction类代码
- <!------------------文件名:UploadAction.java ------------------>
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.FileNotFoundException;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.OutputStream;
- import java.util.List;
-
- import org.apache.struts2.ServletActionContext;
- import com.opensymphony.xwork2.ActionSupport;
-
-
- public class UploadAction extends ActionSupport {
-
- private final static String UPLOADDIR = "/upload";
-
- private List<File> file;
-
- private List<String> fileFileName;
-
- private List<String> fileContentType;
-
- public List<File> getFile() {
- return file;
- }
-
- public void setFile(List<File> file) {
- this.file = file;
- }
-
- public List<String> getFileFileName() {
- return fileFileName;
- }
-
- public void setFileFileName(List<String> fileFileName) {
- this.fileFileName = fileFileName;
- }
-
- public List<String> getFileContentType() {
- return fileContentType;
- }
-
- public void setFileContentType(List<String> fileContentType) {
- this.fileContentType = fileContentType;
- }
-
- public String execute() throws Exception {
- for (int i = 0; i < file.size(); i++) {
-
- uploadFile(i);
- }
- return "success";
- }
-
-
- private void uploadFile(int i) throws FileNotFoundException, IOException {
- try {
- InputStream in = new FileInputStream(file.get(i));
- String dir = ServletActionContext.getRequest().getRealPath(UPLOADDIR);
- File uploadFile = new File(dir, this.getFileFileName().get(i));
- OutputStream out = new FileOutputStream(uploadFile);
- byte[] buffer = new byte[1024 * 1024];
- int length;
- while ((length = in.read(buffer)) > 0) {
- out.write(buffer, 0, length);
- }
-
- in.close();
- out.close();
- } catch (FileNotFoundException ex) {
- ex.printStackTrace();
- } catch (IOException ex) {
- ex.printStackTrace();
- }
- }
- }
<!------------------文件名:UploadAction.java ------------------>
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import org.apache.struts2.ServletActionContext;
import com.opensymphony.xwork2.ActionSupport;
//文件上传Action
public class UploadAction extends ActionSupport {
//上传文件存放路径
private final static String UPLOADDIR = "/upload";
//上传文件集合
private List<File> file;
//上传文件名集合
private List<String> fileFileName;
//上传文件内容类型集合
private List<String> fileContentType;
public List<File> getFile() {
return file;
}
public void setFile(List<File> file) {
this.file = file;
}
public List<String> getFileFileName() {
return fileFileName;
}
public void setFileFileName(List<String> fileFileName) {
this.fileFileName = fileFileName;
}
public List<String> getFileContentType() {
return fileContentType;
}
public void setFileContentType(List<String> fileContentType) {
this.fileContentType = fileContentType;
}
public String execute() throws Exception {
for (int i = 0; i < file.size(); i++) {
//循环上传每个文件
uploadFile(i);
}
return "success";
}
//执行上传功能
private void uploadFile(int i) throws FileNotFoundException, IOException {
try {
InputStream in = new FileInputStream(file.get(i));
String dir = ServletActionContext.getRequest().getRealPath(UPLOADDIR);
File uploadFile = new File(dir, this.getFileFileName().get(i));
OutputStream out = new FileOutputStream(uploadFile);
byte[] buffer = new byte[1024 * 1024];
int length;
while ((length = in.read(buffer)) > 0) {
out.write(buffer, 0, length);
}
in.close();
out.close();
} catch (FileNotFoundException ex) {
ex.printStackTrace();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
struts.xml配置文件中有关文件上传的配置:
- <!--------------------文件名:struts.xml------------------->
- <struts>
- <!-- 系统常量定义,定义上传文件字符集编码 -->
- <constant name="struts.i18n.encoding" value="gb2312"></constant>
- <!-- 系统常量定义,定义上传文件临时存放路径 -->
- <constant name="struts.multipart.saveDir" value="c:\"></constant>
- <!-- Action所在包定义 -->
- <package name="C04.4" extends="struts-default">
- <!-- Action名字,类以及导航页面定义 -->
- <!-- 通过Action类处理才导航的的Action定义 -->
- <action name="upload" class="action.UploadAction">
- <result name="input">/jsp/upload.jsp</result>
- <result name="success">/jsp/result.jsp</result>
- </action>
- </package>
- </struts>
<!--------------------文件名:struts.xml------------------->
<struts>
<!-- 系统常量定义,定义上传文件字符集编码 -->
<constant name="struts.i18n.encoding" value="gb2312"></constant>
<!-- 系统常量定义,定义上传文件临时存放路径 -->
<constant name="struts.multipart.saveDir" value="c:\"></constant>
<!-- Action所在包定义 -->
<package name="C04.4" extends="struts-default">
<!-- Action名字,类以及导航页面定义 -->
<!-- 通过Action类处理才导航的的Action定义 -->
<action name="upload" class="action.UploadAction">
<result name="input">/jsp/upload.jsp</result>
<result name="success">/jsp/result.jsp</result>
</action>
</package>
</struts>
(1):文件上传页面如图4.8所示。
图4.8 文件上传
(2):选择文件如图4.9所示。
图4.9 选择上传的文件
(3):单击“提交”按钮后文件上传成功页面,并显示上传文件名,如图4.10所示。
图4.10 上传文件成功后效果
(4):两个被上传文件最终在服务器上存放路径效果如图4.11所示。
图4.11 上传文件存放路径图
代码解释
(1)在upload.jsp中通过Form标签和File标签定义了两个上传文件。Struts2标签笔者会在之后章节里具体介绍,这里只是让读者知道是如何使用标签显示图4.8显示的内容。如果上传成功,笔者在result.jsp中“[”和“]”之间显示上传文件的文件名,如果是多个文件,以“,”相隔。这些显示格式都是用Property标签定义的。
注意:如果上传文件,在JSP的Form中一定要定义如upload.jsp文件中黑体表示的部分。method和enctype属性都必须要如代码中所示。这样Form中上传文件才会起作用。
(2)UploadAction文件中先定义了常量UPLOADDIR,它是上传文件上传后存放的文件夹名字。比如笔者使用的是JBoss(附录中有安装和在MyEclipse中部署的操作说明),则在它的已部署Web项目下的upload文件夹中,会有所有上传成功的文件。如图4.11读者也可以看见它的上传文件最终存放路径。
注意:在MyEclipse中开发的“WebRoot”目录下也要新建一个upload文件夹,否则部署后在JBoss的已部署Web项目下将没有upload文件夹。因为部署的时候会将所有“WebRoot”目录下的文件夹和文件都部署到JBoss的已部署Web项目下。
定义好UPLOADDIR后,在定义上传文件的属性变量。也许其中的“fileFileName”和“fileContentType”读者看了有点别扭,尤其是“fileFileName”感觉不符合Java命名规范,但是这两个属性变量是4.1小节中介绍的“fileUpload”拦截器类中的类公有变量名字,只有这样定义,UploadAction执行时候会把在页面上选择的上传文件的属性值放在这两个变量里面,否则调试UploadAction时候会发现这两个变量都会是“null”即空值。不相信的读者可以自行改变这两个变量名再执行上传文件功能进行调试看一下这两个变量得到的值。
注意:因为这里笔者是进行多个文件上传功能开发,因此“file”、“fileFileName”、“fileFileName”属性变量都设定为List类型,其实还可以设定为数组类型。个人觉得没有啥大区别。完全凭个人喜好而定。还有如果读者自己开发单个文件上传,就没必要把它们设定为List类型或数组类型。直接把“file”定义为Java的IO包中的File类型,“fileFileName”、“fileFileName”定义为普通的String类型即字符串类型。
之后在execute方法中,写一个循环,对所有页面中选择的上传文件一个个进行上传。这里笔者运用了重构中的“抽取方法”的方式,将上传文件的功能封装成一个私有方法,名字为“uploadFile”。其中运用了Java的IO包中很多API方法。有对重构和Java的IO功能不了解的读者可以去查阅相关资料去理解掌握,这里不是本书以及本节重点,因此不再具体记述。
(3)struts.xml中定义了<constant>标签,主要定义了文件名和文件内容显示的字符编码集以及这些被上传文件临时存放路径。
先说明一下<constant>标签,顾名思义这是定义整个Web项目的一些常量属性值,如果不定义则在Struts2自带的default.properties(读者们可到自己安装Struts2的文件路径src\core\src\main\resources\org\apache\struts2\下找到)文件中有这些常量的定义,比如在本节struts.xml文件中的“struts.i18n.encoding”和“struts.multipart.saveDir”在default.properties定义代码如下:
- <!--------------------文件名:default.properties---------------->
- ### This can be used to set your default locale and encoding scheme
- # struts.locale=en_US
- struts.i18n.encoding=UTF-8
-
- ### Parser to handle HTTP POST requests, encoded using the MIME-type multipart/form-data
- # struts.multipart.parser=cos
- # struts.multipart.parser=pell
- struts.multipart.parser=jakarta
- # uses javax.servlet.context.tempdir by default
- struts.multipart.saveDir=
<!--------------------文件名:default.properties---------------->
### This can be used to set your default locale and encoding scheme
# struts.locale=en_US
struts.i18n.encoding=UTF-8
### Parser to handle HTTP POST requests, encoded using the MIME-type multipart/form-data
# struts.multipart.parser=cos
# struts.multipart.parser=pell
struts.multipart.parser=jakarta
# uses javax.servlet.context.tempdir by default
struts.multipart.saveDir=
如果不在struts.xml文件中定义,则Web项目会缺省使用default.properties文件中这两个常量属性的定义。一个将使字符编码集变为“UTF-8”,另一个干脆没有任何文件路径指定。而笔者开发的该Web项目缺省支持的字符编码集是“gb2312”,而且需要指定临时上传文件存放路径。(当然如果读者开发的Web项目缺省编码集就是“UTF-8”,而且也并不需要指定临时路径时候,就没必要在struts.xml中定义这两个<constant>),因此有必要定义这两个属性符合项目开发要求。
注意:也可以如第3章那样,把这两个属性定义在自定义的struts.properties文件中,具体代码可以如下:
- <!------------------------文件名:struts.properties------------------>
- struts.i18n.encoding =gb2312
- struts.multipart.saveDir= c:\
<!------------------------文件名:struts.properties------------------>
struts.i18n.encoding =gb2312
struts.multipart.saveDir= c:\
笔者个人认为比在struts.xml中定义更加好,毕竟Struts2自己也是定义在properties属性文件中,而不是定义在自己的xml配置文件中。(Struts2自带的xml配置文件为struts-default.xml,在4.1小节中已记述)。这里是为了让读者知道struts.xml配置文件也可以配置这些属性,因此写在struts.xml配置文件中。从3.2小节笔者说明struts.xml配置文件时并没有介绍<constant>标签这点也可以知道笔者个人其实是不赞同这样的配置手段即在struts.xml中配置<constant>标签。
在<Action>标签中配置“result”,和第3章类似,将这两个JSP文件的导航流程配置好即可。
(4)开始进行文件上传功能展示,按照如上记述的步骤执行即可。笔者在桌面上新建了两个文本文件,将它们上传到JBoss已部署的Web项目中展示文件上传的upload文件夹下。如图4.11所示。
其实还可以指定上传文件的格式,让它只上传特定类型的文件。比如只能上传文本和xml文件,则在struts.xml需要显示配置“uploadFile”拦截器。如下代码:
- <!-----------------------文件名:struts.xml------------------>
- <struts>
- <!-- Action所在包定义 -->
- <package name="C04.4" extends="struts-default">
- <!-- Action名字,类以及导航页面定义 -->
- <!-- 通过Action类处理才导航的的Action定义 -->
- <action name="upload" class="action.UploadAction">
- <result name="input">/jsp/upload.jsp</result>
- <result name="success">/jsp/result.jsp</result>
- </action>
- <!—显示配置文件上传拦截器 -->
- <interceptor-ref name=”fileUpload”>
- <!—指定特定类型的上传文件 -->
- <param name =”allowedTypes”>text/plain,application/xml</param>
- </ interceptor-ref >
- <interceptor-ref name=”defaultStack”></ interceptor-ref >
- </package>
- </struts>
<!-----------------------文件名:struts.xml------------------>
<struts>
<!-- Action所在包定义 -->
<package name="C04.4" extends="struts-default">
<!-- Action名字,类以及导航页面定义 -->
<!-- 通过Action类处理才导航的的Action定义 -->
<action name="upload" class="action.UploadAction">
<result name="input">/jsp/upload.jsp</result>
<result name="success">/jsp/result.jsp</result>
</action>
<!—显示配置文件上传拦截器 -->
<interceptor-ref name=”fileUpload”>
<!—指定特定类型的上传文件 -->
<param name =”allowedTypes”>text/plain,application/xml</param>
</ interceptor-ref >
<interceptor-ref name=”defaultStack”></ interceptor-ref >
</package>
</struts>
定义了一个名为“allowedTypes”的参数,其中在<param></param>之间的是文件类型,也可以用“,”间隔,表示允许上传多个文件类型。这里允许上传文件类型为txt、xml格式的文件。如果读者不知道各个文件类型的定义,可在自己的JBoss安装目录中的server\default\deploy\jboss-web.deployer\conf\下的web.xml文件中找到(搜索<mime-mapping>即可)。
注意:如果显示配置Struts2自己的缺省拦截器一定要写在“defaultStack”前,否则“fileUpload”拦截器不会执行拦截。因为Struts2中如果某个拦截器执行拦截时候发现自己已经执行过,第二个乃至之后同名的拦截器都不会执行。这里因为“defaultStack”拦截器栈中包含了“fileUpload”拦截器,而“fileUpload”拦截器已经执行拦截了,则不会再执行拦截。如果把“defaultStack”拦截器栈放在“fileUpload”拦截器前配置,则只执行“defaultStack”拦截器栈中的“fileUpload”拦截器,这里是没有定义“allowedTypes”的,Struts2缺省默认的是支持所有文件类型。因此它会支持所有文件类型的文件上传。因此再设定“allowedTypes”就没有任何意义了。
posted @
2009-04-13 15:33 lanxin1020 阅读(226) |
评论 (0) |
编辑 收藏
Struts2文件下载功能开发
技术要点
本节代码详细说明文件下载功能的开发流程,介绍知识点如下:
上传成功页面重修改后支持文件下载代码内容。
DownloadAction文件下载功能开发。
struts.xml中DownloadAction配置,以及支持文件名为中文字符的文件下载。
下载文件流程展示。
演示代码
上传成功页面,这里笔者让其在每个上传文件后提供“下载”链接。
- <!------------------------文件名:result.jsp------------------->
- <%@taglib prefix="s" uri="/struts-tags"%>
- <body>
- 上传文件:
- <table>
- <!-- 循环显示上传成功文件名 -->
- <s:iterator value="fileFileName" status="fn">
- <tr>
- <td>
- <!-- 上传成功文件名 -->
- <s:property />
- </td>
- <td>
- <!-- 下载文件链接内容为定义的下载Action -->
- <!-- 下载文件名作为链接参数fileName值,用OGNL表达式表达 -->
- <a href="<s:url value='download.action'>
- <s:param name='fileName'
- value='fileFileName[#fn.getIndex()]'/>
- </s:url>">下载</a>
- </td>
- </tr>
- </s:iterator>
- </table>
- </body>
<!------------------------文件名:result.jsp------------------->
<%@taglib prefix="s" uri="/struts-tags"%>
<body>
上传文件:
<table>
<!-- 循环显示上传成功文件名 -->
<s:iterator value="fileFileName" status="fn">
<tr>
<td>
<!-- 上传成功文件名 -->
<s:property />
</td>
<td>
<!-- 下载文件链接内容为定义的下载Action -->
<!-- 下载文件名作为链接参数fileName值,用OGNL表达式表达 -->
<a href="<s:url value='download.action'>
<s:param name='fileName'
value='fileFileName[#fn.getIndex()]'/>
</s:url>">下载</a>
</td>
</tr>
</s:iterator>
</table>
</body>
DownLoadAction类代码
- <!------------文件名:DownLoadAction.java ------------------>
- import java.io.InputStream;
- import java.io.UnsupportedEncodingException;
-
- import org.apache.struts2.ServletActionContext;
- import com.opensymphony.xwork2.ActionSupport;
-
- public class DownLoadAction extends ActionSupport {
-
- private final static String DOWNLOADFILEPATH="/upload/";
-
- private String fileName;
-
- public String getFileName() {
- return fileName;
- }
-
- public void setFileName(String fileName) {
- this.fileName = fileName;
- }
-
-
- public InputStream getDownloadFile() {
- return
- ServletActionContext.getServletContext().getResourceAsStream(DOWNLOADFILEPATH+fileName);
- }
-
- public String getDownloadChineseFileName() {
- String downloadChineseFileName = fileName;
-
- try {
- downloadChineseFileName = new String(downloadChineseFileName.getBytes(), "ISO8859-1");
- } catch (UnsupportedEncodingException e) {
- e.printStackTrace();
- }
-
- return downloadChineseFileName;
- }
-
- public String execute() {
- return SUCCESS;
- }
- }
<!------------文件名:DownLoadAction.java ------------------>
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import org.apache.struts2.ServletActionContext;
import com.opensymphony.xwork2.ActionSupport;
public class DownLoadAction extends ActionSupport {
//下载文件原始存放路径
private final static String DOWNLOADFILEPATH="/upload/";
//文件名参数变量
private String fileName;
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
//从下载文件原始存放路径读取得到文件输出流
public InputStream getDownloadFile() {
return
ServletActionContext.getServletContext().getResourceAsStream(DOWNLOADFILEPATH+fileName);
}
//如果下载文件名为中文,进行字符编码转换
public String getDownloadChineseFileName() {
String downloadChineseFileName = fileName;
try {
downloadChineseFileName = new String(downloadChineseFileName.getBytes(), "ISO8859-1");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return downloadChineseFileName;
}
public String execute() {
return SUCCESS;
}
}
struts.xml配置文件中有关文件下载的配置:
- <!------------------文件名:struts.xml----------------->
- <struts>
- <!-- 下载文件的Action定义 -->
- <action name="download" class="action.DownLoadAction">
- <!-- 设置文件名参数,由页面上传入 -->
- <param name="fileName"></param>
- <result name="success" type="stream">
- <!-- 下载文件类型定义 -->
- <param name="contentType">text/plain</param>
- <!-- 下载文件处理方法 -->
- <param name="contentDisposition">
- attachment;filename="${downloadChineseFileName}"
- </param>
- <!-- 下载文件输出流定义 -->
- <param name="inputName">downloadFile</param>
- </result>
- </action>
- </struts>
<!------------------文件名:struts.xml----------------->
<struts>
<!-- 下载文件的Action定义 -->
<action name="download" class="action.DownLoadAction">
<!-- 设置文件名参数,由页面上传入 -->
<param name="fileName"></param>
<result name="success" type="stream">
<!-- 下载文件类型定义 -->
<param name="contentType">text/plain</param>
<!-- 下载文件处理方法 -->
<param name="contentDisposition">
attachment;filename="${downloadChineseFileName}"
</param>
<!-- 下载文件输出流定义 -->
<param name="inputName">downloadFile</param>
</result>
</action>
</struts>
(1):文件开始下载页面如图4.12所示。
图4.12 文件下载
(2):单击“下载”链接,比如点“下载文件1.txt”文件右边“下载”链接,出现对话框如图4.13所示。
图4.13 下载文件处理方式
(3):单击“保存”按钮后选择下载文件存放路径,如图4.14所示。
图4.14 下载文件选择存放路径
代码解释
(1)在result.jsp中通过iterator标签和url标签定义了“fileFileName”的循环显示以及链接。其中有关“status”和OGNL表达式笔者会在之后章节里具体介绍,这里只是让读者知道是如何使用标签显示图4.12显示的内容。特别指出<param>标签为downloadAction定义了一个参数,该参数名为“fileName”,因为在4.4.1小节中笔者定义的“fileFileName”是个List类型的数据集合,因此利用OGNL表达式将文件名作为“fileName”参数值传入downloadAction中。
(2)DownLoadAction文件中先定义了常量DOWNLOADFILEPATH,它是下载文件在服务器存放的路径名,也就是4.4.1小节中上传文件在服务器存放的路径名。
定义好DOWNLOADFILEPATH后,在定义DownLoadAction的属性变量。因为在result.jsp中定义了参数“fileName”,则它作为DownLoadAction的属性变量,需要定义相应的getter、setter方法。
然后定义了getDownloadFile方法,它返回的是一个文件流,表明将被下载文件转换为输出流,方便下载。利用Struts2自带的“ServletActionContext”类的API把下载文件存放路径作为方法参数,读取下载文件,将其转换为文件流。
还有一个getDownloadChineseFileName方法,该方法主要作用是将文件名为中文字符的文件进行文件名的字符编码集合转换。因为在Web系统中由JSP等视图页面传入的变量值,特别是中文字符的变量。缺省的字符编码集合都是“ISO8859-1”,因此利用Java的字符串类的API,将字符编码转成开发需要的字符编码集。防止中文字符乱码问题发生。
(3)struts.xml中定义了名为“download”的Action。其中它自己的参数“fileName”因为在这里它的值会从JSP页面上传入,所以这里只是定义,没有具体给它赋任何值
在<result>标签中定义了type属性,值为“stream”。如果是下载文件功能开发,DownLoadAction一定要设置type属性,而且值为“stream”。这是因为在Struts2自带的xml配置文件为struts-default.xml中有关于“stream”的result返回类型的定义,代码如下:
- <!-------------------文件名:struts-default.xml-------------->
- <result-type name="stream" class="org.apache.struts2.dispatcher.StreamResult"/>
<!-------------------文件名:struts-default.xml-------------->
<result-type name="stream" class="org.apache.struts2.dispatcher.StreamResult"/>
这里Struts2定义了result返回类型为“stream”,这个result类型主要是处理文件的输入和输出流时候需要使用的。因为下载文件就是把文件转换成输入输出流,将其从一个文件路径放到另外一个文件路径中去。所以肯定要设置这个result类型的。
“contentType”、“contentDisposition”、“inputName”都是这个result的属性。“contentType”就是文件类型。这里因为下载的文件是文本文件,因此设定的值为文本文件类型,具体各个文件类型如何定义,4.4.1小节已经介绍过,这里不再做说明。“contentDisposition”是指定下载文件处理方式,如图4.13就是处理方式的效果。特别指出如果“contentDisposition”定义的值把前面的“attachment”去掉,则下载方式不是以附件方式下载,如果单击“下载”链接,则会把下载文件的内容显示在浏览器中。读者可以去试验一下。这里有个“${downloadChineseFileName}”,这就是在DownLoadAction中定义getDownloadChineseFileName方法的目的,${downloadChineseFileName}是OGNL的表达式,它显示了“downloadChineseFileName”变量的具体值,因为在DownLoadAction中定义getDownloadChineseFileName方法,则把已经转换成符合需要字符编码集的下载文件名作为下载文件方式对话框中显示的名称,不会造成任何乱码问题。“inputName”是最关键的一个属性,也是一定要定义的属性,“inputName”参数中定义的值“downloadFile”就是DownLoadAction中getDownloadFile方法返回的文件流名字。在Struts2中Acion用前缀名为get的方法得到各种属性的值,这些属性有些是在Action中定义,有些就像本示例在配置文件中利用OGNL表达式或直接定义。
(4)开始进行文件下载功能展示,按照如上记述的步骤执行即可。笔者将两个文本文件上传上去,然后在上传成功页面对具体的文件进行下载。在图4.13中单击“保存”按钮就显示图4.14,选择在本机上存放下载文件的路径即可完成下载文件功能。
posted @
2009-04-13 15:30 lanxin1020 阅读(761) |
评论 (0) |
编辑 收藏
Struts2标签使用原理解疑
在笔者下载的Struts2的包中,读者可以在/lib下找到struts2-core-2.0.11.1.jar包,解压该包在其根目录下的/META-INF文件夹下可以看到一个名字为“struts-tags.tld”文件。该文件就是Struts2中所有自带的标签库定义。本节通过对该文件代码的介绍来让读者知晓Struts2内部是如何使用这些标签来进行工作。并简单说明JSP中是如何用其来书写标签代码。
技术要点
本节代码说明Struts2内部定义标签的格式和在JSP中使用方式。
struts-tags.tld文件定标签定义配置格式。
JSP中使用标签功能介绍。
演示代码
- <!------------------文件名: struts-tags.tld----------------->
- <taglib>
- <tlib-version>2.2.3</tlib-version>
- <jsp-version>1.2</jsp-version>
- <short-name>s</short-name>
- <uri>/struts-tags</uri>
- <display-name>"Struts Tags"</display-name>
- <description>………………</description>
- <tag>
- <name>action</name>
- <tag-class>org.apache.struts2.views.jsp.ActionTag</tag-class>
- <body-content>JSP</body-content>
- <description><![CDATA[Execute an action from within a view]]></description>
- <attribute>
- <name>executeResult</name>
- <required>false</required>
- <rtexprvalue>false</rtexprvalue>
- <description><![CDATA[Whether the result of this action (probably a view) should be executed/rendered]]></description>
- </attribute>
- …………………………
- <attribute>
- <name>namespace</name>
- <required>false</required>
- <rtexprvalue>false</rtexprvalue>
- <description><![CDATA[Namespace for action to call]]></description>
- </attribute>
- </tag>
- </taglib>
<!------------------文件名: struts-tags.tld----------------->
<taglib>
<tlib-version>2.2.3</tlib-version>
<jsp-version>1.2</jsp-version>
<short-name>s</short-name>
<uri>/struts-tags</uri>
<display-name>"Struts Tags"</display-name>
<description>………………</description>
<tag>
<name>action</name>
<tag-class>org.apache.struts2.views.jsp.ActionTag</tag-class>
<body-content>JSP</body-content>
<description><![CDATA[Execute an action from within a view]]></description>
<attribute>
<name>executeResult</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
<description><![CDATA[Whether the result of this action (probably a view) should be executed/rendered]]></description>
</attribute>
…………………………
<attribute>
<name>namespace</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
<description><![CDATA[Namespace for action to call]]></description>
</attribute>
</tag>
</taglib>
代码解释
(1)struts-tags.tld是Struts2自标签定义文件。所有标签定义都是在<tablib>和</taglib>之间定义。以<tag></tag>用来定义一个具体标签。每个标签因为都可以有很多自己的属性。这些属性定义都是以<attribute></attribute>来定义。
(2)<tlib-version></tlib-version>之间定义的是标签库的版本。<jsp-version></jsp-version>定义的是标签库这些标签是支持JSP的哪个版本。<short-name> </short-name>其实是标签库的默认名,也可以认为是其昵称。<uri> </uri>定义的是标签库的URI,在JSP中会使用到。<display-name></display-name>是显示名。<description></description>是标签库的记述,记述标签库的使用用途等等。
(3)<attribute>中<name></name>是属性名称定义。<required></required>表示的该属性是否是必须的属性,如果是必须的则<required></required>之间为true,否则为false。<rtexprvalue></rtexprvalue>表示的是可否使用表达式,大多数标签都是为false。这里不是不能使用表达式,而是恰恰相反表示可以使用表达式。<description></description>定义和前面介绍相同。
(4)在JSP中,如之前章节的演示代码所示,都是在文件头有个使用标签的声明,代码如下。
- <!---------------------文件名: *.jsp------------------------->
- <%@taglib prefix="s" uri="/struts-tags"%>
<!---------------------文件名: *.jsp------------------------->
<%@taglib prefix="s" uri="/struts-tags"%>
有了这个声明,在JSP文件中就可以使用Struts2的标签。比如form标签定义要像如下代码所示。
- <s:form action="upload" ………>
<s:form action="upload" ………>
记住一定要用“s”,它是Struts2中标签的默认名也是相当于一个昵称,当然读者也可以把它改为自己想取的名字,不过在标签声明中的“prefix”中就要改成那个自己取的名字。
注意:因为笔者使用的Servlet版本是2.3之上的版本,因此没必要在web.xml中定义标签库。如果读者使用的Servlet版本比较低,则在web.xml文件中需要定义如下的代码:
- <!----------------------文件名:web.xml----------------------->
- <taglib>
- <!- 定义URI - ->
- <taglib-uri>/Struts 2-tags</taglib-uri>
- <!- 定义标签库支持的jar包位置- ->
- <taglib-location>/WEB-INF/lib/struts2-core-2.0.11.1.jar</taglib-location>
- </taglib>
<!----------------------文件名:web.xml----------------------->
<taglib>
<!- 定义URI - ->
<taglib-uri>/Struts 2-tags</taglib-uri>
<!- 定义标签库支持的jar包位置- ->
<taglib-location>/WEB-INF/lib/struts2-core-2.0.11.1.jar</taglib-location>
</taglib>
只有这样标签库才会在Servlet版本比较低的情况下使用有效果。
posted @
2009-04-13 15:28 lanxin1020 阅读(454) |
评论 (0) |
编辑 收藏
--sql structured query language
--DML--Data Manipulation Language--数据操作语言
query information (SELECT),
add new rows (INSERT),
modify existing rows (UPDATE),
delete existing rows (DELETE),
perform a conditional update or insert operation (MERGE),
see an execution plan of SQL (EXPLAIN PLAN),
and lock a table to restrict access (LOCK TABLE).
--DDL--Data Definition Language--数据定义语言
create, modify,drop, or rename objects (CREATE,ALTER,DROP,RENAME),
remove all rows from a database object without dropping the structure (TRUNCATE),
manage access privileges (GRANT,REVOKE),
audit database use (AUDIT,NOAUDIT)
and add a description about an object to the dictionary (COMMENT).
--Transaction Control事务控制语句
save the changes(COMMIT)
or discard the changes (ROLLBACK) made by DML statements.
Also included in the transaction-control statements are statements to set a point or marker in the transaction for possible rollback (SAVEPOINT)
and to define the properties for the transaction (SET TRANSACTION).
Used to manage the properties of the database.
There isonly one statement in this category (ALTER SYSTEM).
--DCL--Data Control Language--与开发关系不是很密切,用于权限的分配与回收
grant,revoke,data control
--Session Control
control the session properties (ALTER SESSION)
and to enable/disable roles (SET ROLE).
--System Control
--------------------------------------------------------
select的用法
--每个员工的所有信息
select * from emp
--每个人的部门编号,姓名,薪水
select deptno,ename,sal from emp;
--每个人的年薪
select ename,sal*12 from emp;
--计算2*3的值
select 2*3 from emp;
--计算2*3的值(dual)
select 2*3 from dual;
select * from dual;
--得到当前时间
select sysdate from dual
--可以给列起别名,比如求每个人的年薪
select ename,sal*12 salperyear from emp;
--如果别名中有空格,需要用双引号
select ename,sal*12 "sal per year" from emp;
--如果没有内容,则为空
select comm from emp;
--当空字段参与计算,则结果是null
--例如:计算每个人的全年的收入包括月薪和年终奖
select ename,sal*12+comm from emp;
--可以将多个字符串拼在一起。比如:求每个人的薪水,格式为smith-sal-123
select ename||'-sal-'||sal from emp;
--如果字符串中有单引号,需要用另外一个单引号转义,比如:这样一个字符串: he's friend
select ename||'''s sal is'||sal from emp;
--------------------------------------------------------
--distinct 关键词的用法
--求有哪些个部门
select distinct deptno from emp
--可以用来修饰多个字段。比如:求有哪些个部门和job的组合
select distinct deptno,job from emp
--------------------------------------------------------
where关键词的用法
--可以是数值类型的等值判断。比如:求10这个部门的所有员工
select * from emp where deptno=20
--可以是字符串类型的等值判断。比如:求叫KING的这个人的信息
select * from emp where ename = 'KING'
--也可以是不等值判断。比如:求薪水小于2000的员工信息
select * from emp where sal<2000;
--字符串也可以做不等值判断,比如:求所有ename大于'CBA'的员工信息。
select * from emp where ename>'CBA';
--求部门不是10的员工
select * from emp where deptno <> 10;
--求薪水在800和1500之间的员工信息
select * from emp where sal >=800 and sal <=1500;
--也可以写成
select * from emp where sal between 800 and 1500
--这样写则不可以
-----------------------------select * from emp where 800<=sal<=1500
--where...in..的用法。比如:求薪水是800或者1500或正2000的员工信息
select * from emp where sal=800 or sal=1500 or sal=2000
--相当于写成这样
select * from emp where sal in(1500,800,2000,1500,1500,1500,1500);
--再比如求姓名是KING,SMITH,AA的员工信息
select * from emp where ename in ('KING','SMITH','AA')
--求入职时间在20-2月-81之后的员工信息
select * from emp where hiredate < '23-5月 -87';
--------------------------------------------------------
--and or not的用法
--求薪水大于1000或者部门在20这个部门的员工信息
select * from emp where sal>1000 and deptno=20
--求薪水不是800或者不是1500或者不是3000的员工信息
select * from emp where sal not in (800,1500,3000)
--也可以这样来写
select * from emp where sal <>800 and sal <> 1500 and sal<>3000
--------------------------------------------------------
--like的用法
--求名字中包含ALL这三个字符的员工信息
select * from emp where ename like '%E%';
--求名字中的第二个字母是A的员工
select * from emp where ename like '_A%';
--特殊字符需要转义。比如:求员工中包含特殊字符%的员工信息
select * from emp where ename like '%\%%' escape '\'
--------------------------------------------------------
--null的用法
--求没有年终奖的员工
select * from emp where comm is null
--求有年终奖的员工
select * from emp where comm is not null
--------------------------------------------------------
--order by的用法
--员工信息按照姓名正序排列
select * from emp order by ename asc;
--员工信息按照倒叙排列
select * from emp order by ename desc;
--也可以是多个字段组合排列。例如:员工信息按照部门正序排列,并且按照姓名倒叙排列
select * from emp order by deptno asc,ename desc
--------------------------------------------------------
--function的用法
--把所有姓名变成小写
select lower(ename) from emp;
--把所有姓名变成大写
select upper(ename) from emp;
--求所有人名中包含'a'的员工信息不区分大小写
select * from emp where lower(ename) like '%a%'
--截取子字符串,比如求Hello的一部分
select substr('hello',2,2) from dual;
select substr(ename,2,2) from emp;
--求Hello的一部分,并指明长度
--求ascii码对应的字符
select chr(65) from dual
--求字符对应的ascii码
select ascii('中')from dual
--四舍五入
select round(12.456,2) from dual
select round(12.456,-1) from dual
--四舍五入小数点后面多少位
--四舍五入小数点前面多少位
--------------------------------------------------------
--important!日期转换函数
--------------------------------------------------------
--将当前日期转换成1981-03-12 12:00:00这种形式的字符串
select to_char(sysdate,'YYYY-MM-DD HH24:MI:SS') from dual;
--将1981-03-12 12:00:00字符串转换成日期
select to_date('1981-03-12 12:00:00','YYYY-MM-DD HH24:MI:SS') from dual;
--将每个人的薪水转换成固定格式的字符串
select to_char(sal,'$999,999,999.99') from emp;
--将固定格式的字符串转换成数值
select to_number('$8,000.00','$999,999,999.99') from dual;
--当null参与计算时候,可以用nvl这个函数。比如求每个人一年总共的收入
select ename,sal*12+comm from emp
--------------------------------------------------------
--group function组函数
--求所有人的薪水的总和,平均值,最大值,最小值
select sum(sal),avg(sal),max(sal) ,min(sal) from emp;
--求总的行数
select count(*) from emp;
--求总的行树,(可以指定具体的字段)但如果字段有null值的时候需要小心使用
select count(comm) from emp;
--也可以过滤掉重复的行之后统计行数
select count(distinct deptno) from emp
--可以指明按照哪个字段进行分组.比如;分部门统计最高薪水
select deptno,max(sal) from emp where deptno is not null group by deptno
--也可以按照多个字段来分组统计,比如:分部门和岗位,统计最高薪水和行数
select deptno,job,max(sal),count(*) from emp group by deptno,job
--------------------------------------------------------
--重要:出现在select列表中的字段,如果没有在组函数中,那么必须出现在group by 子句中。
--------------------------------------------------------
select ename,deptno,job,max(sal),count(*) from emp group by deptno,job
--求薪水最高的员工姓名
select * from emp where sal=(select max(sal) from emp);
delete from emp where ename='TEST2'
update emp set deptno=10 where deptno=99
select * from dept
insert into dept (deptno,dname,loc) values('10','ACCOUNTING','NEW YORK');
--having从句的用法
--求平均薪水是2000以上的部门
select deptno,avg(sal) as avg_sal from emp group by deptno
having avg(sal) >2000
--------------------------------------------------------
--总结一下select语法
select
from
where
group by
having
order by
--------------------------------------------------------
-- 执行顺序very important!
-- 首先执行where语句将原有记录过滤;
-- 第二执行group by 进行分组;
-- 第三执行having过滤分组;
-- 然后将select 中的字段值选出来;
-- 最后执行order by 进行排序;
--------------------------------------------------------
/*
按照部门分组统计,求最高薪水,平均薪水
只有薪水是1200以上的才参与统计
并且分组结果中只包括平均薪水在1500以上的部门
而且按照平均薪水倒叙排列
*/
select max(sal),avg(sal) from emp
where sal>1200
group by deptno
having avg(sal) >1500
order by avg(sal) desc
--------------------------------------------------------
/*
把雇员按部门分组,
求最高薪水, 部门号,
过滤掉名字中第二个字母是'A'的,
要求分组后的平均薪水>1500,
按照部门编号倒序排列
*/
select max(sal) ,deptno from emp where ename not like '_A%'group by deptno
having avg(sal) >1500
order by deptno desc
/* very very important! */
select ename, deptno from emp;
select deptno, dname from dept;
--------------------------------------------------------------------------------------
--老语法:----------------------------------------------------------------------------
--------------------------------------------------------------------------------------
--等值连接:求员工姓名以及员工所在部门的名字同时显示出来
select ename,emp.deptno,dname,dept.deptno from emp,dept
where emp.deptno = dept.deptno
select ename,e.deptno,dname,d.deptno from emp e,dept d
where e.deptno = d.deptno
--非等值连接:要求每位雇员的薪水等级
select * from salgrade
select ename,sal,grade,losal,hisal from emp,salgrade
where sal >=losal and sal <=hisal
--跨3个表:求工作职位是’PRESIDENT’的雇员姓名,部门名称和薪水等级时
select ename,dname,grade from emp,dept,salgrade
where emp.deptno = dept.deptno
and sal >=losal and sal <=hisal
and job ='PRESIDENT'
--也可以同一个表做跨表连接:求每位员工的姓名,及其上级经理的姓名
select e1.ename,e2.ename from emp e1,emp e2
where e1.mgr = e2.empno
--------------------------------------------------------------------------------------
--新语法------------------------------------------------------------------------------
--在SQL1992的语法规则中,语句过滤的条件和表连接的条件都被放在了where子句中,当条件过多时,容易造成混淆,
--SQL1999修正了这个缺点,将连接条件和数据过滤条件区分开来,
--------------------------------------------------------------------------------------
--交叉连接
--结果会产生这两张表的笛卡尔乘积
select * from emp cross join dept
--要用deptno作为等值连接条件,我们可以这样写
select * from emp join dept using (deptno)
select ename, dname from emp join dept using(deptno);
--相当于
select ename, dname from emp join dept on emp.deptno = dept.deptno
--也可以写成这样
--也可以用于非等值连接
--求每位雇员的薪水等级
select * from emp join salgrade on (sal >=losal and sal<= hisal)
--多个join,where组合使用
--(求工作职位是’PRESIDENT’的雇员姓名,部门名称和薪水等级时)
select * from emp join dept on emp.deptno = dept.deptno
join salgrade on (sal >=losal and sal<= hisal)
where job = 'PRESIDENT'
--外连接--取出表中连接不到一起的多余的数据
--没有全内连接,没有右内连接
--其中outer也可以省略,简写为left join , right join , full join
--left inner join可以缩写成inner join 也可以缩写成join,意思是左内。
--update emp set deptno=20 where ename='SMITH';
--commit;
select * from emp;
select * from dept;
delete from dept where deptno=99;
--左内,从左往右找,匹配不上的记录不显示
select ename,emp.deptno from emp join dept on emp.deptno = dept.deptno;
select ename,emp.deptno from emp inner join dept on emp.deptno = dept.deptno;
--没有这种语法:select ename,emp.deptno from emp left inner join dept on emp.deptno = dept.deptno;
--左外连接,从左往右找,匹配不上的记录也显示一行
select ename,dept.deptno from emp left /*outer*/ join dept on emp.deptno = dept.deptno;
--右外连接,从右往左找,匹配不上的记录,也显示一行
select ename,dept.deptno from emp right /*outer*/ join dept on emp.deptno = dept.deptno;
--没有右内连接:select ename,dept.deptno from emp right inner join dept on emp.deptno = dept.deptno;
--全外连接
select ename,dept.deptno from emp full /*outer*/ join dept on emp.deptno = dept.deptno;
--左外,右外的区别
--什么时候用外连接呢?比如领导向你要所有学生的列表,顺便把所属的班级也列出来,就需要外连接
--在Where语句中使用子查询
-----------------------------------------------------------------
--雇员中最高薪水的人员名称
--1,先求出最高薪水
--2,再求雇员中最高薪水的人员名称
select ename from emp where sal=(select max(sal) from emp)
--有哪些人的薪水是在整个雇员的平均薪水之上的
select ename,sal from emp where sal >(select avg(sal) from emp)
-----------------------------------------------------------------
--雇员中哪些人是经理人
--1,首先查询mgr中有哪些号码
--2,再看有哪些人员的号码在此出现
select distinct mgr from emp where mgr is not null order by mgr
select ename
from emp
where empno in (select distinct mgr from emp where mgr is not null )
--where in 中不让写orderby
select ename
from emp
where empno in (select distinct mgr from emp where mgr is not null order by mgr)
-----------------------------------------------------------------
--在From子句中使用子查询
------------------------------------------------------------------
--部门平均薪水的等级
--1,首先将每个部门的平均薪水求出来
--2,然后把结果当成一张表,再用这张结果表和salgrade表做连接,以此求得薪水等级
select deptno,avg(sal) from emp group by deptno
select * from (select deptno,avg(sal) avg_sal from emp group by deptno) t join salgrade
on avg_sal between losal and hisal;
-----------------------------------------------------------------
--每个部门最高薪水的人员名称
--1,首先将每个部门的最高薪水求出来
--2,然后把结果当成一张表,再用emp和这张结果表做连接,以此求得每个部门最高薪水的人员名称
select deptno,max(sal) from emp where deptno is not null group by deptno
select ename from emp e join
(select deptno,max(sal) max_sal from emp where deptno is not null group by deptno ) t
on sal = max_sal and e.deptno = t.deptno
-----------------------------------------------------------------
--哪些人的薪水在部门的平均薪水之上
--1,首先将每个部门的平均薪水求出来
--2,然后把结果当成一张表,再用emp和这张结果表做连接,以此求得哪些人的薪水在部门的平均薪水之上
select deptno,avg(sal) avg_sal from emp group by deptno
select * from emp join (select deptno,avg(sal) avg_sal from emp group by deptno)t
on (sal>avg_sal and emp.deptno=t.deptno)
-----------------------------------------------------------------
--求部门中(所有人的)平均的薪水等级,形式如:
-- deptno avg_grade
-- 10 3.67
-- 20 2.8
-- 30 2.5
--1,先求每个人的薪水等级
--2,再按照部门分组,求平均数
select deptno,sal,grade from emp join salgrade on sal between losal and hisal
select deptno,avg(grade) from (select deptno,sal,grade from emp join salgrade on sal between losal and hisal)t group by deptno
------------------------------------------------------------------------------------------
--使用伪字段:rownum,----------------------
------------------------------------------------------------------------------------------
--用来标识每条记录的行号,行号从1开始,每次递增1
select rownum,emp.* from emp;
--oracle下rownum只能使用 < <=, 不能使用 = > >= 等比较操作符,
select rownum,emp.* from emp where rownum<5;
--当rownum和order by 一起使用时,会首先选出符合rownum条件的记录,然后再排序
--(错误的写法)例如,当我们要求薪水最高的前5个人时,最直接的想法可以这样写:
select * from emp where rownum<5 order by sal desc
--(正确的写法)可以这样写
select * from
(select * from emp order by sal desc) t
where rownum<=5
--------------------------------------------------------
--不准用组函数(即MAX()),求薪水的最高值(面试题)
--第一种解决办法:
--1,先把所有薪水按照倒序排列
--2,再取第一行
select * from
(select sal from emp order by sal desc) t
where rownum=1
--第二种解决办法:
--1,先跨表查询自己,先求出的结果中,e1.sal不可能出现最大数
--2,然后再not in
select e2.sal from emp e1,emp e2 where e1.sal>e2.sal
select sal from emp where sal not in(select e2.sal from emp e1,emp e2 where e1.sal>e2.sal)
-----------------------------------------------------------------
--求平均薪水最高的部门的部门编号
--第一种解决办法:
--1,先求出每个部门的平均薪水,
select deptno,avg(sal) avg_sal from emp group by deptno
--2,再求每个部门的平均薪水的最高值,
select max(avg_sal) from (1111111111111111111111111)
--3,最后再求第一步结果中avg_sal = 最高薪水的记录.
select deptno from (111111111111) where avg_sal = (22222222)
select deptno
from (select deptno,avg(sal) avg_sal from emp group by deptno)
where avg_sal =
(select max(avg_sal)
from (select deptno,avg(sal) avg_sal from emp group by deptno))
--没法考虑并列第一的情况
select deptno from
(select deptno,avg(sal) avg_sal from emp group by deptno order by avg(sal) desc)
where rownum<=1
--第二种解决办法:
--1,将上面的第一步第二步合并,先求最高平均薪水,用max(avg(sal))的办法
--不能写成select deptno,max(avg(sal)) from emp group by deptno
select max(avg(sal)) from emp group by deptno
--2,求出每个部门的平均薪水
select deptno,avg(sal) avg_sal from emp group by deptno
--3,最后再求第二步结果中(即每个部门的平均薪水),avg_sal = (第一步结果)的记录.即avg_sal =最高薪水的记录.
select deptno from (select deptno,avg(sal) avg_sal from emp group by deptno)
where avg_sal =(select max(avg(sal)) from emp group by deptno)
--第三种解决办法:
--1,先求出每个部门的平均薪水,
select avg(sal) avg_sal from emp group by deptno
--2,求最高平均薪水,用max(avg(sal))的办法
select max(avg(sal)) from emp group by deptno
--3,再使用having语句, avg(sal) = 第二步的结果
注意:为组函数起的别名在having中不能用
select deptno from emp group by deptno
having avg(sal) = (select max(avg(sal)) from emp group by deptno)
-----------------------------------------------------------------
--求平均薪水最高的部门的部门名称
--1,部门平均最高薪水
--2,得到部门编号列表,注意用group by deptno
--3,再应用having子句, having avg(sal) = (第一步的结果)
--4,得到平均最高薪水的那个部门的编号
--5,再得到部门名称
select dname from dept where deptno in
(
select deptno
from (select deptno,avg(sal) avg_sal from emp group by deptno)
where avg_sal =
(select max(avg_sal)
from (select deptno,avg(sal) avg_sal from emp group by deptno))
)
-----------------------------------------------------------------
--求平均薪水的等级最低的部门的部门名称
--第一步:部门平均薪水的等级,分成两个小步骤,第一小步是求部门平均薪水
select * from
(select deptno,avg(sal) avg_sal from emp group by deptno) t
join salgrade on avg_sal between losal and hisal
--第二步:最低的等级值
select min(grade) from (1111111111111111111111111)
--第三步:等于最低值的部门编号
------------有错误,应该是grade=
select deptno from (111111111111) where grade = (22222222222222)
--第四步:求名称
select dname from dept where deptno in(33333333333)
select dname
from dept
where deptno in
(
select deptno
from (select *
from (select deptno, avg(sal) avg_sal
from emp
group by deptno) t
join salgrade on avg_sal between losal and hisal)
where grade =
(select min(grade)
from (select *
from (select deptno, avg(sal) avg_sal
from emp
group by deptno) t
join salgrade on avg_sal between losal and hisal)))
--也可以用视图的方式来解决
--conn sys/bjsxt as sysdba
--grant create table, create view, create sequence to scott
--根据第一步的结果,建立一个view
create or replace view v1 as
--必须明确定义列
select deptno, avg_sal, grade from
(select deptno,avg(sal) avg_sal from emp group by deptno) t
join salgrade on avg_sal between losal and hisal
--查看一下
select * from v1
--查询一下
--带入view
select dname from dept where deptno in
(select deptno from (v1) where grade = (select min(grade) from v1))
-------------------------------------------------------------
--为什么in的后面不能order by ?
---------------------------------------------------------------
--求部门经理人中平均薪水最低的部门名称 (思考题)
第一步,求部门经理的雇员编号
select distinct mgr from emp where mgr is not null
第二步,按部门统计,求部门经理的平均薪水
select deptno,avg(sal) avg_sal from emp where empno in (select distinct mgr from emp where mgr is not null)group by deptno
第三步,求最低值
select min(avg(sal)) from emp where empno in (select distinct mgr from emp where mgr is not null)group by deptno
第四步,求部门经理人中平均薪水最低的部门名称
select deptno from (2222222222222) where avg_sal =(333333333333333333333333)
select dname from dept where deptno in (select deptno from (select deptno,avg(sal) avg_sal from emp where empno in (select distinct mgr from emp where mgr is not null)group by deptno) where avg_sal =(select min(avg(sal)) from emp where empno in (select distinct mgr from emp where mgr is not null)group by deptno))
----------------------------------------------------------------------------
--求比普通员工的最高薪水还要高的经理人名称
--1,求所有经理的编号
create or replace view v1 as
select distinct mgr from emp where mgr is not null
select * from v1
--2,普通员工的最高薪水
select max(sal) from emp where empno not in (select distinct mgr from emp where mgr is not null)
--3,
select ename from emp where empno in (select * from v1)
and sal > (select max(sal) from emp where empno not in (select distinct mgr from emp where mgr is not null))
--即:
select ename from emp where empno in (select distinct mgr from emp where mgr is not null)
and sal > (select max(sal) from emp where empno not in (select distinct mgr from emp where mgr is not null))
------------------------------------------------------------------------------
--求薪水最高的前5名雇员
--1,先观察一下
--2,看看rownum的作用
--3,不是我们想要的结果
select ename,sal from emp where rownum<=5 order by sal desc
--4,先order by,再rownum
select * from
(select ename,sal from emp order by sal desc ) t
where rownum<=5
--------------------------------------------------------------------------------
--求薪水最高的第6到第10名雇员(重点掌握)
--这种没法实现,oracle下rownum只能使用 < <=, 不能使用 = > >= 等比较操作符
--注意里面的rownum和外面的rownum的区别,外面要想访问里面的rownum,必须取得一个别名。
select * from
(select ename,sal from emp order by sal desc ) t
where rownum>=5
and rownum<=10
--所以再套一层select
select * from
(select t.*,rownum r from
(select ename,sal from emp order by sal desc ) t
)
where r>=5
and r<=10
--还有一种排序方式
select * from
(select * from emp order by sal desc)where rownum<=10
minus
select * from
(select * from emp order by sal desc)where rownum<=5
--------------------------------------------------------------------
--练习: 求最后入职的5名员工
--1,每个人的入职时间
--2,取前5行
-----------------------------------------------------------------
--求每个部门中薪水最高的前两名雇员
--1,每个员工的姓名,部门,工资,按部门和工资(倒序)排列
select ename,deptno,sal from emp order by deptno,sal desc
--2,套一层,加上个r
select ename,deptno,sal,rownum r from
(select ename,deptno,sal from emp order by deptno,sal desc) t
--3,创建试图
create or replace view v1
as
select ename,deptno,sal,rownum r from
(select ename,deptno,sal from emp order by deptno,sal desc) t
--观察一下
select * from v1
--每个部门中,薪水最高的第一行,并创建试图
create or replace view v2 as
select deptno,min(r) min_r from v1 group by deptno
--两个view跨表连接,大于薪水最高的行数,小于最高的行数+1,并且部门编号要匹配
select ename from v1 join v2
on ( v1.deptno = v2.deptno and v1.r >=v2.min_r and v1.r<=v2.min_r+1)
-------------------------------------------------------------------------------
--面试题: 比较效率
select * from emp where deptno = 10 and ename like '%A%';
select * from emp where ename like '%A%' and deptno = 10;
---------------------------------------------------------
--使用union、minus
--使用union、minus可以用来实现结果集的合并和去除(可以理解为加和减),例如:
select * from emp where deptno=10
union
select * from emp where deptno=20;
--相当于
select * from emp where deptno=10 or deptno=20
--而下面的语句
select * from emp where deptno in (10,20)
minus
select * from emp where sal < 1500;
--相当于
select * from emp where deptno in(10,20) and sal>=1500
--求分段显示薪水的个数
如:
scale total
<800 0
801-1000 2
1001-2000 3
2001-5000 6
>5000 8
select '<800' as scale ,count(*) as total from emp where sal<800
union
select '<801-1000' as scale ,count(*) as total from emp where sal<=1000 and sal>=801
--或者显示成为
--注意:使用between .. and .. 的时候,包含了最大和最小值。
800-1000 1001-2000 2001-5000
2 3 6
select * from
(select count(*) as "800-1000" from emp where sal >=800 and sal <= 1000),
(select count(*) as "1001-2000" from emp where sal >=1001 and sal <= 2000),
(select count(*) as "2001-5000" from emp where sal >=2001 and sal <= 5000)
--或显示成为
DEPTNO 800-2000 2001-5000
------ ---------- ----------
30 5 1
20 2 3
10 1 2
select t.deptno,"800-2000","2001-5000" from
(select deptno,count(*) as "800-2000" from emp where sal between 800 and 2000 group by deptno) t
join
(select deptno,count(*) as "2001-5000" from emp where sal between 2001 and 5000 group by deptno) t1
on t.deptno = t1.deptno
-----------------------------------------------------------------------------------
--每个薪水等级有多少名雇员 ?
--1,先求出每个雇员的薪水等级
--2,再group一下
posted @
2009-04-13 10:40 lanxin1020 阅读(183) |
评论 (0) |
编辑 收藏
(转)Hibernate支持两种锁机制:
即通常所说的“悲观锁(Pessimistic Locking)”和
“乐观锁(OptimisticLocking)”。
悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。
Hibernate的加锁模式有:
Ø LockMode.NONE : 无锁机制。
Ø LockMode.WRITE :Hibernate在Insert和Update记录的时候会自动
获取。
Ø LockMode.READ : Hibernate在读取记录的时候会自动获取。
以上这三种锁机制一般由Hibernate内部使用,如Hibernate为了保证Update
过程中对象不会被外界修改,会在save方法实现中自动为目标对象加上WRITE锁。
Ø LockMode.UPGRADE :利用数据库的for update子句加锁。
Ø LockMode. UPGRADE_NOWAIT :Oracle的特定实现,利用Oracle的for
update nowait子句实现加锁。
乐观锁,大多是基于数据版本(Version)记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个“version”字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
悲观锁与乐观锁的比较:
悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受;
相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。乐观锁机制往往基于系统中的数据存储逻辑,因此也具备一定的局限性,如在上例中,由于乐观锁机制是在我们的系统中实现,来自外部系统的更新操作不受我们系统的控制,因此可能会造成脏数据被更新到数据库中。在
系统设计阶段,我们应该充分考虑到这些情况出现的可能性,并进行相应调整(如将乐观锁策略在数据库存储过程中实现,对外只开放基于此存储过程的数据更新途径,而不是将数据库表直接对外公开)。
Hibernate 在其数据访问引擎中内置了乐观锁实现。如果不用考虑外部系统对数据库的更新操作,利用Hibernate提供的透明化乐观锁实现,将大大提升我们的生产力。
Hibernate中可以通过class描述符的optimistic-lock属性结合version描述符指定。
optimistic-lock属性有如下可选取值:
Ø none
无乐观锁
Ø version
通过版本机制实现乐观锁
Ø dirty
通过检查发生变动过的属性实现乐观锁
Ø all
通过检查所有属性实现乐观锁
其中通过version实现的乐观锁机制是Hibernate官方推荐的乐观锁实现,同时也是Hibernate中,目前唯一在数据对象脱离 Session发生修改的情况下依然有效的锁机制。因此,一般情况下,我们都选择version方式作为Hibernate乐观锁实现机制。
posted @
2009-04-12 16:12 lanxin1020 阅读(175) |
评论 (0) |
编辑 收藏
使用Hibernate自带的工具hbm2ddl,建立根据你的对象建立数据库:
首先建好POJO object, XML Mapping File(也可以使用工具根据POJO class建立),配置文件(hibernate.cfg.xml)
Java代码
import org.hibernate.cfg.Configuration;
import org.hibernate.tool.hbm2ddl.SchemaExport;
public class SchemaUtil {
public static void main(String[] args) {
Configuration cfg = new Configuration().configure();
SchemaExport schemaExport= new SchemaExport(cfg);
schemaExport.create(false, true);
}
}
posted @
2009-04-11 16:31 lanxin1020 阅读(158) |
评论 (0) |
编辑 收藏
在您撰寫好*.hbm.xml映射文件之後,您可以使用 net.sf.hibernate.tool.hbm2ddl.SchemaExportTask來自動建立資料庫表格,這邊所使用的方式是結合Ant進行自動化建構,首先我們假設將使用以下的User.hbm.xml:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping
PUBLIC "-
"http:>
<hibernate-mapping>
<class name="onlyfun.caterpillar.User" table="USER">
<id name="id" type="string" unsaved-value="null">
<column name="user_id" sql-type="char(32)"/>
<generator class="uuid.hex"/>
</id>
<property name="name" type="string" not-null="true">
<column name="name" length="16" not-null="true"/>
</property>
<property name="sex" type="char" />
<property name="age" type="int"/>
</class>
</hibernate-mapping>
在這個映射文件中,<column/>標籤用於指定建立表格時的一些資訊,例如映射的表格欄位名稱,或是sql-type或 length等屬性,如果不指定這些資訊時,SchemaExportTask將自動使用Hibernate的類型至SQL類型等資訊來建立表格;sql -type用於指定表格欄位型態,not-null表示欄位不能為null,length則用於指定表格文字欄位長度,這些屬性的說明,都可以在 Hibernate參考手冊的表15.1找到。
下面的build.xml用於Ant自動化建構時,生成資料庫表格之用:
<project name="Hibernate" default="schema" basedir=".">
<property name="source.root" value="src"/>
<property name="class.root" value="classes"/>
<property name="lib.dir" value="lib"/>
<property name="data.dir" value="data"/>
<path id="project.class.path">
<!-- Include our own classes, of course -->
<pathelement location="${class.root}" />
<!-- Include jars in the project library directory -->
<fileset dir="${lib.dir}">
<include name="*.jar"/>
</fileset>
<pathelement path ="${classpath}"/>
</path>
<target name="schema" description="Generate DB schema from the O/R mapping files">
<!-- Teach Ant how to use Hibernate's schema generation tool -->
<taskdef name="schemaexport"
classname="net.sf.hibernate.tool.hbm2ddl.SchemaExportTask"
classpathref="project.class.path"/>
<schemaexport properties="${source.root}/hibernate.properties"
quiet="no" text="no" drop="no" delimiter=";">
<fileset dir="${source.root}">
<include name="**/*.hbm.xml"/>
</fileset>
</schemaexport>
</target>
</project>
<taskdef/>標籤定義一個新的任務schemaexport,相關的屬性設定是根據參考手冊的建議設定的,我們在這邊使用 hibernate.properties來告訴SchemaExportTask相關的JDBC資訊,quiet、text等屬性的定義,可以看參考手冊的表15.2。
這個Ant建構檔案,會找尋src目錄下包括子目錄中有的*.hbm.xml,並自動根據映射資訊建立表格,我們還必須提供hibernate.properties(置於src下)來告知JDBC連接的相關訊息:
hibernate.dialect=net.sf.hibernate.dialect.MySQLDialect
hibernate.connection.driver_class=com.mysql.jdbc.Driver
hibernate.connection.url=jdbc:mysql:hibernate.connection.username=caterpillar
hibernate.connection.password=123456
這邊使用的是MySQL,請實際根據您所使用的資料庫設定dialect、驅動程式等資訊,在開始運行Ant使用SchemaExportTask進行自動表格建立之前,您要先建立資料庫,這邊的例子則是在MySQL中先建立HibernateTest:
mysql> create database HibernateTest;
Query OK, 1 row affected (0.03 sec)
接著就可以運行Ant了,執行結果如下:
ant
Buildfile: build.xml
schema:
[schemaexport] log4j:WARN No appenders could be found for logger (net.sf.hiberna
te.cfg.Environment).
[schemaexport] log4j:WARN Please initialize the log4j system properly.
[schemaexport] drop table if exists USER;
[schemaexport] create table USER (
[schemaexport] user_id char(32) not null,
[schemaexport] name varchar(16) not null,
[schemaexport] sex char(1),
[schemaexport] age integer,
[schemaexport] primary key (user_id)
[schemaexport] );
BUILD SUCCESSFUL
Total time: 5 seconds
運行的過程中,我們可以看到建立表格的SQL語句,而自動建立好的資料庫表格資訊如下:
mysql> DESCRIBE user;
+---------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+-------------+------+-----+---------+-------+
| user_id | varchar(32) | | PRI | | |
| name | varchar(16) | | | | |
| sex | char(1) | YES | | NULL | |
| age | int(11) | YES | | NULL | |
+---------+-------------+------+-----+---------+-------+
4 rows in set (0.04 sec)
更多有關SchemaExportTask的資訊,可以看看參考手冊的第15章工具箱指南的部份。
posted @
2009-04-11 11:37 lanxin1020 阅读(252) |
评论 (0) |
编辑 收藏
延迟加载:
延迟加载机制是为了避免一些无谓的性能开销而提出来的,所谓延迟加载就是当在真正需要数据的时候,才真正执行数据加载操作。在Hibernate中提供了对实体对象的延迟加载以及对集合的延迟加载,另外在Hibernate3中还提供了对属性的延迟加载。下面我们就分别介绍这些种类的延迟加载的细节。
A、实体对象的延迟加载:
如果想对实体对象使用延迟加载,必须要在实体的映射配置文件中进行相应的配置,如下所示:
<hibernate-mapping>
<class name=”com.neusoft.entity.User” table=”user”
lazy=”true”>
……
</class>
</hibernate-mapping>
通过将class的lazy属性设置为true,来开启实体的延迟加载特性。如果我们运行下面的代码:
User user=(User)session.load(User.class,”1”);(1)
System.out.println(user.getName());(2)
当运行到(1)处时,Hibernate并没有发起对数据的查询,如果我们此时通过一些调试工具(比如JBuilder2005的Debug工具),观察此时user对象的内存快照,我们会惊奇的发现,此时返回的可能是User$EnhancerByCGLIB$$bede8986类型的对象,而且其属性为null,这是怎么回事?还记得前面我曾讲过session.load()方法,会返回实体对象的代理类对象,这里所返回的对象类型就是User对象的代理类对象。在Hibernate中通过使用CGLIB,来实现动态构造一个目标对象的代理类对象,并且在代理类对象中包含目标对象的所有属性和方法,而且所有属性均被赋值为null。通过调试器显示的内存快照,我们可以看出此时真正的User对象,是包含在代理对象的CGLIB$CALBACK_0.target属性中,当代码运行到(2)处时,此时调用user.getName()方法,这时通过CGLIB赋予的回调机制,实际上调用CGLIB$CALBACK_0.getName()方法,当调用该方法时,Hibernate会首先检查CGLIB$CALBACK_0.target属性是否为null,如果不为空,则调用目标对象的getName方法,如果为空,则会发起数据库查询,生成类似这样的SQL语句:select * from user where id=’1’;来查询数据,并构造目标对象,并且将它赋值到CGLIB$CALBACK_0.target属性中。
这样,通过一个中间代理对象,Hibernate实现了实体的延迟加载,只有当用户真正发起获得实体对象属性的动作时,才真正会发起数据库查询操作。所以实体的延迟加载是用通过中间代理类完成的,所以只有session.load()方法才会利用实体延迟加载,因为只有session.load()方法才会返回实体类的代理类对象。
B、 集合类型的延迟加载:
在Hibernate的延迟加载机制中,针对集合类型的应用,意义是最为重大的,因为这有可能使性能得到大幅度的提高,为此Hibernate进行了大量的努力,其中包括对JDK Collection的独立实现,我们在一对多关联中,定义的用来容纳关联对象的Set集合,并不是java.util.Set类型或其子类型,而是net.sf.hibernate.collection.Set类型,通过使用自定义集合类的实现,Hibernate实现了集合类型的延迟加载。为了对集合类型使用延迟加载,我们必须如下配置我们的实体类的关于关联的部分:
<hibernate-mapping>
<class name=”com.neusoft.entity.User” table=”user”>
…..
<set name=”addresses” table=”address” lazy=”true” inverse=”true”>
<key column=”user_id”/>
<one-to-many class=”com.neusoft.entity.Arrderss”/>
</set>
</class>
</hibernate-mapping>
通过将<set>元素的lazy属性设置为true来开启集合类型的延迟加载特性。我们看下面的代码:
User user=(User)session.load(User.class,”1”);
Collection addset=user.getAddresses(); (1)
Iterator it=addset.iterator(); (2)
while(it.hasNext()){
Address address=(Address)it.next();
System.out.println(address.getAddress());
}
当程序执行到(1)处时,这时并不会发起对关联数据的查询来加载关联数据,只有运行到(2)处时,真正的数据读取操作才会开始,这时Hibernate会根据缓存中符合条件的数据索引,来查找符合条件的实体对象。
这里我们引入了一个全新的概念——数据索引,下面我们首先将接一下什么是数据索引。在Hibernate中对集合类型进行缓存时,是分两部分进行缓存的,首先缓存集合中所有实体的id列表,然后缓存实体对象,这些实体对象的id列表,就是所谓的数据索引。当查找数据索引时,如果没有找到对应的数据索引,这时就会一条select SQL的执行,获得符合条件的数据,并构造实体对象集合和数据索引,然后返回实体对象的集合,并且将实体对象和数据索引纳入Hibernate的缓存之中。另一方面,如果找到对应的数据索引,则从数据索引中取出id列表,然后根据id在缓存中查找对应的实体,如果找到就从缓存中返回,如果没有找到,在发起select SQL查询。在这里我们看出了另外一个问题,这个问题可能会对性能产生影响,这就是集合类型的缓存策略。如果我们如下配置集合类型:
<hibernate-mapping>
<class name=”com.neusoft.entity.User” table=”user”>
…..
<set name=”addresses” table=”address”
lazy=”true” inverse=”true”>
<cache usage=”read-only”/><key column=”user_id”/>
<one-to-many class=”com.neusoft.entity.Arrderss”/>
</set>
</class>
</hibernate-mapping>
这里我们应用了<cache usage=”read-only”/>配置,如果采用这种策略来配置集合类型,Hibernate将只会对数据索引进行缓存,而不会对集合中的实体对象进行缓存。如上配置我们运行下面的代码:
User user=(User)session.load(User.class,”1”);
Collection addset=user.getAddresses();
Iterator it=addset.iterator();
while(it.hasNext()){
Address address=(Address)it.next();
System.out.println(address.getAddress());
}
System.out.println(“Second query……”);
User user2=(User)session.load(User.class,”1”);
Collection it2=user2.getAddresses();
while(it2.hasNext()){
Address address2=(Address)it2.next();
System.out.println(address2.getAddress());
}
运行这段代码,会得到类似下面的输出:
Select * from user where id=’1’;
Select * from address where user_id=’1’;
Tianjin
Dalian
Second query……
Select * from address where id=’1’;
Select * from address where id=’2’;
Tianjin
Dalian
我们看到,当第二次执行查询时,执行了两条对address表的查询操作,为什么会这样?这是因为当第一次加载实体后,根据集合类型缓存策略的配置,只对集合数据索引进行了缓存,而并没有对集合中的实体对象进行缓存,所以在第二次再次加载实体时,Hibernate找到了对应实体的数据索引,但是根据数据索引,却无法在缓存中找到对应的实体,所以Hibernate根据找到的数据索引发起了两条select SQL的查询操作,这里造成了对性能的浪费,怎样才能避免这种情况呢?我们必须对集合类型中的实体也指定缓存策略,所以我们要如下对集合类型进行配置:
<hibernate-mapping>
<class name=”com.neusoft.entity.User” table=”user”>
…..
<set name=”addresses” table=”address”
lazy=”true” inverse=”true”>
<cache usage=”read-write”/><key column=”user_id”/>
<one-to-many class=”com.neusoft.entity.Arrderss”/>
</set>
</class>
</hibernate-mapping>
此时Hibernate会对集合类型中的实体也进行缓存,如果根据这个配置再次运行上面的代码,将会得到类似如下的输出:
Select * from user where id=’1’;
Select * from address where user_id=’1’;
Tianjin
Dalian
Second query……
Tianjin
Dalian
这时将不会再有根据数据索引进行查询的SQL语句,因为此时可以直接从缓存中获得集合类型中存放的实体对象。
C、 属性延迟加载:
在Hibernate3中,引入了一种新的特性——属性的延迟加载,这个机制又为获取高性能查询提供了有力的工具。在前面我们讲大数据对象读取时,在User对象中有一个resume字段,该字段是一个java.sql.Clob类型,包含了用户的简历信息,当我们加载该对象时,我们不得不每一次都要加载这个字段,而不论我们是否真的需要它,而且这种大数据对象的读取本身会带来很大的性能开销。在Hibernate2中,我们只有通过我们前面讲过的面性能的粒度细分,来分解User类,来解决这个问题(请参照那一节的论述),但是在Hibernate3中,我们可以通过属性延迟加载机制,来使我们获得只有当我们真正需要操作这个字段时,才去读取这个字段数据的能力,为此我们必须如下配置我们的实体类:
<hibernate-mapping>
<class name=”com.neusoft.entity.User” table=”user”>
……
<property name=”resume” type=”java.sql.Clob” column=”resume” lazy=”true”/> </class>
</hibernate-mapping>
通过对<property>元素的lazy属性设置true来开启属性的延迟加载,在Hibernate3中为了实现属性的延迟加载,使用了类增强器来对实体类的Class文件进行强化处理,通过增强器的增强,将CGLIB的回调机制逻辑,加入实体类,这里我们可以看出属性的延迟加载,还是通过CGLIB来实现的。CGLIB是Apache的一个开源工程,这个类库可以操纵java类的字节码,根据字节码来动态构造符合要求的类对象。根据上面的配置我们运行下面的代码:
String sql=”from User user where user.name=’zx’ ”;
Query query=session.createQuery(sql); (1)
List list=query.list();
for(int i=0;i<list.size();i++){
User user=(User)list.get(i);
System.out.println(user.getName());
System.out.println(user.getResume()); (2)
}
当执行到(1)处时,会生成类似如下的SQL语句:
Select id,age,name from user where name=’zx’;
这时Hibernate会检索User实体中所有非延迟加载属性对应的字段数据,当执行到(2)处时,会生成类似如下的SQL语句:
Select resume from user where id=’1’;
这时会发起对resume字段数据真正的读取操作。
posted @
2009-04-11 10:35 lanxin1020 阅读(113) |
评论 (0) |
编辑 收藏
1.1.1. 基本的缓存原理
Hibernate缓存分为二级,第一级存放于session中称为一级缓存,默认带有且不能卸载。
第二级是由sessionFactory控制的进程级缓存。是全局共享的缓存,凡是会调用二级缓存的查询方法 都
会从中受益。只有经正确的配置后二级缓存才会发挥作用。同时在进行条件查询时必须使用相应的方法
才能从缓存中获取数据。比如Query.iterate()方法、load、get方法等。必须注意的是session.find方
法永远是从数据库中获取数据,不会从二级缓存中获取数据,即便其中有其所需要的数据也是如此。
查询时使用缓存的实现过程为:首先查询一级缓存中是否具有需要的数据,如果没有,查询二级缓存,
如果二级缓存中也没有,此时再执行查询数据库的工作。要注意的是:此3种方式的查询速度是依次降低
的。
1.2. 存在的问题
1.2.1. 一级缓存的问题以及使用二级缓存的原因
因为Session的生命期往往很短,存在于Session内部的第一级最快缓存的生命期当然也很短,所以
第一级缓存的命中率是很低的。其对系统性能的改善也是很有限的。当然,这个Session内部缓存的主要
作用是保持Session内部数据状态同步。并非是hibernate为了大幅提高系统性能所提供的。
为了提高使用hibernate的性能,除了常规的一些需要注意的方法比如:
使用延迟加载、迫切外连接、查询过滤等以外,还需要配置hibernate的二级缓存。其对系统整体性能的
改善往往具有立竿见影的效果!
(经过自己以前作项目的经验,一般会有3~4倍的性能提高)
1.2.2. N+1次查询的问题
执行条件查询时,iterate()方法具有著名的 “n+1”次查询的问题,也就是说在第一次查询时
iterate方法会执行满足条件的查询结果数再加一次(n+1)的查询。但是此问题只存在于第一次查询时
,在后面执行相同查询时性能会得到极大的改善。此方法适合于查询数据量较大的业务数据。
但是注意:当数据量特别大时(比如流水线数据等)需要针对此持久化对象配置其具体的缓存策略,比
如设置其存在于缓存中的最大记录数、缓存存在的时间等参数,以避免系统将大量的数据同时装载入内
存中引起内存资源的迅速耗尽,反而降低系统的性能!!!
1.3. 使用hibernate二级缓存的其他注意事项:
1.3.1. 关于数据的有效性
另外,hibernate会自行维护二级缓存中的数据,以保证缓存中的数据和数据库中的真实数据的一致性!
无论何时,当你调用save()、update()或 saveOrUpdate()方法传递一个对象时,或使用load()、 get()
、list()、iterate() 或scroll()方法获得一个对象时, 该对象都将被加入到Session的内部缓存中。
当随后flush()方法被调用时,对象的状态会和数据库取得同步。
也就是说删除、更新、增加数据的时候,同时更新缓存。当然这也包括二级缓存!
只要是调用hibernate API执行数据库相关的工作。hibernate都会为你自动保证 缓存数据的有效性!!
但是,如果你使用了JDBC绕过hibernate直接执行对数据库的操作。此时,Hibernate不会/也不可能自行
感知到数据库被进行的变化改动,也就不能再保证缓存中数据的有效性!!
这也是所有的ORM产品共同具有的问题。幸运的是,Hibernate为我们暴露了Cache的清除方法,这给我们
提供了一个手动保证数据有效性的机会!!
一级缓存,二级缓存都有相应的清除方法。
其中二级缓存提供的清除方法为:
按对象class清空缓存
按对象class和对象的主键id清空缓存
清空对象的集合中的缓存数据等。
1.3.2. 适合使用的情况
并非所有的情况都适合于使用二级缓存,需要根据具体情况来决定。同时可以针对某一个持久化对象配
置其具体的缓存策略。
适合于使用二级缓存的情况:
1、数据不会被第三方修改;
一般情况下,会被hibernate以外修改的数据最好不要配置二级缓存,以免引起不一致的数据。但是如果
此数据因为性能的原因需要被缓存,同时又有可能被第3方比如SQL修改,也可以为其配置二级缓存。只
是此时需要在sql执行修改后手动调用cache的清除方法。以保证数据的一致性
2、数据大小在可接收范围之内;
如果数据表数据量特别巨大,此时不适合于二级缓存。原因是缓存的数据量过大可能会引起内存资
源紧张,反而降低性能。
如果数据表数据量特别巨大,但是经常使用的往往只是较新的那部分数据。此时,也可为其配置二级缓
存。但是必须单独配置其持久化类的缓存策略,比如最大缓存数、缓存过期时间等,将这些参数降低至
一个合理的范围(太高会引起内存资源紧张,太低了缓存的意义不大)。
3、数据更新频率低;
对于数据更新频率过高的数据,频繁同步缓存中数据的代价可能和 查询缓存中的数据从中获得的
好处相当,坏处益处相抵消。此时缓存的意义也不大。
4、非关键数据(不是财务数据等)
财务数据等是非常重要的数据,绝对不允许出现或使用无效的数据,所以此时为了安全起见最好不要
使用二级缓存。
因为此时 “正确性”的重要性远远大于 “高性能”的重要性。
2. 目前系统中使用hibernate缓存的建议
1.4. 目前情况
一般系统中有三种情况会绕开hibernate执行数据库操作:
1、多个应用系统同时访问一个数据库
此种情况使用hibernate二级缓存会不可避免的造成数据不一致的问题,
此时要进行详细的设计。比如在设计上避免对同一数据表的同时的写入操作,
使用数据库各种级别的锁定机制等。
2、动态表相关
所谓“动态表”是指在系统运行时根据用户的操作系统自动建立的数据表。
比如“自定义表单”等属于用户自定义扩展开发性质的功能模块,因为此时数据表是运行时建立的,
所以不能进行hibernate的映射。因此对它的操作只能是绕开hibernate的直接数据库JDBC操作。
如果此时动态表中的数据没有设计缓存,就不存在数据不一致的问题。
posted @
2009-04-11 10:24 lanxin1020 阅读(505) |
评论 (0) |
编辑 收藏
缓存是介于应用程序和物理数据源之间,其作用是为了降低应用程序对物理数据源访问的频次,从而提高了应用的运行性能。缓存内的数据是对物理数据源中的数据的复制,应用程序在运行时从缓存读写数据,在特定的时刻或事件会同步缓存和物理数据源的数据。
缓存的介质一般是内存,所以读写速度很快。但如果缓存中存放的数据量非常大时,也会用硬盘作为缓存介质。缓存的实现不仅仅要考虑存储的介质,还要考虑到管理缓存的并发访问和缓存数据的生命周期。
Hibernate的缓存包括Session的缓存和SessionFactory的缓存,其中SessionFactory的缓存又可以分为两类:内置缓存和外置缓存。Session的缓存是内置的,不能被卸载,也被称为Hibernate的第一级缓存。SessionFactory的内置缓存和Session的缓存在实现方式上比较相似,前者是SessionFactory对象的一些集合属性包含的数据,后者是指Session的一些集合属性包含的数据。SessionFactory的内置缓存中存放了映射元数据和预定义SQL语句,映射元数据是映射文件中数据的拷贝,而预定义SQL语句是在Hibernate初始化阶段根据映射元数据推导出来,SessionFactory的内置缓存是只读的,应用程序不能修改缓存中的映射元数据和预定义SQL语句,因此SessionFactory不需要进行内置缓存与映射文件的同步。SessionFactory的外置缓存是一个可配置的插件。在默认情况下,SessionFactory不会启用这个插件。外置缓存的数据是数据库数据的拷贝,外置缓存的介质可以是内存或者硬盘。SessionFactory的外置缓存也被称为Hibernate的第二级缓存。
Hibernate的这两级缓存都位于持久化层,存放的都是数据库数据的拷贝,那么它们之间的区别是什么呢?为了理解二者的区别,需要深入理解持久化层的缓存的两个特性:缓存的范围和缓存的并发访问策略。
持久化层的缓存的范围
缓存的范围决定了缓存的生命周期以及可以被谁访问。缓存的范围分为三类。
1 事务范围:缓存只能被当前事务访问。缓存的生命周期依赖于事务的生命周期,当事务结束时,缓存也就结束生命周期。在此范围下,缓存的介质是内存。事务可以是数据库事务或者应用事务,每个事务都有独自的缓存,缓存内的数据通常采用相互关联的的对象形式。
2 进程范围:缓存被进程内的所有事务共享。这些事务有可能是并发访问缓存,因此必须对缓存采取必要的事务隔离机制。缓存的生命周期依赖于进程的生命周期,进程结束时,缓存也就结束了生命周期。进程范围的缓存可能会存放大量的数据,所以存放的介质可以是内存或硬盘。缓存内的数据既可以是相互关联的对象形式也可以是对象的松散数据形式。松散的对象数据形式有点类似于对象的序列化数据,但是对象分解为松散的算法比对象序列化的算法要求更快。
3 集群范围:在集群环境中,缓存被一个机器或者多个机器的进程共享。缓存中的数据被复制到集群环境中的每个进程节点,进程间通过远程通信来保证缓存中的数据的一致性,缓存中的数据通常采用对象的松散数据形式。
对大多数应用来说,应该慎重地考虑是否需要使用集群范围的缓存,因为访问的速度不一定会比直接访问数据库数据的速度快多少。
持久化层可以提供多种范围的缓存。如果在事务范围的缓存中没有查到相应的数据,还可以到进程范围或集群范围的缓存内查询,如果还是没有查到,那么只有到数据库中查询。事务范围的缓存是持久化层的第一级缓存,通常它是必需的;进程范围或集群范围的缓存是持久化层的第二级缓存,通常是可选的。
持久化层的缓存的并发访问策略
当多个并发的事务同时访问持久化层的缓存的相同数据时,会引起并发问题,必须采用必要的事务隔离措施。
在进程范围或集群范围的缓存,即第二级缓存,会出现并发问题。因此可以设定以下四种类型的并发访问策略,每一种策略对应一种事务隔离级别。
事务型:仅仅在受管理环境中适用。它提供了Repeatable Read事务隔离级别。对于经常被读但很少修改的数据,可以采用这种隔离类型,因为它可以防止脏读和不可重复读这类的并发问题。
读写型:提供了Read Committed事务隔离级别。仅仅在非集群的环境中适用。对于经常被读但很少修改的数据,可以采用这种隔离类型,因为它可以防止脏读这类的并发问题。
非严格读写型:不保证缓存与数据库中数据的一致性。如果存在两个事务同时访问缓存中相同数据的可能,必须为该数据配置一个很短的数据过期时间,从而尽量避免脏读。对于极少被修改,并且允许偶尔脏读的数据,可以采用这种并发访问策略。
只读型:对于从来不会修改的数据,如参考数据,可以使用这种并发访问策略。
事务型并发访问策略是事务隔离级别最高,只读型的隔离级别最低。事务隔离级别越高,并发性能就越低。
posted @
2009-04-11 10:18 lanxin1020 阅读(118) |
评论 (0) |
编辑 收藏