一个计算机专业学生几年的编程经验汇总 (该系列一共 11 篇,看完之后,java 基础绝对有不小的提升!)

Java 杂谈(一)  

想来学习 Java 也有两个年头了,永远不敢说多么精通,但也想谈谈自己的感受,写给 软件学院的同仁们,帮助大家在技术的道路上少一点弯路。说得伟大一点是希望大家为软件 学院争气,其实主要的还是大家自身的进步提升。  
1. 关于动态加载机制  
    学习 Java 比 C++更容易理解 OOP 的思想,毕竟 C++还混合了不少面向过程的成分。 很多人都能背出来 Java 语言的特点,所谓的动态加载机制等等。当然概念往往是先记住而 后消化的,可有多少人真正去体会过动态加载的机制,试图去寻找过其中的细节呢? 提供大 家一个方法:在命令行窗口运行 Java 程序的时候,加上这个很有用的参数:      java -verbose *.class     这样会清晰的打印出被加载的类文件,大部分是 jdk 自身运行需要的,后几行会明 显的看到自己用到的那几个类文件被加载进来的顺序。即使你声明了一个类对象,不实例化 也不会加载,说明只有真正用到那个类的实例即对象的时候,才会执行加载。这样是不是大 家稍微能明白一点动态加载了呢?^_^  
2. 关于寻找 class 文件原理  
    建议大家在入门的时候在命令行窗口编译和运行,不要借助 JCreator 或者 Eclipse 等 IDE 去帮助做那些事情。尝试自己这样做:  
    javac -classpath yourpath *.java     java -classpath yourpath *.class  
    也许很多人都能看懂,设置 classpath 的目的就是告诉编译器去哪里寻找你的 class 文件. 不过至少笔者今日才弄懂 JVM 去查询类的原理,编译器加载类要依靠 classloader, 而 classloader 有 3 个级别,从高到低分别是 BootClassLoader(名字可能不准确) , ExtClassLoader, AppClassLoader。  
    这 3 个加载器分别对应着编译器去寻找类文件的优先级别和不同的路径: BootClassLoader 对应 jre/classes 路径,是编译器优先寻找 class 的地方。 ExtClassLoader 对应 jre/lib/ext 路径,是编译器次优先寻找 class 的地方。 AppClassLoader 对应当前路径,所以也是编译器默认找 class 的地方。  
    其实大家可以自己写个程序简单的测试,对任何 class,例如 A,  调用 new A().getClass().getClassLoader().toString() 打印出来就可以看到,把 class 文件放在不同的路径下再次执行,就会看到区别。特别注意的是如果打印出来是 null 就表 示到了高级 BootClassLoader, 因为它是 C++编写的,不存在 Java 对应的类加载器的
名字。  
    寻找的顺序是一种向上迂回的思想,即如果本级别找不到,就只能去本级别之上的找, 不会向下寻找。不过似乎从 Jdk1.4 到 Jdk1.6 这一特点又有改变,没有找到详细资料。所 以就不举例子了。告诉大家设计这种体系的是 Sun 公司曾经的技术核心宫力先生,一个纯 种华人哦!^_^  
    这样希望大家不至于迷惑为什么总报错找不到类文件,不管是自己写的还是导入的第三 方的 jar 文件(J2ee 中经常需要导入的)。  
3. 关于 jdk 和 jre      大家肯定在安装 JDK 的时候会有选择是否安装单独的 jre,一般都会一起安装,我也建 议大家这样做。因为这样更能帮助大家弄清楚它们的区别:  
    Jre 是 java runtime environment, 是 java 程序的运行环境。既然是运行,当然要 包含 jvm,也就是大家熟悉的虚拟机啦, 还有所有 java 类库的 class 文件,都在 lib 目录 下打包成了 jar。大家可以自己验证。至于在 windows 上的虚拟机是哪个文件呢? 学过 MFC 的都知道什么是 dll 文件吧,那么大家看看 jre/bin/client 里面是不是有一个 jvm.dll 呢?那就是虚拟机。  
    Jdk 是 java development kit,是 java 的开发工具包,里面包含了各种类库和工具。 当然也包括了另外一个 Jre. 那么为什么要包括另外一个 Jre 呢?而且 jdk/jre/bin 同时有 client 和 server 两个文件夹下都包含一个 jvm.dll。 说明是有两个虚拟机的。这一点不知 道大家是否注意到了呢?  
    相信大家都知道 jdk 的 bin 下有各种 java 程序需要用到的命令,与 jre 的 bin 目录 明显的区别就是 jdk 下才有 javac,这一点很好理解,因为 jre 只是一个运行环境而已。 与开发无关,正因为如此,具备开发功能的 jdk 自己的 jre 下才会同时有 client 性质的 jvm 和 server 性质的 jvm, 而仅仅作为运行环境的 jre 下只需要 client 性质的 jvm.dll 就够 了。  
    记得在环境变量 path 中设置 jdk/bin 路径麽?这应该是大家学习 Java 的第一步吧, 老师会告诉大家不设置的话 javac 和 java 是用不了的。确实 jdk/bin 目录下包含了所有的 命令。可是有没有人想过我们用的 java 命令并不是 jdk/bin 目录下的而是 jre/bin 目录下 的呢?不信可以做一个实验,大家可以把 jdk/bin 目录下的 java.exe 剪切到别的地方再运 行 java 程序,发现了什么?一切 OK!  
    那么有人会问了?我明明没有设置 jre/bin 目录到环境变量中啊?  
    试想一下如果 java 为了提供给大多数人使用,他们是不需要 jdk 做开发的,只需要 jre 能让 java 程序跑起来就可以了,那么每个客户还需要手动去设置环境变量多麻烦啊?所以 安装 jre 的时候安装程序自动帮你把 jre 的 java.exe 添加到了系统变量中,验证的方法很 简单,大家看到了系统环境变量的 path 前面有
“%SystemRoot%\system32;%SystemRoot%;” 这样的配置,那么再去 Windows/system32 下面去看看吧,发现了什么?有一个 java.exe。  
    如果强行能够把 jdk/bin 挪到 system32 变量前面,当然也可以迫使使用 jdk/jre 里 面的 java,不过除非有必要,我不建议大家这么做。使用单独的 jre 跑 java 程序也算是客 户环境下的一种测试。  
    这下大家应该更清楚 jdk 和 jre 内部的一些联系和区别了吧?  

PS: 其实还有满多感想可以总结的,一次写多了怕大家扔砖头砸死我,怪我太罗唆。大家 应该更加踏实更加务实的去做一些研究并互相分享心得,大方向和太前沿的技术讨论是必要 的但好不要太多,毕竟自己基础都还没打好,什么都讲新版本其实是进步的一大障碍!  

 

Java 杂谈(二)   
鉴于上回写的一点感想大家不嫌弃,都鼓励小弟继续写下去,好不容易等到国庆黄金周, 实习总算有一个休息的阶段,于是这就开始写第二篇了。希望这次写的仍然对志同道合的朋 友们有所帮助。上回讲了 Java 动态加载机制、classLoader 原理和关于 jdk 和 jre 三个问题。 这次延续着讲一些具体的类库。  
1. 关于集合框架类  
    相信学过 Java 的各位对这个名词并不陌生,对 java.util.*这个 package 肯定也不陌生。 不知道大家查询 API 的时候怎么去审视或者分析其中的一个 package,每个包重要的两 个部分就是 interfaces 和 classes,接口代表了它能做什么,实现类则代表了它如何去做。 关注实现类之前,我们应该先理解清楚它的来源接口,不管在 j2se 还是 j2ee 中,都应该是 这样。那么我们先看这三个接口:List、Set、Map。  
    也许有些人不太熟悉这三个名字,但相信大部分人都熟悉 ArrayList,LinkedList, TreeSet,HashSet,HashMap, Hashtable 等实现类的名字。它们的区别也是满容易理解 的,List 放可以重复的对象集合,Set 放不可重复的对象组合,而 Map 则放 <Key,Value > 这 样的名值对, Key 不可重复,Value 可以。这里有几个容易混淆的问题:  
    到底 Vector 和 ArrayList,Hashtable 和 HashMap 有什么区别?  
    很多面试官喜欢问这个问题,其实更专业一点应该这样问:新集合框架和旧集合框架有 哪些区别?新集合框架大家可以在这些包中找 since jdk1.2 的,之前的如 vector 和 Hashtable 都是旧的集合框架包括的类。那么区别是?  
     a. 新集合框架的命名更加科学合理。例如 List 下的 ArrayList 和 LinkedList      b. 新集合框架下全部都是非线程安全的。  
建议去 jdk 里面包含的源代码里面自己去亲自看看 vector 和 ArrayList 的区别吧。当然 如果是 jdk5.0 之后的会比较难看一点,因为又加入了泛型的语法,类似 c++的 template 语 法。  
     那么大家是否想过为什么要从旧集合框架默认全部加锁防止多线程访问更新到新集合 框架全部取消锁,默认方式支持多线程?(当然需要的时候可以使用 collections 的静态方法 加锁达到线程安全)  
    笔者的观点是任何技术的发展都未必是遵循它们的初衷的,很多重大改变是受到客观环 境的影响的。大家知道 Java 的初衷是为什么而开发的么?是为嵌入式程序开发的。记得上 一篇讲到 classLoader 机制么?那正是为了节约嵌入式开发环境下内存而设计的。而走到今 天,Java 成了人们心中为互联网诞生的语言。互联网意味着什么?多线程是必然的趋势。 客观环境在变,Java 技术也随着飞速发展,导致越来越脱离它的初衷。据说 Sun 公司其实 主打的是 J2se,结果又是由于客观环境影响,J2se 几乎遗忘,留在大家谈论焦点的一直是 j2ee。  
    技术的细节这里就不多说了,只有用了才能真正理解。解释这些正是为了帮助大家理解 正在学的和将要学的任何技术。之后讲 j2ee 的时候还会再讨论。  
    多扯句题外话:几十年前的 IT 巨人是 IBM,Mainframe 市场无人可比。微软如何打败 IBM?正是由于硬件飞速发展,对个人 PC 的需求这个客观环境,让微软通过 OS 称为了第 二个巨人。下一个打败微软的呢?Google。如何做到的?如果微软并不和 IBM 争大型机, Google 借着互联网飞速发展这个客观环境作为决定性因素,避开跟微软争 OS,而是走搜 索引擎这条路,称为第 3 个巨人。那么第 4 个巨人是谁呢?很多专家预言将在亚洲或者中 国出现, Whatever,客观环境变化趋势才是决定大方向的关键。当然笔者也希望会出现在 中国,^_^~~  
2. 关于 Java 设计模式  
    身边的很多在看 GOF 的 23 种设计模式,似乎学习它无论在学校还是在职场,都成了 一种流行风气。我不想列举解释这 23 种 Design Pattern, 我写这些的初衷一直都是谈自己 的经历和看法,希望能帮助大家理解。  
    首先我觉得设计模式只是对一类问题的一种通用解决办法,只要是面向对象的编程预言 都可以用得上这 23 种。理解它们好的方法就是亲自去写每一种,哪怕是一个简单的应用 就足够了。如果代码实现也记不住的话,记忆它们对应的 UML 图会是一个比较好的办法, 当然前提是必须了解 UML。  
    同时好能利用 Java 自身的类库帮助记忆,例如比较常用的观察者模式,在 java.util.* 有现成的 Observer 接口和 Observable 这个实现类,看看源代码相信就足够理解观察者模 式了。再比如装饰器模式,大家只要写几个关于 java.io.*的程序就可以完全理解什么是装饰 器模式了。有很多人觉得刚入门的时候不该接触设计模式,比如图灵设计丛书系列很出名的 那本《Java 设计模式》,作者: Steven John Metsker,大部分例子老实说令现在的我也很迷 惑。但我仍然不同意入门跟学习设计模式有任何冲突,只是我们需要知道每种模式的概念的
和典型的应用,这样我们在第一次编写 FileOutputStream、BufferedReader、PrintWriter 的时候就能感觉到原来设计模式离我们如此之近,而且并不是多么神秘的东西。  
    另外,在学习某些模式的同时,反而更能帮助我们理解 java 类库的某些特点。例如当 你编写原型(Prototype)模式的时候,你必须了解的是 java.lang.Cloneable 这个接口和所有 类的基类 Object 的 clone()这个方法。即深 copy 和浅 copy 的区别:  
    Object.clone()默认实现的是浅 copy,也就是复制一份对象拷贝,但如果对象包含其他 对象的引用,不会复制引用,所以原对象和拷贝共用那个引用的对象。  
    深 copy 当然就是包括对象的引用都一起复制啦。这样原对象和拷贝对象,都分别拥有 一份引用对象。如果要实现深 copy 就必须首先实现 java.lang.Cloneable 接口,然后重写 clone()方法。因为在 Object 中的 clone()方法是 protected 签名的,而 Cloneable 接口的作 用就是把 protected 放大到 public,这样 clone()才能被重写。  
    那么又有个问题了?如果引用的对象又引用了其他对象呢?这样一直判断并复制下去, 是不是显得很麻烦?曾经有位前辈告诉我的方法是重写 clone 方法的时候直接把原对象序 列化到磁盘上再反序列化回来,这样不用判断就可以得到一个深 copy 的结果。如果大家不 了解序列化的作法建议看一看 ObjectOutputStream 和 ObjectInputStream。  

    归根结底,模式只是思想上的东西,把它当成前人总结的经验其实一点都不为过。鼓励 大家动手自己去写,例如代理模式,可以简单的写一个 Child 类, Adult 类。Child 要买任 何东西由Adult来代理实现。简单来说就是Adult里的buy()内部实际调用的是Child的buy(), 可是暴露在 main 函数的却是 Adult.buy()。这样一个简单的程序就足够理解代理模式的基本 含义了。

   

