文章列表
 
您正在查看 "软件开发" 分类下的文章

2011-01-02 14:57

千里之行,始于足下,无论你有多么的豪情万丈,总要从最基础的东西做起。

然而要做一个好的基层工作人员,并不是低头认认真真写好代码就可以的,其中可大有学问。


按照余世维所论,一个好的下属应该:

主动向上司汇报你的工作进度——让上司知道!
对上司的询问,有问必答,而且清楚——让上司放心!
充实自己,努力学习,才能了解上司的言语——让上司轻松!
接受批评,不犯两次过错——让上司省事!
不忙的时候,主动帮助他人——让上司有效!
毫无怨言的接受任务——让上司圆满!
对自己的业务,主动提出改善计划——让上司进步!

 

我也总结了如下几点,欢迎大家补充。

(1) 做得快还是做得好?

当前的项目管理中,多是强调结果的,号称结果导向或者结果驱动。

作为一个基层,做人重要,做事更重要,除了良好的沟通能力,能拿得出真金白银的成果,更是每个项目经理愿意看到的事情。

然而怎么叫好的结果呢?

一九五八年党的八大二次会议上,提出多快好省的建设社会主义。多快好省四个字即体现了前辈革命家的理想壮志,也成为后来中国管理者心中的梦。所以我们时常听到如下的话:"这些功能下个月一定要出来","代码质量要高,要有详细的注释,测试用例,code review","最好提前一周至少三天,可以准备demo","项目现在经费相对比较紧张,希望大家克服一下"。

然而现代的项目管理给我们画出了如下的三角形:

 

范围,预算,时间三者相互制约,牵一发而动全身。欲范围大(多),就应该增加项目预算(不省),如增加人手,增加资源,买第三方成品,或者应该延长时间(不快),如推迟release的时间等。欲按期完成(快),则可以增加预算(不省),或者减少功能(不多)。

然而现实中,老板可不这样想,预算是早就做好了的,时间也是确定了的,功能缺一不可,作为基层的程序员我们唯一可以影响的就是用加班换来更多的时间,当然还有中间的一个圆圈——项目的质量。

到底是尽快的做出一个实现基本功能但设计稍有缺陷,测试不太完备,有少量的Bug的版本出来然后慢慢改进呢,还是经过慢慢的精心设计,做出有完备的测试用例,经过严格测试少有Bug的版本呢?

这个问题如果你问程序员,大部分人会选择后者。尤其对于初涉职场,充满激情的程序员们,往往满脑设计模式,满口软件工程,几乎见不得注释中的错别字和没有覆盖到的测试边界,似乎一个不完美的方案就有辱于软件工程师的名号了,我们称之"技术洁癖"。

如果你问项目经理,也会告诉你后者,而且最好以后者的形式用前者的时间(多少有些多快好省的味道)。然而很少有项目经理会直接看你的代码,更不关心你用的何种设计模式,也不会一个个看你的测试用例来思考是否覆盖所有的边界,更不会看你写的注释了。

所以很不幸,除了少数精通技术,熟悉细节,了解程序员苦衷的项目经理外(这可是大多数程序员都翘首以待的领导啊),大部分喜欢前者。

因为精心的设计,良好的文档是需要大量的时间,完备的测试用例的代码量几倍于实现本身,功能测试,性能测试以及Bug的修改更是难以估计时间的,所以总的时间将几倍于前者的时间,在此过程中,你献给项目经理的,除了等待,还是等待。

人是不喜欢等待的,尤其是很少反馈的等待,当我们用windows的时候,往往会出现估计时间相当不准确的进度条,然而我们还是喜欢看着进度条一直走到底,同样项目经理也是。

总是能够很快的做出项目经理能够看到的版本,便容易给人一种能力很强的感觉,至少大部分人会这样认为。

也许你会说:我做的版本Bug很少,后期维护成本低,QA一测不就能够看出孰优孰劣来了吗?

仍然很不幸,在大多数人看来,你一个月做了一个稳定的版本被认为是做了一件事情,而别人两个星期做了一个不稳定的版本,后两个星期改了三个Bug是做了四件事情,而且其每次的会议总有进度可以说,成绩斐然。

而且一个人身上所挂着的Bug的个数,实在不是一件值得羞愧的事情,反而是一件令人感到荣耀的事情,这说明了你重担在身,举足轻重。

如果你做了一个模块,用了一个月,后来半年都不曾出过Bug,而另一个人做一个模块两个星期,出过多个Bug,并且后来兼任其他模块的时候还在改Bug,还是很不幸,你会被认为所做的模块相对简单,不容易出Bug,并随着项目的进行而被淡忘,会被这样提及:"他在半年前做过一个项目",而另一个人却会被认为所做的很复杂,有很多疑难杂症,而且后来会被认为身兼数职,会这样被提及:"随着能力的提升,同时维护并负责多个模块,有并行工作的能力,有很强的解决问题的能力"。

也许你觉得我说的太极端,那就举一个历史上的例子吧,有兴趣大家可以看李亚平的《前清秘史——入主中原之路》,其中是这样描写当时并称“南戚北李”的两位将军的:

李成梁屡立大功,受封为伯爵,跻身于帝国贵族行列。在当时,他的地位、名望等,很有可能已在戚继光之上。有一种看法,包括《明史》的作者们认为,恰恰因为戚继光威名太盛,坐镇蓟门十六年,使敌人从来不敢来犯(没有Bug啊),于是转道入侵辽东,才给了李成梁屡立战功的机会。张居正死后,新政被废,受到张居正支持重用的戚继光被迅速边缘化,在郁郁寡欢中死去(可能明朝认为蓟门这个模块不需要再维护了)。而李成梁,他同样受到过张居正的支持和倚重,然而,可能由于下面的三个原因:其一,远离京师,与张居正没有过多的私人交往;其二,赫赫战功给万历皇帝留下了深刻印象(每次总有进度可说);其三,动荡不安的辽东局势离不开这位骁将(辽东模块还需要维护啊)。从而,李成梁避免了池鱼之灾。

当前社会中,不是如此吗?是修了坚固的河堤的市长感动中国,还是和民众一起抗洪抢险的市长感动中国呢?是治理的地方路不拾遗的公安局长应该升职,还是让黑社会猖狂十年然后一举击溃的公安局长会升职呢?

如果你觉得你的项目经理英明过于历史甚至当朝首辅,那么恭喜你。

(2) 有问题要尽早喊

当一个模块或者一个任务交给你的时候,可能存在各种各样的困难,会出现各种各样的问题,需要各种各样的资源,这一切都应该慎重考虑后尽早提出。

问题的尽早提出,其实是风险控制的一种手段。

调配资源,排除干扰,风险控制是一个项目经理的重要责任之一,然而不要认为项目经理会英明神武到知道一切细节,也不要认为这是项目经理的事情,与你无关。其实一个模块,真正了解细节的是你。

所以对团队来讲,事先问题没有提出,到时候出现是你的甚至你的团队的责任,问题及早提出了,项目经理向相关人员请求资源,到时候没有解决就不是你的责任,甚至也不是你们团队的责任了。这个样子既帮助你的项目经理控制风险,又能够在外国人面前撇清责任,是每一个项目经理都欢迎的事情。

对你个人来讲,问题及早提出了,以后或有Bug,或有delay,都不会给人一种突然的感觉,也给项目经理一种对你,也对整个项目可控的感觉。

从心理学上来讲,人们多惯于先听坏消息,再听好消息,而不愿意先听好消息,再听坏消息,这就是我们常说的冷热水效应:一杯温水,保持温度不变,另有一杯冷水,一杯热水。当先将手放在冷水中,再放到温水中,会感到温水热;当先将手放在热水中,再放到温水中,会感到温水凉。

一个经常举得例子是:某汽车销售公司的老李,每月都能卖出30辆以上汽车,深得公司经理的赏识。由于种种原因,老李预计到这个月只能卖出10辆车。深懂人性奥妙的老李对经理说:“由于银根紧缩,市场萧条,我估计这个月顶多卖出5辆车。”经理点了点头,对他的看法表示赞成。没想到一个月过后,老李竟然卖了12辆汽车,公司经理对他大大夸奖一番。假若老李说本月可以卖15辆或者事先对此不说,结果只卖了12辆,公司经理会怎么认为呢?他会强烈地感受到老李失败了,不但不会夸奖,反而可能指责。在这个事例中,老李把最糟糕情况――顶多卖5辆车,报告给经理,使得经理心中的“秤砣”变小,因此当月绩出来以后,对老李的评价不但不会降低,反而提高了。

(3) 用Bug来说不

不知从何时开始《致加西亚的信》以及《没有任何借口》此类的书开始畅销,从而以执行力的名义把责任全部推到被领导的一方,用军队的方式来要求自己的员工,不讲条件,没有借口,从不说不,来完成领导所给的任务。真不知道资本家有什么资格这样要求自己的员工,作为军人为祖国献身后至少能够成为烈士,家人受到抚慰,而资本家在员工连跳九人的情况下却在论证这个数字其实低于全国平均自杀率的。

然而大多数的领导的的确确喜欢没有借口的下属,也不喜欢听到说不。所以当一个任务下达的时候,或者一种方案被指定的时候,不要直接说不。

领导毕竟是领导,能做到现在的位置,毕竟有强于你的地方;领导毕竟也是人,提出的方案也可能是拍脑袋拍出来的,也许会有不合理性。

然而需要记住的一点是:上情下达可以拍脑袋,下情上达则要用证据。

当你认为领导给的任务或者方案有问题的时候,除了上面提到的喊难在前之外,一定要加一句,"我试试看"。

当你经过实验测试,有数据或者日志足以证明你的结论的时候,可以尝试说,"我觉得可能有些问题"。

然而有时候简单的测试并不能够证明的时候,或者领导再次坚持的时候,那就上手做吧,只是别忘了做的有扩展性一些,能在多种方案之间较容易的切换,并将领导坚持的方案暴露出来。当测试人员发现问题的时候,将比你说不有效果的多。这时候领导关心的便是如何Bug进行修复,不在纠结到底应该采用你的方案还是他的方案了,当然此时你千万不要得意洋洋的指出领导原来方案的不合理性,你不指出,领导其实是从心里认可了你的方案的,并且为你记了一功,如果你指出来,就适得其反了,大部分领导绝不会表面承认自己的错误的,可能会再次坚持自己的方案的合理性,并把因此带来的项目失败或者delay记在你的头上。也许大家清晰的记得曹操不承认"鸡肋"的退兵禁令而杀杨修的故事吧。如果你觉得你的领导气度大于曹操,那么再次恭喜你。

也许你会说:这不是浪费了一个过程吗?其实不然,你先做了领导的方案,然后改Bug的时候应用了自己的方案,在领导眼中,你是一个好的下属,好的执行者,你是做了两件事情的。

如果你坚持做了自己的方案而没有优先用领导的方案,则会有以下风险:

你永远失去了证明你的方案优于领导的方案的机会 你会被认为固执,难于沟通,执行力差 一旦你的方案出现问题,你将单独的承担责任,甚至整个项目delay的责任。如果你优先采用了领导的方案出现问题的时候,一般合格的领导会勇于承担起责任,替你说好话:"我们采取的方案是相对较优的,也是经过测试的,Bug是难免的",相反,如果你固执己见,则没有人会替你说话,反而会说:"要是用原来的方案就不会出现这个问题" 。 领导的方案一般是由一定原理上合理性的,你的方案可能是比较符合你的实际需要,然而当时过境迁,context不在的时候,你百口难辨。

所以,毫无怨言的接受任务——让上司圆满,如果有问题,让Bug来说。

(4) 该干什么的时候干什么

在外企,一个常说的词叫"professional",何为职业化,一个通俗的说法就是,该干什么的时候就干什么,当然无论干什么,永远不要忘记,你是一个程序员,一个基层的程序员。

前面说过,除了写程序,外企的生活是丰富多彩的,健身,按摩,小食品,饮料,旅游,年会,各种协会等等不一而足,而且外企的氛围是相对宽松的,你可以在任何时间尽情享用,没有人会有意见,当然是在你完成了工作的基础之上的。

然而永远需要记住的是,写程序才是你的天职,而多彩的生活是公司对员工的福利,是一种施舍,说的不好听一点,公司花钱请你来是写软件的,不是让你来娱乐的,公司让你娱乐是给你脸,你总不能给脸不要脸吧。话说的难听一点,但细想想,话糙理不糙,试想如果项目经理每次来巡查的时候都看到你或大声的说笑,或尽情的饮食,或玩桌上足球的时候,其内心不会有上面的想法?只不过是一种优雅的方式表达出来罢了。比如走到你的面前,微笑着问:"你的feature做的如何了?","Bug XXX有没有结果?",顺便强调一下你所做的模块的难度和重要性:"你做的这部分比较有难度,是对你能力的挑战",并在最后来一句:"慢慢做,不着急"。你可知晓此彬彬有礼下面的深意?经理两次到你这里来说"不着急"的间隔越短,其实是说明这件事情越着急的。

所以说在办公室的大部分时间,你都应该低头写程序,谈话也要讨论技术问题,娱乐要适度,除非你想被人觉得工作量太少,不努力,或者你有足够的信心自己负责的模块不会出问题。

另外在开会的时候,你由于任务太多,总是盯着自己的笔记本默默写自己的代码吗?不要这样,这样会让组织会议的人感到不被尊重,会让领导觉得你对项目组不够关心,不够投入,甚至不够忠诚。开会的时候,就要像开会的样子。你可以提前阅读材料来准备几个问题;你可以支持,补充或建议组织者的方案;你可以在外国人面前举出证据来维护中国团队的利益;实在没招,你至少可以记会议记录,会后发meeting minutes。这样你给人的印象永远是你是有想法的,你是有贡献的,你是关心项目的,你是热爱团队的。

一个Team出去吃饭,或者出去旅游的时候,你是得意忘形的放开手脚去玩吗?甚至脱离团队和要好的朋友去逛吗?不要这个样子。余世维在《经理人常犯的11个错误》的演讲中曾经说过,出去Team building,对于员工来说是休息,而对于经理来说是工作。的确,你要清楚资本家为什么会出钱让员工去做这些和工作看起来无关的事情?为什么要大家一起出去而不是每人发钱自己去玩?当然是要增加团队的凝聚力和归属感,为共同合作奠定基础。既然对于经理来讲是工作,难道你不应该有责任辅助你的经理做好工作吗?在大家一起吃饭的时候,如果冷场,积极的起一个话题吧;在经理提出玩一个团队游戏的时候,率先支持,主动去做吧;在外出旅游的时候,帮助你的经理订餐馆,清点人数,摄影照相吧;当爬到山峰,或者年会表演节目的时候,喊出增加团队凝聚力和影响力的口号吧;在活动结束后,整理资料,相片,发出email来进一步增加活动的效果吧。这样你就是有组织能力的,辅助经理成功的,有良好影响力的,也是热爱团队的。

这里提到了团队聚餐中的话题问题,这里顺便提一下,当然根据不同的Team的氛围以及当时的情况而定,话题的优先级依次如下:

项目话题:如进度,难点,后面还会提到,学会喊累,喊忙,这是一个比较好的机会。当然此话题比较适合加班或者中午时的团队聚餐,不太适合旅游时候的团队聚餐。此话题可表明你对项目的关心。 技术话题:比如语言排名,那家公司被收购了,平台之间的差异等等。此话题可说明你对技术无限热爱。 员工生活话题:比如周末干什么了,介绍女朋友,结婚,孩子教育等,当然以当事人意愿为准,不要太当真。此话题可说明你关心同事。 娱乐话题:比如看什么电影,娱乐圈出来什么事情等,这是万能话题,也是最保险的话题。 员工敏感话题:比如非议其他Team,或者美国团队等,此类话题最好不要涉及,背后议人不太好。 公司敏感话题:如有的Team裁员,减薪,福利下降等,此话题千万不要提及,这是领导层想尽力遮盖的问题,甚至不在项目经理的权利范围之内。涉及此类话题将给人以你是一个不可托付大任的人。

