posts - 88, comments - 50, trackbacks - 0, articles - 7
  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

2006年6月9日

   Ajax目前是社区内最热门的话题之一了,最近在我们的项目中用了大量的Ajax,现在把我们的使用方法在这儿写出来,希望大家能指教。

因为要用到Ajax就肯定要用到XMLHttpRequest对象,但由于不同的浏览器版本,相应的生成它的方法也有所不同,所以我们不得不对浏览器的版本进行判断,试想,如果我们要在很多地方都要写那些繁琐的代码会觉的很麻烦,代码的重用也很低,所以我们写一个Ajax的对象。代码如下:

//*********************************************************
// 目的:    AJAX类
// 输入:    无
// 返回:    返回XMLHttp对象
// 例子:    var myConn = new XHConn();
//
//           if (!myConn) alert("XMLHTTP not available. Try a newer/better browser.");
//
//           var fnWhenDone = function (oXML) { alert(oXML.responseText); };
//
//           myConn.connect("mypage.php", "POST", "foo=bar&baz=qux", fnWhenDone);
//
//*********************************************************
function XHConn()
{
  var xmlhttp = false, bComplete = false;
  try
  {
   xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
  }
  catch (e)
  {
   try
   {
    xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
   }
    catch (e)
    {
    try
    {
     xmlhttp = new XMLHttpRequest();
    }
    catch (e)
    {
     xmlhttp = false;
    }
   }
  }
  if (!xmlhttp) return null;
  this.connect = function(sURL, sMethod, sVars, fnDone)
  {
    if (!xmlhttp) return false;
    bComplete = false;
    sVars = (sVars == '') ? Math.random() : sVars + "&" + Math.random();
    sMethod = sMethod.toUpperCase();

    try
    {
      if (sMethod == "GET")
      {
        xmlhttp.open(sMethod, sURL+"?"+sVars, true);
        xmlhttp.setRequestHeader("Content-Type", "text/html;charset=GB2312");
        sVars = "";
      }
      else
      {
        xmlhttp.open(sMethod, sURL, true);
        xmlhttp.setRequestHeader("Method", "POST "+sURL+" HTTP/1.1");
        xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
      }
      xmlhttp.onreadystatechange = function()
      {
        if (xmlhttp.readyState == 4 && !bComplete)
        {
          bComplete = true;
          fnDone(xmlhttp);
        }
      };
     
      xmlhttp.send(sVars);
    }
    catch(z)
    {
     return false;
    }
    return true;
  };
 
  return this;
}


通过这个对象,我们把那些繁琐的代码都封装到里面,这样大大提高了代码的重用性,每次要用Ajax时我们只需要在我们的页面上 new一个XHConn()对象就行了,然后通过调用它的方法connect(sURL, sMethod, sVars, fnDone)就可以和服务器进行异步交互了。

posted @ 2006-06-09 16:50 nbt 阅读(113) | 评论 (0)编辑 收藏

Java虚拟机(JVM)是可运行Java代码的假想计算机。只要根据JVM规格描述将解释器移植到特定的计算机上,就能保证经过编译的任何Java代码能够在该系统上运行。本文首先简要介绍从Java文件的编译到最终执行的过程,随后对JVM规格描述作一说明。

  一.Java源文件的编译、下载、解释和执行
  Java应用程序的开发周期包括编译、下载、解释和执行几个部分。Java编译程序将Java源程序翻译为JVM可执行代码—字节码。这一编译过程同C/C++的编译有些不同。当C编译器编译生成一个对象的代码时,该代码是为在某一特定硬件平台运行而产生的。因此,在编译过程中,编译程序通过查表将所有对符号的引用转换为特定的内存偏移量,以保证程序运行。Java编译器却不将对变量和方法的引用编译为数值引用,也不确定程序执行过程中的内存布局,而是将这些符号引用信息保留在字节码中,由解释器在运行过程中创立内存布局,然后再通过查表来确定一个方法所在的地址。这样就有效的保证了Java的可移植性和安全性。
运行JVM字节码的工作是由解释器来完成的。解释执行过程分三部进行:代码的装入、代码的校验和代码的执行。装入代码的工作由"类装载器"(class loader)完成。类装载器负责装入运行一个程序需要的所有代码,这也包括程序代码中的类所继承的类和被其调用的类。当类装载器装入一个类时,该类被放在自己的名字空间中。除了通过符号引用自己名字空间以外的类,类之间没有其他办法可以影响其他类。在本台计算机上的所有类都在同一地址空间内,而所有从外部引进的类,都有一个自己独立的名字空间。这使得本地类通过共享相同的名字空间获得较高的运行效率,同时又保证它们与从外部引进的类不会相互影响。当装入了运行程序需要的所有类后,解释器便可确定整个可执行程序的内存布局。解释器为符号引用同特定的地址空间建立对应关系及查询表。通过在这一阶段确定代码的内存布局,Java很好地解决了由超类改变而使子类崩溃的问题,同时也防止了代码对地址的非法访问。
随后,被装入的代码由字节码校验器进行检查。校验器可发现操作数栈溢出,非法数据类型转化等多种错误。通过校验后,代码便开始执行了。
  Java字节码的执行有两种方式:
  1.即时编译方式:解释器先将字节码编译成机器码,然后再执行该机器码。
  2.解释执行方式:解释器通过每次解释并执行一小段代码来完成Java字节码程 序的所有操作。
  通常采用的是第二种方法。由于JVM规格描述具有足够的灵活性,这使得将字节码翻译为机器代码的工作具有较高的效率。对于那些对运行速度要求较高的应用程序,解释器可将Java字节码即时编译为机器码,从而很好地保证了Java代码的可移植性和高性能。

  二.JVM规格描述
  JVM的设计目标是提供一个基于抽象规格描述的计算机模型,为解释程序开发人员提很好的灵活性,同时也确保Java代码可在符合该规范的任何系统上运行。JVM对其实现的某些方面给出了具体的定义,特别是对Java可执行代码,即字节码(Bytecode)的格式给出了明确的规格。这一规格包括操作码和操作数的语法和数值、标识符的数值表示方式、以及Java类文件中的Java对象、常量缓冲池在JVM的存储映象。这些定义为JVM解释器开发人员提供了所需的信息和开发环境。Java的设计者希望给开发人员以随心所欲使用Java的自由。
  JVM定义了控制Java代码解释执行和具体实现的五种规格,它们是:
  JVM指令系统
  JVM寄存器
  JVM栈结构
  JVM碎片回收堆
  JVM存储区
  2.1JVM指令系统
JVM指令系统同其他计算机的指令系统极其相似。Java指令也是由 操作码和操作数两部分组成。操作码为8位二进制数,操作数进紧随在操作码的后面,其长度根据需要而不同。操作码用于指定一条指令操作的性质(在这里我们采用汇编符号的形式进行说明),如iload表示从存储器中装入一个整数,anewarray表示为一个新数组分配空间,iand表示两个整数的"与",ret用于流程控制,表示从对某一方法的调用中返回。当长度大于8位时,操作数被分为两个以上字节存放。JVM采用了"big endian"的编码方式来处理这种情况,即高位bits存放在低字节中。这同 Motorola及其他的RISC CPU采用的编码方式是一致的,而与Intel采用的"little endian "的编码方式即低位bits存放在低位字节的方法不同。
  Java指令系统是以Java语言的实现为目的设计的,其中包含了用于调用方法和监视多先程系统的指令。Java的8位操作码的长度使得JVM最多有256种指令,目前已使用了160多种操作码。
  2.2JVM指令系统
所有的CPU均包含用于保存系统状态和处理器所需信息的寄存器组。如果虚拟机定义较多的寄存器,便可以从中得到更多的信息而不必对栈或内存进行访问,这有利于提高运行速度。然而,如果虚拟机中的寄存器比实际CPU的寄存器多,在实现虚拟机时就会占用处理器大量的时间来用常规存储器模拟寄存器,这反而会降低虚拟机的效率。针对这种情况,JVM只设置了4个最为常用的寄存器。它们是:
  pc程序计数器
  optop操作数栈顶指针
  frame当前执行环境指针
  vars指向当前执行环境中第一个局部变量的指针
所有寄存器均为32位。pc用于记录程序的执行。optop,frame和vars用于记录指向Java栈区的指针。
  2.3JVM栈结构
  作为基于栈结构的计算机,Java栈是JVM存储信息的主要方法。当JVM得到一个Java字节码应用程序后,便为该代码中一个类的每一个方法创建一个栈框架,以保存该方法的状态信息。每个栈框架包括以下三类信息:
  局部变量
  执行环境
  操作数栈
局部变量用于存储一个类的方法中所用到的局部变量。vars寄存器指向该变量表中的第一个局部变量。
  执行环境用于保存解释器对Java字节码进行解释过程中所需的信息。它们是:上次调用的方法、局部变量指针和操作数栈的栈顶和栈底指针。执行环境是一个执行一个方法的控制中心。例如:如果解释器要执行iadd(整数加法),首先要从frame寄存器中找到当前执行环境,而后便从执行环境中找到操作数栈,从栈顶弹出两个整数进行加法运算,最后将结果压入栈顶。
操作数栈用于存储运算所需操作数及运算的结果。
  2.4JVM碎片回收堆
  Java类的实例所需的存储空间是在堆上分配的。解释器具体承担为类实例分配空间的工作。解释器在为一个实例分配完存储空间后,便开始记录对该实例所占用的内存区域的使用。一旦对象使用完毕,便将其回收到堆中。
在Java语言中,除了new语句外没有其他方法为一对象申请和释放内存。对内存进行释放和回收的工作是由Java运行系统承担的。这允许Java运行系统的设计者自己决定碎片回收的方法。在SUN公司开发的Java解释器和Hot Java环境中,碎片回收用后台线程的方式来执行。这不但为运行系统提供了良好的性能,而且使程序设计人员摆脱了自己控制内存使用的风险。
  2.5JVM存储区
