posts - 51, comments - 17, trackbacks - 0, articles - 9
  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

2007年4月28日

引子 Web 2.0,在过去的一年里也许还是一个新的名词,曾几何时它像网上核武一样爆发了,并以不可阻挡之势燃烧了整个互联网,其热度不压于当年的超女,又曾几何时它悄悄地走进了我们的生活,从陌生走向了熟悉,从概念走向了应用。今天,Web 2.0构成了我们网络生活不可缺少的一部分,今天,你Web 2.0了吗?

什么是Web2.0?
如果你是一只老网虫但还不知道什么是Web2.0,赶紧去跳海吧,不怕死的就去跳(记得带个救生圈,出事了我不负责哈!),怕死的还不赶紧补补课,免得被人看穿了让人笑话。如果你是个善于表现自己的人,多学点东西吧,学了Web 2.0你可以不分对象地大谈特谈地讲述你的Web 2.0观点, 嘿嘿,想象一下别人对你崇拜的表情吧。

Web2.0是以 Flickr、Craigslist、Linkedin、Tribes、Ryze、 Friendster、Del.icio.us、43Things.com等网站为代表,以Blog、TAG、SNS、RSS、wiki等应用为核心,依据六度分隔、xml、ajax等新理论和技术实现的互联网新一代模式。

 1. 什么是Wiki

  WIKI的来源

  WIKI概念的发明人是Ward Cunningham,该词来源于夏威夷语的“wee kee wee kee”,原本是“快点快点” (quick)的意思。

  Wiki--一种多人协作的写作工具。Wiki站点可以有多人(甚至任何访问者)维护,每个人都可以发表自己的意见,或者对共同的主题进行扩展或者探讨。

  Wiki指一种超文本系统。这种超文本系统支持面向社群的协作式写作,同时也包括一组支持这种写作的辅助工具。有人认为,Wiki系统属于一种人类知识网格系统,我们可以在Web的基础上对Wiki文本进行浏览、创建、更改,而且创建、更改、发布的代价远比HTML文本小;同时Wiki系统还支持面向社群的协作式写作,为协作式写作提供必要帮助;最后,Wiki的写作者自然构成了一个社群,Wiki系统为这个社群提供简单的交流工具。与其它超文本系统相比,Wiki有使用方便及开放的特点,所以Wiki系统可以帮助我们在一个社群内共享某领域的知识。

      WIKI可以做什么

  WIKI最适合做百科全书、知识库、整理某一个领域的知识等知识型站点,几个分在不同地区的人利用wiki协同工作共同写一本书等等。Wiki技术已经被较好的用在百科全书、手册/FAQ编写、专题知识库方面。

  Wiki的特点

  使用方便

  维护快捷:快速创建、存取、更改超文本页面(这也是为什幺叫作“wiki wiki”的原因)。

  格式简单:用简单的格式标记来取代 HTML 的复杂格式标记。(类似所见即所得的风格)

  链接方便:通过简单标记,直接以关键字名来建立链接(页面、外部连接、图像等)。

  命名平易:关键字名就是页面名称,并且被置于一个单层、平直的名空间中。

  有组织

  自组织的:同页面的内容一样,整个超文本的组织结构也是可以修改、演化的。

  可汇聚的:系统内多个内容重复的页面可以被汇聚于其中的某个,相应的链接结构也随之改变。

  可增长

  可增长:页面的链接目标可以尚未存在,通过点击链接,我们可以创建这些页面,从而使系统得到增长。

  修订历史:记录页面的修订历史,页面的各个版本都可以被获取。

  开放性

  开放的:社群的成员可以任意创建、修改、删除页面。

  可观察:系统内页面的变动可以被访问者观察到。

2.

  什么是RSS

  RSS是站点用来和其他站点之间共享内容的一种简易方式(也叫聚合内容)的技术。最初源自浏览器“新闻频道”的技术,现在通常被用于新闻和其他按顺序排列的网站,例如Blog。

  RSS可以干什么?

  1、订阅BLOG(BLOG上,你可以订阅你工作中所需的技术文章;也可以订阅与你有共同爱好的作者的日志,总之,BLOG上你对什么感兴趣你就可以订什么)

  2、订阅新闻(无论是奇闻怪事、明星消息、体坛风云,只要你想知道的,都可以订阅)

  如何使用RSS

  ·下载和安装一个RSS新闻阅读器

  ·从网站提供的聚合新闻目录列表中订阅您感兴趣的新闻栏目的内容

  ·订阅后您将会及时获得所订阅新闻频道的最新内容

  RSS的几个缩写来源

  1、Really Simple Syndication(真正简易的聚合)

  2、Rich Site Summary(丰富的站点摘要)

  3、RDF Site Summary(RDF站点摘要)

  RSS新闻特点

  对网民而言:对网站而言:

  1.没有广告或者图片来影响标题或者文章概要的阅读。

  2.RSS阅读器自动更新你定制的网站内容,保持新闻的及时性。

  3.用户可以加入多个定制的RSS提要,从多个来源搜集新闻整合到单个数据流中。 1.扩大了网站内容的传播面,也增加了网站访问量,因为访问者调阅的RSS文件和浏览的网页,都是从网站服务器上下载的。

  2.RSS文件的网址是固定不变的,网站可以随时改变其中的内容。RSS内容一旦更新,浏览者看到的内容也随即更新了


3.什么是Tag?

  Tag(标签)是一种更为灵活、有趣的分类方式,您可以为每篇日志、每个帖子或者每张图片等添加一个或多个Tag(标签),你可以看到网站上所有和您使用了相同Tag的内容,由此和他人产生更多的联系。Tag体现了群体的力量,使得内容之间的相关性和用户之间的交互性大大增强。

  比如,你在一篇日志上添加了“读书”和“Tag”两个标签,就能通过这两个tag看到和你有相同兴趣的其他日志。同样,如果你给自己的网络书签贴上不同标签,那么,在下一次去寻找时,会轻易找到自己想要的信息。

  那么,如果我贴了Tag,能产生什么效果呢?首先,信息将会条理化。其次,当你积累了一定数量的Tag之后,你会发现自己最关心的话题。GOOGLE的"我的搜索历史"功能就是采用了标签,你的每次搜索关键词都可以成为tag,之后,你会了解自己这一天在关心什么。

  当然,你也可以看到有哪些人和自己使用了一样的Tag(标签),进而找到和您志趣相投的人。

  2、Tag究竟有哪些不同?

  Tag不是关键词,因为,一个机器就没有办法提取一张照片的关键字,但人可以给它设定一个或多个Tag。而Tag真正不同的地方在于,你可以随意用任何词来标记一件事物,只要方便你找到它。因此,这一标志是活跃的、无序的、个人化、相当自我的一种标记方式。

  当我可以为我自己的言论作出自己想要的标志,而不是别人给予我的分类,那么,我将说些什么呢?我又会通过这种标志找到什么样的人什么样的文章、图片呢?Tag创造了一个新的无序但充满生机的网络联合体,通过这个联合,人们找到和自己最接近的内容。

  3、如何使用Tag?

  现在很多网站都使用了Tag模式,只要使用者自身打开了界限,随心所欲地给自己注释标签,不被旧有思维局限住,就对了。简单地说,Tag是一种随心所欲的标签,当我读一篇文章或者看一张图片的时候想什么就写什么,不受原有分类的束缚,怎么想就怎么使用。

posted @ 2007-08-07 20:46 chenweicai 阅读(267) | 评论 (0)编辑 收藏

Abraham Lincoln

Delivered on the 19th Day of November, 1863

Cemetery Hill, Gettysburg, Pennsylvania