最后,如果你在学校中是演艺明星或者体育明星,那么年会的表演以及团队之间的比赛也不是你表现英雄主义的地方,而是体现团队意识的地方,也是交流沟通的好机会。所以不妨在节目中介绍一下自己团队的产品;不妨在角色设定的时候劝说core team的人加入扮演一个牛人角色(欢乐可以一定程度上冲淡马屁味道);不妨申请印有团队logo的运动衣;不妨在运功过后和高层一同边走边聊(比平时冲到高层办公室里面好的多的机会);不妨去敬HR一杯酒,被她们多灌几杯(HR的办公室是个敏感区,平时很难交流感情啊);不妨去维护机房的团队那里敬酒以感谢他们的工作,去前台那里敬酒以夸赞她们的服装,发型等(他们对你来说真的很重要,想想几百人的团队,前台和运维都只有两三个人,还是那就话,当供需相差很大的时候,价格都会越来越高)。这将使你成为一个受欢迎的人。

(5) 适当的增加影响力

做一个好的基层程序员,除了完成自己的本职工作以外,也需不断增加自己的影响力,这既是你的品牌,也是日后加薪升职不可缺少的因素。

增加影响力主要有以下几种方式:

在工作中,如果完成了一定的功能,或者测试有了详细的报告,可发邮件给领导并cc整个Team,让领导知道你的付出,和同事分享你的喜悦,让众人知道你的亮点。邮件或者报告要在开头做精炼的总结,使得大部分人能够尽快的了解结果,具体细节可放在后面,供同模块的员工详细查阅。千万不要默认你的上司和其他人都显而易见的知道你完成了什么,这也可能是很多人觉得怀才不遇,难遇明主的原因吧。台湾作家黄明坚有一个形象的比喻:“做完蛋糕要记得裱花。有很多做好的蛋糕,因为看起来不够漂亮,所以卖不出去。但是在上面涂满奶油,裱上美丽的花朵,人们自然就会喜欢来买。” 在各类的会议中,如上面所说,事先准备问题,合理提出建议,适时提供证据,都是在同事,领导,以及外国人面前展现自己的机会。 有时候美国有或管理或技术的老大来中国,都会召开all hands,这是一个不可多得的在整个公司面前展现自己的机会。而在外企,程序员的竞争力大约包括对产品的把握,对技术的把握和对英语的把握等能力。all handls也是展现这三种能力的好地方。也许你会发现这样的事实,在all hands上英语流利的提问者们,提出问题的目的也许并不是为了想弄明白什么,而是为了展现什么。他们大多是这样问的:"As what you side A, but actually what we did in our project is B, so how/what/when C",你会发现,A和B会说的很具体,而C很抽象,显然A是为了展现产品把握能力(你讲的我都听懂了),B是为了展现技术把握的能力(我们采取了什么样的技术),整篇都用英语表达自然展现了英语的能力,最后问一个很Open的问题C,总不能问老大个很难的问题吧。 tech talk:当有了一定的技术积累,tech talk是一个很好的展现技术实力的平台,毕竟程序员是吃技术这碗饭的,所以良好的技术口碑对最初的升职至关重要。tech talk所讲的对象一般不是同项目的员工,因而难度要适当的把握,太简单则不足以体现你的技术实力,太难则大家会听的云里雾里,不能真正了解你的价值。在做tech talk的时候,最好一开始有一个整体的流程或者框架的介绍,以使得听众不会在途中迷路。一般有一个规律,就是在最前面几个slides的问题是最多的,大家总能够提出各种各样的问题,所以开始的几页,一定要是你最最熟悉的,最最有价值的,然而随着信息量的增大,后面就几乎提不出什么问题来了,到演讲最后,一般也就只能提出一些open question了,一般可以通过三个阶段轻松回答,其一,that is a good question,其二,it really depends,其三,I'd like to give an example。 demo:在很多施行迭代开发的项目管理的公司里,一个阶段是会有一个demo的。很多程序员重代码,而轻demo,明明实现的非常优雅的功能,却懒得花时间生动的demo出来,中国有句古话:六十四拜都拜了,就差最后一哆嗦,多对不起你前面没黑没夜的工作啊。demo是应该好好准备的,应该有一个详细的demo流程,先录入什么数据,然后如何操作,最后应该看到什么等等。然而demo是容易失败的,似乎成为一个难以规避的定律,即无论原来demo如何准备,临阵总会有意想不到的结果,大概因为看demo的人可能会提出奇怪的尝试需求,而可能正是程序员没有考虑过的边界。所以demo中,应该事先将良好的过程录制下来,以防止真实demo过程中有差错,造成功亏一篑,至少可以证明原来是好的。在demo中一定要用近似真实的数据,如输入人名,就用真实身边的员工姓名,输入日期就用当天的真实日期,千万不要用aaa, bbb, 123456此类的数据,既不美观,也容易出问题。而且在demo的过程中,应该严格按照已经准备好的流程走,当完全走完流程后,方才可以处理现场提出的各种尝试,可保证能够完成demo任务。 帮助他人:在不耽误自己工作的情况下帮助他人解决技术难题,是比tech talk和demo更能够体现技术实力的地方,并会积累下人脉,当众望所归的时候,你的升职也就仅仅是时间和名额问题了。举个相似的例子,tech talk好比是保健医生,只不过是给你宣扬养生之道,而解决技术难题就如同主治医师,可以使你药到病除。很显然,如果一个人如果能够很好的按照保健医生的养生之道去做,就很少会去找主治医师去看病了。然而主治医师却比保健医生更受欢迎,一方面因为相对重要的事情,人们多会更重视紧急的事情,一方面可能因为只有在逆境,出现问题的时候,人们才会虚心接受他人的意见。试想听tech talk的人们,就如同6000点的股市中的股民,多抱有"你懂,我可能比你还懂"的想法,而出现了问题的人们,便如跌至2000点股市的股民,才会虚心向专家请教,并对给出的方案五体投地了。而且此点满足,不忙的时候,主动帮助他人——让上司有效! 培训新人:有时候公司会招来一些学校来的实习生,一年一度也会招很多应届毕业生。当然一般公司都会有各种的培训,然而一旦进入团队的时候,如何更快的上手项目,仍然需要一个过程,如果能有老员工指导一二,将会轻松很多。在项目比较紧的情况下,很多老员工不愿意培训新人,万事开头难,起步总是相对缓慢的,害怕因此而耽误进度。当然,以耽误自己的工作换来对新人的培训我也是不赞成的,这也是后面所说的要给自己留buffer的原因所在。但我需要指出的是,给一个饥饿的人一个烧饼要比其成为千万富翁后给一百万更加让人感恩。人的眼光应该长远一些,无论如何都不要轻视一个年轻人,因为你不知道其将来会是什么样子。有的人,年纪大,level高,但是基本可以看出其一辈子的轨迹了,有的人年纪轻,level低,却可能前途无量,你永远不能把握将来谁会是你的贵人。

在增加的影响力的前面,我加了一个形容词——适当。要有和你的level相匹配的影响力,小心功高盖主啊。外国人有时候会强调leadership without authority,然而如果你果真这样做了,多半会招来同事的敌意(你凭什么指手画脚的啊),也可能会招来你的lead心中的隔阂(没有authority你都能够lead啦,给你authoriy还不反了天了,你lead,那我干嘛),所以还是不在其位,不谋其政的好。

(6) 给别人光环

当同事完成一项功能或修改完一个Bug的时候,你是否给过真诚的赞誉,帮其增加上述的影响力?

当同事帮助你解决了问题或者提出了优秀的方案,你是否公开表示感谢,让群众和领导都知晓?

当领导问及你做的模块的时候,你是否有意隐瞒了他人的功劳而突出自己的贡献?

当会议的时候,你是否会处心积虑的故意反对竞争对手的方案,虽然你觉得其实这真的是个好方案?

当发现其他人的Bug,Code reivew的时候发现他人的设计缺陷,你是否幸灾乐祸的大声疾呼,唯恐他人和领导不知?

你是否在同事,领导,HR的面前非议他人,嘲笑他人的设计,褒贬他人的缺陷,鄙视他人的技术,虽然的确你是此方面的大拿?

当你有幸获得一份荣誉的时候,你是否先谢国家(公司),再谢政府(团队),再谢领导,再谢同事?

给别人光环吧,别人也会给你光环。

一个只顾自己头上光环的人,以及一个别人给了光环而不知回报的人,最终都会孤立无援,难以开展工作,是涸泽而渔的做法。

我们必须承认的一点是,每个人都有自己的长处,也有自己的短处,每个模块都有优美的地方,也有不尽人意的地方,你有你擅长的技术,别人也有别人擅长的方向。如果大家互相向别人的头上套光环,则外人和领导看到的会多数是光环,从而大家精诚合作,团队蒸蒸日上。如果大家全部互相揭疮疤,则外人和领导则看到的会多数是负面的,从而大家互相猜忌,整个团队都没有希望。

历朝历代都有权臣,权臣之间必有党争,派别之间多知道对方的优势,也掌握了对方的把柄,如果派系之间相互合作,则大家都会壮大,皇帝则不会知道下面的暗流涌动,然而英明的皇帝多挑拨派系之间的关系,使他们互相揭露对方的把柄,从而下情上达,从中制衡。

我们也会经常看到,当一家公司的高管离开他的老东家而投奔新东家的时候,公开场合下,此高管多会十分赞誉在老东家中工作过的时光,赞誉工作过的团队,赞誉老东家的产品和项目,赞誉自己在老东家取得的进步,而老东家也多会给与此高管十分积极的评价,肯定其作出的贡献,其为公司带来的价值,其培养出的团队。其实如果两者之间如此的默契,如此的互相满意,就不会发生跳槽的事件了,既然选择分道扬镳,则其中必有隔阂,只不过互相心知肚明,各不言明罢了。这样无论对公司的发展,还是对此高管的职业生涯都有好处。试想,此高管必然是清清楚楚公司的优势劣势,公司也明明白白高管的功过是非,就像一对生活了很久的夫妻,既知道你能言善辩,也知道你鼾声震天,既知道你玉树临风,也知道你不爱洗澡,如果在公开场合互相指责,甚至谩骂,岂不家丑外扬?

(7) Daily report和weekly report很重要

很多程序员宁愿多写程序,也不愿意写report,觉得十分麻烦,而又无聊。

但是Daily report,weekly report真的非常的重要。

首先report可以帮助你管理自己的时间。在时间管理中,我们知道,人总是有重要而紧急的事情,重要而不紧急的事情,不重要而紧急的事情,不重要而不紧急的事情之分。你是否总结和思考过自己真的总是做了重要而紧急的事情么?人们总是忙啊忙,从早忙到晚,天天加班,其实每天都在处理各种各样的突发紧急的事件,使得计划一拖再拖,而忽略了对自己很重要的事情。试想想吧,你原来计划过要读的书有多少是真正去读了的?你在朋友面前畅谈的宏图大志有多少是真正实践过的?你还记得儿时的梦想吗?你是一直向着自己想要的方向在不断的进步吗?时间管理的原则告诉我们,每个人应该有一张思考的床,不要穷忙、瞎忙、无心的忙。写daily report和weekly report不完全是应付上司的,也是自己思考总结的过程啊。我还记得最初每周定计划的情况,当一周过去进行回顾的时候,我当时吓了自己一跳,我原以为自己一直过的非常的充实,却发现计划做得事情真的只做了大概三分之一,照此下去,如何进步啊。有兴趣大家可是试着制定一下计划,越详细越好,工作方面的可以写给上司看,学习,社交等方面的可以自己写给自己,有时候多少有些身在江湖,身不由己的感觉。

其二report可以帮助你总结自己的进步。当天做的事情一般人还是记得的,一个星期做的事情,大概就模糊了,半年前做的事情,则很多细节都忘记了。很多的时候,当我们每年对自己进行年终总结以期待明年加薪的时候,当我们想要跳槽来总结自己忙碌了几年的成果的时候,往往会发现,report到用时方恨少啊。面试的时候,对于有经验的人,往往会将项目经验问的很细很细,当时你为什么选择这种方案呢?系统的速度如何一步步改进提高的?你发现你可能说不清楚了。平时尽量多详细的记录一下自己每天的进步吧,这可是影响到你薪水的闪光点啊,对方的公司正等着一个牛人来提高他们的性能呢,你明明取得很不错的结果,只是忘记了自己做了哪些改进,岂不可惜啊。

其三report可以帮助你增加自己的影响力。如上面所述,report不但可以让自己知道自己做了什么,也可以让上司知道你完成了什么,如果写到wiki上,还能让更多的人知道你的成绩。

其四report可以作为维护权利的证据。这一点前面也说过,作为初入职场的基层,作为相对美国来说弱势的中方,证据是非常重要的。当项目delay的时候,你如何证明你是提前schedule完成的?当性能遇到瓶颈,你如何证明你曾经高效且没有做过改动?在写程序的时候,我们知道,当context不在的时候,唯一能够定位问题的,就是log了,report就是你工作的log。

其五report可以使你的上司对你放心。很多初入职场的基层,埋怨上司过多的干预自己的工作,总是有事没事的过来问,做的如何了?有什么困难?这段时间在做什么?有时候这种询问会打断你的思路,着实让人困扰。其实情有可原,你刚来,不放心是可以理解的。如何做到你办事,他放心是你的责任。当有阶段性成果的时候实时报告自己的状态,每天写daily report报告自己的进度都是让上司放心的办法。一个有意思的现象是,当你一天一封daily report向他汇报的时候,他却不怎么过来干预你的工作了,甚至到最后daily report他也不怎么看了。

做到此一点,方能主动向上司汇报你的工作进度——让上司知道,对上司的询问,有问必答,而且清楚——让上司放心!

(8) 给自己留buffer,学会喊累,学会喊忙

初入职场,激情高涨,多喜欢将自己满负荷运作,无论是需求来自领导还是来自同事,都不好意思拒绝,最后弄得自己疲于奔命,焦头烂额。

其实工作中,是应该给自己留有一定的buffer的,一方面,可以在项目有了突发事件的时候,不至于临阵慌乱,尚有调整和处理的时间,不至于第二天demo,当天晚上代码才完成。另一方面,要做好以上所述的事情,还都是需要buffer的,绝不能够低头干个不停。

另外,公司是要对项目负责的,而自己的职业前途,却只有自己负责。除了每天忙于项目之外,总要有一定的时间进行自我进步,从而提升自己的价值。前面也说过,有的人面试的时候,仅仅知道项目中用到的知识点,而相关的却很少知道,从而使自己的职业生涯既不广,也不深。所以日常工作中,留有一定的buffer来将知识点变成知识面是很重要的。

所以如果你真的很忙,真的很累,要勇于喊出来,而不要默默的承受着,当然这不是让你装忙装累,而是向周围散发出一种信息,就是你已经负荷较满了。这样你的领导在安排任务的时候,会综合考虑,你的同事在向你提出需求的时候,也拒绝的合情合理。当然你不能总是喊忙,总是不出成果,如第一点中所说,result永远是最重要的。而总是喊忙,总是能出成果,确是一种工作努力的表现。有的人认为,只有相互说话才叫沟通,其实不然,殊不知自言自语,凝重的表情,同样是沟通的手段。

也只有在有buffer的情况下,你才能做到充实自己,努力学习,才能了解上司的言语——让上司轻松,你才能够做到对自己的业务,主动提出改善计划——让上司进步。

 
2009-05-24 11:19

作者 Stefan Tilkov 译者 苑永凯

不知你是否意识到,围绕着什么才是实现异构的应用到应用通信的“正确”方式,一场争论正进行的如火如荼:虽然当前主流的方式明显地集中在基于 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狂热分子首领。

查看英文原文A Brief Introduction to REST
 
2007-11-27 20:58
一、简介

1.1、概述
       随着WEB2.0及 ajax思想在互联网上的快速发展传播,陆续出现了一些优秀的Js框架,其中比较著名的有Prototype、YUI、jQuery、mootools、 Bindows以及国内的JSVM框架等,通过将这些JS框架应用到我们的项目中能够使程序员从设计和书写繁杂的JS应用中解脱出来,将关注点转向功能需 求而非实现细节上,从而提高项目的开发速度。
       jQuery是继prototype 之后的又一个优秀的Javascript框架。它是由 John Resig 于 2006 年初创建的,它有助于简化 JavaScript™ 以及Ajax 编程。有人使用这样的一比喻来比较prototype和jQuery:prototype就像Java,而jQuery就像ruby. 它是一个简洁快速灵活的JavaScript框架,它能让你在你的网页上简单的操作文档、处理事件、实现特效并为Web页面添加Ajax交互。