JVM有两类存储区:常量缓冲池和方法区。常量缓冲池用于存储类名称、方法和字段名称以及串常量。方法区则用于存储Java方法的字节码。对于这两种存储区域具体实现方式在JVM规格中没有明确规定。这使得Java应用程序的存储布局必须在运行过程中确定,依赖于具体平台的实现方式。
  JVM是为Java字节码定义的一种独立于具体平台的规格描述,是Java平台独立性的基础。目前的JVM还存在一些限制和不足,有待于进一步的完善,但无论如何,JVM的思想是成功的。

  对比分析:如果把Java原程序想象成我们的C++原程序,Java原程序编译后生成的字节码就相当于C++原程序编译后的80x86的机器码(二进制程序文件),JVM虚拟机相当于80x86计算机系统,Java解释器相当于80x86CPU。在80x86CPU上运行的是机器码,在Java解释器上运行的是Java字节码。
  Java解释器相当于运行Java字节码的“CPU”,但该“CPU”不是通过硬件实现的,而是用软件实现的。Java解释器实际上就是特定的平台下的一个应用程序。只要实现了特定平台下的解释器程序,Java字节码就能通过解释器程序在该平台下运行,这是Java跨平台的根本。当前,并不是在所有的平台下都有相应Java解释器程序,这也是Java并不能在所有的平台下都能运行的原因,它只能在已实现了Java解释器程序的平台下运行。

posted @ 2006-06-09 16:25 nbt 阅读(62) | 评论 (0)编辑 收藏

跑系统时,难免遇到了数据量大的情况,只好让爱机彻夜工作了,自己闪人。毕竟对它不放心,这时得用到日志,日志里的时间肯定是要的啦。至少得知道他什么时候罢工吧(今天一来,我电脑就在昨天不明时间罢工了!)。下面是转自一位网友的:

Java中一些关于日期、日期格式、日期的解析和日期的计算

Java 语言的Calendar(日历),Date(日期), 和DateFormat(日期格式)组成了Java标准的一个基本但是非常重要的部分. 日期是商业逻辑计算一个关键的部分. 所有的开发者都应该能够计算未来的日期, 定制日期的显示格式, 并将文本数据解析成日期对象. 我们写了两篇文章, 这是第一篇, 我们将大概的学习日期, 日期格式, 日期的解析和日期的计算.

我们将讨论下面的类:

1、具体类(和抽象类相对)java.util.Date
2、抽象类java.text.DateFormat 和它的一个具体子类,java.text.SimpleDateFormat
3、抽象类java.util.Calendar 和它的一个具体子类,java.util.GregorianCalendar

具体类可以被实例化, 但是抽象类却不能. 你首先必须实现抽象类的一个具体子类.

Date 类从Java 开发包(JDK) 1.0 就开始进化, 当时它只包含了几个取得或者设置一个日期数据的各个部分的方法, 比如说月, 日, 和年. 这些方法现在遭到了批评并且已经被转移到了Calendar类里去了, 我们将在本文中进一步讨论它. 这种改进旨在更好的处理日期数据的国际化格式. 就象在JDK 1.1中一样, Date 类实际上只是一个包裹类, 它包含的是一个长整型数据, 表示的是从GMT(格林尼治标准时间)1970年, 1 月 1日00:00:00这一刻之前或者是之后经历的毫秒数.

一、创建一个日期对象

让我们看一个使用系统的当前日期和时间创建一个日期对象并返回一个长整数的简单例子. 这个时间通常被称为Java 虚拟机(JVM)主机环境的系统时间.
//------------------------------------------------------
import java.util.Date;

public class DateExample1
{
public static void main(String[] args)
{
// Get the system date/time
Date date = new Date();

System.out.println(date.getTime());
}
}
//------------------------------------------------------

1145681088396


在星期六, 2001年9月29日, 下午大约是6:50的样子, 上面的例子在系统输出设备上显示的结果是 1001803809710. 在这个例子中,值得注意的是我们使用了Date 构造函数创建一个日期对象, 这个构造函数没有接受任何参数. 而这个构造函数在内部使用了System.currentTimeMillis() 方法来从系统获取日期.

那么, 现在我们已经知道了如何获取从1970年1月1日开始经历的毫秒数了. 我们如何才能以一种用户明白的格式来显示这个日期呢? 在这里类java.text.SimpleDateFormat 和它的抽象基类 java.text.DateFormat 就派得上用场了.

二、日期数据的定制格式

假如我们希望定制日期数据的格式, 比方星期六-9月-29日-2001年. 下面的例子展示了如何完成这个工作:

//------------------------------------------------------
import java.text.SimpleDateFormat;
import java.util.Date;

public class DateExample2
{

public static void main(String[] args)
{

SimpleDateFormat bartDateFormat =
new SimpleDateFormat("EEEE-MMMM-dd-yyyy");

Date date = new Date();

System.out.println(bartDateFormat.format(date));
}
}
//------------------------------------------------------

星期六-四月-22-2006


只要通过向SimpleDateFormat 的构造函数传递格式字符串"EEE-MMMM-dd-yyyy", 我们就能够指明自己想要的格式. 你应该可以看见, 格式字符串中的ASCII 字符告诉格式化函数下面显示日期数据的哪一个部分. EEEE是星期, MMMM是月, dd是日, yyyy是年. 字符的个数决定了日期是如何格式化的.传递"EE-MM-dd-yy"会显示 Sat-09-29-01. 请察看Sun 公司的Web 站点获取日期格式化选项的完整的指示.

三、将文本数据解析成日期对象

假设我们有一个文本字符串包含了一个格式化了的日期对象, 而我们希望解析这个字符串并从文本日期数据创建一个日期对象. 我们将再次以格式化字符串"MM-dd-yyyy" 调用SimpleDateFormat类, 但是这一次, 我们使用格式化解析而不是生成一个文本日期数据. 我们的例子, 显示在下面, 将解析文本字符串"9-29-2001"并创建一个值为001736000000 的日期对象.

//------------------------------------------------------
import java.text.SimpleDateFormat;
import java.util.Date;

public class DateExample3
{

public static void main(String[] args)
{
// Create a date formatter that can parse dates of
// the form MM-dd-yyyy.
SimpleDateFormat bartDateFormat =
new SimpleDateFormat("MM-dd-yyyy");

// Create a string containing a text date to be parsed.
String dateStringToParse = "9-29-2001";

try {
// Parse the text version of the date.
// We have to perform the parse method in a
// try-catch construct in case dateStringToParse
// does not contain a date in the format we are expecting.
Date date = bartDateFormat.parse(dateStringToParse);

// Now send the parsed date as a long value
// to the system output.
System.out.println(date.getTime());
}
catch (Exception ex) {
System.out.println(ex.getMessage());
}
}
}
//------------------------------------------------------

1001692800000



四、使用标准的日期格式化过程

既然我们已经可以生成和解析定制的日期格式了, 让我们来看一看如何使用内建的格式化过程. 方法 DateFormat.getDateTimeInstance() 让我们得以用几种不同的方法获得标准的日期格式化过程. 在下面的例子中, 我们获取了四个内建的日期格式化过程. 它们包括一个短的, 中等的, 长的, 和完整的日期格式.

//------------------------------------------------------
import java.text.DateFormat;
import java.util.Date;

public class DateExample4
{

public static void main(String[] args)
{
Date date = new Date();

DateFormat shortDateFormat =
DateFormat.getDateTimeInstance(
DateFormat.SHORT,
DateFormat.SHORT);

DateFormat mediumDateFormat =
DateFormat.getDateTimeInstance(
DateFormat.MEDIUM,
DateFormat.MEDIUM);

DateFormat longDateFormat =
DateFormat.getDateTimeInstance(
DateFormat.LONG,
DateFormat.LONG);

DateFormat fullDateFormat =
DateFormat.getDateTimeInstance(
DateFormat.FULL,
DateFormat.FULL);

System.out.println(shortDateFormat.format(date));
System.out.println(mediumDateFormat.format(date));
System.out.println(longDateFormat.format(date));
System.out.println(fullDateFormat.format(date));
}
}
//------------------------------------------------------

01-9-29 上午12:00
2001-9-29 0:00:00
2001年9月29日 上午12时00分00秒
2001年9月29日 星期六 上午12时00分00秒 CST


注意我们在对 getDateTimeInstance的每次调用中都传递了两个值. 第一个参数是日期风格, 而第二个参数是时间风格. 它们都是基本数据类型int(整型). 考虑到可读性, 我们使用了DateFormat 类提供的常量: SHORT, MEDIUM, LONG, 和 FULL. 要知道获取时间和日期格式化过程的更多的方法和选项, 请看Sun 公司Web 站点上的解释.

运行我们的例子程序的时候, 它将向标准输出设备输出下面的内容:
9/29/01 8:44 PM
Sep 29, 2001 8:44:45 PM
September 29, 2001 8:44:45 PM EDT
Saturday, September 29, 2001 8:44:45 PM EDT

五、Calendar 类

我们现在已经能够格式化并创建一个日期对象了, 但是我们如何才能设置和获取日期数据的特定部分呢, 比如说小时, 日, 或者分钟? 我们又如何在日期的这些部分加上或者减去值呢? 答案是使用Calendar 类. 就如我们前面提到的那样, Calendar 类中的方法替代了Date 类中被人唾骂的方法.

假设你想要设置, 获取, 和操纵一个日期对象的各个部分, 比方一个月的一天或者是一个星期的一天. 为了演示这个过程, 我们将使用具体的子类 java.util.GregorianCalendar. 考虑下面的例子, 它计算得到下面的第十个星期五是13号.

//------------------------------------------------------
import java.util.GregorianCalendar;
import java.util.Date;
import java.text.DateFormat;

