Recently in Java Category

Eclipse和jbuilder开发全接触






















重点推荐:十四种Java开发工具点评


  在计算机开发语言的历史中,从来没有哪种语言象Java那样受到如此众多厂商的支持,有如此多的开发工具,Java菜鸟们如初入大观园的刘姥姥,看花了眼,不知该何种选择。的确,这些工具各有所长,都没有绝对完美的,就算是老鸟也很难做出选择。本期专题简要介绍了常见的十四种Java开发工具的特点。管中窥豹,虽不是那么全面,期望能对大家有所帮助。[全文阅读















主题社区

  主题社区是我们新开发的板块,在主题社区中网友将讨论更专业的问题,获得更自由的空间。














JBuilder










· E夏Java轻松行 JBuilder 2005全接触
· JBuilder开发常用的十九个快捷键
· JBuilder2005创建开发文档之创建文档
· JBuilder2005创建开发文档之标签介绍
· JBuilder 2005 Struts深度体验
· JBuilder2005 Struts深度体验之升级
· JBuilder 2005 Servlet高级开发
· JBuilder2005 Servlet开发之监听器
· JBuilder 2005 代码重构深度探索
· JBuilder 2005中创建Javadoc文档
· JBuilder2005创建开发文档之编写注释
· JBuilder2005创建开发文档之Javadoc
· JBuilder2005 Struts深度体验之新增
· JBuilder2005 Struts深度体验之概述
· JBuilder2005 Servlet开发之下载型
· JBuilder2005 Servlet开发之过滤器






Eclipse










· 开发不再是苦差事 用Eclipse简化开发
· 浅析Eclipse建模框架(EMF)及其动态能力
· 开发Eclipse下的自定义控件
· 基于Eclipse 3.0的SWT编程
· 扩展Eclipse辅助和规范开发流程
· Eclipse Form程序设计指南之入门
· Eclipse开发工具使用指南
· 利用Eclipse编辑中文资源文件
· Eclipse插件开发如虎添翼
· Eclipse中使用Junit插件测试
· Eclipse3.1中体验J2SE5.0之注释类型
· Eclipse3.1中体验J2SE 5.0之枚举类型
· Eclipse Form设计指南之定制布局
· 品味Eclipse 3.1 中的新特性
· Eclipse中用SWT和JFace开发入门
· Eclipse 平台Java开发入门






其他Java集成开发工具









· 群雄逐鹿 十四种Java开发工具点评
· 十四种Java开发工具点评
· Forte For Java开发指南
· VisualAge for Java开发Servlets








特色专区































hibernate、Spring、Struts编程宝典






















重点推荐:江湖风雨十年灯 七剑与Java开源工具


  开放源码,几乎所有产品都有开放源码的替代产品,这些开放源码项目人员整齐,技术力量雄厚,他们是那些厂商之外的构成Java大厦的另一块重要基石。[全文阅读][















主题社区

  主题社区是我们新开发的板块,在主题社区中网友将讨论更专业的问题,获得更自由的空间。














Hibernate










· Hibernate 3.0 的Formulas编程
· Hibernate 3.0 的规则应用分析
· 利用开源项目Hibernate开发Blog系统
· Hibernate3的DetachedCriteria支持
· Hibernate 3新增XML关系持久性介绍
· Weblogic81和Hibernate 的集成问题
· Struts+Hibernate简化J2EE的文件操作
· 手低眼高 初学者学习Hibernate的方法
· 在Spring中集成Hibernate事务
· 在Weblogic上配置Hibernate为JNDI
· Hibernate配置文件在单元测试中的应用
· Struts+Spring+Hibernate快速入门






Spring










· 利用Spring框架改进J2EE编程
· 在Spring中集成Hibernate事务
· 用Spring framework实现定时器功能
· Struts+Spring+Hibernate快速入门
· Spring 编程入门十大问题解答
· POJO应用架构:Spring与EJB 3.0的对比
· Struts VS Spring 两种MVC框架比较
· 用Spring framework实现定时器功能
· Hibernate+Spring+Struts扩展Struts
· 开发线程安全的Spring Web应用






Struts










· Struts+Hibernate简化J2EE的文件操作
· Struts VS Spring 两种MVC框架比较
· JBuilder2005 Struts深度体验之新增
· JBuilder2005 Struts深度体验之概述
· Hibernate+Spring+Struts扩展Struts
· JSP和Struts解决用户退出问题
· J2EE MVC模式JSF与Struts的异同
· JBuilder 2005 Struts深度体验
· JBuilder2005 Struts深度体验之升级
· Struts+Spring+Hibernate快速入门
· 让Struts与Hibernate顺利协同工作
· Struts+Hibernate中解决汉字编码问题








特色专区































java模式设计全接触























重点推荐:什么是设计模式


  模式理论的基本思想其实起源于中国,是中国文化的固有思想。你,我,我们中每一个自幼受到中国思想熏陶的人,都自然具有这一基本思想。[全文阅读
 

Java中的模式

  世上一直有一个神话:设计可以并且应该独立于实现的细节,设计通常被看作是一个抽象的概念而实现是一个代码的具体实例。[全文阅读]
















主题社区

  主题社区是我们新开发的板块,在主题社区中网友将讨论更专业的问题,获得更自由的空间。














创建模式










· Java模式设计之单例模式(四)
· Java模式设计之单例模式(二)
· 对《Java与模式》中工厂方法模式的异议
· 爪哇语言抽象工厂创立性模式介绍
· Java模式设计之单例模式(三)
· Java模式设计之单例模式(一)
· Java模式设计之多态模式与多语言支持
· 爪哇语言工厂方法创立性模式介绍






结构模式










· 深入浅出Java设计模式之迭代器模式
· Decorator模式中遭遇继承与聚合的冲突
· Java模式研究之Flyweight模式
· 设计模式之Facade(外观)
· 浅议Java设计模式的中介者模式
· 两种设计模式在EJB开发中的应用
· 从重构的角度学习bridge设计模式






行为模式










· 深入浅出Java设计模式之适配器模式
· 深入浅出Java设计模式之备忘录模式
· 深入浅出Java的访问者模式
· Java源码分析:深入探讨Iterator模式
· J2EE MVC模式JSF与Struts的异同
· 深入浅出Java模式设计之模板方法模式
· 深入浅出基于Java的责任链模式
· 通过Java Swing看透MVC设计模式








特色专区































(转)23种设计模式汇集

 

如果你还不了解设计模式是什么的话?


那就先看设计模式引言 !

《Java与模式》的目录

 














1:前言

学习GoF设计模式的重要性


建筑和软件中模式之异同







2:GoF设计模式
A.创建模式









设计模式之Singleton(单态/单件) 阎宏博士讲解:单例(Singleton)模式
保证一个类只有一个实例,并提供一个访问它的全局访问点 2002/10/9更新

设计模式之Factory(工厂方法和抽象工厂)
使用工厂模式就象使用new一样频繁.2002/10/9更新

设计模式之Builder
汽车由车轮 方向盘 发动机很多部件组成,同时,将这些部件组装成汽车也是一件复杂的工作,Builder模式就是将这两种情况分开进行。
设计模式之Prototype(原型)
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

B.结构模式
















设计模式之Adapter(适配器)
使用类再生的两个方式:组合(new)和继承(extends),这个已经在"thinking in java"中提到过.


设计模式之Proxy(代理)
以Jive为例,剖析代理模式在用户级别授权机制上的应用


设计模式之Facade(门面?)
可扩展的使用JDBC针对不同的数据库编程,Facade提供了一种灵活的实现.

设计模式之Composite(组合)
就是将类用树形结构组合成一个单位.你向别人介绍你是某单位,你是单位中的一个元素,别人和你做买卖,相当于和单位做买卖。文章中还对Jive再进行了剖析。
设计模式之Decorator(装饰器)
Decorator是个油漆工,给你的东东的外表刷上美丽的颜色.
设计模式之Bridge(桥连)
将"牛郎织女"分开(本应在一起,分开他们,形成两个接口),在他们之间搭建一个桥(动态的结合)
设计模式之Flyweight(共享元)
提供Java运行性能,降低小而大量重复的类的开销.

C.行为模式























设计模式之Command(命令)
什么是将行为封装,Command是最好的说明.
设计模式之Observer(观察者)
介绍如何使用Java API提供的现成Observer
设计模式之Iterator(迭代器)
这个模式已经被整合入Java的Collection.在大多数场合下无需自己制造一个Iterator,只要将对象装入Collection中,直接使用Iterator进行对象遍历。
设计模式之Template(模板方法)
实际上向你介绍了为什么要使用Java 抽象类,该模式原理简单,使用很普遍.
设计模式之Strategy(策略)
不同算法各自封装,用户端可随意挑选需要的算法.
设计模式之Chain of Responsibility(责任链)
各司其职的类串成一串,好象击鼓传花,当然如果自己能完成,就不要推委给下一个.
设计模式之Mediator(中介)
Mediator很象十字路口的红绿灯,每个车辆只需和红绿灯交互就可以.
设计模式之State(状态)
状态是编程中经常碰到的实例,将状态对象化,设立状态变换器,便可在状态中轻松切换.
设计模式之Memento(注释状态?)
很简单一个模式,就是在内存中保留原来数据的拷贝.
设计模式之Interpreter(解释器)
主要用来对语言的分析,应用机会不多.
设计模式之Visitor(访问者)
访问者在进行访问时,完成一系列实质性操作,而且还可以扩展.






3:其他资料





23种设计模式的java实现(提供源代码)


Thinking in Patterns with Java Thinking in Java的作者Eckel又一著作!


CMSC491D Design Patterns In Java
Overview of Design Patterns 精确定义各个模式以及他们的关系
Design Patterns Java Companion


设计模式在EJB中应用 这是我发表在《程序员》第6期的文章。写得很简单。
EJB设计模式(英文) 从设计模式去理解EJB或J2EE我认为是个非常有效的办法.


 




无所不能的“蚂蚁”--Ant

| 1 Comment

     说他无所不能,好像有点夸张,但是用过Ant之后,感觉真的是只有想不到没有作不到.Ant,原作者选择他作为软件名字的意思是指"令一个简洁的工具"(Another Neat Tool),而这个真正的名字现在去很少为人所知,但这丝毫不影响他成为最优秀的构建工具.

     现在开始我将进入一个"蚂蚁"的世界,通过例子,真真正正去了解他!


     文章参考资料可以到http://www.manning.com/antbook去下载


     Ant的最好学习资料<<使用Ant进行Java开发>>


      Ant的官方网站: http://ant.apache.org/


      Ant的最新版本:Ant 1.6.5


      本文所有的例子运行的环境:JDK1.4.2,Ant1.6.2,eclipse3.0


一.使用Ant运行Java程序


我们先从简单的Hello学起,目录结构如下


project--


             |src--


             |         |--org.ant.chapter1.Hello


             |bin       


             |build.xml


以后的例子大多采用此目录结构,特例会额外声明


build.xml文件






<?xml version="1.0"?>
<project name="project" default="run">
 <target name="compile">
  <javac destdir="bin" srcdir="src"></javac>
 </target>
 
 <target name="run" depends="compile">
  <java classname="org.ant.chapter1.Hello">
  </java>
 </target>
</project>


       从结构来看构建文件很简单,里面的内容大家也一定能够看得懂,可以看出Ant的核心任务就是target,一个Ant文件有多个target组成,而这些target之间,又有相互的依赖关系--depends,运行的时候默认运行project中指定的target.


javac--编译java文件     java--运行java文件


使用eclipse中集成的Ant运行build.xml文件(当然,也可以将ANT_HOME加到Path中,在命令行中运行)






 Buildfile: D:\MyEclipse\workspace\sad\build.xml
compile:
run:
     [java] Working directory ignored when same JVM is used.
     [java] Could not find org.ant.chapter1.Hello. Make sure you have it in your classpath
     [java]  at org.apache.tools.ant.taskdefs.ExecuteJava.execute(ExecuteJava.java:166)
     [java]  at org.apache.tools.ant.taskdefs.Java.run(Java.java:705)
     [java]  at org.apache.tools.ant.taskdefs.Java.executeJava(Java.java:177)
     [java]  at org.apache.tools.ant.taskdefs.Java.execute(Java.java:83)
     [java]  at org.apache.tools.ant.UnknownElement.execute(UnknownElement.java:275)
     [java]  at org.apache.tools.ant.Task.perform(Task.java:364)
     [java]  at org.apache.tools.ant.Target.execute(Target.java:341)
     [java]  at org.apache.tools.ant.Target.performTasks(Target.java:369)
     [java]  at org.apache.tools.ant.Project.executeTarget(Project.java:1214)
     [java]  at org.eclipse.ant.internal.ui.antsupport.InternalAntRunner.run(InternalAntRunner.java:379)
     [java]  at org.eclipse.ant.internal.ui.antsupport.InternalAntRunner.main(InternalAntRunner.java:135)
BUILD SUCCESSFUL
Total time: 703 milliseconds         



      ,java入门的经典错误,ClassNotDefException,可见是classpath设置问题,而观察得到compile成功运行,所以我们在run-target里面加入classpath的配置






<?xml version="1.0"?>
<project name="project" default="run">
 <target name="compile">
  <javac destdir="bin" srcdir="src"></javac>
 </target>
 
 <target name="run" depends="compile">
  <java classname="org.ant.chapter1.Hello">
   <classpath path="bin"></classpath>
  </java>
 </target>
</project>
 


运行






Buildfile: D:\MyEclipse\workspace\sad\build.xml
compile:
run:
     [java] Hello World!
BUILD SUCCESSFUL
Total time: 672 milliseconds



成功!!第一个Ant应用完成,有人会说:用IDE运行岂不是更简单,但是你要知道运行java程序只是Ant的一个小小的功能,后面我们会看到Ant的更强大的功能!



下一篇文章将介绍java程序运行的扩展及用Ant运行tomcat!



一个很不错介绍session的文章

一个很不错介绍session的文章


摘要:虽然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机制本身并不复杂,然而其实现和配置上的灵活性却使得具体情况复杂多变。这也要求我们不能把仅仅某一次的经验或者某一个浏览器,服务器的经验当作普遍适用的经验,而是始终需要具体情况具体分析。


参考文档:
[1] Preliminary Specification http://wp.netscape.com/newsref/std/cookie_spec.html
[2] RFC2109 http://www.rfc-editor.org/rfc/rfc2109.txt
[3] RFC2965 http://www.rfc-editor.org/rfc/rfc2965.txt
[4] The Unofficial Cookie FAQ http://www.cookiecentral.com/faq/
[5] http://e-docs.bea.com/wls/docs70/webapp/weblogic_xml.html#1036869
[6] http://e-docs.bea.com/wls/docs70/webapp/sessions.html#100770
[7] RFC2616 http://www.rfc-editor.org/rfc/rfc2616.txt



java中四种操作xml方式的比较

                 java中四种操作xml方式的比较


1. 介绍
1)DOM(JAXP Crimson解析器)
                   
    DOM是用与平台和语言无关的方式表示XML文档的官方W3C标准。DOM是以层次结构组织的节点或信息片断的集合。这个层次结构允许开发人员在树中寻找特定信息。分析该结构通常需要加载整个文档和构造层次结构,然后才能做任何工作。由于它是基于信息层次的,因而DOM被认为是基于树或基于对象的。DOM以及广义的基于树的处理具有几个优点。首先,由于树在内存中是持久的,因此可以修改它以便应用程序能对数据和结构作出更改。它还可以在任何时候在树中上下导航,而不是像SAX那样是一次性的处理。DOM使用起来也要简单得多。
           
2)SAX
                   
    SAX处理的优点非常类似于流媒体的优点。分析能够立即开始,而不是等待所有的数据被处理。而且,由于应用程序只是在读取数据时检查数据,因此不需要将数据存储在内存中。这对于大型文档来说是个巨大的优点。事实上,应用程序甚至不必解析整个文档;它可以在某个条件得到满足时停止解析。一般来说,SAX还比它的替代者DOM快许多。


    选择DOM还是选择SAX? 对于需要自己编写代码来处理XML文档的开发人员来说,选择DOM还是SAX解析模型是一个非常重要的设计决策。 DOM采用建立树形结构的方式访问XML文档,而SAX采用的事件模型。


    DOM解析器把XML文档转化为一个包含其内容的树,并可以对树进行遍历。用DOM解析模型的优点是编程容易,开发人员只需要调用建树的指令,然后利用navigation APIs访问所需的树节点来完成任务。可以很容易的添加和修改树中的元素。然而由于使用DOM解析器的时候需要处理整个XML文档,所以对性能和内存的要求比较高,尤其是遇到很大的XML文件的时候。由于它的遍历能力,DOM解析器常用于XML文档需要频繁的改变的服务中。


    SAX解析器采用了基于事件的模型,它在解析XML文档的时候可以触发一系列的事件,当发现给定的tag的时候,它可以激活一个回调方法,告诉该方法制定的标签已经找到。SAX对内存的要求通常会比较低,因为它让开发人员自己来决定所要处理的tag。特别是当开发人员只需要处理文档中所包含的部分数据时,SAX这种扩展能力得到了更好的体现。但用SAX解析器的时候编码工作会比较困难,而且很难同时访问同一个文档中的多处不同数据。


3)JDOM http://www.jdom.org
                     
    JDOM的目的是成为Java特定文档模型,它简化与XML的交互并且比使用DOM实现更快。由于是第一个Java特定模型,JDOM一直得到大力推广和促进。正在考虑通过“Java规范请求JSR-102”将它最终用作“Java标准扩展”。从2000年初就已经开始了JDOM开发。


    JDOM与DOM主要有两方面不同。首先,JDOM仅使用具体类而不使用接口。这在某些方面简化了API,但是也限制了灵活性。第二,API大量使用了Collections类,简化了那些已经熟悉这些类的Java开发者的使用。


    JDOM文档声明其目的是“使用20%(或更少)的精力解决80%(或更多)Java/XML问题”(根据学习曲线假定为20%)。JDOM对于大多数Java/XML应用程序来说当然是有用的,并且大多数开发者发现API比DOM容易理解得多。JDOM还包括对程序行为的相当广泛检查以防止用户做任何在XML中无意义的事。然而,它仍需要您充分理解XML以便做一些超出基本的工作(或者甚至理解某些情况下的错误)。这也许是比学习DOM或JDOM接口都更有意义的工作。


    JDOM自身不包含解析器。它通常使用SAX2解析器来解析和验证输入XML文档(尽管它还可以将以前构造的DOM表示作为输入)。它包含一些转换器以将JDOM表示输出成SAX2事件流、DOM模型或XML文本文档。JDOM是在Apache许可证变体下发布的开放源码。
          