它具有如下一些特点:
1、代码简练、语义易懂、学习快速、文档丰富。
2、jQuery是一个轻量级的脚本,其代码非常小巧,最新版的JavaScript包只有20K左右。
3、jQuery支持CSS1-CSS3,以及基本的xPath。
4、jQuery是跨浏览器的,它支持的浏览器包括IE 6.0+, FF 1.5+, Safari 2.0+, Opera 9.0+。
5、可以很容易的为jQuery扩展其他功能。
6、能将JS代码和HTML代码完全分离,便于代码和维护和修改。
7、插件丰富,除了jQuery本身带有的一些特效外,可以通过插件实现更多功能,如表单验证、tab导航、拖放效果、表格排序、DataGrid,树形菜单、图像特效以及ajax上传等。

jQuery的设计会改变你写JavaScript代码的方式,降低你学习使用JS操作网页的复杂度,提高网页JS开发效率,无论对于js初学者还是资深专家,jQuery都将是您的首选。
jQuery适合于设计师、开发者以及那些还好者,同样适合用于商业开发,可以说jQuery适合任何JavaScript应用的地方,可用于不同的Web应用程序中。
官方站点:http://jquery.com/   中文站点:http://jquery.org.cn/

1.2、目的
通过学习本文档,能够对jQuery有一个简单的认识了解,清楚JQuery与其他JS框架的不同,掌握jQuery的常用语法、使用技巧及注意事项。

二、使用方法
在需要使用JQuery的页面中引入JQuery的js文件即可。
例如:<script type="text/javascript" src="js/jquery.js"></script>
引入之后便可在页面的任意地方使用jQuery提供的语法。

三、学习教程及参考资料
请参照《jQuery中文API手册》和http://jquery.org.cn/visual/cn/index.xml
推荐两篇不错的jquery教程:《jQuery的起点教程》和《使用 jQuery 简化 Ajax 开发》
(说明:以上文档都放在了【附件】中)

四、语法总结和注意事项

1、关于页面元素的引用
通过jquery的$()引用元素包括通过id、class、元素名以及元素的层级关系及dom或者xpath条件等方法,且返回的对象为jquery对象(集合对象),不能直接调用dom定义的方法。

2、jQuery对象与dom对象的转换
只有jquery对象才能使用jquery定义的方法。注意dom对象和jquery对象是有区别的,调用方法时要注意操作的是dom对象还是jquery对象。
普通的dom对象一般可以通过$()转换成jquery对象。
如:$(document.getElementById("msg"))则为jquery对象,可以使用jquery的方法。
由于jquery对象本身是一个集合。所以如果jquery对象要转换为dom对象则必须取出其中的某一项,一般可通过索引取出。
如:$("#msg")[0],$("div").eq(1)[0],$("div").get()[1],$("td")[5]这些都是dom对象,可以使用dom中的方法,但不能再使用Jquery的方法。
以下几种写法都是正确的:
$("#msg").html();
$("#msg")[0].innerHTML;
$("#msg").eq(0)[0].innerHTML;
$("#msg").get(0).innerHTML;

3、如何获取jQuery集合的某一项
对于获取的元素集合,获取其中的某一项(通过索引指定)可以使用eqget(n)方法或者索引号获取,要注意,eq返回的是jquery对象,而get(n)和索引返回的是dom元素对象。对于jquery对象只能使用jquery的方法,而dom对象只能使用dom的方法,如要获取第三个<div>元素的内容。有如下两种方法:
$("div").eq(2).html();     //调用jquery对象的方法
$("div").get(2).innerHTML;   //调用dom的方法属性

4、同一函数实现set和get
Jquery中的很多方法都是如此,主要包括如下几个:
$("#msg").html();     //返回id为msg的元素节点的html内容。
$("#msg").html("<b>new content</b>");  
//将“<b>new content</b>” 作为html串写入id为msg的元素节点内容中,页面显示粗体的new content

$("#msg").text();     //返回id为msg的元素节点的文本内容。
$("#msg").text("<b>new content</b>");  
//将“<b>new content</b>” 作为普通文本串写入id为msg的元素节点内容中,页面显示<b>new content</b>

$("#msg").height();     //返回id为msg的元素的高度
$("#msg").height("300");   //将id为msg的元素的高度设为300
$("#msg").width();     //返回id为msg的元素的宽度
$("#msg").width("300");   //将id为msg的元素的宽度设为300

$("input").val(");   //返回表单输入框的value值
$("input").val("test");   //将表单输入框的value值设为test

$("#msg").click();   //触发id为msg的元素的单击事件
$("#msg").click(fn);   //为id为msg的元素单击事件添加函数
同样blur,focus,select,submit事件都可以有着两种调用方法

5、集合处理功能
对于jquery返回的集合内容无需我们自己循环遍历并对每个对象分别做处理,jquery已经为我们提供的很方便的方法进行集合的处理。
包括两种形式:
$("p").each(function(i){this.style.color=['#f00','#0f0','#00f'][i]})  
//为索引分别为0,1,2的p元素分别设定不同的字体颜色。

$("tr").each(function(i){this.style.backgroundColor=['#ccc','#fff'][i%2]})  
//实现表格的隔行换色效果

$("p").click(function(){alert($(this).html())})    
//为每个p元素增加了click事件,单击某个p元素则弹出其内容

6、扩展我们需要的功能
$.extend({
   min: function(a, b){return a < b?a:b; },
   max: function(a, b){return a > b?a:b; }
});   //为jquery扩展了min,max两个方法
使用扩展的方法(通过“$.方法名”调用):
alert("a=10,b=20,max="+$.max(10,20)+",min="+$.min(10,20));

7、支持方法的连写
所谓连写,即可以对一个jquery对象连续调用各种不同的方法。
例如:
$("p").click(function(){alert($(this).html())})
.mouseover(function(){alert('mouse over event')})
.each(function(i){this.style.color=['#f00','#0f0','#00f'][i]});

8、操作元素的样式
主要包括以下几种方式:
$("#msg").css("background");     //返回元素的背景颜色
$("#msg").css("background","#ccc")   //设定元素背景为灰色
$("#msg").height(300); $("#msg").width("200");   //设定宽高
$("#msg").css({ color: "red", background: "blue" });//以名值对的形式设定样式
$("#msg").addClass("select");   //为元素增加名称为select的class
$("#msg").removeClass("select");   //删除元素名称为select的class
$("#msg").toggleClass("select");   //如果存在(不存在)就删除(添加)名称为select的class

9、完善的事件处理功能
Jquery已经为我们提供了各种事件处理方法,我们无需在html元素上直接写事件,而可以直接为通过jquery获取的对象添加事件。
如:
$("#msg").click(function(){alert("good")})   //为元素添加了单击事件
$("p").click(function(i){this.style.color=['#f00','#0f0','#00f'][i]})
//为三个不同的p元素单击事件分别设定不同的处理
jQuery中几个自定义的事件:
(1)hover(fn1,fn2):一个模仿悬停事件(鼠标移动到一个对象上面及移出这个对象)的方法。当鼠标移动到一个匹配的元素上面时,会触发指定的第一个函数。当鼠标移出这个元素时,会触发指定的第二个函数。
//当鼠标放在表格的某行上时将class置为over,离开时置为out。
$("tr").hover(function(){
$(this).addClass("over");
},
   function(){
   $(this).addClass("out");
});
(2)ready(fn):当DOM载入就绪可以查询及操纵时绑定一个要执行的函数。
$(document).ready(function(){alert("Load Success")})
//页面加载完毕提示“Load Success”,相当于onload事件。与$(fn)等价
(3)toggle(evenFn,oddFn): 每次点击时切换要调用的函数。如果点击了一个匹配的元素,则触发指定的第一个函数,当再次点击同一元素时,则触发指定的第二个函数。随后的每次点击都重复对这两个函数的轮番调用。
   //每次点击时轮换添加和删除名为selected的class。
   $("p").toggle(function(){
     $(this).addClass("selected");  
   },function(){
     $(this).removeClass("selected");
   });
(4)trigger(eventtype): 在每一个匹配的元素上触发某类事件。
例如:
   $("p").trigger("click");     //触发所有p元素的click事件
(5)bind(eventtype,fn),unbind(eventtype): 事件的绑定与反绑定
从每一个匹配的元素中(添加)删除绑定的事件。
例如:
$("p").bind("click", function(){alert($(this).text());});   //为每个p元素添加单击事件
$("p").unbind();   //删除所有p元素上的所有事件
$("p").unbind("click")   //删除所有p元素上的单击事件

10、几个实用特效功能
其中toggle()和slidetoggle()方法提供了状态切换功能。
如toggle()方法包括了hide()和show()方法。
slideToggle()方法包括了slideDown()和slideUp方法。

11、几个有用的jQuery方法
$.browser.浏览器类型:检测浏览器类型。有效参数:safari, opera, msie, mozilla。如检测是否ie:$.browser.isie,是ie浏览器则返回true。
$.each(obj, fn):通用的迭代函数。可用于近似地迭代对象和数组(代替循环)。

$.each( [0,1,2], function(i, n){ alert( "Item #" + i + ": " + n ); });
等价于:
var tempArr=[0,1,2];
for(var i=0;i<tempArr.length;i++){
   alert("Item #"+i+": "+tempArr[i]);
}
也可以处理json数据,如
$.each( { name: "John", lang: "JS" }, function(i, n){ alert( "Name: " + i + ", Value: " + n ); });
结果为:
Name:name, Value:John
Name:lang, Value:JS
$.extend(target,prop1,propN):用一个或多个其他对象来扩展一个对象,返回这个被扩展的对象。这是jquery实现的继承方式。
如:
$.extend(settings, options);  
//合并settings和options,并将合并结果返回settings中,相当于options继承setting并将继承结果保存在setting中。
var settings = $.extend({}, defaults, options);
//合并defaults和options,并将合并结果返回到setting中而不覆盖default内容。
可以有多个参数(合并多项并返回)
$.map(array, fn):数组映射。把一个数组中的项目(处理转换后)保存到到另一个新数组中,并返回生成的新数组。
如:
var tempArr=$.map( [0,1,2], function(i){ return i + 4; });
tempArr内容为:[4,5,6]
var tempArr=$.map( [0,1,2], function(i){ return i > 0 ? i + 1 : null; });
tempArr内容为:[2,3]
$.merge(arr1,arr2):合并两个数组并删除其中重复的项目。
如:$.merge( [0,1,2], [2,3,4] )   //返回[0,1,2,3,4]
$.trim(str):删除字符串两端的空白字符。
如:$.trim("   hello, how are you?   ");   //返回"hello,how are you? "

12、解决自定义方法或其他类库与jQuery的冲突
很多时候我们自己定义了$(id)方法来获取一个元素,或者其他的一些js类库如prototype也都定义了$方法,如果同时把这些内容放在一起就会引起变量方法定义冲突,Jquery对此专门提供了方法用于解决此问题。
使用jquery中的jQuery.noConflict();方法即可把变量$的控制权让渡给第一个实现它的那个库或之前自定义的$方法。之后应用Jquery的时候只要将所有的$换成jQuery即可,如原来引用对象方法$("#msg")改为jQuery("#msg")。
如:
jQuery.noConflict();
// 开始使用jQuery
jQuery("div   p").hide();
// 使用其他库的 $()
$("content").style.display = 'none';
 
2007-11-26 19:42

1、对于IE使用filter,对于非IE浏览器使用png背景图填充

先请看如下代码:

filter:alpha(opacity=50);       /* IE */
-moz-opacity:0.5; /* Moz + FF */
opacity: 0.5; /* 支持CSS3的浏览器(FF 1.5也支持)*/

简单解释,IE使用私有属性filter:alpha(opacity),Moz Family使用私有属性-moz-opacity,而标准的属性是opacity(CSS 3, Moz Family部分支持CSS3)。后面的数值是透明度,使用百分比或者小数(alpha(opacity))使用大于0小于100的数值,其实也是百分 比)。

从上面的代码中你没有看到Opera。没错,Opera还未支持标准的opacity从Opera9开始支持CSS3的opacity了,也没有其私有的可支持Alpha透明的属性。

但是,我们知道,Opera是支持Alpha透明的PNG图片的(当然Moz Family也支持)。所以我们可以使用背景图片来实现Alpha透明效果。

例子:http://www.omemo.net/neo/lab/alpha/

关键在于:

background: transparent url(alpha80.png) left top repeat!important;
background: #ccc;
filter:alpha(opacity=50);

既然Moz Family支持Alpha透明的PNG,所以我们没有必要使用其私有属性了。当然,你可以使用标准的opacity,但别同时使用Alpha透明图片和 opacity,这样的话就成了两者的混合了。你可以把上面的例子下载过来,然后/*opacity: .5;*/的注释看看。

这部分内容来自于:http://www.omemo.net/neo/blog/?p=87

2、如想实现父标签透明,而子标签不透明,采用对于采用png透明的父标签来说不存在问题,如果采用alpha值无论ie还是非ie都存在这样的问题,css声明了position透明标签包含的内容都透明。

例如:

<div id="out"><div id="in">不透明<div><div>
out{
position: absolute;
top:0;
left: 0;
width: 100%;
background:url(../img/alpha30.png);/*非ie*/
filter:alpha(opacity=30);/*ie*/
}
in{
background:#fff
}

这个时候看到in也是透明的

hack方法:增加一个子标签,采用css hack 使其在ie下充满整个父标签,并使其透明,由于透明部分和不透明部分是兄弟关系,所以不影响。

<div id="out"><div id="in">不透明<div><div id="ie">不透明<div><div>
out{
position: absolute;
top:0;
left: 0;
width: 100%;
background:url(../img/alpha30.png);
z-index: 100;
}
in{
background:#fff
}

*html out{
background:out;
}
*html ie{
position: absolute;
top:0;
left: 0;
width: 100%;
height:100%;
background:url(../img/alpha30.png);
filter:alpha(opacity=30);
z-index: -1;/*让其位于in的下面*/
}
 
2007-11-25 17:17
你觉得自己的正则表达式足够精通了么,当遇上实际问题的时候,能很熟练的解决问题么,即使解决问题了,解决问题的方法足够漂亮么,效率够高么。
我这段时间正在学习和内容抽取方面的东东,当然少不了大量和正则表达式打交道了,刚开始的时候还以为自己以前用过不少正则表达式,用起来应该能得心应手,可是面对一些奇怪而特殊的要求时,写起来就特别的别扭,才发现自己那三脚猫功夫根本不够用了。正好在公司的书架上翻到了 这本 《精通正则表达式 第三版》,由Friedl,J.E.F. 著作,余晟译著。信手翻阅之后,立即深深的被它吸引了。这本书绝对不是那种只能让你入门,让你只懂得怎么用却不懂得原理的书。这本书详细的讲解了正则表达式实现的原理,NFA引擎和DFA引擎的区别,更难能可贵的是,书中全面而详细的讲解了正则表达式的各种知识点,读过之余,绝对让你重新认识正则表达式!
嘿嘿,现在这书被我“据为己有”啦,每天课余有时间都要好好翻阅...

嗯啊,要是觉得自己的正则表达式很牛了,麻烦再看看一下的这堆代码,对你肯定会有帮助的,这些代码可是出自余晟老师之手的哦,