Fourscore and seven years ago, our fathers brought forth upon this continent a new Nation, conceived in Liberty, and dedicated to the proposition that all men are created equal. Now, we are engaged in a great Civil War,testing whether that Nation, or any nation so conceived and so dedicated, can long endure. We are met on a great battlefield of that war. We have come to dedicate a portion of that field as a final resting-place for those who gave their lives that Nation might live. It is altogether fitting and proper that we should do this.

But, in a larger sense, we cannot dedicate, we cannot consecrate, we cannot hallow this ground. The brave men, living and dead, who struggled here, have consecrated it far above our power to add or detract. The world will little note nor long remember what we say here, but it can never forget what they did here. It is for us, the living, rather to be dedicated to the great task remaining before us; that from these honored dead, we take increased devotion to that cause for which they gave the last full measure of devotion; that this Nation, under GOD, shall have a new birth of freedom; and that government of the People by the People and for the People shall not perish from the earth.

葛底斯堡演说

亚伯拉罕·林肯,1963年11月19日

87年前,我们的先辈们在这个大陆上创立了一个新国家,它孕育于自由之中,奉行一切人生来平等的原则。现在我们正从事一场伟大的内战,以考验这个国家,或者任何一个孕育于自由和奉行上述原则的国家是否能够长久存在下去。我们在这场战争中的一个伟大战场上集会。烈士们为使这个国家能够生存下去而献出了自己的生命,我们来到这里,是要把这个战场的一部分奉献给他们作为最后安息之所。我们这样做是完全应该而且是非常恰当的。

但是,从更广泛的意义上来说,这块土地我们不能够奉献,不能够圣化,不能够神化。那些曾在这里战斗过的勇士们,活着的和去世的,已经把这块土地圣化了,这远不是我们微薄的力量所能增减的。我们今天在这里所说的话,全世界不大会注意,也不会长久地记住,但勇士们在这里所做过的事,全世界却永远不会忘记。毋宁说,倒是我们这些还活着的人,应该在这里把自己奉献于勇士们已经如此崇高地向前推进但尚未完成的事业。倒是我们应该在这里把自己奉献于仍然留在我们面前的伟大任务??我们要从这些光荣的死者身上汲取更多的献身精神,来完成他们已经完全彻底为之献身的事业;我们要在这里下定最大的决心,不让这些死者白白牺牲;我们要使国家在上帝福佑下得到自由的新生,要使这个民有、民治、民享的政府永世长存。

posted @ 2007-06-30 11:23 chenweicai 阅读(291) | 评论 (0)编辑 收藏

Inaugural Address of John F. Kennedy  肯尼迪总统就职演说
January 20, 1961

Vice President Johnson, Mr. Speaker, Mr. Chief Justice, President Eisenhower, Vice President Nixon, President Truman, Reverend Clergy, fellow citizens:

We observe today not a victory of party but a celebration of freedom, symbolizing an end as well as a beginning, signifying renewal as well as change. For I have sworn before you and Almighty God the same solemn oath our forebears prescribed nearly a century and three-quarters ago.

我们今天所看到的,并非是某一党派的胜利,而是自由的庆典。它象征着结束,亦象征着开始;意味着更新,亦意味着变化。因为我已在你们及万能的上帝面前,依着我们先辈175年前写下的誓言宣誓。

The world is very different now. For man holds in his mortal hands the power to abolish all forms of human poverty and all forms of human life. And yet the same revolutionary beliefs for which our forebears fought are still at issue around the globe -- the belief that the rights of man come not from the generosity of the state but from the hand of God.

世界已然今非昔比,因为人类手中已经掌握了巨大的力量,既可以用来消除各种形式的贫困,亦可用以毁灭人类社会。然而,我们先辈曾为之战斗的那些革命性的信念还依然在世界上受人争议——那就是,每个人享有的各项权利决非来自国家政权的慷慨赐予,而是出自上帝之手。

We dare not forget today that we are the heirs of that first revolution. Let the word go forth from this time and place, to friend and foe alike, that the torch has been passed to a new generation of Americans -- born in this century, tempered by war, disciplined by a hard and bitter peace, proud of our ancient heritage -- and unwilling to witness or permit the slow undoing of those human rights to which this nation has always been committed, and to which we are committed today at home and around the world.

今天,我们不敢有忘,我们乃是那第一次革命的后裔。此时,让这个声音从这里同时向我们的朋友和敌人传达:火炬现已传递到新一代美国人手中——他们生于本世纪,既经受过战火的锤炼,又经历过艰难严峻的和平岁月的考验。他们深为我们古老的遗产所自豪——决不愿目睹或听任诸项人权受到无形的侵蚀,这些权利不仅为这个国家始终信守不渝,亦是我们正在国内和世界上誓死捍卫的东西。

Let every nation know, whether it wishes us well or ill, that we shall pay any price, bear any burden, meet any hardship, support any friend, oppose any foe to assure the survival and the success of liberty.

让每一个国家都知道,无论它们对我们抱有善意还是恶意,我们都准备付出任何代价、承受任何重任、迎战任何艰险、支持任何朋友、反对任何敌人,以使自由得以维系和胜利。

This much we pledge -- and more.

这是我们矢志不移的承诺,且远不止此!

To those old allies whose cultural and spiritual origins we share, we pledge the loyalty of faithful friends. United there is little we cannot do in a host of cooperative ventures. Divided there is little we can do, for we dare not meet a powerful challenge at odds and split asunder.

对于那些与我们共享同一文化和精神源头的老朋友,我们许以朋友的忠诚。在许许多多的合作事业中,我们会尽己所能以促进我们的团结,而决不故意制造分裂,因为我们不敢轻易面对由分歧或体系崩溃而导致的巨大挑战。

To those new states whom we welcome to the ranks of the free, we pledge our word that one form of colonial control shall not have passed away merely to be replaced by a far more iron tyranny. We shall not always expect to find them supporting our view. But we shall always hope to find them strongly supporting their own freedom -- and to remember that, in the past, those who foolishly sought power by riding the back of the tiger ended up inside.

对于那些新成立的国家,我们欢迎它们加入自由阵营,并在此许以忠告:某种形式的殖民控制决不会仅仅因为被另一种更为残酷的霸权所取代就消声匿迹。我们不会期待他们始终支持我们的观点,但我们希望他们能始终坚定地维护他们自己的自由——并且牢记,在过去,那些愚蠢地骑上独~裁的虎背以谋求权力的人最终都以葬身虎腹而告终。

To those people in the huts and villages of half the globe struggling to break the bonds of mass misery, we pledge our best efforts to help them help themselves, for whatever period is required -- not because the communists may be doing it, not because we seek their votes, but because it is right.

对于那些寄居于大半个地球上的草舍村落、为着挣脱无尽苦难的枷锁而奋斗的人民,我们承诺将尽我们最大的努力,以使他们获得自助的能力。因为这是时代对我们提出的要求——不是因为共~产~党人可能如此行事、不是因为我们需要他们的选票,仅仅是因为这样做是正当的。

If a free society cannot help the many who are poor, it cannot save the few who are rich.

如果一个自由的社会不能帮助贫穷的多数,它就不能拯救那富裕的少数。

To our sister republics south of our border, we offer a special pledge: to convert our good words into good deeds, in a new alliance for progress, to assist free men and free governments in casting off the chains of poverty. But this peaceful revolution of hope cannot become the prey of hostile powers. Let all our neighbors know that we shall join with them to oppose aggression or subversion anywhere in the Americas.

对于我们的南部邻邦共和国,我们许以特殊的承诺:将我们的良言转为善行,在为了进步而结成的新盟邦里,帮助自由的人民和自由的政府摆脱贫困。但这一希翼中的和平革命不能成为敌对势力的牺牲品,让我们所有的邻邦都知道,我们将与他们一道,反对发生在美洲任何地区的侵略和颠覆。

And let every other power know that this hemisphere intends to remain the master of its own house.

让所有其他势力都知道,这一半球的人民致力于维护他们作为自己家园主人的地位。

