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) |
编辑 收藏
什么是 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 阅读(242) |
评论 (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) |
编辑 收藏