2009年11月28日星期六

[原创]Java Web开发参数传输的编码心得

今天做了个测试:

表单的Get或Post方式、AJAX的Get或POST参数传值的情况。使用英文时没什么问题,但是当使用中文时就会出现不同情况。

在此做一个完全的测试。

1. 测试环境一

测试字符为“中文”,“中文”被UTF-8编码一次后为“%E4%B8%AD%E6%96%87”,两次为“%25E4%25B8%25AD%25E6%2596%2587”。
客户端Html编码为UTF-8,服务器端setCharaterEncoding("utf-8");并且不设置server.xml中connector元素的URIEncoding属性。
测试浏览器为Firefox 3.5 和IE 8.服务器为Tomcat 6.
客户端提交时使用的jquery的get和post方法提交的参数。

表单Get方式
                                                            服务器端不解码               服务器端UTF-8解码一次             服务器端UTF-8解码两次
客户端不加密                                              乱码                                 乱码                                       乱码

表单Post方式
                                                            服务器端不解码              服务器端UTF-8解码一次             服务器端UTF-8解码两次
客户端不加密                                              中文                                 中文                                       中文

AJAX的Get方式
                                                            服务器端不解码              服务器端UTF-8解码一次             服务器端UTF-8解码两次
客户端不加密                                              乱码                                 乱码                                       乱码
客户端encodeURIComponent加密一次   编码一次后结果                     中文                                       中文
客户端encodeURIComponent加密两次   编码两次后结果                    编码一次后结果                        中文

AJAX的Post方式
                                                            服务器端不解码              服务器端UTF-8解码一次             服务器端UTF-8解码两次
客户端不加密                                             中文                                    中文                                     中文
客户端encodeURIComponent加密一次  编码一次后结果                        中文                                      中文
客户端encodeURIComponent加密两次  编码两次后结果                    编码一次后结果                          中文

2. 测试环境二

测试字符为“中文”,“中文”被UTF-8编码一次后为“%E4%B8%AD%E6%96%87”,两次为“%25E4%25B8%25AD%25E6%2596%2587”。
客户端Html编码为UTF-8,服务器端不设置setCharaterEncoding("utf-8");并且不设置server.xml中connector元素的URIEncoding属性。
测试浏览器为Firefox 3.5 和IE 8.服务器为Tomcat 6.
客户端提交时使用的jquery的get和post方法提交的参数。

表单Get方式
                                                            服务器端不解码               服务器端UTF-8解码一次             服务器端UTF-8解码两次
客 户端不加密                                              乱码                                 乱码                                       乱码

表单Post方式
                                                            服务器端不解码              服务器端UTF-8解码一次             服务器端UTF-8解码两次
客户端不加密                                               乱码                                 乱码                                       乱码

AJAX的Get方式
                                                            服务器端不解码              服务器端UTF-8解码一次             服务器端UTF-8解码两次
客户端不加密                                              乱码                                 乱码                                       乱码
客户端encodeURIComponent加密一次   编码一次后结果                     中文                                       中文
客户端encodeURIComponent加密两次   编码两次后结果                    编码一次后结果                        中文

AJAX的Post方式
                                                            服务器端不解码              服务器端UTF-8解码一次             服务器端UTF-8解码两次
客户端不加密                                              乱码                                  乱码                                       乱码
客户端encodeURIComponent加密一次  编码一次后结果                        中文                                    中文
客户端encodeURIComponent加密两次  编码两次后结果                    编码一次后结果                        中文

3. 总结

  • 对比两次实验,setCharaterEncoding("utf-8");只有在表单的Post方式和AJAX的post方式时会起作用。post方式会对参数进行utf-8编码(取决于html的编码),而服务器端得默认解码是iso-8859-1,不设置编码的类型所以会产生乱码。
  • Get方式时,始终使用默认的编码类型“iso-8895-1”进行解码,除非设置server.xml的配置文件。所以也可以在客户端不编码的情况下,在服务器端用iso-8859-1编码,再用utf-8解码。(如:new String(old.getBytes("iso-8859-1"),"utf-8"));)。之所以要用utf-8编码是为了保持一致性,.java文件都是utf-8编码存储的。
  • 客户端在参数的传输时,都是先根据网页的编码,对参数编码,此处的情况就是utf-8。
  • 中文时,用utf-8编码,再用iso-8859-1解码就会出现乱码,但是英文则不会。因为UTF-8和iso-8859-1对非中文字符的编码是一样的。
  • 为了同时照顾到get和post的两种情况,以及表单提交的情况。
    • 首先编码需一致采用utf-8(自定),使用setCharaterEncoding("utf-8");
    • 表单方式提交数据时不使用get,既不安全又有乱码问题。实在需要就是用new String(old.getBytes("iso-8859-1"),"utf-8"));的方法。
    • AJAX的get方式时,可以在客户端编码一次(或两次),在服务器端对应的解码一次(两次)。
4. 疑问

  • 在客户端进行一次(两次)编码,在服务器端手动配置了一次(两次)解码。但是服务器默认的getParameter也会进行解码!那不就多解码了一次?
  • 原因是这里使用了jquery,它的ajax方法会自动对参数进行编码
  •         function add( key, value ){
                s[ s.length ] = encodeURIComponent(key) + '=' + encodeURIComponent(value);
            };
  •  如果您是手动添加参数到url上的,那么客户端必须还要encodeURIComponent(value)一次。

2009年11月27日星期五

[转]java类初始化顺序

我们大家都知道,对于静态变量、静态初始化块、变量、初始化块、构造器,它们的初始化顺序依次是(静态变量、静态初始化块)>(变量、初始化块)>构造器。我们也可以通过下面的测试代码来验证这一点:
Java代码
  1. public class InitialOrderTest {   
  2.   
  3.     // 静态变量   
  4.     public static String staticField = "静态变量";   
  5.     // 变量   
  6.     public String field = "变量";   
  7.   
  8.     // 静态初始化块   
  9.     static {   
  10.         System.out.println(staticField);   
  11.         System.out.println("静态初始化块");   
  12.     }   
  13.   
  14.     // 初始化块   
  15.     {   
  16.         System.out.println(field);   
  17.         System.out.println("初始化块");   
  18.     }   
  19.   
  20.     // 构造器   
  21.     public InitialOrderTest() {   
  22.         System.out.println("构造器");   
  23.     }   
  24.   
  25.     public static void main(String[] args) {   
  26.         new InitialOrderTest();   
  27.     }   
  28. }  

运行以上代码,我们会得到如下的输出结果:
  1. 静态变量
  2. 静态初始化块
  3. 变量
  4. 初始化块
  5. 构造器

这与上文中说的完全符合。那么对于继承情况下又会怎样呢?我们仍然以一段测试代码来获取最终结果:
Java代码 
  1. class Parent {   
  2.     // 静态变量   
  3.     public static String p_StaticField = "父类--静态变量";   
  4.     // 变量   
  5.     public String p_Field = "父类--变量";   
  6.   
  7.     // 静态初始化块   
  8.     static {   
  9.         System.out.println(p_StaticField);   
  10.         System.out.println("父类--静态初始化块");   
  11.     }   
  12.   
  13.     // 初始化块   
  14.     {   
  15.         System.out.println(p_Field);   
  16.         System.out.println("父类--初始化块");   
  17.     }   
  18.   
  19.     // 构造器   
  20.     public Parent() {   
  21.         System.out.println("父类--构造器");   
  22.     }   
  23. }   
  24.   
  25. public class SubClass extends Parent {   
  26.     // 静态变量   
  27.     public static String s_StaticField = "子类--静态变量";   
  28.     // 变量   
  29.     public String s_Field = "子类--变量";   
  30.     // 静态初始化块   
  31.     static {   
  32.         System.out.println(s_StaticField);   
  33.         System.out.println("子类--静态初始化块");   
  34.     }   
  35.     // 初始化块   
  36.     {   
  37.         System.out.println(s_Field);   
  38.         System.out.println("子类--初始化块");   
  39.     }   
  40.   
  41.     // 构造器   
  42.     public SubClass() {   
  43.         System.out.println("子类--构造器");   
  44.     }   
  45.   
  46.     // 程序入口   
  47.     public static void main(String[] args) {   
  48.         new SubClass();   
  49.     }   
  50. }  

运行一下上面的代码,结果马上呈现在我们的眼前:
  1. 父类--静态变量
  2. 父类--静态初始化块
  3. 子类--静态变量
  4. 子类--静态初始化块
  5. 父类--变量
  6. 父类--初始化块
  7. 父类--构造器
  8. 子类--变量
  9. 子类--初始化块
  10. 子类--构造器

现在,结果已经不言自明了。大家可能会注意到一点,那就是,并不是父类完全初始化完毕后才进行子类的初始化,实际上子类的静态变量和静态初始化块的初始化是在父类的变量、初始化块和构造器初始化之前就完成了。