Java 杂谈(三)   

这已经笔者写的第三篇 Java 杂记了,庆幸前两篇一直得到论坛朋友们的支持鼓励,还 望大家继续指正不足之处。笔者也一直渴望通过这样方式清醒的自审,来寻找自己技术上的 不足之处,希望和共同爱好 Java 的同仁们一起提高。  
    前两次分别讲述了关于 jvm、jdk、jre、collection、classLoader 和一些 Design Pattern 的自我理解。这次仍然不准备开始过渡到 j2ee 中,因为觉得还有一些琐碎的 j2se 的问题没 有总结完毕。  
1. 关于 Object 类理解  
    大家都知道 Object 是所有 Java 类的基类, 意味着所有的 Java 类都会继承了 Object 的 11 个方法。建议大家去看看 Object 的 11 个成员函数的源代码,就会知道默认的实现方 式。比如 equals 方法,默认实现就是用"=="来比较,即直接比较内存地址,返回 true 或者 false。而 toString()方法,返回的串组成方式是: 
        "getClass().getName() + "@" + Integer.toHexString(hashCode())"  
    其实不用我过多的解释,大家都能看懂这个串的组成。接下来再看看 hashCode():  
        public native int hashCode();  
    由于是 native 方法,跟 OS 的处理方式相关,源代码里仅仅有一个声明罢了。我们有 兴趣的话完全可以去深究它的hashCode到底是由OS怎么样产生的呢?但笔者建议重要 的还是先记住使用它的几条原则吧!首先如果 equals()方法相同的对象具有相通的 hashCode,但 equals ()对象不相通的时候并不保证 hashCode()方法返回不同的整数。而 且下一次运行同一个程序,同一个对象未必还是当初的那个 hashCode() 哦。  
    其余的方法呢?nofigy()、notifyAll()、clone()、wait()都是 native 方法的,说明依赖于 操作系统的实现。后一个有趣的方法是 finalize(),类似 C++的析构函数,签名是 protected, 证明只有继承扩展了才能使用,方法体是空的,默示什么也不做。它的作用据笔者的了解仅 仅是通知 JVM 此对象不再使用,随时可以被销毁,而实际的销毁权还是在于虚拟机手上。 那么它真的什么也不做麽?未必,实际上如果是线程对象它会导致在一定范围内该线程的优 先级别提高,导致更快的被销毁来节约内存提高性能。其实从常理来说,我们也可以大概这 样猜测出 jvm 做法的目的。  
2. 关于重载 hashCode()与 Collection 框架的关系  
    笔者曾经听一位搞 Java 培训多年的前辈说在他看来 hashCode 方法没有任何意义,仅 仅是为了配合证明具有同样的 hashCode 会导致 equals 方法相等而存在的。连有的前辈都 犯这样的错误,其实说明它还是满容易被忽略的。那么 hashCode()方法到底做什么用?  
    学过数据结构的课程大家都会知道有一种结构叫 hash table,目的是通过给每个对象分 配一个唯一的索引来提高查询的效率。那么 Java 也不会肆意扭曲改变这个概念,所以 hashCode 唯一的作用就是为支持数据结构中的哈希表结构而存在的,换句话说,也就是只 有用到集合框架的 Hashtable、HashMap、HashSet 的时候,才需要重载 hashCode()方法, 这样才能使得我们能人为的去控制在哈希结构中索引是否相等。笔者举一个例子:  
    曾经为了写一个求解类程序,需要随机列出 1,2,3,4 组成的不同排列组合,所以笔者写 了一个数组类用 int[]来存组合结果,然后把随机产生的组合加入一个 HashSet 中,就是想 利用 HashSet 不包括重复元素的特点。可是 HashSet 怎么判断是不是重复的元素呢?当然 是通过 hashCode()返回的结果是否相等来判断啦,可做一下这个实验:  
    int[] A = {1,2,3,4};     int[] B = {1,2,3,4};     System.out.println(A.hashCode());     System.out.println(B.hashCode());  
    这明明是同一种组合,却是不同的 hashCode,加入 Set 的时候会被当成不同的对象。 这个时候我们就需要自己来重写 hashCode()方法了,如何写呢?其实也是基于原始的
hashCode(),毕竟那是操作系统的实现, 找到相通对象唯一的标识,实现方式很多,笔者 的实现方式是:  
    首先重写了 toString()方法:        return A[0]“+” A[1]“+” A[2]“+” A[3]; //显示上比较直观    然后利用 toString()来计算 hashCode():        return this.toString().hashCode();    这样上述 A 和 B 返回的就都是”1234”,在测试 toString().hashCode(),由于 String 在内 存中的副本是一样的,”1234”.hashCode()返回的一定是相同的结果。    说到这,相信大家能理解得比我更好,今后千万不要再误解 hashCode()方法的作用。  
3. 关于 Class 类的成员函数与 Java 反射机制  
    很早刚接触 Java 就听很多老师说过 Java 的动态运行时机制、反射机制等。确实它们 都是 Java 的显著特点,运行时加载笔者在第一篇介绍过了,现在想讲讲反射机制。在 Java 中,主要是通过 java.lang 包中的 Class 类和 Method 类来实现内存反射机制的。  
    熟悉 C++的人一定知道下面这样在 C++中是做不到的: 运行时以字符串参数传递一个 类名,就可以得到这个类的所有信息,包括它所有的方法,和方法的详细信息。还可以实例 化一个对象,并通过查到的方法名来调用该对象的任何方法。这是因为 Java 的类在内存中 除了 C++中也有的静态动态数据区之外,还包括一份对类自身的描述,也正是通过这描述 中的信息,才能帮助我们才运行时读取里面的内容,得到需要加载目标类的所有信息,从而 实现反射机制。大家有没有想过当我们需要得到一个 JavaBean 的实例的时候,怎么知道它 有哪些属性呢?再明显简单不过的例子就是自己写一个 JavaBean 的解析器:  
     a. 通过 Class.forName(“Bean 的类名”)得到 Class 对象,例如叫 ABeanClass      b. 通过 ABeanClass 的 getMethods()方法,得到 Method[]对象      c. 按照规范所有 get 方法名后的单词就代表着该 Bean 的一个属性      d. 当已经知道一个方法名,可以调用 newInstance()得到一个实例,然后通过 invoke() 方法将方法的名字和方法需要用的参数传递进去,就可以动态调用此方法。  
    当然还有更复杂的应用,这里就不赘述,大家可以参考 Class 类和 Method 类的方法。  
4. 坦言 Synchronize 的本质  
    Synchronize 大家都知道是同步、加锁的意思,其实它的本质远没有大家想得那么复杂。 声明 Synchronize 的方法被调用的时候,锁其实是加载对象上,当然如果是静态类则是加在 类上的锁,调用结束锁被解除。它的实现原理很简单,仅仅是不让第二把锁再次被加在同一 个对象或类上,仅此而已。一个简单的例子足以说明问题:     class A{       synchronized void f(){}       void g(){}      }      当 A 的一个对象 a 被第一个线程调用其 f()方法的时候,第二个线程不能调用 a 的
synchronized 方法例如 f(),因为那是在试图在对象上加第二把锁。但调用 g()却是可以的, 因为并没有在同一对象上加两把锁的行为产生。  

    这样大家能理解了麽?明白它的原理能更好的帮助大家设计同步机制,不要滥用加锁。 PS:下篇笔者计划开始对 J2ee 接触到的各个方面来进行总结,谈谈自己的经验和想法。希 望大家还能一如既往的支持笔者写下去,指正不足之处。   


Java 杂谈(四)   
不知不觉已经写到第四篇了,论坛里面不断的有朋友鼓励我写下去。坚持自己的作风, 把一切迷惑不容易理清楚的知识讲出来,讲到大家都能听懂,那么自己就真的懂了。近在 公司实习的时候 Trainer 跟我讲了很多经典事迹,对还未毕业的我来说是笔不小的财富,我 自己的信念是:人在逆境中成长的速度要远远快过顺境中,这样来看一切都能欣然接受了。 好了,闲话不说了,第三篇讲的是反射机制集合框架之类的,这次打算讲讲自己对反序列化 和多线程的理解。希望能对大家学习 Java 起到帮助。  
1.关于序列化和反序列化  
    应该大家都大概知道 Java 中序列化和反序列化的意思,序列化就是把一个 Java 对象 转换成二进制进行磁盘上传输或者网络流的传输,反序列化的意思就是把这个接受到的二进 制流重新组装成原来的对象逆过程。它们在 Java 中分别是通过 ObjectInputStream 和 ObjectOutputStream 这两个类来实现的(以下分别用 ois 和 oos 来简称)。  
    oos的 writeObject()方法用来执行序列化的过程,ois 的 readObject()用来执行反序列 化的过程,在传输二进制流之前,需要讲这两个高层流对象连接到同一个 Channel 上,这 个 Channel 可以是磁盘文件,也可以是 socket 底层流。所以无论用哪种方式,底层流对象 都是以构造函数参数的形式传递进 oos 和 ois 这两个高层流,连接完毕了才可以进行二进制 数据传输的。例子:  
    可以是文件流通道      file = new File(“C:/data.dat”);      oos = new ObjectOutputStream(new FileOutputStream(file));      ois = new ObjectInputStream(new FileInputStream(file));     或者网络流通道      oos = new ObjectOutputStream(socket.getOutputStream());      ois = new ObjectInputStream(socket.getInputStream());  
    不知道大家是否注意到 oos 总是在 ois 之前定义,这里不希望大家误解这个顺序是固定 的么?回答是否定的,那么有顺序要求么?回答是肯定的。原则是什么呢?  
    原则是互相对接的输入/输出流之间必须是 output 流先初始化然后再 input 流初始化, 否则就会抛异常。大家肯定会问为什么?只要稍微看一看这两个类的源代码文件就大概知道
了,output 流的任务很简单,只要把对象转换成二进制往通道中写就可以了,但 input 流需 要做很多准备工作来接受并终重组这个 Object,所以 ObjectInputStream 的构造函数中就 需要用到 output 初始化发送过来的 header 信息,这个方法叫做 readStreamHeader(),它 将会去读两个 Short 值用于决定用多大的缓存来存放通道发送过来的二进制流,这个缓存的 size 因 jre 的版本不同是不一样的。所以 output 如果不先初始化,input 的构造函数首先就 无法正确运行。  
    对于上面两个例子,第一个顺序是严格的,第二个因为 oos 和 ois 连接的已经不是对方 了,而是 socket 另外一端的流,需要严格按照另外一方对接的 output 流先于对接的 input 流打开才能顺利运行。  
    这个 writeObject 和 readObject 本身就是线程安全的,传输过程中是不允许被并发访问 的。所以对象能一个一个接连不断的传过来,有很多人在运行的时候会碰到 EOFException, 然后百思不得其解,去各种论坛问解决方案。其实笔者这里想说,这个异常不是必须声明的, 也就是说它虽然是异常,但其实是正常运行结束的标志。EOF 表示读到了文件尾,发送结 束自然连接也就断开了。如果这影响到了你程序的正确性的话,请各位静下心来看看自己程 序的业务逻辑,而不要把注意力狭隘的聚集在发送和接受的方法上。因为笔者也被这样的 bug 困扰了 1 整天,被很多论坛的帖子误解了很多次后得出的教训。如果在 while 循环中 去 readObject,本质上是没有问题的,有对象数据来就会读,没有就自动阻塞。那么抛出 EOFException 一定是因为连接断了还在继续 read,什么原因导致连接断了呢?一定是业务 逻辑哪里存在错误,比如 NullPoint、 ClassCaseException、ArrayOutofBound,即使程序 较大也没关系,多只要单步调适一次就能很快发现 bug 并且解决它。  
    难怪一位程序大师说过:解决问题 90%靠经验,5%靠技术,剩下 5%靠运气!真是金 玉良言,笔者大概查阅过不下 30 篇讨论在 while 循环中使用 readObject 抛出 EOFExceptionde 的帖子,大家都盲目的去关注解释这个名词、反序列化的行为或反对这 样写而没有一个人认为 EOF 是正确的行为,它其实很老实的在做它的事情。为什么大家都 忽略了真正出错误的地方呢?两个字,经验!  
2.关于 Java 的多线程编程      关于 Java 的线程,初学或者接触不深的大概也能知道一些基本概念,同时又会很迷惑 线程到底是怎么回事?如果有人认为自己已经懂了不妨来回答下面的问题:  
    a. A对象实现 Runnable 接口,A.start()运行后所谓的线程对象是谁?是 A 么?     b. 线程的 wait()、notify()方法到底是做什么时候用的,什么时候用?     c. 为什么线程的 suspend 方法会被标注过时,不推荐再使用,线程还能挂起么?     d. 为了同步我们会对线程方法声明 Synchronized 来加锁在对象上,那么如果父类的 f() 方法加了 Synchronized,子类重写 f()方法必须也加 Synchronized 么?如果子类的 f()方法 重写时声明 Synchronized 并调用 super.f(),那么子类对象上到底有几把锁呢?会因为竞争 产生死锁么?  
    呵呵,各位能回答上来几道呢?如果这些都能答上来,说明对线程的概念还是满清晰的, 虽说还远远不能算精通。笔者这里一一做回答,碍于篇幅的原因,笔者尽量说得简介一点,