IMG_LINK_RE = re.compile('(?is)(?:<img[^>]*?\\ssrc\\s*=\\s*['\"]?)([^'\"\\s>]+)(?:[^>]*>)')
RELATIVE_LINK_RE = re.compile('(?i)(?:href|src)\s*=\s*[\'"]?(?!http:)(?!mailto:)(?!javascript)(?:/[^\'"\s]+|[^\'"\s./]+/[^\'"\s]*|[^\'"\s./]+?.[^\'"\s./]+)(?=[\'"\s])')
RELATIVE_SUB_RE = re.compile('(?i)(?<=[\'"=\s])(?=[0-9a-z.])')
TITLE_RE = re.compile('(?i)(?<=<title>)[^<]+')
EMAIL_RE = re.compile('(?i)[a-z0-9][-a-z0-9._]*@(?:\w[-\w]+\.)+[a-z]{2,4}')
HTML_ENTITY_RE = re.compile('(&#(\\d{5});)')
LINE_RE = re.compile('(?is)<tr.*?</tr>')
SPACE_RE = re.compile(r'(?is)(<br/s*/?>|</?p>|(?<=\n)\s*)')
CONTENT_SUB_RE = re.compile(r"(?i)(<[^>]+>|&[0-9a-z]{3,5};|<(?:no)?script.*?</(?:no)?script>)")
HYPERLINK_RE = re.compile('(?i)(?<=href)(\s*=\s*[\'"]?)(?!mailto:)([^\'"\s]+)')
EMPTY_ELEMENT_RE = re.compile('(?is)<([^\s>]+)[^\>]*>[\s\r\n]*(</?\s*br>)*</\\1\s*>')
LINEFEED_RE = re.compile(r'(?is)(?<=\n)\s*')
RELATIVE_RE = re.compile('(?<=[\'"\s])(?=/)')
ENCODE_RE = re.compile('(?i)(?<=content=["\']text/html; charset=)[^\'"]+')
SCRIPT_RE = re.compile('(?is)<(?:no)?script.*?</(?:no)?script>')

嘿嘿,还是上点读书笔记吧..
简单的正则入门就不写啦,需要的话看我以前的笔记吧,正则表达式基础 http://hi.baidu.com/ismayday/blog/item/7f5b86942741d11dd21b708f.html ,

环视(look-around)

环视是很有意思的功能,它用来检查两端的字符,但不会把检查时匹配的字符加入匹配的最终结果。
例如,表达式『\bJeff\b』只能匹配“Jeff”这个单词,如果我们需要精确匹配“Jeffrey”这个单词中的“Jeff”,就可以使用环视『Jeff(?=rey)』,后面的『(?=rey)』表示,如果匹配成功,“Jeff”之后必须出现“rey”。有的读者可能会说,那我直接使用『(Jeff)rey』,先找出来,再提取分组,不是一样吗?请注意,环视的对象又可以是正则表达式,『Jeff(?=(rey|erson))』就可以找到“Jeffrey”或“Jefferson”中的“Jeff”,这种灵活性是前一种做法无法提供的,而且,『(Jeff)rey』使用括号来捕获文本,效率有所降低。
按照环视的方向不同,可以分为顺序环视(lookahead,表示从左向右检查)和逆序环视(lookbehind,从右向左检查);按照环视成立的条件不同,又可分为肯定环视(positive lookaround,只有在环视对象能匹配时才成功)和否定环视(negative lookaround,只有在环视对象无法匹配时才成功)。两者组合起来,就得到四种环视:

* 肯定顺序环视
* 肯定逆序环视
* 否定顺序环视
* 否定逆序环视

所使用的标记也很好识别,『(?=Regex)』表示肯定顺序环视,『(?!Regex)』表示否定顺序环视,『(?<=Regex)』表示肯定逆序环视,『(?<!Regex)』表示否定逆序环视。

在日常的HTML解析中,如果我们需要精确获得“src=...”中的资源地址(这里假定“src=...”的格式统一规范,等号两端没有空格,也没有引号),可以在表达式之前添加『(?<=).*?(?=< /B>)』来精确匹配“...”之中的内容。在这两个例子中,当然也可以使用匹配-括号提取的办法,但使用环视的效率更高,也更切合程序的本意。
环视还可以多个连用,我曾遇到过这样的情形:有站点siteA.com,需要在Apache的配置文件中设定重定向规则,以一个正则表达式匹配除sub1, sub23之外的所有子域名(注意,是匹配所有子域名),首先我想到的是
『^[^.]*(?<!--b(1|23)).siteA.com$』
但这行不通,因为多数系统都不容许在逆序环视中使用变长表达式(只有.NET容许),所以必须连用多个逆序环视
『^[^.]*(?<!--ub1)(?<!^sub23).siteA.com$』


固化分组(Atomic-grouping)

回溯(back-tracking)是匹配过程中常见的现象,如果用『.*ab』来匹配“123456ab”,『.*』首先会匹配整个字符串,之后轮到『ab』,『.*』需要“依次释放”之前匹配的两个字符,供『ab』匹配,整个表达式才能匹配成功。这样“依次释放”的过程,就叫做回溯。但有时回溯完全是徒劳的,例如我们用『\w+:』来匹配“Subject”。因为字符串中不存在冒号,匹配肯定会失败,但引擎仍然必须依次回溯,最终得出失败的结果,而我们知道,『\w』“释放”的字符,:肯定无法匹配。此时可以使用固化分组,将『\w+』匹配的内容“固定”下来,禁止回溯:
『(?>\w+):』
这样报告匹配失败的速度就提高了许多倍,如果字符串很长,使用固化分组就能节省大量的时间。
固化分组的另一个用途是精确控制匹配,防止不期望的匹配。例如,表达式『<(\w+).*?』,本意是匹配对称的tag之间的文本,但它会错误地匹配“ …”。如果使用固化分组『<((?>\w+)).*?』,就能解决这个问题。


模式修饰符(mode-modifier)

模式(mode)表示正则表达式在匹配时所采取的规则,模式修饰符用来设定模式。
最常见的模式是Case-Insensitive Mode(简写为i),它表示“不区分大小写”。在默认情况下,CAT只能匹配单词CAT,如果采用不区分大小写的匹配模式,则『CAT』可以匹配“Cat”, “CAT”,“cat”等任意形式的“cat”。
另一个常用的模式是Dot-match-all Mode(简写为s),它表示“点号通配”。在默认情况下,点号是不能匹配换行符的,此时,如果用『.*?』匹配一个 IMG tag,而这个tag的内容又跨越了两行,那么匹配是不会成功的。但如果使用“点号通配”模式,则点号可以匹配换行符,整个tag得以匹配。
还有个常见的模式是Free-spacing and Comment Mode(简写为x),它表示忽略正则表达式中的空白字符(必须使用『\s』来表示空白字符,同时容许在正则表达式中加入注释)。
最后介绍的模式是Multi-line Mode(多行模式,简写为m),又叫Enhanced Line-anchor Mode(增强的行锚点模式),在默认情况下,『^』和『$』只能匹配字符串的开头和结尾,但在此模式下,『^』和『$』可以匹配字符串当中的行开头/行结束,如果我们需要在包括多行文本的字符串中精确查找满足某条件的一行文本,就可以启用此模式,并使用『^』和『$』精确定位文本的两端。
模式修饰符通常以flag(标志位)的形式指定,

例如在Java中使用
Pattern.compile("CAT", PATTERN.CASE_INSENSITIVE)

在Python中使用
re.compile("CAT", re.IGNORECASE)
如果需要同时使用多个模式修饰符,可以以逻辑运算符“AND”来连接
Pattern.compile("C(?#comment1)A(?#comment2)T", PATTERN.CASE_INSENSITIVE|PATTERN.COMMENTS)。


模式作用范围(mode-modified-span)

这是与模式修饰符对应的概念。通常,我们需要显式地设定标志位,指定匹配模式,但此时模式是对整个表达式起作用的,无法进行更细的限制。而模式修饰范围则可以精确限定各元素的匹配模式,解决此类问题。
不妨考虑这样的情况,一个正则匹配函数,接收两个参数,第一个参数为Tag的名字,第二个参数为匹配Tag内容中内容的表达式,而且,Tag名可以不分大小写,但内容匹配必须区分大小写。如果我们用一个匹配模式统摄整个表达式,必然无法完成要求,此时必须使用模式修饰范围,限定各个元素所使用的匹配模式。以Java为例,如果第一个参数为tagNameRegex,第二个参数为tagContentRegex,那么我们可以这样:

Pattern.compile("(?i)<" + tagNameRegex + ">" + ">(?-i)" + tagContentRegex + "(?i)</"+tagNameRegex + ">" + ">(?-i)")

如果传入的tagNameRegex是“td”,tagContentRegex是“\d{4}[a-z]3\d{3}”,则编译所用的表达式就是 『(?i)<td>(?-i)\d{4}[a-z]3\d{3}(?i)</td>(?-i)』
其中,我们在“<td>”和“</td>”两端分别以『(?i)』和『(?-i)』来开启/关闭不区分大小写的匹配模式,精确限定了此匹配模式的作用范围,而内部的tagContentRegex则不受影响,仍采用默认的匹配模式,如此,这个表达式能匹配<Td>4444azz333<tD>,但不能匹配<td>4444AZZ333</td>,这正是我们需要的。
即使整个表达式采用同一种匹配模式,我们也可以使用模式修饰范围,例如『(?i)CAT』就表示,对整个表达式『CAT』进行不区分大小写的匹配。

 
2007-11-16 17:32
一个正则表达式就是由普通字符(例如字符 a 到 z)以及特殊字符(称为元字符)组成的文字模式。该模式描述在查找文字主体时待匹配的一个或多个字符串。正则表达式作为一个模板,将某个字符模式与所搜索的字符串进行匹配。如:
JScript VBScript 匹配
/^\[ \t]*$/ "^\[ \t]*$" 匹配一个空白行。
/\d{2}-\d{5}/ "\d{2}-\d{5}" 验证一个ID 号码是否由一个2位数字,一个连字符以及一个5位数字组成。
/<(.*)>.*<\/\1>/ "<(.*)>.*<\/\1>" 匹配一个 HTML 标记。

下表是元字符及其在正则表达式上下文中的行为的一个完整列表:
字符 描述
\ 将下一个字符标记为一个特殊字符、或一个原义字符、或一个 向后引用、或一个八进制转义符。例如,'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} mn 均为非负整数,其中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) 负向预查,在任何不匹配 pattern 的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如'Windows (?!95|98|NT|2000)' 能匹配 "Windows 3.1" 中的 "Windows",但不能匹配 "Windows 2000" 中的 "Windows"。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始
x|y 匹配 xy。例如,'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 之前至少有 nm 个获得子表达式,则 nm 为向后引用。如果 \nm 之前至少有 n 个获取,则 n 为一个后跟文字 m 的向后引用。如果前面的条件都不满足,若 nm 均为八进制数字 (0-7),则 \nm 将匹配八进制转义值 nm
\nml 如果 n 为八进制数字 (0-3),且 ml 均为八进制数字 (0-7),则匹配八进制转义值 nml。
\un 匹配 n,其中 n 是一个用四个十六进制数字表示的 Unicode 字符。例如, \u00A9 匹配版权符号 (©)。


下面看几个例子:
"^The":表示所有以"The"开始的字符串("There","The cat"等);
"of despair$":表示所以以"of despair"结尾的字符串;
"^abc$":表示开始和结尾都是"abc"的字符串——呵呵,只有"abc"自己了;
"notice":表示任何包含"notice"的字符串。

'*','+'和'?'这三个符号,表示一个或一序列字符重复出现的次数。它们分别表示“没有或
更多”,“一次或更多”还有“没有或一次”。下面是几个例子:

"ab*":表示一个字符串有一个a后面跟着零个或若干个b。("a", "ab", "abbb",……);
"ab+":表示一个字符串有一个a后面跟着至少一个b或者更多;
"ab?":表示一个字符串有一个a后面跟着零个或者一个b;
"a?b+$":表示在字符串的末尾有零个或一个a跟着一个或几个b。

也可以使用范围,用大括号括起,用以表示重复次数的范围。

"ab{2}":表示一个字符串有一个a跟着2个b("abb");
"ab{2,}":表示一个字符串有一个a跟着至少2个b;
"ab{3,5}":表示一个字符串有一个a跟着3到5个b。

请注意,你必须指定范围的下限(如:"{0,2}"而不是"{,2}")。还有,你可能注意到了,'*','+'和
'?'相当于"{0,}","{1,}"和"{0,1}"。
还有一个'¦',表示“或”操作:

"hi¦hello":表示一个字符串里有"hi"或者"hello";
"(b¦cd)ef":表示"bef"或"cdef";
"(a¦b)*c":表示一串"a""b"混合的字符串后面跟一个"c";

'.'可以替代任何字符:

"a.[0-9]":表示一个字符串有一个"a"后面跟着一个任意字符和一个数字;
"^.{3}$":表示有任意三个字符的字符串(长度为3个字符);

方括号表示某些字符允许在一个字符串中的某一特定位置出现:

"[ab]":表示一个字符串有一个"a"或"b"(相当于"a¦b");
"[a-d]":表示一个字符串包含小写的'a'到'd'中的一个(相当于"a¦b¦c¦d"或者"[abcd]");
"^[a-zA-Z]":表示一个以字母开头的字符串;
"[0-9]%":表示一个百分号前有一位的数字;
",[a-zA-Z0-9]$":表示一个字符串以一个逗号后面跟着一个字母或数字结束。

你也可以在方括号里用'^'表示不希望出现的字符,'^'应在方括号里的第一位。(如:"%[^a-zA-Z]%"表
示两个百分号中不应该出现字母)。

为了逐字表达,必须在"^.$()¦*+?{\"这些字符前加上转移字符'\'。

请注意在方括号中,不需要转义字符。

 
2007-11-15 16:16

10 Status Code Definitions

Each Status-Code is described below, including a description of which method(s) it can follow and any metainformation required in the response.

10.1 Informational 1xx

This class of status code indicates a provisional response, consisting only of the Status-Line and optional headers, and is terminated by an empty line. There are no required headers for this class of status code. Since HTTP/1.0 did not define any 1xx status codes, servers MUST NOT send a 1xx response to an HTTP/1.0 client except under experimental conditions.

A client MUST be prepared to accept one or more 1xx status responses prior to a regular response, even if the client does not expect a 100 (Continue) status message. Unexpected 1xx status responses MAY be ignored by a user agent.

Proxies MUST forward 1xx responses, unless the connection between the proxy and its client has been closed, or unless the proxy itself requested the generation of the 1xx response. (For example, if a

proxy adds a "Expect: 100-continue" field when it forwards a request, then it need not forward the corresponding 100 (Continue) response(s).)

10.1.1 100 Continue

The client SHOULD continue with its request. This interim response is used to inform the client that the initial part of the request has been received and has not yet been rejected by the server. The client SHOULD continue by sending the remainder of the request or, if the request has already been completed, ignore this response. The server MUST send a final response after the request has been completed. See section 8.2.3 for detailed discussion of the use and handling of this status code.

10.1.2 101 Switching Protocols

The server understands and is willing to comply with the client's request, via the Upgrade message header field (section 14.42), for a change in the application protocol being used on this connection. The server will switch protocols to those defined by the response's Upgrade header field immediately after the empty line which terminates the 101 response.

The protocol SHOULD be switched only when it is advantageous to do so. For example, switching to a newer version of HTTP is advantageous over older versions, and switching to a real-time, synchronous protocol might be advantageous when delivering resources that use such features.

10.2 Successful 2xx

This class of status code indicates that the client's request was successfully received, understood, and accepted.

10.2.1 200 OK

The request has succeeded. The information returned with the response is dependent on the method used in the request, for example:

GET an entity corresponding to the requested resource is sent in the response;

HEAD the entity-header fields corresponding to the requested resource are sent in the response without any message-body;

POST an entity describing or containing the result of the action;

TRACE an entity containing the request message as received by the end server.

10.2.2 201 Created

The request has been fulfilled and resulted in a new resource being created. The newly created resource can be referenced by the URI(s) returned in the entity of the response, with the most specific URI for the resource given by a Location header field. The response SHOULD include an entity containing a list of resource characteristics and location(s) from which the user or user agent can choose the one most appropriate. The entity format is specified by the media type given in the Content-Type header field. The origin server MUST create the resource before returning the 201 status code. If the action cannot be carried out immediately, the server SHOULD respond with 202 (Accepted) response instead.

A 201 response MAY contain an ETag response header field indicating the current value of the entity tag for the requested variant just created, see section 14.19.

10.2.3 202 Accepted

The request has been accepted for processing, but the processing has not been completed. The request might or might not eventually be acted upon, as it might be disallowed when processing actually takes place. There is no facility for re-sending a status code from an asynchronous operation such as this.

The 202 response is intentionally non-committal. Its purpose is to allow a server to accept a request for some other process (perhaps a batch-oriented process that is only run once per day) without requiring that the user agent's connection to the server persist until the process is completed. The entity returned with this response SHOULD include an indication of the request's current status and either a pointer to a status monitor or some estimate of when the user can expect the request to be fulfilled.

10.2.4 203 Non-Authoritative Information

The returned metainformation in the entity-header is not the definitive set as available from the origin server, but is gathered from a local or a third-party copy. The set presented MAY be a subset or superset of the original version. For example, including local annotation information about the resource might result in a superset of the metainformation known by the origin server. Use of this response code is not required and is only appropriate when the response would otherwise be 200 (OK).

10.2.5 204 No Content

The server has fulfilled the request but does not need to return an entity-body, and might want to return updated metainformation. The response MAY include new or updated metainformation in the form of entity-headers, which if present SHOULD be associated with the requested variant.

If the client is a user agent, it SHOULD NOT change its document view from that which caused the request to be sent. This response is primarily intended to allow input for actions to take place without causing a change to the user agent's active document view, although any new or updated metainformation SHOULD be applied to the document currently in the user agent's active view.

The 204 response MUST NOT include a message-body, and thus is always terminated by the first empty line after the header fields.

10.2.6 205 Reset Content

The server has fulfilled the request and the user agent SHOULD reset the document view which caused the request to be sent. This response is primarily intended to allow input for actions to take place via user input, followed by a clearing of the form in which the input is given so that the user can easily initiate another input action. The response MUST NOT include an entity.

10.2.7 206 Partial Content

The server has fulfilled the partial GET request for the resource. The request MUST have included a Range header field (section 14.35) indicating the desired range, and MAY have included an If-Range header field (section 14.27) to make the request conditional.

The response MUST include the following header fields:

- Either a Content-Range header field (section 14.16) indicating        the range included with this response, or a multipart/byteranges        Content-Type including Content-Range fields for each part. If a        Content-Length header field is present in the response, its        value MUST match the actual number of OCTETs transmitted in the        message-body.
- Date
- ETag and/or Content-Location, if the header would have been sent        in a 200 response to the same request
- Expires, Cache-Control, and/or Vary, if the field-value might        differ from that sent in any previous response for the same        variant

If the 206 response is the result of an If-Range request that used a strong cache validator (see section 13.3.3), the response SHOULD NOT include other entity-headers. If the response is the result of an If-Range request that used a weak validator, the response MUST NOT include other entity-headers; this prevents inconsistencies between cached entity-bodies and updated headers. Otherwise, the response MUST include all of the entity-headers that would have been returned with a 200 (OK) response to the same request.

A cache MUST NOT combine a 206 response with other previously cached content if the ETag or Last-Modified headers do not match exactly, see 13.5.4.

A cache that does not support the Range and Content-Range headers MUST NOT cache 206 (Partial) responses.

10.3 Redirection 3xx

This class of status code indicates that further action needs to be taken by the user agent in order to fulfill the request. The action required MAY be carried out by the user agent without interaction with the user if and only if the method used in the second request is GET or HEAD. A client SHOULD detect infinite redirection loops, since such loops generate network traffic for each redirection.

Note: previous versions of this specification recommended a      maximum of five redirections. Content developers should be aware      that there might be clients that implement such a fixed      limitation.

10.3.1 300 Multiple Choices

The requested resource corresponds to any one of a set of representations, each with its own specific location, and agent- driven negotiation information (section 12) is being provided so that the user (or user agent) can select a preferred representation and redirect its request to that location.

Unless it was a HEAD request, the response SHOULD include an entity containing a list of resource characteristics and location(s) from which the user or user agent can choose the one most appropriate. The entity format is specified by the media type given in the Content- Type header field. Depending upon the format and the capabilities of

the user agent, selection of the most appropriate choice MAY be performed automatically. However, this specification does not define any standard for such automatic selection.

If the server has a preferred choice of representation, it SHOULD include the specific URI for that representation in the Location field; user agents MAY use the Location field value for automatic redirection. This response is cacheable unless indicated otherwise.

10.3.2 301 Moved Permanently

The requested resource has been assigned a new permanent URI and any future references to this resource SHOULD use one of the returned URIs. Clients with link editing capabilities ought to automatically re-link references to the Request-URI to one or more of the new references returned by the server, where possible. This response is cacheable unless indicated otherwise.

The new permanent URI SHOULD be given by the Location field in the response. Unless the request method was HEAD, the entity of the response SHOULD contain a short hypertext note with a hyperlink to the new URI(s).

If the 301 status code is received in response to a request other than GET or HEAD, the user agent MUST NOT automatically redirect the request unless it can be confirmed by the user, since this might change the conditions under which the request was issued.

Note: When automatically redirecting a POST request after      receiving a 301 status code, some existing HTTP/1.0 user agents      will erroneously change it into a GET request.

10.3.3 302 Found

The requested resource resides temporarily under a different URI. Since the redirection might be altered on occasion, the client SHOULD continue to use the Request-URI for future requests. This response is only cacheable if indicated by a Cache-Control or Expires header field.

The temporary URI SHOULD be given by the Location field in the response. Unless the request method was HEAD, the entity of the response SHOULD contain a short hypertext note with a hyperlink to the new URI(s).

If the 302 status code is received in response to a request other than GET or HEAD, the user agent MUST NOT automatically redirect the request unless it can be confirmed by the user, since this might change the conditions under which the request was issued.

Note: RFC 1945 and RFC 2068 specify that the client is not allowed      to change the method on the redirected request.  However, most      existing user agent implementations treat 302 as if it were a 303      response, performing a GET on the Location field-value regardless      of the original request method. The status codes 303 and 307 have      been added for servers that wish to make unambiguously clear which      kind of reaction is expected of the client.

10.3.4 303 See Other

The response to the request can be found under a different URI and SHOULD be retrieved using a GET method on that resource. This method exists primarily to allow the output of a POST-activated script to redirect the user agent to a selected resource. The new URI is not a substitute reference for the originally requested resource. The 303 response MUST NOT be cached, but the response to the second (redirected) request might be cacheable.

The different URI SHOULD be given by the Location field in the response. Unless the request method was HEAD, the entity of the response SHOULD contain a short hypertext note with a hyperlink to the new URI(s).

Note: Many pre-HTTP/1.1 user agents do not understand the 303      status. When interoperability with such clients is a concern, the      302 status code may be used instead, since most user agents react      to a 302 response as described here for 303.

10.3.5 304 Not Modified

If the client has performed a conditional GET request and access is allowed, but the document has not been modified, the server SHOULD respond with this status code. The 304 response MUST NOT contain a message-body, and thus is always terminated by the first empty line after the header fields.

The response MUST include the following header fields:

- Date, unless its omission is required by section 14.18.1

If a clockless origin server obeys these rules, and proxies and clients add their own Date to any response received without one (as already specified by [RFC 2068], section 14.19), caches will operate correctly.

- ETag and/or Content-Location, if the header would have been sent        in a 200 response to the same request
- Expires, Cache-Control, and/or Vary, if the field-value might        differ from that sent in any previous response for the same        variant

If the conditional GET used a strong cache validator (see section 13.3.3), the response SHOULD NOT include other entity-headers. Otherwise (i.e., the conditional GET used a weak validator), the response MUST NOT include other entity-headers; this prevents inconsistencies between cached entity-bodies and updated headers.

If a 304 response indicates an entity not currently cached, then the cache MUST disregard the response and repeat the request without the conditional.

If a cache uses a received 304 response to update a cache entry, the cache MUST update the entry to reflect any new field values given in the response.

10.3.6 305 Use Proxy

The requested resource MUST be accessed through the proxy given by the Location field. The Location field gives the URI of the proxy. The recipient is expected to repeat this single request via the proxy. 305 responses MUST only be generated by origin servers.

Note: RFC 2068 was not clear that 305 was intended to redirect a      single request, and to be generated by origin servers only.  Not      observing these limitations has significant security consequences.

10.3.7 306 (Unused)

The 306 status code was used in a previous version of the specification, is no longer used, and the code is reserved.

10.3.8 307 Temporary Redirect

The requested resource resides temporarily under a different URI. Since the redirection MAY be altered on occasion, the client SHOULD continue to use the Request-URI for future requests. This response is only cacheable if indicated by a Cache-Control or Expires header field.

The temporary URI SHOULD be given by the Location field in the response. Unless the request method was HEAD, the entity of the response SHOULD contain a short hypertext note with a hyperlink to the new URI(s) , since many pre-HTTP/1.1 user agents do not understand the 307 status. Therefore, the note SHOULD contain the information necessary for a user to repeat the original request on the new URI.

If the 307 status code is received in response to a request other than GET or HEAD, the user agent MUST NOT automatically redirect the request unless it can be confirmed by the user, since this might change the conditions under which the request was issued.

10.4 Client Error 4xx

The 4xx class of status code is intended for cases in which the client seems to have erred. Except when responding to a HEAD request, the server SHOULD include an entity containing an explanation of the error situation, and whether it is a temporary or permanent condition. These status codes are applicable to any request method. User agents SHOULD display any included entity to the user.

If the client is sending data, a server implementation using TCP SHOULD be careful to ensure that the client acknowledges receipt of the packet(s) containing the response, before the server closes the input connection. If the client continues sending data to the server after the close, the server's TCP stack will send a reset packet to the client, which may erase the client's unacknowledged input buffers before they can be read and interpreted by the HTTP application.

10.4.1 400 Bad Request

The request could not be understood by the server due to malformed syntax. The client SHOULD NOT repeat the request without modifications.

10.4.2 401 Unauthorized

The request requires user authentication. The response MUST include a WWW-Authenticate header field (section 14.47) containing a challenge applicable to the requested resource. The client MAY repeat the request with a suitable Authorization header field (section 14.8). If the request already included Authorization credentials, then the 401 response indicates that authorization has been refused for those credentials. If the 401 response contains the same challenge as the prior response, and the user agent has already attempted authentication at least once, then the user SHOULD be presented the entity that was given in the response, since that entity might include relevant diagnostic information. HTTP access authentication is explained in "HTTP Authentication: Basic and Digest Access Authentication" [43].

10.4.3 402 Payment Required

This code is reserved for future use.

10.4.4 403 Forbidden

The server understood the request, but is refusing to fulfill it. Authorization will not help and the request SHOULD NOT be repeated. If the request method was not HEAD and the server wishes to make public why the request has not been fulfilled, it SHOULD describe the reason for the refusal in the entity. If the server does not wish to make this information available to the client, the status code 404 (Not Found) can be used instead.

10.4.5 404 Not Found

The server has not found anything matching the Request-URI. No indication is given of whether the condition is temporary or permanent. The 410 (Gone) status code SHOULD be used if the server knows, through some internally configurable mechanism, that an old resource is permanently unavailable and has no forwarding address. This status code is commonly used when the server does not wish to reveal exactly why the request has been refused, or when no other response is applicable.

10.4.6 405 Method Not Allowed

The method specified in the Request-Line is not allowed for the resource identified by the Request-URI. The response MUST include an Allow header containing a list of valid methods for the requested resource.

10.4.7 406 Not Acceptable

The resource identified by the request is only capable of generating response entities which have content characteristics not acceptable according to the accept headers sent in the request.

Unless it was a HEAD request, the response SHOULD include an entity containing a list of available entity characteristics and location(s) from which the user or user agent can choose the one most appropriate. The entity format is specified by the media type given in the Content-Type header field. Depending upon the format and the capabilities of the user agent, selection of the most appropriate choice MAY be performed automatically. However, this specification does not define any standard for such automatic selection.

Note: HTTP/1.1 servers are allowed to return responses which are      not acceptable according to the accept headers sent in the      request. In some cases, this may even be preferable to sending a      406 response. User agents are encouraged to inspect the headers of      an incoming response to determine if it is acceptable.

If the response could be unacceptable, a user agent SHOULD temporarily stop receipt of more data and query the user for a decision on further actions.

10.4.8 407 Proxy Authentication Required

This code is similar to 401 (Unauthorized), but indicates that the client must first authenticate itself with the proxy. The proxy MUST return a Proxy-Authenticate header field (section 14.33) containing a challenge applicable to the proxy for the requested resource. The client MAY repeat the request with a suitable Proxy-Authorization header field (section 14.34). HTTP access authentication is explained in "HTTP Authentication: Basic and Digest Access Authentication" [43].

10.4.9 408 Request Timeout

The client did not produce a request within the time that the server was prepared to wait. The client MAY repeat the request without modifications at any later time.

10.4.10 409 Conflict

The request could not be completed due to a conflict with the current state of the resource. This code is only allowed in situations where it is expected that the user might be able to resolve the conflict and resubmit the request. The response body SHOULD include enough

information for the user to recognize the source of the conflict. Ideally, the response entity would include enough information for the user or user agent to fix the problem; however, that might not be possible and is not required.

Conflicts are most likely to occur in response to a PUT request. For example, if versioning were being used and the entity being PUT included changes to a resource which conflict with those made by an earlier (third-party) request, the server might use the 409 response to indicate that it can't complete the request. In this case, the response entity would likely contain a list of the differences between the two versions in a format defined by the response Content-Type.

10.4.11 410 Gone

The requested resource is no longer available at the server and no forwarding address is known. This condition is expected to be considered permanent. Clients with link editing capabilities SHOULD delete references to the Request-URI after user approval. If the server does not know, or has no facility to determine, whether or not the condition is permanent, the status code 404 (Not Found) SHOULD be used instead. This response is cacheable unless indicated otherwise.

The 410 response is primarily intended to assist the task of web maintenance by notifying the recipient that the resource is intentionally unavailable and that the server owners desire that remote links to that resource be removed. Such an event is common for limited-time, promotional services and for resources belonging to individuals no longer working at the server's site. It is not necessary to mark all permanently unavailable resources as "gone" or to keep the mark for any length of time -- that is left to the discretion of the server owner.

10.4.12 411 Length Required

The server refuses to accept the request without a defined Content- Length. The client MAY repeat the request if it adds a valid Content-Length header field containing the length of the message-body in the request message.

10.4.13 412 Precondition Failed

The precondition given in one or more of the request-header fields evaluated to false when it was tested on the server. This response code allows the client to place preconditions on the current resource metainformation (header field data) and thus prevent the requested method from being applied to a resource other than the one intended.

10.4.14 413 Request Entity Too Large

The server is refusing to process a request because the request entity is larger than the server is willing or able to process. The server MAY close the connection to prevent the client from continuing the request.

If the condition is temporary, the server SHOULD include a Retry- After header field to indicate that it is temporary and after what time the client MAY try again.

10.4.15 414 Request-URI Too Long

The server is refusing to service the request because the Request-URI is longer than the server is willing to interpret. This rare condition is only likely to occur when a client has improperly converted a POST request to a GET request with long query information, when the client has descended into a URI "black hole" of redirection (e.g., a redirected URI prefix that points to a suffix of itself), or when the server is under attack by a client attempting to exploit security holes present in some servers using fixed-length buffers for reading or manipulating the Request-URI.

10.4.16 415 Unsupported Media Type

The server is refusing to service the request because the entity of the request is in a format not supported by the requested resource for the requested method.

10.4.17 416 Requested Range Not Satisfiable

A server SHOULD return a response with this status code if a request included a Range request-header field (section 14.35), and none of the range-specifier values in this field overlap the current extent of the selected resource, and the request did not include an If-Range request-header field. (For byte-ranges, this means that the first- byte-pos of all of the byte-range-spec values were greater than the current length of the selected resource.)

When this status code is returned for a byte-range request, the response SHOULD include a Content-Range entity-header field specifying the current length of the selected resource (see section 14.16). This response MUST NOT use the multipart/byteranges content- type.

10.4.18 417 Expectation Failed

The expectation given in an Expect request-header field (see section 14.20) could not be met by this server, or, if the server is a proxy, the server has unambiguous evidence that the request could not be met by the next-hop server.

10.5 Server Error 5xx

Response status codes beginning with the digit "5" indicate cases in which the server is aware that it has erred or is incapable of performing the request. Except when responding to a HEAD request, the server SHOULD include an entity containing an explanation of the error situation, and whether it is a temporary or permanent condition. User agents SHOULD display any included entity to the user. These response codes are applicable to any request method.

10.5.1 500 Internal Server Error

The server encountered an unexpected condition which prevented it from fulfilling the request.

10.5.2 501 Not Implemented

The server does not support the functionality required to fulfill the request. This is the appropriate response when the server does not recognize the request method and is not capable of supporting it for any resource.

10.5.3 502 Bad Gateway

The server, while acting as a gateway or proxy, received an invalid response from the upstream server it accessed in attempting to fulfill the request.

10.5.4 503 Service Unavailable

The server is currently unable to handle the request due to a temporary overloading or maintenance of the server. The implication is that this is a temporary condition which will be alleviated after some delay. If known, the length of the delay MAY be indicated in a Retry-After header. If no Retry-After is given, the client SHOULD handle the response as it would for a 500 response.

Note: The existence of the 503 status code does not imply that a      server must use it when becoming overloaded. Some servers may wish      to simply refuse the connection.

10.5.5 504 Gateway Timeout

The server, while acting as a gateway or proxy, did not receive a timely response from the upstream server specified by the URI (e.g. HTTP, FTP, LDAP) or some other auxiliary server (e.g. DNS) it needed to access in attempting to complete the request.

Note: Note to implementors: some deployed proxies are known to      return 400 or 500 when DNS lookups time out.

10.5.6 505 HTTP Version Not Supported

The server does not support, or refuses to support, the HTTP protocol version that was used in the request message. The server is indicating that it is unable or unwilling to complete the request using the same major version as the client, as described in section 3.1, other than with this error message. The response SHOULD contain an entity describing why that version is not supported and what other protocols are supported by that server.

 
2007-10-20 10:30
服务器变量:$_SERVER
注: 在 PHP 4.1.0 及以后版本使用。之前的版本,使用 $HTTP_SERVER_VARS。

$_SERVER 是一个包含诸如头部(headers)、路径(paths)和脚本位置(script locations)的数组。数组的实体由 web 服务器创建。不能保证所有的服务器都能产生所有的信息;服务器可能忽略了一些信息,或者产生了一些未在下面列出的新的信息。这意味着,大量的这些变量在 CGI 1.1 specification 中说明,所以您应该仔细研究它。

这是一个“superglobal”,或者可以描述为自动全局变量。这只不过意味这它在所有的脚本中都有效。在函数或方法中您不需要使用 global $_SERVER; 访问它,就如同使用 $HTTP_SERVER_VARS 一样。

$HTTP_SERVER_VARS 包含着同样的信息,但是不是一个自动全局变量。(注意: $HTTP_SERVER_VARS 和 $_SERVER 是不同的变量,PHP 处理它们的方式不同。)

如果设置了 register_globals 指令,这些变量也在所有脚本中可用;也就是,分离了 $_SERVER 和 $HTTP_SERVER_VARS 数组。相关信息,请参阅安全的相关章节 使用 Register Globals。这些单独的全局变量不是自动全局变量。

您或许会发现下面列出的某些 $_SERVER 元素并不可用。注意,如果以命令行方式运行 PHP,下面列出的元素几乎没有有效的(或是没有任何实际意义的)。



“PHP_SELF”
当前正在执行脚本的文件名,与 document root相关。举例来说,在URL地址为 http://example.com/test.php/foo.bar 的脚本中使用 $_SERVER['PHP_SELF'] 将会得到 /test.php/foo.bar 这个结果。

如果 PHP 以命令行方式运行,该变量无效。

“argv”
传递给该脚本的参数。当脚本运行在命令行方式时,argv 变量传递给程序 C 语言样式的命令行参数。当调用 GET 方法时,该变量包含请求的数据。

“argc”
包含传递给程序的命令行参数的个数(如果运行在命令行模式)。

“GATEWAY_INTERFACE”
服务器使用的 CGI 规范的版本。例如,“CGI/1.1”。

'SERVER_NAME'
当前运行脚本所在服务器主机的名称。如果该脚本运行在一个虚拟主机上,该名称是由那个虚拟主机所设置的值决定。

'SERVER_SOFTWARE'
服务器标识的字串,在响应请求时的头部中给出。

“SERVER_PROTOCOL”
请求页面时通信协议的名称和版本。例如,“HTTP/1.0”。

“REQUEST_METHOD”
访问页面时的请求方法。例如:“GET”、“HEAD”,“POST”,“PUT”。

“QUERY_STRING”
查询(query)的字符串。

“DOCUMENT_ROOT”
当前运行脚本所在的文档根目录。在服务器配置文件中定义。

“HTTP_ACCEPT”
当前请求的 Accept: 头部的内容。

“HTTP_ACCEPT_CHARSET”
当前请求的 Accept-Charset: 头部的内容。例如:“iso-8859-1,*,utf-8”。

“HTTP_ACCEPT_ENCODING”
当前请求的 Accept-Encoding: 头部的内容。例如:“gzip”。

“HTTP_ACCEPT_LANGUAGE”
当前请求的 Accept-Language: 头部的内容。例如:“en”。

“HTTP_CONNECTION”
当前请求的 Connection: 头部的内容。例如:“Keep-Alive”。

“HTTP_HOST”
当前请求的 Host: 头部的内容。

“HTTP_REFERER”
链接到当前页面的前一页面的 URL 地址。不是所有的用户代理(浏览器)都会设置这个变量,而且有的还可以手工修改 HTTP_REFERER。因此,这个变量不总是正确真实的。

“HTTP_USER_AGENT”
当前请求的 User_Agent: 头部的内容。该字符串表明了访问该页面的用户代理的信息。一个典型的例子是:Mozilla/4.5 [en] (X11; U; Linux 2.2.9 i586)。您也可以使用 get_browser() 得到这个信息。

“REMOTE_ADDR”
正在浏览当前页面用户的 IP 地址。

“REMOTE_PORT”
用户连接到服务器时所使用的端口。

“SCRIPT_FILENAME”
当前执行脚本的绝对路径名。

“SERVER_ADMIN”
该值指明了 Apache 服务器配置文件中的 SERVER_ADMIN 参数。如果脚本运行在一个虚拟主机上,则该值是那个虚拟主机的值。

“SERVER_PORT”
服务器所使用的端口。默认为“80”。如果你使用 SSL 安全连接,则这个值为您所设置的 HTTP 端口。

“SERVER_SIGNATURE”
包含服务器版本和虚拟主机名的字符串。

“PATH_TRANSLATED”
当前脚本所在文件系统(不是文档根目录)的基本路径。这是在服务器进行虚拟到真实路径的映像后的结果。

“SCRIPT_NAME”
包含当前脚本的路径。这在页面需要指向自己时非常有用。

“REQUEST_URI”
访问此页面所需的 URI。例如,“/index.html”。

“PHP_AUTH_USER”
当 PHP 运行在 Apache 模块方式下,并且正在使用 HTTP 认证功能,这个变量便是用户输入的用户名。

“PHP_AUTH_PW”
当 PHP 运行在 Apache 模块方式下,并且正在使用 HTTP 认证功能,这个变量便是用户输入的密码。

“PHP_AUTH_TYPE”
当 PHP 运行在 Apache 模块方式下,并且正在使用 HTTP 认证功能,这个变量便是认证的类型。
 
2007-09-05 16:19

下面的例子简单的说明了CountDownLatch的使用方法,模拟了100米赛跑,10名选手已经准备就绪,只等裁判一声令下。当所有人都到达终点时,比赛结束。

同样,线程池需要显式shutdown。

package concurrent;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestCountDownLatch {
  public static void main(String[] args) throws InterruptedException {
    // 开始的倒数锁
    final CountDownLatch begin = new CountDownLatch(1);
    // 结束的倒数锁
    final CountDownLatch end = new CountDownLatch(10);
    // 十名选手
    final ExecutorService exec = Executors.newFixedThreadPool(10);
    for(int index = 0; index < 10; index++) {
      final int NO = index + 1;
      Runnable run = new Runnable(){
        public void run() {
          try {
            begin.await();
            Thread.sleep((long) (Math.random() * 10000));
            System.out.println("No." + NO + " arrived");
          } catch (InterruptedException e) {
          } finally {
            end.countDown();
          }
        }
      };
      exec.submit(run);
    }
    System.out.println("Game Start");
    begin.countDown();
    end.await();
    System.out.println("Game Over");
    exec.shutdown();
  }
}

运行结果:
Game Start
No.4 arrived
No.1 arrived
No.7 arrived
No.9 arrived
No.3 arrived
No.2 arrived
No.8 arrived
No.10 arrived
No.6 arrived
No.5 arrived
Game Over

时 候在实际应用中,某些操作很耗时,但又不是不可或缺的步骤。比如用网页浏览器浏览新闻时,最重要的是要显示文字内容,至于与新闻相匹配的图片就没有那么重 要的,所以此时首先保证文字信息先显示,而图片信息会后显示,但又不能不显示,由于下载图片是一个耗时的操作,所以必须一开始就得下载。

Java的并发库Future类 就可以满足这个要求。Future的重要方法包括get()和cancel(),get()获取数据对象,如果数据没有加载,就会阻塞直到取到数据,而 cancel()是取消数据加载。另外一个get(timeout)操作,表示如果在timeout时间内没有取到就失败返回,而不再阻塞。

下面的Demo简单的说明了Future的使用方法:一个非常耗时的操作必须一开始启动,但又不能一直等待;其他重要的事情又必须做,等完成后,就可以做不重要的事情。

package concurrent;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class TestFutureTask {
  public static void main(String[] args)throws InterruptedException,
      ExecutionException {
    final ExecutorService exec = Executors.newFixedThreadPool(5);
    Callable<String> call = new Callable<String>() {
      public String call() throws Exception {
        Thread.sleep(1000 * 5);
        return "Other less important but longtime things.";
      }
    };
    Future<String> task = exec.submit(call);
    // 重要的事情
    Thread.sleep(1000 * 3);
    System.out.println("Let's do important things.");
    // 其他不重要的事情
    String obj = task.get();
    System.out.println(obj);
    // 关闭线程池
    exec.shutdown();
  }
}

运行结果:
Let's do important things.
Other less important but longtime things.

虑以下场景:浏览网页时,浏览器了5个线程下载网页中的图片文件,由于图片大小、网站访问速度等诸多因素的影响,完成图片下载的时间就会有很大的不同。如果先下载完成的图片就会被先显示到界面上,反之,后下载的图片就后显示。

Java的并发库CompletionService可 以满足这种场景要求。该接口有两个重要方法:submit()和take()。submit用于提交一个runnable或者callable,一般会提 交给一个线程池处理;而take就是取出已经执行完毕runnable或者callable实例的Future对象,如果没有满足要求的,就等待了。 CompletionService还有一个对应的方法poll,该方法与take类似,只是不会等待,如果没有满足要求,就返回null对象。

package concurrent;

import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class TestCompletionService {
  public static void main(String[] args) throws InterruptedException,
      ExecutionException {
    ExecutorService exec = Executors.newFixedThreadPool(10);
    CompletionService<String> serv =
      new ExecutorCompletionService<String>(exec);

    for (int index = 0; index < 5; index++) {
      final int NO = index;
      Callable<String> downImg = new Callable<String>() {
        public String call() throws Exception {
          Thread.sleep((long) (Math.random() * 10000));
          return "Downloaded Image " + NO;
        }
      };
      serv.submit(downImg);
    }

    Thread.sleep(1000 * 2);
    System.out.println("Show web content");
    for (int index = 0; index < 5; index++) {
      Future<String> task = serv.take();
      String img = task.get();
      System.out.println(img);
    }
    System.out.println("End");
    // 关闭线程池
    exec.shutdown();
  }
}

运行结果:
Show web content
Downloaded Image 1
Downloaded Image 2
Downloaded Image 4
Downloaded Image 0
Downloaded Image 3
End

操作系统的信号量是个很重要的概念,在进程控制方面都有应用。Java并发库Semaphore可以很轻松完成信号量控制,Semaphore可以控制某个资源可被同时访问的个数,acquire()获取一个许可,如果没有就等待,而release()释放一个许可。比如在Windows下可以设置共享文件的最大客户端访问个数。

Semaphore维护了当前访问的个数,提供同步机制,控制同时访问的个数。在数据结构中链表可以保存“无限”的节点,用Semaphore可以实现有限大小的链表。另外重入锁ReentrantLock也可以实现该功能,但实现上要负责些,代码也要复杂些。

下面的Demo中申明了一个只有5个许可的Semaphore,而有20个线程要访问这个资源,通过acquire()和release()获取和释放访问许可。

package concurrent;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

public class TestSemaphore {
  public static void main(String[] args) {
    // 线程池
    ExecutorService exec = Executors.newCachedThreadPool();
    // 只能5个线程同时访问
    final Semaphore semp = new Semaphore(5);
    // 模拟20个客户端访问
    for (int index = 0; index < 20; index++) {
      final int NO = index;
      Runnable run = new Runnable() {
        public void run() {
          try {
            // 获取许可
            semp.acquire();
            System.out.println("Accessing: " + NO);
            Thread.sleep((long) (Math.random() * 10000));
            // 访问完后,释放
            semp.release();
          } catch (InterruptedException e) {
          }
        }
      };
      exec.execute(run);
    }
    // 退出线程池
    exec.shutdown();
  }
}

 
2007-09-05 16:16

JDK6 RC已经发布了,而对于JDK5的新特性还来不及使用,虽然在项目中还没有使用,但可以写一些Demo体验一下Tiger的魅力,现在的时代就是体验的时代,事事都要亲历亲为才能有发言权,怎么有点毛主席“实事求是”的感觉。

JDK5中的一个亮点就是将Doug Lea并发库引入到Java标准库中。Doug Lea确实是一个牛人,能教书,能出书,能编码,不过这在国外还是比较普遍的,而国内的教授们就相差太远了。

一般的服务器都需要线程池,比如Web、FTP等服务器,不过它们一般都自己实现了线程池,比如以前介绍过的Tomcat、Resin和Jetty等,现在有了JDK5,我们就没有必要重复造车轮了,直接使用就可以,何况使用也很方便,性能也非常高。

package concurrent;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestThreadPool {
  public static void main(String args[]) throws InterruptedException {
    // only two threads
    ExecutorService exec = Executors.newFixedThreadPool(2);
    for(int index = 0; index < 100; index++) {
      Runnable run = new Runnable() {
        public void run() {
          long time = (long) (Math.random() * 1000);
          System.out.println("Sleeping " + time + "ms");
            try {
              Thread.sleep(time);
            } catch (InterruptedException e) {
            }
        }
      };
      exec.execute(run);
    }
    // must shutdown
    exec.shutdown();
  }
}

上面是一个简单的例子,使用了2个大小的线程池来处理100个线程。但有一个问题:在for循环的过程中,会等待线程池有空闲的线程,所以主线程会阻塞的。 为了解决这个问题,一般启动一个线程来做for循环,就是为了避免由于线程池满了造成主线程阻塞。不过在这里我没有这样处理。[重要修正:经过测试,即使 线程池大小小于实际线程数大小,线程池也不会阻塞的,这与Tomcat的线程池不同,它将Runnable实例放到一个“无限”的 BlockingQueue中,所以就不用一个线程启动for循环,Doug Lea果然厉害]

另 外它使用了Executors的静态函数生成一个固定的线程池,顾名思义,线程池的线程是不会释放的,即使它是Idle。这就会产生性能问题,比如如果线 程池的大小为200,当全部使用完毕后,所有的线程会继续留在池中,相应的内存和线程切换(while(true)+sleep循环)都会增加。如果要避 免这个问题,就必须直接使用ThreadPoolExecutor()来构造。可以像Tomcat的线程池一样设置“最大线程数”、“最小线程数”和“空 闲线程keepAlive的时间”。通过这些可以基本上替换Tomcat的线程池实现方案。

需要注意的是线程池必须使用shutdown来显式关闭,否则主线程就无法退出。shutdown也不会阻塞主线程。多长时间运行的应用有时候需要定时运行任务完成一些诸如统计、优化等工作,比如在电信行业中处理用户话单时,需要每隔1分钟处理话单;网站每天凌晨统计用户 访问量、用户数;大型超时凌晨3点统计当天销售额、以及最热卖的商品;每周日进行数据库备份;公司每个月的10号计算工资并进行转帐等,这些都是定时任 务。通过 java的并发库concurrent可以轻松的完成这些任务,而且非常的简单。

package concurrent;
import static java.util.concurrent.TimeUnit.SECONDS;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
public class TestScheduledThread {
  public static void main(String[] args) {
    final ScheduledExecutorService scheduler = Executors
        .newScheduledThreadPool(2);
    final Runnable beeper = new Runnable() {
      int count = 0;
      public void run() {
        System.out.println(new Date() + " beep " + (++count));
      }
    };
    // 1秒钟后运行,并每隔2秒运行一次
    final ScheduledFuture<?> beeperHandle = scheduler.scheduleAtFixedRate(
        beeper, 1, 2, SECONDS);
    // 2秒钟后运行,并每次在上次任务运行完后等待5秒后重新运行
    final ScheduledFuture<?> beeperHandle2 = scheduler
        .scheduleWithFixedDelay(beeper, 2, 5, SECONDS);
    // 30秒后结束关闭任务,并且关闭Scheduler
    scheduler.schedule(new Runnable() {
      public void run() {
        beeperHandle.cancel(true);
        beeperHandle2.cancel(true);
        scheduler.shutdown();
      }
    }, 30, SECONDS);
  }
}

为了退出进程,上面的代码中加入了关闭Scheduler的操作。而对于24小时运行的应用而言,是没有必要关闭Scheduler的。

实际应用中,有时候需要多个线程同时工作以完成同一件事情,而且在完成过程中,往往会等待其他线程都完成某一阶段后再执行,等所有线程都到达某一个阶段后再统一执行。

比如有几个旅行团需要途经深圳、广州、韶关、长沙最后到达武汉。旅行团中有自驾游的,有徒步的,有乘坐旅游大巴的;这些旅行团同时出发,并且每到一个目的地,都要等待其他旅行团到达此地后再同时出发,直到都到达终点站武汉。

这时候CyclicBarrier就可以派上用场。CyclicBarrier最重要的属性就是参与者个数,另外最要方法是await()。当所有线程都调用了await()后,就表示这些线程都可以继续执行,否则就会等待。

package concurrent;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestCyclicBarrier {
  // 徒步需要的时间: Shenzhen, Guangzhou, Shaoguan, Changsha, Wuhan
  private static int[] timeWalk = { 5, 8, 15, 15, 10 };
  // 自驾游
  private static int[] timeSelf = { 1, 3, 4, 4, 5 };
  // 旅游大巴
  private static int[] timeBus = { 2, 4, 6, 6, 7 };
  
  static String now() {
    SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
    return sdf.format(new Date()) + ": ";
  }

  static class Tour implements Runnable {
    private int[] times;
    private CyclicBarrier barrier;
    private String tourName;
    public Tour(CyclicBarrier barrier, String tourName, int[] times) {
      this.times = times;
      this.tourName = tourName;
      this.barrier = barrier;
    }
    public void run() {
      try {
        Thread.sleep(times[0] * 1000);
        System.out.println(now() + tourName + " Reached Shenzhen");
        barrier.await();
        Thread.sleep(times[1] * 1000);
        System.out.println(now() + tourName + " Reached Guangzhou");
        barrier.await();
        Thread.sleep(times[2] * 1000);
        System.out.println(now() + tourName + " Reached Shaoguan");
        barrier.await();
        Thread.sleep(times[3] * 1000);
        System.out.println(now() + tourName + " Reached Changsha");
        barrier.await();
        Thread.sleep(times[4] * 1000);
        System.out.println(now() + tourName + " Reached Wuhan");
        barrier.await();
      } catch (InterruptedException e) {
      } catch (BrokenBarrierException e) {
      }
    }
  }

  public static void main(String[] args) {
    // 三个旅行团
    CyclicBarrier barrier = new CyclicBarrier(3);
    ExecutorService exec = Executors.newFixedThreadPool(3);
    exec.submit(new Tour(barrier, "WalkTour", timeWalk));
    exec.submit(new Tour(barrier, "SelfTour", timeSelf));
    exec.submit(new Tour(barrier, "BusTour", timeBus));
    exec.shutdown();
  }
}

运行结果:
00:02:25: SelfTour Reached Shenzhen
00:02:25: BusTour Reached Shenzhen
00:02:27: WalkTour Reached Shenzhen
00:02:30: SelfTour Reached Guangzhou
00:02:31: BusTour Reached Guangzhou
00:02:35: WalkTour Reached Guangzhou
00:02:39: SelfTour Reached Shaoguan
00:02:41: BusTour Reached Shaoguan

发库中的BlockingQueue是一个比较好玩的类,顾名思义,就是阻塞队列。该类主要提供了两个方法put()和take(),前者将一个对象放到队列中,如果队列已经满了,就等待直到有空闲节点;后者从head取一个对象,如果没有对象,就等待直到有可取的对象。

 
2007-08-30 8:59

#001 Class cc[];

#002 Class ctmp;

#003 //找出所有被实现的interfaces

#004 cc = c.getInterfaces();

#005 if (cc.length != 0)

#006    System.out.print(", \r\n" + " implements "); //关键词

#007 for (Class cite : cc) //JDK1.5 新式循环写法

#008    System.out.print(tName(cite.getName(), null)+", ");

执行结果(例):

public class LinkedList<E>

extends AbstractSequentialList,

implements List, Queue, Cloneable, Serializable,

列表5-6:找出implemented interfaces。执行结果多出一个不该有的逗号于尾端。此非本处重点,为简化计,不多做处理。

#001 cc = c.getDeclaredClasses(); //找出inner classes

#002 for (Class cite : cc)

#003    System.out.println(tName(cite.getName(), null));

#004

#005 ctmp = c.getDeclaringClass(); //找出outer classes

#006 if (ctmp != null)

#007    System.out.println(ctmp.getName());

执行结果(例):

LinkedList$Entry

LinkedList$ListItr

列表-7:找出inner classes outer class

#001 Constructor cn[];

#002 cn = c.getDeclaredConstructors();

#003 for (int i = 0; i < cn.length; i++) {

#004    int md = cn[i].getModifiers();

#005    System.out.print(" " + Modifier.toString(md) + " " +

#006    cn[i].getName());

#007    Class cx[] = cn[i].getParameterTypes();

#008    System.out.print("(");

#009    for (int j = 0; j < cx.length; j++) {

#010        System.out.print(tName(cx[j].getName(), null));

#011        if (j < (cx.length - 1)) System.out.print(", ");

#012    }

#013    System.out.print(")");

#014 }

执行结果(例):

public java.util.LinkedList(Collection)

public java.util.LinkedList()

列表5-8a:找出所有constructors

#004 System.out.println(cn[i].toGenericString());

执行结果(例):

public java.util.LinkedList(java.util.Collection<? extends E>)

public java.util.LinkedList()

列表5-8b:找出所有constructors。本例在for 循环内使用toGenericString(),省事。

#001 Method mm[];

#002 mm = c.getDeclaredMethods();

#003 for (int i = 0; i < mm.length; i++) {

#004    int md = mm[i].getModifiers();

#005    System.out.print(" "+Modifier.toString(md)+" "+

#006    tName(mm[i].getReturnType().getName(), null)+" "+

#007    mm[i].getName());

#008    Class cx[] = mm[i].getParameterTypes();

#009    System.out.print("(");

#010    for (int j = 0; j < cx.length; j++) {

#011        System.out.print(tName(cx[j].getName(), null));

#012    if (j < (cx.length - 1)) System.out.print(", ");

#013    }

#014    System.out.print(")");

#015 }

执行结果(例):

public Object get(int)

public int size()

列表5-9a:找出所有methods

#004 System.out.println(mm[i].toGenericString());

public E java.util.LinkedList.get(int)

public int java.util.LinkedList.size()

列表5-9b:找出所有methods。本例在for 循环内使用toGenericString(),省事。

#001 Field ff[];

#002 ff = c.getDeclaredFields();

#003 for (int i = 0; i < ff.length; i++) {

#004    int md = ff[i].getModifiers();

#005    System.out.println(" "+Modifier.toString(md)+" "+

#006    tName(ff[i].getType().getName(), null) +" "+

#007    ff[i].getName()+";");

#008 }

执行结果(例):

private transient LinkedList$Entry header;

private transient int size;

列表5-10a:找出所有fields

#004 System.out.println("G: " + ff[i].toGenericString());

private transient java.util.LinkedList.java.util.LinkedList$Entry<E> ??

java.util.LinkedList.header

private transient int java.util.LinkedList.size

列表5-10b:找出所有fields。本例在for 循环内使用toGenericString(),省事。

找出class参用(导入)的所有classes

没有直接可用的Reflection API可以为我们找出某个class参用的所有其它classes。要获得这项信息,必须做苦工,一步一脚印逐一记录。我们必须观察所有fields的类型、所有methods(包括constructors)的参数类型和回返类型,剔除重复,留下唯一。这正是为什么5-2程序代码要为tName()指定一个hashtable(而非一个null)做为第二自变量的缘故:hashtable可为我们储存元素(本例为字符串),又保证不重复。

本文讨论至此,几乎可以还原一个class的原貌(唯有methods ctors的定义无法取得)。接下来讨论Reflection 的另三个动态性质:(1) 运行时生成instances(2)

行期唤起methods(3) 运行时改动fields

运行时生成instances

欲生成对象实体,在Reflection 动态机制中有两种作法,一个针对“无自变量ctor”,

一个针对“带参数ctor6是面对“无自变量ctor”的例子。如果欲调用的是“带参数ctor“就比较麻烦些,7是个例子,其中不再调用ClassnewInstance(),而是调用Constructor newInstance()7首先准备一个Class[]做为ctor的参数类型(本例指定为一个double和一个int),然后以此为自变量调用getConstructor(),获得一个专属ctor。接下来再准备一个Object[] 做为ctor实参值(本例指定3.14159125),调用上述专属ctornewInstance()

#001 Class c = Class.forName("DynTest");

#002 Object obj = null;

#003 obj = c.newInstance(); //不带自变量

#004 System.out.println(obj);

列表6:动态生成“Class object 所对应之class”的对象实体;无自变量。

#001 Class c = Class.forName("DynTest");

#002 Class[] pTypes = new Class[] { double.class, int.class };

#003 Constructor ctor = c.getConstructor(pTypes);

#004 //指定parameter list,便可获得特定之ctor

#005

#006 Object obj = null;

#007 Object[] arg = new Object[] {3.14159, 125}; //自变量

#008 obj = ctor.newInstance(arg);

#009 System.out.println(obj);

7:动态生成“Class object 对应之class”的对象实体;自变量以Object[]表示。

运行时调用methods

这个动作和上述调用“带参数之ctor”相当类似。首先准备一个Class[]做为ctor的参数类型(本例指定其中一个是String,另一个是Hashtable),然后以此为自变量调用getMethod(),获得特定的Method object。接下来准备一个Object[]放置自变量,然后调用上述所得之特定Method objectinvoke(),如8。知道为什么索取Method object时不需指定回返类型吗?因为method overloading机制要求signature(署名式)必须唯一,而回返类型并非signature的一个成份。换句话说,只要指定了method名称和参数列,就一定指出了一个独一无二的method

#001 public String func(String s, Hashtable ht)

#002 {

#003 System.out.println("func invoked"); return s;

#004 }

#005 public static void main(String args[])

#006 {

#007 Class c = Class.forName("Test");

#008 Class ptypes[] = new Class[2];

#009 ptypes[0] = Class.forName("java.lang.String");

#010 ptypes[1] = Class.forName("java.util.Hashtable");

#011 Method m = c.getMethod("func",ptypes);

#012 Test obj = new Test();

#013 Object args[] = new Object[2];

#014 arg[0] = new String("Hello,world");

#015 arg[1] = null;

#016 Object r = m.invoke(obj, arg);

#017 Integer rval = (String)r;

#018 System.out.println(rval);

#019 }

8:动态唤起method

运行时变更fields

与先前两个动作相比,“变更field内容”轻松多了,因为它不需要参数和自变量。首先调用ClassgetField()并指定field名称。获得特定的Field object之后便可直接调用Fieldget()set(),如9

#001 public class Test {

#002 public double d;

#003

#004 public static void main(String args[])

#005 {

#006 Class c = Class.forName("Test");

#007 Field f = c.getField("d"); //指定field 名称

#008 Test obj = new Test();

#009 System.out.println("d= " + (Double)f.get(obj));

#010 f.set(obj, 12.34);

#011 System.out.println("d= " + obj.d);

#012 }

#013 }

列表9:动态变更field 内容

Java 源码改动办法

先前我曾提到,原本想借由“改动Java标准库源码”来测知Class object的生成,但由于其ctor原始设计为private,也就是说不可能透过这个管道生成Class object(而是由class loader负责生成),因此“在ctor打印出某种信息”的企图也就失去了意义。

这里我要谈点题外话:如何修改Java标准库源码并让它反应到我们的应用程序来。假设我想修改java.lang.Class,让它在某些情况下打印某种信息。首先必须找出标准源码!当你下载JDK 套件并安装妥当,你会发现jdk150\src\java\lang 目录(见10)之中有Class.java,这就是我们此次行动的标准源码。备份后加以修改,编译获得Class.class。接下来准备将.class 搬移到jdk150\jre\lib\endorsed(见10)。

这是一个十分特别的目录,class loader将优先从该处读取内含classes.jar文件——成功的条件是.jar内的classes压缩路径必须和Java标准库的路径完全相同。为此,我们可以将刚才做出的Class.class先搬到一个为此目的而刻意做出来的\java\lang目录中,压缩为foo.zip(任意命名,唯需夹带路径java\lang),再将这个foo.zip搬到jdk150\jre\lib\endorsed并改名为foo.jar。此后你的应用程序便会优先用上这里的java.lang.Class。整个过程可写成一个批处理文件(batch file),如11,在DOS Box中使用。

图10

10JDK1.5 安装后的目录组织。其中的endorsed 是我新建。

del e:\java\lang\*.class //清理干净

del c:\jdk150\jre\lib\endorsed\foo.jar //清理干净

c:

cd c:\jdk150\src\java\lang

javac -Xlint:unchecked Class.java //编译源码

javac -Xlint:unchecked ClassLoader.java //编译另一个源码(如有必要)

move *.class e:\java\lang //搬移至刻意制造的目录中

e:

cd e:\java\lang //以下压缩至适当目录

pkzipc -add -path=root c:\jdk150\jre\lib\endorsed\foo.jar *.class

cd e:\test //进入测试目录

javac -Xlint:unchecked Test.java //编译测试程序

java Test //执行测试程序

列表11:一个可在DOS Box中使用的批处理文件(batch file),用以自动化java.lang.Class

的修改动作。Pkzipc(.exe)是个命令列压缩工具,addpath都是其命令。

更多信息

以下是视野所及与本文主题相关的更多讨论。这些信息可以弥补因文章篇幅限制而带来的不足,或带给您更多视野。

l         "Take an in-depth look at the Java Reflection API -- Learn about the new Java 1.1 tools forfinding out information about classes", by Chuck McManis。此篇文章所附程序代码是本文示例程序的主要依据(本文示例程序示范了更多Reflection APIs,并采用JDK1.5 新式的for-loop 写法)。

l         "Take a look inside Java classes -- Learn to deduce properties of a Java class from inside aJava program", by Chuck McManis

l         "The basics of Java class loaders -- The fundamentals of this key component of the Javaarchitecture", by Chuck McManis

l         The Java Tutorial Continued, Sun microsystems. Lesson58-61, "Reflection".

1用过诸如MFC这类所谓 Application Framework的程序员也许知道,MFC有所谓的dynamic creation。但它并不等同于Java的动态加载或动态辨识;所有能够在MFC程序中起作用的classes,都必须先在编译期被编译器看见

2如果操作对象是ObjectClass.getSuperClass()会返回null

 
2007-08-30 8:58

摘要

Reflection Java被视为动态(或准动态)语言的一个关键性质。这个机制允许程序在运行时透过Reflection APIs取得任何一个已知名称的class的内部信息,包括其modifiers(诸如public, static 等等)、superclass(例Object)、实现之interfaces(例如Cloneable),也包括fieldsmethods的所有信息,并可于运行时改变fields内容或唤起methods。本文借由实例,大面积示范Reflection APIs

于本文:

读者基础:具备Java 语言基础。

本文适用工具:JDK1.5

关键词:

Introspection(内省、内观)Reflection(反射)

    有时候我们说某个语言具有很强的动态性,有时候我们会区分动态和静态的不同技术与作法。我们朗朗上口动态绑定(dynamic binding)、动态链接(dynamic linking)、动态加载(dynamic loading)等。然而动态一词其实没有绝对而普遍适用的严格定义,有时候甚至像对象导向当初被导入编程领域一样,一人一把号,各吹各的调。

一般而言,开发者社群说到动态语言,大致认同的一个定义是:程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言。从这个观点看,PerlPythonRuby是动态语言,C++JavaC#不是动态语言。

       尽管在这样的定义与分类下Java不是动态语言,它却有着一个非常突出的动态相关机制:Reflection。这个字的意思是反射、映象、倒影,用在Java身上指的是我们可以于运行时加载、探知、使用编译期间完全未知的classes。换句话说,Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods1。这种看透class的能力(the ability of the program to examine itself)被称为introspection内省、内观、反省)。Reflectionintrospection是常被并提的两个术语。