那么对于静态变量和静态初始化块之间、变量和初始化块之间的先后顺序又是怎样呢?是否静态变量总是先于静态初始化块,变量总是先于初始化块就被初始化了呢?实际上这取决于它们在类中出现的先后顺序。我们以静态变量和静态初始化块为例来进行说明。

同样,我们还是写一个类来进行测试:
Java代码
  1. public class TestOrder {   
  2.     // 静态变量   
  3.     public static TestA a = new TestA();   
  4.        
  5.     // 静态初始化块   
  6.     static {   
  7.         System.out.println("静态初始化块");   
  8.     }   
  9.        
  10.     // 静态变量   
  11.     public static TestB b = new TestB();   
  12.   
  13.     public static void main(String[] args) {   
  14.         new TestOrder();   
  15.     }   
  16. }   
  17.   
  18. class TestA {   
  19.     public TestA() {   
  20.         System.out.println("Test--A");   
  21.     }   
  22. }   
  23.   
  24. class TestB {   
  25.     public TestB() {   
  26.         System.out.println("Test--B");   
  27.     }   
  28. }  

运行上面的代码,会得到如下的结果:
  1. Test--A
  2. 静态初始化块
  3. Test--B

[原创][精品]一道Java题

在网上看到一道题目,解答之后对类的重写又有了新一步认识,在此分享解答及经验。

代码如下:

/**
 * 父类
 * 
@author rongxinhua
 *
 
*/
public class Father{
    
private String name="FATHER";
    
public Father(){
        whoAmI();
        tellName(name);
    }
    
public void whoAmI(){
        System.out.println(
"Father says, I am " + name);
    }
    
public void tellName(String name){
        System.out.println(
"Father's name is " + name);
    }
}

/**
 * 子类
 * 
@author rongxinhua
 *
 
*/
public class Son extends Father{
    
private String name="SON";
    
public Son(){
        whoAmI();
        tellName(name);
    }
    
public void whoAmI(){
        System.out.println(
"Son says, I am " + name);
    }
    
public void tellName(String name){
        System.out.println(
"Son's name is " + name);
    }
}
问题:当执行 Father who = new Son(); 时,会输出什么?

结果为:

Son says, I am null
Son's name is FATHER
Son says, I am SON
Son's name is SON

 下面两个打印结果很容易想到。但是前面两个就有点摸不着头脑。我一开始也难以理解,主要是考虑的方向错误,误以为是与初始化顺序有关的题目。但其实是一道关于”重写“的题目。

解答:
Son的两个方法whoAmI和tellName重写了父类的方法,这一点是毋庸置疑的。这里有一点:重写是静态的,重写的过程在编译时就完成了,而重载则是动态的(延迟绑定)。所以当执行 Father who = new Son(); 时,实际的执行顺序如下

1. Son的whoAmI()方法:System.out.println("Father says, I am " + name);
2. Son的tellName(String name)方法:System.out.println("Son's name is " + name);
3. Son的whoAmI()方法:System.out.println("Father says, I am " + name);
4. Son的tellName(String name)方法:System.out.println("Son's name is " + name);
看起来有点奇怪,这不都一样吗?的确差不多。但是name不同!3与4的name是Son德name属性(这很容易理解)。
2的name是方法参数传过来的,而且是在父类的构造函数中执行的,那么name的值应该是父类的name属性。可能有点不理解,那么在父类的构造函数中加上一句
        System.out.println("Father says, I am " + name);
得到的结果是”Father says, I am FATHER“。所以name属性在父类的构造方法中的确为”Father“且传给了被重写的tellName方法。
1的name值又为什么为null呢?因为whoAmI方法中的name值直接来自类的实例变量。或许你认为Father的name值应该传给它,不就打印出Son's name is FATHER吗?其实不是。虽然变量名都是name,但是这两个变量是不同的。在编译成字节码的时候这两个变量就区分出来了。Java中方法可以重写,但类的属性不行。对此你可以把name的访问权限都改成public结果仍旧不变。还不理解,那就把Father的name属性名改为name_f,并且在子类的whoAmI方法中加入如下代码:

super.whoAmI();

得到的结果为:

Son says, I am null
Son's name is FATHER
Father says, I am FATHER
Son says, I am SON
Son's name is SON
可见父类的whoAmI方法仍旧可以正常打印结果。这样理解就方便多了!

结论:从这到题可以知道几点:

Java的重写是在编译成字节码时执行的。
Java的方法可以重写但是变量不行。
父类的变量会被保留,如果此变量的权限不为private,则可以通过super来访问。
被重写的方法中引用的实例变量是子类的变量,即使与父类变量名重复,也不会在父类的执行环境中(如构造函数)使用父类变量。
被重写的父类方法可以用super访问。

2009年11月25日星期三

[转]Java虚拟机类装载:原理、实现与应用

转自:http://lxj8495138.javaeye.com/blog/380598


       一、引言 

  Java虚拟机(JVM)的类装载就是指将包含在类文件中的字节码装载到JVM中, 并使其成为JVM一部分的过程。JVM的类动态装载技术能 够在运行时刻动态地加载或者替换系统的某些功能模块, 而不影响系统其他功能模块的正常运行。本文将分析JVM中的类装载系统,探讨JVM中类装载的原 理、实现以及应用。 

  二、Java虚拟机的类装载实现与应用 

  2.1 装载过程简介 

  所谓装载就是寻找一个类或是一个接口的二进制形式并用该二进制形式来构造代表这个类或是这个接口的class对象的过程,其中类或接口的名称是给定了的。当然名称也可以通过计算得到,但是更常见的是通过搜索源代码经过编译器编译后所得到的二进制形式来构造。 

  在Java中,类装载器把一个类装入Java虚拟机中,要经过三个步骤来完成:装载、链接和初始化,其中链接又可以分成校验、准备和解析三步,除了解析外,其它步骤是严格按照顺序完成的,各个步骤的主要工作如下: 

  装载:查找和导入类或接口的二进制数据; 
  链接:执行下面的校验、准备和解析步骤,其中解析步骤是可以选择的; 
  校验:检查导入类或接口的二进制数据的正确性; 
  准备:给类的静态变量分配并初始化存储空间; 
  解析:将符号引用转成直接引用; 
  初始化:激活类的静态变量的初始化Java代码和静态Java代码块。 

  至于在类装载和虚拟机启动的过程中的具体细节和可能会抛出的错误,请参看《Java虚拟机规范》以及《深入Java虚拟机》,它们在网络上面的资源地址是: 
  http://java.sun.com/docs/books/vmspec/2nd-edition/html/Preface.doc.html 
  http://www.artima.com/insidejvm/ed2/index.html 
  由于本文的讨论重点不在此就不再多叙述。 

  2.2 装载的实现 

  JVM中类的装载是由ClassLoader和它的子类来实现的,Java ClassLoader 是一个重要的Java运行时系统组件。它负责在运行时查找和装入类文件的类。 

  在Java中,ClassLoader是一个抽象类,它在包java.lang中,可以这样说,只要了解了在ClassLoader中的一些重 要的方法,再结合上面所介绍的JVM中类装载的具体的过程,对动态装载类这项技术就有了一个比较大概的掌握,这些重要的方法包括以下几个: 

  ①loadCass方法 loadClass(String name ,boolean resolve)其中name参数指定了JVM需要 的类的名称,该名称以包表示法表示,如Java.lang.Object;resolve参数告诉方法是否需要解析类,在初始化类之前,应考虑类解析,并 不是所有的类都需要解析,如果JVM只需要知道该类是否存在或找出该类的超类,那么就不需要解析。这个方法是ClassLoader 的入口点。 

  ②defineClass方法 这个方法接受类文件的字节数组并把它转换成Class对象。字节数组可以是从本地文件系统或网络装入的数据。它把字节码分析成运行时数据结构、校验有效性等等。 

  ③findSystemClass方法 findSystemClass方法从本地文件系统装入文件。它在本地文件系统中寻找类文件,如果存 在,就使用defineClass将字节数组转换成Class对象,以将该文件转换成类。当运行Java应用程序时,这是JVM 正常装入类的缺省机 制。 

  ④resolveClass方法 resolveClass(Class c)方法解析装入的类,如果该类已经被解析过那么将不做处理。当调用loadClass方法时,通过它的resolve 参数决定是否要进行解析。 

  ⑤findLoadedClass方法 当调用loadClass方法装入类时,调用findLoadedClass 方法来查看 ClassLoader是否已装入这个类,如果已装入,那么返回Class对象,否则返回NULL。如果强行装载已存在的类,将会抛出链接错误。 

  2.3 装载的应用 

  一般来说,我们使用虚拟机的类装载时需要继承抽象类java.lang.ClassLoader,其中必须实现的方法是 loadClass(),对于这个方法需要实现如下操作:(1) 确认类的名称;(2) 检查请求要装载的类是否已经被装载;(3) 检查请求加载的类是 否是系统类;(4) 尝试从类装载器的存储区获取所请求的类;(5) 在虚拟机中定义所请求的类;(6) 解析所请求的类;(7) 返回所请求的类。 

  所有的Java 虚拟机都包括一个内置的类装载器,这个内置的类库装载器被称为根装载器(bootstrap ClassLoader)。根装 载器的特殊之处是它只能够装载在设计时刻已知的类,因此虚拟机假定由根装载器所装载的类都是安全的、可信任的,可以不经过安全认证而直接运行。当应用程序 需要加载并不是设计时就知道的类时,必须使用用户自定义的装载器(user-defined ClassLoader)。下面我们举例说明它的应用。 

  public abstract class MultiClassLoader extends ClassLoader{ 
  ... 
  public synchronized Class loadClass(String s, boolean flag) 
  throws ClassNotFoundException 
  { 
  /* 检查类s是否已经在本地内存*/ 
  Class class1 = (Class)classes.get(s); 

  /* 类s已经在本地内存*/ 
  if(class1 != null) return class1; 
  try/*用默认的ClassLoader 装入类*/ { 
  class1 = super.findSystemClass(s); 
  return class1; 
  } 
  catch(ClassNotFoundException _ex) { 
  System.out.println(">> Not a system class."); 
  } 

  /* 取得类s的字节数组*/ 
  byte abyte0[] = loadClassBytes(s); 
  if(abyte0 == null) throw new ClassNotFoundException(); 

  /* 将类字节数组转换为类*/ 
  class1 = defineClass(null, abyte0, 0, abyte0.length); 
  if(class1 == null) throw new ClassFormatError(); 
  if(flag) resolveClass(class1); /*解析类*/ 

  /* 将新加载的类放入本地内存*/ 
  classes.put(s, class1); 
  System.out.println(">> Returning newly loaded class."); 

  /* 返回已装载、解析的类*/ 
  return class1; 
  } 
  ... 
  } 