4)DOM4J http://dom4j.sourceforge.net
   
    虽然DOM4J代表了完全独立的开发结果,但最初,它是JDOM的一种智能分支。它合并了许多超出基本XML文档表示的功能,包括集成的XPath支持、XML Schema支持以及用于大文档或流化文档的基于事件的处理。它还提供了构建文档表示的选项,它通过DOM4J API和标准DOM接口具有并行访问功能。从2000下半年开始,它就一直处于开发之中。


    为支持所有这些功能,DOM4J使用接口和抽象基本类方法。DOM4J大量使用了API中的Collections类,但是在许多情况下,它还提供一些替代方法以允许更好的性能或更直接的编码方法。直接好处是,虽然DOM4J付出了更复杂的API的代价,但是它提供了比JDOM大得多的灵活性。


    在添加灵活性、XPath集成和对大文档处理的目标时,DOM4J的目标与JDOM是一样的:针对Java开发者的易用性和直观操作。它还致力于成为比JDOM更完整的解决方案,实现在本质上处理所有Java/XML问题的目标。在完成该目标时,它比JDOM更少强调防止不正确的应用程序行为。


    DOM4J是一个非常非常优秀的Java XML API,具有性能优异、功能强大和极端易用使用的特点,同时它也是一个开放源代码的软件。如今你可以看到越来越多的Java软件都在使用DOM4J来读写XML,特别值得一提的是连Sun的JAXM也在用DOM4J。