如果大家有疑惑的欢迎一起讨论。  
    首先第一点,线程跟对象完全是两回事,虽然我们也常说线程对象。但当你用 run()和 start()来启动一个线程之后,线程其实跟这个继承了 Thread 或实现了 Runnable 的对象已 经没有关系了,对象只能算内存中可用资源而对象的方法只能算内存正文区可以执行的代码 段而已。既然是资源和代码段,另外一个线程当然也可以去访问,main 函数执行就至少会 启动两个线程,一个我们称之为主线程,还一个是垃圾收集器的线程,主线程结束就意味着 程序结束,可垃圾收集器线程很可能正在工作。  
    第二点,wait()和 sleep()类似,都是让线程处于阻塞状态暂停一段时间,不同之处在于 wait 会释放当前线程占有的所有的锁,而 sleep 不会。我们知道获得锁的唯一方法是进入 了 Synchronized 保护代码段,所以大家会发现只有 Synchronized 方法中才会出现 wait, 直接写会给警告没有获得当前对象的锁。所以 notify 跟 wait 配合使用,notify 会重新把锁还 给阻塞的线程重而使其继续执行,当有多个对象 wait 了,notify 不能确定唤醒哪一个,必经 锁只有一把,所以一般用 notifyAll()来让它们自己根据优先级等竞争那唯一的一把锁,竞争 到的线程执行,其他线程只要继续 wait。  
    从前 Java 允许在一个线程之外把线程挂起,即调用 suspend 方法,这样的操作是极不 安全的。根据面向对象的思想每个对象必须对自己的行为负责,而对自己的权力进行封装。 如果任何外步对象都能使线程被挂起而阻塞的话,程序往往会出现混乱导致崩溃,所以这样 的方法自然是被毙掉了啦。  
    后一个问题比较有意思,首先回答的是子类重写 f()方法可以加 Synchronized 也可以 不加,如果加了而且还内部调用了 super.f ()的话理论上是应该对同一对象加两把锁的,因 为每次调用 Synchronized 方法都要加一把,调用子类的 f 首先就加了一把,进入方法内部 调用父类的 f又要加一把,加两把不是互斥的么?那么调父类 f 加锁不就必须永远等待已经 加的锁释放而造成死锁么?实际上是不会的,这个机制叫重进入,当父类的 f 方法试图在本 对象上再加一把锁的时候,因为当前线程拥有这个对象的锁,也可以理解为开启它的钥匙, 所以同一个线程在同一对象上还没释放之前加第二次锁是不会出问题的,这个锁其实根本就 没有加,它有了钥匙,不管加几把还是可以进入锁保护的代码段,畅通无阻,所以叫重进入, 我们可以简单认为第二把锁没有加上去。  

总而言之,Synchronized 的本质是不让其他线程在同一对象上再加一把锁。

   

Java 杂谈(五)  
本来预计 J2se 只讲了第四篇就收尾了,可是版主厚爱把帖子置顶长期让大家浏览让小 弟倍感责任重大,务必追求到更好,所以关于 J2se 一些没有提到的部分,决定再写几篇 把常用的部分经验全部写出来供大家讨论切磋。这一篇准备讲一讲 Xml 解析包和 Java Swing,然后下一篇再讲 java.security 包关于 Java 沙箱安全机制和 RMI 机制,再进入 J2ee 的部分,暂时就做这样的计划了。如果由于实习繁忙更新稍微慢了一些,希望各位见 谅!   
1. Java 关于 XML 的解析   
    相信大家对 XML 都不陌生,含义是可扩展标记语言。本身它也就是一个数据的载体以 树状表现形式出现。后来慢慢的数据变成了信息,区别是信息可以包括可变的状态从而针对 程序硬编码的做法变革为针对统一接口硬编码而可变状态作为信息进入了 XML 中存储。这 样改变状态实现扩展的唯一工作是在 XML 中添加一段文本信息就可以了,代码不需要改动 也不需要重新编译。这个灵活性是 XML 诞生时候谁也没想到的。   
    当然,如果接口要能提取 XML 中配置的信息就需要程序能解析规范的 XML 文件,Java 中当然要提高包对这个行为进行有利支持。笔者打算讲到的两个包是 org.w3c.dom 和 javax.xml.parsers 和。(大家可以浏览一下这些包中间的接口和类定义)  
    Javax.xml.parsers 包很简单,没有接口,两个工厂配两个解析器。显然解析 XML 是 有两种方式的:DOM 解析和 SAX 解析。本质上并没有谁好谁不好,只是实现的思想不一 样罢了。给一个 XML 文件的例子:   
    <?xml version=”1.0” encoding=”UTF-8” >      <root >          <child name=”Kitty” >  A Cat </child >      </root >   
    所谓 DOM 解析的思路是把整个树状图存入内存中,需要那个节点只需要在树上搜索就 可以读到节点的属性,内容等,这样的好处是所有节点皆在内存可以反复搜索重复使用,缺 点是需要消耗相应的内存空间。  
    自然 SAX 解析的思路就是为了克服 DOM 的缺点,以事件触发为基本思路,顺序的搜 索下来,碰到了 Element 之前触发什么事件,碰到之后做什么动作。由于需要自己来写触 发事件的处理方案,所以需要借助另外一个自定义的 Handler,处于 org.xml.sax.helpers 包中。它的优点当然是不用整个包都读入内存,缺点也是只能顺序搜索,走完一遍就得重来。   
    大家很容易就能猜到,接触到的 J2ee 框架用的是哪一种,显然是 DOM。因为类似 Struts,Hibernate 框架配置文件毕竟是很小的一部分配置信息,而且需要频繁搜索来读 取,当然会采用 DOM 方式(其实 SAX 内部也是用 DOM 采用的结构来存储节点信息的)。 现在无论用什么框架,还真难发现使用 SAX 来解析 XML 的技术了,如果哪位仁兄知道, 请让笔者也学习学习。  
    既然解析方式有了,那么就需要有解析的存储位置。不知道大家是否发现 org.w3c.dom 这个包是没有实现类全部都是接口的。这里笔者想说一下 Java 如何对 XML 解析是 Jdk 应该考虑的事,是它的责任。而 w3c 组织是维护定义 XML 标准的组织,所以 一个 XML 结构是怎么样的由 w3c 说了算,它不关心 Java 如何去实现,于是乎规定了所有 XML 存储的结构应该遵循的规则,这就是 org.w3c.dom 里全部的接口目的所在。在笔者 看来,简单理解接口的概念就是实现者必须遵守的原则。   
    整个 XML 对应的结构叫 Document、子元素对应的叫做 Element、还有节点相关的
Node、NodeList、Text、Entity、 CharacterData、CDATASection 等接口,它们都可 以在 XML 的语法中间找到相对应的含义。由于这里不是讲解 XML 基本语法,就不多介绍 了。如果大家感兴趣,笔者也可以专门写一篇关于 XML 的语法规则帖与大家分享一下。   
2. Java Swing   
    Swing是一个让人又爱又恨的东西,可爱之处在于上手很容易,较 AWT 比起来 Swing 提供的界面功能更加强大,可恨之处在于编复杂的界面工作量实在是巨大。笔者写过超过 3000 行的 Swing 界面,感觉用户体验还不是那么优秀。近又写过超过 6000 行的,由 于功能模块多了,整体效果还只是一般般。体会深的就一个字:累! 所以大家现在都陆续 不怎么用 Swing 在真正开发的项目上了,太多界面技术可以取代它了。笔者去写也是迫于 无奈组里面大家都没写过,我不入地狱谁入?   
    尽管 Swing 慢慢的在被人忽略,特别是随着 B/S 慢慢的在淹没 C/S,笔者倒是很愿意 站出来为 Swing 正身。每一项技术的掌握绝不是为了流行时尚跟风。真正喜欢 Java 的朋 友们还是应该好好体会一下 Swing,相信在校的很多学生也很多在学习它。很可能从 Jdk 1.1、1.2 走过来的很多大学老师可能是不熟悉它的。   
    Swing 提供了一组轻组件统称为 JComponent,它们与 AWT 组件的大区别是 JComponent 全部都是 Container,而 Container 的特点是里面可以装载别的组件。在 Swing 组件中无论是 JButton、JLabel、JPanel、JList 等都可以再装入任何其他组件。好 处是程序员可以对 Swing 组件实现“再开发”,针对特定需求构建自己的按钮、标签、画板、 列表之类的特定组件。  
    有轻自然就有重,那么轻组件和重组件区别是?重组件表现出来的形态因操作系统不同 而异,轻组件是 Swing 自己提供 GUI,在跨平台的时候大程度的保持一致。   
    那么在编程的时候要注意一些什么呢?笔者谈谈自己的几点经验:  
    a. 明确一个概念,只有 Frame 组件才可以单独显示的,也许有人会说 JOptionPane 里面的静态方法就实现了单独窗口出现,但追寻源代码会发现其实现实出来的 Dialog 也需 要依托一个 Frame窗体,如果没有指定就会默认产生一个然后装载这个 Dialog 显示出来。   
    b. JFrame是由这么几部分组成:      底下一层 JRootPane,上面是 glassPane (一个 JPanel)和 layeredPane (一个 JLayeredPane),而 layeredPane 又由 contentPane(一个 JPanel)和 menuBar 构成。 我们的组件都是加在 contentPane 上,而背景图片只能加在 layeredPane 上面。 至于 glassPane 是一个透明的覆盖了 contentPane 的一层,在特定效果中将被利用到来记录鼠 标坐标或掩饰组件。   
    c. 为了增强用户体验,我们会在一些按钮上添加快捷键,但 Swing 里面通常只能识别 键盘的 Alt 键,要加入其他的快捷键,必须自己实现一个 ActionListener。   
    d. 通过setLayout(null)可以使得所有组件以setBounds()的四个参数来精确定位各
自的大小、位置,但不推荐使用,因为好的编程风格不应该在 Swing 代码中硬编码具体数 字,所有的数字应该以常数的形式统一存在一个静态无实例资源类文件中。这个静态无实例 类统一负责 Swing 界面的风格,包括字体和颜色都应该包括进去。  
    e. 好的界面设计有一条Golden Rule: 用户不用任何手册通过少数尝试就能学会使用 软件。所以尽量把按钮以菜单的形式(不管是右键菜单还是窗体自带顶部菜单)呈现给顾客, 除非是频繁点击的按钮才有必要直接呈现在界面中。   

    其实 Swing 的功能是相当强大的,只是现在应用不广泛,专门去研究大概是要花不少 时间的。笔者在各网站论坛浏览关于 Swing 的技巧文章还是比较可信的,自己所学非常有 限,各人体会对 Swing 各个组件的掌握就是一个实践积累的过程。笔者只用到过以上这些, 所以只能谈谈部分想法,还望大家见谅! 

 

Java 杂谈(六)   这篇是笔者打算写的 J2se 部分的后一篇了,这篇结束之后,再写 J2ee 部分,不知 道是否还合适写在这个版块?大家可以给点意见,谢谢大家对小弟这么鼓励一路写完前六篇 Java 杂谈的 J2se 部分。后这篇打算谈一谈 Java 中的 RMI 机制和 JVM 沙箱安全框架。   
1. Java 中的 RMI 机制   
    RMI 的全称是远程方法调用,相信不少朋友都听说过,基本的思路可以用一个经典比 方来解释:A 计算机想要计算一个两个数的加法,但 A 自己做不了,于是叫另外一台计算 机 B 帮忙,B 有计算加法的功能,A 调用它就像调用这个功能是自己的一样方便。这个就叫 做远程方法调用了。  
    远程方法调用是 EJB 实现的支柱,建立分布式应用的核心思想。这个很好理解,再拿 上面的计算加法例子,A 只知道去 call 计算机 B 的方法,自己并没有 B 的那些功能,所以 A 计算机端就无法看到 B 执行这段功能的过程和代码,因为看都看不到,所以既没有机会窃 取也没有机会去改动方法代码。EJB 正式基于这样的思想来完成它的任务的。当简单的加 法变成复杂的数据库操作和电子商务交易应用的时候,这样的安全性和分布式应用的便利性 就表现出来优势了。   
    好了,回到细节上,要如何实现远程方法调用呢?我希望大家学习任何技术的时候可以 试着依赖自己的下意识判断,只要你的想法是合理健壮的,那么很可能实际上它就是这么做 的,毕竟真理都蕴藏在平凡的生活细节中。这样只要带着一些薄弱的 Java 基础来思考 RMI, 其实也可以想出个大概来。   
    a) 需要有一个服务器角色,它拥有真正的功能代码方法。例如 B,它提供加法服务      b) 如果想远程使用 B 的功能,需要知道 B 的 IP 地址      c) 如果想远程使用 B 的功能,还需要知道 B 中那个特定服务的名字   
    我们很自然可以想到这些,虽然不完善,但已经很接近正确的做法了。实际上 RMI 要