public class DateExample5
{

public static void main(String[] args)
{
DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.FULL);

// Create our Gregorian Calendar.
GregorianCalendar cal = new GregorianCalendar();

// Set the date and time of our calendar
// to the system&s date and time
cal.setTime(new Date());

System.out.println("System Date: " +
dateFormat.format(cal.getTime()));

System.out.println("Befor Setting Day of Week to Friday: " +
dateFormat.format(cal.getTime()));  

// Set the day of week to FRIDAY
cal.set(GregorianCalendar.DAY_OF_WEEK,
GregorianCalendar.FRIDAY);
System.out.println("After Setting Day of Week to Friday: " +
dateFormat.format(cal.getTime()));

int friday13Counter = 0;

while (friday13Counter <= 10)
{

// Go to the next Friday by adding 7 days.
cal.add(GregorianCalendar.DAY_OF_MONTH, 7);

// If the day of month is 13 we have
// another Friday the 13th.
if (cal.get(GregorianCalendar.DAY_OF_MONTH) == 13)
{
friday13Counter++;
System.out.println(dateFormat.format(cal.getTime()));
}
}
}
}
//------------------------------------------------------

    Befor Setting Day of Week to Friday: 2006年4月22日 星期六
After Setting Day of Week to Friday: 2006年4月21日 星期五
2006年10月13日 星期五
2007年4月13日 星期五
2007年7月13日 星期五
2008年6月13日 星期五
2009年2月13日 星期五
2009年3月13日 星期五
2009年11月13日 星期五
2010年8月13日 星期五
2011年5月13日 星期五
2012年1月13日 星期五
2012年4月13日 星期五



在这个例子中我们作了有趣的函数调用:
cal.set(GregorianCalendar.DAY_OF_WEEK,
GregorianCalendar.FRIDAY);

和:
cal.add(GregorianCalendar.DAY_OF_MONTH, 7);

set 方法能够让我们通过简单的设置星期中的哪一天这个域来将我们的时间调整为星期五. 注意到这里我们使用了常量 DAY_OF_WEEK 和 FRIDAY来增强代码的可读性. add 方法让我们能够在日期上加上数值. 润年的所有复杂的计算都由这个方法自动处理.

我们这个例子的输出结果是:
System Date: Saturday, September 29, 2001
当我们将它设置成星期五以后就成了: Friday, September 28, 2001
Friday, September 13, 2002
Friday, December 13, 2002
Friday, June 13, 2003
Friday, February 13, 2004
Friday, August 13, 2004
Friday, May 13, 2005
Friday, January 13, 2006
Friday, October 13, 2006
Friday, April 13, 2007
Friday, July 13, 2007
Friday, June 13, 2008

六、时间掌握在你的手里

有了这些Date 和Calendar 类的例子, 你应该能够使用 java.util.Date, java.text.SimpleDateFormat, 和 java.util.GregorianCalendar 创建许多方法了.

posted @ 2006-06-09 16:00 nbt 阅读(91) | 评论 (1)编辑 收藏

在Java中有许多的容器集合。初一看起来有些糊涂,特别是对刚接触Java来说(至少我当初就是这样的)!其实稍微细心,深入一点点就会发现原来一切都是有规律的。我想别的事情也会是如此。
       Java中的容器,接口都是由一些接口,抽象类及它们的实现类所组成。而它们全部封装在java.util
包中。


1:Collection接口。

       大多数的集合都实现了此接口,它基本方法是add(没有get()方法,实现类中可能有如Arrylist),添加一对象。添加成功则返回true ,否则返回false。这是与Map不同的地方。还有一些常用的方法如iterator(),size(),toArray()(注:toArray()是返回一对象----object数组,而Arrays----也是java.util下的一个类,有一个asList方法它们通常认为是各集合之间转换的桥梁)等等!具体用法可以参考API文档。


2:Map(映射)
       Map接口跟Collection接口实际上没有半点关系。集合中的每一个元素都包含一对键对对象和值对象,集合中没有重复的键对象,值对象可以重复。它的有些实现类能对集合中的键对象进行排序。与Collection截然不同的是,它其中所存取的是一些值与名相对应的数据。也就是一个Key对应一个Value的方式来存储。所以它就有与之对应的一些方法如:put (K key, V value)等等,更多可以参考API文档。

3:List(列表)
      集合中的对象按索引位置排序,可以有重复对象,允许按照对象在集合中的索引位置检索对象

4:Set(集)
      集合中的对象中按特定的方式排序,并且没有重复对象。它的有些实现类能对集合中的对象
按特定的方式排序

5:迭代器:Iterator
       它是一个接口,只有三个方法hasnext(),next(),remove()只有最后一个是可选的,也就是remove()是可选(在实现的时候)。其可选性也意味着它的实现类中,remove方法是可有可无的。例如,若有一个如下的List 实例。
        
    Arrylist al  =   new  Arrylist();
    Object[] ob 
=  al.toArray();
    List list 
=  Arrays.asList(ob);
    Iterator itor 
=  list.iterator();
    itor.remove();      
// Error 

        当调用Ierator itr = list.iterator()方法返回一迭代器的时候,便不支持remove方法,所以当你再使用irt.remove()时程序就是异常!

        使用此迭代器要注意的是remove()方法。它所删除的是指指针(暂这么叫着)上次所移经过的位置(Removes from the underlying collection the last element returned by the iterator (optional operation).)。我个人觉得有点象在JDBC中的ResultSet rs = ....;rs.last();rowsCount=rs.getRow();类似呢。
        前面所讲的,由于clollection提供了iterator()方法,所以迭代器是很容易实现的!

6:常用实现类的一些继承关系:

        Collections,它是Java.util下的一个类。它为我们提供了许多有用的方法,如sort(...),max()等其具体用法可以参考API文档,比如sort(List list);中list内的所有元素都必须实现Comparable接口(All elements in the list must implement the Comparable interface)。

      
Arrylist,它是List接口的实现类,而List则是继承于Collection。
       LinkedList,它也是间接对Colections的实现。用linkedlist的一些方法如addfirst(),removefirst(),addlast()等等可以用来实现如C中的堆栈,链表。(对于频繁使用插入与删除操作使用linkedlist是个不错的选择,对于经常进行索引操作则arrylist较好)。


        HashSet(散列表),它实现了Set接口,也就意味着它的元素不能有重复值出现。并且在HashSet中没有get()方法,但可以通过iterator()来实现。要注意的是假如要在HasSet中存放一些对象,那么你得重定义hashCode()与equals()二个方法来保不可以存放相同的内容的元素。对于hashcode()所返回的值,hashset用它来计算(通过特定的函数)该对象在内存中的存放位置;后者主要用来判断二个对象的内容是否相等而返回对应的boolen型。
       TreeSet,主要用来对元素进行排序操作,假如要往其中添加对象,则对象得实现
Comparable接口。(假如不要对元素排序,则一般可选用HashSet)。

       HashMap,主要特点是存放的一个键值对,一些有用的方法是可返回视图(我觉得可以把它理解为一个集合)如:keyset(),values(),entyset()等。
        TreeMap,它与HashMap差不多,不过是增加了对元素的排序功能,所以运行速度也就当然没有hashmap来得快了。
         以下是HashMap的一个实例(在对DB进行操作的时候很有用):
HashMap valueMap;
//this function just get key-value form DB ,defined by yourself
valueMap = commondb.getElementStringValues("COMMENT_ID""content");
java.util.Set tempkeys 
= valueMap.entrySet();
            java.util.Iterator keys 
= tempkeys.iterator();
while(keys.hasNext())
                
{
                    java.util.Map.Entry me
=(java.util.Map.Entry)keys.next();
String value 
= me.getValue();
int key = me.getKey();
}


       要注意的是entrySet()所返回的每一个元素都是Map.Entry类型的!(Returns a collection view of the mappings contained in this map. Each element in the returned collection is a Map.Entry.)      
 
       Properties,继承于hashtable。这个东东相信我们比较的喜欢了(在i18n,ant中可以是常见得很),呵呵。它可以从外部导入属性文件。文件中的键值都是String类型。just like this:
        
company=study
author
=Jkallen
copyright
=2005-2006
       
         操作如下:
       
import java.util.*;
import java.io.*;
class PropTest
{
    
public static void main(String[] args)
    
{
        
/*Properties pps=System.getProperties();
        pps.list(System.out);
*/

        Properties pps
=new Properties();
        
try
        
{
            pps.load(
new FileInputStream("winsun.ini"));
            Enumeration 
enum=pps.propertyNames();
            
while(enum.hasMoreElements())
            
{
                String strKey
=(String)enum.nextElement();
                String strValue
=pps.getProperty(strKey);
                
                System.out.println(strKey
+"="+strValue);
            }

        }

        
catch(Exception e)
        
{
            e.printStackTrace();
        }

    }

}

        其用法可以查看API文档呢。

        Java中的集合容器确实不少呢...其中有些我们也许一直都用不到,(我也是查看了些相关的资料再加上自己的一些想法整理了一下,希望对相关朋友有用!)可是重要的是知道我们在实现一个功能时应该选用哪种集合类来实现就OK了。

posted @ 2006-06-09 15:57 nbt 阅读(97) | 评论 (0)编辑 收藏

摘要:虽然session机制在web应用程序中被采用已经很长时间了,但是仍然有很多人不清楚session机制的本质,以至不能正确的应用这一技术。本文将详细讨论session的工作机制并且对在Java web application中应用session机制时常见的问题作出解答。

目录:
一、术语session
二、HTTP协议与状态保持
三、理解cookie机制
四、理解session机制
五、理解javax.servlet.http.HttpSession
六、HttpSession常见问题
七、跨应用程序的session共享
八、总结
参考文档

一、术语session
在我的经验里,session这个词被滥用的程度大概仅次于transaction,更加有趣的是transaction与session在某些语境下的含义是相同的。

session,中文经常翻译为会话,其本来的含义是指有始有终的一系列动作/消息,比如打电话时从拿起电话拨号到挂断电话这中间的一系列过程可以称之为一个 session。有时候我们可以看到这样的话“在一个浏览器会话期间,...”,这里的会话一词用的就是其本义,是指从一个浏览器窗口打开到关闭这个期间 ①。最混乱的是“用户(客户端)在一次会话期间”这样一句话,它可能指用户的一系列动作(一般情况下是同某个具体目的相关的一系列动作,比如从登录到选购商品到结账登出这样一个网上购物的过程,有时候也被称为一个transaction),然而有时候也可能仅仅是指一次连接,也有可能是指含义①,其中的差别只能靠上下文来推断②。