三、Java虚拟机的类装载原理 

  前面我们已经知道,一个Java应用程序使用两种类型的类装载器:根装载器(bootstrap)和用户定义的装载器(user- defined)。根装载器是Java虚拟机实现的一部分,举个例子来说,如果一个Java虚拟机是在现在已经存在并且正在被使用的操作系统的顶部用C程 序来实现的,那么根装载器将是那些C程序的一部分。根装载器以某种默认的方式将类装入,包括那些Java API的类。在运行期间一个Java程序能安装 用户自己定义的类装载器。根装载器是虚拟机固有的一部分,而用户定义的类装载器则不是,它是用Java语言写的,被编译成class文件之后然后再被装入 到虚拟机,并像其它的任何对象一样可以被实例化。 Java类装载器的体系结构如下所示: 
   
  Java的类装载模型是一种代理 (delegation)模型。当JVM 要求类装载器CL(ClassLoader)装载一个类时,CL首先将这个类装载请求转发给他的父装载器。只有 当父装载器没有装载并无法装载这个类时,CL才获得装载这个类的机会。这样, 所有类装载器的代理关系构成了一种树状的关系。树的根是类的根装载器 (bootstrap ClassLoader) , 在JVM 中它以"null"表示。除根装载器以外的类装载器有且仅有一个父装载器。在创建一个装 载器时, 如果没有显式地给出父装载器, 那么JVM将默认系统装载器为其父装载器。Java的基本类装载器代理结构如图2所示: 
下面针对各种类装载器分别进行详细的说明。 
      根(Bootstrap) 装载器:该装载器没有父装载器,它是JVM实现的一部分,从sun.boot.class.path装载运行时库的核心代码。 
     扩展(Extension) 装载器:继承的父装载器为根装载器,不像根装载器可能与运行时的操作系统有关,这个类装载器是用纯Java代码实现的,它从java.ext.dirs (扩展目录)中装载代码。 
   系统(System or Application) 装载器:装载器为扩展装载器,我们都知道在安装JDK的时候要设置环境变量 (CLASSPATH ),这个类装载器就是从java.class.path(CLASSPATH 环境变量)中装载代码的,它也是用纯Java代码实 现的,同时还是用户自定义类装载器的缺省父装载器。 

  小应用程序(Applet) 装载器: 装载器为系统装载器,它从用户指定的网络上的特定目录装载小应用程序代码。 

  在设计一个类装载器的时候,应该满足以下两个条件: 

  对于相同的类名,类装载器所返回的对象应该是同一个类对象 

  如果类装载器CL1将装载类C的请求转给类装载器CL2,那么对于以下的类或接口,CL1和CL2应该返回同一个类对象:a)S为C的直接超 类;b)S为C的直接超接口;c)S为C的成员变量的类型;d)S为C的成员方法或构建器的参数类型;e)S为C的成员方法的返回类型。 
  每 个已经装载到JVM中的类都隐式含有装载它的类装载器的信息。类方法getClassLoader 可以得到装载这个类的类装载器。一个类装载器认识的类 包括它的父装载器认识的类和它自己装载的类,可见类装载器认识的类是它自己装载的类的超集。注意我们可以得到类装载器的有关的信息,但是已经装载到JVM 中的类是不能更改它的类装载器的。 

  Java中的类的装载过程也就是代理装载的过程。比如:Web浏览器中的JVM需要装载一个小应用程序TestApplet。JVM调用小应用 程序装载器ACL(Applet ClassLoader)来完成装载。ACL首先请求它的父装载器, 即系统装载器装载TestApplet是否装载了 这个类, 由于TestApplet不在系统装载器的装载路径中, 所以系统装载器没有找到这个类, 也就没有装载成功。接着ACL自己装载 TestApplet。ACL通过网络成功地找到了TestApplet.class 文件并将它导入到了JVM中。在装载过程中, JVM发现 TestAppet是从超类java.applet.Applet继承的。所以JVM再次调用ACL来装载java.applet.Applet类。 ACL又再次按上面的顺序装载Applet类, 结果ACL发现他的父装载器已经装载了这个类, 所以ACL就直接将这个已经装载的类返回给了 JVM , 完成了Applet类的装载。接下来,Applet类的超类也一样处理。最后, TestApplet及所有有关的类都装载到了JVM中。 

  四、结论 

  类的动态装载机制是JVM的一项核心技术, 也是容易被忽视而引起很多误解的地方。本文介绍了JVM中类装载的原理、实现以及应用,尤其分析了 ClassLoader的结构、用途以及如何利用自定义的ClassLoader装载并执行Java类,希望能使读者对JVM中的类装载有一个比较深入的 理解

[转]ClassLoader详解

转自:http://wolfware.bokee.com/3412061.html

当JVM(Java虚拟机)启动时,会形成由三个类加载器组成的初始类加载器层次结构:

        bootstrap classloader
                 |
        extension classloader
                 |
        system classloader

bootstrap classloader - 引导(也称为原始)类加载器,它负责加载Java的核心类。在Sun的JVM中,在执行java的命令中使用-Xbootclasspath选项或使用 -D选项指定sun.boot.class.path系统属性值可以指定附加的类。这个加载器的是非常特殊的,它实际上不是 java.lang.ClassLoader的子类,而是由JVM自身实现的。大家可以通过执行以下代码来获得bootstrap classloader加载了那些核心类库:
    URL[] urls=sun.misc.Launcher.getBootstrapClassPath().getURLs();
    for (int i = 0; i < urls.length; i++) {
      System.out.println(urls[i].toExternalForm());
    }
在我的计算机上的结果为:
file:/C:/j2sdk1.4.1_01/jre/lib/endorsed/dom.jar
file:/C:/j2sdk1.4.1_01/jre/lib/endorsed/sax.jar
file:/C:/j2sdk1.4.1_01/jre/lib/endorsed/xalan-2.3.1.jar
file:/C:/j2sdk1.4.1_01/jre/lib/endorsed/xercesImpl-2.0.0.jar
file:/C:/j2sdk1.4.1_01/jre/lib/endorsed/xml-apis.jar
file:/C:/j2sdk1.4.1_01/jre/lib/endorsed/xsltc.jar
file:/C:/j2sdk1.4.1_01/jre/lib/rt.jar
file:/C:/j2sdk1.4.1_01/jre/lib/i18n.jar
file:/C:/j2sdk1.4.1_01/jre/lib/sunrsasign.jar
file:/C:/j2sdk1.4.1_01/jre/lib/jsse.jar
file:/C:/j2sdk1.4.1_01/jre/lib/jce.jar
file:/C:/j2sdk1.4.1_01/jre/lib/charsets.jar
file:/C:/j2sdk1.4.1_01/jre/classes
这时大家知道了为什么我们不需要在系统属性CLASSPATH中指定这些类库了吧,因为JVM在启动的时候就自动加载它们了。