得以实现还得意于 Java 一个很重要的特性,就是 Java 反射机制。我们需要知道服务的名 字,但又必须隐藏实现的代码,如何去做呢?答案就是:接口!     举个例子:      public interface Person(){          public void sayHello();     }     Public class PersonImplA implements Person{        public PersonImplA(){}        public void sayHello(){ System.out.println(“Hello!”);}     }     Public class PersonImplB implements Person{        public PersonImplB(){}        public void sayHello(){ System.out.println(“Nice to meet you!”);}      }     客户端:Person p = Naming.lookup(“PersonService”);             p.sayHello();   
    就这几段代码就包含了几乎所有的实现技术,大家相信么?客户端请求一个 say hello 服务,服务器运行时接到这个请求,利用 Java 反射机制的 Class.newInstance()返回一个对 象,但客户端不知道服务器返回的是 ImplA 还是 ImplB,它接受用的参数签名是 Person, 它知道实现了 Person 接口的对象一定有 sayHello()方法,这就意味着客户端并不知道服务 器真正如何去实现的,但它通过了解 Person 接口明确了它要用的服务方法名字叫做 sayHello()。   
    如此类推,服务器只需要暴露自己的接口出来供客户端,所有客户端就可以自己选择需 要的服务。这就像餐馆只要拿出自己的菜单出来让客户选择,就可以在后台厨房一道道的按 需做出来,它怎么做的通常是不让客户知道的!(祖传菜谱吧,^_^)   
    后一点是我调用 lookup,查找一个叫 PersonService 名字的对象,服务器只要看到 这个名字,在自己的目录(相当于电话簿)中找到对应的对象名字提供服务就可以了,这个 目录就叫做 JNDI (Java 命名与目录接口),相信大家也听过的。  
    有兴趣的朋友不妨自己做个 RMI 的应用,很多前辈的博客中有简单的例子。提示一下 利用 Jdk 的 bin 目录中 rmi.exe 和 rmiregistry.exe 两个命令就可以自己建起一个服务器,提 供远程服务。因为例子很容易找,我就不自己举例子了!   
2. JVM 沙箱&框架       RMI 罗唆得太多了,实在是尽力想把它说清楚,希望对大家有帮助。后的后,给 大家简单讲一下 JVM 框架,我们叫做 Java 沙箱。Java 沙箱的基本组件如下:   
      a) 类装载器结构        b) class文件检验器  
      c) 内置于 Java 虚拟机的安全特性        d) 安全管理器及 Java API   
   其中类装载器在 3 个方面对 Java 沙箱起作用:   
       a. 它防止恶意代码去干涉善意的代码         b. 它守护了被信任的类库边界         c. 它将代码归入保护域,确定了代码可以进行哪些操作   
   虚拟机为不同的类加载器载入的类提供不同的命名空间,命名空间由一系列唯一的名称 组成,每一个被装载的类将有一个名字,这个命名空间是由 Java 虚拟机为每一个类装载器 维护的,它们互相之间甚至不可见。   
    我们常说的包(package)是在 Java 虚拟机第 2 版的规范第一次出现,正确定义是由 同一个类装载器装载的、属于同一个包、多个类型的集合。类装载器采用的机制是双亲委派 模式。具体的加载器框架我在 Java 杂谈(一)中已经解释过了,当时说外层的加载器是 AppClassLoader,其实算上网络层的话 AppClassLoader 也可以作为 parent,还有更外层 的加载器 URLClassLoader。为了防止恶意攻击由 URL 加载进来的类文件我们当然需要分 不同的访问命名空间,并且制定安全的加载次序,简单来说就是两点:   
    a. 从内层 JVM 自带类加载器开始加载,外层恶意同名类得不到先加载而无法使用      b. 由于严格通过包来区分了访问域,外层恶意的类通过内置代码也无法获得权限访问 到内层类,破坏代码就自然无法生效。   
附:关于 Java 的平台无关性,有一个例子可以很明显的说明这个特性:      一般来说,C 或 C++中的 int 占位宽度是根据目标平台的字长来决定的,这就意味着针 对不同的平台编译同一个 C++程序在运行时会有不同的行为。然而对于 Java 中的 int 都是 32 位的二进制补码标识的有符号整数,而 float 都是遵守 IEEE 754 浮点标准的 32 位浮点 数。  

PS: 这个小弟近也没时间继续研究下去了,只是想抛砖引玉的提供给大家一个初步认识 JVM 的印象。有机会了解一下 JVM 的内部结构对今后做 Java 开发是很有好处的。 

  

Java 杂谈(七)--接口& 组件、容器    终于又静下来继续写这个主题的续篇,前六篇主要讲了一些 J2se 方面的经验和感受, 眼下 Java 应用范围已经被 J2ee 占据了相当大的一块领域,有些人甚至声称 Java 被 J2ee 所取代了。不知道大家如何来理解所谓的 J2ee (Java2 Enterprise Edition),也就是 Java 企 业级应用。   
    笔者的观点是,技术的发展是顺应世界变化的趋势的,从 C/S 过渡到 B/S 模式,从客 户端的角度考虑企业级应用或者说电子商务领域不在关心客户端维护问题,这个任务已经交 给了任何一台 PC 都会有的浏览器去维护;从服务器端的角度考虑,以往 C/S 中的 TCP/IP
协议实现载体 ServerSocket 被 Web Server Container 所取代,例如大家都很熟悉的 Tomcat、JBoss、WebLogic 等等。总之一切的转变都是为了使得 Java 技术能更好的为人 类生产生活所服务。   
    有人会问,直接去学 J2ee 跳过 J2se 行否?笔者是肯定不赞成的,实际上确实有人走 这条路,但笔者自身体会是正是由于 J2se 的基础很牢固,才会导致在 J2ee 学习的道路上 顺风顺水,知识点上不会有什么迷惑的地方。举个简单的例子吧:   
     笔者曾经跟大学同学讨论下面这两种写法的区别:           ArrayList list = new ArrayList(); //笔者不说反对,但至少不赞成           List list = new ArrayList(); //笔者支持       曾经笔者跟同学争论了几个小时,他非说第一种写法更科学,第二种完全没有必要。 我无法完全说服他,但笔者认为良好的习惯和意识是任何时候都应该针对接口编程,以达到 解耦合和可扩展性的目的。下面就以接口开始进入 J2ee 的世界吧:   
1. J2ee 与接口   
    每一个版本的 J2ee 都对应着一个确定版本的 JDK,J2ee1.4 对应 Jdk1.4,现在比较新 的是 JDK5.0,自然也会有 J2EE 5.0。其实笔者一直在用的是 J2EE1.4,不过没什么关系, 大家可以下任何一个版本的 J2ee api 来稍微浏览一下。笔者想先声明一个概念,J2ee 也是 源自Java,所以底层的操作依然调用到很多J2se的库,所以才建议大家先牢牢掌握J2se 的 主流技术。   
    J2ee api 有一个特点,大家比较熟悉的几个包 java.jms、javax.servlet.http、javax.ejb 等都以 interface 居多,实现类较少。其实大家真正在用的时候百分之六十以上都在反复的 查着 javax.servlet.http 这个包下面几个实现类的 api 函数,其他的包很少问津。笔者建议在 学习一种技术之前,对整体的框架有一个了解是很有必要的,J2ee 旨在通过 interface 的声 明来规范实现的行为,任何第三方的厂商想要提供自己品牌的实现前提也是遵循这些接口定 义的规则。如果在从前 J2se 学习的道路上对接口的理解很好的话,这里的体会将是非常深 刻的,举个简单的例子:   
    public interface Mp3{            public void play();            public void record();            public void stop();      }   
    如果我定义这个简单的接口,发布出去,规定任何第三方的公司想推出自己的名字为 Mp3 的产品都必须实现这个接口,也就是至少提供接口中方法的具体实现。这个意义已经 远远不止是面向对象的多态了,只有厂商遵循 J2ee 的接口定义,世界上的 J2ee 程序员才 能针对统一的接口进行程序设计,终不用改变代码只是因为使用了不同厂商的实现类而有 不同的特性罢了,本质上说,无论哪一种厂商实现都完成了职责范围内的工作。这个就是笔 者想一直强调的,针对接口编程的思想。   
    接口到底有什么好处呢?我们这样设想,现在有 AppleMp3、SonyMp3、SamsungMp3 都实现了这个 Mp3 的接口,于是都有了 play、 record、stop 这三个功能。我们将 Mp3 产 品座位一个组件的时候就不需要知道它的具体实现,只要看到接口定义知道这个对象有 3 个功能就可以使用了。那么类似下面这样的业务就完全可以在任何时间从 3 个品牌扩展到 任意个品牌,开个玩笑的说,项目经理高高在上的写完 10 个接口里的方法声明,然后就丢 给手下的程序员去写里面的细节,由于接口已经统一(即每个方法传入和传出的格式已经统 一),经理只需关注全局的业务就可以天天端杯咖啡走来走去了,^_^:       public Mp3 create();       public void copy(Mp3 mp3);       public Mp3 getMp3();   
    后用一个简单的例子说明接口:一个 5 号电池的手电筒,可以装入任何牌子的 5 号 电池,只要它符合 5 号电池的规范,装入之后任何看不到是什么牌子,只能感受到手电筒 在完成它的功能。那么生产手电筒的厂商和生产 5 号电池的厂商就可以完全解除依赖关系, 可以各自自由开发自己的产品,因为它们都遵守 5 号电池应有的形状、正负极位置等约定。 这下大家能对接口多一点体会了么?   
2. 组件和容器   
    针对接口是笔者特意强调的 J2ee 学习之路必备的思想,另外一个就是比较常规的组件 和容器的概念了。很多教材和专业网站都说 J2EE 的核心是一组规范与指南,强调 J2ee 的 核心概念就是组件+容器,这确实是无可厚非的。随着越来越多的 J2ee 框架出现,相应的 每种框架都一般有与之对应的容器。   
    容器,是用来管理组件行为的一个集合工具,组件的行为包括与外部环境的交互、组件 的生命周期、组件之间的合作依赖关系等等。J2ee 包含的容器种类大约有 Web 容器、 Application Client 容器、EJB 容器、Applet 客户端容器等。但在笔者看来,现在容器的概 念变得有点模糊了,大家耳熟能详是那些功能强大的开源框架,比如 Hibernate、Struts2、 Spring、JSF 等,其中 Hibernate 就基于 JDBC 的基础封装了对事务和会话的管理,大大方 便了对数据库操作的繁琐代码,从这个意义上来说它已经接近容器的概念了,EJB 的实体 Bean 也逐渐被以 Hibernate 为代表的持久化框架所取代。   
    组件,本意是指可以重用的代码单元,一般代表着一个或者一组可以独立出来的功能模 块,在 J2ee 中组件的种类有很多种,比较常见的是 EJB 组件、DAO 组件、客户端组件或 者应用程序组件等,它们有个共同特点是分别会打包成.war,.jar,.jar,.ear,每个组件由 特定格式的 xml 描述符文件进行描述,而且服务器端的组件都需要被部署到应用服务器上 面才能够被使用。   
    稍微理解完组件和容器,还有一个重要的概念就是分层模型,著名的当然是 MVC 三 层模型。在一个大的工程或项目中,为了让前台和后台各个模块的编程人员能够同时进行工 作提高开发效率,重要的就是实现层与层之间的耦合关系,许多分层模型的宗旨和开源框 架所追求的也就是这样的效果。在笔者看来,一个完整的 Web 项目大概有以下几个层次:   
    a) 表示层(Jsp、Html、Javascript、Ajax、Flash 等等技术对其支持)  
    b) 控制层(Struts、JSF、WebWork 等等框架在基于 Servlet 的基础上支持,负责把具 体的请求数据(有时卸载重新装载)导向适合处理它的模型层对象)      c) 模型层(笔者认为目前好的框架是 Spring,实质就是处理表示层经由控制层转发 过来的数据,包含着大量的业务逻辑)      d) 数据层(Hibernate、JDBC、EJB 等,由模型层处理完了持久化到数据库中)   

    当然,这仅仅是笔者个人的观点,仅仅是供大家学习做一个参考,如果要实现这些层之 间的完全分离,那么一个大的工程,可以仅仅通过增加人手就来完成任务。虽然《人月神话》 中已经很明确的阐述了增加人手并不能是效率增加,很大程度上是因为彼此做的工作有顺序 上的依赖关系或者说难度和工作量上的巨大差距。当然理想状态在真实世界中是不可能达到 的,但我们永远应该朝着这个方向去不断努力。开始所提倡的针对接口来编程,哪怕是小 小的细节,写一条List list= = new ArrayList()语句也能体现着处处皆使用接口的思想在里面。 Anyway,这只是个开篇,笔者会就自己用过的 J2ee 技术和框架再细化谈一些经验  