2. 比较
1)DOM4J性能最好,连Sun的JAXM也在用DOM4J。目前许多开源项目中大量采用DOM4J,例如大名鼎鼎的Hibernate也用DOM4J来读取XML配置文件。如果不考虑可移植性,那就采用DOM4J.


2)JDOM和DOM在性能测试时表现不佳,在测试10M文档时内存溢出。在小文档情况下还值得考虑使用DOM和JDOM。虽然JDOM的开发者已经说明他们期望在正式发行版前专注性能问题,但是从性能观点来看,它确实没有值得推荐之处。另外,DOM仍是一个非常好的选择。DOM实现广泛应用于多种编程语言。它还是许多其它与XML相关的标准的基础,因为它正式获得W3C推荐(与基于非标准的Java模型相对),所以在某些类型的项目中可能也需要它(如在JavaScript中使用DOM)。


3)SAX表现较好,这要依赖于它特定的解析方式-事件驱动。一个SAX检测即将到来的XML流,但并没有载入到内存(当然当XML流被读入时,会有部分文档暂时隐藏在内存中)。


3. 四种xml操作方式的基本使用方法


xml文件:
<?xml version="1.0" encoding="GB2312"?>
  <RESULT>
    <VALUE>
      <NO>A1234</NO>
      <ADDR>四川省XX县XX镇XX路X段XX号</ADDR>
    </VALUE>
    <VALUE>
      <NO>B1234</NO>
      <ADDR>四川省XX市XX乡XX村XX组</ADDR>
    </VALUE>
  </RESULT>