extension classloader - 扩展类加载器,它负责加载JRE的扩展目录(JAVA_HOME/jre/lib/ext或者由java.ext.dirs系统属性指定的)中JAR的类 包。这为引入除Java核心类以外的新功能提供了一个标准机制。因为默认的扩展目录对所有从同一个JRE中启动的JVM都是通用的,所以放入这个目录的 JAR类包对所有的JVM和system classloader都是可见的。在这个实例上调用方法getParent()总是返回空值null,因为引导加载器bootstrap classloader不是一个真正的ClassLoader实例。所以当大家执行以下代码时:
    System.out.println(System.getProperty("java.ext.dirs"));
    ClassLoader extensionClassloader=ClassLoader.getSystemClassLoader().getParent();
    System.out.println("the parent of extension classloader : "+extensionClassloader.getParent());
结果为:
C:\j2sdk1.4.1_01\jre\lib\ext
the parent of extension classloader : null
extension classloader是system classloader的parent,而bootstrap classloader是extension classloader的parent,但它不是一个实际的classloader,所以为null。

system classloader - 系统(也称为应用)类加载器,它负责在JVM被启动时,加载来自在命令java中的-classpath或者java.class.path系统属性或者 CLASSPATH操作系统属性所指定的JAR类包和类路径。总能通过静态方法ClassLoader.getSystemClassLoader()找 到该类加载器。如果没有特别指定,则用户自定义的任何类加载器都将该类加载器作为它的父加载器。执行以下代码即可获得:
    System.out.println(System.getProperty("java.class.path"));
输出结果则为用户在系统属性里面设置的CLASSPATH。
classloader 加载类用的是全盘负责委托机制。所谓全盘负责,即是当一个classloader加载一个Class的时候,这个Class所依赖的和引用的所有 Class也由这个classloader负责载入,除非是显式的使用另外一个classloader载入;委托机制则是先让parent(父)类加载器 (而不是super,它与parent classloader类不是继承关系)寻找,只有在parent找不到的时候才从自己的类路径中去寻找。此外类加载还采用了cache机制,也就是如果 cache中保存了这个Class就直接返回它,如果没有才从文件中读取和转换成Class,并存入cache,这就是为什么我们修改了Class但是必 须重新启动JVM才能生效的原因。


每个ClassLoader加载Class的过程是:

1.检测此Class是否载入过(即在cache中是否有此Class),如果有到8,如果没有到2
2.如果parent classloader不存在(没有parent,那parent一定是bootstrap classloader了),到4
3.请求parent classloader载入,如果成功到8,不成功到5
4.请求jvm从bootstrap classloader中载入,如果成功到8
5.寻找Class文件(从与此classloader相关的类路径中寻找)。如果找不到则到7.
6.从文件中载入Class,到8.
7.抛出ClassNotFoundException.
8.返回Class.

其中5.6步我们可以通过覆盖ClassLoader的findClass方法来实现自己的载入策略。甚至覆盖loadClass方法来实现自己的载入过程。

类加载器的顺序是:
先 是bootstrap classloader,然后是extension classloader,最后才是system classloader。大家会发现加载的Class越是重要的越在靠前面。这样做的原因是出于安全性的考虑,试想如果system classloader“亲自”加载了一个具有破坏性的“java.lang.System”类的后果吧。这种委托机制保证了用户即使具有一个这样的类, 也把它加入到了类路径中,但是它永远不会被载入,因为这个类总是由bootstrap classloader来加载的。大家可以执行一下以下的代码:
    System.out.println(System.class.getClassLoader());
将会看到结果是null,这就表明java.lang.System是由bootstrap classloader加载的,因为bootstrap classloader不是一个真正的ClassLoader实例,而是由JVM实现的,正如前面已经说过的。

下面就让我们来看看JVM是如何来为我们来建立类加载器的结构的:
sun.misc.Launcher,顾名思义,当你执行java命令的时候,JVM会先使用bootstrap classloader载入并初始化一个Launcher,执行下来代码:
   System.out.println("the Launcher's classloader is "+sun.misc.Launcher.getLauncher().getClass().getClassLoader());
结果为:
   the Launcher's classloader is null (因为是用bootstrap classloader加载,所以class loader为null)
Launcher 会根据系统和命令设定初始化好class loader结构,JVM就用它来获得extension classloader和system classloader,并载入所有的需要载入的Class,最后执行java命令指定的带有静态的main方法的Class。extension classloader实际上是sun.misc.Launcher$ExtClassLoader类的一个实例,system classloader实际上是sun.misc.Launcher$AppClassLoader类的一个实例。并且都是 java.net.URLClassLoader的子类。

让我们来看看Launcher初试化的过程的部分代码。

Launcher的部分代码:
public class Launcher  {
    public Launcher() {
        ExtClassLoader extclassloader;
        try {
            //初始化extension classloader
            extclassloader = ExtClassLoader.getExtClassLoader();
        } catch(IOException ioexception) {
            throw new InternalError("Could not create extension class loader");
        }
        try {
            //初始化system classloader,parent是extension classloader
            loader = AppClassLoader.getAppClassLoader(extclassloader);
        } catch(IOException ioexception1) {
            throw new InternalError("Could not create application class loader");
        }
        //将system classloader设置成当前线程的context classloader(将在后面加以介绍)
        Thread.currentThread().setContextClassLoader(loader);
        ......
    }
    public ClassLoader getClassLoader() {
        //返回system classloader
        return loader;
    }
}

extension classloader的部分代码:
static class Launcher$ExtClassLoader extends URLClassLoader {

    public static Launcher$ExtClassLoader getExtClassLoader()
        throws IOException
    {
        File afile[] = getExtDirs();
        return (Launcher$ExtClassLoader)AccessController.doPrivileged(new Launcher$1(afile));
    }
   private static File[] getExtDirs() {
        //获得系统属性“java.ext.dirs”
        String s = System.getProperty("java.ext.dirs");
        File afile[];
        if(s != null) {
            StringTokenizer stringtokenizer = new StringTokenizer(s, File.pathSeparator);
            int i = stringtokenizer.countTokens();
            afile = new File[i];
            for(int j = 0; j < i; j++)
                afile[j] = new File(stringtokenizer.nextToken());

        } else {
            afile = new File[0];
        }
        return afile;
    }
}

system classloader的部分代码:
static class Launcher$AppClassLoader extends URLClassLoader
{

    public static ClassLoader getAppClassLoader(ClassLoader classloader)
        throws IOException
    {
        //获得系统属性“java.class.path”
        String s = System.getProperty("java.class.path");
        File afile[] = s != null ? Launcher.access$200(s) : new File[0];
        return (Launcher$AppClassLoader)AccessController.doPrivileged(new Launcher$2(s, afile, classloader));
    }
}

看 了源代码大家就清楚了吧,extension classloader是使用系统属性“java.ext.dirs”设置类搜索路径的,并且没有parent。system classloader是使用系统属性“java.class.path”设置类搜索路径的,并且有一个parent classloader。Launcher初始化extension classloader,system classloader,并将system classloader设置成为context classloader,但是仅仅返回system classloader给JVM。

  这里怎么又出来一个context classloader呢?它有什么用呢?我们在建立一个线程Thread的时候,可以为这个线程通过setContextClassLoader方法来 指定一个合适的classloader作为这个线程的context classloader,当此线程运行的时候,我们可以通过getContextClassLoader方法来获得此context classloader,就可以用它来载入我们所需要的Class。默认的是system classloader。利用这个特性,我们可以“打破”classloader委托机制了,父classloader可以获得当前线程的context classloader,而这个context classloader可以是它的子classloader或者其他的classloader,那么父classloader就可以从其获得所需的 Class,这就打破了只能向父classloader请求的限制了。这个机制可以满足当我们的classpath是在运行时才确定,并由定制的 classloader加载的时候,由system classloader(即在jvm classpath中)加载的class可以通过context classloader获得定制的classloader并加载入特定的class(通常是抽象类和接口,定制的classloader中是其实现),例 如web应用中的servlet就是用这种机制加载的.


好了,现在我们了解了classloader的结构和工作原理,那么我们如 何实现在运行时的动态载入和更新呢?只要我们能够动态改变类搜索路径和清除classloader的cache中已经载入的Class就行了,有两个方 案,一是我们继承一个classloader,覆盖loadclass方法,动态的寻找Class文件并使用defineClass方法来;另一个则非常 简单实用,只要重新使用一个新的类搜索路径来new一个classloader就行了,这样即更新了类搜索路径以便来载入新的Class,也重新生成了一 个空白的cache(当然,类搜索路径不一定必须更改)。噢,太好了,我们几乎不用做什么工作,java.netURLClassLoader正是一个符 合我们要求的classloader!我们可以直接使用或者继承它就可以了!

这是j2se1.4 API的doc中URLClassLoader的两个构造器的描述:
URLClassLoader(URL[] urls)
          Constructs a new URLClassLoader for the specified URLs using the default delegation parent ClassLoader.
URLClassLoader(URL[] urls, ClassLoader parent)
          Constructs a new URLClassLoader for the given URLs.