然而当session一词与网络协议相关联时,它又往往隐含了“面向连接”和/或“保持状态”这样两个含义, “面向连接”指的是在通信双方在通信之前要先建立一个通信的渠道,比如打电话,直到对方接了电话通信才能开始,与此相对的是写信,在你把信发出去的时候你并不能确认对方的地址是否正确,通信渠道不一定能建立,但对发信人来说,通信已经开始了。“保持状态”则是指通信的一方能够把一系列的消息关联起来,使得消息之间可以互相依赖,比如一个服务员能够认出再次光临的老顾客并且记得上次这个顾客还欠店里一块钱。这一类的例子有“一个TCP session”或者 “一个POP3 session”③。

而到了web服务器蓬勃发展的时代,session在web开发语境下的语义又有了新的扩展,它的含义是指一类用来在客户端与服务器之间保持状态的解决方案④。有时候session也用来指这种解决方案的存储结构,如“把xxx保存在session 里”⑤。由于各种用于web开发的语言在一定程度上都提供了对这种解决方案的支持,所以在某种特定语言的语境下,session也被用来指代该语言的解决方案,比如经常把Java里提供的javax.servlet.http.HttpSession简称为session⑥。

鉴于这种混乱已不可改变,本文中session一词的运用也会根据上下文有不同的含义,请大家注意分辨。
在本文中,使用中文“浏览器会话期间”来表达含义①,使用“session机制”来表达含义④,使用“session”表达含义⑤,使用具体的“HttpSession”来表达含义⑥

二、HTTP协议与状态保持
HTTP 协议本身是无状态的,这与HTTP协议本来的目的是相符的,客户端只需要简单的向服务器请求下载某些文件,无论是客户端还是服务器都没有必要纪录彼此过去的行为,每一次请求之间都是独立的,好比一个顾客和一个自动售货机或者一个普通的(非会员制)大卖场之间的关系一样。

然而聪明(或者贪心?)的人们很快发现如果能够提供一些按需生成的动态信息会使web变得更加有用,就像给有线电视加上点播功能一样。这种需求一方面迫使HTML逐步添加了表单、脚本、DOM等客户端行为,另一方面在服务器端则出现了CGI规范以响应客户端的动态请求,作为传输载体的HTTP协议也添加了文件上载、 cookie这些特性。其中cookie的作用就是为了解决HTTP协议无状态的缺陷所作出的努力。至于后来出现的session机制则是又一种在客户端与服务器之间保持状态的解决方案。

让我们用几个例子来描述一下cookie和session机制之间的区别与联系。笔者曾经常去的一家咖啡店有喝5杯咖啡免费赠一杯咖啡的优惠,然而一次性消费5杯咖啡的机会微乎其微,这时就需要某种方式来纪录某位顾客的消费数量。想象一下其实也无外乎下面的几种方案:
1、该店的店员很厉害,能记住每位顾客的消费数量,只要顾客一走进咖啡店,店员就知道该怎么对待了。这种做法就是协议本身支持状态。
2、发给顾客一张卡片,上面记录着消费的数量,一般还有个有效期限。每次消费时,如果顾客出示这张卡片,则此次消费就会与以前或以后的消费相联系起来。这种做法就是在客户端保持状态。
3、发给顾客一张会员卡,除了卡号之外什么信息也不纪录,每次消费时,如果顾客出示该卡片,则店员在店里的纪录本上找到这个卡号对应的纪录添加一些消费信息。这种做法就是在服务器端保持状态。

由于HTTP协议是无状态的,而出于种种考虑也不希望使之成为有状态的,因此,后面两种方案就成为现实的选择。具体来说cookie机制采用的是在客户端保持状态的方案,而session机制采用的是在服务器端保持状态的方案。同时我们也看到,由于采用服务器端保持状态的方案在客户端也需要保存一个标识,所以session机制可能需要借助于cookie机制来达到保存标识的目的,但实际上它还有其他选择。

三、理解cookie机制 
cookie机制的基本原理就如上面的例子一样简单,但是还有几个问题需要解决:“会员卡”如何分发;“会员卡”的内容;以及客户如何使用“会员卡”。

正统的cookie分发是通过扩展HTTP协议来实现的,服务器通过在HTTP的响应头中加上一行特殊的指示以提示浏览器按照指示生成相应的cookie。然而纯粹的客户端脚本如JavaScript或者VBScript也可以生成cookie。

而cookie 的使用是由浏览器按照一定的原则在后台自动发送给服务器的。浏览器检查所有存储的cookie,如果某个cookie所声明的作用范围大于等于将要请求的资源所在的位置,则把该cookie附在请求资源的HTTP请求头上发送给服务器。意思是麦当劳的会员卡只能在麦当劳的店里出示,如果某家分店还发行了自己的会员卡,那么进这家店的时候除了要出示麦当劳的会员卡,还要出示这家店的会员卡。

cookie的内容主要包括:名字,值,过期时间,路径和域。
其中域可以指定某一个域比如.google.com,相当于总店招牌,比如宝洁公司,也可以指定一个域下的具体某台机器比如www.google.com或者froogle.google.com,可以用飘柔来做比。
路径就是跟在域名后面的URL路径,比如/或者/foo等等,可以用某飘柔专柜做比。
路径与域合在一起就构成了cookie的作用范围。
如果不设置过期时间,则表示这个cookie的生命期为浏览器会话期间,只要关闭浏览器窗口,cookie就消失了。这种生命期为浏览器会话期的 cookie被称为会话cookie。会话cookie一般不存储在硬盘上而是保存在内存里,当然这种行为并不是规范规定的。如果设置了过期时间,浏览器就会把cookie保存到硬盘上,关闭后再次打开浏览器,这些cookie仍然有效直到超过设定的过期时间。

存储在硬盘上的cookie 可以在不同的浏览器进程间共享,比如两个IE窗口。而对于保存在内存里的cookie,不同的浏览器有不同的处理方式。对于IE,在一个打开的窗口上按 Ctrl-N(或者从文件菜单)打开的窗口可以与原窗口共享,而使用其他方式新开的IE进程则不能共享已经打开的窗口的内存cookie;对于 Mozilla Firefox0.8,所有的进程和标签页都可以共享同样的cookie。一般来说是用javascript的window.open打开的窗口会与原窗口共享内存cookie。浏览器对于会话cookie的这种只认cookie不认人的处理方式经常给采用session机制的web应用程序开发者造成很大的困扰。

下面就是一个goolge设置cookie的响应头的例子
HTTP/1.1 302 Found
Location: http://www.google.com/intl/zh-CN/
Set-Cookie: PREF=ID=0565f77e132de138:NW=1:TM=1098082649:LM=1098082649:S=KaeaCFPo49RiA_d8; expires=Sun, 17-Jan-2038 19:14:07 GMT; path=/; domain=.google.com
Content-Type: text/html




这是使用HTTPLook这个HTTP Sniffer软件来俘获的HTTP通讯纪录的一部分




浏览器在再次访问goolge的资源时自动向外发送cookie




使用Firefox可以很容易的观察现有的cookie的值
使用HTTPLook配合Firefox可以很容易的理解cookie的工作原理。




IE也可以设置在接受cookie前询问




这是一个询问接受cookie的对话框。

四、理解session机制
session机制是一种服务器端的机制,服务器使用一种类似于散列表的结构(也可能就是使用散列表)来保存信息。

当程序需要为某个客户端的请求创建一个session的时候,服务器首先检查这个客户端的请求里是否已包含了一个session标识 - 称为 session id,如果已包含一个session id则说明以前已经为此客户端创建过session,服务器就按照session id把这个 session检索出来使用(如果检索不到,可能会新建一个),如果客户端请求不包含session id,则为此客户端创建一个session并且生成一个与此session相关联的session id,session id的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串,这个 session id将被在本次响应中返回给客户端保存。

保存这个session id的方式可以采用cookie,这样在交互过程中浏览器可以自动的按照规则把这个标识发挥给服务器。一般这个cookie的名字都是类似于SEEESIONID,而。比如weblogic对于web应用程序生成的cookie,JSESSIONID= ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764,它的名字就是 JSESSIONID。

由于cookie可以被人为的禁止,必须有其他机制以便在cookie被禁止时仍然能够把session id传递回服务器。经常被使用的一种技术叫做URL重写,就是把session id直接附加在URL路径的后面,附加方式也有两种,一种是作为URL路径的附加信息,表现形式为http://...../xxx;jsessionid= ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764
另一种是作为查询字符串附加在URL后面,表现形式为http://...../xxx?jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764
这两种方式对于用户来说是没有区别的,只是服务器在解析的时候处理的方式不同,采用第一种方式也有利于把session id的信息和正常程序参数区分开来。
为了在整个交互过程中始终保持状态,就必须在每个客户端可能请求的路径后面都包含这个session id。

另一种技术叫做表单隐藏字段。就是服务器会自动修改表单,添加一个隐藏字段,以便在表单提交时能够把session id传递回服务器。比如下面的表单
<form name="testform" action="/xxx">
<input type="text">
</form>
在被传递给客户端之前将被改写成
<form name="testform" action="/xxx">
<input type="hidden" name="jsessionid" value="ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764">
<input type="text">
</form>
这种技术现在已较少应用,笔者接触过的很古老的iPlanet6(SunONE应用服务器的前身)就使用了这种技术。
实际上这种技术可以简单的用对action应用URL重写来代替。

在谈论session机制的时候,常常听到这样一种误解“只要关闭浏览器,session就消失了”。其实可以想象一下会员卡的例子,除非顾客主动对店家提出销卡,否则店家绝对不会轻易删除顾客的资料。对session来说也是一样的,除非程序通知服务器删除一个session,否则服务器会一直保留,程序一般都是在用户做log off的时候发个指令去删除session。然而浏览器从来不会主动在关闭之前通知服务器它将要关闭,因此服务器根本不会有机会知道浏览器已经关闭,之所以会有这种错觉,是大部分session机制都使用会话cookie来保存session id,而关闭浏览器后这个 session id就消失了,再次连接服务器时也就无法找到原来的session。如果服务器设置的cookie被保存到硬盘上,或者使用某种手段改写浏览器发出的HTTP请求头,把原来的session id发送给服务器,则再次打开浏览器仍然能够找到原来的session。