1)DOM
import java.io.*;
import java.util.*;
import org.w3c.dom.*;
import javax.xml.parsers.*;
public class MyXMLReader{
  public static void main(String arge[]){
    long lasting =System.currentTimeMillis();
    try{ 
         File f=new File("data_10k.xml");
         DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance();
         DocumentBuilder builder=factory.newDocumentBuilder();
         Document doc = builder.parse(f);
         NodeList nl = doc.getElementsByTagName("VALUE");
         for (int i=0;i<nl.getLength();i++){
           System.out.print("车牌号码:" +                                     doc.getElementsByTagName("NO").item(i).getFirstChild().getNodeValue());
           System.out.println("车主地址:" +
                  doc.getElementsByTagName("ADDR").item(i).getFirstChild().getNodeValue());
         }
     }catch(Exception e){
        e.printStackTrace();
     }
  }
}


2)SAX
import org.xml.sax.*;
import org.xml.sax.helpers.*;
import javax.xml.parsers.*;
public class MyXMLReader extends DefaultHandler {
  java.util.Stack tags = new java.util.Stack();
  public MyXMLReader() {
    super();
  }
  public static void main(String args[]) {
    long lasting = System.currentTimeMillis();
    try {
           SAXParserFactory sf = SAXParserFactory.newInstance();
           SAXParser sp = sf.newSAXParser();
           MyXMLReader reader = new MyXMLReader();
           sp.parse(new InputSource("data_10k.xml"), reader);
         } catch (Exception e) {
            e.printStackTrace();
         }
    System.out.println("运行时间:" + (System.currentTimeMillis() - lasting) + "毫秒");
  }
  public void characters(char ch[], int start, int length) throws SAXException {
    String tag = (String) tags.peek();
    if (tag.equals("NO")) { 
       System.out.print("车牌号码:" + new String(ch, start, length));
    }
    if (tag.equals("ADDR")) {
       System.out.println("地址:" + new String(ch, start, length));
    }
  }
  public void startElement(String uri,String localName,String qName,Attributes attrs) {
    tags.push(qName);
  }


3) JDOM
import java.io.*;
import java.util.*;
import org.jdom.*;
import org.jdom.input.*;
public class MyXMLReader {
  public static void main(String arge[]) {
    long lasting = System.currentTimeMillis();
    try {
       SAXBuilder builder = new SAXBuilder(); 
       Document doc = builder.build(new File("data_10k.xml")); 
       Element foo = doc.getRootElement(); 
       List allChildren = foo.getChildren(); 
       for(int i=0;i<allChildren.size();i++) { 
         System.out.print("车牌号码:" + ((Element)allChildren.get(i)).getChild("NO").getText());
         System.out.println("车主地址:" + ((Element)allChildren.get(i)).getChild("ADDR").getText());
       }
     } catch (Exception e) {
         e.printStackTrace();
     }
  }
}