其中URL[] urls就是我们要设置的类搜索路径,parent就是这个classloader的parent classloader,默认的是system classloader。


好,现在我们能够动态的载入Class了,这样我们就可以利用newInstance方法来获得一个Object。但我们如何将此Object造型呢?可以将此Object造型成它本身的Class吗?

首先让我们来分析一下java源文件的编译,运行吧!javac命令是调用“JAVA_HOME/lib/tools.jar”中的“com.sun.tools.javac.Main”的compile方法来编译:

    public static int compile(String as[]);

    public static int compile(String as[], PrintWriter printwriter);

返回0表示编译成功,字符串数组as则是我们用javac命令编译时的参数,以空格划分。例如:
javac -classpath c:\foo\bar.jar;. -d c:\ c:\Some.java
则 字符串数组as为{"-classpath","c:\\foo\\bar.jar;.","-d","c:\\","c:\\Some.java"}, 如果带有PrintWriter参数,则会把编译信息出到这个指定的printWriter中。默认的输出是System.err。

其中 Main是由JVM使用Launcher初始化的system classloader载入的,根据全盘负责原则,编译器在解析这个java源文件时所发现的它所依赖和引用的所有Class也将由system classloader载入,如果system classloader不能载入某个Class时,编译器将抛出一个“cannot resolve symbol”错误。

所以首先编译就通不过,也就是编译器无法编译一个引用了不在CLASSPATH中的未知Class的java源文件,而由于拼写错误或者没有把所需类库放到CLASSPATH中,大家一定经常看到这个“cannot resolve symbol”这个编译错误吧!

其 次,就是我们把这个Class放到编译路径中,成功的进行了编译,然后在运行的时候不把它放入到CLASSPATH中而利用我们自己的 classloader来动态载入这个Class,这时候也会出现“java.lang.NoClassDefFoundError”的违例,为什么呢?

我 们再来分析一下,首先调用这个造型语句的可执行的Class一定是由JVM使用Launcher初始化的system classloader载入的,根据全盘负责原则,当我们进行造型的时候,JVM也会使用system classloader来尝试载入这个Class来对实例进行造型,自然在system classloader寻找不到这个Class时就会抛出“java.lang.NoClassDefFoundError”的违例。

OK, 现在让我们来总结一下,java文件的编译和Class的载入执行,都是使用Launcher初始化的system classloader作为类载入器的,我们无法动态的改变system classloader,更无法让JVM使用我们自己的classloader来替换system classloader,根据全盘负责原则,就限制了编译和运行时,我们无法直接显式的使用一个system classloader寻找不到的Class,即我们只能使用Java核心类库,扩展类库和CLASSPATH中的类库中的Class。

还 不死心!再尝试一下这种情况,我们把这个Class也放入到CLASSPATH中,让system classloader能够识别和载入。然后我们通过自己的classloader来从指定的class文件中载入这个Class(不能够委托 parent载入,因为这样会被system classloader从CLASSPATH中将其载入),然后实例化一个Object,并造型成这个Class,这样JVM也识别这个Class(因为 system classloader能够定位和载入这个Class从CLASSPATH中),载入的也不是CLASSPATH中的这个Class,而是从 CLASSPATH外动态载入的,这样总行了吧!十分不幸的是,这时会出现“java.lang.ClassCastException”违例。

为 什么呢?我们也来分析一下,不错,我们虽然从CLASSPATH外使用我们自己的classloader动态载入了这个Class,但将它的实例造型的时 候是JVM会使用system classloader来再次载入这个Class,并尝试将使用我们的自己的classloader载入的Class的一个实例造型为system classloader载入的这个Class(另外的一个)。大家发现什么问题了吗?也就是我们尝试将从一个classloader载入的Class的一 个实例造型为另外一个classloader载入的Class,虽然这两个Class的名字一样,甚至是从同一个class文件中载入。但不幸的是JVM 却认为这个两个Class是不同的,即JVM认为不同的classloader载入的相同的名字的Class(即使是从同一个class文件中载入的)是 不同的!这样做的原因我想大概也是主要出于安全性考虑,这样就保证所有的核心Java类都是system

classloader载入的,我们无法用自己的classloader载入的相同名字的Class的实例来替换它们的实例。

看到这里,聪明的读者一定想到了该如何动态载入我们的Class,实例化,造型并调用了吧!

那 就是利用面向对象的基本特性之一的多形性。我们把我们动态载入的Class的实例造型成它的一个system classloader所能识别的父类就行了!这是为什么呢?我们还是要再来分析一次。当我们用我们自己的classloader来动态载入这我们只要把 这个Class的时候,发现它有一个父类Class,在载入它之前JVM先会载入这个父类Class,这个父类Class是system classloader所能识别的,根据委托机制,它将由system classloader载入,然后我们的classloader再载入这个Class,创建一个实例,造型为这个父类Class,注意了,造型成这个父类 Class的时候(也就是上溯)是面向对象的java语言所允许的并且JVM也支持的,JVM就使用system classloader再次载入这个父类Class,然后将此实例造型为这个父类Class。大家可以从这个过程发现这个父类Class都是由 system classloader载入的,也就是同一个class loader载入的同一个Class,所以造型的时候不会出现任何异常。而根据多形性,调用这个父类的方法时,真正执行的是这个Class(非父类 Class)的覆盖了父类方法的方法。这些方法中也可以引用system classloader不能识别的Class,因为根据全盘负责原则,只要载入这个Class的classloader即我们自己定义的 classloader能够定位和载入这些Class就行了。

这样我们就可以事先定义好一组接口或者基类并放入CLASSPATH中,然后 在执行的时候动态的载入实现或者继承了这些接口或基类的子类。还不明白吗?让我们来想一想Servlet吧,web application server能够载入任何继承了Servlet的Class并正确的执行它们,不管它实际的Class是什么,就是都把它们实例化成为一个Servlet Class,然后执行Servlet的init,doPost,doGet和destroy等方法的,而不管这个Servlet是从web- inf/lib和web-inf/classes下由system classloader的子classloader(即定制的classloader)动态载入。说了这么多希望大家都明白了。在applet,ejb等 容器中,都是采用了这种机制.

对于以上各种情况,希望大家实际编写一些example来实验一下。

最后我再说点别 的,classloader虽然称为类加载器,但并不意味着只能用来加载Class,我们还可以利用它也获得图片,音频文件等资源的URL,当然,这些资 源必须在CLASSPATH中的jar类库中或目录下。我们来看API的doc中关于ClassLoader的两个寻找资源和Class的方法描述吧:
        public URL getResource(String name)
        用指定的名字来查找资源,一个资源是一些能够被class代码访问的在某种程度上依赖于代码位置的数据(图片,音频,文本等等)。
                一个资源的名字是以'/'号分隔确定资源的路径名的。
                这个方法将先请求parent classloader搜索资源,如果没有parent,则会在内置在虚拟机中的classloader(即bootstrap classloader)的路径中搜索。如果失败,这个方法将调用findResource(String)来寻找资源。
        public static URL getSystemResource(String name)
                从用来载入类的搜索路径中查找一个指定名字的资源。这个方法使用system class loader来定位资源。即相当于ClassLoader.getSystemClassLoader().getResource(name)。

例如:
    System.out.println(ClassLoader.getSystemResource("java/lang/String.class"));
的结果为:
    jar:file:/C:/j2sdk1.4.1_01/jre/lib/rt.jar!/java/lang/String.class
表明String.class文件在rt.jar的java/lang目录中。
因此我们可以将图片等资源随同Class一同打包到jar类库中(当然,也可单独打包这些资源)并添加它们到class loader的搜索路径中,我们就可以无需关心这些资源的具体位置,让class loader来帮我们寻找了!

讲了这么多,希望大家能够明白java classloader的载入机制和载入规则,更希望能够对大家以后的开发有所帮助. ^_^

关于class loader的一些资料:

http://dev.csdn.net/article/60/60806.shtm


[转]Play! framework hot swap 浅析

转自:http://www.blogjava.net/mingj/archive/2009/01/05/249208.html


play! 最大的卖点就在于 hot swap,正如它自己宣称的:
reach your maximum productivity。play! 允许开发人员修改java文件,保存,然后刷新浏览器,立马可以看到效果。不需要编译,也不需要重启服务器。
Java 要想实现动态更新 class 文件,不外乎两种手段:替换 classloader、替换 JVM。因为替换 JVM 引起的开销更大,需要维护 JVM 的堆、栈等运行信息,所以 hot swap 通常是选择替换 classloader。比如 grails 里面就是选择替换 classloader,它会自己维护一个线程,定期轮询源文件是否发生修改,以替换原来的 classloader。那么 play! 宣称的 hot swap 又是怎么实现的呢?
让我们来看看play! 的内部流程:
1. play! 使用了 Apache Mina 作为底层的 http server,然后使用了自己关于 Mina IoHandler 接口的实现―― HttpHandler
2. 当浏览器发起一个 request:
2.1 Mina Server 生成一个 Mina Request,转发给 HttpHandler 的 messageReceived 方法
2.2 play! 解析 Mina Request 和 Mina Session,包装成自己的 Request 对象