恰恰是由于关闭浏览器不会导致session被删除,迫使服务器为seesion设置了一个失效时间,当距离客户端上一次使用session的时间超过这个失效时间时,服务器就可以认为客户端已经停止了活动,才会把session删除以节省存储空间。

五、理解javax.servlet.http.HttpSession
HttpSession是Java平台对session机制的实现规范,因为它仅仅是个接口,具体到每个web应用服务器的提供商,除了对规范支持之外,仍然会有一些规范里没有规定的细微差异。这里我们以BEA的Weblogic Server8.1作为例子来演示。

首先,Weblogic Server提供了一系列的参数来控制它的HttpSession的实现,包括使用cookie的开关选项,使用URL重写的开关选项,session持久化的设置,session失效时间的设置,以及针对cookie的各种设置,比如设置cookie的名字、路径、域, cookie的生存时间等。

一般情况下,session都是存储在内存里,当服务器进程被停止或者重启的时候,内存里的session也会被清空,如果设置了session的持久化特性,服务器就会把session保存到硬盘上,当服务器进程重新启动或这些信息将能够被再次使用, Weblogic Server支持的持久性方式包括文件、数据库、客户端cookie保存和复制。

复制严格说来不算持久化保存,因为session实际上还是保存在内存里,不过同样的信息被复制到各个cluster内的服务器进程中,这样即使某个服务器进程停止工作也仍然可以从其他进程中取得session。

cookie生存时间的设置则会影响浏览器生成的cookie是否是一个会话cookie。默认是使用会话cookie。有兴趣的可以用它来试验我们在第四节里提到的那个误解。

cookie的路径对于web应用程序来说是一个非常重要的选项,Weblogic Server对这个选项的默认处理方式使得它与其他服务器有明显的区别。后面我们会专题讨论。

关于session的设置参考[5] http://e-docs.bea.com/wls/docs70/webapp/weblogic_xml.html#1036869

六、HttpSession常见问题
(在本小节中session的含义为⑤和⑥的混合)


1、session在何时被创建
一个常见的误解是以为session在有客户端访问时就被创建,然而事实是直到某server端程序调用 HttpServletRequest.getSession(true)这样的语句时才被创建,注意如果JSP没有显示的使用 <% @page session="false"%> 关闭session,则JSP文件在编译成Servlet时将会自动加上这样一条语句 HttpSession session = HttpServletRequest.getSession(true);这也是JSP中隐含的 session对象的来历。

由于session会消耗内存资源,因此,如果不打算使用session,应该在所有的JSP中关闭它。

2、session何时被删除
综合前面的讨论,session在下列情况下被删除a.程序调用HttpSession.invalidate();或b.距离上一次收到客户端发送的session id时间间隔超过了session的超时设置;或c.服务器进程被停止(非持久session)

3、如何做到在浏览器关闭时删除session
严格的讲,做不到这一点。可以做一点努力的办法是在所有的客户端页面里使用javascript代码window.oncolose来监视浏览器的关闭动作,然后向服务器发送一个请求来删除session。但是对于浏览器崩溃或者强行杀死进程这些非常规手段仍然无能为力。

4、有个HttpSessionListener是怎么回事
你可以创建这样的listener去监控session的创建和销毁事件,使得在发生这样的事件时你可以做一些相应的工作。注意是session的创建和销毁动作触发listener,而不是相反。类似的与HttpSession有关的listener还有 HttpSessionBindingListener,HttpSessionActivationListener和 HttpSessionAttributeListener。

5、存放在session中的对象必须是可序列化的吗
不是必需的。要求对象可序列化只是为了session能够在集群中被复制或者能够持久保存或者在必要时server能够暂时把session交换出内存。在 Weblogic Server的session中放置一个不可序列化的对象在控制台上会收到一个警告。我所用过的某个iPlanet版本如果 session中有不可序列化的对象,在session销毁时会有一个Exception,很奇怪。

6、如何才能正确的应付客户端禁止cookie的可能性
对所有的URL使用URL重写,包括超链接,form的action,和重定向的URL,具体做法参见[6]
http://e-docs.bea.com/wls/docs70/webapp/sessions.html#100770

7、开两个浏览器窗口访问应用程序会使用同一个session还是不同的session
参见第三小节对cookie的讨论,对session来说是只认id不认人,因此不同的浏览器,不同的窗口打开方式以及不同的cookie存储方式都会对这个问题的答案有影响。

8、如何防止用户打开两个浏览器窗口操作导致的session混乱
这个问题与防止表单多次提交是类似的,可以通过设置客户端的令牌来解决。就是在服务器每次生成一个不同的id返回给客户端,同时保存在session里,客户端提交表单时必须把这个id也返回服务器,程序首先比较返回的id与保存在session里的值是否一致,如果不一致则说明本次操作已经被提交过了。可以参看《J2EE核心模式》关于表示层模式的部分。需要注意的是对于使用javascript window.open打开的窗口,一般不设置这个id,或者使用单独的id,以防主窗口无法操作,建议不要再window.open打开的窗口里做修改操作,这样就可以不用设置。

9、为什么在Weblogic Server中改变session的值后要重新调用一次session.setValue
做这个动作主要是为了在集群环境中提示Weblogic Server session中的值发生了改变,需要向其他服务器进程复制新的session值。

10、为什么session不见了
排除session正常失效的因素之外,服务器本身的可能性应该是微乎其微的,虽然笔者在iPlanet6SP1加若干补丁的Solaris版本上倒也遇到过;浏览器插件的可能性次之,笔者也遇到过3721插件造成的问题;理论上防火墙或者代理服务器在cookie处理上也有可能会出现问题。
出现这一问题的大部分原因都是程序的错误,最常见的就是在一个应用程序中去访问另外一个应用程序。我们在下一节讨论这个问题。

七、跨应用程序的session共享

常常有这样的情况,一个大项目被分割成若干小项目开发,为了能够互不干扰,要求每个小项目作为一个单独的web应用程序开发,可是到了最后突然发现某几个小项目之间需要共享一些信息,或者想使用session来实现SSO(single sign on),在session中保存login的用户信息,最自然的要求是应用程序间能够访问彼此的session。

然而按照Servlet规范,session的作用范围应该仅仅限于当前应用程序下,不同的应用程序之间是不能够互相访问对方的session的。各个应用服务器从实际效果上都遵守了这一规范,但是实现的细节却可能各有不同,因此解决跨应用程序session共享的方法也各不相同。

首先来看一下Tomcat是如何实现web应用程序之间session的隔离的,从 Tomcat设置的cookie路径来看,它对不同的应用程序设置的cookie路径是不同的,这样不同的应用程序所用的session id是不同的,因此即使在同一个浏览器窗口里访问不同的应用程序,发送给服务器的session id也可以是不同的。


  

根据这个特性,我们可以推测Tomcat中session的内存结构大致如下。




笔者以前用过的iPlanet也采用的是同样的方式,估计SunONE与iPlanet之间不会有太大的差别。对于这种方式的服务器,解决的思路很简单,实际实行起来也不难。要么让所有的应用程序共享一个session id,要么让应用程序能够获得其他应用程序的session id。

iPlanet中有一种很简单的方法来实现共享一个session id,那就是把各个应用程序的cookie路径都设为/(实际上应该是/NASApp,对于应用程序来讲它的作用相当于根)。
<session-info>
<path>/NASApp</path>
</session-info>

需要注意的是,操作共享的session应该遵循一些编程约定,比如在session attribute名字的前面加上应用程序的前缀,使得 setAttribute("name", "neo")变成setAttribute("app1.name", "neo"),以防止命名空间冲突,导致互相覆盖。


在Tomcat中则没有这么方便的选择。在Tomcat版本3上,我们还可以有一些手段来共享session。对于版本4以上的Tomcat,目前笔者尚未发现简单的办法。只能借助于第三方的力量,比如使用文件、数据库、JMS或者客户端cookie,URL参数或者隐藏字段等手段。

我们再看一下Weblogic Server是如何处理session的。


  

从截屏画面上可以看到Weblogic Server对所有的应用程序设置的cookie的路径都是/,这是不是意味着在Weblogic Server中默认的就可以共享session了呢?然而一个小实验即可证明即使不同的应用程序使用的是同一个session,各个应用程序仍然只能访问自己所设置的那些属性。这说明Weblogic Server中的session的内存结构可能如下




对于这样一种结构,在 session机制本身上来解决session共享的问题应该是不可能的了。除了借助于第三方的力量,比如使用文件、数据库、JMS或者客户端 cookie,URL参数或者隐藏字段等手段,还有一种较为方便的做法,就是把一个应用程序的session放到ServletContext中,这样另外一个应用程序就可以从ServletContext中取得前一个应用程序的引用。示例代码如下,

应用程序A
context.setAttribute("appA", session); 

应用程序B
contextA = context.getContext("/appA");
HttpSession sessionA = (HttpSession)contextA.getAttribute("appA"); 

值得注意的是这种用法不可移植,因为根据ServletContext的JavaDoc,应用服务器可以处于安全的原因对于context.getContext("/appA");返回空值,以上做法在Weblogic Server 8.1中通过。

那么Weblogic Server为什么要把所有的应用程序的cookie路径都设为/呢?原来是为了SSO,凡是共享这个session的应用程序都可以共享认证的信息。一个简单的实验就可以证明这一点,修改首先登录的那个应用程序的描述符weblogic.xml,把cookie路径修改为/appA 访问另外一个应用程序会重新要求登录,即使是反过来,先访问cookie路径为/的应用程序,再访问修改过路径的这个,虽然不再提示登录,但是登录的用户信息也会丢失。注意做这个实验时认证方式应该使用FORM,因为浏览器和web服务器对basic认证方式有其他的处理方式,第二次请求的认证不是通过 session来实现的。具体请参看[7] secion 14.8 Authorization,你可以修改所附的示例程序来做这些试验。