To that world assembly of sovereign states, the United Nations, our last best hope in an age where the instruments of war have far outpaced the instruments of peace, we renew our pledge of support -- to prevent it from becoming merely a forum for invective, to strengthen its shield of the new and the weak, and to enlarge the area in which its writ may run.

对于那个主权国家的世界性会议组织——联合国,我们最后一次良好祝愿是发生在战争机器远远超过和平机器的时代。为了防止它沦为仅仅用来谩骂攻讦的论坛,为了加强它对新成立国家及弱小国家的保障功能、为了扩展其权力涵盖的领域,我们现在重申对它的支持承诺。

Finally, to those nations who would make themselves our adversary, we offer not a pledge but a request: that both sides begin anew the quest for peace -- before the dark powers of destruction unleashed by science engulf all humanity in planned or accidental self-destruction.

最后,对于那些主动站到我们敌对面的国家,我们提出的不是许诺,而是恳求:在被科学释放出的、黑暗的破坏力量以有计划的或偶然性的自我毁灭方式吞噬全人类之前,恳求双方再一次地开始谋求和平的努力。

We dare not tempt them with weakness. For only when our arms are sufficient beyond doubt can we be certain beyond doubt that they will never be employed. But neither can two great and powerful groups of nations take comfort from our present course -- both sides overburdened by the cost of modern weapons, both rightly alarmed by the steady spread of the deadly atom, yet both racing to alter that uncertain balance of terror that stays the hand of mankind's final war. So let us begin anew -- remembering on both sides that civility is not a sign of weakness, and sincerity is always subject to proof.

我们不敢以软弱诱惑它们,因为只有当我们的军备充足到确切无疑的程度时,我们才能确切无疑地肯定它们永远不会被投入使用。但这两个强大的国家集团都无法从彼此当前的做法中得到安慰——双方都背负了过高的现代武器系统的成本、双方都理所当然地对致死性原子武器的持续扩散感到惊恐不安,但双方都竞相改变不确定的恐怖均衡,这种均衡恰恰抑制了人类最后摊牌的冲动。

Let us never negotiate out of fear. But let us never fear to negotiate.

让我们永远不要因为惧怕而谈判,让我们永远不要惧怕谈判。

Let both sides explore what problems unite us instead of belaboring those problems which divide us.

让双方探寻那些能将我们团结在一起的因素,而不是那些刻意挑出那些分裂我们的因素。

Let both sides, for the first time, formulate serious and precise proposals for the inspection and control of arms, and bring the absolute power to destroy other nations under the absolute control of all nations.

让双方首先提出认真细致的方案来核查及控制军备,并将毁灭其他国家的绝对力量置于所有国家的绝对控制之下。

Let both sides seek to invoke the wonders of science instead of its terrors. Together let us explore the stars, conquer the deserts, eradicate disease, tap the ocean depths, and encourage the arts and commerce.

让双方努力去激发科学的奇迹,而非科学的恐怖。让我们一同探索星空、征服沙漠、消除疾病、开发海洋深处,鼓励艺术和商业。

Let both sides unite to heed, in all corners of the earth, the command of Isaiah -- to "undo the heavy burdens... [and] let the oppressed go free."

让双方在世界每一个角落,都共同信守《圣经.以赛亚书》中的教诲——“卸下重负……让被压迫者自由。”

And if a beachhead of cooperation may push back the jungle of suspicion, let both sides join in creating a new endeavor -- not a new balance of power, but a new world of law -- where the strong are just, and the weak secure, and the peace preserved.

如果合作的滩头堡能够遏制重重猜疑,让双方携手进行新的努力——不是为了建立新的势力均衡,而是为了建立新的规则体系——以使强者正义,弱者安全,和平维系。

All this will not be finished in the first one hundred days. Nor will it be finished in the first one thousand days; nor in the life of this Administration; nor even perhaps in our lifetime on this planet. But let us begin.

所有这些工作将不会在从现在起的一百天、一千天内完成,也不会在本届行政分支任期内完成,甚至可能不会在我们的有生之年完成,但是,请让我们现在开始工作。

In your hands, my fellow citizens, more than mine, will rest the final success or failure of our course. Since this country was founded, each generation of Americans has been summoned to give testimony to its national loyalty. The graves of young Americans who answered the call to service surround the globe.

我的同胞们,我们事业的最终成败将掌握在你们的手中而不仅仅是我的手中。从这个国家被创建那天起,每一代美国人都被召唤去证实自己对国家的忠诚。那些响应号召献身国家的年轻美国人的安息之所遍布全球。

Now the trumpet summons us again -- not as a call to bear arms, though arms we need -- not as a call to battle, though embattled we are -- but a call to bear the burden of a long twilight struggle, year in and year out, rejoicing in hope, patient in tribulation, a struggle against the common enemies of man: tyranny, poverty, disease, and war itself.

现在,召唤的号角又一次吹响——不是号召我们扛起武器,虽然武器是我们所需要的——也不是号召我们去参加战斗,虽然我们准备战斗——而是号召我们年复一年地去进行一场漫长而未分胜负的搏斗,在希望中欢乐,而患难中忍耐,以反对人类共同的敌人:暴政、贫困、疾病以及战争本身。

Can we forge against these enemies a grand and global alliance, North and South, East and West, that can assure a more fruitful life for all mankind? Will you join in that historic effort?

为了反对这些敌人,我们能够将南方与北方、东方与西方团结起来,熔铸成一个伟大的和全球性的联盟,以确保全人类得享更为成果累累的生活吗?你们愿意参与这项历史性的努力吗?

In the long history of the world, only a few generations have been granted the role of defending freedom in its hour of maximum danger. I do not shrink from this responsibility -- I welcome it. I do not believe that any of us would exchange places with any other people or any other generation. The energy, the faith, the devotion which we bring to this endeavor will light our country and all who serve it. And the glow from that fire can truly light the world.

在世界历史的长河里,只有少数几代人被赋予了在自由面临最大危机时捍卫自由的使命,我不会畏缩于这一责任——我欢迎它!我也不相信我们中的任何人会愿意与其他国家的人民或其他世代的人民易地而处。我们在这场努力中所倾注的精力、信念和奉献将照耀我们的国家以及所有为之献身的人,火焰所放射出的光芒必将普照全世界。

And so, my fellow Americans, ask not what your country can do for you; ask what you can do for your country.

所以,我的美国同胞们,不要问你的国家为你做了什么,而应问你能为你的国家做些什么。

My fellow citizens of the world, ask not what America will do for you, but what together we can do for the freedom of man.

我的世界同胞们,不要问美国将为你做些什么,而应问我们应该一起为了全人类的自由做些什么。

Finally, whether you are citizens of America or citizens of the world, ask of us here the same high standards of strength and sacrifice which we ask of you. With a good conscience our only sure reward, with history the final judge of our deeds, let us go forth to lead the land we love, asking His blessing and His help, but knowing that here on earth God's work must truly be our own.

最后,无论是美国公民还是世界其他国家的公民,请用我们要求于你们的关于力量和牺牲的高标准来要求我们,本着我们唯一可以指望有所回报的善意良知,依着能最终裁决我们功业的历史,让我们着手领导我们所热爱的国家,在祈求神的赐福和神的帮助的同时,也能深切体认,在这片土地上,神的工作必定也是我们自己所应承担的使命。

posted @ 2007-06-30 11:18 chenweicai 阅读(578) | 评论 (0)编辑 收藏

摘要:

虽然session机制在web应用程序中被采用已经很长时间了,但是仍然有很多人不清楚session机制的本质,以至不能正确的应用这一技术。本文将详细讨论session的工作机制并且对在Java web application中应用session机制时常见的问题作出解答。
 
一、术语session
在我的经验里,session这个词被滥用的程度大概仅次于transaction,更加有趣的是transaction与session在某些语境下的含义是相同的。

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