Request request = parseRequest(minaRequest, session);

2.3 play! 检测 Route 文件修改情况,根据 Route 配置信息将 Route/Action 的信息赋给 Request 对象

Router.detectChanges();
Router.route(request);

2.4 play! 根据当前配置的开发模式来采用不同的策略调用 Action 来理 Request

if (Play.mode == Play.Mode.DEV) {
Invoker.invokeInThread(
new MinaInvocation(session, minaRequest, minaResponse, request, response));
}
else {
Invoker.invoke(
new MinaInvocation(session, minaRequest, minaResponse, request, response));
}

2.5 如果 play! 当前是 DEV 模式,invokeInThread方法会让 invocation 对象代理 run() 方法

public void run() {
try {
before();
execute();
after();
}
catch (Throwable e) {
onException(e);
}
finally {
_finally();
}
}

咱们来看看 before() 方法:
public static void before() {
Thread.currentThread().setContextClassLoader(Play.classloader);
if(!Play.id.equals("test")) {
Play.detectChanges();
if (!Play.started) {
Play.start();
}
}
//
}

在 Play 类的 detectChanges() 方法里面,有这么一句:
classloader.detectChanges();

哈哈,play! 修改源文件后,刷新浏览器即见效的奥秘就在这里了。再进去看看 play! 自定义 classloader 的 detectChanges() 方法:

public void detectChanges() {
// Now check for file modification
List<ApplicationClass> modifieds = new ArrayList<ApplicationClass>();
for (ApplicationClass applicationClass : Play.classes.all()) {
if (applicationClass.timestamp < applicationClass.javaFile.lastModified()) {
applicationClass.refresh();
modifieds.add(applicationClass);
}
}
List
<ClassDefinition> newDefinitions = new ArrayList<ClassDefinition>();
Map
<Class, Integer> annotationsHashes = new HashMap<Class, Integer>();
for (ApplicationClass applicationClass : modifieds) {
annotationsHashes.put(applicationClass.javaClass, computeAnnotationsHash(applicationClass.javaClass));
if (applicationClass.compile() == null) {
Play.classes.classes.remove(applicationClass.name);
}
else {
applicationClass.enhance();
BytecodeCache.cacheBytecode(applicationClass.enhancedByteCode, applicationClass.name, applicationClass.javaSource);
newDefinitions.add(
new ClassDefinition(applicationClass.javaClass, applicationClass.enhancedByteCode));
}
}
try {
HotswapAgent.reload(newDefinitions.toArray(
new ClassDefinition[newDefinitions.size()]));
}
catch (ClassNotFoundException e) {
throw new UnexpectedException(e);
}
catch (UnmodifiableClassException e) {
throw new UnexpectedException(e);
}
// Check new annotations
for (Class clazz : annotationsHashes.keySet()) {
if (annotationsHashes.get(clazz) != computeAnnotationsHash(clazz)) {
throw new RuntimeException("Annotations change !");
}
}
// Now check if there is new classes or removed classes
int hash = computePathHash();
if (hash != this.pathHash) {
// Remove class for deleted files !!
for (ApplicationClass applicationClass : Play.classes.all()) {
if (!applicationClass.javaFile.exists()) {
Play.classes.classes.remove(applicationClass.name);
}
if(applicationClass.name.contains("$")) {
Play.classes.classes.remove(applicationClass.name);
}
}
throw new RuntimeException("Path has changed");
}
}

HotswapAgent类的 reload 方法如下:
public static void reload(ClassDefinition definitions) throws UnmodifiableClassException, ClassNotFoundException {
instrumentation.redefineClasses(definitions);
}

读到这里,也就弄清楚了 play! 怎么实现 hot swap 的原理了,还是调用java.lang.instrument目录下的类和方法来实现的 hot swap。不存在魔法,play! 还是选择了替换 classloader,只不过这个替换动作发生在处理 http request 的时候,于是开发人员用起来就是"刷新浏览器就可以看见效果了"。

2009年11月23日星期一

11111


2009年11月22日星期日

[转]Forward与SendRedirect的区别

一、原理.
1、 Forward
这种方式是在服务器端作的重定向。服务器往client 发送数据的过程是这样的:服务器在向客户端发送数据之前,是先将数据输出到缓冲区,然后将缓冲区中数据发送给client端。什么时候将缓冲区里的数据发 送给client端呢?(1)当对来自client的request处理完,并把所有数据输出到缓冲区,(2)当缓冲区满,(3)在程序中调用缓冲区的输 出方法out.flush()或response.flushbuffer(),web container才将缓冲区中的数据发送给client。
这种重定向方式是利用服务器端的缓冲区机制,在把缓冲区的数据发送到客户端之前,原来的数据不发送,将执行转向重定向页面,发送重定向页面的数据,重定向调用页的数据将被清除。特别提示:在<JSP:FORWORD>之前有很多输出,前面的输出已使缓冲区满,将自动输出到客户端,那么这种重定向方式将不起作用。