Java如何能够做出上述的动态特性呢?这是一个深远话题,本文对此只简单介绍一些概念。整个篇幅最主要还是介绍Reflection APIs,也就是让读者知道如何探索class的结构、如何对某个运行时才获知名称的class生成一份实体、为其fields设值、调用其methods。本文将谈到java.lang.Class,以及java.lang.reflect中的MethodFieldConstructor等等classes

Classclass

       众所周知Java有个Object class,是所有Java classes的继承根源,其内声明了数个应该在所有Java class中被改写的methodshashCode()equals()clone()toString()getClass()等。其中getClass()返回一个Class object

       Class class十分特殊。它和一般classes一样继承自Object,其实体用以表达Java程序运行时的classesinterfaces,也用来表达enumarrayprimitive Java typesboolean, byte, char, short, int, long, float, double)以及关键词void。当一个class被加载,或当加载器(class loader)的defineClass()JVM调用,JVM 便自动产生一个Class object。如果您想借由修改Java标准库源码来观察Class object的实际生成时机(例如在Classconstructor内添加一个println()),不能够!因为Class并没有public constructor(见1)。本文最后我会拨一小块篇幅顺带谈谈Java标准库源码的改动办法。

       ClassReflection故事起源。针对任何您想探勘的class,唯有先为它产生一个Class object,接下来才能经由后者唤起为数十多个的Reflection APIs。这些APIs将在稍后的探险活动中一一亮相。