八、总结
session机制本身并不复杂,然而其实现和配置上的灵活性却使得具体情况复杂多变。这也要求我们不能把仅仅某一次的经验或者某一个浏览器,服务器的经验当作普遍适用的经验,而是始终需要具体情况具体分析。
摘要:虽然session机制在web应用程序中被采用已经很长时间了,但是仍然有很多人不清楚session机制的本质,以至不能正确的应用这一技术。本文将详细讨论session的工作机制并且对在Java web application中应用session机制时常见的问题作出解答。

posted @ 2006-06-09 15:54 nbt 阅读(126) | 评论 (0)编辑 收藏

本文的预定读者首先要对j2ee有所了解,熟悉xml,tomcat等基本内容,本文主要是简单介绍一下web服务的基本内容,怎样在java web开发中构建SOAP服务:
 一、SOAP(Simple Object Access Protocol)简单对象访问协议,要了解SOAP,首先就需要了解分布式计算的由来,随着下一代的分布式计算体系web服务的出现,SOAP成为了创建和调用通过网络发布的应用程序的实际通信标准。SOAP类似传统的二进制协议IIOP(CORBA)和JRMP(RMI),但它不采用二进制数据表示法,而是采用使用XML的,基于文本的数据表示法。
        通过XML表示法,SOAP定义了一种小型有线连接协议和编码格式,以表示数据类型、编程语言和数据库,还可以使用各种Internet标准协议作为其消息传输工具,还可以提供表示RPC和文档驱动的消息交换等通信模型的约定。请注意,W3C正致力于SOAP的研究,http://www.w3c.org/2000/xp/Group/ ,并得到了主流供应商的积极响应,以便对于基于XML的协议相关的重要任务达成共识,并定义其关键要求和使用场景。
        SOAP1.2的基本规范定义了以下基本内容:
       1)用于将XML文档表示为结构化SOAP消息的语法和语义
       2)在SOAP消息中表示数据的编码标准
       3)用于交换SOAP消息的通信模型
       4)SOAP传输等底层协议的绑定
      SOAP消息主要包括了信封头,消息头,主体,附件几部分
      一个简单的SOAP消息表示:
      POST   /StudentInfo   HTTP/1.1
      Host:anthropology.cun.edu
      Content-Type: text/xml;charset="utf-8"
      Content-Length: 640
      SOAPAction:  "GetStudentInfo"

     <SOAP-ENV:Envelop xmlns:SOAP-ENV="http://www.w3c.org/2001/06/soap-envelope"
              xmlns:xsi="http://www.w3c.org/2001/XMLSchema-instance"
              xmlns:xsd="http://www.w3c.org/2001/XMLSchema"
             SOAP-ENV:encodingStyle="http://www.w3c.org/2001/06/soap-encoding">
        <SOAP-ENV:Header>
            <person:mail xmlns:person="http://www.cun.edu/Header">xyz@cun.edu
        </SOAP-ENV:Header> 

        <SOAP-ENV:Body>
            <m:GetStudentInfo   xmlns:m="http://www.cun.edu/jws.student.studentInfo">
                 <student_name xsi:type='xsd:string'>
                        Wang wen yin
                 </student>
             </m:GetStudentInfo>
        </SOAP-ENV:Body>
    </SOAP-ENV:Envelop>
   以上是1.2版本命名空间,1.1的命名空间 SOAP ENVELOPE:http://schemas.xmlsoap.org/soap/envelop/ ,SOAP ENCODING: http://schemas.xmlsoap.org/soap/encoding/ 
   关于SOAP编码规范请参阅www.w3c.org/TR/xmlschema-2/ 定义的编码值,其他的一些规范可以上www.w3c.org 上具体查看。