Java杂谈(八)--Servlet/Jsp   终于正式进入 J2ee 的细节部分了,首当其冲的当然是 Servlet 和 Jsp 了,上篇曾经提 到过 J2ee 只是一个规范和指南,定义了一组必须要遵循的接口,核心概念是组件和容器。 曾经有的人问笔者 Servlet 的 Class 文件是哪里来的?他认为是 J2ee 官方提供的,我举了 一个简单的反例:稍微检查了一下 Tomcat5.0 里面的 Servlet.jar 文件和 JBoss 里面的 Servlet.jar 文件大小,很明显是不一样的,至少已经说明了它们不是源自同根的吧。其实 Servlet 是由容器根据 J2ee 的接口定义自己来实现的,实现的方式当然可以不同,只要都遵 守 J2ee 规范和指南。   
    上述只是一个常见的误区罢了,告诉我们要编译运行 Servlet,是要依赖于实现它的容 器的,不然连 jar 文件都没有,编译都无法进行。那么 Jsp 呢? Java Server Page 的简称, 是为了开发动态网页而诞生的技术,其本质也是 Jsp,在编写完毕之后会在容器启动时经过 编译成对应的 Servlet。只是我们利用 Jsp 的很多新特性,可以更加专注于前后台的分离, 早期 Jsp 做前台是满流行的,毕竟里面支持 Html 代码,这让前台美工人员可以更有效率的 去完成自己的工作。然后 Jsp 将请求转发到后台的 Servlet,由 Servlet 处理业务逻辑,再转 发回另外一个 Jsp 在前台显示出来。这似乎已经成为一种常用的模式,初笔者学习 J2ee 的时候,大量时间也在编写这样的代码。   
    尽管现在做前台的技术越来越多,例如 Flash、Ajax 等,已经有很多人不再认为 Jsp 重 要了。笔者觉得 Jsp 带来的不仅仅是前后端分离的设计理念,它的另外一项技术成就了我 们今天用的很多框架,那就是 Tag 标签技术。所以与其说是在学习 Jsp,不如更清醒的告诉 自己在不断的理解 Tag 标签的意义和本质。   
1. Servlet 以及 Jsp 的生命周期      Servlet是 Jsp 的实质,尽管容器对它们的处理有所区别。Servlet 有 init()方法初始化, service()方法进行 Web 服务, destroy()方法进行销毁,从生到灭都由容器来掌握,所以这 些方法除非你想自己来实现 Servlet,否则是很少会接触到的。正是由于很少接触,才容易 被广大初学者所忽略,希望大家至少记住 Servlet 生命周期方法都是回调方法。回调这个概
念简单来说就是把自己注入另外一个类中,由它来调用你的方法,所谓的另外一个类就是 Web 容器,它只认识接口和接口的方法,注入进来的是怎样的对象不管,它只会根据所需 调用这个对象在接口定义存在的那些方法。由容器来调用的 Servlet 对象的初始化、服务和 销毁方法,所以叫做回调。这个概念对学习其他 J2ee 技术相当关键!   
    那么 Jsp 呢?本事上是 Servlet,还是有些区别的,它的生命周期是这样的:   
    a) 一个客户端的 Request 到达服务器 ->      b) 判断是否第一次调用 -> 是的话编译 Jsp 成 Servlet      c) 否的话再判断此 Jsp 是否有改变 -> 是的话也重新编译 Jsp 成 Servlet      d) 已经编译近版本的 Servlet 装载所需的其他 Class      e) 发布 Servlet,即调用它的 Service()方法   
    所以 Jsp 号称的是第一次 Load 缓慢,以后都会很快的运行。从它的生命的周期确实不 难看出来这个特点,客户端的操作很少会改变 Jsp 的源码,所以它不需要编译第二次就一 直可以为客户端提供服务。这里稍微解释一下 Http 的无状态性,因为发现很多人误解,Http 的无状态性是指每次一张页面显示出来了,与服务器的连接其实就已经断开了,当再次有提 交动作的时候,才会再次与服务器进行连接请求提供服务。当然还有现在比较流行的是 Ajax 与服务器异步通过 xml 交互的技术,在做前台的领域潜力巨大,笔者不是 Ajax 的高手,这 里无法为大家解释。   
2. Tag 标签的本质   
    笔者之前说了,Jsp 本身初衷是使得 Web 应用前后台的开发可以脱离耦合分开有效的 进行,可惜这个理念的贡献反倒不如它带来的 Tag 技术对 J2ee 的贡献要大。也许已经有很 多人开始使用 Tag 技术了却并不了解它。所以才建议大家在学习 J2ee 开始的时候一定要认 真学习 Jsp,其实重要的就是明白标签的本质。   
    Html标签我们都很熟悉了,有 <html> 、 <head> 、 <body> 、 <title> ,Jsp 带来 的 Tag 标签遵循同样的格式,或者说更严格的 Xml 格式规范,例如 <jsp:include> 、 <jsp:useBean> 、 <c:if> 、 <c:forEach> 等等。它们没有什么神秘的地方,就其源头也还 是 Java Class 而已,Tag 标签的实质也就是一段 Java 代码,或者说一个 Class 文件。当配 置文件设置好去哪里寻找这些 Class 的路径后,容器负责将页面中存在的标签对应到相应的 Class 上,执行那段特定的 Java 代码,如此而已。      说得明白一点的话还是举几个简单的例子说明一下吧:   
    <jsp:include> 去哪里找执行什么 class 呢?首先这是个 jsp 类库的标签,当然要去 jsp 类库寻找相应的 class 了,同样它也是由 Web 容器来提供,例如 Tomcat 就应该去安装目 录的 lib 文件夹下面的 jsp-api.jar 里面找,有兴趣的可以去找一找啊!   
    <c:forEach> 又去哪里找呢?这个是由 Jsp2.0 版本推荐的和核心标记库的内容,例如 <c:if> 就对应在页面中做 if 判断的功能的一断 Java 代码。它的 class 文件在 jstl.jar 这个类 库里面,往往还需要和一个 standard.jar 类库一起导入,放在具体 Web 项目的 WEB-INF 的 lib 目录下面就可以使用了。  
     顺便罗唆一句,Web Project 的目录结构是相对固定的,因为容器会按照固定的路径去 寻找它需要的配置文件和资源,这个任何一本 J2ee 入门书上都有,这里就不介绍了。了解 Tag 的本质还要了解它的工作原理,所以大家去 J2ee 的 API 里找到并研究这个包: javax.servlet.jsp.tagext。它有一些接口,和一些实现类,专门用于开发 Tag,只有自己亲自 写出几个不同功能的标签,才算是真正理解了标签的原理。别忘记了自己开发的标签要自己 去完成配置文件,容器只是集成了去哪里寻找 jsp 标签对应 class 的路径,自己写的标签库 当然要告诉容器去哪里找啦。   
    说了这么多,我们为什么要用标签呢?完全在 Jsp 里面来个 <% %> 就可以在里面任 意写 Java 代码了,但是长期实践发现页面代码统一都是与 html 同风格的标记语言更加有助 于美工人员进行开发前台,它不需要懂 Java,只要 Java 程序员给个列表告诉美工什么标签 可以完成什么逻辑功能,他就可以专注于美工,也算是进一步隔离了前后台的工作吧!   
3. 成就 Web 框架   
    框架是什么?曾经看过这样的定义:与模式类似,框架也是解决特定问题的可重用方法, 框架是一个描述性的构建块和服务集合,开发人员可以用来达成某个目标。一般来说,框架 提供了解决某类问题的基础设施,是用来创建解决方案的工具,而不是问题的解决方案。   
    正是由于 Tag 的出现,成就了以后出现的那么多 Web 框架,它们都开发了自己成熟实 用的一套标签,然后由特定的 Xml 文件来配置加载信息,力图使得 Web 应用的开发变得更 加高效。下面这些标签相应对很多人来说相当熟悉了:   
<html:password>  <logic:equal>  <bean:write>  <f:view>  <h:form>  <h:message>   
    它们分别来自 Struts 和 JSF 框架,强大的功能在于控制转发,就是 MVC 三层模型中 间完成控制器的工作。Struts-1 实际上并未做到真正的三层隔离,这一点在 Struts-2 上得到 了很大的改进。而 Jsf 向来以比较完善合理的标签库受到人们推崇。   

    今天就大概讲这么多吧,再次需要强调的是 Servlet/Jsp 是学习 J2ee 必经之路,也是 基础的知识,希望大家给与足够的重视!   


Java杂谈(九)--Struts     J2ee 的开源框架很多,笔者只能介绍自己熟悉的几个,其他的目前在中国 IT 行业应用 得不是很多。希望大家对新出的框架不要盲目的推崇,首先一定要熟悉它比旧的到底好在哪
里,新的理念和特性是什么?然后再决定是否要使用它。   
    这期的主题是 Struts,直译过来是支架。Struts 的第一个版本是在 2001 年 5 月发布的, 它提供了一个 Web 应用的解决方案,如何让 Jsp 和 servlet 共存去提供清晰的分离视图和 业务应用逻辑的架构。在 Struts 之前,通常的做法是在 Jsp 中加入业务逻辑,或者在 Servlet 中生成视图转发到前台去。Struts 带着 MVC 的新理念当时退出几乎成为业界公认的 Web 应用标准,于是当代 IT 市场上也出现了众多熟悉 Struts 的程序员。即使有新的框架再出来 不用,而继续用 Struts 的理由也加上了一条低风险,因为中途如果开发人员变动,很容易的 招进新的会 Struts 的 IT 民工啊, ^_^!   
    笔者之前说的都是 Struts-1,因为新出了 Struts-2,使得每次谈到 Struts 都必须注明它 是 Struts-1 还是 2。笔者先谈比较熟悉的 Struts-1,下次再介绍一下与 Struts-2 的区别:   
1. Struts 框架整体结构   
    Struts-1 的核心功能是前端控制器,程序员需要关注的是后端控制器。前端控制器是是 一个 Servlet,在 Web.xml 中间配置所有 Request 都必须经过前端控制器,它的名字是 ActionServlet,由框架来实现和管理。所有的视图和业务逻辑隔离都是应为这个 ActionServlet, 它就像一个交通警察,所有过往的车辆必须经过它的法眼,然后被送往特 定的通道。所有,对它的理解就是分发器,我们也可以叫做 Dispatcher,其实了解 Servlet 编程的人自己也可以写一个分发器,加上拦截 request 的 Filter,其实自己实现一个 struts 框架并不是很困难。主要目的就是让编写视图的和后台逻辑的可以脱离紧耦合,各自同步的 完成自己的工作。   
    那么有了 ActionServlet 在中间负责转发,前端的视图比如说是 Jsp,只需要把所有的 数据 Submit,这些数据就会到达适合处理它的后端控制器 Action,然后在里面进行处理, 处理完毕之后转发到前台的同一个或者不同的视图 Jsp 中间,返回前台利用的也是 Servlet 里面的 forward 和 redirect 两种方式。所以到目前为止,一切都只是借用了 Servlet 的 API 搭建起了一个方便的框架而已。这也是 Struts 显著的特性- 控制器。   
    那么另外一个特性,可以说也是 Struts-1 带来的一个比较成功的理念,就是以 xml 配置 代替硬编码配置信息。以往决定 Jsp 往哪个 servlet 提交,是要写进 Jsp 代码中的,也就是 说一旦这个提交路径要改,我们必须改写代码再重新编译。而 Struts 提出来的思路是,编码 的只是一个逻辑名字,它对应哪个 class 文件写进了 xml 配置文件中,这个配置文件记录着 所有的映射关系,一旦需要改变路径,改变 xml 文件比改变代码要容易得多。这个理念可 以说相当成功,以致于后来的框架都延续着这个思路,xml 所起的作用也越来越大。   
    大致上来说 Struts 当初给我们带来的新鲜感就这么多了,其他的所有特性都是基于方便 的控制转发和可扩展的 xml 配置的基础之上来完成它们的功能的。      下面将分别介绍 Action 和 FormBean, 这两个是 Struts 中核心的两个组件。   
2. 后端控制器 Action       Action就是我们说的后端控制器,它必须继承自一个 Action 父类,Struts 设计了很多
种 Action,例如 DispatchAction、 DynaValidationAction。它们都有一个处理业务逻辑的方 法 execute(),传入的 request, response, formBean 和 actionMapping 四个对象,返回 actionForward 对象。到达 Action 之前先会经过一个 RequestProcessor 来初始化配置文件 的映射关系,这里需要大家注意几点:   
    1) 为了确保线程安全,在一个应用的生命周期中,Struts 框架只会为每个 Action 类创 建一个 Action 实例,所有的客户请求共享同一个 Action 实例,并且所有线程可以同时执行 它的 execute()方法。所以当你继承父类 Action,并添加了 private 成员变量的时候,请记住 这个变量可以被多个线程访问,它的同步必须由程序员负责。(所有我们不推荐这样做)。在 使用 Action 的时候,保证线程安全的重要原则是在 Action 类中仅仅使用局部变量,谨慎的 使用实例变量。局部变量是对每个线程来说私有的,execute 方法结束就被销毁,而实例变 量相当于被所有线程共享。   
    2) 当 ActionServlet 实例接收到 Http 请求后,在 doGet()或者 doPost()方法中都会调用 process()方法来处理请求。 RequestProcessor类包含一个HashMap,作为存放所有Action 实例的缓存,每个 Action 实例在缓存中存放的属性 key 为 Action 类名。在 RequestProcessor 类的 processActionCreate()方法中,首先检查在 HashMap 中是否存在 Action 实例。创建 Action 实例的代码位于同步代码块中,以保证只有一个线程创建 Action 实例。一旦线程创建了 Action 实例并把它存放到 HashMap 中,以后所有的线程会直接使 用这个缓存中的实例。   
    3) <action> 元素的 <roles> 属性指定访问这个 Action 用户必须具备的安全角色,多 个角色之间逗号隔开。RequestProcessor 类在预处理请求时会调用自身的 processRoles() 方法,检查配置文件中是否为 Action 配置了安全角色,如果有,就调用 HttpServletRequest 的 isUserInRole()方法来判断用户是否具备了必要的安全性角色,如果不具备,就直接向客 户端返回错误。(返回的视图通过 <input> 属性来指定)   