#001 public final

#002 class Class<T> implements java.io.Serializable,

#003 java.lang.reflect.GenericDeclaration,

#004 java.lang.reflect.Type,

#005 java.lang.reflect.AnnotatedElement {

#006    private Class() {}

#007    public String toString() {

#008        return ( isInterface() ? "interface " :

#009        (isPrimitive() ? "" : "class "))

#010    + getName();

#011 }

...

列表1Class class片段。注意它的private empty ctor,意指不允许任何人经由编程方式产生Class object。是的,其object 只能由JVM 产生。

Class object的取得途径

Java允许我们从多种管道为一个class生成对应的Class object2是一份整理。

Class object 诞生管道

示例

运用getClass()

注:每个class 都有此函数

String str = "abc";

Class c1 = str.getClass();

运用

Class.getSuperclass()2

Button b = new Button();

Class c1 = b.getClass();

Class c2 = c1.getSuperclass();

运用static method

Class.forName()

(最常被使用)

Class c1 = Class.forName ("java.lang.String");

Class c2 = Class.forName ("java.awt.Button");

Class c3 = Class.forName ("java.util.LinkedList$Entry");

Class c4 = Class.forName ("I");

Class c5 = Class.forName ("[I");

运用

.class 语法

Class c1 = String.class;

Class c2 = java.awt.Button.class;