四、理解session机制

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

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

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

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

另一种技术叫做表单隐藏字段。就是服务器会自动修改表单,添加一个隐藏字段,以便在表单提交时能够把session id传递回服务器。比如下面的表单
<form name="testform" action="/xxx">
<input type="text">
</form>


在被传递给客户端之前将被改写成
<form name="testform" action="/xxx">
<input type="hidden" name="jsessionid" value="ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764">
<input type="text">
</form>


这种技术现在已较少应用,笔者接触过的很古老的iPlanet6(SunONE应用服务器的前身)就使用了这种技术。
实际上这种技术可以简单的用对action应用URL重写来代替。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

七、跨应用程序的session共享

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

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

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

image
image

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

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

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


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

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

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

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

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

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


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


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

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

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

posted @ 2007-06-27 22:36 chenweicai 阅读(249) | 评论 (0)编辑 收藏

counting 1 bits C implementations

(idea) by bis (1.5 mon) (print)   ?   Thu Oct 18 2001 at 4:34:42

Here are C implementations of all the methods for counting 1 bits mentioned in that node. (Go read that first, if you haven't already.) All of the statistical information is purely anecdotal, but for what it's worth, it's based on my testing the code on a Pentium 3 and a Celeron 2, using the cl compiler of Microsoft Visual C++, and on a Sun Ultra 5, using gcc and Sun's own cc. For testing 64-bit code, I used __int64 on the Intel machines, and long long on the Sparc. It's worth noting that while Sun's compiler outputs faster executables than gcc, it doesn't change the relative performance of the different methods.

Table Lookup

Use a pre-built lookup table of all the 1-bit counts for every possibly byte, then index into that for each byte that comprises the word. This is the fastest method (slightly) for 32 bits on both Intel and Sparc, and (even more slightly) the fastest for 64 bits on Sparc, falling to second fastest on 64 bits on Intel. Changing the lookup table from anything but unsigned or int makes it a little slower (what with the extra casting and byte-loading the compiler is forced to add.)
unsigned numbits_lookup_table[256] = {
                0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2,
                3, 3, 4, 3, 4, 4, 5, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3,
                3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3,
                4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4,
                3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5,
                6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4,
                4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5,
                6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5,
                3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3,
                4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6,
                6, 7, 6, 7, 7, 8
                };
                unsigned numbits_lookup(unsigned i)
                {
                unsigned n;
                n = numbits_lookup_table[i & 0xff];
                n += numbits_lookup_table[i>>8  & 0xff];
                n += numbits_lookup_table[i>>16 & 0xff];
                n += numbits_lookup_table[i>>24 & 0xff];
                return n;
                }
                

 

Counters

If you want a full explanation of how this works, read my writeup at counting 1 bits, but suffice it to say that you are essentially partitioning the word into groups, and combining the groups by adding them together in pairs until you are left with only one group, which is the answer. (performance notes in the next section.)
unsigned numbits(unsigned int i)
                {
                unsigned int const MASK1  = 0x55555555;
                unsigned int const MASK2  = 0x33333333;
                unsigned int const MASK4  = 0x0f0f0f0f;
                unsigned int const MASK8  = 0x00ff00ff;
                unsigned int const MASK16 = 0x0000ffff;
                i = (i&MASK1 ) + (i>>1 &MASK1 );
                i = (i&MASK2 ) + (i>>2 &MASK2 );
                i = (i&MASK4 ) + (i>>4 &MASK4 );
                i = (i&MASK8 ) + (i>>8 &MASK8 );
                i = (i&MASK16) + (i>>16&MASK16);
                return i;
                }
                

 

Optimized Counters

call pointed out in counting 1 bits that you could optimize the Counters method further if you pay attention to which bits you care about and which you don't, which allows you to skip applying some of the masks.

 

Some symbols that I'll use to represent what's going on:

  • 0: bits we know are zero from the previous step
  • o: bits we know are zero due to masking
  • -: bits we know are zero due to shifting
  • X: bits that might be 1 and we care about their values
  • x: bits that might be 1 but we don't care about their values

 

So a 0 plus a 0 is still a 0, obviously; the tricky ones are the others, but they're not even so bad. 0 plus X is X, since if the X is a 0 or a 1, added to 0 it will pass through unchanged. However, X plus X is XX, because the sum can range from 0 (0+0), to 10 (1+1). The same holds true with xs, once those show up.

Step 1:

        oXoXoXoXoXoXoXoXoXoXoXoXoXoXoXoX
                +       -XoXoXoXoXoXoXoXoXoXoXoXoXoXoXoX
                XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
                
Step 2:
        ooXXooXXooXXooXXooXXooXXooXXooXX
                +       --XXooXXooXXooXXooXXooXXooXXooXX
                0XXX0XXX0XXX0XXX0XXX0XXX0XXX0XXX
                
Step 3:
        oooo0XXXoooo0XXXoooo0XXXoooo0XXX
                +       ----0XXXoooo0XXXoooo0XXXoooo0XXX
                0000XXXX0000XXXX0000XXXX0000XXXX
                
Step 4:
        oooooooo0000XXXXoooooooo0000XXXX
                +       --------0000XXXXoooooooo0000XXXX
                00000000000XXXXX00000000000XXXXX
                
Step 5:
        oooooooooooooooo00000000000XXXXX
                +       ----------------00000000000XXXXX
                00000000000000000000000000XXXXXX
                
You'll notice that the higher the step, the more known zeros (0) there are. call's suggestion was to change step 5 to this:

Step 5:
        ooooooooooooxxxx00000000000XXXXX
                +       ----------------00000000000XXXXX
                000000000000xxxx0000000000XXXXXX
                (mask)  ooooooooooooooooooooooooooXXXXXX
                
(where "(mask)" means "after adding, apply a mask".)

 

However, you can go back even further and apply the same technique - all the way to step 3, in fact. The best I can think to optimize this changes the last three steps into the following: Step 3:

        0xxx0XXX0xxx0XXX0xxx0XXX0xxx0XXX
                +       ----0XXX0xxx0XXX0xxx0XXX0xxx0XXX
                0xxxXXXX0xxxXXXX0xxxXXXX0xxxXXXX
                (mask)  0000XXXX0000XXXX0000XXXX0000XXXX
                
Step 4:
        0000xxxx0000XXXX0000xxxx0000XXXX
                +       --------0000XXXX0000xxxx0000XXXX
                0000xxxx000XXXXX000xxxxx000XXXXX
                
Step 5:
        0000xxxx000xxxxx000xxxxx000XXXXX
                +       ----------------000xxxxx000XXXXX
                0000xxxx000xxxxx00xxxxxx00XXXXXX
                (mask)  ooooooooooooooooooooooooooXXXXXX
                
Anyway, that's all very lovely, but here's the C to do it:
unsigned numbits(unsigned int i)
                {
                unsigned int const MASK1  = 0x55555555;
                unsigned int const MASK2  = 0x33333333;
                unsigned int const MASK4  = 0x0f0f0f0f;
                unsigned int const MASK6 = 0x0000003f;
                unsigned int const w = (v & MASK1) + ((v >> 1) & MASK1);
                unsigned int const x = (w & MASK2) + ((w >> 2) & MASK2);
                unsigned int const y = (x + (x >> 4) & MASK4);
                unsigned int const z = (y + (y >> 8));
                unsigned int const c = (z + (z >> 16)) & MASK6;
                return c;
                }
                
The performance on this method is marginally worse than the lookup method in the 32 bit cases, slightly better than lookup on 64 bit Intel, and right about the same on 64 bit Sparc. Of note is the fact that loading one of these bitmasks into a register actually takes two instructions on RISC machines, and a longer-than-32-bit instruction on the Intel, because it's impossible to pack an instruction and 32 bits worth of data into a single 32 bit instruction. See the bottom of jamesc's writeup at MIPS for more details on that...

 

Mind-bending "best" method (even more optimized counters)

A slightly-modified version of the code on this page: http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel, which in turn stole the code from the "Software Optimization Guide for AMD AthlonTM 64 and OpteronTM Processors":
unsigned numbits(unsigned int i)
                {
                unsigned int const MASK1 = 0x55555555;
                unsigned int const MASK2 = 0x33333333;
                unsigned int const MASK4 = 0x0f0f0f0f;
                unsigned int const w = v - ((v >> 1) & MASK1);
                unsigned int const x = (w & MASK2) + ((w >> 2) & MASK2);
                unsigned int const y = (x + (x >> 4) & MASK4);
                unsigned int const c = (y * 0x01010101) >> 24;
                return c;
                }
                
This method is identical to the "Optimized Counters" method, with two tricks applied:
  1. To get rid of an AND in the first line: instead of adding adjacent bits, it subtracts the high bit by itself of a pair of the bits from the pair together, because the results are the same. 00 - 0 = 0, 01 - 0 = 01, 10 - 1 = 01, 11 - 1 = 10
  2. To merge the last two lines into one, it uses a multiply and a shift, which adds the four remaining byte-sized "counters" together in one step.

 

Subtract 1 and AND

See counting 1 bits SPOILER for a fuller explanation of this one, but basically the lowest 1-bit gets zeroed out every iteration, so when you run out of 1s to zero, you've iterated to the number of bits in the word. Clever. Unfortunately, not that terribly fast; it's roughly two to three times slower than the lookup and counters methods on both architectures.
unsigned numbits_subtraction(unsigned i)
                {
                unsigned n;
                for(n=0; i; n++)
                i &= i-1;
                return n;
                }
                

 

Straightforwardly Examine Each Bit

The most easily understandable and slowest method: iterate over all the bits in the word; if the current bit is a 1, then increment the counter, otherwise, do nothing. That's actually done here by looking at the least-significant bit on each iteration, then shifting to the right one, and iterating until there are no more 1 bits in the word. There's a little optimization in the #ifndef here: instead of doing if (i & 1) n++;, which uses a branch instruction, just add the actual value of the least-significant bit to the counter ( n += (i & 1); ), as it will be a 1 when you want to add 1, and 0 when you don't. (We're just twiddling bits anyway, so why not?) This actually makes the processor do more adds, but adding is fast, and branching is slow, on modern processors, so it turns out to be about twice as fast. However, even "twice as fast" is still four to five times slower than the lookup method, again, on all architectures.
unsigned numbits(unsigned int i)
                {
                unsigned n;
                for(n=0; i; i >>= 1)
                #ifndef MORE_OPTIMAL
                if (i & 1) n++;
                #else
                n += (i & 1);
                #endif
                return n;
                }
                
Now, why does this all matter? It doesn't, really, but it was sure a good way to waste some time, and maybe someone learned some optimizing tricks from it... (Well, I did, actually - so I hope someone else did as well.)

posted @ 2007-06-27 18:30 chenweicai 阅读(329) | 评论 (0)编辑 收藏

步一:定义标签

步二:创建标签库描述器TLD diagnostics.tld, 将它放在WEB-INF目录下的tlds文件夹下,diagnostics.tld如下:

<?xml version="1.0"?>
<taglib>
 <tlibversion>1.0</tlibversion>
 <jspversion>1.1</jspversion>
 <shortname>diag</shortname>
 <tag>
   <name>getWebServer</name>
   <tagclass>servlet.GetWebServerTag</tagclass>
   <bodycontent>empty</bodycontent>
 </tag>
</taglib>

步三:编写标签处理器 GetServerTag.java

package servlet;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.TagSupport;

public class GetWebServerTag extends TagSupport {

 @Override
 public int doStartTag() throws JspException {
  try{
   // get the request object from the page context
   HttpServletRequest request = (HttpServletRequest)pageContext.getRequest();
   
   // Request information form the web server
   URL url = new URL("http", request.getServerName(), request.getServerPort(), "/");
   URLConnection con = url.openConnection();
   ((HttpURLConnection)con).setRequestMethod("OPTIONS");
   String webserver = con.getHeaderField("server");
   
   // write it to the output stream
   JspWriter out = pageContext.getOut();
   out.print(webserver);
  }catch (IOException e)
  {
   throw new JspException(e.getMessage());
  }
  
  return SKIP_BODY;
 }
 
}


步四: 编写WEB.xml文件

加入:
<jsp-config>
<taglib>
<taglib-uri>diagnostics</taglib-uri>
<taglib-location>/WEB-INF/tlds/diagnostics.tld</taglib-location>
</taglib>
</jsp-config>

步五:编写jsp页面,将标签并入该JSP页面

<%@ taglib prefix="diag" uri="diagnostics"%>
   
<html>
<head>
<title>Basci Example of a Custom Tag</title>
</head>
<body>
<H3>Basci Example of a Custom Tag</H3>
 The web server is <diag:getWebServer/>
</body>
</html>


OK  , so much so this ,


wish you success

<注: 注意红色的地方要一致哦>

posted @ 2007-06-26 20:04 chenweicai 阅读(278) | 评论 (0)编辑 收藏

本教程目的 第 1 页(共3 页)
                                                                                        

想要在 JavaServer Pages (JSP) 应用程序中添加自定义标签吗?本教程将为您展示如何用这些标签编写类似于 JSP 技术自带操作 —— 如 jsp:useBeanjsp:getPropertyjsp:forward —— 的自定义操作。介绍如何用特定于自已的域的表示逻辑的自定义操作来扩展 JSP 语法。

在 JSP 应用程序中添加 自定义标签 的能力可以使您将工作重点放到以文档为中心的开发方式上。可以使 Java 代码不出现在 JSP 页中,从而使这些页面更容易维护。(我从经验中学到,在 JSP 页中放入过多的 Java 代码时,代码维护就会成为可怕的任务)。本教程将使您可以立即开发出自定义标签。了解了 JSP 自定义标签开发的好处后,您可能会对程序员没有更多地使用它而感到意外。

在本教程中,我将讨论使用自定义标签的基本内容。将介绍如何用自定义标签创建可重用的表示组件并避免在 JSP 页加入 Java scriptlet。

在本教程中,我们将:

  • 定义一个 JSP 自定义标签体系结构。
  • 解释简单标签。
  • 定义嵌套标签。
  • BodyContent 解释标签。
  • 在标签中添加属性。
  • 在标签中添加 scriptlet 变量。
  • 用自定义标签实现控制流程。
  • 用 Struts 简化标签部署。

我要学习本教程吗? 第 2 页(共3 页)


如果发现自己在 JSP 应用程序中加入了大量 Java scriptlet,那么本教程就是为您准备的。 阅读本教程后,就会掌握将 Java 代码从 JSP 页面中清除出去所需要的信息。

本教程假定读者熟悉 Java 平台、JavaServer Pages (JSP) 技术、MVC 模式、Reflection API、Model 2,最好还有 Struts 框架。此外,要从本教程中得到最大的收获,还需要很好的使用标签库的经验

关于作者 第 3 页(共3 页)


Rick Hightower 是一位 J2EE 开发人员和顾问,他热衷于使用 J2EE、Ant、Hibernate、Struts、IMB 的 ETTK 和 Xdoclet。 Rick 是 Trivera Technologies 的前任 CTO,这是一家全球培训、指导和咨询公司,其重点是企业开发。他经常在 IBM developerWorks 上发表文章,并编写了 10 多篇 developerWorks 教程,内容从 EJB 技术到 Web 服务到 XDoclet。 Rick 不久前与别人共同开办了另一家名为 ArcMind 的公司,它专门研究 agile 方法,还从事 Struts/JavaServer Faces 开发、咨询和指导。

在为 eBlox 工作时,Rick 和 eBlox 小组远在 1.0 版本之前就已使用 Struts 为电子商务站点构建了两个框架和一个 ASP (应用程序服务提供者)。这个框架目前正在为 2000 多个在线商店店面提供支持。

Rick 最近完成了一本名为 Professional Jakarta Struts 的书。在周游全国对 J2EE 和 Struts 项目提供咨询,或者在大会上发表关于 J2EE 和极端编程 (extreme programing)的讲演之余,Rick 喜欢在通宵咖啡店喝咖啡,写一些有关 Struts、J2EE 和其他内容的文章,并以第三人称描写他自己。

标签处理程序 第 1 页(共2 页)


在创建自定义标签之前,需要创建一个 标签处理程序。标签处理程序是一个执行自定义标签操作的 Java 对象。在使用自定义标签时,要导入一个 标签库 —— 即一组标签/标签处理程序对。通过在 Web 部署描述符中声明库导入它,然后用指令 taglib 将它导入 JSP 页。

如果 JSP 容器在转换时遇到了自定义标签,那么它就检查 标签库描述符(tag library descriptor) (TLD) 文件以查询相应的标签处理程序。TLD 文件对于自定义标签处理程序,就像 Web 部署描述符对于 servlet 一样。

在运行时,JSP 页生成的 servlet 得到对应于这一页面所使用的标签的标签处理程序的一个实例。生成的 servlet 用传递给它的属性初始化标签处理程序。

标签处理程序实现了 生存周期 方法。生成的 servlet 用这些方法通知标签处理程序应当启动、停止或者重复自定义标签操作。生成的 servlet 调用这些生存周期方法执行标签的功能。

标签的类型 第 2 页(共2 页)


可以定义两种类型的标签:

  • javax.servlet.jsp.tagext.Tag
  • javax.servlet.jsp.tagext.BodyTag

正文 进行操作 —— 即对在开始和结束标签之间的内容进行操作的 —— 标签必须实现 BodyTag 接口。在这个教程中,我们将称这些标签为 正文标签。我们将不对其正文操作的标签称为 简单标签。简单标签可以实现 Tag 接口,尽管不要求它们这样做。要记住不对其正文操作的标签仍然 正文,只不过,它的标签处理程序不能读取这个正文。

简单标签的例子 第 1 页(共9 页)
                                                                                                   上一页      下一页

Struts 框架带有几个自定义标签库(有关 Struts 的更多信息的链接请参阅 参考资料 )。这些库中的一个标签可以创建一个支持改写 URL 的链接并用 jsessionid 对改写的连接编码。

不过有一个问题:如果希望传递一组请求参数(如查询字符串),也许必须为此创建一个 Java scriptlet。真是乱!下面的清单 (search_results.jap) 展示了一个 JSP 页,它被迫加入了这样一个 scriptlet。

 <%@ taglib uri="struts-html" prefix="html" %> <jsp:useBean class="java.util.HashMap" id="deleteParams" /> <% deleteParams.put("id", cd.getId()); deleteParams.put("method","delete"); %> <!-- Pass the map named deleteParams to html:link to generate the request parameters--> <html:link action="/deleteCD" name="deleteParams">delete </html:link> </font></td>  

search_results.jsp 创建一个 hashmap 并向这个 map 传递两个属性。在下面几小节,我们将创建一个不用 Java 代码完成这项工作的自定义标签。我们的标签将定义如下的一个 hashmap:

 <map:mapDefine id="deleteParams"> <map:mapEntry id="id" name="cd" property="id"/> <map:mapEntry id="method" value="delete"/> </map:mapDefine> <!-- Pass the map named deleteParams to html:link to generate the request parameters--> <html:link action="/deleteCD" name="deleteParams">delete </html:link> </font></td>  

这将使我们可以容易地创建小型 map。

这个例子将展示几个关键概念,包括使用嵌套标签和定义 scriplet 变量。首先我将解释这个标签是如何工作的。然后在以后的几节中建立这些概念,并介绍如何编写这个标签的不同形式,使它们处理其正文并控制执行流程。

构建简单标签的步骤 第 2 页(共9 页)


让我们创建一个定义一个 HashMap scriptlet 变量的标签。为此,需要实现标签处理程序接口 (javax.servlet.jsp.tagext.Tag)。因此,我们要创建的第一个标签将是一个简单标签。

这个标签将实例化一个 map。使用这个标签的开发人员可以指定要实例化的 map 的类型 —— HashMapTreeMapFastHashMap 或者 FastTreeMapFastHashMapFastTreeMap 来自 Jakarta Commons Collection library (有关链接请参阅 参考资料)。开发人员还可以指定标签所在的范围 —— 页、请求、会话还是应用程序范围。

要构建这个简单标签,我们需要完成以下步骤:

  1. 创建实现了 Tag 接口(准确地说是 javax.servlet.jsp.tagext.Tag)的标签处理程序类。

  2. 创建一个 TLD 文件。

  3. 在标签处理程序 Java 类中创建属性。

  4. 在 TLD 文件中定义与标签处理程序 Java 类中定义的属性对应的属性。

  5. 在 TLD 文件中声明 scriptlet 变量。

  6. 实现 doStartTag() 方法。在标签处理程序类中,根据属性将值设置到 scriptlet 变量中。

如果您像我一样,可能会提前阅读书的结尾,所以请查看 附录 中标签处理程序类的完整列表以了解这个过程是如何结束的。

在下面几小节中,我们将分析 MapDefineTag 的实现,并分析如何到达这一步。

第 1 步:创建一个实现了 Tag 接口的标签处理程序 第 3 页(共9 页)


为了编写标签处理程序,必须实现 Tag 接口。如前所述,这个接口用于不操纵其标签正文的简单标签处理程序。就像 J2EE API 文档 (有关链接请参阅 参考资料)所说的:Tag 接口定义了标签处理程序和 JSP 页实现类之间的基本协议。它定义了在标签开始和结束时调用的生存周期和方法。

标签处理程序接口有以下方法:

方法 作用
int doStartTag() throws JspException 处理开始标签
int doEndTag() throws JspException 处理结束标签
Tag getParent()/void setParent(Tag t) 获得/设置标签的父标签
void setPageContext(PageContext pc) pageContext 属性的 setter 方法
void release() 释放获得的所有资源

TagSupport

现在,不必直接实现 Tag 接口,相反,用 map 定义的(map-defining)标签将继承 TagSupport 类。这个类以有意义的默认方法实现 Tag 接口,因而使开发自定义标签更容易 (有关 TagSupport 的 API 文档的链接请参阅 参考资料)。 例如,TagSupport 类定义了 get/setParent()setPageContext(),这与所有标签处理程序几乎相同。 get/setParent() 方法允许标签嵌套。TagSupport 类还定义了一个可以被子类使用的 pageContext 实例变量 (protected PageContext pageContext),这个变量是由 setPageContext() 方法设置的。

在默认情况下,TagSupport 实现了 doStartTag() 以使它返回 SKIP_BODY 常量,表示将不对标签正文进行判断。 此外,在默认情况下,doEndTag() 方法返回 EVAL_PAGE,它表示 JSP 运行时引擎应当对页面的其余部分进行判断。 最后,TagSupport 实现了 release(),它设置 pageContext 及其父元素为 null

TagSupport 类还实现了 IterationTag 接口和 doAfterBody(),这样它就返回 SKIP_BODY。 在后面讨论进行迭代的标签时我将对此加以更详细的解释(请参阅 用自定义标签控制流程)。

好了,现在让我们通过继承 TagSupport 来实现 Tag 接口:

 ... import javax.servlet.jsp.tagext.TagSupport; ... public class MapDefineTag extends TagSupport { ...  

我们已经定义了标签处理程序,现在需要增加从处理程序到 TLD 文件中的标签的映射。我们将在下一小节中对此进行处理。然后,将完成 MapDefineTag 中剩余的代码。

第 2 步:创建一个 TLD 文件 第 4 页(共9 页)


TLD 文件对自定义标签处理程序的作用就像 Web 部署描述符对 servlet 的作用。 TLD 文件列出了从标签名到标签处理程序的映射。 这个文件中的大多数数据都是在 JSP 页转换时使用的。 TLD 文件通常保存在 Web 应用程序的 WEB-INF 目录,并在 web.xml 文件中声明。它们一般用 .tld 扩展名结束。

TLD 文件有一个 导言(preamble),在这里标识 JSP 技术的版本和使用的标签库。这个导言通常看起来像这样:

 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN" "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd"> <taglib> <tlib-version>1.0</tlib-version> <jsp-version>1.2</jsp-version> <short-name>map</short-name>  

让我们更详细地分析一下这些标签:

  • TLD 文件的根元素是 taglibtaglib 描述了一个 标签库 —— 即一组标签/标签处理程序对。

  • 因为我们使用的是 JSP 版本 1.2,所以在这个例子中需要 tlib-versionshort-name 元素。

  • tlib-version 元素对应于标签库版本。

  • jsp-version 对应于标签库所依赖的 JSP 技术的版本。

  • short-name 元素定义了 IDE 和其他开发工具可以使用的标签库的简单名。

  • taglib 元素包含许多 tag 元素,标签库中每一个标签有一个 tag 元素。

因为我们刚创建了自己的类,所以我们将继续往下进行,在 TLD 文件中声明这个类,如下所示:

 <taglib> 
...
<tag>
<name>mapDefine</name>
<tag-class>trivera.tags.map.MapDefineTag</tag-class>
<body-content>JSP</body-content>
...

tag 元素用于将自定义标签映射到它们的自定义标签处理程序。上述清单中的 tag 元素将自定义标签 mapDefine 映射到处理程序 trivera.tags.map.MapDefineTag。 因此,不论在 mapDefine 上运行的是什么转换引擎,都会调用 trivera.tags.map.MapDefineTag

已经在 TLD 中定义了标签,接下来要在标签处理程序类中定义这个标签的一些属性了。

第 3 步:在标签处理程序 Java 类中创建属性 第 5 页(共9 页)
                                                                                                               上一页      下一页

我们希望为 mapDefine 标签指定三个属性,如下所示:

属性说明
id 新 scriptlet 变量的名字。
scope 新 scriptlet 变量所在的范围。
type 新 scriptlet 变量的类型 (HashMapFastHashMapTreeMap 或者 FastTreeMap)。 如果 type 设置为 hash,那么就会创建一个 HashMap。如果 type 设置为 fasthash,那么将创建 FastHashMap

在 JSP 页中使用这个标签时,它看起来将像下面这样:

 <map:mapDefine id="editParams" scope="session" type="hash"> 
...
</map:mapDefine>

这个标签将在会话范围内创建一个名为 editParamsHashMap

为了在标签处理程序中创建属性,需要定义相应的 JavaBean 属性。 因此,每一个属性在标签处理程序中都有对应的 setter 方法,如下所示:

 public class MapDefineTag extends TagSupport { 
...
private String type = FASTTREE;
private String id;
private String scope;


public void setType(String string)
{ type = string; }

public void setId(String string)
{ id = string; }
public void setScope(String string)
{ scope = string; }

转换引擎将用硬编码的配置数据或者运行时表达式设置这个标签的属性。 我们将在 第 4 步:在 TLD 文件中定义属性 中对此做更详细的讨论。

第 5 步:实现 doStartTag() 方法 中,我们将在标签处理程序的 doStartTag() 方法中使用这些属性。

第 4 步:在 TLD 文件中定义属性 第 6 页(共9 页)


就 像上一小节中所做的那样,通过声明 JavaBean 属性定义自定义属性,然后在 TLD 文件中声明这些属性。 每一个 JavaBean 属性都必须与相应的自定义标签属性相匹配。 在 TLD 中定义的属性必须匹配 JavaBean 属性,不过却可以有与标签属性不匹配的 JavaBean 属性。

下面是 MapDefineTag 的属性声明:

 <tag> <name>mapDefine</name> <tag-class>trivera.tags.map.MapDefineTag</tag-class> <body-content>JSP</body-content> ... <attribute> <name>id</name> <required>true</required> <rtexprvalue>false</rtexprvalue> <description>The id attribute</description> </attribute> <attribute> <name>scope</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description>The scope attribute</description> </attribute> <attribute> <name>type</name> <required>false</required> <rtexprvalue>false</rtexprvalue> <description> Specifies the type of map valid values are fasttree, fasthash, hash, tree </description> </attribute> </tag>  

name 元素指定属性的名字。required 元素指定属性是否是必需的(默认值是 false)。rtexprvalue 元素表明属性是硬编码了转换时的值还是允许使用运行时 scriptlet 表达式。

记住,MapDefineTag 类必须为前面描述的每一个属性定义一个 JavaBean 属性,我们在 第 3 步:在标签处理程序 Java 类中创建属性 中完成这个任务。

第 5 步:实现 doStartTag() 方法 第 7 页(共9 页)


标签开始时调用 doStartTag() 方法 —— 从开发人员的角度看,这是当引擎遇到 <map:mapDefine> 时发生的。如果 doStartTag() 返回 SKIP_BODY,那么将不处理标签正文。 如果它返回一个 EVAL_BODY_INCLUDE,那么将处理正文。

MapDefine 类的 doStartTag() 方法完成以下工作:

  • 根据 type 属性确定要创建的 map 的属性。
  • 根据 scope 属性确定新的 map 对象放在什么范围内。
  • 根据 id 属性确定新 map 对象要放入的范围的名字。

让我们更详细地分析这个过程。MapDefine 类检查 type 属性是设置为 FASTTREEHASHTREE 还是 FASTHASH。然后创建相应的 map,如下所示:

 /* String constants for the different types of maps we support */ public static final String FASTHASH = "FASTHASH"; public static final String FASTTREE = "FASTTREE"; public static final String HASH = "HASH"; public static final String TREE = "TREE"; /** The map we are going to create */ private Map map = null; /** The member variable that holds the type attribute */ private String type = FASTTREE; ... public int doStartTag() throws JspException { /** Based on the type attribute, determines which type of Map to create */ if (type.equalsIgnoreCase(FASTTREE)) { map = new FastTreeMap(); } else if (type.equalsIgnoreCase(HASH)) { map = new HashMap(); } else if (type.equalsIgnoreCase(TREE)) { map = new TreeMap(); } else if (type.equalsIgnoreCase(FASTHASH)) { map = new FastHashMap(); }  

然后,用 idscope 属性将 hashmap 以一个给定的名字设置到一个给定范围中:

 private String id; private String scope; public int doStartTag() throws JspException { ... if (scope == null){ pageContext.setAttribute(id, map); }else if("page".equalsIgnoreCase(scope)){ pageContext.setAttribute(id, map); }else if("request".equalsIgnoreCase(scope)){ pageContext.getRequest().setAttribute(id, map); }else if("session".equalsIgnoreCase(scope)){ pageContext.getSession().setAttribute(id, map); }else if("application".equalsIgnoreCase(scope)){ pageContext.getServletContext().setAttribute(id, map); } return EVAL_BODY_INCLUDE; }  

如果范围属性是 null,那么 map 将放入页范围。否则,参数将放入通过 scope 属性传递的范围名所对应的范围中。

到目前为止,我们已经有一个非常简单的标签,它有三个属性:idscopetype。 我们将用给定的名字将 map 放到一个范围中。但是,我们还有一件事没做,就是声明 scriptlet 变量。 为了做到这一点,需要向 TLD 再添加一项,这就是我们在下一小节中所要做的事。

posted @ 2007-06-26 19:19 chenweicai 阅读(1935) | 评论 (0)编辑 收藏

最近因为比较闲,而且有几个师弟来上海找工作,交流后有点感受,所以发了一个贴子.那是第一次在CSDN上发贴,最近几天又想了想,所以再发一贴,可能也是最后一贴,我只谈谈我在上海做了6年软件开发所了解到的一些东西,本人能力一般,所以我说的也只针对能力一般的人,特别厉害的牛人不需要理会我说的话.
希望能对有些人有一点帮助.
   对于快要毕业的大学生来上海找工作,我觉得最重要的是基础知识一定要好.我推荐看JAVA编程思想这本书,很多公司笔试的JAVA方面的题目出自这本书.另外JSP,SERVLET,JDBC一定要有所了解,这个能说明你在大学的时候实习过,做过一些小项目的.HIBERNATE,STRUTS,SPRING这些东西我不推荐你去学习,我觉得你应该学习如何处理异常,学会用JDBC访问数据库,学会如何释放资源,如何控制数据库事务,学会如何写自定义标签,这样能够显著的提高你的编程水平.
   工作一年的后我觉得应该回过去看看大学的数据结构,算法,操作系统,然后应该对面象对象要有更深刻的理解,设计模式也应该慢慢开始涉及到(JAVA变编程思想这本书建议多看几次).我不建议工作一年就开始换工作,因为找工作要花时间,新工作就要适应新的框架和新的业务,这样你就会乱费很多时间的.我还是不建议你去看HIBERNATE,STRUTS,SPRING这样的东西,虽然你工作中可能要用到它.对于英语我觉得应该早做准备,强迫自己看一些英文方面的书籍,提高自己的口语水平,另外我还要强调一点,适当的参加培训是很有必要的,上海有很多的外语培训机构(主要是培训商务交流),效果很不错的,不过也很贵的,所以去前一定要计划好,不要象大学那样交了钱不去上课或者不认真听课,那样就最好先不要去.
   工作两年后,如果你算法,数据结构,设计模式等等有比较深的了解的时候
 我觉得HIBERNATE,STRUTS,SPRING的实现方法你应该能够很快理解.
 我还是不推荐你学习这些,我推荐学习EJB.推荐书籍精通EJB,ED ROMAN写的那本.就象他说的这本书不是宣传EJB,不是写关于EJB世界如何精彩,不是为了消灭某个其他对手等等,学习这本书是希望你能构建出健壮,安全,可扩展的服务器部署.他里面很多的概念你可能在工作中碰不到,但是不表示你可以不需要理会,而应该去思考如果碰到这样的问题我应该怎样解决,然后可以去网上找资料解决它.(这本书是写EJB2.0的,但是我还是推荐,看完后你绝对不会觉得是乱费时间,我也觉得没有做过项目也很难理解这本书的)
   我觉得一个程序员要到一定档次,英语是必须的,所以我觉得如果能有一个全英文的工作环境工作一段时间是很应该的.现在基本上的技术规范全部是英文写的,很多技术论文也是英文的,如果英语不行,水平很难提高.其实对于在上海工作三年的程序员基本上知道自己应该如何发展的.

   再说说上海JAVA程序员的大概工资吧.一般应届毕业生3K左右,三年工作经验5K以上,5年工作经验8K以上.这个工资是指很多公司全部能够给出,而且也比较容易找到的.对于很厉害的人肯定不合适.大公司和外企在我说的基础上乘1.5倍.(其实上海找软件开发大专就可以了,只不过要牺牲一部分福利,和需要更高的能力,本科就一点问题也没有)

   对于从外地来上海找工作的学生,我建议做好长期打算,毕竟找工作是需要开销的,上海的交通费用很高,而且是省不掉的.但是心态一定要摆正,首先要有决心,工作一定可以找到,另外不要浮躁,其实找工作应该是一个不段学习的过程,
当你面试一家公司,那么笔试的题目在你面试完后一定要花时间把你你不会的搞明白,笔试十家后你会发现其实题目差不多的.态度一定要谦虚,待遇可以适当降低
但是不要低于2.5K(不过一般你转正都能要求到这个数目的,只要你好好和老板谈).要相信工作可以找到也要做好长期打算(就是万一短时间找不到工作的打算,不要轻易退场,挺一挺就过去了),经济上要准备充足.而且面试不要担心什么的,上海软件公司多得很,你今天面试不过,三个月后这个公司可能又要你去面试的.他不会记得你的.在投简历的时候,简单点的两页就可以突出你在学校期间做过哪些项目.只要是软件工程师就可以投,最多乱费两页纸,不要做太好的简历,是乱费钱.网上投简历就应该全部投,最多是不要你去面试,不会少什么的.找工作能力是一方面,技巧也有的,只有自己慢慢体会了.
  再说说为什么我不推荐学习HIBERNATE,STRUTS,SPRING,因为我一直就在用这个,现在突然发现自己好象无法前进了.所以现在又重新学习基础,但是问题是我年龄大了,事情多了,可以供学习的时间不多了.所以我才极力推荐在工作的前三年一定要把基本功打扎实.现在有很多程序员会用HIBERNATE,STRUTS,SPRING来写项目,可是确不会写一个JDBC连接,对JAVA的多线程一点不知道,SERVLET不会的也很多.这个是很不好的,也是现在国内很多程序员的通病.为什么很多人说程序员到30就写不动代码,学不会新东西了,你基础那么差还能学会就真的怪了.
   不要把时间放到讨论EJB和SPRING哪个好哪个差上面,不过我还是倾向EJB的,很多人说EJB能实现的SPRING也能实现(好象有EJB能实现的但SPRING不能实现,但是没有完全了解不敢说),但是为什么不想想SPRING能实现的难道EJB不能实现,实现麻烦一点有什么关系呢,学习一个东西不是要看到它的优点,而更应该看到它的缺点.呵呵,不要骂我,我认为SOA将成为潮流,把骂我的时间放到了解SOA上.而且想去大的外企,那么社会招聘百分之八十会问EJB的.进去大的外企有两个方式,一个是社会招聘,但是这个面试特别严格,基础不好外语不好的基本上没有戏.(社会招聘指招聘有工作经验的,应届毕业生叫校园招聘)另外一个就是通过外包进去,这样进去要求要低,但是你必须乱费几年,等机会直接进你外包出去的公司或者跳巢到和他同一档次的外企.

   很多人说待遇不重要,重要的是学到东西,这个是不对的.待遇是你价值的体现,学东西就为了更好的待遇,所以要工资的时候不要觉得不好意思,想要多少就直接说出来.只有生活有保障才能更好的学习,更好的工作.工资高,心情就好,做什么也快呀.其实应该这么说,能学到什么不重要,重要的是工资要高.

   所谓程序员只能到30岁的说法是明显不对的,程序员也不是一个跳板,不要总是想去做系统分析,去搞管理,而应该扎扎实实的学习.做程序员有什么不好,从程序员开始,往中级程序员,高级程序员,职深程序员,专家程序员一直走下去.

   再次强调,外语很重要,请把它放在第一位置.最近好象软件行业也开始好转起来了,对日的外包多不说,欧美的外包也多起来了,而且外包的性质也发生变化,从最初的编码和测试发展到分析,设计,编码,测试全部外包了.这样和做国内的项目区别不大,所以还是很有发展前景的.

   好象还有很多想说的但是说不出来,发现在CSDN上发贴很费时间,所以这个也是我最后一次发贴,项目马上开启了,也没有时间来论坛了.希望想来上海发展的和在上海做JAVA的程序员全部能找到满意的工作,生活更开心点,毕竟做程序还是很辛苦的.以上只是个人观点,没有任何强加给任何人的意思.

posted @ 2007-04-28 22:55 chenweicai 阅读(279) | 评论 (0)编辑 收藏