3. 数据传输对象 FormBean   
    Struts并没有把模型层的业务对象直接传递到视图层,而是采用 DTO(Data Transfer Object)来传输数据,这样可以减少传输数据的冗余,提高传输效率;还有助于实现各层之 间的独立,使每个层分工明确。Struts 的 DTO 就是 ActionForm,即 formBean。由于模型 层应该和 Web 应用层保持独立。由于 ActionForm 类中使用了 Servlet API, 因此不提倡把 ActionForm 传递给模型层, 而应该在控制层把 ActionForm Bean 的数据重新组装到自定 义的 DTO 中, 再把它传递给模型层。它只有两个 scope,分别是 session 和 request。(默 认是 session)一个 ActionForm 标准的生命周期是:       1) 控制器收到请求 ->      2) 从 request 或 session 中取出 ActionForm 实例,如不存在就创建一个 ->      3) 调用 ActionForm 的 reset()方法 ->      4) 把实例放入 session 或者 request 中 ->      5) 将用户输入表达数据组装到 ActionForm 中 ->      6) 如眼张方法配置了就调用 validate()方法 ->      7) 如验证错误就转发给 <input> 属性指定的地方,否则调用 execute()方法  
     validate()方法调用必须满足两个条件:   
    1) ActionForm 配置了 Action 映射而且 name 属性匹配      2) <aciton> 元素的 validate 属性为 true   
    如果 ActionForm 在 request 范围内,那么对于每个新的请求都会创建新的 ActionForm 实例,属性被初始化为默认值,那么 reset ()方法就显得没有必要;但如果 ActionForm 在 session 范围内,同一个 ActionForm 实例会被多个请求共享,reset()方法在这种情况下极 为有用。   
4. 验证框架和国际化   
    Struts有许多自己的特性,但是基本上大家还是不太常用,说白了它们也是基于JDK中 间的很多Java基础包来完成工作。例如国际化、验证框架、插件自扩展功能、与其他框架 的集成、因为各大框架基本都有提供这样的特性,Struts也并不是做得好的一个,这里也 不想多说。Struts的验证框架,是通过一个validator.xml的配置文件读入验证规则,然后在 validation-rules.xml里面找到验证实现通过自动为Jsp插入 Javascript来实现,可以说做得 相当简陋。弹出来的JavaScript框不但难看还很多冗余信息,笔者宁愿用formBean验证或者 Action的saveErrors(),验证逻辑虽然要自己写,但页面隐藏/浮现的警告提示更加人性化和 美观一些。  
    至于Struts的国际化,其实无论哪个框架的国际化,java.util.Locale类是重要的Java I18N类。在Java语言中,几乎所有的对国际化和本地化的支持都依赖于这个类。如果Java 类库中的某个类在运行的时候需要根据Locale对象来调整其功能,那么就称这个类是本地敏 感的(Locale-Sensitive), 例如java.text.DateFormat类就是,依赖于特定Locale。   
    创建Locale对象的时候,需要明确的指定其语言和国家的代码,语言代码遵从的是 ISO-639 规范,国家代码遵从ISO-3166 规范,可以从      http://www.unicode.org/unicode/onlinedat/languages.html      http://www.unicode.org/unicode/onlinedat/countries.htm   
    Struts的国际化是基于properties的message/key对应来实现的,笔者曾写过一个程序, 所有Jsp页面上没有任何Text文本串,全部都用的是 <bean:message> 去Properties文件里 面读,这个时候其实只要指定不同的语言区域读不同的Properties文件就实现了国际化。需 要注意的是不同语言的字符写进Properties文件的时候需要转化成Unicode码,JDK已经带 有转换的功能。JDK的bin目录中有native2ascii这个命令,可以完成对*.txt和*.properties的 Unicode码转换。   

    OK,今天就说到这里,本文中的很多内容也不是笔者的手笔,是笔者一路学习过来自 己抄下来的笔记,希望对大家有帮助!Java杂谈一路走来,感谢大家持续的关注,大概再 有个 2 到 3 篇续篇就改完结了!笔者尽快整理完成后续的写作吧……^_^   