Class c3 = Main.InnerClass.class;

Class c4 = int.class;

Class c5 = int[].class;

运用

primitive wrapper classes

TYPE 语法

Class c1 = Boolean.TYPE;

Class c2 = Byte.TYPE;

Class c3 = Character.TYPE;

Class c4 = Short.TYPE;

Class c5 = Integer.TYPE;

Class c6 = Long.TYPE;

Class c7 = Float.TYPE;

Class c8 = Double.TYPE;

Class c9 = Void.TYPE;

2Java 允许多种管道生成Class object

Java classes 组成分析

首先容我以3java.util.LinkedList为例,将Java class的定义大卸八块,每一块分别对应4所示的Reflection API5则是“获得class各区块信息”的程序示例及执行结果,它们都取自本文示例程序的对应片段。

package java.util;                      //(1)

import java.lang.*;                     //(2)

public class LinkedList<E>              //(3)(4)(5)

extends AbstractSequentialList<E>       //(6)

implements List<E>, Queue<E>,

Cloneable, java.io.Serializable         //(7)

{

private static class Entry<E> { }//(8)

public LinkedList() { }           //(9)

public LinkedList(Collection<? extends E> c) { }

public E getFirst() { }           //(10)

public E getLast() { }

private transient Entry<E> header = ; //(11)

private transient int size = 0;

}