public void doPost(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException
{
response.setContentType("text/html; charset=UTF-8″);
ServletContext sc = getServletContext();
RequestDispatcher rd = null;
rd = sc.getRequestDispatcher("/index.jsp");
rd.forward(request, response);
}

2、 sendRedirect
这 种方式是在客户端作的重定向处理。该方法通过修改HTTP协议的HEADER部分(设置状态代码302,命令浏览器发重新发送请求),对浏览器下达重定向 指令的,让浏览器对在location中指定的URL提出请求,使浏览器显示重定向网页的内容。该方法可以接受绝对的或相对的URLs。如果传递到该方法 的参数是一个相对的URL,那么Web container在将它发送到客户端前会把它转换成一个绝对的URL。

public void doPost(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException
{
response.setContentType("text/html; charset=UTF-8″);
//response.sendRedirect("/index.jsp"); 效果和前面forward一样
response.sendRedirect("http://www.sohu.com"); //跳转到sohu
}

二、区别.
1、forward重定向是在容器内部实现的同一个Web应用程序的重定向,所以forward方法只能重定向到同一个Web应 用程序中的一个资源,重定向后浏览器地址栏URL不变,而sendRedirect方法可以重定向到任何URL, 因为这种方法是修改http头来实现的,URL没什么限制,重定向后浏览器地址栏URL改变。
2、forward重定向将原始的HTTP请求对象(request)从一个servlet实例传递到另一个实例,而采用sendRedirect方式两者不是同一个application。
3、基于第二点,参数的传递方式不一样。forward的form参数跟着传递,所以在第二个实例中可以取得HTTP请求的参数。sendRedirect只能通过链接传递参数,response.sendRedirect("login.jsp?param1=a")。
4、 sendRedirect能够处理相对URL,自动把它们转换成绝对URL,如果地址是相对的,没有一个'/',那么Web container就认为它是相对于当前的请求URI的。比如,如果为response.sendRedirect("login.jsp"),则会从当 前servlet 的URL路径下找login.jsp: http://127.0.0.1:8080/test/servlet/Servlet 重定向的URL: http://127.0.0.1:8080/test/servlet/login.jsp,如果为response.sendRedirect(" /login.jsp")则会从当前应用径下查找url:http://127.0.0.1:8080/login.jsp。而forward不能这样处理相对路径。

他们的区别是:
response.sendRedirect是向客户浏览器发送页面重定向指令,浏览器接收后将向web服务器重新发送页面请求,所以执行完后浏览器的url显示的是跳转后的页面。跳转页面可以是一个任意的url(本服务器的和其他服务器的均可)。
RequestDispatcher.forward 则是直接在服务器中进行处理,将处理完后的信息发送给浏览器进行显示,所以完成后在url中显示的是跳转前的页面。在forward的时候将上一页面中传送的request和response信息一同发送给下一页面(而response.sendRedirect不能将上一页面的request和 response信息发送到下一页面)。由于forward是直接在服务器中进行处理,所以forward的页面只能是本服务器的。

2009年11月21日星期六

[note]Servlet中监听器(listener)与过滤器(filter)的区别

监听器(listener):
         用于监听事件的发生,与swing中的监听器类似。可以监听客户端的请求、服务端的操作等。通过监听器,可以自动激发一些操作,如监听在线用户数量,当增加一个HttpSession时,给在线人数加1。编写监听器需要实现相应的接口,编写完成后在web.xml文件中配置一下,就可以起作用了,可以在不修改现有系统基础上,增加web应用程序生命周期事件的跟踪。
         常用的监听器如下:

Listener接口                                       Event类
ServletContextListener                       ServletContextEvent
ServletContextAttributeListener         ServletContextAttributeEvent
HttpSessionListener                           HttpSessionEvent
HttpSessionActivationListener            HttpSessionEvent
HttpSessionAttributeListener             HttpSessionBindingEvent
HttpSessionBindingListener                HttpSessionBindingEvent
ServletRequestListener                       ServletRequestEvent
ServletRequestAttributeListener         ServletRequestAttributeEvent

过滤器(filter):

          用于对服务器请求的过滤,它与监听器最大的不同是:监听器只能够监听请求而无法改变原来的请求、而过滤器可以改变任何请求。所有的过滤器都实现了filter接口中的doFilter方法来实现对请求的过滤,doFilter方法又使用了FilterChain这个传入的值实现过滤链,来进行层层的过滤。过滤器也同样需要对web.xml进行设置。

Servlet过滤器的适用场合:
A.认证过滤
B.登录和审核过滤
C.图像转换过滤
D.数据压缩过滤
E.加密过滤
F.令牌过滤
G.资源访问触发事件过滤
H. 其他

2009年11月20日星期五

[转]Ajax 应用程序中实现实时数据推送

<<<在IBM devleopwork上发现的一篇好文章,主要是先介绍了AJAX实现实事推送的三种方法:短论询、长轮询和流通道。又主讲了AJAX操作TCP套接字连接的两种方法:基于Adobe Flex 或 OpenLaszlo。这里只截取了三种轮询方法的定义,个人认为是我看到的文章里最清晰简洁的定义,原文地址为:http://www.ibm.com/developerworks/cn/web/wa-aj-socket/?ca=drs-tp4608>>>

短轮询

短轮询也称为高频轮询,就是我在本文开头处介绍的技术。这种方法在以下情况中表现最好:

  1. 有足够的带宽可用。
  2. 根据统计数据,大多数时候,请求都能获得更新。例如,股市数据就总是有可用更新。
  3. 使用 HTTP 1.1 协议。设置 keepAlive=true,因而,同一个套接字连接始终保持活动状态,并可重用。

长轮询

长 轮询是用于更新服务器数据的另外一种方法。这种方法的理念就是客户端建立连接,服务器阻塞连接(通过使请求线程在某些条件下处于等待状态),有数据可用 时,服务器将通过阻塞的连接发送数据,随后关闭连接。客户端在接收到更新后,立即重新建立连接,服务器重复上述过程,以此实现近于实时的通信。然而,长轮 询具有以下缺陷:

  1. 一般的浏览器默认允许每台服务器具有两个连接。在这种情况下,一个连接始终是繁忙状态。因而,UI 只有一个连接(也就是说,能力减半)可用于为用户请求提供服务。这可能会导致某些操作的性能降低。
  2. 仍然需要打开和关闭 HTTP 连接,如果采用的是非持久连接模式(keepAlive=false),那么这种方法的代价可能极高。
  3. 这种方法近于实时,但并非真正的实时。(当然,某些外部因素总是不可控的,比如网络延时,在任何方法中都会存在这些因素。)

流通道

流通道(streaming channel)与长轮询大致相同,差别在于服务器不会关闭响应流。而是特意保持其处于打开状态,使浏览器认为还有更多数据即将到来。但是,流通道也有着自己的缺陷:

  1. 最 大的问题就是数据刷新(flushing)。过去,Web 服务器会缓存响应数据,仅在接受到足够的字节数或块数后才会发送出去。在这种情况下,即便应用程序刷新数据,也仍然会由服务器缓存,以实现优化。更糟的 是,如果在客户端和服务器之间存在代理服务器,那么代理也可能会为自身之便缓存数据。
  2. 如果发现套接字将打开较长的时间,某些浏览器实现可能会自行决定关闭套接字。在这种情况下,通道需要重新建立。

通常,第一个问题可通过为每个流响应附加垃圾有效载荷来解决,使响应数据足以填满缓冲区。第二个问题可通过 “保持活动” 或按固定间隔 “同步” 消息来欺瞒浏览器,使浏览器认为数据是以较慢的速率传入的。

这 些解决方案适用的用例范围狭窄。所有这些方法都已经在 Internet 上的某些解决方案中得到了应用。然而,这些解决方案都遭遇了相同的问题:缺乏可伸缩性。典型情况下,要阻塞一个请求,您需要阻塞处理请求的线程,因为如今 几乎所有应用服务器都会执行阻塞 I/O。即便不是这样,Java™ 2 Platform, Enterprise Edition (J2EE) 也未提供为 HTTP 请求和响应执行非阻塞 I/O 的标准。(Servlets 3.0 API 可解决这一问题,因为这些 API 中包含 Comet Servlet。)

至此,您需要具备非阻塞 I/O(NIO)服务器,客户端应用程序通过它进行连接。由于此类套接字是纯 TCP 二进制套接字,因而将实现以下目标:

  1. 由于服务器端具有 NIO,因而可实现更高的可伸缩性。
  2. 响应缓存的问题不复存在,因为这个套接字直接受应用程序的控制。

基于上述说明,有必要指出这种方法的四个缺点:

  1. 由于使用的是二进制 TCP 套接字,因而应用程序无法真正地利用 HTTPS 层提供的 SSL 安全性。所以,要求数据安全性的应用程序可能需要提供自己的加密工具。
  2. 通常情况下,服务器套接字将在 80 以外的端口上运行,如果防火墙仅允许来自端口 80 的流量,将出现问题。因而,可能需要进行一些端口配置。
  3. Ajax 客户端无法通过后端打开 TCP 套接字连接。
  4. 即便 Ajax 客户端能够执行 open 函数,也无法理解二进制内容,这是因为 Ajax 使用的是 XML 或 JSON(基于文本)格式。

在这篇文章中,我要强调的是如何真正地绕开第三个和第四个问题。如果您能够处理安全性和防火墙问题,那么其他问题也能得到处理。这种做法的获益极为显著。

您可为应用程序实现最大程度的实时服务器推送行为(不考虑网络延时等外部因素),您将获得高度可伸缩的解决方案(以同时连接的客户端数量为准)。




[转]Java Web开发字符编码详解

来源:[url]http://www.wangchao.net.cn/bbsdetail_1757458.html[/url]

一、概要
  在Java应用程序非凡是基于WEB的程序中,经常碰到字符的编码问题。为了防止出现乱码,首先需要了解JAVA是如何处理字符的,这样就可以有目的地在输入/输出环节中增加必要的转码。其次,由于各种服务器有不同的处理方式,还需要多做试验,确保使用中不出现乱码。
二、基本概念
2.1 JAVA中字符的表达
  JAVA中有char、byte、String这几个概念。char 指的是一个UNICODE字符,为16位的整数。byte 是字节,字符串在网络传输或存储前需要转换为byte数组。在从网络接收或从存储设备读取后需要将byte数组转换成String。String是字符串,可以看成是由char组成的数组。String 和 char 为内存形式,byte是网络传输或存储的序列化形式。
举例:

String ying = “英”;
char ying = ying.charAt(0);
String yingHex = Integer.toHexString(ying);
82 F1
byte yingGBBytes = ying.getBytes(“GBK”);
GB编码的字节数值
D3 A2
 

2.2 编码方式的简介
  String序列化成byte数组或反序列化时需要选择正确的编码方式。假如编码方式不正确,就会得到一些0x3F的值。常用的字符编码方式有ISO8859_1、GB2312、GBK、UTF-8/UTF-16/UTF-32。
       ISO8859_1用来编码拉丁文,它由单字节(0-255)组成。
  GB2312、GBK用来编码简体中文,它有单字节和双字节混合组成。最高位为1的字节和下一个字节构成一个汉字,最高位为0的字节是ASCII码。
  UTF-8/UTF-16/UTF-32是国际标准UNICODE的编码方式。 用得最多的是UTF-8,主要是因为它在对拉丁文编码时节约空间。

UNICODE值 UTF-8编码
U-00000000 - U-0000007F: 0xxxxxxx
U-00000080 - U-000007FF: 110xxxxx 10xxxxxx
U-00000800 - U-0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx
U-00010000 - U-001FFFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
U-00200000 - U-03FFFFFF: 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
U-04000000 - U-7FFFFFFF: 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

三、J2SE中相关的函数
String str =”英”;
//取得GB2312编码的字节
byte[] bytesGB2312 = str.getBytes(“GB2312”);

//取得平台缺省编码的字节(solaris为ISO8859_1,windows为GB2312)
byte[] bytesDefault = str.getBytes();

//用指定的编码将字节转换成字符串
String newStrGB = new String(bytesGB2312, “GB2312”);
//用平台缺省的编码将字节转换成字符串(solaris为ISO8859_1,windows为GB2312)
String newStrDefault = new String(bytesDefault);

//用指定的编码从字节流里面读取字符
InputStream in = xxx;
InputStreamReader reader = InputStreamReader( in, “GB2312”);
char aChar = reader.read();

四、jsp、数据库的编码
4.1 JSP中的编码
(1) 静态声明:
CHARSET有两个作用:
JSP文件的编码方式:在读取JSP文件、生成JAVA类时,源JSP文件中汉字的编码
JSP输出流的编码方式:在执行JSP时,往response流里面写入数据的编码方式
(2) 动态改变:在往response流里面写数据前可以调用response.setContentType(),设定正确的编码类型。
(3) 在TOMCAT中,由Request.getParameter() 得到的参数,编码方式都是ISO8859_1。所以假如在浏览器输入框内输入一个汉字“英”,在服务器端就得到一个ISO8859_1编码的(0x00,0xD3,0x00,0xA2)。所以通常在接收参数时转码:
String wrongStr = response.getParameter(“name”);
String correctStr = new String(wrongStr.getBytes(“ISO8859_1”),”GB2312”);
在最新的SERVLET规范里面,也可以在获取参数之前执行如下代码:
request.setCharacterEncoding(“GB2312”);

2009年11月18日星期三

picture


2009年11月16日星期一

[note]常在河边走、哪有不湿鞋。被闭包灭了

如题,今天被一个闭包导致的bug困扰了大概15分钟。代码如下(基于jQuery):

    for (var i=1; i<3; i++) {
        $('label.labelTag:first').clone().text("第"+ (i+1) +"条跑道").click(function() {
                console.log(i);
                Runway.moveToRunway(i);
        }).appendTo(wrapper);
    }

看似天衣无缝,但执行的时候 打印出来的i都是“3”!!!

原因就是闭包作怪,jQuery的click函数是设置DOM元素被点击时的动作,它的参数只有一个函数引用。这里就是

function() {
       console.log(i);
       Runway.moveToRunway(i);
}

这个函数在非闭包语言中肯定不能执行,因为变量i未定义!!!在JS中却可以,因为闭包所以只要i被引用了,它就一直会存在。
说到这里,原因也明白了。i一直在由于i++的原因,一直增长到了3。把代码修改成以下形式就又可以使用了!

    for (var i=1; i<3; i++) {
        $('label.labelTag:first').clone().text("第"+ (i+1) +"条跑道").attr("onClick",
                "Runway.moveToRunway("+ i +");").appendTo(wrapper);
    }

这样就不引用函数了,产生不了闭包!

2009年11月13日星期五

[note][精品]Javascript的prototype属性与new机制

<<<<<<<<<<今天看了一篇博客,把我搞得一头雾水,最后终于理解了,在这里写下自己的一些心得>>>>>>>>>>

Object的prototype属性

w3school是这么解释的:
Prototype
对该对象的对象原型的引用。对于所有的对象,它默认返回 Object 对象的一个实例。
prototype的行为类似于C++中的静态域,
将一个属性添加为prototype的属性,这个属性将被该类型创建的所有实例所共享,但是这 种共享是“只读”(无法修改属性)的。在任何一个实例中只能够用自己的同名属性覆盖这个属性,而不能够改变它。换句话说,对象在读取某个属性时,总是先检查自身域的属性表,如果有这个属性,则会返回这个属性,否则就去读取prototype域,返回protoype域上的属性。另外,JavaScript允许protoype 域引用任何类型的对象,因此,如果对protoype域的读取依然没有找到这个属性,则JavaScript将递归地查找prototype域所指向对象 的prototype域,直到这个对象的prototype域为它本身或者出现循环为止。下面的代码可以证明以上解释。

function Dog(name) {
    this.set=function(name1) {
         this.name = name1;
    }
}
Dog.prototype = {
    name:'zhou',
    shout: function() { console.log("I am " + this.name); }
};
var dog1 = new Dog();
dog1.set('xu');
dog1.shout();
Dog.prototype.shout();

运行结果为:
I am xu
I am zhou

new机制

new是Javascript的一个关键字,用于新建一个对象。如下所示:

function class() {}
var obj = new class();

那么new的内部机制是什么样的呢?new其实可以看成一个函数,代码如下:

function newObj(class, arguments) {
     var o ={};
     if (class && typeof class == 'function') {
              o.prototype = class.prototype;
              o.constructor = class;
              class.apply(o, arguments);
     }
     return o;
}

 可见new所做的事是:
  • 新建一个空对象;
  • 设置空对象的prototype与constructor;
  • 执行构造函数;
  • 返回构造完毕的对象;
例子分析:

代码如下:
function Dog(name) {
this.name = name;
Dog.prototype = {
shout: function() { alert("I am " + this.name); }
};
}
var dog1 = new Dog("Dog 1");
dog1.shout();

看上去OK,但其实有错:“Object doesn’t support this property or method”。原因可以通过new函数的内部机制知道:

     var o ={};
     if (class && typeof Dog== 'function') {
              o.prototype = Dog.prototype;
              o.constructor = Dog;
              o.name = name;
              Dog.prototype = {
                       shout: function() { alert("I am " + this.name); }
              };     
     }
     return o;
 
可以知道新建的对象根本没有shout属性。那么建立一个标准类的方法又是如何呢?

function Dog(name) {
        this.name = name;
}
Dog.prototype = {
       constructor: Dog,
       shout: function() { /* ... */ },
       run: function() { /* ... */ }
};

以上才是一个比较好的建立类的方法。把这个拆成new的机制看看如何呢?代码如下

     Dog.prototype = {
             constructor: Dog,
             shout: function() { /* ... */ },
             run: function() { /* ... */ }
     };
     var o ={};
     if (class && typeof Dog== 'function') {
              o.prototype = Dog.prototype;
              o.constructor = Dog;
              o.name = name;
     }
     return o;

因为Dog.prototype得赋值先执行了,所以这回o虽然没有新的方法,但是o.prototype有,所以同样能执行!

[转]Javascript对象的constructor属性

<<<<<<<<<<<<<<<<<<<注意:constructor属性是对象才有的,不是类所有的属性>>>>>>>>>>>>>>>>>>


constructor属性始终指向创建当前对象的构造函数。比如下面例子:

  1. // 等价于 var foo = new Array(1, 56, 34, 12);  
  2. var arr = [1563412];  
  3. console.log(arr.constructor === Array); // true  
  4. // 等价于 var foo = new Function();  
  5. var Foo = function() { };  
  6. console.log(Foo.constructor === Function); // true  
  7. // 由构造函数实例化一个obj对象  
  8. var obj = new Foo();  
  9. console.log(obj.constructor === Foo); // true  
  10.  
  11. // 将上面两段代码合起来,就得到下面的结论  
  12. console.log(obj.constructor.constructor === Function); // true 

但是当constructor遇到prototype时,有趣的事情就发生了。

我们知道每个函数都有一个默认的属性prototype,而这个prototype的constructor默认指向这个函数。如下例所示:

  1. function Person(name) {  
  2.     this.name = name;  
  3. };  
  4. Person.prototype.getName = function() {  
  5.     return this.name;  
  6. };  
  7. var p = new Person("ZhangSan");  
  8.  
  9. console.log(p.constructor === Person);  // true  
  10. console.log(Person.prototype.constructor === Person); // true  
  11. // 将上两行代码合并就得到如下结果  
  12. console.log(p.constructor.prototype.constructor === Person); // true 

当时当我们重新定义函数的prototype时(注意:和上例的区别,这里不是修改而是覆盖),constructor属性的行为就有点奇怪了,如下示例:

  1. function Person(name) {  
  2.     this.name = name;  
  3. };  
  4. Person.prototype = {  
  5.     getName: function() {  
  6.         return this.name;  
  7.     }  
  8. };  
  9. var p = new Person("ZhangSan");  
  10. console.log(p.constructor === Person);  // false  
  11. console.log(Person.prototype.constructor === Person); // false  
  12. console.log(p.constructor.prototype.constructor === Person); // false 

为什么呢?

原来是因为覆盖Person.prototype时,等价于进行如下代码操作:

  1. Person.prototype = new Object({  
  2.     getName: function() {  
  3.         return this.name;  
  4.     }  
  5. }); 

而constructor属性始终指向创建自身的构造函数,所以此时Person.prototype.constructor === Object,即是:

  1. function Person(name) {  
  2.     this.name = name;  
  3. };  
  4. Person.prototype = {  
  5.     getName: function() {  
  6.         return this.name;  
  7.     }  
  8. };  
  9. var p = new Person("ZhangSan");  
  10. console.log(p.constructor === Object);  // true  
  11. console.log(Person.prototype.constructor === Object); // true  
  12. console.log(p.constructor.prototype.constructor === Object); // true 

怎么修正这种问题呢?方法也很简单,重新覆盖Person.prototype.constructor即可:

function Person(name) {  
    this.name = name;  
};  
Person.prototype = new Object({  
    getName: function() {  
        return this.name;  
    }  
});  
Person.prototype.constructor = Person;  
var p = new Person("ZhangSan");  
console.log(p.constructor === Person);  // true  
console.log(Person.prototype.constructor === Person); // true  
console.log(p.constructor.prototype.constructor === Person); // true