Java杂谈(十)--Struts2     近业余时间笔者一直 Java Virtual Machine 的研究,由于实习分配到项目组里面,不 想从前那么闲了,好不容易才抽出时间来继续这个话题的帖子。我打算把 J2ee 的部分结束 之后,再谈谈 JVM 和 JavaScript,只要笔者有新的学习笔记总结出来,一定会拿来及时 和大家分享的。衷心希望与热爱 Java 的关大同仁共同进步……   
    这次准备继续上次的话题先讲讲 Struts-2,手下简短回顾一段历史:随着时间的推移, Web 应用框架经常变化的需求,产生了几个下一代 Struts 的解决方案。其中的 Struts Ti 继 续坚持 MVC 模式的基础上改进,继续 Struts 的成功经验。 WebWork 项目是在 2002 年 3 月发布的,它对 Struts 式框架进行了革命性改进,引进了不少新的思想,概念和功能,但和 原 Struts 代码并不兼 容。WebWork 是一个成熟的框架,经过了好几次重大的改进与发布。 在 2005 年 12 月,WebWork 与 Struts Ti 决定合拼, 再此同时, Struts Ti 改名为 Struts Action Framework 2.0,成为 Struts 真正的下一代。   
    看看 Struts-2 的处理流程:   
    1) Browser 产生一个请求并提交框架来处理:根据配置决定使用哪些拦截器、action 类和结果等。      2) 请求经过一系列拦截器:根据请求的级别不同拦截器做不同的处理。这和 Struts-1 的 RequestProcessor 类很相似。      3) 调用 Action: 产生一个新的 action 实例,调用业务逻辑方法。      4) 调用产生结果:匹配 result class 并调用产生实例。      5) 请求再次经过一系列拦截器返回:过程也可配置减少拦截器数量      6) 请求返回用户:从 control 返回 servlet,生成 Html。   
    这里很明显的一点是不存在 FormBean 的作用域封装,直接可以从 Action 中取得数据。 这里有一个 Strut-2 配置的 web.xml 文件:      <filter>       <filter-name> controller </filter-name>       <filter-class> org.apache.struts.action2.dispatcher.FilterDispatcher </filter-class>      </filter>      <filter-mapping>       <filter-name> cotroller </filter-name>       <url-pattern> /* </url-pattern>      </filter-mapping>   
    注意到以往的 servlet 变成了 filter,ActionServlet 变成了 FilterDispatcher,*.do 变成了 /*。filter 配置定义了名称(供关联)和 filter 的类。filter mapping 让 URI 匹配成功的的请求调 用该 filter。默认情况下,扩展名为 ".action "。这个是在 default.properties 文件里的 "struts.action.extension "属性定义的。   
    default.properties 是属性定义文件,通过在项目 classpath 路径中包含一个名为
“struts.properties”的文件来设置不同的属性值。而 Struts-2 的默认配置文件名为 struts.xml。 由于 1 和 2 的 action 扩展名分别为.do 和.action,所以很方便能共存。我们再来看一个 Struts-2 的 action 代码:      public class MyAction {         public String execute() throws Exception {        //do the work        return "success ";       }     }   
    很明显的区别是不用再继承任何类和接口,返回的只是一个 String,无参数。实际上在 Struts-2 中任何返回 String 的无参数方法都可以通过配置来调用 action。所有的参数从哪里 来获得呢?答案就是 Inversion of Control 技术(控制反转)。笔者尽量以通俗的方式来解 释,我们先试图让这个 Action 获得 reuqest 对象,这样可以提取页面提交的任何参数。那 么我们把 request 设为一个成员变量,然后需要一个对它的 set 方法。由于大部分的 action 都需要这么做,我们把这个 set 方法作为接口来实现。      public interface ServletRequestAware {        public void setServletRequest(HttpServletRequest request);      }   
    public class MyAction implements ServletRequestAware {         private HttpServletRequest request;         public void setServletRequest(HttpServletRequest request) {             this.request = request;         }         public String execute() throws Exception {            // do the work directly using the request           return Action.SUCCESS;         }     }   
    那么谁来调用这个 set 方法呢?也就是说谁来控制这个 action 的行为,以往我们都是自 己在适当的地方写上一句 action.setServletRequest(…),也就是控制权在程序员这边。然 而控制反转的思想是在哪里调用交给正在运行的容器来决定,只要利用 Java 反射机制来获 得 Method 对象然后调用它的 invoke 方法传入参数就能做到,这样控制权就从程序员这边 转移到了容器那边。程序员可以减轻很多繁琐的工作更多的关注业务逻辑。Request 可以这 样注入到action中,其他任何对象也都可以。为了保证action的成员变量线程安全, Struts-2 的 action 不是单例的,每一个新的请求都会产生一个新的 action 实例。   
    那么有人会问,到底谁来做这个对象的注入工作呢?答案就是拦截器。拦截器又是什么 东西?笔者再来尽量通俗的解释拦截器的概念。大家要理解拦截器的话,首先一定要理解 GOF23 种设计模式中的 Proxy 模式。   
    A对象要调用 f(),它希望代理给 B 来做,那么 B 就要获得 A 对象的引用,然后在 B 的
f()中通过 A 对象引用调用 A 对象的 f()方法,终达到 A 的 f()被调用的目的。有没有人会觉 得这样很麻烦,为什么明明只要 A.f()就可以完成的一定要封装到 B 的 f()方法中去?有哪些 好处呢?   
    1) 这里我们只有一个 A,当我们有很多个 A 的时候,只需要监视 B 一个对象的 f()方法 就可以从全局上控制所有被调用的 f()方法。      2) 另外,既然代理人 B 能获得 A 对象的引用,那么 B 可以决定在真正调 A 对象的 f() 方法之前可以做哪些前置工作,调完返回前可有做哪些后置工作。   
    讲到这里,大家看出来一点拦截器的概念了么?它拦截下一调 f()方法的请求,然后统 一的做处理(处理每个的方式还可以不同,解析 A 对象就可以辨别),处理完毕再放行。这 样像不像对流动的河水横切了一刀,对所有想通过的水分子进行搜身,然后再放行?这也就 是 AOP(Aspect of Programming 面向切面编程)的思想。   
    Anyway,Struts-2 只是利用了 AOP 和 IoC 技术来减轻 action 和框架的耦合关系,力图 到大程度重用 action 的目的。在这样的技术促动下,Struts-2 的 action 成了一个简单被框 架使用的 POJO(Plain Old Java Object)罢了。实事上 AOP 和 IoC 的思想已经遍布新出 来的每一个框架上,他们并不是多么新的技术,利用的也都是 JDK 早已可以到的事情, 它们代表的是更加面向接口编程,提高重用,增加扩展性的一种思想。Struts-2 只是部分的 使用这两种思想来设计完成的,另外一个近很火的框架 Spring,更大程度上代表了这两 种设计思想,笔者将于下一篇来进一步探讨 Spring 的结构。   

PS: 关于 Struts-2 笔者也没真正怎么用过,这里是看了网上一些前辈的帖子之后写下自己的 学习体验,不足之处请见谅!   


Java杂谈(十一)--Spring     笔者近比较忙,一边在实习一边在寻找明年毕业更好的工作,不过论坛里的朋友非常 支持小弟继续写,今天是周末,泡上一杯咖啡,继续与大家分享 J2ee 部分的学习经验。今 天的主题是目前很流行也很好的一个开源框架-Spring。   
    引用《Spring2.0 技术手册》上的一段话:  Spring 的核心是个轻量级容器,它是实现 IoC 容器和非侵入性的框架,并提供 AOP 概念的 实现方式;提供对持久层、事务的支持;提供 MVC Web 框架的实现,并对于一些常用的企 业服务 API 提供一致的模型封装,是一个全方位的应用程序框架,除此之外,对于现存的 各种框架,Spring 也提供了与它们相整合的方案。      接下来笔者先谈谈自己的一些理解吧,Spring 框架的发起者之前一本很著名的书名字 大概是《J2ee Development without EJB》,他提倡用轻量级的组件代替重量级的 EJB。笔 者还没有看完那本著作,只阅读了部分章节。其中有一点分析觉得是很有道理的:   
    EJB里在服务器端有 Web Container 和 EJB Container,从前的观点是各层之间应该在 物理上隔离,Web Container 处理视图功能、在 EJB Container 中处理业务逻辑功能、然后
也是 EBJ Container 控制数据库持久化。这样的层次是很清晰,但是一个很严重的问题是 Web Container 和 EJB Container 毕竟是两个不同的容器,它们之间要通信就得用的是 RMI 机制和 JNDI 服务,同样都在服务端,却物理上隔离,而且每次业务请求都要远程调用,有 没有必要呢?看来并非隔离都是好的。   
    再看看轻量级和重量级的区别,笔者看过很多种说法,觉得有道理的是轻量级代表是 POJO + IoC,重量级的代表是 Container + Factory。(EJB2.0 是典型的重量级组件的技术) 我们尽量使用轻量级的 Pojo 很好理解,意义就在于兼容性和可适应性,移植不需要改变原 来的代码。而 Ioc 与 Factory 比起来,Ioc 的优点是更大的灵活性,通过配置可以控制很多 注入的细节,而 Factory 模式,行为是相对比较封闭固定的,生产一个对象就必须接受它全 部的特点,不管是否需要。其实轻量级和重量级都是相对的概念,使用资源更少、运行负载 更小的自然就算轻量。   
    话题扯远了,因为 Spring 框架带来了太多可以探讨的地方。比如它的非侵入性:指的 是它提供的框架实现可以让程序员编程却感觉不到框架的存在,这样所写的代码并没有和框 架绑定在一起,可以随时抽离出来,这也是 Spring 设计的目标。Spring 是唯一可以做到真 正的针对接口编程,处处都是接口,不依赖绑定任何实现类。同时,Spring 还设计了自己 的事务管理、对象管理和 Model2 的 MVC 框架,还封装了其他 J2ee 的服务在里面,在实 现上基本都在使用依赖注入和 AOP 的思想。由此我们大概可以看到 Spring 是一个什么概念 上的框架,代表了很多优秀思想,值得深入学习。笔者强调,学习并不是框架,而是框架代 表的思想,就像我们当初学 Struts 一样……   
1.Spring MVC   
    关于 IoC 和 AOP 笔者在上篇已经稍微解释过了,这里先通过 Spring 的 MVC 框架来给 大家探讨一下 Spring 的特点吧。(毕竟大部分人已经很熟悉 Struts 了,对比一下吧)      众所周知 MVC 的核心是控制器。类似 Struts 中的 ActionServlet,Spring 里面前端控制 器叫做 DispatcherServlet。里面充当 Action 的组件叫做 Controller,返回的视图层对象叫做 ModelAndView,提交和返回都可能要经过过滤的组件叫做 Interceptor。   
    让我们看看一个从请求到返回的流程吧:         (1)前台 Jsp 或 Html 通过点击 submit,将数据装入了 request 域         (2)请求被 Interceptor 拦截下来,执行 preHandler()方法出前置判断         (3)请求到达 DispathcerServlet         (4)DispathcerServlet 通过 Handler Mapping 来决定每个 reuqest 应该转发给哪个后端 控制器 Controller         (5)各式各样的后端控制器 Controller 来处理请求,调用业务层对象来处理业务逻辑,然 后返回一个 ModelAndView 对象         (6)当 Controller 执行完毕,Interceptor 会调用 postHandle 来做后置处理         (7)ModelAndView 代表了呈现画面是使用的 Model 数据对象和 View 对象,由于只能返 回一个对象所有起了这个名字封装这两个对象。        (8)由ViewResolver对象来解析每个返回的ModelAndView对象应该呈现到哪一个视图 (Jsp/Html 等)中(包括 Exception  Resolver)         (9)当 View 绘制完成之后 Interceptor 又会跳出来执行它的 afterCompletion 方法做善后
处理。当然 Interceptor 的行为完全是配置的而不是强制的。  
     这样一个完整的流程就这样结束了,个人感觉 Spring 的 MVC 框架稍显复杂,不像 Struts-1 那么容易上手。不管是 Controller、Model、ViewRosovler、Handle Mapping 还是 View,Spring MVC 框架都已经为你提供了多种实现,想大程度的减少程序员的编码,增 加框架的适用性。大家有兴趣可以继续深入研究哈!     
  2.Spring   AOP     
      记得初笔者请教他人 Spring 是一个什么东西的时候,每个人都会提到 AOP 这个词 语。笔者在上一篇已经解释过 AOP 基本原理,这次来跟大家说说 Spring 的 AOP 编程吧。 不同的 AOP 框架会有其对 AOP 概念不同的实现方式,主要的差别在于所提供的 Pointcut、 Aspects 的丰富程度,以及它们如何被织入应用程序、代理的方式等等。先熟悉一下 AOP 中的几个重要概念:        (1) Cross-cutting:横切,说白了就是需要统一处理的集合        (2) Aspects:将散落各处的横切收集起来,设计成各个独立可重用的对象称为Aspects。        (3) Advice: 对横切的具体实现,即等待插入一段逻辑。        (4) Joinpoint:Advice 插入流程的时机点。        (5) Pointcut: 用于选择 Joinpoint 的程序结构,可以通过 Annotation 或者 XML 实现。        (6)Weave:  Advice 被应用至对象之上的过程称之为织入,有编译期、类加载期、运行 期三种时间点策略。     
     如果你采用实现接口的方式,Spring 会在执行时期适用 java 的动态代理,如果不实现 接口,Spring 会使用 CGLIB 产生代理类。AOP 的概念很大很泛,而 Spring 只使用了其中 的部分特性,毕竟 Spring 的目标是轻量级框架,比如它只支持对 Method 的 Joinpoint,而 不支持对 Field 的 Joinpoint,理由是为了封装性。              其实我们可以把概念看得简单一点,AOP 的目的是减少冗余代码,增强对较大项目的 全局监控。Spring 利用 AOP 可以规定一个集合和一套规则,在这个集合里所有的方法被 invoke 即调用的时候,都必须按照那套规则走一遍。那么首先对其中 10 个方法都要用到的 处理代码就只用写一遍,如果是这 10 个方法来了就织入这段代码;其次,按照规则,也许 所有的牵扯某个模块的方法调用的时候,我都需要做日志或者进行验证,那么我只要立足于 这个集合的入口和出口,管他从哪里来去哪里,都能被有效的监控。我监控的可能不止是某 个方法单独的行为,我还可以加入对流程控制的监控规则。例如是论坛,我规定注册了才能 登录,而登录后才能发帖回帖下资源,于是所有这类流程都会被收集到我眼皮地下通过。     
    PS:笔者近忙于找工作的事,没有太多经历在论坛跟大家整理自己的笔记。近也只 是接触 Spring 的 MVC 比较多,对于 Spring 的其他特性,还没有更多的去实践,所以仅仅 是泛泛而谈,只是介绍一个印象罢了。还是那句话,我们学习一个框架不是如何使用,而是 它所带来的优秀的思想和理念,这比如何使用这个框架更有意义得多   
Java杂谈(十二)JVM  
      本来这次应该讲讲 ORM 的几个框架,但是笔者还没有完全总结出来,所以这里先插 入一次学习 JVM 的心得。作为一个 Java 程序员,如果不了解 JVM 的工作原理,就很难从 底层去把握 Java 语言和 Java 程序的运作机制。这里先推荐一个权威的讲解 JVM 的文档, 大家只要查过 Java API 的可以在里面的一个叫“API,Language,and Virtual  Machine Document”的标题下看到四个子标题,第一个是我们熟悉的 Java  API  Specification, 很少会有人注意到第三和第四个子标题,分别是“The Java  Language  Specification”和 “The  Java  Machine  Specification”后面都带有(Download)字样,JVM 的那个 URL 直 接链接到 http://java.sun.com/docs/books/vmspec/2nd-edition/这里地址。我们可以下载到 一份非常权威详细的讲解 JVM 原理的官方文档。笔者业余时间花了 1 个星期来阅读,这里 把自己的收获跟大家来分享一下,大概从这么几个方面来谈一谈:     
 1.JVM 的实现机制     
     Java 虚拟机就是一个小的计算机,有自己的指令集,有自己的文件系统,管理内部的 表和数据,负责读取 class 文件里面字节码,然后转换成不同操作系统的 CPU 指令,从而 使得 Java 程序在不同的操作系统上顺利的跑起来。所以 Window 的 JVM 能把字节码转换 成 Window 系统的指令集,Linux 的 JVM 能把字节码转换成 Linux 系统的字节,同理还有 Solaris,它们彼此之间是不能通用的。早一款的原型虽然是 Sun 公司开发的,但发展到 现在其实任何厂商都可以自己去实现一个虚拟机,用来读取字节码转换成 OS 指令。甚至我 们可以认为 JVM 跟 Java 编程语言都没有关系,因为你自己哪怕用记事本写一串字节码, 也可以让 JVM 来解析运行,只要你的字节码能通过 JVM 的验证。      JVM的验证其实是很严格的,这里只讲一些有趣的地方。大家还记得 Java 的图标是一 个杯咖啡麽?究其历史我们也许可以查出为什么,但还有更显而易见的方式是 JVM 怎么判 断一个文件是否是 class 文件?JVM 的做法是读取前 4 个字节转换成 16 进制数,判断是否 等于 0xCAFEBABE 这个数。注意到这个单词了麽?“cafebabe”,代表着国外一种咖啡品牌, 似乎叫做 Peet’s       coffee-baristas 之类。创造 Java 的人为了方便记忆,选择了这样一 个 16 进制数作为标准 class 文件的头,所以任何 class 文件都必须具有这 4 个字节的头部。 我们可以用DataInput这个接口的实现类来验证一下,读取任何一个class文件的第一个int, int 在 Java 里面是四个字节。转换成 16 进制一定会是 0xcafebabe 的。         所以这里想告诉大家的是,JVM 其实并没有那么神秘,我们完全可以理解它的构造。     
 2.Java 相关的基础概念    
      配合 JVM 的结构,在 Java 语言中也会有很多特点比较鲜明的地方。比如对数值计算 从来不会检查位溢出。任何变量存储的二进制即使位全部为 1 了仍然可以加,全部为 0 了 仍然可以减。大家只要稍微测试一下就知道了,看这几个例子:                    int  max = Integer.MAX_VALUE;                    int  min = Integer.MIN_VALUE;                    max+1  ==  min;   //true                    min-1   ==   max;  //true                    0.0/0.0   //得到“NaN”(Not  a  number)                    1/0.0   //Infinity                    -1/0.0   //-Infinity    
                1或-1/0   //ArithmeticException 唯一的异常情况     
     看完这几个例子,大家是否能更好的把握 Java 的数值运算呢?Java 完全遵照 IEEE-754 的标准来定义单双精度浮点数以及其他的数值存储方式。                          另外 Java 里面有一个概念叫做 Daemon Thread(守护线程),知道它的存在主要是为 了理解虚拟机的生命周期。当我们运行 java 命令,从 main 函数进入的那一刻起,虚拟机就 开始启动运行了。Main 所在的主线程也会启动起来,它属于非守护线程。与之同时一些守 护线程也会同时启动,典型的守护线程代表就是 GC(垃圾收集器)线程。JVM 虚拟机什 么时候退出呢?是在所有的非守护线程结束的那一刻,JVM 就 exit。注意这个时候守护线 程并未退出,很可能还要继续完成它的本职工作之后才会结束,但虚拟机的生命周期已经提 前于它结束了。     
 3.JVM 内部的基本概念     
      虚拟机内部还有一些概念,全部列举是不现实的,太繁琐也没有意义。除非您真的想 自己去做一个 JVM。笔者只列举部分概念:           首先我们来看一个叫做 ReturnAddress 的变量,它是 JVM 用来存储方法出口或者说进 行跳转的依据,把任何地址存入这个变量就一定会按照这个地址来跳转。我们需要注意的就 是 finally 有比方法 return 更高的赋值给 ReturnAddress 的优先级。同时存在方法 return 和 finally  return 的话,一定是按照 finally 里面的 return 为准。                          JVM有自己的 Heap,能被所有线程共享,存储着所有的对象,内存是动态被分配的。 对于每个线程,拥有自己的 Stack,栈里面存储的单位叫做 Frame(桢)。桢里面就记录着 零时变量、对象引用地址、方法返回值等数据。JVM 还有一个叫做 Method       Area 的地 方,存储着一段一段的可执行代码,每一段就是一个方法体,也能被所有线程共享。所以我 们说一个线程其实从 run 方法跑起来,跟它的类中声明的其他方法是两个概念。因为其他的 方法包括的所有的对象,这个时候都充当为资源被线程使用。     
     JVM 有自己管理内存的方案,因为它具有文件系统的功能,我们可以看成一个小型的 数据库,内部有许许多多不同的表。表的字段可能是另外一张表的地址,也可以直接就是一 个存储数据值的地址值。JVM 所有对运行时候类的解析验证计算等管理工作,实际上都是 在管理这些表的变动,如果我们从数据库的角度来看,JVM 所做的就是根据你的代码来操 作那么多个表后返回给你结果的过程。里面的表结构包括 class 的表、field 表、method 表、attribute 表等。     
  4.JVM 的指令集         JVM 有自己的指令集,笔者从前也看过一些计算机组成结构和汇编语言的数,建议大 家也稍微看看,了解设计一个高效可用的计算机指令集是多么复杂又多么重要的过程。对于 JVM 的指令集,职责是管理好 Java 程序编译出来的字节码,相对而言指令集的名称就多少 和 Java 语言相关了,比如指令集里就有 sastore,、saload 表示 array 里面 short 的存和取、 类似还有 d2i 表示从 double 转换成 int、monitorenter 表示进入 synchronized 块加锁、 getstatic 和 putstatic 表示对静态标量的存取、 jsr 和 ret 等跳转指令……    
      为了便于记忆,设计 JVM 指令集的人们约定 f 开头的跟 float 有关,d 跟 double 有关, i 跟 int 有关,s 跟 short 有关,a 跟 array 有关。有兴趣的可以细读文档里面的每一个指令 的作用。因为只是作为初步了解,这里就不多说了。     
 5.一些 Java 关键字的实现原理     
    文档还很详细的列举了很多加载、初始化、加锁等操作的过程。笔者觉得比较有用的第 一是记住 Java 里面只有 Array 不是由 ClassLoader 加载的对象,其他的对象全部都必须由 一个 ClassLoader 来加载。另外 package 的概念除了类似于 C++的 namespace,是一种命 名空间之外,底层的实现是规定同一个 package 下的类必须由同一个类加载器来加载,所 以 package 的概念还可以认为是被同一个类加载器加载的类。                          另外在多线程中,有很多细节值得去体会。每个线程有自己的 Working  memory,它 们从能被共享的 Main  Memory 中去读数据、修改、然后再存回去。笔者一直认为线程就 是数据库里面事务的前身或者说祖先。我们只要稍微比较一下它们的行为,就会发现很多一 致性。事务也是操作被事务共享的表数据,你改完我改,顺序不一致就会出现脏数据,而线 程同样会出现脏数据。我们对线程加的锁策略,同样在事务中也有适用。当然多事务的情况 显然比多线程更加复杂,但我们只要理解了多线程,相信对学习数据库事务的效果也是非常 有帮助的。Java 里面除了 synchronized 能够帮助同步多线程之外,还有一个弱同步的操作 关键字是 volatile,它产生在变量上的约束在文档中也有详细的说明。因为很复杂,考虑到 篇幅笔者就不打算解释一遍了。     
      好了,又是新的一篇结束了。大概再有一两篇笔者大学关于 Java 所学就差不多说完 了。不足之处大家尽管提出来,笔者愿意接受各种职责批评,因为笔者认为失败的教训往往 比成功更加助人成长。这个帖子一直以来得到那么多朋友的大力支持和鼓励,笔者在这里真 诚的说一声谢谢!因为笔者即将毕业投入茫茫人海去从草根阶层开始挣扎,近冷静的想了 很多,即使毕业了,要提高的不止是技术,还包括很多综合素质,也许并不能马上找到如意 的团队和工作岗位,只能承认自己是弱势群体,有时不得不向现实的生活低头,不知道今后 是否还有这闲心去写学习笔记,去坚持走分享的道路。其实很多人我认为也很有心去分享, 但被现实的生活束缚了手脚。所以也期望还呆在学校里的大学生们好好努力的珍惜那份无忧 虑的心境和安静的环境,好好充实自己吧!  Java杂谈(十三)ORM   这是后一篇 Java 杂谈了,以 ORM 框架的谈论收尾,也算是把 J2ee 的后一方面 给涵盖到了,之所以这么晚才总结出 ORM 这方面,一是笔者这两周比较忙,另一方面也想 善始善终,仔细的先自己好好研究一下 ORM 框架技术,不想草率的敷衍了事。   
    其实 J2ee 的规范指南里面就已经包括了一些对象持久化技术,例如 JDO(Java Data Object)就是Java对象持久化的新规范,一个用于存取某种数据仓库中的对象的标准化API, 提供了透明的对象存储,对开发人员来说,存储数据对象完全不需要额外的代码(如 JDBC API 的使用)。这些繁琐的工作已经转移到 JDO 产品提供商身上,使开发人员解脱出来,从 而集中时间和精力在业务逻辑上。另外,JDO 很灵活,因为它可以在任何数据底层上运行。
JDBC 只是面向关系数据库(RDBMS)JDO 更通用,提供到任何数据底层的存储功能,比 如关系数据库、文件、XML 以及对象数据库(ODBMS)等等,使得应用可移植性更强。我 们如果要理解对象持久化技术,首先要问自己一个问题:为什么传统的 JDBC 来持久化不 再能满足大家的需求了呢?   
    笔者认为好是能用JDBC真正编写过程序了才能真正体会ORM的好处,同样的道理, 真正拿 Servlet/Jsp 做过项目了才能体会到 Struts、 Spring 等框架的方便之处。很幸运的是 笔者这两者都曾经经历过,用混乱的内嵌 Java 代码的 Jsp 加 Servlet 转发写过完整的 Web 项目,也用 JDBC 搭建过一个完整 C/S 项目的后台。所以现在接触到新框架才更能体会它 们思想和实现的优越之处,回顾从前的代码,真是丑陋不堪啊。^_^   
    回到正题,我们来研究一下为什么要从 JDBC 发展到 ORM。简单来说,传统的 JDBC 要花大量的重复代码在初始化数据库连接上,每次增删改查都要获得 Connection 对象,初 始化 Statement,执行得到 ResultSet 再封装成自己的 List 或者 Object,这样造成了在每个 数据访问方法中都含有大量冗余重复的代码,考虑到安全性的话,还要加上大量的事务控制 和 log 记录。虽然我们学习了设计模式之后,可以自己定义 Factory 来帮助减少一部分重复 的代码,但是仍然无法避免冗余的问题。其次,随着 OO 思想深入人心,连典型的过程化 语言 Perl 等都冠冕堂皇的加上了 OO 的外壳,何况是 Java 中繁杂的数据库访问持久化技 术呢?强调面向对象编程的结果就是找到一个桥梁,使得关系型数据库存储的数据能准确的 映射到 Java 的对象上,然后针对 Java 对象来设计对象和方法,如果我们把数据库的 Table 当作 Class,Record 当作 Instance 的话,就可以完全用面向对象的思想来编写数据层的代 码。于是乎,Object Relationship Mapping 的概念开始普遍受到重视,尽管很早很早就已经 有人提出来了。      缺点我们已经大概清楚了,那么如何改进呢?对症下药,首先我们要解决的是如何从 Data Schema 准备完美的映射到 Object Schema,另外要提供对数据库连接对象生命周期 的管理,对事务不同粒度的控制和考虑到扩展性后提供对 XML、Properties 等可配置化的 文件的支持。到目前为止,有很多框架和技术在尝试着这样做。例如似乎是封装管理得过了 头的 EJB、很早就出现目前已经不在开发和升级了的 Apache OJB、首先支持 Manual SQL 的 iBATIS,还有公认非常优秀的 Hibernate 等等。在分别介绍它们之前,我还想反复强调 这些框架都在试图做什么:   
    毕竟 Java Object 和数据库的每一条 Record 还是有很大的区别,就是类型上来说,DB 是没有 Boolean 类型的。而 Java 也不得不用封装类(Integer、Double 等)为了能映射上 数据库中为 null 的情况,毕竟 Primitive 类型是没有 null 值的。还有一个比较明显的问题是, 数据库有主键和外键,而 Java 中仍然只能通过基本类型来对应字段值而已,无法规定 Unique 等特征,更别提外键约束、事务控制和级联操作了。另外,通过 Java Object 预设 某 Field 值去取数据库记录,是否在这样的记录也是不能保证的。真的要设计到完全映射的 话,Java 的 Static 被所有对象共享的变量怎么办?在数据库中如何表现出来……  我们能看到大量的问题像一座座大山横在那些框架设计者们面前,他们并不是没有解决办 法,而是从不同的角度去考虑,会得到很多不同的解决方案,问题是应该采取哪一种呢?甚 至只有等到真正设计出来了投入生产使用了,才能印证出当初的设想是否真的能为项目开发 带来更多的益处。笔者引用一份文档中提到一个健壮的持久化框架应该具有的特点:  A robust persistence layer should support----  1. Several types of persistence mechanism  
2. Full encapsulation of the persistence mechanism.  3. Multi-object actions  4. Transactions Control  5. Extensibility  6. Object identifiers  7. Cursors: logical connection to the persistence mechanism  8. Proxies: commonly used when the results of a query are to be displayed in a list  9. Records: avoid the overhead of converting database records to objects and then back to records  10. Multi architecture  11. Various database version and/or vendors  12. Multiple connections  13. Native and non-native drivers  14. Structured query language queries(SQL)   
现在来简短的介绍一下笔者用过的一些持久化框架和技术,之所以前面强调那么多共通 的知识,是希望大家不要盲从流行框架,一定要把握它的本质和卓越的思想好在哪里。   1. Apache OJB    OJB 代表 Apache Object Relational Bridge,是 Apache 开发的一个数据库持久型框架。它 是基于 J2ee 规范指南下的持久型框架技术而设计开发的,例如实现了 ODMG 3.0 规范的 API,实现了 JDO 规范的 API, 核心实现是 Persistence Broker API。OJB 使用 XML 文件 来实现映射并动态的在Metadata layer听过一个Meta-Object-Protocol(MOP)来改变底层数 据的行为。更高级的特点包括对象缓存机制、锁管理机制、 Virtual 代理、事务隔离性级别 等等。举个 OJB Mapping 的简单例子 ojb-repository.xml:   
<class-descriptor class=”com.ant.Employee” table=”EMPLOYEE”>  <field-descriptor name=”id” column=”ID”  jdbc-type=”INTEGER” primarykey=”true” autoincrement=”true”/>   
<field-descriptor name=”name” column=”NAME” jdbc-type=”VARCHAR”/>  </class-descrptor>   
<class-descriptor class=”com.ant.Executive” table=”EXECUTIVE”>  <field-descriptor name=”id” column=”ID”  jdbc-type=”INTEGER” primarykey=”true” autoincrement=”true”/>   
<field-descriptor name=”department” column=”DEPARTMENT” jdbc-type=”VARCHAR”/>   
<reference-descriptor name=”super” class-ref=”com.ant.Employee”>  <foreignkey field-ref=”id”/>  </reference-descriptor>  </class-descrptor>  
 2. iBATIS      iBATIS大的特点就是允许用户自己定义 SQL 来组配 Bean 的属性。因为它的 SQL 语句是直接写入 XML 文件中去的,所以可以大程度上利用到 SQL 语法本身能控制的全 部特性,同时也能允许你使用特定数据库服务器的额外特性,并不局限于类似 SQL92 这样 的标准,它大的缺点是不支持枚举类型的持久化,即把枚举类型的几个对象属性拼成与数 据库一个字段例如 VARCHAR 对应的行为。这里也举一个 Mapping 文件的例子 sqlMap.xml:  <sqlMap>  <typeAlias type=”com.ant.Test” alias=”test”/>   
<resultMap class=”test” id=”result”>  <result property=”testId” column=”TestId”/>  <result property=”name” column=”Name”/>  <result property=”date” column=”Date”/>  </resultMap>   
<select id=”getTestById” resultMap=”result” parameterClass=”int”>  select * from Test where TestId=#value#  </select>   
<update id=”updateTest” parameterClass=”test”>  Update Tests set Name=#name#, Date=”date” where TestId=#testId#  </update>  </sqlMap>   
3. Hibernate      Hibernate 无疑是应用广泛受欢迎的持久型框架,它生成的 SQL 语句是非常优秀。 虽然一度因为不能支持手工 SQL 而性能受到局限,但随着新一代 Hibernate 3.x 推出,很 多缺点都被改进,Hibernate 也因此变得更加通用而时尚。同样先看一个 Mapping 文件的例 子 customer.hbm.xml 来有一个大概印象:   
<hibernate-mapping>  <class name=”com.ant.Customer” table=”Customers”>  <id name=”customerId” column=”CustomerId” type=”int” unsaved-value=”0”>  <generator class=”sequence”>  <param name=”sequence”> Customers_CustomerId_Seq </param>  </generator>  </id>   
<property name=”firstName” column=”FirstName”/>  <property name=”lastName” column=”LastName”/>   
<set name=”addresses” outer-join=”true”>  
<key column=”Customer”/>  <one-to-many class=”com.ant.Address”/>  </set>  </class> </hibernate-mapping>   
    Hibernate 有很多显著的特性,突出的就是它有自己的查询语言叫做 HQL,在 HQL 中 select from 的不是 Table 而是类名,一方面更加面向对象,另外一方面通过在 hibernate.cfg.xml 中配置 Dialect 为 HQL 可以使得整个后台与数据库脱离耦合,因为不管用 那种数据库我都是基于 HQL 来查询,Hibernate 框架负责帮我终转换成特定数据库里的 SQL 语句。另外 Hibernate 在 Object-Caching 这方面也做得相当出色,它同时管理两个级 别的缓存,当数据被第一次取出后,真正使用的时候对象被放在一级缓存管理,这个时候任 何改动都会影响到数据库;而空闲时候会把对象放在二级缓存管理,虽然这个时候与数据库 字段能对应上但未绑定在一起,改动不会影响到数据库的记录,主要目的是为了在重复读取 的时候更快的拿到数据而不用再次请求连接对象。其实关于这种缓存的设计建议大家研究一 下 Oracle 的存储机制(原理是相通的),Oracle 牺牲了空间换来时间依赖于很健壮的缓存 算法来保证优的企业级数据库访问速率。   
    以上是一些 Mapping 的例子,真正在 Java 代码中使用多半是继承各个框架中默认的 Dao 实现类,然后可以通过 Id 来查找对象,或者通过 Example 来查找,更流行的是根据 Criteria 查找对象。Criteria 是完全封装了 SQL 条件查询语法的一个工具类,任何一个查询 条件都可以在 Criteria 中找到方法与之对应,这样可以在 Java 代码级别实现 SQL 的完全控 制。另外,现在许多 ORM 框架的新版本随着 JDk 5.0 加入 Annotation 特性都开始支持用 XDoclet 来自动根据 Annotation 来生成 XML 配置文件了。   
    笔者不可能详细的讲解每一个框架,也许更多的人在用 Hibernate,笔者是从 OJB 开始 接触 ORM 技术的,它很原始却更容易让人理解从 JDBC 到 ORM 的过渡。更多的细节是可 以从官方文档和书籍中学到的,但我们应该更加看中它们设计思想的来源和闪光点,不是盲 从它们的使用方法。  
 到这里全部 Java 杂谈这个主题就正式结束了,笔者也可以长叹一口气,拿到毕设题目 后也该开始忙自己的毕业设计了。近看了很多 Spring  AOP 的资料,实习下班回家还会 研究 Ajax 和极限编程,如果有时间了话,会把自己的学习笔记拿出来分享的。衷心希望大 家都能不断的进步,在喜欢的道路上不回头的走下去……^_^   

你可能感兴趣的:(java)