憨厚生

----Java's Slave----
***Java's Host***

  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  165 随笔 :: 17 文章 :: 90 评论 :: 0 Trackbacks

2009年2月17日 #

【转】http://www.cnblogs.com/myssh/archive/2009/12/18/1627368.html

在《Pragmatic AJAX中文问题 A Web 2.0 Primer 》中偶然看到对readyStae状态的介绍,感觉这个介绍很实在,摘译如下:

 0: (Uninitialized) the send( ) method has not yet been invoked. 
 1: (Loading) the send( ) method has been invoked, request in progress. 
 2: (Loaded) the send( ) method has completed, entire response received.
 3: (Interactive) the response is being parsed. 
 4: (Completed) the response has been parsed, is ready for harvesting. 

 0 - (未初始化)还没有调用send()方法
 1 - (载入)已调用send()方法,正在发送请求
 2 - (载入完成)send()方法执行完成,已经接收到全部响应内容
 3 - (交互)正在解析响应内容
 4 - (完成)响应内容解析完成,可以在客户端调用了

对于readyState的这五种状态,其他书中大都语焉不详。像《Foundations of AJAX中文问题》中,只在书中的表2-2简单地列举了状态的“名称”--The state of the request. The five possible values are 0 = uninitialized, 1 = loading, 2 = loaded, 3 = interactive, and 4 = complete。而《Ajax in Action》中好像根本就没有提到这5种状态的细节。《Professional AJAX中文问题》中虽不尽人意,但还是有可取之处:

There are five possible values for readyState: 
0 (Uninitialized): The object has been created but the open() method hasn’t been called. 
1 (Loading): The open() method has been called but the request hasn’t been sent. 
2 (Loaded): The request has been sent. 
3 (Interactive). A partial response has been received. 
4 (Complete): All data has been received and the connection has been closed. 

readyState有五种可能的值:
0 (未初始化): (XMLHttpRequest)对象已经创建,但还没有调用open()方法。
1 (载入):已经调用open() 方法,但尚未发送请求。
2 (载入完成): 请求已经发送完成。
3 (交互):可以接收到部分响应数据。
4 (完成):已经接收到了全部数据,并且连接已经关闭。

在《Understanding AJAX中文问题: Using JavaScript to Create Rich Internet Applications》中,则用下表进行了说明:

readyState Status Code

Status of the XMLHttpRequest Object
(0) UNINITIALIZED
未初始化
The object has been created but not initialized. (The open method has not been called.)
(XMLHttpRequest)对象已经创建,但尚未初始化(还没有调用open方法)。
(1) LOADING
载入
The object has been created, but the send method has not been called.
(XMLHttpRequest)对象已经创建,但尚未调用send方法。
(2) LOADED
载入完成
The send method has been called, but the status and headers are not yet available.
已经调用send方法,(HTTP响应)状态及头部还不可用。
(3) INTERACTIVE
交互
Some data has been received. Calling the responseBody and responseText properties at this state to obtain partial results will return an error, because status and response headers are not fully available.
已经接收部分数据。但若在此时调用responseBody和responseText属性获取部分结果将会产生错误,因为状态和响应头部还不完全可用。
(4) COMPLETED
完成
All the data has been received, and the complete data is available in the responseBody and responseText properties.
已经接收到了全部数据,并且在responseBody和responseText属性中可以提取到完整的数据。

根据以上几本书中的关于readyState五种状态的介绍,我认为还是《Pragmatic AJAX中文问题 A Web 2.0 Primer 》比较到位,因为它提到了对接收到的数据的解析问题,其他书中都没有提到这一点,而这一点正是“(3)交互”阶段作为一个必要的转换过程存在于“(2)载入完成”到“(4)完成”之间的理由,也就是其任务是什么。归结起来,我觉得比较理想的解释方法应该以“状态:任务(目标)+过程+表现(或特征)”表达模式来对这几个状态进行定义比较准确,而且让人容易理解。现试总结如下:

readyState 状态

状态说明

(0)未初始化

此阶段确认XMLHttpRequest对象是否创建,并为调用open()方法进行未初始化作好准备。值为0表示对象已经存在,否则浏览器会报错--对象不存在。

(1)载入

此阶段对XMLHttpRequest对象进行初始化,即调用open()方法,根据参数(method,url,true)完成对象状态的设置。并调用send()方法开始向服务端发送请求。值为1表示正在向服务端发送请求。

(2)载入完成

此阶段接收服务器端的响应数据。但获得的还只是服务端响应的原始数据,并不能直接在客户端使用。值为2表示已经接收完全部响应数据。并为下一阶段对数据解析作好准备。

(3)交互

此阶段解析接收到的服务器端响应数据。即根据服务器端响应头部返回的MIME类型把数据转换成能通过responseBody、responseText或responseXML属性存取的格式,为在客户端调用作好准备。状态3表示正在解析数据。

(4)完成

此阶段确认全部数据都已经解析为客户端可用的格式,解析已经完成。值为4表示数据解析完毕,可以通过XMLHttpRequest对象的相应属性取得数据。

概而括之,整个XMLHttpRequest对象的生命周期应该包含如下阶段:
创建-初始化请求-发送请求-接收数据-解析数据-完成

在具体应用中,明确了readyState的五个状态(XMLHttpRequest对象的生命周期各个阶段)的含义,就可以消除对Ajax核心的神秘感(语焉不详的背后要么是故弄玄虚,制造神秘感;要么就是“以其昏昏,使人昭昭”),迅速把握其实质,对减少学习中的挫折感和增强自信心都极其有益。

比如,通过如下示例:

//声明数组 var states = [“正在初始化……”, “正在初始化请求……成功! 正在发送请求……”, “成功! 正在接收数据……”, “完成! 正在解析数据……”, “完成! ”]; //回调函数内部代码片段 if (xmlHttp.readyState==4) { var span = document.createElement(“span”); span.innerHTML = states[xmlHttp.readyState]; document.body.appendChild(span); if (xmlHttp.status == 200) { var xmldoc = xmlHttp.responseXML; //其他代码 } //别忘记销毁,防止内存泄漏 xmlHttp = null; }else{ var span = document.createElement(“span”); span.innerHTML = states[xmlHttp.readyState]; document.body.appendChild(span); }

结果如下:

正在初始化请求……成功!
正在发送请求……成功!
正在接收数据……完成!
正在解析数据……完成!

我们很容易明白XMLHttpRequest对象在各个阶段都在做什么。因此,也就很容易对Ajax的核心部分有一个真正简单明了的理解。

本博PS:readyState一般用在异步请求时程序响应的判断,Iframe, javaScript脚本同样适用,参考另一篇文章:http://d-tune.javaeye.com/blog/506074

文章出处:http://www.cn-cuckoo.com/2007/07/16/the-details-for-five-states-of-readystate-9.html

posted @ 2010-06-07 09:19 二胡 阅读(617) | 评论 (2)编辑 收藏

历史 
    CVS 诞生于 1986 年,当时作为一组 shell 脚本而出现;1989年3月,Brian Berlinor用C语言重新设计并编写了CVS的代码;1993年前后,Jim Kingdon最终将CVS设计成基于网络的平台,开发者们能从Internet任何地方获得程序源代码。截至目前最新版本是2004年12月13日发布的1.12.11。 

功能介绍 
一、 代码统一管理,保存所有代码文件更改的历史记录。对代码进行集中统一管理,可以方便查看新增或删除的文件,能够跟踪所有代码改动痕迹。可以随意恢复到以前任意一个历史版本。并避免了因为版本不同引入的深层BUG。 
二、 完善的冲突解决方案,可以方便的解决文件冲突问题,而不需要借助其它的文件比较工具和手工的粘贴复制。 
三、 代码权限的管理。可以为不同的用户设置不同的权限。可以设置访问用户的密码、只读、修改等权限,而且通过CVS ROOT目录下的脚本,提供了相应功能扩充的接口,不但可以完成精细的权限控制,还能完成更加个性化的功能。 
四、 支持方便的版本发布和分支功能。

基本概念 
资源库(Repository)
 
CVS的资源库存储全部的版本控制下的文件copy,通常不容许直接访问,只能通过cvs命令,获得一份本地copy,改动后再check in(commit)回资源库。而资源库通常为与工作目录分离的。CVS通过多种方式访问资源库。每种方法有不同目录表示形式。 
版本(Revision) 
每一个文件的各个版本都不相同,形如1.1, 1.2.1,一般1.1是该文件的第一个revision,后面的一个将自动增加最右面的一个整数,比如1.2, 1.3, 1.4...有时候会出现1.3.2.2,原因见后。revision总是偶数个数字。一般情况下将revision看作时CVS自己内部的一个编号,而tag则可以标志用户的特定信息。 
标签(Tag) 
用符号化的表示方法标志文件特定revision的信息。通常不需要对某一个孤立的文件作tag,而是对所有文件同时作一个tag,以后用户可以仅向特定tag的文件提交或者checkout。另外一个作用是在发布软件的时候表示哪些文件及其哪个版本是可用的;各文件不同revision可以包括在一个tag中。如果命名一个已存在的tag默认将不会覆盖原来的; 
分支(Branch) 
当用户修改一个branch时不会对另外的branch产生任何影响。可以在适当的时候通过合并的方法将两个版本合起来;branch总是在当前revision后面加上一个偶数整数(从2开始,到0结束),所以branch总是奇数个数字,比如1.2后面branch为1.2.2,该分支下revision可能为1.2.2.1,1.2.2.2,... 
冲突(Conflct) 
完全是纯文本的冲突,不包含逻辑上的矛盾。一般是一份文件,A做了改动,B在A提交之前也做了改动,这样最后谁commit就会出现冲突,需要手工解决冲突再提交。 

CVS与eclipse集成开发 
  前面对CVS的历史、功能、概论等理论知识做了介绍。下面我们将使用最流行的Java IDE Eclipse中内置的CVS工具,以一个完整开发流程,介绍实际环境中CVS的正确使用。关于CVS系统的安装,不是本文的内容,您可以从附录的链接中获取安装的介绍资料。 

常用的CVS控制命令 
Check Out(检出) 
把源文件从cvs源代码仓库中取出,缺省的版本是最新的版本,你也可以选择指定的版本。在每次更改源代码之前,需要Check Out最新的版本,再起基础之上对源代码进行修改。将代码目录checkout到指定目录下,所有文件都是read-write。 
Check In(检入) 
把源代码加入到cvs源代码仓库中,每一个添加进代码库中的文件的版本是 1.1。以后每次修改文件重新ci以后,此文件的版本递增为1.2 ,1.3.……。在每次对源代码修改之后,需要Check In,提交最新版本的源代码。 
Synchronize with Repository(与资源库同步,简称同步) 
使本地更改与资源库同步,它会列出本地和资源库之间不同的所有文件。 
Add to Version Control 
将新的文件加入到版本控制之中。 
Add to .cvsIgnore 
将文件设置到版本控制之外,这样该文件或目录中的文件的更改在CVS中不可见,即使同步也无法发现。

CVS正确使用步骤 
一、 同步(Synchronize)
 
就是将本地更改与服务器同步,同步之后可以清晰的看到上一捡出(Check Out)版本之后本地、服务器上的最新改动。这是非常有用的,特别是敏捷开发,强调集体拥有代码。有了同步功能,你可以全局把握项目的代码,可以很方便的跟踪公共模块代码的任何改动。 
具体操作:在Eclipse的资源视图(Resource Perspective)或者Java视图(Java Perspective)中,选中要同步的目录,点击右键选择"Synchronize with Repository",之后它将显示同步的视图。如下图: 

(图一、CVS同步视图) 
同步之后,它有四种Mode可以选择,见上图绿色框框里按钮。从做到右分别为: 
Incoming Mode:表示修改是来自服务器,对应于更新(update)操作。 
Outgoing Mode:表示修改是来自本地,对应提交(commit)操作。 
Incoming/ Outgoing Mode:本地和服务器修改都在该模式(Mode)中显示。 
Conflicts Mode:显示本地和服务器修改的冲突文件。 
二、 更新(update) 
比较简单,选择Incoming Mode,再选中要更新的文件,右键选择update操作。 
三、 解决冲突并合并(solve conflct and merge) 
如果有冲突文件,冲突文件不能更新。你必须先解决冲突再操作。选中冲突的文件,再点右键选择"Open in Compare Editor",用比较工具打开该文件。如下图: 

(图二、CVS比较器视图)

比较器(Compare)视图,左边版本底的是本地文件(Local File),右边是远程服务器文件(Remote File)。使用"Select Next Change"按钮(绿框中的第一箭头向下按钮),逐一查看不同点。如果不同点标识为黑色框框,则不用管它。如果是蓝色框框,则需要手工调整。如上图,不同点是蓝色框框,将鼠标放到两个不同点的中间小方框中,则凸出一个向右的按钮,并显示提示信息"Copy Current Change from Right to Left",意思是将右边服务器的不同点覆盖到左边的本地文件。点中此按钮。重复这样的操作,将所有服务器上的更改拷贝到本地。 
如果有一行代码,本地和服务器都同时做了修改。这时,修改点则显示红色框框。这时,你就必须手工做正确的修改。全部修改完成,保存本地文件。 
此时,如果修改点没有了蓝色的框框,就可以开始做合并(merge)操作了。操作也很简单,选择该文件,点击右键,选择"Mark as merged"。 
注意:必须确保没有蓝色框框,即完全拷贝了服务器的修改才可以做合并(merge)操作,否则会覆盖服务器上的代码。 
四、 提交(commit) 
更新服务器代码,解决冲突之后,首先要查看本地文件修改之后是否有错误。如果有,当然首先解决错误,再提交。 

posted @ 2010-03-30 09:48 二胡 阅读(511) | 评论 (0)编辑 收藏

转  http://blog.cnw.com.cn/index.php/20937/viewspace-3418

HTTP头字段包括4类:
       general-header ;
     request-header ;
       response-header ;
     entity-header .
 
*******************************************************************************
 General Header Fields
=============================
   general header是request、response都可用的, 但是不能用于entity.
 
 
       -- Cache-Control
       -- Connection
       -- Date
       -- Pragma
       -- Trailer
       -- Transfer-Encoding
       -- Upgrade
       -- Via
       -- Warning
 
*******************************************************************************
 Request Header Fields
======================
 
   request-header fields 允许客户端传递关于request和客户端的附加信息到服务端,
 
       -- Accept
       -- Accept-Charset
       -- Accept-Encoding
       -- Accept-Language
       -- Authorization
       -- Expect
       -- From
       -- Host
       -- If-Match
       -- If-Modified-Since
       -- If-None-Match
       -- If-Range
       -- If-Unmodified-Since
       -- Max-Forwards
       -- Proxy-Authorization
       -- Range
       -- Referer
       -- TE
       -- User-Agent
 
*******************************************************************************
  Response Header Fields
===============================
 
   response-header fields 允许服务端传递关于response的、不能放到Status-Line的附加信息。
   这些头给出关于服务端的信息。 
 
      -- Accept-Ranges
      -- Age
      -- ETag
      -- Location
      -- Proxy-Authenticate
      -- Retry-After
      -- Server
      -- Vary
      -- WWW-Authenticate
 
*******************************************************************************
 Entity Header Fields
========================
 
   Entity-header fields 定义关于entity-body的metainformation(标题字段数据),
   如果当前没有body, 则定义被request确定的资源信息.
   一些metainformation是可选的; 一些是必须的。
 
       -- Allow
       -- Content-Encoding
       -- Content-Language
       -- Content-Length
       -- Content-Location
       -- Content-MD5
       -- Content-Range
       -- Content-Type
       -- Expires
       -- Last-Modified
       -- extension-header


【转自】http://www.x5dj.com/userforum/00100239/00305167.shtml


一、基础篇
HTTP(HyperTextTransferProtocol)是超文本传输协议的缩写,它用于传送WWW方式的数据,关于HTTP协议的详细内容请参考RFC2616。HTTP协议采用了请求/响应模型。客户端向服务器发送一个请求,请求头包含请求的方法、URI、协议版本、以及包含请求修饰符、客户信息和内容的类似于MIME的消息结构。服务器以一个状态行作为响应,相应的内容包括消息协议的版本,成功或者错误编码加上包含服务器信息、实体元信息以及可能的实体内容。
通常HTTP消息包括客户机向服务器的请求消息和服务器向客户机的响应消息。这两种类型的消息由一个起始行,一个或者多个头域,一个只是头域结束的空行和可选的消息体组成。HTTP的头域包括通用头,请求头,响应头和实体头四个部分。每个头域由一个域名,冒号(:)和域值三部分组成。域名是大小写无关的,域值前可以添加任何数量的空格符,头域可以被扩展为多行,在每行开始处,使用至少一个空格或制表符。
1、通用头域
通用头域包含请求和响应消息都支持的头域,通用头域包含Cache-Control、Connection、Date、Pragma、Transfer-Encoding、Upgrade、Via。对通用头域的扩展要求通讯双方都支持此扩展,如果存在不支持的通用头域,一般将会作为实体头域处理。下面简单介绍几个在UPnP消息中使用的通用头域。
Cache-Control头域
Cache-Control指定请求和响应遵循的缓存机制。在请求消息或响应消息中设置Cache-Control并不会修改另一个消息处理过程中的缓存处理过程。请求时的缓存指令包括no-cache、no- store、max-age、max-stale、min-fresh、only-if-cached,响应消息中的指令包括public、 private、no-cache、no-store、no-transform、must-revalidate、proxy-revalidate、 max-age。各个消息中的指令含义如下:
Public指示响应可被任何缓存区缓存。
Private指示对于单个用户的整个或部分响应消息,不能被共享缓存处理。这允许服务器仅仅描述当用户的部分响应消息,此响应消息对于其他用户的请求无效。
no-cache指示请求或响应消息不能缓存
no-store用于防止重要的信息被无意的发布。在请求消息中发送将使得请求和响应消息都不使用缓存。
max-age指示客户机可以接收生存期不大于指定时间(以秒为单位)的响应。
min-fresh指示客户机可以接收响应时间小于当前时间加上指定时间的响应。
max-stale指示客户机可以接收超出超时期间的响应消息。如果指定max-stale消息的值,那么客户机可以接收超出超时期指定值之内的响应消息。
Date头域
Date头域表示消息发送的时间,时间的描述格式由rfc822定义。例如,Date:Mon,31Dec200104:25:57GMT。Date描述的时间表示世界标准时,换算成本地时间,需要知道用户所在的时区。
Pragma头域
Pragma头域用来包含实现特定的指令,最常用的是Pragma:no-cache。在HTTP/1.1协议中,它的含义和Cache-Control:no-cache相同。
2、请求消息
请求消息的第一行为下面的格式:
Method SP Request-URI SP HTTP-Version CRLF 
Method表示对于Request-URI完成的方法,这个字段是大小写敏感的,包括OPTIONS、GET、HEAD、POST、PUT、DELETE、TRACE。方法GET和HEAD应该被所有的通用WEB服务器支持,其他所有方法的实现是可选的。GET方法取回由Request-URI标识的信息。HEAD方法也是取回由Request-URI标识的信息,只是可以在响应时,不返回消息体。POST方法可以请求服务器接收包含在请求中的实体信息,可以用于提交表单,向新闻组、BBS、邮件群组和数据库发送消息。
SP表示空格。
Request-URI遵循URI格式,在此字段为星号(*)时,说明请求并不用于某个特定的资源地址,而是用于服务器本身。
HTTP-Version表示支持的HTTP版本,例如为HTTP/1.1。
CRLF表示换行回车符。
请求头域允许客户端向服务器传递关于请求或者关于客户机的附加信息。请求头域可能包含下列字段Accept、Accept-Charset、Accept- Encoding、Accept-Language、Authorization、From、Host、If-Modified-Since、If- Match、If-None-Match、If-Range、If-Range、If-Unmodified-Since、Max-Forwards、 Proxy-Authorization、Range、Referer、User-Agent。对请求头域的扩展要求通讯双方都支持,如果存在不支持的请求头域,一般将会作为实体头域处理。
典型的请求消息:
GEThttp://class/download.microtool.de:80/somedata.exe
Host:download.microtool.de
Accept:*/*
Pragma:no-cache
Cache-Control:no-cache
Referer:http://class/download.microtool.de/
User-Agent:Mozilla/4.04[en](Win95;I;Nav)
Range:bytes=554554-
上例第一行表示HTTP客户端(可能是浏览器、下载程序)通过GET方法获得指定URL下的文件。棕色的部分表示请求头域的信息,绿色的部分表示通用头部分。
Host头域
Host头域指定请求资源的Intenet主机和端口号,必须表示请求url的原始服务器或网关的位置。HTTP/1.1请求必须包含主机头域,否则系统会以400状态码返回。
Referer头域
Referer头域允许客户端指定请求uri的源资源地址,这可以允许服务器生成回退链表,可用来登陆、优化cache等。他也允许废除的或错误的连接由于维护的目的被追踪。如果请求的uri没有自己的uri地址,Referer不能被发送。如果指定的是部分uri地址,则此地址应该是一个相对地址。
Range头域
Range头域可以请求实体的一个或者多个子范围。例如,
表示头500个字节:bytes=0-499
表示第二个500字节:bytes=500-999
表示最后500个字节:bytes=-500
表示500字节以后的范围:bytes=500-
第一个和最后一个字节:bytes=0-0,-1
同时指定几个范围:bytes=500-600,601-999
但是服务器可以忽略此请求头,如果无条件GET包含Range请求头,响应会以状态码206(PartialContent)返回而不是以200(OK)。
User-Agent头域
User-Agent头域的内容包含发出请求的用户信息。

3、响应消息
响应消息的第一行为下面的格式:
HTTP-Version SP Status-Code SP Reason-Phrase CRLF
HTTP-Version表示支持的HTTP版本,例如为HTTP/1.1。
Status-Code是一个三个数字的结果代码。
Reason-Phrase给Status-Code提供一个简单的文本描述。Status-Code主要用于机器自动识别,Reason-Phrase主要用于帮助用户理解。Status-Code的第一个数字定义响应的类别,后两个数字没有分类的作用。第一个数字可能取5个不同的值:
1xx:信息响应类,表示接收到请求并且继续处理
2xx:处理成功响应类,表示动作被成功接收、理解和接受
3xx:重定向响应类,为了完成指定的动作,必须接受进一步处理
4xx:客户端错误,客户请求包含语法错误或者是不能正确执行
5xx:服务端错误,服务器不能正确执行一个正确的请求
响应头域允许服务器传递不能放在状态行的附加信息,这些域主要描述服务器的信息和Request-URI进一步的信息。响应头域包含Age、 Location、Proxy-Authenticate、Public、Retry-After、Server、Vary、Warning、WWW- Authenticate。对响应头域的扩展要求通讯双方都支持,如果存在不支持的响应头域,一般将会作为实体头域处理。
典型的响应消息:
HTTP/1.0200OK
Date:Mon,31Dec200104:25:57GMT
Server:Apache/1.3.14(Unix)
Content-type:text/html
Last-modified:Tue,17Apr200106:46:28GMT
Etag:"a030f020ac7c01:1e9f"
Content-length:39725426
Content-range:bytes554554-40279979/40279980
上例第一行表示HTTP服务端响应一个GET方法。棕色的部分表示响应头域的信息,绿色的部分表示通用头部分,红色的部分表示实体头域的信息。
Location响应头
Location响应头用于重定向接收者到一个新URI地址。
Server响应头
Server响应头包含处理请求的原始服务器的软件信息。此域能包含多个产品标识和注释,产品标识一般按照重要性排序。
4、实体信息
请求消息和响应消息都可以包含实体信息,实体信息一般由实体头域和实体组成。实体头域包含关于实体的原信息,实体头包括Allow、Content-Base、Content-Encoding、Content-Language、 Content-Length、Content-Location、Content-MD5、Content-Range、Content-Type、 Etag、Expires、Last-Modified、extension-header。extension-header允许客户端定义新的实体头,但是这些域可能无法未接受方识别。实体可以是一个经过编码的字节流,它的编码方式由Content-Encoding或Content-Type定义,它的长度由Content-Length或Content-Range定义。
Content-Type实体头
Content-Type实体头用于向接收方指示实体的介质类型,指定HEAD方法送到接收方的实体介质类型,或GET方法发送的请求介质类型Content-Range实体头
Content-Range实体头
用于指定整个实体中的一部分的插入位置,他也指示了整个实体的长度。在服务器向客户返回一个部分响应,它必须描述响应覆盖的范围和整个实体长度。一般格式:
Content-Range:bytes-unit SP first-byte-pos - last-byte-pos/entity-legth
例如,传送头500个字节次字段的形式:Content-Range:bytes0-499/1234如果一个http消息包含此节(例如,对范围请求的响应或对一系列范围的重叠请求),Content-Range表示传送的范围,Content-Length表示实际传送的字节数。
Last-modified实体头
Last-modified实体头指定服务器上保存内容的最后修订时间。
5、 HTTP 头参考(microsoft)
HTTP 请求和 HTTP 响应都使用头发送有关 HTTP 消息的信息。头由一系列行组成,每行都包含名称,然后依次是冒号、空格、值。字段可按任何顺序排列。某些头字段既能用于请求头也能用于响应头,而另一些头字段只能用于其中之一。
许多请求头字段都允许客户端在值部分指定多个可接受的选项,有时甚至可以对这些选项的首选项进行排名。多个项以逗号分隔。例如,客户端可以发送包含 “Content-Encoding: gzip, compress,”的请求头,表示可以接受各种压缩类型。如果服务器的响应正文使用 gzip 编码,其响应头中将包含“Content-Encoding: gzip”。
有些字段可以在单个头中出现多次。例如,头可以有多个“Warning”字段。
下表列出了 HTTP 1.1 头字段。注意:有些头字段是 MIME 字段。MIME 字段在 Internet Engineering Task Force (IETF) 文档 RFC 2045 中进行了定义,但也可用于 HTTP 1.1 协议。有关 MIME 和 HTTP 1.1 规范的详细信息,请参阅 IEIF 页。
一般头字段
一般头字段可用于请求消息和响应消息。
 名称          示例值
Cache-Control  "max-age=10"
Connection    "close"
Date          "Tue, 11 Jul 2000 18:23:51 GMT"
Pragma        "no-cache"
Trailer         "Date"
Transfer-Encoding"chunked"
Upgrade       "SHTTP/1.3"
Via            "HTTP/1.1 Proxy1, HTTP/1.1 Proxy2"
Warning       "112 Disconnected Operation"
请求头字段
请求头字段仅用于请求消息。
   名称             示例值
Accept           "text/html, image/*"
Accept-Charset   "iso8859-5"
Accept-Encoding "gzip, compress"
Accept-Language "en, fr"
Authorization     [credentials]
Content-Encoding "gzip"
Expect           "100-continue"
From            "user@microsoft.com"
Host            "www.microsoft.com"
If-Match         "entity_tag001"
If-Modified-Since"Tue, 11 Jul 2000 18:23:51 GMT"
If-None-Match    "entity_tag001"
If-Range         "entity_tag001" or "Tue, 11 Jul 2000 18:23:51 GMT"
If-Unmodified-Since"Tue, 11 Jul 2000 18:23:51 GMT"
Max-Forwards    "3"
Proxy-Authorization[credentials]
Range       "bytes=100-599"
Referer      "http://www.microsoft.com/resources.asp"
TE          "trailers"
User-Agent   "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0)"
 
>>请求头字段的具体含义
Accept:浏览器可接受的MIME类型。
Accept-Charset:浏览器可接受的字符集。
Accept-Encoding:浏览器能够进行解码的数据编码方式,比如gzip。
Accept-Language:浏览器所希望的语言种类,当服务器能够提供一种以上的语言版本时要用到。
Authorization:授权信息,通常出现在对服务器发送的WWW-Authenticate头的应答中。
Connection:表示是否需要持久连接。如果Servlet看到这里的值为“Keep-Alive”,或者看到请求使用的是HTTP 1.1(HTTP 1.1默认进行持久连接),它就可以利用持久连接的优点,当页面包含多个元素时(例如Applet,图片),显著地减少下载所需要的时间。要实现这一点, Servlet需要在应答中发送一个Content-Length头,最简单的实现方法是:先把内容写入ByteArrayOutputStream,然后在正式写出内容之前计算它的大小。
Content-Length:表示请求消息正文的长度。
Cookie:设置cookie,这是最重要的请求头信息之一
From:请求发送者的email地址,由一些特殊的Web客户程序使用,浏览器不会用到它。
Host:初始URL中的主机和端口。
If-Modified-Since:只有当所请求的内容在指定的日期之后又经过修改才返回它,否则返回304“Not Modified”应答。
Pragma:指定“no-cache”值表示服务器必须返回一个刷新后的文档,即使它是代理服务器而且已经有了页面的本地拷贝。
Referer:包含一个URL,用户从该URL代表的页面出发访问当前请求的页面。
User-Agent:浏览器类型,如果Servlet返回的内容与浏览器类型有关则该值非常有用。
UA-Pixels,UA-Color,UA-OS,UA-CPU:由某些版本的IE浏览器所发送的非标准的请求头,表示屏幕大小、颜色深度、操作系统和CPU类型。
响应头字段
响应头字段仅用于响应消息。
  名称          示例值
Accept-Ranges  "none"
Age            "2147483648(2^31)"
ETag           "b38b9-17dd-367c5dcd"
Last-Modified    "Tue, 11 Jul 2000 18:23:51 GMT"
Location        "http://localhost/redirecttarget.asp"
Proxy-Authenticate[challenge]
Retry-After      "Tue, 11 Jul 2000 18:23:51 GMT" or "60"
Server         "Microsoft-IIS/5.0"
Vary            "Date"
WWW-Authenticate[challenge]
实体头字段
实体头字段可以用于请求消息或响应消息。实体头字段中包含消息实体正文的有关信息,如使用的编码格式。
   名称            示例值
Allow              "GET, HEAD"
Content-Encoding   "gzip"
Content-Language  "en"
Content-Length     "8445"
Content-Location   "http://localhost/page.asp"
Content-MD5       [md5-digest]
Content-Range     "bytes 2543-4532/7898"
Content-Type      "text/html"
Expires           "Tue, 11 Jul 2000 18:23:51 GMT"
Last-Modified      "Tue, 11 Jul 2000 18:23:51 GMT"
>>实体头字段的具体含义
Allow服务器支持哪些请求方法(如GET、POST等)。
Content-Encoding文档的编码(Encode)方法。只有在解码之后才可以得到Content-Type头指定的内容类型。利用gzip压缩文档能够显著地减少HTML文档的下载时间。Java的GZIPOutputStream可以很方便地进行gzip压缩,但只有Unix上的Netscape和Windows上的IE 4、IE 5才支持它。
Content-Length表示内容长度。只有当浏览器使用持久HTTP连接时才需要这个数据。
Content-Type表示后面的文档属于什么MIME类型。Servlet默认为text/plain,但通常需要显式地指定为text/html。
Date当前的GMT时间。你可以用setDateHeader来设置这个头以避免转换时间格式的麻烦。
Expires应该在什么时候认为文档已经过期,从而不再缓存它?
Last-Modified文档的最后改动时间。客户可以通过If-Modified-Since请求头提供一个日期,该请求将被视为一个条件GET,只有改动时间迟于指定时间的文档才会返回,否则返回一个304(Not Modified)状态。
Location表示客户应当到哪里去提取文档。Location通常不是直接设置的,而是通过HttpServletResponse的sendRedirect方法,该方法同时设置状态代码为302。
Refresh表示浏览器应该在多少时间之后刷新文档,以秒计。除了刷新当前文档之外,你还可以通过setHeader("Refresh", "5; URL=http://host/path")让浏览器读取指定的页面。
注意这种功能通常是通过设置HTML页面HEAD区的<META. HTTP-EQUIV="Refresh" C>实现,这是因为,自动刷新或重定向对于那些不能使用CGI或Servlet的HTML编写者十分重要。但是,对于Servlet来说,直接设置 Refresh头更加方便。
注意Refresh的意义是“N秒之后刷新本页面或访问指定页面”,而不是“每隔N秒刷新本页面或访问指定页面 ”。因此,连续刷新要求每次都发送一个Refresh头,而发送204状态代码则可以阻止浏览器继续刷新,不管是使用Refresh头还是<META. HTTP-EQUIV="Refresh" ...>。
注意Refresh头不属于HTTP 1.1正式规范的一部分,而是一个扩展,但Netscape和IE都支持它。
请求头示例
以下是 HTTP 请求的简单示例。
GET /articles/news/today.asp HTTP/1.1
Accept: */*
Accept-Language: en-us
Connection: Keep-Alive
Host: localhost
Referer:http://localhost/links.asp
User-Agent: Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0)
Accept-Encoding: gzip, deflate
该请求具有请求行,其中包括方法 (GET)、资源路径 (/articles/news/today.asp) 和 HTTP 版本 (HTTP/1.1)。由于该请求没有正文,故所有请求行后面的内容都是头的一部分。紧接着头之后是一个空行,表示头已结束。
响应头示例
Web 服务器可以通过多种方式响应前一个请求。假设文件是可以访问的,并且用户具有查看该文件的权限,则响应类似于:
HTTP/1.1 200 OK
Server: Microsoft-IIS/5.0
Date: Thu, 13 Jul 2000 05:46:53 GMT
Content-Length: 2291
Content-Type: text/html
Set-Cookie: ASPSESSIONIDQQGGGNCG=LKLDFFKCINFLDMFHCBCBMFLJ; path=/
Cache-control: private
...
响应的第一行称为状态行。它包含响应所用的 HTTP 版本、状态编码 (200) 和原因短语。示例中包含一个头,其中具有五个字段,接着是一个空行(回车和换行符),然后是响应正文的头两行。
有关HTTP头完整、详细的说明,请参见http://www.w3.org/Protocols/的HTTP规范。
 
附录:HTTP协议状态码的含义
  状态代码 状态信息 含义
100 Continue初始的请求已经接受,客户应当继续发送请求的其余部分。(HTTP 1.1新)
101 Switching Protocols服务器将遵从客户的请求转换到另外一种协议(HTTP 1.1新
200 OK一切正常,对GET和POST请求的应答文档跟在后面。
201 Created服务器已经创建了文档,Location头给出了它的URL。
202 Accepted已经接受请求,但处理尚未完成。
203 Non-Authoritative Information文档已经正常地返回,但一些应答头可能不正确,因为使用的是文档的拷贝(HTTP 1.1新)。
204 No Content没有新文档,浏览器应该继续显示原来的文档。
205 Reset Content没有新的内容,但浏览器应该重置它所显示的内容。用来强制浏览器清除表单输入内容(HTTP 1.1新)。
206 Partial Content客户发送了一个带有Range头的GET请求,服务器完成了它(HTTP 1.1新)。
300 Multiple Choices客户请求的文档可以在多个位置找到,这些位置已经在返回的文档内列出。如果服务器要提出优先选择,则应该在Location应答头指明。
301 Moved Permanently客户请求的文档在其他地方,新的URL在Location头中给出,浏览器应该自动地访问新的URL。
302 Found类似于301,但新的URL应该被视为临时性的替代,而不是永久性的。注意,在HTTP1.0中对应的状态信息是“Moved Temporatily”,出现该状态代码时,浏览器能够自动访问新的URL,因此它是一个很有用的状态代码。注意这个状态代码有时候可以和301替换使用。例如,如果浏览器错误地请求http://host/~user(缺少了后面的斜杠),有的服务器返回301,有的则返回302。严格地说,我们只能假定只有当原来的请求是GET时浏览器才会自动重定向。请参见307。
303 See Other类似于301/302,不同之处在于,如果原来的请求是POST,Location头指定的重定向目标文档应该通过GET提取(HTTP 1.1新)。
304 Not Modified客户端有缓冲的文档并发出了一个条件性的请求(一般是提供If-Modified-Since头表示客户只想比指定日期更新的文档)。服务器告诉客户,原来缓冲的文档还可以继续使用。
305 Use Proxy客户请求的文档应该通过Location头所指明的代理服务器提取(HTTP 1.1新)。
307 Temporary Redirect和302(Found)相同。许多浏览器会错误地响应302应答进行重定向,即使原来的请求是POST,即使它实际上只能在POST请求的应答是303时才能重定向。由于这个原因,HTTP 1.1新增了307,以便更加清除地区分几个状态代码:当出现303应答时,浏览器可以跟随重定向的GET和POST请求;如果是307应答,则浏览器只能跟随对GET请求的重定向。(HTTP 1.1新)
400 Bad Request请求出现语法错误。
401 Unauthorized客户试图未经授权访问受密码保护的页面。应答中会包含一个WWW-Authenticate头,浏览器据此显示用户名字/密码对话框,然后在填写合适的Authorization头后再次发出请求。
403 Forbidden资源不可用。服务器理解客户的请求,但拒绝处理它。通常由于服务器上文件或目录的权限设置导致。
404 Not Found无法找到指定位置的资源。这也是一个常用的应答,
405 Method Not Allowed请求方法(GET、POST、HEAD、DELETE、PUT、TRACE等)对指定的资源不适用。(HTTP 1.1新)
406 Not Acceptable指定的资源已经找到,但它的MIME类型和客户在Accpet头中所指定的不兼容(HTTP 1.1新)。
407 Proxy Authentication Required类似于401,表示客户必须先经过代理服务器的授权。(HTTP 1.1新)
408 Request Timeout在服务器许可的等待时间内,客户一直没有发出任何请求。客户可以在以后重复同一请求。(HTTP 1.1新)
409 Conflict通常和PUT请求有关。由于请求和资源的当前状态相冲突,因此请求不能成功。(HTTP 1.1新)
410 Gone所请求的文档已经不再可用,而且服务器不知道应该重定向到哪一个地址。它和404的不同在于,返回407表示文档永久地离开了指定的位置,而404表示由于未知的原因文档不可用。(HTTP 1.1新)
411 Length Required服务器不能处理请求,除非客户发送一个Content-Length头。(HTTP 1.1新)
412 Precondition Failed请求头中指定的一些前提条件失败(HTTP 1.1新)。
413 Request Entity Too Large目标文档的大小超过服务器当前愿意处理的大小。如果服务器认为自己能够稍后再处理该请求,则应该提供一个Retry-After头(HTTP 1.1新)。
414 Request URI Too LongURI太长(HTTP 1.1新)。
416 Requested Range Not Satisfiable服务器不能满足客户在请求中指定的Range头。(HTTP 1.1新)
500 Internal Server Error服务器遇到了意料不到的情况,不能完成客户的请求。
501 Not Implemented服务器不支持实现请求所需要的功能。例如,客户发出了一个服务器不支持的PUT请求。
502 Bad Gateway服务器作为网关或者代理时,为了完成请求访问下一个服务器,但该服务器返回了非法的应答。
503 Service Unavailable服务器由于维护或者负载过重未能应答。
504 Gateway Timeout由作为代理或网关的服务器使用,表示不能及时地从远程服务器获得应答。(HTTP 1.1新)
505 HTTP Version Not Supported服务器不支持请求中所指明的HTTP版本

posted @ 2010-01-08 09:15 二胡 阅读(268) | 评论 (0)编辑 收藏

JQuery作者John Resig的讲座
http://v.youku.com/v_show/id_XMjQzMDY4NDQ=.html

posted @ 2009-12-23 15:04 二胡 阅读(153) | 评论 (0)编辑 收藏

转 http://blog.youmila.com/?p=513

关于跨域名问题还是问题么,这方面的解决实践非常多,今天我就旧话重提把我所知道的通过几个应用场景来分别总结一下

先说明一点:我说的某某域名在您的控制下的意思是这个域名下的网页由您来负责开发内部的JavaScript
场景一:将bbs.xxx.com的页面用iframe嵌入到www.xxx.com的中,如何在iframe内外使用js通信
一级域名都是xxx.com 这个域名一定是在您的控制下,所以你只要在两个页面中同时升级域名即可
在父窗口和iframe内部分别加上js语句:document.domain=”xxx.com”;
之后2个页面就等于在同一域名下,通过window.parent oIframe.contentDocument就可以相互访问,进行无障碍的JS通信
在新浪、淘宝等很多页面都能找到这样的语句。不过document.domain不可以随便指定,只能向上升级,从bbs.xxx.com升级到yyy.com肯定会出错

场景二:将www.yyy.com的页面用iframe嵌入到www.xxx.com的中,两个域名都在您的控制下,如何在iframe内外进行一定的数据交流
你可以通过相互改变hash值的方式来进行一些数据的通信

这里的实现基于如下技术要点:
1、父窗口通过改变子窗口的src中的hash值把一部分信息传入,如果src只有hash部分改变,那么子窗口是不会重新载入的。
2、子窗口可以重写父窗口的location.href,但是注意这里子窗口无法读取而只能重写location.href所以要求前提是您控制两个域名,知道当前父窗口的location.href是什么并写在子窗口内,这样通过parent.location.href = “已知的父窗口的href”+”#”+hash。这样父窗口只有hash改变也不会重载。
3、上面两步分别做到了两个窗口之间的无刷新数据通知,那么下面的来说如何感知数据变化。标准中没有相关规定,所以当前的任意浏览器遇到location.hash变化都不会触发任何javaScript事件,也就是说您要自己写监听函数来监视loaction.hash的值的变化。做法是通过setTimeout或者setInterval来写一个监听函数每20-100ms查看一下hash是否变化,如果变化了驱动js根据新的数据做想做的事情。

这种实现的一些分析:
1、信息通道是双向的,当然会兼容单向,如果只是父窗口向子窗口通知数据,只需要子窗口写hash监听,反之亦然。
2、局限性也是颇大,因为这种通信的前提是双方知道对方的location.href。如果父窗口带有动态的location.search也就是查询参数,那么子窗口的处理上就比较困难,需要把父窗口的location.search作为传递信息的一部分告知子窗口。
3、另外的困扰会有浏览器带给你,IE之外的浏览器遇到hash的改变会记录历史,这样你在处理前进后退的时候会非常头疼

场景三:将www.yyy.com的页面用iframe嵌入到www.xxx.com的中,只有被嵌入的yyy.com在您的控制下,如何在iframe内外进行一定的交流
真实场景:google adsence的一个需求,你希望google发现您的页面不能匹配出相关性非常好的按点击付费广告时,你希望google的广告iframe能够隐藏。
google的广告iframe在google域下显然不能把自己隐藏掉,那么怎么办呢?
1、google会提供给你一个html页面
2、您将这个页面放置在您的域名下,并告诉google它的位置
3、当google发现没有很好的广告时,会将子窗口的loaction重定向到您的那个页面下,这样您的页面因为同域名就可以访问父页面来隐藏自己了
是不是很巧的方法?

场景四:您是内容发布商,如何改造接口,让其他域名下的页面可以从浏览器端出发获得您的数据
我们知道ajax的xmlHttpRequest()说到底是一个无刷新请求服务器数据的辅助工具,但是xmlHttpRequest并不能跨域名请求数据,在某些情况下成了极大的限制。
但是我们如果通过其他方式完成无刷新请求数据不也可以么,我们用Dom方法操作动态JS脚本请求来做这件事。
    //创建一个脚本节点
    var oScript = document.createElement(’script’);
    //指定脚本src src可以指向任意域名
    //注意src不再指向静态js,而是带着查询参数指向一个动态脚本广播服务。
    oScript.src = “http://yyy.com/query.php?”+yourQueryString;   
    //如果指定了charset 同时还可以解决xmlHttpRequest另一大困扰 乱码问题                                                                                                                           
    //oScript.charset = “utf-8″;
    //通过Dom操作把这个新的节点加入到文档当中                                 
    document.getElementsByTagName(”head”)[0].appendChild(oScript);

这样只要query.php的输出是可执行的javaScript脚本,比如:djsCallBack({jsondata});
当他从服务器返回后就会自动执行,你可以方便的用json方式来做数据传递了。
要注意,您的脚本请求最好带上时间戳,避免浏览器缓存造成取回数据实时性下降。

如果您是数据提供者,您可以要求数据索取者在查询参数中提供回调函数名,比如query.php?callback=myDataHandler&key=…?
这样您就可以根据参数来提供给他myDataHandler({jsondata}),这样不同的数据索取者都会得到自定义的正确的异步回调。

场景五:通过后端程序语言,为了跨域名而做各自的后台数据抓取转化服务,比如php curl,YAHOO  CHINA NCP就是用这种方案。

场景六:通过flash proxy,因为flash的跨域调用可以通过crossdomain.xml和security.allowdomain(’*')文件实现,而js又可以和flash进行通信,所以js完全可以借用flash

               实现js跨域通信。

 
总结总结
第一种场景,相应的处理办法有这非常好的效果,可以说完全解决了问题。
第二种场景,相应的处理办法具有一定的跨域数据交流功效,具有相当大的局限,并不适合在复杂业务流程中应用,实际上我也确实也没看到过基于此的大规模应用。
第三种场景,相应的处理办法比较巧妙,虽然redirect之后就不干你什么事了,但如果你是google一样面向众多域名的内容提供商,也是个不错的解决思路。
第四种场景,相应的处理办法非常强大,对比Ajax可以看到,跨域名没问题,无刷新没问题,本身又是异步的,JSON比xml快的多,同时解决乱码问题,只是请求都是Get方式的,不能做Post方式的请求。多一种武器自然可以从容选择了。

第五种场景,处理很方便,也很实用。

第六种场景,需要一定的flash基础哈,作用当然非常强大。

posted @ 2009-12-16 17:51 二胡 阅读(1086) | 评论 (0)编辑 收藏


The Java Community Process(SM) Program

转 http://blog.csdn.net/sergeycao/archive/2009/02/04/3861560.aspx

J2ME 配置规范
=========
JSR 30 --- Connected Limited Device Configuration 1.0
http://jcp.org/en/jsr/detail?id=30

JSR 139 --- Connected Limited Device Configuration 1.1
http://jcp.org/en/jsr/detail?id=139

JSR 36 --- Connected Device Configuration 1.0
http://jcp.org/en/jsr/detail?id=36

JSR 218 --- Connected Device Configuration 1.1
http://jcp.org/en/jsr/detail?id=218

========================================
1、JSR 30、JSR139 简介 及它们之间的关系
CLDC全称为Connected Limited Device Configuration(有限连接设备配置),
分别对应了JSR 30和JSR 139两个JSR。

CLDC专门针对移动电话、阅读器和主流的PDA(个人数字助理)定义了一组基础的应用程序编程接口和虚拟机标准,
和简表文件一起配合,就构成了一套实用的Java平台,可以为内存不多、处理器性能有限、图形能力一般的设备开发应用程序。

JSR 30 CLDC 1.0 提供了基本的语言类库,主要是定义了JAVA编程语言的一套子集,包括虚拟机的功能上,网络支持,安全安装以及其他核心API上都是子集和全集的关系,主要目标是某类嵌入式的消费类产品。由于不支持浮点运算,可以用CLDC1.1替代CLDC1.0;

JSR 139 CLDC 1.1是CLDC 1.0技术标准的修订版本,包含了一些新的特性比如浮点运算和弱引用等方面的支持,和CLDC-1.0是完全向后兼容的;

2、JSR 36、JSR218 简介 及 它们之间的关系
JSR 36 CDC (Connected Device Configuration,连接设备配置)。CDC的目标设备较CLDC具有更大的内存、更快速的处理器、更稳定的电源,以及更出色的网络连接能力。
CDC主要应用在工业控制器、高端PDA、电视机顶盒及车载娱乐与导航系统上。

JSR 218 是在JSR 36基础上进行补充,并兼容JSR 36.

J2ME 简表规范
=========
CDC 简表规范
------------------
JSR 46 --- Foundation Profile
http://jcp.org/en/jsr/detail?id=46

JSR 129 --- Personal Basis Profile Specification
http://jcp.org/en/jsr/detail?id=129

JSR 62 --- Personal Profile Specification
http://jcp.org/en/jsr/detail?id=62

JSR 219 --- Foundation Profile 1.1
http://jcp.org/en/jsr/detail?id=219

JSR 217 --- Personal Basis Profile 1.1
http://jcp.org/en/jsr/detail?id=217

JSR 216 --- Personal Profile 1.1
http://jcp.org/en/jsr/detail?id=216

CLDC 简表规范
--------------------
JSR 37 --- Mobile Information Device Profile 1.0
http://jcp.org/en/jsr/detail?id=37

JSR 118 --- Mobile Information Device Profile 2.0
http://jcp.org/en/jsr/detail?id=118

JSR 195 --- Information Module Profile
http://jcp.org/en/jsr/detail?id=195

JSR 228 --- Information Module Profile 2.0)
http://jcp.org/en/jsr/detail?id=228


厂商可选包(Optional Packages)
-----------------------------------------

CDC设备厂商可选包
...........................
JSR 66 --- RMI Optional Package Specification Version 1.0
http://jcp.org/en/jsr/detail?id=66

JSR 80 --- Java USB API
http://jcp.org/en/jsr/detail?id=80

JSR 113 --- Java Speech API 2.0
http://jcp.org/en/jsr/detail?id=113

JSR 169 --- JDBC Optional Package for CDC/Foundation Profile
http://jcp.org/en/jsr/detail?id=169

JSR 209 --- Advanced Graphics and User Interface Optional Package for the J2ME Platform
http://jcp.org/en/jsr/detail?id=209

CLDC设备厂商可选包
...........................
JSR 75 --- PDA Optional Packages for the J2ME Platform
http://jcp.org/en/jsr/detail?id=75

JSR 82 --- Java APIs for Bluetooth
http://jcp.org/en/jsr/detail?id=82

JSR 120 --- Wireless Messaging API
http://jcp.org/en/jsr/detail?id=120

JSR 135 --- Mobile Media API
http://jcp.org/en/jsr/detail?id=135

JSR 172 --- J2ME Web Services Specification
http://jcp.org/en/jsr/detail?id=172

JSR 177 --- Security and Trust Services API for J2ME
http://jcp.org/en/jsr/detail?id=177

JSR 179 --- Location API for J2ME
http://jcp.org/en/jsr/detail?id=179

JSR 180 --- SIP API for J2ME
http://jcp.org/en/jsr/detail?id=180

JSR 184 --- Mobile 3D Graphics API for J2ME
http://jcp.org/en/jsr/detail?id=184

JSR 190 --- Event Tracking API for J2ME
http://jcp.org/en/jsr/detail?id=190

JSR 205 --- Wireless Messaging API 2.0
http://jcp.org/en/jsr/detail?id=205

JSR 211 --- Content Handler API
http://jcp.org/en/jsr/detail?id=211

JSR 226 --- Scalable 2D Vector Graphics API for J2ME
http://jcp.org/en/jsr/detail?id=226

JSR 229 --- Payment API
http://jcp.org/en/jsr/detail?id=229

JSR 230 --- Data Sync API
http://jcp.org/en/jsr/detail?id=230

运行环境规范
..................
JSR 185 --- JavaTM Technology for the Wireless Industry
http://jcp.org/en/jsr/detail?id=185

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/sergeycao/archive/2009/02/04/3861560.aspx

posted @ 2009-12-15 09:32 二胡 阅读(1210) | 评论 (0)编辑 收藏

     摘要: 我google一下,已有人翻译了此文.比我翻译的要好!是译言站翻译的 见url: http://www.yeeyan.com/articles/view/92135/47626/dz 原文见:http://code.google.com/intl/zh-CN/speed/articles/optimizing-javascript.html 不合适的地方,请大家指出来!希望对你有用! ...  阅读全文
posted @ 2009-12-14 21:43 二胡 阅读(1919) | 评论 (0)编辑 收藏

转 http://war.news.163.com/09/1211/18/5Q98H5TU00011232.html

国家战略是一个范军事话题,并非只有军事家才能成为战略精英。在这一点上,美国培养未来的国家决策人才的经验就值得中国反思。

前美国国防部长拉姆斯菲尔德

原文作者:于铁军(北京大学国际关系学院副教授)

美国的精英比较有活力,给人一种生机勃勃的感觉,因为他们更注重全面发展,更注重训练。反观国内精英,大多比较文弱,缺乏朝气,口号、程式,大话、空话很多,行动力、执行力却比较差。各种原因当然很多,其中重要之一是国内的精英培养机制有问题。在这方面,我们应该好好地研究一下美国培养精英的做法。如果我们对精英培养机制和培养内容不重视,中国要实现真正的崛起,难度是很大的。

什么是精英

精英,或者称战略性人才,是个很值得讨论的题目。这里首先需要回答一个问题,什么是精英?精英,一定是有所担当的人,有社会责任感的人,而绝不能是蝇营狗苟之辈。真正的精英,他身上一定带有一些利他的东西,不是完全为了自己。这是精英的一个必备条件。

以美国外交和国家安全领域的情况为例来做具体的说明,哈尔伯斯坦的《出类拔萃之辈》、艾萨克森和托马斯的《美国智囊六人传》,以及詹姆斯·曼的《布什战争内阁史》等传记作品。这些书中对艾奇逊、哈里曼、凯南、麦克罗伊、洛维特、邦迪兄弟、麦克纳马拉、罗斯托、拉姆斯菲尔德、鲍威尔、赖斯、沃尔福威茨、阿米蒂奇等美国外交和国家安全界精英的成长及活动,有惟妙惟肖的描写,有助于增加我们对美国精英养成方式的感性认识。掩卷之余,你可以对书中人物做出或褒或贬的评价,但你一般不会否认,这是一批有理想、有追求、有能力并且关心公共事务的精英,美国这个国家的大政方针为他们所引领。

叱咤风云的美国前国务卿赖斯

美国培养精英的一个重要理念:尚武精神与体育运动

具体到精英的培养,大学里面的培养机制还是比较关键的。当然我们不否认有草根出身的英雄,而且国人还经常说,英雄莫问来路。但社会发展到现在这个阶段,应该说大部分人才还是经过大学的正规教育训练出来的。美国的高等教育制度十分复杂,不像我们那样有一个统一的体制。从精英大学到普通大学,从研究性大学到小型的文理学院,从州立大学到私立大学,各种类型,应有尽有。不管是什么样的大学,从学生录取方面来看,美国比我们更重视学生的综合素质和发展潜力。除了SAT、GRE和GMAT这样的标准化考试之外,推荐信、研究计划、习作、课外活动等也都是很重要的指标,这无疑有助于引导和鼓励学生全面发展。

骑自行车的小布什,体育运动爱好在美国高官中相当普及

中国从小学就开始教育要德、智、体全面发展,但遗憾的是没有落到实处。就拿体育来说,正如中国留美学者薛涌所观察到的,美国精英层中运动员出身或者喜爱体育运动的人高得不成比例。他们在大学时代往往喜欢参加各种体育项目,如橄榄球、棒球、篮球、皮划艇等。参加体育运动,在激烈的竞争中使你的体力达到极限,这有助于一系列优秀品质的培养,如全力以赴、坚忍不拔、公平竞争、团队精神、尊重规则、强身健体,等等,而且还能找到一些志同道合者,为未来的事业编制人脉。在美国大学里,学生的领导能力常常是通过参加某某运动队和某某社团的活动来体现的。光是闷头学习,即使成绩再好,可能也不为人看重。

小布什的身体强壮,当总统时公务再繁忙,也要挤时间骑山地车和跑3英里。还有拉姆斯菲尔德,上大学时是普林斯顿摔跤队的,要不是因为肩伤,本来可以入选美国的奥运代表队。前总统福特,原来是密歇根大学橄榄球队的,水平之高几乎可以当职业运动员,但后来他选择去了耶鲁,毕业后开始从政。赖斯年轻的时候练花样滑冰,据说具备专业水准。其他例子还有许多,美国精英对体育的重视由此可见一斑。

传说中赖斯年轻时学习花样滑冰的照片

体育运动、强健的体魄与尚武精神是密切联系的。西方自古希腊以来便有一种尚武的传统,当代美国也继承了这一传统,并成为其培养精英的一个重要理念。尚武精神在当代的一个重要表现就是重视体育。而在中国方面,长期以来则是尚文轻武的传统占上风。历史学家雷海宗先生早就提出过一种观点,认为中国是“无兵的文化”。春秋战国的时候,还是贵族在打仗。从那以后,兵的文化就日渐弱化,好男不当兵的观念流传甚广,这与西方的情况大相径庭。

美国战略精英的培养机制

1.耶鲁大学的大战略研究:阅读经典、海外游历与现实关怀

耶鲁大学的大战略研究以历史路径为主。冷战史名家约翰·加迪斯教授和曾撰写过《大国的兴衰》的名教授保罗·肯尼迪,都非常重视通过研究历史来探究战略。他们在耶鲁大学设立的大战略研究项目,大致分为阅读经典、海外旅行和当代重大问题讨论三个板块。学生进入到这个项目后,春季学期被用来大量阅读经典著作,读修昔底德,读孙子,读马基雅维利、伊丽莎白二世、美国的开国元勋、康德、梅特涅、克劳塞维茨、林肯、俾斯麦、威尔逊、丘吉尔、两个罗斯福(西奥多·罗斯福和富兰克林·罗斯福)、列宁、斯大林、毛泽东、凯南、基辛格等人的著作。培养战略精英,不了解过去伟大的思想怎么行?不研究战略思想发展史怎么行?

耶鲁大战略研究班的一次集体讨论,未来的国家决策者们通过辩论与他人交流知识

夏季小学期学生们被安排到海外旅行。耶鲁特别鼓励学生到世界各地去游历,主要不是看那些名胜古迹,而是到那些普通观光者不怎么去的地方,比如说,你可以沿着古代的丝绸之路去看看今日的输油管道;你可以先到圣彼得堡学一个月俄语,然后花两个月时间途经西伯利亚回国;你可以到叙利亚、埃及去学阿拉伯语;你可以去中国那些外国人平常不怎么去的地方,了解一下中国的风土人情。这有点像田野调查。他们希望通过这种海外经历,一方面使学生增加对不同文化、不同国家的了解,另一方面把他们置于一种自己不熟悉的、带有挑战性的环境中去磨练他们的意志,增加他们的自信心。

回来之后,在秋季学期开始时讨论美国所面临的当代重大问题。这时候学生要读亨廷顿、福山、扎卡里亚这些人的著作,然后让他们分组进行辩论,假定自己担任国家公职,肩负着重要的职责,你要指出现在美国所面临的主要问题是什么?美国的国家利益是什么?美国到底要维护什么?美国的最大威胁是什么?然后尽自己最大的努力去说服同学、说服教师来接受你的观点。可以想见,这一年的读万卷书、行万里路的训练,对培育学生的战略素养将是很有帮助的。

2.斯坦福大学国际安全与合作中心:走科学技术与跨学科研究之路

 

美国海军预备役军官团的一次课程,相当于中国的国防生制度,但是历史更悠久,制度更健全

斯坦福大学集中搞战略研究的地方,是国际安全与合作中心。该中心的突出特点是重视科技与战略研究的关系,开展真正的跨学科研究。中心的研究人员分成两大拨儿,一拨儿人是科学家和工程师,包括核物理学家、化学家、生物学家、研究导弹的等等;另一拨儿人则是搞人文社会科学的,包括政治学家、历史学家、法学家、经济学家、社会学家等。这两拨儿人整天在一个屋檐下搞研究。在国家安全问题上,如果要提出一项政策,那么该政策在技术上的可行性如何,将是非常重要的一个问题。而对科学家和工程师们来说,他们在人文社科方面的知识、在政策的敏感性等方面可能就稍微欠缺一些。一项政策的历史变迁、在法律上如何操作、如何将技术上的可能性转化成政策,这些问题,他们不太擅长,因此也就需要由一些搞人文社科的来帮助,来协调。两边一搭伙儿就形成了一个良性循环。

中心的跨学科研究取向也体现在中心的教育功能上。中心为斯坦福大学的本科生开设的课程有“军事技术与国家安全”、“核武器国际史”,以及通过模拟来解决国际危机的课程,走的都是技术与战略研究的结合之路。中心还设立了一个本科生国际安全辅修项目,从斯坦福不同专业但都对国际安全问题感兴趣的本科生中选拔优秀者参加,集中学习一年课程,包括实习和撰写有政策意义的研究论文。另外,中心还设立奖学金,将全美名校中从事国际安全问题研究的有前途的年轻人吸收到中心的博士后和博士前项目中,让他们与中心的研究人员密切互动,从而将研究和教学很好地结合起来。

斯坦福的这套做法,对国内的战略研究来说很有借鉴意义。北大在国内也算是顶尖大学,但在国际问题研究领域几乎看不到类似斯坦福那样真正的跨自然和人文社会科学的研究机构。不同领域的专家很少有机会能坐下来一块儿交流、讨论、合作攻关。北大国际关系学院两年多前成立了一个国际战略研究中心,打算在这个方面做一点努力,但未来的路很长。无论是在观念意识方面还是组织架构方面,中国的跨学科战略研究与美国相比,都还有很大差距。

3.哈佛的战略研究:重决策过程与案例研究

哈佛大学的国际事务研究中心

哈佛的战略研究分好几个单位,一个单位是哈佛国际事务研究中心,“文明冲突论”的提出者亨廷顿曾长期担任这个中心的主任;基辛格出道之前也在这儿。后来在该中心下又成立一个奥林战略研究所。在过去近20年中,奥林国家安全项目通过为美国国家安全领域优秀的年轻学者(包括博士待位人、博士后和高等院校的年轻教员,每年10人左右)提供奖学金的方式,为美国的国际战略研究界培养了很多人才。这些人实际上形成了一个圈子。

哈佛的肯尼迪学院在战略研究领域是后起之秀

目前来看,在哈佛的战略研究中,更为活跃的是设立较晚的肯尼迪政府学院。从1960年代起,在政治学家诺伊斯塔特,外交史学家欧内斯特·梅,以及他们的学生、现任肯尼迪政府学院名誉院长格雷厄姆·艾利森的持续努力下,发展了一条从案例和决策过程入手来研究国际战略的路径。艾利森以古巴导弹危机为例所归纳出的三种决策模式,便是其中最为突出的一个范例。在长期关注决策和案例的基础上,肯尼迪学院开设了各种高级别的战略培训班,与政府部门合作,培养各类战略研究人才。

4.麻省理工学院的安全研究项目:与军方密切合作

麻省理工学院(MIT)的安全研究项目(Security Studies Program,简称SSP项目)在美国大学的安全研究领域中是名列前茅的。该项目的前身是麻省理工学院国际问题研究所,在冷战时期主要做宣传战、心理战和第三世界的政治和经济发展研究,后来研究重点转向军控和苏联军事等领域。MIT是一个以工科为主的学校。给人印象特别深刻的是它跟军方的关系特别密切,好多大实验室的研发依靠的都是军工项目。MIT安全项目的规模在美国大学中大概是数一数二的,为美国培养了不少高水平的战略研究人才。在这里只简单介绍一下它独特的“军事研究员”(military fellow)制度。

所谓军事研究员制度,就是MIT跟美国军方签订协议,接受来自美国陆军、空军、海军和海军陆战队的现役校级军官到SSP项目做一年访问学者。他们可以参加SSP项目的所有课程与活动,一年访问结束后,从其所在军种的院校取得学分。我们知道,军事人员有自己的专业知识,他们到MIT来,可以给这里从事战略问题研究的师生传授很多军事知识,弥补他们知识结构上的欠缺。另一方面,军事研究员们也可以充分利用大学的智力资源,在开阔自己视野的同时,把那些他们在实际工作中遇到的难题提供给专家学者们研究讨论,寻找答案。这对双方都大有好处。搞战略研究的,军事是很重要的一个方面。如果对战役、战术这类东西完全不了解,上来就谈战略,其实是相当困难的。

5.哥伦比亚大学的军事行动与战略分析研讨班

哥伦比亚大学的战略研究与教学也非常重视军事。哥伦比亚大学有个战争与和平研究所,它主办的一个名为“军事行动与战略分析”的暑期研讨班(Summer Workshopon Analysis of Military Operations and Strategy,简称SWAMOS)每年夏季开班,主要讲授和讨论军事及战略问题。暑期班的学员,一部分是研究安全问题的博士生,另一部分是在大学中从事国际安全教学与研究的年轻教员,每期学员大约有20人,时间大概是三个星期左右。研讨班的讲师基本上是全美这个领域中最好的专家,有大学教授,有智库中的研究人员,还有政府部门中的相关人士。讲授的内容包括战略思想、陆海空军的基本知识、军事预算、常规战争、反叛乱战争、核战略等。每天上午上三个小时的课,中间休息15分钟,下午分组讨论两小时,然后是师生自由交流和体育活动,晚上再安排看战争片,看完之后还要讨论各种战略战术和战争伦理问题。去参加研究班之前,已经被要求先完成1000页左右的阅读量,材料寄到家。研讨班开班后,每天要看个七八十页材料。这样三个星期下来,等于上了一门本科生、一门研究生的课程。

哥伦比亚大学的“军事行动与战略分析”暑期研讨班课堂

通过考察上述这几所大学的战略精英培养机制,可以使我们认识到美国在培养自己的战略精英方面是多么地不遗余力而又行之有道。相比之下,中国的精英培养机制却存在着种种不足。许多口号都飘在空中,落不到实处,形不成真正的生产力。最重要的是,人的精气神,无论是在精英层次还是民众层次,都做得远远不如欧美。 (本文来源:网易军事 )

posted @ 2009-12-13 11:10 二胡 阅读(253) | 评论 (0)编辑 收藏

说起Google,可谓无人不知无人不晓。作为世界第一的搜索引擎,其强大的搜索功能,可以让你在瞬间找到你想要的一切。不过对于普通的计算机用户而言,Google是一个强大的搜索引擎;而对于黑客而言,则可能是一款绝佳的黑客工具。正因为google的检索能力强大,黑客可以构造特殊的关键字,使用Google搜索互联网上的相关隐私信息。通过Google,黑客甚至可以在几秒种内黑掉一个网站。这种利用Google搜索相关信息并进行入侵的过程就叫做Google Hack。

搜索也是一门艺术

         在我们平时使用搜索引擎的过程中,通常是将需要搜索的关键字输入搜索引擎,然后就开始了漫长的信息提取过程。其实Google对于搜索的关键字提供了多种语法,合理使用这些语法,将使我们得到的搜索结果更加精确。当然,Google允许用户使用这些语法的目的是为了获得更加精确的结果,但是黑客却可以利用这些语法构造出特殊的关键字,使搜索的结果中绝大部分都是存在漏洞的网站。
下面我们先来看看Google的部分语法:
         intitle:搜索网页标题中包含有特定字符的网页。例如输入“intitle: cbi”,这样网页标题中带有cbi的网页都会被搜索出来。
         inurl:搜索包含有特定字符的URL。例如输入“inurl:cbi”,则可以找到带有cbi字符的URL。
         intext:搜索网页正文内容中的指定字符,例如输入“intext:cbi”。这个语法类似我们平时在某些网站中使用的“文章内容搜索”功能。
         Filetype:搜索指定类型的文件。例如输入“filetype:cbi”,将返回所有以cbi结尾的文件URL。
         Site:找到与指定网站有联系的URL。例如输入“Site:family.chinaok.com”。所有和这个网站有联系的URL都会被显示。
         这些就是Google的常用语法,也是Google Hack的必用语法。虽然这只是Google语法中很小的部分,但是合理使用这些语法将产生意想不到的效果。

语法在Google Hack中的作用

         了解了Google的基本语法后,我们来看一下黑客是如何使用这些语法进行Google Hack的,这些语法在入侵的过程中又会起到怎样的作用呢?
         intitle
         intitle语法通常被用来搜索网站的后台、特殊页面和文件,通过在Google中搜索“intitle:登录”、“intitle:管理”就可以找到很多网站的后台登录页面。此外,intitle语法还可以被用在搜索文件上,例如搜索“intitle:"indexof"etc/shadow”就可以找到Linux中因为配置不合理而泄露出来的用户密码文件。
         inurl
         Google Hack中,inurl发挥的作用的最大,主要可以分为以下两个方面:寻找网站后台登录地址,搜索特殊URL。
         寻找网站后台登录地址:和intitle不同的是,inurl可以指定URL中的关键字,我们都知道网站的后台URL都是类似login.asp、admin.asp为结尾的,那么我们只要以“inurl:login.asp”、“inurl:admin.asp”为关键字进行搜索,同样可以找到很多网站的后台。此外,我们还可以搜索一下网站的数据库地址,以“inurl:data”、“inurl:db”为关键字进行搜索即可。


1.寻找网站的后台登录页面
         搜索特殊URL:通过inurl语法搜索特殊URL,我们可以找到很多网站程序的漏洞,例如最早IIS中的Uncode目录遍历漏洞,我们可以构造“inurl:/winnt/system32/cmd exe?/c+dir”这样的关键字进行搜索,不过目前要搜索到存在这种古董漏洞的网站是比较困难的。再比如前段日子很火的上传漏洞,我们使用““inurl:upload.asp”或“inurl:upload_soft.asp”即可找到很多上传页面,此时再用工具进行木马上传就可以完成入侵。


         intext
         intext的作用是搜索网页中的指定字符,这貌似在Google Hack中没有什么作用,不过在以“intext:to parent directory”为关键字进行搜索后,我们会很惊奇的发现,无数网站的目录暴露在我们眼前。我们可以在其中随意切换目录,浏览文件,就像拥有了一个简单的Webshell。形成这种现象的原因是由于IIS的配置疏忽。同样,中文IIS配置疏忽也可能出现类似的漏洞,我们用“intext:转到父目录”就可以找到很多有漏洞的中文网站。


2.随意浏览网站中的文件
         Filetype
         Filetype的作用是搜索指定文件。假如我们要搜索网站的数据库文件,那么可以以“filetype:mdb”为关键字进行搜索,很快就可以下载到不少网站的数据库文件。当然,Filetype语法的作用不仅于此,在和其他语法配合使用的时候更能显示出其强大作用。
         Site
         黑客使用Site,通常都是做入侵前的信息刺探。Site语法可以显示所有和目标网站有联系的页面,从中或多或少存在一些关于目标网站的资料,这对于黑客而言就是入侵的突破口,是关于目标网站的一份详尽的报告。

语法组合,威力加倍

         虽然上文中介绍的这几个语法能各自完成入侵中的一些步骤,但是只使用一个语法进行入侵,其效率是很低下的。Google Hack的威力在于能将多个语法组合起来,这样就可以快速地找到我们需要的东西。下面我们来模拟黑客是如何使用Google语法组合来入侵一个网站的。


    信息刺探
         黑客想入侵一个网站,通常第一步都是对目标网站进行信息刺探。这时可以使用“Site:目标网站”来获取相关网页,从中提取有用的资料。


3.搜索相关页面
         下载网站的数据库
         搜索“Site:目标网站 Filetype:mdb”就可以寻找目标网站的数据库,其中的Site语法限定搜索范围,Filetype决定搜索目标。用这种方法有一个缺点,就是下载到数据库的成功率较低。在这里我们还可以采用另一种语法组合,前提是目标网站存在IIS配置缺陷,即可以随意浏览站点文件夹,我们搜索“Site:目标网站 intext:to parent directory”来确定其是否存在此漏洞。在确定漏洞存在后,可以使用“Site:目标网站 intext:to parent directory+intext.mdb”进行数据库的搜索。


4.找到网站数据库


    登录后台管理
         下载到数据库后,我们就可以从中找到网站的管理员帐户和密码,并登录网站的后台。对于网站后台的查找,可以使用语法组合“Site:目标网站 intitle:管理”或者“Site:目标网站 inurl:login.asp”进行搜索,当然我们可以在这里进行联想,以不同的字符进行搜索,这样就有很大的概率可以找到网站的后台管理地址。接下去黑客就可以在后台上传Webshll,进一步提升权限,在此不再阐述。


    利用其他漏洞
         如果下载数据库不成功,我们还可以尝试其他的入侵方法。例如寻找上传漏洞,搜索“Site:目标网站 inurl:upload.asp”。此外,我们还可以根据一些程序漏洞的特征,定制出Google Hack的语句。
         Google Hack可以灵活地组合法语,合理的语法组合将使入侵显得易如反掌,再加入自己的搜索字符,Google完全可以成为你独一无二的黑客工具。

合理设置,防范Google Hack
   
5. 合理设置网站
         Google Hack貌似无孔不入,实则无非是利用了我们配置网站时的疏忽。例如上文中搜索“intext:to parent directory”即可找到很多可以浏览目录文件的网站,这都是由于没有设置好网站权限所造成的。在IIS中,设置用户访问网站权限时有一个选项,叫做“目录浏览”,如果你不小心选中了该项,那么其结果就如上文所述,可以让黑客肆意浏览你网站中的文件。
         这种漏洞的防范方法十分简单,在设置用户权限时不要选中“目录浏览”选项即可。


6.不要选中该项
         编写robots.txt文件
         robot.txt是专门针对搜索引擎机器人robot编写的一个纯文本文件。我们可以在这个文件中说明网站中不想被robot访问的部分,这样,我们网站的部分或全部内容就可以不被搜索引擎收录了,或者让搜索引擎只收录指定的内容。因此我们可以利用robots.txt让Google的机器人访问不了我们网站上的重要文件,Google Hack的威胁也就不存在了。


         编写的robots.txt文件内容如下:
User-agent: *
Disallow: /data/
Disallow: /db/


         其中“Disallow”参数后面的是禁止robot收录部分的路径,例如我们要让robot禁止收录网站目录下的“data”文件夹,只需要在Disallow参数后面加上“/data/”即可。如果想增加其他目录,只需按此格式继续添加。文件编写完成后将其上传到网站的根目录,就可以让网站远离Google Hack了

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/chaosa/archive/2007/10/16/1828301.aspx

posted @ 2009-12-11 09:41 二胡 阅读(1124) | 评论 (6)编辑 收藏

  这样的错误以前我也犯过,也见过不少人这样的写法!下面我也举个例子:
  

 public void writeFile(File f) {
  String content 
= null;
  
try {
   
byte[] b = new byte[1024];
   FileInputStream in 
= new FileInputStream(f);
   in.read(b);
   content 
= new String(b);
  }
 catch (Exception e) {
   System.out.println(e.getMessage());
  }


  
if (content.indexOf("hello"> -1{
   System.out.println(
"yes");
  }
 else {
   System.out.println(
"no");
  }

 }


 上面是个简单的方法,代码中有个隐藏的bug。我在维护一个系统的时候就遇到类似的代码,实际中类似的BUG隐藏
的更深!在对系统业务和代码不是很很熟悉的情况下,我推荐如下写法:

 1 public void writeFile(File f) {
 2  String content = null;
 3  try {
 4   byte[] b = new byte[1024];
 5   FileInputStream in = new FileInputStream(f);
 6   in.read(b);
 7   content = new String(b);
 8  }
 catch (Exception e) {
 9   content="";
10   //如果异常发生的话,content可能为空
11   //下面对content的操作就有可能发生NullPointerException异常
12   System.out.println(e.getMessage());
13  }

14  //下面操作有可能发生NullPointerException异常
15  if (content.indexOf("hello"> -1{
16   System.out.println("yes");
17  }
 else {
18   System.out.println("no");
19  }

20 }


 一般来说异常处理不推荐直接system.out.println打印出来!
 几条建议:
 如果无法处理某个异常,那就不要捕获它。
  ☆ 如果捕获了一个异常,请不要胡乱处理它。
  ☆ 尽量在靠近异常被抛出的地方捕获异常。
  ☆ 在捕获异常的地方将它记录到日志中,除非您打算将它重新抛出。
  ☆ 按照您的异常处理必须多精细来构造您的方法。
  ☆ 需要用几种类型的异常就用几种,尤其是对于应用程序异常。
  ☆ 把低层次的异常封装成层次较高程序员较容易理解的异常。
  ☆ 尽量输出造成异常的完整数据
  ☆ 尽量捕获具有特定含义的异常:比如SqlException,而不是简单地捕获一个Exception


  希望对大家有帮助!

参考:
http://www.blogjava.net/usherlight/archive/2006/10/23/76782.html

posted @ 2009-12-09 16:59 二胡 阅读(390) | 评论 (0)编辑 收藏

        cookie在web开发中应用的比较多!浏览器默认的接受cookie的,但是如果浏览器禁止了cookie,会出现什么情况了?
        在此我测试了如下系统:gmail,163邮箱,126邮箱,tom邮箱
        我在浏览器禁用cookie的情况下,登陆上述4邮箱!
        Gmail:
        正确输入用户名密码后,系统给出如下提示:
        

    163邮箱:

 正确输入用户名密码后,系统给出如下提示:让我搞不清楚是用户名密码错误还是其它错误!



    126邮箱
    
    正确输入用户名密码后,系统给出如下提示:让我搞不清楚是用户名密码错误还是其它错误!

    
    tom邮箱

    正确输入用户名密码后,系统给出如下提示:居然提示我是"非法请求"

 

        从上面测试中感受到不同的用户体验.对大多数做web开发的人来说,判断浏览器是否支持Cookie并不是什么难事!难的是对细节的处理!
        也许我们与优秀的产品差距最大的是对细节的处理!
        
        附: 我测试用的是IE6.0

posted @ 2009-12-08 15:03 二胡 阅读(1752) | 评论 (4)编辑 收藏

 关于沟通,大家都知道其重要性!但是,在实际工作中有不少人做的不够好,也包括我自己!
 客户提出的问题,原因大概有一下几种!
 一、客户对软件系统不熟悉,把属正常情况的现象误以为是问题
     这时候就需要我们听清楚客户的描述,然后根据客户的描述一步步的确认其操作等!
     确保用户的操作的正确性。
 二、系统的bug
   一般要了解如下情况!
   2.1 who:谁操作系统的时候出问题了!一般记录系统的登陆名
   2.2 when:在什么时候操作出问题了!
   2.3 how:怎么操作的!这个比较重要,一般记录的是操作步骤!
   2.4 contact:客户的联系方式,邮件还是电话等。
   2.4 feedback:给客户的反馈,答复什么时候解决此问题!
   2.5 follow:既跟踪,确保客户已经解决此问题!而不能简单告诉用户怎么做,在此之后最后跟用户确认一下此问题是否解决!
   
   我的感受:听客户的意见,然后确认!反复之!
   
posted @ 2009-12-08 11:01 二胡 阅读(156) | 评论 (0)编辑 收藏

        在Web开发中常用到Cookie,所以有时需要判断客户端浏览器是否禁用Cookie!我看了一个gmail的页面原码!它是这样写的,如下:

var c="jscookietest=valid";
document.cookie=c;
if(document.cookie.indexOf(c)==-1)
location="html/zh-CN/nocookies.html";
//不支持Cookie

原理是:先对Cookie赋值,然后再读取
希望对大家有用!
posted @ 2009-12-07 09:17 二胡 阅读(2246) | 评论 (1)编辑 收藏

     JavaScript中的null和undefined,感觉有点易混淆!把Ecma-262下载下来看了看!摘要如下:

     Undefined Value
The undefined value is a primitive value used when a variable has not been assigned a value.
4.3.10 Undefined Type
The type Undefined has exactly one value, called undefined.
4.3.11 Null Value
The null value is a primitive value that represents the null, empty, or non-existent reference.
4.3.12 Null Type
The type Null has exactly one value, called null.

感兴趣的可以看看如下文章:
http://www.blogjava.net/hulizhong/archive/2009/10/22/299430.html
posted @ 2009-12-05 13:45 二胡 阅读(224) | 评论 (0)编辑 收藏

使用聚集索引  
  聚集索引确定表中数据的物理顺序。聚集索引类似于电话簿,后者按姓氏排列数据。由于聚集索引规定数据在表中的物理存储顺序,因此一个表只能包含一个聚集索引。但该索引可以包含多个列(组合索引),就像电话簿按姓氏和名字进行组织一样。  
   
  聚集索引对于那些经常要搜索范围值的列特别有效。使用聚集索引找到包含第一个值的行后,便可以确保包含后续索引值的行在物理相邻。例如,如果应用程序执行的一个查询经常检索某一日期范围内的记录,则使用聚集索引可以迅速找到包含开始日期的行,然后检索表中所有相邻的行,直到到达结束日期。这样有助于提高此类查询的性能。同样,如果对从表中检索的数据进行排序时经常要用到某一列,则可以将该表在该列上聚集(物理排序),避免每次查询该列时都进行排序,从而节省成本。  
   
  当索引值唯一时,使用聚集索引查找特定的行也很有效率。例如,使用唯一雇员   ID   列   emp_id   查找特定雇员的最快速的方法,是在   emp_id   列上创建聚集索引或   PRIMARY   KEY   约束  
   
   
  使用非聚集索引  
  非聚集索引与课本中的索引类似。数据存储在一个地方,索引存储在另一个地方,索引带有指针指向数据的存储位置。索引中的项目按索引键值的顺序存储,而表中的信息按另一种顺序存储(这可以由聚集索引规定)。如果在表中未创建聚集索引,则无法保证这些行具有任何特定的顺序。  
   
  与使用书中索引的方式相似,Microsoft®   SQL   Server™   2000   在搜索数据值时,先对非聚集索引进行搜索,找到数据值在表中的位置,然后从该位置直接检索数据。这使非聚集索引成为精确匹配查询的最佳方法,因为索引包含描述查询所搜索的数据值在表中的精确位置的条目。如果基础表使用聚集索引排序,则该位置为聚集键值;否则,该位置为包含行的文件号、页号和槽号的行   ID   (RID)。例如,对于在   emp_id   列上有非聚集索引的表,如要搜索其雇员   ID   (emp_id),SQL   Server   会在索引中查找这样一个条目,该条目精确列出匹配的   emp_id   列在表中的页和行,然后直接转到该页该行。  

-------------------------------------------------------分割线-------------------------------------------------------
        在工作中遇见了SQL性能问题,最后在同事的帮助下解决了此问题:就是通过建聚集索引的方式解决的!
         原来的SQL中有如下的where语句:
         and  datediff(day, date1,'2009-1-01')<=

     and  datediff(day, date1,'2009-2-03')>=

        在date1建了聚集索引,SQL修改如下:
          and   date1>='2009-1-01'

     and  date1<='2009-2-03' 

         去掉了datediff函数,因为用函数的话就不会用到date1的聚集索引! 在此记录一下,以备后查!
    
posted @ 2009-12-04 15:19 二胡 阅读(158) | 评论 (0)编辑 收藏

     在一些框架中看到了类似这样的写法:+new Date();感觉有些怪,查阅了相关资料和一些网友的帮助.对此用法解释如下,希望对大家有所帮助,不合适的地方请大家指正!
一,对于引用类型对象(我指的是String,Date,Object,Array,Function,Boolean)的+运算符运算过程如下!
    1,首先调用此对象的valueOf方法,得到返回数值A
    2,然后把此数值A转换成数字,得到的是最终数值 

    我的测试如下:
   
function w(s){
      document.writeln("<br/>");
      document.writeln(s);
      document.writeln("<br/>-----------------------------");
      }
   String.prototype.valueOf=function(){return 1;};
   w(+new String("sss"));//输出1
   String.prototype.valueOf=function(){return "a";};
    w(+new String("sss"));//输出NaN
  
  
   Date.prototype.valueOf=function(){return 1;};
   w(+new Date());//输出1
   Date.prototype.valueOf=function(){return "a";};
    w(+new Date());//输出NaN
  
   Object.prototype.valueOf=function(){return 1;};
   w(+{});//输出1
   Object.prototype.valueOf=function(){return "a";};
    w(+{});//输出NaN
  
   Array.prototype.valueOf=function(){return 1;};
   w(+[]);//输出1
   Array.prototype.valueOf=function(){return "a";};
    w(+[]);//输出NaN
  
   var s=function(){};
   Function.prototype.valueOf=function(){return 1;};
   w(+s);//输出1
   Function.prototype.valueOf=function(){return "a";};
    w(+s);//输出NaN
  
   Boolean.prototype.valueOf=function(){return 1;};
   w(+new Boolean());//输出1
   Boolean.prototype.valueOf=function(){return "a";};
   w(+new Boolean());//输出NaN
二,对于基本数据数据类型,其值转换成数字
    w(+5);//输出5
    w(+true);//输出1
    w(+false);//输出0
    w(+"ss");//输出NaN
    w(+"111");//输出111

posted @ 2009-12-04 10:00 二胡 阅读(1138) | 评论 (0)编辑 收藏

    在看gmail页面原码的时候,有类似(new Date).getTime()/(new Image).src的写法.这样的写法与new Date().getTime()有什么不同?这样写有什么好处呢?
不明白,那位知道给说一下!
posted @ 2009-12-03 13:34 二胡 阅读(1136) | 评论 (1)编辑 收藏

      最近领导安排我做手机软件开发(j2me),领一手机专做测试!今天不经意见看了手机的短信收件箱,
短信主要有如下:
  1.1     周立波的"我为财狂"手机视频免费看(可惜俺听不懂上海话)
  1.2     移动的短信回执服务推荐
  1.3     基金推荐(既然你提醒投资有风险了,还买它干啥)
  1.4     航班、机票推荐(哥们一直坐火车)
  1.5     某星级酒店招聘(哥们不是帅哥,不符条件)
  1.6     放高利贷的(听说是驴打滚利)
  1.7     <风声>经典镜头免费看
  1.8     给个卡号让汇钱的(你呀谁啊,我不认识,而且发了2条)

      呵呵,列举一下!博你一笑!
posted @ 2009-12-03 10:56 二胡 阅读(167) | 评论 (0)编辑 收藏

SQL Server 中对已经定义的变量赋值的方式用两种,分别是 SET 和 SELECT。
对于这两种方式的区别,SQL Server 联机丛书中已经有详细的说明,但很多时候我们
并没有注意,其实这两种方式还是有很多差别的。

SQL Server推荐使用 SET 而不是 SELECT 对变量进行赋值。
当表达式返回一个值并对一个变量进行赋值时,推荐使用 SET 方法。

下表列出 SET 与 SELECT 的区别。请特别注意红色部分。

set select
同时对多个变量同时赋值 不支持 支持
表达式返回多个值时 出错 将返回的最后一个值赋给变量
表达式未返回值 变量被赋null值 变量保持原值

下面以具体示例来说明问题:

create table chinadba1(
userid int ,
addr varchar(128)
)
go
insert into chinadba1(userid,addr) values(1,'addr1')
insert into chinadba1(userid,addr) values(2,'addr2')
insert into chinadba1(userid,addr) values(3,'addr3')
go

表达式返回多个值时,使用 SET 赋值


declare @addr varchar(128)
set @addr = (select addr from chinadba1)
/*
--出错信息为
服务器: 消息 512,级别 16,状态 1,行 2
子查询返回的值多于一个。当子查询跟随在 =、!=、<、<=、>、>= 之后,或子查询用作表达式时,这种情况是不允许的。
*/
go

表达式返回多个值时,使用 SELECT 赋值

declare @addr varchar(128)
select @addr = addr from chinadba1
print @addr --结果集中最后一个 addr 列的值
--结果: addr3
go

表达式未返回值时,使用 SET 赋值

declare @addr varchar(128)
set @addr = '初始值'
set @addr = (select addr from chinadba1 where userid = 4 )
print @addr --null值
go

表达式未返回值时,使用 SELECT 赋值

declare @addr varchar(128)
set @addr = '初始值'
select @addr = addr from chinadba1 where userid = 4
print @addr --保持原值
go

需要注意的是,SELECT 也可以将标量子查询的值赋给变量,如果标量子查询不返回值,则变量被置为 null 值。
此时与 使用 SET 赋值是完全相同的
对标量子查询的概念大家应该都觉得陌生,举个例子就能说明

declare @addr varchar(128)
set @addr = '初始值'
--select addr from chinadba1 where userid = 4 为标量子查询语句
select @addr = (select addr from chinadba1 where userid = 4)
print @addr --null值
go

转自:http://blog.csdn.net/shouyenet1/archive/2008/12/13/3511818.aspx

posted @ 2009-12-02 17:10 二胡 阅读(202) | 评论 (0)编辑 收藏

     十字路口过多了,发现一个小现象!就是在黄灯亮起后,绿灯亮起之前!不少人蜂拥而过,大家都想着快一点到马路对面!就是过马路的过程中,有不少插曲:被自行车碰到了,因马路不平而崴脚了,等等!在人生的道路上也有类似的现象!为了更好的奔向目标,请大家毋忘脚下!
posted @ 2009-12-02 08:54 二胡 阅读(168) | 评论 (0)编辑 收藏

转 http://blog.csdn.net/rjchen/archive/2009/11/19/4835865.aspx

管理下属方面有三个人对我影响比较大。

第一个人是金山前任CEO的雷军先生,他说过一句话我印象很深,他说”什么是执行力,执行力就是Double Check!”

第二个人是阿里巴巴的前COO李琪先生,他有个著名的‘16字真言’,一直被我奉为与下属共同成长的宝典。

 

第三个人是台湾的星云大师,他说过“最高明的管理,就是不管理”,他在《包容的智慧》一书中讲过这样一个故事。

“有一年,台湾陷入经济恐慌,大家为了年终奖金、加薪,游行示威,社会动荡不安。我们佛光山也有退伍老兵在这里服务,我跟他们开玩笑说,你们也拿个小旗子,到我们的门口摇旗,要求加薪。一位老兵说,我们不要。我问为什么?他说:比金钱更宝贵的东西,就是尊重。我们在这里服务,法师们每天见到我们,合掌、点头,跟我们讲老伯早、老伯好,我们在精神上就很富有了。我们不要加薪,我们要人尊重。”

最后,星云大师说这个世界上人与人相处,最好的管理就是尊重他,爱护他,善用他。

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/rjchen/archive/2009/11/19/4835865.aspx

posted @ 2009-12-01 17:39 二胡 阅读(169) | 评论 (0)编辑 收藏

     在工作中,有如下需求.通过case的嵌套解决了此问题!特此记录一下!(我用的是sql server 2005)
    
    

在查询的时候,有这样的一个需求
数据库内容如下:
iD  description

1     TableClassA.getName()
2     TableClassB.getName(),exception
3     TableClassC,getName()
4     TableClassD,getName().null

我想查询输入以下结果:
iD  description

1     TableClassA
2     TableClassB
3     TableClassC
4     TableClassD
及查询的结果是.号或,前的内容
SQL如下

select id,
     case
    when (CHARINDEX(',',description)>0) and (CHARINDEX('.',description)>0)
     then --在这个做了判断,如同时包含.和,则判断.和,那个在前
      case 
       when CHARINDEX(',',description)>CHARINDEX('.',description) then left(description,CHARINDEX('.',description)-1)
       when CHARINDEX(',',description)<CHARINDEX('.',description) then left(description,CHARINDEX(',',description)-1)
      end
    when (CHARINDEX(',',description)>0) then left(description,CHARINDEX(',',description)-1)
    when (CHARINDEX('.',description)>0) then left(description,CHARINDEX('.',description)-1)
    else description
   end
   as description
  from  tablename

posted @ 2009-12-01 16:28 二胡 阅读(891) | 评论 (0)编辑 收藏

转 http://blog.csdn.net/g9yuayon/archive/2007/02/23/1512851.aspx

从Jao的Programming Musing 看到的:Babar Kazar 整理了一堆经典论文。Jao强烈建议每个严肃的程序员读每篇论文,说它们都或多或少有意思。粗粗扫了一下,很多论文都没读过。挑了些俺多少知道一点的介绍。


· An axiomatic basis for computer programming C. A. R. Hoare
Tony Hoare名下的公理化语义(Axiomatic Semantics)。著名的Hoare Triples, P{C}Q, 就是从这里来的。论文不长,双列6页。前辈们就是这样的,6页纸就能开宗立派。不像俺,6页纸连介绍部分都写不周全。哪位老大想知道怎么证明程序正确。前置条件,不变条件,后置条件的妙用,可以用这篇论文开牙。
· Communicating Sequential Processes (CSP) C. A. R. Hoare
Hoare, 又见Hoare。其实也正常。牛人之牛,就在于成就深广。链接的文档应该不算论文,而算专著。260页。从1985年推出到现在20多年过去,这本书的引用率在CS历史上排名第三,可见其影响之深。对并发编程有强烈兴趣的老大可以去钻研一把。我没读过。
· Call-by-name, call-by-value, and the lambda calculus Gordon Plotkin
没读过。只见LtU介绍过。Gordon老大这篇论文的要点之一是要想顺利地对程序进行推导,就需要有合适的lambda理论。想深入理解call-by-name,call-by-value,和lambda算子的老大们可以上了。
· Towards a theory of type structure John C. Reynolds
号称经典中的经典。不过也没读过。类型系统一直是编程语言研发的热点,也是非常有趣的方向――类型系统的编程好比让机器证明一系列定理。Reynolds在论文里讨论了什么才是正确的类型结构,和句法正确必须独立于任何具体的类型表达形式,并且给出了带类型的lambda算子的一种扩展,允许他描述用户自定义类型和多态函数。满篇公式,有勇气去读的老大要有心理准备。
· Structured Programming with go to Statements Donald E. Knuth
这篇论文详细结构化编程时讨论了什么时候用goto,什么时候不用goto。高爷爷精细务实的态度非常值得学习。高老太爷用了一辈子goto(MIX和MMIX程序里没了Goto怎么玩儿得转嗫?),岂能轻易被Dijkstra对goto的批评吓退?他仔细探讨了几种不同的程序,考察goto用在那些程序里的利弊。最后得出结论,goto在某些程序里仍然高效实用。虽然论文是30年前的,但里面的分析手法和利用goto的优化技术至今可用。
· Definitional interpreters for higher-order programming languages John C. Reynolds
这篇文章俺喜欢。”Metacircular”这个性感的概念就是在这篇论文里首次提出的。想深入了解用一门语言写出的解释器定义这门语言自身的神奇理念,这篇论文是必读材料。有兴趣的老大可以先读SICP的第四章。
· An APL Machine 1970 Philip S. Abrams
只知道APL是门有历史意义的语言。顺便说一句,APL这个名字太土了。A Programming Language ==APL。象什么话嘛。


· The Anatomy of a Large-Scale Hypertextual Web Search Engine Sergey Brin and Lawrence Page
网络是个大的矩阵(transition probability matrix of Markov Chain)。网页的声誉(page rank)就是这个巨大矩阵的principle eigenvector的某个元素。嗯,反正我只有佩服的份儿。
· No Silver Bullet: Essence and Accidents of Software Engineering Frederic P. Brooks, Jr.
地球银都知道。不用俺多嘴了。
· A Mathematical Theory of Communication Claude Shannon
Bell实验室当年辉煌一时。出了名的叫人做A,结果发明了B。香农老大就是其中杰出代表。香农进了Bell实验室后,居然没人吩咐他干嘛。香农老大转念一想,自己喜欢数学,Bell的生意尽在通讯,干嘛不看看把数学应用到通讯上有什么结果呢?于是1948年这篇论文问世乐。搞通讯的人崩溃乐。现代信息理论就诞生乐。
· Bayesian Networks without Tears
贝叶斯理论热了好几年了。估计还会继续热下去。现在信息越来越多,我们已经审美疲劳。大家渴望的不是信息,而是知识。靠个人的力量把信息提炼成知识太慢,我们需要机器的帮忙。机器学习不热都难,而贝叶斯理论在机器学习里有很好的应用。这篇文章行为浅显,可以轻松读完。对了,那个人人喝骂的微软回形针的智能引擎就是用贝叶斯网络实现的。
· A Universal Algorithm for Sequential Data Compression
没读过。无耻地找个借口:我们系开信息理论课的时候,俺刚好毕业。
· A Relational Model of Data for Large Shared Data Banks 1970 Edgar F. Codd
没有关系代数,人类将会怎样?Codd划时代的论文奠定了现代数据库的基础。嘿嘿,其实俺也没有读过这篇论文。顺便说一句,现在的ORM试图把data schema和对象系统映射起来。问题是,data schema只是对关系的一种表达方式而已,还和具体的系统实现有关。也许把对象间的结构和关系映射起来才是正道。
· Let's Build a Compiler 1988-1995
教你一步一步写出一坨编译器。不算论文吧。一篇相当不错的指南。
· Gauging Similarity via N-Grams: Language-Independent Sorting... Marc Damashek
第一次听说
· Worse Is Better Richard P. Gabriel
网上脍炙人口的文章。很有教育意义。简单说,worse is better包括下面几点:
-- 简单:设计要简单。但如果接口和实现不能两全,追求实现的简单。文章里给出的Unix vs Multics的例子非常有意思。
-- 正确:程序必须在所有可见的方面正确。其它地方,如果简单和正确不能两全,追求简单。
-- 一致性:程序不能太不一致。但为了简单,可以在少数地方不一致。
-- 完备性:程序应该尽可能照顾到重要的地方,但是不能牺牲简洁。
强烈推荐。
· Hints on Programming Language Design C.A.R. Hoare
Hoare对设计语言的经验总结。这些经验至今有效。文章很容易读,读后绝对增长程序设计的功力。
· Why Functional Programming Matters John Hughes
为普通程序员准备的大餐,所以写得通俗。没有公式,也没有拗口的术语。着重展示了Fold和Map的强大抽象能力。不由想到我在大学里修的一门课,编程语言。课是好课,老师是一流老师。课上我们学习了浅显的程序语言理论,重点学习了函数编程(用Common Lisp)和逻辑编程(用Prolog)。这门课彻底改变我对编程的理解,明白了imperative programming和OO programming外还有精彩世界。至今想来都觉得幸运。那门课的作业也很有意思,实现一个驻留内存的数据库,支持关系代数里的常见操作。
· On the Expressive Power of Programming Languages Matthias Felleisen
没读过。待读。
· The Early History Of Smalltalk Alan Kay
还有什么好说的呢?Alan Kay这个名字说明一切。30年前Alan Kay就做出来Smalltalk,现在想来仍然让人惊叹。引一段文章Alan Kay评述Smalltalk的话:In computer terms, Smalltalk is a recursion on the notion of computer itself. Instead of dividing "computer stuff" into things each less strong than the whole--like data structures, procedures, and functions which are the usual paraphernalia of programming languages--each Smalltalk object is a recursion on the entire possibilities of the computer. Thus its semantics are a bit like having thousands and thousands of computer all hooked together by a very fast network. Questions of concrete representation can thus be postponed almost indefinitely because we are mainly concerned that the computers behave appropriately, and are interested in particular strategies only if the results are off or come back too slowly.
· Computer Programming as an Art Donald E. Knuth
高老太爷在1974年图灵奖仪式上的致词。真是顶尖geek的风范啊。高太爷在文章里解释了问什么他的书取名为《编程的艺术》。明显他对人们谈到编程时把科学置于艺术之上很不了然。高爷爷追溯“艺术”的词源,说艺术的本意就是技能,也是技术和技巧两次的起源。从这里开始,他开始讨论艺术和科学的关联,讨论艺术在编程里的表现形式和意义。用他的话说,他作为教育者和作者的毕生目标就是叫人写美妙的程序。读起来让人心潮彭湃的说。
· The next 700 programming languages Peter J. Landin
42年前的论文,影响深远。Peter在论文里描述的函数语言ISWIM(If You See What I Mean)现在没有几个人知道了。但他对lambda算子的推崇和对函数语言的论述影响了后来的函数语言设计。
· Recursive Functions of Symbolic Expressions and their Computation by Machine (Part I) 1960 John McCarthy
47年前提出LISP的那篇著名论文。没读过。动态类型检查,Garbage Collection, 递归函数,S-expression, 程序及数据。。。可谓贡献辉煌。


· FORTH - A Language for Interactive Computing Charles H.Moore
只知道Forth是一门stack oriented的编程语言,影响了后来的一些语言,比如CAT。其它的就不知道了。
· Teach Yourself Programming in Ten Years 2001 Peter Norvig
大牛之所以为大牛,原因之一就是目光深远。这篇文章批评那些《24秒学会C++》之类教材的无稽,讨论了学习编程,从菜鸟变成鲲鹏的方法。中文版已经传得满世界都是,赶快找来看吧。Peter Norvig的网站上还有很多高质量的文章。强烈推荐一读。
· The Definition and Implementation of a Computer Language based on constraints Guy Lewis Steele Jr.
好像是Guy Steels的硕士论文。没读过。
· Growing a Language Guy Lewis Steele Jr.
好文!G老大在OOPSLA 98上的主题演讲。G老大主张应该采取渐进的方式设计一门可以被自由扩展的语言(LISP圈子里的牛人们多半都持这种观点吧?)。这篇演讲稿针对该观点做了精练地论述。说起进化的观点,可以参看另外一篇好文章,SICP作者之一,Jay Sussman的近作。
· Epigrams on Programming Alan J. Perlis
A老大发表的一系列关于编程的格言。幽默而深刻。每读必笑。笑后必哭。嗯嗯嗯,夸张一下。不要当真。
· The Complexity of Theorem Proving Procedures Stephen A. Cook
仙风道骨的库克爷爷的成名作。这篇文章一出,好比有人在加州荒漠里发现第一块狗头金,立刻掀起开发加州的狂潮。计算复杂性理论迅速遍地开花。相比这篇论文开创性的贡献,库克因此得到图灵奖不过小小点缀。NP-Complete在这篇论文里被严格定义。更重要的是,库克证明了第一个NP-Complete的问题,SAT(Boolean Satisfiability Problem)。有了SAT,再加上折磨了无数学生的Polynomial Reducibility,无数的NPC问题就出现乐。。。别看俺在这里唾沫横飞,当年做有关计算理论的证明题还是相当吃力的,没有少熬夜。奇怪的是,某一天我给同学讲解我的解法,NPC的相关定义突然变得清晰起来。当初让我绞尽脑汁的证明竟然变得相当机械。后来知道,给人讲解(包括写作)是非常有效地学习方法。怀着备课的目标读文章,假设自己给别人讲解正在读的文章,有助快速理解所读内容。SAT的证明相当复杂,我反正没有耐心读完。
· Steps Toward Artificial Intelligence Marvin Minsky
AI的奠基论文。不过我没读过。
· The Original 'Lambda Papers' Guy Steele and Gerald Sussman
一系列讲解lambda算子和scheme设计的经典论文。学scheme时读过,对理解scheme的设计理念很有帮助。


· The UNIX Time-Sharing System Dennis Ritchie and Ken Thompson
作者不用介绍了吧?这篇文章里介绍的Unix特性早为人熟知。不过第八部分(VIII Perspective)讨论了作者的设计理念,仍然值得一读。


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/g9yuayon/archive/2007/02/23/1512851.aspx

posted @ 2009-11-25 14:53 二胡 阅读(269) | 评论 (0)编辑 收藏

转 http://xiaofengtoo.javaeye.com/blog/149129
利用Eclipse进行重构

重构和单元测试是程序员的两大法宝,他们的作用就像空气和水对于人一样,平凡,不起眼,但是意义深重。预善事,必先利器,本文就介绍怎样在Eclipse中进行重构。

本文介绍了Eclipse支持的重构种类,它们的含义,以及怎样重构。本文同时也可以作为学习重构知识的快速手册。

什么是重构
重构是指在保持程序的全部功能的基础上改变程序结构的过程。重构的类型有很多,如更改类名,改变方法名,或者提取代码到方法中。每一次重构,都要执行一系列的步骤,这些步骤要保证代码和原代码相一致。

为什么重构很重要   
         手工重构时,很容易在代码中引入错误,例如拼写错误或者漏掉了重构的某一步。为了防止引入错误,在每次重构前后,都要执行充分的测试。你可能会好奇重构是否是值得的。
重构的理由很多。你可能想要更新一段代码很烂的程序。或者最初的设计队伍都不在了,现在队伍中每人了解这些程序。为了更新,你必须要重新设计构建程序来满足你的需求。另一个原因是原来的设计无法使你将新的特性添加进去。为了添加进去,你要重构这些代码。第三个原因是一个自动重构的工具可以为你自动生成代码,例如Eclipse中的重构功能。使用重构,你可以在重写尽量少的代码和仍保持软件功能的同时,使代码的逻辑性更好。


测试
在重构时,测试是十分重要的。应为重构改变了代码的结构,你要保证重构后代码的功能没有被改变。手工重构时,一个好的测试套是必须的。使用自动重构工具是,测试也是必要的,但不需要很频繁,应为自动重构工具不会产生手工重构时的那些错误,如拼写错误。
Eclipse中可以使用JUnit方便的为程序创建测试代码,具体方法不在本文描述。


Eclipse中的重构
JDT,Eclipse中的Java插件,能够对Java项目,类,或成员进行多种类型的自动重构。可以采取多种方法快速的为Java项目中的某个元素进行重构。
为某些元素进行重构的前提是你必须选中他们。你可以在多个视图中选择这些元素,像大纲视图或包浏览视图。可以按住Ctrl或Shift键,在视图中选择多个元素。另外一种选择的方法是使该元素的编辑区高亮显示,或者把鼠标定位到源程序文件。在选中希望重构的元素后,可以从重构菜单的下拉项选择重构,也可以从右键单击后弹出菜单中选择重构子菜单。同时,Eclipse还提供了重构的快捷键操作。
某些重构可以应用在任意元素上,有些则只能用在特定类型的元素上,如类或方法。在本文的最后的表格中,列出了重构能够应用的元素类型,以及重构的快捷键。
Eclipse中,所有的重构都能够在正式执行之前预览一下。在重构对话框中点击“预览”按钮,可以查看所有将要被改变的地方。唯一没有预览按钮的的重构是Pull Up,在它的重构向导中,到最后,预览面板总会出现。可以将其中的个别变化反选掉,这样这些改变就不会生效。


撤销和重做
       在重构菜单中有撤销和重做项。他们和编辑菜单中的撤销重做不同。即使重构改变了很多文件,编辑菜单中的撤销重做只会更改当前文件。重构菜单中的撤销和重做则会对一次重构的所有文件进行撤销和重做操作。但是在使用时,它们有一定的限制。
重构后,无论重构改变了文件与否,如果任一个文件被另外改变而且保存了,你就无法撤销或重做这个重构。假如一个文件在重构中被修改了,然后又被编辑了,但是还没有保存,这时就会有错误信息提示,如果你想要撤销或重做该重构,必须撤销未保存的文件。
只要注意到以上的限制条件,你就可以随心所欲的对重构进行撤销或重做。你甚至能够编译,运行你的程序测试一下,然后再撤销该重构,只要你没有改变并保存任何文件。

Eclipse中的重构类型
       如果你看一下Eclipse的重构菜单,可以看到四部分。第一部分是撤销和重做。其他的三部分包含Eclipse提供的三种类型的重构。

       第一种类型的重构改变代码的物理结构,像Rename和Move。第二种是在类层次上改变代码结构,例如Pull Up和Push Down。第三种是改变类内部的代码,像Extract Method和Encapsulate Field。这三部分的重构列表如下。

类型1 物理结构
l         Rename
l         Move
l         Change Method signature
l         Convert Anonymous Class to Nested
l         Convert Member Type to New File

类型2 类层次结构
l         Push Down
l         Push Up
l         Extract Interface
l         Generalize Type (Eclipse 3)
l         User Supertype Where Possible
类型3 类内部结构
l         Inline
l         Extract Method
l         Extract Local Variable
l         Extract Constant
l         Introduce Parameter
l         Introduce Factory
l         Encapsulate Field



Rename:
       Rename用来改变一个Java元素的名字。虽然你可以手工改变Java文件Java元素的名字,但是这样不能自动更新所有引用它们的文件或Java元素。你必须在项目中搜索文件然后手工替换这些引用。很可能你就会漏掉一个或者改错一个。Rename重构会智能的更新所有有此引用的地方。
       有时候,Java元素的名字不是很明了,或者它的功能已经改变了。为了保持代码的可读性,该元素的名字也要更新。使用Rename重构,能够十分快捷的更新元素的名字和所有引用它的地方。
       要为一个Java元素改名,在包浏览视图或大纲视图选中该元素,从重构菜单中选择Rename项,或者使用快捷键Alt+Shift+R。Rename对话框会出现。在这里添入新的名字,选择是否更新该元素的引用。点击预览按钮,会打开预览窗口,在这里,你可以看到那些内容会被改变。点击OK按钮,重构结束。

Move
       Move和Rename很相似。它用来把元素从一个位置移动到另一个位置。它主要用来将类从一个包移动到另一个包。选中要移动的元素,从重构菜单中选择Move,或者使用快捷键,Alt+Shift+V,在弹出窗口中选择要移动的目的地。你仍然可以用预览功能检查一下有什么改变,也可以按OK按钮直接让其生效。


Change Method Signature
       更改方法签名能够改变参数名,参数类型,参数顺序,返回类型,以及方法的可见性。也可以添加,删除参数。
       要执行此重构,选择要重构的方法,选中重构菜单的更改方法签名项,会出现更改方法签名对话框。

在此对话框中选择方法的修饰词,返回类型,参数。参数的添加,修改,移动,删除可以通过右边的按钮控制。当添加新的参数时,会自动赋予默认值。凡是调用此方法的地方都会用此默认值作为参数输入。
       改变方法签名可能在方法中导致问题,如果有问题,当你点击预览或OK时,会被标记出来。
      

Move Members Type to New File
       此重构将嵌套类转为一个单独类。将会创建一个新的Java文件包含此嵌套类。选中要重构的类,在重构菜单上选择Move Member Type to New File项,在弹出的对话框中添入要创建的实例的名字。

      

Push Down
       此重构将算中的方法和成员从父类中移动到它的直接子类中,所有下推的方法都可选作为一个抽象方法留在父类中。下推重构对于重新构建项目设计十分有用。
         选择若干方法或成员,从重构菜单中选择下推项,弹出下推对话框。
        
       在此对话框中,可以分别选择方法或成员,所有选中元素都会移动到当前类的子类中。当点击Add Required按钮时,所有已选择元素所必需的元素也会自动选上,此行为并不能保证所有必须的元素都能自动选中,还是需要人工确认。当有方法被选中时,编辑按钮就会可用,点击编辑按钮,弹出编辑对话框。在其中可以选择为选中方法在当前类中遗留抽象方法,还是在当前类中删除这些方法。双击一天选中的方法,也可以打开编辑对话框。在方法的Action列点击,会出现一个下拉列表,可以在其中选择遗留抽象方法还是在当前类中删除方法。按回车键确认编辑结果。


Pull Up
       上移与下推类似,也是在类之间移动方法和成员。上移将方法或成员从一个类移动到它的一个父类中。选中若干个方法或成员,在重构菜单中选择上移项,上移向导马上会出现。
       在选择目标类多选框中,列出了当前类继承的所有父类。你只能将方法或成员移动到它们其中的一个里面。
       如果在选中方法的Action列,被设置成在目标类中声明抽象方法,那么在目标类的非抽象子类中创建必须的方法选项变为可选。当它选中时,目标类的所有子类,如果它们中没有选中的方法,则会为它们创建选中的方法。
       和在下推中一样,选择多个方法,点击编辑按钮,或者双击一个方法,都会打开编辑成员对话框。其中有两个选项,上移和在目标类中声明抽象方法。上移只是简单的复制方法到到父类中,并提供选择是否在当前类中删除该方法。在目标类中声明抽象方法会在父类中创建一个选中方法的抽象方法,如果父类不是抽象类则置为抽象类,最后选中方法留在当前类中。和在下推中一样,也可以点击Action列,可以在出现的下拉列表中选择。
       如果方法的Action列选为上移,在下一步的向导中,将会要求你选择是否在当前类中删除这些方法,选中的方法会在当前类中被删除。
       在向导的任意一步都可以按完成按钮,结束重构操作,此时按照默认规则进行重构。


Extract Interface
       提炼接口可以从一个存在的类中创建一个接口。你可以选择在接口中包含着个类的那些方法。选中一个类,从重构菜单选择提炼接口项,就可以打开提炼接口对话框。
       这此对话框中添入接口的名字,选择希望包含的方法,在这个列表里面只列出了公共方法。选中改变对类[当前类名]的应用为对接口的引用选择框,将把所有对当前类的引用更新为对此接口的引用。


Generalize Type
       泛化类型重构可以将一个声明对象的类型改变为它的超类,选择变量,参数,对象成员,方法返回类型,然后选择重构菜单的泛化类型项。在打开的泛化类型对话框,选择希望的新类型,然后点击完成按钮,结束重构。


Use Supertype Where Possible
       使用超类会将对一个特定类型的引用改变为对它的超类的引用。选择一个类,选中重构菜单的使用超类项,会打开使用超类对话框。选中希望的超类类型,点击完成按钮完成重构。重构后,instanceof 表达式也会做相应的替换。



Inline
       内联是用代码或值来取代调用方法的地方,静态final对象成员,或局部变量。比如说,如果你内联一个方法调用,这个调用的地方就会被替换为该方法体。要内联一个方法,静态final对象成员,局部变量,选中这些元素,在重构菜单中选择内联项,或者使用快捷键Alt + Ctrl + I。在随后打开的内联对话框,你可以选择是否要内联所有的调用,或者是选择的调用。如果选择所有调用,你还可以选择是否删除声明本身。

Extract Method
       如果方法中含有过多特定的操作,方法太长,或者其中的某段代码被多次使用,这时,可以用提炼方法重构将这部分代码提取到单独的方法中。在Eclipse中应用此重构方便快捷。
       选中要提炼的代码段,从重构菜单中选择提炼方法项,或者使用快捷键Alt + Shift + M。
       在提炼方法对话框中,输入新方法的名字,选择修饰词,选择是否让新方法抛出运行时异常。在底部提供了新方法的预览。


Extract Local Variable
       使用一个变量来代替一个表达式有很多好处。如果表达式在多处被使用,这样能够提高性能,而且也提高了代码的可读性。要把一个表达式提炼为局部变量,选择要提炼的表达式,从重构菜单中选择提炼局部变量项,或者使用快捷键Alt + Shift + L。
       在提炼局部变量对话框中输入新变量的名字,选择是否要替换所有的表达式,是否使此变量为final。在对话框的底部提供变量的预览。
      
Extract Constant
       提炼常量与提炼局部变量很相似,唯一的区别是提炼常量重构可以选择提炼出的常量的修饰词,而且此常量将作为类的成员变量。

Introduce Parameter
       介绍参数重构在方法中创建新的参数,然后用此新参数取代局部变量或者成员变量的实例。要是用此重构,选中方法中一个成员变量或局部变量的引用,然后从重构菜单中选择介绍参数项。


Introduce Factory
       工厂是用来创建新对象,返回新创建对象的方法。你可以选择一个类的构造方法,从重构菜单中选择介绍工厂项,应用此重构,为此类创建工厂方法。
      
在介绍工厂对话框,输入工厂方法的名字和需要工厂方法创建的对象的名字。选择构造方法的修饰词是否为私有。
       点击OK按钮后,在指定的类中会出现此指定工厂方法。此方法创建一个当前类的实例,然后返回此实例。

Convert Local Variable to Field
       转换局部变量为成员变量重构,将方法内的变量声明移动到方法所在类中,使该变量对整个类可见。选择一个局部变量,从重构菜单中选择转换局部变量为成员变量项,随后打开配置的对话框。
      
       在此对话框中,添入成员变量的名字,选择修饰词,选择在哪里实例化此成员变量。随后的声明为静态,声明为final 选择项是否可以使用,取决于实例化位置的选择情况。

Encapsulate Field
       要正确的实践面向对象编程,应该将成员变量的修饰词置为私有,提供相应的访问器来访问这些成员变量。但是这些操作很烦琐。如果使用了封装成员变量重构,则十分方便。选择一个成员变量,从重构菜单中选择封装成员变量项。

       在封装局部变量对话框中,添入Getter, Setter方法的名字,选择新方法在哪个方法后出现。选择合适的修饰词。应用了此重构会创建两个新方法,将此成员变量的修饰词置为私有,将对此成员变量的引用改变为对新方法的引用。

重构项列表:
       下表从Eclipse帮助中提取,列出了各种重构支持的Java资源类型,对应的快捷键。

名字 可应用的Java元素 快捷键
Undo 在一次重构后可执行 Alt + Shift + Z
Redo 在一次撤销重构后可执行 Alt + Shift + Y
Rename 对方法,成员变量,局部变量,方法参数,对象,类,包,源代码目录,工程可用。 Alt + Shift + R
Move 对方法,成员变量,局部变量,方法参数,对象,类,包,源代码目录,工程可用。 Alt + Shift + V
Change Method Signature 对方法可用。 Alt + Shift + C
Convert Anonymous Class to Nested 对匿名内部类可用。  
Move Member Type to New File 对嵌套类可用。  
Push Down 对同一个类中成员变量和方法可用。  
Pull Up 对同一个类中成员变量和方法,嵌套类可用。  
Extract Interface 对类可用。  
Generalize Type 对对象的声明可用。  
Use Supertype Where Possible 对类可用。  
Inline 对方法,静态final类,局部变量可用。 Alt + Shift + I
 
Extract Method 对方法中的一段代码可用。 Alt + Shift + M
 
Extract Local Variable 对选中的与局部变量相关的代码可用。 Alt + Shift + L
 
Extract Constant 对静态final类变量,选中的与静态final类变量相关的代码可用。  
Introduce Parameter 对方法中对成员变量和局部变量的引用可用。  
Introduce Factory 对构造方法可用。  
Convert Local Variable to Field 对局部变量可用。 Alt + Shift + F
Encapsulate Field 对成员变量可用。  


       本文介绍了Eclipse提供的各种重构。这些重构易于使用,可以确保代码重构更加方便安全。而且可以自动生成代码以提高生产率。
       某些重构改变了某些类的结构,但没有改变项目中其他类的结构,如下推,上移重构。这时,就要确保项目中所有对改变元素的引用都要被更新。这也是为什么要有一个好的测试套。同时,你也要更新测试套中的对改变元素的引用。所以说,重构和单元测试的有机结合对于软件开发是多么的重要。
posted @ 2009-11-25 10:49 二胡 阅读(985) | 评论 (0)编辑 收藏

 在jquery-1.3.1.js中发现了如下写法:
 var
 // Will speed up references to window, and allows munging its name.
 window = this,
 // Will speed up references to undefined, and allows munging its name.
 undefined
 
 从上面的注释可以得知:这些写是为了提高性能
 
 查了一下资料,得知这些写为什么能提高性能。
 首先,jquery-1.3.1.js的代码都写在一个匿名函数中,上面的代码就定义2个局部变量:window和undefined;
 window变量被赋值为this(既window对象,感觉有点迷惑人,只不过2个变量名一样而已);局部变量undefined没有
 赋值,但是在js代码预编译的时候会被赋值为window.undefined;这样在匿名函数内部引用window,undefined时候
 直接引用局部变量window,undefined就可以了!不用再向上引用全局变量的window,undefined;

 从本质上说,这里利用了函数的作用域效果;当在函数内部调用window,undefined对象时候,系统会先在本地作用域
 查找,找到就调用;找不到,就到上一级的作用域查找;如此类推;
 
 不合适的地方,请大家指出!
posted @ 2009-11-05 18:19 二胡 阅读(1641) | 评论 (1)编辑 收藏

转 http://www.cnblogs.com/yslow/archive/2009/04/29/1446236.html

耦合异步脚本

英文原文地址

 

最近我的工作都是围绕异步加载外部脚本(loading external scripts asynchronously) 展开。当外部脚本以普通方式加载时(<script src="...">) 会阻塞页面内所有其它资源的下载和脚本下方元素的渲染.你可以查看将脚本放在底部(Put Scripts at the Bottom)的样例的效果. 异步加载脚本会避免阻塞行为进而更快的加载页面.

异步加载脚本产生的问题是内嵌脚本使用外部脚本中定义的符号的问题. 如果内嵌脚本使用了异步加载的外部脚本符号,竞争条件下可能会导致未定义的符号错误。因此有必要保证异步脚本和内嵌脚本以下面方式进行耦合:在异步脚本下载完毕之前内嵌脚本不能被执行。

下面有几种方式耦合异步脚本.

  • window’s onload - 内嵌脚本可以尝试使用窗口的onload事件. 实现起来非常简单,但是内嵌脚本不会尽可能早的执行.
  • script’s onreadystatechange - 内嵌脚本可以尝试使用脚本的onreadystatechangeonload 事件(在所有流行的浏览器里面均需要你自己来实现) 代码比较长而复杂,但是可以确保在脚本下载完毕之后马上执行内嵌脚本.
  • hardcoded callback - 外部脚本可以修改为通过一个回调函数(callback function)来明确的调用内嵌脚本,如果外部脚本和内嵌脚本是一个团队开发的话,这样是没有问题的,但如果使用第3方的脚本,这就提供不了必要的灵活性。

在这个博客帖子里我讨论两个问题: 如何使用异步脚本加快页面,如何通过Degrading Script Tags模式耦合外部脚本和内嵌脚本. 我通过我最近刚刚完成的一个项目UA Profiler results sortable图表来演示. 我还使用了Stuart Langridge的 sorttable排序脚本. 把他的脚本增加到我的页面并排序结果耗费了我大约5分钟.通过增加一点使用异步脚本和耦合脚本的工作量我可以使这个页面提高30%的加载速度

普通Script标记

最初我使用普通的方法(<script src="...">)将Stuart Langridge的sorttable排序脚本加入到UA Profiler, 例子见Normal Script Tag. HTTP瀑布图见图1.

普通Script标记
图1: 普通Script标记的瀑布图

表格排序工作正常,但由于它使页面慢了许多,我并不满意. 图1中我们可以看到脚本(sorttable-async.js)阻塞了页面内唯一的后继HTTP请求(arrow-right- 20X9.gif), 造成页面加载变慢. 瀑布图是使用Firebug 1.3 beta来产生的(你也可以使用httpwatch基调网络webwatch工具来查看效果)。 新版本的Firebug在onload事件发生的地方标记了一条红竖线. (蓝竖线是DOMContentLoaded事件.) 对于普通Script标记 来说, onload 事件在第487毫秒产生.

异步加载脚本

对初始页面渲染来说,脚本sorttable-async.js是没有必要的 - 表格被渲染之后才会排序. 这种情况(外部脚本不会被初始页面使用)是可以使用异步脚本加载的主要特征 . 例子异步加载脚本 使用DOM方式异步加载:

var script = document.createElement('script');
script.src = "sorttable-async.js";
script.text = "sorttable.init()"; // 这会在下面解释
document.getElementsByTagName('head')[0].appendChild(script);

异步加载脚本的HTTP瀑布图见图2。注意我是如何使用异步加载技术来避免阻塞行为的 - sorttable-async.js和arrow-right-20×9.gif 被同时下载. onload时间为429毫秒.

异步加载脚本
图2: 异步加载脚本的HTTP瀑布图

John Resig介绍的 Degrading Script Tags 模式

例子异步加载脚本使页面加载更快了,但仍旧有进一步提高的空间. 默认sorttable排序是通过在onload事件中增加sorttable.init()来触发。当外部脚本被加载完毕后内嵌脚本立即调用sorttable.init()能进一步提升性能. 在这种情况下,我使用的API仅仅是一个函数,但是我将尝试一个足够灵活的模式来支持更复杂的情况。

前面我列出了各种内嵌脚本和外部异步脚本耦合的方法: window’s onload, script’s onreadystatechange, 和 hardcoded callback. 这里,我使用了来自John Resig的被称为Degrading Script Tags模式的技术。 John描述了如何耦合一个内嵌脚本和外部脚本,例如:

<script src="jquery.js">
jQuery("p").addClass("pretty");
</script>

他提到的这个方法是使内联脚本在外部脚本下载完毕之后才开始执行。使用这种方式耦合内嵌脚本和外部脚本有几个好处:

  • 更简单 - 将2个script标记替换为1个script标记
  • 更清晰 - 内嵌代码依赖于外部脚本的关系更为明显
  • 更安全 - 如果外部脚本下载失败,内嵌脚本就不会执行,避免抛出未定义的符号错误

当使用异步加载外部脚本时这也是一个很棒的模式。为了使用这个技术,我必须修改我的内嵌代码和外部脚本. 对于内嵌代码, 我增加了第3行来设置 script.text的属性. 为了耦合代码, 我在sorttable-async.js末尾增加了如下代码:

var scripts = document.getElementsByTagName("script");
var cntr = scripts.length;
while ( cntr ) {
var curScript = scripts[cntr-1];
if ( -1 != curScript.src.indexOf('sorttable-async.js') ) {
eval( curScript.innerHTML );
break;
}
cntr--;
}

此代码遍历网页的所有脚本,直到它找到脚本块本身 (此时脚本的src属性包含sorttable-async.js),然后利用eval将代码(sorttable.init ())增加到脚本触发运行. (备注:虽然内嵌代码使用text属性增加代码内容,但是需要使用innerHTML获取代码. 这是代码跨浏览器工作的必要保证) 经过这样的优化,外部脚本不会阻塞其它资源的下载,同时,内嵌代码也会尽可能早的执行.

延迟加载

通过延迟加载能更快的加载页面 (通过onload事件动态加载). 例如 Lazyload 是在onload 事件中包含如下代码:

window.onload = function() {
var script = document.createElement('script');
script.src = "sorttable-async.js";
script.text = "sorttable.init()";
document.getElementsByTagName('head')[0].appendChild(script);
}

这种情况绝对需要脚本耦合技术,在onload事件里面的代码sorttable.init()不会被执行,因为此时onload事件已经发生,而sorttable-async.js还没被下载。延迟加载的好处是使onload事件更快的发生,见图3。红竖线表明onload事件发生在第320毫秒.

延迟加载
图3: 延迟加载的HTTP瀑布图

结论

通过避免通常的阻塞行为,异步加载脚本和延迟加载脚本可以提高网页的加载时间. 下面是增加不同版本的sorttable排序例子代码:

以上时间指的是onload事件发生的时间。



posted @ 2009-11-04 15:25 二胡 阅读(228) | 评论 (0)编辑 收藏

posted @ 2009-11-03 09:40 二胡 阅读(203) | 评论 (0)编辑 收藏

        在JAVA原代码中可以看到ArrayList类的实现.其中定义ArrayList类的时候,如下

        public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, java.io.Serializable
        
        在这里我有个疑惑,ArrayList继承了AbstractList,而AbstractList实现了List接口.为什么不直接这样写呢,如下:

        public class ArrayList<E> extends AbstractList<E>
            implements RandomAccess, Cloneable, java.io.Serializable

        那个能解释一下!谢谢先!
posted @ 2009-10-27 08:53 二胡 阅读(1886) | 评论 (10)编辑 收藏

转 http://www.playgoogle.com/view.asp?id=58

在JavaScript开发中,被人问到:null与undefined到底有啥区别?
    一时间不好回答,特别是undefined,因为这涉及到undefined的实现原理。于是,细想之后,写下本文,请各位大侠拍砖。
    总所周知:null == undefined
    但是:null !== undefined
    那么这两者到底有啥区别呢?
    请听俺娓娓道来…

null
    这是一个对象,但是为空。因为是对象,所以 typeof null  返回 ‘object’ 。
    null 是 JavaScript 保留关键字。
    null 参与数值运算时其值会自动转换为 0 ,因此,下列表达式计算后会得到正确的数值:
表达式:123 + null 结果值:123
表达式:123 * null 结果值:0

undefined
undefined是全局对象(global)的一个特殊属性,其值是未定义的。但 typeof undefined 返回 ‘undefined’ 。
      虽然undefined是有特殊含义的,但它确实是一个属性,而且是全局对象(window)的属性。请看下面的代码:
    alert(’undefined’ in window);   //输出:true
     var anObj = {};
     alert(’undefined’ in anObj);    //输出:false

从中可以看出,undefined是window对象的一个属性,但却不是anObj对象的一个属性。

注意:
尽管undefined是有特殊含义的属性,但却不是JavaScript的保留关键字。
undefined参与任何数值计算时,其结果一定是NaN。
顺便说一下,NaN是全局对象(global)的另一个特殊属性,Infinity也是。这些特殊属性都不是JavaScript的保留关键字!全局对象是预定义的对象,作为 JavaScript 的全局函数和全局属性的占位符。通过使用全局对象,可以访问所有其他所有预定义的对象、函数和属性。全局对象不是任何对象的属性,所以它没有名称。全局对象只是一个对象,而不是类。既没有构造函数,也无法实例化一个新的全局对象。(感谢sunder同学的留言)

提高undefined性能
当我们在程序中使用undefined值时,实际上使用的是global对象的undefined属性。
同样,当我们定义一个变量但未赋予其初始值,例如:
var aValue;
这时,JavaScript在所谓的预编译时会将其初始值设置为对window.undefined属性的引用,
于是,当我们将一个变量或值与undefined比较时,实际上是与window对象的undefined属性比较。这个比较过程中,JavaScript会搜索window对象名叫’undefined’的属性,然后再比较两个操作数的引用指针是否相同。
由 于window对象的属性值是非常多的,在每一次与undefined的比较中,搜索window对象的undefined属性都会花费时 间。在需要频繁与undefined进行比较的函数中,这可能会是一个性能问题点。因此,在这种情况下,我们可以自行定义一个局部的undefined变 量,来加快对undefined的比较速度。例如:


    function anyFunc()
    {
        var undefined;          //自定义局部undefined变量
        
        if(x == undefined)      //作用域上的引用比较
        
        
        while(y != undefined)   //作用域上的引用比较
        
    };

 

其 中,定义undefined局部变量时,其初始值会是对window.undefined属性值的引用。新定义的局部undefined变 量存在与该函数的作用域上。在随后的比较操作中,JavaScript代码的书写方式没有任何的改变,但比较速度却很快。因为作用域上的变量数量会远远少 于window对象的属性,搜索变量的速度会极大提高。
这就是许多前端JS框架为什么常常要自己定义一个局部undefined变量的原因!



posted @ 2009-10-22 22:14 二胡 阅读(232) | 评论 (0)编辑 收藏

转 http://code.google.com/p/grace/wiki/DojoStyle

前言

相当不错的 Javascript 编程风格规范,建议大家采用此规范编写 Javascript。原文链接: http://dojotoolkit.org/developer/StyleGuide

翻译(Translated by):i.feelinglucky{at}gmail.com from http://www.gracecode.com ,转载请注明出处、作者和翻译者,谢谢配合。

本文地址: http://code.google.com/p/grace/wiki/DojoStyle

Any violation to this guide is allowed if it enhances readability.

所有的代码都要变成可供他人容易阅读的。

快读参考

核心 API 请使用下面的风格:

 

结构 规则 注释
模块 小写 不要使用多重语义(Never multiple words)
骆驼
公有方法 混合 其他的外部调用也可以使用 lower_case(),这样的风格
公有变量 混合
常量 骆驼 或 大写

下面的虽然不是必要的,但建议使用:

 

结构 规则
私有方法 混合,例子:_mixedCase
私有变量 混合,例子:_mixedCase
方法(method)参数 混合,例子:_mixedCase, mixedCase
本地(local)变量 混合,例子:_mixedCase, mixedCase

命名规范

  1. 变量名称 必须为 小写字母。
  2. 类的命名使用骆驼命名规则,例如:
  3. Account, EventHandler
  4. 常量 必须 在对象(类)或者枚举变量的前部声明。枚举变量的命名必须要有实际的意义,并且其成员 必须 使用骆驼命名规则或使用大写:
  5. var NodeTypes = {
       
    Element : 1,
        DOCUMENT
    : 2
    }
  6. 简写单词 不能使用 大写名称作为变量名:
  7. getInnerHtml(), getXml(), XmlDocument
  8. 方法的命令 必须 为动词或者是动词短语:
  9. obj.getSomeValue()
  10. 公有类的命名 必须 使用混合名称(mixedCase)命名。
  11. CSS 变量的命名 必须 使用其对应的相同的公共类变量。
  12. 私有类的变量属性成员 必须 使用混合名称(mixedCase)命名,并前面下下划线(_)。例如:
  13. var MyClass = function(){
       
    var _buffer;
       
    this.doSomething = function(){
       
    };
    }
  14. 变量如果设置为私有,则前面 必须 添加下划线。
  15. this._somePrivateVariable = statement;
  16. 通用的变量 必须 使用与其名字一致的类型名称:
  17. setTopic(topic) // 变量 topic 为 Topic 类型的变量
  18. 所有的变量名 必须 使用英文名称。
  19. 变量如有较广的作用域(large scope),必须使用全局变量;此时可以设计成一个类的成员。相对的如作用域较小或为私有变量则使用简洁的单词命名。
  20. 如果变量有其隐含的返回值,则避免使用其相似的方法:
  21. getHandler(); // 避免使用 getEventHandler()
  22. 公有变量必须清楚的表达其自身的属性,避免字义含糊不清,例如:
  23. MouseEventHandler,而非 MseEvtHdlr
    请再次注意这条规定,这样做得的好处是非常明显的。它能明确的表达表达式所定义的含义。例如:
    dojo.events.mouse.Handler // 而非 dojo.events.mouse.MouseEventHandler
  24. 类/构造函数 可以使用 扩展其基类的名称命名,这样可以正确、迅速的找到其基类的名称:
  25. EventHandler
    UIEventHandler
    MouseEventHandler
    基类可以在明确描述其属性的前提下,缩减其命名:
    MouseEventHandler as opposed to MouseUIEventHandler.

特殊命名规范

  1. 术语 "get/set" 不要和一个字段相连,除非它被定义为私有变量。
  2. 前面加 "is" 的变量名 应该 为布尔值,同理可以为 "has", "can" 或者 "should"。
  3. 术语 "compute" 作为变量名应为已经计算完成的变量。
  4. 术语 "find" 作为变量名应为已经查找完成的变量。
  5. 术语 "initialize" 或者 "init" 作为变量名应为已经实例化(初始化)完成的类或者其他类型的变量。
  6. UI (用户界面)控制变量应在名称后加控制类型,例如: leftComboBox, TopScrollPane。
  7. 复数必须有其公共的名称约定(原文:Plural form MUST be used to name collections)。
  8. 带有 "num" 或者 "count" 开头的变量名约定为数字(对象)。
  9. 重复变量建议使用 "i", "j", "k" (依次类推)等名称的变量。
  10. 补充用语必须使用补充词,例如: get/set, add/remove, create/destroy, start/stop, insert/delete, begin/end, etc.
  11. 能缩写的名称尽量使用缩写。
  12. 避免产生歧义的布尔变量名称,例如:
  13. isNotError, isNotFound 为非法
  14. 错误类建议在变量名称后加上 "Exception" 或者 "Error"。
  15. 方法如果返回一个类,则应该在名称上说明返回什么;如果是一个过程,则应该说明做了什么。

文件

  1. 缩进请使用 4 个空白符的制表位。
  2. 如果您的编辑器支持 文件标签_(file tags),请加添如下的一行使我们的代码更容易阅读:
  3. // vim:ts=4:noet:tw=0:

译注:老外用 VIM 编辑器比较多,此条可以选择遵循。

  1. 代码折叠必须看起来是完成并且是合乎逻辑的:
  2. var someExpression = Expression1
       
    + Expression2
       
    + Expression3;

    var o = someObject.get(
       
    Expression1,
       
    Expression2,
       
    Expression3
    );
    注:表达式的缩进与变量声明应为一致的。
注:函数的参数应采用明确的缩进,缩进规则与其他块保持一致。

变量

  1. 变量必须在声明初始化以后才能使用,即便是 NULL 类型。
  2. 变量不能产生歧义。
  3. 相关的变量集应该放在同一代码块中,非相关的变量集不应该放在同一代码块中。
  4. 变量应该尽量保持最小的生存周期。
  5. 循环/重复变量的规范:
    1. 只有循环控制块的话,则必须使用 FOR 循环。
    2. 循环变量应该在循环开始前就被初始化;如使用 FOR 循环,则使用 FOR 语句初始化循环变量。
    3. "do ... while" 语句是被允许的。
    4. "break" 和 "continue" 语句仍然允许使用(但请注意)。
  6. 条件表达式
    1. 应该尽量避免复杂的条件表达式,如有必要可以使用临时布尔变量。
    2. The nominal case SHOULD be put in the "if" part and the exception in the "else" part of an "if" statement.
    3. 应避免在条件表达式中加入块。
  7. 杂项
    1. 尽量避免幻数(Magic numbers),他们应该使用常量来代替。
    2. 浮点变量必须指明小数点后一位(即使是 0)。
    3. 浮点变量必须指明实部,即使它们为零(使用 0. 开头)。

布局

  1. 普通代码段 应该 看起来如下:
  2. while (!isDone){
            doSomething
    ();
        isDone
    = moreToDo();
    }
  3. IF 语句 应该 看起来像这样:
  4. if (someCondition){
            statements
    ;
    } else if (someOtherCondition){
        statements
    ;
    } else {
        statements
    ;
    }
  5. FOR 语句 应该 看起来像这样:
  6. for (initialization; condition; update){
            statements
    ;
    }
  7. WHILE 语句 应该 看起来像这样:
  8. while (!isDone) {
            doSomething
    ();
        isDone
    = moreToDo();
    }
  9. DO ... WHILE 语句 应该 看起来像这样:
  10. do {
            statements
    ;
    } while (condition);
  11. SWITCH 语句 应该 看起来像这样:
  12. switch (condition) {
    case ABC:
        statements
    ;
       
    //  fallthrough
    case DEF:
        statements
    ;
       
    break;
    default:
            statements
    ;
       
    break;
    }
  13. TRY ... CATCH 语句 应该 看起来像这样:
  14. try {
        statements
    ;
    } catch(ex) {
        statements
    ;
    } finally {
        statements
    ;
    }
  15. 单行的 IF - ELSE,WHILE 或者 FOR 语句也 必须 加入括号,不过他们可以这样写:
  16. if (condition){ statement; }
    while (condition){ statement; }
    for (intialization; condition; update){ statement; }

空白

  1. 操作符 建议 使用空格隔开(包括三元操作符)。
  2. 下面的关键字 避免使用 空白隔开:
  3. 下面的关键字必须使用空白隔开:
  4. 逗号(,) 建议 使用空白隔开。
  5. 冒号(:) 建议 使用空白隔开。
  6. 点(.) 在后部 建议 使用空白隔开。
  7. 点(.) 避免 在前部使用空白。
  8. 函数调用和方法 避免 使用空白,例如: doSomething(someParameter); // 而非 doSomething (someParameter)
  9. 逻辑块 之间使用空行。
  10. 声明 建议 对齐使其更容易阅读。

注释

  1. 生涩的代码就 没有必要 添加注释了,首先您需要 重写 它们。
  2. 所有的注释请使用英文。
  3. 从已解决的方案到未开发的功能,注释 必须 与代码相关。
  4. 大量的变量申明后 必须 跟随一段注释。
  5. 注释需要说明的是代码段的用处,尤其是接下来的代码段。
  6. 注释 没有必要 每行都添加。

文档

下面提供了一些基本的函数或者对象的描述方法:

基本函数信息

function(){
   
// summary: Soon we will have enough treasure to rule all of New Jersey.
   
// description: Or we could just get a new roomate.
   
//          Look, you go find him.  He don't yell at you.
   
//          All I ever try to do is make him smile and sing around
   
//          him and dance around him and he just lays into me.
   
//          He told me to get in the freezer 'cause there was a carnival in there.
   
// returns:  Look, a Bananarama tape!
}

对象函数信息

没有返回值描述

{
   
// summary: Dingle, engage the rainbow machine!
   
// description:
   
//          Tell you what, I wish I was--oh my g--that beam,
   
//          coming up like that, the speed, you might wanna adjust that.
   
//          It really did a number on my back, there. I mean, and I don't
   
//          wanna say whiplash, just yet, cause that's a little too far,
   
//          but, you're insured, right?
}

函数的声明

在有的情况下,对于函数的调用和声明是隐义(invisible)的。在这种情况下,我们没有办法在函数中加入说明等(供程序调用)。如果您遭遇了这种情况,您可以使用一个类来封装函数。

注:此此方法只能在函数没有初始化的参数情况下。如过不是,则它们会被忽略。

dojo.declare(
   
"foo",
   
null,
   
{
       
// summary: Phew, this sure is relaxing, Frylock.
       
// description:
       
//              Thousands of years ago, before the dawn of
       
//              man as we knew him, there was Sir Santa of Claus: an
       
//              ape-like creature making crude and pointless toys out
       
//              of dino-bones, hurling them at chimp-like creatures with
       
//              crinkled hands regardless of how they behaved the
       
//              previous year.
       
// returns: Unless Carl pays tribute to the Elfin Elders in space.
       
}
);

参数

  1. 简单类型
  2. 简单的类型的参数可以直接在函数参数定义中注释说明。
    function(/*String*/ foo, /*int*/ bar)...
  3. 可变类型参数
  4. 下面是几个修饰符供参考:
  5. 全局参数描述
  6. 如果你想增加一个描述,你可以将它们移至初始化块。
基本信息格式为: *关键字* 描述字段 *key* Descriptive sentence
参数和变量的格式为: *关键字* ~*类型*~ 描述字段 *key* ~*type*~ Descriptive sentence
注: *关键字* 和 ~*类型*~ 可以使用任何字母和数字表述。
function (foo, bar) {
   
// foo: String
   
//          used for being the first parameter
   
// bar: int
   
//          used for being the second parameter
}

变量

由于实例变量、原型变量和外部变量的声明是一致的,所以有很多的方法声明、修改变量。具体的如何定义和定位应在变量最先出现的位置指明变量的名称、类型、作用域等信息。

function foo() {
   
// myString: String
   
// times: int
   
//          How many times to print myString
   
// separator: String
   
//          What to print out in between myString*
   
this.myString = "placeholder text";
   
this.times = 5;
}

foo
.prototype.setString = function (myString) {
   
this.myString = myString;
}

foo
.prototype.toString = function() {
   
for(int i = 0; i < this.times; i++) {
        dojo
.debug(this.myString);
        dojo
.debug(foo.separator);
       
}
}
foo
.separator = "=====";

对象中的变量注释

应使用和对象值和方法一致的标注方式,比如在他们声明的时候:

{
   
// key: String
   
//          A simple value
    key
: "value",
   
// key2: String
   
//          Another simple value
}

返回值

因为函数可以同时返回多个不同(类型)的值,所以应每个返回值之后加入返回类型的注释。注释在行内注释即可,如果所有的返回值为同一类型,则指明返回的类型;如为多个不同的返回值,则标注返回类型为"mixed"。

function() {
       
if (arguments.length) {
               
return "You passed argument(s)"; // String
       
} else {
       
return false; // Boolean
   
}
}

伪代码(有待讨论)

有时候您需要在函数或者类中添加对于此函数和类的功能性流程描述。如果您打算这样做,您可以使用 /*======== (= 字符最好出现 5 次或者更多),这样做的好处就是可以不用将这些东西加入代码(译注:原作者的意思可能为代码管理系统)。

这样看起来在 /*===== =====*/ 会有非常长的一段注释,等待功能调整完毕以后就可以考虑是否删除。

/*=====
module.pseudo.kwArgs = {
        // url: String
    //          The location of the file
    url: "",
    // mimeType: String
    //          text/html, text/xml, etc
    mimeType: ""
}
=====*/


function(/*module.pseudo.kwArgs*/ kwArgs){
    dojo
.debug(kwArgs.url);
        dojo
.debug(kwArgs.mimeType);
}
posted @ 2009-10-22 13:40 二胡 阅读(203) | 评论 (0)编辑 收藏

转 http://blog.csdn.net/gaoweipeng/archive/2009/10/18/4695780.aspx
平时的开发如果我们能有些得心应手的开发工具,就好比是如虎添翼。会大大的提高我们的开发效率。Visual Studio 自不必说,通过此文,和大家回忆下除此之外的经典的开发工具,同时希望能把这些经典的工具介绍给新手,相信对平日的开发会有很大的帮助。

Internet Explorer Developer Toolbar
简介:微软发布了Internet Explorer Developer Toolbar最新版.该产品让开发人员能够深入探索和理解Web页面,帮助开发者更好地创建Web应用.安装后可以在IE中快速分析网页的软件.该工具条可集成在IE窗口,或以浮动窗口形式存在.

IE Developer Toolbar特性如下:

浏览和修改Web页的文档对象模型(DOM).通过多种技术方式定位、选定Web页上的特定元素.禁止或激活IE设置.查看HTML对象的类名、 ID,以及类似链接路径、tab顺序、快捷键等细节.描绘表格、单元格、图片或选定标签的轮廓.显示图片象素、大小、路径、替代文字等.即时重定义浏览器 窗口大小到800x600或自定义大小.清空浏览器缓存和cookie,被清除项可从所有对象或给定域中选择.直接访问关联W3C规范参考、IE开发组 blog或其他来源.-显示设计时标尺,帮助对齐对象. .... 

下载地址及相关资料:
http://www.microsoft.com/downloads/details.aspx?FamilyID=e59c3964-672d-4511-bb3e-2d5e1db91038&DisplayLang=en#Overview


HttpWatch
简介:强大的网页数据分析工具。集成在Internet Explorer工具栏。包括网页摘要。Cookies管理。缓存管理。消息头发送/接受。字符查询。POST 数据和目录管理功能。报告输出。

安装完HttpWatch后,就会在浏览器中找到他:

主界面:
 
下载地址:http://www.crsky.com/soft/3455.html

使用介绍:http://www.cnblogs.com/mayingbao/archive/2007/11/30/978530.html


Fiddler2

简介:Fiddler2是一个网络调试代理,用来监测本地计算 机和Internet之间所有的HTTP通讯。可以监测所有的HTTP通讯,设置断点,并且可以修改到进入到本地计算机的数据和从本地计算机出去的数据 (就是可以伪造数据)。Fiddler包含一个JScript .NET 事件脚本子系统(event-based scripting subsystem),可以使用任何一种.Net语言扩展。该软件是免费的,支持多种浏览器,包括Internet Explorer,Mozilla Firefox,Opera和其它一些浏览器。


从Fiddler官方网站上可以看见原版的英文介绍:http://www.fiddler2.com/fiddler2/
下载地址:http://www.fiddler2.com/fiddler2/

使用介绍:http://blog.csdn.net/lihongzhai/archive/2009/09/14/4551035.aspx


NUnit 

NUnit是一个单元测试框架,专门针对于。NET来写的.其实在前面有JUnit(Java),CPPUnit(C++),他们都是xUnit的一员。最初它是从JUnit而来。现在的版本是2.2.接下来我所用的都是基于这个版本。

下载地址:http://www.nunit.org/index.php

使用介绍:http://www.uml.org.cn/net/200702273.asp


PowerDesigner

简介:PowerDesigner是Sybase公司的CASE工具集,使用它可以方便地对管理信息系统进行分析设计,它几乎包括了数据库模型设计的全过程。
利用PowerDesigner可以制作数据流程图、概念数据模型、物理数据模型,可以生成多种客户端开发工具的应用程序,还可为数据仓库制作结构模型,也能对团队设计模型进行控制。

主要功能:DataArchitect:这是一个强大的数据库设计工具,使用DataArchitect 可利用实体-关系图为一个信息系统。创建"概念数据模型"-CDM(Conceptual Data Model)。并且可根据CDM 产生基于某一特定数据库管理系统(例如:Sybase System 11)的"物理数据模型"-PDM(Physical Data Model)。还可优化PDM,产生为特定DBMS 创建数据库的SQL 语句并可以文件形式存储以便在其他时刻运行这些SQL 语句创建数据库。另外,DataArchitect还可根据已存在的数据库反向生成
PDM,CDM 及创建数据库的SQL脚本。
ProcessAnalyst:这部分用于创建功能模型和数据流图,创建"处理层次关系"。
AppModeler:为客户/服务器应用程序创建应用模型。
ODBC Administrator:此部分用来管理系统的各种数据源。


下载地址:http://www.baidu.com/s?tn=sitehao123&bs=PowerDesigner&f=3&wd=powerdesigner%CF%C2%D4%D8&oq=powerdesigner&rsp=0 :)

使用介绍: http://www.cnblogs.com/yxonline/archive/2007/04.html


 Reflector

简介:Visual Studio内置的ILDASM成为最初挖掘程序集的上佳工具。 但自从Reflector出现后,ILDASM相形见拙。因为,Reflector能提供更多的程序集信息。Reflector可以将.NET程序集中的 中间语言反编译成C#或者Visual Basic代码。除了能将IL转换为C#或Visual Basic以外,Reflector还能够提供程序集中类及其成员的概要信息、提供查看程序集中IL的能力以及提供对第三方插件的支持。


下载地址/Files/gaoweipeng/Reflector.rar

使用介绍及其他相关下载:http://www.cnblogs.com/zzy2740/archive/2005/09/20/240216.html

1st JavaScript Editor

简介: 1st JavaScript Editor 是一款强大的JavaScript开发、校验和调试工具, 它简单易用,不论你是初学者或者是专业的开发人士,都可以轻松上手!同时它又是完美的Ajax (Asynchronous JavaScript and XML),CSS, HTML, DOM DHTML开发工具!它提供了简单而且实用的功能:丰富的代码编辑功能(JavaScript, HTML, CSS, VBScript, PHP ,ASP(Net)),语法高亮,内置预览功能,提供了完整的HTML 标记, HTML 属性, HTML 事件, JavaScript 事件和JavaScript 函数等完整的代码库,同时有着贴心的代码自动补全功能。


官网及下载地址:http://yaldex.com/

Regulator

简介:使用Reglator可以方便的验证正则表达式的执行结果,带有智能感知功能。帮助文档是一个很好的学习正则的入门教程,也可以当作参考手册。基本上就全了,同时进行学习和实践。还有生成c# or vb.net代码等附加功能。 类似的软件现在很多:RegexBuddy ,RegexTester。等

 下载地址:http://sourceforge.net/projects/regulator/

 使用介绍:http://www.ctochina.net/topic/ShowArticle/112.html

 小结:上面的开发工具都是我平时喜欢用的,希望通过此文的介绍,对没有使用过的朋友带来帮助。也希望园子中的朋友们秀一秀自己平时常用的开发工具,分享些更实用,方便的开发工具!



posted @ 2009-10-20 13:04 二胡 阅读(276) | 评论 (1)编辑 收藏

转 http://ued.alipay.com/?p=707

关于跨域这个话题,很早就答应过要分享,但是因为懒,一直拖着,直到D2上有人谈起了“完美跨域”。“跨域”应该已经算不上什么难题了,只是提起“完美”这两个字,突然觉得有了新意,那就写点什么吧,至少对自己有个交代嘛!跨域方案有很多,接下来一一枚举,会给出demo,demo的宗旨是尽可能简单的让新手明白,各方案中跨域的原理,实际应用请酌情修改。

方案一、剪贴板

原理:IE本身依附于windows平台的特性为我们提供了一种基于iframe,利用内存来“绕行”的方案,在这里我称之为,本地存储原理。

缺点:不支持非IE浏览器,并且影响到用户对剪贴板的操作,用户体验非常不好,特别是在IE7下,受安全等级影响,会弹出提示框。

演示:[ 点此阅览 ]

子页面在子域:demo.ioldfish.cn下,在页面中加入如下代码获取页面高度并存入剪贴板。

  1. <script type="text/javascript">
  2. var ua = navigator.userAgent;
  3. if ((i = ua.indexOf("MSIE")) >= 0) 
  4. {
  5.  var iObjH = window.document.documentElement.scrollHeight;
  6.  window.clipboardData.setData('text',String(iObjH));
  7. }
  8. </script>

 

主页面在主域:www.ioldfish.cn下,在页面中加入如下代码,获取剪贴板的值并赋值为子页面所在的iframe的高度。

  1. <script type="text/javascript">
  2. window.onload = function()
  3. {
  4.  var iObj =document.getElementById('iId');
  5.  iObj.style.height=parseInt(window.clipboardData.getData('text'))+'px';
  6. }
  7. </script>

 

方案二、document.domain

原理:设置了document.domain,欺骗浏览器

缺点:无法实现不同主域之间的通讯。并且当在一个页面中还包含有其它的iframe时,会产生安全性异常,拒绝访问。

演示:[ 点此阅览 ]

主页面在主域:www.ioldfish.cn下,子页面在子域:demo.ioldfish.cn下,在两个页面的头部都加入如下代码:

  1. <script type="text/javascript">
  2.  document.domain="ioldfish.cn";
  3. </script>

 

方案三、通过JS获取hash值实现通讯

原理:hash可以实现跨域传值实现跨域通讯。

缺点:对于父窗口URL参数动态生成的,处理过程比较复杂一些。并且IE之外的浏览器遇到hash的改变会记录历史,影响浏览器的前进后退功能,体验不佳。

演示:[ 点此阅览 ]

子页面在主域:www.lzdaily.com下,在页面中添加如下代码,因为hash是不受跨域限制的,所以可以将本页面的高度顺利的传送给主页面的hash。

  1. <script type="text/javascript">
  2.  var hashH = document.documentElement.scrollHeight;
  3.  var urlA = "http://www.ioldfish.cn/wp-content/demo/domain/hash/a2.html";
  4.  parent.location.href= urlA+"#"+hashH;
  5. </script>

 

主页面在主域:www.ioldfish.cn下,在页面中添加如下代码,首先取得子页面传递过来的hash值,然后将hash值赋到子页面所在的iframe的高度上。

  1. <script type="text/javascript">
  2. window.onload = function()
  3. {
  4.  var iObj = document.getElementById('iId');
  5.  if(location.hash)
  6.  {
  7.   iObj.style.height=location.hash.split("#")[1]+"px";
  8.  }
  9. }
  10. </script>

 

方案四、传hash值实现通讯改良版

原理:该方案通过“前端代理”的方式,实现hash的传值,体验上比之方案三有了很大的提升。

缺点:对于父窗口URL参数动态生成的,处理过程比较复杂一些。

演示:[ 点此阅览 ]

子页面在主域:www.lzdaily.com下,在页面中添加如下代码,首先在页面里添加一个和主页面同域的iframe[也可动态生成],他的作用就像是个跳板。C页面中不需任何代码,只要确保有个页面就防止404错误就可以了!该方法通过修改iframe的name值同样可以跨域传值,只是比较”猥琐“而已。

  1. <iframe id="iframeC" name="iframeC" src="http://www.ioldfish.cn/wp-content/demo/domain/hashbetter/c.html" frameborder="0" height="0"></iframe>

 

然后在页面中加上如下代码,利用页面C的URL接收hash值,并负责把hash值传给同域下的主页面。

  1. <script type="text/javascript">
  2.  hashH = document.documentElement.scrollHeight;
  3.  urlC = "http://www.ioldfish.cn/wp-content/demo/domain/hashbetter/c.html";
  4.  document.getElementById("iframeC").src=urlC+"#"+hashH;
  5. </script>

 

主页面在主域:www.ioldfish.cn下,在页面中添加如下代码,获取从C页面中传递过来的hash值。这里应用到一个技巧,就是直接从A页面用frames["iId"].frames["iframeC"].location.hash,可以直接访问并获取C页面的hash值。这样一来,通过代理页面传递hash值,比起方案三,大大提高了用户体验。

  1. <script type="text/javascript">
  2. window.onload = function()
  3. {
  4.  var iObj = document.getElementById('iId');
  5.  iObjH = frames["iId"].frames["iframeC"].location.hash;
  6.  iObj.style.height = iObjH.split("#")[1]+"px";
  7. }
  8. </script>

 

方案五、JSONP

原理:有点脚本注入的味道

缺点:服务器端程序运行比脚本早,跨域交互时无法捕获前端页面元素的相关数据,比如自适应高度。

演示:[ 点此阅览 ]

主页面在主域:www.ioldfish.cn下,在页面中添加如下代码,动态创建一个script,然后指定到子域的动态文件,在动态文件后面可以添加参数,在这里我加了一个回调函数,当请求返回后,会运行这个回调函数。

  1. <script type="text/javascript">
  2. function loadContent()
  3. {
  4.  var scriptDom=document.createElement('script');
  5.  var url = "http://www.lzdaily.com/domain/jsonp/Test.aspx?f=setDivContent'";
  6.  scriptDom.src= url;
  7.  document.body.appendChild(scriptDom);
  8. } 
  9. function setDivContent(love)
  10. {
  11.  var fishDiv = document.getElementById("oldFish");
  12.  fishDiv.innerHTML = love;
  13. }
  14. </script>

 

子页面在主域:www.lzdaily.com下,在页面中添加如下代码,首先先取得主页面传过来的回调函数名,然后生成一段javascript代码,以回调函数带参数的形式返回主页面,这样就完成了跨域之间的通讯。由于服务器端程序执行总是优先于javascript代码,所以基本上没办法获取到子页面中DOM的相关数据,所以小白同学,我可以很负责人的告诉你,想通过这种方法实现跨域自适应高度是不可能的!

  1. <script language="C#" runat="server">
  2. void Page_Load(object sender, EventArgs e)
  3. {
  4.   string f = Request.QueryString["f"];
  5.   Response.Clear();
  6.   Response.ContentType = "application/x-javascript";
  7.   Response.Write(String.Format(@"{0}({1});", f,1122));
  8.   Response.End();
  9. }
  10. </script>

 

方案六、window.name

原理:window.name本身并不能实现跨域,只是中间做了代理。

缺点:获取异域的前端页面元素值有一定局限性,比如取自适应高度的值。但是此方法在前端页面元素的数据交互上明显比JSONP强。

演示:[ 点此阅览 ]

这个方案,YAHOO的同事已经给出了详细的demo,我就不重复了,演示demo出自YAHOO克军之手。详细的说明可以参看“怿飞的BLOG”,个人觉得方案四比window.name适用面更广一些。

方案七、window.navigator

原理:window.navigator这个对象是在所有的Iframe中均可以访问的对象。应该是IE的一个漏洞!

缺点:不支持IE外的浏览器下的跨域。严格的dtd申明后,该方法失效!

演示:[ 点此阅览 ]

主页面在主域:www.ioldfish.cn下,首先先申明一个Json用来保存所有页面的高度window.navigator.PagesHeight={”":0};,然后根据name的属性找到页面的数据window.navigator.getData,最后将页面的数据注册到window.navigator.PagesHeight中。这里还定义了一个函数resetIframeData,在页面加载的时候调用它,完成跨域的数据交互。注释中详细说明了参数的作用。

  1. <script type="text/javascript">
  2. window.navigator.PagesHeight={"":0};   
  3. window.navigator.getData=function(pageName) {     
  4.  return window.navigator.PagesHeight[pageName];  
  5. };   
  6. window.navigator.putData=function(pageName,pageHeight) 
  7. {  
  8.  window.navigator.PagesHeight[pageName]=pageHeight;  
  9. };
  1. /*
  2. *iframeId:页面中iframe的标识id
  3. *key:子页面自定义的json标识,这里就是子页面定义的"PortalData".
  4. *defaultData:无法取到值时候调用
  5. */
  6. function resetIframeData(iframeId,key,defualtData)
  7. {       
  8.  var obj=document.getElementById(iframeId);  
  9.  if(window.navigator.getData)
  10.  {  
  11.   var pageHeight = window.navigator.getData(key)
  12.   if(pageHeight && String(pageHeight).match(/\d+/))
  13.   {  
  14.    obj.style.height=pageHeight+'px';  
  15.   }
  16.   else
  17.   {  
  18.    obj.style.height=defualtData + 'px';  
  19.   }  
  20.  }
  21.  else
  22.  {  
  23.   obj.style.height=defualtData + 'px';  
  24.  }     
  25. } 
  26. </script>

 

子页面在主域:www.lzdaily.com下,获取到页面高度后,将高度存到主页定义的Json中,Json标识为”PortalData”,这里可以自定义。

  1. <script type="text/javascript">
  2. window.onload = function getPageData()
  3. {         
  4.  var pageHeight = document.body.scrollHeight;  
  5.  if(window.navigator.putData)
  6.  {
  7.   window.navigator.putData("PortalData",pageHeight);
  8.  }
  9. } 
  10. </script>

 

其实通过css也可以实现跨域,数据获取的实质其实就是动态载入一段CSS,然后解析其中的字段提取数据,这个方法比较“猥琐”,再这里就不多介绍了,当然flash也可以实现跨域,只是还没去实践,实践完了再补充。啥时候能补完呢?恩……

以上这么多方案,有可以“完美跨域”的吗?单一的看,我想没有吧,都有缺陷,但是只要不同情况下使用合适的方法,我想这才是最完美的!原来绕了一圈,我只是再说废话,哎!不论怎么样,还是希望这些废话对还在苦苦追求“完美”的同学们有所启发!

posted @ 2009-09-30 09:05 二胡 阅读(292) | 评论 (0)编辑 收藏

转 http://blog.csdn.net/Mailbomb/archive/2005/01/19/258948.aspx

中文:

              http://www.cnjm.net

              http://www.j2me.com.cn

              http://www.j2medev.com

              http://gceclub.sun.com.cn/NASApp/sme/controller/teclist?tid=0103

              http://discussion.forum.nokia.com/forum/forumdisplay.php?s=&forumid=71

              http://mobilityworld.ericsson.com.cn/development/download_hit.asp

              http://games.sina.com.cn/m/

 

       英文:

              http://java.sun.com/j2me

              http://www.forum.nokia.com/main.html

              https://communication-market.siemens.de/portal/main.aspx?LangID=0&MainMenuID=2&LeftID=2&pid=1&cid=0&tid=3000&xid=0

              http://www.motocoder.com/motorola/pcsHome.jsp

              http://developer.samsungmobile.com/eng/front_zone/bbs/bbs_main.jsp?p_menu_id=1500

              http://developer.sonyericsson.com/site/global/docstools/java/p_java.jsp

              http://idenphones.motorola.com/iden/developer/developer_home.jsp

              http://www.j2me.org/

              http://proguard.sourceforge.net/

              http://www.j3d.org/

            

             .[http://www.javaalmanac.com] – Java开发者年鉴一书的在线版本. 要想快速查到某种Java技巧的用法及示例代码, 这是一个不错的去处.

2.[http://www.onjava.com] – O’Reilly的Java网站. 每周都有新文章.

3.[http://java.sun.com] – 官方的Java开发者网站 – 每周都有新文章发表.

4.[http://www.developer.com/java] – 由Gamelan.com 维护的Java技术文章网站.

5.[http://www.java.net] – Sun公司维护的一个Java社区网站.

6.[http://www.builder.com] – Cnet的Builder.com网站 – 所有的技术文章, 以Java为主.

7.[http://www.ibm.com/developerworks/java] – IBM的Developerworks技术网站; 这是其中的Java技术主页.

8.[http://www.javaworld.com] – 最早的一个Java站点. 每周更新Java技术文章.

9.[http://www.devx.com/java] – DevX维护的一个Java技术文章网站.

10.[http://www.fawcette.com/javapro] – JavaPro在线杂志网站.

11.[http://www.sys-con.com/java] – Java Developers Journal的在线杂志网站.

12.[http://www.javadesktop.org] – 位于Java.net的一个Java桌面技术社区网站.

13.[http://www.theserverside.com] – 这是一个讨论所有Java服务器端技术的网站.

14.[http://www.jars.com] – 提供Java评论服务. 包括各种framework和应用程序.

15.[http://www.jguru.com] – 一个非常棒的采用Q&A形式的Java技术资源社区.

16.[http://www.javaranch.com] – 一个论坛,得到Java问题答案的地方,初学者的好去处。

17.[http://www.ibiblio.org/javafaq/javafaq.html] – comp.lang.java的FAQ站点 – 收集了来自comp.lang.java新闻组的问题和答案的分类目录.

18.[http://java.sun.com/docs/books/tutorial/] – 来自SUN公司的官方Java指南 – 对于了解几乎所有的java技术特性非常有帮助.

19.[http://www.javablogs.com] – 互联网上最活跃的一个Java Blog网站.

20.[http://java.about.com/] – 来自About.com的Java新闻和技术文章网站.



posted @ 2009-08-07 08:52 二胡 阅读(203) | 评论 (0)编辑 收藏

转 http://blog.csdn.net/microrain/archive/2007/05/03/1595241.aspx

一个好的程序员除了具备扎实的基本功外,还应该具有更为灵活的逻辑思维与判断能力。除此之外,撑握一些行之有效的辅助工具也很重要。工欲善其事,必先利其 器。有好的工具辅助,所做的工作将事半功倍。下面向大家推荐基于WEB应用开发的java程序员应该必备的辅助开发工具。

1,Java剖析工具 -- profiler
是一个全功能的Java剖析工具(profiler),专用于分析J2SE和J2EE应用程序。它把CPU、执行绪和内存的剖析组合在一个强大的应用 中。JProfiler可提供许多IDE整合和应用服务器整合用途。JProfiler直觉式的GUI让你可以找到效能瓶颈、抓出内存漏失 (memory leaks)、并解决执行绪的问题。它让你得以对heap walker作资源回收器的root analysis,可以轻易找出内存漏失;heap快照(snapshot)模式让未被参照(reference)的对象、稍微被参照的对象、或在终结 (finalization)队列的对象都会被移除;整合精灵以便剖析浏览器的Java外挂功能。
下载地址:http://www.ej-technologies.com/download/overview.html



2,冗余代码检查 -- Simian
Simian UI 是一个用来发现重复代码的eclipse插件,对于改善设计,消除冗余代码很有帮助。
安装方法:使用eclipse的Help->Software Update进行安装。站点地址为:http://www.integility.com/eclipse/
安装后,按照提示重新启动eclipse。在希望进行分析的项目上点击右键,选择Simian->Add Simian to this project,就可以为该项目进行代码检查。


3,浏览器端调试工具(IE) -- Internet Explorer Developer Toolbar
微软发布了Internet Explorer Developer Toolbar Beta版。该产品让开发人员能够深入探索和理解Web页面,帮助开发者更好地创建Web应用。浏览和修改Web页的文档对象模型(DOM)。具备以下特性:
-通过多种技术方式定位、选定Web页上的特定元素。
-禁止或激活IE设置。
-查看HTML对象的类名、ID,以及类似链接路径、tab顺序、快捷键等细节。
-描绘表格、单元格、图片或选定标签的轮廓。
-显示图片象素、大小、路径、替代文字等。
-即时重定义浏览器窗口大小到800x600或自定义大小。
-清空浏览器缓存和cookie,被清除项可从所有对象或给定域中选择。
-直接访问关联W3C规范参考、IE开发组blog或其他来源。
-显示设计时标尺,帮助对齐对象。
该工具条可集成在IE窗口,或以浮动窗口形式存在。

下载地址:从微软官方下载



4,浏览器端调试工具(Firefox) -- Firebug
JavaScript, CSS, HTML,Ajax调试工具。功能包括HTML/CSS检查,除错工具,错误控制台和命令行等。可对javascript做调试和性能分析,查看html 代码的结构,css样式动态提示,检测并显示页面错误,浏览树型结构的DOM数据,可以显示javascript的log等等功能。

下载地址:http://www.getfirebug.com/



posted @ 2009-07-31 09:10 二胡 阅读(1422) | 评论 (0)编辑 收藏

转 http://www.cftea.com/c/751.asp

我们这里说说四种浏览器对 document.body 的 clientHeight、offsetHeight 和 scrollHeight 的解释,这里说的是 document.body,如果是 HTML 控件,则又有不同,点击这里查看。

这四种浏览器分别为IE(Internet Explorer)、NS(Netscape)、Opera、FF(FireFox)。

文尾的重要说明比较重要,请注意。

clientHeight
大家对 clientHeight 都没有什么异议,都认为是内容可视区域的高度,也就是说页面浏览器中可以看到内容的这个区域的高度,一般是最后一个工具条以下到状态栏以上的这个区域,与页面内容无关。

offsetHeight
IE、Opera 认为 offsetHeight = clientHeight + 滚动条 + 边框。
NS、FF 认为 offsetHeight 是网页内容实际高度,可以小于 clientHeight。

scrollHeight
IE、Opera 认为 scrollHeight 是网页内容实际高度,可以小于 clientHeight。
NS、FF 认为 scrollHeight 是网页内容高度,不过最小值是 clientHeight。

简单地说
clientHeight 就是透过浏览器看内容的这个区域高度。
NS、FF 认为 offsetHeight 和 scrollHeight 都是网页内容高度,只不过当网页内容高度小于等于 clientHeight 时,scrollHeight 的值是 clientHeight,而 offsetHeight 可以小于 clientHeight。
IE、Opera 认为 offsetHeight 是可视区域 clientHeight 滚动条加边框。scrollHeight 则是网页内容实际高度。

同理
clientWidth、offsetWidth 和 scrollWidth 的解释与上面相同,只是把高度换成宽度即可。

重要说明

以上是在没有指定 DOCTYPE 的情况下,如果指定了 DOCTYPE,比如:DTD XHTML 1.0 Transitional,则意义又会不同,在这种情况下这三个值都是同一个值,都表示内容的实际高度。新版本的浏览器大多支持根据页面指定的 DOCTYPE 来启用不同的解释器。下载或浏览测试文件。 如果要在指定的 DOCTYPE 下按上述意义来应用,怎么办呢?答案是:将 document.body 和 document.documentElement 一起应用,比如:document.documentElement.scrollLeft || document.body.scrollLeft(一般将 document.documentElement 写在前面),类似应用请参见:http://www.cftea.com/c/2008/06/U1FSRIC247DWTK2M.asp



posted @ 2009-07-21 17:44 二胡 阅读(225) | 评论 (0)编辑 收藏

转 http://extjs2.javaeye.com/blog/394128

正则表达式在字符串处理中经常使用,关于正则简单的用法相信有一点程序基础的人都懂得一些,这里就不介绍简单基础了。这里主要讲解一下在JAVA中实现了的正则的高级用法-分组与捕获。

    对于要重复单个字符,非常简单,直接在字符后卖弄加上限定符即可,例如 a+ 表示匹配1个或一个以上的a,a?表示匹配0个或1个a。这些限定符如下所示:

  X? X,一次或一次也没有
X* X,零次或多次
X+ X,一次或多次
X{n} X,恰好 n 次
X{n,} X,至少 n 次
X{n,m} X,至少 n 次,但是不超过 m 次





但是我们如果要对多个字符进行重复怎么办呢?此时我们就要用到分组,我们可以使用小括号"()"来指定要重复的子表达式,然后对这个子表达式进行重复,例如:(abc)? 表示0个或1个abc 这里一个括号的表达式就表示一个分组。



   分组可以分为两种形式,捕获组和非捕获组。



捕获组

捕获组可以通过从左到右计算其开括号来编号。例如,在表达式 ((A)(B(C))) 中,存在四个这样的组:

1     ((A)(B(C)))
2     \A
3     (B(C))
4     (C)

组零始终代表整个表达式

之所以这样命名捕获组是因为在匹配中,保存了与这些组匹配的输入序列的每个子序列。捕获的子序列稍后可以通过 Back 引用在表达式中使用,也可以在匹配操作完成后从匹配器检索。



Back 引用 是说在后面的表达式中我们可以使用组的编号来引用前面的表达式所捕获到的文本序列(是文本不是正则)。



例如 ([" ']).* \1   其中使用了分组,\1就是对引号这个分组的引用,它匹配包含在两个引号或者两个单引号中的所有字符串,如,"abc" 或 " ' " 或 ' " '  ,但是请注意,它并不会对" a'或者 'a"匹配。原因上面已经说明,Back引用只是引用文本而不是表达式。



非捕获组

      以 (?) 开头的组是纯的非捕获 组,它不捕获文本,也不针对组合计进行计数。就是说,如果小括号中以?号开头,那么这个分组就不会捕获文本,当然也不会有组的编号,因此也不存在Back 引用。

      在Java中,支持的非捕获组,有如下几种:

 
   
  
(?=X)     X,通过零宽度的正 lookahead
(?!X)     X,通过零宽度的负 lookahead
(?<=X)     X,通过零宽度的正 lookbehind
(?<!X)     X,通过零宽度的负 lookbehind
 




这四个非捕获组用于匹配表达式X,但是不包含表达式的文本。

(?=X ) 零宽度正先行断言。仅当子表达式 X 在 此位置的右侧匹配时才继续匹配。例如,\w+(?=\d) 与后跟数字的单词匹配,而不与该数字匹配。此构造不会回溯。
(?!X) 零宽度负先行断言。仅当子表达式 X 不在 此位置的右侧匹配时才继续匹配。例如,例如,\w+(?!\d) 与后不跟数字的单词匹配,而不与该数字匹配。
(?<=X) 零宽度正后发断言。仅当子表达式 X 在 此位置的左侧匹配时才继续匹配。例如,(?<=19)99 与跟在 19 后面的 99 的实例匹配。此构造不会回溯。
(?<!X) 零宽度负后发断言。仅当子表达式 X 不在此位置的左侧匹配时才继续匹配。例如,(?<!19)99 与不跟在 19 后面的 99 的实例匹配






举例:



上面都是理论性的介绍,这里就使用一些例子来说明一下问题:

   1、测试匹配性   (?<!4)56(?=9) 这里的含义就是匹配后面的文本56前面不能是4,后面必须是9组成。因此,可以匹配如下文本 5569  ,与4569不匹配。



  2 、提取字符串   提取 da12bka3434bdca4343bdca234bm   提取包含在字符a和b之间的数字,但是这个a之前的字符不能是c,b后面的字符必须是d才能提取。



        例如这里就只有3434这个数字满足要求。那么我们怎么提取呢?

       首先我们写出提取这个字符串的表达式: (?<!c)a(\d+)bd  这里就只有一个捕获组(\d+)

       JAVA代码片段如下:

Pattern p = Pattern.compile("(?<!c)a(\\d+)bd");
Matcher m = p.matcher("da12bca3434bdca4343bdca234bm");
while(m.find()){
   System.out.println(m.group(1)); //我们只要捕获组1的数字即可。结果 3434
   System.out.println(m.group(0)); // 0组是整个表达式,看这里,并没有提炼出(?<!c)的字符 。结果 a3434bd
}
    可以看到,非捕获组,最后是不会返回结果的,因为它本身并不捕获文本。

posted @ 2009-07-16 09:43 二胡 阅读(513) | 评论 (0)编辑 收藏

转 http://www.blogjava.net/magicdoom/archive/2006/02/27/32555.html

1.传值(by value)

变量的值被复制出一份,与原来的值将不相干,也就是说即使新的值被修改,原来的值也不会改变,JavaScript中基本类型都是传值的.

function testPassValue()

{

   var m=1;

   var n=2;

  

   //m,n的值复制一份,传递到passValue

   passValue(m,n);

  

   alert(m);  //将是原有的值

}

 

function passValue(a,b)

{

  a = a+b; //改变a的值,这里a只是原有值的一份copy

  alert(a);  

}
输出结果:
3
1 

2.传引用(by reference).

引用本身复制一份传给function,引用指向的对象并没有被复制传递(java中也是如此),function,如果改变了对象的属性的值,由于和原来的引用指向的是同一个对象,因此在通过原来的引用访问到的将是修改过的值;

但是如果只是在function中将引用指向一个新的对象,将不会改变原对象的值,改变的只是复制的这一份引用.

(我的理解是:当对象作为参数传给函数的时候,传递的是对象的拷贝!)

function testPassValue()

{

  var date = new Date(2006,02,27);

  alert(date.getDate());  //输出为 27

 

  //date引用本身复制一份,传递到passReference,注意date所指向的对象并没有被复

  passReference(date);

  alert(date.getDate());  //输出为12

 

  //同上

  changeReference(date);

  alert(date.getDate());  //输出还为12

}

function passReference(da)

{

 //由于da和原引用指向的是同一个对象,function,通过原有的引用访问到的将是对象的日期属性值,将是修改之后的值.

   da.setDate(12); 

}

function changeReference(da)

{

   //此时da引用实际上是原引用的一份copy,将引用本身重新赋值,将不会影响原引用

   da= new Date(2007,05,11); 

//da引用指向一个新的对象,此时原引用指向的还是原来的对象

   alert(da.getDate());     // 输出为11

  

}

posted @ 2009-07-15 11:06 二胡 阅读(544) | 评论 (0)编辑 收藏

Ctrl-F3 搜索位于插入点的词
F3/Shift-F3 在文件中查找下一个/上一个
Ctrl-F/H 在文件中查找/替换
Alt-F7 查找使用实例
Ctrl-Shift-P 在项目中查找
Alt-Shift-U 查找使用实例结果
Alt-Shift-H 关闭搜索结果突出显示
Alt-Shift-L 跳转列表中的下一个(所有文件)
Alt-Shift-K 跳转列表中的上一个(所有文件)
Ctrl-R 重新装入窗体
Alt-U-U 将选定内容转换为大写
Alt-U-L 将选定内容转换为小写
Alt-U-R 对选定内容切换大小写

在源代码中导航
Alt-Shift-O 转至类
Alt-Shift-E  转至 JUnit 测试
Alt-O 转至源代码
Alt-G 转至声明
Ctrl-B 转至超级实现
Alt-K/Alt-L 后退/前进
Ctrl-G 转至行
Ctrl-F2  切换添加/删除书签
F2/Shift-F2  下一个/上一个书签
F12/Shift-F12 下一个/上一个使用实例/编译错误
Ctrl-Shift-1/2/3 在“项目”/“文件”/“收藏夹”中选择
Ctrl-[ 将插入记号移至匹配的方括号
Ctrl-^ Ctrl-[(法语/比利时语键盘)

用Java编码
Ctrl-I 覆盖方法
Alt-Shift-F/I 修复全部/选定类的导
Alt-Shift-W 以 try-catch 块围绕
Ctrl-Shift-F 重新设置选定内容的
Ctrl-D/Ctrl-T 左移/右移一个制表符
Ctrl-Shift-T/D 添加/撤消注释行 ("//
Ctrl-L/K 插入下一个/上一个匹
Esc/Ctrl-空格键 关闭/打开代码完成
Ctrl-M 选择下一个参数
Shift-空格键 输入空格,不展开缩写
Alt-F1/Shift-F1 显示/搜索 Javadoc
Ctrl-Shift-M  提取方法
Alt-U-G 将 “get” 放置到标识符前面
Alt-U-S 将 “set” 放置到标识符前面
Alt-U-I 将 “is” 放置到标识符前面
Ctrl-Backspace/Del 删除上一个/当前词
Ctrl-E 删除当前行
Ctrl-J-S/E 开始/结束录制宏
Ctrl-Shift-J  插入国际化字符串
Ctrl-数字键盘上的 - 折叠(隐藏)代码块
Ctrl-数字键盘上的 + 展开已折叠的代码块
Ctrl-Shift-数字键盘上的 - 折叠所有代码块
Ctrl-Shift-数字键盘上的 + 展开所有代码块
Alt-Enter 显示建议/提示

打开和切换视图
Ctrl-Shift-0 显示“搜索结果”窗口
Ctrl-0 显示源代码编辑器
Ctrl-1 显示“项目”窗口
Ctrl-2 显示“文件”窗口
Ctrl-3 显示“收藏夹”窗口
Ctrl-4 显示“输出”窗口
Ctrl-5 显示“运行环境”窗口
Ctrl-6 显示“待做事项”窗口
Ctrl-7 显示“导航”窗口
Ctrl-Shift-7 显示“属性”对话框
Ctrl-Shift-8 显示组件面板
Ctrl-8 显示“版本控制”窗口
Ctrl-9 显示“VCS 输出”窗口
Shift-F4 显示“文档”对话框
Alt-向左方向键 移动到左侧窗口
Alt-向右方向键 移动到右侧窗口
Ctrl-Tab (Ctrl-`) 在打开的文档之间切换
Shift-Escape 最大化窗口(切换)
Ctrl-F4/Ctrl-W 关闭当前选定的窗口
Ctrl-Shift-F4 关闭所有窗口
Shift-F10 打开上下文菜单

编译、测试和运行
F9 编译选定的包或文件
F11 生成主项目
Shift-F11 清理并生成主项目
Ctrl-Q 设置请求参数
Ctrl-Shift-U 创建 JUnit 测试
Ctrl-F6/Alt-F6 为文件/项目运行JUnit测试
F6/Shift-F6 运行主项目/文件

调试
F5 开始调试主项目
Ctrl-Shift-F5 开始调试当前文件
Ctrl-Shift-F6 开始为文件调试测试 (JU
Shift-F5/Ctrl-F5 停止/继续调试会话
F4 运行到文件中的光标位置
F7/F8 步入/越过
Ctrl-F7 步出
Ctrl-Alt-向上方向键 转至被调用的方法
Ctrl-Alt-向下方向键 转至调用方法
Ctrl-F9 计算表达式的值
Ctrl-F8 切换断点
Ctrl-Shift-F8 新建断点
Ctrl-Shift-F7 新建监视
Ctrl-Shift-5 显示 HTTP 监视器
Ctrl-Shift-0 显示“搜索结果”窗口
Alt-Shift-1 显示“局部变量”窗口
Alt-Shift-2 显示“监视”窗口
Alt-Shift-3 显示“调用栈”窗口
Alt-Shift-4 显示“类”窗口
Alt-Shift-5 显示“断点”窗口
Alt-Shift-6 显示“会话”窗口
Ctrl-Shift-6 切换到“执行”窗口
Alt-Shift-7 切换到“线程”窗口
Alt-Shift-8 切换到“源”窗口
posted @ 2009-07-12 21:53 二胡 阅读(396) | 评论 (0)编辑 收藏

一、 异常的概念和Java异常体系结构

    异常是程序运行过程中出现的错误。本文主要讲授的是Java语言的异常处理。Java语言的异常处理框架,
    是Java语言健壮性的一个重要体现。

    Java把异常当作对象来处理,并定义一个基类java.lang.Throwable作为所有异常的超类。
    在Java API中已经定义了许多异常类,这些异常类分为两大类,错误Error和异常Exception。
    Java异常体系结构呈树状,其层次结构图如图 1所示: 
    
     

    图 1  Java异常体系结构

    Thorwable类所有异常和错误的超类,有两个子类Error和Exception,分别表示错误和异常。
    其中异常类Exception又分为运行时异常(RuntimeException)和非运行时异常,
    这两种异常有很大的区别,也称之为不检查异常(Unchecked Exception)
    和检查异常(Checked Exception)。下面将详细讲述这些异常之间的区别与联系:

    1、Error与Exception

    Error是程序无法处理的错误,比如OutOfMemoryError、ThreadDeath等。这些异常发生时,
    Java虚拟机(JVM)一般会选择线程终止。


    Exception是程序本身可以处理的异常,这种异常分两大类运行时异常和非运行时异常。
    程序中应当尽可能去处理这些异常。

    2、运行时异常和非运行时异常

    运行时异常都是RuntimeException类及其子类异常,如NullPointerException、IndexOutOfBoundsException等,
    这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,
    程序应该从逻辑角度尽可能避免这类异常的发生。

    非运行时异常是RuntimeException以外的异常,类型上都属于Exception类及其子类。
    从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。
    如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。


二、 异常的捕获和处理

    Java异常的捕获和处理是一个不容易把握的事情,如果处理不当,不但会让程序代码的可读性大大降低,
    而且导致系统性能低下,甚至引发一些难以发现的错误。


    Java异常处理涉及到五个关键字,分别是:try、catch、finally、throw、throws。下面将骤一介绍,
    通过认识这五个关键字,掌握基本异常处理知识。

    1、 异常处理的基本语法
    在java中,异常处理的完整语法是:

Java代码
  1.  try{   
  2.   //(尝试运行的)程序代码   
  3. }catch(异常类型 异常的变量名){   
  4.   //异常处理代码   
  5. }finally{   
  6.   //异常发生,方法返回之前,总是要执行的代码   
  7. }  




    以上语法有三个代码块:
    try语句块,表示要尝试运行代码,try语句块中代码受异常监控,其中代码发生异常时,会抛出异常对象。

    catch语句块会捕获try代码块中发生的异常并在其代码块中做异常处理,catch语句带一个Throwable类型的参数,
    表示可捕获异常类型。当try中出现异常时,catch会捕获到发生的异常,并和自己的异常类型匹配,
    若匹配,则执行catch块中代码,并将catch块参数指向所抛的异常对象。catch语句可以有多个,
    用来匹配多个中的一个异常,一旦匹配上后,就不再尝试匹配别的catch块了。
    通过异常对象可以获取异常发生时完整的JVM堆栈信息,以及异常信息和异常发生的原因等。

    finally语句块是紧跟catch语句后的语句块,这个语句块总是会在方法返回前执行,
    而不管是否try语句块是否发生异常。并且这个语句块总是在方法返回前执行。
    目的是给程序一个补救的机会。这样做也体现了Java语言的健壮性。

    2、 try、catch、finally三个语句块应注意的问题
    第一、try、catch、finally三个语句块均不能单独使用,三者可以组成 try...catch...finally、try...catch、
    try...finally三种结构,catch语句可以有一个或多个,finally语句最多一个。
    第二、try、catch、finally三个代码块中变量的作用域为代码块内部,分别独立而不能相互访问。
    如果要在三个块中都可以访问,则需要将变量定义到这些块的外面。
    第三、多个catch块时候,只会匹配其中一个异常类并执行catch块代码,而不会再执行别的catch块,
    并且匹配catch语句的顺序是由上到下。

    3、throw、throws关键字
    throw关键字是用于方法体内部,用来抛出一个Throwable类型的异常。如果抛出了检查异常,
    则还应该在方法头部声明方法可能抛出的异常类型。该方法的调用者也必须检查处理抛出的异常。
    如果所有方法都层层上抛获取的异常,最终JVM会进行处理,处理也很简单,就是打印异常消息和堆栈信息。
    如果抛出的是Error或RuntimeException,则该方法的调用者可选择处理该异常。有关异常的转译会在下面说明。

    throws关键字用于方法体外部的方法声明部分,用来声明方法可能会抛出某些异常。仅当抛出了检查异常,
    该方法的调用者才必须处理或者重新抛出该异常。当方法的调用者无力处理该异常的时候,应该继续抛出,
    而不是囫囵吞枣一般在catch块中打印一下堆栈信息做个勉强处理。下面给出一个简单例子,
    看看如何使用这两个关键字:
Java代码
  1. public static void test3() throws Exception{   
  2.   //抛出一个检查异常   
  3.         throw new Exception("方法test3中的Exception");   
  4.     }   


    4、 Throwable类中的常用方法
    getCause():返回抛出异常的原因。如果 cause 不存在或未知,则返回 null。
    getMessage():返回异常的消息信息。
    printStackTrace():对象的堆栈跟踪输出至错误输出流,作为字段 System.err 的值。



三、 异常处理的一般原则

    1、 能处理就早处理,抛出不去还不能处理的就想法消化掉或者转换为RuntimeException处理。
    因为对于一个应用系统来说,抛出大量异常是有问题的,应该从程序开发角度尽可能的控制异常发生的可能。
    2、 对于检查异常,如果不能行之有效的处理,还不如转换为RuntimeException抛出。
    这样也让上层的代码有选择的余地――可处理也可不处理。
    3、 对于一个应用系统来说,应该有自己的一套异常处理框架,这样当异常发生时,也能得到统一的处理风格,
    将优雅的异常信息反馈给用户。

四、 异常的转译与异常链

    1、异常转译的原理


    所谓的异常转译就是将一种异常转换另一种新的异常,也许这种新的异常更能准确表达程序发生异常。
    在Java中有个概念就是异常原因,异常原因导致当前抛出异常的那个异常对象,
    几乎所有带异常原因的异常构造方法都使用Throwable类型做参数,这也就为异常的转译提供了直接的支持,
    因为任何形式的异常和错误都是Throwable的子类。比如将SQLException转换为另外一个新的异常DAOException,
    可以这么写:

    先自定义一个异常DAOException:
Java代码
  1. public class DAOException extends RuntimeException {   
  2. /(省略了部分代码)   
  3.   public DAOException(String message, Throwable cause) {   
  4.       super(message, cause);   
  5.   }   


    比如有一个SQLException类型的异常对象e,要转换为DAOException,可以这么写:
Java代码
  1. DAOException daoEx = new DAOException ( "SQL异常", e);   


    异常转译是针对所有继承Throwable超类的类而言的,从编程的语法角度讲,其子类之间都可以相互转换。
    但是,从合理性和系统设计角度考虑,可将异常分为三类:Error、Exception、RuntimeException,笔者认为,
    合理的转译关系图应该如图 2: 

 


    图 2 异常转译

    为什么要这么做呢?笔者认为,异常的处理存在着一套哲学思想:对于一个应用系统来说,
    系统所发生的任何异常或者错误对操作用户来说都是系统"运行时"异常,都是这个应用系统内部的异常。
    这也是异常转译和应用系统异常框架设计的指导原则。在系统中大量处理非检查异常的负面影响很多,
    最重要的一个方面就是代码可读性降低,程序编写复杂,异常处理的代码也很苍白无力。
    因此,很有必要将这些检查异常Exception和错误Error转换为RuntimeException异常,
    让程序员根据情况来决定是否捕获和处理所发生的异常。


    图中的三条线标识转换的方向,分三种情况:

    ①:Error到Exception:将错误转换为异常,并继续抛出。例如Spring WEB框架中,
    将org.springframework.web.servlet.DispatcherServlet的doDispatch()方法中,
    将捕获的错误转译为一个NestedServletException异常。这样做的目的是为了最大限度挽回因错误发生带来的负面影响。
    因为一个Error常常是很严重的错误,可能会引起系统挂起。

    ②:Exception到RuntimeException:将检查异常转换为RuntimeException可以让程序代码变得更优雅,
    让开发人员集中经理设计更合理的程序代码,反过来也增加了系统发生异常的可能性。

    ③:Error到RuntimeException:目的还是一样的。把所有的异常和错误转译为不检查异常,
    这样可以让代码更为简洁,还有利于对错误和异常信息的统一处理。


    1、 异常链

    异常链顾名思义就是将异常发生的原因一个传一个串起来,即把底层的异常信息传给上层,这样逐层抛出。
    Java API文档中给出了一个简单的模型:

Java代码
  1. try {   
  2.  lowLevelOp();   
  3. catch (LowLevelException le) {   
  4.   throw (HighLevelException)   
  5.   new HighLevelException().initCause(le);   
  6. }  


    当程序捕获到了一个底层异常le,在处理部分选择了继续抛出一个更高级别的新异常给此方法的调用者。
    这样异常的原因就会逐层传递。这样,位于高层的异常递归调用getCause()方法,就可以遍历各层的异常原因。
    这就是Java异常链的原理。异常链的实际应用很少,发生异常时候逐层上抛不是个好注意,
    上层拿到这些异常又能奈之何?而且异常逐层上抛会消耗大量资源,
    因为要保存一个完整的异常链信息.


五、 设计一个高效合理的异常处理框架

    对于一个应用系统来说,发生所有异常在用户看来都是应用系统内部的异常。因此应该设计一套应用系统的异常框架,
    以处理系统运行过程中的所有异常。

    基于这种观点,可以设计一个应用系统的异常比如叫做AppException。并且对用户来说,
    这些异常都是运行应用系统运行时发生的,因此AppException应该继承RuntimeException,
    这样系统中所有的其他异常都转译为AppException,当异常发生的时候,前端接收到AppExcetpion并做统一的处理。
   
    画出异常处理框架如图 3 : 

 



     图 3 一个应用系统的异常处理框架

    在这个设计图中,AppRuntimeException是系统异常的基类,对外只抛出这个异常,
    这个异常可以由前端(客户端)接收处理,当异常发生时,客户端的相关组件捕获并处理这些异常,
    将"友好"的信息展示给客户。

    在AppRuntimeException下层,有各种各样的异常和错误,最终都转译为AppRuntimeException,
    AppRuntimeException下面还可以设计一些别的子类异常,比如AppDAOException、OtherException等,
    这些都根据实际需要灵活处理。
    在往下就是如何将捕获的原始异常比如SQLException、HibernateException转换为更高级一点AppDAOException。


    有关异常框架设计这方面公认比较好的就是Spring,Spring中的所有异常都可以用org.springframework.core.NestedRuntimeException来表示,并且该基类继承的是RuntimeException。
    Spring框架很庞大,因此设计了很多NestedRuntimeException的子类,还有异常转换的工具,
    这些都是非常优秀的设计思想。


六、 Java异常处理总结

    回顾全文,总结一下Java异常处理的要点:
    1、 异常是程序运行过程过程出现的错误,在Java中用类来描述,用对象来表示具体的异常。
        Java将其区分为Error与Exception,Error是程序无力处理的错误,Exception是程序可以处理的错误。
        异常处理是为了程序的健壮性。
    2、 Java异常类来自于Java API定义和用户扩展。通过继承Java API异常类可以实现异常的转译。
    3、 异常能处理就处理,不能处理就抛出,最终没有处理的异常JVM会进行处理。
    4、 异常可以传播,也可以相互转译,但应该根据需要选择合理的异常转译的方向。
    5、 对于一个应用系统,设计一套良好的异常处理体系很重要。这一点在系统设计的时候就应该考虑到。

posted @ 2009-07-11 12:04 二胡 阅读(1036) | 评论 (0)编辑 收藏

转 http://www.blogjava.net/cenwenchu/archive/2008/01/22/177082.html

OOM这个缩写就是Java程序开发过程中让人最头痛的问题:Out of Memory。在很多开发人员的开发过程中,或多或少的都会遇到这类问题,这类问题定位比较困难,往往需要根据经验来判断可能出现问题的代码。原因主要是两个:对象没有被释放(多种情况引起,往往是比较隐蔽的引用导致被Hold而无法被回收)。另一种就是真的Memory不够用了,需要增加JVMHeap来满足应用程序的需求。最近有同事发的关于解决OOM的问题,让我了解了原来OOM除了在JVM Heap不够时会发生,在Native Heap不够的时候也会发生,同时JVM HeapNative Heap存在着相互影响和平衡的关系,因此就仔细的去看了关于OOMJVM配置优化的内容。

OOM

       在其他语言类似于C,Delphi等等由于内存都是由自己分配和管理,因此内存泄露的问题比较常见,同时也是很头痛的一件事情。而Java的对象生命周期管理都是JVM来做的,简化了开发人员的非业务逻辑的处理,但是这种自动管理回收机制也是基于一些规则的,而违背了这些规则的时候,就会造成所谓的“Memory Leak”。

OOM(Java Heap)

       错误提示:java.lang.OutOfMemoryError

这类OOM是由于JVM分配的给应用的Heap Memory已经被耗尽,可能是因为应用在高负荷的情况下的却需要很大的内存,因此可以通过修改JVM参数来增加Java Heap Memory(不过也不能无限制增加,后面那种OOM有可能就是因为这个原因而产生)。另一种情况是因为应用程序使用对象或者资源没有释放,导致内存消耗持续增加,最后出现OOM,这类问题引起的原因往往是应用已不需要的对象还被其他有效对象所引用,那么就无法释放,可能是业务代码逻辑造成的(异常处理不够例如IO等资源),也可能是对于第三方开源项目中资源释放了解不够导致使用以后资源没有释放(例如JDBCResultSet等)。

       几个容易出现问题的场景:

       1.应用的缓存或者Collection:如果应用要缓存Java对象或者是在一个Collection中保存对象,那么就要确定是否会有大量的对象存入,要做保护,以防止在大数据量下大量内存被消耗,同时要保证Cache的大小不会无限制增加。

       2.生命周期较长的对象:尽量简短对象的生命周期,现在采用对象的创建释放代价已经很低,同时作了很好的优化,要比创建一个对象长期反复使用要好。如果能够设置超时的情景下,尽量设置超时。

       3.类似于JDBCConnection Pool,在使用Pool中的对象以后需要释放并返回,不然就会造成Pool的不断增大,在其他Pool中使用也是一样。同样ResultSetIO这类资源的释放都需要注意。

       解决的方法就是查找错误或者是增加Java Heap Memory。对于此类问题检测工具相当多,这里就不做介绍了。      

OOM(Native Heap)

错误提示:requested XXXX bytes for ChunkPool::allocate. Out of swap space

       Native Heap MemoryJVM内部使用的Memory,这部分的Memory可以通过JDK提供的JNI的方式去访问,这部分Memory效率很高,但是管理需要自己去做,如果没有把握最好不要使用,以防出现内存泄露问题。JVM 使用Native Heap Memory用来优化代码载入(JTI代码生成),临时对象空间申请,以及JVM内部的一些操作。这次同事在压力测试中遇到的问题就是这类OOM,也就是这类Memory耗尽。同样这类OOM产生的问题也是分成正常使用耗尽和无释放资源耗尽两类。无释放资源耗尽很多时候不是程序员自身的原因,可能是引用的第三方包的缺陷,例如很多人遇到的Oracle 9 JDBC驱动在低版本中有内存泄露的问题。要确定这类问题,就需要去观察Native Heap Memory的增长和使用情况,在服务器应用起来以后,运行一段时间后JVM对于Native Heap Memory的使用会达到一个稳定的阶段,此时可以看看什么操作对于Native Heap Memory操作频繁,而且使得Native Heap Memory增长,对于Native Heap Memory的情况我还没有找到办法去检测,现在能够看到的就是为JVM启动时候增加-verbose:jni参数来观察对于Native Heap Memory的操作。另一种情况就是正常消耗Native Heap Memory,对于Native Heap Memory的使用主要取决于JVM代码生成,线程创建,用于优化的临时代码和对象产生。当正常耗尽Native Heap Memory时,那么就需要增加Native Heap Memory,此时就会和我们前面提到增加java Heap Memory的情况出现矛盾。

应用内存组合

       对于应用来说,可分配的内存受到OS的限制,不同的OS对进程所能访问虚拟内存地址区间直接影响对于应用内存的分配,32位的操作系统通常最大支持4G的内存寻址,而Linux一般为3GWindows2G。然而这些大小的内存并不会全部给JVMJava Heap使用,它主要会分成三部分:Java HeapNative Heap,载入资源和类库等所占用的内存。那么由此可见,Native Heap Java Heap大小配置是相互制约的,哪一部分分配多了都可能会影响到另外一部分的正常工作,因此如果通过命令行去配置,那么需要确切的了解应用使用情况,否则采用默认配置自动监测会更好的优化应用使用情况。

       同样要注意的就是进程的虚拟内存和机器的实际内存还是有区别的,对于机器来说实际内存以及硬盘提供的虚拟内存都是提供给机器上所有进程使用的,因此在设置JVM参数时,它的虚拟内存绝对不应该超过实际内存的大小。

待续……


JVM
优化配置


更多内容可访问:http://blog.csdn.net/cenwenchu79/
posted @ 2009-07-11 09:04 二胡 阅读(233) | 评论 (0)编辑 收藏

        现在web开发中,ajax应用的比较多。关于此方面的框架也不少。在应用中都会遇到session过期
的问题,如处理不当会影响用户体验,也有可能产生莫名的问题。
        结合自己的思考和网上相关内容的参考,给出如下解决方案。每个方案都有不同的优缺点,欢迎
大家指正。
     方案1:检查AJAX返回的返回的内容是否有<html>标签
           在web系统中,当session过期时,当用户有操作的时候,此时系统一般会返回登陆界面。
           让用户重新输入用户名和密码。当session过期的时候,AJAX请求返回的内容应该是登陆界面的页面
          内容(即登陆界面的页面的html代码)。通过判断返回内容是否用<html>来判断session是否过期。
     var result=request.responseText;/* ajax返回的内容*/
    
     if(result.indexOf('<HTML>')>-1){/*返回内容中有html标签*/}
     或者
     var r=/<html>/ig;
     if(r.test(result)){/*返回内容中有html标签*/}
     通过上面的方法可以判断session是否过期,然后根据具体的业务进行异常处理。

     方案2:返回的结果中有session是否过期的标志。也有人称为true/false模式
                   此解决方案一般结合json使用。
     如返回的结果是:
        var res={
             "result":true,/*session没有过期,false(session过期)*/
             "data"  :""/*其它数据*/
                     }
     if(res["result"])
     {
          /*session没有过期*/
      }else{
         /*session过期*/
     }

     方案3:利用时间戳
     在页面上搞个全局变量
     var startDate; /*ajax最近一次访问服务器的时间,Date类型*/
     if(new Date().getTime()-startDate.getTime()<30*60*1000)
     {
           /*假设session过期的时间30分钟*/
           /*session没有过期*/
     }else{
           /*session过期*/
     }
     哈哈!Do not trust the client

    方案4:延长session过期时间
             此方案有性能问题
          4.1:延长session过期时间
          4.2:client轮循server。(AJAX轮循server或client,server保持长连接)

posted @ 2009-07-08 20:52 二胡 阅读(1548) | 评论 (0)编辑 收藏

      大家都知道,毛主席对游击战有个十六字诀:"敌进我退,敌驻我扰,敌疲我打,敌退我追"。
我对此的理解就是当敌人的形势发生了变化,我们行动也要相应变动。其实在软件开发过程中也有类式情景。当A对象的状态变化的时候,B对象的状态也要相应变化。我们常用观察者模式解决此类问题。代码如下,可能和大家平常见的观察者模式的代码实现不太一样,其实原理都是一样的。
    在下面的代码里,我方的情报人员显的很重要。即被观察者要有到观察者的引用。

 public class 敌人 {
 private 我 my;

 public 敌人() {
  my = new 我(); /* 哈哈,敌人内部的我方情报人员 */
 }

 public void 进() {
  System.out.println("--敌进--");
  my.退();
 }

 public void 驻() {
  System.out.println("--敌驻--");
  my.扰();
 }

 public void 疲() {
  System.out.println("--敌疲--");
  my.打();
 }

 public void 退() {
  System.out.println("--敌退--");
  my.追();
 }
}


 public class 我 {
 public void 退() {
  System.out.println("--我退--");
 }

 public void 扰() {
  System.out.println("--我扰--");
 }

 public void 打() {
  System.out.println("--我打--");
 }

 public void 追() {
  System.out.println("--我追--");
 }
}

 

posted @ 2009-07-07 12:34 二胡 阅读(1204) | 评论 (4)编辑 收藏

zhuan  http://blog.csdn.net/tzsmin/archive/2009/01/15/3784683.aspx

attachEvent方法,为某一事件附加其它的处理事件。(不支持Mozilla系列)

addEventListener方法 用于 Mozilla系列

举例:
document.getElementById("btn").onclick = method1;
document.getElementById(
"btn").onclick = method2;
document.getElementById(
"btn").onclick = method3;
如果这样写,那么将会只有medhot3被执行

写成这样:
var btn1Obj = document.getElementById("btn1"); 
//object.attachEvent(event,function);
btn1Obj.attachEvent("onclick",method1);
btn1Obj.attachEvent(
"onclick",method2);
btn1Obj.attachEvent(
"onclick",method3);
执行顺序为method3->method2->method1

如果是Mozilla系列,并不支持该方法,需要用到addEventListener
var btn1Obj = document.getElementById("btn1");
//element.addEventListener(type,listener,useCapture);
btn1Obj.addEventListener("click",method1,false);
btn1Obj.addEventListener(
"click",method2,false);
btn1Obj.addEventListener(
"click",method3,false);
执行顺序为method1->method2->method3

使用实例:

1。 
var el = EDITFORM_DOCUMENT.body; 
//先取得对象,EDITFORM_DOCUMENT实为一个iframe
if (el.addEventListener){
 el.addEventListener(
'click', KindDisableMenu, false);
}
 else if (el.attachEvent){
 el.attachEvent(
'onclick', KindDisableMenu);
}

2。 
if (window.addEventListener) {
 window.addEventListener(
'load', _uCO, false);
}
 else if (window.attachEvent) {
 window.attachEvent(
'onload', _uCO);
}


posted @ 2009-07-06 14:28 二胡 阅读(583) | 评论 (0)编辑 收藏

转 http://blog.csdn.net/haoel/archive/2009/03/26/4028232.aspx

深入浅出单实例Singleton设计模式

陈皓

前序

单实例Singleton设计模式可能是被讨论和使用的最广泛的一个设计模式了,这可能也是面试中问得最多的一个设计模式了。这个设计模式主要目的 是想在整个系统中只能出现一个类的实例。这样做当然是有必然的,比如你的软件的全局配置信息,或者是一个Factory,或是一个主控类,等等。你希望这 个类在整个系统中只能出现一个实例。当然,作为一个技术负责人的你,你当然有权利通过使用非技术的手段来达到你的目的。比如:你在团队内部明文规 定,“XX类只能有一个全局实例,如果某人使用两次以上,那么该人将被处于2000元的罚款!”(呵呵),你当然有权这么做。但是如果你的设计的是东西是 一个类库,或是一个需要提供给用户使用的API,恐怕你的这项规定将会失效。因为,你无权要求别人会那么做。所以,这就是为什么,我们希望通过使用技术的 手段来达成这样一个目的的原因。

本文会带着你深入整个Singleton的世界,当然,我会放弃使用C++语言而改用Java语言,因为使用Java这个语言可能更容易让我说明一些事情。


Singleton的教学版本

这里,我将直接给出一个Singleton的简单实现,因为我相信你已经有这方面的一些基础了。我们姑且把这具版本叫做1.0版

  1. // version 1.0   
  2. public class Singleton   
  3. {   
  4.     private static final Singleton singleton = null;   
  5.   
  6.     private Singleton()   
  7.     {   
  8.     }   
  9.     public static Singleton getInstance()   
  10.     {   
  11.         if (singleton== null)   
  12.         {   
  13.             singleton= new Singleton();   
  14.         }   
  15.         return singleton;   
  16.     }   
  17. }  

在上面的实例中,我想说明下面几个Singleton的特点:(下面这些东西可能是尽人皆知的,没有什么新鲜的)

  1. 私有(private)的构造函数,表明这个类是不可能形成实例了。这主要是怕这个类会有多个实例。
  2. 即然这个类是不可能形成实例,那么,我们需要一个静态的方式让其形成实例:getInstance()。注意这个方法是在new自己,因为其可以访问私有的构造函数,所以他是可以保证实例被创建出来的。
  3. 在getInstance()中,先做判断是否已形成实例,如果已形成则直接返回,否则创建实例。
  4. 所形成的实例保存在自己类中的私有成员中。
  5. 我们取实例时,只需要使用Singleton.getInstance()就行了。

当然,如果你觉得知道了上面这些事情后就学成了,那我给你当头棒喝一下了,事情远远没有那么简单。

Singleton的实际版本

上面的这个程序存在比较严重的问题,因为是全局性的实例,所以,在多线程情况下,所有的全局共享的东西都会变得非常的危险,这个也一样,在多线程情 况下,如果多个线程同时调用getInstance()的话,那么,可能会有多个进程同时通过 (singleton== null)的条件检查,于是,多个实例就创建出来,并且很可能造成内存泄露问题。嗯,熟悉多线程的你一定会说——“我们需要线程互斥或同步”,没错,我们 需要这个事情,于是我们的Singleton升级成1.1版,如下所示:

  1. // version 1.1   
  2. public class Singleton   
  3. {   
  4.     private static final Singleton singleton = null;   
  5.   
  6.     private Singleton()   
  7.     {   
  8.     }   
  9.     public static Singleton getInstance()   
  10.     {   
  11.         if (singleton== null)   
  12.         {   
  13.             synchronized (Singleton.class) {   
  14.                 singleton= new Singleton();   
  15.             }   
  16.         }   
  17.         return singleton;   
  18.     }   
  19. }  

嗯,使用了Java的synchronized方法,看起来不错哦。应该没有问题了吧?!错!这还是有问题!为什么呢?前面已经说过,如果有多个线 程同时通过(singleton== null)的条件检查(因为他们并行运行),虽然我们的synchronized方法会帮助我们同步所有的线程,让我们并行线程变成串行的一个一个去 new,那不还是一样的吗?同样会出现很多实例。嗯,确实如此!看来,还得把那个判断(singleton== null)条件也同步起来。于是,我们的Singleton再次升级成1.2版本,如下所示:

 
  1. // version 1.2   
  2. public class Singleton   
  3. {   
  4.     private static final Singleton singleton = null;   
  5.   
  6.     private Singleton()   
  7.     {   
  8.     }   
  9.     public static Singleton getInstance()   
  10.     {   
  11.         synchronized (Singleton.class)   
  12.         {   
  13.             if (singleton== null)   
  14.             {   
  15.                 singleton= new Singleton();   
  16.             }   
  17.         }   
  18.         return singleton;   
  19.     }   
  20. }  

不错不错,看似很不错了。在多线程下应该没有什么问题了,不是吗?的确是这样的,1.2版的Singleton在多线程下的确没有问题了,因为我们 同步了所有的线程。只不过嘛……,什么?!还不行?!是的,还是有点小问题,我们本来只是想让new这个操作并行就可以了,现在,只要是进入 getInstance()的线程都得同步啊,注意,创建对象的动作只有一次,后面的动作全是读取那个成员变量,这些读取的动作不需要线程同步啊。这样的 作法感觉非常极端啊,为了一个初始化的创建动作,居然让我们达上了所有的读操作,严重影响后续的性能啊!

还得改!嗯,看来,在线程同步前还得加一个(singleton== null)的条件判断,如果对象已经创建了,那么就不需要线程的同步了。OK,下面是1.3版的Singleton。

  1. // version 1.3   
  2. public class Singleton   
  3. {   
  4.     private static final Singleton singleton = null;   
  5.   
  6.     private Singleton()   
  7.     {   
  8.     }   
  9.     public static Singleton getInstance()   
  10.     {   
  11.         if (singleton== null)   
  12.         {   
  13.             synchronized (Singleton.class)   
  14.             {   
  15.                 if (singleton== null)   
  16.                 {   
  17.                     singleton= new Singleton();   
  18.                 }   
  19.             }   
  20.         }   
  21.         return singleton;   
  22.     }   
  23. }  

感觉代码开始变得有点罗嗦和复杂了,不过,这可能是最不错的一个版本了,这个版本又叫“双重检查”Double-Check。下面是说明:

  1. 第一个条件是说,如果实例创建了,那就不需要同步了,直接返回就好了。
  2. 不然,我们就开始同步线程。
  3. 第二个条件是说,如果被同步的线程中,有一个线程创建了对象,那么别的线程就不用再创建了。

相当不错啊,干得非常漂亮!请大家为我们的1.3版起立鼓掌!

Singleton的其它问题

怎么?还有问题?!当然还有,请记住下面这条规则——“无论你的代码写得有多好,其只能在特定的范围内工作,超出这个范围就要出Bug了”,这是“陈式第一定理”,呵呵。你能想一想还有什么情况会让这个我们上面的代码出问题吗?

在C++下,我不是很好举例,但是在Java的环境下,嘿嘿,还是让我们来看看下面的一些反例和一些别的事情的讨论(当然,有些反例可能属于钻牛角尖,可能有点学院派,不过也不排除其实际可能性,就算是提个醒吧):

其一、Class Loader。不知道你对Java的Class Loader熟悉吗?“类装载器”?!C++可没有这个东西啊。这是Java动态性的核心。顾名思义,类装载器是用来把类(class)装载进JVM的。 JVM规范定义了两种类型的类装载器:启动内装载器(bootstrap)和用户自定义装载器(user-defined class loader)。 在一个JVM中可能存在多个ClassLoader,每个ClassLoader拥有自己的NameSpace。一个ClassLoader只能拥有一个 class对象类型的实例,但是不同的ClassLoader可能拥有相同的class对象实例,这时可能产生致命的问题。如ClassLoaderA, 装载了类A的类型实例A1,而ClassLoaderB,也装载了类A的对象实例A2。逻辑上讲A1=A2,但是由于A1和A2来自于不同的 ClassLoader,它们实际上是完全不同的,如果A中定义了一个静态变量c,则c在不同的ClassLoader中的值是不同的。

于是,如果咱们的Singleton 1.3版本如果面对着多个Class Loader会怎么样?呵呵,多个实例同样会被多个Class Loader创建出来,当然,这个有点牵强,不过他确实存在。难道我们还要整出个1.4版吗?可是,我们怎么可能在我的Singleton类中操作 Class Loader啊?是的,你根本不可能。在这种情况下,你能做的只有是——“保证多个Class Loader不会装载同一个Singleton”。

其二、序例化。如果我们的这个Singleton类是一个关于我们程序配置信息的类。我们需要它有序列化的功能,那么,当反序列化的时候,我们将无法控制别人不多次反序列化。不过,我们可以利用一下Serializable接口的readResolve()方法,比如:

  1. public class Singleton implements Serializable   
  2. {   
  3.     ......   
  4.     ......   
  5.     protected Object readResolve()   
  6.     {   
  7.         return getInstance();   
  8.     }   
  9. }  

其三、多个Java虚拟机。如果我们的程序运行在多个Java的虚拟机中。什么?多个虚拟机?这是一种什么样的情况啊。嗯,这种情况是有点极端,不过还是可能出现,比如EJB或RMI之流的东西。要在这种环境下避免多实例,看来只能通过良好的设计或非技术来解决了。

其四,volatile变量。关于volatile这个关键字所声明的变量可以被看作是一种 “程度较轻的同步synchronized”;与 synchronized 块相比,volatile 变量所需的编码较少,并且运行时开销也较少,但是它所能实现的功能也仅是synchronized的一部分。当然,如前面所述,我们需要的 Singleton只是在创建的时候线程同步,而后面的读取则不需要同步。所以,volatile变量并不能帮助我们即能解决问题,又有好的性能。而且, 这种变量只能在JDK 1.5+版后才能使用。

其五、关于继承。是的,继承于Singleton后的子类也有可能造成多实例的问题。不过,因为我们早把Singleton的构造函数声明成了私有的,所以也就杜绝了继承这种事情。

其六,关于代码重用。也话我们的系统中有很多个类需要用到这个模式,如果我们在每一个类都中有这样的代码,那么 就显得有点傻了。那么,我们是否可以使用一种方法,把这具模式抽象出去?在C++下这是很容易的,因为有模板和友元,还支持栈上分配内存,所以比较容易一 些(程序如下所示),Java下可能比较复杂一些,聪明的你知道怎么做吗?

  1. template<CLASS T> class Singleton   
  2. {   
  3.     public:   
  4.         static T& Instance()   
  5.         {   
  6.             static T theSingleInstance; //假设T有一个protected默认构造函数   
  7.             return theSingleInstance;   
  8.         }   
  9. };   
  10.   
  11. class OnlyOne : public Singleton<ONLYONE>   
  12. {   
  13.     friend class Singleton<ONLYONE>;   
  14.     int example_data;   
  15.   
  16.     public:   
  17.         int GetExampleData() const {return example_data;}   
  18.     protected:   
  19.         OnlyOne(): example_data(42) {}   // 默认构造函数   
  20.         OnlyOne(OnlyOne&) {}   
  21. };   
  22.   
  23. int main( )   
  24. {   
  25.     cout << OnlyOne::Instance().GetExampleData()<< endl;   
  26.     return 0;   
  27. }  

 (转载时请注明作者和出处。未经许可,请勿用于商业用途)

(全文完)



posted @ 2009-07-06 14:25 二胡 阅读(223) | 评论 (0)编辑 收藏

转 http://blog.csdn.net/kongbu0622/archive/2009/02/04/3862089.aspx

以前看正则表达式,但没有注意到正则表达式的贪婪与非贪婪模式,今天在经典上看到了这么段代码:

<script>

try{

str="<p>abcdefg</p><p>abcdefghijkl</p>";

re1=str.match(/<p>[\W\w]+?<\/p>/ig);

alert("非贪婪模式:\r\n\r\n1:"+re1[0]+"\r\n2:"+re1[1]);

re1=str.match(/<p>[\W\w]+<\/p>/ig);

alert("贪婪模式:\r\n\r\n"+re1);

re1=str.match(/<p>(.+?)<\/p>/i);

alert("非贪婪模式,且不要标记:\r\n\r\n1:"+re1[1]);

re1=str.match(/<p>(.+)<\/p>/i);

alert("贪婪模式,且不要标记:\r\n\r\n"+re1[1]);

}catch(e){alert(e.description)}

</script>

 

 匹配次数中的贪婪与非贪婪

    在使用修饰匹配次数的特殊符号时,有几种表示方法可以使同一个表达式能够匹配不同的次数,比如:"{m,n}", "{m,}", "?", "*", "+",具体匹配的次数随被匹配的字符串而定。这种重复匹配不定次数的表达式在匹配过程中,总是尽可能多的匹配。比如,针对文本 "dxxxdxxxd",举例如下:

表达式

匹配结果

(d)(\w+)

"\w+" 将匹配第一个 "d" 之后的所有字符 "xxxdxxxd"

(d)(\w+)(d)

"\w+" 将匹配第一个 "d" 和最后一个 "d" 之间的所有字符 "xxxdxxx"。虽然 "\w+" 也能够匹配上最后一个 "d",但是为了使整个表达式匹配成功,"\w+" 可以 "让出" 它本来能够匹配的最后一个 "d"

    由此可见,"\w+" 在匹配的时候,总是尽可能多的匹配符合它规则的字符。虽然第二个举例中,它没有匹配最后一个 "d",但那也是为了让整个表达式能够匹配成功。同理,带 "*" 和 "{m,n}" 的表达式都是尽可能地多匹配,带 "?" 的表达式在可匹配可不匹配的时候,也是尽可能的 "要匹配"。这 种匹配原则就叫作 "贪婪" 模式 。

    非贪婪模式:

    在修饰匹配次数的特殊符号后再加上一个 "?" 号,则可以使匹配次数不定的表达式尽可能少的匹配,使可匹配可不匹配的表达式,尽可能的 "不匹配"。这种匹配原则叫作 "非贪婪" 模式,也叫作 "勉强" 模式。如果少匹配就会导致整个表达式匹配失败的时候,与贪婪模式类似,非贪婪模式会最小限度的再匹配一些,以使整个表达式匹配成功。举例如下,针对文本 "dxxxdxxxd" 举例:

表达式

匹配结果

(d)(\w+?)

"\w+?" 将尽可能少的匹配第一个 "d" 之后的字符,结果是:"\w+?" 只匹配了一个 "x"

(d)(\w+?)(d)

为了让整个表达式匹配成功,"\w+?" 不得不匹配 "xxx" 才可以让后边的 "d" 匹配,从而使整个表达式匹配成功。因此,结果是:"\w+?" 匹配 "xxx"

    更多的情况,举例如下:

    举 例1:表达式 "<td>(.*)</td>" 与字符串 "<td><p>aa</p></td> <td><p>bb</p></td>" 匹配时,匹配的结果是:成功;匹配到 的内容是 "<td><p>aa</p></td> <td><p>bb</p></td>" 整个字符串, 表达式中的 "</td>" 将与字符串中最后一个 "</td>" 匹配。

    举例2:相比之下,表达式 "<td>(.*?)</td>" 匹配举例1中同样的字符串时,将只得到 "<td><p>aa</p></td>", 再次匹配下一个时,可以得到第二个 "<td><p>bb</p></td>"。

 function isTrueName(s) 
 { 
 var patrn=/^[a-zA-Z]{1,30}$/; 
 if (!patrn.exec(s)) return false 
 return true 
 } 
 }} 
  
 //校验密码:只能输入6-20个字母、数字、下划线 
 <pre name="code" class="java">function isPasswd(s) 
 { 
 var patrn=/^(\w){6,20}$/; 
 if (!patrn.exec(s)) return false 
 return true 
 } 
 </pre> 
  
 //校验普通电话、传真号码:可以“+”开头,除数字外,可含有“-” 
 <pre name="code" class="java">function isTel(s) 
 { 
 //var patrn=/^[+]{0,1}(\d){1,3}[ ]?([-]?(\d){1,12})+$/; 
 var patrn=/^[+]{0,1}(\d){1,3}[ ]?([-]?((\d)|[ ]){1,12})+$/; 
 if (!patrn.exec(s)) return false 
 return true 
 } 
 </pre> 
  
 //校验手机号码:必须以数字开头,除数字外,可含有“-” 
 <pre name="code" class="java">function isMobil(s) 
 { 
 var patrn=/^[+]{0,1}(\d){1,3}[ ]?([-]?((\d)|[ ]){1,12})+$/; 
 if (!patrn.exec(s)) return false 
 return true 
 } 
 </pre> 
  
 //校验邮政编码 
 <pre name="code" class="java">function isPostalCode(s) 
 { 
 //var patrn=/^[a-zA-Z0-9]{3,12}$/; 
 var patrn=/^[a-zA-Z0-9 ]{3,12}$/; 
 if (!patrn.exec(s)) return false 
 return true 
 } 
 </pre> 
  
 //校验搜索关键字 
 <pre name="code" class="java">function isSearch(s) 
 { 
 var patrn=/^[^`~!@$%^&*()+=|\\\][\]\{\}:;'\,.<>/?]{1}[^`~!@$%^&()+=|\\\] 
         [\]\{\}:;'\,.<>?]{0,19}$/; 
 if (!patrn.exec(s)) return false 
 return true 
 } 
  
 function isIP(s) //by zergling 
 { 
 var patrn=/^[0-9.]{1,20}$/; 
 if (!patrn.exec(s)) return false 
 return true 
 } 
 </pre> 
  
 <span style="font-size: 18pt;">正则表达式</span> 
 <pre name="code" class="java">"^\\d+$"  //非负整数(正整数 + 0) 
 "^[0-9]*[1-9][0-9]*$"  //正整数  
 "^((-\\d+)|(0+))$"  //非正整数(负整数 + 0)  
 "^-[0-9]*[1-9][0-9]*$"  //负整数  
 "^-?\\d+$"    //整数  
 "^\\d+(\\.\\d+)?$"  //非负浮点数(正浮点数 + 0)  
 "^(([0-9]+\\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\\.[0-9]+)|([0-9]*[1-9][0-9]*))$"  
 //正浮点数  
 "^((-\\d+(\\.\\d+)?)|(0+(\\.0+)?))$"  //非正浮点数(负浮点数 + 0)  
 "^(-(([0-9]+\\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\\.[0-9]+)|([0-9]*[1-9][0-9]*)))$"  
 //负浮点数  
 "^(-?\\d+)(\\.\\d+)?$"  //浮点数  
 "^[A-Za-z]+$"  //由26个英文字母组成的字符串  
 "^[A-Z]+$"  //由26个英文字母的大写组成的字符串  
 "^[a-z]+$"  //由26个英文字母的小写组成的字符串  
 "^[A-Za-z0-9]+$"  //由数字和26个英文字母组成的字符串  
 "^\\w+$"  //由数字、26个英文字母或者下划线组成的字符串  
 "^[\\w-]+(\\.[\\w-]+)*@[\\w-]+(\\.[\\w-]+)+$"    //email地址  
 "^[a-zA-z]+://(\\w+(-\\w+)*)(\\.(\\w+(-\\w+)*))*(\\?\\S*)?$"  //url 
 "^[A-Za-z0-9_]*$" 
 </pre> 
  
 <span style="font-size: 18pt;">正则表达式使用详解</span> 
  
 简介  
  
 简单的说,正则表达式是一种可以用于模式匹配和替换的强有力的工具。其作用如下: 
 测试字符串的某个模式。例如,可以对一个输入字符串进行测试,看在该字符串是否存在一个电话号码模式或一个信用卡号码模式。这称为数据有效性验证。  
 替换文本。可以在文档中使用一个正则表达式来标识特定文字,然后可以全部将其删除,或者替换为别的文字。  
 根据模式匹配从字符串中提取一个子字符串。可以用来在文本或输入字段中查找特定文字。  
  
 基本语法  
  
 在对正则表达式的功能和作用有了初步的了解之后,我们就来具体看一下正则表达式的语法格式。 
   
 正则表达式的形式一般如下:   
  
 /love/   其中位于“/”定界符之间的部分就是将要在目标对象中进行匹配的模式。用户只要把希望查找匹配对象的模式内容放入“/”定界符之间即可。为了能够使用户更加灵活的定制模式内容,正则表达式提供了专门的“元字符”。所谓元字符就是指那些在正则表达式中具有特殊意义的专用字符,可以用来规定其前导字符(即位于元字符前面的字符)在目标对象中的出现模式。  
 较为常用的元字符包括: “+”, “*”,以及 “?”。 
  
 “+”元字符规定其前导字符必须在目标对象中连续出现一次或多次。 
  
 “*”元字符规定其前导字符必须在目标对象中出现零次或连续多次。 
  
 “?”元字符规定其前导对象必须在目标对象中连续出现零次或一次。 
  
 下面,就让我们来看一下正则表达式元字符的具体应用。  
  
 /fo+/  因为上述正则表达式中包含“+”元字符,表示可以与目标对象中的 “fool”, “fo”, 或者 “football”等在字母f后面连续出现一个或多个字母o的字符串相匹配。  
  
 /eg*/  因为上述正则表达式中包含“*”元字符,表示可以与目标对象中的 “easy”, “ego”, 或者 “egg”等在字母e后面连续出现零个或多个字母g的字符串相匹配。  
  
 /Wil?/  因为上述正则表达式中包含“?”元字符,表示可以与目标对象中的 “Win”, 或者“Wilson”,等在字母i后面连续出现零个或一个字母l的字符串相匹配。  
  
 有时候不知道要匹配多少字符。为了能适应这种不确定性,正则表达式支持限定符的概念。这些限定符可以指定正则表达式的一个给定组件必须要出现多少次才能满足匹配。 
  
 {n} n 是一个非负整数。匹配确定的 n 次。例如,'o{2}' 不能匹配 "Bob" 中的 'o',但是能匹配 "food" 中的两个 o。 
  
 {n,} n 是一个非负整数。至少匹配 n 次。例如,'o{2,}' 不能匹配 "Bob" 中的 'o',但能匹配 "foooood" 中的所有 o。'o{1,}' 等价于 'o+'。'o{0,}' 则等价于 'o*'。 
  
 {n,m} m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,"o{1,3}" 将匹配 "fooooood" 中的前三个 o。'o{0,1}' 等价于 'o?'。请注意在逗号和两个数之间不能有空格。 
  
 除了元字符之外,用户还可以精确指定模式在匹配对象中出现的频率。例如,/jim {2,6}/ 上述正则表达式规定字符m可以在匹配对象中连续出现2-6次,因此,上述正则表达式可以同jimmy或jimmmmmy等字符串相匹配。  
 在对如何使用正则表达式有了初步了解之后,我们来看一下其它几个重要的元字符的使用方式。  
 <pre name="code" class="java">\s:用于匹配单个空格符,包括tab键和换行符;  
 \S:用于匹配除单个空格符之外的所有字符;  
 \d:用于匹配从0到9的数字;  
 \w:用于匹配字母,数字或下划线字符;  
 \W:用于匹配所有与\w不匹配的字符;  
 . :用于匹配除换行符之外的所有字符。  
 </pre> 
 (说明:我们可以把\s和\S以及\w和\W看作互为逆运算)  
 下面,我们就通过实例看一下如何在正则表达式中使用上述元字符。  
 /\s+/ 上述正则表达式可以用于匹配目标对象中的一个或多个空格字符。  
 /\d000/ 如果我们手中有一份复杂的财务报表,那么我们可以通过上述正则表达式轻而易举的查找到所有总额达千元的款项。  
 除了我们以上所介绍的元字符之外,正则表达式中还具有另外一种较为独特的专用字符,即定位符。定位符用于规定匹配模式在目标对象中的出现位置。 较为常用的定位符包括: “^”, “$”, “\b” 以及 “\B”。 
 <pre name="code" class="java">“^”定位符规定匹配模式必须出现在目标字符串的开头 
 “$”定位符规定匹配模式必须出现在目标对象的结尾 
 “\b”定位符规定匹配模式必须出现在目标字符串的开头或结尾的两个边界之一 
 “\B”定位符则规定匹配对象必须位于目标字符串的开头和结尾两个边界之内, 
       即匹配对象既不能作为目标字符串的开头,也不能作为目标字符串的结尾。 
 </pre> 
  同样,我们也可以把“^”和“$”以及“\b”和“\B”看作是互为逆运算的两组定位符。举例来说: /^hell/ 因为上述正则表达式中包含“^”定位符,所以可以与目标对象中以 “hell”, “hello”或“hellhound”开头的字符串相匹配。 /ar$/ 因为上述正则表达式中包含 “$”定位符,所以可以与目标对象中以 “car”, “bar”或 “ar” 结尾的字符串相匹配。 /\bbom/ 因为上述正则表达式模式以 “\b”定位符开头,所以可以与目标对象中以 “bomb”, 或 “bom”开头的字符串相匹配。/man\b/ 因为上述正则表达式模式以“\b”定位符结尾,所以可以与目标对象中以 “human”, “woman”或 “man”结尾的字符串相匹配。  
 为了能够方便用户更加灵活的设定匹配模式,正则表达式允许使用者在匹配模式中指定某一个范围而不局限于具体的字符。例如:  
 <pre name="code" class="java">/[A-Z]/  上述正则表达式将会与从A到Z范围内任何一个大写字母相匹配。 
 /[a-z]/  上述正则表达式将会与从a到z范围内任何一个小写字母相匹配。  
 /[0-9]/  上述正则表达式将会与从0到9范围内任何一个数字相匹配。  
 /([a-z][A-Z][0-9])+/ 上述正则表达式将会与任何由字母和数字组成的字符串,如 “aB0” 等相匹配。 
 </pre> 
  这里需要提醒用户注意的一点就是可以在正则表达式中使用 “()” 把字符串组合在一起。“()”符号包含的内容必须同时出现在目标对象中。因此,上述正则表达式将无法与诸如 “abc”等的字符串匹配,因为“abc”中的最后一个字符为字母而非数字。  
 如果我们希望在正则表达式中实现类似编程逻辑中的“或”运算,在多个不同的模式中任选一个进行匹配的话,可以使用管道符 “|”。例如:/to|too|2/ 上述正则表达式将会与目标对象中的 “to”, “too”, 或 “2” 相匹配。 
  正则表达式中还有一个较为常用的运算符,即否定符 “[^]”。与我们前文所介绍的定位符 “^” 不同,否定符 “[^]”规定目标对象中不能存在模式中所规定的字符串。例如:/[^A-C]/ 上述字符串将会与目标对象中除A,B,和C之外的任何字符相匹配。一般来说,当“^”出现在 “[]”内时就被视做否定运算符;而当“^”位于“[]”之外,或没有“[]”时,则应当被视做定位符。 
 最后,当用户需要在正则表达式的模式中加入元字符,并查找其匹配对象时,可以使用转义符“\”。例如:/Th\*/  上述正则表达式将会与目标对象中的“Th*”而非“The”等相匹配。 
 在构造正则表达式之后,就可以象数学表达式一样来求值,也就是说,可以从左至右并按照一个优先级顺序来求值。优先级如下: 
 <pre name="code" class="java">1.\ 转义符 
 2.(), (?:), (?=), [] 圆括号和方括号 
 3.*, +, ?, {n}, {n,}, {n,m} 限定符 
 4.^, $, \anymetacharacter 位置和顺序 
 5.|“或”操作 
 </pre> 
  
 <span style="font-size: 18pt;">使用实例</span>  
 在JavaScript 1.2中带有一个功能强大的RegExp()对象,可以用来进行正则表达式的匹配操作。其中的test()方法可以检验目标对象中是否包含匹配模式,并相应的返回true或false。 
 我们可以使用JavaScript编写以下脚本,验证用户输入的邮件地址的有效性。 
 <pre name="code" class="java"><html>  
 <head>  
   <script language="Javascript1.2">  
      <!-- start hiding  
      function verifyAddress(obj)  
      {  
       var email = obj.email.value;  
       var pattern =  
 /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+(\.[a-zA-Z0-9_-])+/;  
       flag = pattern.test(email);  
       if(flag)  
       {  
        alert(“Your email address is correct!”);  
        return true;  
       }  
       else  
        {  
         alert(“Please try again!”);  
         return false;  
         }  
       }  
      // stop hiding -->  
     </script>  
   </head>  
   <body>  
    <form onSubmit="return verifyAddress(this);">  
     <input name="email" type="text">  
     <input type="submit">  
     </form>  
   </body>  
 </html>  
 </pre> 
  
 <span style="font-size: 18pt;">正则表达式对象</span> 
 本对象包含正则表达式模式以及表明如何应用模式的标志。 
 <pre name="code" class="java">语法 1 re = /pattern/[flags] 
 语法 2 re = new RegExp("pattern",["flags"])  
 </pre> 
 参数 
 re 
 必选项。将要赋值为正则表达式模式的变量名。  
  
 Pattern 
 必选项。要使用的正则表达式模式。如果使用语法 1,用 "/" 字符分隔模式。如果用语法 2,用引号将模式引起来。  
  
 Flags  
 可选项。如果使用语法 2 要用引号将 flag 引起来。标志可以组合使用,可用的有:  
 <pre name="code" class="java">g (全文查找出现的所有 pattern)  
 i (忽略大小写)  
 m (多行查找)  
 </pre> 
  
 <span style="font-size: 18pt;">示例</span> 
 下面的示例创建一个包含正则表达式模式及相关标志的对象(re),向您演示正则表达式对象的用法。在本例中,作为结果的正则表达式对象又用于 match 方法中: 
 <pre name="code" class="java">function MatchDemo() 
 { 
 var r, re; // 声明变量。 
 var s = "The rain in Spain falls mainly in the plain"; 
 re = new RegExp("ain","g"); // 创建正则表达式对象。 
 r = s.match(re); // 在字符串 s 中查找匹配。 
 return(r);  
 } 
 </pre> 
  
 返回值: ain,ain,ain,ain\\ 
 属性 lastIndex 属性 | source 属性\\ 
 方法 compile 方法 | exec 方法 | test 方法\\ 
 要求 版本 3\\ 
 请参阅 RegExp 对象 | 正则表达式语法 | String 对象\\ 
  
 <span style="font-size: 18pt;">exec 方法</span> 
 用正则表达式模式在字符串中运行查找,并返回包含该查找结果的一个数组。 
 rgExp.exec(str) 
  
 参数 
  
 rgExp  
 必选项。包含正则表达式模式和可用标志的正则表达式对象。  
  
 str  
 必选项。要在其中执行查找的 String 对象或字符串文字。  
  
 说明\\ 
 如果 exec 方法没有找到匹配,则它返回 null。如果它找到匹配,则 exec 方法返回一个数组,并且更新全局 RegExp 对象的属性,以反映匹配结果。数组的0元素包含了完整的匹配,而第1到n元素中包含的是匹配中出现的任意一个子匹配。这相当于没有设置全局标志 (g) 的 match 方法。 
 如果为正则表达式设置了全局标志,exec 从以 lastIndex 的值指示的位置开始查找。如果没有设置全局标志,exec 忽略 lastIndex 的值,从字符串的起始位置开始搜索。 
  
 exec 方法返回的数组有三个属性,分别是 input、index 和 lastIndex。Input 属性包含了整个被查找的字符串。Index 属性中包含了整个被查找字符串中被匹配的子字符串的位置。LastIndex 属性中包含了匹配中最后一个字符的下一个位置。 
  
 示例\\ 
 下面的例子举例说明了 exec 方法的用法: 
 <pre name="code" class="java">function RegExpTest() 
 { 
 var ver = Number(ScriptEngineMajorVersion() + "." + ScriptEngineMinorVersion()) 
 if (ver >= 5.5){ // 测试 JScript 的版本。 
 var src = "The rain in Spain falls mainly in the plain."; 
 var re = /\w+/g; // 创建正则表达式模式。 
 var arr; 
 while ((arr = re.exec(src)) != null) 
 document.write(arr.index + "-" + arr.lastIndex + arr + "\t"); 
 } 
 else{ 
 alert("请使用 JScript 的更新版本"); 
 } 
 } 
 </pre> 
  
 返回值:0-3The 4-8rain 9-11in 12-17Spain 18-23falls 24-30mainly 31-33in 34-37the 38-43plain 
  
 test 方法\\ 
 返回一个 Boolean 值,它指出在被查找的字符串中是否存在模式。 
 rgexp.test(str)  
  
 参数\\ 
 rgexp 
 必选项。包含正则表达式模式或可用标志的正则表达式对象。  
  
 str  
 必选项。要在其上测试查找的字符串。  
  
 说明 
 test 方法检查在字符串中是否存在一个模式,如果存在则返回 true,否则就返回 false。 
 全局 RegExp 对象的属性不由 test 方法来修改。 
  
 示例 
 下面的例子举例说明了 test 方法的用法: 
 <pre name="code" class="java">function TestDemo(re, s) 
 { 
 var s1; // 声明变量。 
 // 检查字符串是否存在正则表达式。 
 if (re.test(s)) // 测试是否存在。 
 s1 = " contains "; // s 包含模式。 
 else 
 s1 = " does not contain "; // s 不包含模式。 
 return("'" + s + "'" + s1 + "'"+ re.source + "'"); // 返回字符串。 
 } 
 </pre> 
  
 函数调用:document.write (TestDemo(/ain+/ ,"The rain in Spain falls mainly in the plain.")); 
  
 返回值:'The rain in Spain falls mainly in the plain.' contains 'ain+' 
  
 <span style="font-size: 18pt;">match 方法</span> 
 使用正则表达式模式对字符串执行查找,并将包含查找的结果作为数组返回。\\ 
 stringObj.match(rgExp)  
  
 参数\\ 
 stringObj  
 必选项。对其进行查找的 String 对象或字符串文字。  
  
 rgExp  
 必选项。为包含正则表达式模式和可用标志的正则表达式对象。也可以是包含正则表达式模式和可用标志的变量名或字符串文字。  
  
 说明\\ 
 如果 match 方法没有找到匹配,返回 null。如果找到匹配返回一个数组并且更新全局 RegExp 对象的属性以反映匹配结果。 
 match 方法返回的数组有三个属性:input、index 和 lastIndex。Input 属性包含整个的被查找字符串。Index 属性包含了在整个被查找字符串中匹配的子字符串的位置。LastIndex 属性包含了最后一次匹配中最后一个字符的下一个位置。 
 如果没有设置全局标志 (g),数组的 0 元素包含整个匹配,而第 1 到 n 元素包含了匹配中曾出现过的任一个子匹配。这相当于没有设置全局标志的 exec 方法。如果设置了全局标志,元素 0 到 n 中包含所有匹配。 
  
 示例\\ 
 下面的示例演示了match 方法的用法: 
 <pre name="code" class="java">function MatchDemo() 
 { 
 var r, re; // 声明变量。 
 var s = "The rain in Spain falls mainly in the plain"; 
 re = /ain/i; // 创建正则表达式模式。 
 r = s.match(re); // 尝试匹配搜索字符串。 
 return(r); // 返回第一次出现 "ain" 的地方。 
 } 
 </pre> 
 返回值:ain 
  
 本示例说明带 g 标志设置的 match 方法的用法。 
 <pre name="code" class="java">function MatchDemo() 
 { 
 var r, re; // 声明变量。 
 var s = "The rain in Spain falls mainly in the plain"; 
 re = /ain/ig; // 创建正则表达式模式。 
 r = s.match(re); // 尝试去匹配搜索字符串。 
 return(r); // 返回的数组包含了所有 "ain"  
 // 出现的四个匹配。 
 } 
 </pre> 
 返回值:ain,ain,ain,ain 
  
 上面几行代码演示了字符串文字的 match 方法的用法。 
 <pre name="code" class="java">var r, re = "Spain"; 
 r = "The rain in Spain".replace(re, "Canada"); 
 return r; 
 </pre> 
 返回值:The rain in Canada 
  
 <span style="font-size: 18pt;">search 方法</span> 
 返回与正则表达式查找内容匹配的第一个子字符串的位置。 
  
 stringObj.search(rgExp) 
  
 参数\\ 
 stringObj  
 必选项。要在其上进行查找的 String 对象或字符串文字。  
  
 rgExp  
 必选项。包含正则表达式模式和可用标志的正则表达式对象。  
  
 说明 
  
 search 方法指明是否存在相应的匹配。如果找到一个匹配,search 方法将返回一个整数值,指明这个匹配距离字符串开始的偏移位置。如果没有找到匹配,则返回 -1。 
  
 示例\\ 
 下面的示例演示了 search 方法的用法。 
 <pre name="code" class="java">function SearchDemo() 
 { 
 var r, re; // 声明变量。 
 var s = "The rain in Spain falls mainly in the plain."; 
 re = /falls/i; // 创建正则表达式模式。 
 r = s.search(re); // 查找字符串。 
 return(r); // 返回 Boolean 结果。 
 } 
 </pre> 
 返回值:18 
  
  
 <span style="font-size: 18pt;">正则表达式语法</span> 
 一个正则表达式就是由普通字符(例如字符 a 到 z)以及特殊字符(称为元字符)组成的文字模式。该模式描述在查找文字主体时待匹配的一个或多个字符串。正则表达式作为一个模板,将某个字符模式与所搜索的字符串进行匹配。 
  
 这里有一些可能会遇到的正则表达式示例: 
 <pre name="code" class="java">JScript VBScript 匹配  
 /^\[ \t]*$/ "^\[ \t]*$" 匹配一个空白行。  
 /\d{2}-\d{5}/ "\d{2}-\d{5}" 验证一个ID 号码是否由一个2位数字,一个连字符以及一个5位数字组成。  
 /<(.*)>.*<\/\1>/ "<(.*)>.*<\/\1>" 匹配一个 HTML 标记。  
 </pre> 
  
 下表是元字符及其在正则表达式上下文中的行为的一个完整列表: 
  
 字符 描述  
 \ 将下一个字符标记为一个特殊字符、或一个原义字符、或一个 后向引用、或一个八进制转义符。例如,'n' 匹配字符 "n"。'\n' 匹配一个换行符。序列 '\\' 匹配 "\" 而 "\(" 则匹配 "("。  
  
 ^ 匹配输入字符串的开始位置。如果设置了 RegExp 对象的 Multiline 属性,^ 也匹配 '\n' 或 '\r' 之后的位置。  
  
 $ 匹配输入字符串的结束位置。如果设置了RegExp 对象的 Multiline 属性,$ 也匹配 '\n' 或 '\r' 之前的位置。  
  
 * 匹配前面的子表达式零次或多次。例如,zo* 能匹配 "z" 以及 "zoo"。 * 等价于{0,}。  
  
 + 匹配前面的子表达式一次或多次。例如,'zo+' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等价于 {1,}。  
  
 ? 匹配前面的子表达式零次或一次。例如,"do(es)?" 可以匹配 "do" 或 "does" 中的"do" 。? 等价于 {0,1}。  
  
 {n} n 是一个非负整数。匹配确定的 n 次。例如,'o{2}' 不能匹配 "Bob" 中的 'o',但是能匹配 "food" 中的两个 o。  
  
 {n,} n 是一个非负整数。至少匹配n 次。例如,'o{2,}' 不能匹配 "Bob" 中的 'o',但能匹配 "foooood" 中的所有 o。'o{1,}' 等价于 'o+'。'o{0,}' 则等价于 'o*'。  
  
 {n,m} m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。刘, "o{1,3}" 将匹配 "fooooood" 中的前三个 o。'o{0,1}' 等价于 'o?'。请注意在逗号和两个数之间不能有空格。  
  
 ? 当该字符紧跟在任何一个其他限制符 (*, +, ?, {n}, {n,}, {n,m}) 后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串 "oooo",'o+?' 将匹配单个 "o",而 'o+' 将匹配所有 'o'。  
  
 . 匹配除 "\n" 之外的任何单个字符。要匹配包括 '\n' 在内的任何字符,请使用象 '[.\n]' 的模式。  
 (pattern) 匹配pattern 并获取这一匹配。所获取的匹配可以从产生的 Matches 集合得到,在VBScript 中使用 SubMatches 集合,在JScript 中则使用 $0…$9 属性。要匹配圆括号字符,请使用 '\(' 或 '\)'。  
  
 (?:pattern) 匹配 pattern 但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用 "或" 字符 (|) 来组合一个模式的各个部分是很有用。例如, 'industr(?:y|ies) 就是一个比 'industry|industries' 更简略的表达式。  
  
 (?=pattern) 正向预查,在任何匹配 pattern 的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如, 'Windows (?=95|98|NT|2000)' 能匹配 "Windows 2000" 中的 "Windows" ,但不能匹配 "Windows 3.1" 中的 "Windows"。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。  
  
  (?!pattern) 负向预查,在任何不匹配 Negative lookahead matches the search string at any point where a string not matching pattern 的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如'Windows (?!95|98|NT|2000)' 能匹配 "Windows 3.1" 中的 "Windows",但不能匹配 "Windows 2000" 中的 "Windows"。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始  
  
 x|y 匹配 x 或 y。例如,'z|food' 能匹配 "z" 或 "food"。'(z|f)ood' 则匹配 "zood" 或 "food"。  
  
 [xyz] 字符集合。匹配所包含的任意一个字符。例如, '[abc]' 可以匹配 "plain" 中的 'a'。  
  
 [^xyz] 负值字符集合。匹配未包含的任意字符。例如, '[^abc]' 可以匹配 "plain" 中的'p'。  
  
 [a-z] 字符范围。匹配指定范围内的任意字符。例如,'[a-z]' 可以匹配 'a' 到 'z' 范围内的任意小写字母字符。  
  
 [^a-z] 负值字符范围。匹配任何不在指定范围内的任意字符。例如,'[^a-z]' 可以匹配任何不在 'a' 到 'z' 范围内的任意字符。  
  
 \b 匹配一个单词边界,也就是指单词和空格间的位置。例如, 'er\b' 可以匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'。  
  
 \B 匹配非单词边界。'er\B' 能匹配 "verb" 中的 'er',但不能匹配 "never" 中的 'er'。  
  
 \cx 匹配由x指明的控制字符。例如, \cM 匹配一个 Control-M 或回车符。 x 的值必须为 A-Z 或 a-z 之一。否则,将 c 视为一个原义的 'c' 字符。  
  
 \d 匹配一个数字字符。等价于 [0-9]。  
  
 \D 匹配一个非数字字符。等价于 [^0-9]。  
  
 \f 匹配一个换页符。等价于 \x0c 和 \cL。  
  
 \n 匹配一个换行符。等价于 \x0a 和 \cJ。  
  
 \r 匹配一个回车符。等价于 \x0d 和 \cM。  
  
 \s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。  
  
 \S 匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。  
  
 \t 匹配一个制表符。等价于 \x09 和 \cI。  
  
 \v 匹配一个垂直制表符。等价于 \x0b 和 \cK。  
  
 \w 匹配包括下划线的任何单词字符。等价于'[A-Za-z0-9_]'。  
  
 \W 匹配任何非单词字符。等价于 '[^A-Za-z0-9_]'。  
  
 \xn 匹配 n,其中 n 为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如, '\x41' 匹配 "A"。'\x041' 则等价于 '\x04' & "1"。正则表达式中可以使用 ASCII 编码。.  
  
 \num 匹配 num,其中 num 是一个正整数。对所获取的匹配的引用。例如,'(.)\1' 匹配两个连续的相同字符。  
  
 \n 标识一个八进制转义值或一个后向引用。如果 \n 之前至少 n 个获取的子表达式,则 n 为后向引用。否则,如果 n 为八进制数字 (0-7),则 n 为一个八进制转义值。  
  
 \nm 标识一个八进制转义值或一个后向引用。如果 \nm 之前至少有is preceded by at least nm 个获取得子表达式,则 nm 为后向引用。如果 \nm 之前至少有 n 个获取,则 n 为一个后跟文字 m 的后向引用。如果前面的条件都不满足,若 n 和 m 均为八进制数字 (0-7),则 \nm 将匹配八进制转义值 nm。  
  
 \nml 如果 n 为八进制数字 (0-3),且 m 和 l 均为八进制数字 (0-7),则匹配八进制转义值 nml。  
  
 \un 匹配 n,其中 n 是一个用四个十六进制数字表示的 Unicode 字符。例如, \u00A9 匹配版权符号 (?)。  
  
  
 <span style="font-size: 18pt;">优先权顺序</span> 
 在构造正则表达式之后,就可以象数学表达式一样来求值,也就是说,可以从左至右并按照一个优先权顺序来求值。  
  
 下表从最高优先级到最低优先级列出各种正则表达式操作符的优先权顺序: 
 <pre name="code" class="java">操作符 描述  
 \ 转义符  
 (), (?:), (?=), [] 圆括号和方括号  
 *, +, ?, {n}, {n,}, {n,m} 限定符  
 ^, $, \anymetacharacter 位置和顺序  
 | “或”操作  
 </pre> 
  
 普通字符 
  
 普通字符由所有那些未显式指定为元字符的打印和非打印字符组成。这包括所有的大写和小写字母字符,所有数字,所有标点符号以及一些符号。  
  
 最简单的正则表达式是一个单独的普通字符,可以匹配所搜索字符串中的该字符本身。例如,单字符模式 'A' 可以匹配所搜索字符串中任何位置出现的字母 'A'。这里有一些单字符正则表达式模式的示例: 
 <pre name="code" class="java">/a/ 
 /7/ 
 /M/ 
 </pre> 
 等价的 VBScript 单字符正则表达式为: 
 <pre name="code" class="java">"a" 
 "7" 
 "M" 
 </pre> 
 可以将多个单字符组合在一起得到一个较大的表达式。例如,下面的 JScript 正则表达式不是别的,就是通过组合单字符表达式 'a'、'7'以及 'M' 所创建出来的一个表达式。  
  
 /a7M/ 
 等价的 VBScript 表达式为: 
  
 "a7M" 
 请注意这里没有连接操作符。所需要做的就是将一个字符放在了另一个字符后面。 
  
  



posted @ 2009-07-06 09:20 二胡 阅读(2009) | 评论 (0)编辑 收藏

向后引用

正则表达式一个最重要的特性就是将匹配成功的模式的某部分进行存储供以后使用这一能力。请回想一下,对一个正则表达式模式或部分模式两边添加圆括号将导致 这部分表达式存储到一个临时缓冲区中。可以使用非捕获元字符 '?:', '?=', or '?!' 来忽略对这部分正则表达式的保存。

所捕获的每个子匹配都按照在正则表达式模式中从左至右所遇到的内容存储。存储子匹配的缓冲区编号从 1 开始,连续编号直至最大 99 个子表达式。每个缓冲区都可以使用 '\n' 访问,其中 n 为一个标识特定缓冲区的一位或两位十进制数。

向后引用一个最简单,最有用的应用是提供了确定文字中连续出现两个相同单词的位置的能力。请看下面的句子:

Is is the cost of of gasoline going up up?
根据所写内容,上面的句子明显存在单词多次重复的问题。如果能有一种方法无需查找每个单词的重复现象就能修改该句子就好了。下面的 JScript 正则表达式使用一个子表达式就可以实现这一功能。

/\b([a-z]+) \1\b/gi
等价的 VBScript 表达式为:

"\b([a-z]+) \1\b"
在这个示例中,子表达式就是圆括号之间的每一项。所捕获的表达式包括一个或多个字母字符,即由 '[a-z]+' 所指定的。该正则表达式的第二部分是对前面所捕获的子匹配的引用,也就是由附加表达式所匹配的第二次出现的单词。'\1'用来指定第一个子匹配。单词边界 元字符确保只检测单独的单词。如果不这样,则诸如 "is issued" 或 "this is" 这样的短语都会被该表达式不正确地识别。

在 JScript 表达式中,正则表达式后面的全局标志 ('g') 表示该表达式将用来在输入字符串中查找尽可能多的匹配。大小写敏感性由表达式结束处的大小写敏感性标记 ('i') 指定。多行标记指定可能出现在换行符的两端的潜在匹配。对 VBScript 而言,在表达式中不能设置各种标记,但必须使用 RegExp 对象的属性来显式设置。

使用上面所示的正则表达式,下面的 JScript 代码可以使用子匹配信息,在一个文字字符串中将连续出现两次的相同单词替换为一个相同的单词:

var ss = "Is is the cost of of gasoline going up up?.\n";
var re = /\b([a-z]+) \1\b/gim;       // 创建正则表达式样式。
var rv = ss.replace(re,"$1");   // 用一个单词替代两个单词。
posted @ 2009-07-02 09:21 二胡 阅读(168) | 评论 (0)编辑 收藏

转 http://topic.csdn.net/u/20090523/23/6F9DA96F-04CE-4403-A7B8-AEAB3B44DC06.html

告诉你什么是“五险一金”,花五分钟读一下不吃亏的

“五险”讲的是五种保险,包括养老保险、医疗保险、失业保险、工伤保险和生育保险;

“一金”指的是住房公积金。

其中养老保险、医疗保险和失业保险,这三种险是由企业和个人共同缴纳的保费,工伤保险和生育保险完全是由企业承担的。个人不需要缴纳。这里要注意的是“五险”是法定的,而“一金”不是法定的。

“五险一金”的缴费比例是什么?

目前北京养老保险缴费比例:单位20%(其中17%划入统筹基金,3%划入个人帐户),个人8%(全部划入个人帐户);

医疗保险缴费比例:单位10%,个人2%+3元;

失业保险缴费比例:单位1.5%,个人0.5%;

工伤保险根据单位被划分的行业范围来确定它的工伤费率;

生育保险缴费比例:单位0.8%,个人不交钱。非工作所在地户口的没有这个,所以非工作所在地户口的最多只有“四险一金”。



公积金缴费比例:根据企业的实际情况,选择住房公积金缴费比例。但原则上最高缴费额不得超过北京市职工平均工资300%的10%。

(统筹基金即:在养老保险制度从国家—单位制逐渐向国家—社会制转变的过程中需要国家统筹,以解决经济发展不平衡及人口老龄化等问题。

(1)以企业缴费为主建立社会统筹基金;(2)由职工和企业缴费为主建立个人帐户;(3)政府负担养老保险基金的管理费用。这种社会统筹和个人帐户相 结合的半基金制有利于应付中国人口老龄化危机,逐渐分散旧制度到新制度的转轨成本,逐步实现由企业养老保险制度到个人养老保险制度的转变。)

四险一金的缴纳额度每个地区的规定都不同,基数是以工资总额为基数。有的企业在发放时有基本工资,有相关一些补贴,但有的企业在缴纳时,只是基本工资,这是违反法律规定的。具体比例要向当地的劳动部门去咨询。

关于养老保险、失业保险和医疗保险的支取,是在法定允许的情况下才可以领取,是由设保登记部门来发放,比如“养老保险,要达到法定的年龄才可以,失业 保险金的领取也是要具备条件,比如你到户口所在地的街道办事处办理失业证明,同时又办了求职证,就是指你失业以后还必须有求职的意愿,这样的条件才可以领 取。

如果失业之后你不想工作,那么就不能给你发保险金。另外,养老金和失业金是不能同时享受的。

•试用期内是否享有保险?

在试用期内也应该有享受保险,因为试用期是合同期的一个组成部分,它不是隔离在合同期之外的。所以在试用期内也应该上保险。另外,企业给员工上保险是 一个法定的义务,不取决于当事人的意思或自愿与否,即使员工表示不需要交保险也不行,而且商业保险不能替代社会保险。

养老保险的享受待遇:累计缴纳养老保险15年以上,并达到法定退休年龄,可以享受养老保险待遇:

1、按月领取按规定计发的基本养老金,直至死亡。

基本养老金的计算公式如下:

基本养老金=基础养老金+个人账户养老金+过渡性养老金=退休前一年全市职工月平均工资×20%(缴费年限不满15年的按15%)+个人账户本息和÷120+指数化月平均缴费工资×1997年底前缴费年限×1.4%。

2、死亡待遇。(1)丧葬费(2)一次性抚恤费(3)符合供养条件的直系亲属生活困难补助费,按月发放,直至供养直系亲属死亡。

注意:养老保险应尽量连续缴纳,根据有关文件规定,凡企业或被保险人间断缴纳基本养老保险费的(失业人员领取失业保险金期间或按有关规定不缴费的人员 除外),被保险人符合国家规定的养老条件,计算基本养老金时,其基础性养老金的计算基数,按累计间断的缴费时间逐年前推至相应年度上一年的本市职工平均工 资计算(累计间断的缴费时间,按每满12个月为一个间断缴费年度计算,不满12个月不计算)

举例来说吧:

如果你2020年退休,正常你的基础养老金是2019年的社会平均工资×20%,但是如果你在退休之前养老保险中断了30个月,就是中断了2.5年,按2年算,你的基础养老金就是2017年社会平均工资×20%

医疗保险的享受待遇

1、门、急诊医疗费用

在职职工年度内(1月1日-12月31日)符合基本医疗保险规定范围的医疗费累计超过2000元以上部分;

2、结算比例:

合同期内派遣人员2000元以上部分报销50%,个人自付50%;

在一个年度内累计支付派遣人员门、急诊报销最高数额为2万元。

3、参保人员要妥善保管好在定点医院就诊的门诊医疗单据(含大额以下部分的收据、处方底方等),作为医疗费用报销凭证;

4、三种特殊病的门诊就医:参保人员患恶性肿瘤放射治疗和化学治疗、肾透析、肾移植后服抗排异药需在门诊就医时,由参保人就医的二、三级定点医院开 据"疾病诊断证明",并填写《北京市医疗保险特殊病种申报审批表》,报区医保中心审批备案。这三种特殊病的门诊就医及取药仅限在批准就诊的定点医院,不能 到定点零售药店购买。发生的医疗费符合门诊特殊病规定范围的,参照住院进行结算;

5、住院医疗

●住院押金:符合住院条件的参保人员,在收入住院时,医院收取参保人员部分押金,押金数额由医院根据病情按比例确定。如被派遣人员单位和参保人员未能按时足额缴纳医疗保险费的,住院押金由派遣人员个人全额垫付;

●结算周期:参保人员住院治疗每90天为一个结算周期:不超过90天的,每次住院为一个结算周期;

●恶性肿瘤患者门诊放射治疗和化学治疗、肾透析、肾移植后服抗排异药、患有精神病需常年住院的患者其发生的医疗费用每360天为一个结算周期;

●参保人员在定点的社区卫生服务中心(站)的家庭病床治疗发生的医疗费用,每90天为一个结算周期;

●参保人员出院或阶段治疗结束时,需由派遣人员个人先与医院结清应由派遣人员个人自费和自付的费用,应由基本医疗保险统筹基金和大额医疗互助资金支付的医疗费用,由医院向医保中心申报审核、结算;

●参保人员住院治疗,符合基本医疗保险规定范围的医疗费的结算,设定基本医疗统筹基金支付起付线和最高支付额;

●起付线第一次住院为1300元,以后住院为650元,最高支付限额为5万元;超过最高支付上限的(不含起付标准以下以及派遣人员个人负担部分)大额 医疗费用互助资金支付70%,派遣人员个人负担30%。在一个年度内最高支付10万元。住院费用的结算标准,在一个结算周期内按医院等级和费用数额采取分 段计算、累加支付的办法。

(各项比例有调整时,按新的标准执行)

注意啊:非因公交通事故,医保是免责的!

失业保险享受待遇

失业保险连续缴纳一年以上,档案退回街道后。可以在街道享受失业保险待遇。

1.失业保险金:是指失业保险经办机构按规定支付给符合条件的失业人员的基本生活费用,它是最主要的失业保险待遇。失业保险待遇根据北京市相关文件执行;北京大约是100左右。

2.领取失业保险金期间的医疗补助金:是指支付给失业人员领取失业保险金期间发生的医疗费用的补助。根据北京市有关政策法规执行;

3.领取失业保险金期间死亡的失业人员的丧葬补助金和其供养的配偶、直系亲属的抚恤金按有关规定执行。

工伤保险享受待遇

在合同期内不幸发生意外,需向企业索取情况说明,并加盖企业公章,尽快(最好在三个工作日内)申请工伤认定并需提供下列材料:

1、初次治疗诊断书或住院病历;

2、职业病诊断证明(原件、复印件各一份);

3、交通事故需提供交通大队的事故裁决书或交通部门的交通事故证明;

4、身份证复印件;

5、有效期内的劳动合同原件

生育保险享受待遇

可以报销与生育有关费用

报销范围包括,生育津贴、生育医疗费用、计划生育手术医疗费用、国家和本市规定的其他与生育有关的费用。

生育津贴按照女职工本人生育当月的缴费基数除以30再乘以产假天数计算。生育津贴为女职工产假期间的工资,生育津贴低于本人工资标准的,差额部分由企业补足



生育医疗费用包括女职工因怀孕、生育发生的医疗检查费、接生费、手术费、住院费和药品费。

计划生育手术医疗费用包括职工因计划生育发生的医疗费用。

现在要求,医保缴够20年,养老交够15年才有资格领养老金和享受退休后的医保报销

然后还有一点在网上查不出来的东西

首先如果去非户口所在地工作比如原户口在北京而去深圳工作无论户口是否转过去深圳万一将来不在深圳工作了那么在深圳交的五险一金能否转出来就很成问题 因为现在全国五险一金这个东西没有联网只有北京市内的联网了就是说如果你本来在北京的一家单位工作几年后跳到另外的一个北京单位工作这样五险一金是没有问 题的但是从其他省市跳的话转的时候会很麻烦能不能转还成问题!

关于前面提到的五险中的三险是企业和个人共同交纳意思是这样比如对于养老保险比如你的工资是2000那么你个人要交纳8%单位给交纳15%(以上数据 都是假设),这些钱都存到你的一个帐户里等到你满足了可以享受养老保险的时候才可以支取(具体条件见前面)然后考虑到上面一段写的内容万一要是转不了的话 那么你的钱就白交了还取不出来!(fuck!我自己说的。)

所有企业和个人共同交纳的部分都是这样的(shit--也是我说的。)住房公积金也是这样比如按照工资乘以比例假如你每个月要交500单位给你交 1000如果你跳槽了这些钱又不能转那么你每个月500就白交了也不能取出来——因为你不符合享受住房公积金的条件,只有到买房子的时候才能取出来

那么有人可能会问了那我如不打算在刚开始工作的城市发展一辈子为了防止将来转不出来五险一金我能不能不交纳?答案是不能至少三险那部分是国家强制必须 上的所以比较无奈。另外关于五险一金上缴的基数就是拿你哪部分工资乘以那个百分比的问题是这样的首先来看一下工资的构成基本工资+各项福利+奖金很多企业 都是只按照基本工资上五险一金的因为如果按照三部分来算的话你个人上的多一些但是单位给你上的更多(企业和个人共同交纳的五险一金都是个人交小头单位交大 头)所以很多企业为了给自己省钱基数都是能少则少另外对于福利部分有很大猫腻这个大家将来签合同的时候一定要问!!!

前面说过住房公积金也是企业和个人共同交纳的但是企业给你交的那部分早晚也是你的(只要你将来买房的话是可以一次性取出来的)比如说你每个月按照比例 交500单位可能给你贴1000虽然在你买房子之前这1500是取不出来的但理论上说那1000也是给你的那么有的企业就会把那1000算做给你的福利其 实不应该算的!!!!因为那1000是虚的是你在不买房子的时候就取不出来的所以大家在签的时候要问清楚这福利里面有没有包括住房公积金

小心啊小心!

如果觉得好一定要分享。。。独乐乐不如人人乐。。。
文章摘自 漂泊者 原文地址:http://www.piaobozhe.com/read.php?tid=3915
posted @ 2009-06-24 13:06 二胡 阅读(211) | 评论 (0)编辑 收藏

转 http://hi.baidu.com/buzhizhe/blog/item/0031c8178664420dc83d6d72.html

select 字段1 from 表1 where 字段1.IndexOf("云")=1;
这条语句不对的原因是indexof()函数不是sql函数,改成sql对应的函数就可以了。
left()是sql函数。
select 字段1 from 表1 where charindex('云',字段1)=1;

字符串函数对二进制数据、字符串和表达式执行不同的运算。此类函数作用于CHAR、VARCHAR、 BINARY、 和VARBINARY 数据类型以及可以隐式转换为CHAR 或VARCHAR的数据类型。可以在SELECT 语句的SELECT 和WHERE 子句以及表达式中使用字符串函数。
常用的字符串函数有:

一、字符转换函数
1、ASCII()
返回字符表达式最左端字符的ASCII 码值。在ASCII()函数中,纯数字的字符串可不用‘’括起来,但含其它字符的字符串必须用‘’括起来使用,否则会出错。
2、CHAR()
将ASCII 码转换为字符。如果没有输入0 ~ 255 之间的ASCII 码值,CHAR() 返回NULL 。
3、LOWER()和UPPER()
LOWER()将字符串全部转为小写;UPPER()将字符串全部转为大写。
4、STR()
把数值型数据转换为字符型数据。
STR (<float_expression>[,length[, <decimal>]])
length 指定返回的字符串的长度,decimal 指定返回的小数位数。如果没有指定长度,缺省的length 值为10, decimal 缺省值为0。
当length 或者decimal 为负值时,返回NULL;
当length 小于小数点左边(包括符号位)的位数时,返回length 个*;
先服从length ,再取decimal ;
当返回的字符串位数小于length ,左边补足空格。
二、去空格函数
1、LTRIM() 把字符串头部的空格去掉。

2、RTRIM() 把字符串尾部的空格去掉。

三、取子串函数
1、left()
LEFT (<character_expression>, <integer_expression>)
返回character_expression 左起 integer_expression 个字符。

2、RIGHT()
RIGHT (<character_expression>, <integer_expression>)
返回character_expression 右起 integer_expression 个字符。

3、SUBSTRING()
SUBSTRING (<expression>, <starting_ position>, length)
返回从字符串左边第starting_ position 个字符起length个字符的部分。

四、字符串比较函数
1、CHARINDEX()
返回字符串中某个指定的子串出现的开始位置。
CHARINDEX (<’substring_expression’>, <expression>)
其中substring _expression 是所要查找的字符表达式,expression 可为字符串也可为列名表达式。如果没有发现子串,则返回0 值。
此函数不能用于TEXT 和IMAGE 数据类型。
2、PATINDEX()
返回字符串中某个指定的子串出现的开始位置。
PATINDEX (<’%substring _expression%’>, <column_ name>)其中子串表达式前后必须有百分号“%”否则返回值为0。
与CHARINDEX 函数不同的是,PATINDEX函数的子串中可以使用通配符,且此函数可用于CHAR、 VARCHAR 和TEXT 数据类型。

五、字符串操作函数
1、QUOTENAME()
返回被特定字符括起来的字符串。
QUOTENAME (<’character_expression’>[, quote_ character]) 其中quote_ character 标明括字符串所用的字符,缺省值为“[]”。
2、REPLICATE()
返回一个重复character_expression 指定次数的字符串。
REPLICATE (character_expression integer_expression) 如果integer_expression 值为负值,则返回NULL 。

3、REVERSE()
将指定的字符串的字符排列顺序颠倒。
REVERSE (<character_expression>) 其中character_expression 可以是字符串、常数或一个列的值。

4、REPLACE()
返回被替换了指定子串的字符串。
REPLACE (<string_expression1>, <string_expression2>, <string_expression3>) 用string_expression3 替换在string_expression1 中的子串string_expression2。

4、SPACE()
返回一个有指定长度的空白字符串。
SPACE (<integer_expression>) 如果integer_expression 值为负值,则返回NULL 。

5、STUFF()
用另一子串替换字符串指定位置、长度的子串。
STUFF (<character_expression1>, <start_ position>, <length>,<character_expression2>)
如果起始位置为负或长度值为负,或者起始位置大于character_expression1 的长度,则返回NULL 值。
如果length 长度大于character_expression1 中 start_ position 以右的长度,则character_expression1 只保留首字符。
六、数据类型转换函数
1、CAST()
CAST (<expression> AS <data_ type>[ length ])

2、CONVERT()
CONVERT (<data_ type>[ length ], <expression> [, style])

1)data_type为SQL Server系统定义的数据类型,用户自定义的数据类型不能在此使用。
2)length用于指定数据的长度,缺省值为30。
3)把CHAR或VARCHAR类型转换为诸如INT或SAMLLINT这样的INTEGER类型、结果必须是带正号或负号的数值。
4)TEXT类型到CHAR或VARCHAR类型转换最多为8000个字符,即CHAR或VARCHAR数据类型是最大长度。
5)IMAGE类型存储的数据转换到BINARY或VARBINARY类型,最多为8000个字符。
6)把整数值转换为MONEY或SMALLMONEY类型,按定义的国家的货币单位来处理,如人民币、美元、英镑等。
7)BIT类型的转换把非零值转换为1,并仍以BIT类型存储。
8)试图转换到不同长度的数据类型,会截短转换值并在转换值后显示“+”,以标识发生了这种截断。
9)用CONVERT()函数的style 选项能以不同的格式显示日期和时间。style 是将DATATIME 和SMALLDATETIME 数据转换为字符串时所选用的由SQL Server 系统提供的转换样式编号,不同的样式编号有不同的输出格式。
七、日期函数
1、day(date_expression)
返回date_expression中的日期值

2、month(date_expression)
返回date_expression中的月份值

3、year(date_expression)
返回date_expression中的年份值

4、DATEADD()
DATEADD (<datepart>, <number>, <date>)
返回指定日期date 加上指定的额外日期间隔number 产生的新日期。
5、DATEDIFF()
DATEDIFF (<datepart>, <date1>, <date2>)
返回两个指定日期在datepart 方面的不同之处,即date2 超过date1的差距值,其结果值是一个带有正负号的整数值。

6、DATENAME()
DATENAME (<datepart>, <date>)
以字符串的形式返回日期的指定部分此部分。由datepart 来指定。

7、DATEPART()
DATEPART (<datepart>, <date>)
以整数值的形式返回日期的指定部分。此部分由datepart 来指定。
DATEPART (dd, date) 等同于DAY (date)
DATEPART (mm, date) 等同于MONTH (date)
DATEPART (yy, date) 等同于YEAR (date)

8、GETDATE()
以DATETIME 的缺省格式返回系统当前的日期和时间。

posted @ 2009-06-23 09:06 二胡 阅读(1220) | 评论 (0)编辑 收藏

 转 http://www.cnblogs.com/coconut_zhang/archive/2009/02/02/1382598.html

SQLServer时间日期函数详解,SQLServer,时间日期,

1.      当前系统日期、时间

        select getdate()

2. dateadd      在向指定日期加上一段时间的基础上,返回新的 datetime 值

       例如:向日期加上2天

       select dateadd(day,2,'2004-10-15')      --返回:2004-10-17 00:00:00.000

3. datediff 返回跨两个指定日期的日期和时间边界数。

       select datediff(day,'2004-09-01','2004-09-18')       --返回:17

       select datediff(day,'2004-09-18','2004-09-01')       --返回:-17

4. datepart 返回代表指定日期的指定日期部分的整数。

      SELECT DATEPART(month, '2004-10-15')      --返回 10

5. datename 返回代表指定日期的指定日期部分的字符串

       SELECT datename(weekday, '2004-10-15')      --返回:星期五

6. day(), month(),year() --可以与datepart对照一下

select 当前日期=convert(varchar(10),getdate(),120)

,当前时间=convert(varchar(8),getdate(),114)

select datename(dw,'2004-10-15')

select 本年第多少周=datename(week,'2004-10-15')

          ,今天是周几=datename(weekday,'2004-10-15')

函数 参数/功能

GetDate( )   返回系统目前的日期与时间

DateDiff (interval,date1,date2) 以interval 指定的方式,返回date2 与date1两个日期之间的差值

date2-date1

DateAdd (interval,number,date) 以interval指定的方式,加上number之后的日期

DatePart (interval,date) 返回日期date中,interval指定部分所对应的整数值

DateName (interval,date) 返回日期date中,interval指定部分所对应的字符串名称

参数 interval的设定值如下:

值 缩 写(Sql Server) (Access 和 ASP) 说明

Year Yy yyyy 年 1753 ~ 9999

Quarter Qq q    季 1 ~ 4

Month Mm m    月1 ~ 12

Day of year Dy y   一年的日数,一年中的第几日 1-366

Day Dd d    日,1-31

Weekday Dw w 一周的日数,一周中的第几日 1-7

Week Wk ww   周,一年中的第几周 0 ~ 51

Hour Hh h    时0 ~ 23

Minute Mi n   分钟0 ~ 59

Second Ss s 秒 0 ~ 59

Millisecond Ms - 毫秒 0 ~ 999

access 和 asp 中用date()和now()取得系统日期时间;其中DateDiff,DateAdd,DatePart也同是能用于

Access和asp中,这些函数的用法也类似

举例:

1.GetDate() 用于sql server :select GetDate()

2.DateDiff('s','2005-07-20','2005-7-25 22:56:32')返回值为 514592 秒

DateDiff('d','2005-07-20','2005-7-25 22:56:32')返回值为 5 天

3.DatePart('w','2005-7-25 22:56:32')返回值为 2 即星期一(周日为1,周六为7)

DatePart('d','2005-7-25 22:56:32')返回值为 25即25号

DatePart('y','2005-7-25 22:56:32')返回值为 206即这一年中第206天

DatePart('yyyy','2005-7-25 22:56:32')返回值为 2005即2005年

具体的语法:

日期函数用来操作DATETIME 和SMALLDATETIME 类型的数据,执行算术运算。与其它函数一样,可以在

Select 语句的Select 和Where 子句以及表达式中使用日期函数。其使用方法如下:

日期函数参数,其中参数个数应不同的函数而不同。

·DAY()

DAY() 函数语法如下:

DAY (<date_expression>)

DAY() 函数返回date_expression 中的日期值。

·MONTH()

MONTH() 函数语法如下:

MONTH (<date_expression>)

MONTH() 函数返回date_expression 中的月份值。

与DAY() 函数不同的是,MONTH() 函数的参数为整数时,一律返回整数值1,即SQL Server 认为其

是1900 年1 月。

·YEAR()

YEAR() 函数语法如下:

YEAR (<date_expression>)

YEAR() 函数返回date_expression 中的年份值。

提醒:在使用日期函数时,其日期值应在1753年到9999年之间,这是SQL Server系统所能识别的日期范

围,否则会出现错误。

·DATEADD()

DATEADD() 函数语法如下:

DATEADD (<datepart>, <number>, <date>)

DATEADD() 函数返回指定日期date 加上指定的额外日期间隔number 产生的新日期。参数“datepart

” 在日期函数中经常被使用,它用来指定构成日期类型数据的各组件,如年、季、月、日、星期等。

其取值如表4-9 所示:

·DATEDIFF()

DATEDIFF() 函数语法如下:

DATEDIFF() (<datepart>, <date1>, <date2>)

DATEDIFF() 函数返回两个指定日期在datepart 方面的不同之处,即date2 超过date1的差距值,其

结果值是一个带有正负号的整数值。针对不同的datepart, DATEDIFF()函数所允许的最大差距值不

一样,如:datepart 为second 时,DATEDIFF() 函数所允许的最大差距值为68: 年datepart 为

millisecond 时,DATEDIFF() 函数所允许的最大差距值为24 天20 小时30 分23 秒647 毫秒。

·DATENAME()

DATENAME() 函数语法如下:

DATENAME (<datepart>, <date)>

DATENAME() 函数以字符串的形式返回日期的指定部分此部分。由datepart 来指定。

·DATEPART()

DATEPART() 函数语法如下:

DATEPART (<datepart>, <date>)

DATEPART() 函数以整数值的形式返回日期的指定部分。此部分由datepart 来指定。

DATEPART (dd, date) 等同于DAY (date)

DATEPART (mm, date) 等同于MONTH (date)

DATEPART (yy, date) 等同于YEAR (date)

·GETDATE()

GETDATE() 函数语法如下:

GETDATE()

GETDATE() 函数以DATETIME 的缺省格式返回系统当前的日期和时间,它常作为其它函数或命令的参

数使用。

在开发数据库应用中,经常会遇到处理时间的问题,如查询指定时间的记录等。下面就这些常见的问题

,结合自己的一些经验,和大家探讨一下这类问题。

  首先介绍一下,SQL Server里处理时间的几个主要函数的用法:

getdate()函数:取得系统当前的日期和时间。返回值为datetime类型的。

用法:getdate()

例子:

select getdate() as dte,dateadd(day,-1,getdate()) as nowdat

输出结果:

dte nowdat

1999-11-21 19:13:10.083 1999-11-20 19:13:10.083

(1 row(s) affected)

datepart()函数:以整数的形式返回时间的指定部分。

用法:datepart(datepart,date)

参数说明:datepart时要返回的时间的部分,常用取值year、month、day、hour、minute。

date是所指定的时间。

例子:

SELECT DATEPART(month, GETDATE()) AS 'Month Number'

输出结果:

Month Number

11

(1 row(s) affected)

dateadd()函数:通过给指定的时间的指定部分加上一个整数值以返回一个新时间值。

用法:dateadd(datepart,number,date)

参数说明:datepart(同上)

date(同上)

number要增加的值,整型,可正可负,正值返回date之后的时间值,负值返回date

之前的时间值

例子:

select getdate() as today

select dateadd(day,-1,getdate())

select dateadd(day,1,getdate())

输出:

today

1999-11-21 19:42:41.410

(1 row(s) affected)

yesterday

1999-11-20 19:42:41.410

(1 row(s) affected)

tomorrow

1999-11-22 19:42:41.410

(1 row(s) affected)

datediff()函数:返回两个时间以指定时间部分来计算的差值。返回整数值。如1991-6-12和1991-6-21

之间以天

来算相差9天,1998-6-12和1999-6-23按年算相差1年,1999-12-1和1999-3-12按月算相差9个月

用法:datediff(darepart,date1,date2)

参数说明:datepart(同上)

date1、date2(同上date)

例子:

select datediff(month,'1991-6-12','1992-6-21') as a

posted @ 2009-06-22 12:56 二胡 阅读(1900) | 评论 (0)编辑 收藏

转 http://blog.csdn.net/RichardSundusky/archive/2007/02/12/1508028.aspx

解析Java对象的equals()和hashCode()的使用

前言

在Java语言中,equals()和hashCode()两个函数的使用是紧密配合的,你要是自己设计其中一个,就要设计另外一个。在多数情况 下,这两个函数是不用考虑的,直接使用它们的默认设计就可以了。但是在一些情况下,这两个函数最好是自己设计,才能确保整个程序的正常运行。最常见的是当 一个对象被加入收集对象(collection object)时,这两个函数必须自己设计。更细化的定义是:如果你想将一个对象A放入另一个收集对象B里,或者使用这个对象A为查找一个元对象在收集对 象B里位置的钥匙,并支持是否容纳,删除收集对象B里的元对象这样的操作,那么,equals()和hashCode()函数必须开发者自己定义。其他情 况下,这两个函数是不需要定义的。

equals():
它是用于进行两个对象的比较的,是对象内容的比较,当然也能用于进行对象参阅值的比较。什么是对象参阅值的比较?就是两个参阅变量的值得比较,我们 都知道参阅变量的值其实就是一个数字,这个数字可以看成是鉴别不同对象的代号。两个对象参阅值的比较,就是两个数字的比较,两个代号的比较。这种比较是默 认的对象比较方式,在Object这个对象中,这种方式就已经设计好了。所以你也不用自己来重写,浪费不必要的时间。

对象内容的比较才是设计equals()的真正目的,Java语言对equals()的要求如下,这些要求是必须遵循的。否则,你就不该浪费时间:
对称性:如果x.equals(y)返回是“true”,那么y.equals(x)也应该返回是“true”。
反射性:x.equals(x)必须返回是“true”。
类推性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true”。
还有一致性:如果x.equals(y)返回是“true”,只要x和y内容一直不变,不管你重复x.equals(y)多少次,返回都是“true”。
任何情况下,x.equals(null),永远返回是“false”;x.equals(和x不同类型的对象)永远返回是“false”。
hashCode():
这 个函数返回的就是一个用来进行赫希操作的整型代号,请不要把这个代号和前面所说的参阅变量所代表的代号弄混了。后者不仅仅是个代号还具有在内存中才查找对 象的位置的功能。hashCode()所返回的值是用来分类对象在一些特定的收集对象中的位置。这些对象是HashMap, Hashtable, HashSet,等等。这个函数和上面的equals()函数必须自己设计,用来协助HashMap, Hashtable, HashSet,等等对自己所收集的大量对象进行搜寻和定位。

这些收集对象究竟如何工作的,想象每个元对象hashCode是一个箱子的 编码,按照编码,每个元对象就是根据hashCode()提供的代号归入相应的箱子里。所有的箱子加起来就是一个HashSet,HashMap,或 Hashtable对象,我们需要寻找一个元对象时,先看它的代码,就是hashCode()返回的整型值,这样我们找到它所在的箱子,然后在箱子里,每 个元对象都拿出来一个个和我们要找的对象进行对比,如果两个对象的内容相等,我们的搜寻也就结束。这种操作需要两个重要的信息,一是对象的 hashCode(),还有一个是对象内容对比的结果。

hashCode()的返回值和equals()的关系如下:

如果x.equals(y)返回“true”,那么x和y的hashCode()必须相等。
如果x.equals(y)返回“false”,那么x和y的hashCode()有可能相等,也有可能不等。

为什么这两个规则是这样的,原因其实很简单,拿HashSet来说吧,HashSet可以拥有一个或更多的箱子,在同一个箱子中可以有一个 或更多的独特元对象(HashSet所容纳的必须是独特的元对象)。这个例子说明一个元对象可以和其他不同的元对象拥有相同的hashCode。但是一个 元对象只能和拥有同样内容的元对象相等。所以这两个规则必须成立。

设计这两个函数所要注意到的:
如果你设计的对象类型并不使用于收集性对象,那么没有必要自己再设计这两个函数的处理方式。这是正确的面向对象设计方法,任何用户一时用不到的功能,就先不要设计,以免给日后功能扩展带来麻烦。

如果你在设计时想别出心裁,不遵守以上的两套规则,那么劝你还是不要做这样想入非非的事。我还没有遇到过哪一个开发者和我说设计这两个函数要违背前面说的两个规则,我碰到这些违反规则的情况时,都是作为设计错误处理。

当一个对象类型作为收集型对象的元对象时,这个对象应该拥有自己处理equals(),和/或处理hashCode()的设计,而且要遵守前面所说 的两种原则。equals()先要查null和是否是同一类型。查同一类型是为了避免出现ClassCastException这样的异常给丢出来。查 null是为了避免出现NullPointerException这样的异常给丢出来。

如果你的对象里面容纳的数据过多,那么这两个函数 equals()和hashCode()将会变得效率低。如果对象中拥有无法serialized的数据,equals()有可能在操作中出现错误。想象 一个对象x,它的一个整型数据是transient型(不能被serialize成二进制数据流)。然而equals()和hashCode()都有依靠 这个整型数据,那么,这个对象在serialization之前和之后,是否一样?答案是不一样。因为serialization之前的整型数据是有效的 数据,在serialization之后,这个整型数据的值并没有存储下来,再重新由二进制数据流转换成对象后,两者(对象在serialization 之前和之后)的状态已经不同了。这也是要注意的。


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/RichardSundusky/archive/2007/02/12/1508028.aspx

posted @ 2009-06-22 09:46 二胡 阅读(189) | 评论 (0)编辑 收藏

转 http://zhaoyl.javaeye.com/blog/200021

        CategoryPlot categoryplot = (CategoryPlot)chart.getPlot(); 

        NumberAxis numberaxis = (NumberAxis)categoryplot.getRangeAxis(); 

         decimalFormat = new DecimalFormat("格式");         

         numberaxis.setNumberFormatOverride(decimalFormat);

 //格式将指定纵坐标的显示格式,可以显示整数,小数,百分比等

比如:

 0.00  显示如:56.89

 0.00%  显示如:56.89%

 0  显示如57

 0% 显示如57%

posted @ 2009-06-19 10:44 二胡 阅读(858) | 评论 (0)编辑 收藏

        查询了ER图相关资料,如下:
        简单说方框代表实体;椭圆代表属性;菱形框代表关系。
        下图是在网上找到的,都是说是ER图.它们有什么不同?
posted @ 2009-05-14 15:47 二胡 阅读(3850) | 评论 (2)编辑 收藏

简单说方框代表实体;椭圆代表属性;菱形框代表关系。
posted @ 2009-05-14 13:50 二胡 阅读(209) | 评论 (0)编辑 收藏

转 http://shiningray.cn/improve-javascript-performance.html

随着网络的发展,网速和机器速度的提高,越来越多的网站用到了丰富客户端技术。而现在Ajax则是最为流行的一种方式。JavaScript是一种 解释型语言,所以能无法达到和C/Java之类的水平,限制了它能在客户端所做的事情,为了能改进他的性能,我想基于我以前给JavaScript做过的 很多测试来谈谈自己的经验,希望能帮助大家改进自己的JavaScript脚本性能。

语言层次方面

循环

循环是很常用的一个控制结构,大部分东西要依靠它来完成,在JavaScript中,我们可以使用for(;;),while(),for(in)三种循环,事实上,这三种循环中for(in)的效率极差,因为他需要查询散列键,只要可以就应该尽量少用。for(;;)while循环的性能应该说基本(平时使用时)等价。

而事实上,如何使用这两个循环,则有很大讲究。我在测试中有些很有意思的情况,见附录。最后得出的结论是:

  • 如果是循环变量递增或递减,不要单独对循环变量赋值,应该在它最后一次读取的时候使用嵌套的++--操作符。

  • 如果要与数组的长度作比较,应该事先把数组的length属性放入一个局部变量中,减少查询次数。

举例,假设arr是一个数组,最佳的遍历元素方式为:

for(var i=0, len = arr.length;i<len;i++){...}

或者,如果无所谓顺序的话:

for(var i=arr.length;i>0;i--){...}

局部变量和全局变量

局部变量的速度要比全局变量的访问速度更快,因为全局变量其实是全局对象的成员,而局部变量是放在函数的栈当中的。

不使用Eval

使用eval相当于在运行时再次调用解释引擎对内容进行运行,需要消耗大量时间。这时候使用JavaScript所支持的闭包可以实现函数模版(关于闭包的内容请参考函数式编程的有关内容)

减少对象查找

因为JavaScript的解释性,所以a.b.c.d.e,需要进行至少4次查询操作,先检查a再检查a中的b,再检查b中的c,如此往下。所以如果这样的表达式重复出现,只要可能,应该尽量少出现这样的表达式,可以利用局部变量,把它放入一个临时的地方进行查询。

这一点可以和循环结合起来,因为我们常常要根据字符串、数组的长度进行循环,而通常这个长度是不变的,比如每次查询a.length,就要额外进行一个操作,而预先把var
len=a.length
,则就少了一次查询。

字符串连接

如果是追加字符串,最好使用s+=anotherStr操作,而不是要使用s=s+anotherStr

如果要连接多个字符串,应该少使用+=,如

s+=a;
s+=b;
s+=c;

应该写成

s+=a + b + c;

而如果是收集字符串,比如多次对同一个字符串进行+=操作的话,最好使用一个缓存。怎么用呢?使用JavaScript数组来收集,最后使用join方法连接起来,如下

var buf = new Array();
for(var i = 0; i < 100; i++){
buf.push(i.toString());
}
var all = buf.join("");

类型转换

类型转换是大家常犯的错误,因为JavaScript是动态类型语言,你不能指定变量的类型。

1.
把数字转换成字符串,应用"" + 1,虽然看起来比较丑一点,但事实上这个效率是最高的,性能上来说:

("" + ) > String() > .toString() > new String()

这条其实和下面的“直接量”有点类似,尽量使用编译时就能使用的内部操作要比运行时使用的用户操作要快。

String()属于内部函数,所以速度很快,而.toString()要查询原型中的函数,所以速度逊色一些,new String()用于返回一个精确的副本。

2.
浮点数转换成整型,这个更容易出错,很多人喜欢使用parseInt(),其实parseInt()是用于将字符串转换成数字,而不是浮点数和整型之间的转换,我们应该使用Math.floor()或者Math.round()

另外,和第二节的对象查找中的问题不一样,Math是内部对象,所以Math.floor()其实并没有多少查询方法和调用的时间,速度是最快的。

3.
对于自定义的对象,如果定义了toString()方法来进行类型转换的话,推荐显式调用toString(),因为内部的操作在尝试所有可能性之后,会尝试对象的toString()方法尝试能否转化为String,所以直接调用这个方法效率会更高

使用直接量

其实这个影响倒比较小,可以忽略。什么叫使用直接量,比如,JavaScript支持使用[param,param,param,...]来直接表达一个数组,以往我们都使用new Array(param,param,...),使用前者是引擎直接解释的,后者要调用一个Array内部构造器,所以要略微快一点点。

同样,var foo = {}的方式也比var foo = new Object();快,var reg = /../;要比var reg=new RegExp()快。

字符串遍历操作

对字符串进行循环操作,譬如替换、查找,应使用正则表达式,因为本身JavaScript的循环速度就比较慢,而正则表达式的操作是用C写成的语言的API,性能很好。

高级对象

自定义高级对象和DateRegExp对象在构造时都会消耗大量时间。如果可以复用,应采用缓存的方式。

DOM相关

插入HTML

很多人喜欢在JavaScript中使用document.write来给页面生成内容。事实上这样的效率较低,如果需要直接插入HTML,可以找一个容器元素,比如指定一个div或者span,并设置他们的innerHTML来将自己的HTML代码插入到页面中。

对象查询

使用[""]查询要比.items()更快,这和前面的减少对象查找的思路是一样的,调用.items()增加了一次查询和函数的调用。

创建DOM节点

通常我们可能会使用字符串直接写HTML来创建节点,其实这样做

  1. 无法保证代码的有效性

  2. 字符串操作效率低

所以应该是用document.createElement()方法,而如果文档中存在现成的样板节点,应该是用cloneNode()方法,因为使用createElement()方法之后,你需要设置多次元素的属性,使用cloneNode()则可以减少属性的设置次数——同样如果需要创建很多元素,应该先准备一个样板节点。

定时器

如果针对的是不断运行的代码,不应该使用setTimeout,而应该是用setIntervalsetTimeout每次要重新设置一个定时器。

其他

脚本引擎

据我测试Microsoft的JScript的效率较Mozilla的Spidermonkey要差很多,无论是执行速度还是内存管理上,因为JScript现在基本也不更新了。但SpiderMonkey不能使用ActiveXObject

文件优化

文件优化也是一个很有效的手段,删除所有的空格和注释,把代码放入一行内,可以加快下载的速度,注意,是下载的速度而不是解析的速度,如果是本地,注释和空格并不会影响解释和执行速度。

总结

本文总结了我在JavaScript编程中所找到的提高JavaScript运行性能的一些方法,其实这些经验都基于几条原则:

  1. 直接拿手头现成的东西比较快,如局部变量比全局变量快,直接量比运行时构造对象快等等。

  2. 尽可能少地减少执行次数,比如先缓存需要多次查询的。

  3. 尽可能使用语言内置的功能,比如串链接。

  4. 尽可能使用系统提供的API,因为这些API是编译好的二进制代码,执行效率很高

同时,一些基本的算法上的优化,同样可以用在JavaScript中,比如运算结构的调整,这里就不再赘述了。但是由于JavaScript是解释型的,一般不会在运行时对字节码进行优化,所以这些优化仍然是很重要的。

当然,其实这里的一些技巧同样使用在其他的一些解释型语言中,大家也可以进行参考。

参考

附录1

由于是以前做过的测试,测试代码已经不全,我补充了一部分如下:

var print;

if(typeof document != "undefined" ){
print = function(){
document.write(arguments[0]);
}
}else if(typeof WScript != "undefined" ){
print = function(){
WScript.Echo(arguments[0],arguments[1],arguments[2]);
}
}

function empty(){
}

function benchmark(f){
var i = 0;
var start = (new Date()).getTime();

while(i < pressure){
f(i++);
}
var end = (new Date()).getTime();
WScript.Echo(end-start);
}

/*
i=0
start = (new Date()).getTime();
while(i < 60000){
c = [i,i,i,i,i,i,i,i,i,i];
i++;
}
end = (new Date()).getTime();
WScript.Echo(end-start);
i=0
start = (new Date()).getTime();
while(i < 60000){
c = new Array(i,i,i,i,i,i,i,i,i,i);
i++;
}
var end = (new Date()).getTime();
WScript.Echo(end-start);
*/

function internCast(i){
return "" + i;
}

function StringCast(i){
return String(i)
}
function newStringCast(i){
return new String(i)
}
function toStringCast(i){
return i.toString();
}
function ParseInt(){
return parseInt(j);
}
function MathFloor(){
return Math.floor(j);
}
function Floor(){
return floor(j);
}
var pressure = 50000;
var a = "";
var floor = Math.floor;
j = 123.123;
print("-------------\nString Conversion Test");
print("The empty:", benchmark(empty));
print("intern:", benchmark(internCast));
print("String:");
benchmark(StringCast);
print("new String:");
benchmark(newStringCast);
print("toString:");
benchmark(toStringCast);
print("-------------\nFloat to Int Conversion Test");
print("parseInt");
benchmark(ParseInt);
print("Math.floor");
benchmark(MathFloor);
print("floor")
benchmark(Floor);

function newObject(){
return new Object();
}

function internObject(){
return {};
}
print("------------\nliteral Test");
print("runtime new object", benchmark(newObject));
print("literal object", benchmark(internObject));

附录2

代码1:

    for(var i=0;i<100;i++){
arr[i]=0;
}


代码2:

    var i = 0;
while(i < 100){
arr[i++]=0;
}


代码3:

    var i = 0;
while(i < 100){
arr[i]=0;
i++;
}


在firefox下测试这两段代码,结果是代码2优于代码1和3,而代码1一般优于代码3,有时会被代码3超过;而在IE
6.0下,测试压力较大的时候(如测试10000次以上)代码2和3则有时候优于代码1,有时候就会远远落后代码1,而在测试压力较小(如5000次),则代码2>代码3>代码1。

代码4:

    var i = 0;
var a;
while(i < 100){
a = 0;
i++;
}


代码5:

    var a;
for(var i=0;i<100;i++){
a = 0;
}

上面两段代码在Firefox和IE下测试结果都是性能接近的。

代码6:

    var a;
var i=0;
while(i<100){
a=i;
i++;
}


代码7:

    var a;
var i=0;
while(i<100){
a=i++;
}


代码8:

    var a;
for(var i=0;i<100;i++){
a = i;
}


代码9:

    var a;
for(var i=0;i<100;){
a = i++;
}

这四段代码在Firefox下6和8的性能接近,7和9的性能接近,而6,
8 < 7, 9;

最后我们来看一下空循环

代码10:

    for(var i=0;i<100;i++){   }


代码11:

    var i;
while(i<100){ i++; }

最后的测试出现了神奇的结果,Firefox下代码10所花的时间与代码11所花的大约是24:1。所以它不具备参考价值,于是我没有放在一开始给大家看。



posted @ 2009-05-11 11:32 二胡 阅读(187) | 评论 (0)编辑 收藏

有时, 为了让应用程序运行得更快,所做的全部工作就是在这里或那里做一些很小调整。啊,但关键在于确定如何进行调整!迟早您会遇到这种情况:应用程序中的 SQL 查询不能按照您想要的方式进行响应。它要么不返回数据,要么耗费的时间长得出奇。如果它降低了报告或您的企业应用程序的速度,用户必须等待的时间过长,他 们就会很不满意。就像您的父母不想听您解释为什么在深更半夜才回来一样,用户也不会听你解释为什么查询耗费这么长时间。(“对不起,妈妈,我使用了太多的 LEFT JOIN。”)用户希望应用程序响应迅速,他们的报告能够在瞬间之内返回分析数据。就我自己而言,如果在 Web 上冲浪时某个页面要耗费十多秒才能加载(好吧,五秒更实际一些),我也会很不耐烦。

为了解决这些问题,重要的是找到问题的根源。那么,从哪里开始呢?根本原因通常在于数据库设计和访问它的查询。在本月的专栏中,我将讲述四项技术,这些技 术可用于提高基于 SQL Server? 的应用程序的性能或改善其可伸缩性。我将仔细说明 LEFT JOIN、CROSS JOIN 的使用以及 IDENTITY 值的检索。请记住,根本没有神奇的解决方案。调整您的数据库及其查询需要占用时间、进行分析,还需要大量的测试。这些技术都已被证明行之有效,但对您的应 用程序而言,可能其中一些技术比另一些技术更适用。

从 INSERT 返回 IDENTITY
我决定从遇到许多问题的内容入手:如何在执行 SQL INSERT 后检索 IDENTITY 值。通常,问题不在于如何编写检索值的查询,而在于在哪里以及何时进行检索。在 SQL Server 中,下面的语句可用于检索由最新在活动数据库连接上运行的 SQL 语句所创建的 IDENTITY 值:

SELECT @@IDENTITY这个 SQL 语句并不复杂,但需要记住的一点是:如果这个最新的 SQL 语句不是 INSERT,或者您针对非 INSERT SQL 的其他连接运行了此 SQL,则不会获得期望的值。您必须运行下列代码才能检索紧跟在 INSERT SQL 之后且位于同一连接上的 IDENTITY,如下所示:

INSERT INTO Products (ProductName) VALUES ('Chalk')
SELECT @@IDENTITY在一个连接上针对 Northwind 数据库运行这些查询将返回一个名称为 Chalk 的新产品的 IDENTITY 值。所以,在使用 ADO 的 Visual Basic? 应用程序中,可以运行以下语句:

Set oRs = oCn.Execute("SET NOCOUNT ON;INSERT INTO Products _
(ProductName) VALUES ('Chalk');SELECT @@IDENTITY")
lProductID = oRs(0)此代码告诉 SQL Server 不要返回查询的行计数,然后执行 INSERT 语句,并返回刚刚为这个新行创建的 IDENTITY 值。SET NOCOUNT ON 语句表示返回的记录集有一行和一列,其中包含了这个新的 IDENTITY 值。如果没有此语句,则会首先返回一个空的记录集(因为 INSERT 语句不返回任何数据),然后会返回第二个记录集,第二个记录集中包含 IDENTITY 值。这可能有些令人困惑,尤其是因为您从来就没有希望过 INSERT 会返回记录集。之所以会发生此情况,是因为 SQL Server 看到了这个行计数(即一行受到影响)并将其解释为表示一个记录集。因此,真正的数据被推回到了第二个记录集。当然您可以使用 ADO 中的 NextRecordset 方法获取此第二个记录集,但如果总能够首先返回该记录集且只返回该记录集,则会更方便,也更有效率。

此方法虽然有效,但需要在 SQL 语句中额外添加一些代码。获得相同结果的另一方法是在 INSERT 之前使用 SET NOCOUNT ON 语句,并将 SELECT @@IDENTITY 语句放在表中的 FOR INSERT 触发器中,如下面的代码片段所示。这样,任何进入该表的 INSERT 语句都将自动返回 IDENTITY 值。

CREATE TRIGGER trProducts_Insert ON Products FOR INSERT AS
    SELECT @@IDENTITY
GO
触发器只在 Products 表上发生 INSERT 时启动,所以它总是会在成功 INSERT 之后返回一个 IDENTITY。使用此技术,您可以始终以相同的方式在应用程序中检索 IDENTITY 值。

内嵌视图与临时表
某些时候,查询需要将数据与其他一些可能只能通过执行 GROUP BY 然后执行标准查询才能收集的数据进行联接。例如,如果要查询最新五个定单的有关信息,您首先需要知道是哪些定单。这可以使用返回定单 ID 的 SQL 查询来检索。此数据就会存储在临时表(这是一个常用技术)中,然后与 Products 表进行联接,以返回这些定单售出的产品数量:

CREATE TABLE #Temp1 (OrderID INT NOT NULL, _
                    OrderDate DATETIME NOT NULL)
INSERT INTO #Temp1 (OrderID, OrderDate)
SELECT    TOP 5 o.OrderID, o.OrderDate
FROM Orders o ORDER BY o.OrderDate DESC
SELECT    p.ProductName, SUM(od.Quantity) AS ProductQuantity
FROM    #Temp1 t
    INNER JOIN [Order Details] od ON t.OrderID = od.OrderID
    INNER JOIN Products p ON od.ProductID = p.ProductID
GROUP BY p.ProductName
ORDER BY p.ProductName
DROP TABLE #Temp1这些 SQL 语句会创建一个临时表,将数据插入该表中,将其他数据与该表进行联接,然后除去该临时表。这会导致此查询进行大量 I/O 操作,因此,可以重新编写查询,使用内嵌视图取代临时表。内嵌视图只是一个可以联接到 FROM 子句中的查询。所以,您不用在 tempdb 中的临时表上耗费大量 I/O 和磁盘访问,而可以使用内嵌视图得到同样的结果:

SELECT p.ProductName,
    SUM(od.Quantity) AS ProductQuantity
FROM    (
    SELECT TOP 5 o.OrderID, o.OrderDate
    FROM    Orders o
    ORDER BY o.OrderDate DESC
    ) t
    INNER JOIN [Order Details] od ON t.OrderID = od.OrderID
    INNER JOIN Products p ON od.ProductID = p.ProductID
GROUP BY
    p.ProductName
ORDER BY
    p.ProductName此查询不仅比前面的查询效率更高,而且长度更短。临时表会消耗大量资源。如果只需要将数据联接到其他查询,则可以试试使用内嵌视图,以节省资源。

避免 LEFT JOIN 和 NULL
当然,有很多时候您需要执行 LEFT JOIN 和使用 NULL 值。但是,它们并不适用于所有情况。改变 SQL 查询的构建方式可能会产生将一个花几分钟运行的报告缩短到只花几秒钟这样的天壤之别的效果。有时,必须在查询中调整数据的形态,使之适应应用程序所要求的 显示方式。虽然 TABLE 数据类型会减少大量占用资源的情况,但在查询中还有许多区域可以进行优化。SQL 的一个有价值的常用功能是 LEFT JOIN。它可以用于检索第一个表中的所有行、第二个表中所有匹配的行、以及第二个表中与第一个表不匹配的所有行。例如,如果希望返回每个客户及其定单, 使用 LEFT JOIN 则可以显示有定单和没有定单的客户。

此工具可能会被过度使用。LEFT JOIN 消耗的资源非常之多,因为它们包含与 NULL(不存在)数据匹配的数据。在某些情况下,这是不可避免的,但是代价可能非常高。LEFT JOIN 比 INNER JOIN 消耗资源更多,所以如果您可以重新编写查询以使得该查询不使用任何 LEFT JOIN,则会得到非常可观的回报(请参阅图 1 中的图)。

图 1 查询

加快使用 LEFT JOIN 的查询速度的一项技术涉及创建一个 TABLE 数据类型,插入第一个表(LEFT JOIN 左侧的表)中的所有行,然后使用第二个表中的值更新 TABLE 数据类型。此技术是一个两步的过程,但与标准的 LEFT JOIN 相比,可以节省大量时间。一个很好的规则是尝试各种不同的技术并记录每种技术所需的时间,直到获得用于您的应用程序的执行性能最佳的查询。

测试查询的速度时,有必要多次运行此查询,然后取一个平均值。因为查询(或存储过程)可能会存储在 SQL Server 内存中的过程缓存中,因此第一次尝试耗费的时间好像稍长一些,而所有后续尝试耗费的时间都较短。另外,运行您的查询时,可能正在针对相同的表运行其他查 询。当其他查询锁定和解锁这些表时,可能会导致您的查询要排队等待。例如,如果您进行查询时某人正在更新此表中的数据,则在更新提交时您的查询可能需要耗 费更长时间来执行。

避免使用 LEFT JOIN 时速度降低的最简单方法是尽可能多地围绕它们设计数据库。例如,假设某一产品可能具有类别也可能没有类别。如果 Products 表存储了其类别的 ID,而没有用于某个特定产品的类别,则您可以在字段中存储 NULL 值。然后您必须执行 LEFT JOIN 来获取所有产品及其类别。您可以创建一个值为“No Category”的类别,从而指定外键关系不允许 NULL 值。通过执行上述操作,现在您就可以使用 INNER JOIN 检索所有产品及其类别了。虽然这看起来好像是一个带有多余数据的变通方法,但可能是一个很有价值的技术,因为它可以消除 SQL 批处理语句中消耗资源较多的 LEFT JOIN。在数据库中全部使用此概念可以为您节省大量的处理时间。请记住,对于您的用户而言,即使几秒钟的时间也非常重要,因为当您有许多用户正在访问同 一个联机数据库应用程序时,这几秒钟实际上的意义会非常重大。

灵活使用笛卡尔乘积
对于此技巧,我将进行非常详细的介绍,并提倡在某些情况下使用笛卡尔乘积。出于某些原因,笛卡尔乘积 (CROSS JOIN) 遭到了很多谴责,开发人员通常会被警告根本就不要使用它们。在许多情况下,它们消耗的资源太多,从而无法高效使用。但是像 SQL 中的任何工具一样,如果正确使用,它们也会很有价值。例如,如果您想运行一个返回每月数据(即使某一特定月份客户没有定单也要返回)的查询,您就可以很方 便地使用笛卡尔乘积。 图 2 中的 SQL 就执行了上述操作。

虽然这看起来好像没什么神奇的,但是请考虑一下,如果您从客户到定单(这些定单按月份进行分组并对销售额进行小计)进行了标准的 INNER JOIN,则只会获得客户有定单的月份。因此,对于客户未订购任何产品的月份,您不会获得 0 值。如果您想为每个客户都绘制一个图,以显示每个月和该月销售额,则可能希望此图包括月销售额为 0 的月份,以便直观标识出这些月份。如果使用 图 2 中的 SQL,数据则会跳过销售额为 0 美元的月份,因为在定单表中对于零销售额不会包含任何行(假设您只存储发生的事件)。

图 3 中的代码虽然较长,但是可以达到获取所有销售数据(甚至包括没有销售额的月份)的目标。首先,它会提取去年所有月份的列表,然后将它们放入第一个 TABLE 数据类型表 (@tblMonths) 中。下一步,此代码会获取在该时间段内有销售额的所有客户公司的名称列表,然后将它们放入另一个 TABLE 数据类型表 (@tblCus-tomers) 中。这两个表存储了创建结果集所必需的所有基本数据,但实际销售数量除外。 第一个表中列出了所有月份(12 行),第二个表中列出了这个时间段内有销售额的所有客户(对于我是 81 个)。并非每个客户在过去 12 个月中的每个月都购买了产品,所以,执行 INNER JOIN 或 LEFT JOIN 不会返回每个月的每个客户。这些操作只会返回购买产品的客户和月份。

笛卡尔乘积则可以返回所有月份的所有客户。笛卡尔乘积基本上是将第一个表与第二个表相乘,生成一个行集合,其中包含第一个表中的行数与第二个表中的行数相 乘的结果。因此,笛卡尔乘积会向表 @tblFinal 返回 972 行。最后的步骤是使用此日期范围内每个客户的月销售额总计更新 @tblFinal 表,以及选择最终的行集。

如果由于笛卡尔乘积占用的资源可能会很多,而不需要真正的笛卡尔乘积,则可以谨慎地使用 CROSS JOIN。例如,如果对产品和类别执行了 CROSS JOIN,然后使用 WHERE 子句、DISTINCT 或 GROUP BY 来筛选出大多数行,那么使用 INNER JOIN 会获得同样的结果,而且效率高得多。如果需要为所有的可能性都返回数据(例如在您希望使用每月销售日期填充一个图表时),则笛卡尔乘积可能会非常有帮助。 但是,您不应该将它们用于其他用途,因为在大多数方案中 INNER JOIN 的效率要高得多。

拾遗补零
这里介绍其他一些可帮助提高 SQL 查询效率的常用技术。假设您将按区域对所有销售人员进行分组并将他们的销售额进行小计,但是您只想要那些数据库中标记为处于活动状态的销售人员。您可以按 区域对销售人员分组,并使用 HAVING 子句消除那些未处于活动状态的销售人员,也可以在 WHERE 子句中执行此操作。在 WHERE 子句中执行此操作会减少需要分组的行数,所以比在 HAVING 子句中执行此操作效率更高。HAVING 子句中基于行的条件的筛选会强制查询对那些在 WHERE 子句中会被去除的数据进行分组。

另一个提高效率的技巧是使用 DISTINCT 关键字查找数据行的单独报表,来代替使用 GROUP BY 子句。在这种情况下,使用 DISTINCT 关键字的 SQL 效率更高。请在需要计算聚合函数(SUM、COUNT、MAX 等)的情况下再使用 GROUP BY。另外,如果您的查询总是自己返回一个唯一的行,则不要使用 DISTINCT 关键字。在这种情况下,DISTINCT 关键字只会增加系统开销。

您已经看到了,有大量技术都可用于优化查询和实现特定的业务规则,技巧就是进行一些尝试,然后比较它们的性能。最重要的是要测试、测试、再测试。在此专栏 的将来各期内容中,我将继续深入讲述 SQL Server 概念,包括数据库设计、好的索引实践以及 SQL Server 安全范例。
   ◆尽量不要在where中包含子查询;

关于时间的查询,尽量不要写成:where to_char(dif_date,'yyyy-mm-dd')=to_char('2007-07-01','yyyy-mm-dd');

◆在过滤条件中,可以过滤掉最大数量记录的条件必须放在where子句的末尾;

FROM子句中写在最后的表(基础表,driving table)将被最先处理,在FROM子句中包含多个表的情况下,你必须选择记录条数最少的表作为基础表。如果有三个以上的连接查询,那就需要选择交叉表 (intersection table)作为基础表,交叉表是指那个被其他表所引用的表;

◆采用绑定变量

◆在WHERE中尽量不要使用OR

◆用EXISTS替代IN、用NOT EXISTS替代NOT IN;

◆避免在索引列上使用计算:WHERE SAL*12>25000;

◆用IN来替代OR: WHERE LOC_ID=10 OR LOC_ID=15 OR LOC_ID=20

◆避免在索引列上使用IS NULL和IS NOT NULL;

◆总是使用索引的第一个列;

◆用UNION-ALL替代UNION;

◆避免改变索引列的类型:SELECT...FROM EMP WHERE EMPNO='123',由于隐式数据类型转换,to_char(EMPNO)='123',因此,将不采用索引,一般在采用字符串拼凑动态SQL语句出现;

◆'!=' 将不使用索引;

◆优化GROUP BY;

◆避免带有LIKE参数的通配符,LIKE '4YE%'使用索引,但LIKE '%YE'不使用索引

◆避免使用困难的正规表达式,例如select * from customer where zipcode like "98___",即便在zipcode上建立了索引,在这种情况下也还是采用顺序扫描的方式。如果把语句改成select * from customer where zipcode>"98000",在执行查询时就会利用索引来查询,显然会大大提高速度;

◆尽量明确的完成SQL语句,尽量少让数据库工作。比如写SELECT语句时,需要把查询的字段明确指出表名。尽量不要使用SELECT *语句。组织SQL语句的时候,尽量按照数据库的习惯进行组织。
posted @ 2009-05-10 13:14 二胡 阅读(230) | 评论 (0)编辑 收藏

转 http://www.fboss.com/article.asp?id=816

近来总有朋友咨询cPanel 的Awstats中“HTTP错误码(HTTP Error codes)”的含义,以及是否需要关注和处理。
关于Awstats请查看《CP How-To:如何使用cPanel查看站点统计数据(awstats)》 文章地址: http://bbs.netpu.net/viewthread.php tid=694 其实这是一个误会,在这里它应该是“HTTP状态码(HTTP Status codes)”。
OK,既然是状态码,那么就可能有正确和错误两种状态了(至少不全是错误了,大大的松口气吧)。那么这些代码都代表什么含义呢?到底哪些是错误状态,哪些是正确状态呢?不要急,下边我冒充内行为大家做一个简单的介绍。
HTTP与Status codes
HTTP可能大家都熟悉,就是超文本传输协议。浏览器通过HTTP与WEB Server通讯(也有一些其它软件比如IM使用HTTP协议传递数据),把我们的请求(HTTP Request)传递给服务器,服务器响应这个请求,返回应答(HTTP Response)以及我们需要的数据。大致就是这个样子了。
如果 我们请求啥,服务器就返回啥,是乎就不需要HTTP Status codes了。但是事情往往不是那么简单。比如我们请求一个网页页面,可是服务器不存在这个页面,或者这个页面被转移到其它地方,或者服务器禁止我们查看 这个页面等等,所以为了便于浏览器处理这些正确与不正确的情况,HTTP用Status codes来表示请求(HTTP Request)在服务器端被处理的情况。Status codes通过应答(HTTP Response)返回给浏览器,浏览器根据这个作相应的处理。
HTTP Status codes的分类
既然有正确和错误的状态,HTTP定义两大类状态码是不是就可以了?人家制订协议的可是专家(不象我是冒充的),想得比我们要周全,要长远。HTTP 1.1中定义了5大类Status codes,分别是:
Informational 意义:信息 范围:1XX
Successful 意义:成功 范围:2XX
Redirection 意义:重定向 范围:3XX
Client Error 意义:客户端错误 范围:4XX
Server Error 意义:服务器错误 范围:5XX
您看看人家想得多周到啊,真专家就是真专家。
常见HTTP Status codes简介
下面简单介绍一下我们经常碰到的HTTP Status codes。
也许是我孤陋寡闻,常遇到的HTTP Status codes就那么几个,见笑啦。
Successful 200 - OK:OK 这个是最常见的啦(也许我们不会直接看到,但是如果您使用一些抓包工具,大多数http应答中都有这个)。意义很简单,就是说服务器收到并理解客户端的请 求而且正常处理了。 206 - Partial Content:部分内容。 这个也经常发生。很容易让大家发懵。 通俗点说就是如果客户端请求文档(图像,文本,声音等等)的部分内容,服务器正常处理,那么就返回206。大致意思就是它请求的时候,除了指定请求的内 容,还指定了偏移量以及长度。 部分内容,没搞错吧?呵呵没搞错,现在很多浏览器以及软件支持断点续传就是靠这个的。呵呵,以后看到206不要怕了。
Redirection 301 - Moved Permanently:永久移动。 这个状态码是指客户端要请求的资源已经被永久的转移到一个新的地方了。这个应答(HTTP Response)里边同时包含了资源的新地址。它告诉客户端,如果下次还想要这个资源,那么就用新的地址去取。 302 Found:临时重定向。 这个状态码是指客户端要请求的资源临时放到一个新地方了。同样,应答中也包含了资源的新地址。 307 - Temporary Redirect:临时重定向。(如果不去实现协议或者做相关开发,我们大致理解它很302差不多就可以啦) 有一篇谈重定向的文章《从Business.com遭封看302重定向》很有意思 我们转载了一份,地址: http://bbs.netpu.net/viewthread.php tid=810 Client Error 400 - Bad Request:错误请求 就是请求的语法错误,服务器无法理解。 401 – Unauthorized:未授权 当服务器返回401 Code,就是告诉说客户端访问指定资源以前,必须通过服务器的授权。 403 – Forbidden:禁止访问 就是不允许访问某些资源。 404 - Not Found:找不到 找不到客户端请求的内容
Server Error 500 - Internal Server Error 服务器内部错误。
结束语
越写越懒,所以就简简单单写这么多啦。没有啥大用处。如果能消除一两位关于这方面朋友的疑虑,就足以令我欣慰了。既然之前都说过是冒充内行,所以有错漏之处在所难免,还望大家不吝赐教。
需要深入研究这方面内容的朋友,千万不要看这篇文章,以免被此文误导。请学习官方的协议内容。 官方的资料地址:
http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html


posted @ 2009-05-06 11:38 二胡 阅读(657) | 评论 (0)编辑 收藏

   关于RequestDispatcher机制的文章,只看懂个大概,那位E文好,给翻译一下!
  

RequestDispatcher defines an object that receives requests from the client and sends them to any resource (such as a servlet, HTML file, or JSP file) on the server. The servlet container creates the RequestDispatcher object, which is used as a wrapper around a server resource located at a particular path or given by a particular name.

 

An object implementing the RequestDispatcher interface may be obtained via the following methods:

  • ServletContext.getRequestDispatcher(String path)

  • ServletContext.getNamedDispatcher(String name)

  • ServletRequest.getRequestDispatcher(String path)

 

The ServletContext.getRequestDispatcher method takes a String argument describing a path within the scope of the ServletContext. This path must be relative to the root of the ServletContext and begin with a '/'. The method uses the path to look up a servlet, using the servlet path matching rules, wraps it with a RequestDispatcher object, and returns the resulting object. If no servlet can be resolved based on the given path, a RequestDispatcher is provided that returns the content for that path.

The ServletContext.getNamedDispatcher method takes a String argument indicating the NAME of a servlet known to the ServletContext. If a servlet is found, it is wrapped with a RequestDispatcher object and the object is returned. If no servlet is associated with the given name, the method must return null.

To allow RequestDispatcher objects to be obtained using relative paths that are relative to the path of the current request (not relative to the root of the ServletContext), the ServletRequest.getRequestDispatcher method is provided in the ServletRequest interface. The behavior of this method is similar to the method of the same name in the ServletContext. The servlet container uses information in the request object to transform the given relative path against the current servlet to a complete path. For example, in a context rooted at '/' and a request to /garden/tools.html, a request dispatcher obtained via ServletRequest.getRequestDispatcher("header.html") will behave exactly like a call to ServletContext.getRequestDispatcher("/garden/header.html").

RequestDispatcher creation and using.

 

public class Dispatcher extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse res) {
RequestDispatcher dispatcher =
request.getRequestDispatcher("/template.jsp");
if (dispatcher != null) dispatcher.forward(request, response);
}
}

forward should be called before the response has been committed to the client (before response body output has been flushed). If the response already has been committed, this method throws an IllegalStateException. Uncommitted output in the response buffer is automatically cleared before the forward.

 

 

public class Dispatcher extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse res) {
RequestDispatcher dispatcher =
getServletContext().getRequestDispatcher("/banner");
if (dispatcher != null) dispatcher.include(request, response);
}
}

Includes the content of a resource (servlet, JSP page, HTML file) in the response. In essence, this method enables programmatic server-side includes. The ServletResponse object has its path elements and parameters remain unchanged from the caller's. The included servlet cannot change the response status code or set headers; any attempt to make a change is ignored.

 

 

package javax.servlet;
public interface RequestDispatcher {
public void forward(ServletRequest request, ServletResponse response)
throws ServletException, java.io.IOException;
public void include(ServletRequest request, ServletResponse response)
throws ServletException, java.io.IOException;
}

 

The include method of the RequestDispatcher interface may be called at ANY time. The target servlet of the include method has access to all aspects of the request object, but its use of the response object is more limited. It can only write information to the ServletOutputStream or Writer of the response object and commit a response by writing content past the end of the response buffer, or by explicitly calling the flushBuffer method of the ServletResponse interface. It CANNOT set headers or call any method that affects the headers of the response. Any attempt to do so must be ignored.

The forward method of the RequestDispatcher interface may be called by the calling servlet ONLY when NO output has been committed to the client. If output data exists in the response buffer that has not been committed, the content must be cleared before the target servlet's service method is called. If the response has been committed, an IllegalStateException must be thrown.

The path elements of the request object exposed to the target servlet must reflect the path used to obtain the RequestDispatcher. The only exception to this is if the RequestDispatcher was obtained via the getNamedDispatcher method. In this case, the path elements of the request object must reflect those of the original request. Before the forward method of the RequestDispatcher interface returns, the response content MUST be sent and committed, and closed by the servlet container.

The ServletContext and ServletRequest methods that create RequestDispatcher objects using path information allow the optional attachment of query string information to the path. For example, a Developer may obtain a RequestDispatcher by using the following code:

String path = "/raisins.jsp?orderno=5";
RequestDispatcher rd = context.getRequestDispatcher(path);
rd.include(request, response);

Parameters specified in the query string used to create the RequestDispatcher take precedence over other parameters of the same name passed to the included servlet. The parameters associated with a RequestDispatcher are scoped to apply only for the duration of the include or forward call.

 

Additional request-scoped attributes.

Except for servlets obtained by using the getNamedDispatcher method, a servlet that has been invoked by another servlet using the include method of RequestDispatcher has access to the path by which it was invoked.

The following request attributes must be set:

  • javax.servlet.include.request_uri

  • javax.servlet.include.context_path

  • javax.servlet.include.servlet_path

  • javax.servlet.include.path_info

  • javax.servlet.include.query_string

 

These attributes are accessible from the included servlet via the getAttribute method on the request object and their values must be equal to the request URI, context path, servlet path, path info, and query string of the INCLUDED servlet, respectively. If the request is subsequently included, these attributes are replaced for that include.

If the included servlet was obtained by using the getNamedDispatcher method, these attributes MUST NOT be set.

Except for servlets obtained by using the getNamedDispatcher method, a servlet that has been invoked by another servlet using the forward method of RequestDispatcher has access to the path of the ORIGINAL request.

The following request attributes must be set:

  • javax.servlet.forward.request_uri

  • javax.servlet.forward.context_path

  • javax.servlet.forward.servlet_path

  • javax.servlet.forward.path_info

  • javax.servlet.forward.query_string

 

The values of these attributes must be equal to the return values of the HttpServletRequest methods getRequestURI, getContextPath, getServletPath, getPathInfo, getQueryString respectively, invoked on the request object passed to the first servlet object in the call chain that received the request from the client.

These attributes are accessible from the forwarded servlet via the getAttribute method on the request object. Note that these attributes must always reflect the information in the original request even under the situation that multiple forwards and subsequent includes are called.

If the forwarded servlet was obtained by using the getNamedDispatcher method, these attributes must not be set.

posted @ 2009-05-05 11:59 二胡 阅读(1210) | 评论 (0)编辑 收藏

转 http://javathinker.blog.ccidnet.com/blog-htm-itemid-1262479-do-showone-type-blog-uid-36384.html

在《Pragmatic AJAX中文问题 A Web 2.0 Primer 》中偶然看到对readyStae状态的介绍,感觉这个介绍很实在,摘译如下:

0: (Uninitialized) the send( ) method has not yet been invoked.
1: (Loading) the send( ) method has been invoked, request in progress.
2: (Loaded) the send( ) method has completed, entire response received.
3: (Interactive) the response is being parsed.
4: (Completed) the response has been parsed, is ready for harvesting.

0 - (未初始化)还没有调用send()方法
1 - (载入)已调用send()方法,正在发送请求
2 - (载入完成)send()方法执行完成,已经接收到全部响应内容
3 - (交互)正在解析响应内容
4 - (完成)响应内容解析完成,可以在客户端调用了

对 于readyState的这五种状态,其他书中大都语焉不详。像《Foundations of AJAX中文问题》中,只在书中的表2-2简单地列举了状态的“名称”--The state of the request. The five possible values are 0 = uninitialized, 1 = loading, 2 = loaded, 3 = interactive, and 4 = complete。而《Ajax in Action》中好像根本就没有提到这5种状态的细节。《Professional AJAX中文问题》中虽不尽人意,但还是有可取之处:

There are five possible values for readyState:
0 (Uninitialized): The object has been created but the open() method hasn’t been called.
1 (Loading): The open() method has been called but the request hasn’t been sent.
2 (Loaded): The request has been sent.
3 (Interactive). A partial response has been received.
4 (Complete): All data has been received and the connection has been closed.

readyState有五种可能的值:
0 (未初始化): (XMLHttpRequest)对象已经创建,但还没有调用open()方法。
1 (载入):已经调用open() 方法,但尚未发送请求。
2 (载入完成): 请求已经发送完成。
3 (交互):可以接收到部分响应数据。
4 (完成):已经接收到了全部数据,并且连接已经关闭。

在《Understanding AJAX中文问题: Using JavaScript to Create Rich Internet Applications》中,则用下表进行了说明:

readyState Status Code
Status of the XMLHttpRequest Object
(0) UNINITIALIZED
未初始化 The object has been created but not initialized. (The open method has not been called.)
(XMLHttpRequest)对象已经创建,但尚未初始化(还没有调用open方法)。
(1) LOADING
载入 The object has been created, but the send method has not been called.
(XMLHttpRequest)对象已经创建,但尚未调用send方法。
(2) LOADED
载入完成 The send method has been called, but the status and headers are not yet available.
已经调用send方法,(HTTP响应)状态及头部还不可用。
(3) INTERACTIVE
交 互 Some data has been received. Calling the responseBody and responseText properties at this state to obtain partial results will return an error, because status and response headers are not fully available.
已经接收部分数据。但若在此时调用responseBody和responseText属性获取部分结果将会产生错误,因为状态和响应头部还不完全可用。
(4) COMPLETED
完成 All the data has been received, and the complete data is available in the responseBody and responseText properties.
已经接收到了全部数据,并且在responseBody和responseText属性中可以提取到完整的数据。

根 据以上几本书中的关于readyState五种状态的介绍,我认为还是《Pragmatic AJAX中文问题 A Web 2.0 Primer 》比较到位,因为它提到了对接收到的数据的解析问题,其他书中都没有提到这一点,而这一点正是“(3)交互”阶段作为一个必要的转换过程存在于“(2)载 入完成”到“(4)完成”之间的理由,也就是其任务是什么。归结起来,我觉得比较理想的解释方法应该以“状态:任务(目标)+过程+表现(或特征)”表达 模式来对这几个状态进行定义比较准确,而且让人容易理解。现试总结如下:

readyState 状态
状态说明

(0)未初始化
此阶段确认XMLHttpRequest对象是否创建,并为调用open()方法进行未初始化作好准备。值为0表示对象已经存在,否则浏览器会报错--对象不存在。

(1)载入
此阶段对XMLHttpRequest对象进行初始化,即调用open()方法,根据参数(method,url,true)完成对象状态的设置。并调用send()方法开始向服务端发送请求。值为1表示正在向服务端发送请求。

(2)载入完成
此阶段接收服务器端的响应数据。但获得的还只是服务端响应的原始数据,并不能直接在客户端使用。值为2表示已经接收完全部响应数据。并为下一阶段对数据解析作好准备。

(3)交互
此阶段解析接收到的服务器端响应数据。即根据服务器端响应头部返回的MIME类型把数据转换成能通过responseBody、responseText或responseXML属性存取的格式,为在客户端调用作好准备。状态3表示正在解析数据。

(4)完成
此阶段确认全部数据都已经解析为客户端可用的格式,解析已经完成。值为4表示数据解析完毕,可以通过XMLHttpRequest对象的相应属性取得数据。


概而括之,整个XMLHttpRequest对象的生命周期应该包含如下阶段:
创建-初始化请求-发送请求-接收数据-解析数据-完成

在 具体应用中,明确了readyState的五个状态(XMLHttpRequest对象的生命周期各个阶段)的含义,就可以消除对Ajax核心的神秘感 (语焉不详的背后要么是故弄玄虚,制造神秘感;要么就是“以其昏昏,使人昭昭”),迅速把握其实质,对减少学习中的挫折感和增强自信心都极其有益。

比如,通过如下示例:


//声明数组
var states = [“正在初始化……”,
“正在初始化请求……成功!
正在发送请求……”,
“成功!
正在接收数据……”,
“完成!
正在解析数据……”,
“完成!
”];

//回调函数内部代码片段
if (xmlHttp.readyState==4)
{
var span = document.createElement(“span”);
span.innerHTML = states[xmlHttp.readyState];
document.body.appendChild(span);

if (xmlHttp.status == 200)
{
var xmldoc = xmlHttp.responseXML;
//其他代码
}

//别忘记销毁,防止内存泄漏
xmlHttp = null;
}else{
var span = document.createElement(“span”);
span.innerHTML = states[xmlHttp.readyState];
document.body.appendChild(span);
}结果如下:

正在初始化请求……成功!
正在发送请求……成功!
正在接收数据……完成!
正在解析数据……完成!

我们很容易明白XMLHttpRequest对象在各个阶段都在做什么。因此,也就很容易对Ajax的核心部分有一个真正简单明了的理解。


posted @ 2009-05-04 17:03 二胡 阅读(10277) | 评论 (0)编辑 收藏

   JS开发中,调试比较麻烦!常用的有ff的debug工具。IE的debug工具用的到不多,给大家推荐一个IE的JS调试工具
   下载地址:http://www.my-debugbar.com/wiki/CompanionJS/HomePage

   安装后,在IE工具栏有如下图标
  


posted @ 2009-04-29 13:08 二胡 阅读(3806) | 评论 (5)编辑 收藏

转 http://www.blogjava.net/eamoi/archive/2005/01/14/299.html

首先,请到http://www.andykhan.com/jexcelapi/index.html下载java excel api,主页上同时有比较详细的介绍。最新版本为2.4.3,同时也可以到:http://www.andykhan.com/jexcelapi/jexcelapi_2_4_3.tar.gz下载到该最新版的API,由于该项目是开源的,所以下载的文件中已经包含了源代码,同样的,文件中也有javadoc,大家在开发中可以参考javadoc。

下载完毕后,我们需要把文件中的jxl.jar加入到你的开发classpath中。
下图是现在要生产的excel截图:
http://blog.csdn.net/beming/gallery/image/3437.aspx

代码如下:

   File excel = new File("d:/aming.xls");
   if(!excel.exists()){
    excel.createNewFile();
   }   
   WritableWorkbook wwb = Workbook.createWorkbook(excel);
   WritableSheet ws = wwb.createSheet("testexcel",0);
   Label lable = null;
   
   //对中文的支持非常好
   lable = new Label(0,0,"我的中国心");
   ws.addCell(lable);
   
   //可以定义模板格式化你的cell
   WritableFont wf = new WritableFont(WritableFont.ARIAL,10,WritableFont.NO_BOLD,false,UnderlineStyle.NO_UNDERLINE, Colour.BLACK);
   WritableCellFormat wcf = new WritableCellFormat(wf);
   wcf.setBackground(Colour.WHITE);
   lable = new Label(0,1,"fdsl",wcf);
   ws.addCell(lable);
   
   wf = new WritableFont(WritableFont.TIMES,18,WritableFont.BOLD,true);
   wcf = new WritableCellFormat(wf);
   lable = new Label(0,2,"aming",wcf);
   ws.addCell(lable);
   
   //cell的类型同样可以定义为数字类型
   Number nb = new Number(0,3,21.4321321);
   ws.addCell(nb);
   
   //支持格式化你的数字串
   NumberFormat nf = new NumberFormat("#.###");
   wcf = new WritableCellFormat(nf);
   nb = new Number(0,4,21.43254354354354,wcf);
   ws.addCell(nb);

   //cell的类型可以为boolean类型
   Boolean bl = new Boolean(0,5,true);
   ws.addCell(bl);

   //cell的类型同样可以为日期,时间
   DateTime dt = new DateTime(0,6,new Date());
   ws.addCell(dt);

   //并且可以很好格式化你的日期格式
   DateFormat df = new DateFormat("MM dd yyyy hh:mm:ss");
   wcf = new WritableCellFormat(df);
   dt = new DateTime(0,7,new Date(),wcf);
   ws.addCell(dt);
   
   //开始写文件了
   wwb.write();
   wwb.close();

上面的下载地址无法打开.
下载请到:http://prdownloads.sourceforge.net/jexcelapi



posted @ 2009-04-28 09:02 二胡 阅读(248) | 评论 (0)编辑 收藏

转 http://blog.csdn.net/fenglibing/archive/2009/03/19/4005449.aspx

5.2 分配JSP初始化参数
给JSP页面提供初始化参数在三个方面不同于给servlet提供初始化参数。
1)使用jsp-file而不是servlet-class。因此,WEB-INF/web.xml文件的servlet元素如下所示:

Java代码
  1. <servlet>   
  2. <servlet-name>PageName</servlet-name>   
  3. <jsp-file>/RealPage.jsp</jsp-file>   
  4. <init-param>   
  5. <param-name>...</param-name>   
  6. <param-value>...</param-value>   
  7. </init-param>   
  8. ...   
  9. </servlet>  

2) 几乎总是分配一个明确的URL模式。对servlet,一般相应地使用以http://host/webAppPrefix/servlet/ 开始的缺省URL。只需记住,使用注册名而不是原名称即可。这对于JSP页面在技术上也是合法的。例如,在上面给出的例子中,可用URL http://host/webAppPrefix/servlet/PageName 访问RealPage.jsp的对初始化参数具有访问权的版本。但在用于JSP页面时,许多用户似乎不喜欢应用常规的servlet的URL。此外,如果 JSP页面位于服务器为其提供了目录清单的目录中(如,一个既没有index.html也没有index.jsp文件的目录),则用户可能会连接到此 JSP页面,单击它,从而意外地激活未初始化的页面。因此,好的办法是使用url-pattern(5.3节)将JSP页面的原URL与注册的 servlet名相关联。这样,客户机可使用JSP页面的普通名称,但仍然激活定制的版本。例如,给定来自项目1的servlet定义,可使用下面的 servlet-mapping定义:

Java代码
  1. <servlet-mapping>   
  2. <servlet-name>PageName</servlet-name>   
  3. <url-pattern>/RealPage.jsp</url-pattern>   
  4. </servlet-mapping>  

3)JSP页使用jspInit而不是init。自动从JSP页面建立的servlet或许已经使用了inti方法。因此,使用JSP声明提供一个init方法是不合法的,必须制定jspInit方法。
为 了说明初始化JSP页面的过程,程序清单5-9给出了一个名为InitPage.jsp的JSP页面,它包含一个jspInit方法且放置于 deployDemo Web应用层次结构的顶层。一般,http://host/deployDemo/InitPage.jsp 形式的URL将激活此页面的不具有初始化参数访问权的版本,从而将对firstName和emailAddress变量显示null。但是, web.xml文件(程序清单5-10)分配了一个注册名,然后将该注册名与URL模式/InitPage.jsp相关联。

程序清单5-9 InitPage.jsp

Java代码
  1. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">   
  2. <HTML>   
  3. <HEAD><TITLE>JSP Init Test</TITLE></HEAD>   
  4. <BODY BGCOLOR="#FDF5E6">   
  5. <H2>Init Parameters:</H2>   
  6. <UL>   
  7. <LI>First name: <%= firstName %>   
  8. <LI>Email address: <%= emailAddress %>   
  9. </UL>   
  10. </BODY></HTML>   
  11. <%!   
  12. private String firstName, emailAddress;   
  13.   
  14. public void jspInit() {   
  15. ServletConfig config = getServletConfig();   
  16. firstName = config.getInitParameter("firstName");   
  17. emailAddress = config.getInitParameter("emailAddress");   
  18. }   
  19. %>  



程序清单5-10 web.xml(说明JSP页面的init参数的摘录)

Java代码
  1. <?xml version="1.0" encoding="ISO-8859-1"?>   
  2. <!DOCTYPE web-app   
  3. PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"  
  4. "http://java.sun.com/dtd/web-app_2_3.dtd">   
  5.   
  6. <web-app>   
  7. <!-- ... -->   
  8. <servlet>   
  9. <servlet-name>InitPage</servlet-name>   
  10. <jsp-file>/InitPage.jsp</jsp-file>   
  11. <init-param>   
  12. <param-name>firstName</param-name>   
  13. <param-value>Bill</param-value>   
  14. </init-param>   
  15. <init-param>   
  16. <param-name>emailAddress</param-name>   
  17. <param-value>gates@oracle.com</param-value>   
  18. </init-param>   
  19. </servlet>   
  20. <!-- ... -->   
  21. <servlet-mapping>   
  22. <servlet-name> InitPage</servlet-name>   
  23. <url-pattern>/InitPage.jsp</url-pattern>   
  24. </servlet-mapping>   
  25. <!-- ... -->   
  26. </web-app>  



5.3 提供应用范围内的初始化参数
一般,对单个地servlet或JSP页面分配初始化参数。指定的servlet或 JSP页面利用ServletConfig的getInitParameter方法读取这些参数。但是,在某些情形下,希望提供可由任意servlet或 JSP页面借助ServletContext的getInitParameter方法读取的系统范围内的初始化参数。
可利用context-param元素声明这些系统范围内的初始化值。context-param元素应该包含param-name、param-value以及可选的description子元素,如下所示:
<context-param>
<param-name>support-email</param-name>
<param-value>blackhole@mycompany.com</param-value>
</context-param>
可 回忆一下,为了保证可移植性,web.xml内的元素必须以正确的次序声明。但这里应该注意,context-param元素必须出现任意与文档有关的元 素(icon、display-name或description)之后及filter、filter-mapping、listener或 servlet元素之前。
5.4 在服务器启动时装载servlet
假如servlet或JSP页面有一个要花很长时间执行的 init (servlet)或jspInit(JSP)方法。例如,假如init或jspInit方法从某个数据库或ResourceBundle查找产量。这种 情况下,在第一个客户机请求时装载servlet的缺省行为将对第一个客户机产生较长时间的延迟。因此,可利用servlet的load-on- startup元素规定服务器在第一次启动时装载servlet。下面是一个例子。

Java代码
  1. <servlet>   
  2. <servlet-name> … </servlet-name>   
  3. <servlet-class> … </servlet-class> <!-- or jsp-file -->   
  4. <load-on-startup/>   
  5. </servlet>  

可以为此元素体提供一个整数而不是使用一个空的load-on-startup。想法是服务器应该在装载较大数目的servlet或JSP页面之前 装载较少数目的servlet或JSP页面。例如,下面的servlet项(放置在Web应用的WEB-INF目录下的web.xml文件中的web- app元素内)将指示服务器首先装载和初始化SearchServlet,然后装载和初始化由位于Web应用的result目录中的index.jsp文 件产生的 servlet。

Java代码
  1. <servlet>   
  2. <servlet-name>Search</servlet-name>   
  3. <servlet-class>myPackage.SearchServlet</servlet-class> <!-- or jsp-file -->   
  4. <load-on-startup>1</load-on-startup>   
  5. </servlet>   
  6. <servlet>   
  7. <servlet-name>Results</servlet-name>   
  8. <servlet-class>/results/index.jsp</servlet-class> <!-- or jsp-file -->   
  9. <load-on-startup>2</load-on-startup>   
  10. </servlet>  


6 声明过滤器

servlet版本2.3引入了过滤器的概念。虽然所有支持servlet API版本2.3的服务器都支持过滤器,但为了使用与过滤器有关的元素,必须在web.xml中使用版本2.3的DTD。
过 滤器可截取和修改进入一个servlet或JSP页面的请求或从一个servlet或JSP页面发出的相应。在执行一个servlet或JSP页面之前, 必须执行第一个相关的过滤器的doFilter方法。在该过滤器对其FilterChain对象调用doFilter时,执行链中的下一个过滤器。如果没 有其他过滤器,servlet或JSP页面被执行。过滤器具有对到来的ServletRequest对象的全部访问权,因此,它们可以查看客户机名、查找 到来的cookie等。为了访问servlet或JSP页面的输出,过滤器可将响应对象包裹在一个替身对象(stand-in object)中,比方说把输出累加到一个缓冲区。在调用FilterChain对象的doFilter方法之后,过滤器可检查缓冲区,如有必要,就对它 进行修改,然后传送到客户机。
例如,程序清单5-11帝国难以了一个简单的过滤器,只要访问相关的servlet或JSP页面,它就截取请求并在标准输出上打印一个报告(开发过程中在桌面系统上运行时,大多数服务器都可以使用这个过滤器)。

程序清单5-11 ReportFilter.java

Java代码
  1. package moreservlets;   
  2.   
  3. import java.io.*;   
  4. import javax.servlet.*;   
  5. import javax.servlet.http.*;   
  6. import java.util.*;   
  7.   
  8. /** Simple filter that prints a report on the standard output
  9. * whenever the associated servlet or JSP page is accessed.
  10. * <P>
  11. * Taken from More Servlets and JavaServer Pages
  12. * from Prentice Hall and Sun Microsystems Press,
  13. * http://www.moreservlets.com/.
  14. * © 2002 Marty Hall; may be freely used or adapted.
  15. */  
  16.   
  17. public class ReportFilter implements Filter {   
  18. public void doFilter(ServletRequest request,   
  19. ServletResponse response,   
  20. FilterChain chain)   
  21. throws ServletException, IOException {   
  22. HttpServletRequest req = (HttpServletRequest)request;   
  23. System.out.println(req.getRemoteHost() +   
  24. " tried to access " +   
  25. req.getRequestURL() +   
  26. " on " + new Date() + ".");   
  27. chain.doFilter(request,response);   
  28. }   
  29.   
  30. public void init(FilterConfig config)   
  31. throws ServletException {   
  32. }   
  33.   
  34. public void destroy() {}   
  35. }  

一旦建立了一个过滤器,可以在web.xml中利用filter元素以及filter-name(任意名称)、file-class(完全限定的类 名)和(可选的)init-params子元素声明它。请注意,元素在web.xml的web-app元素中出现的次序不是任意的;允许服务器(但不是必 需的)强制所需的次序,并且实际中有些服务器也是这样做的。但这里要注意,所有filter元素必须出现在任意filter-mapping元素之前, filter-mapping元素又必须出现在所有servlet或servlet-mapping元素之前。
例如,给定上述的ReportFilter类,可在web.xml中作出下面的filter声明。它把名称Reporter与实际的类ReportFilter(位于moreservlets程序包中)相关联。

Java代码
  1. <filter>   
  2. <filter-name>Reporter</filter-name>   
  3. <filter-class>moresevlets.ReportFilter</filter-class>   
  4. </filter>  

一旦命名了一个过滤器,可利用filter-mapping元素把它与一个或多个servlet或JSP页面相关联。关于此项工作有两种选择。
首 先,可使用filter-name和servlet-name子元素把此过滤器与一个特定的servlet名(此servlet名必须稍后在相同的 web.xml文件中使用servlet元素声明)关联。例如,下面的程序片断指示系统只要利用一个定制的URL访问名为SomeServletName 的servlet或JSP页面,就运行名为Reporter的过滤器。

Java代码
  1. <filter-mapping>   
  2. <filter-name>Reporter</filter-name>   
  3. <servlet-name>SomeServletName</servlet-name>   
  4. </filter-mapping>  

其次,可利用filter-name和url-pattern子元素将过滤器与一组servlet、JSP页面或静态内容相关联。例如,相面的程序片段指示系统只要访问Web应用中的任意URL,就运行名为Reporter的过滤器。

Java代码
  1. <filter-mapping>   
  2. <filter-name>Reporter</filter-name>   
  3. <url-pattern>/*</url-pattern>   
  4. </filter-mapping>  

例如,程序清单5-12给出了将ReportFilter过滤器与名为PageName的servlet相关联的web.xml文件的一部分。名字 PageName依次又与一个名为TestPage.jsp的JSP页面以及以模式http: //host/webAppPrefix/UrlTest2/ 开头的URL相关联。TestPage.jsp的源代码已经JSP页面命名的谈论在前面的3节"分配名称和定制的URL"中给出。事实上,程序清单5- 12中的servlet和servlet-name项从该节原封不动地拿过来的。给定这些web.xml项,可看到下面的标准输出形式的调试报告(换行是 为了容易阅读)。
audit.irs.gov tried to access
http://mycompany.com/deployDemo/UrlTest2/business/tax-plan.html
on Tue Dec 25 13:12:29 EDT 2001.

程序清单5-12 Web.xml(说明filter用法的摘录)

Java代码
  1. <?xml version="1.0" encoding="ISO-8859-1"?>   
  2. <!DOCTYPE web-app   
  3. PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"  
  4. "http://java.sun.com/dtd/web-app_2_3.dtd">   
  5.   
  6. <web-app>   
  7. <filter>   
  8. <filter-name>Reporter</filter-name>   
  9. <filter-class>moresevlets.ReportFilter</filter-class>   
  10. </filter>   
  11. <!-- ... -->   
  12. <filter-mapping>   
  13. <filter-name>Reporter</filter-name>   
  14. <servlet-name>PageName</servlet-name>   
  15. </filter-mapping>   
  16. <!-- ... -->   
  17. <servlet>   
  18. <servlet-name>PageName</servlet-name>   
  19. <jsp-file>/RealPage.jsp</jsp-file>   
  20. </servlet>   
  21. <!-- ... -->   
  22. <servlet-mapping>   
  23. <servlet-name> PageName </servlet-name>   
  24. <url-pattern>/UrlTest2/*</url-pattern>   
  25. </servlet-mapping>   
  26. <!-- ... -->   
  27. </web-app>  


7 指定欢迎页 假如用户提供了一个像http: //host/webAppPrefix/directoryName/ 这样的包含一个目录名但没有包含文件名的URL,会发生什么事情呢?用户能得到一个目录表?一个错误?还是标准文件的内容?如果得到标准文件内容,是 index.html、index.jsp、default.html、default.htm或别的什么东西呢? Welcome-file-list 元素及其辅助的welcome-file元素解决了这个模糊的问题。例如,下面的web.xml项指出,如果一个URL给出一个目录名但未给出文件名,服 务器应该首先试用index.jsp,然后再试用index.html。如果两者都没有找到,则结果有赖于所用的服务器(如一个目录列表)。 虽然许多服务器缺省遵循这种行为,但不一定必须这样。因此,明确地使用welcom-file-list保证可移植性是一种良好的习惯。

8 指定处理错误的页面 现在我了解到,你在开发servlet和JSP页面时从不会犯错误,而且你的所有页面是那样的清晰,一般的程序员都不会被它们的搞糊涂。但是,是人总会犯 错误的,用户可能会提供不合规定的参数,使用不正确的URL或者不能提供必需的表单字段值。除此之外,其它开发人员可能不那么细心,他们应该有些工具来克 服自己的不足。 error-page元素就是用来克服这些问题的。它有两个可能的子元素,分别是:error-code和exception- type。第一个子元素error-code指出在给定的HTTP错误代码出现时使用的URL。第二个子元素excpetion-type指出在出现某个 给定的Java异常但未捕捉到时使用的URL。error-code和exception-type都利用location元素指出相应的URL。此 URL必须以/开始。location所指出的位置处的页面可通过查找HttpServletRequest对象的两个专门的属性来访问关于错误的信息, 这两个属性分别是:javax.servlet.error.status_code和javax.servlet.error.message。 可回忆一下,在web.xml内以正确的次序声明web-app的子元素很重要。这里只要记住,error-page出现在web.xml文件的末尾附 近,servlet、servlet-name和welcome-file-list之后即可。

8.1 error-code元素 为了更好地了解error-code元素的值,可考虑一下如果不正确地输入文件名,大多数站点会作出什么反映。这样做一般会出现一个404错误信息,它表 示不能找到该文件,但几乎没提供更多有用的信息。另一方面,可以试一下在www.microsoft.com、www.ibm.com 处或者特别是在www.bea.com 处输出未知的文件名。这是会得出有用的消息,这些消息提供可选择的位置,以便查找感兴趣的页面。提供这样有用的错误页面对于Web应用来说是很有价值得。 事实上rm-error-page子元素)。由form-login-page给出的HTML表单必须具有一个j_security_check的 ACTION属性、一个名为j_username的用户名文本字段以及一个名为j_password的口令字段。 例如,程序清单5-19指示服务器使用基于表单的验证。Web应用的顶层目录中的一个名为login.jsp的页面将收集用户名和口令,并且失败的登陆将 由相同目录中名为login-error.jsp的页面报告。 程序清单5-19 web.xml(说明login-config的摘录)

9.2 限制对Web资源的访问 现在,可以指示服务器使用何种验证方法了。"了不起,"你说道,"除非我能指定一个来收到保护的 URL,否则没有多大用处。"没错。指出这些URL并说明他们应该得到何种保护正是security-constriaint元素的用途。此元素在 web.xml中应该出现在login-config的紧前面。它包含是个可能的子元素,分别是:web-resource-collection、 auth-constraint、user-data-constraint和display-name。下面各小节对它们进行介绍。 l web-resource-collection 此元素确定应该保护的资源。所有security-constraint元素都必须包含至少一个web-resource-collection项。此元 素由一个给出任意标识名称的web-resource-name元素、一个确定应该保护的URL的url-pattern元素、一个指出此保护所适用的 HTTP命令(GET、POST等,缺省为所有方法)的http-method元素和一个提供资料的可选description元素组成。例如,下面的 Web-resource-collection项(在security-constratint元素内)指出Web应用的proprietary目录中 所有文档应该受到保护。 重要的是应该注意到,url-pattern仅适用于直接访问这些资源的客户机。特别是,它不适合于通过MVC体系结构利用 RequestDispatcher来访问的页面,或者不适合于利用类似jsp:forward的手段来访问的页面。这种不匀称如果利用得当的话很有好 处。例如,servlet可利用MVC体系结构查找数据,把它放到bean中,发送请求到从bean中提取数据的JSP页面并显示它。我们希望保证决不直 接访问受保护的JSP页面,而只是通过建立该页面将使用的bean的servlet来访问它。url-pattern和auth-contraint元素 可通过声明不允许任何用户直接访问JSP页面来提供这种保证。但是,这种不匀称的行为可能让开发人员放松警惕,使他们偶然对应受保护的资源提供不受限制的 访问。 l auth-constraint 尽管web-resource-collention元素质出了哪些URL应该受到保护,但是auth-constraint元素却指出哪些用户应该具有 受保护资源的访问权。此元素应该包含一个或多个标识具有访问权限的用户类别role- name元素,以及包含(可选)一个描述角色的description元素。例如,下面web.xml中的security-constraint元素部 门规定只有指定为Administrator或Big Kahuna(或两者)的用户具有指定资源的访问权。 重要的是认识到,到此为止,这个过程的可移植部分结束了。服务器怎样确定哪些用户处于任何角色以及它怎样存放用户的口令,完全有赖于具体的系统。 例如,Tomcat使用install_dir/conf/tomcat-users.xml将用户名与角色名和口令相关联,正如下面例子中所示,它指出 用户joe(口令bigshot)和jane(口令enaj)属于administrator和kahuna角色。 l user-data-constraint 这个可选的元素指出在访问相关资源时使用任何传输层保护。它必须包含一个transport-guarantee子元素(合法值为NONE、 INTEGRAL或CONFIDENTIAL),并且可选地包含一个description元素。transport-guarantee为NONE值将 对所用的通讯协议不加限制。INTEGRAL值表示数据必须以一种防止截取它的人阅读它的方式传送。虽然原理上(并且在未来的HTTP版本中),在 INTEGRAL和CONFIDENTIAL之间可能会有差别,但在当前实践中,他们都只是简单地要求用SSL。例如,下面指示服务器只允许对相关资源做 HTTPS连接: l display-name security-constraint的这个很少使用的子元素给予可能由GUI工具使用的安全约束项一个名称。 9.3 分配角色名 迄今为止,讨论已经集中到完全由容器(服务器)处理的安全问题之上了。但servlet以及JSP页面也能够处理它们自己的安全问题。 例如,容器可能允许用户从bigwig或bigcheese角色访问一个显示主管人员额外紧贴的页面,但只允许bigwig用户修改此页面的参数。完成这 种更细致的控制的一种常见方法是调用HttpServletRequset的isUserInRole方法,并据此修改访问。 Servlet的 security-role-ref子元素提供出现在服务器专用口令文件中的安全角色名的一个别名。例如,假如编写了一个调用 request.isUserInRole("boss")的servlet,但后来该servlet被用在了一个其口令文件调用角色manager而不 是boss的服务器中。下面的程序段使该servlet能够使用这两个名称中的任何一个。 也可以在web-app内利用security-role元素提供将出现在role-name元素中的所有安全角色的一个全局列表。分别地生命角色使高级 IDE容易处理安全信息。 10 控制会话超时 如果某个会话在一定的时间内未被访问,服务器可把它扔掉以节约内存。可利用HttpSession的setMaxInactiveInterval方法直 接设置个别会话对象的超时值。如果不采用这种方法,则缺省的超时值由具体的服务器决定。但可利用session-config和session- timeout元素来给出一个适用于所有服务器的明确的超时值。超时值的单位为分钟,因此,下面的例子设置缺省会话超时值为三个小时(180分钟)。

11 Web应用的文档化 越来越多的开发环境开始提供servlet和JSP的直接支持。例子有Borland Jbuilder Enterprise Edition、Macromedia UltraDev、Allaire JRun Studio(写此文时,已被Macromedia收购)以及IBM VisuaAge for Java等。 大量的web.xml元素不仅是为服务器设计的,而且还是为可视开发环境设计的。它们包括icon、display-name和discription 等。 可回忆一下,在web.xml内以适当地次序声明web-app子元素很重要。不过,这里只要记住icon、display-name和 description是web.xml的web-app元素内的前三个合法元素即可。 l icon icon元素指出GUI工具可用来代表Web应用的一个和两个图像文件。可利用small-icon元素指定一幅16 x 16的GIF或JPEG图像,用large-icon元素指定一幅32 x 32的图像。下面举一个例子: l display-name display-name元素提供GUI工具可能会用来标记此Web应用的一个名称。下面是个例子。 l description description元素提供解释性文本,如下所示: 12 关联文件与MIME类型 服务器一般都具有一种让Web站点管理员将文件扩展名与媒体相关联的方法。例如,将会自动给予名为mom.jpg的文件一个image/jpeg的 MIME 类型。但是,假如你的Web应用具有几个不寻常的文件,你希望保证它们在发送到客户机时分配为某种MIME类型。mime-mapping元素(具有 extension和mime-type子元素)可提供这种保证。例如,下面的代码指示服务器将application/x-fubar的MIME类型分 配给所有以.foo结尾的文件。 或许,你的Web应用希望重载(override)标准的映射。例如,下面的代码将告诉服务器在发送到客户机时指定.ps文件作为纯文本 (text/plain)而不是作为PostScript(application/postscript)。

13 定位TLD JSP taglib元素具有一个必要的uri属性,它给出一个TLD(Tag Library Descriptor)文件相对于Web应用的根的位置。TLD文件的实际名称在发布新的标签库版本时可能会改变,但我们希望避免更改所有现有JSP页 面。此外,可能还希望使用保持taglib元素的简练性的一个简短的uri。这就是部署描述符文件的taglib元素派用场的所在了。Taglib包含两 个子元素:taglib-uri和taglib-location。taglib-uri元素应该与用于JSP taglib元素的uri属性的东西相匹配。Taglib-location元素给出TLD文件的实际位置。例如,假如你将文件chart-tags- 1.3beta.tld放在WebApp/WEB-INF/tlds中。现在,假如web.xml在web-app元素内包含下列内容。 给出这个说明后,JSP页面可通过下面的简化形式使用标签库。 14 指定应用事件监听程序 应用事件监听器程序是建立或修改servlet环境或会话对象时通知的类。它们是servlet规范的版本2.3中的新内容。这里只简单地说明用来向 Web应用注册一个监听程序的web.xml的用法。 注册一个监听程序涉及在web.xml的web-app元素内放置一个listener元素。在listener元素内,listener-class元 素列出监听程序的完整的限定类名,如下所示: <listener> 虽然listener元素的结构很简单,但请不要忘记,必须正确地给出web-app元素内的子元素的次序。listener元素位于所有的 servlet 元素之前以及所有filter-mapping元素之后。此外,因为应用生存期监听程序是serlvet规范的2.3版本中的新内容,所以必须使用 web.xml DTD的2.3版本,而不是2.2版本。 例如,程序清单5-20给出一个名为ContextReporter的简单的监听程序,只要Web应用的Servlet-Context建立(如装载 Web应用)或消除(如服务器关闭)时,它就在标准输出上显示一条消息。程序清单5-21给出此监听程序注册所需要的web.xml文件的一部分。 程序清单5-20 ContextReporterjava 程序清单5-21 web.xml(声明一个监听程序的摘录) 15 J2EE元素 本节描述用作J2EE环境组成部分的Web应用的web.xml元素。这里将提供一个简明的介绍,详细内容可以参阅 http://java.sun.com/j2ee/j2ee-1_3-fr-spec.pdf的Java 2 Plantform Enterprise Edition版本1.3规范的第5章。 l distributable distributable 元素指出,Web应用是以这样的方式编程的:即,支持集群的服务器可安全地在多个服务器上分布Web应用。例如,一个可分布的应用必须只使用 Serializable对象作为其HttpSession对象的属性,而且必须避免用实例变量(字段)来实现持续性。distributable元素直 接出现在discription元素之后,并且不包含子元素或数据,它只是一个如下的标志。 l resource-env-ref resource -env-ref元素声明一个与某个资源有关的管理对象。此元素由一个可选的description元素、一个resource-env-ref- name元素(一个相对于java:comp/env环境的JNDI名)以及一个resource-env-type元素(指定资源类型的完全限定的 类),如下所示: l env-entry env -entry元素声明Web应用的环境项。它由一个可选的description元素、一个env-entry-name元素(一个相对于java: comp/env环境JNDI名)、一个env-entry-value元素(项值)以及一个env-entry-type元素(java.lang程序 包中一个类型的完全限定类名,java.lang.Boolean、java.lang.String等)组成。下面是一个例子: l ejb-ref ejb -ref元素声明对一个EJB的主目录的应用。它由一个可选的description元素、一个ejb-ref-name元素(相对于java: comp/env的EJB应用)、一个ejb-ref-type元素(bean的类型,Entity或Session)、一个home元素(bean的主 目录接口的完全限定名)、一个remote元素(bean的远程接口的完全限定名)以及一个可选的ejb-link元素(当前bean链接的另一个 bean的名称)组成。 l ejb-local-ref ejb-local-ref元素声明一个EJB的本地主目录的引用。除了用local-home代替home外,此元素具有与ejb-ref元素相同的属 性并以相同的方式使用。



posted @ 2009-04-24 17:17 二胡 阅读(233) | 评论 (0)编辑 收藏

转 http://blog.csdn.net/fenglibing/archive/2009/03/19/4005446.aspx

3.2 定义定制的URL
大多数服务器具有一个缺省的serlvet URL:
http://host /webAppPrefix/servlet/packageName.ServletName。虽然在开发中使用这个URL很方便,但是我们常常会希望 另一个URL用于部署。例如,可能会需要一个出现在Web应用顶层的URL(如,http: //host/webAppPrefix/Anyname),并且在此URL中没有servlet项。位于顶层的URL简化了相对URL的使用。此外,对 许多开发人员来说,顶层URL看上去比更长更麻烦的缺省URL更简短。
事实上,有时需要使用定制的URL。比如,你可能想关闭缺省URL映射,以便更好地强制实施安全限制或防止用户意外地访问无初始化参数的servlet。如果你禁止了缺省的URL,那么你怎样访问servlet呢?这时只有使用定制的URL了。
为 了分配一个定制的URL,可使用servlet-mapping元素及其servlet-name和url-pattern子元素。Servlet- name元素提供了一个任意名称,可利用此名称引用相应的servlet;url-pattern描述了相对于Web应用的根目录的URL。url- pattern元素的值必须以斜杠(/)起始。
下面给出一个简单的web.xml摘录,它允许使用URL http://host/webAppPrefix/UrlTest而不是http://host/webAppPrefix/servlet/Test或
http: //host/webAppPrefix/servlet/moreservlets.TestServlet。请注意,仍然需要XML头、 DOCTYPE声明以及web-app封闭元素。此外,可回忆一下,XML元素出现地次序不是随意的。特别是,需要把所有servlet元素放在所有 servlet-mapping元素之前。
Java代码
  1. <servlet>   
  2. <servlet-name>Test</servlet-name>   
  3. <servlet-class>moreservlets.TestServlet</servlet-class>   
  4. </servlet>   
  5. <!-- ... -->   
  6. <servlet-mapping>   
  7. <servlet-name>Test</servlet-name>   
  8. <url-pattern>/UrlTest</url-pattern>   
  9. </servlet-mapping>  

URL模式还可以包含通配符。例如,下面的小程序指示服务器发送所有以Web应用的URL前缀开始,以..asp结束的请求到名为BashMS的servlet。

Java代码
  1. <servlet>   
  2. <servlet-name>BashMS</servlet-name>   
  3. <servlet-class>msUtils.ASPTranslator</servlet-class>   
  4. </servlet>   
  5. <!-- ... -->   
  6. <servlet-mapping>   
  7. <servlet-name>BashMS</servlet-name>   
  8. <url-pattern>/*.asp</url-pattern>   
  9. </servlet-mapping>  


3.3 命名JSP页面
因为JSP页面要转换成sevlet,自然希望就像命名servlet一样命名JSP页面。毕竟,JSP 页面可能会从初始化参数、安全设置或定制的URL中受益,正如普通的serlvet那样。虽然JSP页面的后台实际上是servlet这句话是正确的,但 存在一个关键的猜疑:即,你不知道JSP页面的实际类名(因为系统自己挑选这个名字)。因此,为了命名JSP页面,可将jsp-file元素替换为 servlet-calss元素,如下所示:

Java代码
  1. <servlet>   
  2. <servlet-name>Test</servlet-name>   
  3. <jsp-file>/TestPage.jsp</jsp-file>   
  4. </servlet>  

命名JSP页面的原因与命名servlet的原因完全相同:即为了提供一个与定制设置(如,初始化参数和安全设置)一起使用的名称,并且,以便能更 改激活 JSP页面的URL(比方说,以便多个URL通过相同页面得以处理,或者从URL中去掉.jsp扩展名)。但是,在设置初始化参数时,应该注意,JSP页 面是利用jspInit方法,而不是init方法读取初始化参数的。
例如,程序清单5-3给出一个名为TestPage.jsp的简单JSP 页面,它的工作只是打印出用来激活它的URL的本地部分。TestPage.jsp放置在deployDemo应用的顶层。程序清单5-4给出了用来分配 一个注册名PageName,然后将此注册名与http://host/webAppPrefix/UrlTest2/anything 形式的URL相关联的web.xml文件(即,deployDemo/WEB-INF/web.xml)的一部分。

程序清单5-3 TestPage.jsp

Java代码
  1. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">   
  2. <HTML>   
  3. <HEAD>   
  4. <TITLE>   
  5. JSP Test Page   
  6. </TITLE>   
  7. </HEAD>   
  8. <BODY BGCOLOR="#FDF5E6">   
  9. <H2>URI: <%= request.getRequestURI() %></H2>   
  10. </BODY>   
  11. </HTML>  



程序清单5-4 web.xml(说明JSP页命名的摘录)

Java代码
  1. <?xml version="1.0" encoding="ISO-8859-1"?>   
  2. <!DOCTYPE web-app   
  3. PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"  
  4. "http://java.sun.com/dtd/web-app_2_3.dtd">   
  5.   
  6. <web-app>   
  7. <!-- ... -->   
  8. <servlet>   
  9. <servlet-name>PageName</servlet-name>   
  10. <jsp-file>/TestPage.jsp</jsp-file>   
  11. </servlet>   
  12. <!-- ... -->   
  13. <servlet-mapping>   
  14. <servlet-name> PageName </servlet-name>   
  15. <url-pattern>/UrlTest2/*</url-pattern>   
  16. </servlet-mapping>   
  17. <!-- ... -->   
  18. </web-app>  



4 禁止激活器servlet

对servlet或JSP页面建立定制URL的一个原因是,这样做可以注册从 init(servlet)或jspInit(JSP页面)方法中读取得初始化参数。但是,初始化参数只在是利用定制URL模式或注册名访问 servlet或JSP页面时可以使用,用缺省URL http://host/webAppPrefix/servlet/ServletName 访问时不能使用。因此,你可能会希望关闭缺省URL,这样就不会有人意外地调用初始化servlet了。这个过程有时称为禁止激活器servlet,因为 多数服务器具有一个用缺省的servlet URL注册的标准servlet,并激活缺省的URL应用的实际servlet。
有两种禁止此缺省URL的主要方法:
l 在每个Web应用中重新映射/servlet/模式。
l 全局关闭激活器servlet。
重 要的是应该注意到,虽然重新映射每个Web应用中的/servlet/模式比彻底禁止激活servlet所做的工作更多,但重新映射可以用一种完全可移植 的方式来完成。相反,全局禁止激活器servlet完全是针对具体机器的,事实上有的服务器(如ServletExec)没有这样的选择。下面的讨论对每 个Web应用重新映射/servlet/ URL模式的策略。后面提供在Tomcat中全局禁止激活器servlet的详细内容。
4.1 重新映射/servlet/URL模式
在 一个特定的Web应用中禁止以http://host/webAppPrefix/servlet/ 开始的URL的处理非常简单。所需做的事情就是建立一个错误消息servlet,并使用前一节讨论的url-pattern元素将所有匹配请求转向该 servlet。只要简单地使用:
<url-pattern>/servlet/*</url-pattern>
作为servlet-mapping元素中的模式即可。
例如,程序清单5-5给出了将SorryServlet servlet(程序清单5-6)与所有以http://host/webAppPrefix/servlet/ 开头的URL相关联的部署描述符文件的一部分。

程序清单5-5 web.xml(说明JSP页命名的摘录)

Java代码
  1. <?xml version="1.0" encoding="ISO-8859-1"?>   
  2. <!DOCTYPE web-app   
  3. PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"  
  4. "http://java.sun.com/dtd/web-app_2_3.dtd">   
  5.   
  6. <web-app>   
  7. <!-- ... -->   
  8. <servlet>   
  9. <servlet-name>Sorry</servlet-name>   
  10. <servlet-class>moreservlets.SorryServlet</servlet-class>   
  11. </servlet>   
  12. <!-- ... -->   
  13. <servlet-mapping>   
  14. <servlet-name> Sorry </servlet-name>   
  15. <url-pattern>/servlet/*</url-pattern>   
  16. </servlet-mapping>   
  17. <!-- ... -->   
  18. </web-app>  



程序清单5-6 SorryServlet.java

Java代码
  1. package moreservlets;   
  2.   
  3. import java.io.*;   
  4. import javax.servlet.*;   
  5. import javax.servlet.http.*;   
  6.   
  7. /** Simple servlet used to give error messages to
  8. * users who try to access default servlet URLs
  9. * (i.e., http://host/webAppPrefix/servlet/ServletName)
  10. * in Web applications that have disabled this
  11. * behavior.
  12. * <P>
  13. * Taken from More Servlets and JavaServer Pages
  14. * from Prentice Hall and Sun Microsystems Press,
  15. * http://www.moreservlets.com/.
  16. * © 2002 Marty Hall; may be freely used or adapted.
  17. */  
  18.   
  19. public class SorryServlet extends HttpServlet {   
  20. public void doGet(HttpServletRequest request,   
  21. HttpServletResponse response)   
  22. throws ServletException, IOException {   
  23. response.setContentType("text/html");   
  24. PrintWriter out = response.getWriter();   
  25. String title = "Invoker Servlet Disabled.";   
  26. out.println(ServletUtilities.headWithTitle(title) +   
  27. "<BODY BGCOLOR=\"#FDF5E6\">\n" +   
  28. "<H2>" + title + "</H2>\n" +   
  29. "Sorry, access to servlets by means of\n" +   
  30. "URLs that begin with\n" +   
  31. "http://host/webAppPrefix/servlet/\n" +   
  32. "has been disabled.\n" +   
  33. "</BODY></HTML>");   
  34. }   
  35.   
  36. public void doPost(HttpServletRequest request,   
  37. HttpServletResponse response)   
  38. throws ServletException, IOException {   
  39. doGet(request, response);   
  40. }   
  41. }  


4.2 全局禁止激活器:Tomcat
Tomcat 4中用来关闭缺省URL的方法与Tomcat 3中所用的很不相同。下面介绍这两种方法:
1.禁止激活器: Tomcat 4
Tomcat 4用与前面相同的方法关闭激活器servlet,即利用web.xml中的url-mapping元素进行关闭。不同之处在于Tomcat使用了放在 install_dir/conf中的一个服务器专用的全局web.xml文件,而前面使用的是存放在每个Web应用的WEB-INF目录中的标准 web.xml文件。
因此,为了在Tomcat 4中关闭激活器servlet,只需在install_dir/conf/web.xml中简单地注释出/servlet/* URL映射项即可,如下所示:

Java代码
  1. <!--   
  2. <servlet-mapping>   
  3. <servlet-name>invoker</servlet-name>   
  4. <url-pattern>/servlet/*</url-pattern>   
  5. </servlet-mapping>   
  6. -->  

再次提醒,应该注意这个项是位于存放在install_dir/conf的Tomcat专用的web.xml文件中的,此文件不是存放在每个Web应用的WEB-INF目录中的标准web.xml。
2.禁止激活器:Tomcat3
在Apache Tomcat的版本3中,通过在install_dir/conf/server.xml中注释出InvokerInterceptor项全局禁止缺省 servlet URL。例如,下面是禁止使用缺省servlet URL的server.xml文件的一部分。

Java代码
  1. <!--   
  2. <RequsetInterceptor   
  3. className="org.apache.tomcat.request.InvokerInterceptor"  
  4. debug="0" prefix="/servlet/" />   
  5. -->  


5 初始化和预装载servlet与JSP页面

这里讨论控制servlet和JSP页面的启动行为的方法。特别是,说明了怎样分配初始化参数以及怎样更改服务器生存期中装载servlet和JSP页面的时刻。
5.1 分配servlet初始化参数
利 用init-param元素向servlet提供初始化参数,init-param元素具有param-name和param-value子元素。例如, 在下面的例子中,如果initServlet servlet是利用它的注册名(InitTest)访问的,它将能够从其方法中调用getServletConfig(). getInitParameter("param1")获得"Value 1",调用getServletConfig().getInitParameter("param2")获得"2"。

Java代码
  1. <servlet>   
  2. <servlet-name>InitTest</servlet-name>   
  3. <servlet-class>moreservlets.InitServlet</servlet-class>   
  4. <init-param>   
  5. <param-name>param1</param-name>   
  6. <param-value>value1</param-value>   
  7. </init-param>   
  8. <init-param>   
  9. <param-name>param2</param-name>   
  10. <param-value>2</param-value>   
  11. </init-param>   
  12. </servlet>  

在涉及初始化参数时,有几点需要注意:
l 返回值。GetInitParameter的返回值总是一个String。因此,在前一个例子中,可对param2使用Integer.parseInt获得一个int。
l JSP中的初始化。JSP页面使用jspInit而不是init。JSP页面还需要使用jsp-file元素代替servlet-class。
l 缺省URL。初始化参数只在通过它们的注册名或与它们注册名相关的定制URL模式访问Servlet时可以使用。因此,在这个例子中,param1和 param2初始化参数将能够在使用URL http://host/webAppPrefix/servlet/InitTest 时可用,但在使用URL http://host/webAppPrefix/servlet/myPackage.InitServlet 时不能使用。
例如,程序清单5-7给出一个名为InitServlet的简单servlet,它使用init方法设置firstName和emailAddress字段。程序清单5-8给出分配名称InitTest给servlet的web.xml文件。
程序清单5-7 InitServlet.java

Java代码
  1. package moreservlets;   
  2.   
  3. import java.io.*;   
  4. import javax.servlet.*;   
  5. import javax.servlet.http.*;   
  6.   
  7. /** Simple servlet used to illustrate servlet
  8. * initialization parameters.
  9. * <P>
  10. * Taken from More Servlets and JavaServer Pages
  11. * from Prentice Hall and Sun Microsystems Press,
  12. * http://www.moreservlets.com/.
  13. * © 2002 Marty Hall; may be freely used or adapted.
  14. */  
  15.   
  16. public class InitServlet extends HttpServlet {   
  17. private String firstName, emailAddress;   
  18.   
  19. public void init() {   
  20. ServletConfig config = getServletConfig();   
  21. firstName = config.getInitParameter("firstName");   
  22. emailAddress = config.getInitParameter("emailAddress");   
  23. }   
  24.   
  25. public void doGet(HttpServletRequest request,   
  26. HttpServletResponse response)   
  27. throws ServletException, IOException {   
  28. response.setContentType("text/html");   
  29. PrintWriter out = response.getWriter();   
  30. String uri = request.getRequestURI();   
  31. out.println(ServletUtilities.headWithTitle("Init Servlet") +   
  32. "<BODY BGCOLOR=\"#FDF5E6\">\n" +   
  33. "<H2>Init Parameters:</H2>\n" +   
  34. "<UL>\n" +   
  35. "<LI>First name: " + firstName + "\n" +   
  36. "<LI>Email address: " + emailAddress + "\n" +   
  37. "</UL>\n" +   
  38. "</BODY></HTML>");   
  39. }   
  40. }  


程序清单5-8 web.xml(说明初始化参数的摘录)

Java代码
  1. <?xml version="1.0" encoding="ISO-8859-1"?>   
  2. <!DOCTYPE web-app   
  3. PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"  
  4. "http://java.sun.com/dtd/web-app_2_3.dtd">   
  5.   
  6. <web-app>   
  7. <!-- ... -->   
  8. <servlet>   
  9. <servlet-name>InitTest</servlet-name>   
  10. <servlet-class>moreservlets.InitServlet</servlet-class>   
  11. <init-param>   
  12. <param-name>firstName</param-name>   
  13. <param-value>Larry</param-value>   
  14. </init-param>   
  15. <init-param>   
  16. <param-name>emailAddress</param-name>   
  17. <param-value>Ellison@Microsoft.com</param-value>   


posted @ 2009-04-24 17:15 二胡 阅读(224) | 评论 (0)编辑 收藏

转 http://blog.csdn.net/fenglibing/archive/2009/03/19/4005441.aspx

转自http://elevenet.javaeye.com/blog/67862

这篇文章还是不错的,有些老,有些地址还是直接翻译的。不是很准确。

关键字: J2EE

1 定义头和根元素

部署描述符文件就像所有XML文件一样,必须以一个XML头开始。这个头声明可以使用的XML版本并给出文件的字符编码。
DOCYTPE声明必须立即出现在此头之后。这个声明告诉服务器适用的servlet规范的版本(如2.2或2.3)并指定管理此文件其余部分内容的语法的DTD(Document Type Definition,文档类型定义)。
所有部署描述符文件的顶层(根)元素为web-app。请注意,XML元素不像HTML,他们是大小写敏感的。因此,web-App和WEB-APP都是不合法的,web-app必须用小写。

2 部署描述符文件内的元素次序

XML 元素不仅是大小写敏感的,而且它们还对出现在其他元素中的次序敏感。例如,XML头必须是文件中的第一项,DOCTYPE声明必须是第二项,而web- app元素必须是第三项。在web-app元素内,元素的次序也很重要。服务器不一定强制要求这种次序,但它们允许(实际上有些服务器就是这样做的)完全 拒绝执行含有次序不正确的元素的Web应用。这表示使用非标准元素次序的web.xml文件是不可移植的。
下面的列表给出了所有可直接出现在web-app元素内的合法元素所必需的次序。例如,此列表说明servlet元素必须出现在所有servlet-mapping元素之前。请注意,所有这些元素都是可选的。因此,可以省略掉某一元素,但不能把它放于不正确的位置。
l icon                          icon 元素指出IDE和GUI工具用来表示Web应用的一个和两个图像文件的位置。
l display-name           display-name元素提供GUI工具可能会用来标记这个特定的Web应用的一个名称。
l description               description元素给出与此有关的说明性文本。
l context-param          context-param元素声明应用范围内的初始化参数。
l filter                         过滤器元素将一个名字与一个实现javax.servlet.Filter接口的类相关联。
l filter-mapping          一旦命名了一个过滤器,就要利用filter-mapping元素把它与一个或多个servlet或JSP 页面相关联。
l listener                    servlet API的版本2.3增加了对事件监听程序的支持,事件监听程序在建立、修改和删除会话或servlet环境时得到通知。Listener元素指出事件监听程序类。
l servlet                    在向servlet或JSP页面制定初始化参数或定制URL时,必须首先命名servlet或JSP页面。Servlet元素就是用来完成此项任务的。
l servlet-mapping      服务器一般为servlet提供一个缺省的URL:http://host/webAppPrefix/servlet/ServletName。但是,常常会更改这个URL,以便servlet可以访问初始化参数或更容易地处理相对URL。在更改缺省URL时,使用servlet-mapping元素。
l session-config         如果某个会话在一定时间内未被访问,服务器可以抛弃它以节省内存。可通过使用HttpSession的setMaxInactiveInterval方法 明确设置单个会话对象的超时值,或者可利用session-config元素制定缺省超时值。
l mime-mapping         如果Web应用具有想到特殊的文件,希望能保证给他们分配特定的MIME类型,则mime-mapping元素提供这种保证。
l welcom-file-list         welcome-file-list元素指示服务器在收到引用一个目录名而不是文件名的URL时,使用哪个文件。
l error-page               error-page元素使得在返回特定HTTP状态代码时,或者特定类型的异常被抛出时,能够制定将要显示的页面。
l taglib                        taglib元素对标记库描述符文件(Tag Libraryu Descriptor file)指定别名。此功能使你能够更改TLD文件的位置,而不用编辑使用这些文件的JSP页面。
l resource-env-ref      resource-env-ref元素声明与资源相关的一个管理对象。
l resource-ref             resource-ref元素声明一个资源工厂使用的外部资源。
l security-constraint    security-constraint元素制定应该保护的URL。它与login-config元素联合使用
l login-config 用login-config元素来指定服务器应该怎样给试图访问受保护页面的用户授权。它与sercurity-constraint元素联合使用。
l security-role              security-role元素给出安全角色的一个列表,这些角色将出现在servlet元素内的security-role-ref元素的role- name子元素中。分别地声明角色可使高级IDE处理安全信息更为容易。
l env-entry                  env-entry元素声明Web应用的环境项。
l ejb-ref                      ejb-ref元素声明一个EJB的主目录的引用。
l ejb-local-ref              ejb-local-ref元素声明一个EJB的本地主目录的应用。

3 分配名称和定制的UL

在web.xml中完成的一个最常见的任务是对servlet或JSP页面给出名称和定制的URL。用servlet元素分配名称,使用servlet-mapping元素将定制的URL与刚分配的名称相关联。
3.1 分配名称
为 了提供初始化参数,对servlet或JSP页面定义一个定制URL或分配一个安全角色,必须首先给servlet或JSP页面一个名称。可通过 servlet元素分配一个名称。最常见的格式包括servlet-name和servlet-class子元素(在web-app元素内),如下所示:
Java代码
  1. <servlet>   
  2. <servlet-name>Test</servlet-name>   
  3. <servlet-class>moreservlets.TestServlet</servlet-class>   
  4. </servlet>   
这 表示位于WEB-INF/classes/moreservlets/TestServlet的servlet已经得到了注册名Test。给 servlet一个名称具有两个主要的含义。首先,初始化参数、定制的URL模式以及其他定制通过此注册名而不是类名引用此servlet。其次,可在 URL而不是类名中使用此名称。因此,利用刚才给出的定义,URL http://host/webAppPrefix/servlet/Test 可用于 http://host/webAppPrefix/servlet/moreservlets.TestServlet 的场所。
请 记住:XML元素不仅是大小写敏感的,而且定义它们的次序也很重要。例如,web-app元素内所有servlet元素必须位于所有servlet- mapping元素(下一小节介绍)之前,而且还要位于5.6节和5.11节讨论的与过滤器或文档相关的元素(如果有的话)之前。类似地,servlet 的servlet-name子元素也必须出现在servlet-class之前。5.2节"部署描述符文件内的元素次序"将详细介绍这种必需的次序。
例 如,程序清单5-1给出了一个名为TestServlet的简单servlet,它驻留在moreservlets程序包中。因为此servlet是扎根 在一个名为deployDemo的目录中的Web应用的组成部分,所以TestServlet.class放在deployDemo/WEB- INF/classes/moreservlets中。程序清单5-2给出将放置在deployDemo/WEB-INF/内的web.xml文件的一部 分。此web.xml文件使用servlet-name和servlet-class元素将名称Test与TestServlet.class相关联。图 5-1和图5-2分别显示利用缺省URL和注册名调用TestServlet时的结果。

程序清单5-1 TestServlet.java
Java代码
  1. package moreservlets;   
  2.   
  3. import java.io.*;   
  4. import javax.servlet.*;   
  5. import javax.servlet.http.*;   
  6.   
  7. /** Simple servlet used to illustrate servlet naming
  8. * and custom URLs.
  9. * <P>
  10. * Taken from More Servlets and JavaServer Pages
  11. * from Prentice Hall and Sun Microsystems Press,
  12. * http://www.moreservlets.com/.
  13. * © 2002 Marty Hall; may be freely used or adapted.
  14. */  
  15.   
  16. public class TestServlet extends HttpServlet {   
  17. public void doGet(HttpServletRequest request,   
  18. HttpServletResponse response)   
  19. throws ServletException, IOException {   
  20. response.setContentType("text/html");   
  21. PrintWriter out = response.getWriter();   
  22. String uri = request.getRequestURI();   
  23. out.println(ServletUtilities.headWithTitle("Test Servlet") +   
  24. "<BODY BGCOLOR=\"#FDF5E6\">\n" +   
  25. "<H2>URI: " + uri + "</H2>\n" +   
  26. "</BODY></HTML>");   
  27. }   
  28. }  

程序清单5-2 web.xml(说明servlet名称的摘录)
Java代码
  1. <?xml version="1.0" encoding="ISO-8859-1"?>   
  2. <!DOCTYPE web-app   
  3. PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"  
  4. "http://java.sun.com/dtd/web-app_2_3.dtd">   
  5.   
  6. <web-app>   
  7. <!-- … -->   
  8. <servlet>   
  9. <servlet-name>Test</servlet-name>   
  10. <servlet-class>moreservlets.TestServlet</servlet-class>   
  11. </servlet>   
  12. <!-- … -->   
  13. </web-app>  


posted @ 2009-04-24 17:13 二胡 阅读(306) | 评论 (0)编辑 收藏

转 http://www.cnblogs.com/land/archive/2009/04/20/1439766.html

JavaScript中void(0)的含义:
JavaScript中void是一个操作符,该操作符指定要计算一个表达式但是不返回值。
void 操作符用法格式如下:
1. javascript:void (expression)
2. javascript:void expression
expression 是一个要计算的 JavaScript 标准的表达式。表达式外侧的圆括号是可选的,但是写上去是一个好习惯。我们可以使用 void 操作符指定超级链接。表达式会被计算但是不会在当前文档处装入任何内容。面的代码创建了一个超级链接,当用户点击以后不会发生任何事。当用户点击链接 时,void(0) 计算为 0,但在 JavaScript 上没有任何效果。
<a href=”javascript:void(0)”>单击此处什么也不会发生</a>
也就是说,要执行某些处理,但是不整体刷新页面的情况下,可以使用void(0),但是在需要对页面进行refresh的情况下,那就要仔细了。

在调用自这自定义的JS函数时,如果我们使用<a href="#" onclick="method;">click</a>时,虽然方法可以执行,但是如果页面含有滚动条,会自动滚动到页面的顶端,如 果这个时候我们使用<a href="javascript:void(0); onclick="method;">click</a>,执行时,页面将不会发生滚动,这个对于图片切换、AJAX调用 应该非常有用



posted @ 2009-04-22 18:12 二胡 阅读(351) | 评论 (0)编辑 收藏

转 http://www.cnblogs.com/kingjiong/archive/2009/03/12/1310908.html

SQL中char、varchar、text和nchar、nvarchar、ntext的区别
     1、CHAR。CHAR存储定长数据很方便,CHAR字段上的索引效率级高,比如定义char(10),那么不论你存储的数据是否达到了10个字节,都要占去10个字节的空间。
     2、VARCHAR。存储变长数据,但存储效率没有CHAR高。如果一个字段可能的值是不固定长度的,我们只知道它不可能超过10个字符,把它定义为 VARCHAR(10)是最合算的。VARCHAR类型的实际长度是它的值的实际长度+1。为什么“+1”呢?这一个字节用于保存实际使用了多大的长度。
    从空间上考虑,用varchar合适;从效率上考虑,用char合适,关键是根据实际情况找到权衡点。
    3、TEXT。text存储可变长度的非Unicode数据,最大长度为2^31-1(2,147,483,647)个字符。
    4、NCHAR、NVARCHAR、NTEXT。这三种从名字上看比前面三种多了个“N”。它表示存储的是Unicode数据类型的字符。我们知 道字符中,英文字符只需要一个字节存储就足够了,但汉字众多,需要两个字节存储,英文与汉字同时存在时容易造成混乱,Unicode字符集就是为了解决字 符集这种不兼容的问题而产生的,它所有的字符都用两个字节表示,即英文字符也是用两个字节表示。nchar、nvarchar的长度是在1到4000之 间。和char、varchar比较起来,nchar、nvarchar则最多存储4000个字符,不论是英文还是汉字;而char、varchar最多 能存储8000个英文,4000个汉字。可以看出使用nchar、nvarchar数据类型时不用担心输入的字符是英文还是汉字,较为方便,但在存储英文 时数量上有些损失。
     所以一般来说,如果含有中文字符,用nchar/nvarchar,如果纯英文和数字,用char/varchar。
     如果是存储文章等大段内容的时候,纯英文和数字,用text,含有中文字符的,用ntext。

posted @ 2009-04-21 09:17 二胡 阅读(457) | 评论 (0)编辑 收藏

转 http://www.cftea.com/c/2006/12/128KWTC67M14NQJD.asp

selectinto destTbl from srcTbl
insert into destTbl(fld1, fld2) select fld1, 5 from srcTbl

以上两句都是将 srcTbl 的数据插入到 destTbl,但两句又有区别的:

  • 第一句(select into from)要求目标表(destTbl)不存在,因为在插入时会自动创建。
  • 第二句(insert into select from)要求目标表(destTbl)存在,由于目标表已经存在,所以我们除了插入源表(srcTbl)的字段外,还可以插入常量,如例中的:5。

列出所有的表名:
select name from sysobjects where type='u'

列出表中列名: 
select name from syscolumns where id=object_id(tablename)


判断临时表是否存在
 if   object_id('tempdb..#avg_tbl')   is  not null 
    print 'yes'
else
    print 'no'

posted @ 2009-04-20 13:56 二胡 阅读(221) | 评论 (0)编辑 收藏

转 http://www.cnblogs.com/xmaiC/archive/2009/04/15/1436813.html

T-SQL之SET ANSI_NULLS

      当 SET ANSI_NULLS 为 ON 时,即使 column_name 中包含空值,使用 WHERE column_name = NULL 的 SELECT 语句仍返回零行。即使 column_name 中包含非空值,使用 WHERE column_name <> NULL 的 SELECT 语句仍会返回零行。
      当 SET ANSI_NULLS 为 OFF 时,等于 (=) 和不等于 (<>) 比较运算符不遵守 ISO 标准。使用 WHERE column_name = NULL 的 SELECT 语句返回 column_name 中包含空值的行。使用 WHERE column_name <> NULL 的 SELECT 语句返回列中包含非空值的行。此外,使用 WHERE column_name <> XYZ_value 的 SELECT 语句返回所有不为 XYZ_value 也不为 NULL 的行。
      当 SET ANSI_NULLS 为 ON 时,所有对 null 值的比较均取值为 UNKNOWN。当 SET ANSI_NULLS 为 OFF 时,如果数据值为 NULL,则所有数据对空值的比较将取值为 TRUE。如果未指定 SET ANSI_NULLS,则应用当前数据库的 ANSI_NULLS 选项设置。有关 ANSI_NULLS 数据库选项的详细信息,请参阅 ALTER DATABASE (Transact-SQL) 和设置数据库选项。


仅当某个比较操作数是值为 NULL 的变量或文字 NULL 时,SET ANSI_NULLS ON 才会影响比较。如果比较双方是列或复合表达式,则该设置不会影响比较。
为使脚本按预期运行,不管 ANSI_NULLS 数据库选项或 SET ANSI_NULLS 的设置如何,请在可能包含空值的比较中使用 IS NULL 和 IS NOT NULL。
在执行分布式查询时应将 SET ANSI_NULLS 设置为 ON。
      对 计算列或索引视图创建或更改索引时,SET ANSI_NULLS 也必须为 ON。如果 SET ANSI_NULLS 为 OFF,则针对表(包含计算列或索引视图的索引)的 CREATE、UPDATE、INSERT 和 DELETE 语句将失败。SQL Server 将返回一个错误消息,该错误消息会列出所有违反所需值的 SET 选项。另外,在执行 SELECT 语句时,如果 SET ANSI_NULLS 为 OFF,则 SQL Server 将忽略计算列或视图的索引值并解析选择操作,就好像表或视图没有这样的索引一样。

实例:
--1.设置ANSI_NULLS为ON时,返回零行
SET ANSI_NULLS ON
GO
SELECT*FROM xmai WHERE id=NULL
SELECT*FROM xmai WHERE id<>NULL
执行结果:
id          name
----------- --------------------
(0 行受影响)

id          name
----------- --------------------
(0 行受影响)

--2设置ANSI_NULLS为OFF时,等于 (=) 和不等于 (<>) 比较运算符不遵守 ISO 标准。

SET ANSI_NULLS OFF
GO
SELECT*FROM xmai WHERE id=NULL
SELECT*FROM xmai WHERE id<>NULL
执行结果:
id          name
----------- --------------------
NULL        XMAI                
(
1 行受影响)

id          name
----------- --------------------
1984        NULL
2009        HJ                  
2007        MAIHUASHA           
(
3 行受影响)

--3.使用WHERE column_name<>XYZ_value的SELECT语句返回所有不为XYZ_value也不为NULL的行。
SELECT*FROM xmai WHERE id<>1984
执行结果:
id          name
----------- --------------------
2009        HJ                  
2007        MAIHUASHA           

(
2 行受影响)


posted @ 2009-04-17 17:04 二胡 阅读(551) | 评论 (0)编辑 收藏

转 http://blog.csdn.net/zhaowei001/archive/2007/12/29/2001800.aspx

作者:EasyJF开源团队 大峡

一、简介

在Java Web应用程中,特别是网站开发中,我们有时候需要为应用程序增加一个入侵检测程序来防止恶意刷新的功能,防止非法用户不断的往Web应用中重复发送数 据。当然,入侵检测可以用很多方法实现,包括软件、硬件防火墙,入侵检测的策略也很多。在这里我们主要介绍的是Java Web应用程序中通过软件的方式实现简单的入侵检测及防御。

  该方法的实现原理很简单,就是用户访问Web系统时记录每个用户的信息,然后进行对照,并根据设定的策略(比如:1秒钟刷新页面10次)判断用户是否属于恶意刷新。

我们的入侵检测程序应该放到所有Java Web程序的执行前,也即若发现用户是恶意刷新就不再继续执行Java Web中的其它部分内容,否则就会失去了意义。这就需要以插件的方式把入侵检测的程序置入Java Web应用中,使得每次用户访问Java Web,都先要到这个入侵检测程序中报一次到,符合规则才能放行。

  Java Web应用大致分为两种,一种纯JSP(+Java Bean)方式,一种是基于框架(如Struts、EasyJWeb等)的。第一种方式的Java Web可以通过Java Servlet中的Filter接口实现,也即实现一个Filter接口,在其doFilter方法中插入入侵检测程序,然后再web.xml中作简单的 配置即可。在基于框架的Web应用中,由于所有应用都有一个入口,因此可以把入侵检测的程序直接插入框架入口引擎中,使框架本身支持入侵检测功能。当然, 也可以通过实现Filter接口来实现。

  在EasyJWeb框架中,已经置入了简单入侵检测的程序,因此,这里我们以EasyJWeb框架为例,介绍具体的实现方法及源码,完整的代码可以在EasyJWeb源码中找到。

  在基于EasyJWeb的Java Web应用中(如http://www.easyjf.com/bbs/),默认情况下你只要连续刷新页面次数过多,即会弹出如下的错误:

  EasyJWeb框架友情提示!:-):
  您对页面的刷新太快,请等待60秒后再刷新页面!
详细请查询http://www.easyjf.com


二、用户访问信息记录UserConnect.java类  

这个类是一个简单的Java Bean,主要代表用户的信息,包括用户名、IP、第一次访问时间、最后登录时间、登录次数、用户状态等。全部

代码如下:

package com.easyjf.web;

import java.util.Date;
/**
*
*

Title:用户验证信息


*

Description:记录用户登录信息,判断用户登录情况


*

Copyright: Copyright (c) 2006


*

Company: www.easyjf.com

>
* @author 蔡世友
* @version 1.0
*/
public class UserConnect {
private String userName;
private String ip;
private Date firstFailureTime;
private Date lastLoginTime;
private int failureTimes;//用户登录失败次数
private int status=0;//用户状态0表示正常,-1表示锁定
public int getFailureTimes() {
 return failureTimes;
}
public void setFailureTimes(int failureTimes) {
 this.failureTimes = failureTimes;
}
public Date getFirstFailureTime() {
 return firstFailureTime;
}

public void setFirstFailureTime(Date firstFailureTime) {
 this.firstFailureTime = firstFailureTime;
}

public String getIp() {
 return ip;
}

public void setIp(String ip) {
 this.ip = ip;
}

public Date getLastLoginTime() {
 return lastLoginTime;
}

public void setLastLoginTime(Date lastLoginTime) {
 this.lastLoginTime = lastLoginTime;
}

public String getUserName() {
 return userName;
}

public void setUserName(String userName) {
 this.userName = userName;
}

public int getStatus() {
 return status;
}

public void setStatus(int status) {
 this.status = status;
}

}


三、监控线程UserConnectManage.java类

这是入侵检测的核心部分,主要实现具体的入侵检测、记录、判断用户信息、在线用户的刷新等功能,并提供其它应用程序使用本组件的调用接口。

package com.easyjf.web;

import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;
/**
*
*

Title:用户入侵检测信息


*

Description:用于判断用户刷新情况检查,默认为10秒钟之内连续连接10次为超时


*

Copyright: Copyright (c) 2006


*

Company: www.easyjf.com

>
* @author 蔡世友
* @version 1.0
*/
public class UserConnectManage {
private static final Logger logger = (Logger) Logger.getLogger(UserConnectManage.class.getName());
private static int maxFailureTimes=10;//最大登录失败次数
private static long maxFailureInterval=10000;//毫秒,达到最大登录次数且在这个时间范围内
private static long waitInterval=60000;//失败后接受连接的等待时间,默认1分钟
private static int maxOnlineUser=200;//同时在线的最大数
private final static Map users=new HashMap();//使用ip+userName为key存放用户登录信息UserLoginAuth
private static Thread checkThread=null;
private static class CheckTimeOut implements Runnable{ 
private Thread parentThread;
public  CheckTimeOut(Thread parentThread) 
 {
  this.parentThread=parentThread; 
  synchronized(this){
  if(checkThread==null){   
   checkThread= new Thread(this);
   //System.out.println("创建一个新线程!");
   checkThread.start();   
     }
  }
 } 
 public void run() {  
  while(true)
  {
   if(parentThread.isAlive()){
   try{
   Thread.sleep(2000);
   int i=0;
   if(users.size()>maxOnlineUser)//当达到最大用户数时清除
   {
    synchronized(users){//执行删除操作
    Iterator it=users.keySet().iterator();
    Set set=new HashSet();
    Date now=new Date();
    while(it.hasNext())
    {
     Object key=it.next();
     UserConnect user=(UserConnect)users.get(key);
     if(now.getTime()-user.getFirstFailureTime().getTime()>maxFailureInterval)//删除超时的用户
     {      
      set.add(key);
      logger.info("删除了一个超时的连接"+i);
      i++;
     }
    }
    if(i<5)//如果删除少于5个,则强行删除1/2在线记录,牺牲性能的情况下保证内存
    {
     int num=maxOnlineUser/2;
     it=users.keySet().iterator();
     while(it.hasNext() && i
     {      
      set.add(it.next());
      logger.info("删除了一个多余的连接"+i);
      i++;
     }
    }
    users.keySet().removeAll(set);
    }
   }
   
   }
   catch(Exception e)
   {
    e.printStackTrace();
   }
   
  }
   else
   {   
   break;
   }
  }
  logger.info("监视程序运行结束!"); 
 }
}
//通过checkLoginValidate判断是否合法的登录连接,如果合法则继续,非法则执行
public static boolean checkLoginValidate(String ip,String userName)//只检查认证失败次数
{
 boolean ret=true;
 Date now=new Date(); 
 String key=ip+":"+userName;
 UserConnect auth=(UserConnect)users.get(key);
 if(auth==null)//把用户当前的访问信息加入到users容器中
 {
  auth=new UserConnect();
  auth.setIp(ip);
  auth.setUserName(userName);
  auth.setFailureTimes(0);
  auth.setFirstFailureTime(now);
  users.put(key,auth);  
  if(checkThread==null)new CheckTimeOut(Thread.currentThread());
 } 
 else
 {
  if(auth.getFailureTimes()>maxFailureTimes)
  {
           //如果在限定的时间间隔内,则返回拒绝用户连接的信息
   if((now.getTime()-auth.getFirstFailureTime().getTime())
   {
    ret=false;
    auth.setStatus(-1);
   }
   else  if(auth.getStatus()==-1 && (now.getTime()-auth.getFirstFailureTime().getTime()< (maxFailureInterval+waitInterval)))//重置计数器
   {
    ret=false;
   }
   else    
   {    
    auth.setFailureTimes(0);
    auth.setFirstFailureTime(now);
    auth.setStatus(0);
   }
   
  }
  //登录次数加1
  auth.setFailureTimes(auth.getFailureTimes()+1);
 }
 //System.out.println(key+":"+auth.getFailureTimes()+":"+ret+":"+(now.getTime()-auth.getFirstFailureTime().getTime()));
 return ret;
}

public static void reset(String ip,String userName)//重置用户信息

 Date now=new Date(); 
 String key=ip+":"+userName;
 UserConnect auth=(UserConnect)users.get(key);
 if(auth==null)//把用户当前的访问信息加入到users容器中
 {
  auth=new UserConnect();
  auth.setIp(ip);
  auth.setUserName(userName);
  auth.setFailureTimes(0);
  auth.setFirstFailureTime(now);
  users.put(key,auth);
 } 
 else
 {
  auth.setFailureTimes(0);
  auth.setFirstFailureTime(now);
 }
}
public static void remove(String ip,String userName)//删除用户在容器中的记录
{
 String key=ip+":"+userName;
 users.remove(key);
}
public static void clear()//清空容器中内容
{
 if(!users.isEmpty())users.clear();
}
public static long getMaxFailureInterval() {
 return maxFailureInterval;
}

public static void setMaxFailureInterval(long maxFailureInterval) {
 UserConnectManage.maxFailureInterval = maxFailureInterval;
}

public static int getMaxFailureTimes() {
 return maxFailureTimes;
}

public static void setMaxFailureTimes(int maxFailureTimes) {
 UserConnectManage.maxFailureTimes = maxFailureTimes;
}

public static int getMaxOnlineUser() {
 return maxOnlineUser;
}

public static void setMaxOnlineUser(int maxOnlineUser) {
 UserConnectManage.maxOnlineUser = maxOnlineUser;
}

public static long getWaitInterval() {
 return waitInterval;
}

public static void setWaitInterval(long waitInterval) {
 UserConnectManage.waitInterval = waitInterval;
}


四、调用接口

在需要进入侵检测判断的地方,直接使用UserConnectManage类中的checkLoginValidate方法即可。如EasyJWeb的核心Servlet 

com.easyjf.web.ActionServlet中调用UserConnectManage的代码:
   if(!UserConnectManage.checkLoginValidate(request.getRemoteAddr(),"guest"))
       {            
           info(request,response,new Exception("您对页面的刷新太快,请等待"+UserConnectManage.getWaitInterval()/1000+"秒

后再刷新页面!"));
           return;
       }      



五、总结
当然,这里提供的方法只是一个简单的实现示例,由于上面的用户信息是直接保存在内存中,若并发用户很大的时候的代码的占用,可以考虑引入数据库来记录用 户的访问信息,当然相应的执行效率肯定用降低。上面介绍的实现中,入侵检测判断的策略也只有用户访问次数及时间间隔两个元素,您还可以根据你的实现情况增 加其它的检测元素。

由于水平有限,该设计上有N不合理或者需要改进的地方,恳请大家指正!



Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=657199



posted @ 2009-04-17 16:42 二胡 阅读(202) | 评论 (0)编辑 收藏

转 http://www.ibm.com/developerworks/cn/java/j-findbug1/#resources

FindBugs,第 1 部分: 提高代码质量

使用 FindBugs 的原因和方法

developerWorks
文档选项

未显示需要 JavaScript 的文档选项

将打印机的版面设置成横向打印模式

打印本页

将此页作为电子邮件发送

将此页作为电子邮件发送


级别: 初级

Chris Grindstaff (chris@gstaff.org), 软件工程师

2004 年 5 月 25 日

静 态分析工具承诺无需开发人员费劲就能找出代码中已有的缺陷。当然,如果有多年的编写经验,就会知道这些承诺并不是一定能兑现。尽管如此,好的静态分析工具 仍然是工具箱中的无价之宝。在这个由两部分组成的系列文章的第一部分中,高级软件工程师 Chris Grindstaff 分析了 FindBugs 如何帮助提高代码质量以及排除隐含的缺陷。

代码质量工具的一个问题是它们容易为开发人员提供大量但并非真正问题的问题——即 伪问题(false positives)。 出现伪问题时,开发人员要学会忽略工具的输出或者放弃它。FindBugs 的设计者 David Hovemeyer 和 William Pugh 注意到了这个问题,并努力减少他们所报告的伪问题数量。与其他静态分析工具不同,FindBugs 不注重样式或者格式,它试图只寻找真正的缺陷或者潜在的性能问题。

FindBugs 是什么?

FindBugs 是一个静态分析工具,它检查类或者 JAR 文件,将字节码与一组缺陷模式进行对比以发现可能的问题。有了静态分析工具,就可以在不实际运行程序的情况对软件进行分析。不是通过分析类文件的形式或结 构来确定程序的意图,而是通常使用 Visitor 模式(请参阅 参考资料)。图 1 显示了分析一个匿名项目的结果(为防止可怕的犯罪,这里不给出它的名字):


图 1. FindBugs UI
Configure Detector 的图形表示

让我们看几个 FindBugs 可以发现的问题。

本系列的第二篇文章“ 编写自定义检测器”解释了如何编写自定义检测器, 以便发现特定于应用程序的问题。





回页首


问题发现的例子

下面的列表没有包括 FindBug 可以找到的 所有问题。相反,我侧重于一些更有意思的问题。

检测器:找出 hash equals 不匹配
这个检测器寻找与 equals()hashCode() 的实现相关的几个问题。这两个方法非常重要,因为几乎所有基于集合的类—— List、Map、Set 等都调用它们。一般来说,这个检测器寻找两种不同类型的问题——当一个类:

  • 重写对象的 equals() 方法,但是没有重写它的 hashCode 方法,或者相反的情况时。

  • 定义一个 co-variant 版本的 equals()compareTo() 方法。例如, Bob 类定义其 equals() 方法为布尔 equals(Bob) ,它覆盖了对象中定义的 equals() 方法。因为 Java 代码在编译时解析重载方法的方式,在运行时使用的几乎总是在对象中定义的这个版本的方法,而不是在 Bob 中定义的那一个(除非显式将 equals() 方法的参数强制转换为 Bob 类型)。因此,当这个类的一个实例放入到类集合中的任何一个中时,使用的是 Object.equals() 版本的方法,而不是在 Bob 中定义的版本。在这种情况下, Bob 类应当定义一个接受类型为 Object 的参数的 equals() 方法。

检测器:忽略方法返回值
这个检测器查找代码中忽略了不应该忽略的方法返回值的地方。这种情况的一个常见例子是在调用 String 方法时,如在清单 1 中:


清单 1. 忽略返回值的例子
1  String aString = "bob";
2 b.replace('b', 'p');
3 if(b.equals("pop"))

这个错误很常见。在第 2 行,程序员认为他已经用 p 替换了字符串中的所有 b。确实是这样,但是他忘记了字符串是不可变的。所有这类方法都返回一个新字符串,而从来不会改变消息的接收者。

检测器:Null 指针对 null 的解引用(dereference)和冗余比较
这个检测器查找两类问题。它查找代码路径将会或者可能造成 null 指针异常的情况,它还查找对 null 的冗余比较的情况。例如,如果两个比较值都为 null,那么它们就是冗余的并可能表明代码错误。FindBugs 在可以确定一个值为 null 而另一个值不为 null 时,检测类似的错误,如清单 2 所示:


清单 2. Null 指针示例
1  Person person = aMap.get("bob");
2 if (person != null) {
3 person.updateAccessTime();
4 }
5 String name = person.getName();

在这个例子中,如果第 1 行的 Map 不包括一个名为“bob”的人,那么在第 5 行询问 person 的名字时就会出现 null 指针异常。因为 FindBugs 不知道 map 是否包含“bob”,所以它将第 5 行标记为可能 null 指针异常。

检测器:初始化之前读取字段
这个检测器寻找在构造函数中初始化之前被读取的字段。这个错误通常是——尽管不总是如此——由使用字段名而不是构造函数参数引起的,如清单 3 所示:


清单 3. 在构造函数中读取未初始化的字段
1  public class Thing {
2 private List actions;
3 public Thing(String startingActions) {
4 StringTokenizer tokenizer = new StringTokenizer(startingActions);
5 while (tokenizer.hasMoreTokens()) {
6 actions.add(tokenizer.nextToken());
7 }
8 }
9 }

在这个例子中,第 6 行将产生一个 null 指针异常,因为变量 actions 还没有初始化。

这些例子只是 FindBugs 所发现的问题种类的一小部分(更多信息请参阅 参考资料)。在撰写本文时,FindBugs 提供总共 35 个检测器。





回页首


开始使用 FindBugs

要运行 FindBugs,需要一个版本 1.4 或者更高的 Java Development Kit (JDK),尽管它可以分析由老的 JDK 创建的类文件。要做的第一件事是下载并安装最新发布的 FindBugs——当前是 0.7.1 (请参阅 参考资料)。幸运的是,下载和安全是相当简单的。在下载了 zip 或者 tar 文件后,将它解压缩到所选的目录中。就是这样了——安装就完成了。

安 装完后,对一个示例类运行它。就像一般文章中的情况,我将针对 Windows 用户进行讲解,并假定那些 Unix 信仰者可以熟练地转化这些内容并跟进。打开命令行提示符号并进入 FindBugs 的安装目录。对我来说,这是 C:\apps\FindBugs-0.7.3。

在 FindBugs 主目录中,有几个值得注意的目录。文档在 doc 目录中,但是对我们来说更重要的是,bin 目录包含了运行 FindBugs 的批处理文件,这使我们进入下一部分。





回页首


运行 FindBugs

像 如今的大多数数工具一样,可以以多种方式运行 FindBugs——从 GUI、从命令行、使用 Ant、作为 Eclipse 插件程序和使用 Maven。我将简要提及从 GUI 运行 FindBugs,但是重点放在用 Ant 和命令行运行它。部分原因是由于 GUI 没有提供命令行的所有选项。例如,当前不能指定要加入的过滤器或者在 UI 中排除特定的类。但是更重要的原因是我认为 FindBugs 最好作为编译的集成部分使用,而 UI 不属于自动编译。

使用 FindBugs UI

使用 FindBugs UI 很直观,但是有几点值得说明。如 图 1所示,使用 FindBugs UI 的一个好处是对每一个检测到的问题提供了说明。图 1 显示了缺陷 Naked notify in method的说明。对每一种缺陷模式提供了类似的说明,在第一次熟悉这种工具时这是很有用的。窗口下面的 Source code 选项卡也同样有用。如果告诉 FindBugs 在什么地方寻找代码,它就会在转换到相应的选项卡时突出显示有问题的那一行。

值得一提的还有在将 FinBugs 作为 Ant 任务或者在命令行中运行 FindBugs 时,选择 xml 作为 ouput 选项,可以将上一次运行的结果装载到 UI 中。这样做是同时利用基于命令行的工具和 UI 工具的优点的一个很好的方法。

将 FindBugs 作为 Ant 任务运行

让 我们看一下如何在 Ant 编译脚本中使用 FindBugs。首先将 FindBugs Ant 任务拷贝到 Ant 的 lib 目录中,这样 Ant 就知道新的任务。将 FIND_BUGS_HOME\lib\FindBugs-ant.jar 拷贝到 ANT_HOME\lib。

现在看看在编译脚本中要加入什么才能使用 FindBugs 任务。因为 FindBugs 是一个自定义任务,将需要使用 taskdef 任务以使 Ant 知道装载哪一个类。通过在编译文件中加入以下一行做到这一点:

<taskdef name="FindBugs" classname="edu.umd.cs.FindBugs.anttask.FindBugsTask"/>

在定义了 taskdef 后,可以用它的名字 FindBugs 引用它。下一步要在编译中加入使用新任务的目标,如清单 4 所示:


清单 4. 创建 FindBugs 目录
1  <target name="FindBugs" depends="compile">
2 <FindBugs home="${FindBugs.home}" output="xml" outputFile="jedit-output.xml">
3 <class location="c:\apps\JEdit4.1\jedit.jar" />
4 <auxClasspath path="${basedir}/lib/Regex.jar" />
5 <sourcePath path="c:\tempcbg\jedit" />
6 </FindBugs>
7 </target>

让我们更详细地分析这段代码中所发生的过程。

第 1 行: 注意 target 取决于编译。一定要记住处理的是类文件而 是源文件,这样使 target 对应于编译目标保证了 FindBugs 可在最新的类文件运行。FindBugs 可以灵活地接受多种输入,包括一组类文件、JAR 文件、或者一组目录。

第 2 行:必须指定包含 FindBugs 的目录,我是用 Ant 的一个属性完成的,像这样:

<property name="FindBugs.home" value="C:\apps\FindBugs-0.7.3" />

可选属性 output 指定 FindBugs 的结果使用的输出格式。可能的值有 xmltext 或者 emacs 。如果没有指定 outputFile ,那么 FindBugs 会使用标准输出。如前所述,XML 格式有可以在 UI 中观看的额外好处。

第 3 行: class 元素用于指定要 FindBugs 分析哪些 JAR、类文件或者目录。分析多个 JAR 或者类文件时,要为每一个文件指定一个单独的 class 元素。除非加入了 projectFile 元素,否则需要 class 元素。更多细节请参阅 FindBugs 手册。

第 4 行: 用嵌套元素 auxClasspath 列出应用程序的依赖性。这些是应用程序需要但是不希望 FindBugs 分析的类。如果没有列出应用程序的依赖关系,那么 FindBugs 仍然会尽可能地分析类,但是在找不到一个缺少的类时,它会抱怨。与 class 元素一样,可以在 FindBugs 元素中指定多个 auxClasspath 元素。 auxClasspath 元素是可选的。

第 5 行: 如果指定了 sourcePath 元素,那么 path 属性应当表明一个包含应用程序源代码的目录。指定目录使 FindBugs 可以在 GUI 中查看 XML 结果时突出显示出错的源代码。这个元素是可选的。

上面就是基本内容了。让我们提前几个星期。

过滤器

您 已经将 FindBugs 引入到了团队中,并运行它作为您的每小时/每晚编译过程的一部分。当团队越来越熟悉这个工具时,出于某些原因,您决定所检测到的一些缺陷对于团队来说不重 要。也许您不关心一些类是否返回可能被恶意修改的对象——也许,像 JEdit,有一个真正需要的(honest-to-goodness)、合法的理由调用 System.gc()

总是可以选择“关闭”特定的 检测器。在更细化的水平上,可以在指定的一组类甚至是方法中查找问题时,排除某些检测器。FindBugs 提供了这种细化的控制,可以排除或者包含过滤器。当前只有用命令行或者 Ant 启动的 FindBugs 中支持排除和包含过滤器。正如其名字所表明的,使用排除过滤器来排除对某些缺陷的报告。较为少见但仍然有用的是,包含过滤器只能用于报告指定的缺陷。过滤 器是在一个 XML 文件中定义的。可以在命令行中用一个排除或者包含开关、或者在 Ant 编译文件中用 excludeFilterincludeFilter 指定它们。在下面的例子中,假定使用排除开关。还要注意在下面的讨论中,我对 “bugcode”、“bug” 和“detector”的使用具有某种程度的互换性。

可以有不同的方式定义过滤器:

  • 匹配一个类的过滤器。可以用这些过滤器 忽略在特定类中发现的所有问题。

  • 匹配一个类中特定缺陷代码(bugcode)的 过滤器。可以用这些过滤器忽略在特定类中发现的一些缺陷。

  • 匹配一组缺陷的过滤器。可以用这些过滤器 忽略所分析的所有类中的一组缺陷。

  • 匹配所分析的一个类中的某些方法的过滤器。可以用这些过滤器忽略在一个类中的一组方法中发现的所有缺陷。

  • 匹配在所分析的一个类中的方法中发现的某些缺陷的过滤器。可以用这些过滤器忽略在一组方法中发现的特定缺陷。

知道了这些就可以开始使用了。有关其他定制 FindBugs 方法的更多信息,请参阅 FindBugs 文档。知道如何设置编译文件以后,就让我们更详细地分析如何将 FindBugs 集成到编译过程中吧!





回页首


将 FindBugs 集成到编译过程中

在将 FindBugs 集成到编译过程当中可以有几种选择。总是可以在命令行执行 FindBugs,但是您很可能已经使用 Ant 进行编译,所以最自然的方法是使用 FindBugs Ant 任务。因为我们在 如何运行 FindBugs一节中讨论了使用 FindBugs Ant 任务的基本内容,所以现在讨论应当将 FindBugs 加入到编译过程中的几个理由,并讨论几个可能遇到的问题。

为什么应该将 FindBugs 集成到编译过程中?

经 常问到的第一个问题是为什么要将 FindBugs 加入到编译过程中?虽然有大量理由,最明显的回答是要保证尽可能早地在进行编译时发现问题。当团队扩大,并且不可避免地在项目中加入更多新开发人员 时,FindBugs 可以作为一个安全网,检测出已经识别的缺陷模式。我想重申在一篇 FindBugs 论文中表述的一些观点。如果让一定数量的开发人员共同工作,那么在代码中就会出现缺陷。像 FindBugs 这样的工具当然不会找出所有的缺陷,但是它们会帮助找出其中的部分。现在找出部分比客户在以后找到它们要好——特别是当将 FindBugs 结合到编译过程中的成本是如此低时。

一旦确定了加入哪些过滤器和类,运行 FindBugs 就没什么成本了,而带来的好处就是它会检测出新缺陷。如果编写特定于应用程序的检测器,则这个好处可能更大。

生成有意义的结果

重 要的是要认识到这种成本/效益分析只有在不生成大量误检时才有效。换句话说,如果在每次编译时,不能简单地确定是否引入了新的缺陷,那么这个工具的价值就 会被抵消。分析越自动化越好。如果修复缺陷意味着必须吃力地分析检测出的大量不相干的缺陷,那么您就不会经常使用它,或者至少不会很好地使用它。

确定不关心哪些问题并从编译中排除它们。也可以挑出 确实关注的一小部分检测器并只运行它们。另一种选择是从个别的类中排除一组检测器,但是其他的类不排除。FindBugs 提供了使用过滤器的极大灵活性,这可帮助生成对团队有意义的结果,由此我们进入下一节。

确定用 FindBugs 的结果做什么

可 能看来很显然,但是您想不到我参与的团队中有多少加入了类似 FindBugs 这样的工具而没有真正利用它。让我们更深入地探讨这个问题——用结果做什么?明确回答这个问题是困难的,因为这与团队的组织方式、如何处理代码所有权问题 等有很大关系。不过,下面是一些指导:

  • 可以考虑将 FindBugs 结果加入到源代码管理(SCM)系统中。一般的经验做法是不将编译工件(artifact)放到 SCM 系统中。不过,在这种特定情况下,打破这个规则可能是正确的,因为它使您可以监视代码质量随时间的变化。

  • 可以选择将 XML 结果转换为可以发送到团队的网站上的 HTML 报告。转换可以用 XSL 样式表或者脚本实现。有关例子请查看 FindBugs 网站或者邮件列表(请参阅 参考资料)。

  • 像 FindBugs 这样的工具通常会成为用于敲打团队或者个人的政治武器。尽量抵制这种做法或者不让它发生——记住,它只是一个工具,它可以帮助改进代码的质量。有了这种思想,在下一部分中,我将展示如何编写自定义缺陷检测器。




回页首


结束语

我 鼓励读者对自己的代码试用静态分析工具,不管是 FindBugs、PMD 还是其他的。它们是有用的工具,可以找出真正的问题,而 FindBugs 是在消除误检方面做得最好的工具。此外,它的可插入结构提供了编写有价值的、特定于应用程序的检测器的、有意思的测试框架。在本系列的 第 2 部分中,我将展示如何编写自定义检测器以找出特定于应用程序的问题。



参考资料



关于作者


Chris Grindstaff 是在北加利福尼亚 Research Triangle Park 工作的 IBM 高级软件工程师。Chris 在 7 岁时编写了他的第一个程序,当时他让小学老师认识到“键入”句子与手写它们一样费力。Chris 目前参与了不同的开放源代码项目。他大量使用 Eclipse 并编写了几个流行的 Eclipse 插件程序,可以在他的 网站找到这些插件程序。可以通过 cgrinds@us.ibm.com或者 chris@gstaff.org与 Chrise 联系。




posted @ 2009-04-16 13:12 二胡 阅读(214) | 评论 (0)编辑 收藏

转 http://www.blogjava.net/vincent/archive/2009/04/14/265591.html

安装就不用说了,很简单,在FireFox上插件库里找到FireBug就Ok了。下图是FireBug Debug 窗口。

     FireBug美工用的非常普遍,公司美工妹妹用的非常熟练 呵呵,而对于我们开发人员,主要用它来Debug JS。看看官方对Debug功能的介绍。
 Firebug includes a powerful JavaScript debugger that lets you pause execution at any time and see what each variable looked like at that moment. If your code is a little sluggish, use the JavaScript profiler to measure performance and find bottlenecks fast.
      简而言之,FireBug 可以让我们在任何时候debugJS 并查看变量,同时可以通过它找出JS中性能瓶颈。
      下图是如何找到Web应用的JS,激活Firebug,点Script,然后在All 旁边选择你要Debug的JS。

下图是如何打断点 基本跟Eclipse一样,点击行号就Ok了。
下图是如何在断点上设定条件,如果条件符合,就进入断点,这个功能很不错,特别是Debug很复杂的Function时候。
 下图是如何单步调试,跟Eclipse一样 F11单步
下图是查看调用的Stack,对以复杂的JS Debug很有帮助。

下图是查看变量 基本跟EclipseDebug 一样。


下图是在断点处查看变量。


有个很好用的功能,代码行之间快速调转,使得对上千行的JS调试很轻松。
下图就是Performance 测试结果,使用很简单 点Profile

还有一个Log功能比较实用,看下图 如果你不想每次都进入断点,用这个就再好不过了。
       基本用法就这些了,希望对大家有帮助。


posted @ 2009-04-16 09:14 二胡 阅读(1509) | 评论 (0)编辑 收藏

转 http://dev.yesky.com/241/8757741.shtml#pls

UML建模的要点总结

2009-03-18 11:57作者:出处:天极网责任编辑:郑重

  预备知识:

  一、UML的特性与发展现状

  UML是一种Language(语言)

  UML是一种Modeling(建模)Language

  UML是Unified(统一)Modeling Language

  1、已进入全面应用阶段的事实标准

  2、应用领域正在逐渐扩展,包括嵌入式系统建模、业务建模、流程建模等多个领域

  3、成为“产生式编程”的重要支持技术:MDA、 可执行UML等

  二、建模的目的与原则

  1、帮助我们按照实际情况或按我们需要的样式对系统进行可视化;提供一种详细说明系统的结构或行为的方法;给出一个指导系统构造的模板;对我们所做出的决策进行文档化。

  2、仅当需要模型时,才构建它。

  3、选择要创建什么模型对如何动手解决问题和如何形成解决方案有着意义深远的影响;每一种模型可以在不同的精度级别上表示;最好的模型是与现实相联系的;单个模型是不充分的。对每个重要的系统最好用一组几乎独立的模型去处理。

  三、谁应该建模

  1、业务建模:以领域专家为主,需求分析人员是主力,系统分析员、架构师可参与

  2、需求模型:以需求分析人员为主,系统分析员是主力,领域专家提供指导,架构师和资深开发人员参与

  3、设计模型:高层设计模型以架构师为主,系统分析员从需求方面提供支持,资深开发人员从技术实现方面提供支持。详细设计模型则以资深开发人员为主,架构师提供指导。

  4、实现模型:以资深开发人员(设计人员)为主,架构师提供总体指导。

  5、数据库模型:以数据库开发人员为主,架构师提供指导,资深开发人员(设计人员)予以配合。

  正式开始

  UML组成,三部分(构造块、规则、公共机制),关系如下图所示:

  

  一、构造块

  1、构造块是对模型中最具有代表性的成分的抽象

  建模元素:UML中的名词,它是模型基本物理元素。

  行为元素:UML中的动词,它是模型中的动态部分,是一种跨越时间、空间的行为。

  分组元素:UML中的容器,用来组织模型,使模型更加的结构化。

  注释元素:UML中的解释部分,和代码中的注释语句一样,是用来描述模型的。

  1.1、建模元素

  类(class)和对象(object)

  接口(interface)

  主动类(active class)

  用例(use case)

  协作(collaboration)

  构件(component)

  节点(node)

  类(class)和对象(object)

  类是对一组具有相同属性、相同操作、相同关系和相同语义的对象的抽象

  UML中类是用一个矩形表示的,它包含三个区域,最上面是类名、中间是类的属性、最下面是类的方法

  对象则是类的一个实例 (object is a Instance of Class)

  接口(interface)

  接口是描述某个类或构件的一个服务操作集

  主动类(active class)

  主动类实际上是一种特殊的类。引用它的原因,实际上是在开发中需要有一些类能够起到 启动控制活动的作用

  主动类是指其对象至少拥有一个进 程或线程,能够启动控制活动的类

  用例(use case)

  用例是著名的大师Ivar Jacobson首先提出的,现已经成为了面向对象软件开发中一个需求分析的最常用工具

  用例实例是在系统中执行的一系列动作,这些动作将生成特定执行者可见的价值结果。一个 用例定义一组用例实例。

  协作(collaboration)

  协作定义了一个交互,它是由一组共同工作以提供某协作行为的角色和其他元素构 成的一个群体。

  对于某个用例的实现就可 以表示为一个协作

  构件(component)

  在实际的软件系统中,有许多要比“类”更大的实体,例如一个COM组件、一个DLL文件、一个JavaBeans、一个执行文件等等。为了更好地对在UML模型中对它们进行表示,就引入了构件(也译为组件)

  构件是系统设计的一个模块化部分,它隐藏了内部的实现,对外提供了一组外部接口。在系统中满足相同接口的组件可以自由地替换

  节点(node)

  为了能够有效地对部署的结构进行建模,UML引入了节点这一概念,它可以用来描述实际的PC机、打印机、服务器等软件运行的基础硬件

  节点是运行时存在的物理元素,它表示了一种可计算的资源,通常至少有存储空间和处理能力

  1.2、行为元素

  交互(interaction): 是在特定语境中,共同完成某个任务的一组对象之间交换的信息集合

  交互的表示法很简单,就是一条有向直线,并在上面标有操作名

  状态机(state machine):是一个对象或交互在生命周期内响应事件所经历的状态序列

  在UML模型中将状态画为一个圆 角矩形,并在矩形内写出状态名 称及其子状态

  1.3、分组元素

  对于一个中大型的软件系统而言,通常会包含大量的类,因此也就会存在大量的结构事物、行为事物,为了能够更加有效地对其进行整合,生成或简或繁、或宏观或微观的模型,就需要对其进行分组。在UML中,提供了“包(Package)”来完成这一目标

  1.4、注释元素

  结构事物是模型的主要构造块,行为事物则是补充了模型中的动态部分,分组事物而是用来更好地组织模型,似乎已经很完整了。而注释事物则是用来锦上添花的,它是用来在UML模型上添加适当的解释部分

  2、关系

  UML模型的关系比较多,下图

  

  2.1 关联关系

  关联(Association)表示两个类之间存在某种语义上的联系。关联关系提供了通信的路径,它是所有关系中最通用、语义最弱的。

  在UML中,使用一条实线来表示关联关系

  在关联关系中,有两种比较特殊的关系:聚合和组合

  聚合关系:聚合(Aggregation)是一种特殊形式的关联。聚合表示类之间的关系是整体与部分的关系

  如果发现“部分”类的存在,是完全依赖于“整体”类的,那么就应该使用“组合”关系来描述

  组合是聚合的变种,加入了一些重要的语义。也就是说,在一个组合关系中一个对象一次就只是一个组合的一部分,“整体”负责“部分”的创建和破坏,当“整体”被破坏时,“部分”也随之消失

  聚合就像汽车和车胎,汽车坏了胎还可以用。组合就像公司和下属部门,公司倒闭了部门也就不存在了!

  2.2  泛化、实现与依赖

  泛化关系描述了一般事物与该事物中的特殊种类之间的关系,也就是父类与子类之间的关系。

  实现关系是用来规定接口和实现接口的类或组件之间的关系。接口是操作的集合,这些操作用于规定类或组件的服务。

  有两个元素X、Y,如果修改元素X的定义可能会引起对另一个元素Y的定义的修改,则称元素Y依赖(Dependency)于元素X。

  二、规则

  命名:也就是为事物、关系和图起名字。和任何语言一样,名字都是一个标识符

  范围:与类的作用域相似.

  可见性:Public,Protected,Private,Package

  三、UML公共机制

  1、规格描述

  在图形表示法的每个部分后面都有一个规格描述(也称为详述),它用来对构造块的语法和语义进行文字叙述。这种构思,也就使可视化视图和文字视图的分离 :

  2、UML修饰与通用划分

  在为了更好的表示这些细节,UML中还提供了一些修饰符号,例如不同可视性的符号、用斜体字表示抽象类

  UML通用划分:

  1)类与对象的划分:类是一种抽象,对象是一个具体 的实例

  2)接口与实现的分离:接口是一种声明、是一个契 约,也是服务的入口;实现则是负责实施接口提供 的契约

  3、UML扩展机制

  这部分不容易描述,待改(邀月注 2009.2.18)

  构造型:在实际的建模过程中,可能会需要定义一些特定于某个领域或某个系统的构造块

  标记值则是用来为事物添加新特性的。标记值的表示方法是用形如“{标记信息}”的字符串

  约束是用来增加新的语义或改变已存在规则的一种机制(自由文本和OCL两种表示法)。约束的表示法和标记值法类似,都是使用花括号括起来的串来表示,不过它是不能够放在元素中的,而是放在相关的元素附近。

  4、UML视图和图

  

  图名            功能                备注

  类图      描述类、类的特性以及类之间的关系        UML 1原有

  对象图     描述一个时间点上系统中各个对象的一个快照    UML 1非正式图

  复合结构图   描述类的运行时刻的分解             UML 2.0新增

  构件图     描述构件的结构与连接              UML 1原有

  部署图     描述在各个节点上的部署             UML 1原有

  包图      描述编译时的层次结构              UML中非正式图

  用例图     描述用户与系统如何交互             UML 1原有

  活动图     描述过程行为与并行行为             UML 1原有

  状态机图    描述事件如何改变对象生命周期          UML 1原有

  顺序图     描述对象之间的交互,重点在强调顺序       UML 1原有

  通信图     描述对象之间的交互,重点在于连接        UML 1中的协作图

  定时图     描述对象之间的交互,重点在于定时        UML 2.0 新增

  交互概观图   是一种顺序图与活动图的混合           UML 2.0新增

  附:开发过程与图的对应关系

  



posted @ 2009-04-15 16:12 二胡 阅读(192) | 评论 (0)编辑 收藏

转 http://bbs.blueidea.com/viewthread.php?tid=2732509&extra=page%3D2%26amp%3Bfilter%3Drate
引伸阅读
    * 解读absolute与relative
    * position:relative/absolute无法冲破的等级
    * 对《无法冲破的等级》一文的补充

定位一直是WEB标准应用中的难点,如果理不清楚定位那么可能应实现的效果实现不了,实现了的效果可能会走样。如果理清了定位的原理,那定位会让网页实现的更加完美。

定位的定义:

在CSS中关于定位的内容是:position:relative | absolute | static | fixed

static 没有特别的设定,遵循基本的定位规定,不能通过z-index进行层次分级。
relative 不脱离文档流,参考自身静态位置通过 top,bottom,left,right 定位,并且可以通过z-index进行层次分级。
absolute 脱离文档流,通过 top,bottom,left,right 定位。选取其最近的父级定位元素,当父级 position 为 static 时,absolute元素将以body坐标原点进行定位,可以通过z-index进行层次分级。
fixed 固定定位,这里他所固定的对像是可视窗口而并非是body或是父级元素。可通过z-index进行层次分级。

CSS中定位的层叠分级:z-index: auto | namber;

auto 遵从其父对象的定位
namber  无单位的整数值。可为负数

定位的原理:

可以位移的元素 (相对定位)

在本文流中,任何一个元素都被文本流所限制了自身的位置,但是通过CSS我们依然使得这些元素可以改变自己的位置,我们可以通过float来让元素浮动, 我们也可以通过margin来让元素产生位置移动。但是事实上那并非是真实的位移,因为,那只是通过加大margin值来实现的障眼法。而真正意义上的位 移是通过top,right,bottom,left(下称TRBL,TRBL可以折分使用。)针对一个相对定位的元素所产生的。我们看下面的图:

我们看图中是一个宽度为200px,高度为50px,margin:25px; border:25px solid #333; padding:25px; 相对定位的元素,并且位移距上50px,距左50px。而下方是一块默认定位的黑色区块。我们看到这个处在文本流的区块被上面的相对定位挡住了一部分,这 说明:“当元素被设置相对定位或是绝对定位后,将自动产生层叠,他们的层叠级别自然的高于文本流”。除非设置其z-index值为负值,但是在 Firefox等浏览器中z-index为负值时将不会显示。并且我们发现当相对定位元素进行位移后,表现内容已经脱离了文本流,只是在本文流中还为原来 的相对对定位留下了原有的总宽与总高(内容的高度或是宽度加上margin\border\padding的数值)。这说明在相对定位中,虽然表现区脱离 了原来的文本流,但是在文本流中还还有此相对定位的老窩。这点要特别注意,因为在实际应用中如果相对定位的位移数值过大,那么原有的区域就会形成一块空 白。

并且我们注意,定位元素的坐标点是在margin值的左上边缘点,即图中的B点。那么所有的位移的计算将以这个点为基础进行元素的推动。当TRBL为正值 时位移的方向是内聚的。由此可推,当TRBL为负值时位移的方向是外放的。在图片中有位移的箭头指向标识,带有加号的是正值位移方向,带有减号的是负值位 移方向。关于位移方位,可以延伸阅读怿飞的《由浅入深漫谈margin属性(一)》

可以在任意一个位置的元素 (绝对定位)

如上所述:相对定位只可以在文本流中进行位置的上下左右的移动,同样存在一定的局限性,虽然他的表现区脱离了文本流,但是在文本流却依然为其保留了一席之 地,这就好比一个打工的人他到了外地,但是在老家依然有一个专属于他的位置,这个位置不随他的移动而改变。但是这样很明显就会空出一块空白来,如果希望文 本流抛弃这个部分就需要用到绝对定位。绝对定位不光脱离了文本流,而且在文本流中也不会给这个绝对定位元素留下专属空位。这就好比是一个工厂里的职位,如 果有一个工人走了自然会要有别的工人来填充这个位置。而移动出去的部分自然也就成为了自由体。绝对定位将可以通过TRBL来设置元素,使之处在任何一个位 置。在父层position属性为默认值时,TRBL的坐标原点以body的坐标原点为起始。看下图:


上图可知,文本流中的内容会顶替绝对定位无素的位置,一点都不会客气。而绝对定位元素自然的层叠于文本流之上。而在单一的绝对定位中,定位元素将会跑到网页的左上角,因为那里是他们的被绝对定位后的坐标原点。

被关联的绝对定位

上面说的是单一的绝对定位,而在实际的应用中我们常常会需要用到一种特别的形式。即希望定位元素要有绝对定位的特性,但是又希望绝对定位的坐标原点可以固 定在网页中的某一个点,当这个点被移动时绝对位定元素能保证相对于这个原坐标的相对位置。也就是说需要这个绝对定位要跟着网页移动,而并且是因定在网页的 某一个固定位置。通常当网页是居中形式的时候这种效果就会显得特别的重要。要实现这种效果基本方式就是为这个绝对定位的父级设置为相对定位或是绝对定位。 那么绝对定位的坐标就会以父级为坐标起始点。

虽然是如此,但是这个坐标原点却并不是父级的坐标原点,这是一个很奇怪的坐标位置。我们看一下模型图示:


我们看到,这个图中父级为黑灰色区块,子级为青色区块。父级是相对定位,子级是绝对定位。子级设置了顶部位移50个像素,左倾位移50个像素。那么我们 看,子级的坐标原点并不是从父级的坐标原点位移50个像素,而是从父级块的padding左上边缘点为坐标起始点(即A点)。而父级这里如果要产生位置移 动,或是浏览器窗口大小有所变动都不会影响到这个绝对定位元素与父级的相对定位元素之间的位置关系。这个子级也不用调整数值。

这是一种很特别并且也是非常实用的应用方式。如果你之前对于定位的控制并不自如的话,相信看完对这里对定位的解释一定可以把定位使用得随心所欲。

总在视线里的元素 (固定定位)

由于广告的滥用,使得一些浏览器软件都开始有了广告内容拦截,使得一些很好的效果现在都不推荐使用了。比如让一个元素可能随着网页的滚动而不断改变自己的 位置。而现在我可以通过CSS中的一个定位属性来实现这样的一个效果,这个元素属性就是曾经不被支持的position:fixed; 他的含义就是:固定定位。这个固定与绝对定位很像,唯一不同的是绝对定位是被固定在网页中的某一个位置,而固定定位则是固定在浏览器的视框位置。

虽然原来的浏览器并不支持过个属性,但是浏览器的发展使得现在的高级浏览器都可以正确的解析这个CSS属性。并且通过CSS HACK来让IE6都可以实现这样的效果(目前无法使IE5.x)实现这种效果。为了不使本文变成冗长的大论,这里只给出这个实例算是这篇文章的结束。关 于这个实例的一些问题大家可以自行分析。有不理解的地方可以给我留言!


posted @ 2009-04-14 11:54 二胡 阅读(138) | 评论 (0)编辑 收藏

转 http://bbs.blueidea.com/viewthread.php?tid=339278&extra=page%3D2%26amp%3Bfilter%3Drate

clientTop 返回对象的offsetTop属性值和到当前窗口顶部的真实值之间的距离

clientLeft 返回对象的offsetLeft属性值和到当前窗口左边的真实值之间的距离

clientWidth 返回对象的宽度,包括padding,但是不包括margin、border和scroll bar的宽度

clientHeight 返回对象的高度,包括padding,但是不包括margin、border和scroll bar的高度



offsetTop 返回对象相对于父级对象的布局或坐标的top值,就是以父级对象左上角为坐标原点,向右和向下为X、Y轴正方向的Y坐标

offsetLeft 返回对象相对于父级对象的布局或坐标的left值,就是以父级对象左上角为坐标原点,向右和向下为X、Y轴正方向的x坐标

offsetHeight :返回对象相对于父级对象的布局或坐标的高度值
offsetWidth :返回对象相对于父级对象的布局或坐标的宽度值


pixelTop 设置或返回对象相对于窗口顶部的位置
pixelLeft 设置或返回对象相对于窗口左边的位置
还有pixelRight  pixelBottom
pixelHeight pixelWidth 设置或返回对象高宽

posLeft posTop posHeight posWidth 以对象相应属性中指定的单位设置或返回对象的相应属性值

这是昨天在msdn属性表中找到的几个,但是单单top和left的不敢翻。
以上是翻译的也夹带个人的理解,欢迎大家讨论,把这几个搞透彻对与控制层的运动很有帮助咯
目前我的理解是:
offset 相对值
pixel、pos是绝对值
client 反映父级元素绝对值

posted @ 2009-04-14 11:51 二胡 阅读(192) | 评论 (0)编辑 收藏

转 http://www.cnitblog.com/yemoo/archive/2007/10/10/34642.html

对于instanceof和typeof,以前偶尔的用到过,特别是typeof用到的相对更多一些,今日研究ext源码,很多地方都用到了instanceof,突然觉得他们两个有些相似但也应该有他们区别,网上看了一些文章,对它们之间的关系有了一定的了解。

instanceof和typeof都能用来判断一个变量是否为空或是什么类型的变量。
typeof用以获取一个变量的类型,typeof一般只能返回如下几个结果:number,boolean,string,function,object,undefined。 我们可以使用typeof来获取一个变量是否存在,如if(typeof a!="undefined"){},而不要去使用if(a)因为如果a不存在(未声明)则会出错,对于Array,Null等特殊对象使用typeof 一律返回object,这正是typeof的局限性。

如果我们希望获取一个对象是否是数组,或判断某个变量是否是某个对象的实例则要选择 使用instanceof。instanceof用于判断一个变量是否某个对象的实例,如var a=new Array();alert(a instanceof Array);会返回true,同时alert(a instanceof Object)也会返回true;这是因为Array是object的子类。再如:function test(){};var a=new test();alert(a instanceof test)会返回true。

谈到instanceof我们要多插入一个问题,就是function的arguments,我们大家也许都认为arguments是一个Array,但如果使用instaceof去测试会发现arguments不是一个Array对象,尽管看起来很像。

posted @ 2009-04-12 16:04 二胡 阅读(257) | 评论 (0)编辑 收藏

转 http://www.cnitblog.com/yemoo/archive/2007/10/11/34712.html

在jquery中我们见到window.undefined=window.undefined的写法,今日又在ext中见到window["undefined"]=window["undefined"],对其写法非常不理解,将自身赋给自身有什么意义,在网上狂搜一番也没有明确的解释,后来看到ext注释写了这么一句:for old browsers,很明显是为了兼容老的浏览器,思虑半天,似乎有了些理解。

  在较老的浏览器中,如IE5之前的浏览器,undefined并不是window对象的一个子对象,并不是一个已实现的系统保留字,而是代表一个未定义类 型,除了直接赋值和typeof()之外,其它任何对undefined的操作都将导致异常。如果需要知道一个变量是否是undefined,只能采用 typeof()的方法:如var v;if (typeof(v) == 'undefined') {// ...}。如果使用if(a==="undefined")则会报“undefined未定义”的错误。
      因此为了兼容IE5及之前的 浏览器,我们可以使用一些方法来解决这个问题。本文前面说到的就是其中一种方式。window.undefined=window.undefined; 咋一看很难理解,写法有点bt,但理解一下就不觉得奇怪了,在较早的浏览器中因为window.undefined不存在所以会返回undefined, 将此赋给等号前的window.undefined这样后面就可以直接使用if(a==="undefined")的判断方式了。在新版本的浏览器中 window.undefined=undefined;因此不会造成什么负面影响。

  除了使用window.undefined=window.undefined和window["undefined"]=window["undefined"]外,还有很多别的办法来实现对IE5及之前浏览器的兼容,如
var undefined = void null;  //void函数永远返回undefined
var undefined = function(){}();
var undefined = void 0;
只要等号后的表达式返回undefined即可。

posted @ 2009-04-12 15:58 二胡 阅读(290) | 评论 (0)编辑 收藏

转 http://www.cnblogs.com/sunwangji/archive/2006/08/21/482341.html

(在看到大家如此关注JS里头的这几个对象,我试着把原文再修改一下,力求能再详细的阐明个中意义  2007-05-21
在提到上述的概念之前,首先想说说javascript中函数的隐含参数:arguments

Arguments

该对象代表正在执行的函数和调用它的函数的参数。

[function.]arguments[n]
参数
function :选项。当前正在执行的 Function 对象的名字。
n :选项。要传递给 Function 对象的从0开始的参数值索引。

说明

Arguments是进行函数调用时,除了指定的参数外,还另外创建的一个隐藏对象。Arguments是一个类似数组但不是数组的对象,说它类似数组是因为其具有数组一样的访问性质及方式,可以由arguments[n]来访问对应的单个参数的值,并拥有数组长度属性length。还有就是arguments对象存储的是实际传递给函数的参数,而不局限于函数声明所定义的参数列表,而且不能显式创建 arguments 对象。arguments 对象只有函数开始时才可用。下边例子详细说明了这些性质:

//arguments 对象的用法。
function ArgTest(a, b){
   
var i, s = "The ArgTest function expected "
;
   
var numargs = arguments.length;     // 获取被传递参数的数值。

   var expargs = ArgTest.length;       // 获取期望参数的数值。
   if (expargs < 2)
      s 
+= expargs + " argument. "
;
   
else

      s 
+= expargs + " arguments. ";
   
if (numargs < 2
)
      s 
+= numargs + " was passed."
;
   
else

      s 
+= numargs + " were passed.";
   s 
+= "\n\n"

   
for (i =0 ; i < numargs; i++){      // 获取参数内容。
   s += "  Arg " + i + " = " + arguments[i] + "\n";
   }
   
return(s);                          // 返回参数列表。

}


在此添加了一个说明arguments不是数组(Array类)的代码:

Array.prototype.selfvalue = 1;
alert(
new
 Array().selfvalue);
function
 testAguments(){
    alert(arguments.selfvalue);
}


运行代码你会发现第一个alert显示1,这表示数组对象拥有selfvalue属性,值为1,而当你调用函数testAguments时,你会发现显示的是“undefined”,说明了不是arguments的属性,即arguments并不是一个数组对象。
在此附加上大家推荐的一个简单方法

alert(arguments instanceof Array);
alert(arguments 
instanceof Object);



 caller
  返回一个对函数的引用,该函数调用了当前函数。
  functionName.caller
  functionName 对象是所执行函数的名称。
说明
对于函数来说,caller 属性只有在函数执行时才有定义。如果函数是由顶层调用的,那么 caller 包含的就是 null 。如果在字符串上下文中使用 caller 属性,那么结果和 functionName.toString 一样,也就是说,显示的是函数的反编译文本。
下面的例子说明了 caller 属性的用法:

// caller demo {
function callerDemo() {
    
if (callerDemo.caller) {
        
var a= callerDemo.caller.toString();
        alert(a);
    } 
else {
        alert(
"this is a top function");
    }
}
function handleCaller() {
    callerDemo();
}


callee

    返回正被执行的 Function 对象,也就是所指定的 Function 对象的正文。

[function.]arguments.callee

可选项 function 参数是当前正在执行的 Function 对象的名称。

说明

callee 属性的初始值就是正被执行的 Function 对象。

callee 属性是 arguments 对象的一个成员,它表示对函数对象本身的引用,这有利于匿名
函数的递归或者保证函数的封装性
,例如下边示例的递归计算1n的自然数之和。而该属性
仅当相关函数正在执行时才可用。还有需要注意的是callee拥有length属性,这个属性有时候
用于验证还是比较好的。arguments.length是实参长度,arguments.callee.length
形参长度,由此可以判断调用时形参长度是否和实参长度一致。

示例

//callee可以打印其本身
function calleeDemo() {
    alert(arguments.callee);
}
//用于验证参数
function calleeLengthDemo(arg1, arg2) {
    
if (arguments.length==arguments.callee.length) {
        window.alert(
"验证形参和实参长度正确!");
        
return;
    } 
else {
        alert(
"实参长度:" +arguments.length);
        alert(
"形参长度: " +arguments.callee.length);
    }
}
//递归计算
var sum = function(n){
  
if (n <= 0)                        
  
return 1;
  
else
    
return n +arguments.callee(n - 1)
}

比较一般的递归函数:

var sum = function(n){
    
if (1==n) return 1;
else return n + sum (n-1);

调用时:alert(sum(100));
其中函数内部包含了对sum自身的引用,函数名仅仅是一个变量名,在函数内部调用sum即相当于调用
一个全局变量,不能很好的体现出是调用自身,这时使用callee会是一个比较好的方法。

apply and call

   它们的作用都是将函数绑定到另外一个对象上去运行,两者仅在定义参数方式有所区别:

    apply(thisArg,argArray);

    call(thisArg[,arg1,arg2…] ]);

即所有函数内部的this指针都会被赋值为thisArg,这可实现将函数作为另外一个对象的方法运行的目的

apply的说明

如果 argArray 不是一个有效的数组或者不是 arguments 对象,那么将导致一个 TypeError
如果没有提供 argArray
thisArg任何一个参数,那么 Global 对象将被用作 thisArg
并且无法被传递任何参数。

call的说明

call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisArg指定的新对象。
如果没有提供
thisArg参数,那么 Global 对象被用作 thisArg

相关技巧

应用callapply还有一个技巧在里面,就是用callapply应用另一个函数(类)以后,当前的
函数(类)就具备了另一个函数(类)的方法或者是属性,这也可以称之为“继承”。看下面示例:

// 继承的演示
function base() {
    
this.member = " dnnsun_Member";
    
this.method = function() {
        window.alert(
this.member);
    }
}
function extend() {
    base.call(
this);
    window.alert(member);
    window.alert(
this.method);
}


上面的例子可以看出,通过call之后,extend可以继承到base的方法和属性。

 

顺便提一下,在javascript框架prototype里就使用apply来创建一个定义类的模式,

其实现代码如下:

var Class = {
  create: 
function() {
    
return function() {
      
this.initialize.apply(this, arguments);
    }
  }
}

解析:从代码看,该对象仅包含一个方法:Create,其返回一个函数,即类。但这也同时是类的
构造函数,其中调用initialize,而这个方法是在类创建时定义的初始化函数。通过如此途径,
就可以实现prototype中的类创建模式

示例

var vehicle=Class.create();
vehicle.prototype
={
    initialize:
function(type){
        
this.type=type;
    }
    showSelf:
function(){
        alert(
"this vehicle is "+ this.type);
    }
}

var moto=new vehicle("Moto");
moto.showSelf();


更详细的关于prototype信息请到其官方网站查看。

评论还有更精彩对此的理解,在这我就不再添加了,大家可以看看加深理解。



posted @ 2009-04-10 12:41 二胡 阅读(182) | 评论 (0)编辑 收藏

     摘要: 转 http://www.cnitblog.com/yemoo/archive/2008/06/23/45937.html 一、概述 javascript函数劫持,也就是老外提到的javascript hijacking技术。最早还是和剑心同学讨论问题时偶然看到的一段代码,大概这样写的: window.alert = function(s) {}; 觉得这种用法很巧妙新颖,和A...  阅读全文
posted @ 2009-04-10 09:02 二胡 阅读(254) | 评论 (0)编辑 收藏

转 http://blog.csdn.net/nileel/archive/2009/04/08/4056463.aspx
微软关于IE、Firefox、Opera和Safari的JavaScript兼容性研究曾经发表过一份草案,可以点击下载《
JScript Deviations from ES3
 以下为网上的一些搜集和整理(FF代表Firefox)

 集合类对象问题
现有代码中存在许多 document.form.item("itemName") 这样的语句,不能在 FF 下运行
解决方法:
改用 document.form.elements["elementName"]
说明:IE下,可以使用()或[]获取集合类对象;Firefox下,只能使用[]获取集合类对象.
解决方法:统一使用[]获取集合类对象.


window.event
现有问题:
使用 window.event 无法在 FF 上运行
解决方法:
FF 的 event 只能在事件发生的现场使用,此问题暂无法解决。可以这样变通:
原代码(可在IE中运行):
<input type="button" name="someButton" value="提交" onclick="javascript:gotoSubmit()"/>
<script language="javascript">
    function gotoSubmit() {
       alert(window.event);    // use window.event
    }
</script>

新代码(可在IE和FF中运行):
<input type="button" name="someButton" value="提交" onclick="javascript:gotoSubmit(event)"/>
<script language="javascript">
   function gotoSubmit(e) {
      e = e? e : (window.event ? window.event : null);
      alert(e);           
  }
</script>

此外,如果新代码中第一行不改,与老代码一样的话(即 gotoSubmit 调用没有给参数),则仍然只能在IE中运行,但不会出错。所以,这种方案 tpl 部分仍与老代码兼容。

 

HTML 对象的 id 作为对象名的问题
现有问题:
在 IE 中,HTML 对象的 ID 可以作为 document 的下属对象变量名直接使用。在 FF 中不能。
解决方法:
用 getElementById("idName") 代替 idName 作为对象变量使用。

用idName字符串取得对象的问题
现有问题:
在IE中,利用 eval(idName) 可以取得 id 为 idName 的 HTML 对象,在FF 中不能。
解决方法:
用 getElementById(idName) 代替 eval(idName)。

 

变量名与某 HTML 对象 id 相同的问题
现有问题:
在 FF 中,因为对象 id 不作为 HTML 对象的名称,所以可以使用与 HTML 对象 id 相同的变量名,IE 中不能。
解决方法:
在声明变量时,一律加上 var ,以避免歧义,这样在 IE 中亦可正常运行。
此外,最好不要取与 HTML 对象 id 相同的变量名,以减少错误。

 

event.x 与 event.y 问题
现有问题:
在IE 中,event 对象有 x, y 属性,FF中没有。
解决方法:
在FF中,与event.x 等效的是 event.pageX。但event.pageX IE中没有。
故采用 event.clientX 代替 event.x。在IE 中也有这个变量。
event.clientX 与 event.pageX 有微妙的差别(当整个页面有滚动条的时候),不过大多数时候是等效的。
如果要完全一样,可以稍麻烦些:
mX = event.x ? event.x : event.pageX;
然后用 mX 代替 event.x
其它:
event.layerX 在 IE 与 FF 中都有,具体意义有无差别尚未试验。

 

关于frame
现有问题:
在 IE中 可以用window.testFrame取得该frame,FF中不行
解决方法:
在frame的使用方面FF和ie的最主要的区别是:
如果在frame标签中书写了以下属性:
<frame src="/xx.htm" id="frameId" name="frameName" />
那么ie可以通过id或者name访问这个frame对应的window对象
而FF只可以通过name来访问这个frame对应的window对象
例如如果上述frame标签写在最上层的window里面的htm里面,那么可以这样访问
IE: window.top.frameId或者window.top.frameName来访问这个window对象
FF: 只能这样window.top.frameName来访问这个window对象
另外,在FF和ie中都可以使用window.top.document.getElementById("frameId")来访问frame标签
并且可以通过window.top.document.getElementById("testFrame").src = 'xx.htm'来切换frame的内容
也都可以通过window.top.frameName.location = 'xx.htm'来切换frame的内容

 

父结点的问题
在FF中没有 parentElement parentElement.children  而用 parentNode parentNode.childNodes
childNodes的下标的含义在IE和FF中不同,FF使用DOM规范,childNodes中会插入空白文本节点。
一般可以通过node.getElementsByTagName()来回避这个问题。当html中节点缺失时,IE和FF对parentNode的解释不同,例如
<form>
<table>
  <input/>
</table>
</form>

FF中input.parentNode的值为form, 而IE中input.parentNode的值为空节点
FF中节点没有removeNode方法,必须使用如下方法 node.parentNode.removeChild(node)

 

const 问题
现有问题:
在 IE 中不能使用 const 关键字。如 const constVar = 32; 在IE中这是语法错误。
解决方法:
不使用 const ,以 var 代替。

body 对象
FF的body在body标签没有被浏览器完全读入之前就存在,而IE则必须在body完全被读入之后才存在

 

URLencoding
在js中如果书写url就直接写&不要写&例如var url = 'xx.jsp?objectName=xx&objectEvent=xxx';
frm.action = url那么很有可能url不会被正常显示以至于参数没有正确的传到服务器
一般会服务器报错参数没有找到
当然如果是在tpl中例外,因为tpl中符合xml规范,要求&书写为&
一般FF无法识别js中的&

 

nodeName 和 tagName 问题
现有问题:
在FF中,所有节点均有 nodeName 值,但 textNode 没有 tagName 值。在 IE 中,nodeName 的使用好象有问题
解决方法:
使用 tagName,但应检测其是否为空。

 

元素属性
IE下 input.type属性为只读,但是FF下可以修改
document.getElementsByName() 和 document.all[name] 的问题
在 IE 中,getElementsByName()、document.all[name] 均不能用来取得多个具有相同name的div 元素集合。

 

兼容firefox的 outerHTML,FF中没有outerHtml的方法

if (window.HTMLElement) {
  HTMLElement.prototype.__defineSetter__("outerHTML",function(sHTML) {
        var r=this.ownerDocument.createRange();
        r.setStartBefore(this);
        var df=r.createContextualFragment(sHTML);
        this.parentNode.replaceChild(df,this);
        return sHTML;
    });

    HTMLElement.prototype.__defineGetter__("outerHTML",function() {
        var attr;
        var attrs=this.attributes;
        var str="<"+this.tagName.toLowerCase();
        for (var i=0;i<attrs.length;i++) {
            attr=attrs[i];
            if(attr.specified)
                str+=" "+attr.name+'="'+attr.value+'"';
        }

        if(!this.canHaveChildren)
            return str+">";
        return str+">"+this.innerHTML+"</"+this.tagName.toLowerCase()+">";
        });

   HTMLElement.prototype.__defineGetter__("canHaveChildren",function() {
     switch(this.tagName.toLowerCase()) {
         case "area":
         case "base":
         case "basefont":
         case "col":
         case "frame":
         case "hr":
         case "img":
         case "br":
         case "input":
         case "isindex":
         case "link":
         case "meta":
         case "param":
         return false;
     }
     return true;
   });
}


自定义属性问题
说明:IE下,可以使用获取常规属性的方法来获取自定义属性,也可以使用getAttribute()获取自定义属性;FF下,只能使用getAttribute()获取自定义属性.
解决方法:统一通过getAttribute()获取自定义属性.

 
event.srcElement问题
说明:IE下,even对象有srcElement属性,但是没有target属性;Firefox下,even对象有target属性,但是没有srcElement属性.
解决方法:使用obj(obj = event.srcElement ? event.srcElement : event.target;)来代替IE下的event.srcElement或者Firefox下的event.target.


window.location.href问题
说明:IE或者Firefox2.0.x下,可以使用window.location或window.location.href;Firefox1.5.x下,只能使用window.location.
解决方法:使用window.location来代替window.location.href.


模态和非模态窗口问题
说明:IE下,可以通过showModalDialog和showModelessDialog打开模态和非模态窗口;Firefox下则不能.
解决方法:直接使用window.open(pageURL,name,parameters)方式打开新窗口
如果需要将子窗口中的参数传递回父窗口,可以在子窗口中使用window.opener来访问父窗口. 例如:var parWin = window.opener; parWin.document.getElementById("Aqing").value = "Aqing";

 

事件委托方法
IE:document.body.onload = inject; //Function inject()在这之前已被实现
FF:document.body.onload = inject();
如果要加传递参数,可以做个闭包
(function(arg){

   document.body.onload=function(){inject(arg);};

})(arg)

innerText在IE中能正常工作,但是innerText在FireFox中却不行.
解决方法:
if(navigator.appName.indexOf("Explorer") > -1){
    document.getElementById('element').innerText = "my text";
} else{
    document.getElementById('element').textContent = "my text";
}


FF中类似 obj.style.height = imgObj.height 的语句无效
解决方法:
obj.style.height = imgObj.height + 'px';


IE,FF以及其它浏览器对于 table 标签的操作都各不相同,在ie中不允许对table和tr的innerHTML赋值,使用js增加一个tr时,使用appendChile方法也不管用
解决方法:
//向table追加一个空行:
var row = otable.insertRow(-1);
var cell = document.createElement("td");
cell.innerHTML = " ";
cell.className = "XXXX";
row.appendChild(cell);


样式部分
-----------------------------------------------------------------------------------------------------------
cursor:hand VS cursor:pointer
FF不支持hand,但ie支持pointer
解决方法: 统一使用pointer

padding 问题
padding 5px 4px 3px 1px FireFox无法解释简写,
必须改成 padding-top:5px; padding-right:4px; padding-bottom:3px; padding-left:1px;

消除ulol等列表的缩进时
样式应写成:list-style:none;margin:0px;padding:0px;
其中margin属性对IE有效,padding属性对FireFox有效

CSS透明
IE:filter:progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=60)
FF:opacity:0.6

CSS圆角
IE:不支持圆角
FF: -moz-border-radius:4px,或者-moz-border-radius-topleft:4px;-moz-border- radius-topright:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius- bottomright:4px;

CSS双线凹凸边框
IE:border:2px outset;
FF: -moz-border-top-colors: #d4d0c8 white;-moz-border-left-colors: #d4d0c8 white;-moz-border-right-colors:#404040 #808080;-moz-border-bottom-colors:#404040 #808080;

posted @ 2009-04-09 21:02 二胡 阅读(352) | 评论 (1)编辑 收藏

转 http://www.52web.com/52article/?view-145.html

摘要: 现有的 JavaScript 引擎是单线程处理任务的。它把任务放到队列中,不会同步去执行,必须在完成一个任务后才开始另外一个任务。

由 John Resig 的 How JavaScript Timers Work 可以知道,现有的 JavaScript 引擎是单线程处理任务的。它把任务放到队列中,不会同步去执行,必须在完成一个任务后才开始另外一个任务。

让我们看看之前的文章:JavaScript的9个陷阱及评点,在第 9 点 Focus Pocus 中提到的问题。原作者对这个认识有所偏差,其实不只是 IE 的问题,而是现有 JavaScript 引擎对于线程实现的问题(关于线程,我的概念其实不多,如果不对,希望读者多多指教)。我们通过一个关于settimeout的例子来说明,请访问 http://realazy.org/lab/settimeout.html. 我们来看 1 和 2。如果你能看看源代码,会发现我们的任务很简单,就是给文档增加一个 input 文本框,并聚焦和选中。请现在分别点击一下,可以看到,1 并没有能够聚焦和选中,而 2 可以。它们之间的区别在于,在执行:


  1. input.focus();  
  2. input.select();  

时, 2 多了一个延迟时间为 0 的 setTimeout 的外围函数,即:

  1. setTimeout(function(){  
  2. input.focus();  
  3. input.select();  
  4. }, 0);  

按照 JavaScript: The Definitive Guide 5th 的 14.1 所说:

在实践中,setTimeout 会在其完成当前任何延宕事件的事件处理器的执行,以及完成文档当前状态更新后,告诉浏览器去启用 setTimeout 内注册的函数。

其实,这是一个把需要执行的任务从队列中跳脱的技巧。回到前面的例子,JavaScript 引擎在执行 onkeypress 时,由于没有多线程的同步执行,不可能同时去处理刚创建元素的 focus 和 select 事件,由于这两个事件都不在队列中,在完成 onkeypress 后,JavaScript 引擎已经丢弃了这两个事件,正如你看到的例子 1 的情况。而在例子 2 中,由于setTimeout可以把任务从某个队列中跳脱成为新队列,因而能够得到期望的结果。

这才是延迟事件为 0 的setTimeout的真正目的。在此,你可以看看例子 3,它的任务是实时更新输入的文本,现在请试试,你会发现预览区域总是落后一拍,比如你输 a, 预览区并没有出现 a, 在紧接输入 b 时, a 才不慌不忙地出现。其实我们是有办法让预览区跟输入框同步地,在此我没有给出答案,因为上面所说的,就是解决思路,try it yourself!



posted @ 2009-04-08 13:04 二胡 阅读(310) | 评论 (2)编辑 收藏

转 http://www.52web.com/52article/?view-144.html

摘要: JavaScript容易犯错的九个陷阱,虽然不是什么很高深的技术问题,但注意一下,会使您的编程轻松些,即所谓make life easier. 笔者对某些陷阱会混杂一些评点。

以下是JavaScript容易犯错的九个陷阱,来自 Nine Javascript Gotchas 。虽然不是什么很高深的技术问题,但注意一下,会使您的编程轻松些,即所谓make life easier. 笔者对某些陷阱会混杂一些评点。

1.最后一个逗号

如这段代码,注意最后一个逗号,按语言学角度来说应该是不错的(python的类似数据类型辞典dictionary就允许如此)。IE会报语法错误,但语焉不详,你只能用人眼从几千行代码中扫描。


  1. <script>  
  2. var theObj = {  
  3. city : "Boston",  
  4. state : "MA",  
  5. }  
  6. </script>  

2.this的引用会改变

如这段代码:


  1. <input type="button" value="Gotcha!" id="MyButton" >  
  2. <script>  
  3. var MyObject = function () {  
  4. this.alertMessage = "Javascript rules";  
  5. this.ClickHandler = function() {  
  6. alert(this.alertMessage );  
  7. }  
  8. }();  
  9. document.getElementById(”theText”).onclick =  MyObject.ClickHandler  
  10. </script>  

并不如你所愿,答案并不是”JavaScript rules”。在执行MyObject.ClickHandler时,代码中红色这行,this的引用实际上指向的是document.getElementById("theText")的引用。可以这么解决:


  1. <input type="button" value="Gotcha!" id="theText" >  
  2. <script>  
  3. var MyObject = function () {  
  4. var self = this;  
  5. this.alertMessage = “Javascript rules”;  
  6. this.OnClick = function() {  
  7. alert(self.value);  
  8. }  
  9. }();  
  10. document.getElementById(”theText”).onclick =  MyObject.OnClick  
  11. </script>  

实质上,这就是JavaScript作用域的问题。如果你看过,你会发现解决方案不止一种。

3.标识盗贼

在JavaScript中不要使用跟HTML的id一样的变量名。如下代码:


  1. <input type="button" id="TheButton">  
  2. <script>  
  3. TheButton = get("TheButton");  
  4. </script>  

IE会报对象未定义的错误。我只能说:IE sucks.

4.字符串只替换第一个匹配

如下代码:


  1. <script>  
  2. var fileName = "This is a title".replace(" ","_");  
  3. </script>  

而实际上,结果是”This_is a title“. 在JavaScript中,String.replace的第一个参数应该是正则表达式。所以,正确的做法是这样:


  1. var fileName = "This is a title".replace(/ /g,"_");  

5.mouseout意味着mousein

事实上,这是由于事件冒泡导致的。IE中有mouseenter和mouseleave,但不是标准的。作者在此建议大家使用库比如YUI来解决问题。

6.parseInt是基于进制体系的

这个是常识,可是很多人给忽略了parseInt还有第二个参数,用以指明进制。比如,parseInt("09"),如果你认为答案是9,那就错 了。因为,在此,字符串以0开头,parseInt以八进制来处理它,在八进制中,09是非法,返回false,布尔值false转化成数值就是0. 因此,正确的做法是parseInt("09", 10).

7.for...in...会遍历所有的东西

有一段这样的代码:


  1. var arr = [5,10,15]  
  2. var total = 1;  
  3. for ( var x in arr) {  
  4. total = total * arr[x];  
  5. }  

运行得好好的,不是吗?但是有一天它不干了,给我返回的值变成了NaN, 晕。我只不过引入了一个库而已啊。原来是这个库改写了Array的prototype,这样,我们的arr平白无过多出了一个属性(方法),而 for...in...会把它给遍历出来。所以这样做才是比较安全的:


  1. for ( var x = 0; x < arr.length; x++) {  
  2. total = total * arr[x];  
  3. }  

其实,这也是污染基本类的prototype会带来危害的一个例证。

8.事件处理器的陷阱

这其实只会存在使用作为对象属性的事件处理器才会存在的问题。比如window.onclick = MyOnClickMethod这样的代码,这会复写掉之前的window.onclick事件,还可能导致IE的内容泄露(sucks again)。在IE还没有支持DOM 2的事件注册之前,作者建议使用库来解决问题,比如使用YUI:


  1. YAHOO.util.Event.addListener(window"click", MyOnClickMethod);  

这应该也属于常识问题,但新手可能容易犯错。

9.Focus Pocus

新建一个input文本元素,然后把焦点挪到它上面,按理说,这样的代码应该很自然:


  1. var newInput = document.createElement("input");  
  2. document.body.appendChild(newInput);  
  3. newInput.focus();  
  4. newInput.select();  

但是IE会报错(sucks again and again)。理由可能是当你执行fouce()的时候,元素尚未可用。因此,我们可以延迟执行:


  1. var newInput = document.createElement("input");  
  2. newInput.id = "TheNewInput";  
  3. document.body.appendChild(newInput);  
  4. setTimeout(function(){ //这里我使用闭包改写过,若有兴趣可以对比原文  
  5. document.getElementById('TheNewInput').focus();  
  6. document.getElementById('TheNewInput').select();}, 10);  

在实践中,JavaScript的陷阱还有很多很多,大多是由于解析器的实现不到位而引起。这些东西一般都不会在教科书中出现,只能靠开发者之间的经验分享。谢天谢地,我们生活在网络时代,很多碰到的问题,一般都可以在Google中找到答案。



posted @ 2009-04-08 12:59 二胡 阅读(166) | 评论 (0)编辑 收藏

     摘要: 转 http://www.blogjava.net/Werther/archive/2009/04/03/263653.html Java编码规范               1 介绍(Introduction)     ...  阅读全文
posted @ 2009-04-03 15:09 二胡 阅读(243) | 评论 (0)编辑 收藏

   在http://www.cnitblog.com/yemoo/archive/2008/06/18/45850.html文中巧用try finally;但是让我对finally执行过程有点疑惑,发现java的try catch功能和js是一样的。下面列出例子:
   public int test1(){
   int i=4;
   try{ return i;}finally{ i=0;System.out.println("---test----");}
}
   执行结果:输出---test----,test1方法返回4;我的疑惑是为什么不返回0
   在QQ群里讨论的时候,有人说finally中的语句在try中的return后执行。但是如下代码执行否决了上面的结论。
public int test2(){
   int i=4;
   try{ return i;}finally{ i=0;System.out.println("---test----");return i;}
}
   注意:在finally中多了个return i;
   执行结果:输出---test----,test2方法返回0;
   这个例说明了finally中的语句是在try的return执行前执行的。那么test1方法的finally中i=0了,但是为什么test1方法还返回4呢?这是我的疑惑,那位知道解释一下。

posted @ 2009-04-01 11:54 二胡 阅读(2178) | 评论 (10)编辑 收藏

转 http://www.cnitblog.com/yemoo/archive/2007/11/30/37070.html

JavaScript中有一个call和apply方法,其作用基本相同,但也有略微的区别。

先来看看JS手册中对call的解释:

call 方法
调用一个对象的一个方法,以另一个对象替换当前对象。

call([thisObj[,arg1[, arg2[,   [,.argN]]]]])

参数
thisObj
可选项。将被用作当前对象的对象。

arg1, arg2,  , argN
可选项。将被传递方法参数序列。

说明
call 方法可以用来代替另一个对象调用一个方法。call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。

如果没有提供 thisObj 参数,那么 Global 对象被用作 thisObj。

说明白一点其实就是更改对象的内部指针,即改变对象的this指向的内容。这在面向对象的js编程过程中有时是很有用的。

引用网上一个代码段,运行后自然就明白其道理。

<input type="text" id="myText"   value="input text">
<script>
    
function Obj(){this.value="对象!";}
    
var value="global 变量";
    
function Fun1(){alert(this.value);}

    window.Fun1();   
//global 变量
    Fun1.call(window);  //global 变量
    Fun1.call(document.getElementById('myText'));  //input text
    Fun1.call(new Obj());   //对象!
</script>

call函数和apply方法的第一个参数都是要传入给当前对象的对象,及函数内部的this。后面的参数都是传递给当前对象的参数。
运行如下代码:
<script>
   
var func=new function(){this.a="func"}
    
var myfunc=function(x){
        
var a="myfunc";
        alert(
this.a);
        alert(x);
    }
    myfunc.call(func,
"var");
</script>

可见分别弹出了func和var。到这里就对call的每个参数的意义有所了解了。

对于apply和call两者在作用上是相同的,但两者在参数上有区别的。
对于第一个参数意义都一样,但对第二个参数:
apply传入的是一个参数数组,也就是将多个参数组合成为一个数组传入,而call则作为call的参数传入(从第二个参数开始)。

如 func.call(func1,var1,var2,var3)对应的apply写法为:func.apply(func1,[var1,var2,var3])

同时使用apply的好处是可以直接将当前函数的arguments对象作为apply的第二个参数传入

posted @ 2009-04-01 09:30 二胡 阅读(214) | 评论 (0)编辑 收藏

转 http://www.blogjava.net/Unmi/articles/260197.html
jQuery, MooTools, Prototype 等优秀的 JavaScript 框架拥有各种强大的功能,包括绘制 Web 图表,使用这些框架以及相应插件,我们可以非常轻松地实现曲线图,圆饼图,柱状图等 Web 图表的绘制,而不必象以往那样通过复杂的 Flash 技术实现。本文介绍了9个优秀的基于 JavaScript 与 CSS  的 Web 图表框架。


jQuery, MooTools, Prototype 等优秀的 JavaScript 框架拥有各种强大的功能,包括绘制 Web 图表,使用这些框架以及相应插件,我们可以非常轻松地实现曲线图,圆饼图,柱状图等 Web 图表的绘制,而不必象以往那样通过复杂的 Flash 技术实现。本文介绍了9个优秀的基于 JavaScript 与 CSS 的 Web 图表框架。
1. Flot


Flot 是一个纯粹的 jQuery JavaScript 绘图库,可以在客户端即时生成图形,使用非常简单,支持放大缩小以及鼠标追踪等交互功能。该插件支持 IE6/7/8, Firefox 2.x+, Safari 3.0+, Opera 9.5+ 以及 Konqueror 4.x+。使用的是 Safari 最先引入的 Canvas 对象,目前所有主流浏览器都支持该对象,除了 IE, 因此在 IE中使用 JavaScript 进行模拟。这里有一些实例
2. JS Charts


JS Charts 是一个免费的基于 JavaScript 的图表生成器,表格绘制非常简单,几乎不需要编码,也不需要插件和服务器模块,使用XML 或 JavaScript 数组缓存数据。
3. TableToChart
TableToChart 是一个 MooTools 脚本,可以将 HTML Table 对象中存储的数据绘制成图表。你可以使用 table 标签生成图表,柱状图,曲线图,圆饼图等。
4. PlotKit
PlotKit 是一个 JavaScript 绘图库,支持 HTML Canvas 标签,也支持 SVG。
5. Yahoo UI Charts Control


YUI Charts Control 可以在网页上将表格数据转换为图表,支持柱状图,曲线图以及圆饼图。支持 DataSource 工具,可设置的轴,鼠标盘旋提示,图表组合,以及皮肤等功能。
6. ProtoChart


ProtoChart 是一个基于 Prototype 和 Canvas 标签的开源库,这个库深受 Flot, Flotr, Plotkit 等启发,支持曲线图,柱状图,圆饼图等,可以在同一个图表上显示多套数据,支持可定制的图例,网格,边界以及背景图。支持 IE6/7, Firefox 2/3 以及 Safari,甚至支持 iPhone.
7. EJSChart


EJSChart 支持鼠标追踪,鼠标事件,按键追踪与事件,缩放,滚动,交互等功能,将用户体验上升到一个新高度。支持曲线图,面积图,离散图,圆饼图,柱状图等形式,拥有完备文档的属性和方法可以帮助实现很好的定制。
8. fgCharting
fgCharting 是一个很出色的 jQuery 插件,支持多种图形。
9. Pure Css Data Chart


以往的数据展示往往通过 flash 实现,现在,我们可以通过纯粹的 CSS 实现类似的功能。CSSGlobe 有一个非常实用的教程帮你实现基于 CSS 的绘图,甚至不需要 JavaScript。

本文国际来源:http://woork.blogspot.com/2009/03/useful-scripts-to-plot-charts-in-web.html
中文翻译来源:COMSHARP CMS 官方网站

摘自:http://www.cnbeta.com/articles/79418.htm



posted @ 2009-03-25 18:26 二胡 阅读(132) | 评论 (0)编辑 收藏

转 http://www.v-ec.com/dh20156/article.asp?id=87
在网上查阅了不少Javascript闭包(closure)相关的资料,写的大多是非常的学术和专业。对于初学者来说别说理解闭包了,就连文字叙述都很难看懂。撰写此文的目的就是用最通俗的文字揭开Javascript闭包的真实面目。

一、什么是闭包?
“官方”的解释是:所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。
相信很少有人能直接看懂这句话,因为他描述的太学术。我想用如何在Javascript中创建一个闭包来告诉你什么是闭包,因为跳过闭包的创建过程直接理解闭包的定义是非常困难的。看下面这段代码:
function a(){
 var i=0;
 function b(){
 alert(++i);
 }
 return b;
}
var c = a();
c();
这段代码有两个特点:
1、函数b嵌套在函数a内部;
2、函数a返回函数b。
这样在执行完var c=a()后,变量c实际上是指向了函数b,再执行c()后就会弹出一个窗口显示i的值(第一次为1)。这段代码其实就创建了一个闭包,为什么?因为函数a外的变量c引用了函数a内的函数b,就是说:
当函数a的内部函数b被函数a外的一个变量引用的时候,就创建了一个闭包。

我猜想你一定还是不理解闭包,因为你不知道闭包有什么作用,下面让我们继续探索。

二、闭包有什么作用?
简而言之,闭包的作用就是在a执行完并返回后,闭包使得Javascript的垃圾回收机制GC不会收回a所占用的资源,因为a的内部函数b的执行需要依赖a中的变量。这是对闭包作用的非常直白的描述,不专业也不严谨,但大概意思就是这样,理解闭包需要循序渐进的过程。
在上面的例子中,由于闭包的存在使得函数a返回后,a中的i始终存在,这样每次执行c(),i都是自加1后alert出i的值。

那 么我们来想象另一种情况,如果a返回的不是函数b,情况就完全不同了。因为a执行完后,b没有被返回给a的外界,只是被a所引用,而此时a也只会被b引 用,因此函数a和b互相引用但又不被外界打扰(被外界引用),函数a和b就会被GC回收。(关于Javascript的垃圾回收机制将在后面详细介绍)

三、闭包内的微观世界
如 果要更加深入的了解闭包以及函数a和嵌套函数b的关系,我们需要引入另外几个概念:函数的执行环境(excution context)、活动对象(call object)、作用域(scope)、作用域链(scope chain)。以函数a从定义到执行的过程为例阐述这几个概念。

1、当定义函数a的时候,js解释器会将函数a的作用域链(scope chain)设置为定义a时a所在的“环境”,如果a是一个全局函数,则scope chain中只有window对象。
2、当函数a执行的时候,a会进入相应的执行环境(excution context)
3、在创建执行环境的过程中,首先会为a添加一个scope属性,即a的作用域,其值就为第1步中的scope chain。即a.scope=a的作用域链。
4、然后执行环境会创建一个活动对象(call object)。活动对象也是一个拥有属性的对象,但它不具有原型而且不能通过JavaScript代码直接访问。创建完活动对象后,把活动对象添加到a的作用域链的最顶端。此时a的作用域链包含了两个对象:a的活动对象和window对象。
5、下一步是在活动对象上添加一个arguments属性,它保存着调用函数a时所传递的参数。
6、最后把所有函数a的形参和内部的函数b的引用也添加到a的活动对象上。在这一步中,完成了函数b的的定义,因此如同第3步,函数b的作用域链被设置为b所被定义的环境,即a的作用域。

到此,整个函数a从定义到执行的步骤就完成了。此时a返回函数b的引用给c,又函数b的作用域链包含了对函数a的活动对象的引用,也就是说b可以访问到a中定义的所有变量和函数。函数b被c引用,函数b又依赖函数a,因此函数a在返回后不会被GC回收。

当函数b执行的时候亦会像以上步骤一样。因此,执行时b的作用域链包含了3个对象:b的活动对象、a的活动对象和window对象,如下图所示:


如图所示,当在函数b中访问一个变量的时候,搜索顺序是先搜索自身的活动对象,如果存在则返回,如果不存在将继续搜索函数a的活动对象,依 次查找,直到找到为止。如果整个作用域链上都无法找到,则返回undefined。如果函数b存在prototype原型对象,则在查找完自身的活动对象 后先查找自身的原型对象,再继续查找。这就是Javascript中的变量查找机制。

四、闭包的应用场景
1、保护函数内的变量安全。以最开始的例子为例,函数a中i只有函数b才能访问,而无法通过其他途径访问到,因此保护了i的安全性。
2、在内存中维持一个变量。依然如前例,由于闭包,函数a中i的一直存在于内存中,因此每次执行c(),都会给i自加1。
以上两点是闭包最基本的应用场景,很多经典案例都源于此。

五、Javascript的垃圾回收机制
在Javascript中,如果一个对象不再被引用,那么这个对象就会被GC回收。如果两个对象互相引用,而不再被第3者所引用,那么这两个互相引用的对象也会被回收。因为函数a被b引用,b又被a外的c引用,这就是为什么函数a执行后不会被回收的原因。



posted @ 2009-03-25 18:15 二胡 阅读(132) | 评论 (0)编辑 收藏

转 http://www.blogjava.net/bibi/archive/2006/08/21/64890.html

1.   简介

1.1     结构

DECLARE -- 定义

BEGIN  -- 执行部分

EXCEPTION  -- 例外处理

END;  -- 结束

 

set serveroutput on;
DECLARE v_ename VARCHAR2(
5 );
BEGIN
SELECT ename INTO v_ename FROM emp WHERE empno=&no;
dbms_output.put_line(
'¹ÍÔ±Ãû:' || v_ename);
EXCEPTION
WHEN NO_DATA_FOUND THEN
dbms_output.put_line(
'please input correct employees no!' );
END;
/

 

1.2     块分类

<<outer>> <<inner>>

1.3     子程序

1.1.1       过程

执行特定的操作

  CREATE or replace PROCEDURE update_sal(nameVARCHAR2,newsal NUMBER)
IS
BEGIN
     UPDATE emp SET sal=newsal WHERE lower(ename)=lower(name);
END;
/

调用:

exec update_sal( 'SMITH' , 10 )
call update_sal(
'SMITH' , 800 )

1.1.2       函数

返回特定数据

CREATE or replace FUNCTION annual_income(nameVARCHAR2)
RETURNNUMBERIS
       annual_salary NUMBER(
7 , 2 );
BEGIN
SELECT sal*
12 + nvl(comm, 0 )  into annual_salary FROM emp WHERE lower(ename)=lower(name);
RETURN annual_salary;
END;
/

调用 :

SQL> VAR income NUMBER

SQL> CALL annual_income('scott') INTO : income;

调用完成。

SQL> print income

    INCOME

----------

     36000

1.1.3      

逻辑组合相关的过程和函数

-- 包规范
CREATE or replace PACKAGE emp_pkg IS
  PROCEDURE update_sal(nameVARCHAR2, newsal NUMBER);
  FUNCTION annual_income(nameVARCHAR2) RETURNNUMBER;
END;

-- 包体
CREATE or replace PACKAGE BODY emp_pkg IS
       PROCEDURE update_sal(nameVARCHAR2, newsal NUMBER)
       IS
       BEGIN
            UPDATE emp SET sal = newsal WHERE lower(ename) = lower(name);
       END;
      
       FUNCTION annual_income(nameVARCHAR2) RETURNNUMBER
       IS
         annual_salary NUMBER(
7 , 2 );
       BEGIN
            SELECT sal *
12 + nvl(comm, 0 ) into annual_salary FROM emp WHERE lower(ename) = lower(name);
            RETURN annual_salary;
       END;
END;
/

调用:

SQL> call emp_pkg .update_sal('SMITH',1500);

Or

SQL> VAR income NUMBER

SQL> CALL emp_pkg . annual_income('scott') INTO : income;

调用完成。

SQL> print income

    INCOME

----------

     36000

1.4     触发器

     是隐含执行的存储过程。

create or replace trigger update_cascade
 afterupdateof deptno on dept
 foreachrow
begin
     update emp set deptno=:new.deptno
     where deptno=:old.deptno;
end;
/    

2.   定义并使用变量

1.5     标量变量

1.1.4       特殊变量说明

LONG(32760 字节 ) VARCHAR2(32767 字节 ) 类似,定义变长的字符串

LONG RAW 用于定义变长的二进制数据 (32760 字节 )

BINARY_INTEGER 定义整数,范围为: -2147483647~2147483647 ( 非表列使用 )

BOOLEAN TRUE/FALSE/NULL ( 非表列使用 )

BINARY_FLOAT/BINARY_DOUBLE ORACLE10 所有

1.1.5       定义使用

Identifier [CONSTANT] datatype [not null] [:= | default expr]

例如: v_valid BOOLEAN NOT NULL DEFAULT FALSE;

C_tax_rate CONSTANT NUMBER(3,2):=0.03;

V_ename emp.ename%TYPE;

1.6     复合变量

1.1.6       Pl/sql 记录

类似于高级语言中的结构

DECLARE
TYPE emp_record_type ISRECORD(
    name emp.ename%TYPE,
    salary emp.sal%TYPE,
  title emp.job%TYPE
);
emp_record emp_record_type;
BEGIN
     SELECT ename,sal,job INTO emp_record FROM emp WHERE empno=
7788 ;
     dbms_output.put_line(
' 雇员名 ' ||emp_record.name);
end;
/    

1.1.7       Pl/sql

类似于高级语言中的数组,下标可以为负 , 个数无限制。

DECLARE
TYPE ename_table_type ISTABLEOF emp.ename%TYPE
     INDEXBYBINARY_INTEGER;
     ename_table ename_table_type;
BEGIN
     SELECT ename  INTO ename_table(-
1 ) FROM emp WHERE empno= 7788 ;
     dbms_output.put_line(
' 雇员名 ' ||ename_table(- 1 ));
end;
/    

1.1.8       嵌套表

类似于高级语言中的数组,下标不可以为负,个数无限制。

CREATE OR REPLACE TYPE emp_type  ASOBJECT(
       nameVARCHAR2(
10 ),
       salary NUMBER(
6 , 2 ),
       hiredate DATE
);
/
CREATEORREPLACETYPE emp_array ISTABLEOF emp_type;
/

CREATEORREPLACEtable department(
       deptno NUMBER(
2 ),
       dname VARCHAR2(
10 ),
       employee emp_array      
)nestedtable employee storeas employee;

 

1.1.9       VARRAY

VARRAY 类似于嵌套表,它可以作为表列和对象类型属性的数据类型,个数有限制。

CREATE OR REPLACE TYPE article_type  ASOBJECT(
       title VARCHAR2(
30 ),
       pubdate DATE
);
/
CREATEORREPLACETYPE article_array ISVARRAY(
20 ) OF article_type;
/

CREATEORREPLACEtable author(
       idNUMBER(
6 ),
       nameVARCHAR2(
10 ),
       article article_array      
);

1.7     参照变量

用于存放数值指针的变量。使得应用程序共享相同对象,从而降低占用空间。

1.1.10 REF CURSOR

游标变量

DECLARE
  TYPE c1 ISREFCURSOR;
  emp_cursor c1;
  v_ename emp.ename%TYPE;
  v_sal   emp.sal%TYPE;
BEGIN
  OPEN emp_cursor FOR
    SELECT ename, sal FROM emp ;
--WHERE deptno = 10;
  LOOP
    FETCH emp_cursor
      INTO v_ename, v_sal;
    EXITWHEN emp_cursor%NOTFOUND;
    dbms_output.put_line(v_ename);
  ENDLOOP;
  CLOSE emp_cursor;
END;
/

1.1.11 REF obj_type

为了共享相同对象,可以用 ref 引用对象类型。

CREATE OR REPLACE TYPE home_type AS OBJECT(

       street VARCHAR2(50),city VARCHAR2(20),

       state VARCHAR2(20),zipcode VARCHAR2(6),

       owner VARCHAR2(10)

);

/

CREATE TABLE homes OF home_type;

INSERT INTO homes VALUES(' 呼伦北路 12 ',' 呼和浩特 ',' 内蒙 ','010010',' 马鸣 ');

INSERT INTO homes VALUES(' 呼伦北路 13 ',' 呼和浩特 ',' 内蒙 ','010010',' 秦斌 ');

CREATE TABLE person(

       id NUMBER(6) PRIMARY KEY,

       name VARCHAR2(10), addr REF home_type

);

INSERT INTO  person SELECT 1,' 马鸣 ',ref(p) FROM homes p WHERE p.owner=' 马鸣 ';

1.8     LOB 变量

内部 : CLOB BLOB NCLOB ;外部: BFILE

1.9     PL/SQL 变量

1.1.12 使用 sql*plus 变量

var namevarchar2( 10 )
begin
     select ename into :namefrom emp
     where empno=
7788 ;
end;
/
print name  

 

1.1.13 使用 procedure Builder 变量

.createcharname length 10
begin
     select ename into :namefrom emp
     where empno=
7788 ;
end;
/
text_to.put_line(:name);  

1.1.14 使用 pro*c/c++ 变量

char name[ 10 ];
execsqlexecute
begin
     select ename into :namefrom emp
     where empno=
7788 ;
end-exec;
printf(
' 雇员名: %s\n' ,name);

posted @ 2009-03-25 11:24 二胡 阅读(214) | 评论 (0)编辑 收藏

转 http://www.cn-cuckoo.com/2008/09/13/the-origin-of-jsonp-262.html

浏览器安全模型规定,XMLHttpRequest、框架(frame)等只能在一个域中通信。从安全角度考虑,这个规定很合理;但是,也确实给分布式(面向服务、混搭等等本周提到的概念)Web开发带来了麻烦。

为了实现跨域通信,通常的解决方案有3种:

本地代理:
需要一些硬件设施(没有服务器的客户端无法运行),并且带宽和潜伏时间也要加倍(远程服务器-代理服务器-客户端)。

Flash:
远程主机中需要部署一个crossdomain.xml文件,而且,Flash作为一门专有技术,其前途尚不明朗;换句话说,开发人员很可能要学习一种目标不确定的编程语言。

Script标签:
无法确切知道内容是否有效,没有标准的实现方法,又可能被认为是一种“安全风险”。


在此,我建议使用一种新技术,也是一种独立于标准的方法,即通过script标签来跨域获取数据,名为JSON with Padding,或者就叫JSONP。JSONP的原理很简单,但需要服务器端给予相应配合。大致来说,JSONP的实现思路就是在客户端编程时作好使用JSON数据的准备,然后再通过圆括号将这些数据括起来以创建一条有效的JavaScript语句(可能是一次有效的函数调用)。

也就是说,客户端可以使用一个用于命名jsonp的查询参数来决定可以获取的数据。最简单的情况下,如果jsonp参数为空,则返回的数据就是被括在圆括号中的JSON。

下面,我们就以del.icio.us的JSON API为例,来说明JSONP的原理。该API有一个“script tag”变量(即,可以将下面的URL作为script标签的src属性值,用以加载del.icio.us这个API提供的数据。——译者注)如下所示:

http://del.icio.us/feeds/json/bob/mochikit+interpreter:

  1. if(typeof(Delicious) == 'undefined') Delicious = {};
  2. Delicious.posts = [{
  3. "u": "http://mochikit.com/examples/interpreter/index.html",
  4. "d": "Interpreter - JavaScript Interactive Interpreter",
  5. "t": [
  6. "mochikit","webdev","tool","tools",
  7. "javascript","interactive","interpreter","repl"
  8. ]
  9. }]

如果用JSONP的方式来表示,那么与此具有相同语义的URL应该是这样的:

http://del.icio.us/feeds/json/bob/mochikit+interpreter?
jsonp=if(typeof(Delicious)%3D%3D%27undefined%27)
Delicious%3D%7B%7D%3BDelicious.posts%3D

单纯看这个URL似乎没有什么,但我们可以要求服务器在数据有效时给出通知。因此,我可以编写一个用于跟踪数据的小系统:

  1. var delicious_callbacks = {};
  2. function getDelicious(callback, url) {
  3. var uid = (new Date()).getTime();
  4. delicious_callbacks[uid] = function () {
  5. delete delicious_callbacks[uid];
  6. callback();
  7. };
  8. url += "?jsonp=" + encodeURIComponent("delicious_callbacks[" + uid + "]");
  9. // 手工输入代码,向文档中插入script标签
  10. };
  11.  
  12. getDelicious(doSomething, "http://del.icio.us/feeds/json/bob/mochikit+interpreter");

根据以上假设,用于获取数据的URL应该如下所示:
http://del.icio.us/feeds/json/bob/mochikit+interpreter?jsonp=delicious_callbacks%5B12345%5D

  1. delicious_callbacks[12345]([{
  2. "u": "http://mochikit.com/examples/interpreter/index.html",
  3. "d": "Interpreter - JavaScript Interactive Interpreter",
  4. "t": [
  5. "mochikit","webdev","tool","tools",
  6. "javascript","interactive","interpreter","repl"
  7. ]
  8. }])

可见,由于使用圆括号括住了返回的数据,这就相当于把一个JSONP请求转化成了一次函数调用,或者得到了一个纯粹的JSON直接量。服务器所要配合做的,就是在JSON数据的开头添加一小段文本(即回调函数的名称。——译者注)并将JSON数据放在括号中!

当然,接下来最好是使用Mochikit、Dojo等框架来抽象JSONP,从而让自己省去动手编写DOM以插入script标签的麻烦。

没错,JSONP只是解决了标准化的问题。假如远程主机想通过script标签向页面中注入恶意代码,而不是返回JSON数据,那么页面安全可能会 随时受到威胁。不过,一旦实现了JSONP,那么对开发人员来说肯定是一件省时省力的大好事,在此基础上各种一般化的抽象、教程及文档也会应运而生的。

注:缩写词 JSONP 由 Bob Ippolito 在一篇名为 “Remote JSON - JSONP” 的文章中提出。但许多支持以 JSONP 技术实现跨域通信的厂商没有称其为 JSONP。例如,雅虎公司就称这种技术为 “JSON with callbacks”。另外,原文发表于2005年12月5日。

留言(9)

hello cuckoo九月 16th, 2008 at 9:42 上午

中秋好。
怎么把我的链接去掉了呢?
给我做个链接可以吗???
非常感谢!!
我的EMAIL:hardcometure@163.com

vampire九月 16th, 2008 at 8:41 下午

大概是说通过script的src来实现跨域,通过经编码的json在url中传递数据?
是不是说页面上的js生产带有不同参数的jsonp,通过src传递给服务端,服务端根据该jsonp返回相应数据?有点不太明白,能否提供一个实例?谢谢

admin九月 17th, 2008 at 8:43 下午

@vampire

你的理解是对的,看来我的翻译你能看明白,呵呵。使用JSONP技术时,一般是由JS在客户端页面中动态插入script标签,将其src属性设置 为带参数的URL。当页面加载时,前述URL会将参数通过GET请求发送到相应服务器端应用程序(由URL表示),服务器根据参数返回数据——注意,这个 数据格式是JSON,并且(注意)被包含在一个函数调用中,例如:callback({json_data})。这样,在客户端页面中存在预定义的 callback(data)函数的定义时,服务器返回的函数调用就会立即执行,由客户端的函数对数据进行操作。

实际的例子有很多,如Yahoo Search、Google Base、Flickr Search、Amazon Search等等。要学习和使用这些站点提供的支持JSONP的API,一般要注意两点:一是有的站点要求注册密钥(使用时必须放到参数中),二是要注意 参数的使用方法。例如,有的API要求使用预定义的回调函数,而有的API则允许使用者自己定义回调函数。

下面就是向Flickr Search请求JSONP响应的URL,其中使用了预定义的回调函数jsonFlickrApi(参数中不必给出):

http://api.flickr.com/services/rest/
?method=flickr.photos.search
&api_key=85618ad7d326d8ef93c6bee9ed32706f
&per_page=5&format=json&text=china

下面这个URL发送到Google Base,它允许开发人员使用自己定义的回调函数:

http://www.google.com/base/feeds/snippets
?q=jquery
&alt=json-in-script
&callback=customCallback

把前面的URL放到浏览器地址栏中,回车,即可看到结果。



posted @ 2009-03-24 21:34 二胡 阅读(527) | 评论 (0)编辑 收藏

转 http://www.cn-cuckoo.com/wordpress/wp-content/uploads/2007/08/JavaScriptClosures.html

Javascript 闭包

翻译:为之漫笔
链接:http://www.cn-cuckoo.com/2007/08/01/understand-javascript-closures-72.html

简介

Closure
所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。

闭包是 ECMAScript (JavaScript)最强大的特性之一,但用好闭包的前提是必须理解闭包。闭包的创建相对容易,人们甚至会在不经意间创建闭包,但这些无意创建的闭包 却存在潜在的危害,尤其是在比较常见的浏览器环境下。如果想要扬长避短地使用闭包这一特性,则必须了解它们的工作机制。而闭包工作机制的实现很大程度上有 赖于标识符(或者说对象属性)解析过程中作用域的角色。

关于闭包,最简单的描述就是 ECMAScript 允许使用内部函数--即函数定义和函数表达式位于另一个函数的函数体内。而且,这些内部函数可以访问它们所在的外部函数中声明的所有局部变量、参数和声明 的其他内部函数。当其中一个这样的内部函数在包含它们的外部函数之外被调用时,就会形成闭包。也就是说,内部函数会在外部函数返回后被执行。而当这个内部 函数执行时,它仍然必需访问其外部函数的局部变量、参数以及其他内部函数。这些局部变量、参数和函数声明(最初时)的值是外部函数返回时的值,但也会受到 内部函数的影响。

遗憾的是,要适当地理解闭包就必须理解闭包背后运行的机制,以及许多相关的技术细节。虽然本文的前半部分并没有涉及 ECMA 262 规范指定的某些算法,但仍然有许多无法回避或简化的内容。对于个别熟悉对象属性名解析的人来说,可以跳过相关的内容,但是除非你对闭包也非常熟悉,否则最 好是不要跳过下面几节。

对象属性名解析

ECMAScript 认可两类对象:原生(Native)对象和宿主(Host)对象,其中宿主对象包含一个被称为内置对象的原生对象的子类(ECMA 262 3rd Ed Section 4.3)。原生对象属于语言,而宿主对象由环境提供,比如说可能是文档对象、DOM 等类似的对象。

原生对象具有松散和动态的命名属性(对于某些实现的内置对象子类别而言,动态性是受限的--但这不是太大的问题)。对象的命名属性用于保存值,该值 可以是指向另一个对象(Objects)的引用(在这个意义上说,函数也是对象),也可以是一些基本的数据类型,比如:String、Number、 Boolean、Null 或 Undefined。其中比较特殊的是 Undefined 类型,因为可以给对象的属性指定一个 Undefined 类型的值,而不会删除对象的相应属性。而且,该属性只是保存着 undefined 值。

下面简要介绍一下如何设置和读取对象的属性值,并最大程度地体现相应的内部细节。

值的赋予

对象的命名属性可以通过为该命名属性赋值来创建,或重新赋值。即,对于:

var objectRef = new Object(); //创建一个普通的 javascript 对象。
可以通过下面语句来创建名为 “testNumber” 的属性:
objectRef.testNumber = 5;

/* - 或- */

objectRef["testNumber"] = 5;
在赋值之前,对象中没有“testNumber” 属性,但在赋值后,则创建一个属性。之后的任何赋值语句都不需要再创建这个属性,而只会重新设置它的值:
objectRef.testNumber = 8;
/* - 或- */
objectRef["testNumber"] = 8;
稍后我们会介绍,Javascript 对象都有原型(prototypes)属性,而这些原型本身也是对象,因而也可以带有命名的属性。但是,原型对象命名属性的作用并不体现在赋值阶段。同 样,在将值赋给其命名属性时,如果对象没有该属性则会创建该命名属性,否则会重设该属性的值。

值的读取

当读取对象的属性值时,原型对象的作用便体现出来。如果对象的原型中包含属性访问器(property accessor)所使用的属性名,那么该属性的值就会返回:

/* 为命名属性赋值。如果在赋值前对象没有相应的属性,那么赋值后就会得到一个:*/

objectRef.testNumber = 8;



/* 从属性中读取值 */



var val = objectRef.testNumber;

/* 现在, - val - 中保存着刚赋给对象命名属性的值 8*/

而且,由于所有对象都有原型,而原型本身也是对象,所以原型也可能有原型,这样就构成了所谓的原型链。原型链终止于链中原型为 null 的对象。Object 构造函数的默认原型就有一个 null 原型,因此:

var objectRef = new Object(); //创建一个普通的 JavaScript 对象。

创建了一个原型为 Object.prototype 的对象,而该原型自身则拥有一个值为 null 的原型。也就是说,objectRef 的原型链中只包含一个对象-- Object.prototype。但对于下面的代码而言:

/* 创建 - MyObject1 - 类型对象的函数*/

function MyObject1(formalParameter){

/* 给创建的对象添加一个名为 - testNumber -

的属性并将传递给构造函数的第一个参数指定为该属性的值:*/


this.testNumber = formalParameter;

}



/* 创建 - MyObject2 - 类型对象的函数*/

function MyObject2(formalParameter){

/* 给创建的对象添加一个名为 - testString -

的属性并将传递给构造函数的第一个参数指定为该属性的值:*/


this.testString = formalParameter;

}





/* 接下来的操作用 MyObject1 类的实例替换了所有与 MyObject2

类的实例相关联的原型。而且,为 MyObject1 构造函数传递了参数

- 8 - ,因而其 - testNumber - 属性被赋予该值:*/


MyObject2.prototype = new MyObject1( 8 );





/* 最后,将一个字符串作为构造函数的第一个参数,

创建一个 - MyObject2 - 的实例,并将指向该对象的

引用赋给变量 - objectRef - :*/




var objectRef = new MyObject2( "String_Value" );

被变量 objectRef 所引用的 MyObject2 的实例拥有一个原型链。该链中的第一个对象是在创建后被指定给 MyObject2 构造函数的 prototype 属性的 MyObject1 的一个实例。MyObject1 的实例也有一个原型,即与 Object.prototype 所引用的对象对应的默认的 Object 对象的原型。最后, Object.prototype 有一个值为 null 的原型,因此这条原型链到此结束。

当某个属性访问器尝试读取由 objectRef 所引用的对象的属性值时,整个原型链都会被搜索。在下面这种简单的情况下:

var val = objectRef.testString;

因为 objectRef 所引用的 MyObject2 的实例有一个名为“testString”的属性,因此被设置为“String_Value”的该属性的值被赋给了变量 val。但是:

var val = objectRef.testNumber;

则不能从 MyObject2 实例自身中读取到相应的命名属性值,因为该实例没有这个属性。然而,变量 val 的值仍然被设置为 8,而不是未定义--这是因为在该实例中查找相应的命名属性失败后,解释程序会继续检查其原型对象。而该实例的原型对象是 MyObject1 的实例,这个实例有一个名为“testNumber”的属性并且值为 8,所以这个属性访问器最后会取得值 8。而且,虽然 MyObject1MyObject2 都没有定义 toString 方法,但是当属性访问器通过 objectRef 读取 toString 属性的值时:

var val = objectRef.toString;

变量 val 也会被赋予一个函数的引用。这个函数就是在 Object.prototypetoString 属性中所保存的函数。之所以会返回这个函数,是因为发生了搜索objectRef 原型链的过程。当在作为对象的 objectRef 中发现没有“toString”属性存在时,会搜索其原型对象,而当原型对象中不存在该属性时,则会继续搜索原型的原型。而原型链中最终的原型是 Object.prototype,这个对象确实有一个 toString 方法,因此该方法的引用被返回。

最后:

var val = objectRef.madeUpProperty;

返回 undefined,因为在搜索原型链的过程中,直至 Object.prototype 的原型--null,都没有找到任何对象有名为“madeUpPeoperty”的属性,因此最终返回 undefined

不论是在对象或对象的原型中,读取命名属性值的时候只返回首先找到的属性值。而当为对象的命名属性赋值时,如果对象自身不存在该属性则创建相应的属性。

这意味着,如果执行像 objectRef.testNumber = 3 这样一条赋值语句,那么这个 MyObject2 的实例自身也会创建一个名为“testNumber”的属性,而之后任何读取该命名属性的尝试都将获得相同的新值。这时候,属性访问器不会再进一步搜索原型链,但 MyObject1 实例值为 8 的“testNumber”属性并没有被修改。给 objectRef 对象的赋值只是遮挡了其原型链中相应的属性。

注意:ECMAScript 为 Object 类型定义了一个内部 [[prototype]] 属性。这个属性不能通过脚本直接访问,但在属性访问器解析过程中,则需要用到这个内部 [[prototype]] 属性所引用的对象链--即原型链。可以通过一个公共的 prototype 属性,来对与内部的 [[prototype]] 属性对应的原型对象进行赋值或定义。这两者之间的关系在 ECMA 262(3rd edition)中有详细描述,但超出了本文要讨论的范畴。

标识符解析、执行环境和作用域链

执行环境

执行环境是 ECMAScript 规范(ECMA 262 第 3 版)用于定义 ECMAScript 实现必要行为的一个抽象的概念。对如何实现执行环境,规范没有作规定。但由于执行环境中包含引用规范所定义结构的相关属性,因此执行环境中应该保有(甚至 实现)带有属性的对象--即使属性不是公共属性。

所有 JavaScript 代码都是在一个执行环境中被执行的。全局代码(作为内置的 JS 文件执行的代码,或者 HTML 页面加载的代码) 是在我将称之为“全局执行环境”的执行环境中执行的,而对函数的每次调用(有可能是作为构造函数)同样有关联的执行环境。通过 eval 函数执行的代码也有截然不同的执行环境,但因为 JavaScript 程序员在正常情况下一般不会使用 eval,所以这里不作讨论。有关执行环境的详细说明请参阅 ECMA 262(第 3 版)第 10.2 节。

当调用一个 JavaScript 函数时,该函数就会进入相应的执行环境。如果又调用了另外一个函数(或者递归地调用同一个函数),则又会创建一个新的执行环境,并且在函数调用期间执行过 程都处于该环境中。当调用的函数返回后,执行过程会返回原始执行环境。因而,运行中的 JavaScript 代码就构成了一个执行环境栈。

在创建执行环境的过程中,会按照定义的先后顺序完成一系列操作。首先,在一个函数的执行环境中,会创建一个“活动”对象。活动对象是规范中规定的另 外一种机制。之所以称之为对象,是因为它拥有可访问的命名属性,但是它又不像正常对象那样具有原型(至少没有预定义的原型),而且不能通过 JavaScript 代码直接引用活动对象。

为函数调用创建执行环境的下一步是创建一个 arguments 对象,这是一个类似数组的对象,它以整数索引的数组成员一一对应地保存着调用函数时所传递的参数。这个对象也有 lengthcallee 属性(这两个属性与我们讨论的内容无关,详见规范)。然后,会为活动对象创建一个名为“arguments”的属性,该属性引用前面创建的 arguments 对象。

接着,为执行环境分配作用域。作用域由对象列表(链)组成。每个函数对象都有一个内部的 [[scope]] 属性(该属性我们稍后会详细介绍),这个属性也由对象列表(链)组成。指定给一个函数调用执行环境的作用域,由该函数对象的 [[scope]] 属性所引用的对象列表(链)组成,同时,活动对象被添加到该对象列表的顶部(链的前端)。

之后会发生由 ECMA 262 中所谓“可变”对象完成的“变量实例化”的过程。只不过此时使用活动对象作为可变对象(这里很重要,请注意:它们是同一个对象)。此时会将函数的形式参数 创建为可变对象命名属性,如果调用函数时传递的参数与形式参数一致,则将相应参数的值赋给这些命名属性(否则,会给命名属性赋 undefined 值)。对于定义的内部函数,会以其声明时所用名称为可变对象创建同名属性,而相应的内部函数则被创建为函数对象并指定给该属性。变量实例化的最后一步是将在函数内部声明的所有局部变量创建为可变对象的命名属性。

根据声明的局部变量创建的可变对象的属性在变量实例化过程会被赋予 undefined 值。在执行函数体内的代码、并计算相应的赋值表达式之前不会对局部变量执行真正的实例化。

事实上,拥有 arguments 属性的活动对象和拥有与函数局部变量对应的命名属性的可变对象是同一个对象。因此,可以将标识符 arguments 作为函数的局部变量来看待。

回到顶部

最后,在this可以被使用之前,还必须先对其赋值。如果赋的值是一个对象的引用,则 this.m 访问的便是该对象上的 m。如果(内部)赋的值是 null,则this就指向全局对象。 (此段由 pangba 刘未鹏 翻译)

(原文备考:Finally a value is assigned for use with the this keyword. If the value assigned refers to an object then property accessors prefixed with the this keyword reference properties of that object. If the value assigned (internally) is null then the this keyword will refer to the global object. )

创建全局执行环境的过程会稍有不同,因为它没有参数,所以不需要通过定义的活动对象来引用这些参数。但全局执行环境也需要一个作用域,而它的作用域 链实际上只由一个对象--全局对象--组成。全局执行环境也会有变量实例化的过程,它的内部函数就是涉及大部分 JavaScript 代码的、常规的顶级函数声明。而且,在变量实例化过程中全局对象就是可变对象,这就是为什么全局性声明 的函数是全局对象属性的原因。全局性声明的变量同样如此。

全局执行环境也会使用 this 对象来引用全局对象。

作用域链与 [[scope]]

调用函数时创建的执行环境会包含一个作用域链,这个作用域链是通过将该执行环境的活动(可变)对象添加到保存于所调用函数对象的 [[scope]] 属性中的作用域链前端而构成的。所以,理解函数对象内部的 [[scope]] 属性的定义过程至关重要。

在 ECMAScript 中,函数也是对象。函数对象在变量实例化过程中会根据函数声明来创建,或者是在计算函数表达式或调用 Function 构造函数时创建。

通过调用 Function 构造函数创建的函数对象,其内部的 [[scope]] 属性引用的作用域链中始终只包含全局对象。

通过函数声明或函数表达式创建的函数对象,其内部的 [[scope]] 属性引用的则是创建它们的执行环境的作用域链。

在最简单的情况下,比如声明如下全局函数:-

function exampleFunction(formalParameter){

... // 函数体内的代码

}

当为创建全局执行环境而进行变量实例化时,会根据上面的函数声明创建相应的函数对象。因为全局执行环境的作用域链中只包含全局对象,所以它就给自己创建的、并以名为“exampleFunction”的属性引用的这个函数对象的内部 [[scope]] 属性,赋予了只包含全局对象的作用域链。

当在全局环境中计算函数表达式时,也会发生类似的指定作用域链的过程:-

var exampleFuncRef = function(){

... // 函数体代码

}

在这种情况下,不同的是在全局执行环境的变量实例化过程中,会先为全局对象创建一个命名属性。而在计算赋值语句之前,暂时不会创建函数对象,也不会将该函 数对象的引用指定给全局对象的命名属性。但是,最终还是会在全局执行环境中创建这个函数对象(当计算函数表达式时。译者注),而为这个创建的函数对象的 [[scope]] 属性指定的作用域链中仍然只包含全局对象。

内部的函数声明或表达式会导致在包含它们的外部函数的执行环境中创建相应的函数对象,因此这些函数对象的作用域链会稍微复杂一些。在下面的代码中,先定义了一个带有内部函数声明的外部函数,然后调用外部函数:

function exampleOuterFunction(formalParameter){

function exampleInnerFuncitonDec(){

... // 内部函数体代码

}

... // 其余的外部函数体代码

}



exampleOuterFunction( 5 );

与外部函数声明对应的函数对象会在全局执行环境的变量实例化过程中被创建。因此,外部函数对象的 [[scope]] 属性中会包含一个只有全局对象的“单项目”作用域链。

当在全局执行环境中调用 exampleOuterFunction 函数时,会为该函数调用创建一个新的执行环境和一个活动(可变)对象。这个新执行环境的作用域就由新的活动对象后跟外部函数对象的 [[scope]] 属性所引用的作用域链(只有全局对象)构成。在新执行环境的变量实例化过程中,会创建一个与内部函数声明对应的函数对象,而同时会给这个函数对象的 [[scope]] 属性指定创建该函数对象的执行环境(即新执行环境。译者注)的作用域值--即一个包含活动对象后跟全局对象的作用域链。

到目前为止,所有过程都是自动、或者由源代码的结构所控制的。但我们发现,执行环境的作用域链定义了执行环境所创建的函数对象的 [[scope]] 属性,而函数对象的 [[scope]] 属性则定义了它的执行环境的作用域(包括相应的活动对象)。不过,ECMAScript 也提供了用于修改作用域链 with 语句。

with 语句会计算一个表达式,如果该表达式是一个对象,那么就将这个对象添加到当前执行环境的作用域链中(在活动<可变>对象之前)。然后,执行 with 语句(它自身也可能是一个语句块)中的其他语句。之后,又恢复到调用它之前的执行环境的作用域链中。

with 语句不会影响在变量实例化过程中根据函数声明创建函数对象。但是,可以在一个 with 语句内部对函数表达式求值:-

/* 创建全局变量 - y - 它引用一个对象:- */

var y = {x:5}; // 带有一个属性 - x - 的对象直接量

function exampleFuncWith(){

var z;

/* 将全局对象 - y - 引用的对象添加到作用域链的前端:- */

with(y){

/* 对函数表达式求值,以创建函数对象并将该函数对象的引用指定给局部变量 - z - :- */

z = function(){

... // 内部函数表达式中的代码;

}

}

...

}



/* 执行 - exampleFuncWith - 函数:- */

exampleFuncWith();

在调用 exampleFuncWith 函数所创建的执行环境中包含一个由其活动对象后跟全局对象构成的作用域链。而在执行 with 语句时,又会把全局变量 y 引用的对象添加到这个作用域链的前端。在对其中的函数表达式求值的过程中,所创建函数对象的 [[scope]] 属性与创建它的执行环境的作用域保持一致--即,该属性会引用一个由对象 y 后跟调用外部函数时所创建执行环境的活动对象,后跟全局对象的作用域链。

当与 with 语句相关的语句块执行结束时,执行环境的作用域得以恢复(y 会被移除),但是已经创建的函数对象(z。译者注)的 [[scope]] 属性所引用的作用域链中位于最前面的仍然是对象 y

标识符解析

标识符是沿作用域链逆向解析的。ECMA 262 将 this 归类为关键字而不是标识符,并非不合理。因为解析 this 值时始终要根据使用它的执行环境来判断,而与作用域链无关。

标识符解析从作用域链中的第一个对象开始。检查该对象中是否包含与标识符对应的属性名。因为作用域链是一条对象链,所以这个检查过程也会包含相应对象的原 型链(如果有)。如果没有在作用域链的第一个对象中发现相应的值,解析过程会继续搜索下一个对象。这样依次类推直至找到作用域链中包含以标识符为属性名的 对象为止,也有可能在作用域链的所有对象中都没有发现该标识符。

当基于对象使用属性访问器时,也会发生与上面相同的标识符解析过程。当属性访问器中有相应的属性可以替换某个对象时,这个属性就成为表示该对象的标识符,该对象在作用域链中的位置进而被确定。全局对象始终都位于作用域链的尾端。

因为与函数调用相关的执行环境将会把活动(可变)对象添加到作用域链的前端,所以在函数体内使用的标识符会首先检查自己是否与形式参数、内部函数声明的名称或局部变量一致。这些都可以由活动(可变)对象的命名属性来确定。

闭包

自动垃圾收集

ECMAScript 要求使用自动垃圾收集机制。但规范中并没有详细说明相关的细节,而是留给了实现来决定。但据了解,相当一部分实现对它们的垃圾收集操作只赋予了很低的优先 级。但是,大致的思想都是相同的,即如果对象不再“可引用(由于不存在对它的引用,使执行代码无法再访问到它)”时,该对象就成为垃圾收集的目标。因而, 在将来的某个时刻会将这个对象销毁并将它所占用的一切资源释放,以便操作系统重新利用。

正常情况下,当退出一个执行环境时就会满足类似的条件。此时,作用域链结构中的活动(可变)对象以及在该执行环境中创建的任何对象--包括函数对象,都不再“可引用”,因此将成为垃圾收集的目标。

构成闭包

闭包是通过在对一个函数调用的执行环境中返回一个函数对象构成的。比如,在对函数调用的过程中,将一个对内部函数对象的引用指定给另一个对象的属 性。或者,直接将这样一个(内部)函数对象的引用指定给一个全局变量、或者一个全局性对象的属性,或者一个作为参数以引用方式传递给外部函数的对象。例 如:-

function exampleClosureForm(arg1, arg2){

var localVar = 8;

function exampleReturned(innerArg){

return ((arg1 + arg2)/(innerArg + localVar));

}

/* 返回一个定义为 exampleReturned 的内部函数的引用 -:- */

return exampleReturned;

}



var globalVar = exampleClosureForm(2, 4);

这种情况下,在调用外部函数 exampleClosureForm 的执行环境中所创建的函数对象就不会被当作垃圾收集,因为该函数对象被一个全局变量所引用,而且仍然是可以访问的,甚至可以通过 globalVar(n) 来执行。

的确,情况比正常的时候要复杂一些。因为现在这个被变量 globalVar 引用的内部函数对象的 [[scope]] 属性所引用的作用域链中,包含着属于创建该内部函数对象的执行环境的活动对象(和全局对象)。由于在执行被 globalVar 引用的函数对象时,每次都要把该函数对象的 [[scope]] 属性所引用的整个作用域链添加到创建的(内部函数的)执行环境的作用域中(即此时的作用域中包括:内部执行环境的活动对象、外部执行环境的活动对象、全局对象。译者注), 所以这个(外部执行环境的)活动对象不会被当作垃圾收集。

闭包因此而构成。此时,内部函数对象拥有自由的变量,而位于该函数作用域链中的活动(可变)对象则成为与变量绑定的环境。

由于活动(可变)对象受限于内部函数对象(现在被 globalVar 变量引用)的 [[scope]] 属性中作用域链的引用,所以活动对象连同它的变量声明--即属性的值,都会被保留。而在对内部函数调用的执行环境中进行作用域解析时,将会把与活动(可 变)对象的命名属性一致的标识符作为该对象的属性来解析。活动对象的这些属性值即使是在创建它的执行环境退出后,仍然可以被读取和设置。

在上面的例子中,当外部函数返回(退出它的执行环境)时,其活动(可变)对象的变量声明中记录了形式参数、内部函数定义以及局部变量的值。arg1 属性的值为 2,而 arg2 属性的值为 4localVar 的值是 8,还有一个 exampleReturned 属性,它引用由外部函数返回的内部函数对象。(为方便起见,我们将在后面的讨论中,称这个活动<可变>对象为 "ActOuter1")。

如果再次调用 exampleClosureForm 函数,如:-

var secondGlobalVar = exampleClosureForm(12, 3);

- 则会创建一个新的执行环境和一个新的活动对象。而且,会返回一个新的函数对象,该函数对象的 [[scope]] 属性引用的作用域链与前一次不同,因为这一次的作用域链中包含着第二个执行环境的活动对象,而这个活动对象的属性 arg1 值为 12 而属性 arg2 值为 3。(为方便起见,我们将在后面的讨论中,称这个活动<可变>对象为 "ActOuter2")。

通过第二次执行 exampleClosureForm 函数,第二个、也是截然不同的闭包诞生了。

通过执行 exampleClosureForm 创建的两个函数对象分别被指定给了全局变量 globalVarsecondGlobalVar,并返回了表达式 ((arg1 + arg2)/(innerArg + localVar))。该表达式对其中的四个标识符应用了不同的操作符。如何确定这些标识符的值是体现闭包价值的关键所在。

我们来看一看,在执行由 globalVar 引用的函数对象--如 globalVar(2)--时的情形。此时,会创建一个新的执行环境和相应的活动对象(我们将称之为“ActInner1”),并把该活动对象添加到执行的函数对象的 [[scope]] 属性所引用的作用域链的前端。ActInner1 会带有一个属性 innerArg,根据传递的形式参数,其值被指定为 2。这个新执行环境的作用域链变成: ActInner1->ActOuter1->全局对象.

为了返回表达式 ((arg1 + arg2)/(innerArg + localVar)) 的值,要沿着作用域链进行标识符解析。表达式中标识符的值将通过依次查找作用域链中每个对象(与标识符名称一致)的属性来确定。

作用域链中的第一个对象是 ActInner1,它有一个名为 innerArg 的属性,值是 2。所有其他三个标识符在 ActOuter1 中都有对应的属性:arg12arg24localVar8。最后,函数调用返回 ((2 + 2)/(2 + 8))

现在再来看一看由 secondGlobalVar 引用的同一个函数对象的执行情况,比如 secondGlobalVar(5)。我们把这次创建的新执行环境的活动对象称为 “ActInner2”,相应的作用域链就变成了:ActInner2->ActOuter2->全局对象。ActInner2 返回 innerArg 的值 5,而 ActOuter2 分别返回 arg1arg2localVar 的值 1238。函数调用返回的值就是 ((12 + 3)/(5 + 8))

如果再执行一次 secondGlobalVar,则又会有一个新活动对象被添加到作用域链的前端,但 ActOuter2 仍然是链中的第二个对象,而他的命名属性会再次用于完成标识符 arg1arg2localVar 的解析。

这就是 ECMAScript 的内部函数获取、维持和访问创建他们的执行环境的形式参数、声明的内部函数以及局部变量的过程。这个过程说明了构成闭包以后,内部的函数对象在其存续过程 中,如何维持对这些值的引用、如何对这些值进行读取的机制。即,创建内部函数对象的执行环境的活动(可变)对象,会保留在该函数对象的 [[scope]] 属性所引用的作用域链中。直到所有对这个内部函数的引用被释放,这个函数对象才会成为垃圾收集的目标(连同它的作用域链中任何不再需要的对象)。

内部函数自身也可能有内部函数。在通过函数执行返回内部函数构成闭包以后,相应的闭包自身也可能会返回内部函数从而构成它们自己的闭包。每次作用域链嵌 套,都会增加由创建内部函数对象的执行环境引发的新活动对象。ECMAScript 规范要求作用域链是临时性的,但对作用域链的长度却没有加以限制。在具体实现中,可能会存在实际的限制,但还没有发现有具体限制数量的报告。目前来看,嵌 套的内部函数所拥有的潜能,仍然超出了使用它们的人的想像能力。

通过闭包可以做什么?

对这个问题的回答可能会令你惊讶--闭包什么都可以做。据我所知,闭包使得 ECMAScript 能够模仿任何事物,因此局限性在于设计和实现要模仿事物的能力。只是从字面上看可能会觉得这么说很深奥,下面我们就来看一些更有实际意义的例子。

例 1:为函数引用设置延时

闭包的一个常见用法是在执行函数之前为要执行的函数提供参数。例如:将函数作为 setTimout 函数的第一个参数,这在 Web 浏览器的环境下是非常常见的一种应用。

setTimeout 用于有计划地执行一个函数(或者一串 JavaScript 代码,不是在本例中),要执行的函数是其第一个参数,其第二个参数是以毫秒表示的执行间隔。也就是说,当在一段代码中使用 setTimeout 时,要将一个函数的引用作为它的第一个参数,而将以毫秒表示的时间值作为第二个参数。但是,传递函数引用的同时无法为计划执行的函数提供参数。

然而,可以在代码中调用另外一个函数,由它返回一个对内部函数的引用,再把这个对内部函数对象的引用传递给 setTimeout 函数。执行这个内部函数时要使用的参数在调用返回它的外部函数时传递。这样,setTimeout 在执行这个内部函数时,不用传递参数,但该内部函数仍然能够访问在调用返回它的外部函数时传递的参数:

function callLater(paramA, paramB, paramC){

/* 返回一个由函数表达式创建的匿名内部函数的引用:- */



return (function(){

/* 这个内部函数将通过 - setTimeout - 执行,

而且当它执行时它会读取并按照传递给

外部函数的参数行事:

*/


paramA[paramB] = paramC;

});

}



...



/* 调用这个函数将返回一个在其执行环境中创建的内部函数对象的引用。

传递的参数最终将作为外部函数的参数被内部函数使用。

返回的对内部函数的引用被赋给一个全局变量:-

*/



var functRef = callLater(elStyle, "display", "none");

/* 调用 setTimeout 函数,将赋给变量 - functRef -

的内部函数的引用作为传递的第一个参数:- */



hideMenu=setTimeout(functRef, 500);

例 2: 通过对象实例方法关联函数

回到顶部

许多时候我们需要将一个函数对象暂时挂到一个引用上留待后面执行,因为不等到执行的时候是很难知道其具体参数的,而先前将它赋给那个引用的时候更是压根不知道的。 (此段由 pangba 刘未鹏 翻译)

(luyy朋友的翻译_2008-7-7更新)很多时候需要将一个函数引用进行赋值,以便在将来某个时候执行该函数,在执行这些函数时给函数提供参数将会是有用处的,但这些参数在执行时不容易获得,他们只有在上面赋值给时才能确定。

(原文备考:There are many other circumstances when a reference to a function object is assigned so that it would be executed at some future time where it is useful to provide parameters for the execution of that function that would not be easily available at the time of execution but cannot be known until the moment of assignment.)

一个相关的例子是,用 JavaScript 对象来封装与特定 DOM 元素的交互。这个 JavaScript 对象具有 doOnClickdoMouseOverdoMouseOut 方法,并且当用户在该特定的 DOM 元素中触发了相应的事件时要执行这些方法。不过,可能会创建与不同的 DOM 元素关联的任意数量的 JavaScript 对象,而且每个对象实例并不知道实例化它们的代码将会如何操纵它们(即注册事件处理函数与定义相应的事件处理函数分离。译者注)。这些对象实例并不知道如 何在全局环境中引用它们自身,因为它们不知道将会指定哪个全局变量(如果有)引用它们的实例。

因而问题可以归结为执行一个与特定的 JavaScript 对象关联的事件处理函数,并且要知道调用该对象的哪个方法。

下面这个例子使用了一个基于闭包构建的一般化的函数(此句多谢未鹏指点),该函数会将对象实例与 DOM 元素事件关联起来,安排执行事件处理程序时调用对象实例的指定方法,给象的指定方法传递的参数是事件对象和与元素关联的引用,该函数返回执行相应方法后的返回值。

/* 一个关联对象实例和事件处理器的函数。 

它返回的内部函数被用作事件处理器。对象实例以 - obj - 参数表示,

而在该对象实例中调用的方法名则以 - methodName - (字符串)参数表示。

*/



function associateObjWithEvent(obj, methodName){

/* 下面这个返回的内部函数将作为一个 DOM 元素的事件处理器*/



return (function(e){

/* 在支持标准 DOM 规范的浏览器中,事件对象会被解析为参数 - e - ,

若没有正常解析,则使用 IE 的事件对象来规范化事件对象。

*/



e = e||window.event;

/* 事件处理器通过保存在字符串 - methodName - 中的方法名调用了对象

- obj - 的一个方法。并传递已经规范化的事件对象和触发事件处理器的元素

的引用 - this - (之所以 this 有效是因为这个内部函数是作为该元素的方法执行的)

*/



return obj[methodName](e, this);

});

}



/* 这个构造函数用于创建将自身与 DOM 元素关联的对象,

DOM 元素的 ID 作为构造函数的字符串参数。

所创建的对象会在相应的元素触发 onclick、

onmouseover 或 onmouseout 事件时,

调用相应的方法。

*/



function DhtmlObject(elementId){

/* 调用一个返回 DOM 元素(如果没找到返回 null)引用的函数,

必需的参数是 ID。 将返回的值赋给局部变量 - el -。

*/


var el = getElementWithId(elementId);

/* - el - 值会在内部通过类型转换变为布尔值,以便 - if - 语句加以判断。

因此,如果它引用一个对象结果将返回 true,如果是 null 则返回 false。

下面的代码块只有当 - el - 变量返回一个 DOM 元素时才会被执行。

*/


if(el){

/* 为给元素的事件处理器指定一个函数,该对象调用了

- associateObjWithEvent - 函数。

同时对象将自身(通过 - this - 关键字)作为调用方法的对象,

并提供了调用的方法名称。 - associateObjWithEvent - 函数会返回

一个内部函数,该内部函数被指定为 DOM 元素的事件处理器。

在响应事件时,执行这个内部函数就会调用必要的方法。

*/


el.onclick = associateObjWithEvent(this, "doOnClick");

el.onmouseover = associateObjWithEvent(this, "doMouseOver");

el.onmouseout = associateObjWithEvent(this, "doMouseOut");

...

}

}

DhtmlObject.prototype.doOnClick = function(event, element){

... // doOnClick 方法体。.

}

DhtmlObject.prototype.doMouseOver = function(event, element){

... // doMouseOver 方法体。

}

DhtmlObject.prototype.doMouseOut = function(event, element){

... // doMouseOut 方法体。

}

这样,DhtmlObject 的任何实例都会将自身与相应的 DOM 元素关联起来,而这些 DOM 元素不必知道其他代码如何操纵它们(即当触发相应事件时,会执行什么代码。译者注),也不必理会全局命名空间的影响以及与 DhtmlObject 的其他实例间存在冲突的危险。

例 3:包装相关的功能

闭包可以用于创建额外的作用域,通过该作用域可以将相关的和具有依赖性的代码组织起来,以便将意外交互的风险降到最低。假设有一个用于构建字符串的 函数,为了避免重复性的连接操作(和创建众多的中间字符串),我们的愿望是使用一个数组按顺序来存储字符串的各个部分,然后再使用 Array.prototype.join 方法(以空字符串作为其参数)输出结果。这个数组将作为输出的缓冲器,但是将数组作为函数的局部变量又会导致在每次调用函数时都重新创建一个新数组,这在每次调用函数时只重新指定数组中的可变内容的情况下并不是必要的。

一种解决方案是将这个数组声明为全局变量,这样就可以重用这个数组,而不必每次都建立新数组。但这个方案的结果是,除了引用函数的全局变量会使用这个缓冲 数组外,还会多出一个全局属性引用数组自身。如此不仅使代码变得不容易管理,而且,如果要在其他地方使用这个数组时,开发者必须要再次定义函数和数组。这 样一来,也使得代码不容易与其他代码整合,因为此时不仅要保证所使用的函数名在全局命名空间中是唯一的,而且还要保证函数所依赖的数组在全局命名空间中也 必须是唯一的。

而通过闭包可以使作为缓冲器的数组与依赖它的函数关联起来(优雅地打包),同时也能够维持在全局命名空间外指定的缓冲数组的属性名,免除了名称冲突和意外交互的危险。

其中的关键技巧在于通过执行一个单行(in-line)函数表达式创建一个额外的执行环境,而将该函数表达式返回的内部函数作为在外部代码中使用的 函数。此时,缓冲数组被定义为函数表达式的一个局部变量。这个函数表达式只需执行一次,而数组也只需创建一次,就可以供依赖它的函数重复使用。

下面的代码定义了一个函数,这个函数用于返回一个 HTML 字符串,其中大部分内容都是常量,但这些常量字符序列中需要穿插一些可变的信息,而可变的信息由调用函数时传递的参数提供。

通过执行单行函数表达式返回一个内部函数,并将返回的函数赋给一个全局变量,因此这个函数也可以称为全局函数。而缓冲数组被定义为外部函数表达式的一个局部变量。它不会暴露在全局命名空间中,而且无论什么时候调用依赖它的函数都不需要重新创建这个数组。

/* 声明一个全局变量 - getImgInPositionedDivHtml - 

并将一次调用一个外部函数表达式返回的内部函数赋给它。



这个内部函数会返回一个用于表示绝对定位的 DIV 元素

包围着一个 IMG 元素 的 HTML 字符串,这样一来,

所有可变的属性值都由调用该函数时的参数提供:

*/


var getImgInPositionedDivHtml = (function(){

/* 外部函数表达式的局部变量 - buffAr - 保存着缓冲数组。

这个数组只会被创建一次,生成的数组实例对内部函数而言永远是可用的

因此,可供每次调用这个内部函数时使用。



其中的空字符串用作数据占位符,相应的数据

将由内部函数插入到这个数组中:

*/


var buffAr = [

'<div id="',

'', //index 1, DIV ID 属性

'" style="position:absolute;top:',

'', //index 3, DIV 顶部位置

'px;left:',

'', //index 5, DIV 左端位置

'px;width:',

'', //index 7, DIV 宽度

'px;height:',

'', //index 9, DIV 高度

'px;overflow:hidden;\"><img src=\"',

'', //index 11, IMG URL

'\" width=\"',

'', //index 13, IMG 宽度

'\" height=\"',

'', //index 15, IMG 调蓄

'\" alt=\"',

'', //index 17, IMG alt 文本内容

'\"><\/div>'

];

/* 返回作为对函数表达式求值后结果的内部函数对象。

这个内部函数就是每次调用执行的函数

- getImgInPositionedDivHtml( ... ) -

*/


return (function(url, id, width, height, top, left, altText){

/* 将不同的参数插入到缓冲数组相应的位置:

*/


buffAr[1] = id;

buffAr[3] = top;

buffAr[5] = left;

buffAr[13] = (buffAr[7] = width);

buffAr[15] = (buffAr[9] = height);

buffAr[11] = url;

buffAr[17] = altText;

/* 返回通过使用空字符串(相当于将数组元素连接起来)

连接数组每个元素后形成的字符串:

*/


return buffAr.join('');

}); //:内部函数表达式结束。

})();

/*^^- :单行外部函数表达式。*/

如果一个函数依赖于另一(或多)个其他函数,而其他函数又没有必要被其他代码直接调用,那么可以运用相同的技术来包装这些函数,而通过一个公开暴露的函数来调用它们。这样,就将一个复杂的多函数处理过程封装成了一个具有移植性的代码单元。

其他例子

有关闭包的一个可能是最广为人知的应用是 Douglas Crockford's technique for the emulation of private instance variables in ECMAScript objects。这种应用方式可以扩展到各种嵌套包含的可访问性(或可见性)的作用域结构,包括 the emulation of private static members for ECMAScript objects

闭包可能的用途是无限的,可能理解其工作原理才是把握如何使用它的最好指南。

意外的闭包

在创建可访问的内部函数的函数体之外解析该内部函数就会构成闭包。这表明闭包很容易创建,但这样一来可能会导致一种结果,即没有认识到闭包是一种语 言特性的 JavaScript 作者,会按照内部函数能完成多种任务的想法来使用内部函数。但他们对使用内部函数的结果并不明了,而且根本意识不到创建了闭包,或者那样做意味着什么。

正如下一节谈到 IE 中内存泄漏问题时所提及的,意外创建的闭包可能导致严重的负面效应,而且也会影响到代码的性能。问题不在于闭包本身,如果能够真正做到谨慎地使用它们,反而会有助于创建高效的代码。换句话说,使用内部函数会影响到效率。

使用内部函数最常见的一种情况就是将其作为 DOM 元素的事件处理器。例如,下面的代码用于向一个链接元素添加 onclick 事件处理器:

/* 定义一个全局变量,通过下面的函数将它的值

作为查询字符串的一部分添加到链接的 - href - 中:

*/


var quantaty = 5;

/* 当给这个函数传递一个链接(作为函数中的参数 - linkRef -)时,

会将一个 onclick 事件处理器指定给该链接,该事件处理器

将全局变量 - quantaty - 的值作为字符串添加到链接的 - href -

属性中,然后返回 true 使该链接在单击后定位到由 - href -

属性包含的查询字符串指定的资源:

*/


function addGlobalQueryOnClick(linkRef){

/* 如果可以将参数 - linkRef - 通过类型转换为 ture

(说明它引用了一个对象):

*/


if(linkRef){

/* 对一个函数表达式求值,并将对该函数对象的引用

指定给这个链接元素的 onclick 事件处理器:

*/


linkRef.onclick = function(){

/* 这个内部函数表达式将查询字符串

添加到附加事件处理器的元素的 - href - 属性中:

*/


this.href += ('?quantaty='+escape(quantaty));

return true;

};

}

}

无论什么时候调用 addGlobalQueryOnClick 函数,都会创建一个新的内部函数(通过赋值构成了闭包)。从效率的角度上看,如果只是调用一两次 addGlobalQueryOnClick 函数并没有什么大的妨碍,但如果频繁使用该函数,就会导致创建许多截然不同的函数对象(每对内部函数表达式求一次值,就会产生一个新的函数对象)。

上面例子中的代码没有关注内部函数在创建它的函数外部可以访问(或者说构成了闭包)这一事实。实际上,同样的效果可以通过另一种方式来完成。即单独 地定义一个用于事件处理器的函数,然后将该函数的引用指定给元素的事件处理属性。这样,只需创建一个函数对象,而所有使用相同事件处理器的元素都可以共享 对这个函数的引用:

/* 定义一个全局变量,通过下面的函数将它的值

作为查询字符串的一部分添加到链接的 - href - 中:

*/


var quantaty = 5;



/* 当把一个链接(作为函数中的参数 - linkRef -)传递给这个函数时,

会给这个链接添加一个 onclick 事件处理器,该事件处理器会

将全局变量 - quantaty - 的值作为查询字符串的一部分添加到

链接的 - href - 中,然后返回 true,以便单击链接时定位到由

作为 - href - 属性值的查询字符串所指定的资源:

*/


function addGlobalQueryOnClick(linkRef){

/* 如果 - linkRef - 参数能够通过类型转换为 true

(说明它引用了一个对象):

*/


if(linkRef){

/* 将一个对全局函数的引用指定给这个链接

的事件处理属性,使函数成为链接元素的事件处理器:

*/


linkRef.onclick = forAddQueryOnClick;

}

}

/* 声明一个全局函数,作为链接元素的事件处理器,

这个函数将一个全局变量的值作为要添加事件处理器的

链接元素的 - href - 值的一部分:

*/


function forAddQueryOnClick(){

this.href += ('?quantaty='+escape(quantaty));

return true;

}

在上面例子的第一个版本中,内部函数并没有作为闭包发挥应有的作用。在那种情况下,反而是不使用闭包更有效率,因为不用重复创建许多本质上相同的函数对象。

类似地考量同样适用于对象的构造函数。与下面代码中的构造函数框架类似的代码并不罕见:

function ExampleConst(param){

/* 通过对函数表达式求值创建对象的方法,

并将求值所得的函数对象的引用赋给要创建对象的属性:

*/


this.method1 = function(){

... // 方法体。

};

this.method2 = function(){

... // 方法体。

};

this.method3 = function(){

... // 方法体。

};

/* 把构造函数的参数赋给对象的一个属性:

*/


this.publicProp = param;

}

每当通过 new ExampleConst(n) 使用这个构造函数创建一个对象时,都会创建一组新的、作为对象方法的函数对象。因此,创建的对象实例越多,相应的函数对象也就越多。

Douglas Crockford 提出的模仿 JavaScript 对象私有成员的技术,就利用了将对内部函数的引用指定给在构造函数中构造对象的公共属性而形成的闭包。如果对象的方法没有利用在构造函数中形成的闭包,那 么在实例化每个对象时创建的多个函数对象,会使实例化过程变慢,而且将有更多的资源被占用,以满足创建更多函数对象的需要。

这那种情况下,只创建一次函数对象,并把它们指定给构造函数 prototype 的相应属性显然更有效率。这样一来,它们就能被构造函数创建的所有对象共享了:

function ExampleConst(param){

/* 将构造函数的参数赋给对象的一个属性:

*/


this.publicProp = param;

}

/* 通过对函数表达式求值,并将结果函数对象的引用

指定给构造函数原型的相应属性来创建对象的方法:

*/


ExampleConst.prototype.method1 = function(){

... // 方法体。

};

ExampleConst.prototype.method2 = function(){

... // 方法体。

};

ExampleConst.prototype.method3 = function(){

... // 方法体。

};



Internet Explorer 的内存泄漏问题

Internet Explorer Web 浏览器(在 IE 4 到 IE 6 中核实)的垃圾收集系统中存在一个问题,即如果 ECMAScript 和某些宿主对象构成了 "循环引用",那么这些对象将不会被当作垃圾收集。此时所谓的宿主对象指的是任何 DOM 节点(包括 document 对象及其后代元素)和 ActiveX 对象。如果在一个循环引用中包含了一或多个这样的对象,那么这些对象直到浏览器关闭都不会被释放,而它们所占用的内存同样在浏览器关闭之前都不会交回系统 重用。

当两个或多个对象以首尾相连的方式相互引用时,就构成了循环引用。比如对象 1 的一个属性引用了对象 2 ,对象 2 的一个属性引用了对象 3,而对象 3 的一个属性又引用了对象 1。对于纯粹的 ECMAScript 对象而言,只要没有其他对象引用对象 1、2、3,也就是说它们只是相互之间的引用,那么仍然会被垃圾收集系统识别并处理。但是,在 Internet Explorer 中,如果循环引用中的任何对象是 DOM 节点或者 ActiveX 对象,垃圾收集系统则不会发现它们之间的循环关系与系统中的其他对象是隔离的并释放它们。最终它们将被保留在内存中,直到浏览器关闭。

闭包非常容易构成循环引用。如果一个构成闭包的函数对象被指定给,比如一个 DOM 节点的事件处理器,而对该节点的引用又被指定给函数对象作用域中的一个活动(或可变)对象,那么就存在一个循环引用。DOM_Node.onevent ->function_object.[[scope]] ->scope_chain ->Activation_object.nodeRef ->DOM_Node。形成这样一个循环引用是轻而易举的,而且稍微浏览一下包含类似循环引用代码的网站(通常会出现在网站的每个页面中),就会消耗大量(甚至全部)系统内存。

多加注意可以避免形成循环引用,而在无法避免时,也可以使用补偿的方法,比如使用 IE 的 onunload 事件来来清空(null)事件处理函数的引用。时刻意识到这个问题并理解闭包的工作机制是在 IE 中避免此类问题的关键。

comp.lang.javascript FAQ notes T.O.C.

  • 撰稿 Richard Cornford,2004 年 3 月
  • 修改建议来自:
    • Martin Honnen.
    • Yann-Erwan Perio (Yep).
    • Lasse Reichstein Nielsen. (definition of closure)
    • Mike Scirocco.
    • Dr John Stockton.


posted @ 2009-03-24 18:25 二胡 阅读(108) | 评论 (0)编辑 收藏

转 http://www.ibm.com/developerworks/cn/java/l-security/index.html

第1章基础知识

1.1. 单钥密码体制

单钥密码体制是一种传统的加密算法,是指信息的发送方和接收方共同使用同一把密钥进行加解密。

通常,使用的加密算法 比较简便高效,密钥简短,加解密速度快,破译极其困难。但是加密的安全性依靠密钥保管的安全性,在公开的计算机网络上安全地传送和保管密钥是一个严峻的问题,并且如果在多用户的情况下密钥的保管安全性也是一个问题。

单钥密码体制的代表是美国的DES

1.2. 消息摘要

一个消息摘要就是一个数据块的数字指纹。即对一个任意长度的一个数据块进行计算,产生一个唯一指印(对于SHA1是产生一个20字节的二进制数组)。

消息摘要有两个基本属性:

  • 两个不同的报文难以生成相同的摘要
  • 难以对指定的摘要生成一个报文,而由该报文反推算出该指定的摘要

代表:美国国家标准技术研究所的SHA1和麻省理工学院Ronald Rivest提出的MD5

1.3. Diffie-Hellman密钥一致协议

密钥一致协议是由公开密钥密码体制的奠基人Diffie和Hellman所提出的一种思想。

先决条件,允许两名用户在公开媒体上交换信息以生成"一致"的,可以共享的密钥

代表:指数密钥一致协议(Exponential Key Agreement Protocol)

1.4. 非对称算法与公钥体系

1976 年,Dittie和Hellman为解决密钥管理问题,在他们的奠基性的工作"密码学的新方向"一文中,提出一种密钥交换协议,允许在不安全的媒体上通过 通讯双方交换信息,安全地传送秘密密钥。在此新思想的基础上,很快出现了非对称密钥密码体制,即公钥密码体制。在公钥体制中,加密密钥不同于解密密钥,加 密密钥公之于众,谁都可以使用;解密密钥只有解密人自己知道。它们分别称为公开密钥(Public key)和秘密密钥(Private key)。

迄今为止的所有公钥密码体系中,RSA系统是最著名、最多使用的一种。RSA公开密钥密码系统是由R.Rivest、A.Shamir和L.Adleman俊教授于1977年提出的。RSA的取名就是来自于这三位发明者的姓的第一个字母

1.5. 数字签名

所 谓数字签名就是信息发送者用其私钥对从所传报文中提取出的特征数据(或称数字指纹)进行RSA算法操作,以保证发信人无法抵赖曾发过该信息(即不可抵赖 性),同时也确保信息报文在经签名后末被篡改(即完整性)。当信息接收者收到报文后,就可以用发送者的公钥对数字签名进行验证。 

在数字签名中有重要作用的数字指纹是通过一类特殊的散列函数(HASH函数)生成的,对这些HASH函数的特殊要求是:

  1. 接受的输入报文数据没有长度限制;
  2. 对任何输入报文数据生成固定长度的摘要(数字指纹)输出
  3. 从报文能方便地算出摘要;
  4. 难以对指定的摘要生成一个报文,而由该报文反推算出该指定的摘要;
  5. 两个不同的报文难以生成相同的摘要

代表:DSA

第2章在JAVA中的实现

2.1. 相关

Diffie-Hellman密钥一致协议和DES程序需要JCE工具库的支持,可以到 http://java.sun.com/security/index.html 下载JCE,并进行安装。简易安装把 jce1.2.1\lib 下的所有内容复制到 %java_home%\lib\ext下,如果没有ext目录自行建立,再把jce1_2_1.jar和sunjce_provider.jar添加到CLASSPATH内,更详细说明请看相应用户手册

2.2. 消息摘要MD5和SHA的使用

使用方法:

首先用生成一个MessageDigest类,确定计算方法

java.security.MessageDigest alga=java.security.MessageDigest.getInstance("SHA-1");

添加要进行计算摘要的信息

alga.update(myinfo.getBytes());

计算出摘要

byte[] digesta=alga.digest();

发送给其他人你的信息和摘要

其他人用相同的方法初始化,添加信息,最后进行比较摘要是否相同

algb.isEqual(digesta,algb.digest())

相关AIP

java.security.MessageDigest 类

static getInstance(String algorithm)

返回一个MessageDigest对象,它实现指定的算法

参数:算法名,如 SHA-1 或MD5

void update (byte input)

void update (byte[] input)

void update(byte[] input, int offset, int len)

添加要进行计算摘要的信息

byte[] digest()

完成计算,返回计算得到的摘要(对于MD5是16位,SHA是20位)

void reset()

复位

static boolean isEqual(byte[] digesta, byte[] digestb)

比效两个摘要是否相同

代码:

import java.security.*;
public class myDigest {
public static void main(String[] args) {
myDigest my=new myDigest();
my.testDigest();
}
public void testDigest()
{
try {
String myinfo="我的测试信息";
//java.security.MessageDigest alg=java.security.MessageDigest.getInstance("MD5");
java.security.MessageDigest alga=java.security.MessageDigest.getInstance("SHA-1");
alga.update(myinfo.getBytes());
byte[] digesta=alga.digest();
System.out.println("本信息摘要是:"+byte2hex(digesta));
//通过某中方式传给其他人你的信息(myinfo)和摘要(digesta) 对方可以判断是否更改或传输正常
java.security.MessageDigest algb=java.security.MessageDigest.getInstance("SHA-1");
algb.update(myinfo.getBytes());
if (algb.isEqual(digesta,algb.digest())) {
System.out.println("信息检查正常");
}
else
{
System.out.println("摘要不相同");
}
}
catch (java.security.NoSuchAlgorithmException ex) {
System.out.println("非法摘要算法");
}
}
public String byte2hex(byte[] b) //二行制转字符串
{
String hs="";
String stmp="";
for (int n=0;n<b.length;n++)
{
stmp=(java.lang.Integer.toHexString(b[n] & 0XFF));
if (stmp.length()==1) hs=hs+"0"+stmp;
else hs=hs+stmp;
if (n<b.length-1) hs=hs+":";
}
return hs.toUpperCase();
}
}

2.3. 数字签名DSA

  1. 对于一个用户来讲首先要生成他的密钥对,并且分别保存

    生成一个KeyPairGenerator实例

       java.security.KeyPairGenerator  keygen=java.security.KeyPairGenerator.getInstance("DSA");
    如果设定随机产生器就用如相代码初始化
    SecureRandom secrand=new SecureRandom();
    secrand.setSeed("tttt".getBytes()); //初始化随机产生器
    keygen.initialize(512,secrand); //初始化密钥生成器
    否则
    keygen.initialize(512);
    生成密钥公钥pubkey和私钥prikey
    KeyPair keys=keygen.generateKeyPair(); //生成密钥组
    PublicKey pubkey=keys.getPublic();
    PrivateKey prikey=keys.getPrivate();
    分别保存在myprikey.dat和mypubkey.dat中,以便下次不在生成
    (生成密钥对的时间比较长
    java.io.ObjectOutputStream out=new java.io.ObjectOutputStream(new java.io.FileOutputStream("myprikey.dat"));
    out.writeObject(prikey);
    out.close();
    out=new java.io.ObjectOutputStream(new java.io.FileOutputStream("mypubkey.dat"));
    out.writeObject(pubkey);
    out.close();


  2. 用他私人密钥(prikey)对他所确认的信息(info)进行数字签名产生一个签名数组

    从文件中读入私人密钥(prikey)

       java.io.ObjectInputStream in=new java.io.ObjectInputStream(new java.io.FileInputStream("myprikey.dat"));
    PrivateKey myprikey=(PrivateKey)in.readObject();
    in.close();
    初始一个Signature对象,并用私钥对信息签名
    java.security.Signature signet=java.security.Signature.getInstance("DSA");
    signet.initSign(myprikey);
    signet.update(myinfo.getBytes());
    byte[] signed=signet.sign();
    把信息和签名保存在一个文件中(myinfo.dat)
    java.io.ObjectOutputStream out=new java.io.ObjectOutputStream(new java.io.FileOutputStream("myinfo.dat"));
    out.writeObject(myinfo);
    out.writeObject(signed);
    out.close();
    把他的公钥的信息及签名发给其它用户


  3. 其他用户用他的公共密钥(pubkey)和签名(signed)和信息(info)进行验证是否由他签名的信息

    读入公钥
    java.io.ObjectInputStream in=new java.io.ObjectInputStream(new java.io.FileInputStream("mypubkey.dat"));
    PublicKey pubkey=(PublicKey)in.readObject();
    in.close();

    读入签名和信息
    in=new java.io.ObjectInputStream(new java.io.FileInputStream("myinfo.dat"));
    String info=(String)in.readObject();
    byte[] signed=(byte[])in.readObject();
    in.close();

    初始一个Signature对象,并用公钥和签名进行验证
    java.security.Signature signetcheck=java.security.Signature.getInstance("DSA");
    signetcheck.initVerify(pubkey);
    signetcheck.update(info.getBytes());
    if (signetcheck.verify(signed)) { System.out.println("签名正常");}

    对于密钥的保存本文是用对象流的方式保存和传送的,也可可以用编码的方式保存.注意要
    import java.security.spec.*
    import java.security.*

    具休说明如下

    • public key是用X.509编码的,例码如下:
        byte[] bobEncodedPubKey=mypublic.getEncoded(); //生成编码
      //传送二进制编码
      //以下代码转换编码为相应key对象
      X509EncodedKeySpec bobPubKeySpec = new X509EncodedKeySpec(bobEncodedPubKey);
      KeyFactory keyFactory = KeyFactory.getInstance("DSA");
      PublicKey bobPubKey = keyFactory.generatePublic(bobPubKeySpec);


    • 对于Private key是用PKCS#8编码,例码如下:
       byte[] bPKCS=myprikey.getEncoded();
      //传送二进制编码
      //以下代码转换编码为相应key对象
      PKCS8EncodedKeySpec priPKCS8=new PKCS8EncodedKeySpec(bPKCS);
      KeyFactory keyf=KeyFactory.getInstance("DSA");
      PrivateKey otherprikey=keyf.generatePrivate(priPKCS8);


  4. 常用API

    java.security.KeyPairGenerator 密钥生成器类
    public static KeyPairGenerator getInstance(String algorithm) throws NoSuchAlgorithmException
    以指定的算法返回一个KeyPairGenerator 对象
    参数: algorithm 算法名.如:"DSA","RSA"

    public void initialize(int keysize)

    以指定的长度初始化KeyPairGenerator对象,如果没有初始化系统以1024长度默认设置

    参数:keysize 算法位长.其范围必须在 512 到 1024 之间,且必须为 64 的倍数

    public void initialize(int keysize, SecureRandom random)
    以指定的长度初始化和随机发生器初始化KeyPairGenerator对象
    参数:keysize 算法位长.其范围必须在 512 到 1024 之间,且必须为 64 的倍数
    random 一个随机位的来源(对于initialize(int keysize)使用了默认随机器

    public abstract KeyPair generateKeyPair()
    产生新密钥对

    java.security.KeyPair 密钥对类
    public PrivateKey getPrivate()
    返回私钥

    public PublicKey getPublic()
    返回公钥

    java.security.Signature 签名类
    public static Signature getInstance(String algorithm) throws NoSuchAlgorithmException
    返回一个指定算法的Signature对象
    参数 algorithm 如:"DSA"

    public final void initSign(PrivateKey privateKey)
    throws InvalidKeyException
    用指定的私钥初始化
    参数:privateKey 所进行签名时用的私钥

    public final void update(byte data)
    throws SignatureException
    public final void update(byte[] data)
    throws SignatureException
    public final void update(byte[] data, int off, int len)
    throws SignatureException
    添加要签名的信息

    public final byte[] sign()
    throws SignatureException
    返回签名的数组,前提是initSign和update

    public final void initVerify(PublicKey publicKey)
    throws InvalidKeyException
    用指定的公钥初始化
    参数:publicKey 验证时用的公钥

    public final boolean verify(byte[] signature)
    throws SignatureException
    验证签名是否有效,前提是已经initVerify初始化
    参数: signature 签名数组

     */
    import java.security.*;
    import java.security.spec.*;
    public class testdsa {
    public static void main(String[] args) throws java.security.NoSuchAlgorithmException,java.lang.Exception {
    testdsa my=new testdsa();
    my.run();
    }
    public void run()
    {
    //数字签名生成密钥
    //第一步生成密钥对,如果已经生成过,本过程就可以跳过,对用户来讲myprikey.dat要保存在本地
    //而mypubkey.dat给发布给其它用户
    if ((new java.io.File("myprikey.dat")).exists()==false) {
    if (generatekey()==false) {
    System.out.println("生成密钥对败");
    return;
    };
    }
    //第二步,此用户
    //从文件中读入私钥,对一个字符串进行签名后保存在一个文件(myinfo.dat)中
    //并且再把myinfo.dat发送出去
    //为了方便数字签名也放进了myifno.dat文件中,当然也可分别发送
    try {
    java.io.ObjectInputStream in=new java.io.ObjectInputStream(new java.io.FileInputStream("myprikey.dat"));
    PrivateKey myprikey=(PrivateKey)in.readObject();
    in.close();
    // java.security.spec.X509EncodedKeySpec pubX509=new java.security.spec.X509EncodedKeySpec(bX509);
    //java.security.spec.X509EncodedKeySpec pubkeyEncode=java.security.spec.X509EncodedKeySpec
    String myinfo="这是我的信息"; //要签名的信息
    //用私钥对信息生成数字签名
    java.security.Signature signet=java.security.Signature.getInstance("DSA");
    signet.initSign(myprikey);
    signet.update(myinfo.getBytes());
    byte[] signed=signet.sign(); //对信息的数字签名
    System.out.println("signed(签名内容)="+byte2hex(signed));
    //把信息和数字签名保存在一个文件中
    java.io.ObjectOutputStream out=new java.io.ObjectOutputStream(new java.io.FileOutputStream("myinfo.dat"));
    out.writeObject(myinfo);
    out.writeObject(signed);
    out.close();
    System.out.println("签名并生成文件成功");
    }
    catch (java.lang.Exception e) {
    e.printStackTrace();
    System.out.println("签名并生成文件失败");
    };
    //第三步
    //其他人通过公共方式得到此户的公钥和文件
    //其他人用此户的公钥,对文件进行检查,如果成功说明是此用户发布的信息.
    //
    try {
    java.io.ObjectInputStream in=new java.io.ObjectInputStream(new java.io.FileInputStream("mypubkey.dat"));
    PublicKey pubkey=(PublicKey)in.readObject();
    in.close();
    System.out.println(pubkey.getFormat());
    in=new java.io.ObjectInputStream(new java.io.FileInputStream("myinfo.dat"));
    String info=(String)in.readObject();
    byte[] signed=(byte[])in.readObject();
    in.close();
    java.security.Signature signetcheck=java.security.Signature.getInstance("DSA");
    signetcheck.initVerify(pubkey);
    signetcheck.update(info.getBytes());
    if (signetcheck.verify(signed)) {
    System.out.println("info="+info);
    System.out.println("签名正常");
    }
    else System.out.println("非签名正常");
    }
    catch (java.lang.Exception e) {e.printStackTrace();};
    }
    //生成一对文件myprikey.dat和mypubkey.dat---私钥和公钥,
    //公钥要用户发送(文件,网络等方法)给其它用户,私钥保存在本地
    public boolean generatekey()
    {
    try {
    java.security.KeyPairGenerator keygen=java.security.KeyPairGenerator.getInstance("DSA");
    // SecureRandom secrand=new SecureRandom();
    // secrand.setSeed("tttt".getBytes()); //初始化随机产生器
    // keygen.initialize(576,secrand); //初始化密钥生成器
    keygen.initialize(512);
    KeyPair keys=keygen.genKeyPair();
    // KeyPair keys=keygen.generateKeyPair(); //生成密钥组
    PublicKey pubkey=keys.getPublic();
    PrivateKey prikey=keys.getPrivate();
    java.io.ObjectOutputStream out=new java.io.ObjectOutputStream(new java.io.FileOutputStream("myprikey.dat"));
    out.writeObject(prikey);
    out.close();
    System.out.println("写入对象 prikeys ok");
    out=new java.io.ObjectOutputStream(new java.io.FileOutputStream("mypubkey.dat"));
    out.writeObject(pubkey);
    out.close();
    System.out.println("写入对象 pubkeys ok");
    System.out.println("生成密钥对成功");
    return true;
    }
    catch (java.lang.Exception e) {
    e.printStackTrace();
    System.out.println("生成密钥对失败");
    return false;
    };
    }
    public String byte2hex(byte[] b)
    {
    String hs="";
    String stmp="";
    for (int n=0;n<b.length;n++)
    {
    stmp=(java.lang.Integer.toHexString(b[n] & 0XFF));
    if (stmp.length()==1) hs=hs+"0"+stmp;
    else hs=hs+stmp;
    if (n<b.length-1) hs=hs+":";
    }
    return hs.toUpperCase();
    }
    }


2.4. DESede/DES对称算法

首先生成密钥,并保存(这里并没的保存的代码,可参考DSA中的方法)

KeyGenerator keygen = KeyGenerator.getInstance(Algorithm);

SecretKey deskey = keygen.generateKey();

用密钥加密明文(myinfo),生成密文(cipherByte)

Cipher c1 = Cipher.getInstance(Algorithm);

c1.init(Cipher.ENCRYPT_MODE,deskey);

byte[] cipherByte=c1.doFinal(myinfo.getBytes());

传送密文和密钥,本文没有相应代码可参考DSA

.............

用密钥解密密文

c1 = Cipher.getInstance(Algorithm);

c1.init(Cipher.DECRYPT_MODE,deskey);

byte[] clearByte=c1.doFinal(cipherByte);

相对来说对称密钥的使用是很简单的,对于JCE来讲支技DES,DESede,Blowfish三种加密术

对于密钥的保存各传送可使用对象流或者用二进制编码,相关参考代码如下

   SecretKey deskey = keygen.generateKey();
byte[] desEncode=deskey.getEncoded();
javax.crypto.spec.SecretKeySpec destmp=new javax.crypto.spec.SecretKeySpec(desEncode,Algorithm);
SecretKey mydeskey=destmp;

相关API

KeyGenerator 在DSA中已经说明,在添加JCE后在instance进可以如下参数

DES,DESede,Blowfish,HmacMD5,HmacSHA1

javax.crypto.Cipher 加/解密器

public static final Cipher getInstance(java.lang.String transformation)
throws java.security.NoSuchAlgorithmException,
NoSuchPaddingException

返回一个指定方法的Cipher对象

参数:transformation 方法名(可用 DES,DESede,Blowfish)

public final void init(int opmode, java.security.Key key)
throws java.security.InvalidKeyException

用指定的密钥和模式初始化Cipher对象

参数:opmode 方式(ENCRYPT_MODE, DECRYPT_MODE, WRAP_MODE,UNWRAP_MODE)

key 密钥

public final byte[] doFinal(byte[] input)
throws java.lang.IllegalStateException,
IllegalBlockSizeException,
BadPaddingException

对input内的串,进行编码处理,返回处理后二进制串,是返回解密文还是加解文由init时的opmode决定

注意:本方法的执行前如果有update,是对updat和本次input全部处理,否则是本inout的内容

/*
安全程序 DESede/DES测试
*/
import java.security.*;
import javax.crypto.*;
public class testdes {
public static void main(String[] args){
testdes my=new testdes();
my.run();
}
public void run() {
//添加新安全算法,如果用JCE就要把它添加进去
Security.addProvider(new com.sun.crypto.provider.SunJCE());
String Algorithm="DES"; //定义 加密算法,可用 DES,DESede,Blowfish
String myinfo="要加密的信息";
try {
//生成密钥
KeyGenerator keygen = KeyGenerator.getInstance(Algorithm);
SecretKey deskey = keygen.generateKey();
//加密
System.out.println("加密前的二进串:"+byte2hex(myinfo.getBytes()));
System.out.println("加密前的信息:"+myinfo);
Cipher c1 = Cipher.getInstance(Algorithm);
c1.init(Cipher.ENCRYPT_MODE,deskey);
byte[] cipherByte=c1.doFinal(myinfo.getBytes());
System.out.println("加密后的二进串:"+byte2hex(cipherByte));
//解密
c1 = Cipher.getInstance(Algorithm);
c1.init(Cipher.DECRYPT_MODE,deskey);
byte[] clearByte=c1.doFinal(cipherByte);
System.out.println("解密后的二进串:"+byte2hex(clearByte));
System.out.println("解密后的信息:"+(new String(clearByte)));
}
catch (java.security.NoSuchAlgorithmException e1) {e1.printStackTrace();}
catch (javax.crypto.NoSuchPaddingException e2) {e2.printStackTrace();}
catch (java.lang.Exception e3) {e3.printStackTrace();}
}
public String byte2hex(byte[] b) //二行制转字符串
{
String hs="";
String stmp="";
for (int n=0;n<b.length;n++)
{
stmp=(java.lang.Integer.toHexString(b[n] & 0XFF));
if (stmp.length()==1) hs=hs+"0"+stmp;
else hs=hs+stmp;
if (n<b.length-1) hs=hs+":";
}
return hs.toUpperCase();
}
}


2.5. Diffie-Hellman密钥一致协议

公开密钥密码体制的奠基人Diffie和Hellman所提出的 "指数密钥一致协议"(Exponential Key Agreement Protocol),该协议不要求别的安全性 先决条件,允许两名用户在公开媒体上交换信息以生成"一致"的,可以共享的密钥。在JCE的中实现用户alice生成DH类型的密钥对,如果长度用1024生成的时间请,推荐第一次生成后保存DHParameterSpec,以便下次使用直接初始化.使其速度加快

System.out.println("ALICE: 产生 DH 对 ...");
KeyPairGenerator aliceKpairGen = KeyPairGenerator.getInstance("DH");
aliceKpairGen.initialize(512);
KeyPair aliceKpair = aliceKpairGen.generateKeyPair();

alice生成公钥发送组bob

byte[] alicePubKeyEnc = aliceKpair.getPublic().getEncoded();

bob从alice发送来的公钥中读出DH密钥对的初始参数生成bob的DH密钥对

注意这一步一定要做,要保证每个用户用相同的初始参数生成的

   DHParameterSpec dhParamSpec = ((DHPublicKey)alicePubKey).getParams();
KeyPairGenerator bobKpairGen = KeyPairGenerator.getInstance("DH");
bobKpairGen.initialize(dhParamSpec);
KeyPair bobKpair = bobKpairGen.generateKeyPair();

bob根据alice的公钥生成本地的DES密钥

   KeyAgreement bobKeyAgree = KeyAgreement.getInstance("DH");
bobKeyAgree.init(bobKpair.getPrivate());
bobKeyAgree.doPhase(alicePubKey, true);
SecretKey bobDesKey = bobKeyAgree.generateSecret("DES");

bob已经生成了他的DES密钥,他现把他的公钥发给alice,

      byte[] bobPubKeyEnc = bobKpair.getPublic().getEncoded();

alice根据bob的公钥生成本地的DES密钥

       ,,,,,,解码
KeyAgreement aliceKeyAgree = KeyAgreement.getInstance("DH");
aliceKeyAgree.init(aliceKpair.getPrivate());
aliceKeyAgree.doPhase(bobPubKey, true);
SecretKey aliceDesKey = aliceKeyAgree.generateSecret("DES");

bob和alice能过这个过程就生成了相同的DES密钥,在这种基础就可进行安全能信

常用API

java.security.KeyPairGenerator 密钥生成器类
public static KeyPairGenerator getInstance(String algorithm)
throws NoSuchAlgorithmException
以指定的算法返回一个KeyPairGenerator 对象
参数: algorithm 算法名.如:原来是DSA,现在添加了 DiffieHellman(DH)

public void initialize(int keysize)
以指定的长度初始化KeyPairGenerator对象,如果没有初始化系统以1024长度默认设置
参数:keysize 算法位长.其范围必须在 512 到 1024 之间,且必须为 64 的倍数
注意:如果用1024生长的时间很长,最好生成一次后就保存,下次就不用生成了

public void initialize(AlgorithmParameterSpec params)
throws InvalidAlgorithmParameterException
以指定参数初始化

javax.crypto.interfaces.DHPublicKey
public DHParameterSpec getParams()
返回
java.security.KeyFactory

public static KeyFactory getInstance(String algorithm)
throws NoSuchAlgorithmException
以指定的算法返回一个KeyFactory
参数: algorithm 算法名:DSH,DH

public final PublicKey generatePublic(KeySpec keySpec)
throws InvalidKeySpecException
根据指定的key说明,返回一个PublicKey对象

java.security.spec.X509EncodedKeySpec
public X509EncodedKeySpec(byte[] encodedKey)
根据指定的二进制编码的字串生成一个key的说明
参数:encodedKey 二进制编码的字串(一般能过PublicKey.getEncoded()生成)
javax.crypto.KeyAgreement 密码一至类

public static final KeyAgreement getInstance(java.lang.String algorithm)
throws java.security.NoSuchAlgorithmException
返回一个指定算法的KeyAgreement对象
参数:algorithm 算法名,现在只能是DiffieHellman(DH)

public final void init(java.security.Key key)
throws java.security.InvalidKeyException
用指定的私钥初始化
参数:key 一个私钥

public final java.security.Key doPhase(java.security.Key key,
boolean lastPhase)
throws java.security.InvalidKeyException,
java.lang.IllegalStateException
用指定的公钥进行定位,lastPhase确定这是否是最后一个公钥,对于两个用户的
情况下就可以多次定次,最后确定
参数:key 公钥
lastPhase 是否最后公钥

public final SecretKey generateSecret(java.lang.String algorithm)
throws java.lang.IllegalStateException,
java.security.NoSuchAlgorithmException,
java.security.InvalidKeyException
根据指定的算法生成密钥
参数:algorithm 加密算法(可用 DES,DESede,Blowfish)

*/
import java.io.*;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.*;
import java.security.interfaces.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import javax.crypto.interfaces.*;
import com.sun.crypto.provider.SunJCE;
public class testDHKey {
public static void main(String argv[]) {
try {
testDHKey my= new testDHKey();
my.run();
} catch (Exception e) {
System.err.println(e);
}
}
private void run() throws Exception {
Security.addProvider(new com.sun.crypto.provider.SunJCE());
System.out.println("ALICE: 产生 DH 对 ...");
KeyPairGenerator aliceKpairGen = KeyPairGenerator.getInstance("DH");
aliceKpairGen.initialize(512);
KeyPair aliceKpair = aliceKpairGen.generateKeyPair(); //生成时间长
// 张三(Alice)生成公共密钥 alicePubKeyEnc 并发送给李四(Bob) ,
//比如用文件方式,socket.....
byte[] alicePubKeyEnc = aliceKpair.getPublic().getEncoded();
//bob接收到alice的编码后的公钥,将其解码
KeyFactory bobKeyFac = KeyFactory.getInstance("DH");
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec (alicePubKeyEnc);
PublicKey alicePubKey = bobKeyFac.generatePublic(x509KeySpec);
System.out.println("alice公钥bob解码成功");
// bob必须用相同的参数初始化的他的DH KEY对,所以要从Alice发给他的公开密钥,
//中读出参数,再用这个参数初始化他的 DH key对
//从alicePubKye中取alice初始化时用的参数
DHParameterSpec dhParamSpec = ((DHPublicKey)alicePubKey).getParams();
KeyPairGenerator bobKpairGen = KeyPairGenerator.getInstance("DH");
bobKpairGen.initialize(dhParamSpec);
KeyPair bobKpair = bobKpairGen.generateKeyPair();
System.out.println("BOB: 生成 DH key 对成功");
KeyAgreement bobKeyAgree = KeyAgreement.getInstance("DH");
bobKeyAgree.init(bobKpair.getPrivate());
System.out.println("BOB: 初始化本地key成功");
//李四(bob) 生成本地的密钥 bobDesKey
bobKeyAgree.doPhase(alicePubKey, true);
SecretKey bobDesKey = bobKeyAgree.generateSecret("DES");
System.out.println("BOB: 用alice的公钥定位本地key,生成本地DES密钥成功");
// Bob生成公共密钥 bobPubKeyEnc 并发送给Alice,
//比如用文件方式,socket.....,使其生成本地密钥
byte[] bobPubKeyEnc = bobKpair.getPublic().getEncoded();
System.out.println("BOB向ALICE发送公钥");
// alice接收到 bobPubKeyEnc后生成bobPubKey
// 再进行定位,使aliceKeyAgree定位在bobPubKey
KeyFactory aliceKeyFac = KeyFactory.getInstance("DH");
x509KeySpec = new X509EncodedKeySpec(bobPubKeyEnc);
PublicKey bobPubKey = aliceKeyFac.generatePublic(x509KeySpec);
System.out.println("ALICE接收BOB公钥并解码成功");
;
KeyAgreement aliceKeyAgree = KeyAgreement.getInstance("DH");
aliceKeyAgree.init(aliceKpair.getPrivate());
System.out.println("ALICE: 初始化本地key成功");
aliceKeyAgree.doPhase(bobPubKey, true);
// 张三(alice) 生成本地的密钥 aliceDesKey
SecretKey aliceDesKey = aliceKeyAgree.generateSecret("DES");
System.out.println("ALICE: 用bob的公钥定位本地key,并生成本地DES密钥");
if (aliceDesKey.equals(bobDesKey)) System.out.println("张三和李四的密钥相同");
//现在张三和李四的本地的deskey是相同的所以,完全可以进行发送加密,接收后解密,达到
//安全通道的的目的
/*
* bob用bobDesKey密钥加密信息
*/
Cipher bobCipher = Cipher.getInstance("DES");
bobCipher.init(Cipher.ENCRYPT_MODE, bobDesKey);
String bobinfo= "这是李四的机密信息";
System.out.println("李四加密前原文:"+bobinfo);
byte[] cleartext =bobinfo.getBytes();
byte[] ciphertext = bobCipher.doFinal(cleartext);
/*
* alice用aliceDesKey密钥解密
*/
Cipher aliceCipher = Cipher.getInstance("DES");
aliceCipher.init(Cipher.DECRYPT_MODE, aliceDesKey);
byte[] recovered = aliceCipher.doFinal(ciphertext);
System.out.println("alice解密bob的信息:"+(new String(recovered)));
if (!java.util.Arrays.equals(cleartext, recovered))
throw new Exception("解密后与原文信息不同");
System.out.println("解密后相同");
}
}






回页首


第3章小结

在加密术中生成密钥对时,密钥对的当然是越长越好,但费时也越多,请从中从实际出发选取合适的长度,大部分例码中的密钥是每次运行就从新生成,在实际的情 况中是生成后在一段时间保存在文件中,再次运行直接从文件中读入,从而加快速度。当然定时更新和加强密钥保管的安全性也是必须的。



关于作者


王辉,具有八年的编程及系统管理经验,所使用的语言为C和Java 编程语言。目前在深圳一家公司做程序员,使用C和JAVA为DB2数据库编程.



posted @ 2009-03-23 14:14 二胡 阅读(197) | 评论 (0)编辑 收藏

     摘要: 转 http://www.mnot.net/cache_docs/ What’s a Web Cache? Why do people use them? Kinds of Web Caches Browser Caches Proxy Caches ArenR...  阅读全文
posted @ 2009-03-22 15:55 二胡 阅读(408) | 评论 (0)编辑 收藏

转 http://www.infoq.com/cn/articles/rest-anti-patterns

   人们在试验REST时,通常会四处寻找样例——而他们往往不仅能找到一大堆自称“符合REST”或标榜为“REST API”的样例,还会发现许多关于某个自称符合REST的特定服务名不副实的讨论。 
  

为什么会这样?HTTP虽不是什么新事物,但人们使用它的方式却五花八门。其中有些做法符合Web设计者的初衷,但许多并非如此。要为你的HTTP 应用(无论是面向人类、还是计算机、或同时面向这两者使用的)应用REST原则,意味着你要恰好反过来:尽量“正确地”使用Web,或者说按符合REST 的方式使用Web(倘若你不喜欢用对或错来评判的话)。对许多人来说,这的确是一种崭新的方式方法。

我经常在文章里作同样的声明:REST、Web和HTTP是不同的事物;REST可以用多种不同技术来实现,而HTTP只是一种恰好符合REST架 构风格的具体架构。所以,其实我应该小心区分“REST”与“REST式HTTP”这两个概念的。但我没有这么做,在本文剩余部分,我们姑且认为它们是相 同的事物。

跟任何新的方式方法一样,发掘一些共同的模式是有益的。在本系列的第一第二篇 文章中,我已经讲述了一些基础——比如集合资源的概念、将计算结果转换为资源本身、以及用聚合(syndication)来模仿事件。后续文章将进一步讲 述这些及其他模式。不过在本文中,我想主要说说反模式(anti-patterns)——即那些力求符合REST式HTTP、但未能成功而造成问题的典型 做法。

首先我们来看看我发掘了哪些反模式:

  1. 全部采用GET
  2. 全部采用POST
  3. 忽视缓存
  4. 忽视响应代码
  5. 误用cookies
  6. 忘记超媒体
  7. 忽视MIME类型
  8. 破坏自描述性

下面我们来逐个详细说明。

全部采用GET

在许多人看来,REST仅仅意味着用HTTP暴露一些应用功能。HTTP GET是最重要的基本操作(operation )(严格地讲,用“动词(verb)”或“方法(method)”这样的术语比较好)。GET方法应当用于获取由URI标识的资源的一个表示 (representation),而许多(即便谈不上所有)现有的HTTP库和服务器编程API不是将URI视为一种资源标识符(resource identifier),而是将之视为一种传递参数的便利手段。这导致了以下这种URIs的出现:

http://example.com/some-api?method=deleteCustomer&id=1234

实际上,你无法根据构成URI的字符获知关于给定系统的“REST性(RESTfulness)”的任何信息,不过对于上面那个URI,我们可以判 断该 GET操作不是“安全的(safe)”——也就是说,调用者很可能要为结果(删除一个客户)负责,尽管规范里说在这种情况下使用GET方法是错误的。

这种做法唯一有利的方面在于它编程起来容易,而且在浏览器中调试也简单——你只要把URI粘贴到浏览器地址栏里、然后调整一些“参数”就行了。这种反模式主要存在以下问题:

  1. URI没有被用作资源标识符,而是被用于传递操作及其参数了。
  2. HTTP方法(HTTP method)不一定跟语义相符。
  3. 这种链接一般不可加入书签。
  4. 有“爬虫”造成非预期副作用的风险。

注意:符合这一反模式的APIs没准最终碰巧符合REST原则。这里有个例子:

http://example.com/some-api?method=findCustomer&id=1234

这个URI是标识操作及其参数呢,还是标识一个资源呢?两种情况都有可能:它可以是一个完全合法的、可加入书签的URI;对它做GET操作也许是“ 安全的 ”;它也许会根据Accept报头返回不同的格式,并支持复杂的缓存机制。在很多情况下,这将是偶然的。API经常在刚开始时采用这种方式来暴露一个“读 ”接口,但当开发者要增添“写”功能时就有问题了(因为你无法通过对上述URI做PUT操作来更新一个客户——开发者得构造另一个URI)。

全部采用POST

这一反模式跟前一个颇为相似,只不过这里用的是POST方法而已。POST除了携带一个URI,还携带一个实体主体(entity body)。一个典型的场景是:将单个URI作为POST请求的目标、通过发送不同的消息来表达不同的意图。实际上,SOAP 1.1 Web服务就是这样做的,它把HTTP当作一种“传输协议”来用。服务器根据SOAP消息(可能还包括一些WS-Addressing SOAP报头)决定做什么。

可能有人认为“全部采用POST”跟“全部采用GET”存在的问题完全一样,只是它更难用一些,而且不能利用缓存(甚至连偶尔的机会都没有),且无法支持书签。事实上,它并不是违反了哪条REST原则,而是根本忽视了REST原则。

忽视缓存

即使你按各个动词的原本意图来使用它们,你仍可以轻易禁止缓存机制。最简单的做法就是在你的HTTP响应里增加这样一个报头:

Cache-control: no-cache

这样可以禁止缓存机制发挥作用。当然,这也许正是你想要做的,然而通常这只是你的Web框架规定的一个缺省设置。不过,对高效的缓存与再验证 (caching and re-validation)的支持,是采用REST式HTTP的诸多关键优点之一。Sam Ruby表示,在判断是否符合REST原则时的一个关键问题就是“你支持ETag吗”? (ETag是HTTP 1.1里引入的一种机制,它允许客户端通过加密的校验和来验证一个被缓存的表示是否仍然有效)。要生成正确的报头,最简单的做法就是把这个任务交给一个“ 知道”怎样做的基础设施——例如通过在Web服务器(比如Apache HTTPD)的目录里生成一个文件。

当然,这也要涉及到客户端一方:你在为一个REST式服务实现程序客户端时,你应充分利用现有的缓存机制,以免每次都重新获取表示。例如,服务器也 许已经发出信息:初次返回的表示在600秒内都可被认为是“新的”(比方说因为后端系统每30分钟才轮询一次)。这样的话,短时间内重复请求同一信息就完 全没必要了。在客户端设置一个代理缓存(比如Squid)也许比自行构建相应逻辑更好。

HTTP的缓存机制强大而复杂;Mark Nottingham的《缓存指南(Cache Tutorial)》是一个很好的指南。

忽视响应代码

HTTP提供了一组丰富的应用级状态代码, 它们可用于应付不同场合,不过许多Web开发者对此并不知晓。大部分人对200(“OK”)、404(“Not found”)和500(“Internal server error”)这些状态代码是比较熟悉的。但除此以外还有很多其他状态代码,正确使用这些状态代码意味着客户端与服务器可以在一个具备较丰富语义的层次上 进行沟通。

例如,201(“Created”)响应代码表明已经创建了一个新的资源,其URI在Location响应报头里。409(“Conflict”) 告诉客户端存在冲突,比如随PUT请求发送的是基于老版本资源的数据。再如,412(“Precondition Failed”)表明服务器不能满足客户端的预期。

正确使用状态代码的另一方面涉及客户端:应该根据一种统一的总体方法对不同类别的状态代码(例如所有2xx段代码、所有5xx段代码)作不同处理——例如,即便客户端不具备处理特定代码的逻辑,但至少应把所有2xx段代码视为成功信号。

许多声称符合REST的应用仅仅返回200或500,甚至只返回200(并在响应实体主体里给出错误文本——SOAP就是这样的)。你要是愿意,可 以称之为“通过状态代码200传达错误”,但无论你觉得采用哪个术语好,假如你不利用HTTP状态代码丰富的应用语义,那么你将错失提高重用性、增强互操 作性和提升松耦合性的机会。

误用cookies

利用cookies来传播某个服务端会话状态的键(key)是另一种REST反模式。

Cookies表明肯定哪个地方不符合REST了。是这样吗?不;不一定。REST的关键思想之一是无状态性(statelessness)——不 是说一个服务器不能保存任何数据:倘若是资源状态(resource state)或客户端状态(client state),那是可以的。服务器不能保存的是会话状态(session state),因为那会造成可伸缩性、可靠性及耦合方面的问题。Cookies的最典型的用法是:保存一个跟“某个保存在服务端内存里的数据结构”相关联 的键(key)。这意味着,浏览器随各次请求发出去的cookie是被用于构建会话状态的。

如果一个cookie被用于保存一些“服务器不依赖于会话状态即可验证”的信息(比如认证令牌),那么这样的cookies是完全符合REST原则 的—— 不过有一点需要注意:如果有其他更为标准的方式来传递一则信息(比如放在URI里、放在某个标准报头里、或较少见地放在消息主体里),那就不应将之放在 cookie里。例如,按REST式HTTP的观点来使用HTTP认证就比较好。

忘记超媒体

最不易接受的REST思想就是标准的方法集合。REST理论并没有规定标准集合由哪些方法组成,它只是规定必须有一组适用于所有资源的方法集合。对 于 HTTP来说,这组集合是GET、PUT、POST和DELETE(至少起初是这样),你需要一定适应时间才能掌握如何将所有应用语义投射到这四个动词 上。但你一旦适应了,就可以开始运用这个REST的子集——一种基于Web的CRUD(Create、Read、Update、 Delete)架构——了。暴露这种反模式的应用不是真正的“非REST式”应用(假如存在这种事物的话),它们只是未能利用一个REST核心概念——“ 超媒体即应用状态引擎(hypermedia as the engine of application state)”。

超媒体(hypermedia)是一个把事物链接起来的概念,正是它造就了Web这个网——一个互联的资源集合,应用通过跟随链接从一个状态进入另一个状态。这听上去也许有点深奥,不过其实遵从这一原则是有正当理由的。

“忘记超媒体”反模式的首要表现就是:表示(representation)里缺少链接。尽管通常客户端可以根据一定的规则来构造URI,但是因为 服务器没有发送任何链接,所以客户端将无法跟随链接。一种较好的做法是:即支持构造URI,又支持跟随链接——这里的链接通常反映了下层数据模型中的关 系。但最好的情况是:客户端应该只需知道一个URI;其他URI(各个URI及其构造模式,如:各种查询字符串)应该通过超媒体(作为资源表示里的链接) 来传达。 Atom发布协议(Atom Publishing Protocol)就是一个好例子,它有一个服务文档(service documents)的概念,服务文档为它所描述的域内的各个集合提供具名元素(named elements)。最后,应用可能经历的状态迁移应该是动态传播的,客户端应该可以不用掌握多少知识就可以跟随它们。HTML就是一个好榜样,它包含足 够的信息,以便浏览器可以向用户提供一个完全动态的接口。

我本想增加一个“人类可读的URI”反模式的。但我没那么做,因为我跟其他人一样也喜欢可读的、好“篡改”的URI。但是当人们采用REST时,他 们经常浪费许多时间来讨论“正确的”URI设计,而忘记了超媒体方面。所以,我建议你不要花太多时间来寻找正确的URI设计(毕竟,它们只是字符串而 已),而是多花一些精力在表示里寻找提供链接的正确地方。

忽视MIME类型

HTTP有个内容协商(content negotiation)的概念,它允许客户端根据需要获取资源的不同表示(representations)。例如,一个资源也许有不同格式的表示(如 XML、JSON或YAML等)以便于用各种不同语言(如Java、JavaScript及Ruby)实现的消费者所使用。再如,一个资源可能即有面向人 类的PDF或JPEG版表示,又有“机器可读的”XML版表示。还有,一个资源可能同时支持v1.1版和v1.2版的自定义表示格式。不管怎样,也许可以 为“只有一个表示格式”找到理由,但这常常意味着丢掉某种机会。

显然,若一个服务能为更多未预见到的客户端所用(或重用)那更好。因此,依靠现有、预定义、广为人知的格式,要好过发明私有格式——这会导致本文讲述的最后一个反模式。

破坏自描述性

这种反模式是如此普遍,以至于几乎在每个、甚至那些由所谓的“REST狂热者们”(包括我在内)创建的REST应用里都可以看到:违反自描述性约束 (这一努力目标并不像人们最初想象的那样跟人工智能科幻小说有多大牵连)。理想情况下,一个消息(HTTP 请求或HTTP响应,包括报头与主体)应该包含足够信息,以便任何通用客户端、服务器或媒介(intermediary)能够处理它。例如,当你的浏览器 获取某个受保护资源的PDF表示(representation)时,你可以看到由标准达成的协定是如何起作用的:有些HTTP认证交换发生,可能会发生 一些缓存(caching)和/或再验证(revalidation),服务器发送的content-type报头(application/pdf)触发了你系统里注册的PDF阅读器,最后你得以在自己的屏幕上阅读该PDF。所有用户都可以用他们自己的基础设施来执行同样的请求。若服务器开发者另外增加一种内容类型,那么服务器的客户端(或服务的消费者)只需确保他们安装了正确的阅读器即可。

你要是发明自己的报头、格式或协议,那就一定程度上破坏了自描述性约束。极端地讲,所有没有被某个标准化组织官方标准化的东西都违反此约束,因而可 被认为符合本反模式。在实践中,你应努力做到尽可能遵循标准,并懂得“某些协定可能只在一个较小的领域(比方说,你的服务和客户端是专门针对它开发的)中 适用” 的道理。

总结

自从“四人组(Gang of Four)”出版了书籍、 掀起模式运动的开端以来,许多人误解了它,并试图在尽可能多的场合下应用模式——这已被其他人所取笑。模式应当仅在符合上下文时才被应用。同样地,可能有 人会不遗余力地在所有场合下虔诚地努力避免所有反模式。许多时候,你有充分理由违反某一规则,或者按REST的术语放松某一约束。这么做是没问题的——但 了解实际情况、作出知情决策是有益的。

但愿本文能有助于你在开始首个REST项目时避免落入这些常见的陷阱。

非常感谢Javier Botana和Burkhard Neppert对本文初稿的反馈。

Stefan Tilkov是InfoQ SOA社区的首席编辑,以及位于德国/瑞士的innoQ公司的合伙人、首席顾问和主要的REST狂热主义者。


posted @ 2009-03-22 15:36 二胡 阅读(380) | 评论 (0)编辑 收藏

转 http://www.infoq.com/cn/articles/rest-introduction

    不知你是否意识到,围绕着什么才是实现异构的应用到应用通信的“正确”方式,一场争论正进行的如火如荼:虽然当前主流的方式明显地集中在基于SOAP、 WSDL和WS-*规范的Web Services领域,但也有少数人用细小但洪亮的声音主张说更好的方式是REST,表述性状态转移(REpresentational State Transfer)的简称。在本文中,我不会涉及争论的话题,而是尝试对REST和RESTful HTTP应用集成做实用性的介绍。以我的经验,有些话题一旦触及就会引来众多的讨论,当涉及到这方面话题的时候,我会深入详细地阐述。
   

REST关键原则

大部分对REST的介绍是以其正式的定义和背景作为开场的。但这儿且先按下不表,我先提出一个简单扼要的定义:REST定义了应该如何正确地使用 (这和大多数人的实际使用方式有很大不同)Web标准,例如HTTP和URI。如果你在设计应用程序时能坚持REST原则,那就预示着你将会得到一个使用 了优质Web架构(这将让你受益)的系统。总之,五条关键原则列举如下:

  • 为所有“事物”定义ID
  • 将所有事物链接在一起
  • 使用标准方法
  • 资源多重表述
  • 无状态通信

下面让我们进一步审视这些原则。

为所有“事物”定义ID

在这里我使用了“事物”来代替更正式准确的术语“资源”,因为一条如此简单的原则,不应该被淹没在术语当中。思考一下人们构建的系统,通常会找到一 系列值得被标识的关键抽象。每个事物都应该是可标识的,都应该拥有一个明显的ID——在Web中,代表ID的统一概念是:URI。URI构成了一个全局命 名空间,使用URI标识你的关键资源意味着它们获得了一个唯一、全局的ID。

对事物使用一致的命名规则(naming scheme)最主要的好处就是你不需要提出自己的规则——而是依靠某个已被定义,在全球范围中几乎完美运行,并且能被绝大多数人所理解的规则。想一下你 构建的上一个应用(假设它不是采用RESTful方式构建的)中的任意一个高级对象(high-level object),那就很有可能看到许多从使用唯一标识中受益的用例。比如,如果你的应用中包含一个对顾客的抽象,那么我可以相当肯定,用户会希望将一个指 向某个顾客的链接,能通过电子邮件发送到同事那里,或者加入到浏览器的书签中,甚至写到纸上。更透彻地讲:如果在一个类似于Amazon.com的在线商 城中,没有用唯一的ID(一个URI)标识它的每一件商品,可想而知这将是多么可怕的业务决策。

当面对这个原则时,许多人惊讶于这是否意味着需要直接向外界暴露数据库记录(或者数据库记录ID)——自从多年以来面向对象的实践告诫我们,要将持 久化的信息作为实现细节隐藏起来之后,哪怕是刚有点想法都常会使人惊恐。但是这条原则与隐藏实现细节两者之间并没有任何冲突:通常,值得被URI标识的事 物——资源——要比数据库记录抽象的多。例如,一个定单资源可以由定单项、地址以及许多其它方面(可能不希望作为单独标识的资源暴露出来)组成。标识所有 值得标识的事物,领会这个观念可以进一步引导你创造出在传统的应用程序设计中不常见的资源:一个流程或者流程步骤、一次销售、一次谈判、一份报价请求—— 这都是应该被标识的事物的示例。同样,这也会导致创建比非RESTful设计更多的持久化实体。

下面是一些你可能想到的URI的例子:

http://example.com/customers/1234
http://example.com/orders/2007/10/776654
http://example.com/products/4554
http://example.com/processes/salary-increase-234

正如我选择了创建便于阅读的URI——这是个有用的观点,尽管不是RESTful设计所必须的——应该能十分容易地推测出URI的含义:它们明显地标识着单一“数据项”。但是再往下看:

http://example.com/orders/2007/11
http://example.com/products?color=green

首先,这两个URI看起来与之前的稍有不同——毕竟,它们不是对一件事物的标识,而是对一类事物集合的标识(假定第一个URI标识了所有在2007年11月份提交的定单,第二个则是绿颜色产品的集合)。但是这些集合自身也是事物(资源),也应该被标识。

注意,使用唯一、全局统一的命名规则的好处,既适用于浏览器中的Web应用,也适用于机对机(machine-to-machine,m2m)通信。

来对第一个原则做下总结:使用URI标识所有值得标识的事物,特别是应用中提供的所有“高级”资源,无论这些资源代表单一数据项、数据项集合、虚拟亦或实际的对象还是计算结果等。

将所有事物链接在一起

接下来要讨论的原则有一个有点令人害怕的正式描述:“超媒体被当作应用状态引擎(Hypermedia as the engine of application state)”,有时简写为HATEOAS。(严格地说,这不是我说的。)这个描述的核心是超媒体概念,换句话说:是链接的思想。链接是我们在HTML中常见的概念,但是它的用处绝不局限于此(用于人们网络浏览)。考虑一下下面这个虚构的XML片段:

 
23


如果你观察文档中product和customer的链接,就可以很容易地想象到,应用程序(已经检索过文档)如何“跟随”链接检索更多的信息。当然,如果使用一个遵守专用命名规范的简单“id”属性作为链接,也是可行的——但是仅限于应用环境之内。使用URI表示链接的优雅之处在于,链接可以指向由不同应用、不同服务器甚至位于另一个大陆上的不同公司提供的资源——因为URI命名规范是全球标准,构成Web的所有资源都可以互联互通。

超媒体原则还有一个更重要的方面——应用“状态”。简而言之,实际上服务器端(如果你愿意,也可以叫服务提供者)为客户端(服务消费者)提供一组链 接,使客户端能通过链接将应用从一个状态改变为另一个状态。稍后我们会在另一篇文章中探究这个方面的影响;目前,只需要记住:链接是构成动态应用的非常有 效的方式。

对此原则总结如下:任何可能的情况下,使用链接指引可以被标识的事物(资源)。也正是超链接造就了现在的Web。

使用标准方法

在前两个原则的讨论中暗含着一个假设:接收URI的应用程序可以通过URI明确地一些有意义的事情。如果你在公共汽车上看到一个URI,你可以将它输入浏览器的地址栏中并回车——但是你的浏览器如何知道需要对这个URI做些什么呢?

它知道如何去处理URI的原因在于所有的资源都支持同样的接口,一套同样的方法(只要你乐意,也可以称为操作)集合。在HTTP中这被叫做动词 (verb),除了两个大家熟知的(GET和POST)之外,标准方法集合中还包含PUT、DELETE、HEAD和OPTIONS。这些方法的含义连同 行为许诺都一起定义在HTTP规范之中。如果你是一名OO开发人员,就可以想象到RESTful HTTP方案中的所有资源都继承自类似于这样的一个类(采用类Java、C#的伪语法描述,请注意关键的方法):

class Resource {
Resource(URI u);
Response get();
Response post(Request r);
Response put(Request r);
Response delete();
}

由于所有资源使用了同样的接口,你可以依此使用GET方法检索一个表述(representation)——也 就是对资源的描述。因为规范中定义了GET的语义,所以可以肯定当你调用它的时候不需要对后果负责——这就是为什么可以“安全”地调用此方法。GET方法 支持非常高效、成熟的缓存,所以在很多情况下,你甚至不需要向服务器发送请求。还可以肯定的是,GET方法具有幂等性[译 注:指多个相同请求返回相同的结果]——如果你发送了一个GET请求没有得到结果,你可能不知道原因是请求未能到达目的地,还是响应在反馈的途中丢失了。 幂等性保证了你可以简单地再发送一次请求解决问题。幂等性同样适用于PUT(基本的含义是“更新资源数据,如果资源不存在的话,则根据此URI创建一个新 的资源”)和DELETE(你完全可以一遍又一遍地操作它,直到得出结论——删除不存在的东西没有任何问题)方法。POST方法,通常表示“创建一个新资 源”,也能被用于调用任过程,因而它既不安全也不具有幂等性。

如果你采用RESTful的方式暴露应用功能(如果你乐意,也可以称为服务功能),那这条原则和它的约束同样也适用于你。如果你已经习惯于另外的设计方式,则很难去接受这条原则——毕竟,你很可能认为你的应用包含了超出这些操作表达范围的逻辑。请允许我花费一些时间来让你相信不存在这样的情况。

来看下面这个简单的采购方案例子:

Sample Scenario

可以看到,例子中定义了两个服务程序(没有包含任何实现细节)。这些服务程序的接口都是为了完成任务(正是我们讨论的 OrderManagement和CustomerManagement服务)而定制的。如果客户端程序试图使用这些服务,那它必须针对这些特定接口进行 编码——不可能在这些接口定义之前,使用客户程序去有目的地和接口协作。这些接口定义了服务程序的应用协议(application protocol)。

在RESTful HTTP方式中,你将通过组成HTTP应用协议的通用接口访问服务程序。你可能会想出像这样的方式:

Sample Scenario, done RESTfully

可以看到,服务程序中的特定操作被映射成为标准的HTTP方法——为了消除歧义,我创建了一组全新的资源。“这是骗人的把戏”,我听见你叫嚷着。 不,这不是欺骗。标识一个顾客的URI上的GET方法正好相当于getCustomerDetails操作。有人用三角形形象化地说明了这一点:

Knobs one can turn

把三个顶点想象为你可以调节的按钮。可以看到在第一种方法中,你拥有许多操作,许多种类的数据以及固定数量的“实例”(本质上和你拥有的服务程序数 量一致)。在第二种方法中,你拥有固定数量的操作,许多种类的数据和许多调用固定方法的对象。它的意义在于,证明了通过这两种方式,你基本上可以表示任何 你喜欢的事情。

为什么使用标准方法如此重要?从根本上说,它使你的应用成为Web的一部分——应用程序为Web变成Internet上最成功的应用所做的贡献,与 它添加到Web中的资源数量成比例。采用RESTful方式,一个应用可能会向Web中添加数以百万计的客户URI;如果采用CORBA技术并维持应用的 原有设计方式,那它的贡献大抵只是一个“端点(endpoint)”——就好比一个非常小的门,仅仅允许有钥匙的人进入其中的资源域。

统一接口也使得所有理解HTTP应用协议的组件能与你的应用交互。通用客户程序(generic client)就是从中受益的组件的例子,例如curl、wget、代理、缓存、HTTP服务器、网关还有Google、Yahoo!、MSN等等。

总结如下:为使客户端程序能与你的资源相互协作,资源应该正确地实现默认的应用协议(HTTP),也就是使用标准的GET、PUT、POST和DELETE方法。

资源多重表述

到目前为止我们一直忽略了一个稍微复杂的问题:客户程序如何知道该怎样处理检索到的数据,比如作为GET或者POST请求的结果?原因是,HTTP 采取的方式是允许数据处理和操作调用之间关系分离的。换句话说,如果客户程序知道如何处理一种特定的数据格式,那就可以与所有提供这种表述格式的资源交 互。让我们再用一个例子来阐明这个观点。利用HTTP内容协商(content negotiation),客户程序可以请求一种特定格式的表述:

GET /customers/1234 HTTP/1.1
Host: example.com
Accept: application/vnd.mycompany.customer+xml

请求的结果可能是一些由公司专有的XML格式表述的客户信息。假设客户程序发送另外一个不同的请求,就如下面这样:

GET /customers/1234 HTTP/1.1
Host: example.com
Accept: text/x-vcard

结果则可能是VCard格式的客户地址。(在这里我没有展示响应的内容,在其HTTP Content-type头中应该包含着关于数据类型的元数据。)这说明为什么理想的情况下,资源表述应该采用标准格式——如果客户程序对HTTP应用协 议和一组数据格式都有所“了解”,那么它就可以用一种有意义的方式与世界上任意一个RESTful HTTP应用交互。 不幸的是,我们不可能拿到所有东西的标准格式,但是,或许我们可以想到在公司或者一些合作伙伴中使用标准格式来营造一个小环境。当然以上情况不仅适用于从 服务器端到客户端的数据,反之既然——倘若从客户端传来的数据符合应用协议,那么服务器端就可以使用特定的格式处理数据,而不去关心客户端的类型。

在实践中,资源多重表述还有着其它重要的好处:如果你为你的资源提供HTML和XML两种表述方式,那这些资源不仅可以被你的应用所用,还可以被任意标准Web浏览器所用——也就是说,你的应用信息可以被所有会使用Web的人获取到。

资源多重表述还有另外一种使用方式:你可以将应用的Web UI纳入到Web API中——毕竟,API的设计通常是由UI可以提供的功能驱动的,而UI也是通过API执行动作的。将这两个任务合二为一带来了令人惊讶的好处,这使得 使用者和调用程序都能得到更好的Web接口。

总结:针对不同的需求提供资源多重表述。

无状态通信

无状态通信是我要讲到的最后一个原则。首先,需要着重强调的是,虽然REST包含无状态性(statelessness)的观念,但这并不是说暴露功能的应用不能有状态——
事 实上,在大部分情况下这会导致整个做法没有任何用处。REST要求状态要么被放入资源状态中,要么保存在客户端上。或者换句话说,服务器端不能保持除了单 次请求之外的,任何与其通信的客户端的通信状态。这样做的最直接的理由就是可伸缩性—— 如果服务器需要保持客户端状态,那么大量的客户端交互会严重影响服务器的内存可用空间(footprint)。(注意,要做到无状态通信往往需要需要一些 重新设计——不能简单地将一些session状态绑缚在URI上,然后就宣称这个应用是RESTful。)

但除此以外,其它方面可能显得更为重要:无状态约束使服务器的变化对客户端是不可见的,因为在两次连续的请求中,客户端并不依赖于同一台服务器。一 个客户端从某台服务器上收到一份包含链接的文档,当它要做一些处理时,这台服务器宕掉了,可能是硬盘坏掉而被拿去修理,可能是软件需要升级重启——如果这 个客户端访问了从这台服务器接收的链接,它不会察觉到后台的服务器已经改变了。

理论上的REST

我承认:以上我所说的REST不是真正的REST,而且我可能有点过多地热衷于简单化。但因为我想有一个与众不同的开场,所以没有在一开始就介绍其正式的定义和背景。现在就让我们稍微简要地介绍一下这方面的内容。

首先,先前我并没有明确地区分HTTP、RESTful HTTP和REST。要理解这些不同方面之间的关系,我们要先来看看REST的历史。

Roy T. Fielding在他的博士学位论文(实际上你应该访问这个链接——至少对于一篇学术论文来说,它是相当易读的。此论文已被翻译成中文) 中定义了术语REST。Roy曾是许多基本Web协议的主要设计者,其中包括HTTP和URIs,并且他在论文中对这些协议提出了很多想法。(这篇论文被 誉为“REST圣经”,这是恰当的——毕竟,是作者发明了这个术语,所以在定义上,他写的任何内容都被认为是权威的。)在论文中,Roy首先定义一种方法 论来谈论架构风格——高级、抽象的模式,来表达架构方法背后的核心理念。每一个架构风格由一系列的约束(constraints)定义形成。架构风格的例子包括“没有风格”(根本没有任何约束)、管道和过滤器(pipe and filter)、客户端/服务器、分布式对象以及——你猜到它了——REST。

如果对你来说这些听起来都太抽象了,那就对了——REST在本质上是一个可以被许多不同技术实现的高层次的风格,而且可以被实例化——通过为它的抽 象特性赋上不同的值。比如,REST中包含资源和统一接口的概念——也就是说,所有资源都应该对这些相同的方法作出反应。但是REST并没有说明是哪些方 法,或者有多少方法。

REST风格的一个“化身”便是HTTP(以及一套相关的一套标准,比如URI),或者稍微抽象一些:Web架构自身。接着上面的例子,HTTP使 用HTTP动词作为REST统一接口的“实例”。由于Fielding是在Web已经(或者至少是大部分)“完善”了之后才定义的REST风格,有人可能 会争论两者是不是100%的匹配。但是无论如何,整体上来说Web、HTTP和URI仅仅是REST风格的一个主要实现。不过,由于Roy Fielding即是REST论文的作者,又对Web架构设计有过深远的影响,两者相似也在情理之中。

最后,我在前面一次又一次地使用着术语“RESTful HTTP”,原因很简单:许多使用HTTP的应用因为一些理由并没有遵循REST原则,有人会说使用HTTP而不遵循REST原则就等同于滥用HTTP。 当然这听起来有点狂热——事实上违反REST约束的原因通常是,仅仅因为每个约束带来的设计权衡可能不适合于一些特殊情况。但通常,违背REST约束的原 因可归咎于对其好处认知的缺乏。来看一个明显的反面案例:使用HTTP GET调用类似于删除对象的操作,这违反了REST的安全约束和一般性常识(客户程序不应为此负责,服务器端开发人员大概不是有意而为之)。但在随后的文 章中,我会提及更多这样或那样的对HTTP的滥用。

总结

本文试图对REST(Web架构)背后的概念提供快速的介绍。RESTful HTTP暴露功能的方式与RPC、分布式对象以及Web Services是不相同的;要真正理解这些不同是需要一些心态的转变。不管你构建的应用是仅仅想暴露Web UI还是想把API变成Web的一份子,了解下REST的原则还是有好处的。

Stefan Tilkov是InfoQ SOA社区的首席编辑,并且是位于德国和瑞士的innoQ公司的共同创始人、首席顾问和REST狂热分子首领。

   译者简介:苑永凯,软件设计师,毕业于山东大学。主要关注领域为Java EE企业应用、Java EE中间件技术以及敏捷开发方法实践,微有心得。他的Blog为http://blog.csdn.net/ai92,您也可以通过yuanyk[AT]gmail.com与他联系。参与InfoQ中文站内容建设,请邮件至china-editorial[at]infoq.com


posted @ 2009-03-22 14:30 二胡 阅读(151) | 评论 (0)编辑 收藏

转 http://www.infoq.com/cn/articles/rest-architecure

作者 骆古道 发布于 2007年5月27日 下午8时18分

社区
Ruby,
Java
主题
Web框架,
REST,
架构,
Ruby on Rails
标签
AJAX,
Ruby on Rails

一 种思维方式影响了软件行业的发展。REST软件架构是当今世界上最成功的互联网的超媒体分布式系统。它让人们真正理解我们的网络协议HTTP本来面貌。它 正在成为网络服务的主流技术,同时也正在改变互联网的网络软件开发的全新思维方式。AJAX技术和Rails框架把REST软件架构思想真正地在实际中很 好表现出来。今天微软也已经应用REST并且提出把我们现有的网络变成为一个语义网,这种网络将会使得搜索更加智能化。

REST与HTTP协议

REST软件架构是由Roy Thomas Fielding博士在2000年首次提出的。他为我们描绘了开发基于互联网的网络软件的蓝图。REST软件架构是一个抽象的概念,是一种为了实现这一互联网的超媒体分布式系统的行动指南。利用任何的技术都可以实现这种理念。而实现这一软件架构最著名的就是HTTP协议。通常我们把REST也写作为REST/HTTP,在实际中往往把REST理解为基于HTTP的REST软件架构,或者更进一步把REST和HTTP看作为等同的概念。

今天,HTTP是互联网上应用最广泛的计算机协议。HTTP不是一个简单的运载数据的协议,而是一个具有丰富内涵的网络软件的 协议。它不仅仅能够对于互联网资源进行唯一定位,而且还能告诉我们对于该资源进行怎样运作。这也是REST软件架构当中最重要的两个理念。而REST软件 架构理念是真正理解HTTP协议而形成的。有了REST软件架构理念出现,才使得软件业避免了对HTTP协议的片面理解。只有正确的理论指导,才能避免在 软件开发的实际工作过程中少走弯路。

REST与URI(资源定位)

REST软件架构之所以是一个超媒体系统,是因为它可以把网络上所有资源进行唯一的定位,不管你的文件是图片、文件Word还是视频文件,也不管你 的文件是txt文件格式、xml文件格式还是其它文本文件格式。它利用支持HTTP的TCP/IP协议来确定互联网上的资源。

REST与CRUD原则

REST软件架构遵循了CRUD原则,该原则告诉我们对于资源(包括网络资源)只需要四种行为:创建(Create)、获取(Read)、更新 (Update)和销毁(DELETE)就可以完成对其操作和处理了。其实世界万物都是遵循这一规律:生、变、见、灭。所以计算机世界也不例外。这个原则 是源自于我们对于数据库表的数据操作:insert(生)、select(见)、update(变)和delete(灭),所以有时候CRUD也写作为 RUDI,其中的I就是insert。这四个操作是一种原子操作,即一种无法再分的操作,通过它们可以构造复杂的操作过程,正如数学上四则运算是数字的最 基本的运算一样。

REST与网络服务

尽管在Java语言世界中网络服务目前是以SOAP技术为主,但是REST将是是网络服务的另一选择,并且是真正意义上的网络服务。基于REST思 想的网络服务不久的将来也会成为是网络服务的主流技术。REST不仅仅把HTTP作为自己的数据运输协议,而且也作为直接进行数据处理的工具。而当前的网 络服务技术都需要使用其它手段来完成数据处理工作,它们完全独立于HTTP协议来进行的,这样增加了大量的复杂软件架构设计工作。REST的思想充分利用 了现有的HTTP技术的网络能力。在德国电视台上曾经出现过一个这样的五十万欧元智力题:如何实现网络服务才能充分利用现有的HTTP协议?该问题给出了 四个答案:去问微软;WSDL2.0/SOAP1.2;WS-Transfer;根本没有。这个问题告诉我们HTTP并不是一个简单的数据传来传去的协 议,而是一个聪明的会表现自己的协议,这也许是REST = Representational State Transfer的真正含义。

实际上目前很多大公司已经采用了REST技术作为网络服务,如Google、Amazon等。在Java语言中重要的两个以SOAP技术开始的网络服务框架XFire和Axis也把REST作为自己的另一种选择。它们的新的项目分别是Apache CXF Axis2 。Java语言也制定关于REST网络服务规范:JAX-RS: Java API for RESTful Web Services (JSR 311)。相信还会出现更多与REST相关的激动人心的信息。

REST与AJAX技术

尽管AJAX技术的出现才不到两年时间,但是AJAX技术遵循了REST的一些重要原则。AJAX技术充分利用了HTTP来获取网络资源并且实现了 HTTP没有的对于异步数据进行传输的功能。AJAX技术还使得软件更好地实现分布性功能,在一个企业内只要一个人下载了AJAX引擎,其它企业内部的人 员,就可以共享该资源了。AJAX技术遵守REST准则的应用程序中简单和可伸缩的架构,凡是采用AJAX技术的页面简洁而又丰富,一个页面表现了丰富多 彩的形态。

AJAX技术还使用了一种不同于XML格式的JSON文件格式,这个意义在哪里呢?在REST软件架构下我们不能对于XML文件进行序列化处理,这 样程序员必须要使用自己的XML绑定框架。而以序列化的JavaScript对象为基础的JSON已经获得了广泛认可,它被认为能以远比XML更好的方式 来序列化和传输简单数据结构,而且它更简洁。这对REST是一个极大贡献和补充。

当前的网络应用软件还违背了REST的“无状态服务器”约束。REST服务器只知道自己的状态。REST不关心客户端的状态,客户端的状态自己来管 理,这是AJAX技术的应用之地。通过AJAX技术,可以发挥有状态网络客户机的优势。而REST的服务器关心的是从所有网络客户端发送到服务器操作的顺 序。这样使得互联网这样一个巨大的网络得到有序的管理。

REST与Rails框架

Ruby on Rails框架(简称Rails或者Rails框架)是一个基于Ruby语言的越来越流行的网络应用软件开发框架。它提供了关于REST最好的支持,也是 当今应用REST最成功的一个软件开发框架。Rails框架(从版本1.2.x起)成为了第一个引入REST作为核心思想的主流网络软件开发框架。在 Rails框架的充分利用了REST软件架构之后,人们更加坚信REST的重要性和必要性。Rails利用REST软件架构思想对网络服务也提供了一流的 支持。从最直观的角度看待REST,它是网络服务最理想的手段,但是Rails框架把REST带到了网络应用软件开发框架。这是一次飞跃,让REST的思 想从网络服务的应用提升到了网络应用软件开发。利用REST思想的simply_restful插件已经成为了Rails框架的核心内容。

REST安全性

我们把现有基于SOAP的网络服务和基于REST/HTTP网络服务作个比喻,前者是一种传统的寄信方式,而后者是现代网络的电子邮件方式。要是是 寄信和电子邮件都有病毒存在的话,传统的寄信被送到对方就很危险,而电子邮件是开发的,电子邮件供应商比如Google为我们检查了电子邮件是否有病毒。 这里并不是说明SOAP网络服务消息包含义病毒,而是说明HTTP是无法处理SOAP信息包究竟好不好,需要额外的软件工具解决这一问题,包括防火墙也用 不上和管不了。

REST/HTTP网络服务的信息包可以被防火墙理解和控制。你可以按照操作和链接进行过滤信息包,如你可以规定从外部来的只能读取(GET操作) 自己服务器的资源。这样对于系统管理员而言使得软件管理更为简单。REST的安全性还可以利用传输安全协议SSL/TLS、基本和摘要式认证(Basic und Digest Authentication)。除了这些REST自身的安全性功能外,还可以利用像基于信息的Web Services Security(JSR 155)作为REST不错的补充。

参考文献

中文参考文献 Roy Thomas Fielding博士论文中文版本 Roy Thomas Fielding博士论文英文版本
作者简介:骆古道,网名Cnruby,八十年代初毕业于西北工业大学数理力学系,1988年公派留学德国,从事组合最优化理论研究,从九十年代初期起一直致力于计算机领域软件开发、设计和管理等方面工作,个人博客为“道喜技术日记”。

posted @ 2009-03-22 14:20 二胡 阅读(166) | 评论 (0)编辑 收藏

    看了Fenng的一篇blog关于Cookie, iframe 与 P3P,自己不是很清楚,查询了相关资料,把相关心得贴出来。
     应用场景:
     比如一个 www.a.com中有个服务应用是引用b.com域的,
通常情况下用户的隐私策略设置为中或者更高.在b.com域的cookie是不能够正常读取的,但是服务端能够改变用户对b.com域的隐私策略使b.com域下的cookie正常存取.
     也可以说是利用p3p解决第三方cookie存取的问题。
     第一方Cookie:是来自当前正在查看的网站,或者发送到当前正在查看的网站。
     第三方Cookie:是来自当前正在查看的网站以外的网站,或者发送到当前正在查看的网站以外的网站。第三方网站通常提供正在查看的网站上的内容。例如,许多站点使用来自第三方网站的广告,或者iframe的别的网站的url,这些第三方的网站可能使用的Cookie。
   
     p3p相关东东
     P3P是万维网联盟(W3C)公布的一项隐私保护推荐标准,旨在为网上冲浪的Internet用户提供隐私保护。现在有越来越多的网站在消费者访问时,都 会收集一些用户信息。制定P3P标准的出发点就是为了减轻消费者因网站收集个人信息所引发的对于隐私权可能受到侵犯的忧虑。P3P标准的构想是:Web 站点的隐私策略应该告之访问者该站点所收集的信息类型、信息将提供给哪些人、信息将被保留多少时间及其使用信息的方式,如站点应做诸如 “本网站将监测您所访问的页面以提高站点的使用率”或“本网站将尽可能为您提供更合适的广告”等申明。访问支持P3P网站的用户有权查看站点隐私报告,然后决定是否接受cookie或是否使用该网站。
     通过指定用户隐私策略,就可以达到存取第三方cookie的目的,看到这也许会觉得跟web应用毫无关系,真正的问题是如何让服务器来指定用户浏览器的隐私策略?
      其实很简单,只要在响应用户请求的时候在http的头信息中增加关于p3p的配置信息就可以了。
     response.setHeader("P3P","CP='IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT'");
      不对之处,请大家指正!
参考:
http://viralpatel.net/blogs/2008/12/how-to-set-third-party-cookies-with-iframe.html
http://www.dbanotes.net/web/cookie_p3p.html
http://ding--lin.javaeye.com/blog/94336
http://www.cnblogs.com/gudai/archive/2008/09/21/1295432.html
http://cailin.javaeye.com/blog/175422 
     
posted @ 2009-03-21 13:34 二胡 阅读(1476) | 评论 (1)编辑 收藏

转 http://www.blogjava.net/wang123/archive/2009/03/19/260729.html

加密java源代码

Java程序的源代码很容易被别人偷看,只要有一个反编译器,任何人都可以分析别人的代码。本文讨论如何在不修改原有程序的情况下,通过加密技术保护源代码。

  一、为什么要加密?

  对于传统的C或C++之类的语言来说,要在Web上保护源代码是很容易的,只要不发布它就可以。遗憾的是,Java程序的源代码很容易被别人偷 看。只要有一个反编译器,任何人都可以分析别人的代码。Java的灵活性使得源代码很容易被窃取,但与此同时,它也使通过加密保护代码变得相对容易,我们 唯一需要了解的就是Java的ClassLoader对象。当然,在加密过程中,有关Java Cryptography Extension(JCE)的知识也是必不可少的。

  有几种技术可以“模糊”Java类文件,使得反编译器处理类文件的效果大打折扣。然而,修改反编译器使之能够处理这些经过模糊处理的类文件并不是什么难事,所以不能简单地依赖模糊技术来保证源代码的安全。

  我们可以用流行的加密工具加密应用,比如PGP(Pretty Good Privacy)或GPG(GNU Privacy Guard)。这时,最终用户在运行应用之前必须先进行解密。但解密之后,最终用户就有了一份不加密的类文件,这和事先不进行加密没有什么差别。

  Java运行时装入字节码的机制隐含地意味着可以对字节码进行修改。JVM每次装入类文件时都需要一个称为ClassLoader的对象,这个 对象负责把新的类装入正在运行的JVM。JVM给ClassLoader一个包含了待装入类(比如java.lang.Object)名字的字符串,然后 由ClassLoader负责找到类文件,装入原始数据,并把它转换成一个Class对象。

  我们可以通过定制ClassLoader,在类文件执行之前修改它。这种技术的应用非常广泛??在这里,它的用途是在类文件装入之时进行解密,因此可以看成是一种即时解密器。由于解密后的字节码文件永远不会保存到文件系统,所以窃密者很难得到解密后的代码。

  由于把原始字节码转换成Class对象的过程完全由系统负责,所以创建定制ClassLoader对象其实并不困难,只需先获得原始数据,接着就可以进行包含解密在内的任何转换。

  Java 2在一定程度上简化了定制ClassLoader的构建。在Java 2中,loadClass的缺省实现仍旧负责处理所有必需的步骤,但为了顾及各种定制的类装入过程,它还调用一个新的findClass方法。

  这为我们编写定制的ClassLoader提供了一条捷径,减少了麻烦:只需覆盖findClass,而不是覆盖loadClass。这种方法避免了重复所有装入器必需执行的公共步骤,因为这一切由loadClass负责。

  不过,本文的定制ClassLoader并不使用这种方法。原因很简单。如果由默认的ClassLoader先寻找经过加密的类文件,它可以找 到;但由于类文件已经加密,所以它不会认可这个类文件,装入过程将失败。因此,我们必须自己实现loadClass,稍微增加了一些工作量。

二、定制类装入器

  每一个运行着的JVM已经拥有一个ClassLoader。这个默认的ClassLoader根据CLASSPATH环境变量的值,在本地文件系统中寻找合适的字节码文件。

  应用定制ClassLoader要求对这个过程有较为深入的认识。我们首先必须创建一个定制ClassLoader类的实例,然后显式地要求它 装入另外一个类。这就强制JVM把该类以及所有它所需要的类关联到定制的ClassLoader。Listing 1显示了如何用定制ClassLoader装入类文件。

  【Listing 1:利用定制的ClassLoader装入类文件】

以下是引用片段:

  // 首先创建一个ClassLoader对象 如 http://www.bt285.cn
ClassLoader myClassLoader = new myClassLoader();
// 利用定制ClassLoader对象装入类文件
// 并把它转换成Class对象
Class myClass = myClassLoader.loadClass( "mypackage.MyClass" );
// 最后,创建该类的一个实例
Object newInstance = myClass.newInstance();
// 注意,MyClass所需要的所有其他类,都将通过
// 定制的ClassLoader自动装入 

   如前所述,定制ClassLoader只需先获取类文件的数据,然后把字节码传递给运行时系统,由后者完成余下的任务。

  ClassLoader有几个重要的方法。创建定制的ClassLoader时,我们只需覆盖其中的一个,即loadClass,提供获取原始类文件数据的代码。这个方法有两个参数:类的名字,以及一个表示JVM是否要求解析类名字的标记(即是否同时装入有依赖关系的类)。如果这个标记是true,我们只需在返回JVM之前调用resolveClass。

  【Listing 2:ClassLoader.loadClass()的一个简单实现】

以下是引用片段:

public Class loadClass( String name, boolean resolve )  如:http://www.5a520.cn
throws ClassNotFoundException {
try {
// 我们要创建的Class对象
Class clasz = null;
// 必需的步骤1:如果类已经在系统缓冲之中,
// 我们不必再次装入它
clasz = findLoadedClass( name );
if (clasz != null)
return clasz;
// 下面是定制部分
byte classData[] = /* 通过某种方法获取字节码数据 */;
if (classData != null) {
// 成功读取字节码数据,现在把它转换成一个Class对象
clasz = defineClass( name, classData, 0, classData.length );
}
// 必需的步骤2:如果上面没有成功,
// 我们尝试用默认的ClassLoader装入它
if (clasz == null)
clasz = findSystemClass( name );
// 必需的步骤3:如有必要,则装入相关的类
if (resolve && clasz != null)
resolveClass( clasz );
// 把类返回给调用者
return clasz;
} catch( IOException ie ) {
throw new ClassNotFoundException( ie.toString() );
} catch( GeneralSecurityException gse ) {
throw new ClassNotFoundException( gse.toString() );
}

    Listing 2显示了一个简单的loadClass实现。代码中的大部分对所有ClassLoader对象来说都一样,但有一小部分(已通过注释标记)是特有的。在处理过程中,ClassLoader对象要用到其他几个辅助方法:

  findLoadedClass:用来进行检查,以便确认被请求的类当前还不存在。loadClass方法应该首先调用它。

  defineClass:获得原始类文件字节码数据之后,调用defineClass把它转换成一个Class对象。任何loadClass实现都必须调用这个方法。

  findSystemClass:提供默认ClassLoader的支持。如果用来寻找类的定制方法不能找到指定的类(或者有意地不用定制方法),则可以调用该方法尝试默认的装入方式。这是很有用的,特别是从普通的JAR文件装入标准Java类时。

  resolveClass:当JVM想要装入的不仅包括指定的类,而且还包括该类引用的所有其他类时,它会把loadClass的resolve参数设置成true。这时,我们必须在返回刚刚装入的Class对象给调用者之前调用resolveClass。

  三、加密、解密

  Java加密扩展即Java Cryptography Extension,简称JCE。它是Sun的加密服务软件,包含了加密和密匙生成功能。JCE是JCA(Java Cryptography Architecture)的一种扩展。

  JCE没有规定具体的加密算法,但提供了一个框架,加密算法的具体实现可以作为服务提供者加入。除了JCE框架之外,JCE软件包还包含了 SunJCE服务提供者,其中包括许多有用的加密算法,比如DES(Data Encryption Standard)和Blowfish。

  为简单计,在本文中我们将用DES算法加密和解密字节码。下面是用JCE加密和解密数据必须遵循的基本步骤:

  步骤1:生成一个安全密匙。在加密或解密任何数据之前需要有一个密匙。密匙是随同被加密的应用一起发布的一小段数据,Listing 3显示了如何生成一个密匙。 【Listing 3:生成一个密匙】

以下是引用片段:

// DES算法要求有一个可信任的随机数源
SecureRandom sr = new SecureRandom();
// 为我们选择的DES算法生成一个KeyGenerator对象
KeyGenerator kg = KeyGenerator.getInstance( "DES" );
kg.init( sr );
// 生成密匙
SecretKey key = kg.generateKey();
// 获取密匙数据
byte rawKeyData[] = key.getEncoded();
/* 接下来就可以用密匙进行加密或解密,或者把它保存
为文件供以后使用 */
doSomething( rawKeyData );  

步骤2:加密数据。得到密匙之后,接下来就可以用它加密数据。除了解密的ClassLoader之外,一般还要有一个加密待发布应用的独立程序(见Listing 4)。 【Listing 4:用密匙加密原始数据】

以下是引用片段:

// DES算法要求有一个可信任的随机数源
SecureRandom sr = new SecureRandom();
byte rawKeyData[] = /* 用某种方法获得密匙数据 */;
// 从原始密匙数据创建DESKeySpec对象
DESKeySpec dks = new DESKeySpec( rawKeyData );
// 创建一个密匙工厂,然后用它把DESKeySpec转换成
// 一个SecretKey对象
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance( "DES" );
SecretKey key = keyFactory.generateSecret( dks );
// Cipher对象实际完成加密操作
Cipher cipher = Cipher.getInstance( "DES" );
// 用密匙初始化Cipher对象
cipher.init( Cipher.ENCRYPT_MODE, key, sr );
// 现在,获取数据并加密
byte data[] = /* 用某种方法获取数据 */
// 正式执行加密操作
byte encryptedData[] = cipher.doFinal( data );
// 进一步处理加密后的数据
doSomething( encryptedData );  

步骤3:解密数据。运行经过加密的应用时,ClassLoader分析并解密类文件。操作步骤如Listing 5所示。 【Listing 5:用密匙解密数据】

  // DES算法要求有一个可信任的随机数源
SecureRandom sr = new SecureRandom();
byte rawKeyData[] = /* 用某种方法获取原始密匙数据 */;
// 从原始密匙数据创建一个DESKeySpec对象
DESKeySpec dks = new DESKeySpec( rawKeyData );
// 创建一个密匙工厂,然后用它把DESKeySpec对象转换成
// 一个SecretKey对象
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance( "DES" );
SecretKey key = keyFactory.generateSecret( dks );
// Cipher对象实际完成解密操作
Cipher cipher = Cipher.getInstance( "DES" );
// 用密匙初始化Cipher对象
cipher.init( Cipher.DECRYPT_MODE, key, sr );
// 现在,获取数据并解密
byte encryptedData[] = /* 获得经过加密的数据 */
// 正式执行解密操作
byte decryptedData[] = cipher.doFinal( encryptedData );
// 进一步处理解密后的数据
doSomething( decryptedData );  

 

四、应用实例

  前面介绍了如何加密和解密数据。要部署一个经过加密的应用,步骤如下:

  步骤1:创建应用。我们的例子包含一个App主类,两个辅助类(分别称为Foo和Bar)。这个应用没有什么实际功用,但只要我们能够加密这个应用,加密其他应用也就不在话下。

  步骤2:生成一个安全密匙。在命令行,利用GenerateKey工具(参见GenerateKey.java)把密匙写入一个文件: % java GenerateKey key.data

  步骤3:加密应用。在命令行,利用EncryptClasses工具(参见EncryptClasses.java)加密应用的类: % java EncryptClasses key.data App.class Foo.class Bar.class

  该命令把每一个.class文件替换成它们各自的加密版本。

  步骤4:运行经过加密的应用。用户通过一个DecryptStart程序运行经过加密的应用。DecryptStart程序如Listing 6所示。 【Listing 6:DecryptStart.java,启动被加密应用的程序】

以下是引用片段:

  import java.io.*;
import java.security.*;
import java.lang.reflect.*;
import javax.crypto.*;
import javax.crypto.spec.*;
public class DecryptStart extends ClassLoader
{
// 这些对象在构造函数中设置,
// 以后loadClass()方法将利用它们解密类
private SecretKey key;
private Cipher cipher;
// 构造函数:设置解密所需要的对象
public DecryptStart( SecretKey key ) throws GeneralSecurityException,
IOException {
this.key = key;
String algorithm = "DES";
SecureRandom sr = new SecureRandom();
System.err.println( "[DecryptStart: creating cipher]" );
cipher = Cipher.getInstance( algorithm );
cipher.init( Cipher.DECRYPT_MODE, key, sr );
}
// main过程:我们要在这里读入密匙,创建DecryptStart的
// 实例,它就是我们的定制ClassLoader。
// 设置好ClassLoader以后,我们用它装入应用实例,
// 最后,我们通过Java Reflection API调用应用实例的main方法
static public void main( String args[] ) throws Exception {
String keyFilename = args[0];
String appName = args[1];
// 这些是传递给应用本身的参数
String realArgs[] = new String[args.length-2];
System.arraycopy( args, 2, realArgs, 0, args.length-2 );
// 读取密匙
System.err.println( "[DecryptStart: reading key]" );
byte rawKey[] = Util.readFile( keyFilename );
DESKeySpec dks = new DESKeySpec( rawKey );
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance( "DES" );
SecretKey key = keyFactory.generateSecret( dks );
// 创建解密的ClassLoader
DecryptStart dr = new DecryptStart( key );
// 创建应用主类的一个实例
// 通过ClassLoader装入它
System.err.println( "[DecryptStart: loading "+appName+"]" );
Class clasz = dr.loadClass( appName );
// 最后,通过Reflection API调用应用实例
// 的main()方法
// 获取一个对main()的引用
String proto[] = new String[1];
Class mainArgs[] = { (new String[1]).getClass() };
Method main = clasz.getMethod( "main", mainArgs );
// 创建一个包含main()方法参数的数组
Object argsArray[] = { realArgs };
System.err.println( "[DecryptStart: running "+appName+".main()]" );
// 调用main()
main.invoke( null, argsArray );
}
public Class loadClass( String name, boolean resolve )
throws ClassNotFoundException {
try {
// 我们要创建的Class对象
Class clasz = null;
// 必需的步骤1:如果类已经在系统缓冲之中
// 我们不必再次装入它
clasz = findLoadedClass( name );
if (clasz != null)
return clasz;
// 下面是定制部分
try {
// 读取经过加密的类文件
byte classData[] = Util.readFile( name+".class" );
if (classData != null) {
// 解密...
byte decryptedClassData[] = cipher.doFinal( classData );
// ... 再把它转换成一个类
clasz = defineClass( name, decryptedClassData,
0, decryptedClassData.length );
System.err.println( "[DecryptStart: decrypting class "+name+"]" );
}
} catch( FileNotFoundException fnfe )
// 必需的步骤2:如果上面没有成功
// 我们尝试用默认的ClassLoader装入它
if (clasz == null)
clasz = findSystemClass( name );
// 必需的步骤3:如有必要,则装入相关的类
if (resolve && clasz != null)
resolveClass( clasz );
// 把类返回给调用者
return clasz;
} catch( IOException ie ) {
throw new ClassNotFoundException( ie.toString()
);
} catch( GeneralSecurityException gse ) {
throw new ClassNotFoundException( gse.toString()
);
}
}

 对于未经加密的应用,正常执行方式如下: % java App arg0 arg1 arg2

  对于经过加密的应用,则相应的运行方式为: % java DecryptStart key.data App arg0 arg1 arg2

  DecryptStart有两个目的。一个DecryptStart的实例就是一个实施即时解密操作的定制ClassLoader;同 时,DecryptStart还包含一个main过程,它创建解密器实例并用它装入和运行应用。示例应用App的代码包含在App.java、 Foo.java和Bar.java内。Util.java是一个文件I/O工具,本文示例多处用到了它。完整的代码请从本文最后下载。

  五、注意事项

  我们看到,要在不修改源代码的情况下加密一个Java应用是很容易的。不过,世上没有完全安全的系统。本文的加密方式提供了一定程度的源代码保护,但对某些攻击来说它是脆弱的。

  虽然应用本身经过了加密,但启动程序DecryptStart没有加密。攻击者可以反编译启动程序并修改它,把解密后的类文件保存到磁盘。降低 这种风险的办法之一是对启动程序进行高质量的模糊处理。或者,启动程序也可以采用直接编译成机器语言的代码,使得启动程序具有传统执行文件格式的安全性。

  另外还要记住的是,大多数JVM本身并不安全。狡猾的黑客可能会修改JVM,从ClassLoader之外获取解密后的代码并保存到磁盘,从而绕过本文的加密技术。Java没有为此提供真正有效的补救措施。

  不过应该指出的是,所有这些可能的攻击都有一个前提,这就是攻击者可以得到密匙。如果没有密匙,应用的安全性就完全取决于加密算法的安全性。虽然这种保护代码的方法称不上十全十美,但它仍不失为一种保护知识产权和敏感用户数据的有效方案。



posted @ 2009-03-20 20:07 二胡 阅读(193) | 评论 (0)编辑 收藏

转 http://www.cnblogs.com/JustinYoung/articles/1350887.html

不唐突的JavaScript的七条准则

经过多年的开发、教学和编写不唐突的JavaScript, 我发现了下面的一些准则。我希望它们可以帮助你对“为什么这样设计和执行JavaScript比较好”有一点理解。这些规则曾经帮助我更快地交付产品,并且产品的质量更高,也更容易维护。

1.不要做任何假设
(JavaScript是一个不可靠的助手)

可能不唐突的JavaScript 的最重要的一个特性就是——你要停止任何假设:

  • 不要假设JavaScript是可用的,你最好认为它很有可能是不可用的,而不是直接依赖于它。
  • 在你经过测试确认一些方法和属性可以使用之前,不要假设浏览器支持它们。
  • 不要假设HTML代码如你想象的那样正确,每次都要进行检查,并且当其不可用的时候就什么也不要做。
  • 让JavaScript的功能独立于输入设备
  • 要记住其他的脚本可能会影响你的JavaScript的功能,所以要保证你的脚本的作用域尽可能地安全。

在开始设计你的脚本之前,要考虑的第一件事情就是检查一下你要为其编写脚本的HTML代码,看看有什么东西可以帮助你达到目的。

2.找出钩子和节点关系(HTML是脚本的基石)

在 开始编写脚本之前,要先看一下你要为之编写JavaScript的HTML。如果HTML是未经组织的或者未知的,那么你几乎不可能有一个好的脚本编写方 案——很可能就会出现下面的情况:要么是会用JavaScript创建太多标记,要么就是应用太依赖于JavaScript。

在HTML中有一些东西需要考虑,那就是钩子和节点关系。

<1>.HTML 钩子

HTML 最初的和最重要的钩子就是ID,而且ID可以通过最快的DOM方法——getElementById 访问到。如果在一个有效的HTML文档中所有的ID都是独一无二的话(在IE中关于name 和 ID 有一个bug,不过有些好的类库解决了这个问题),使用ID就是安全可靠的,并且易于测试。

其他一些钩子就是是HTML元素和CSS 类,HTML元素可以通过getElementsByTagName方法访问,而在多数浏览器中都还不能通过原生的DOM方法来访问CSS类。不过,有很 多外部类库提供了可以访问CSS类名(类似于 getElementsByClassName) 的方法。

<2>.HTML 节点关系

关于HTML的另外比较有意思的一点就是标记之间的关系,思考下面的问题:

  • 要怎样才可以最容易地、通过最少的DOM遍历来到达目标节点?
  • 通过修改什么标记,可以尽可能多地访问到需要修改的子节点?
  • 一个给定的元素有什么属性或信息可以用来到达另外一个元素?

遍历DOM很耗资源而且速度很慢,这就是为什么要尽量使用浏览器中已经在使用的技术来做这件事情。

3.把遍历交给专家来做(CSS,更快地遍历DOM)

有 关DOM的脚本和使用方法或属性(getElementsByTagName, nextSibling, previousSibling, parentNode以及其它)来遍历DOM似乎迷惑了很多人,这点很有意思。而有趣的是,我们其实早已经通过另外一种技术—— CSS ——做了这些事情。

CSS 是这样一种技术,它使用CSS选择器,通过遍历DOM来访问目标元素并改变它们的视觉属性。一段复杂的使用DOM的JavaScript可以用一个CSS选择器取代:

var n = document.getElementById('nav');
if(n){
var as = n.getElementsByTagName('a');
if(as.length > 0){
for(var i=0;as[i];i++){
as[i].style.color = ‘#369′;
as[i].style.textDecoration = ‘none’;
}
}
}

/* 下面的代码与上面功能一样 */

#nav a{
color:#369;
text-decoration:none;
}

这是一个可以好好利用的很强大的技巧。你可以通过动态为DOM中高层的元素添加class 或者更改元素ID来实现这一点。如果你使用DOM为文档的body添加了一个CSS类,那么设计师就很可以容易地定义文档的静态版本和动态版本。

JavaScript:

var dynamicClass = 'js';
var b = document.body;
b.className = b.className ? b.className + ' js' : 'js';

CSS:

/* 静态版本 */

#nav {
....
}

/* 动态版本 */

body.js #nav {
....
}

4.理解浏览器和用户(在既有的使用模式上创建你所需要的东西)

不 唐突的JavaScript 中很重要的一部分就是理解浏览器是如何工作的(尤其是浏览器是如何崩溃的)以及用户期望的是什么。不考虑浏览器你也可以很容易地使用JavaScript 创建一个完全不同的界面。拖拽界面,折叠区域,滚动条和滑动块都可以使用JavaScript创建,但是这个问题并不是个简单的技术问题,你需要思考下面 的问题:

  • 这个新界面可以独立于输入设备么?如果不能,那么可以依赖哪些东西?
  • 我创建的这个新界面是否遵循了浏览器或者其它富界面的准则(你可以通过鼠标在多级菜单中直接切换吗?还是需要使用tab键?)
  • 我需要提供什么功能但是这个功能是依赖于JavaScript的?

最 后一个问题其实不是问题,因为如果需要你就可以使用DOM来凭空创建HTML。关于这点的一个例子就是“打印”链接,由于浏览器没有提供一个非 JavaScript的打印文档功能,所以你需要使用DOM来创建这类链接。同样地,一个实现了展开和收缩内容模块的、可以点击的标题栏也属于这种情况。 标题栏不能被键盘激活,但是链接可以。所以为了创建一个可以点击的标题栏你需要使用JavaScript将链接加入进去,然后所有使用键盘的用户就可以收 缩和展开内容模块了。

解决这类问题的极好的资源就是设计模式库。至于要知道浏览器中的哪些东西是独立于输入设备的,那就要靠经验的积累了。首先你要理解的就是事件处理机制。

5.理解事件(事件处理会引起改变)

事 件处理是走向不唐突的JavaScript的第二步。重点不是让所有的东西都变得可以拖拽、可以点击或者为它们添加内联处理,而是理解事件处理是一个可以 完全分离出来的东西。我们已经将HTML,CSS和JavaScript分离开来,但是在事件处理的分离方面却没有走得很远。

事件处理器会监听发生在文档中元素上的变化,如果有事件发生,处理器就会找到一个很奇妙的对象(一般会是一个名为e的参数),这个对象会告诉元素发生了什么以及可以用它做什么。

对 于大多数事件处理来说,真正有趣的是它不止发生在你想要访问的元素上,还会在DOM中较高层级的所有元素上发生(但是并不是所有的事件都是这 样,focus和blur事件是例外)。举例来说,利用这个特性你可以为一个导航列表只添加一个事件处理器,并且使用事件处理器的方法来获取真正触发事件 的元素。这种技术叫做事件委托,它有几点好处:

  • 你只需要检查一个元素是否存在,而不需要检查每个元素
  • 你可以动态地添加或者删除子节点而并不需要删除相应的事件处理器
  • 你可以在不同的元素上对相同的事件做出响应

需 要记住的另一件事是,在事件向父元素传播的时候你可以停止它而且你可以覆写掉HTML元素(比如链接)的缺省行为。不过,有时候这并不是个好主意,因为浏 览器赋予HTML元素那些行为是有原因的。举个例子,链接可能会指向页面内的某个目标,不去修改它们能确保用户可以将页面当前的脚本状态也加入书签。

6.为他人着想(命名空间,作用域和模式)

你的代码几乎从来不会是文档中的唯一的脚本代码。所以保证你的代码里没有其它脚本可以覆盖的全局函数或者全局变量就显得尤为重要。有一些可用的模式可以来避免这个问题,最基础的一点就是要使用 var 关键字来初始化所有的变量。假设我们编写了下面的脚本:

var nav = document.getElementById('nav');
function init(){
// do stuff
}
function show(){
// do stuff
}
function reset(){
// do stuff
}

上面的代码中包含了一个叫做nav的全局变量和名字分别为 init,show 和 reset 的三个函数。这些函数都可以访问到nav这个变量并且可以通过函数名互相访问:

var nav = document.getElementById('nav');
function init(){
show();
if(nav.className === 'show'){
reset();
}
// do stuff
}
function show(){
var c = nav.className;
// do stuff
}
function reset(){
// do stuff
}

你可以将代码封装到一个对象中来避免上面的那种全局式编码,这样就可以将函数变成对象中的方法,将全局变量变成对象中的属性。 你需要使用“名字+冒号”的方式来定义方法和属性,并且需要在每个属性或方法后面加上逗号作为分割符。

var myScript = {
nav:document.getElementById('nav'),
init:function(){
// do stuff
},
show:function(){
// do stuff
},
reset:function(){
// do stuff
}
}

所有的方法和属性都可以通过使用“类名+点操作符”的方式从外部和内部访问到。

var myScript = {
nav:document.getElementById('nav'),
init:function(){
myScript.show();
if(myScript.nav.className === 'show'){
myScript.reset();
}
// do stuff
},
show:function(){
var c = myScript.nav.className;
// do stuff
},
reset:function(){
// do stuff
}
}

这种模式的缺点就是,你每次从一个方法中访问其它方法或属性都必须在前面加上对象的名字,而且对象中的所有东西都是可以从外部访问的。如果你只是想要部分代码可以被文档中的其他脚本访问,可以考虑下面的模块(module)模式:

var myScript = function(){
//这些都是私有方法和属性
var nav = document.getElementById('nav');
function init(){
// do stuff
}
function show(){
// do stuff
}
function reset(){
// do stuff
}
//公有的方法和属性被使用对象语法包装在return 语句里面
return {
public:function(){

},
foo:'bar'
}
}();

你 可以使用和前面的代码同样的方式访问返回的公有的属性和方法,在本示例中可以这么访问:myScript.public() 和 myScript.foo 。但是这里还有一点让人觉得不舒服:当你想要从外部或者从内部的一个私有方法中访问公有方法的时候,还是要写一个冗长的名字(对象的名字可以非常长)。为 了避免这一点,你需要将它们定义为私有的并且在return语句中只返回一个别名:

var myScript = function(){
// 这些都是私有方法和属性
var nav = document.getElementById('nav');
function init(){
// do stuff
}
function show(){
// do stuff
// do stuff
}
function reset(){
// do stuff
}
var foo = 'bar';
function public(){

}

//只返回指向那些你想要访问的私有方法和属性的指针
return {
public:public,
foo:foo
}
}();

这就保证了代码风格一致性,并且你可以使用短一点的别名来访问其中的方法或属性。

如果你不想对外部暴露任何的方法或属性,你可以将所有的代码封装到一个匿名方法中,并在它的定义结束后立刻执行它:

(function(){
// these are all private methods and properties
var nav = document.getElementById('nav');
function init(){
// do stuff
show(); // 这里不需要类名前缀
}
function show(){
// do stuff
}
function reset(){
// do stuff
}
})();

对于那些只执行一次并且对其它函数没有依赖的代码模块来说,这种模式非常好。

通过遵循上面的那些规则,你的代码更好地为用户工作,也可以使你的代码在机器上更好地运行并与其他开发者的代码和睦相处。不过,还有一个群体需要考虑到。

7.为接手的开发者考虑(使维护更加容易)

使你的脚本真正地unobtrusive的最后一步是在编写完代码之后仔细检查一遍,并且要照顾到一旦脚本上线之后要接手你的代码的开发者。考虑下面的问题:

  • 所有的变量和函数名字是否合理并且易于理解?
  • 代码是否经过了合理的组织?从头到尾都很流畅吗?
  • 所有的依赖都显而易见吗?
  • 在那些可能引起混淆的地方都添加了注释吗?

最重要的一点是:要认识到文档中的HTML和CSS代码相对于JavaScript来说更有可能被改变(因为它们负责视觉效果)。所以不要在脚本代码中包含任何可以让终端用户看到的class和ID,而是要将它们分离出来放到一个保存配置信息的对象中。

myscript = function(){
var config = {
navigationID:'nav',
visibleClass:'show'
};
var nav = document.getElementById(config.navigationID);
function init(){
show();
if(nav.className === config.visibleClass){
reset();
};
// do stuff
};
function show(){
var c = nav.className;
// do stuff
};
function reset(){
// do stuff
};
}();

这样维护者就知道去哪里修改这些属性,而不需要改动其他代码。

更多信息

以上就是我发现的七条准则。如果你想要了解更多与上面所探讨的主题相关的东西,可以看看下面的链接:



posted @ 2009-03-19 16:13 二胡 阅读(141) | 评论 (0)编辑 收藏

转 http://www.infoq.com/cn/articles/thoughtworks-practice-partiii

RichClient/RIA原则与实践(上)

作者 陈金洲 发布于 2009年3月10日 上午4时8分

社区
.NET,
Agile,
Java
主题
RIA,
富客户端/桌面
标签
原则

Web领域的经验在过去十多年的不断的使用和锤炼中,整个 开发领域的技术、理念、缺陷已经趋于成熟。JavaEE Stack, .NET Stack, Ruby On Rails等框架代表了目前这个技术领域的所有经验积累。这样我们在开始一个新的项目的时候,只需要选择对应语言的最佳实践,基本上不会犯大的错误。例 如,如果使用Java开发一个新的Web应用,那么基本上Spring/Guice+Hibernate/iBatis/+Struts /SpringMVC这种架构是不会产生重大的架构问题的;如果使用RoR那么你已经在使用最佳实践了;系统的分层:领域层,数据库层,服务层,表现层等 等;为了保证系统的可扩展性,服务器端应当是无状态架构,等等。总而言之,web开发领域,它丰富的积累使得开发者逐渐将更多的精力投入到应用本身。

来看富客户端,或者富互联网应用。在我看来,今天的RichClient与RIA已经没有分别:只要代表着丰富界面元素和丰富用户体验,需要与服务器进行 交互的应用都可以称为RichClient或者RIA,虽然感觉上RichClient更“企业化”一些(服务器往往在企业内部),RIA更“个人化”一 些(服务器往往处于公网)。从最小的层面来说,我现在正在使用的离线模式的GoogleDoc就是一个RichClient应用──虽然它没有那么 Rich,采用和microsoft office一样土的界面; 我现在正在听音乐的Last.fm客户端显然是一个非常典型的RIA──它所有的个人喜好信息、音乐全都来自远在美国的服务器。本地的这个界面,只是提供 收集个人和音乐信息,以及控制音乐的播放和停止;目前拥有1150万玩家的魔兽世界,则是一个挣钱最多的,最“富”的客户端,10多G的客户端包含了电影 品质的广阔场景,华丽的魔法效果和极其复杂的人机交互。

如今的用户需求已经达到了一个新的高度,那些灰色的,方方正正的界面已经逐渐不能够满足客户的需求。从我们工作的客户看来,他们除了对“完成功能”有着基 本的期待外,对于将应用做得“酷”,也抱有极大的热情。我工作的上一个项目是一个CRM系统,它是基于.NET Framework 3.5的一个RichClient应用。它的主窗口是一个带着红色渐变背景的无边框窗口,还有请专业美工制作的图标,点击某一个菜单还有华丽的二级菜单滑 动效果。我们在这个项目中获得了很多,有些值得借鉴,有些仍然值得反思。我仍然记得我们在项目的不同阶段,做一个技术决定是如此的彷徨和忐忑:因为在当时 的RichClient企业开发领域,几乎没有任何丰富的经验可以借鉴,我们重新发明了一些轮子,然后又推翻它;我们偏离了UI框架给我们提供的各种便利 而自己实现种种基础特性,只是因为他们偏离了我们所倡导的测试性的原则。在写下本文的时候,我尝试搜索了一下,仍然没有比较深入的实践性文章来介绍企业环 境下RichClient开发。大多数的书,如Swing、JavaFX、.NET WPF开发等等,偏向于小规模特性介绍,而在大规模的企业应用中,这些小的技巧对于架构决策往往帮助很小。

我的工作经历应当是和大多数开始进行RichClient开发的开发者类似:有着丰富的Web开发的经验之后开始进行RichClient开发。加入 ThoughtWorks之后参加了多个不同的RichClient项目的开发工作,使用/尝试过的语言包括Java Swing, Flex/Adobe Air, .NET WinForm/.NET WPF. 对于不同平台之间的种种有些体会。在这里我将这些实践和原则总结如下。例子很可能过时,毕竟华丽的界面框架层出不穷,但原则应当通用的。使用和遵循这些原 则将会帮助你少犯错误──至少比我们过去犯的错误要少。如果你拥有一定的web开发经验,那么这篇文章你读起来会很亲切。

这些原则/实践往往不是孤立的,我尝试将他们之间用图的方式关联起来,帮助你在使用的过程中进行选择。例如,你遵循了“一切皆异步”的原则,那么很可能你 需要进行“线程管理”和“事件管理”;如果你需要引入“缓存与本地存储”,那么“数据交互模式”你也需要进行考虑。希望这张图能够帮助读者理解不同原则之间的联系。

下面列出的这些原则或者实践没有严格意义上的区分。按照上面的图,我推荐是,一旦你考虑到了某一个实践,那么与它直接关联的实践你最好也要实现。它会使得你的架构更全面,经得起用户功能的需求和交互的需求。

为了让这些实践更加通用,我采用伪代码书写。相信读者能够转化成相应的语言──Java, C#, ActionScript或者其他。这些实践并非与某一种语言相关。在某些特定的例子中,我会采用特定语言,但大多数都是伪代码描述的。

1 一切皆异步

所有耗时的操作都应当异步进行。这是第一条、也是最重要的原则,违背了这条原则将会导致你的应用完全不可用。

考虑这样的一个功能:点击一个"更新股票信息"按钮,系统会从股票市场(第三方应用)获得最新的股票信息,并将信息更新到主界面。丝毫不考虑用户体验的写法:

void updateStockDataButton_clicked() {
    stockData = stockDataService.getLatest(); // 从远程获取股票信息
    updateUI(stockData); // 这个方法会更新界面
}

那么,当用户点击updateStockDataButton的时候,会有什么反应?难说。如果是一个无限带宽、无限计算资源的世界,这段代码直观又易 懂,而且工作的非常好:它会从第三方股票系统读到股票数据,并且更新到界面上。可惜不是。这段代码在现实世界工作的时候,当用户点击这个按钮,整个界面会 冻结──知道那种感觉吗?就是点完这个按钮,界面不动了;如果你在使用Windows, 然后尝试拽住窗口到处移动,你会发现这个窗口经过的地方都是白的。你的客户不会理解你的程序实际上在很努力的从股票市场获得数据,他们只会很愤怒的说,这 个东西把我的机器弄死了!他们的思路被打断了。于是他们不再使用你的程序,你们的合作没了。你没钱了。你的狗也跑了。

出现界面冻结的原因是,耗时操作阻塞了UI线程。UI线程一般负责着渲染界面,响应用户交互,如果这个线程被阻塞,它将无法响应所有的用户交互请求,甚至 包括拖拽窗口这样简单的操作。所有的界面框架,无论是Java/.NET/ActionScript/JavaScript, 都只有一个UI线程,这个估计永远都不会变。

用户看到的应用通常与程序员大相径庭。用户对应用的期待级别分别是:能用、可用、好用、好看。而我观察到的大多数程序员停留在第一阶段:能用。“一切皆异步”这个原则说来简单,做起来也不会很难。把上面的代码稍作改动,如下:

void updateStockDataButton_clicked() {
    runInAnotherThread( function () {
        stockData = stockDataService.getLatest(); // 从远程获取股票信息
        updateUI(stockData); // 这个方法在UI线程更新界面
    }
}

注意加粗部分。runInAnotherThread是跟语言平台特定的。对于.net C#,可以是一个Dispatcher+delegate或者ThreadPool.QueueUserWorkItem;对于Java,可以干脆是一个Runable。对于AJAX, 可以是XMLHttpRequest或者把这个计算扔到一个IFrame中;对于ActionScript, 似乎没有什么好的方法,把获取数据的部分交给XML.load然后通过事件回调的方式来进行界面刷新吧。

耗时操作一般两种来源产生:网络带来的延迟以及大规模运算。两者对应的异步实现方式有所不同。前者往往可以通过特定语言、平台的获取数据的方式来进行异步,特别是缺乏多线程特性的动态语言。例如典型的AJAX方式:

xhr = new XmlHttpRequest()
xhr.send("POST", '/stockData/MSFT', function() {
    doSomethingWith(xhr.responseText);  // 只有当数据返回的时候,才会调用
})

大规模运算带来的耗时在Java/C#等支持多线程的语言环境中很容易实现,而对于JavaScript/ActionScript等很难,折衷的方式是 将复杂运算延迟到服务器端进行;或者将复杂运算拆解成若干个耗时较少的小运算,例如ActionScript的伪多线程实现方式。

“一切皆异步”这个原则说来容易,但要在企业应用中以一种一致的方式进行实现很难。上例中runInAnotherThread的方式貌似简单,也可能出 现在各种GUI框架的介绍中,但绝不是一个稍具规模的RichClient应当采用的方式。它很难作为一种编程范式被遵循,你绝不会希望看到在你的代码中 所有用到异步的地方都new Runnable(){...}。这样带来的问题不仅仅是异步被不被管理的到处乱扔,还带来了测试的复杂性。为了解决这些只有在至少有点规模的 RichClient中才出现的问题,你最好也实现了“4 线程管理”(见下篇),能够实现“3 事件管理”(见下篇)更好。终极方式是将这些抽象到应用的基础框架中,使得所有的开发人员以一种一致的方式进行编程。

2 视图管理

2.1 视图生命周期管理

视图这个概念在WEB开发中几乎被忽略。这里所说的视图是指页面、页面块等界面元素。在WEB开发中,视图的生命周期很短:在进入页面的时候创建,在离开页面的时候销毁。一不小心页面被弄糟了,或者不能按照预期的渲染了,点下刷新按钮,整个世界一片清净。

WEB下的视图导航也是如此自然。基于超链接的方式,每点击一次,就能够打开一个新的页面,旧的页面被浏览器销毁,新的页面诞生。(这里不考虑AJAX或者其他JavaScript特效)

如果把这种想法带入到RichClient开发,后果会很糟糕。每当点击按钮或者进行其他操作需要导航到新的窗口,你不加任何限制的创建新窗口或者新的视 图。然而CPU不是无限的。创建一个新的视图通常是很耗CPU和内存的。系统响应会变慢。用户会抱怨,拒绝付钱,于是因为饥饿,你的狗再次离开了你。

每次新创建视图产生的严重后果并不仅仅是非功能性的,还包括功能性的缺失。如果你用过Skype,当你在给张三通话的时候,再次点击张三并且进行通话,你 会发现刚刚的通话界面会弹出来,而不是开启新窗口。在我们的一个项目中,有一个功能:点击软件界面上的电话号码就能开启一个新窗口,并直接连到桌上的电话 拨号通话。可以想象,如果每次都会弹出新的窗口,软件的逻辑是根本错误的。

如何解决这个问题?最简单的方式是将所有已知的视图全都保存到本地的一个缓存中,我们命名为ViewFactory,当需要进行获取某个视图的时候,直接从ViewFactory拿到,如果没有创建,那么创建,并放到Cache中:

class ViewFactory {
     cache = {}
     View getView(Object key) {
        if cache.contains(key) {
            return cache[key]
        }
        cache[key] = createView(key)
        return cache[key]  
    }
}

需要注意的是,ViewFactorykey的选择。对于简单的应用,key可以干脆就是某个单独窗口的类名。例如整个系统中往往只有一个配置窗口,那 么key就是这个类名;对于需要复用的窗口,往往需要根据其业务主键来创建相应的视图。例如代码中只有一个UserDetailWindow, 需要用来展示不同用户的信息。当需要同时显示两个以上的用户信息的时候,用同一个窗口实例显然不对。这时候key的选择可以是类名+用户ID。

2.2 视图导航

上面的方案并没有解决导航的问题。导航需要解决的问题有两个,如何导航以及如何在导航时传递数据。这时候不得不羡慕WEB的解决方式。我要访问ID1的用户信息,只需要访问类似于users/1的页面就好;需要访问搜索结果第5页,只需要访问/search?q=someword&page=5就好。这里/search是视图,q=somewordpage=5是传递的数据。目前我还没有发现任何一本书来讲述如何进行视图导航。我们的方式是实现一个Navigator类用来导航,Navigator依赖于前面提到的ViewFactory

class Navigator {
    Navigator(ViewFactory viewFactory) {
        this.viewFactory = viewFactory;
    }
    void goTo(Object viewKey) {
        this.viewFactory.getView(viewKey).show()
    }
}

(这个类看起来跟ViewFactory没什么大的差别,但他们逻辑上是完全不同,并且下面的扩展中会增强)

这样是可以解决问题的。如果要在不同的视图之间传递数据,只需要对Navigator.goTo方法稍加扩展,多添加一个参数就能够传递参数了。例如,在用户列表窗口点击用户名,发送一条消息并打开聊天窗口,可以写为:

void messageButton_clicked() {
    Navigator.goTo("ChatWindow#userId", "聊天消息")
}

然而这种方式并不完美。当你发现大量的数据在窗口之间交互的时候,这种将主动权交给调用方控制的方式,会给状态同步带来不少麻烦;如果你使用了本地存储,它越过存储层直接与服务器交互的方式也会带来不少的不便之处。更好的方式是使用“3 事件管理”(见下篇)。当然,如果窗口之间导航不存在数据传递,基于Navigator的方式仍然简单并且可用。

相关阅读:

[ ThoughtWorks实践集锦(1)] 我和敏捷团队的五个约定

[ ThoughtWorks实践集锦(2)] 如何在敏捷开发中做好数据迁移


作者介绍:陈金洲,Buffalo AJAX中文问题 Framework作者,ThoughtWorks咨询师,现居北京。目前的工作主要集中在RichClient开发,同时一直对Web可用性进行观察,并对其实现保持兴趣。

给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家加入到InfoQ中文站用户讨论组中与我们的编辑和其他读者朋友交流。

posted @ 2009-03-18 09:52 二胡 阅读(131) | 评论 (0)编辑 收藏

转 http://www.infoq.com/cn/articles/thoughtworks-practice-partiii-ii

RichClient/RIA原则与实践(下)

作者 陈金洲 发布于 2009年3月11日 下午10时7分

社区
.NET,
Agile,
Java
主题
RIA,
富客户端/桌面
标签
原则

3 事件管理

事件管理应当是整个RichClient/RIA开发中的最难以把握的部分。这部分控制的好,你的程序用起来将如行云流水,用户的思维不会被打断。任何一 个做RichClient开发的程序员,可以对其他方面毫无所知,但这部分应当非常熟悉。事件是RichClient的核心,是“一切皆异步”的终极实现。前面所说的例子,实际上可以被抽象为事件,例如第一个,获取股票数据,从事件的观点看,应该是:

  • 开始获取股票数据
  • 正在获取股票数据
  • 获取数据完成
  • 获取数据失败

看起来相当复杂。然而这样去考虑的时候,你可以将执行计算与界面展现清晰的分开。界面只需要响应事件,运算可以在另外的地方 悄悄的进行,并当任务完成或者失败的是时候报告相应的事件。从经验看来,往往同样的数据会在不同的地方进行不同的展示,例如skype在通话的时候这个人 的头像会显示为占线,而具体的通话窗口中又是另外不同的展现;MSN的个人签名在好友列表窗口中显示为一个点击可以编辑控件,而同时在聊天窗口显示为一个 不能点击只能看的标签。这是RichClient的特性,你永远不知道同一份数据会以什么形式来展现,更要命的是,当数据在一个地方更新的时候,其他所有 能展现的地方都需要同时做相应的更新。如果我们仍然以第一部分的例子,简单采用runInAnoterThread是完全不能解决这个问题的。

我们曾经犯过一些很严重的错误,导致最终即便重构都积重难返。无视事件的抽象带来的影响是架构级别的,小修小补将无济于事。

事件的实现方式可以有很多种。对于没有事件支持的语言,接口或者干脆某一个约束的方法就可以。有事件支持的语言能够享受到好处,但仍然是语法级别的,根本 是一样的。观察者模式在这里很好用。仍然以股票为例,被观察的对象就是获取股票数据对象StockDataRetriver,观察的就是StockWindow

StockDataRetriver {
observers: []
retrieve() {
try {
theData = ...// 从远程获取数据
observers.each {|o| o.stockDataReady(theData)}  // 触发数据获取成功事件
} catch {
observers.each { |o| o.stockDataFailed() }  // 触发事件获取失败事件
}
}
}
StockDataRetriver.observers.add(StockWindow)  // 将StockWindow加入到观察者队列
StockWindow {
stockDataReady(theData) {
showDataInUIThread(); // 在UI线程显示数据
}
stockDataFailed() {
showErrorInUIThread(); // 在UI线程显示错误
}
}

你会发现代码变得简单。UI与计算之间的耦合被事件解开,并且区分UI线程与运算线程之间也变得容易。当尝试以事件的视角去观察整个应用程序的时候,你会更关注于用户与界面之间的交互。

让我们继续抽象。如果把“获取股票数据”这个按钮点击,让StockDataRetriver去获取数据当作事件来处理,应该怎么写呢?将按钮作为被观察 者,StockDataRetriver作为观察者显然不好,好不容易分开的耦合又黏在一起。引入一个中间的Events看起来不错:

Events {
listeners: {}
register(eventId, listener) {
listeners[eventId].add(listener)
}
broadcast(eventId) {
listeners[eventId].observers.each{|o| o.doSomething(); }
}
}

Events中维护了一个listeners的列表,它是一个简单的Hash结构,key是eventId,value是observer的列表;它提供了两个方法,用来注册事件监听以及通知事件产生。对于上面的案例,可以先注册StockDataRetriver为一个观察者,观察start_retrive_stock_data事件:

Events.register('start_retrive_stock_data', StockDataRetriever)

当点击“获取股票数据”按钮的时候,可以是这样:

Events.broadcast('start_retrive_stock_data')

你会发现StockDataRetriver能够老老实实的开始获取数据了。

需要注意的是,并非将所有事件定义为全局事件是一个好的实践。在更大规模的系统中,将事件进行有效整理和分级是有好处的。在强类型的语言(如 Java/C#)中,抽象出强类型的EventId,能够帮助理解系统和进行编程,避免到处进行强制类型转换。例如,StockEvent

StockDataLoadedEvent {
StockData theData;
StockDataLoadedEvent(StockData theData);
}
Event.broadcast(new StockDataLoadedEvent(loadedData))

这个事件的监听者能够不加类型转换的获得StockData数据。上面的例子是不支持事件的语言,C#语言支持自定义强类型的事件,用起来要自然一些:

delegate void StockDataLoaded(StockData theData)

事件管理原则我相信并不难理解。然而困难的是具体实现。对一个新的UI框架不熟悉的时候,我们经常在“代码的优美”与“界面提供的特性”之间徘徊。实现这 样的一个事件架构需要在项目一开始就稍具雏形,并且所有的事件都有良好的命名和管理。避免在命名、使用事件的时候的随意性,对于让代码可读、应用稳定有非 常大的意义。一个好的事件管理、通知机制是一个良好RichClient应用的根本基础。一般说来,你正在使用的编程平台如Swing/WinForm /WPF/Flex等能够提供良好的事件响应机制,即监听事件、onXXX等,但一般没有统一的事件的监听和管理机制。对于架构师,对于要使用的编程平台 对于这些的原生支持要了熟于心,在编写这样的事件架构的时候也能兼顾这些语言、平台提供给你的支持。

采用了事件的事件后,你不得不同时实践“线程管理”,因为事件一般来说意味着将耗时的操作放到别的地方完成,当完成的时候进行事件通知。简单的模式下,你可以在所有需要进行异步运算的地方,将运算放到另外一个线程,如ThreadPool.QueueUserWorkItem, 在运算完成的时候通知事件。但从资源的角度考虑,将这些线程资源有效的管理也是很重要的,在“线程管理”部分有详细的阐述。另外,如果能将你的应用转变为 数据驱动的,你需要关注“缓存以及本地存储”。

4 线程管理

在WEB开发几乎无需考虑线程,所有的页面渲染由浏览器完成,浏览器会异步的进行文字和图片的渲染。我们只需要写界面和JavaScript就好。如果你认同“一切皆异步”,你一定得考虑线程管理。

毫无管理的线程处理是这样的:凡是需要进行异步调用的地方,都新起一个线程来进行运算,例如前面提到的runInThread的实现。这种方式如果托管在 在“事件管理”之下,问题不大,只会给测试带来一些麻烦:你不得不wait一段时间来确定是否耗时操作完成。这种方式很山寨,也无法实现更高级功能。更好 的的方式是将这些线程资源进行统筹管理。

线程的管理的核心功能是用来统一化所有的耗时操作,最简单的TaskExecutor如下:

TaskExecutor {
void pendTask(task) { //task: 耗时操作任务
runInThread {
task.run(); // 运行任务
}
}
}
RetrieveStockDataTask extends Task {
void run() {
theData = ... // 直接获取远程数据,不用在另外线程中执行
Events.broadcast(new StockDataLoadedEvent(theData)) // 广播事件
}
}

需要进行这个操作的时候,只需要执行类似于下面的代码:

TaskExecutor.pendTask(new RetrieveStockDataTask())

好处很明显。通过引入TaskExecutor,所有线程管理放在同一个地方,耗时操作不需要自行维护线程的生命周期。你可以在TaskExecutor中灵活定义线程策略实现一些有趣的效果,如暂停执行,监控任务状况等,如果你愿意,为了更好的进行调试跟踪,你甚至可以将所有的任务以同步的方式执行。

耗时任务的定义与执行被分开,使得在任务内部能够按照正常的方式进行编码。测试也很容易写了。

不同的语言平台会提供不同的线程管理能力。.NET2.0提供了BackgroundWorker, 提供了一序列对多线程调用的封装,事件如开始调用,调用,跨线程返回值,报告运算进度等等。它内部也实现了对线程的调度处理。在你要开始实现类似的TaskExecutor时,参考一下它的API设计会有参考价值。Java 6提供的Executor也不错。

一个完善的TaskExecutor可以包含如下功能:

  • Task的定义:一个通用的任务定义。最简单的就是run(),复杂的可以加上生命周期的管理:start()end()success()fail()..取决于要控制到多么细致的粒度。
  • pendTask,将任务放入运算线程中
  • reportStatus,报告运算状态
  • 事件:任务完成
  • 事件:任务失败

写这样的一个线程管理的不难。最简单的实现就是每当pendTask的时候新开线程,当运算结束的时候报告状态。或者使用像BackgroundWorker或者Executor这样的高级API。对于像ActionScript/JavaScript这样的,只能用伪线程, 或者干脆将无法拆解的任务扔到服务器端完成。

5 缓存与本地存储

纯粹的B/S结构,浏览器不持有任何数据,包括基本不变的界面和实际展现的数据。RichClient的一大进步是将界面部分本地持有,与服务器只作数据通讯,从而降低数据流量。像《魔兽世界》10多G的超大型客户端,在普通的拨号网络都可以顺畅的游戏。

缓存与本地存储之间的差别在于,前者是在线模式下,将一段时间不变的数据缓存,最少的与服务器进行交互,更快的响应客户;后者是在离线模式下,应用仍然能 够完成某些功能。一般来说,凡是需要类似于“查看XXX历史”功能的,需要“点击列表查看详细信息”的,都会存在本地存储的必要,无论这个功能是否需要向 用户开放。

无论是缓存还是本地存储,最需要处理的问题如何处理本地数据与服务器数据之间的更新机制。当新数据来的时候,当旧数据更新的时候,当数据被删除的时候,等 等。一般来说,引入这个实践,最好也实现基于数据变化的“事件管理”。如果能够实现“客户机-服务器数据交互模式”那就更完美了。

我们犯过这样一个错误。系统启动的时候,将当前用户的联系人列表读取出来,放到内存中。当用户双击这个联系人的时候,弹出这个联系人的详细信息窗口。由于 没有本地存储,由于采用了Navigator方式的导航,于是很自然的采用了Navigator.goTo('ContactDetailWindow', theContactInfo)。由于列表页面一般是不变的,因此显示出来的永远是那份旧的数据。后来有了编辑联系人信息的功能,为了总是显示更新的数 据,我们将调用更改为Navigator.goTo('ContactDetailWindow', 'contactId'),然后在ContactDetailWindow中按照contactId把联系人信息重新读取一次。远在南非的用户抱怨慢。还 好我没养狗,没有狗离开我。后来我们慢慢的实现了本地存储,所有的数据读取都从这个地方获得。当数据需要更新的时候,直接更新这个本地存储。

本地存储会在根本上影响RichClient程序的架构。除非本地不保存任何信息,否则本地存储一定需要优先考虑。某些编程平台需要你在本地存储界面和数 据,如Google Gears的本地存储,置于Adobe Air的AJAX应用等,某些编程平台只需要存储数据,因为界面完全是本地绘制的,如Java/JavaFX/WinForm/WPF等。缓存界面与缓存 数据在实现上差别很大。

本地存储的存储机制最好是采用某一种基于文件的关系数据库,如SQLite、H2(HypersonicSQL)、Firebird等。一旦确定要采用本地存储,就从成熟的数据库中选择一个,而不要尝试着自己写基于文件的某种缓存机制。你会发现到最后你实现了一个山寨版的数据库。

在没有考虑本地存储之前,与远端的数据访问是直接连接的:

我们上面的例子说明,一旦考虑使用本地存储,就不能直接访问远程服务器,那么就需要一个中间的数据层:

数据层的主要职责是维护本地存储与远程服务器之间的数据同步,并提供与应用相关的数据缓存、更新机制。数据更新机制有两种,一种是Proxy(代理)模式,一种是自动同步模式。

代理模式比较容易理解。每当需要访问数据的时候,将请求发送到这个代理。这个代理会检查本地是否可用,如果可用,如缓存处于有效期,那么直接从本地读取数 据,否则它会真正去访问远端服务器,获取数据,更新缓存并返回数据。这种手工处理同步的方式简单并且容易控制。当应用处于离线模式的时候仍然可以工作的很 好。

自动同步模式下,客户端变成都针对本地数据层。有一个健壮的自动同步机制与服务器的保持长连接,保证数据一直都是更新的。这种方式在应用需要完全本地可运行的时候工作的非常好。如果设计得好,自动同步方式健壮的话,这种方式会给编程带来极大的便利。

说到同步,很多人会考虑数据库自带的自动同步机制。我完全不推荐数据库自带的机制。他们的设计初衷本身是为了数据库备份,以及可扩展性 (Scalability)的考虑。在应用层面,数据库的同步机制往往不知道具体应用需要进行哪些数据的同步,同步周期等等。更致命的是,这种机制或多或 少会要求客户端与服务器端具备类似的数据库表结构,迁就这样的设计会给客户端的缓存表设计带来很大的局限。另外,它对客户机-服务器连接也存在一定的局限 性,例如需要开放特定端口,特定服务等等。对于纯粹的Internet应用,这种方式更是完全不可行的,你根本不知道远程数据库的结构,例如 Flickr, Google Docs.

当本地存储+自动同步机制与“事件管理”都实现的时候,应用会是一种全新的架构:基于数据驱动的事件结构。对于所有本地数据的增删改都定义为事件,将关心 这些数据的视图都注册为响应的观察者,彻底将数据的变化于展现隔离。界面永远只是被动的响应数据的变化,在我看来,这是最极致的方式。

结尾

限于篇幅,这篇文章并没有很深入的讨论每一种原则/实践。同时还有一些在RichClient中需要考虑的东西我们并没有讨论:

  • 纯Internat应用离线模式的实现。像AdobeAir/Google Gears都有离线模式和本地存储的支持,他们的特点是缓存的不仅仅是数据,还包括界面。虽然常规的企业应用不太可能包含这些特性,但也具备借鉴意义。
  • 状态的控制。例如管理员能够看到编辑按钮而普通用户无法看见,例如不同操作系统下的快捷键不同。简单情况下,通过if-else或者对应编程平台下提供的绑定能够完成,然而涉及到更复杂的情况时,特别是网络游戏中大量互斥状态时,一个设计良好的分层状态机模型能够解决这些问题。如何定义、分析这些状态之间的互斥、并行关系,也是处理超复杂
  • 测试性。如何对RichClient进行测试?特别是像WPF、JavaFX、Adobe Air等用Runtime+编程实现的框架。它们控制了视图的创建过程,并且倾向于绑定来进行界面更新。采用传统的MVP/MVC方式会带来巨大的不必要的工作量(我们这么做过!),而且测试带来的价值并没有想象那么高。
  • 客户机-服务器数据交互模式。如何进行客户机服务器之间的数据交互?最简单的方式是类似于Http Request/Response。这种方式对于单用户程序工作得很好,但当用户之间需要进行交互的时候,会面临巨大挑战。例如,股票代理人关注亚洲银行板块,刚好有一篇新的关于这方面的评论出现,股票代理人需要在最多5分钟内知道这个消息。如果是Http Request/Response, 你不得不做每隔5分钟刷一次的蠢事,虽然大多数时候都不会给你数据。项目一旦开始,就应当仔细考虑是否存在这样的需求来选择如何进行交互。这部分与本地存储也有密切的关系。
  • 部署方式。RichClient与B/S 直接最大的差异就是,它需要本地安装。如何进行版本检测以及自动升级?如何进行分发?在大规模访问的时候如何进行服务器端分布式部署?这些问题有些被新技术解决了,例如Adobe Air以及Google Gears,但仍然存在考虑的空间。如果是一个安全要求较高的应用,还需要考虑两端之间的安全加密以及客户端正确性验证。新的UI框架层出不穷。开始一个新的RichClient项目的时候,作为架构师/Tech Lead首先应当关注的不是华丽的界面和效果,应当观察如何将上述原则和时间华丽的界面框架结合起来。就像我们开始一个web项目就会考虑domain 层、持久层、服务层、web层的技术选型一样,这些原则和实践也是项目一开始就考虑的问题。

感谢

感谢我的同事周小强、付莹在我写作过程中提供的无私的建议和帮助。小强推荐了介绍Google Gears架构的链接,让我能够写作“本地存储”部分有了更深的体会。

这篇文章是我近两年来在RichClient工作、网络游戏、WebGame众多思考的一个集合。我尝试过JavaFX/WPF/AdobAir 以及相关的文章,然而大多数的例子都是从华丽的界面入手,没有实践相关的内容。有意思的反而是《大型多人在线游戏开发》这本书,给了我在企业 RichClient开发很多启发。我们曾经犯了很多错误,也获得了许多经验,以后我们应当能做得更好。

参考

相关阅读:

[ ThoughtWorks实践集锦(1)] 我和敏捷团队的五个约定

[ ThoughtWorks实践集锦(2)] 如何在敏捷开发中做好数据迁移

[ ThoughtWorks实践集锦(3)] RichClient/RIA原则与实践(上)


作者介绍:陈金洲,Buffalo AJAX中文问题 Framework作者,ThoughtWorks咨询师,现居北京。目前的工作主要集中在RichClient开发,同时一直对Web可用性进行观察,并对其实现保持兴趣。

给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家加入到InfoQ中文站用户讨论组中与我们的编辑和其他读者朋友交流。

posted @ 2009-03-18 09:46 二胡 阅读(144) | 评论 (0)编辑 收藏

   pl/sql是一种高级数据库程序设计语言,用于在各种环境下对数据库进行访问。由于该语言集成于数据库服务器中,所以它可以对数据进行快速高效的处理。
   pl/sql代表面向过程化的语言与SQL语言的结合。在SQL语言中扩展了面向过程语言中使用的程序结构。
   *变量和类型
   *控制语句和循环
   *过程和函数
   *对象类型和方法

PL/SQL的基本特点:
   块结构:
   PL/SQL程序的基本结构是块。所有的PL程序都是由块组成,这些块可以互相签套。通常,程序中的每一块都实现一个逻辑操作,从而把不同的任务进行分割。
    DECLARE
    /*
      declare variables,types,cursors and local  
       subprograms
     */
     BEGIN
     /*
      executable section
     */
     EXCEPTION
     /*
      exception -handling
     */
      END;
      执行部分是必须的,声明部分和异常处理部分是可选的。PL/SQL块采用这种分段结构将程序不同功能各自独立出来。
      变量和类型
      信息在数据库与PL/SQL程序间通过变量进行传递的。变量是在PL/SQL块的生命部分定义的。变量还支持自定义的数据类型,如记录类型等,使用用户自定义的数据类型可以让你定制程序中使用的数据类型结构。
       DECLARE
         TYPE T_stu  IS RECORD(
              firstname VARCHAR2(10)
          );
          v_student T_stu;
      游标
      游标是用来处理使用SELECT语句从数据库中检琐到的多行记录的工具。数据库应用程序可以对一组记录逐个进行处理,每次处理一行。
       过程和函数
       是PL/SQL块的一种特殊类型,可以以编译的形式存放在数据库中,为以后的程序块调用。
        包
        包有2部分组成:说明部分和包体。一个包可以带有多个相关的过程。
        动态SQL
        在运行期间构造并执行SQL语句。
        对象类型(oracle8上的版本)
        由属性和方法组成并可以存储在数据库表中。
    
     
posted @ 2009-03-16 17:43 二胡 阅读(171) | 评论 (0)编辑 收藏

SQL Server 
    从数据库表中的第M条记录开始取N条记录,利用Top关键字:注意如果Select语句中既有top,又有order by,则是从排序好的结果集中选择:
          SELECT *
          FROM   ( SELECT Top N *  
                           FROM   (SELECT Top (M + N - 1) * FROM 表名称
          Order by 主键 desc) t1 ) t2
          Order by 主键 asc

          例如从表Sys_option(主键为sys_id)中从10条记录还是检索20条记录,语句如下:
          SELECT * 
          FROM ( SELECT TOP 20 *
                          FROM (SELECT TOP 29 * FROM Sys_option order by sys_id desc) t1) t2
          Order by sys_id asc

Oralce数据库
         从数据库表中第M条记录开始检索N条记录
          SELECT * 
          FROM (SELECT ROWNUM r,t1.* From 表名称 t1 where rownum < M + N) t2
          where t2.r >= M
          例如从表Sys_option(主键为sys_id)中从10条记录还是检索20条记录,语句如下:
          SELECT * 
          FROM (SELECT ROWNUM R,t1.* From Sys_option where rownum < 30 ) t2
          Where t2.R >= 10

My sql数据库
          My sql数据库最简单,是利用mysql的LIMIT函数,LIMIT [offset,] rows从数据库表中M条记录开始检索N条记录的语句为:
          SELECT * FROM 表名称 LIMIT M,N

           例如从表Sys_option(主键为sys_id)中从10条记录还是检索20条记录,语句如下:
           select * from sys_option limit 10,20
  
posted @ 2009-03-16 14:44 二胡 阅读(160) | 评论 (0)编辑 收藏

说明:复制表(只复制结构,源表名:a 新表名:b)   

   SQL: select * into b from a where 1<>1 

   说明:拷贝表(拷贝数据,源表名:a 目标表名:b)   

   SQL: insert into b(a, b, c) select d,e,f from b; 

   说明:显示文章、提交人和最后回复时间   

   SQL: select a.title,a.username,b.adddate from table a,(select max(adddate) adddate from table where table.title=a.title) b 

   说明:外连接查询(表名1:a 表名2:b)   

   SQL: select a.a, a.b, a.c, b.c, b.d, b.f from a LEFT OUT JOIN b ON a.a = b.c 

   说明:日程安排提前五分钟提醒   

   SQL: select * from 日程安排 where datediff('minute',f开始时间,getdate())>5   

   说明:两张关联表,删除主表中已经在副表中没有的信息 

   SQL:    

   delete from info where not exists ( select * from infobz where info.infid=infobz.infid 

   说明:-- 

   SQL:    

   SELECT A.NUM, A.NAME, B.UPD_DATE, B.PREV_UPD_DATE 

    FROM TABLE1, 

    (SELECT X.NUM, X.UPD_DATE, Y.UPD_DATE PREV_UPD_DATE 

    FROM (SELECT NUM, UPD_DATE, INBOUND_QTY, STOCK_ONHAND 

    FROM TABLE2 

    WHERE TO_CHAR(UPD_DATE,'YYYY/MM') = TO_CHAR(SYSDATE, 'YYYY/MM')) X, 

    (SELECT NUM, UPD_DATE, STOCK_ONHAND 

    FROM TABLE2 

    WHERE TO_CHAR(UPD_DATE,'YYYY/MM') = 

    TO_CHAR(TO_DATE(TO_CHAR(SYSDATE, 'YYYY/MM') &brvbar;&brvbar; '/01','YYYY/MM/DD') - 1, 'YYYY/MM') Y, 

    WHERE X.NUM = Y.NUM (+) 

    AND X.INBOUND_QTY + NVL(Y.STOCK_ONHAND,0) <> X.STOCK_ONHAND B 

   WHERE A.NUM = B.NUM 

   说明:-- 

   SQL:    

  select * from studentinfo where not exists(select * from student where studentinfo.id=student.id) and 系 名称='"&strdepartmentname&"' and 专业名称='"&strprofessionname& amp;"' order by 性别,生源地,高考总成绩 

   说明: 

   从数据库中去一年的各单位电话费统计(电话费定额贺电化肥清单两个表来源) 

   SQL:   

   SELECT a.userper, a.tel, a.standfee, TO_CHAR(a.telfeedate, 'yyyy') AS telyear, 

    SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '01', a.factration)) AS JAN, 

    SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '02', a.factration)) AS FRI, 

    SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '03', a.factration)) AS MAR, 

    SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '04', a.factration)) AS APR, 

    SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '05', a.factration)) AS MAY, 

    SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '06', a.factration)) AS JUE, 

    SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '07', a.factration)) AS JUL, 

    SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '08', a.factration)) AS AGU, 

    SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '09', a.factration)) AS SEP, 

    SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '10', a.factration)) AS OCT, 

    SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '11', a.factration)) AS NOV, 

    SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '12', a.factration)) AS DEC 

   FROM (SELECT a.userper, a.tel, a.standfee, b.telfeedate, b.factration 

    FROM TELFEESTAND a, TELFEE b 

    WHERE a.tel = b.telfax) a 

   GROUP BY a.userper, a.tel, a.standfee, TO_CHAR(a.telfeedate, 'yyyy') 

   说明:四表联查问题:   

   SQL: select * from a left inner join b on a.a=b.b right inner join c on a.a=c.c inner join d on a.a=d.d where ..... 

   说明:得到表中最小的未使用的ID号 

   SQL:  

   SELECT (CASE WHEN EXISTS(SELECT * FROM Handle b WHERE b.HandleID = 1) THEN MIN(HandleID) + 1 ELSE 1 END) as HandleID 

    FROM Handle 

    WHERE NOT HandleID IN (SELECT a.HandleID - 1 FROM Handle a)
posted @ 2009-03-16 14:38 二胡 阅读(186) | 评论 (0)编辑 收藏

    在JS开发中,JS泄露问题比较麻烦!
    主要有4中泄露模式:
    1,循环引用
    2,闭包的使用
    3,Cross-Page Leaks(这个不知道改怎么翻译,标签对象签套?)
    4,Pseudo-Leaks(假泄露?)
    内存泄露的根本原因是:循环引用
    解决办法:破坏循环引用
   
    请看下文:
    来源:http://msdn.microsoft.com/en-us/library/bb250448.aspx

Internet Explorer Development Technical Articles
Understanding and Solving Internet Explorer Leak Patterns

Justin Rogers
Microsoft Corporation

June 2005

The Evolution of the Web Developer

In the past, memory leaks haven't posed huge problems for Web developers. Pages were kept relatively simple and navigation between different locations within a site was a great way to clean up any loose memory. If there was a leak, it was most likely small enough to go unnoticed.

New Web applications live up to higher standards. A page might run for hours without being navigated and retrieve updated information dynamically through Web services. Language features are pushed to the breaking point by combining complex event schemes, object-oriented JScript, and closures to produce entire applications. With these and other changes, certain memory leak patterns are becoming more prominent, especially those previously hidden by navigation.

The good news is that memory leak patterns can be easily spotted if you know what to look for. Most of the troublesome patterns you might face have known workarounds requiring only a small amount of extra work on your behalf. While some pages might still fall prey to small memory leaks, the most noticeable ones can be easily removed.

Leak Patterns

The following sections will discuss patterns of memory leaks and point out some common examples of each pattern. One great example of a pattern is the closure feature of JScript, while another example is the use of closures in hooking events. If you're familiar with the event hooking example, you might be able to find and fix many of your memory leaks, but other closure-related issues might go unnoticed.

Now, let's look at the following patterns:

  1. Circular References—When mutual references are counted between Internet Explorer's COM infrastructure and any scripting engine, objects can leak memory. This is the broadest pattern.

  2. Closures—Closures are a specific form of circular reference that pose the largest pattern to existing Web application architectures. Closures are easy to spot because they rely on a specific language keyword and can be searched for generically.

  3. Cross-Page Leaks—Cross-page leaks are often very small leaks of internal book-keeping objects as you move from site to site. We'll examine the DOM Insertion Order issue, along with a workaround that shows how small changes to your code can prevent the creation of these book-keeping objects.

  4. Pseudo-Leaks—These aren't really leaks, but can be extremely annoying if you don't understand where your memory is going. We'll examine the script element rewriting and how it appears to leak quite a bit of memory, when it is really performing as required.

Circular References

Circular references are the root of nearly every leak. Normally, script engines handle circular references through their garbage collectors, but certain unknowns can prevent their heuristics from working properly. The unknown in the case of IE would be the status of any DOM elements that a portion of script has access to. The basic principle would be as follows:

Figure 1 Basic Circular Reference Pattern

Figure 1. Basic Circular Reference Pattern

The cause of the leak in this pattern is based on COM reference counting. The script engine objects will hold a reference to the DOM element and will be waiting for any outstanding references to be removed before cleaning up and releasing the DOM element pointer. In our case we have two references on the script engine object: the script engine scope, and the DOM element expando property. While terminating the script engine will release the first reference, the DOM element reference will never be released because it is waiting on the script engine object to release it! You might think it would be easy to detect this scenario and fix the problem, but in practice the basic case presented is only the tip of the iceberg. You could have circular references at the end of a 30 object chain and those would be much harder to detect.

If you are wondering what this pattern looks like in HTML, you can cause a leak by using a global script engine variable and a DOM element as shown.

<html>
<head>
<script language="JScript">

var myGlobalObject;

function SetupLeak()
{
// First set up the script scope to element reference
myGlobalObject =
document.getElementById("LeakedDiv");

// Next set up the element to script scope reference
document.getElementById("LeakedDiv").expandoProperty =
myGlobalObject;
}


function BreakLeak()
{
document.getElementById("LeakedDiv").expandoProperty =
null;
}
</script>
</head>

<body onload="SetupLeak()" onunload="BreakLeak()">
<div id="LeakedDiv"></div>
</body>
</html>

To break the leak pattern you can make use of explicit null assignments. By assigning null before the document unloads you are telling the script engine there is no longer an association between the element and the object inside the engine. It can now properly clean up references and will release the DOM element. In this case, you as the Web developer know more about the relationships between your objects than the script engine does.

While that is the basic pattern, it can be difficult to spot more complex scenarios. A common usage of object-oriented JScript is to extend DOM elements by encapsulating them inside of a JScript object. During the construction process, you generally pass in the DOM element you want to attach to and then store a reference to the DOM element on the newly constructed object while at the same time storing an instance of the newly constructed object on the DOM element. That way your application model always has access to everything it needs. The problem is this is a very explicit circular reference, but because it uses different language aspects it might go unnoticed. Breaking up this kind of pattern can become more complex, and you can use the same simple methods discussed earlier.

<html>
<head>
<script language="JScript">

function Encapsulator(element)
{
// Set up our element
this.elementReference = element;

// Make our circular reference
element.expandoProperty = this;
}

function SetupLeak()
{
// The leak happens all at once
new Encapsulator(document.getElementById("LeakedDiv"));
}

function BreakLeak()
{
document.getElementById("LeakedDiv").expandoProperty =
null;
}
</script>
</head>

<body onload="SetupLeak()" onunload="BreakLeak()">
<div id="LeakedDiv"></div>
</body>
</html>

More complex solutions to this problem involve registration schemes to note which elements/properties need to be unhooked, having the peer element hook events so that it can clean up before the document unloads, but often you can run into additional leak patterns without actually fixing the problem.

Closures

Closures are very often responsible for leaks because they create circular references without the programmer being fully aware. It isn't immediately obvious that parent function parameters and local variables will be frozen in time, referenced, and held until the closure itself is released. In fact this has become such a common programming tactic, and users have run into issues so often, there are quite a few resources already available. Because they detail some of the history behind closures as well as some of the specific instances of closure leaks we'll check those out after applying the closure model to our circular reference diagram and figuring out where these extra references are coming from.

Figure 2 Circular References with Closures

Figure 2. Circular References with Closures

With normal circular references there were two solid objects holding references to each other, but closures are different. Rather than make the references directly, they are made instead by importing information from their parent function's scope. Normally, a function's local variables and the parameters used when calling a function only exist for the lifetime of the function itself. With closures, these variables and parameters continue to have an outstanding reference as long as the closure is alive, and since closures can live beyond the lifetime of their parent function so can any of the locals and parameters in that function. In the example, Parameter 1 would normally be released as soon as the function call was over. Because we've added a closure, a second reference is made, and that second reference won't be released until the closure is also released. If you happened to attach the closure to an event, then you would have to detach it from that event. If you happened to attach the closure to an expando then you would need to null that expando.

Closures are also created per call, so calling this function twice will create two individual closures, each holding references to the parameters passed in each time. Because of this transparent nature it is really easy to leak closures. The following example provides the most basic of leaks using closures:

<html>
<head>
<script language="JScript">

function AttachEvents(element)
{
// This structure causes element to ref ClickEventHandler
element.attachEvent("onclick", ClickEventHandler);

function ClickEventHandler()
{
// This closure refs element
}
}

function SetupLeak()
{
// The leak happens all at once
AttachEvents(document.getElementById("LeakedDiv"));
}

function BreakLeak()
{
}
</script>
</head\>

<body onload="SetupLeak()" onunload="BreakLeak()">
<div id="LeakedDiv"></div>
</body>
</html>

If you are wondering how to break this leak, it won't be as easy as a normal circular reference. The "closure" can be viewed as a temporary object that exists in the function scope. Once the function exits, you lose reference to the closure itself, so what would you end up calling detachEvent with? One of the most interesting approaches to this problem was demonstrated on MSN spaces thanks to Scott Isaacs. The approach uses a second closure to additionally hook the window's onUnload event, and because this closure has the same "scoped" objects it is able to detach the event, detach itself, and finish the clean up process. To make everything easily fit with our model we can also store the closure on an expando, detach it, and then null the expando, as in the following example.

<html>
<head>
<script language="JScript">

function AttachEvents(element)
{
// In order to remove this we need to put
// it somewhere. Creates another ref
element.expandoClick = ClickEventHandler;

// This structure causes element to ref ClickEventHandler
element.attachEvent("onclick", element.expandoClick);

function ClickEventHandler()
{
// This closure refs element
}
}

function SetupLeak()
{
// The leak happens all at once
AttachEvents(document.getElementById("LeakedDiv"));
}

function BreakLeak()
{
document.getElementById("LeakedDiv").detachEvent("onclick",
document.getElementById("LeakedDiv").expandoClick);
document.getElementById("LeakedDiv").expandoClick = null;
}
</script>
</head>

<body onload="SetupLeak()" onunload="BreakLeak()">
<div id="LeakedDiv"></div>
</body>
</html>

In a Knowledge Base article, we actually recommend that you try not to use closures unless they are necessary. In the example, I've given we don't need to use a closure as the event handler, instead we can move the closure to a global scope. When the closure becomes a function, it no longer inherits the parameters or local variables from its parent function so we don't have to worry about closure-based circular references at all. Most code can be fixed by creating an architecture that doesn't rely on closures where they aren't necessary.

Finally, Eric Lippert, one of the developers of the scripting engines, has a great post on closures in general. His final recommendations are also along the lines of only using closures when truly necessary. While his article doesn't mention any of the workarounds for the closure pattern, hopefully we've covered enough examples here to get you started.

Cross-Page Leaks

Leaks that are based on order of insertion are almost always caused by the creation of intermediate objects that don't get cleaned up properly. That is exactly the case when creating dynamic elements and then attaching them to the DOM. The basic pattern is attaching two dynamically created objects together temporarily which creates a scope from the child to the parent element. Later, when you attach this two-element tree to the primary tree, they both inherit the scope of the document and a temporary object is leaked. The following diagram shows two methods for attaching dynamically created elements to the tree. In the first model, attach each child element to its parent, and finally attach the entire subtree to the primary tree. This method can cause leaks through temporary objects if other conditions are met. In the second model, we attach elements into the primary tree working our way from top-level dynamically created element down through all of the children. Because each attachment inherits the scope of the primary document we never generate temporary scopes. This method is much better at avoiding potential memory leaks.

Figure 3 DOM Insertion Order Leak Model

Figure 3. DOM Insertion Order Leak Model

Next, we are going to cover an example of a leak that is transparent to most leak-detection algorithms. Because we don't leak any publicly visible elements and the objects we leak are very small you might never notice this problem. For our example to work, the dynamically created elements will have to contain a script pointer in the form of an inline function. This will allow us to leak an internal script object that is created temporarily as we attach elements together. Because the leak is small, we'll have to run thousands of samples. In fact, the objects leaked are only a few bytes. By running the sample and navigating to an empty page, you can see the difference in memory consumption between the two versions. When we use the first DOM model of attaching child to parent, then parent to the primary tree, our memory usage goes up a bit. This is a cross-navigation leak and the memory isn't reclaimed until you restart the IE process. If you run the sample a few more times, using the second DOM model of attaching the parent to the primary tree and then the child to the parent, your memory won't continue to climb and you'll find that you've fixed the cross-page navigation leak.

<html>
<head>
<script language="JScript">

function LeakMemory()
{
var hostElement = document.getElementById("hostElement");

// Do it a lot, look at Task Manager for memory response

for(i = 0; i < 5000; i++)
{
var parentDiv =
document.createElement("<div onClick='foo()'>");
var childDiv =
document.createElement("<div onClick='foo()'>");

// This will leak a temporary object
parentDiv.appendChild(childDiv);
hostElement.appendChild(parentDiv);
hostElement.removeChild(parentDiv);
parentDiv.removeChild(childDiv);
parentDiv = null;
childDiv = null;
}
hostElement = null;
}


function CleanMemory()
{
var hostElement = document.getElementById("hostElement");

// Do it a lot, look at Task Manager for memory response

for(i = 0; i < 5000; i++)
{
var parentDiv =
document.createElement("<div onClick='foo()'>");
var childDiv =
document.createElement("<div onClick='foo()'>");

// Changing the order is important, this won't leak
hostElement.appendChild(parentDiv);
parentDiv.appendChild(childDiv);
hostElement.removeChild(parentDiv);
parentDiv.removeChild(childDiv);
parentDiv = null;
childDiv = null;
}
hostElement = null;
}
</script>
</head>

<body>
<button onclick="LeakMemory()">Memory Leaking Insert</button>
<button onclick="CleanMemory()">Clean Insert</button>
<div id="hostElement"></div>
</body>
</html>

This leak deserves clarification, because our workaround goes against some best practices in IE. The key points to understand about the leak are that DOM elements are being created with scripts already attached. This is actually crucial to the leak, because if we create DOM elements that don't contain any script and attach them together in the same manner we don't have a leak problem. This gives rise to a second workaround that might be even better for larger subtrees (in the example we only have two elements, so building the tree off the primary DOM isn't a performance hit). The second workaround would be to create your elements with no scripts attached initially so that you can safely build your subtree. After you've attached your subtree to the primary DOM, go back and wire up any script events at that point. Remember to follow the principles for circular references and closures so you don't cause a different leak in your code as you hook up your events.

I really wanted to point out this issue because it shows that not all memory leaks are easy to find. It could take thousands of iterations of a smaller pattern to become visible, and it might be something slight, like the order of insertion of DOM elements that causes the problem to arise. If you tend to program using only best practices, then you think you are safe, but this leak shows that even best practices can exhibit leaks. Our solution here was to improve upon the best practice or even introduce a new best practice in order to remove the leaking condition.

Pseudo-Leaks

Often times the actual behavior and expected behavior of some APIs can lead you to misdiagnose memory leaks. Pseudo-leaks almost always appear on the same page during dynamic scripting operations and should rarely be visible after navigation away from the page to a blank page. That is how you can eliminate the issue as a cross-page leak and then start to work on whether the memory consumption is expected. We'll use script text rewriting as our example of a pseudo-leak.

Like the DOM Insertion Order issue, this issue also relies on the creation of temporary objects in order to "leak" memory. By rewriting the script text inside of a script element over and over again, slowly you'll begin to leak various script engine objects that were attached to the previous contents. In particular, objects related to debugging script are left behind as are fully formed code elements.

<html>
<head>
<script language="JScript">

function LeakMemory()
{
// Do it a lot, look at Task Manager for memory response

for(i = 0; i < 5000; i++)
{
hostElement.text = "function foo() { }";
}
}
</script>
</head>

<body>
<button onclick="LeakMemory()">Memory Leaking Insert</button>
<script id="hostElement">function foo() { }</script>
</body>
</html>

If you run the above code and use the Task Manager trick again, while navigating between the "leaking" page and a blank page, you won't notice a script leak. This script leak is entirely within a page and when you navigate away then you get your memory back. The reason this one is bad is due to expected behavior. You expect that after rewriting some script that the original script won't stay around. But it really has to, because it might have been used already for event attachments and there might be outstanding reference counts. As you can see, this is a pseudo-leak. On the surface the amount of memory consumption looks really bad, but there is a completely valid reason.

Conclusion

Every Web developer builds a personal list of code examples that they know leak and learns to work around those leaks when they see them in code. This is extremely handy and is the reason the Web is relatively leak-free today. Thinking about the leaks in terms of patterns instead of individual code examples, you can start to develop even better strategies for dealing with them. The idea is to take them into account during the design phase and make sure you have plans for any potential leaks. Use defensive coding practices and assume that you'll need to clean up all your own memory. While this is an overstatement of the problem, you very rarely need to clean up your own memory; it becomes obvious which variables and expando properties have the potential for leaking.

In the interest of patterns and design I highly recommend Scott's short blog entry because it demonstrates a general purpose example of removing all closure-based leaks. It does require a bit more code, but the practice is sound and the improved pattern is easy to spot in code and to debug. Similar registration schemes can be used for expando-based circular references as long as care is taken that the registration method itself isn't riddled with leaks (especially where closures are used)!

About the author

Justin Rogers recently joined the Internet Explorer team as an Object Model developer working on extensibility and previously worked on such notable projects as the .NET QuickStart Tutorials, .NET Terrarium, and SQL Reporting Services Management Studio in SQL Server 2005.


   

posted @ 2009-03-16 11:32 二胡 阅读(402) | 评论 (0)编辑 收藏

    比如你找张三,全国可能有N多个张三,大家不知道你最终找哪个张三;如果你找X省X市X县X村X户的张三,则大家即可明白你找的是那个张三。同样,现在AJAX应用很广泛,又有很多不错的JS框架,则可能不同的框架中定义相同的方法。如果在应用中要集成多个JS框架,还有自己开发的JS方法,它们的方法有同名的可能。利用命名空间即可解决此类问题。
    什么是命名空间?
    命名空间(namespace)表示标识符(identifier)的上下文(context)。一个标识符可在多个命名空间中定义,它在不同命名空间中的含义是互不相干的。这样,在一个新的命名空间中可定义任何标识符,它们不会与任何已有的标识符发生冲突,因为已有的定义都处于其它命名空间中。
   
     比如你分别调用:A.B.test();和D.E.test();则系统即可区分你要调用的是具体是那个test()方法。这里的A.B和D.E既是命名空间。
     下面给出一段代码:(出处忘了,感谢原作者!)
      此段代码就是定义"命名空间"
      我的理解:JS没有真正意义上的命名空间,我们只是利用JS的一些特性来模拟命名空间的效果
     <script type="text/javascript">
     var Namespace = {};

     // 全局对象仅仅存在register函数,参数为名称空间全路径,如"Grandsoft.GEA"
     Namespace.register = function(fullNS)
    {
      // 将命名空间切成N部分, 比如Grandsoft、GEA等
      var nsArray = fullNS.split('.');
      var sEval = "";
      var sNS = "";
     for (var i = 0; i < nsArray.length; i++)
     {
        if (i != 0) sNS += ".";
        sNS += nsArray[i];
        // 依次创建构造命名空间对象(假如不存在的话)的语句
        // 比如先创建Grandsoft,然后创建Grandsoft.GEA,依次下去
        sEval += "if (typeof(" + sNS + ") == 'undefined') " + sNS + " = new Object();"
     }
       if (sEval != "") eval(sEval);
    }
    Namespace.register("com.companyname");
   </script>
   在这里,我们就定义了一个命名空间对象com.companyname,其实就是定义了2个对象 com和companyname,  而companyname又是com的属性,这里只不过是用一个方法来定义命名空间。这样,利用此命名空间空间可以来定义其它的方法了,例如:
   com.companyname.test=function(){alert("test")};
   调用方法:com.companyname.test();


   参考:http://zh.wikipedia.org/wiki/%E5%91%BD%E5%90%8D%E7%A9%BA%E9%97%B4
posted @ 2009-03-16 10:38 二胡 阅读(627) | 评论 (0)编辑 收藏

转 http://realazy.org/blog/2007/08/16/lazy-function-definition-pattern/

惰性函数定义模式

这 篇文章阐述的是一种函数式编程(functional-programming)设计模式,我称之为惰性函数定义(Lazy Function Definition)。我不止一次发现这种模式在JavaScript中大有用处,尤其是编写跨浏览器的、高效运行的库之时。

热身问题

编写一个函数foo,它返回的是Date对象,这个对象保存的是foo首次调用的时间。

方法一:上古时代的技术

这个最简陋的解决方案使用了全局变量t来保存Date对象。foo首次调用时会把时间保存到t中。接下来的再次调用,foo只会返回保存在t中的值。

var t;
function foo() {
if (t) {
return t;
}
t = new Date();
return t;
}

但是这样的代码有两个问题。第一,变量t是一个多余的全局变量,并且在 foo调用的间隔期间有可能被更改。第二,在调用时这些代码的效率并没有得到优化因为每次调用 foo都必须去求值条件。虽然在这个例子中,求值条件并不显得低效,但在现实世界的实践例子中常常会有极为昂贵的条件求值,比如在if-else-else-…的结构中。

方法二:模块模式

我们可以通过被认为归功于CornfordCrockford模块模式来弥补第一种方法的缺陷。使用闭包可以隐藏全局变量t,只有在 foo内的代码才可以访问它。

var foo = (function() {
var t;
return function() {
if (t) {
return t;
}
t = new Date();
return t;
}
})();

但这仍然没有优化调用时的效率,因为每次调用foo依然需要求值条件。

虽然模块模式是一个强大的工具,但我坚信在这种情形下它用错了地方。

方法三:函数作为对象

由于JavaScript的函数也是对象,所以它可以带有属性,我们可以据此实现一种跟模块模式质量差不多的解决方案。

function foo() {
if (foo.t) {
return foo.t;
}
foo.t = new Date();
return foo.t;
}

在一些情形中,带有属性的函数对象可以产生比较清晰的解决方案。我认为,这个方法在理念上要比模式模块方法更为简单。

这个解决方案避免了第一种方法中的全局变量t,但仍然解决不了foo每次调用所带来的条件求值。

方法四:惰性函数定义

现在,这是你阅读这篇文章的理由:

var foo = function() {
var t = new Date();
foo = function() {
return t;
};
return foo();
};

foo首次调用,我们实例化一个新的Date对象并重置 foo到一个新的函数上,它在其闭包内包含Date对象。在首次调用结束之前,foo的新函数值也已调用并提供返回值。

接下来的foo调用都只会简单地返回t保留在其闭包内的值。这是非常快的查找,尤其是,如果之前那些例子的条件非常多和复杂的话,就会显得很高效。

弄清这种模式的另一种途径是,外围(outer)函数对foo的首次调用是一个保证(promise)。它保证了首次调用会重定义foo为一个非常有用的函数。笼统地说,术语“保证” 来自于Scheme的惰性求值机制(lazy evaluation mechanism)。每一位JavaScript程序员真的都应该学习Scheme,因为它有很多函数式编程相关的东西,而这些东西会出现在JavaScript中。

确定页面滚动距离

编写跨浏览器的JavaScript, 经常会把不同的浏览器特定的算法包裹在一个独立的JavaScript函数中。这就可以通过隐藏浏览器差异来标准化浏览器API,并让构建和维护复杂的页 面特性的JavaScript更容易。当包裹函数被调用,就会执行恰当的浏览器特定的算法。

在拖放库中,经常需要使用由鼠标事件提供的光标位置信息。鼠标事件给予的光标坐标相对于浏览器窗口而不是页面。加上页面滚动距离鼠标的窗口坐标的距离即可得到鼠标相对于页面的坐标。所以我们需要一个反馈页面滚动的函数。演示起见,这个例子定义了一个函数getScrollY。因为拖放库在拖拽期间会持续运行,我们的getScrollY必须尽可能高效。

不过却有四种不同的浏览器特定的页面滚动反馈算法。Richard Cornford在他的feature detection article文章中提到这些算法。最大的陷阱在于这四种页面滚动反馈算法其中之一使用了 document.body. JavaScript库通常会在HTML文档的<head>加载,与此同时docment.body并不存在。所以在库载入的时候,我们并不能使用特性检查(feature detection)来确定使用哪种算法。

考虑到这些问题,大部分JavaScript库会选择以下两种方法中的一种。第一个选择是使用浏览器嗅探navigator.userAgent,为该浏览器创建高效、简洁的getScrollY. 第二个更好些的选择是getScrollY在每一次调用时都使用特性检查来决定合适的算法。但是第二个选择并不高效。

好消息是拖放库中的getScrollY只会在用户与页面的元素交互时才会用到。如果元素业已出现在页面中,那么document.body也会同时存在。getScrollY的首次调用,我们可以使用惰性函数定义模式结合特性检查来创建高效的getScrollY.

var getScrollY = function() {

if (typeof window.pageYOffset == 'number') {
getScrollY = function() {
return window.pageYOffset;
};

} else if ((typeof document.compatMode == 'string') &&
(document.compatMode.indexOf('CSS') >= 0) &&
(document.documentElement) &&
(typeof document.documentElement.scrollTop == 'number')) {
getScrollY = function() {
return document.documentElement.scrollTop;
};

} else if ((document.body) &&
(typeof document.body.scrollTop == 'number')) {
getScrollY = function() {
return document.body.scrollTop;
}

} else {
getScrollY = function() {
return NaN;
};

}

return getScrollY();
}

总结

惰性函数定义模式让我可以编写一些紧凑、健壮、高效的代码。用到这个模式的每一次,我都会抽空赞叹JavaScript的函数式编程能力。

JavaScript同时支持函数式和面向对象便程。市面上有很多重点着墨于面向对象设计模式的书都可以应用到JavaScript编程中。不过却没有多少书涉及函数式设计模式的例子。对于JavaScript社区来说,还需要很长时间来积累良好的函数式模式。

原文:Lazy Function Definition Pattern. 转载没有我的信息没有关系,但你一定得写上原文信息,谢谢。

更新

这个模式虽然有趣,但由于大量使用闭包,可能会由于内存管理的不善而导致性能问题。来自FCKeditor的FredCK改进了getScrollY,既使用了这种模式,也避免了闭包:

var getScrollY = function() {

if (typeof window.pageYOffset == 'number')
return (getScrollY = getScrollY.case1)();

var compatMode = document.compatMode;
var documentElement = document.documentElement;

if ((typeof compatMode == 'string') &&
(compatMode.indexOf('CSS') >= 0) &&
(documentElement) &&
(typeof documentElement.scrollTop == 'number'))
return (getScrollY = getScrollY.case2)();

var body = document.body ;
if ((body) &&
(typeof body.scrollTop == 'number'))
return (getScrollY = getScrollY.case3)();

return (getScrollY = getScrollY.case4)();
};

getScrollY.case1 = function() {
return window.pageYOffset;
};

getScrollY.case2 = function() {
return documentElement.scrollTop;
};

getScrollY.case3 = function() {
return body.scrollTop;
};

getScrollY.case4 = function() {
return NaN;
};

请看具体的评论



posted @ 2009-03-13 17:04 二胡 阅读(155) | 评论 (0)编辑 收藏

转 http://www.blogjava.net/liuwentao253/archive/2006/11/18/81930.html

目录  :



UML视图和图



一  :静态视图

静态视图 的主要组成部分是 类和关系 ,它显示为类图 因为它不描述时间相关的行为,因而是静态的, 关系包括 :关联,继承 和各种依赖,依赖包括实现和使用。类间的关系绘成连接类的路径,不同种类的关系由线上的结构和路径 或端点上的修饰来区分。

例子 :

票房应用的类图




图例 :


二 :用例图

用例作为交互视图中的协作来实现的。 主要从活动者的角度考虑。



三 : 交互视图

1 :顺序图



2 :协作图




四 :状态机视图

状态机显示为状态图



五 :活动视图

横条表示控制的分叉河连接



六 :物理视图

有两种物理视图 :实现视图 和 配置视图

1 :实现视图

实现视图显示为构件图

接口显示为具有哦名称的圆,即相关的服务集 ,连接构件和接口的实线表示构件提供接口所列举的服务。从构件至接口的虚线表明构件需要接口所提供的服务




2 :配置视图

配置视图表达了运行时断 构件实力在结点实例中的分布,结点是运行资源,如计算机,设备或内存,该视图允许分布式的结果和资源分配被评估,下面图中展示了系统中结点的种类和结点所拥有构件的种类,节电显示为方块。



下图 是 一个实例级别的配置图



七 :模型管理视图



最后 来几张图




posted @ 2009-03-10 18:08 二胡 阅读(176) | 评论 (0)编辑 收藏

转 http://www.blogjava.net/liuwentao253/archive/2007/09/03/142245.html




类图中的关系 :

1 :一般化(Generalization )关系



2 :关联( Association )关系



               2.1 :  聚合(Aggregation )关系
                                 

               2.2 : 合成(Composition)关系
                                 

3 :  依赖 ( Dependency )关系
                               

posted @ 2009-03-10 18:06 二胡 阅读(179) | 评论 (0)编辑 收藏


转 http://www.blogjava.net/liuwentao253/archive/2008/08/01/219416.html
一  :


二 :





三 :



四 :



五 :




类与类之间的关系对于理解面向对象具有很重要的作用,存在以下关系:
(1)泛化(Generalization)   :狗与动物  (空箭头)
(2)关联(Association)       :公司与员工有特定的某种关系 (实线)
(3)依赖(Dependency)    :人依赖螺丝刀 (虚线箭头)
(4)聚合(Aggregation)     : 电脑和CPU,主板 (菱形空间头)







详细展开  :
一 .泛化(Generalization)
 表示类与类之间的继承关系接口与接口之间的继承关系或类对接口的实现关系
 一般泛化的关系是从子类指向父类的:
 父类 父类实例=new 子类()




1/**
2* 一个测试类
3*/

4public class Demo{    
5    public void test() {
6        //老虎的实例 也属于动物类型 
7        Animal animal = new Tiger();  
8    }
    
9}
 



1/**
2*  动物类
3*/

4public class Animal{
5
6}
    


1/**
2* 老虎类 
3*/

4public class Tiger extends Animal{
5
6}
  


二 .依赖(Dependency)

对于两个相对独立的对象,当一个对象(螺丝刀)负责构造另一个对象(人)的实例,或者一个对象(人)依赖另一个对象(螺丝刀)的服务时,这两个对象之间主要体现为依赖关系。

下面这个例子显然属于后者 :人要做一个拧螺丝的动作,他就要依赖于 螺丝刀对象,因为只有螺丝刀对象才提供拧螺丝的服务。



 1/**
 2 * 说明 :人 这个 类 
 3 */

 4public class Person {
 5    /**
 6     * 人拥有的一个  拧螺丝  的  动作 依赖于螺丝刀这个类
 7     * @param screwdriver :螺丝刀类
 8     */

 9     public void screw(Screwdriver screwdriver)
10        //螺丝刀类提供了拧螺丝这个服务
11        screwdriver.screw();    
12    }
  
13}


三 .关联(Association)
对于两个相对独立的对象,当一个对象的实例与另一个对象的一些特定实例存在固定的对应关系时,这两个对象之间为关联关系。
关联关系是使用实例变量来实现
比如客户和订单,每个订单对应特定的客户,每个客户对应一些特定的订单;再例如公司和员工,每个公司对应一些特定的员工,每个员工对应一特定的公司




 1/**
 2 * 公司
 3 */

 4public class Company{   
 5    //员工
 6    private Employee employee;
 7
 8    /**
 9     * 公司运作
10      */

11    public void run(){    
12        employee.startWorking();    
13    }

14    
15    public Employee getEmployee(){    
16        return employee;    
17    }
    
18    public void setEmployee(Employee employee){    
19        this.employee=employee;    
20    }
  
21}
 

四 : 聚合(Aggregation)
当对象A被加入到对象B中,成为对象B的组成部分时,对象B和对象A之间为聚集关系。聚合是关联关系的一种,是较强的关联关系,强调的是整体与部分之间的关系。
[关联与聚合的区别]
(1)关联关系所涉及的两个对象是处在同一个层次上的。比如人和自行车就是一种关联关系,而不是聚合关系,因为人不是由自行车组成的。
聚合关系涉及的两个对象处于不平等的层次上,一个代表整体,一个代表部分。比如电脑和它的显示器、键盘、主板以及内存就是聚集关系,因为主板是电脑的组成部分。
(2)对于具有聚集关系(尤其是强聚集关系)的两个对象,整体对象会制约它的组成对象的生命周期。部分类的对象不能单独存在,它的生命周期依赖于整体类的对象的生命周期,当整体消失,部分也就随之消失。比如张三的电脑被偷了,那么电脑的所有组件也不存在了,除非张三事先把一些电脑的组件(比如硬盘和内存)拆了下来。



 1public class Computer{    
 2    private CPU cpu;    
 3    public CPU getCPU(){    
 4        return cpu;    
 5    }
    
 6    public void setCPU(CPU cpu){    
 7        this.cpu=cpu;    
 8    }
    
 9    //开启电脑    
10    public void start(){    
11        //cpu运作    
12        cpu.run();    
13    }
    
14}
  
posted @ 2009-03-10 17:55 二胡 阅读(177) | 评论 (0)编辑 收藏

转 http://www.ibm.com/developerworks/cn/rational/tip-uml/index2.html

用例建模技巧

适合于更好的 UML 用例模型的技术

developerWorks
文档选项
将打印机的版面设置成横向打印模式

打印本页

将此页作为电子邮件发送

将此页作为电子邮件发送


级别: 初级

Scott W. Ambler (scott.ambler@ronin-intl.com), 总裁, Ronin International

2001 年 1 月 04 日

本文介绍了一些提高系统用例模型质量的技巧和技术。本文改编自 Object Primer 2nd Edition 的第 6 章。

从参与者的角度并以主动语态编写用例。
应该以主动语态:“学生表明参加研习班意向”,而不是被动语态“研习班意向被学生表明”来编写用例。而且,应该从参与者的角度来编写用例。毕竟,用例的目的是理解用户如何对系统进行操作。

编写方案文本,而非功能需求。
用例描述的是对参与者来说有价值的一系列行动,而不是特性集。例如,“招收研习班的学生”用例描述的是学生如何与系统交互来参加研习班。它没有描述用户界面看上去是什么样子,或者它是如何工作的。有一些其它的模型来描述这些重要的信息,例如用户界面模型和增补规范。面向对象分析非常复杂,因此需要对它使用几种模型,并且应该适当地应用每一种模型。

用例只记载行为需求。
用例既不是类规范,也不是数据规范。这是应该由概念性模型捕捉的一种信息,在对象世界中,它是通过 UML类模型建模的。您往往会引用概念性模型中描述的类,例如,“参加研习班”用例包括了“研习班”和“学生”等概念,它们都将由概念性模型描述。

不要忘记用户界面。
系统用例经常引用主用户界面 (UI)元素,这些元素常常称为“边界”或“用户界面”项,例如 HTML页面和报表。用例有时也引用一些次要的 UI元素,例如按钮或数据输入字段,但这种级别的细节并不太常见。

创建用例模板。
用例包含了相当数量的信息,这些信息可以轻易地以常见格式记载。您应该考虑开发自己的模板(请参阅技巧“ 记载用例”)。

始终如一地组织用例图。
一般的做法是垂直地绘制继承 (inheritance) 和扩展 (extend)关联,在父/基本用例下面绘制继承/扩展用例。同样,通常水平绘制包含(include) 关联。请注意,这些是简单的经验法则 --只要始终遵循这些法则,产生的图将很容易理解。

不要忘记系统对参与者行动的响应。
用例既应该描述参与者是如何与系统交互的,也应该描述系统如何响应这些交互。例如,在“参加研习班”用例中,如果系统在学生表明他们希望参加研习班时没有做出响应,学生就会很沮丧地离开。

备选行动过程非常重要。
如果一切顺利,使用的将是基本行动过程 --但也不要忘记备选过程。引入备选过程是为了描述潜在的使用错误以及商业逻辑错误和异常。这些重要的信息对于驱动系统的设计来说很有必要,因此不要忘记在用例中对它们建模。

不要被 <<include>> 和 <<extend>>关联所困扰。
我不是很确定到底发生了什么事,但我总是在想包含 (include) 和扩展(extend) 关联,以及旧版本 UML 中使用 (uses) 和扩展 (extends)关联的正确使用从来没有得到很好的描述。结果,用例建模小组往往在这些关联的正确应用上争论不休,在整个建模技术中一些有趣但次要的部分上浪费了惊人的时间。我曾在一个组织中工作,这家组织居然取缔了<<include>> 和 <<extend>>原型的使用,几个星期后,当意识到公司仍然需要这些概念时不得不撤消了这种极端的解决方案,而这时该组织对它们的正确使用还没有达成共识。

让用例带动用户文档。
用户文档的目的是描述如何使用系统。每个用例都描述了参与者通过使用系统所采取的一系列动作。简而言之,用例包含从中开始编写问党用户稳当的信息。例如,可以使用“参加研习班”用例作为基础来编写系统用户文档的“如何参加研习班”一节。

让用例带动演示。
软件开发过程中的一部分是向项目资金管理者通报工作成果,因此有时需要提供演示。因为用例是从用户的角度编写的,它们包含了演示中对资金管理者可能希望听到的事物的有价值的深刻见解。换句话说,用例通常包含制定演示稿所需的逻辑。



参考资料



关于作者

Author photo

Scott W. Ambler 是 Ronin International 的总裁,该公司是一家专门提供面向对象软件过程指导、体系结构建模和 Enterprise JavaBean (EJB) 开发的咨询企业。他创作或者与其他人合著了几本有关面向对象开发的书籍,包括最近出版的 Object Primer 2nd Edition,该书详细介绍了本文所概述的主题。可以通过 scott.ambler@ronin-intl.com 与他联系,他的网站位于 www.ambysoft.com


posted @ 2009-03-10 17:36 二胡 阅读(156) | 评论 (0)编辑 收藏

转 http://www.ibm.com/developerworks/cn/rational/r-usecase-atm/

用例建模指南

developerWorks
文档选项
将打印机的版面设置成横向打印模式

打印本页

将此页作为电子邮件发送

将此页作为电子邮件发送


级别: 初级

傅纯一, Rational中国区技术销售经理, IBM中国有限公司软件部

2004 年 11 月 01 日

用例(Use Case)是一种描述系统需求的方法,使用用例的方法来描述系统需求的过程就是用例建模。用例方法最早是由Iva Jackboson博士提出的,后来被综合到UML规范之中,成为一种标准化的需求表述体系。用例的使用在RUP中被推崇备至,整个RUP流程都被称作是"用例驱动"(Use-Case Driven)的,各种类型的开发活动包括项目管理、分析设计、测试、实现等都是以系统用例为主要输入工件,用例模型奠定了整个系统软件开发的基础。

1. 什么是用例?

在介始用例方法之前,我们首先来看一下传统的需求表述方式-"软件需求规约"(Software Requirement Specification)。传统的软件需求规约基本上采用的是功能分解的方式来描述系统功能,在这种表述方式中,系统功能被分解到各个系统功能模块中,我们通过描述细分的系统模块的功能来达到描述整个系统功能的目的。一个典型的软件需求规约可能具有以下形式:



采用这种方法来描述系统需求,非常容易混淆需求和设计的界限,这样的表述实际上已经包含了部分的设计在内。由此常常导致这样的迷惑:系统需求应该详细到何种程度?一个极端就是需求可以详细到概要设计,因为这样的需求表述既包含了外部需求也包含了内部设计。在有些公司的开发流程中,这种需求被称为"内部需求",而对应于用户的原始要求则被称之为"外部需求"。

功能分解方法的另一个缺点是这种方法分割了各项系统功能的应用环境,从各项功能项入手,你很难了解到这些功能项是如何相互关联来实现一个完成的系统服务的。所以在传统的SRS文档中,我们往往需要另外一些章节来描述系统的整体结构及各部分之间的相互关联,这些内容使得SRS需求更象是一个设计文档。

1.1 参与者和用例

从用户的角度来看,他们并不想了解系统的内部结构和设计,他们所关心的是系统所能提供的服务,也就是被开发出来的系统将是如何被使用的,这就用例方法的基本思想。用例模型主要由以下模型元素构成:

  • 参与者(Actor)
    参与者是指存在于被定义系统外部并与该系统发生交互的人或其他系统,他们代表的是系统的使用者或使用环境。
  • 用例(Use Case)
    用例用于表示系统所提供的服务,它定义了系统是如何被参与者所使用的,它描述的是参与者为了使用系统所提供的某一完整功能而与系统之间发生的一段对话。
  • 通讯关联(Communication Association)
    通讯关联用于表示参与者和用例之间的对应关系,它表示参与者使用了系统中的哪些服务(用例),或者说系统所提供的服务(用例)是被哪些参与者所使用的。

这大三种模型元素在UML中的表述如下图所示。



以银行自动提款机(ATM)为例,它的主要功能可以由下面的用例图来表示。ATM的主要使用者是银行客户,客户主要使用自动提款机来进行银行帐户的查询、提款和转帐交易。



通讯关联表示的是参与者和用例之间的关系,箭头表示在这一关系中哪一方是对话的主动发起者,箭头所指方是对话的被动接受者;如果你不想强调对话中的主动与被动关系,可以使用不带箭头的关联实线。在参与者和用例之间的信息流不是由通讯关联来表示的,该信息流是缺省存在的(用例本身描述的就是参与者和系统之间的对话),并且信息流向是双向的,它与通讯关联箭头所指的方向亳无关系。

1.2 用例的内容

用例图使我们对系统的功能有了一个整体的认知,我们可以知道有哪些参与者会与系统发生交互,每一个参与者需要系统为它提供什么样的服务。用例描述的是参与者与系统之间的对话,但是这个对话的细节并没有在用例图中表述出来,针对每一个用例我们可以用事件流来描述这一对话的细节内容。如在ATM系统中的"提款" 用例可以用事件流表述如下:

提款-基本事件流

1. 用户插入信用卡

2. 输入密码

3. 输入提款金额

4. 提取现金

5. 退出系统,取回信用卡

但是这只描述了提款用例中最顺利的一种情况,作为一个实用的系统,我们还必须考虑可能发生的各种其他情况,如信用卡无效、输入密码错、用户帐号中的现金余额不够等,所有这些可能发生的各种情况(包括正常的和异常的)被称之为用例的场景(Scenario),场景也被称作是用例的实例(Instance)。在用例的各种场景中,最常见的场景是用基本流(Basic Flow)来描述的,其他的场景则是用备选流(Alternative Flow)来描述。对于ATM系统中的"提款"用例,我们可以得到如下一些备选流:

提款-备选事件流

备选流一:用户可以在基本流中的任何一步选择退出,转至基本流步骤5。

备选流二:在基本流步骤1中,用户插入无效信用卡,系统显示错误并退出信用卡,用例结束。

备选流三:在基本流步骤2中,用户输入错误密码,系统显示错误并提示用户重新输入密码,重新回到基本流步骤2;三次输入密码错误后,信用卡被系统没收,用例结束。

通过基本流与备选流的组合,就可以将用例所有可能发生的各种场景全部描述清楚。我们在描述用例的事件流的时候,就是要尽可能地将所有可能的场景都描述出来,以保证需求的完备性。

1.3 用例方法的优点

用例方法完全是站在用户的角度上(从系统的外部)来描述系统的功能的。在用例方法中,我们把被定义系统看作是一个黑箱,我们并不关心系统内部是如何完成它所提供的功能的。用例方法首先描述了被定义系统有哪些外部使用者(抽象成为Actor),这些使用者与被定义系统发生交互;针对每一参与者,用例方法又描述了系统为这些参与者提供了什么样的服务(抽象成为Use Case),或者说系统是如何被这些参与者使用的。所以从用例图中,我们可以得到对于被定义系统的一个总体印象。

与传统的功能分解方式相比,用例方法完全是从外部来定义系统的功能,它把需求与设计完全分离开来。在面向对象的分析设计方法中,用例模型主要用于表述系统的功能性需求,系统的设计主要由对象模型来记录表述。另外,用例定义了系统功能的使用环境与上下文,每一个用例描述的是一个完整的系统服务。用例方法比传统的 SRS更易于被用户所理解,它可以作为开发人员和用户之间针对系统需求进行沟通的一个有效手段。

在RUP中,用例被作为整个软件开发流程的基础,很多类型的开发活动都把用例作为一个主要的输入工件(Artifact),如项目管理、分析设计、测试等。根据用例来对目标系统进行测试,可以根据用例中所描述的环境和上下文来完整地测试一个系统服务,可以根据用例的各个场景(Scenario)来设计测试用例,完全地测试用例的各种场景可以保证测试的完备性。





回页首


2. 建立用例模型

使用用例的方法来描述系统的功能需求的过程就是用例建模,用例模型主要包括以下两部分内容:

  • 用例图(Use Case Diagram)
    确定系统中所包含的参与者、用例和两者之间的对应关系,用例图描述的是关于系统功能的一个概述。
  • 用例规约(Use Case Specification)
    针对每一个用例都应该有一个用例规约文档与之相对应,该文档描述用例的细节内容。

在用例建模的过程中,我们建议的步聚是先找出参与者,再根据参与者确定每个参与者相关的用例,最后再细化每一个用例的用例规约。

2.1 寻找参与者

所谓的参与者是指所有存在于系统外部并与系统进行交互的人或其他系统。通俗地讲,参与者就是我们所要定义系统的使用者。寻找参与者可以从以下问题入手:

  • 系统开发完成之后,有哪些人会使用这个系统?
  • 系统需要从哪些人或其他系统中获得数据?
  • 系统会为哪些人或其他系统提供数据?
  • 系统会与哪些其他系统相关联?
  • 系统是由谁来维护和管理的?

这些问题有助于我们抽象出系统的参与者。对于ATM机的例子,回答这些问题可以使我们找到更多的参与者:操作员负责维护和管理ATM机系统、ATM机也需要与后台服务器进行通讯以获得有关用户帐号的相关信息。



2.1.1 系统边界决定了参与者

参与者是由系统的边界所决定的,如果我们所要定义的系统边界仅限于ATM机本身,那么后台服务器就是一个外部的系统,可以抽象为一个参与者。



如果我们所要定义的系统边界扩大至整个银行系统,ATM机和后台服务器都是整个银行系统的一部分,这时候后台服务器就不再被抽象成为一个参与者。



值得注意的是,用例建模时不要将一些系统的组成结构作为参与者来进行抽象,如在ATM机系统中,打印机只是系统的一个组成部分,不应将它抽象成一个独立的参与者;在一个MIS管理系统中,数据库系统往往只作为系统的一个组成部分,一般不将其单独抽象成一个参与者。

2.1.2 特殊的参与者――系统时钟

有时候我们需要在系统内部定时地执行一些操作,如检测系统资源使用情况、定期地生成统计报表等等。从表面上看,这些操作并不是由外部的人或系统触发的,应该怎样用用例方法来表述这一类功能需求呢?对于这种情况,我们可以抽象出一个系统时钟或定时器参与者,利用该参与者来触发这一类定时操作。从逻辑上,这一参与者应该被理解成是系统外部的,由它来触发系统所提供的用例对话。



2.2 确定用例

找到参与者之后,我们就可以根据参与者来确定系统的用例,主要是看各参与者需要系统提供什么样的服务,或者说参与者是如何使用系统的。寻找用例可以从以下问题入手(针对每一个参与者):

  • 参与者为什么要使用该系统?
  • 参与者是否会在系统中创建、修改、删除、访问、存储数据?如果是的话,参与者又是如何来完成这些操作的?
  • 参与者是否会将外部的某些事件通知给该系统?
  • 系统是否会将内部的某些事件通知该参与者?

综合以上所述,ATM系统的用例图可表示如下,



在用例的抽取过程中,必须注意:用例必须是由某一个主角触发而产生的活动,即每个用例至少应该涉及一个主角。如果存在与主角不进行交互的用例,就可以考虑将其并入其他用例;或者是检查该用例相对应的参与者是否被遗漏,如果是,则补上该参与者。反之,每个参与者也必须至少涉及到一个用例,如果发现有不与任何用例相关联的参与者存在,就应该考虑该参与者是如何与系统发生对话的,或者由参与者确定一个新的用例,或者该参与者是一个多余的模型元素,应该将其删除。

可视化建模的主要目的之一就是要增强团队的沟通,用例模型必须是易于理解的。用例建模往往是一个团队开发的过程,系统分析员在建模过程中必须注意参与者和用例的名称应该符合一定的命名约定,这样整个用例模型才能够符合一定的风格。如参与者的名称一般都是名词,用例名称一般都是动宾词组等。

对于同一个系统,不同的人对于参与者和用例都可能有不同的抽象结果,因而得到不同的用例模型。我们需要在多个用例模型方案中选择一种"最佳"(或"较佳")的结果,一个好的用例模型应该能够容易被不同的涉众所理解,并且不同的涉众对于同一用例模型的理解应该是一致的。

2.3 描述用例规约

应该避免这样一种误解――认为由参与者和用例构成的用例图就是用例模型,用例图只是在总体上大致描述了系统所能提供的各种服务,让我们对于系统的功能有一个总体的认识。除此之外,我们还需要描述每一个有例的详细信息,这些信息包含在用例规约中,用例模型是由用例图和每一个用例的详细描述――用例规约所组成的。RUP中提供了用例规约的模板,每一个用例的用例规约都应该包含以下内容:

  • 简要说明 (Brief Description)
    简要介绍该用例的作用和目的。
  • 事件流 (Flow of Event)
    包括基本流和备选流,事件流应该表示出所有的场景。
  • 用例场景 (Use-Case Scenario)
    包括成功场景和失败场景,场景主要是由基本流和备选流组合而成的。
  • 特殊需求 (Special Requirement)
    描述与该用例相关的非功能性需求(包括性能、可靠性、可用性和可扩展性等)和设计约束(所使用的操作系统、开发工具等)。
  • 前置条件 (Pre-Condition)
    执行用例之前系统必须所处的状态。
  • 后置条件 (Post-Condition)
    用例执行完毕后系统可能处于的一组状态。

用例规约基本上是用文本方式来表述的,为了更加清晰地描述事件流,也可以选择使用状态图、活动图或序列图来辅助说明。只要有助于表达的简洁明了,就可以在用例中任意粘贴用户界面和流程的图形化显示方式,或是其他图形。如活动图有助于描述复杂的决策流程,状态转移图有助于描述与状态相关的系统行为,序列图适合于描述基于时间顺序的消息传递。

2.3.1 基本流

基本流描述的是该用例最正常的一种场景,在基本流中系统执行一系列活动步骤来响应参与者提出的服务请求。我们建议用以下格式来描述基本流:

1) 每一个步骤都需要用数字编号以清楚地标明步骤的先后顺序。

2) 用一句简短的标题来概括每一步骤的主要内容,这样阅读者可以通过浏览标题来快速地了解用例的主要步骤。在用例建模的早期,我们也只需要描述到事件流步骤标题这一层,以免过早地陷入到用例描述的细节中去。

3) 当整个用例模型基本稳定之后,我们再针对每一步骤详细描述参与者和系统之间所发生的交互。建议采用双向(roundtrip)描述法来保证描述的完整性,即每一步骤都需要从正反两个方面来描述:(1)参与者向系统提交了什么信息;(2)对此系统有什么样的响应。具体例子请参见附录。

在描述参与者和系统之间的信息交换时,需指出来回传递的具体信息。例如,只表述参与者输入了客户信息就不够明确,最好明确地说参与者输入了客户姓名和地址。通常可以利用词汇表让用例的复杂性保持在可控范围内,可以在词汇表中定义客户信息等内容,使用例不至于陷入过多的细节。

2.3.2 备选流

备选流负责描述用例执行过程中异常的或偶尔发生的一些情况,备选流和基本流的组合应该能够覆盖该用例所有可能发生的场景。在描述备选流时,应该包括以下几个要素:

1) 起点:该备选流从事件流的哪一步开始;

2) 条件:在什么条件下会触发该备选流;

3) 动作:系统在该备选流下会采取哪些动作;

4) 恢复:该备选流结束之后,该用例应如何继续执行。

备选流的描述格式可以与基本流的格式一致,也需要编号并以标题概述其内容,编号前可以加以字母前缀A(Alternative)以示与基本流步骤相区别。

2.3.3 用例场景

用例在实际执行的时候会有很多的不同情况发生,称之为用例场景;也可以说场景是用例的实例,我们在描述用例的时候要覆盖所有的用例场景,否则就有可能导致需求的遗漏。在用例规约中,场景的描述可以由基本流和备选流的组合来表示。场景既可以帮助我们防止需求的遗漏,同时也可以对后续的开发工作起到很大的帮助:开发人员必须实现所有的场景、测试人员可以根据用例场景来设计测试用例。

2.3.4 特殊需求

特殊需求通常是非功能性需求,它为一个用例所专有,但不适合在用例的事件流文本中进行说明。特殊需求的例子包括法律或法规方面的需求、应用程序标准和所构建系统的质量属性(包括可用性、可靠性、性能或支持性需求等)。此外,其他一些设计约束,如操作系统及环境、兼容性需求等,也可以在此节中记录。

需要注意的是,这里记录的是专属于该用例的特殊需求;对于一些全局的非功能性需求和设计约束,它们并不是该用例所专有的,应把它们记录在《补充规约》中。

2.3.5 前置和后置条件

前置条件是执行用例之前必须存在的系统状态,后置条件是用例一执行完毕后系统可能处于的一组状态。

2.4 检查用例模型

用例模型完成之后,可以对用例模型进行检查,看看是否有遗漏或错误之处。主要可以从以下几个方面来进行检查:

  • 功能需求的完备性
    现有的用例模型是否完整地描述了系统功能,这也是我们判断用例建模工作是否结束的标志。如果发现还有系统功能没有被记录在现有的用例模型中,那么我们就需要抽象一些新的用例来记录这些需求,或是将他们归纳在一些现有的用例之中。
  • 模型是否易于理解
    用例模型最大的优点就在于它应该易于被不同的涉众所理解,因而用例建模最主要的指导原则就是它的可理解性。用例的粒度、个数以及模型元素之间的关系复杂程度都应该由该指导原则决定。
  • 是否存在不一致性
    系统的用例模型是由多个系统分析员协同完成的,模型本身也是由多个工件所组成的,所以我们要特别注意不同工件之前是否存在前后矛盾或冲突的地方,避免在模型内部产生不一致性。不一致性会直接影响到需求定义的准确性。
  • 避免二义性语义
    好的需求定义应该是无二义性的,即不同的人对于同一需求的理解应该是一致的。在用例规约的描述中,应该避免定义含义模糊的需求,即无二义性。




回页首


3. 系统需求

RUP中根据FURPS+模型将系统需求分为以下几类:

  • 功能(Functionality)
  • 可用性(Usability)
  • 可靠性(Reliability)
  • 性能(Performance)
  • 可支持性(Supportability)
  • 设计约束等

除了第一项功能性需求之外的其他需求都归之为非功能性需求。

3.1 需求工件集

用例模型主要用于描述系统的功能性需求,对于其他的非功能性需要用其他文档来记录。RUP中定义了如下的需求工件集合。

  • 用例模型:记录功能性需求
    • 用例图:描述参与者和用例之间的关系
    • 用例规约:描述每一个用例的细节信息
  • 补充规约:记录一些全局性的功能需求、非功能性需求和设计约束等
  • 词汇表:记录一些系统需求相关的术语

在实际应用中,除了这些工件之外,我们还可以根据实际需求灵活选用其他形式的文档来补充说明需求。并不是所有的系统需求都适保合用用例模型来描述的,如编译器,我们很难用用例方法来表述它所处理的语言的方法规则,在这种情况下,采用传统的BNF范式来表述更加合适一些。在电信软件行业中,很多电信标准都是采用SDL语言来描述的,我们也不必用UML来改写这些标准(UML对SDL存在着这样的兼容性),只需将SDL形式的电信标准作为需求工件之一,在其他工件中对其加以引用就可以了。总之,万万不可拘泥于用例建模的形式,应灵活运用各种方式的长处。

3.2 补充规约

补充规约记录那些在用例模型中不易表述的系统需求,主要包括以下内容。

  • 功能性
    功能性需求主要在用例模型中刻画,但是也有部分需求不适合在用例中表述。有些功能性需求是全局性的,适用于所有的用例,如出错处理、I18N支持等,我们不需要在所有的用例中描述这些功能性需求,只需要在补充规约中统一描述就可以了。
  • 可用性
    记录所有可用性相关的需求,如系统的使用者所需要的培训时间、是否应附合一些常见的可用性标准如Windows界面风格等。
  • 可靠性
    定义系统可靠性相关的各种指标,包括:
    • 可用性:指出可用时间百分比(xx.xx%),系统处于使用、维护、降级模式等操作的小时数;
    • 平均故障间隔时间(MTBF):通常表示为小时数,但也可表示为天数、月数或年数;
    • 平均修复时间(MTTR):系统在发生故障后可以暂停运行的时间;
    • 精确度:指出系统输出要求具备的精密度(分辨率)和精确度(按照某一已知的标准);
    • 最高错误或缺陷率:通常表示为bugs/KLOC(每千行代码的错误数目)或bugs/function-point(每个功能点的错误数目)。
  • 性能
    记录系统性能相关的各种指标,包括:
    • 对事务的响应时间(平均、最长);
    • 吞吐量(例如每秒处理的事务数);
    • 容量(例如系统可以容纳的客户或事务数);
    • 降级模式(当系统以某种形式降级时可接受的运行模式);
    • 资源利用情况:内存、磁盘、通信等。
  • 可支持性
    定义所有与系统的可支持性或可维护性相关的需求,其中包括编码标准、命名约定、类库、如何来对系统进行维护操作和相应的维护实用工具等。
  • 设计约束
    设计约束代表已经批准并必须遵循的设计决定,其中包括软件开发流程、开发工具、系统构架、编程语言、第三方构件类库、运行平台和数据库系统等等。

3.3 词汇表

词汇表主要用于定义项目特定的术语,它有助于开发人员对项目中所用的术语有统一的理解和使用,它也是后续阶段中进行对象抽象的基础。





回页首


4. 调整用例模型

在一般的用例图中,我们只表述参与者和用例之间的关系,即它们之间的通讯关联。除此之外,我们还可以描述参与者与参与者之间的泛化 (generalization)、用例和用例之间的包含(include)、扩展(extend)和泛化(generalization)关系。我们利用这些关系来调整已有的用例模型,把一些公共的信息抽取出来重用,使得用例模型更易于维护。但是在应用中要小心选用这些关系,一般来说这些关系都会增加用例和关系的个数,从而增加用例模型的复杂度。而且一般都是在用例模型完成之后才对用例模型进行调整,所以在用例建模的初期不必要急于抽象用例之间的关系。

4.1 参与者之间的关系

参与者之间可以有泛化(Generalization)关系(或称为"继承"关系)。例如在需求分析中常见的权限控制问题(如下图所示),一般的用户只可以使用一些常规的操作,而管理员除了常规操作之外还需要进行一些系统管理工作,操作员既可以进行常规操作又可以进行一些配置操作。



在这个例子中我们会发现管理员和操作员都是一种特殊的用户,他们拥有普通用户所拥有的全部权限,此外他们还有自己独有的权限。这里我们可进一步把普通用户和管理员、操作员之间的关系抽象成泛化(Generalization)关系,管理员和操作员可以继承普通用户的全部特性(包括权限),他们又可以有自己独有的特性(如操作、权限等)。这样可以显著减速少用例图中通讯关联的个数,简化用例模型,使之更易于理解。



4.2 用例之间的关系

用例描述的是系统外部可见的行为,是系统为某一个或几个参与者提供的一段完整的服务。从原则上来讲,用例之间都是并列的,它们之间并不存在着包含从属关系。但是从保证用例模型的可维护性和一致性角度来看,我们可以在用例之间抽象出包含(include)、扩展(extend)和泛化 (generalization)这几种关系。这几种关系都是从现有的用例中抽取出公共的那部分信息,然后通后过不同的方法来重用这部公共信息,以减少模型维护的工作量。

4.2.1 包含(include)

包含关系是通过在关联关系上应用<<include>>构造型来表示的,如下图所示。它所表示的语义是指基础用例(Base)会用到被包含用例(Inclusion),具体地讲,就是将被包含用例的事件流插入到基础用例的事件流中。



包含关系是UML1.3中的表述,在UML1.1中,同等语义的关系被表述为使用(uses),如下图。



在 ATM机中,如果查询、取现、转帐这三个用例都需要打印一个回执给客户,我们就可以把打印回执这一部分内容提取出来,抽象成为一个单独的用例"打印回执",而原有的查询、取现、转帐三个例都会包含这个用例。每当以后要对打印回执部分的需求进行修改时,就只需要改动一个用例,而不用在每一个用例都作相应修改,这样就提高了用例模型的可维护性。



在基础用例的事件流中,我们只需要引用被包含用例即可。

查询-基本事件流

1. 用户插入信用卡

2. 输入密码

3. 选择查询

4. 查看帐号余额

5. 包含用例"打印回执"

6. 退出系统,取回信用卡

在这个例子中,多个用例需要用到同一段行为,我们可以把这段共同的行为单独抽象成为一个用例,然后让其他的用例来包含这一用例。从而避免在多个用例中重复性地描述同一段行为,也可以防止该段行为在多个用例中的描述出现不一致性。当需要修改这段公共的需求时,我们也只需要修改一个用例,避免同时修改多个用例而产生的不一致性和重复性工作。

有时当某一个用例的事件流过于复杂时,为了简化用例的描述,我们也可以把某一段事件流抽象成为一个被包含的用例。这种情况类似于在过程设计语言中,将程序的某一段算法封装成一个子过程,然后再从主程序中调用这一子过程。

4.2.2 扩展(extend)

扩展(extend)关系如下图所示,基础用例(Base)中定义有一至多个已命名的扩展点,扩展关系是指将扩展用例(Extension)的事件流在一定的条件下按照相应的扩展点插入到基础用例(Base)中。对于包含关系而言,子用例中的事件流是一定插入到基础用例中去的,并且插入点只有一个。而扩展关系可以根据一定的条件来决定是否将扩展用例的事件流插入基础用例事件流,并且插入点可以有多个。



例如对于电话业务,可以在基本通话(Call)业务上扩展出一些增值业务如:呼叫等待(Call Waiting)和呼叫转移(Call Transfer)。我们可以用扩展关系将这些业务的用例模型描述如下。





在这个例子中,呼叫等待和呼叫转移都是对基本通话用例的扩展,但是这两个用例只有在一定的条件下(如应答方正忙或应答方无应答)才会将被扩展用例的事件流嵌入基本通话用例的扩展点,并重用基本通话用例中的事件流。

值得注意的是扩展用例的事件流往往可以也可抽象为基础用例的备选流,如上例中的呼叫等待和呼叫转移都可以作为基本通话用例的备选流而存在。但是基本通话用例已经是一个很复杂的用例了,选用扩展关系将增值业务抽象成为单独的用例可以避免基础用例过于复杂,并且把一些可选的操作独立封装在另外的用例中。

4.2.3 泛化(generalization)

当多个用例共同拥有一种类似的结构和行为的时候,我们可以将它们的共性抽象成为父用例,其他的用例作为泛化关系中的子用例。在用例的泛化关系中,子用例是父用例的一种特殊形式,子用例继承了父用例所有的结构、行为和关系。在实际应用中很少使用泛化关系,子用例中的特殊行为都可以作为父用例中的备选流存在。



以下是一个用例泛化关系的例子,执行交易是一种交易抽象,执行房产交易和执行证券交易都是一种特殊的交易形式。

用例泛化关系中的事件流示例如下:



4.3 调整用例模型

用例模型建成之后,我们可以对用例模型进行检视,看是否可以进一步简化用例模型、提高重用程度、增加模型的可维护性。主要可以从以下检查点(checkpoints)入手:

  • 用例之间是否相互独立?如果两个用例总是以同样的顺序被激活,可能需要将它们合并为一个用例。
  • 多个用例之间是否有非常相似的行为或事件流?如果有,可以考虑将它们合并为一个用例。
  • 用例事件流的一部分是否已被构建为另一个用例?如果是,可以让该用例包含(include)另一用例。
  • 是否应该将一个用例的事件流插入另一个用例的事件流中?如果是,利用与另一个用例的扩展关系(extend)来建立此模型。




回页首


5. 管理用例模型复杂度

一般小型的系统,其用例模型中包含的参与者和用例不会太多,一个用例图就可以容纳所有的参与者,所有的参与者和用例也可以并存于同一个层次结构中。对于较复杂的大中型系统,用例模型中的参与者和用例会大大增加,我们需要一些方法来有效地管理由于规模上升而造成的复杂度。

5.1 用例包

包 (Package)是UML中最常用的管理模型复杂度的机制,包也是UML中语义最简单的一种模型元素,它就是一种容器,在包中可以容纳其他任意的模型元素(包括其他的包)。在用例模型中,我们可以用构造型(Sterotype)<<use case>>来扩展标准UML包的语义,这种新的包叫作用例包(Use Case Package),用于分类管理用例模型中的模型元素。

我们可以根据参与者和用例的特性来对它们进行分类,分别置于不同的用例包管理之下。例如对于一个大型的企业管理信息系统,我们可以根据参与者和用例的内容将它们分别归于人力资源、财务、采购、销售、客务服务这些用例包之下。这样我们将整个用例模型划分成为两个层次,在第一层次我们看到的是系统功能总共分为五部分,在第二层次我们可以分别看到每一用例包内部的参与者和用例。



一个用例模型需要有多少个用例包取决你想怎么样来管理用例模型的复杂度(包括参与者和用例的个数,以及它们之间的相互关系)。UML中的包其实就类似于文件系统中的目录,文件数量少的时候不需要额外的目录,文件数量一多就需要有多个目录来分类管理,同样一组文件不同的人会创建不同的目录结构来进行管理,关键是要保证在目录结构下每一个文件都要易于访问。同样的道理存在于用例建模之中,如何创建用例包以及用例包的个数取决于不同的系统和系统分析员,但要保证整个用例模型易于理解。

5.2 用例的粒度

我的系统需要有多少个用例?这是很多人在用例建模时会产生的疑惑。描述同一个系统,不同的人会产生不同的用例模型。例如对于各种系统中常见的"维护用户"用例,它里面包含了添加用户、修改用户信息、删除用户等操作,这些操作在该用例的事件流可以表述成为基本流的子事件流(subflow)。



维护用户-基本事件流

该基本流由三个子事件流构成:

1) 添加用户子事件流

2) 修改用户 子事件流

3) 删除用户子事件流

但是你也可以根据该用例中的具体操作把它抽象成为三个用例,它所表示的系统需求和单个用例的模型是完全一样的。



应该如何确定用例的粒度呢?在一次技术研讨会上,有人问起Ivar Jacoboson博士,一个系统需要有多少个用例?大师的回答是20个,当然他的意思是最好将用例模型的规模控制在几十个用例左右,这样比较容易来管理用例模型的复杂度。在用例个数大致确定的条件下,我们就很容易来确定用例粒度的大小。对于较复杂的系统,我们需要控制用例模型一级的复杂度,所以可以将复杂度适当地移往每一个用例的内部,也就是让一个用例包含较多的需求信息量。对于比较简单的系统,我们则可以将复杂度适度地曝露在模型一级,也就是我们可以将较复杂的用例分解成为多个用例。

用例的粒度不但决定了用例模型级的复杂度,而且也决定了每一个用例内部的复杂度。我们应该根据每个系统的具体情况,因时因宜地来把握各个层次的复杂度,在尽可能保证整个用例模型的易理解性前提下决定用例的大小和数目。

5.3 用例图

用例图的主要作用是描述参与者和用例之间的关系,简单的系统中只需要有一个用例图就可以把所有的关系都描述清楚。复杂的系统中可以有多个用例图,例如每个用例包都可以有一个独立的用例图来描述该用例包中所有的参与者和用例的关系。

在一个用例模型中,如果参与者和用例之间存在着多对多的关系,并且他们之间的关系比较复杂,如果在同一个用例图中表述所有的参与者和用例就显得不够清晰,这时我们可创建多个用例图来分别表示各种关系。



如果想要强调某一个参与者和多个用例的关系,你就可以以该参与者为中心,用一个用例图表述出该参与者和多个用例之间的关系。在这个用例图中,我们强调的是该参与者会使用系统所提供的哪些服务。



如果想要强调某一个用例和多个参与者之间的关系,你就可以以该用例为中心,用一个用例图表述出该用例和多个参与者之间的关系。在这个用例图中,我们强调的是该用例会涉及到哪些参与者,或者说该用例所表示的系统服务有哪些使用者。



总之在用例建模过程中,你可以根据自己的需要创建任意多个用例图,用不同的用例来强调参与者和用例之间不同的关系。但是最重要的是要考虑整个用例模型的可理解性,如果可以用一个用例图把意思表述清楚,就不要再用第二个,因为越是简洁的模型越易于理解。



参考资料

  • 样例代码



  • Jeffrey Friedl, Mastering Regular Expressions, O'Reilly

  • Mendel Cooper, Advanced Bash-Scripting Guide

  • Michael Jang, Mastering Redhat 9


关于作者

 

傅纯一,IBM中国有限公司软件部Rational中国区技术销售经理

posted @ 2009-03-10 12:04 二胡 阅读(165) | 评论 (0)编辑 收藏

转  http://www.jdon.com/designpatterns/prototype.htm

原型模式定义:
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象.

Prototype模式允许一个对象再创建另外一个可定制的对象,根本无需知道任何如何创建的细节,工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建。

如何使用?
因为Java中的提供clone()方法来实现对象的克隆,所以Prototype模式实现一下子变得很简单.

以勺子为例:

public abstract class AbstractSpoon implements Cloneable
{
  String spoonName;

  public void setSpoonName(String spoonName) {this.spoonName = spoonName;}
  public String getSpoonName() {return this.spoonName;}

  public Object clone()
  {
    Object object = null;
    try {
      object = super.clone();
    } catch (CloneNotSupportedException exception) {
      System.err.println("AbstractSpoon is not Cloneable");
    }
    return object;
  }
}

有个具体实现(ConcretePrototype):

public class SoupSpoon extends AbstractSpoon
{
  public SoupSpoon()
  {
    setSpoonName("Soup Spoon");
  }
}

 

调用Prototype模式很简单:

AbstractSpoon spoon = new SoupSpoon();
AbstractSpoon spoon2 = spoon.clone();

当然也可以结合工厂模式来创建AbstractSpoon实例。

在Java中Prototype模式变成clone()方法的使用,由于Java的纯洁的面向对象特性,使得在Java中使用设计模式变得很自然,两者已经几乎是浑然一体了。这反映在很多模式上,如Interator遍历模式。

posted @ 2009-03-09 11:00 二胡 阅读(142) | 评论 (0)编辑 收藏

转 http://www.jdon.com/designpatterns/template.htm

Template模板模式定义:
定义一个操作中算法的骨架,将一些步骤的执行延迟到其子类中.

使用Java的抽象类时,就经常会使用到Template模式,因此Template模式使用很普遍.而且很容易理解和使用。

 

public abstract class Benchmark
{
  /**
  * 下面操作是我们希望在子类中完成
  */
  public abstract void benchmark();

  /**
  * 重复执行benchmark次数
  */
  public final long repeat (int count) {
    if (count <= 0)
      return 0;
    else {
      long startTime = System.currentTimeMillis();

    for (int i = 0; i < count; i++)
      benchmark();

    long stopTime = System.currentTimeMillis();
    return stopTime - startTime;
  }
}
}

在上例中,我们希望重复执行benchmark()操作,但是对benchmark()的具体内容没有说明,而是延迟到其子类中描述:

public class MethodBenchmark extends Benchmark
{
  /**
  * 真正定义benchmark内容
  */
  public void benchmark() {

    for (int i = 0; i < Integer.MAX_VALUE; i++){
      System.out.printtln("i="+i);    
    }
  }
}

至此,Template模式已经完成,是不是很简单?

我们称repeat方法为模板方法, 它其中的benchmark()实现被延迟到子类MethodBenchmark中实现了,

看看如何使用:

Benchmark operation = new MethodBenchmark();
long duration = operation.repeat(Integer.parseInt(args[0].trim()));
System.out.println("The operation took " + duration + " milliseconds");

 

也许你以前还疑惑抽象类有什么用,现在你应该彻底明白了吧? 至于这样做的好处,很显然啊,扩展性强,以后Benchmark内容变化,我只要再做一个继承子类就可以,不必修改其他应用代码.

posted @ 2009-03-09 10:55 二胡 阅读(131) | 评论 (0)编辑 收藏

转 http://www.cnblogs.com/myhsg/archive/2008/08/05/1261386.html

1. GROUP BY 是分组查询, 一般 GROUP BY 是和聚合函数配合使用

group by 有一个原则,就是 select 后面的所有列中,没有使用聚合函数的列,必须出现在 group by 后面(重要)

例如,有如下数据库表:

A    B
1    abc
1    bcd

1    asdfg

 如果有如下查询语句(该语句是错误的,原因见前面的原则

select A,B from table group by A 

该查询语句的意图是想得到如下结果(当然只是一相情愿)

A     B
       abc
1     bcd

       asdfg 

右边3条如何变成一条,所以需要用到聚合函数,如下(下面是正确的写法):

select A,count(B) as 数量 from table group by A
这样的结果就是
A    数量
1    3 

 

2. Having

where 子句的作用是在对查询结果进行分组前,将不符合where条件的行去掉,即在分组之前过滤数据,条件中不能包含聚组函数,使用where条件显示特定的行

having 子句的作用是筛选满足条件的组,即在分组之后过滤数据条件中经常包含聚组函数,使用having 条件显示特定的组,也可以使用多个分组标准进行分组。

having 子句被限制子已经在SELECT语句中定义的列和聚合表达式上。通常,你需要通过在HAVING子句中重复聚合函数表达式来引用聚合值,就如你在SELECT语句中做的那样。例如:

SELECT A COUNT(B) FROM TABLE GROUP BY A HAVING COUNT(B)>2

posted @ 2009-03-08 10:02 二胡 阅读(509) | 评论 (0)编辑 收藏

本文作者:sodme
本文出处:http://blog.csdn.net/sodme
声明: 本文可以不经作者同意, 任意复制, 转载, 但任何对本文的引用都请保留文章开始前三行的作者, 出处以及声明信息. 谢谢.


前记:


我们作团队的目的是干什么?


当然不仅仅是为了建立一种貌似和谐的公司内部关系, 也不是为了弄一个具有专业背景的"高科技"哥们组织, 请一定要记住, 我们从始至终的目的, 只有一个: 作产品, 作更好的产品, 同众多的竞争对手在市场上正面交锋, 除此之外的对作团队的定位, 都有可能让你走入歧途而失去本该追求的东西.


"凝聚力"和"战斗力", 是决定一个团队可以作成一件事的两个最基本因素. 前文所述的"团队及团队成员的自我管理", 意在增强团队的"战斗力", 而本文所说的"分享", 其意义不仅仅在于可以增强战斗力, 也同时可以增强凝聚力. 而凝聚力, 是一个团队可以持久存在的重要支撑. 有了持久的团队, 才会有持久的产品, 有了持久的产品, 才可能有时间和机会让你对产品精益求精, 而精益求精, 是成就一个成功产品所必须的东西.


分享的出发点, 首先是在于"想帮助别人", 帮助那些可能与你遇到相同问题的人; 分享的出发点, 绝不应该是"炫耀", 含有这种心态的分享, 是一种变了味的分享, 也是很难让其他人信服和坦然接受的分享. 既然这种分享难以接受, 那它也理所应当失去了分享原来应该有的意义和效果.


互联网, 从产生到发展到壮大, 靠的就是两样东西: 开放和分享. 也所以, 身处这个充满了开放和分享大环境下的我们自己, 又有什么理由不在团队内部务实的培养分享的心态, 分享的方法, 以及分享的习惯呢?


这是一篇有关"培养分享心态"的作团队感悟, 其核心思想是: 任何人, 不论他贫富贵贱, 都可以分享, 只要他愿意分享, 即使他是个技术新人, 也一定有值得分享的东西, 所谓"三人行, 必有吾师". 作为团队, 应该努力培养每一个人都逐渐具备分享的心态, 要努力在团队内部形成一套可行的用于知识和经验分享的工作方法.  分享, 不仅仅包括着知识与经验, 有时, 甚至更多的, 也包括着利益分享. 一个作大事的人, 必须学会把利益分享出去. 对于员工, 我们希望他能以开放的心态更多地分享自己的知识和经验; 对于老板, 我们希望他能以开放的心态更多地分享自己的利益.


引入正题----


不愿分享的老板, 永远只可能是一个小老板;
而愿意分享的老板, 其获得的东西要比他分享出去的东西要多得多.

不愿分享的团队一员, 永远只可能是一个按步就搬的工作机器;
而愿意分享的团队一员, 其获得的将不仅仅是高效率的协作关系以及优秀的工作成果, 还有现今社会已非常难得的"情义"二字.

分享, 不仅仅是领导者一个人的事, 也是普通的团队成员要学会的协作方法.

一个人, 并不是只有在自己富了之后才能够才可以分享, 愿意分享的心态, 来源于长久以来形成的处事观, 而不是一时冲动或者一时的急功近利.


一个人, 并不是在成为顶级技术高手之后才可以分享, 愿意分享的心态, 可以让你更快找出自己的短板, 也可以让你更快融入一个团队.

一个人, 可以分享的东西, 不仅仅只有金钱和利益, 甚至可以广而泛之包括你的时间, 你的精力, 以及你投入在事业上的感情.


一个建议, 可以分享;
一个创意, 可以分享;
甚至, 一个BUG的修改过程, 也可以分享.


再实际一点的话:
一包烟, 可以分享;
一瓶酒, 可以分享;
一顿饭, 可以分享。
这些, 虽然这些可能与团队无关, 但却能很好的帮助你与他人建立顺畅交流的基础环境.

虽然在某些人看来有点俗气, 但却是一个团队慢慢由攀比走向融洽, 由松散走向凝聚很实在可行的一条路.


有的人, 自命清高, 说自己从来不搞吃饭喝酒这一套也照样可以作好工作, 是, 没错, 但要看你想作一个什么样的团队.


不与团队成员打成一片的领导者, 总让团队成员感觉中间隔了一层什么东西, 可能会被随时背叛, 也可能会被经常误会.


沟通, 便在于这些细节处.


要想降低沟通成本, 最主要的, 是让团队成员充分信任并理解你.


而要达到充分信任与理解, 分享, 是必须要作的一件事, 最起码, 你要学会分享自己的时间和精力, 当然, 这是在你完成自己工作的前提下.

当然, 作一个团队, 肯定不能整天分享吃分享喝.

在其他的团队成员出现困难时, 你愿意把自己的时间和精力分享给他, 跟他讨论解决方法, 为他出出主意, 这也是另一种形式的分享, 也是比吃喝更重要的分享. 友谊的可贵, 体现在他人最需要的时候, 你是不是适时的伸出援手.


对于知识与经验的分享, 我按以下方式排序:
1. 文档形式的分享, 是效果最差的分享;
2. IM, 邮件类的分享, 是效果次差的分享;
3. 电话再次之;
4. 面对面的交流, 效果较好;
5. 带着他人一起作, 一起协同工作, 在具体实践中的分享, 是一种让人记忆最深刻, 效果也最好的分享.


分享, 不仅仅包括着知识与经验, 有时, 甚至更多的, 也包括着利益分享. 一个作大事的人, 必须学会把利益分享出去.

而作为老板或领导者, 愿意分享的心态就显得更加重要, 是直接影响团队稳定的大事. 当然, 这里的分享, 更多的是指利益的分享.

牛根生有句很著名的话: "财聚人散, 财散人聚". 当你散一散自己的钱财时, 大家会更愿意跟着你作事, 即使当你不如意时, 如果你之前一直坚持分享的心态, 你的团队也不会离开你, 因为, 他们相信, 只要你有吃的, 你就会分给他们一口. 这些看似简单的道理, 很多的领导者却不一定能作到. 所以, 从另一个方面说, 能遇到拥有良好分享心态的上司是一件幸事.

象之前文章提到的这三个人: 马云, 史玉柱, 牛根生, 每一个人都有很好的分享的心态, 他们愿意并切实执行着将自己的财富分享给团队成员, 所以, 他们的团队才有了最现实而有效的存在基础, 团队成员才一跟就是八年十年.

马云本人在阿里巴巴上市公司里的股份只有10%的股份, 他的18个创业伙伴同样分得了股份, 马云没有控股;

史玉柱, 虽然没有给团队核心更多的股份, 但他是通过给现金的方式变向分享, 与马云相比, 既跟团队分享了财富, 同时也掌握了公司的控制权, 不得不说是一种比较聪明的作法;

牛根生, 打小时就经常把家里的东西分享给自己的小伙伴, 而作蒙牛时, 又自述种种待遇不如副手. 拿工资时, 将工资分享出去; 作企业时, 将股份分享出去. 在我看来, 这不是炒作, 而是一种很务实的心态, 也是一种很有效的团队管理手段.

在充分自主快乐的作事和获取高额回报之间作选择时, 如果是二选一, 可能不少人会在两者徘徊, 有些人选择拥有更多自主权, 而有些人宁愿暂时委屈一下自己多挣点钱. 而如果, 我们给他提供了两者皆可的选择, 我觉得团队成员除非有不可抗力的因素才可能离开团队, 除此之外, 他会非常死心踏地的跟着你干.

人, 是一切.

得人心者, 得天下.

只要有了人, 你可以作任何事.
只要得了人心, 你可以干成任何一件事.

一个不愿意分享的领导者, 无论他说得再天花乱坠, 团队成员也会认为那是他自己的事业, 而不是我的, 团队成员也就不可能始终跟着他打天下. 分享, 不仅仅是一种姿态, 更是一种作大事的正确心态.

posted @ 2009-03-07 16:20 二胡 阅读(153) | 评论 (0)编辑 收藏

转:http://hi.baidu.com/delphi_relive/blog/item/d7c0034a49c4932208f7ef21.html

in 和 exists也是很好区别的.

in 是一个集合运算符.

a in {a,c,d,s,d....}

这个运算中,前面是一个元素,后面是一个集合,集合中的元素类型是和前面的元素一样的.

而exists是一个存在判断,如果后面的查询中有结果,则exists为真,否则为假.

in 运算用在语句中,它后面带的select 一定是选一个字段,而不是select *.

比如说你要判断某班是否存在一个名为"小明"的学生,你可以用in 运算:

"小明" in (select sname from student)

这样(select sname from student) 返回的是一个全班姓名的集合,in用于判断"小明"是否为此集合中的一个数据;

同时,你也可以用exists语句:

exists (select * from student where sname="小明")

 

这两个涵数是差不多的, 但是由于优化方案的不同, 通常NOT EXISTS要比NOT IN 要快, 因为NOT EXISTS可以使用结合算法而NOT IN 就不行了,而EXISTS则不如IN快, 因为这时候IN可能更多的使用结合算法.

 

select * from 表A where exists(select * from 表B where 表B.id=表A.id)

这句相当于

select * from 表A where id in (select id from 表B)


对于表A的每一条数据,都执行select * from 表B where 表B.id=表A.id的存在性判断,如果表B中存在表A当前行相同的id,则exists为真,该行显示,否则不显示


exits适合内小外大的查询,in适合内大外小的查询

 

IN
确定给定的值是否与子查询或列表中的值相匹配。

EXISTS
指定一个子查询,检测行的存在。

比较使用 EXISTS 和 IN 的查询

这个例子比较了两个语义类似的查询。第一个查询使用 EXISTS 而第二个查询使用 IN。注意两个查询返回相同的信息。

USE pubs
GO
SELECT DISTINCT pub_name
FROM publishers
WHERE EXISTS
(SELECT *
FROM titles
WHERE pub_id = publishers.pub_id
AND type = 'business')
GO

-- Or, using the IN clause:

USE pubs
GO
SELECT distinct pub_name
FROM publishers
WHERE pub_id IN
(SELECT pub_id
FROM titles
WHERE type = 'business')
GO

下面是任一查询的结果集:

pub_name
----------------------------------------
Algodata Infosystems
New Moon Books

(2 row(s) affected)

 

exits 相当于存在量词:表示集合存在,也就是集合不为空只作用一个集合.例如 exist P 表示P不空时为真; not exist P表示p为空时 为真 in表示一个标量和一元关系的关系。例如:s in P表示当s与P中的某个值相等时 为真; s not in P 表示s与P中的每一个值都不相等时 为真

posted @ 2009-03-07 15:56 二胡 阅读(4873) | 评论 (3)编辑 收藏

2者的概念上的不同,网上有很多文章.我从代码实现上说说2者的不同
  关联:如果A类的有个属性是B类型,那么A和B就是关联关系(当然关联也分了几种:单向关联,双向关联)
  依赖:如果A类的方法的参数或方法里的局部变量引用了B类,则是A依赖B
  
  不对地方,大家说说!
posted @ 2009-03-06 11:25 二胡 阅读(222) | 评论 (0)编辑 收藏

      这2个概念不是很清楚,和朋友讨论了一下,把结果整理出来,希望对你有用!
组合: 被拥有者是拥有者的一个组成部分.被拥有者的生命周期和拥有者的有强相关关系.也就是说当拥有者生命周期结束的时候,被拥有者也消亡了.常用于黑色实心菱形表示.
      例如:人死了,毛发也不存在了.
聚合:表示事物的整体/部分关系的较弱情况,常用于空心实心菱形表示


      个人认为:做设计的时候,不要专注细节.呵呵,不知道观点对否?
posted @ 2009-03-06 11:07 二胡 阅读(461) | 评论 (0)编辑 收藏

转 http://hi.baidu.com/platon/blog/item/64a20ff3f96e7fce0b46e031.html


ervlet/JSP技术和ASP、PHP等相比,由于其多线程运行而具有很高的执行效率。由于Servlet/JSP默认是以多线程模式执行的,所以,在编写代码时需要非常细致地考虑多线程的安全性问题。然而,很多人编写Servlet/JSP程序时并没有注意到多线程安全性的问题,这往往造成编写的程序在少量用户访问时没有任何问题,而在并发用户上升到一定值时,就会经常出现一些莫明其妙的问题。

  Servlet的多线程机制
 
  Servlet体系结构是建立在Java多线程机制之上的,它的生命周期是由Web容器负责的。当客户端第一次请求某个Servlet 时,Servlet容器将会根据web.xml配置文件实例化这个Servlet类。当有新的客户端请求该Servlet时,一般不会再实例化该 Servlet类,也就是有多个线程在使用这个实例。Servlet容器会自动使用线程池等技术来支持系统的运行,如图1所示。


图1 Servlet线程池

  这样,当两个或多个线程同时访问同一个Servlet时,可能会发生多个线程同时访问同一资源的情况,数据可能会变得不一致。所以在用Servlet构建的Web应用时如果不注意线程安全的问题,会使所写的Servlet程序有难以发现的错误。

  Servlet的线程安全问题

  Servlet的线程安全问题主要是由于实例变量使用不当而引起的,这里以一个现实的例子来说明。

Import javax.servlet. *;
Import javax.servlet.http. *;
Import java.io. *;
Public class Concurrent Test extends HttpServlet {PrintWriter output;
Public void service (HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {String username;
Response.setContentType ("text/html; charset=gb2312");
Username = request.getParameter ("username");
Output = response.getWriter ();
Try {Thread. sleep (5000); //为了突出并发问题,在这设置一个延时
} Catch (Interrupted Exception e){}
output.println("用户名:"+Username+"<BR>");
}
}

  该Servlet中定义了一个实例变量output,在service方法将其赋值为用户的输出。当一个用户访问该Servlet时,程序会正常的运行,但当多个用户并发访问时,就可能会出现其它用户的信息显示在另外一些用户的浏览器上的问题。这是一个严重的问题。为了突出并发问题,便于测试、观察,我们在回显用户信息时执行了一个延时的操作。假设已在web.xml配置文件中注册了该Servlet,现有两个用户a和b同时访问该Servlet(可以启动两个IE浏览器,或者在两台机器上同时访问),即同时在浏览器中输入:

  a: http://localhost: 8080/servlet/ConcurrentTest? Username=a

  b: http://localhost: 8080/servlet/ConcurrentTest? Username=b

  如果用户b比用户a回车的时间稍慢一点,将得到如图2所示的输出:


图2 a用户和b用户的浏览器输出

  从图2中可以看到,Web服务器启动了两个线程分别处理来自用户a和用户b的请求,但是在用户a的浏览器上却得到一个空白的屏幕,用户a的信息显示在用户b的浏览器上。该Servlet存在线程不安全问题。下面我们就从分析该实例的内存模型入手,观察不同时刻实例变量output的值来分析使该 Servlet线程不安全的原因。

  Java的内存模型JMM(Java Memory Model)JMM 主要是为了规定了线程和内存之间的一些关系。根据JMM的设计,系统存在一个主内存(Main Memory),Java中所有实例变量都储存在主存中,对于所有线程都是共享的。每条线程都有自己的工作内存(Working Memory),工作内存由缓存和堆栈两部分组成,缓存中保存的是主存中变量的拷贝,缓存可能并不总和主存同步,也就是缓存中变量的修改可能没有立刻写到主存中;堆栈中保存的是线程的局部变量,线程之间无法相互直接访问堆栈中的变量。根据JMM,我们可以将论文中所讨论的Servlet实例的内存模型抽象为图3所示的模型。


图3 Servlet实例的JMM模型

  下面根据图3所示的内存模型,来分析当用户a和b的线程(简称为a线程、b线程)并发执行时,Servlet实例中所涉及变量的变化情况及线程的执行情况,如图4所示。

调度时刻 a线程 b线程
T1 访问Servlet页面
T2 访问Servlet页面
T3 output=a的输出username=a休眠5000毫秒,让出CPU
T4 output=b的输出(写回主存)username=b休眠5000毫秒,让出CPU
T5 在用户b的浏览器上输出a线程的username的值,a线程终止。
T6 在用户b的浏览器上输出b线程的username的值,b线程终止。
                  图4 Servlet实例的线程调度情况

  从图4中可以清楚的看到,由于b线程对实例变量output的修改覆盖了a线程对实例变量output的修改,从而导致了用户a的信息显示在了用户b 的浏览器上。如果在a线程执行输出语句时,b线程对output的修改还没有刷新到主存,那么将不会出现图2所示的输出结果,因此这只是一种偶然现象,但这更增加了程序潜在的危险性。
设计线程安全的Servlet

  通过上面的分析,我们知道了实例变量不正确的使用是造成Servlet线程不安全的主要原因。下面针对该问题给出了三种解决方案并对方案的选取给出了一些参考性的建议。

  1、实现 SingleThreadModel 接口

  该接口指定了系统如何处理对同一个Servlet的调用。如果一个Servlet被这个接口指定,那么在这个Servlet中的service方法将不会有两个线程被同时执行,当然也就不存在线程安全的问题。这种方法只要将前面的Concurrent Test类的类头定义更改为:

Public class Concurrent Test extends HttpServlet implements SingleThreadModel {
…………
}

  2、同步对共享数据的操作

  使用synchronized 关键字能保证一次只有一个线程可以访问被保护的区段,在本论文中的Servlet可以通过同步块操作来保证线程的安全。同步后的代码如下:

…………
Public class Concurrent Test extends HttpServlet { …………
Username = request.getParameter ("username");
Synchronized (this){
Output = response.getWriter ();
Try {
Thread. Sleep (5000);
} Catch (Interrupted Exception e){}
output.println("用户名:"+Username+"<BR>");
}
}
}

  3、避免使用实例变量

  本实例中的线程安全问题是由实例变量造成的,只要在Servlet里面的任何方法里面都不使用实例变量,那么该Servlet就是线程安全的。

  修正上面的Servlet代码,将实例变量改为局部变量实现同样的功能,代码如下:

……
Public class Concurrent Test extends HttpServlet {public void service (HttpServletRequest request, HttpServletResponse
Response) throws ServletException, IOException {
Print Writer output;
String username;
Response.setContentType ("text/html; charset=gb2312");
……
}
}

  对上面的三种方法进行测试,可以表明用它们都能设计出线程安全的Servlet程序。但是,如果一个Servlet实现了 SingleThreadModel接口,Servlet引擎将为每个新的请求创建一个单独的Servlet实例,这将引起大量的系统开销。 SingleThreadModel在Servlet2.4中已不再提倡使用;同样如果在程序中使用同步来保护要使用的共享的数据,也会使系统的性能大大下降。这是因为被同步的代码块在同一时刻只能有一个线程执行它,使得其同时处理客户请求的吞吐量降低,而且很多客户处于阻塞状态。另外为保证主存内容和线程的工作内存中的数据的一致性,要频繁地刷新缓存,这也会大大地影响系统的性能。所以在实际的开发中也应避免或最小化 Servlet 中的同步代码;在Serlet中避免使用实例变量是保证Servlet线程安全的最佳选择。从Java 内存模型也可以知道,方法中的临时变量是在栈上分配空间,而且每个线程都有自己私有的栈空间,所以它们不会影响线程的安全。

  小结

  Servlet的线程安全问题只有在大量的并发访问时才会显现出来,并且很难发现,因此在编写Servlet程序时要特别注意。线程安全问题主要是由实例变量造成的,因此在Servlet中应避免使用实例变量。如果应用程序设计无法避免使用实例变量,那么使用同步来保护要使用的实例变量,但为保证系统的最佳性能,应该同步可用性最小的代码路径。因为Struts的Action被设计为线程不安全的,所以也涉及到这个问题,所以也使用同样的方法来解决!
posted @ 2009-02-26 16:40 二胡 阅读(177) | 评论 (0)编辑 收藏

转  http://blog.csdn.net/ilibaba/archive/2009/02/06/3866537.aspx

提起Java内部类(Inner Class)可能很多人不太熟悉,实际上类似的概念在C++里也有,那就是嵌套类(Nested Class),关于这两者的区别与联系,在下文中会有对比。内部类从表面上看,就是在类中又定义了一个类(下文会看到,内部类可以在很多地方定义),而实际上并没有那么简单,乍看上去内部类似乎有些多余,它的用处对于初学者来说可能并不是那么显著,但是随着对它的深入了解,你会发现Java的设计者在内部类身上的确是用心良苦。学会使用内部类,是掌握Java高级编程的一部分,它可以让你更优雅地设计你的程序结构。下面从以下几个方面来介绍:
第一次见面
  1. public interface Contents {  
  2.     int value();  
  3. }  
  4. public interface Destination {  
  5.     String readLabel();  
  6. }  
  7. public class Goods {  
  8.     private class Content implements Contents {  
  9.         private int i = 11;  
  10.         public int value() {  
  11.             return i;  
  12.         }  
  13.     }  
  14.     protected class GDestination implements Destination {  
  15.         private String label;  
  16.         private GDestination(String whereTo) {  
  17.             label = whereTo;  
  18.         }  
  19.         public String readLabel() {  
  20.             return label;  
  21.         }  
  22.     }  
  23.     public Destination dest(String s) {  
  24.         return new GDestination(s);  
  25.     }  
  26.     public Contents cont() {  
  27.         return new Content();  
  28.     }  
  29. }  
  30. class TestGoods {  
  31.     public static void main(String[] args) {  
  32.         Goods p = new Goods();  
  33.         Contents c = p.cont();  
  34.         Destination d = p.dest("Beijing");  
  35.     }  
  36. }  
 
在这个例子里类Content和GDestination被定义在了类Goods内部,并且分别有着protected和private修饰符来控制访问级别。Content代表着Goods的内容,而GDestination代表着Goods的目的地。它们分别实现了两个接口Content和 Destination。在后面的main方法里,直接用 Contents c和Destination d进行操作,你甚至连这两个内部类的名字都没有看见!这样,内部类的第一个好处就体现出来了 隐藏你不想让别人知道的操作,也即封装性。
同时,我们也发现了在外部类作用范围之外得到内部类对象的第一个方法,那就是利用其外部类的方法创建并返回。上例中的cont()和dest()方法就是这么做的。那么还有没有别的方法呢?当然有,其语法格式如下:
outerObject=new outerClass(Constructor Parameters);
outerClass.innerClass innerObject=outerObject.new InnerClass(Constructor Parameters);
注意在创建非静态内部类对象时,一定要先创建起相应的外部类对象。至于原因,也就引出了我们下一个话题 非静态内部类对象有着指向其外部类对象的引用,对刚才的例子稍作修改:
  1. public class Goods {  
  2.     private int valueRate = 2;  
  3.     private class Content implements Contents {  
  4.         private int i = 11 * valueRate;  
  5.         public int value() {  
  6.             return i;  
  7.         }  
  8.     }  
  9.     protected class GDestination implements Destination {  
  10.         private String label;  
  11.         private GDestination(String whereTo) {  
  12.             label = whereTo;  
  13.         }  
  14.         public String readLabel() {  
  15.             return label;  
  16.         }  
  17.     }  
  18.     public Destination dest(String s) {  
  19.         return new GDestination(s);  
  20.     }  
  21.     public Contents cont() {  
  22.         return new Content();  
  23.     }  
  24. }  

在这里我们给Goods类增加了一个private成员变量valueRate,意义是货物的价值系数,在内部类Content的方法value()计算价值时把它乘上。我们发现,value()可以访问valueRate,这也是内部类的第二个好处一个内部类对象可以访问创建它的外部类对象的内容,甚至包括私有变量!这是一个非常有用的特性,为我们在设计时提供了更多的思路和捷径。要想实现这个功能,内部类对象就必须有指向外部类对象的引用。Java编译器在创建内部类对象时,隐式的把其外部类对象的引用也传了进去并一直保存着。这样就使得内部类对象始终可以访问其外部类对象,同时这也是为什么在外部类作用范围之外向要创建内部类对象必须先创建其外部类对象的原因。
有人会问,如果内部类里的一个成员变量与外部类的一个成员变量同名,也即外部类的同名成员变量被屏蔽了,怎么办?没事,Java里用如下格式表达外部类的引用:
outerClass.this
有了它,我们就不怕这种屏蔽的情况了。
静态内部类
和普通的类一样,内部类也可以有静态的。不过和非静态内部类相比,区别就在于静态内部类没有了指向外部的引用。这实际上和C++中的嵌套类很相像了,Java内部类与C++嵌套类最大的不同就在于是否有指向外部的引用这一点上,当然从设计的角度以及以它一些细节来讲还有区别。
除此之外,在任何非静态内部类中,都不能有静态数据,静态方法或者又一个静态内部类(内部类的嵌套可以不止一层)。不过静态内部类中却可以拥有这一切。这也算是两者的第二个区别吧。
局部内部类
是的,Java内部类也可以是局部的,它可以定义在一个方法甚至一个代码块之内。
  1. public class Goods1 {  
  2.     public Destination dest(String s) {  
  3.         class GDestination implements Destination {  
  4.             private String label;  
  5.             private GDestination(String whereTo) {  
  6.                 label = whereTo;  
  7.             }  
  8.             public String readLabel() {  
  9.                 return label;  
  10.             }  
  11.         }  
  12.         return new GDestination(s);  
  13.     }  
  14.     public static void main(String[] args) {  
  15.         Goods1 g = new Goods1();  
  16.         Destination d = g.dest("Beijing");  
  17.     }  
  18. }  

上面就是这样一个例子。在方法dest中我们定义了一个内部类,最后由这个方法返回这个内部类的对象。如果我们在用一个内部类的时候仅需要创建它的一个对象并创给外部,就可以这样做。当然,定义在方法中的内部类可以使设计多样化,用途绝不仅仅在这一点。
下面有一个更怪的例子:
  1. public class Goods2 {  
  2.     private void internalTracking(boolean b) {  
  3.         if (b) {  
  4.             class TrackingSlip {  
  5.                 private String id;  
  6.                 TrackingSlip(String s) {  
  7.                     id = s;  
  8.                 }  
  9.                 String getSlip() {  
  10.                     return id;  
  11.                 }  
  12.             }  
  13.             TrackingSlip ts = new TrackingSlip("slip");  
  14.             String s = ts.getSlip();  
  15.         }  
  16.     }  
  17.     public void track() {  
  18.         internalTracking(true);  
  19.     }  
  20.     public static void main(String[] args) {  
  21.         Goods2 g = new Goods2();  
  22.         g.track();  
  23.     }  
  24. }  

你不能在if之外创建这个内部类的对象,因为这已经超出了它的作用域。不过在编译的时候,内部类TrackingSlip和其他类一样同时被编译,只不过它由它自己的作用域,超出了这个范围就无效,除此之外它和其他内部类并没有区别。
匿名内部类
java的匿名内部类的语法规则看上去有些古怪,不过如同匿名数组一样,当你只需要创建一个类的对象而且用不上它的名字时,使用内部类可以使代码看上去简洁清楚。它的语法规则是这样的:
new interfacename(){......}; 或 new superclassname(){......};
下面接着前面继续举例子:
  1. public class Goods3 {  
  2.     public Contents cont() {  
  3.         return new Contents() {  
  4.             private int i = 11;  
  5.             public int value() {  
  6.                 return i;  
  7.             }  
  8.         };  
  9.     }  
  10. }  

这里方法cont()使用匿名内部类直接返回了一个实现了接口Contents的类的对象,看上去的确十分简洁。
在java的事件处理的匿名适配器中,匿名内部类被大量的使用。例如在想关闭窗口时加上这样一句代码:
  1. frame.addWindowListener(new WindowAdapter(){  
  2.     public void windowClosing(WindowEvent e){  
  3.        System.exit(0);  
  4.     }  
  5. });   

有一点需要注意的是,匿名内部类由于没有名字,所以它没有构造函数(但是如果这个匿名内部类继承了一个只含有带参数构造函数的父类,创建它的时候必须带上这些参数,并在实现的过程中使用super关键字调用相应的内容)。如果你想要初始化它的成员变量,有下面几种方法:
如果是在一个方法的匿名内部类,可以利用这个方法传进你想要的参数,不过记住,这些参数必须被声明为final。
将匿名内部类改造成有名字的局部内部类,这样它就可以拥有构造函数了。
在这个匿名内部类中使用初始化代码块。
为什么需要内部类?
java内部类有什么好处?为什么需要内部类?
首先举一个简单的例子,如果你想实现一个接口,但是这个接口中的一个方法和你构想的这个类中的一个方法的名称,参数相同,你应该怎么办?这时候,你可以建一个内部类实现这个接口。由于内部类对外部类的所有内容都是可访问的,所以这样做可以完成所有你直接实现这个接口的功能。
不过你可能要质疑,更改一下方法的不就行了吗?
的确,以此作为设计内部类的理由,实在没有说服力。
真正的原因是这样的,java中的内部类和接口加在一起,可以的解决常被C++程序员抱怨java中存在的一个问题 没有多继承。实际上,C++的多继承设计起来很复杂,而java通过内部类加上接口,可以很好的实现多继承的效果。
posted @ 2009-02-19 15:08 二胡 阅读(960) | 评论 (1)编辑 收藏

转:http://www.blogjava.net/soddabao/archive/2007/06/17/124809.html
做过跨越多个网站的Ajax开发的朋友都知道,如果在A网站中,我们希望使用Ajax来获得B网站中的特定内容,如果A网站与B网站不在同一个域中,那么就出现了跨域访问问题。

      Ajax的跨域访问问题是现有的Ajax开发人员比较常遇到的问题。
      IE对于跨域访问的处理是,弹出警告框,提醒用户。如果用户将该网站纳入可信任网站,或者调低安全级别,那么这个问题IE就不会在提醒你。  
         FireFox等其它非微软的浏览器遇到跨域访问,则解决方案统一是拒绝访问。
         有 人说,IE是主流浏览器,只要它能正常使用就好了。此言差已,IE虽然能够处理,但是是有前提的,要么用户不厌其烦地在页面弹出警告框之后点击是(点击否 就不执行该Ajax调用了),要么用户将该网站纳入可信任站点。这两种做法,在企业管理系统的应用中倒是比较常见,因为系统管理员可以以行政手段保证用户 的行为。但是对于互联网上的网站或者门户开发,这种做法则不行。
         最近遇到了这个问题,需要在跨域访问结束之后完成使主窗口出现一些特效,搜索了一些资料,通过不断尝试以及在不同浏览器中进行兼容性测试,找到了几个可行的方案:
         1、 Web代理的方式。即用户访问A网站时所产生的对B网站的跨域访问请求均提交到A网站的指定页面,由该页面代替用户页面完成交互,从而返回合适的结果。此 方案可以解决现阶段所能够想到的多数跨域访问问题,但要求A网站提供Web代理的支持,因此A网站与B网站之间必须是紧密协作的,且每次交互过程,A网站 的服务器负担增加,且无法代用户保存session状态。
         2、on-Demand方式。MYMSN的门户就用的这种方式,不过 MYMSN中不涉及跨域访问问题。动态控制script标记的生成,通过修改script标记的src属性完成对跨域页面的调用。此方案存在的缺陷 是,script的src属性完成该调用时采取的方式时get方式,如果请求时传递的字符串过大时,可能会无法正常运行。不过此方案非常适合聚合类门户使 用。
         3、iframe方式。查看过醒来在javaeye上的一篇关于跨域访问的帖子,他提到自己已经用iframe的方式解决 了跨域访问问题。数据提交跟获取,采用iframe这种方式的确可以了,但由于父窗口与子窗口之间不能交互(跨域访问的情况下,这种交互被拒绝),因此无 法完成对父窗口效果的影响。
         4、用户本地转储方式:IE本身依附于windows平台的特性为我们提供了一种基于 iframe,利用内存来“绕行”的方案,即两个window之间可以在客户端通过windows剪贴板的方式进行数据传输,只需要在接受数据的一方设置 Interval进行轮询,获得结果后清除Interval即可。FF的平台独立性决定了它不支持剪贴板这种方式,而以往版本的FF中存在的插件漏洞又被 fixed了,所以FF无法通过内存来完成暗渡陈仓。而由于文件操作FF也没有提供支持(无法通过Cookie跨域完成数据传递),致使这种技巧性的方式 只能在IE中使用。
         5、我自己用于解决这类问题的方式:结合了前面几种方式,在访问A网站时,先请求B网站完成数据处理,再根 据返回的标识来获得所需的结果。这种方法的缺点也很明显,B网站的负载增大了。优点,对session也实现了保持,同时A网站与B网站页面间的交互能力 增强了。最重要的一点,这种方案满足了我的全部需要.



posted @ 2009-02-17 14:23 二胡 阅读(152) | 评论 (0)编辑 收藏

     电子商务系统,站内查询是一个基本功能。但是开发一个高效的站内查询系统也不是件容易的事!好多站内查询系统都是通过lucene的2次开发来实现的。在站内查询系统开发完成之前,调用google的站内查询也是一个办法。
     前几天,查找了相关资料,把其功能写成了一个函数方便调用,贴出来,希望对大家有用!

/**
*@description google查询对象
*@param sitesearch 搜索的指定网站或指定域名
*@param ie 发送搜索词的网页编码,预设值为"UTF-8",由于在中国很多网站是使用 GB2312 编码
*@param oe 显示搜索的网页编码,预设值为"UTF-8",由于现代大部分机器都能支持"UTF-8"无误,因此你大概不需要调整次参数;
*@param hl 显示搜索的网页语言。"zh-CN"为简体中文,"zh-TW"为繁体中文,"en"为英文。
*/

function GoogleSearch(sitesearch,ie,oe,hl){
         this.sitesearch=sitesearch;
         this.ie=ie;
         this.oe=oe;
         this.hl=hl;
}

GoogleSearch.prototype={
             init:function(){
                  var searchdiv_=createEle("DIV",{"style":"display:none;"});
                  var form_=createEle("FORM",{"action":"http://www.google.cn/search", "method":"get", "name":"gs","id":"gs"});
                  var sitesearch_=createEle("input",{"name":"sitesearch","value":this.sitesearch, "type":"hidden"});    
                  var hl_=createEle("INPUT",{"name":"hl", "value":this.hl,"type":"hidden"});
                  var ie_=createEle("INPUT",{"name":"ie","value":this.ie,"type":"hidden"});
                  var oe_=createEle("INPUT",{"name":"oe","value":this.oe,"type":"hidden"});
                  var query_=createEle("INPUT",{"name":"q","id":"q","type":"text"});
                
                  form_.appendChild(sitesearch_);   
                  form_.appendChild(hl_);   
                  form_.appendChild(ie_);   
                  form_.appendChild(oe_);   
                  form_.appendChild(query_);
                 
                  searchdiv_.appendChild(form_);   
                  document.body.appendChild(searchdiv_);
             },
             search:function(key){
                     if(document.getElementById("gs"))
                        this.init();
                     document.getElementById("q")["value"]=key;
                     document.getElementById("gs").submit()
             }
}

function createEle(tagname,obj){
         var ele=document.createElement(tagname);
         for(property in obj){
             ele.setAttribute(property,obj[property]);
         }
         return ele;
}

var GS=new  GoogleSearch("www.youwebsite.com","GB2312","UTF-8","zh-CN");
其中www.youwebsite.com,是你的网站域名;
调用方式很简单:GS.search("查询关键字");



参考:http://www.blogjava.net/hulizhong/archive/2009/02/12/254350.html


posted @ 2009-02-17 14:20 二胡 阅读(1486) | 评论 (2)编辑 收藏