4)DOM4J
import java.io.*;
import java.util.*;
import org.dom4j.*;
import org.dom4j.io.*;
public class MyXMLReader {
  public static void main(String arge[]) {
    long lasting = System.currentTimeMillis();
    try {
          File f = new File("data_10k.xml");
          SAXReader reader = new SAXReader();
          Document doc = reader.read(f);
          Element root = doc.getRootElement();
          Element foo;
          for (Iterator i = root.elementIterator("VALUE"); i.hasNext();) {
              foo = (Element) i.next();
              System.out.print("车牌号码:" + foo.elementText("NO"));
              System.out.println("车主地址:" + foo.elementText("ADDR"));
          }
     } catch (Exception e) {
        e.printStackTrace();
     }
  }
}


作者Blog:http://blog.csdn.net/best2010/

深入理解 abstract class 和 interface

邓辉 、孙鸣(dhui@263.net)



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在语法定义层面更多的细节问题,不是本文的重点,不再赘述,读者可以参阅参考文献〔1〕获得更多的相关内容。


从编程层面看abstract class和interface


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


参考文献
[1] Thinking in Java, Bruce Eckel
[2] Design Patterns Explained: A New Perspective on Object-Oriented Design, Alan Shalloway
and James R. Trott
[3] Effective C++: 50 Specific Ways to Improve Your Programs and Design, Scott Meyers