列表3:将一个Java class 大卸八块,每块相应于一个或一组Reflection APIs(表4)。

Java classes 各成份所对应的Reflection APIs

3的各个Java class成份,分别对应于4Reflection API,其中出现的PackageMethodConstructorField等等classes,都定义于java.lang.reflect

Java class 内部模块(参见3

Java class 内部模块说明

相应之Reflection API,多半为Class methods

返回值类型(return type)

(1) package

class隶属哪个package

getPackage()

Package

(2) import

class导入哪些classes

无直接对应之API

解决办法见5-2

(3) modifier

class(或methods, fields)的属性

int getModifiers()

Modifier.toString(int)

Modifier.isInterface(int)

int

String

bool

(4) class name or interface name

class/interface

名称getName()

String

(5) type parameters

参数化类型的名称

getTypeParameters()

TypeVariable <Class>[]

(6) base class

base class(只可能一个)

getSuperClass()

Class

(7) implemented interfaces

实现有哪些interfaces

getInterfaces()

Class[]

(8) inner classes

内部classes

getDeclaredClasses()

Class[]

(8') outer class

如果我们观察的class 本身是inner classes,那么相对它就会有个outer class

getDeclaringClass()

Class

(9) constructors

构造函数getDeclaredConstructors()

不论 public private 或其它access level,皆可获得。另有功能近似之取得函数。

Constructor[]

(10) methods

操作函数getDeclaredMethods()

不论 public private 或其它access level,皆可获得。另有功能近似之取得函数。

Method[]

(11) fields

字段(成员变量)

getDeclaredFields()不论 public private 或其它access level,皆可获得。另有功能近似之取得函数。

Field[]

4Java class大卸八块后(如图3),每一块所对应的Reflection API。本表并非

Reflection APIs 的全部。

Java Reflection API 运用示例

5示范4提过的每一个Reflection API,及其执行结果。程序中出现的tName()是个辅助函数,可将其第一自变量所代表的Java class完整路径字符串剥除路径部分,留下class名称,储存到第二自变量所代表的一个hashtable去并返回(如果第二自变量为null,就不储存而只是返回)。

#001 Class c = null;

#002 c = Class.forName(args[0]);

#003

#004 Package p;

#005 p = c.getPackage();

#006

#007 if (p != null)

#008    System.out.println("package "+p.getName()+";");

执行结果(例):

package java.util;

列表5-1:找出class 隶属的package。其中的c将继续沿用于以下各程序片段。

#001 ff = c.getDeclaredFields();

#002 for (int i = 0; i < ff.length; i++)

#003    x = tName(ff[i].getType().getName(), classRef);

#004

#005 cn = c.getDeclaredConstructors();

#006 for (int i = 0; i < cn.length; i++) {

#007    Class cx[] = cn[i].getParameterTypes();

#008    for (int j = 0; j < cx.length; j++)

#009        x = tName(cx[j].getName(), classRef);

#010 }

#011

#012 mm = c.getDeclaredMethods();

#013 for (int i = 0; i < mm.length; i++) {

#014    x = tName(mm[i].getReturnType().getName(), classRef);

#015    Class cx[] = mm[i].getParameterTypes();

#016    for (int j = 0; j < cx.length; j++)

#017        x = tName(cx[j].getName(), classRef);

#018 }

#019 classRef.remove(c.getName()); //不必记录自己(不需import 自己)

执行结果(例):

import java.util.ListIterator;

import java.lang.Object;

import java.util.LinkedList$Entry;

import java.util.Collection;

import java.io.ObjectOutputStream;

import java.io.ObjectInputStream
列表
5-2:找出导入的classes,动作细节详见内文说明。

#001 int mod = c.getModifiers();

#002 System.out.print(Modifier.toString(mod)); //整个modifier

#003

#004 if (Modifier.isInterface(mod))

#005    System.out.print(" "); //关键词 "interface" 已含于modifier

#006 else

#007    System.out.print(" class "); //关键词 "class"

#008 System.out.print(tName(c.getName(), null)); //class 名称

执行结果(例):

public class LinkedList

列表5-3:找出classinterface 的名称,及其属性(modifiers)。

#001 TypeVariable<Class>[] tv;

#002 tv = c.getTypeParameters(); //warning: unchecked conversion

#003 for (int i = 0; i < tv.length; i++) {

#004    x = tName(tv[i].getName(), null); //例如 E,K,V...

#005    if (i == 0) //第一个

#006        System.out.print("<" + x);

#007    else //非第一个

#008        System.out.print("," + x);

#009    if (i == tv.length-1) //最后一个

#010        System.out.println(">");

#011 }

执行结果(例):

public abstract interface Map<K,V>

public class LinkedList<E>

列表5-4:找出parameterized types 的名称

#001 Class supClass;

#002 supClass = c.getSuperclass();

#003 if (supClass != null) //如果有super class

#004    System.out.print(" extends" +

#005 tName(supClass.getName(),classRef));

执行结果(例):

public class LinkedList<E>

extends AbstractSequentialList,

列表-5:找出base class。执行结果多出一个不该有的逗号于尾端。此非本处重点,为简化计,不多做处理。

 
2007-08-30 8:55

sourceforge http://www.sourceforge.net
java.net http://www.java.net

www.eclipse.org

www.opensource.org

http://strutstestcase.sourceforge.net (StrutsTestCase 是基于Junit的一个方便测试struts框架的测试框架)

Lomboz http://www.objectlearn.com/index.jsp (J2EE plugin for Eclipse)
htmlArea http://sourceforge.net/projects/itools-htmlarea/ (所见即所得的在线HTML编辑器)
XmlBuddy http://www.xmlbuddy.com/ (XML Editor plugin for Eclipse)
JFreeChart http://www.jfree.org/ (用于生成图表的项目)
EclipseME http://eclipseme.sourceforge.net/ (J2ME Developmnt Plugin for Eclipse)
mvnForum http://sourceforge.net/projects/mvnforum/ (论坛)
jChatBox http://www.javazoom.net/index.shtml (用servlet实现的WEB聊天引擎)
POI http://jakarta.apache.org/poi/index.html (用于处理Excel,WORD等文档的项目)
FileUpload http://jakarta.apache.org/commons/fileupload/ (用于处理HTTP文件上传得项目)
PDFBox http://sourceforge.net/projects/pdfbox/ (处理PDF文档的项目)
Lucene http://jakarta.apache.org/lucene/index.html (搜索引擎)
Digester http://jakarta.apache.org/commons/digester/ (处理XML信息的项目)
DBCP http://jakarta.apache.org/commons/dbcp/ (数据库连接池)
AXIS http://ws.apache.org/axis/ (WebService 的实现框架)
Jetspeed http://portals.apache.org/jetspeed-1/ (Portal)
HSQLDB http://sourceforge.net/projects/hsqldb/ (Im memory Database Engine)
CEWOLF http://sourceforge.net/projects/cewolf/ (一套标签库实现Web报表,使用的是jFreeChart引擎)
Struts Menu http://sourceforge.net/projects/struts-menu/ (基于Struts的Web菜单项目)
htmlparser http://sourceforge.net/projects/htmlparser/ (用于解析HTML信息的项目)
Mondrian http://sourceforge.net/projects/mondrian/(Open Source OLAP Database)
ProGuard http://sourceforge.net/projects/proguard/(Java的混淆器)
InfoGlue http://sourceforge.net/projects/infoglue/ (J2EE 内容管理系统)
JPivot http://sourceforge.net/projects/jpivot/ (基于WEB的OLAP 展现)
http://java-source.net/

(5)几个常用JAVA开源项目的地址荟萃:
Lomboz http://www.objectlearn.com/index.jsp (J2EE plugin for Eclipse)
htmlArea http://sourceforge.net/projects/itools-htmlarea/ (所见即所得的在线HTML

编辑器)
XmlBuddy http://www.xmlbuddy.com/ (XML Editor plugin for Eclipse)
JFreeChart http://www.jfree.org/ (用于生成图表的项目)
EclipseME http://eclipseme.sourceforge.net/ (J2ME Developmnt Plugin for Eclipse)
mvnForum http://sourceforge.net/projects/mvnforum/ (论坛)
jChatBox http://www.javazoom.net/index.shtml (用servlet实现的WEB聊天引擎)
POI http://jakarta.apache.org/poi/index.html (用于处理Excel,WORD等文档

的项目)
FileUpload http://jakarta.apache.org/commons/fileupload/ (用于处理HTTP文件上传得项

目)
PDFBox http://sourceforge.net/projects/pdfbox/ (处理PDF文档的项目)
Lucene http://jakarta.apache.org/lucene/index.html (搜索引擎)
Digester http://jakarta.apache.org/commons/digester/ (处理XML信息的项目)
DBCP http://jakarta.apache.org/commons/dbcp/ (数据库连接池)
AXIS http://ws.apache.org/axis/ (WebService 的实现框架)
Jetspeed http://portals.apache.org/jetspeed-1/ (Portal)
HSQLDB http://sourceforge.net/projects/hsqldb/ (Im memory Database Engine)
CEWOLF http://sourceforge.net/projects/cewolf/

(一套标签库实现Web报表,使用的是jFreeChart引擎)
Struts Menu http://sourceforge.net/projects/struts-menu/ (基于Struts的Web菜单项目

)
htmlparser http://sourceforge.net/projects/htmlparser/ (用于解析HTML信息的项目

)
Mondrian http://sourceforge.net/projects/mondrian/ (Open Source OLAP

Database)
ProGuard http://sourceforge.net/projects/proguard/ (Java的混淆器)
InfoGlue http://sourceforge.net/projects/infoglue/ (J2EE 内容管理系统)
JPivot http://sourceforge.net/projects/jpivot/ (基于WEB的OLAP 展现)
HttpClient http://jakarta.apache.org/commons/httpclient/ 处理http客户端的接口

 
 
   
 
 
文章分类
 
   
 
文章存档
 
     
 
最新文章评论
  

晚上打车回来,听到说在日本,几乎没有在网络上搜不到盗版,更极少有免费的mp3下载,
 

好文
 

顶凯哥
 

这12条里面哪一个的反馈周期是最短的? 好像第三条的时间最长。
 

回复苗苗_2009: 星星之火,即使不去燎原,也可以在某一角落各分秋色。
   
帮助中心 | 空间客服 | 投诉中心 | 空间协议
©2012 Baidu