二、以下从实际例子来学习,这里我使用的是Apache的一个子项目Axis的具体例子,便于深入了解soap的运行:
    1)下载Axis的相关内容http://ws.apache.org/axis/
    2)建立一个实例程序(遵守j2ee的web程序规范),如(WebServiceTest目录)
          把axis中lib文件夹的内容拷到你的WebServiceTest/WEB-INF/lib下,同时上网下载xerces(下载地点:http://xml.apache.org/xerces-j/)解释器的包文件xerces.jar,也拷到WebServiceTest/WEB-INF/lib文件夹下,(若要配置log4j,请把属性文件log4j.properties拷到WebServiceTest/WEB-INF/classes文件夹下)
    3)修改应用程序WebServiceTest/WEB-INF中的web.xml文件:主要servlet设置如下
       <servlet>
              <servlet-name>TestServlet</servlet-name>
              <servlet-class>org.apache.axis.transport.http.AxisServlet</servlet-class>
      </servlet>
      <servlet-mapping>
              <servlet-name>TestServlet</servlet-name>
              <url-pattern>*.jws</url-pattern>
     </servlet-mapping>
     <servlet-mapping>
              <servlet-name>TestServlet</servlet-name>
              <url-pattern>/servlet/TestServlet</url-pattern>
    </servlet-mapping>
  
    <servlet-mapping>
              <servlet-name>TestServlet</servlet-name>
              <url-pattern>/services/*</url-pattern>
     </servlet-mapping>


     <servlet>
          <servlet-name>AdminServlet</servlet-name>
          <servlet-class>
               org.apache.axis.transport.http.AdminServlet
          </servlet-class>
          <load-on-startup>100</load-on-startup>
     </servlet>
 
     <servlet-mapping>
          <servlet-name>AdminServlet</servlet-name>
          <url-pattern>/servlet/AdminServlet</url-pattern>
     </servlet-mapping>
 
     <mime-mapping>
          <extension>wsdl</extension>
          <mime-type>text/xml</mime-type>
     </mime-mapping>
 
     <mime-mapping>
          <extension>xsd</extension>
          <mime-type>text/xml</mime-type>
     </mime-mapping>
    你现在可以在网址里输入http://localhost/WebServiceTest/servlet/TestServlet 看到了吗?Axis是使用axis.jar包里的org.apache.axis.transport.http.AxisServlet对应用程序进行处理的,基本配置就讲到这里。
三、接着我们来说Axis中的内核。
1)不使用Tomcat引擎运行Axis。
       先建立一个脚步文件,对环境变量classpath进行设置要把lib下的那些包文件的路径全都包括进去,运行:java  org.apache.axis.transport.http.SimpleAxisServer  <port>
 2)内部服务处理程序是org.apache.axis.providers.java.RPCProvider,标志出服务所需的方法,然后提供从SOAP请求消息组成部分的参数。
 3)Axis的应用程序端管理功能:
       java  org.apache.axis.client.AdminClient 就会列出参数,可供你选择。我们的例子是:java  org.apache.axis.client.AdminClient  -l http://localhost/WebserviceTest/servlet/TestServlet  list 就会显示出服务列表,返回的是xml文件
4)wsdl2java应用程序可以把wsdl文件创建基于java的程序,如占位程序等
     java  org.apache.axis.wsdl.WSDL2java  <url>
Axis的基本内容说到这里
四、具体例子
 1)编写逻辑程序,简单如:SoapTest.java
    public class SoapTest{
        public String getStr(String name){
            return "Hello,"+name;
        }
    }
  2) 部署服务,编写wsdd文件SoapTest_deploy.wsdd:
     <deployment name="SimapleTest" xmlns="http://xml.apache.org/axis/wsdd/"
         xmlns:java="http://xml.apache.org/axis/wsdd/providers/java"
         xmlns:xsd="http://www.w3.org/2000/10/XMLSchema"
         xmlns:xsi="http://www.w3.org/2000/10/XMLSchema-instance">
             <service name="SoapTest" provider="java:RPC">
                  <parameter name="className" value="SoapTest"/>
                  <parameter name="allowedMethods" value="getStr"/>
             </service>
    </deployment>
    其中className参数是你的想部署的类名(全名),allowedMethods是调用的服务的方法,如果有多个方法的话可以用空格分开(如: <parameter name="allowedMethods" value="getStr  getMoney"/>),当用*的时候表示全部。
     好了现在准备部署了,确保环境路径classpath设置正确,运行:
        java  org.apache.axis.client.AdminClient  -l  http://localhost/WebserviceTest/servlet/TestServlet  SoapTest_deploy.wsdd
     (这里不懂的话,请参考以上的说明) 
 ok,呵呵,至此,我们已经完成了一个web服务的部署:测试http://localhost/WebServiceTest/servlet/TestServlet  看里面是否多了一个选择SoapTest服务?
      如果不想要服务了那重新编写一个wsdd文件,内容改为:
<deployment name="SimapleTest" xmlns="http://xml.apache.org/axis/wsdd/"
         xmlns:java="http://xml.apache.org/axis/wsdd/providers/java"
         xmlns:xsd="http://www.w3.org/2000/10/XMLSchema"
         xmlns:xsi="http://www.w3.org/2000/10/XMLSchema-instance">
            <service name="SoapTest"/>
</deployment>
和上面一样,对比一下就ok了。
五、客户端测试:
     客户端我们也可以使用java来进行测试,网上也有资料的,你可以去学习,很简单的。现在为了体现web服务的魅力,我用.NET平台来测试吧,客户端使用c#编写(先要安装.net framework sdk):
    1)通过wsdl生成web服务代理,在net平台下运行:
      wsdl  /l:CS  /protocol:SOAP  /out:SoapTestClient.cs  http://localhost/WebserviceTest/services/SoapTest?wsdl  
        我们通过wsdl得到了一个cs文件SoapTestClient.cs(当前目录),你可以打开cs文件,研究一下里面的代码,那个getStr(string name)就是我们需要调用的方法,我们的客户端通过调用该方法就可以调用服务器端的方法,内部的转化wsdl.exe工具已经帮我们完成了,axis下的WSDL2Java工具也是一样的功能,可以参考我上面所说的关于Axis的内核内容
   2)编译cs文件成程序集dll:
      csc /target:library /r:System.Web.Services.dll  /r:System.Xml.dll SoapTestClient.cs
      最后我们等到了一个dll文件SoapTestClient.dll,客户端程序通过调用它就行了
   3)编写客户端应用程序SoapTestClientApp.cs
      using System;
      
      namespache  jws.client{
         public class SoapTestClientApp{
            public SoapTestClientApp(){
            }
            public static void Main(string[] args){
                if(args.Length!=1){
                    Console.WriteLine("Usage:SoapTestClientApp <name>");
                    Environment.Exit(1);
                }
                SoapTestService  st_service=new SoapTestService();
                st_service.getStr("Wang wenyin");
           }
        } 
     }
4)编译文件csc  /r:SoapTestClient.dll  SoapTestClientApp.cs
    运行SoapTestClientApp
输出结果:
     Hello,Wang wenyin
与预期结果相符。

posted @ 2006-06-09 15:51 nbt 阅读(84) | 评论 (0)编辑 收藏

Hibernate的JNDI名称绑定是在net.sf.hibernate.impl.SessionFactoryObjectFactory程序里面实现的,我来分析一下Hibernate的绑定JNDI的过程:

我们获得SessionFactory一般是这样写代码:

Configuration conf = new Configuration().addClass(Cat.class);
SessionFactory sf = conf.buildSessionFactory();

首先是new Configuration()创建一个Configuration,在这个构造器里面进行配置文件(hibernate.properties)的读取工作,然后保存到一个Properties对象里面去,和JNDI相关的是这个属性:

hibernate.session_factory_name hibernate/session_factory

接着调用buildSessionFactory()方法,该方法检查一下配置信息,然后调用SessionFactoryImpl的一个构造器。在构造器里面注意下面两行代码:

name = properties.getProperty(Environment.SESSION_FACTORY_NAME);
SessionFactoryObjectFactory.addInstance(uuid, name, this, properties);

调用了SessionFactoryObjectFactory的addInstance方法,并且把自身(SessionFactory的实例)作为参数传递。最后在addInstance方法可以看到如下代码:

Context ctx = NamingHelper.getInitialContext(properties);
NamingHelper.bind(ctx, name, instance);

instance 就是SessionFactory的实例,通过读源代码,可以清楚的看到Hibernate是在conf.buildSessionFactory()的时候通过一系列类方法调用,把创建的SessionFactory实例绑定到配置文件(hibernate.properties)中 hibernate.session_factory_name属性指定的名称上的,因此可见Hibernate自身是具有JNDI的动态绑定功能的。但是Hibernate需要获得一个SessionFactory实例用于绑定,而这个SessionFactory实例需要我们写代码进行预先创建,并且必须保证该过程要在所有其它要从JNDI上获得SessionFactory实例的程序之前完成。

因此对于任何App Server来说,我们都不必去管JNDI名称的绑定过程,只需要保证预先创建一个SessionFactory实例出来就够了,剩下的工作 Hibernate会做的。那么如何确保预创建SessionFactory实例呢,如果是Servlet,可以配置一个初始化的Servlet,只要把

Configuration conf = new Configuration().addClass(Cat.class);
SessionFactory sf = conf.buildSessionFactory();

这样的代码加进去就可以了。如果是包含EJB的的复杂的J2EE应用,可能需要依靠App Server的功能来保证预创建SessionFactory实例。

posted @ 2006-06-09 15:49 nbt 阅读(90) | 评论 (0)编辑 收藏

abstract class和interface是Java语言中对于抽象类定义进行支持的两种机制,正是由于这两种机制的存在,才赋予了Java强大的面向对象能力。abstract class和interface之间在对于抽象类定义的支持方面具有很大的相似性,甚至可以相互替换,因此很多开发者在进行抽象类定义时对于abstract class和interface的选择显得比较随意。其实,两者之间还是有很大的区别的,对于它们的选择甚至反映出对于问题领域本质的理解、对于设计意图的理解是否正确、合理。本文将对它们之间的区别进行一番剖析,试图给开发者提供一个在二者之间进行选择的依据。 

理解抽象类 

abstract class和interface在Java语言中都是用来进行抽象类(本文中的抽象类并非从abstract class翻译而来,它表示的是一个抽象体,而abstract class为Java语言中用于定义抽象类的一种方法,请读者注意区分)定义的,那么什么是抽象类,使用抽象类能为我们带来什么好处呢? 

在面向对象的概念中,我们知道所有的对象都是通过类来描绘的,但是反过来却不是这样。并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。抽象类往往用来表征我们在对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。比如:如果我们进行一个图形编辑软件的开发,就会发现问题领域存在着圆、三角形这样一些具体概念,它们是不同的,但是它们又都属于形状这样一个概念,形状这个概念在问题领域是不存在的,它就是一个抽象概念。正是因为抽象的概念在问题领域没有对应的具体概念,所以用以表征抽象概念的抽象类是不能够实例化的。 

在面向对象领域,抽象类主要用来进行类型隐藏。我们可以构造出一个固定的一组行为的抽象描述,但是这组行为却能够有任意个可能的具体实现方式。这个抽象描述就是抽象类,而这一组任意个可能的具体实现则表现为所有可能的派生类。模块可以操作一个抽象体。由于模块依赖于一个固定的抽象体,因此它可以是不允许修改的;同时,通过从这个抽象体派生,也可扩展此模块的行为功能。熟悉OCP的读者一定知道,为了能够实现面向对象设计的一个最核心的原则OCP(Open-Closed Principle),抽象类是其中的关键所在。 


从语法定义层面看abstract class和interface 

在语法层面,Java语言对于abstract class和interface给出了不同的定义方式,下面以定义一个名为Demo的抽象类为例来说明这种不同。 

使用abstract class的方式定义Demo抽象类的方式如下: 

abstract class Demo { 
 abstract void method1(); 
 abstract void method2(); 
 … 
} 

使用interface的方式定义Demo抽象类的方式如下: 

interface Demo { 
 void method1(); 
 void method2(); 
 … 


在abstract class方式中,Demo可以有自己的数据成员,也可以有非abstarct的成员方法,而在interface方式的实现中,Demo只能够有静态的不能被修改的数据成员(也就是必须是static final的,不过在interface中一般不定义数据成员),所有的成员方法都是abstract的。从某种意义上说,interface是一种特殊形式的abstract class。 

      从编程的角度来看,abstract class和interface都可以用来实现"design by contract"的思想。但是在具体的使用上面还是有一些区别的。 

首先,abstract class在Java语言中表示的是一种继承关系,一个类只能使用一次继承关系。但是,一个类却可以实现多个interface。也许,这是Java语言的设计者在考虑Java对于多重继承的支持方面的一种折中考虑吧。 

其次,在abstract class的定义中,我们可以赋予方法的默认行为。但是在interface的定义中,方法却不能拥有默认行为,为了绕过这个限制,必须使用委托,但是这会 增加一些复杂性,有时会造成很大的麻烦。 

在抽象类中不能定义默认行为还存在另一个比较严重的问题,那就是可能会造成维护上的麻烦。因为如果后来想修改类的界面(一般通过abstract class或者interface来表示)以适应新的情况(比如,添加新的方法或者给已用的方法中添加新的参数)时,就会非常的麻烦,可能要花费很多的时间(对于派生类很多的情况,尤为如此)。但是如果界面是通过abstract class来实现的,那么可能就只需要修改定义在abstract class中的默认行为就可以了。 

同样,如果不能在抽象类中定义默认行为,就会导致同样的方法实现出现在该抽象类的每一个派生类中,违反了"one rule,one place"原则,造成代码重复,同样不利于以后的维护。因此,在abstract class和interface间进行选择时要非常的小心。 


从设计理念层面看abstract class和interface 

上面主要从语法定义和编程的角度论述了abstract class和interface的区别,这些层面的区别是比较低层次的、非本质的。本小节将从另一个层面:abstract class和interface所反映出的设计理念,来分析一下二者的区别。作者认为,从这个层面进行分析才能理解二者概念的本质所在。 

前面已经提到过,abstarct class在Java语言中体现了一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在"is a"关系,即父类和派生类在概念本质上应该是相同的(参考文献〔3〕中有关于"is a"关系的大篇幅深入的论述,有兴趣的读者可以参考)。对于interface 来说则不然,并不要求interface的实现者和interface定义在概念本质上是一致的,仅仅是实现了interface定义的契约而已。为了使论述便于理解,下面将通过一个简单的实例进行说明。 

考虑这样一个例子,假设在我们的问题领域中有一个关于Door的抽象概念,该Door具有执行两个动作open和close,此时我们可以通过abstract class或者interface来定义一个表示该抽象概念的类型,定义方式分别如下所示: 

使用abstract class方式定义Door: 

abstract class Door { 
 abstract void open(); 
 abstract void close(); 


  
使用interface方式定义Door: 


interface Door { 
 void open(); 
 void close(); 


  
其他具体的Door类型可以extends使用abstract class方式定义的Door或者implements使用interface方式定义的Door。看起来好像使用abstract class和interface没有大的区别。 

如果现在要求Door还要具有报警的功能。我们该如何设计针对该例子的类结构呢(在本例中,主要是为了展示abstract class和interface反映在设计理念上的区别,其他方面无关的问题都做了简化或者忽略)?下面将罗列出可能的解决方案,并从设计理念层面对这些不同的方案进行分析。 

解决方案一: 

简单的在Door的定义中增加一个alarm方法,如下: 

abstract class Door { 
 abstract void open(); 
 abstract void close(); 
 abstract void alarm(); 


  
或者 

interface Door { 
 void open(); 
 void close(); 
 void alarm(); 


  
那么具有报警功能的AlarmDoor的定义方式如下: 

class AlarmDoor extends Door { 
 void open() { … } 
 void close() { … } 
 void alarm() { … } 


  
或者 

class AlarmDoor implements Door { 
 void open() { … } 
 void close() { … } 
 void alarm() { … } 
} 

这种方法违反了面向对象设计中的一个核心原则ISP(Interface Segregation Priciple),在Door的定义中把Door概念本身固有的行为方法和另外一个概念"报警器"的行为方法混在了一起。这样引起的一个问题是那些仅仅依赖于Door这个概念的模块会因为"报警器"这个概念的改变(比如:修改alarm方法的参数)而改变,反之依然。 

解决方案二: 

既然open、close和alarm属于两个不同的概念,根据ISP原则应该把它们分别定义在代表这两个概念的抽象类中。定义方式有:这两个概念都使用abstract class方式定义;两个概念都使用interface方式定义;一个概念使用abstract class方式定义,另一个概念使用interface方式定义。 

显然,由于Java语言不支持多重继承,所以两个概念都使用abstract class方式定义是不可行的。后面两种方式都是可行的,但是对于它们的选择却反映出对于问题领域中的概念本质的理解、对于设计意图的反映是否正确、合理。我们一一来分析、说明。 

如果两个概念都使用interface方式来定义,那么就反映出两个问题:1、我们可能没有理解清楚问题领域,AlarmDoor在概念本质上到底是Door还是报警器?2、如果我们对于问题领域的理解没有问题,比如:我们通过对于问题领域的分析发现AlarmDoor在概念本质上和Door是一致的,那么我们在实现时就没有能够正确的揭示我们的设计意图,因为在这两个概念的定义上(均使用interface方式定义)反映不出上述含义。 

如果我们对于问题领域的理解是:AlarmDoor在概念本质上是Door,同时它有具有报警的功能。我们该如何来设计、实现来明确的反映出我们的意思呢?前面已经说过,abstract class在Java语言中表示一种继承关系,而继承关系在本质上是"is a"关系。所以对于Door这个概念,我们应该使用abstarct class方式来定义。另外,AlarmDoor又具有报警功能,说明它又能够完成报警概念中定义的行为,所以报警概念可以通过interface方式定义。如下所示: 

abstract class Door { 
 abstract void open(); 
 abstract void close(); 

interface Alarm { 
 void alarm(); 

class AlarmDoor extends Door implements Alarm { 
 void open() { … } 
 void close() { … } 
    void alarm() { … } 


  
这种实现方式基本上能够明确的反映出我们对于问题领域的理解,正确的揭示我们的设计意图。其实abstract class表示的是"is a"关系,interface表示的是"like a"关系,大家在选择时可以作为一个依据,当然这是建立在对问题领域的理解上的,比如:如果我们认为AlarmDoor在概念本质上是报警器,同时又具有Door的功能,那么上述的定义方式就要反过来了。 



结论 

abstract class和interface是Java语言中的两种定义抽象类的方式,它们之间有很大的相似性。但是对于它们的选择却又往往反映出对于问题领域中的概念本质的理解、对于设计意图的反映是否正确、合理,因为它们表现了概念间的不同的关系(虽然都能够实现需求的功能)。这其实也是语言的一种的惯用法,希望读者朋友能够细细体会。

posted @ 2006-06-09 15:42 nbt 阅读(58) | 评论 (0)编辑 收藏

Aspect Oriented Programming(AOP),面向切面编程,是一个比较热门的话题。AOP主要实现的目的是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。比如我们最常见的就是日志记录了,举个例子,我们现在提供一个服务查询学生信息的,但是我们希望记录有谁进行了这个查询。如果按照传统的OOP的实现的话,那我们实现了一个查询学生信息的服务接口(StudentInfoService)和其实现类(StudentInfoServiceImpl.java),同时为了要进行记录的话,那我们在实现类(StudentInfoServiceImpl.java)中要添加其实现记录的过程。这样的话,假如我们要实现的服务有多个呢?那就要在每个实现的类都添加这些记录过程。这样做的话就会有点繁琐,而且每个实现类都与记录服务日志的行为紧耦合,违反了面向对象的规则。那么怎样才能把记录服务的行为与业务处理过程中分离出来呢?看起来好像就是查询学生的服务自己在进行,但是背后日志记录对这些行为进行记录,但是查询学生的服务不知道存在这些记录过程,这就是我们要讨论AOP的目的所在。AOP的编程,好像就是把我们在某个方面的功能提出来与一批对象进行隔离,这样与一批对象之间降低了耦合性,可以就某个功能进行编程。
    我们直接从代码入手吧,要实现以上的目标,我们可以使用一个动态代理类(Proxy),通过拦截一个对象的行为并添加我们需要的功能来完成。Java中的java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口为我们实现动态代理类提供了一个方案,但是该方案针对的对象要实现某些接口;如果针对的目的是类的话,cglib为我们提供了另外一个实现方案。等下会说明两者的区别。
一、接口的实现方案:
1)首先编写我们的业务接口(StudentInfoService.java):
public interface StudentInfoService{
 void findInfo(String studentName);
}
     及其实现类(StudentInfoServiceImpl.java):
public class StudentInfoServiceImpl implements StudentInfoService{
 public void findInfo(String name){
  System.out.println("你目前输入的名字是:"+name);
 }
}
2)现在我们需要一个日志功能,在findInfo行为之前执行并记录其行为,那么我们就首先要拦截该行为。在实际执行的过程中用一个代理类来替我们完成。Java中为我们提供了实现动态代理类的方案:

1'处理拦截目的的类(MyHandler.java)
import org.apache.log4j.Logger;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.lang.reflect.Method;

public class MyHandler implements InvocationHandler{
 private Object proxyObj;
 private static Logger log=Logger.getLogger(MyHandler.class);
 
 public Object bind(Object obj){
  this.proxyObj=obj;
  return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),this);
 }
 
 public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{
  Object result=null;
  try{
   //请在这里插入代码,在方法前调用
   log.info("调用log日志方法"+method.getName());
   result=method.invoke(proxyObj,args); //原方法
   //请在这里插入代码,方法后调用
  }catch(Exception e){
   e.printStackTrace();
  }
  return result;
 }
}
2'我们实现一个工厂,为了方便我们使用该拦截类(AOPFactory.java):
public class AOPFactory{
 private static Object getClassInstance(String clzName){
  Object obj=null;
  try{
   Class cls=Class.forName(clzName);
   obj=(Object)cls.newInstance();
  }catch(ClassNotFoundException cnfe){
   System.out.println("ClassNotFoundException:"+cnfe.getMessage());
  }catch(Exception e){
   e.printStackTrace();
  }
  return obj;
 }
 
 public static Object getAOPProxyedObject(String clzName){
  Object proxy=null;
  MyHandler handler=new MyHandler();
  Object obj=getClassInstance(clzName);
  if(obj!=null) {
   proxy=handler.bind(obj);
  }else{
   System.out.println("Can't get the proxyobj");
   //throw
  }
  return proxy;
 }
}

3)基本的拦截与其工厂我们都实现了,现在测试(ClientTest.java):
public class ClientTest{
 public static void main(String[] args){
  StudentInfoService studentInfo=(StudentInfoService)AOPFactory.getAOPProxyedObject("StudentInfoServiceImpl");
  studentInfo.findInfo("阿飞");
 }
}
输出结果(看你的log4j设置):
[INFO]调用log日志方法findInfo
你目前输入的名字是:阿飞
     这样我们需要的效果就出来了,业务处理自己在进行,但是我们实现了日志功能,而业务处理(StudentInfoService)根本不知道存在该行为的。但是Java中提供的动态代理类的实现是针对实现了某些接口的类,如果没有实现接口的话,不能创建代理类,看以上部分:
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),this);
看到了没有?obj.getClass().getInterfaces()要求实现了某些接口。以下提供哪些没有实现接口的实现方案:

二、子类的实现方案。
      首先,请上网下CGLib的包,http://sourceforge.net/project/showfiles.php?group_id=56933 。设置好classpath路径,CGLib与java标准库提供的实现方案不同,cglib主要是基于实现类(如StudentInfoServiceImpl.java)扩展一个子类来实现。与Dynamic Proxy中的Proxy和InvocationHandler相对应,net.sf.cglib.proxy.Enhancer和MethodInterceptor在CGLib中负责完成代理对象创建和方法截获处理,产生的是目标类的子类而不是通过接口来实现方法拦截的,Enhancer主要是用于构造动态代理子类来实现拦截,MethodInterceptor(扩展了Callback接口)主要用于实现around advice(AOP中的概念):
     1)我们的业务处理(StudentInfoServiceImpl.java):
public class StudentInfoServiceImpl{
 public void findInfo(String name){
  System.out.println("你目前输入的名字是:"+name);
 }
}
     2)实行一个工具来处理日志功能(AOPInstrumenter.java):
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
import org.apache.log4j.Logger;

public class AOPInstrumenter implements MethodInterceptor{
 private Logger log=Logger.getLogger(AOPInstrumenter.class);
 private Enhancer enhancer=new Enhancer();
 
 public Object getInstrumentedClass(Class clz){
  enhancer.setSuperclass(clz);
  enhancer.setCallback(this);
  return enhancer.create();
 }
 
 public Object intercept(Object o,Method method,Object[] args,MethodProxy proxy) throws Throwable{
  log.info("调用日志方法"+method.getName());
  Object result=proxy.invokeSuper(o,args);
  return result;
 }
 
}
     3)我们来测试一下(AOPTest.java):
public class AOPTest{
 public static void main(String[] args){
  AOPInstrumenter instrumenter=new AOPInstrumenter();
  StudentInfoServiceImpl studentInfo=(StudentInfoServiceImpl)instrumenter.getInstrumentedClass(StudentInfoServiceImpl.class);
  studentInfo.findInfo("阿飞");
 }
}
   输出结果与以上相同。
 CGLib中为实现以上目的,主要提供的类
1)Enhancer:setCallback(Callback) ,setSuperclass(Class) ,create()返回动态子类Object
2)MethodInterceptor必须实现的接口:intercept(Object,Method,Object[],MethodProxy)返回的是原方法调用的结果。和Proxy原理一样。

三、以上的两个简单实现AOP的方案都为你准备好了,你可以自己编写测试一下,以下简单介绍一下AOP的基本概念:
1)aspect(切面):实现了cross-cutting功能,是针对切面的模块。最常见的是logging模块,这样,程序按功能被分为好几层,如果按传统的继承的话,商业模型继承日志模块的话根本没有什么意义,而通过创建一个logging切面就可以使用AOP来实现相同的功能了。
2)jointpoint(连接点):连