关于作者

邓辉,软件工程师,主要兴趣在OO、Generic Programming。可以通过dhui@263.net联系到作者。
孙鸣,软件工程师,目前在一个大型通信公司从事数据网管的开发,主要兴趣在Java和数据库。可以通过dhui@263.net联系到作者。

使用SMTP协议发送邮件

使用SMTP协议发送邮件,可以不通过SMTP服务器,直接将邮件发送到邮件服务器。很多服务器端程序可能需要向很多用户发送邮件,直接通过SMTP发送可能是最有效的。

关于SMTP协议定义在RFC821,可以在此看中文版

第一步:通过目标email查找邮件服务器。
例如:asklxf@sohu.com,其邮件服务器地址为:sohumx.sohu.com



import java.net.*;
import java.io.*;
import java.util.*;
import javax.naming.*;
import javax.naming.directory.*;


public class Smtp {


    public static void main(String[] args) throws Exception {
        // DNS服务器,看看本机的DNS配置
        String dns = "dns://192.168.1.1";
        // 邮箱后缀:
        String domain = "sohu.com";
        Hashtable env = new Hashtable();
        env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory");
        env.put("java.naming.provider.url", dns);
        DirContext ctx = new InitialDirContext(env);
        Attributes attr = ctx.getAttributes(domain, new String[]{"MX" });
        NamingEnumeration servers = attr.getAll();
        // 列出所有邮件服务器:
        while(servers.hasMore()) {
            System.out.println(servers.next());
        }
    }
}


第二步:直接连接邮件服务器的25端口,用SMTP协议发送邮件。
这里使用sohu信箱,邮件服务器为sohumx.sohu.com,收信人必须在此服务器上:



import java.net.*;
import java.io.*;
import java.util.*;
import javax.naming.*;
import javax.naming.directory.*;


public class Smtp {


    private static String END_FLAG = "\r\n";


    public static void main(String[] args) throws Exception {
        String mx = "sohumx.sohu.com";
        InetAddress addr = InetAddress.getByName(mx);
        Socket socket = new Socket(addr, 25);


        InputStream in = socket.getInputStream();
        OutputStream out = socket.getOutputStream();


        // 连接成功后服务器会响应:
        response(in);


        // 首先发送HELO命令:
        send("HELO
www.javasprite.com" + END_FLAG, out);
        response(in);


        // 然后发送发件人地址:
        send("MAIL FROM: someone@somewhere.com" + END_FLAG, out);
        response(in);


        // 设置收件人地址:
        send("RCPT TO: asklxf@sohu.com" + END_FLAG, out);
        response(in);


        // 开始发送邮件正文:
        send("DATA" + END_FLAG, out);
        response(in);


        send("From: someone@somewhere.com" + END_FLAG, out);
        send("To: asklxf@sohu.com" + END_FLAG, out);
        send("Subject: Test without smtp server" + END_FLAG, out);
        send("Content-Type: text/plain;" + END_FLAG, out);
        send(END_FLAG + END_FLAG, out);


        // 发送邮件正文,如果用中文,需要BASE64编码:
        send("text message body!" + END_FLAG, out);
        // 每行以\r\n结束,不可过长,可拆成多行。


        // 以"\r\n.\r\n"作为结束标志:
        send(END_FLAG + "." + END_FLAG, out);
        response(in);


        // 结束并确认发送:
        send("QUIT" + END_FLAG, out);
        response(in);
        in.close();
        out.close();
        socket.close();
    }


    public static void response(InputStream in) throws Exception {
        byte[] buffer = new byte[1024];
        int n = in.read(buffer);
        String s = new String(buffer, 0, n);
        // 服务器会返回:### Text
        // 具体含义见RFC821
        System.out.println(s);
    }


    public static void send(String s, OutputStream out) throws Exception {
        byte[] buffer = s.getBytes();
        out.write(buffer);
        // 不要忘了flush(),否则可能在缓冲区:
        out.flush();
    }
}


Ok,打开outlook收信,会发现有一封来自someone@somewhere.com的信件。

第三步:处理服务器返回码,各种异常,包装成Java组件以便重用:



public interface SendMail {
    void send(String from, String to, String subject, String text)
}

public class SendMailImpl extends Thread implements SendMail {
    // TODO: 自己写......
}

Java的ClassLoader与Package机制

为了深入了解Java的ClassLoader机制,我们先来做以下实验:



package java.lang;
public class Test {
    public static void main(String[] args) {
        char[] c = "1234567890".toCharArray();
        String s = new String(0, 10, c);
    }
}


String类有一个Package权限的构造函数String(int offset, int length, char[] array),按照默认的访问权限,由于Test属于java.lang包,因此理论上应该可以访问String的这个构造函数。编译通过!执行时结果如下:



Exception in thread "main" java.lang.SecurityException: Prohibited package name:
 java.lang
        at java.lang.ClassLoader.defineClass(Unknown Source)
        at java.security.SecureClassLoader.defineClass(Unknown Source)
        at java.net.URLClassLoader.defineClass(Unknown Source)
        at java.net.URLClassLoader.access$100(Unknown Source)
        at java.net.URLClassLoader$1.run(Unknown Source)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        at java.lang.ClassLoader.loadClassInternal(Unknown Source)


奇怪吧?要弄清为什么会有SecurityException,就必须搞清楚ClassLoader的机制。


Java的ClassLoader就是用来动态装载class的,ClassLoader对一个class只会装载一次,JVM使用的ClassLoader一共有4种:


启动类装载器,标准扩展类装载器,类路径装载器网络类装载器


这4种ClassLoader的优先级依次从高到低,使用所谓的“双亲委派模型”。确切地说,如果一个网络类装载器被请求装载一个java.lang.Integer,它会首先把请求发送给上一级的类路径装载器,如果返回已装载,则网络类装载器将不会装载这个java.lang.Integer,如果上一级的类路径装载器返回未装载,它才会装载java.lang.Integer。


类似的,类路径装载器收到请求后(无论是直接请求装载还是下一级的ClassLoader上传的请求),它也会先把请求发送到上一级的标准扩展类装载器,这样一层一层上传,于是启动类装载器优先级最高,如果它按照自己的方式找到了java.lang.Integer,则下面的ClassLoader都不能再装载java.lang.Integer,尽管你自己写了一个java.lang.Integer,试图取代核心库的java.lang.Integer是不可能的,因为自己写的这个类根本无法被下层的ClassLoader装载。


再说说Package权限。Java语言规定,在同一个包中的class,如果没有修饰符,默认为Package权限,包内的class都可以访问。但是这还不够准确。确切的说,只有由同一个ClassLoader装载的class才具有以上的Package权限。比如启动类装载器装载了java.lang.String,类路径装载器装载了我们自己写的java.lang.Test,它们不能互相访问对方具有Package权限的方法。这样就阻止了恶意代码访问核心类的Package权限方法。

About this Archive

This page is a archive of recent entries in the Java category.

.Net is the previous category.

Javascript is the next category.

Find recent content on the main index or look in the archives to find all content.