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

2007年3月1日

引子 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 阅读(275) | 评论 (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 阅读(295) | 评论 (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 阅读(598) | 评论 (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 阅读(255) | 评论 (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 阅读(342) | 评论 (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 阅读(288) | 评论 (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 阅读(1941) | 评论 (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 阅读(285) | 评论 (0)编辑 收藏

面向对象的特征有哪些方面 
 
1. 抽象:抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面。抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节。抽象包括两个方面,一是过程抽象,二是数据抽象。

    继承:继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法。对象的一个新类可以从现有的类中派生,这个过程称为类继承。新类继承了原始类的特性,新类称为原始类的派生类(子类),而原始类称为新类的基类(父类)。派生类可以从它的基类那里继承方法和实例变量,并且类可以修改或增加新的方法使之更适合特殊的需要。

   封装:封装是把过程和数据包围起来,对数据的访问只能通过已定义的界面。面向对象计算始于这个基本概念,即现实世界可以被描绘成一系列完全自治、封装的对象,这些对象通过一个受保护的接口访问其他对象。

   多态性:多态性是指允许不同类的对象对同一消息作出响应。多态性包括参数化多态性和包含多态性。多态性语言具有灵活、抽象、行为共享、代码共享的优势,很好的解决了应用程序函数同名问题。

2、String是最基本的数据类型吗?
基本数据类型包括byte、int、char、long、float、double、boolean和short。
java.lang.String类是final类型的,因此不可以继承这个类、不能修改这个类。为了提高效率节省空间,我们应该用StringBuffer类。

3、int 和 Integer 有什么区别
Java 提供两种不同的类型:引用类型和原始类型(或内置类型)。Int是java的原始数据类型,Integer是java为int提供的封装类。Java为每个原始类型提供了封装类。
原始类型封装类boolean Boolean  char Character  byte Byte  short Short  int Integer  long Long  float Float  double Double
引用类型和原始类型的行为完全不同,并且它们具有不同的语义。引用类型和原始类型具有不同的特征和用法,它们包括:大小和速度问题,这种类型以哪种类型的数据结构存储,当引用类型和原始类型用作某个类的实例数据时所指定的缺省值。对象引用实例变量的缺省值为 null,而原始类型实例变量的缺省值与它们的类型有关。

4、String 和StringBuffer的区别
JAVA平台提供了两个类:String和StringBuffer,它们可以储存和操作字符串,即包含多个字符的字符数据。这个String类提供了数值不可改变的字符串。而这个StringBuffer类提供的字符串进行修改。当你知道字符数据要改变的时候你就可以使用StringBuffer。典型地,你可以使用StringBuffers来动态构造字符数据。

5、运行时异常与一般异常有何异同?
异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误。java编译器要求方法必须声明抛出可能发生的非运行时异常,但是并不要求必须声明抛出未被捕获的运行时异常。

6、说出Servlet的生命周期,并说出Servlet和CGI的区别。
Servlet被服务器实例化后,容器运行其init方法,请求到达时运行其service方法,service方法自动派遣运行与请求对应的doXXX方法(doGet,doPost)等,当服务器决定将实例销毁的时候调用其destroy方法。
与cgi的区别在于servlet处于服务器进程中,它通过多线程方式运行其service方法,一个实例可以服务于多个请求,并且其实例一般不会销毁,而CGI对每个请求都产生新的进程,服务完成后就销毁,所以效率上低于servlet。

7、说出ArrayList,Vector, LinkedList的存储性能和特性
ArrayList和Vector都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢Vector由于使用了synchronized方法(线程安全),通常性能上较ArrayList差,而LinkedList使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。

8、EJB是基于哪些技术实现的?并说出SessionBean和EntityBean的区别,StatefulBean和StatelessBean的区别。
    EJB包括Session Bean、Entity Bean、Message Driven Bean,基于JNDI、RMI、JAT等技术实现。
SessionBean在J2EE应用程序中被用来完成一些服务器端的业务操作,例如访问数据库、调用其他EJB组件。EntityBean被用来代表应用系统中用到的数据。
对于客户机,SessionBean是一种非持久性对象,它实现某些在服务器上运行的业务逻辑。
对于客户机,EntityBean是一种持久性对象,它代表一个存储在持久性存储器中的实体的对象视图,或是一个由现有企业应用程序实现的实体。
Session Bean 还可以再细分为 Stateful Session Bean 与 Stateless Session Bean ,这两种的 Session Bean都可以将系统逻辑放在 method之中执行,不同的是 Stateful Session Bean 可以记录呼叫者的状态,因此通常来说,一个使用者会有一个相对应的 Stateful Session Bean 的实体。Stateless Session Bean 虽然也是逻辑组件,但是他却不负责记录使用者状态,也就是说当使用者呼叫 Stateless Session Bean 的时候,EJB Container 并不会找寻特定的 Stateless Session Bean 的实体来执行这个 method。换言之,很可能数个使用者在执行某个 Stateless Session Bean 的 methods 时,会是同一个 Bean 的 Instance 在执行。从内存方面来看, Stateful Session Bean 与 Stateless Session Bean 比较, Stateful Session Bean 会消耗 J2EE Server 较多的内存,然而 Stateful Session Bean 的优势却在于他可以维持使用者的状态。

9、Collection 和 Collections的区别。 Collection是集合类的上级接口,继承与他的接口主要有Set 和List.
Collections是针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作。

10、&和&&的区别。 &是位运算符,表示按位与运算,&&是逻辑运算符,表示逻辑与(and)。


11、HashMap和Hashtable的区别。  HashMap是Hashtable的轻量级实现(非线程安全
的实现),他们都完成了Map接口,主要区别在于HashMap允许空(null)键值(key),由于非线程安全,效率上可能高于Hashtable。
HashMap允许将null作为一个entry的key或者value,而Hashtable不允许。
HashMap把Hashtable的contains方法去掉了,改成containsvaluecontainsKey。因为contains方法容易让人引起误解。 Hashtable继承自Dictionary类,而HashMap是Java1.2引进的Map interface的一个实现。
最大的不同是,Hashtable的方法是Synchronize,而HashMap不是,在多个线程访问Hashtable时,不需要自己为它的方法实现同步,而HashMap 就必须为之提供外同步
Hashtable和HashMap采用的hash/rehash算法都大概一样,所以性能不会有很大的差异。

12、final, finally, finalize的区别。 final 用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可继承。finally是异常处理语句结构的一部分,表示总是执行finalize是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等。

13、sleep() 和 wait() 有什么区别? sleep是线程类(Thread)的方法,导致此线程暂停执行指定时间,将执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复调用sleep不会释放对象锁wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。

14、Overload和Override的区别。Overloaded的方法是否可以改变返回值的类型?
方法的重写Overriding和重载Overloading是Java多态性的不同表现。重写Overriding是父类与子类之间多态性的一种表现,重载Overloading是一个类中多态性的一种表现。如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写 (Overriding)。子类的对象使用这个方法时,将调用子类中的定义,对它而言,父类中的定义如同被“屏蔽”了。如果在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型,则称为方法的重载(Overloading)。Overloaded的方法是可以改变返回值的类型

15、error和exception有什么区别?
error 表示恢复不是不可能但很困难的情况下的一种严重问题。比如说内存溢出。不可能指望程序能处理这样的情况。
exception 表示一种设计或实现问题。也就是说,它表示如果程序运行正常,从不会发生的情况。

16、同步和异步有何异同,在什么情况下分别使用他们?举例说明。
如果数据将在线程间共享。例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就是共享数据,必须进行同步存取。当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。

17、abstract class和interface有什么区别?声明方法的存在而不去实现它的类被叫做抽象类(abstract class),它用于要创建一个体现某些基本行为的类,并为该类声明方法,但不能在该类中实现该类的情况。不能创建abstract 类的实例。然而可以创建一个变量,其类型是一个抽象类,并让它指向具体子类的一个实例。不能有抽象构造函数或抽象静态方法。Abstract 类的子类为它们父类中的所有抽象方法提供实现,否则它们也是抽象类。取而代之,在子类中实现该方法。知道其行为的其它类可以在类中实现这些方法。接口(interface)是抽象类的变体。在接口中,所有方法都是抽象的多继承性可通过实现这样的接口而获得。接口中的所有方法都是抽象的,没有一个有程序体。接口只可以定义static final成员变量。接口的实现与子类相似,除了该实现类不能从接口定义中继承行为。当类实现特殊接口时,它定义(即将程序体给予)所有这种接口的方法。然后,它可以在实现了该接口的类的任何对象上调用接口的方法。由于有抽象类,它允许使用接口名作为引用变量的类型。通常的动态联编将生效。引用可以转换到接口类型或从接口类型转换,instanceof 运算符可以用来决定某对象的类是否实现了接口。

18、heap和stack有什么区别。栈是一种线形集合,其添加和删除元素的操作应在同一段完成。栈按照后进先出的方式进行处理。堆是栈的一个组成元素

19、forward 和redirect的区别forward是服务器请求资源,服务器直接访问目标地址的URL,把那个URL的响应内容读取过来,然后把这些内容再发给浏览器,浏览器根本不知道服务器发送的内容是从哪儿来的,所以它的地址栏中还是原来的地址。 redirect就是服务端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址,一般来说浏览器会用刚才请求的所有参数重新请求,所以session,request参数都可以获取。

20、EJB与JAVA BEAN的区别?Java Bean 是可复用的组件,对Java Bean并没有严格的规范,理论上讲,任何一个Java类都可以是一个Bean。但通常情况下,由于Java Bean是被容器所创建(如Tomcat)的,所以Java Bean应具有一个无参的构造器,另外,通常Java Bean还要实现Serializable接口用于实现Bean的持久性。Java Bean实际上相当于微软COM模型中的本地进程内COM组件,它是不能被跨进程访问的。Enterprise Java Bean 相当于DCOM,即分布式组件。它是基于Java的远程方法调用(RMI)技术的,所以EJB可以被远程访问(跨进程、跨计算机)。但EJB必须被布署在诸如Webspere、WebLogic这样的容器中,EJB客户从不直接访问真正的EJB组件,而是通过其容器访问。EJB容器是EJB组件的代理,EJB组件由容器所创建和管理。客户通过容器来访问真正的EJB组件。

21、Static Nested Class 和 Inner Class的不同。 Static Nested Class是被声明为静态(static)的内部类,它可以不依赖于外部类实例被实例化。而通常的内部类需要在外部类实例化后才能实例化。

22、JSP中动态INCLUDE与静态INCLUDE的区别?动态INCLUDE用jsp:include动作实现 <jsp:include page="included.jsp" flush="true" />它总是会检查所含文件中的变化,适合用于包含动态页面,并且可以带参数。静态INCLUDE用include伪码实现,定不会检查所含文件的变化,适用于包含静态页面<%@ include file="included.htm" %>

23、什么时候用assert。  assertion(断言)在软件开发中是一种常用的调试方式,很多开发语言中都支持这种机制。在实现中,assertion就是在程序中的一条语句,它对一个boolean表达式进行检查,一个正确程序必须保证这个boolean表达式的值为true;如果该值为false,说明程序已经处于不正确的状态下,系统将给出警告或退出。一般来说,assertion用于保证程序最基本、关键的正确性。assertion检查通常在开发和测试时开启。为了提高性能,在软件发布后,assertion检查通常是关闭的。

24、GC是什么? 为什么要有GC? GC是垃圾收集的意思(Gabage Collection),内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java语言没有提供释放已分配内存的显示操作方法

25、short s1 = 1; s1 = s1 + 1;有什么错? short s1 = 1; s1 += 1;有什么错? short s1 = 1; s1 = s1 + 1; (s1+1运算结果是int型,需要强制转换类型)  short s1 = 1; s1 += 1;(可以正确编译)

26、Math.round(11.5)等於多少? Math.round(-11.5)等於多少?  Math.round(11.5)==12  Math.round(-11.5)==-11  round方法返回与参数最接近的长整数,参数加1/2后求其floor.

27、String s = new String("xyz");创建了几个String Object?    两个

28、设计4个线程,其中两个线程每次对j增加1,另外两个线程对j每次减少1。写出程序。
以下程序使用内部类实现线程,对j增减的时候没有考虑顺序问题。
public class ThreadTest1{
   private int j;  
   public static void main(String args[])
      {  
         ThreadTest1 tt=new ThreadTest1();  
         Inc inc=tt.new Inc();
         Dec dec=tt.new Dec();  
         for(int i=0;i<2;i++){ 
                Thread t=new Thread(inc); 
                t.start(); 
                t=new Thread(dec); 
                t.start(); 
          } 
      }  
    private synchronized void inc(){ 
       j++; 
       System.out.println(Thread.currentThread().getName()+"-inc:"+j); 
    }

   private synchronized void dec(){
       j--;
       System.out.println(Thread.currentThread().getName()+"-dec:"+j); 
   }

class Inc implements Runnable{  
      public void run(){  
            for(int i=0;i<100;i++){  
                  inc(); 
             } 
      } 
 }
class Dec implements Runnable{
     public void run(){  
         for(int i=0;i<100;i++){
             dec(); 
          } 
     } 
  }

}

29、Java有没有goto?  java中的保留字,现在没有在java中使用。

30、启动一个线程是用run()还是start()?启动一个线程是调用start()方法,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由JVM调度并执行。这并不意味着线程就会立即运行。run()方法可以产生必须退出的标志来停止一个线程。

31、EJB包括(SessionBean,EntityBean)说出他们的生命周期,及如何管理事务的?
SessionBean:Stateless Session Bean 的生命周期是由容器决定的,当客户机发出请求要建立一个Bean的实例时,EJB容器不一定要创建一个新的Bean的实例供客户机调用,而是随便找一个现有的实例提供给客户机。当客户机第一次调用一个Stateful Session Bean 时,容器必须立即在服务器中创建一个新的Bean实例,并关联到客户机上,以后此客户机调用Stateful Session Bean 的方法时容器会把调用分派到与此客户机相关联的Bean实例。EntityBean:Entity Beans能存活相对较长的时间,并且状态是持续的。只要数据库中的数据存在,Entity beans就一直存活。而不是按照应用程序或者服务进程来说的。即使EJB容器崩溃了,Entity beans也是存活的。Entity Beans生命周期能够被容器或者 Beans自己管理。EJB通过以下技术管理实务:对象管理组织(OMG)的对象实务服务(OTS),Sun Microsystems的Transaction Service(JTSJava Transaction API(JTA),开发组(X/Open)的XA接口

32、应用服务器有那些?
BEA WebLogic Server,IBM WebSphere Application Server,Oracle9i Application Server,jBoss,Tomcat

33、给我一个你最常见到的runtime exception。
ArithmeticException, ArrayStoreException, BufferOverflowException, BufferUnderflowException, CannotRedoException, CannotUndoException, ClassCastException, CMMException, ConcurrentModificationException, DOMException, EmptyStackException, IllegalArgumentException, IllegalMonitorStateException, IllegalPathStateException, IllegalStateException, ImagingOpException, IndexOutOfBoundsException, MissingResourceException, NegativeArraySizeException, NoSuchElementException, NullPointerException, ProfileDataException, ProviderException, RasterFormatException, SecurityException, SystemException, UndeclaredThrowableException, UnmodifiableSetException, UnsupportedOperationException

34、接口是否可继承接口? 抽象类是否可实现(implements)接口? 抽象类是否可继承实体类(concrete class)?
接口可以继承接口。抽象类可以实现(implements)接口,抽象类是否可继承实体类,但前提是实体类必须有明确的构造函数。

35、List, Set, Map是否继承自Collection接口?  List,Set是,Map不是

36、说出数据连接池的工作机制是什么?
J2EE服务器启动时会建立一定数量的池连接,并一直维持不少于此数目的池连接。客户端程序需要连接时,池驱动程序会返回一个未使用的池连接并将其表记为忙。如果当前没有空闲连接,池驱动程序就新建一定数量的连接,新建连接的数量有配置参数决定。当使用的池连接调用完成后,池驱动程序将此连接表记为空闲,其他调用就可以使用这个连接。

37、abstract的method是否可同时是static,是否可同时是native,是否可同时是synchronized?  都不能

38、数组有没有length()这个方法? String有没有length()这个方法?数组没有length()这个方法,有length的属性。String有有length()这个方法。

39、Set里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用==还是equals()? 它们有何区别?
Set里的元素是不能重复的,那么用iterator()方法来区分重复与否。equals()是判读两个Set是否相等。equals()和==方法决定引用值是否指向同一对象equals()在类中被覆盖,为的是当两个分离的对象的内容和类型相配的话,返回真值。

40、构造器Constructor是否可被override?构造器Constructor不能被继承,因此不能重写Overriding,但可以被重载Overloading。

41、是否可以继承String类?String类是final类故不可以继承。

42、swtich是否能作用在byte上,是否能作用在long上,是否能作用在String上?switch(expr1)中,expr1是一个整数表达式。因此传递给 switch 和 case 语句的参数应该是 int、 short、 char 或者 byte。long,string 都不能作用于swtich。

43、try {}里有一个return语句,那么紧跟在这个try后的finally {}里的code会不会被执行,什么时候被执行,在return前还是后?会执行,在return前执行

44、编程题: 用最有效率的方法算出2乘以8等於几? 2 << 3

45、两个对象值相同(x.equals(y) == true),但却可有不同的hash code,这句话对不对?不对,有相同的hash code

46、当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递? 是值传递。Java 编程语言只有值传递参数。当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的内容可以在被调用的方法中改变,但对象的引用是永远不会改变的。

47、当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法?
不能,一个对象的一个synchronized方法只能由一个线程访问。

48、编程题: 写一个Singleton出来。
Singleton模式主要作用是保证在Java应用程序中,一个类Class只有一个实例存在。一般Singleton模式通常有几种种形式:第一种形式: 定义一个类,它的构造函数为private的,它有一个static的private的该类变量,在类初始化时实例话,通过一个public的getInstance方法获取对它的引用,继而调用其中的方法。public class Singleton {private Singleton(){} private static Singleton instance = new Singleton();  public static Singleton getInstance() {  return instance;   }  }  第二种形式: public class Singleton {  private static Singleton instance = null;  public static synchronized Singleton getInstance() { if (instance==null) instance=new Singleton();
return instance;   } }   其他形式:  定义一个类,它的构造函数为private的,所有方法为static的。一般认为第一种形式要更加安全些

49、Java的接口和C++的虚类的相同和不同处。
由于Java不支持多继承,而有可能某个类或对象要使用分别在几个类或对象里面的方法或属性,现有的单继承机制就不能满足要求。与继承相比,接口有更高的灵活性,因为接口中没有任何实现代码。当一个类实现了接口以后,该类要实现接口里面所有的方法和属性,并且接口里面的属性在默认状态下面都是public static,所有方法默认情况下是public.一个类可以实现多个接口。

50、Java中的异常处理机制的简单原理和应用。
当JAVA程序违反了JAVA的语义规则时,JAVA虚拟机就会将发生的错误表示为一个异常。违反语义规则包括2种情况。一种是JAVA类库内置的语义检查。例如数组下标越界,会引发IndexOutOfBoundsException;访问null的对象时会引发NullPointerException。另一种情况就是JAVA允许程序员扩展这种语义检查,程序员可以创建自己的异常,并自由选择在何时用throw关键字引发异常。所有的异常都是java.lang.Thowable的子类。

51、垃圾回收的优点和原理。并考虑2种回收机制。
Java语言中一个显著的特点就是引入了垃圾回收机制,使c++程序员最头疼的内存管理的问题迎刃而解,它使得Java程序员在编写程序的时候不再需要考虑内存管理。由于有个垃圾回收机制,Java中的对象不再有“作用域”的概念,只有对象的引用才有“作用域”。垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。垃圾回收器通常是作为一个单独的低级别的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清楚和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。回收机制有分代复制垃圾回收和标记垃圾回收,增量垃圾回收

52、请说出你所知道的线程同步的方法。
wait():使一个线程处于等待状态,并且释放所持有的对象的lock。sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException异常。notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。Allnotity():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。

53、你所知道的集合类都有哪些?主要方法?最常用的集合类是 List 和 Map。 List 的具体实现包括 ArrayList 和 Vector,它们是可变大小的列表,比较适合构建、存储和操作任何类型对象的元素列表。 List 适用于按数值索引访问元素的情形。 Map 提供了一个更通用的元素存储方法。 Map 集合类用于存储元素对(称作“键”和“值”),其中每个键映射到一个值。

54、描述一下JVM加载class文件的原理机制?JVM中类的装载是由ClassLoader和它的子类来实现的,Java ClassLoader 是一个重要的Java运行时系统组件。它负责在运行时查找和装入类文件的类。

55、char型变量中能不能存贮一个中文汉字?为什么?
能够定义成为一个中文的,因为java中以unicode编码,一个char占16个字节,所以放一个中文是没问题的

56、多线程有几种实现方法,都是什么?同步有几种实现方法,都是什么?
多线程有两种实现方法,分别是继承Thread类与实现Runnable接口 ,同步的实现方面有两种,分别是synchronized,wait与notify

57、JSP的内置对象及方法。
request表示HttpServletRequest对象。它包含了有关浏览器请求的信息,并且提供了几个用于获取cookie, header, 和session数据的有用的方法,response表示HttpServletResponse对象,并提供了几个用于设置送回 浏览器的响应的方法(如cookies,头信息等)
out对象是javax.jsp.JspWriter的一个实例,并提供了几个方法使你能用于向浏览器回送输出结果。 pageContext表示一个javax.servlet.jsp.PageContext对象。它是用于方便存取各种范围的名字空间、servlet相关的对象的API,并且包装了通用的servlet相关功能的方法。  session表示一个请求的javax.servlet.http.HttpSession对象。Session可以存贮用户的状态信息  applicaton 表示一个javax.servle.ServletContext对象。这有助于查找有关servlet引擎和servlet环境的信息  config表示一个javax.servlet.ServletConfig对象。该对象用于存取servlet实例的初始化参数。  page表示从该页面产生的一个servlet实例

58、线程的基本概念、线程的基本状态以及状态之间的关系线程指在程序执行过程中,能够执行程序代码的一个执行单位,每个程序至少都有一个线程,也就是程序本身。Java中的线程有四种状态分别是:运行、就绪、挂起、结束。

59、JSP的常用指令<%@page language=”java” contenType=”text/html;charset=gb2312” session=”true” buffer=”64kb” autoFlush=”true” isThreadSafe=”true” info=”text” errorPage=”error.jsp” isErrorPage=”true” isELIgnored=”true” pageEncoding=”gb2312” import=”java.sql.*”%>isErrorPage(是否能使用Exception对象),isELIgnored(是否忽略表达式) <%@include file=”filename”%><%@taglib prefix=”c”uri=”http://……”%>

60、什么情况下调用doGet()和doPost()?Jsp页面中的form标签里的method属性为get时调用doGet(),为post时调用doPost()。

61、servlet的生命周期web容器加载servlet,生命周期开始。通过调用servlet的init()方法进行servlet的初始化。通过调用service()方法实现,根据请求的不同调用不同的do***()方法。结束服务,web容器调用servlet的destroy()方法。

62、如何现实servlet的单线程模式  <%@ page isThreadSafe=”false”%>

63、页面间对象传递的方法  request,session,application,cookie等

64、JSP和Servlet有哪些相同点和不同点,他们之间的联系是什么?
JSP是Servlet技术的扩展,本质上是Servlet的简易方式,更强调应用的外表表达。JSP编译后是"类servlet"。Servlet和JSP最主要的不同点在于,Servlet的应用逻辑是在Java文件中,并且完全从表示层中的HTML里分离开来。而JSP的情况是Java和HTML可以组合成一个扩展名为.jsp的文件。JSP侧重于视图,Servlet主要用于控制逻辑。

65、四种会话跟踪技术 cookie,url重写,session,隐藏域

65,jsp的四种范围
page否是代表与一个页面相关的对象和属性。一个页面由一个编译好的 Java servlet 类(可以带有任何的 include 指令,但是没有 include 动作)表示。这既包括 servlet 又包括被编译成 servlet 的 JSP 页面
request是是代表与 Web 客户机发出的一个请求相关的对象和属性。一个请求可能跨越多个页面,涉及多个 Web 组件(由于 forward 指令和 include 动作的关系)
session是是代表与用于某个 Web 客户机的一个用户体验相关的对象和属性。一个 Web 会话可以也经常会跨越多个客户机请求
application是是代表与整个 Web 应用程序相关的对象和属性。这实质上是跨越整个 Web 应用程序,包括多个页面、请求和会话的一个全局作用域

66、Request对象的主要方法:
setAttribute(String name,Object):设置名字为name的request的参数值
getAttribute(String name):返回由name指定的属性值
getAttributeNames():返回request对象所有属性的名字集合,结果是一个枚举的实例
getCookies():返回客户端的所有Cookie对象,结果是一个Cookie数组
getCharacterEncoding():返回请求中的字符编码方式
getContentLength():返回请求的Body的长度
getHeader(String name):获得HTTP协议定义的文件头信息
getHeaders(String name):返回指定名字的request Header的所有值,结果是一个枚举的实例
getHeaderNames():返回所以request Header的名字,结果是一个枚举的实例
getInputStream():返回请求的输入流,用于获得请求中的数据
getMethod():获得客户端向服务器端传送数据的方法
getParameter(String name):获得客户端传送给服务器端的有name指定的参数值
getParameterNames():获得客户端传送给服务器端的所有参数的名字,结果是一个枚举的实例
getParameterValues(String name):获得有name指定的参数的所有值
getProtocol():获取客户端向服务器端传送数据所依据的协议名称
getQueryString():获得查询字符串
getRequestURI():获取发出请求字符串的客户端地址
getRemoteAddr():获取客户端的IP地址
getRemoteHost():获取客户端的名字
getSession([Boolean create]):返回和请求相关Session
getServerName():获取服务器的名字
getServletPath():获取客户端所请求的脚本文件的路径
getServerPort():获取服务器的端口号
removeAttribute(String name):删除请求中的一个属性

67、J2EE是技术还是平台还是框架?J2EE本身是一个标准,一个为企业分布式应用的开发提供的标准平台。
J2EE也是一个框架,包括JDBC、JNDI、RMI、JMS、EJB、JTA等技术。

68、我们在web应用开发过程中经常遇到输出某种编码的字符,如iso8859-1等,如何输出一个某种编码的字符串?
  Public String translate (String str) {  String tempStr = "";  try { tempStr = new String(str.getBytes("ISO-8859-1"), "GBK"); tempStr = tempStr.trim(); } catch (Exception e) {  System.err.println(e.getMessage());  } return tempStr;  }

69、简述逻辑操作(&,|,^)与条件操作(&&,||)的区别。区别主要答两点:a.条件操作只能操作布尔型的,而逻辑操作不仅可以操作布尔型,而且可以操作数值型b.逻辑操作不会产生短路

70、XML文档定义有几种形式?它们之间有何本质区别?解析XML文档有哪几种方式?
a: 两种形式 dtd  schema,b: 本质区别:schema本身是xml的,可以被XML解析器解析(这也是从DTD上发展schema的根本目的),c:有DOM,SAX,STAX等  DOM:处理大型文件时其性能下降的非常厉害。这个问题是由DOM的树结构所造成的,这种结构占用的内存较多,而且DOM必须在解析文件之前把整个文档装入内存,适合对XML的随机访问  SAX:不现于DOM,SAX是事件驱动型的XML解析方式。它顺序读取XML文件,不需要一次全部装载整个文件。当遇到像文件开头,文档结束,或者标签开头与标签结束时,它会触发一个事件,用户通过在其回调事件中写入处理代码来处理XML文件,适合对XML的顺序访问 STAX:Streaming API for XML (StAX)

71、简述synchronized和java.util.concurrent.locks.Lock的异同 ?
主要相同点:Lock能完成synchronized所实现的所有功能主要不同点:Lock有比synchronized更精确的线程语义和更好的性能。synchronized会自动释放锁,而Lock一定要求程序员手工释放,并且必须在finally从句中释放。

72、EJB的角色和三个对象
一个完整的基于EJB的分布式计算结构由六个角色组成,这六个角色可以由不同的开发商提供,每个角色所作的工作必须遵循Sun公司提供的EJB规范,以保证彼此之间的兼容性。这六个角色分别是EJB组件开发者(Enterprise Bean Provider) 、应用组合者(Application Assembler)、部署者(Deployer)、EJB 服务器提供者(EJB Server Provider)、EJB 容器提供者(EJB Container Provider)、系统管理员(System Administrator)三个对象是Remote(Local)接口、Home(LocalHome)接口,Bean类

73、EJB容器提供的服务主要提供声明周期管理、代码产生、持续性管理、安全、事务管理、锁和并发行管理等服务。

74、EJB规范规定EJB中禁止的操作有哪些? 1.不能操作线程和线程API(线程API指非线程对象的方法如notify,wait等),2.不能操作awt,3.不能实现服务器功能,4.不能对静态属生存取,5.不能使用IO操作直接存取文件系统,6.不能加载本地库.,7.不能将this作为变量和返回,8.不能循环调用。

75、remote接口和home接口主要作用remote接口定义了业务方法,用于EJB客户端调用业务方法。home接口是EJB工厂用于创建和移除查找EJB实例

76、bean 实例的生命周期对于Stateless Session Bean、Entity Bean、Message Driven Bean一般存在缓冲池管理,而对于Entity Bean和Statefull Session Bean存在Cache管理,通常包含创建实例,设置上下文、创建EJB Object(create)、业务方法调用、remove等过程,对于存在缓冲池管理的Bean,在create之后实例并不从内存清除,而是采用缓冲池调度机制不断重用实例,而对于存在Cache管理的Bean则通过激活和去激活机制保持Bean的状态并限制内存中实例数量。

77、EJB的激活机制 以Stateful Session Bean 为例:其Cache大小决定了内存中可以同时存在的Bean实例的数量,根据MRU或NRU算法,实例在激活和去激活状态之间迁移,激活机制是当客户端调用某个EJB实例业务方法时,如果对应EJB Object发现自己没有绑定对应的Bean实例则从其去激活Bean存储中(通过序列化机制存储实例)回复(激活)此实例。状态变迁前会调用对应的ejbActive和ejbPassivate方法。

78、EJB的几种类型会话(Session)Bean ,实体(Entity)Bean 消息驱动的(Message Driven)Bean  ;会话Bean又可分为有状态(Stateful)和无状态(Stateless)两种;实体Bean可分为Bean管理的持续性(BMP)和容器管理的持续性(CMP)两种

79、客服端调用EJB对象的几个基本步骤设置JNDI服务工厂以及JNDI服务地址系统属性,查找Home接口,从Home接口调用Create方法创建Remote接口,通过Remote接口调用其业务方法。

80、如何给weblogic指定大小的内存? 在启动Weblogic的脚本中(位于所在Domian对应服务器目录下的startServerName),增加set MEM_ARGS=-Xms32m -Xmx200m,可以调整最小内存为32M,最大200M

81、如何设定的weblogic的热启动模式(开发模式)与产品发布模式?可以在管理控制台中修改对应服务器的启动模式为开发或产品模式之一。或者修改服务的启动文件或者commenv文件,增加set PRODUCTION_MODE=true。

82、如何启动时不需输入用户名与密码?修改服务启动文件,增加 WLS_USER和WLS_PW项。也可以在boot.properties文件中增加加密过的用户名和密码.

83、在weblogic管理制台中对一个应用域(或者说是一个网站,Domain)进行jms及ejb或连接池等相关信息进行配置后,实际保存在什么文件中?保存在此Domain的config.xml文件中,它是服务器的核心配置文件。

84、说说weblogic中一个Domain的缺省目录结构?比如要将一个简单的helloWorld.jsp放入何目录下,然的在浏览器上就可打入http://主机:端口号//helloword.jsp就可以看到运行结果了? 又比如这其中用到了一个自己写的javaBean该如何办?
Domain目录\服务器目录\applications,将应用目录放在此目录下将可以作为应用访问,如果是Web应用,应用目录需要满足Web应用目录要求,jsp文件可以直接放在应用目录中,Javabean需要放在应用目录的WEB-INF目录的classes目录中,设置服务器的缺省应用将可以实现在浏览器上无需输入应用名。

85、在weblogic中发布ejb需涉及到哪些配置文件不同类型的EJB涉及的配置文件不同,都涉及到的配置文件包括ejb-jar.xml,weblogic-ejb-jar.xmlCMP实体Bean一般还需要weblogic-cmp-rdbms-jar.xml

86、如何在weblogic中进行ssl配置与客户端的认证配置或说说j2ee(标准)进行ssl的配置缺省安装中使用DemoIdentity.jks和DemoTrust.jks  KeyStore实现SSL,需要配置服务器使用Enable SSL,配置其端口,在产品模式下需要从CA获取私有密钥和数字证书,创建identity和trust keystore,装载获得的密钥和数字证书。可以配置此SSL连接是单向还是双向的。

87、如何查看在weblogic中已经发布的EJB?可以使用管理控制台,在它的Deployment中可以查看所有已发布的EJB

88、CORBA是什么?用途是什么? CORBA 标准是公共对象请求代理结构(Common Object Request Broker Architecture),由对象管理组织 (Object Management Group,缩写为 OMG)标准化。它的组成是接口定义语言(IDL), 语言绑定(binding:也译为联编)和允许应用程序间互操作的协议。 其目的为:用不同的程序设计语言书写在不同的进程中运行,为不同的操作系统开发。

89、说说你所熟悉或听说过的j2ee中的几种常用模式?及对设计模式的一些看法
  Session Facade Pattern:使用SessionBean访问EntityBean;Message Facade Pattern:实现异步调用;EJB Command Pattern:使用Command JavaBeans取代SessionBean,实现轻量级访问;Data Transfer Object Factory:通过DTO Factory简化EntityBean数据提供特性;Generic Attribute Access:通过AttibuteAccess接口简化EntityBean数据提供特性;Business Interface:通过远程(本地)接口和Bean类实现相同接口规范业务逻辑一致性;EJB架构的设计好坏将直接影响系统的性能、可扩展性、可维护性、组件可重用性及开发效率。项目越复杂,项目队伍越庞大则越能体现良好设计的重要性。

90、说说在weblogic中开发消息Bean时的persistent与non-persisten的差别persistent方式的MDB可以保证消息传递的可靠性,也就是如果EJB容器出现问题而JMS服务器依然会将消息在此MDB可用的时候发送过来,而non-persistent方式的消息将被丢弃。

91、Servlet执行时一般实现哪几个方法?public void init(ServletConfig config);public ServletConfig getServletConfig();public String getServletInfo();public void service(ServletRequest request,ServletResponse response);public void destroy()

92、常用的设计模式?说明工厂模式。 Java中的23种设计模式:Factory(工厂模式),Builder(建造模式), Factory Method(工厂方法模式),Prototype(原始模型模式),Singleton(单例模式), Facade(门面模式),Adapter(适配器模式), Bridge(桥梁模式), Composite(合成模式),Decorator(装饰模式), Flyweight(享元模式), Proxy(代理模式),Command(命令模式), Interpreter(解释器模式), Visitor(访问者模式),Iterator(迭代子模式), Mediator(调停者模式), Memento(备忘录模式),Observer(观察者模式),State(状态模式),Strategy(策略模式),Template Method(模板方法模式), Chain Of Responsibleity(责任链模式)。工厂模式:工厂模式是一种经常被使用到的模式,根据工厂模式实现的类可以根据提供的数据生成一组类中某一个类的实例,通常这一组类有一个公共的抽象父类并且实现了相同的方法,但是这些方法针对不同的数据进行了不同的操作。首先需要定义一个基类,该类的子类通过不同的方法实现了基类中的方法。然后需要定义一个工厂类,工厂类可以根据条件生成不同的子类实例。当得到子类的实例后,开发人员可以调用基类中的方法而不必考虑到底返回的是哪一个子类的实例。

93、EJB需直接实现它的业务接口或Home接口吗,请简述理由。远程接口和Home接口不需要直接实现,他们的实现代码是由服务器产生的,程序运行中对应实现类会作为对应接口类型的实例被使用。

94、排序都有哪几种方法?请列举。用JAVA实现一个快速排序。排序的方法有:插入排序(直接插入排序、希尔排序),交换排序(冒泡排序、快速排序),选择排序(直接选择排序、堆排序),归并排序,分配排序(箱排序、基数排序)
快速排序的伪代码。/ /使用快速排序方法对a[ 0 :n- 1 ]排序,从a[ 0 :n- 1 ]中选择一个元素作为m i d d l e,该元素为支点,
把余下的元素分割为两段left 和r i g h t,使得l e f t中的元素都小于等于支点,而right 中的元素都大于等于支点,递归地使用快速排序方法对left 进行排序,递归地使用快速排序方法对right 进行排序,所得结果为l e f t + m i d d l e + r i g h t。

95、请对以下在J2EE中常用的名词进行解释(或简单描述)web容器:给处于其中的应用程序组件(JSP,SERVLET)提供一个环境,使JSP,SERVLET直接更容器中的环境变量接口交互,不必关注其它系统问题。主要有WEB服务器来实现。例如:TOMCAT,WEBLOGIC,WEBSPHERE等。该容器提供的接口严格遵守J2EE规范中的WEB APPLICATION 标准。我们把遵守以上标准的WEB服务器就叫做J2EE中的WEB容器。EJB容器:Enterprise java bean 容器。更具有行业领域特色。他提供给运行在其中的组件EJB各种管理功能。只要满足J2EE规范的EJB放入该容器,马上就会被容器进行高效率的管理。并且可以通过现成的接口来获得系统级别的服务。例如邮件服务、事务管理。JNDI:(Java Naming & Directory Interface)JAVA命名目录服务。主要提供的功能是:提供一个目录系统,让其它各地的应用程序在其上面留下自己的索引,从而满足快速查找和定位分布式应用程序的功能。JMS:(Java Message Service)JAVA消息服务。主要实现各个应用程序之间的通讯。包括点对点和广播。JTA:(Java Transaction API)JAVA事务服务。提供各种分布式事务服务。应用程序只需调用其提供的接口即可。JAF:(Java Action FrameWork)JAVA安全认证框架。提供一些安全控制方面的框架。让开发者通过各种部署和自定义实现自己的个性安全控制策略。RMI/IIOP:(Remote Method Invocation /internet对象请求中介协议)他们主要用于通过远程调用服务。例如,远程有一台计算机上运行一个程序,它提供股票分析服务,我们可以在本地计算机上实现对其直接调用。当然这是要通过一定的规范才能在异构的系统之间进行通信。RMI是JAVA特有的。

96、JAVA语言如何进行异常处理,关键字:throws,throw,try,catch,finally分别代表什么意义?在try块中可以抛出异常吗?
Java通过面向对象的方法进行异常处理,把各种不同的异常进行分类,并提供了良好的接口。在Java中,每个异常都是一个对象,它是Throwable类或其它子类的实例。当一个方法出现异常后便抛出一个异常对象,该对象中包含有异常信息,调用这个对象的方法可以捕获到这个异常并进行处理。Java的异常处理是通过5个关键词来实现的:try、catch、throw、throws和finally。一般情况下是用try来执行一段程序,如果出现异常,系统会抛出(throws)一个异常,这时候你可以通过它的类型来捕捉(catch)它,或最后(finally)由缺省处理器来处理。用try来指定一块预防所有“异常”的程序。紧跟在try程序后面,应包含一个catch子句来指定你想要捕捉的“异常”的类型。throw语句用来明确地抛出一个“异常”。throws用来标明一个成员函数可能抛出的各种“异常”。Finally为确保一段代码不管发生什么“异常”都被执行一段代码。可以在一个成员函数调用的外面写一个try语句,在这个成员函数内部写另一个try语句保护其他代码。每当遇到一个try语句,“异常”的框架就放到堆栈上面,直到所有的try语句都完成。如果下一级的try语句没有对某种“异常”进行处理,堆栈就会展开,直到遇到有处理这种“异常”的try语句。

97、一个“.java”源文件中是否可以包括多个类(不是内部类)?有什么限制?可以。必须只有一个类名与文件名相同。

98、MVC的各个部分都有那些技术来实现?如何实现? MVC是Model-View-Controller的简写。"Model" 代表的是应用的业务逻辑(通过JavaBean,EJB组件实现), "View" 是应用的表示面(由JSP页面产生),"Controller" 是提供应用的处理过程控制(一般是一个Servlet),通过这种设计模型把应用逻辑,处理过程和显示逻辑分成不同的组件实现。这些组件可以进行交互和重用。

99、java中有几种方法可以实现一个线程?用什么关键字修饰同步方法? stop()和suspend()方法为何不推荐使用?有两种实现方法,分别是继承Thread类与实现Runnable接口用synchronized关键字修饰同步方法反对使用stop(),是因为它不安全。它会解除由线程获取的所有锁定,而且如果对象处于一种不连贯状态,那么其他线程能在那种状态下检查和修改它们。结果很难检查出真正的问题所在。suspend()方法容易发生死锁。调用suspend()的时候,目标线程会停下来,但却仍然持有在这之前获得的锁定。此时,其他任何线程都不能访问锁定的资源,除非被“挂起”的线程恢复运行。对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成死锁。所以不应该使用suspend(),而应在自己的Thread类中置入一个标志,指出线程应该活动还是挂起。若标志指出线程应该挂起,便用wait()命其进入等待状态。若标志指出线程应当恢复,则用一个notify()重新启动线程。

100、java中有几种类型的流?JDK为每种类型的流提供了一些抽象类以供继承,请说出他们分别是哪些类?
字节流,字符流。字节流继承于InputStream \ OutputStream,字符流继承于InputStreamReader \ OutputStreamWriter。在java.io包中还有许多其他的流,主要是为了提高性能和使用方便。

101、java中会存在内存泄漏吗,请简单描述。会。如:int i,i2;  return (i-i2);   //when i为足够大的正数,i2为足够大的负数。结果会造成溢位,导致错误。

102、java中实现多态的机制是什么?方法的重写Overriding和重载Overloading是Java多态性的不同表现。重写Overriding是父类与子类之间多态性的一种表现,重载Overloading是一个类中多态性的一种表现。

103、垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。通常,GC采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对象是"可达的",哪些对象是"不可达的"。当GC确定一些对象为"不可达"时,GC就有责任回收这些内存空间。可以。程序员可以手动执行System.gc(),通知GC运行,但是Java语言规范并不保证GC一定会执行。

104、静态变量和实例变量的区别?static i = 10; //常量; class A a;  a.i =10;//可变

105、什么是java序列化,如何实现java序列化?
序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。序列化的实现:将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。

106、是否可以从一个static方法内部发出对非static方法的调用?不可以,如果其中包含对象的method();不能保证对象初始化.

107、写clone()方法时,通常都有一行代码,是什么?Clone 有缺省行为,super.clone();他负责产生正确大小的空间,并逐位复制。

108、在JAVA中,如何跳出当前的多重嵌套循环?用break; return 方法。

109、List、Map、Set三个接口,存取元素时,各有什么特点?List 以特定次序来持有元素,可有重复元素。Set 无法拥有重复元素,内部排序。Map 保存key-value值,value可多值。

110、J2EE是什么?J2EE是Sun公司提出的多层(multi-diered),分布式(distributed),基于组件(component-base)的企业级应用模型(enterpriese application model).在这样的一个应用系统中,可按照功能划分为不同的组件,这些组件又可在不同计算机上,并且处于相应的层次(tier)中。所属层次包括客户层(clietn tier)组件,web层和组件,Business层和组件,企业信息系统(EIS)层。

111、UML方面 标准建模语言UML。用例图,静态图(包括类图、对象图和包图),行为图,交互图(顺序图,合作图),实现图。

112、说出一些常用的类,包,接口,请各举5个常用的类:BufferedReader  BufferedWriter  FileReader  FileWirter  String  Integer;常用的包:java.lang  java.awt  java.io  java.util  java.sql;常用的接口:Remote  List  Map  Document  NodeList

113、开发中都用到了那些设计模式?用在什么场合? 每个模式都描述了一个在我们的环境中不断出现的问题,然后描述了该问题的解决方案的核心。通过这种方式,你可以无数次地使用那些已有的解决方案,无需在重复相同的工作。主要用到了MVC的设计模式。用来开发JSP/Servlet或者J2EE的相关应用。简单工厂模式等。

114、jsp有哪些动作?作用分别是什么? JSP共有以下6种基本动作 jsp:include:在页面被请求的时候引入一个文件。 jsp:useBean:寻找或者实例化一个JavaBean。 jsp:setProperty:设置JavaBean的属性。 jsp:getProperty:输出某个JavaBean的属性。 jsp:forward:把请求转到一个新的页面。 jsp:plugin:根据浏览器类型为Java插件生成OBJECT或EMBED标记。

115、Anonymous Inner Class (匿名内部类) 是否可以extends(继承)其它类,是否可以implements(实现)interface(接口)? 可以继承其他类或完成其他接口,在swing编程中常用此方式。

116、应用服务器与WEB SERVER的区别?应用服务器:Weblogic、Tomcat、Jboss; WEB SERVER:IIS、 Apache

117、BS与CS的联系与区别。C/S是Client/Server的缩写。服务器通常采用高性能的PC、工作站或小型机,并采用大型数据库系统,如Oracle、Sybase、Informix或 SQL Server。客户端需要安装专用的客户端软件。B/S是Brower/Server的缩写,客户机上只要安装一个浏览器(Browser),如Netscape Navigator或Internet Explorer,服务器安装Oracle、Sybase、Informix或 SQL Server等数据库。在这种结构下,用户界面完全通过WWW浏览器实现,一部分事务逻辑在前端实现,但是主要事务逻辑在服务器端实现。浏览器通过Web Server 同数据库进行数据交互。C/S 与 B/S 区别: 1.硬件环境不同: C/S 一般建立在专用的网络上, 小范围里的网络环境, 局域网之间再通过专门服务器提供连接和数据交换服务; B/S 建立在广域网之上的, 不必是专门的网络硬件环境,例与电话上网, 租用设备. 信息自己管理. 有比C/S更强的适应范围, 一般只要有操作系统和浏览器就行 2.对安全要求不同 :C/S 一般面向相对固定的用户群, 对信息安全的控制能力很强. 一般高度机密的信息系统采用C/S 结构适宜. 可以通过B/S发布部分可公开信息.B/S 建立在广域网之上, 对安全的控制能力相对弱, 可能面向不可知的用户。3.对程序架构不同 : C/S 程序可以更加注重流程, 可以对权限多层次校验, 对系统运行速度可以较少考虑. B/S 对安全以及访问速度的多重的考虑, 建立在需要更加优化的基础之上. 比C/S有更高的要求 B/S结构的程序架构是发展的趋势, 从MS的.Net系列的BizTalk 2000 Exchange 2000等, 全面支持网络的构件搭建的系统. SUN 和IBM推的JavaBean 构件技术等,使 B/S更加成熟. 4.软件重用不同: C/S 程序可以不可避免的整体性考虑, 构件的重用性不如在B/S要求下的构件的重用性好. B/S 对的多重结构,要求构件相对独立的功能. 能够相对较好的重用.就入买来的餐桌可以再利用,而不是做在墙上的石头桌子 。5.系统维护不同  :C/S 程序由于整体性, 必须整体考察, 处理出现的问题以及系统升级. 升级难. 可能是再做一个全新的系统, B/S 构件组成,方面构件个别的更换,实现系统的无缝升级. 系统维护开销减到最小.用户从网上自己下载安装就可以实现升级. 6.处理问题不同 :C/S 程序可以处理用户面固定, 并且在相同区域, 安全要求高需求, 与操作系统相关. 应该都是相同的系统,B/S 建立在广域网上, 面向不同的用户群, 分散地域, 这是C/S无法作到的. 与操作系统平台关系最小. 7.用户接口不同: C/S 多是建立的Window平台上,表现方法有限,对程序员普遍要求较高,B/S 建立在浏览器上, 有更加丰富和生动的表现方式与用户交流. 并且大部分难度减低,减低开发成本.
8.信息流不同 : C/S 程序一般是典型的中央集权的机械式处理, 交互性相对低,B/S 信息流向可变化, B-B B-C B-G等信息、流向的变化, 更像交易中心。

posted @ 2007-04-23 17:20 chenweicai 阅读(330) | 评论 (0)编辑 收藏

一、什么是Java虚拟机
     当你谈到Java虚拟机时,你可能是指:
     1、抽象的Java虚拟机规范
     2、一个具体的Java虚拟机实现
     3、一个运行的Java虚拟机实例
二、Java虚拟机的生命周期
     一个运行中的Java虚拟机有着一个清晰的任务:执行Java程序。程序开始执行时他才运行,程序结束时他就停止。你在同一台机器上运行三个程序,就会有三个运行中的Java虚拟机
     Java虚拟机总是开始于一个main()方法,这个方法必须是公有、返回void、直接受一个字符串数组。在程序执行时,你必须给Java虚拟机指明这个包含main()方法的类名
     Main()方法是程序的起点,他被执行的线程初始化为程序的初始线程。程序中其他的线程都由他来启动。Java中的线程分为两种:守护线程 (daemon)和普通线程(non-daemon)。守护线程是Java虚拟机自己使用的线程,比如负责垃圾收集的线程就是一个守护线程。当然,你也可以把自己的程序设置为守护线程。包含Main()方法的初始线程不是守护线程。
     只要Java虚拟机中还有普通的线程在执行,Java虚拟机就不会停止。如果有足够的权限,你可以调用exit()方法终止程序。
三、Java虚拟机的体系结构
     在Java虚拟机的规范中定义了一系列的子系统、内存区域、数据类型和使用指南。这些组件构成了Java虚拟机的内部结构,他们不仅仅为Java虚拟机的实现提供了清晰的内部结构,更是严格规定了Java虚拟机实现的外部行为。 
     每一个Java虚拟机都由一个类加载器子系统(class loader subsystem),负责加载程序中的类型(类和接口),并赋予唯一的名字。每一个Java虚拟机都有一个执行引擎(execution engine)负责执行被加载类中包含的指令。
     程序的执行需要一定的内存空间,如字节码、被加载类的其他额外信息、程序中的对象、方法的参数、返回值、本地变量、处理的中间变量等等。Java虚拟机将这些信息统统保存在数据区(data areas)中。虽然每个Java虚拟机的实现中都包含数据区,但是Java虚拟机规范对数据区的规定却非常的抽象。许多结构上的细节部分都留给了 Java虚拟机实现者自己发挥。不同Java虚拟机实现上的内存结构千差万别。一部分实现可能占用很多内存,而其他以下可能只占用很少的内存;一些实现可能会使用虚拟内存,而其他的则不使用。这种比较精炼的Java虚拟机内存规约,可以使得Java虚拟机可以在广泛的平台上被实现。
     数据区中的一部分是整个程序共有,其他部分被单独的线程控制。每一个Java虚拟机都包含方法区(method area)堆(heap),他们都被整个程序共享。Java虚拟机加载并解析一个类以后,将从类文件中解析出来的信息保存与方法区中。程序执行时创建的 对象都保存在堆中。 
     当一个线程被创建时,会被分配只属于他自己的PC寄存器“pc register”(程序计数器)和Java堆栈(Java stack)。当线程不掉用本地方法时,PC寄存器中保存线程执行的下一条指令。Java堆栈保存了一个线程调用方法时的状态,包括本地变量、调用方法的 参数、返回值、处理的中间变量。调用本地方法时的状态保存在本地方法堆栈中(native method stacks),可能再寄存器或者其他非平台独立的内存中。
     Java堆栈有堆栈块(stack frames (or frames))组成。堆栈块包含Java方法调用的状态。当一个线程调用一个方法时,Java虚拟机会将一个新的块压到Java堆栈中,当这个方法运行结束时,Java虚拟机会将对应的块弹出并抛弃。
     Java虚拟机不使用寄存器保存计算的中间结果,而是用Java堆栈在存放中间结果。这是的Java虚拟机的指令更紧凑,也更容易在一个没有寄存器的设备上实现Java虚拟机。 
     图中的Java堆栈中向下增长的,PC寄存器中线程三为灰色,是因为它正在执行本地方法,他的下一条执行指令不保存在PC寄存器中。
四、数据类型(Data Types)
     所有Java虚拟机中使用的数据都有确定的数据类型,数据类型和操作都在Java虚拟机规范中严格定义。Java中的数据类型分为原始数据类型 (primitive types)引用数据类型(reference type)。引用类型依赖于实际的对象,但不是对象本身。原始数据类型不依赖于任何东西,他们就是本身表示的数据。
所有Java程序语言中的原始 数据类型,都是Java虚拟机的原始数据类型,除了布尔型(boolean)。当编译器将Java源代码编译为自己码时,使用整型(int)或者字节型 (byte)去表示布尔型。在Java虚拟机中使用整数0表示布尔型的false,使用非零整数表示布尔型的true,布尔数组被表示为字节数组,虽然他 们可能会以字节数组或者字节块(bit fields)保存在堆中。
     除了布尔型,其他Java语言中的原始类型都是Java虚拟机中的数据类型。在Java中数据类型被分为:整形的byte,short,int,long;char和浮点型的float,double。Java语言中的数据类型在任何主机上都有同样的范围。 
     在Java虚拟机中还存在一个Java语言中不能使用的原始数据类型返回值类型(returnValue)。这种类型被用来实现Java程序中的“finally clauses”,具体的参见18章的“Finally Clauses”。
     引用类型可能被创建为:类类型(class type),接口类型(interface type),数组类型(array type。他们都引用被动态创建的对象。当引用类型引用null时,说明没有引用任何对象。
     Java虚拟机规范只定义了每一种数据类型表示的范围,没有定义在存储时每种类型占用的空间。他们如何存储由Java虚拟机的实现者自己决定。

TypeRange
byte8-bit signed two's complement integer (-27 to 27 - 1, inclusive)
short16-bit signed two's complement integer (-215 to 215 - 1, inclusive)
int32-bit signed two's complement integer (-231 to 231 - 1, inclusive)
long64-bit signed two's complement integer (-263 to 263 - 1, inclusive)
char16-bit unsigned Unicode character (0 to 216 - 1, inclusive)
float32-bit IEEE 754 single-precision float
double64-bit IEEE 754 double-precision float
returnValueaddress of an opcode within the same method
referencereference to an object on the heap, or null
五、字节长度
     Java虚拟机中最小的数据单元式字(word),其大小由Java虚拟机的实现者定义。但是一个字的大小必须足够容纳byte,short,int, char,float,returnValue,reference;两个字必须足够容纳long,double。所以虚拟机的实现者至少提供的字不能小 于31bits的字,但是最好选择特定平台上最有效率的字长。
     在运行时,Java程序不能决定所运行机器的字长。字长也不会影响程序的行为,他只是在Java虚拟机中的一种表现方式。
六、类加载器子系统
     Java虚拟机中的类加载器分为两种:原始类加载器(primordial class loader)类加载器对象(class loader objects)。原始类加载器是Java虚拟机实现的一部分,类加载器对象是运行中的程序的一部分。不同类加载器加载的类被不同的命名空间所分割。
     类加载器调用了许多Java虚拟机中其他的部分和java.lang包中的很多类。比如,类加载对象就是java.lang.ClassLoader子类 的实例,ClassLoader类中的方法可以访问虚拟机中的类加载机制;每一个被Java虚拟机加载的类都会被表示为一个 java.lang.Class类的实例。像其他对象一样,类加载器对象和Class对象都保存在堆中,被加载的信息被保存在方法区中。
     1、加载、连接、初始化(Loading, Linking and Initialization)
类加载子系统不仅仅负责定位并加载类文件,他按照以下严格的步骤作了很多其他的事情:(具体的信息参见第七章的“类的生命周期”)
          1)、加载:寻找并导入指定类型(类和接口)的二进制信息
          2)、连接:进行验证、准备和解析
               ①验证:确保导入类型的正确性
               ②准备:为类型分配内存并初始化为默认值
               ③解析:将字符引用解析为直接饮用
          3)、初始化:调用Java代码,初始化类变量为合适的值
     2、原始类加载器(The Primordial Class Loader)
     每个Java虚拟机都必须实现一个原始类加载器,他能够加载那些遵守类文件格式并且被信任的类。但是,Java虚拟机的规范并没有定义如何加载类,这由 Java虚拟机实现者自己决定。对于给定类型名的类型,原始莱加载器必须找到那个类型名加“.class”的文件并加载入虚拟机中。
     3、类加载器对象
     虽然类加载器对象是Java程序的一部分,但是ClassLoader类中的三个方法可以访问Java虚拟机中的类加载子系统。
          1)、protected final Class defineClass(…):使用这个方法可以出入一个字节数组,定义一个新的类型。
          2)、protected Class findSystemClass(String name):加载指定的类,如果已经加载,就直接返回。
          3)、protected final void resolveClass(Class c):defineClass()方法只是加载一个类,这个方法负责后续的动态连接和初始化。
     具体的信息,参见第八章“连接模型”( The Linking Model)。
     4、命名空间
     当多个类加载器加载了同一个类时,为了保证他们名字的唯一性,需要在类名前加上加载该类的类加载器的标识。具体的信息,
七、方法区(The Method Area)
     在Java虚拟机中,被加载类型的信息都保存在方法区中。这写信息在内存中的组织形式由虚拟机的实现者定义,比如,虚拟机工作在一个“little- endian”的处理器上,他就可以将信息保存为“little-endian”格式的,虽然在Java类文件中他们是以“big-endian”格式保 存的。设计者可以用最适合并地机器的表示格式来存储数据,以保证程序能够以最快的速度执行。但是,在一个只有很小内存的设备上,虚拟机的实现者就不会占用 很大的内存。
     程序中的所有线程共享一个方法区,所以访问方法区信息的方法必须是线程安全的。如果你有两个线程都去加载一个叫Lava的类,那只能由一个线程被容许去加载这个类,另一个必须等待。
     在程序运行时,方法区的大小是可变的,程序在运行时可以扩展。有些Java虚拟机的实现也可以通过参数也订制方法区的初始大小,最小值和最大值。
     方法区也可以被垃圾收集。因为程序中的内由类加载器动态加载,所有类可能变成没有被引用(unreferenced)的状态。当类变成这种状态时,他就可 能被垃圾收集掉。没有加载的类包括两种状态,一种是真正的没有加载,另一个种是“unreferenced”的状态。
     1、类型信息(Type Information)
          每一个被加载的类型,在Java虚拟机中都会在方法区中保存如下信息:
          1)、类型的全名(The fully qualified name of the type)
          2)、类型的父类型的全名(除非没有父类型,或者弗雷形式java.lang.Object)(The fully qualified name of the typeís direct superclass)
          3)、给类型是一个类还是接口(class or an interface)(Whether or not the type is a class )
          4)、类型的修饰符(public,private,protected,static,final,volatile,transient等)(The typeís modifiers)
          5)、所有父接口全名的列表(An ordered list of the fully qualified names of any direct superinterfaces)
          类型全名保存的数据结构由虚拟机实现者定义。除此之外,Java虚拟机还要为每个类型保存如下信息:
          1)、类型的常量池(The constant pool for the type)
          2)、类型字段的信息(Field information)
          3)、类型方法的信息(Method information)
          4)、所有的静态类变量(非常量)信息(All class (static) variables declared in the type, except constants)
          5)、一个指向类加载器的引用(A reference to class ClassLoader)
          6)、一个指向Class类的引用(A reference to class Class)


          1)、类型的常量池(The constant pool for the type)
          常量池中保存中所有类型是用的有序的常量集合,包含直接常量(literals)如字符串、整数、浮点数的常量,和对类型、字段、方法的符号引用。常量池 中每一个保存的常量都有一个索引,就像数组中的字段一样。因为常量池中保存中所有类型使用到的类型、字段、方法的字符引用,所以它也是动态连接的主要对 象。          2)、类型字段的信息(Field information)
          字段名、字段类型、字段的修饰符(public,private,protected,static,final,volatile,transient等)、字段在类中定义的顺序。
          3)、类型方法的信息(Method information)
          方法名、方法的返回值类型(或者是void)、方法参数的个数、类型和他们的顺序、字段的修饰符(public,private,protected,static,final,volatile,transient等)、方法在类中定义的顺序
          如果不是抽象和本地本法还需要保存
          方法的字节码、方法的操作数堆栈的大小和本地变量区的大小(稍候有详细信息)、异常列表
          4)、类(静态)变量(Class Variables)
          类变量被所有类的实例共享,即使不通过类的实例也可以访问。这些变量绑定在类上(而不是类的实例上),所以他们是类的逻辑数据的一部分。在Java虚拟机使用这个类之前就需要为类变量(non-final)分配内存
          常量(final)的处理方式于这种类变量(non-final)不一样。每一个类型在用到一个常量的时候,都会复制一份到自己的常量池中。常量也像类变 量一样保存在方法区中,只不过他保存在常量池中。(可能是,类变量被所有实例共享,而常量池是每个实例独有的)。Non-final类变量保存为定义他的 类型数据(data for the type that declares them)的一部分,而final常量保存为使用他的类型数据(data for any type that uses them)的一部分。  
          5)、指向类加载器的引用(A reference to class ClassLoader)
          每一个被Java虚拟机加载的类型,虚拟机必须保存这个类型是否由原始类加载器或者类加载器加载。那些被类加载器加载的类型必须保存一个指向类加载器的引 用。当类加载器动态连接时,会使用这条信息。当一个类引用另一个类时,虚拟机必须保存那个被引用的类型是被同一个类加载器加载的,这也是虚拟机维护不同命 名空间的过程。
          6)、指向Class类的引用(A reference to class Class)
          Java虚拟机为每一个加载的类型创建一个java.lang.Class类的实例。你也可以通过Class类的方法:
public static Class forName(String className)来查找或者加载一个类,并取得相应的Class类的实例。通过这个Class类的实例,我们可以访问Java虚拟机方法区中的信息。具体参照Class类的JavaDoc。
     2、方法列表(Method Tables)
     为了更有效的访问所有保存在方法区中的数据,这些数据的存储结构必须经过仔细的设计。所有方法区中,除了保存了上边的那些原始信息外,还有一个为了加快存 取速度而设计的数据结构,比如方法列表。每一个被加载的非抽象类,Java虚拟机都会为他们产生一个方法列表,这个列表中保存了这个类可能调用的所有实例 方法的引用,报错那些父类中调用的方法。

八、堆
     当Java程序创建一个类的实例或者数组时,都在堆中为新的对象分配内存。虚拟机中只有一个堆,所有的线程都共享他。
     1、垃圾收集(Garbage Collection)
     垃圾收集是释放没有被引用的对象的主要方法。它也可能会为了减少堆的碎片,而移动对象。在Java虚拟机的规范中没有严格定义垃圾收集,只是定义一个Java虚拟机的实现必须通过某种方式管理自己的堆。   2、对象存储结构(Object Representation)
     Java虚拟机的规范中没有定义对象怎样在堆中存储。每一个对象主要存储的是他的类和父类中定义的对象变量。对于给定的对象的引用,虚拟机必须嫩耨很快的 定位到这个对象的数据。另为,必须提供一种通过对象的引用方法对象数据的方法,比如方法区中的对象的引用,所以一个对象保存的数据中往往含有一个某种形式 指向方法区的指针。
     一个可能的堆的设计是将堆分为两个部分:引用池和对象池。一个对象的引用就是指向引用池的本地指针。每一个引用池中的条目都包含两个部分:指向对象池中对 象数据的指针和方法区中对象类数据的指针。这种设计能够方便Java虚拟机堆碎片的整理。当虚拟机在对象池中移动一个对象的时候,只需要修改对应引用池中 的指针地址。但是每次访问对象的数据都需要处理两次指针。  另一种堆的设计是:一个对象的引用就是一个指向一堆数据和指向相应对象的偏移指针。这种设计方便了对象的访问,可是对象的移动要变的异常复杂。   当程序试图将一个对象转换为另一种类型时,虚拟机需要判断这种转换是否是这个对象的类型,或者是他的父类型。当程序适用instanceof语句的时候也 会做类似的事情。当程序调用一个对象的方法时,虚拟机需要进行动态绑定,他必须判断调用哪一个类型的方法。这也需要做上面的判断。
     无论虚拟机实现者使用哪一种设计,他都可能为每一个对象保存一个类似方法列表的信息。因为他可以提升对象方法调用的速度,对提升虚拟机的性能非常重要,但 是虚拟机的规范中比没有要求必须实现类似的数据结构。       每一个Java虚拟机中的对象必须关联一个用于同步多线程的lock(mutex)。同一时刻,只能有一个对象拥有这个对象的锁。当一个拥有这个这个对象 的锁,他就可以多次申请这个锁,但是也必须释放相应次数的锁才能真正释放这个对象锁。很多对象在整个生命周期中都不会被锁,所以这个信息只有在需要时才需 要添加。很多Java虚拟机的实现都没有在对象的数据中包含“锁定数据”,只是在需要时才生成相应的数据。除了实现对象的锁定,每一个对象还逻辑关联到一 个“wait set”的实现。锁定帮组线程独立处理共享的数据,不需要妨碍其他的线程。“wait set”帮组线程协作完成同一个目标。“wait set”往往通过Object类的wait()和notify()方法来实现。 
     垃圾收集也需要堆中的对象是否被关联的信息。Java虚拟机规范中指出垃圾收集一个运行一个对象的finalizer方法一次,但是容许 finalizer方法重新引用这个对象,当这个对象再次不被引用时,就不需要再次调用finalize方法。所以虚拟机也需要保存finalize方法 是否运行过的信息。更多信息参见第九章的“垃圾收集”
     3、数组的保存(Array Representation)
在Java 中,数组是一种完全意义上的对象,他和对象一样保存在堆中、有一个指向Class类实例的引用。所有同一维度和类型的数组拥有同样的Class,数组的长 度不做考虑。对应Class的名字表示为维度和类型。比如一个整型数据的Class为“[I”,字节型三维数组Class名为“[[[B”,两维对象数据 Class名为“[[Ljava.lang.Object”。
多维数组被表示为数组的数组 , 数组必须在堆中保存数组的长度,数组的数据和一些对象数组类型数据的引用。通过一个数组引用的,虚拟机应该能够取得一个数组的长度,通过索引能够访问特定 的数据,能够调用Object定义的方法。Object是所有数据类的直接父类。

posted @ 2007-04-23 14:18 chenweicai 阅读(337) | 评论 (0)编辑 收藏

Historical Collection Classes(JDK1.1 之前)
提供的容器有Arrays,Vector,Stack,Hashtable,Properties,BitSet。其中定义出一种走访群集内各元素的标准方式,称为Enumeration(列举器)接口,用法如下:
Vector v=new Vector();
for (Enumeration enum =v.elements(); enum.hasMoreElements();) {
Object o 
= enum.nextElement();
processObject(o);
}

而在JDK1.2版本中引入了Iterator接口,新版本的集合对象(HashSet,HashMap,WeakHeahMap,ArrayList,TreeSet,TreeMap, LinkedList)是通过Iterator接口访问集合元素的。
例如:
List list=new ArrayList();
for(Iterator it=list.iterator();it.hasNext();)
{
    System.out.println(it.next());
}

这样,如果将老版本的程序运行在新的Java编译器上就会出错。因为List接口中已经没有elements(),而只有iterator()了。那么如何可以使老版本的程序运行在新的Java编译器上呢?如果不加修改,是肯定不行的,但是修改要遵循“开-闭”原则。
这时候我想到了Java设计模式中的适配器模式。


package net.blogjava.lzqdiy;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;

public class NewEnumeration implements Enumeration
{

    Iterator it;
    
public NewEnumeration(Iterator it)
    
{
        
this.it=it;
        
// TODO Auto-generated constructor stub
    }


    
public boolean hasMoreElements()
    
{
        
// TODO Auto-generated method stub
        return it.hasNext();
    }


    
public Object nextElement()
    
{
        
// TODO Auto-generated method stub
        return it.next();
    }

    
public static void main(String[] args)
    
{
        List list
=new ArrayList();
        list.add(
"a");
        list.add(
"b");
        list.add(
"C");
        
for(Enumeration e=new NewEnumeration(list.iterator());e.hasMoreElements();)
        
{
            System.out.println(e.nextElement());
        }

    }

}

NewEnumeration是一个适配器类,通过它实现了从Iterator接口到Enumeration接口的适配,这样我们就可以使用老版本的代码来使用新的集合对象了。

posted @ 2007-04-23 10:35 chenweicai 阅读(161) | 评论 (0)编辑 收藏

一、反射的概念 :
反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力。这一概念的提出很快引发了计算机科学领域关于应用反射性的研究。它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成绩。其中LEAD/LEAD++ 、OpenC++ 、MetaXa和OpenJava等就是基于反射机制的语言。最近,反射机制也被应用到了视窗系统、操作系统和文件系统中。

反射本身并不是一个新概念,它可能会使我们联想到光学中的反射概念,尽管计算机科学赋予了反射概念新的含义,但是,从现象上来说,它们确实有某些相通之处,这些有助于我们的理解。在计算机科学领域,反射是指一类应用,它们能够自描述自控制。也就是说,这类应用通过采用某种机制来实现对自己行为的描述(self-representation)和监测(examination),并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。可以看出,同一般的反射概念相比,计算机科学领域的反射不单单指反射本身,还包括对反射结果所采取的措施。所有采用反射机制的系统(即反射系统)都希望使系统的实现更开放。可以说,实现了反射机制的系统都具有开放性,但具有开放性的系统并不一定采用了反射机制,开放性是反射系统的必要条件。一般来说,反射系统除了满足开放性条件外还必须满足原因连接(Causally-connected)。所谓原因连接是指对反射系统自描述的改变能够立即反映到系统底层的实际状态和行为上的情况,反之亦然。开放性和原因连接是反射系统的两大基本要素.

Java中,反射是一种强大的工具。它使您能够创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代表链接反射允许我们在编写与执行时,使我们的程序代码能够接入装载到JVM中的类的内部信息,而不是源代码中选定的类协作的代码。这使反射成为构建灵活的应用的主要工具。但需注意的是:如果使用不当,反射的成本很高。

二、Java中的类反射:
Reflection 是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查,或者说“自审”,并能直接操作程序的内部属性。Java 的这一能力在实际应用中也许用得不是很多,但是在其它的程序设计语言中根本就不存在这一特性。例如,Pascal、C 或者 C++ 中就没有办法在程序中获得函数定义相关的信息。

1.检测类:

1.1 reflection的工作机制

考虑下面这个简单的例子,让我们看看 reflection 是如何工作的。

import java.lang.reflect.*;
public class DumpMethods {
    public static void main(String args[]) {
        try {
            Class c = Class.forName(args[0]);
            Method m[] = c.getDeclaredMethods();
            for (int i = 0; i < m.length; i++)
                System.out.println(m[i].toString());
        } catch (Throwable e) {
            System.err.println(e);
        }
    }
}

按如下语句执行:

java DumpMethods java.util.Stack

它的结果输出为:

public java.lang.Object java.util.Stack.push(java.lang.Object)

public synchronized java.lang.Object java.util.Stack.pop()

public synchronized java.lang.Object java.util.Stack.peek()

public boolean java.util.Stack.empty()

public synchronized int java.util.Stack.search(java.lang.Object)

这样就列出了java.util.Stack 类的各方法名以及它们的限制符和返回类型。

这个程序使用 Class.forName 载入指定的类,然后调用 getDeclaredMethods 来获取这个类中定义了的方法列表。java.lang.reflect.Methods 是用来描述某个类中单个方法的一个类。

1.2 Java类反射中的主要方法

对于以下三类组件中的任何一类来说 -- 构造函数、字段和方法 -- java.lang.Class 提供四种独立的反射调用,以不同的方式来获得信息。调用都遵循一种标准格式。以下是用于查找构造函数的一组反射调用:

       Constructor getConstructor(Class[] params) -- 获得使用特殊的参数类型的公共构造函数,

       Constructor[] getConstructors() -- 获得类的所有公共构造函数

       Constructor getDeclaredConstructor(Class[] params) -- 获得使用特定参数类型的构造函数(与接入级别无关)

       Constructor[] getDeclaredConstructors() -- 获得类的所有构造函数(与接入级别无关)

获得字段信息的Class 反射调用不同于那些用于接入构造函数的调用,在参数类型数组中使用了字段名:

l         Field getField(String name) -- 获得命名的公共字段

l         Field[] getFields() -- 获得类的所有公共字段

l         Field getDeclaredField(String name) -- 获得类声明的命名的字段

l         Field[] getDeclaredFields() -- 获得类声明的所有字段

用于获得方法信息函数:

l         Method getMethod(String name, Class[] params) -- 使用特定的参数类型,获得命名的公共方法

l         Method[] getMethods() -- 获得类的所有公共方法

l         Method getDeclaredMethod(String name, Class[] params) -- 使用特写的参数类型,获得类声明的命名的方法

l         Method[] getDeclaredMethods() -- 获得类声明的所有方法

 

1.3开始使用 Reflection:

用于 reflection 的类,如 Method,可以在 java.lang.relfect 包中找到。使用这些类的时候必须要遵循三个步骤:第一步是获得你想操作的类的 java.lang.Class 对象。在运行中的 Java 程序中,用 java.lang.Class 类来描述类和接口等。

下面就是获得一个 Class 对象的方法之一:

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

这条语句得到一个 String 类的类对象。还有另一种方法,如下面的语句:

Class c = int.class;

或者

Class c = Integer.TYPE;

它们可获得基本类型的类信息。其中后一种方法中访问的是基本类型的封装类 (如 Integer) 中预先定义好的 TYPE 字段。

第二步是调用诸如 getDeclaredMethods 的方法,以取得该类中定义的所有方法的列表。

一旦取得这个信息,就可以进行第三步了——使用 reflection API 来操作这些信息,如下面这段代码:

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

Method m[] = c.getDeclaredMethods();

System.out.println(m[0].toString());

它将以文本方式打印出 String 中定义的第一个方法的原型。

2.处理对象:

如果要作一个开发工具像debugger之类的,你必须能发现filed values,以下是三个步骤:

a.创建一个Class对象
b.通过getField 创建一个Field对象
c.调用Field.getXXX(Object)方法(XXX是Int,Float等,如果是对象就省略;Object是指实例).

例如:
import java.lang.reflect.*;
import java.awt.*;

class SampleGet {

   public static void main(String[] args) {
      Rectangle r = new Rectangle(100, 325);
      printHeight(r);

   }

   static void printHeight(Rectangle r) {
      Field heightField;
      Integer heightValue;
      Class c = r.getClass();
      try {
        heightField = c.getField("height");
        heightValue = (Integer) heightField.get(r);
        System.out.println("Height: " + heightValue.toString());
      } catch (NoSuchFieldException e) {
          System.out.println(e);
      } catch (SecurityException e) {
          System.out.println(e);
      } catch (IllegalAcces***ception e) {
          System.out.println(e);
      }
   }
}

三、安全性和反射:
在处理反射时安全性是一个较复杂的问题。反射经常由框架型代码使用,由于这一点,我们可能希望框架能够全面接入代码,无需考虑常规的接入限制。但是,在其它情况下,不受控制的接入会带来严重的安全性风险,例如当代码在不值得信任的代码共享的环境中运行时。

由于这些互相矛盾的需求,Java编程语言定义一种多级别方法来处理反射的安全性。基本模式是对反射实施与应用于源代码接入相同的限制:

n         从任意位置到类公共组件的接入

n         类自身外部无任何到私有组件的接入

n         受保护和打包(缺省接入)组件的有限接入

不过至少有些时候,围绕这些限制还有一种简单的方法。我们可以在我们所写的类中,扩展一个普通的基本类java.lang.reflect.AccessibleObject 类。这个类定义了一种setAccessible方法,使我们能够启动或关闭对这些类中其中一个类的实例的接入检测。唯一的问题在于如果使用了安全性管理器,它将检测正在关闭接入检测的代码是否许可了这样做。如果未许可,安全性管理器抛出一个例外。

下面是一段程序,在TwoString 类的一个实例上使用反射来显示安全性正在运行:

public class ReflectSecurity {

    public static void main(String[] args) {

        try {

            TwoString ts = new TwoString("a", "b");

            Field field = clas.getDeclaredField("m_s1");

//          field.setAccessible(true);

            System.out.println("Retrieved value is " +

                field.get(inst));

        } catch (Exception ex) {

            ex.printStackTrace(System.out);

        }

    }

}

如果我们编译这一程序时,不使用任何特定参数直接从命令行运行,它将在field .get(inst)调用中抛出一个IllegalAcces***ception异常。如果我们不注释field.setAccessible(true)代码行,那么重新编译并重新运行该代码,它将编译成功。最后,如果我们在命令行添加了JVM参数-Djava.security.manager以实现安全性管理器,它仍然将不能通过编译,除非我们定义了ReflectSecurity类的许可权限。

四、反射性能:
反射是一种强大的工具,但也存在一些不足。一个主要的缺点是对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于只直接执行相同的操作。

下面的程序是字段接入性能测试的一个例子,包括基本的测试方法。每种方法测试字段接入的一种形式 -- accessSame 与同一对象的成员字段协作,accessOther 使用可直接接入的另一对象的字段,accessReflection 使用可通过反射接入的另一对象的字段。在每种情况下,方法执行相同的计算 -- 循环中简单的加/乘顺序。

程序如下:

public int accessSame(int loops) {

    m_value = 0;

    for (int index = 0; index < loops; index++) {

        m_value = (m_value + ADDITIVE_VALUE) *

            MULTIPLIER_VALUE;

    }

    return m_value;

}

 

public int accessReference(int loops) {

    TimingClass timing = new TimingClass();

    for (int index = 0; index < loops; index++) {

        timing.m_value = (timing.m_value + ADDITIVE_VALUE) *

            MULTIPLIER_VALUE;

    }

    return timing.m_value;

}

 

public int accessReflection(int loops) throw* **ception {

    TimingClass timing = new TimingClass();

    try {

        Field field = TimingClass.class.

            getDeclaredField("m_value");

        for (int index = 0; index < loops; index++) {

            int value = (field.getInt(timing) +

                ADDITIVE_VALUE) * MULTIPLIER_VALUE;

            field.setInt(timing, value);

        }

        return timing.m_value;

    } catch (Exception ex) {

        System.out.println("Error using reflection");

        throw ex;

    }

}

在上面的例子中,测试程序重复调用每种方法,使用一个大循环数,从而平均多次调用的时间衡量结果。平均值中不包括每种方法第一次调用的时间,因此初始化时间不是结果中的一个因素。下面的图清楚的向我们展示了每种方法字段接入的时间:

图 1:字段接入时间 :


我们可以看出:在前两副图中(Sun JVM),使用反射的执行时间超过使用直接接入的1000倍以上。通过比较,IBM JVM可能稍好一些,但反射方法仍旧需要比其它方法长700倍以上的时间。任何JVM上其它两种方法之间时间方面无任何显著差异,但IBM JVM几乎比Sun JVM快一倍。最有可能的是这种差异反映了Sun Hot Spot JVM的专业优化,它在简单基准方面表现得很糟糕。反射性能是Sun开发1.4 JVM时关注的一个方面,它在反射方法调用结果中显示。在这类操作的性能方面,Sun 1.4.1 JVM显示了比1.3.1版本很大的改进。

如果为为创建使用反射的对象编写了类似的计时测试程序,我们会发现这种情况下的差异不象字段和方法调用情况下那么显著。使用newInstance()调用创建一个简单的java.lang.Object实例耗用的时间大约是在Sun 1.3.1 JVM上使用new Object()的12倍,是在IBM 1.4.0 JVM的四倍,只是Sun 1.4.1 JVM上的两部。使用Array.newInstance(type, size)创建一个数组耗用的时间是任何测试的JVM上使用new type[size]的两倍,随着数组大小的增加,差异逐步缩小。

结束语:
Java语言反射提供一种动态链接程序组件的多功能方法。它允许程序创建和控制任何类的对象(根据安全性限制),无需提前硬编码目标类。这些特性使得反射特别适用于创建以非常普通的方式与对象协作的库。例如,反射经常在持续存储对象为数据库、XML或其它外部格式的框架中使用。Java reflection 非常有用,它使类和数据结构能按名称动态检索相关信息,并允许在运行着的程序中操作这些信息。Java 的这一特性非常强大,并且是其它一些常用语言,如 C、C++、Fortran 或者 Pascal 等都不具备的。

但反射有两个缺点。第一个是性能问题。用于字段和方法接入时反射要远慢于直接代码。性能问题的程度取决于程序中是如何使用反射的。如果它作为程序运行中相对很少涉及的部分,缓慢的性能将不会是一个问题。即使测试中最坏情况下的计时图显示的反射操作只耗用几微秒。仅反射在性能关键的应用的核心逻辑中使用时性能问题才变得至关重要。

许多应用中更严重的一个缺点是使用反射会模糊程序内部实际要发生的事情。程序人员希望在源代码中看到程序的逻辑,反射等绕过了源代码的技术会带来维护问题。反射代码比相应的直接代码更复杂,正如性能比较的代码实例中看到的一样。解决这些问题的最佳方案是保守地使用反射——仅在它可以真正增加灵活性的地方——记录其在目标类中的使用。

posted @ 2007-04-23 10:27 chenweicai 阅读(154) | 评论 (0)编辑 收藏

public class ServerSocket
extends Object

此类实现服务器套接字服务器套接字等待请求通过网络传入。它基于该请求执行某些操作,然后可能向请求者返回结果。

服务器套接字的实际工作由 SocketImpl 类的实例执行。应用程序可以更改创建套接字实现的套接字工厂来配置它自身,从而创建适合本地防火墙的套接字。



public class Socket
extends Object

此类实现客户端套接字(也可以就叫“套接字”)。套接字是两台机器之间的通信端点

套接字的实际工作由 SocketImpl 类的实例执行。应用程序通过更改创建套接字实现的套接字工厂可以配置它自身,以创建适合本地防火墙的套接字。


/**
 * 为了验证我们的服务器程序能否正常工作,还必须有一个客户端程序与之通信。
 * 我们也没必要每次都要编写客户端程序来测试。其实, Windows提供的telnet
 * 程序,就是一个TCP客户端程序,我们只要使用telnet程序对我们的服务器程序
 * 进行测试,我们只要在运行telnet程序时指定所要连接的服务器程序的IP地址
 * 和端口号,telnet程序就会按照指定的参数去与服务器程序进行连接。建立连接
 * 后,在telnet程序窗口中键入的内容会发送到服务器,从服务器端接收到的数据
 * 会显示在窗口中。
 * 先运行TcpServer程序,然后在命令行中输入 telnet 10.214.16.80 8001
 * 就会执行
 */

// TCP 服务端程序
public class TcpServer {

 public static void main(String[] args){
  
  try{
   //建立一个在8001端口上等待连接的ServerSocket对象
   ServerSocket ss = new ServerSocket(8001);
   
   //侦听并接受到此套接字的连接。此方法在进行连接之前一直阻塞,返回客户端套接字
   Socket s = ss.accept();
   
   InputStream ips = s.getInputStream();//返回此套接字的输入流
   OutputStream ops = s.getOutputStream();//返回此套接字的输出流
   
   //将字节从指定的字节数组写入此输出流
   ops.write("chenweicai...".getBytes());
   
//   byte[] buf = new byte[1024];
//   // 从输入流中读取一定数量的字节并将其存储在缓冲区数组buf中
//   int len = ips.read(buf);
//   System.out.println(new String(buf, 0, len));
   
   //由于telent只要输入就发送,而不管有没有回车,所以只有第一个字符被发送。
   //java提供了一个BufferedReader类,可以帮助我们按行处理输入流。
   BufferedReader br = new BufferedReader (new InputStreamReader(ips));
   System.out.println("The message comes form client: " + br.readLine());//输出客户端输入的内容
   
   br.close();//关闭包装类,会自动关闭包装类中所包装的底层类,所以不用调用ips.close().
//   ips.close();
   ops.close();
   s.close();
   ss.close();
  }catch (Exception e){
   e.printStackTrace();
  }
 }
}

给个具体的例子

/**
 * 实现服务器和客户端之间的对话功能
 */
public class Servicer implements Runnable {

 Socket s;
 
 public Servicer (Socket s){
  this.s = s;
 }
 
 public void run(){
  
  try {
   InputStream ips = s.getInputStream();
   OutputStream ops = s.getOutputStream();
   BufferedReader br = new BufferedReader(new InputStreamReader(ips));
   DataOutputStream dos = new DataOutputStream(ops);
   
   while(true){
    String strWord = br.readLine();
    System.out.println("From Client StrWord: " + strWord + strWord.length());
    if(strWord.equalsIgnoreCase("quit"))
     break;
    
    String strEcho = (new StringBuffer(strWord).reverse()).toString();
    dos.writeBytes(strWord + "---->" + strEcho + System.getProperty("line.separator"));
   }
   br.close();
   dos.close();
   s.close();
  } catch (IOException e) {
   e.printStackTrace();
  }
  
 }
}


import java.net.ServerSocket;
import java.net.Socket;

public class TcpServer {

 public static void main(String[] args){
  
  try{
   ServerSocket ss = new ServerSocket(8001);
   
   while(true){
    Socket s = ss.accept();
    new Thread(new Servicer(s)).start();
   }
  }catch(Exception e){
   
  }
 }
}



public class TcpClient {

 public static void main(String[] args){
  
  try{
   if(args.length < 2){
    System.out.println("Usage: java TcpClient ServerIP ServerPort");
    return;
   }
   
   Socket s = new Socket(InetAddress.getByName(args[0]), Integer.parseInt(args[1]));
   InputStream ips = s.getInputStream();
   OutputStream ops = s.getOutputStream();
   
   BufferedReader brKey = new BufferedReader(new InputStreamReader(System.in));
   DataOutputStream dos = new DataOutputStream(ops);
   BufferedReader brNet = new BufferedReader(new InputStreamReader(ips));
   
   while(true){
    String strWord = brKey.readLine();
    dos.writeBytes(strWord + System.getProperty("ling.separator"));
    if(strWord.equalsIgnoreCase("quit"))
     break;
    else
     System.out.println(brNet.readLine());
   }
   dos.close();
   brNet.close();
   brKey.close();
   s.close();
  }catch (Exception e){
   
  }
 }
}

posted @ 2007-04-20 22:54 chenweicai 阅读(365) | 评论 (0)编辑 收藏

public class DatagramSocket
extends Object

此类表示用来发送和接收数据报包的套接字

数据报套接字是包投递服务的发送或接收点。每个在数据报套接字上发送或接收的包都是单独编址和路由的。从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。

在 DatagramSocket 上总是启用 UDP 广播发送。为了接收广播包,应该将 DatagramSocket 绑定到通配符地址。在某些实现中,将 DatagramSocket 绑定到一个更加具体的地址时广播包也可以被接收。

示例:DatagramSocket s = new DatagramSocket(null); s.bind(new InetSocketAddress(8888)); 这等价于:DatagramSocket s = new DatagramSocket(8888); 两个例子都能创建能够在 UDP 8888 端口上接收广播的 DatagramSocket。



public final class DatagramPacket
extends Object

此类表示数据报包

数据报包用来实现无连接包投递服务。每条报文仅根据该包中包含的信息从一台机器路由到另一台机器。从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。不对包投递做出保证。



给个具体实现的例子

//UDP数据的发送类似发送寻呼一样的道理。就像寻呼机必须先处于开机接收状态才能接收寻呼一样的道理
//我们先要运行UDP接收程序,再运行UDP发送程序。UDP数据包的接收是过期作废的。
public class UdpSend {

 public static void main(String[] args) throws SocketException, UnknownHostException{
  DatagramSocket ds = new DatagramSocket();//创建用来发送数据报包的套接字
  String str = "Hello World,陈伟才";
  DatagramPacket dp = new DatagramPacket(str.getBytes(), str.getBytes().length,
    InetAddress.getByName("10.214.16.80"), 3000);
  //构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号
  
  try {
   ds.send(dp);
  } catch (IOException e) {
   e.printStackTrace();
  }
  ds.close();
 }
}

public class UdpRecv {

 public static void main(String[] args) throws Exception{
  DatagramSocket ds = new DatagramSocket(3000);//创建接收数据报套接字并将其绑定到本地主机上的指定端口
  byte[] buf = new byte[1024];
  DatagramPacket dp = new DatagramPacket(buf, 1024);
  ds.receive(dp);
  String strRecv = new String (dp.getData(), 0, dp.getLength()) + " from "
  + dp.getAddress().getHostAddress() + ":" + dp.getPort();
  System.out.println(strRecv);
  ds.close();
  
 }
}

再给个例子Chat

public class Chat {

 Frame f = new Frame("我的聊天室");
 
 /*
  * tfIp 是用于输入IP地址的文本框,在发送数据时, 要取出其中的IP地址,
  * 所以将其定义成员变量,以便发送消息的程序代码能访问。
  */
 TextField tfIP = new TextField(15);
 
 /**
  * lst时用于显示接收消息的列表框, 在接收到数据时,要向其中增加新的记录项
  * 所以将其定义成员变量,以便发送消息的程序代码能访问
  */
 List lst = new List(6);
 
 DatagramSocket ds;
 
 public Chat(){
  try{
   ds = new DatagramSocket(3000);
  }catch(Exception e){
   e.printStackTrace();
  }
  
  new Thread (new Runnable(){
   
   public void run(){
    
    byte[] buf = new byte[1024];
    DatagramPacket dp = new DatagramPacket(buf, 1024);
    while (true){
     try{
      ds.receive(dp);
      lst.add(new String(buf, 0 , dp.getLength()) +
        " : form " + dp.getAddress().getHostAddress(), 0);
     }catch(Exception ee){
      ee.printStackTrace();
     }
    }
   }
  }).start();
 }
 
 public void init(){
  f.setSize(300, 300);
  f.add(lst);
  
  Panel p = new Panel();
  p.setLayout(new BorderLayout());
  p.add("West", tfIP);
  TextField tfData = new TextField (20);
  p.add("East", tfData);
  f.add("South", p);
  
  f.setVisible(true);
  f.setResizable(false);//不能改变窗口大小
  
  //增加关闭窗口的事件处理代码
  f.addWindowListener(new WindowAdapter(){
   public void windowClosing(WindowEvent e){
    ds.close();//程序退出时,关闭Socket,释放相关的资源
    f.setVisible(false);
    f.dispose();
    System.exit(0);
   }
  });
  
  //增加在消息文本框中按下回车键的事件处理代码
  tfData.addActionListener(new ActionListener(){
   public void actionPerformed (ActionEvent e){
    //要在这里增加网络消息发送相关程序代码
    //下面的语句用于数据发送后,清空文本框中原来的内容
    
    // 取出文本框的消息字符串,并将其转换成字节数组
    byte[] buf;
    buf = e.getActionCommand().getBytes();
    try {
     DatagramPacket dp = new DatagramPacket(buf, buf.length,
       InetAddress.getByName(tfIP.getText()), 3000);
     try {
      ds.send(dp);
     } catch (IOException e1) {
      e1.printStackTrace();
     }
    } catch (UnknownHostException e1) {
     e1.printStackTrace();
    }
    ((TextField)e.getSource()).setText("");
   }
  });
 }
 
 public static void main(String[] args){
  Chat chat = new Chat();
  chat.init();
 }
}

posted @ 2007-04-20 22:50 chenweicai 阅读(897) | 评论 (0)编辑 收藏

课前思考
  1. 什么是TCP/ IP协议?
  2. TCP/IP有哪两种传输协议,各有什么特点?
  3. 什么是URL
  4. URLIP地址有什么样的关系?
  5. 什么叫套接字(Socket)?
  6. 套接字(Socket)和TCP/IP协议的关系?
  7. URL和套接字(Socket)的关系?
8.1 网络编程基本概念,TCP/IP协议简介

8.1.1 网络基础知识

网络编程的目的就是指直接或间接地通过网络协议与其他计算机进行通讯。网络编程中有两个主要的问题,一个是如何准确的定位网络上一台或多台主机,另一个就是找到主机后如何可靠高效的进行数据传输。在TCP/IP协议中IP层主要负责网络主机的定位,数据传输的路由,由IP地址可以唯一地确定Internet上的一台主机。而TCP层则提供面向应用的可靠的或非可靠的数据传输机制,这是网络编程的主要对象,一般不需要关心IP层是如何处理数据的。
  目前较为流行的网络编程模型是客户机/服务器(C/S)结构。即通信双方一方作为服务器等待客户提出请求并予以响应。客户则在需要服务时向服务器提出申请。服务器一般作为守护进程始终运行,监听网络端口,一旦有客户请求,就会启动一个服务进程来响应该客户,同时自己继续监听服务端口,使后来的客户也能及时得到服务。

8.1.3两类传输协议:TCP;UDP
  尽管TCP/IP协议的名称中只有TCP这个协议名,但是在TCP/IP的传输层同时存在TCPUDP两个协议。

TCPTranfer Control Protocol的简称,是一种面向连接的保证可靠传输的协议。通过TCP协议传输,得到的是一个顺序的无差错的数据流。发送方和接收方的成对的两个socket之间必须建立连接,以便在TCP协议的基础上进行通信,当一个socket(通常都是server socket)等待建立连接时,另一个socket可以要求进行连接,一旦这两个socket连接起来,它们就可以进行双向数据传输,双方都可以进行发送或接收操作。
  UDPUser Datagram Protocol的简称,是一种无连接的协议,每个数据报都是一个独立的信息,包括完整的源地址或目的地址,它在网络上以任何可能的路径传往目的地,因此能否到达目的地,到达目的地的时间以及内容的正确性都是不能被保证的。
  下面我们对这两种协议做简单比较:
  使用UDP时,每个数据报中都给出了完整的地址信息,因此无需要建立发送方和接收方的连接。对于TCP协议,由于它是一个面向连接的协议,在socket之间进行数据传输之前必然要建立连接,所以在TCP中多了一个连接建立的时间。
 
 使用UDP传输数据时是有大小限制的,每个被传输的数据报必须限定在64KB之内。而TCP没有这方面的限制,一旦连接建立起来,双方的socket就可以按统一的格式传输大量的数据。UDP是一个不可靠的协议,发送方所发送的数据报并不一定以相同的次序到达接收方。而TCP是一个可靠的协议,它确保接收方完全正确地获取发送方所发送的全部数据。
  总之,TCP在网络通信上有极强的生命力,例如远程连接(Telnet)和文件传输(FTP)都需要不定长度的数据被可靠地传输。相比之下UDP操作简单,而且仅需要较少的监护,因此通常用于局域网高可靠性的分散系统中client/server应用程序。
  读者可能要问,既然有了保证可靠传输的TCP协议,为什么还要非可靠传输的UDP协议呢?主要的原因有两个。一是可靠的传输是要付出代价的,对数据内容正确性的检验必然占用计算机的处理时间和网络的带宽,因此TCP传输的效率不如UDP高。二是在许多应用中并不需要保证严格的传输可靠性,比如视频会议系统,并不要求音频视频数据绝对的正确,只要保证连贯性就可以了,这种情况下显然使用UDP会更合理一些。

8.2 基于URL的高层次Java网络编程

8.2.1一致资源定位器URL

URL(Uniform Resource Locator)是一致资源定位器的简称,它表示Internet上某一资源的地址。通过URL我们可以访问Internet上的各种网络资源,比如最常见的WWWFTP站点。浏览器通过解析给定的URL可以在网络上查找相应的文件或其他资源。

8.2.2 URL的组成

protocol://resourceName
  协议名(protocol)指明获取资源所使用的传输协议,如httpftpgopherfile等,资源名(resourceName)则应该是资源的完整地址,包括主机名、端口号、文件名或文件内部的一个引用。例如:
  http://www.sun.com/ 协议名://主机名
  http://home.netscape.com/home/welcome.html 协议名://机器名+文件名
  http://www.gamelan.com:80/Gamelan/network.html#BOTTOM 协议名://机器名+端口号+文件名+内部引用.

8.2.3 创建一个URL

为了表示URL, java.net中实现了类URL。我们可以通过下面的构造方法来初始化一个URL对象:
  (1) public URL (String spec);
     通过一个表示URL地址的字符串可以构造一个URL对象。
     URL urlBase=new URL("http://www. 263.net/")
  (2) public URL(URL context, String spec);
     通过基URL和相对URL构造一个URL对象。
     URL net263=new URL ("http://www.263.net/");
     URL index263=new URL(net263, "index.html")
  (3) public URL(String protocol, String host, String file);
     new URL("http", "www.gamelan.com", "/pages/Gamelan.net. html");
  (4) public URL(String protocol, String host, int port, String file);
     URL gamelan=new URL("http", "www.gamelan.com", 80, "Pages/Gamelan.network.html");

  注意:URL的构造方法都声明抛弃非运行时例外(MalformedURLException),因此生成URL对象时,我们必须要对这一例外进行处理,通常是用try-catch语句进行捕获。格式如下:

try{
     URL myURL= new URL(…)
  }catch (MalformedURLException e){
    }

8.2.4 解析一个URL

一个URL对象生成后,其属性是不能被改变的,但是我们可以通过类URL所提供的方法来获取这些属性:
   public String getProtocol() 获取该URL的协议名。
   public String getHost() 获取该URL的主机名。
   public int getPort() 获取该URL的端口号,如果没有设置端口,返回-1。
   public String getFile() 获取该URL的文件名。
   public String getRef() 获取该URL在文件中的相对位置。
   public String getQuery() 获取该URL的查询信息。
   public String getPath() 获取该URL的路径
   public String getAuthority() 获取该URL的权限信息
   public String getUserInfo() 获得使用者的信息
    public String getRef() 获得该URL的锚

8.2.5 从URL读取WWW网络资源

当我们得到一个URL对象后,就可以通过它读取指定的WWW资源。这时我们将使用URL的方法openStream(),其定义为:
         InputStream openStream();
  
  方法openSteam()与指定的URL建立连接并返回InputStream类的对象以从这一连接中读取数据。
  public class URLReader {
  public static void main(String[] args) throws Exception {
                      //声明抛出所有例外
    URL tirc = new URL("http://www.tirc1.cs.tsinghua.edu.cn/");
                      //构建一URL对象
    BufferedReader in = new BufferedReader(new InputStreamReader(tirc.openStream()));
    //使用openStream得到一输入流并由此构造一个BufferedReader对象
    String inputLine;
    while ((inputLine = in.readLine()) != null)
                 //从输入流不断的读数据,直到读完为止
       System.out.println(inputLine); //把读入的数据打印到屏幕上
    in.close(); //关闭输入流
  }
  }

8.2.6 通过URLConnetction连接WWW

通过URL的方法openStream(),我们只能从网络上读取数据,如果我们同时还想输出数据,例如向服务器端的CGI程序发送一些数据,我们必须先与URL建立连接,然后才能对其进行读写,这时就要用到类URLConnection了。CGI是公共网关接口(Common Gateway Interface)的简称,它是用户浏览器和服务器端的应用程序进行连接的接口,有关CGI程序设计,请读者参考有关书籍。
  URLConnection也在包java.net中定义,它表示Java程序和URL在网络上的通信连接。当与一个URL建立连接时,首先要在一个URL对象上通过方法openConnection()生成对应的URLConnection对象。例如下面的程序段首先生成一个指向地址http://edu.chinaren.com/index.shtml的对象,然后用openConnection()打开该URL对象上的一个连接,返回一个URLConnection对象。如果连接过程失败,将产生IOException.
  Try{
    URL netchinaren = new URL ("http://edu.chinaren.com/index.shtml");
    URLConnectonn tc = netchinaren.openConnection();
  }catch(MalformedURLException e){ //创建URL()对象失败
  
  }catch (IOException e){ //openConnection()失败
  
  }
  类URLConnection提供了很多方法来设置或获取连接参数,程序设计时最常使用的是getInputStream()getOurputStream(),其定义为:
     InputSteram getInputSteram();
     OutputSteram getOutputStream();
  通过返回的输入/输出流我们可以与远程对象进行通信。看下面的例子:
  URL url =new URL ("http://www.javasoft.com/cgi-bin/backwards");
  //创建一URL对象
  URLConnectin con=url.openConnection();
  //URL对象获取URLConnection对象
  DataInputStream dis=new DataInputStream (con.getInputSteam());
  //URLConnection获取输入流,并构造DataInputStream对象
  PrintStream ps=new PrintSteam(con.getOutupSteam());
  //URLConnection获取输出流,并构造PrintStream对象
  String line=dis.readLine(); //从服务器读入一行
  ps.println("client…"); //向服务器写出字符串 "client…"
    其中backwards为服务器端的CGI程序。实际上,类URL的方法openSteam()是通过URLConnection来实现的。它等价于
    openConnection().getInputStream();
  基于URL的网络编程在底层其实还是基于下面要讲的Socket接口的。WWWFTP等标准化的网络服务都是基于TCP协议的,所以本质上讲URL编程也是基于TCP的一种应用.

8.3 基于Socket的低层次Java网络编程

8.3.1 Socket通讯

网络上的两个程序通过一个双向的通讯连接实现数据的交换,这个双向链路的一端称为一个SocketSocket通常用来实现客户方和服务方的连接。SocketTCP/IP协议的一个十分流行的编程界面,一个Socket由一个IP地址和一个端口号唯一确定。
  在传统的UNIX环境下可以操作TCP/IP协议的接口不止Socket一个,Socket所支持的协议种类也不光TCP/IP一种,因此两者之间是没有必然联系的。在Java环境下,Socket编程主要是指基于TCP/IP协议的网络编程。

8.3.2 Socket通讯的一般过程

使用Socket进行Client/Server程序设计的一般连接过程是这样的:ServerListen(监听)某个端口是否有连接请求,Client端向Server端发出Connect(连接)请求,Server端向Client端发回Accept(接受)消息。一个连接就建立起来了。Server端和Client端都可以通过SendWrite等方法与对方通信。

对于一个功能齐全的Socket,都要包含以下基本结构,其工作过程包含以下四个基本的步骤:
  (1 创建Socket
  (2 打开连接到Socket的输入/出流;
  (3 按照一定的协议对Socket进行读/写操作;
  (4 关闭Socket.

8.3.3 创建Socket

java在包java.net中提供了两个类SocketServerSocket,分别用来表示双向连接的客户端和服务端。这是两个封装得非常好的类,使用很方便。其构造方法如下:
  Socket(InetAddress address, int port);
  Socket(InetAddress address, int port, boolean stream);
  Socket(String host, int prot);
  Socket(String host, int prot, boolean stream);
  Socket(SocketImpl impl)
  Socket(String host, int port, InetAddress localAddr, int localPort)
  Socket(InetAddress address, int port, InetAddress localAddr, int localPort)
  ServerSocket(int port);
  ServerSocket(int port, int backlog);
  ServerSocket(int port, int backlog, InetAddress bindAddr)
  其中addresshostport分别是双向连接中另一方的IP地址、主机名和端口号,stream指明socket是流socket还是数据报socketlocalPort表示本地主机的端口号,localAddrbindAddr是本地机器的地址(ServerSocket的主机地址),implsocket的父类,既可以用来创建serverSocket又可以用来创建Socketcount则表示服务端所能支持的最大连接数。例如:
  Socket client = new Socket("127.0.01.", 80);
  ServerSocket server = new ServerSocket(80);
  注意,在选择端口时,必须小心。每一个端口提供一种特定的服务,只有给出正确的端口,才能获得相应的服务。0~1023的端口号为系统所保留,例如http服务的端口号为80,telnet服务的端口号为21,ftp服务的端口号为23, 所以我们在选择端口号时,最好选择一个大于1023的数以防止发生冲突。
  在创建socket时如果发生错误,将产生IOException,在程序中必须对之作出处理。所以在创建SocketServerSocket是必须捕获或抛出例外。

8.3.8 简单的Client/Server程序设计

下面我们给出一个用Socket实现的客户和服务器交互的典型的C/S结构的演示程序,读者通过仔细阅读该程序,会对前面所讨论的各个概念有更深刻的认识。程序的意义请参考注释。

1. 客户端程序
  import java.io.*;
  import java.net.*;
  public class TalkClient {
    public static void main(String args[]) {
      try{
        Socket socket=new Socket("127.0.0.1",4700);
        
//向本机的4700端口发出客户请求
        BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));
        
//由系统标准输入设备构造BufferedReader对象
        PrintWriter os=new PrintWriter(socket.getOutputStream());
        
//由Socket对象得到输出流,并构造PrintWriter对象
        BufferedReader is=new BufferedReader(new InputStreamReader(socket.getInputStream()));
        
//由Socket对象得到输入流,并构造相应的BufferedReader对象
        String readline;
        readline=sin.readLine();
//从系统标准输入读入一字符串
        while(!readline.equals("bye")){
        
//若从标准输入读入的字符串为 "bye"则停止循环
          os.println(readline);
          
//将从系统标准输入读入的字符串输出到Server
          os.flush();
          
//刷新输出流,使Server马上收到该字符串
          System.out.println("Client:"+readline);

          //在系统标准输出上打印读入的字符串

          System.out.println("Server:"+is.readLine());
          
//从Server读入一字符串,并打印到标准输出上
          readline=sin.readLine();
//从系统标准输入读入一字符串
        }
//继续循环
        os.close();
//关闭Socket输出流
        is.close();
//关闭Socket输入流
        socket.close();
//关闭Socket
      }catch(Exception e) {
        System.out.println("Error"+e);
//出错,则打印出错信息
      }
  }
}

 2. 服务器端程序
  import java.io.*;
  import java.net.*;
  import java.applet.Applet;
  public class TalkServer{
    public static void main(String args[]) {
      try{
        ServerSocket server=null;
        try{
          server=new ServerSocket(4700);
        
//创建一个ServerSocket在端口4700监听客户请求
        }catch(Exception e) {
          System.out.println("can not listen to:"+e);
        
//出错,打印出错信息
        }

        Socket socket=null;
        try{
          socket=server.accept();
          
//使用accept()阻塞等待客户请求,有客户
          //请求到来则产生一个Socket对象,并继续执行

        }catch(Exception e) {
          System.out.println("Error."+e);
          
//出错,打印出错信息
        }
        String line;
        BufferedReader is=new BufferedReader(new InputStreamReader(socket.getInputStream()));
         
//由Socket对象得到输入流,并构造相应的BufferedReader对象
        PrintWriter os=newPrintWriter(socket.getOutputStream());
         
//由Socket对象得到输出流,并构造PrintWriter对象
        BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));
         
//由系统标准输入设备构造BufferedReader对象

        System.out.println("Client:"+is.readLine());
        
//在标准输出上打印从客户端读入的字符串
        line=sin.readLine();
        
//从标准输入读入一字符串
        while(!line.equals("bye")){
        
//如果该字符串为 "bye",则停止循环
          os.println(line);
          
//向客户端输出该字符串
          os.flush();
          
//刷新输出流,使Client马上收到该字符串
          System.out.println("Server:"+line);
          
//在系统标准输出上打印读入的字符串
          System.out.println("Client:"+is.readLine());
          
//从Client读入一字符串,并打印到标准输出上
          line=sin.readLine();
          
//从系统标准输入读入一字符串
        }  
//继续循环
        os.close();
//关闭Socket输出流
        is.close();
//关闭Socket输入流
        socket.close();
//关闭Socket
        server.close();
//关闭ServerSocket
      }catch(Exception e){
        System.out.println("Error:"+e);
        
//出错,打印出错信息
      }
    }
  }

8.3.9 支持多客户的client/server程序设计

前面提供的Client/Server程序只能实现Server和一个客户的对话。在实际应用中,往往是在服务器上运行一个永久的程序,它可以接收来自其他多个客户端的请求,提供相应的服务。为了实现在服务器方给多个客户提供服务的功能,需要对上面的程序进行改造,利用多线程实现多客户机制。服务器总是在指定的端口上监听是否有客户请求,一旦监听到客户请求,服务器就会启动一个专门的服务线程来响应该客户的请求,而服务器本身在启动完线程之后马上又进入监听状态,等待下一个客户的到来。

ServerSocket serverSocket=null;
    boolean listening=true;
    try{
      serverSocket=new ServerSocket(4700);
      //创建一个ServerSocket在端口4700监听客户请求
    }catch(IOException e) {  }
    while(listening){ //永远循环监听
      new ServerThread(serverSocket.accept(),clientnum).start();
      //监听到客户请求,根据得到的Socket对象和
       客户计数创建服务线程,并启动之
      clientnum++; //增加客户计数
    }
    serverSocket.close(); //关闭ServerSocket

设计ServerThread

 public class ServerThread extends Thread{
   Socket socket=null; //保存与本线程相关的Socket对象
   int clientnum; //保存本进程的客户计数
   public ServerThread(Socket socket,int num) { //构造函数
    this.socket=socket; //初始化socket变量
    clientnum=num+1; //初始化clientnum变量
   }
   public void run() { //线程主体
    try{//在这里实现数据的接受和发送}

8.3.10 据报Datagram通讯

前面在介绍TCP/IP协议的时候,我们已经提到,在TCP/IP协议的传输层除了TCP协议之外还有一个UDP协议,相比而言UDP的应用不如TCP广泛,几个标准的应用层协议HTTPFTPSMTP…使用的都是TCP协议。但是,随着计算机网络的发展,UDP协议正越来越来显示出其威力,尤其是在需要很强的实时交互性的场合,如网络游戏,视频会议等,UDP更是显示出极强的威力,下面我们就介绍一下Java环境下如何实现UDP网络传输。

8.3.11 什么是Datagram

所谓数据报(Datagram)就跟日常生活中的邮件系统一样,是不能保证可靠的寄到的,而面向链接的TCP就好比电话,双方能肯定对方接受到了信息。在本章前面,我们已经对UDPTCP进行了比较,在这里再稍作小节:
  TCP,可靠,传输大小无限制,但是需要连接建立时间,差错控制开销大。
  UDP,不可靠,差错控制开销较小,传输大小限制在64K以下,不需要建立连接。

8.3.12 Datagram通讯的表示方法:DatagramSocketDatagramPacket

java.net中提供了两个类DatagramSocketDatagramPacket用来支持数据报通信,DatagramSocket用于在程序之间建立传送数据报的通信连接, DatagramPacket则用来表示一个数据报。先来看一下DatagramSocket的构造方法:
   DatagramSocket();
   DatagramSocketint prot;
   DatagramSocket(int port, InetAddress laddr)
    其中,port指明socket所使用的端口号,如果未指明端口号,则把socket连接到本地主机上一个可用的端口。laddr指明一个可用的本地地址。给出端口号时要保证不发生端口冲突,否则会生成SocketException类例外。注意:上述的两个构造方法都声明抛弃非运行时例外SocketException,程序中必须进行处理,或者捕获、或者声明抛弃。
  
用数据报方式编写client/server程序时,无论在客户方还是服务方,首先都要建立一个DatagramSocket对象,用来接收或发送数据报,然后使用DatagramPacket类对象作为传输数据的载体。下面看一下DatagramPacket的构造方法
   DatagramPacketbyte buf[],int length);
   DatagramPacket(byte buf[], int length, InetAddress addr, int port);
   DatagramPacket(byte[] buf, int offset, int length)
   DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port)

  其中,buf中存放数据报数据,length为数据报中数据的长度,addrport旨明目的地址,offset指明了数据报的位移量。
  在接收数据前,应该采用上面的第一种方法生成一个DatagramPacket对象,给出接收数据的缓冲区及其长度。然后调用DatagramSocket 的方法receive()等待数据报的到来,receive()将一直等待,直到收到一个数据报为止。
  DatagramPacket packet=new DatagramPacket(buf, 256);
  Socket.receive (packet);
  发送数据前,也要先生成一个新的DatagramPacket对象,这时要使用上面的第二种构造方法,在给出存放发送数据的缓冲区的同时,还要给出完整的目的地址,包括IP地址和端口号。发送数据是通过DatagramSocket的方法send()实现的,send()根据数据报的目的地址来寻径,以传递数据报。
  DatagramPacket packet=new DatagramPacket(buf, length, address, port);
  Socket.send(packet)
   
在构造数据报时,要给出InetAddress类参数。类InetAddress在包java.net中定义,用来表示一个Internet地址,我们可以通过它提供的类方法getByName()从一个表示主机名的字符串获取该主机的IP地址,然后再获取相应的地址信息。

8.3.14 用数据报进行广播通讯

DatagramSocket只允许数据报发送一个目的地址,java.net包中提供了一个类MulticastSocket,允许数据报以广播方式发送到该端口的所有客户。MulticastSocket用在客户端,监听服务器广播来的数据。

1. 客户方程序:MulticastClient.java
  import java.io.*;
  import java.net.*;
  import java.util.*;
  public class MulticastClient {
    public static void main(String args[]) throws IOException
    {
     MulticastSocket socket=new MulticastSocket(4446);
     
//创建4446端口的广播套接字
     InetAddress address=InetAddress.getByName("230.0.0.1");
     
//得到230.0.0.1的地址信息
     socket.joinGroup(address);
     
//使用joinGroup()将广播套接字绑定到地址上
     DatagramPacket packet;

     for(int i=0;i<5;i++) {
       byte[] buf=new byte[256];
       
//创建缓冲区

       packet=new DatagramPacket(buf,buf.length);
       
//创建接收数据报
       socket.receive(packet);
//接收
       String received=new String(packet.getData());
       
//由接收到的数据报得到字节数组,
       
//并由此构造一个String对象
       System.out.println("Quote of theMoment:"+received);
       
//打印得到的字符串
     }
//循环5次
     socket.leaveGroup(address);
     
//把广播套接字从地址上解除绑定
     socket.close();
//关闭广播套接字
   }
 }

 2. 服务器方程序:MulticastServer.java
  public class MulticastServer{
    public static void main(String args[]) throws java.io.IOException
    {
      new MulticastServerThread().start();
      
//启动一个服务器线程
    }
  }

 3. 程序MulticastServerThread.java
  import java.io.*;
  import java.net.*;
  import java.util.*;
  public class MulticastServerThread extends QuoteServerThread
  
//从QuoteServerThread继承得到新的服务器线程类MulticastServerThread
  {
    Private long FIVE_SECOND=5000;
//定义常量,5秒钟
    public MulticastServerThread(String name) throws IOException
    {
      super("MulticastServerThread");
      
//调用父类,也就是QuoteServerThread的构造函数
    }

    public void run() //重写父类的线程主体
    {
     while(moreQuotes) {
     
//根据标志变量判断是否继续循环
      try{
        byte[] buf=new byte[256];
        
//创建缓冲区
        String dString=null;
        if(in==null) dString=new Date().toString();
        
//如果初始化的时候打开文件失败了,
        //则使用日期作为要传送的字符串

        else dString=getNextQuote();
        
//否则调用成员函数从文件中读出字符串
        buf=dString.getByte();
        
//把String转换成字节数组,以便传送send it
        InetAddress group=InetAddress.getByName("230.0.0.1");
        
//得到230.0.0.1的地址信息
        DatagramPacket packet=new DatagramPacket(buf,buf.length,group,4446);
        
//根据缓冲区,广播地址,和端口号创建DatagramPacket对象
        socket.send(packet);
//发送该Packet
        try{
          sleep((long)(Math.random()*FIVE_SECONDS));
          
//随机等待一段时间,0~5秒之间
        }catch(InterruptedException e) { }
//异常处理
      }catch(IOException e){
//异常处理
        e.printStackTrace( );
//打印错误栈

        moreQuotes=false; //置结束循环标志
      }
    }
    socket.close( );
//关闭广播套接口
   }
 }

【本讲小结】

本讲主要讲解了Java环境下的网络编程。因为TCP/IP协议是Java网络编程的基础知识,本讲开篇重点介绍了TCP/IP协议中的一些概念,TCP/IP协议本身是一个十分庞大的系统,用几个小节是不可能讲清楚的。所以我们只是联系实际,讲解了一些最基本的概念,帮助学生理解后面的相关内容。重点有一下几个概念:主机名,IP,端口,服务类型,TCPUDP
  后续的内容分为两大块,一块是以URL为主线,讲解如何通过URL类和URLConnection类访问WWW网络资源,由于使用URL十分方便直观,尽管功能不是很强,还是值得推荐的一种网络编程方法,尤其是对于初学者特别容易接受。本质上讲,URL网络编程在传输层使用的还是TCP协议。
  另一块是以Socket接口和C/S网络编程模型为主线,依次讲解了如何用Java实现基于TCPC/S结构,主要用到的类有SocketServerSocket。以及如何用Java实现基于UDPC/S结构,还讨论了一种特殊的传输方式,广播方式,这种方式是UDP所特有的,主要用到的类有DatagramSocket , DatagramPacket, MulticastSocket。这一块在Java网络编程中相对而言是最难的(尽管Java在网络编程这方面已经做的够"傻瓜"了,但是网络编程在其他环境下的却是一件极为头痛的事情,再"傻瓜"还是有一定的难度),也是功能最为强大的一部分,读者应该好好研究,领悟其中的思想。
  最后要强调的是要学好Java网络编程,Java语言,最重要的还是在于多多练习!

posted @ 2007-04-20 17:01 chenweicai 阅读(330) | 评论 (0)编辑 收藏

java中的事件机制

Java做的图形界面软件通过事件响应机制实现用户与程序的交互,原理大概是这样:

 

首先,在java控件对象(比如文本框)上添加一个监控对象,方法是one.addXXXListenner(two),这就相当于你要对某人进行监听,先要在他身上绑定一个窃听器一样,这里“one”就是你要监听的那个家伙,two就是你自己造的一个窃听器。

 

第二步就是要考虑怎样造这个窃听器了,我们首先要搞清楚它要实现的功能:它不仅能够监听one的一举一动,还能够把监听到的事件告诉系统,并让系统对这个事件做出相应的处理。Java中是通过接口实现这样的功能的。这些接口请参见jdk中java.awt.event包,里面那几个XXXListener就是(不是很多,常用的更少)。在这些接口中定义了一些方法,这些方法就是相应的事件处理方式,它们只由系统调用并且都有一个事件类的对象作为参数,这个参数正是联系发生事件主体one和操作系统的纽带。当然接口是虚的,不能产生对象的;所以想必你也猜到,上面的“窃听器”two的类型肯定是某个实现了XXXListener接口的类。

好了,现在在回顾一下这个过程:

(1)用户通过鼠标、键盘等在one对象上做动作(比如点击鼠标),

(2)这个动作被two监听到并产生事件对象e(即XXXEvent的对象),

(3)two通过事件e对象向系统打报告,

(4)系统调用two中实现的方法对事件进行处理,并向方法传送事件e。

 

 

如果你清楚了这个过程,再来细看一下这个XXXListener接口。我们必须用类来实现接口的方法(这是java基础知识吧◎),再用这个类产生two这样的对象。类实现接口必须实现接口中的每个方法,而实际上我们需要的也许只是该接口中的某一个方法(比如处理鼠标右键点击的那个),那么每个方法还得去实现一个空的,真是浪费。Sun为了让程序员省点事,在JDK中已经为我们把这些接口实现好了,这些类就是我们所谓的“适配器”(XXXAdapter),我们只需要继承(extends)这些类并重写里面我们需要的方法就OK了,所以,其实适配器就是类,只不过这些类中只有一些空的方法,别无所有。

到此,你大概明白程序员在事件处理过程中该做哪些事了吧:

(1)制造“窃听器”,即:实现事件监听接口,重写相关方法,

(2)安装窃听器,即:为监控对象添加监听器。

 

下面,我们通过程序再来仔细看看事件响应的实现方式(待续):

(以下内容参考陈刚《eclipse从入门到精通》)

1)、 匿名内部类写法

例程:

text.addMouseListener(new MouseAdapter() {

    public void mouseDoubleClick(MouseEvent e) {//鼠标双击事件的方法

         //打开一个信息框

        MessageDialog.openInformation (null,"","Hello World");

    }

});

new MouseAdapter()就是一个匿名内部类。其实就是在实现类的同时用new构造一个该类的对象,并把它作为addMouseListener的参数,它的效果和下面的一样,只不过代码比较集中。

 

(2)、命名内部类写法

public class HelloWorld {

    public static void main(String[] args) {

           ……

        Text text = new Text(shell, SWT.BORDER);

//加入鼠标事件监听器,并用下面代码所定义的内部类生成一个对象

        text.addMouseListener(new MyMouseDoubleClick());

        ……

    }

 

//定义一个名为MyMouseDoubleClick的内部类

    private static final class MyMouseDoubleClick extends MouseAdapter {

        public void mouseDoubleClick(MouseEvent e) {

            MessageDialog.openInformation(null, "", "Hello World");

        }

    }

}

 

(3)、外部类写法

这种写法和命名内部类有些相似,只不过是将MyMouseDoubleClick类从HelloWorld.java中拿出去,单独写成一个类文件。实现代码如下

//文件1: HelloWorld.java

public class HelloWorld {

    public static void main(String[] args) {

              ……

        Text text = new Text(shell, SWT.BORDER);

//加入鼠标事件监听器,并用下面代码所定义的内部类生成一个对象

        text.addMouseListener(new MyMouseDoubleClick());

        ……

    }

}

 

//文件2:MyMouseDoubleClick.java

public class MyMouseDoubleClick extends MouseAdapter {

    public void mouseDoubleClick(MouseEvent e) {

        MessageDialog.openInformation(null, "", "Hello World");

    }

}

(4)、实现监听接口的写法

将HelloWorld类实现MouseListener接口,这样类本身就成了一个监听器,使得加入监听器的代码可以更简洁。这种方式适合加入监听器的组件较多,且要求监听器的事件处理代码可以被组件共用。这种方式还有一个要注意的地方:事件方法和其他方法混合在了一起,容易引起误读,所以应该在事件方法前加入详细的注释说明。

实现MouseListener接口要写的事件方法多一些,当然没用的事件方法可以空实现。如果继承MouseListener接口的适配器MouseAdapter,则只写需要的方法就行了。另外要注意:只有接口才能有多继承的特性,所以如果HelloWorld已经是某个类的子类,就只能用实现接口的方式,而不能继承接口的适配器了。

给出示例代码如下:

public class HelloWorld extends MouseAdapter{//或implements MouseListener

    public static void main(String[] args) {

            ……

        Text text1 = new Text(shell, SWT.BORDER);

        Text text2 = new Text(shell, SWT.BORDER);

        text1.addMouseListener(this);

        text2.addMouseListener(this);

        ……

    }

 

    public void mouseDoubleClick(MouseEvent e) {

        MessageDialog.openInformation(null, "", "Hello World");

    }

}

posted @ 2007-04-13 09:37 chenweicai 阅读(349) | 评论 (0)编辑 收藏

java中的事件机制的参与者有3种角色:

1.event object:就是事件产生时具体的“事件”,用于listener的相应的方法之中,作为参数,一般存在与listerner的方法之中

2.event source:具体的接受事件的实体,比如说,你点击一个button,那么button就是event source,这样你必须使button对某些事件进行相应,你就需要注册特定的listener,比如说MouseEvent之中的MouseClicked方法,这是他就必须有了add方法

3.event listener:具体的对监听的事件类,当有其对应的event object产生的时候,它就调用相应的方法,进行处理。在windows程序设计里边这种相应使用callback机制来实现的

先看看jdk提供的event包:
public interface EventListener:所有事件侦听器接口必须扩展的标记接口。
public class EventObject extends Object implements Serializable

所有事件状态对象都将从其派生的根类。 所有 Event 在构造时都引用了对象 "source",在逻辑上认为该对象是最初发生有关 Event 的对象。

        在Java2处理事件时,没有采用dispatchEvent()-postEvent()-handleEvent()方式,采用了监听器类,每个事件类都有相关联的监听器接口。事件从事件源到监听者的传递是通过对目标监听者对象的Java方法调用进行的。

  对每个明确的事件的发生,都相应地定义一个明确的Java方法。这些方法都集中定义在事件监听者(EventListener)接口中,这个接口要继承 java.util.EventListener。 实现了事件监听者接口中一些或全部方法的类就是事件监听者。

  伴随着事件的发生,相应的状态通常都封装在事件状态对象中,该对象必须继承自java.util.EventObject。事件状态对象作为单参传递给应响应该事件的监听者方法中。发出某种特定事件的事件源的标识是:遵从规定的设计格式为事件监听者定义注册方法,并接受对指定事件监听者接口实例的引用。

开始之前首先问个问题:您熟悉java.util.EventObject 和java.util.EventListener两个类以及他们已有的子类吗?

如果你已经能够熟练使用jdk为我们提供的事件监听器,并且很熟悉MouseEvent, KeyEvent, WindowEvent等等这些jdk为我们准备好的事件,那么想必你对java的事件机制已经有所理解。但是也许你还是觉得虽然用起来没什么问题,但是原理还是有些糊涂,那么下面我们再进一步自己实现这些事件和监听器,我们把这个取名为自定义事件。

其实自定义事件在java中很有用处,我们有的时候想让自己的程序产生一个事件,但有不希望(或者不可能)用鼠标,键盘之类的输入设备进行操作,比如你写一个应用程序,在这个程序中一旦收到邮件就对邮件进行相关处理,对于“收到邮件”这个事件,jdk中就没有定义。对于这样的事件,以及对于这样的事件的监听器,我们只能自己动手完成了。

那么下面就以实例开始我们这个“创新”的过程:首先,我们要明确jdk中需要的资源:类EventObject作为父类用来生成我们自己的事件类,接口EventListener用来实现我们自己的监听器;剩下的事情就是如何注册这些事件以及测试他们了。

(1)       通过DemoEvent.java文件创建DemoEvent类,这个类继承EventObject。这个类的构造函数的参数传递了产生这个事件的事件源(比如各种控件),方法getSource用来获得这个事件源的引用。

DemoEvent.java

package demo.listener;

 

import java.util.EventObject;

 

public class DemoEvent extends EventObject

{

        Object obj;

        public DemoEvent(Object source)

        {

               super(source);

               obj = source;

        }

        public Object getSource()

        {

               return obj;

        }

        public void say()

        {

               System.out.println("This is say method...");

        }

}

 

(2)       定义新的事件监听接口,该接口继承自EventListener;该接口包含对DemeEvent事件的处理程序:

DemoListener.java

package demo.listener;

 

import java.util.EventListener;

 

public interface DemoListener extends EventListener

{

       public void demoEvent(DemoEvent dm);

}

 

通过上面的接口我们再定义事件监听类,这些类具体实现了监听功能和事件处理功能。回想一下上文中那四种实现方式,我们这里不正是使用了其中的第三种——外部类写法的方式吗?

Listener1.java

package demo.listener;

 

public class Listener1 implements DemoListener

{

       public void demoEvent(DemoEvent de)

       {

              System.out.println("Inside listener1...");

       }

}



Listener2.java

package demo.listener;

 

public class Listener2 implements DemoListener

{

       public void demoEvent(DemoEvent de)

       {

              System.out.println("Inside listener2...");

       }

}


Listener3.java

package demo.listener;

 

public class Listener3 implements DemoListener

{

       public void demoEvent(DemoEvent de)

       {

              System.out.println("Inside listener3...");

       }

}

 

(3)       通过DemeSource..ava文件创造一个事件源类,它用一个java.utile.Vector对象来存储所有的事件监听器对象,存储方式是通过addListener(..)这样的方法。notifyDemeEvent(..)是触发事件的方法,用来通知系统:事件发生了,你调用相应的处理函数(回调函数)吧。

DemoSource.java

 

package demo.listener;

import java.util.*;

 

public class DemoSource

{

       private Vector repository = new Vector();

       DemoListener dl;

       public DemoSource()

       {

 

       }

       public void addDemoListener(DemoListener dl)

       {

              repository.addElement(dl);

       }

       public void notifyDemoEvent()

       {

              Enumeration enum = repository.elements();

              while(enum.hasMoreElements())

              {

                    dl = (DemoListener)enum.nextElement();

                    dl.demoEvent(new DemoEvent(this));

              }

       }

}

 

 

             

(4)       好了,最后写一个测试程序测试一下我们自定义的事件吧,这段程序应该不难理解吧:)

TestDemo.java

 

package demo.listener;

 

public class TestDemo

{

       DemoSource ds;

 

       public TestDemo()

       {

              try{

                    ds = new DemoSource();

                    Listener1 l1 = new Listener1();

                    Listener2 l2 = new Listener2();

                    Listener3 l3 = new Listener3();

 

                    ds.addDemoListener(l1);

                    ds.addDemoListener(l2);

                    ds.addDemoListener(l3);

                    ds.addDemoListener(new DemoListener(){
                               public void demoEvent(DemoEvent event){
                                         System.out.println("Method come from 匿名类...");
                               }
                       });

                    ds.notifyDemoEvent();

 

              }catch(Exception ex)

              {ex.printStackTrace();}

       }

 

       public static void main(String args[])

       {

              new TestDemo();

       }

}


posted @ 2007-04-13 09:22 chenweicai 阅读(10328) | 评论 (14)编辑 收藏

1.
import java.net.InetAddress;
import java.net.UnknownHostException;

public class NetWork {

 //static fields and static block
 
    //InetAddress of the local host
 private static InetAddress local = null;
 
 //integer version of the local host
 private static int packedLocal = 0;
 
 static {
  try{
   local = InetAddress.getLocalHost();
  }catch(UnknownHostException e){
   e.printStackTrace();
  }

  System.out.println("localhost : " + local);
  packedLocal = translate(local);
 }
 
 public static InetAddress getLocalHost(){
  return local;
 }
 
 public static String getLocalHostName(){
  return local.getHostName();
 }
 
 public static String getLocalMachineName(){
  String hostname = local.getHostName();
  int machEndIndex = hostname.indexOf('.');
  if(machEndIndex == -1)
   return hostname;
  else
   return hostname.substring(0, machEndIndex);
 }
 
 public static boolean isLocal(InetAddress address){
  return local.equals(address);
 }
 
 public static boolean isLocal(int packedAddress){
  return packedAddress == packedLocal;
 }
 
 /**
  * return an integer representation of the specified
  * InetAddress. This is used for efficiency
  */
 public static int translate(InetAddress address){
  byte[] ad = address.getAddress();//原始IP地址
  if(ad.length != 4)
   throw new IllegalArgumentException("only accept 32-byte IP address");
  int packedAddress = ((ad[0] << 24) & 0xFF000000) |
       ((ad[1] << 16) & 0xFF0000) |
       ((ad[2] <<  8) & 0xFF00) |
       (ad[3] & 0xFF);
  return packedAddress;
 }
 
 /**
  * return an InetAddress representation of the specified integer.
  * This is used for performance improvements in serivalization.
  */
 public static InetAddress translate (int packed)
  throws UnknownHostException{
  int[] ad = new int[4];
     ad[0] = (packed  >>> 24);
     ad[1] = ((packed >>> 16) & 0x000000FF);
     ad[2] = ((packed >>>  8) & 0x000000FF);
     ad[3] = (packed          & 0x000000FF);
     StringBuffer buf = new StringBuffer();
     buf.append(ad[0]);
     buf.append(".");
     buf.append(ad[1]);
     buf.append(".");
     buf.append(ad[2]);
     buf.append(".");
     buf.append(ad[3]);
    
     return InetAddress.getByName(buf.toString());
 }
 
 public static String translateToString(int packed){
  int[] ad = new int[4];
  ad[0] = (packed >>> 24);
  ad[1] = ((packed >>> 16) & 0x000000FF);
  ad[2] = ((packed >>> 8) & 0x000000FF);
  ad[3] = (packed & 0x000000FF);
  
  StringBuffer buf = new StringBuffer();
  buf.append(ad[0]);
  buf.append(".");
  buf.append(ad[1]);
  buf.append(".");
  buf.append(ad[2]);
  buf.append(".");
  buf.append(ad[3]);
  
  return buf.toString();
 }
 
 public static void main(String[] args){
  InetAddress localhost = getLocalHost();
  System.out.println("Local Host Name is : " + getLocalHostName());
  System.out.println("Local Host Machine Name is : " + getLocalMachineName());
  if(isLocal(localhost))
   System.out.println(localhost.getHostName() + " is Local Host.");
  int intforaddress = translate(localhost);
  System.out.println("localhost integer representation is " + intforaddress);
  System.out.println("localhost string representation is " + translateToString(intforaddress));
  System.out.println(translateToString(intforaddress).toString());
  
 }
}



2. public interfece Externalizable extendx Serializable
Externalizable 实例类的惟一特性是可以被写入序列化流中,该类负责保存和恢复实例内容。 若某个要完全控制某一对象及其超类型的流格式和内容,则它要实现 Externalizable 接口的 writeExternal 和 readExternal 方法。这些方法必须显式与超类型进行协调以保存其状态。这些方法将代替自定义的 writeObject 和 readObject 方法实现。
Serialization 对象将使用 Serializable 和 Externalizable 接口。对象持久性机制也可以使用它们。要存储的每个对象都需要检测是否支持 Externalizable 接口。如果对象支持 Externalizable,则调用 writeExternal 方法。如果对象不支持 Externalizable 但实现了 Serializable,则使用 ObjectOutputStream 保存该对象。
在重构 Externalizable 对象时,先使用无参数的公共构造方法创建一个实例,然后调用 readExternal 方法。通过从 ObjectInputStream 中读取 Serializable 对象可以恢复这些对象。
Externalizable 实例可以通过 Serializable 接口中记录的 writeReplace 和 readResolve 方法来指派一个替代对象。

writeExternal

void writeExternal(ObjectOutput out)
throws IOException
该对象可实现 writeExternal 方法来保存其内容,它可以通过调用 DataOutput 的方法来保存其基本值,或调用 ObjectOutput 的 writeObject 方法来保存对象、字符串和数组。

 

参数:
out - 要写入对象的流
抛出:
IOException - 包含可能发生的所有 I/O 异常

readExternal

void readExternal(ObjectInput in)
throws IOException,
ClassNotFoundException
对象实现 readExternal 方法来恢复其内容,它通过调用 DataInput 的方法来恢复其基础类型,调用 readObject 来恢复对象、字符串和数组。readExternal 方法必须按照与 writeExternal 方法写入值时使用的相同顺序和类型来读取这些值。

 

参数:
in - 为了恢复对象而从中读取数据的流
抛出:
IOException - 如果发生 I/O 错误
ClassNotFoundException - 如果无法找到需要恢复的某个对象的类。

import java.io.Externalizable;
import java.net.InetAddress;

public interface EndPoint extends Externalizable {

 /**
  *
  * @return Returns the IP address contained in this endpoint.
  */
 public InetAddress getAddress();
 
 /**
  * returns the IP address contained in this endpoint.
  * enclosed in an integer value.
  */
 public int getIntAddress();
 
 /**
  * Returns the port number contained in this endpoint.
  */
 public int getPort();
 
 
 /**
  * Return true if this endpoint is the local endpoint.
  */
 public boolean isLocal();
 
 /**
  * Returns true if this endpoint is a multicast endpoint.
  */
 public boolean isMulticastEndPoint();
}



import internet.network.NetWork;

import java.io.Externalizable;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;

public class EndPointImpl implements EndPoint, Comparable, Externalizable{

 //Constants
 
 /* Size of this object (in bytes) when marshalled. */
 public static final int SIZE = 6;
 
 //Fields
 protected InetAddress address;
 
 private int packedAddress;
 
 private int port;
 
 protected boolean local;
 
 //Constructors
 
 /**
  * Default constructor for externalization
  */
 public EndPointImpl(){
  
 }
 
 public EndPointImpl(InetAddress address, int port){
  this.address = address;
  this.port = port;
  packedAddress = NetWork.translate(address);
  local = NetWork.isLocal(packedAddress);
 }
 
 public EndPointImpl(int packedAddress, int port){
  this.packedAddress = packedAddress;
  this.port = port;
  this.local = NetWork.isLocal(packedAddress);
  //The InetAddress version of the address is computed
  //only when needed, to avoid uselesss DNS lookups.
  address = null;
 }
 
 public InetAddress getAddress() {
  if(address == null)
   try{
    address = NetWork.translate(packedAddress);
   }catch(UnknownHostException e){
    System.out.println(e.getMessage());
   }
   return address;
 }

 public int getIntAddress() {
  return this.packedAddress;
 }

 public int getPort() {
  return this.port;
 }

 public boolean isLocal() {
  return this.local;
 }

 public boolean isMulticastEndPoint() {
  return address.isMulticastAddress();
 }
 
 
 ////////////////////////////////////////////////////////////
 // Methods from Externalizable
 ////////////////////////////////////////////////////////////

 /**
  * Restores the content of this object form the marshaled data contained
  * in the specified input stream.
  *
  * @param in the stream to be read.
  */
 public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
  
  packedAddress = in.readInt();
  port = (int) in.readUnsignedShort();
  local = NetWork.isLocal(packedAddress);
  address = null;
 }

 /**
  * Marshals the content of this object to the specified output stream.
  *
  * @param out the stream to be written
  */
 public void writeExternal(ObjectOutput out) throws IOException {
  
  out.writeInt(packedAddress);
  out.writeShort(port);
 }

 
 ////////////////////////////////////////////////////////////
 // Methods from Object and Comparable
 ////////////////////////////////////////////////////////////
 /**
  *  Compares this object with the specified object for order.
  */
 public int compareTo(Object obj) {
  if(this == obj){
   return 0;
  }
  else{
   EndPointImpl endpoint = (EndPointImpl) obj;
   if(packedAddress < endpoint.packedAddress){
    return -1;
   }
   else if(packedAddress == endpoint.packedAddress){
    if(port < endpoint.port)
     return -1;
    else if(port == endpoint.port)
     return 0;
    else
     return 1;
   }
   else{
    return 1;
   }
  }
 }
 
 public int hashcode(){
  return packedAddress ^ port;
 }

 /**
  * Compares two EndPointImpl objects for content equality.
  */
 public boolean equals (Object obj){
  if(!(obj instanceof EndPointImpl))
   return false;
  EndPointImpl endpoint = (EndPointImpl) obj;
  return (endpoint.packedAddress == packedAddress && endpoint.port == port);
 }
 
 /**
  * Returns a string representation of this object.
  */
 public String toString(){
  StringBuffer buf = new StringBuffer();
  buf.append("[");
  buf.append(NetWork.translateToString(packedAddress));
  buf.append(":");
  buf.append(port);
  buf.append("]");
  
  return buf.toString();
 }
 
 
 public static void main(String[] args) throws FileNotFoundException, IOException{
  InetAddress localhost = NetWork.getLocalHost();
  EndPointImpl endpoint = new EndPointImpl(localhost, 100);
  System.out.println("This EndPoint is : " + endpoint.toString());
  EndPointImpl endpoint2 = new EndPointImpl();
  ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("endpoint.txt"));
  try {
   endpoint.writeExternal(oos);
  } catch (IOException e) {
   e.printStackTrace();
  }
  oos.close();
  
  ObjectInputStream ios = new ObjectInputStream(new FileInputStream("endpoint.txt"));
  try {
   endpoint2.readExternal(ios);
  } catch (IOException e) {
   e.printStackTrace();
  } catch (ClassNotFoundException e) {
   e.printStackTrace();
  }
  ios.close();
  System.out.println("This EndPoint is : " + endpoint2.toString());
 }
}

posted @ 2007-04-11 21:41 chenweicai 阅读(161) | 评论 (0)编辑 收藏

类Deflater 和 类Inflater:此类使用流行的 ZLIB 压缩程序库为通用压缩提供支持。ZLIB 压缩程序库最初是作为 PNG 图形标准的一部分开发的,不受专利的保护。

  //encoding a string into bytes
  String inputString = "chenweicai";
  byte[] input = inputString.getBytes("UTF-8");
  System.out.println("before compressed, the bytes length is :" + input.length);
  
  //compress the bytes
  byte[] output = new byte[100];
  Deflater compresser = new Deflater();
  compresser.setInput(input);
  compresser.finish();
  int compressedDataLength = compresser.deflate(output);
  System.out.println("compressed bytes length is :" + compressedDataLength);
  
  //decompress the bytes
  Inflater decompresser = new Inflater();
  decompresser.setInput(output, 0, compressedDataLength);
  byte[] result = new byte[100];
  int resultLength = decompresser.inflate(result);
  decompresser.end();
  System.out.println("after decompressed, the bytes length is :" + resultLength);
  
  //decode the bytes into a string
  String outputString = new String(result, 0, resultLength, "UTF-8");
  System.out.println("after compress and decompress the string is :" + outputString);


2.


import java.io.Serializable;

public class Employee implements Serializable {

 private String name;
 
 private double salary;

 public Employee(String name, double salary) {
  super();
  // TODO Auto-generated constructor stub
  this.name = name;
  this.salary = salary;
 }
 
 public void raiseSalary(double byPercent){
  double temp = salary * byPercent / 100;
  salary += temp;
 }

 public String toString() {
  // TODO Auto-generated method stub
  return getClass().getName() +
   "[ Name = " + name + ", salary = " + salary +"]";
 }
 
}





public class Test2 {

 protected byte[] deflate(Object object) throws IOException{
  
  ByteArrayOutputStream baos = new ByteArrayOutputStream();
  Deflater def = new Deflater (Deflater.BEST_COMPRESSION);
  DeflaterOutputStream dos = new DeflaterOutputStream(baos, def);
  
  ObjectOutputStream out = new ObjectOutputStream(dos);
  out.writeObject(object);
  out.close();
  dos.finish();
  dos.close();
  
  return baos.toByteArray();
 }
 
 protected Object inflate(byte[] compressedContent) throws IOException{
  if(compressedContent == null)
   return null;
  
  try{
   ObjectInputStream in = new ObjectInputStream(
     new InflaterInputStream(new ByteArrayInputStream(compressedContent)));
   Object object = in.readObject();
   in.close();
   return object;
  }catch(Exception e){
   throw new IOException(e.toString());
  }
 }
 
 public static void main(String[] args) throws IOException{
  Employee employee = new Employee("LiLei", 1000);
  Test2 test2 = new Test2();
  byte[] bytes = new byte[1000];
  
  bytes = test2.deflate(employee);
  System.out.println(bytes);
  
  System.out.println(test2.inflate(bytes));
  
 }
}

posted @ 2007-04-10 21:29 chenweicai 阅读(590) | 评论 (0)编辑 收藏

  JDK为程序员提供了大量的类库,而为了保持类库的可重用性,可扩展性和灵活性,其中使用到了大量的设计模式,本文将介绍JDK的I/O包中使用到的Decorator模式,并运用此模式,实现一个新的输出流类。

  Decorator模式简介

  Decorator模式又名包装器(Wrapper),它的主要用途在于给一个对象动态的添加一些额外的职责。与生成子类相比,它更具有灵活性。
有时候,我们需要为一个对象而不是整个类添加一些新的功能,比如,给一个文本区添加一个滚动条的功能。我们可以使用继承机制来实现这一功能,但是这种方法不够灵活,我们无法控制文本区加滚动条的方式和时机。而且当文本区需要添加更多的功能时,比如边框等,需要创建新的类,而当需要组合使用这些功能时无疑将会引起类的爆炸。

  我们可以使用一种更为灵活的方法,就是把文本区嵌入到滚动条中。而这个滚动条的类就相当于对文本区的一个装饰。这个装饰(滚动条)必须与被装饰的组件(文本区)继承自同一个接口,这样,用户就不必关心装饰的实现,因为这对他们来说是透明的。装饰会将用户的请求转发给相应的组件(即调用相关的方法),并可能在转发的前后做一些额外的动作(如添加滚动条)。通过这种方法,我们可以根据组合对文本区嵌套不同的装饰,从而添加任意多的功能。这种动态的对对象添加功能的方法不会引起类的爆炸,也具有了更多的灵活性。

  以上的方法就是Decorator模式,它通过给对象添加装饰来动态的添加新的功能。如下是Decorator模式的UML图:



  Component为组件和装饰的公共父类,它定义了子类必须实现的方法。

  ConcreteComponent是一个具体的组件类,可以通过给它添加装饰来增加新的功能。

  Decorator是所有装饰的公共父类,它定义了所有装饰必须实现的方法,同时,它还保存了一个对于Component的引用,以便将用户的请求转发给Component,并可能在转发请求前后执行一些附加的动作。

  ConcreteDecoratorA和ConcreteDecoratorB是具体的装饰,可以使用它们来装饰具体的Component。

  Java IO包中的Decorator模式

  JDK提供的java.io包中使用了Decorator模式来实现对各种输入输出流的封装。以下将以java.io.OutputStream及其子类为例,讨论一下Decorator模式在IO中的使用。

  首先来看一段用来创建IO流的代码:

以下是代码片段:
try {
 OutputStream out = new DataOutputStream(new FileOutputStream("test.txt"));
} catch (FileNotFoundException e) {
 e.printStackTrace();
}

  这段代码对于使用过JAVA输入输出流的人来说再熟悉不过了,我们使用DataOutputStream封装了一个FileOutputStream。这是一个典型的Decorator模式的使用,FileOutputStream相当于Component,DataOutputStream就是一个Decorator。将代码改成如下,将会更容易理解:

以下是代码片段:
try {
 OutputStream out = new FileOutputStream("test.txt");
 out = new DataOutputStream(out);
} catch(FileNotFoundException e) {
 e.printStatckTrace();
}

  由于FileOutputStream和DataOutputStream有公共的父类OutputStream,因此对对象的装饰对于用户来说几乎是透明的。下面就来看看OutputStream及其子类是如何构成Decorator模式的:



  OutputStream是一个抽象类,它是所有输出流的公共父类,其源代码如下:

以下是代码片段:
public abstract class OutputStream implements Closeable, Flushable {
 public abstract void write(int b) throws IOException;
 ...
}

  它定义了write(int b)的抽象方法。这相当于Decorator模式中的Component类。

  ByteArrayOutputStream,FileOutputStream 和 PipedOutputStream 三个类都直接从OutputStream继承,以ByteArrayOutputStream为例:

以下是代码片段:
public class ByteArrayOutputStream extends OutputStream {
 protected byte buf[];
 protected int count;
 public ByteArrayOutputStream() {
  this(32);
 }
 public ByteArrayOutputStream(int size) {
  if (size 〈 0) {
   throw new IllegalArgumentException("Negative initial size: " + size);
  }
  buf = new byte[size];
 }
 public synchronized void write(int b) {
  int newcount = count + 1;
  if (newcount 〉 buf.length) {
   byte newbuf[] = new byte[Math.max(buf.length 〈〈 1, newcount)];
   System.arraycopy(buf, 0, newbuf, 0, count);
   buf = newbuf;
  }
  buf[count] = (byte)b;
  count = newcount;
 }
 ...
}

  它实现了OutputStream中的write(int b)方法,因此我们可以用来创建输出流的对象,并完成特定格式的输出。它相当于Decorator模式中的ConcreteComponent类。

  接着来看一下FilterOutputStream,代码如下:

以下是代码片段:
public class FilterOutputStream extends OutputStream {
 protected OutputStream out;
 public FilterOutputStream(OutputStream out) {
  this.out = out;
 }
 public void write(int b) throws IOException {
  out.write(b);
 }
 ...
}

  同样,它也是从OutputStream继承。但是,它的构造函数很特别,需要传递一个OutputStream的引用给它,并且它将保存对此对象的引用。而如果没有具体的OutputStream对象存在,我们将无法创建FilterOutputStream。由于out既可以是指向FilterOutputStream类型的引用,也可以是指向ByteArrayOutputStream等具体输出流类的引用,因此使用多层嵌套的方式,我们可以为ByteArrayOutputStream添加多种装饰。这个FilterOutputStream类相当于Decorator模式中的Decorator类,它的write(int b)方法只是简单的调用了传入的流的write(int b)方法,而没有做更多的处理,因此它本质上没有对流进行装饰,所以继承它的子类必须覆盖此方法,以达到装饰的目的。

  BufferedOutputStream 和 DataOutputStream是FilterOutputStream的两个子类,它们相当于Decorator模式中的ConcreteDecorator,并对传入的输出流做了不同的装饰。以BufferedOutputStream类为例:

以下是代码片段:
public class BufferedOutputStream extends FilterOutputStream {
 ...
 private void flushBuffer() throws IOException {
  if (count 〉 0) {
   out.write(buf, 0, count);
   count = 0;
  }
 }
 public synchronized void write(int b) throws IOException {
  if (count 〉= buf.length) {
   flushBuffer();
  }
  buf[count++] = (byte)b;
 }
 ...
}

  这个类提供了一个缓存机制,等到缓存的容量达到一定的字节数时才写入输出流。首先它继承了FilterOutputStream,并且覆盖了父类的write(int b)方法,在调用输出流写出数据前都会检查缓存是否已满,如果未满,则不写。这样就实现了对输出流对象动态的添加新功能的目的。

  下面,将使用Decorator模式,为IO写一个新的输出流。  JDK为程序员提供了大量的类库,而为了保持类库的可重用性,可扩展性和灵活性,其中使用到了大量的设计模式,本文将介绍JDK的I/O包中使用到的Decorator模式,并运用此模式,实现一个新的输出流类。

  Decorator模式简介

  Decorator模式又名包装器(Wrapper),它的主要用途在于给一个对象动态的添加一些额外的职责。与生成子类相比,它更具有灵活性。
有时候,我们需要为一个对象而不是整个类添加一些新的功能,比如,给一个文本区添加一个滚动条的功能。我们可以使用继承机制来实现这一功能,但是这种方法不够灵活,我们无法控制文本区加滚动条的方式和时机。而且当文本区需要添加更多的功能时,比如边框等,需要创建新的类,而当需要组合使用这些功能时无疑将会引起类的爆炸。

  我们可以使用一种更为灵活的方法,就是把文本区嵌入到滚动条中。而这个滚动条的类就相当于对文本区的一个装饰。这个装饰(滚动条)必须与被装饰的组件(文本区)继承自同一个接口,这样,用户就不必关心装饰的实现,因为这对他们来说是透明的。装饰会将用户的请求转发给相应的组件(即调用相关的方法),并可能在转发的前后做一些额外的动作(如添加滚动条)。通过这种方法,我们可以根据组合对文本区嵌套不同的装饰,从而添加任意多的功能。这种动态的对对象添加功能的方法不会引起类的爆炸,也具有了更多的灵活性。

  以上的方法就是Decorator模式,它通过给对象添加装饰来动态的添加新的功能。如下是Decorator模式的UML图:



  Component为组件和装饰的公共父类,它定义了子类必须实现的方法。

  ConcreteComponent是一个具体的组件类,可以通过给它添加装饰来增加新的功能。

  Decorator是所有装饰的公共父类,它定义了所有装饰必须实现的方法,同时,它还保存了一个对于Component的引用,以便将用户的请求转发给Component,并可能在转发请求前后执行一些附加的动作。

  ConcreteDecoratorA和ConcreteDecoratorB是具体的装饰,可以使用它们来装饰具体的Component。

  Java IO包中的Decorator模式

  JDK提供的java.io包中使用了Decorator模式来实现对各种输入输出流的封装。以下将以java.io.OutputStream及其子类为例,讨论一下Decorator模式在IO中的使用。

  首先来看一段用来创建IO流的代码:

以下是代码片段:
try {
 OutputStream out = new DataOutputStream(new FileOutputStream("test.txt"));
} catch (FileNotFoundException e) {
 e.printStackTrace();
}

  这段代码对于使用过JAVA输入输出流的人来说再熟悉不过了,我们使用DataOutputStream封装了一个FileOutputStream。这是一个典型的Decorator模式的使用,FileOutputStream相当于Component,DataOutputStream就是一个Decorator。将代码改成如下,将会更容易理解:

以下是代码片段:
try {
 OutputStream out = new FileOutputStream("test.txt");
 out = new DataOutputStream(out);
} catch(FileNotFoundException e) {
 e.printStatckTrace();
}

  由于FileOutputStream和DataOutputStream有公共的父类OutputStream,因此对对象的装饰对于用户来说几乎是透明的。下面就来看看OutputStream及其子类是如何构成Decorator模式的:



  OutputStream是一个抽象类,它是所有输出流的公共父类,其源代码如下:

以下是代码片段:
public abstract class OutputStream implements Closeable, Flushable {
 public abstract void write(int b) throws IOException;
 ...
}

  它定义了write(int b)的抽象方法。这相当于Decorator模式中的Component类。

  ByteArrayOutputStream,FileOutputStream 和 PipedOutputStream 三个类都直接从OutputStream继承,以ByteArrayOutputStream为例:

以下是代码片段:
public class ByteArrayOutputStream extends OutputStream {
 protected byte buf[];
 protected int count;
 public ByteArrayOutputStream() {
  this(32);
 }
 public ByteArrayOutputStream(int size) {
  if (size 〈 0) {
   throw new IllegalArgumentException("Negative initial size: " + size);
  }
  buf = new byte[size];
 }
 public synchronized void write(int b) {
  int newcount = count + 1;
  if (newcount 〉 buf.length) {
   byte newbuf[] = new byte[Math.max(buf.length 〈〈 1, newcount)];
   System.arraycopy(buf, 0, newbuf, 0, count);
   buf = newbuf;
  }
  buf[count] = (byte)b;
  count = newcount;
 }
 ...
}

  它实现了OutputStream中的write(int b)方法,因此我们可以用来创建输出流的对象,并完成特定格式的输出。它相当于Decorator模式中的ConcreteComponent类。

  接着来看一下FilterOutputStream,代码如下:

以下是代码片段:
public class FilterOutputStream extends OutputStream {
 protected OutputStream out;
 public FilterOutputStream(OutputStream out) {
  this.out = out;
 }
 public void write(int b) throws IOException {
  out.write(b);
 }
 ...
}

  同样,它也是从OutputStream继承。但是,它的构造函数很特别,需要传递一个OutputStream的引用给它,并且它将保存对此对象的引用。而如果没有具体的OutputStream对象存在,我们将无法创建FilterOutputStream。由于out既可以是指向FilterOutputStream类型的引用,也可以是指向ByteArrayOutputStream等具体输出流类的引用,因此使用多层嵌套的方式,我们可以为ByteArrayOutputStream添加多种装饰。这个FilterOutputStream类相当于Decorator模式中的Decorator类,它的write(int b)方法只是简单的调用了传入的流的write(int b)方法,而没有做更多的处理,因此它本质上没有对流进行装饰,所以继承它的子类必须覆盖此方法,以达到装饰的目的。

  BufferedOutputStream 和 DataOutputStream是FilterOutputStream的两个子类,它们相当于Decorator模式中的ConcreteDecorator,并对传入的输出流做了不同的装饰。以BufferedOutputStream类为例:

以下是代码片段:
public class BufferedOutputStream extends FilterOutputStream {
 ...
 private void flushBuffer() throws IOException {
  if (count 〉 0) {
   out.write(buf, 0, count);
   count = 0;
  }
 }
 public synchronized void write(int b) throws IOException {
  if (count 〉= buf.length) {
   flushBuffer();
  }
  buf[count++] = (byte)b;
 }
 ...
}

  这个类提供了一个缓存机制,等到缓存的容量达到一定的字节数时才写入输出流。首先它继承了FilterOutputStream,并且覆盖了父类的write(int b)方法,在调用输出流写出数据前都会检查缓存是否已满,如果未满,则不写。这样就实现了对输出流对象动态的添加新功能的目的。

  下面,将使用Decorator模式,为IO写一个新的输出流。


自己写一个新的输出流

  了解了OutputStream及其子类的结构原理后,我们可以写一个新的输出流,来添加新的功能。这部分中将给出一个新的输出流的例子,它将过滤待输出语句中的空格符号。比如需要输出"java io OutputStream",则过滤后的输出为"javaioOutputStream"。以下为SkipSpaceOutputStream类的代码:

以下是代码片段:
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
* A new output stream, which will check the space character
* and won’t write it to the output stream.
* @author Magic
*
*/
public class SkipSpaceOutputStream extends FilterOutputStream {
 public SkipSpaceOutputStream(OutputStream out) {
  super(out);
 }
 /**
 * Rewrite the method in the parent class, and
 * skip the space character.
 */
 public void write(int b) throws IOException{
  if(b!=’ ’){
   super.write(b);
  }
 }
}

  它从FilterOutputStream继承,并且重写了它的write(int b)方法。在write(int b)方法中首先对输入字符进行了检查,如果不是空格,则输出。

  以下是一个测试程序:

以下是代码片段:
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* Test the SkipSpaceOutputStream.
* @author Magic
*
*/
public class Test {
 public static void main(String[] args){
  byte[] buffer = new byte[1024];

  /**
  * Create input stream from the standard input.
  */
  InputStream in = new BufferedInputStream(new DataInputStream(System.in));

  /**
  * write to the standard output.
  */
  OutputStream out = new SkipSpaceOutputStream(new DataOutputStream(System.out));

  try {
   System.out.println("Please input your words: ");
   int n = in.read(buffer,0,buffer.length);
   for(int i=0;i〈n;i++){
    out.write(buffer[i]);
   }
  } catch (IOException e) {
   e.printStackTrace();
  }
 }
}

  执行以上测试程序,将要求用户在console窗口中输入信息,程序将过滤掉信息中的空格,并将最后的结果输出到console窗口。比如:

以下是引用片段:
Please input your words:
a b c d e f
abcdef

  总 结

  在java.io包中,不仅OutputStream用到了Decorator设计模式,InputStream,Reader,Writer等都用到了此模式。而作为一个灵活的,可扩展的类库,JDK中使用了大量的设计模式,比如在Swing包中的MVC模式,RMI中的Proxy模式等等。对于JDK中模式的研究不仅能加深对于模式的理解,而且还有利于更透彻的了解类库的结构和组成。

posted @ 2007-04-10 09:44 chenweicai 阅读(114) | 评论 (0)编辑 收藏

synchronized的一个简单例子

public class TextThread
{

 /**
  * @param args
  */
 public static void main(String[] args)
 {
  // TODO 自动生成方法存根
        TxtThread tt = new TxtThread();
        new Thread(tt).start();
        new Thread(tt).start();
        new Thread(tt).start();
        new Thread(tt).start();
 }

}
class TxtThread implements Runnable
{
 int num = 100;
 String str = new String();
 public void run()
 {
  while (true)
  {
   synchronized(str)
   {
   if (num>0)
   {
    try
    {
     Thread.sleep(10);
    }
    catch(Exception e)
    {
     e.getMessage();
    }
    System.out.println(Thread.currentThread().getName()+ "this is "+ num--);
   }
   }
  }
 }
}

上面的例子中为了制造一个时间差,也就是出错的机会,使用了Thread.sleep(10)


Java对多线程的支持与同步机制深受大家的喜爱,似乎看起来使用了synchronized关键字就可以轻松地解决多线程共享数据同步问题。到底如何?――还得对synchronized关键字的作用进行深入了解才可定论。

总的说来,synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。如果再细的分类,synchronized可作用于instance变量、object reference(对象引用)、static函数和class literals(类名称字面常量)身上。

在进一步阐述之前,我们需要明确几点:

A.无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁――而且同步方法很可能还会被其他线程的对象访问。

B.每个对象只有一个锁(lock)与之相关联。

C.实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。

接着来讨论synchronized用到不同地方对代码产生的影响:

 

假设P1、P2是同一个类的不同对象,这个类中定义了以下几种情况的同步块或同步方法,P1、P2就都可以调用它们。

 

1.  把synchronized当作函数修饰符时,示例代码如下:

Public synchronized void methodAAA()

{

//….

}

这也就是同步方法,那这时synchronized锁定的是哪个对象呢?它锁定的是调用这个同步方法对象。也就是说,当一个对象P1在不同的线程中执行这个同步方法时,它们之间会形成互斥,达到同步的效果。但是这个对象所属的Class所产生的另一对象P2却可以任意调用这个被加了synchronized关键字的方法。

上边的示例代码等同于如下代码:

public void methodAAA()

{

synchronized (this)      //  (1)

{

       //…..

}

}

 (1)处的this指的是什么呢?它指的就是调用这个方法的对象,如P1。可见同步方法实质是将synchronized作用于object reference。――那个拿到了P1对象锁的线程,才可以调用P1的同步方法,而对P2而言,P1这个锁与它毫不相干,程序也可能在这种情形下摆脱同步机制的控制,造成数据混乱:(

2.同步块,示例代码如下:

            public void method3(SomeObject so)

              {

                     synchronized(so)

{

       //…..

}

}

这时,锁就是so这个对象,谁拿到这个锁谁就可以运行它所控制的那段代码。当有一个明确的对象作为锁时,就可以这样写程序,但当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的instance变量(它得是一个对象)来充当锁:

class Foo implements Runnable

{

       private byte[] lock = new byte[0];  // 特殊的instance变量

    Public void methodA()

{

       synchronized(lock) { //… }

}

//…..

}

注:零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。

3.将synchronized作用于static 函数,示例代码如下:

      Class Foo

{

public synchronized static void methodAAA()   // 同步的static 函数

{

//….

}

public void methodBBB()

{

       synchronized(Foo.class)   //  class literal(类名称字面常量)

}

       }

   代码中的methodBBB()方法是把class literal作为锁的情况,它和同步的static函数产生的效果是一样的,取得的锁很特别,是当前调用这个方法的对象所属的类(Class,而不再是由这个Class产生的某个具体对象了)。

记得在《Effective Java》一书中看到过将 Foo.class和 P1.getClass()用于作同步锁还不一样,不能用P1.getClass()来达到锁这个Class的目的。P1指的是由Foo类产生的对象。

可以推断:如果一个类中定义了一个synchronized的static函数A,也定义了一个synchronized 的instance函数B,那么这个类的同一对象Obj在多线程中分别访问A和B两个方法时,不会构成同步,因为它们的锁都不一样。A方法的锁是Obj这个对象,而B的锁是Obj所属的那个Class。

 

小结如下:

搞清楚synchronized锁定的是哪个对象,就能帮助我们设计更安全的多线程程序。


 

还有一些技巧可以让我们对共享资源的同步访问更加安全:

1.  定义private 的instance变量+它的 get方法,而不要定义public/protected的instance变量。如果将变量定义为public,对象在外界可以绕过同步方法的控制而直接取得它,并改动它。这也是JavaBean的标准实现方式之一。

2.  如果instance变量是一个对象,如数组或ArrayList什么的,那上述方法仍然不安全蛭蓖饨缍韵笸ü齡et方法拿到这个instance对象的引用后,又将其指向另一个对象,那么这个private变量也就变了,岂不是很危险。 这个时候就需要将get方法也加上synchronized同步,并且,只返回这个private对象的clone()――这样,调用端得到的就是对象副本的引用了。

synchronized的一个简单例子

public class TextThread
{

 /**
  * @param args
  */
 public static void main(String[] args)
 {
  // TODO 自动生成方法存根
        TxtThread tt = new TxtThread();
        new Thread(tt).start();
        new Thread(tt).start();
        new Thread(tt).start();
        new Thread(tt).start();
 }

}
class TxtThread implements Runnable
{
 int num = 100;
 String str = new String();
 public void run()
 {
  while (true)
  {
   synchronized(str)
   {
   if (num>0)
   {
    try
    {
     Thread.sleep(10);
    }
    catch(Exception e)
    {
     e.getMessage();
    }
    System.out.println(Thread.currentThread().getName()+ "this is "+ num--);
   }
   }
  }
 }
}

上面的例子中为了制造一个时间差,也就是出错的机会,使用了Thread.sleep(10)


Java对多线程的支持与同步机制深受大家的喜爱,似乎看起来使用了synchronized关键字就可以轻松地解决多线程共享数据同步问题。到底如何?――还得对synchronized关键字的作用进行深入了解才可定论。

总的说来,synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。如果再细的分类,synchronized可作用于instance变量、object reference(对象引用)、static函数和class literals(类名称字面常量)身上。

在进一步阐述之前,我们需要明确几点:

A.无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁――而且同步方法很可能还会被其他线程的对象访问。

B.每个对象只有一个锁(lock)与之相关联。

C.实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。

接着来讨论synchronized用到不同地方对代码产生的影响:

 

假设P1、P2是同一个类的不同对象,这个类中定义了以下几种情况的同步块或同步方法,P1、P2就都可以调用它们。

 

1.  把synchronized当作函数修饰符时,示例代码如下:

Public synchronized void methodAAA()

{

//….

}

这也就是同步方法,那这时synchronized锁定的是哪个对象呢?它锁定的是调用这个同步方法对象。也就是说,当一个对象P1在不同的线程中执行这个同步方法时,它们之间会形成互斥,达到同步的效果。但是这个对象所属的Class所产生的另一对象P2却可以任意调用这个被加了synchronized关键字的方法。

上边的示例代码等同于如下代码:

public void methodAAA()

{

synchronized (this)      //  (1)

{

       //…..

}

}

 (1)处的this指的是什么呢?它指的就是调用这个方法的对象,如P1。可见同步方法实质是将synchronized作用于object reference。――那个拿到了P1对象锁的线程,才可以调用P1的同步方法,而对P2而言,P1这个锁与它毫不相干,程序也可能在这种情形下摆脱同步机制的控制,造成数据混乱:(

2.同步块,示例代码如下:

            public void method3(SomeObject so)

              {

                     synchronized(so)

{

       //…..

}

}

这时,锁就是so这个对象,谁拿到这个锁谁就可以运行它所控制的那段代码。当有一个明确的对象作为锁时,就可以这样写程序,但当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的instance变量(它得是一个对象)来充当锁:

class Foo implements Runnable

{

       private byte[] lock = new byte[0];  // 特殊的instance变量

    Public void methodA()

{

       synchronized(lock) { //… }

}

//…..

}

注:零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。

3.将synchronized作用于static 函数,示例代码如下:

      Class Foo

{

public synchronized static void methodAAA()   // 同步的static 函数

{

//….

}

public void methodBBB()

{

       synchronized(Foo.class)   //  class literal(类名称字面常量)

}

       }

   代码中的methodBBB()方法是把class literal作为锁的情况,它和同步的static函数产生的效果是一样的,取得的锁很特别,是当前调用这个方法的对象所属的类(Class,而不再是由这个Class产生的某个具体对象了)。

记得在《Effective Java》一书中看到过将 Foo.class和 P1.getClass()用于作同步锁还不一样,不能用P1.getClass()来达到锁这个Class的目的。P1指的是由Foo类产生的对象。

可以推断:如果一个类中定义了一个synchronized的static函数A,也定义了一个synchronized 的instance函数B,那么这个类的同一对象Obj在多线程中分别访问A和B两个方法时,不会构成同步,因为它们的锁都不一样。A方法的锁是Obj这个对象,而B的锁是Obj所属的那个Class。

 

小结如下:

搞清楚synchronized锁定的是哪个对象,就能帮助我们设计更安全的多线程程序。


 

还有一些技巧可以让我们对共享资源的同步访问更加安全:

1.  定义private 的instance变量+它的 get方法,而不要定义public/protected的instance变量。如果将变量定义为public,对象在外界可以绕过同步方法的控制而直接取得它,并改动它。这也是JavaBean的标准实现方式之一。

2.  如果instance变量是一个对象,如数组或ArrayList什么的,那上述方法仍然不安全蛭蓖饨缍韵笸ü齡et方法拿到这个instance对象的引用后,又将其指向另一个对象,那么这个private变量也就变了,岂不是很危险。 这个时候就需要将get方法也加上synchronized同步,并且,只返回这个private对象的clone()――这样,调用端得到的就是对象副本的引用了。




1.
public class Thread1 implements Runnable{

 //当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,
 //一个时间内只能有一个线程得到执行。
 //另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块
 public void run(){
  synchronized (this){
   for(int i=0; i<5; i++){
    System.out.println(Thread.currentThread().getName() + " synchronize loop " + i);
   }
  }
 }
 
 public static void main(String[] args) {

  Thread1 thread1 = new Thread1();
  Thread thread2 = new Thread(thread1, "A");
  Thread thread3 = new Thread(thread1, "B");
  thread2.start();
  thread3.start();//thead3必须等待thread2的执行完毕就可立即执行
 }

}


2.

public class Thread2 {

 public void method1(){
  synchronized(this){
   int i = 5;
   while(i-- > 0){
    System.out.println(Thread.currentThread().getName() + " : " + i);
   
    try{
     Thread.sleep(500);
     }
    catch(InterruptedException  ie){
     ie.printStackTrace();
     }
   }
  }
 }
 
 //当一个线程访问object的一个synchronized(this)同步代码块时,
 //另一个线程仍然可以访问该object中的非synchronized(this)同步代码块
 public void method2(){
  int i = 5;
  while(i-- > 0){
   System.out.println(Thread.currentThread().getName() + " : " + i);
   
   try{
    Thread.sleep(500);
   }catch(InterruptedException ie){
    ie.printStackTrace();
   }
  }
 }
 
 public static void main(String[] args) {

  final Thread2 thread2 = new Thread2();
  Thread thread1 = new Thread(new Runnable(){
   public void run(){
    thread2.method1();
   }
  }, "Thread1");
  
  Thread thread3 = new Thread(new Runnable(){
   public void run(){
    thread2.method2();
   }
  }, "Thread2");
  
  thread1.start();
  thread3.start();//thead3不用等待thread1的执行完毕就可立即执行
 }

3.

public class Thread3 {

 class Inner{
  private void method1(){
   int i = 5;
   while(i-- > 0){
    System.out.println(Thread.currentThread().getName() + " : Inner.method1() = " + i);
    try{
     Thread.sleep(500);
    }catch(InterruptedException ie){
     ie.printStackTrace();
    }
   }
  }
  
  //尽管线程thread1获得了对Inner的对象锁,但由于线程thread2访问的是同一个Inner中的非同步部分。
  //所以两个线程互不干扰。
  private void method2(){
   int i = 5;
   while(i-- > 0){
    System.out.println(Thread.currentThread().getName() + " : Inner.method2() = " + i);
    try{
     Thread.sleep(500);
    }catch(InterruptedException ie){
     ie.printStackTrace();
    }
   }
  }
 }

 
 public void method1(Inner inner){
  synchronized (inner){//使用对象锁
   inner.method1();
  }
 }
 
 public void method2(Inner inner){
  inner.method2();
 }
 
 
 public static void main(String[] args){
  final Thread3 thread3 = new Thread3();
  final Inner inner = thread3.new Inner();
  
  Thread thread1 = new Thread(new Runnable(){
   public void run(){
    thread3.method1(inner);
   }
  }, "Thread1");
  
  Thread thread2 = new Thread(new Runnable(){
   public void run(){
    thread3.method2(inner);
   }
  }, "Thread2");
  
  thread1.start();
  thread2.start();//thread2无需等待thread1运行完毕,就可执行,
 }
}

4.

public class Thread4 {

 class Inner{
  private void method1(){
   int i = 5;
   while(i-- > 0){
    System.out.println(Thread.currentThread().getName() + " : Inner.method1() = " + i);
    try{
     Thread.sleep(500);
    }catch(InterruptedException ie){
     ie.printStackTrace();
    }
   }
  }
  
  //尽管线程thread1与thread2访问了同一个Inner对象中两个毫不相关的部分,但因为thread1先获得了对Inner的对象锁,
  //所以thread2对Inner.mmethod2()的访问也被阻塞,因为method2()是Inner中的一个同步方法。
  private synchronized void method2(){
   int i = 5;
   while(i-- > 0){
    System.out.println(Thread.currentThread().getName() + " : Inner.method2() = " + i);
    try{
     Thread.sleep(500);
    }catch(InterruptedException ie){
     ie.printStackTrace();
    }
   }
  }
 }

 
 public void method1(Inner inner){
  synchronized (inner){//使用对象锁
   inner.method1();
  }
 }
 
 public void method2(Inner inner){
  inner.method2();
 }
 
 
 public static void main(String[] args){
  final Thread4 thread4 = new Thread4();
  final Inner inner = thread4.new Inner();
  
  Thread thread1 = new Thread(new Runnable(){
   public void run(){
    thread4.method1(inner);
   }
  }, "Thread1");
  
  Thread thread2 = new Thread(new Runnable(){
   public void run(){
    thread4.method2(inner);
   }
  }, "Thread2");
  
  thread1.start();
  thread2.start();//thread2要等待thread1运行完才能执行,因为method2是一个同步方法
 }
}

5.

import java.text.SimpleDateFormat;
import java.util.Date;

public class TestThread extends Thread{

 private static Integer threadCounterLock;//用于同步,防止数据被写乱,可以是任意的对象
 private static int threadCount;//静态变量用于本类的线程计数器
 
 static{
  threadCount = 0;
  threadCounterLock = new Integer(0);
 }
 
 public TestThread(){
  super();
 }
 
 public synchronized static void incThreadCount(){
  threadCount++;
  System.out.println("thread count after enter: " + threadCount);
 }
 
 public synchronized static void decThreadCount(){
  threadCount--;
  System.out.println("thread count after leave: " + threadCount);
 }
 
 public void run(){
//  synchronized(threadCounterLock){//同步
//   threadCount++;
//   System.out.println("thread count after enter: " + threadCount);
//  }
  
  incThreadCount();//此语句作用同上面注释掉的同步代码块
  
  final long nSleepMilliSecs = 1000; //循环中的休眠时间

  
  long nCurrentTime = System.currentTimeMillis();
  long nEndTime = nCurrentTime + 10000;//运行截止时间

  SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  
  try{
   while(nCurrentTime < nEndTime){
    nCurrentTime = System.currentTimeMillis();
    System.out.println("Thread " + this.hashCode() + ", current time: " + simpleDateFormat.format(new Date(nCurrentTime)));
    
    try{
     sleep(nSleepMilliSecs);
    }catch(InterruptedException ie){
     ie.printStackTrace();
    }
   }
  }finally{
//   synchronized(threadCounterLock){
//    threadCount--;
//    System.out.println("thread count after leave: " + threadCount);
//   }
   
   decThreadCount();//此语句作用同上面注释掉的同步代码块
  }
  
 }
 
 public static void main(String[] args){
  TestThread[] testThread = new TestThread[2];
  for(int i=0; i<2; i++){
   testThread[i] = new TestThread();
   testThread[i].start();
  }
 }
}



posted @ 2007-04-09 20:54 chenweicai 阅读(1088) | 评论 (0)编辑 收藏


1。类中定义内部类

public class Outer {

 private int outer_i = 100;//内部类可以访问外部类的变量
 
 void test(){
  Inner inner = new Inner();
  inner.display();
 }
 
 //当一个类中的程序代码要用到另外一个类的实例对象,
 //而另外一个类的程序代码又要访问第一个类的成员时
 // 将另外一个类作成第一个类的内部类。
 class Inner{
  
  private int inner_i;
  //内部类的成员只有在内部类的范围之内是可知的,
  //并不能被外部类使用
  
  void display(){
   System.out.println("display: outer_i = " + outer_i);
  }
 }
}


 public static void main(String[] args) {

  Outer outer = new Outer();
  outer.test();
 }

2.

public class Outer {

 private int size = 1;
 
 public void print(){
  Inner inner = new Inner();
  inner.method(11);
 }
 
 public class Inner{
  private int size = 3;
  
  public void method(int size){
   size++;//method的形参
   this.size++;//Inner类中的成员变量
   Outer.this.size++;//Outer类中的成员变量
   
   System.out.println("method size is :" +size);
   System.out.println("Inner size is :" + this.size);
   System.out.println("Outer size is :" + Outer.this.size);
  }
 }
 
 public static void main(String[] args){
  Outer outer = new Outer();
  outer.print();
 }
}

3.内部类被外部引用

public class Outer {

 private int size = 10;
 
 //内部类声明为public 可从外部类之外被调用
 public class Inner{
  public void print(){
   System.out.println(++size);
  }
 }
}

public class test {


 public static void main(String[] args) {

  Outer outer = new Outer();
  Outer.Inner inner = outer.new Inner();
  inner.print();
 }
}

4.方法中定义内部类

public class Outer {

 int outer_i = 100;
 String str = new String("Between");
 
 void test(){
  for(int i=0; i<5; i++){
   
   class Inner{
    void display(){
     System.out.println("display: outer_i = " + outer_i);
    }
   }
   
   Inner inner = new Inner();
   inner.display();
  }
 }
 
 //在方法中定义的内部类只能访问方法中的final类型的局部变量
 //因为用final定义的局部变量相当于一个常量,她的生命周期超出了方法运行的生命周期
 public void amethod(final int iArgs){
  int it315;//此变量内部类不能访问
  
  class Bicycle{
   public void sayhello(){
    System.out.println(str);
    System.out.println(iArgs);
   }
  }
  Bicycle bicycle = new Bicycle();
  bicycle.sayhello();
 }
}


 public static void main(String[] args) {

  Outer outer = new Outer();
  outer.test();
  outer.amethod(18);
  
 }

posted @ 2007-04-08 22:13 chenweicai 阅读(122) | 评论 (0)编辑 收藏

1.静态变量

public class Chinese {

 static String country = "中国";
 
 String name;
 int age;
 
 void singOurCountry(){
  System.out.println("啊, 亲爱的" + country);
 }
}

 public static void main(String[] args) {

  System.out.println("Chinese country is :" + Chinese.country);//类.成员
  Chinese ch1 = new Chinese();
  System.out.println("Chinese country is :" + ch1.country);//对象.成员
        ch1.singOurCountry();
 }


注:1. 不能把任何方法内的变量声明为static类型
        2. 静态成员变量为该类的各个实例所共享,所以在内存中该变量只有一份,
            经常用来计数统计
如:
class A{
     private static int  count = 0;

      public A(){
          count = count + 1;  
    }

public void finalize(){//在垃圾回收时,finalize方法被调用
       count = count - 1;
    }
}


2.静态方法
public class Chinese {

 //在静态方法里,只能直接调用同类中的其他静态成员,不能直接访问类中的非静态成员和方法
 //因为对如非静态成员和方法,需先创建类的实例对象后才可使用。
 //静态方法不能使用关键字this和super,道理同上
 //main方法是静态方法,不能直接访问该类中的非静态成员,必须先创建该类的一个实例
 static void sing(){
  System.out.println("啊...");
 }
 
 void singOurCountry(){
  sing();//类中的成员方法可以直接访问静态成员方法
  System.out.println("啊...祖国...");
 }
}

 public static void main(String[] args) {

  Chinese.sing();//类.方法
  
  Chinese ch1 = new Chinese();
  ch1.sing();//对象名.方法
  ch1.singOurCountry();
 }


3. 静态代码块
public class StaticCode {

 static String country;
 
 static{
  country = "Chine";
  System.out.println("Static code is loading...");
 }
}

public class TestStatic {

 static{
  System.out.println("TestStatic code is loading..."); 
 }
 
 public static void main(String[] args){
  System.out.println("begin executing main method...");
  //类中的静态代码将会自动执行,尽管产生了类StaticCode的两个实例对象,
  //但其中的静态代码块只被执行了一次。同时也反过来说明:类是在第一次被使用时
  //才被加载,而不是在程序启动时就加载程序中所有可能要用到的类
  new StaticCode();
  new StaticCode();
 }
 
}



posted @ 2007-04-08 20:12 chenweicai 阅读(127) | 评论 (0)编辑 收藏

java实现多线程有两种方法:
一种是继承Thread类

如下面例子:
public class TestThread extends Thread {

 //实现多线程方法一:继承Thread类,并重载run方法
 public void run(){
  //要将一段代码在一个新的线程上运行,该代码应写在run函数中
  while(true){
   System.out.println(Thread.currentThread().getName() + "is running...");
  }
 }
}



public class ThreadDemo2 {

 public static void main(String[] args) {
  
  //启动一个新的线程,不是直接调用Thread类子类对象的run方法,
  //而是调用Thread类子类对象的start方法,Thread类对象的start方法
  //将产生新的线程,并在该线程上运行该Thread类对象中的run方法,根据
  //多态性 实际上运行的是Thread子类的run方法
  TestThread testThread = new TestThread();
  testThread.start();
  
  while(true){
   System.out.println("main thread is running...");
  }
 }

}



方法二是:实现接口Runnable中的run函数

如下列所示

//实现多线程方法二:实现Runnable接口,重载run函数,
//大多数情况下,如果只想重写 run() 方法,而不重写其他 Thread 方法,
//那么应使用 Runnable 接口。这很重要,
//因为除非程序员打算修改或增强类的基本行为,否则不应为该类创建子类。
public class TestThread implements Runnable {

 public void run() {
  // TODO Auto-generated method stub
  System.out.println(Thread.currentThread().getName() + "is running......");
 }
 
}


 public static void main(String[] args) {
  // TODO Auto-generated method stub
  TestThread testThread = new TestThread();//创建TestThread类的一个实例
  Thread thread = new Thread(testThread);//创建一个Thread类的实例
  thread.start();//使线程进入runnable状态
  
  while(true){
   System.out.println("main thread is running...");
  }
 }


2.这两种方法的区别:实现Runnable接口适合多个相同的程序代码访问处理同一资源


如下列所示:
//四个售票窗口售同一中车票,总共有100张,要求这四个售票多线程进行
public class TicketRunnable implements Runnable {

 private int tickets = 100;
 public void run() {
   while(true){
    if(tickets > 0){
     System.out.println(Thread.currentThread().getName() +
       " is saling ticket " + tickets--);
    }
   }
 }

}

public class TicketRunnableDemo {

 public static void main(String[] args){
  //创建了四个线程,每个线程调用了同一各TicketRunnable对象中run方法,
  //访问的是同一个对象中的变量tickets,从而满足了我们的要求。
  //实现Runnable接口适合多个相同的程序代码访问处理同一资源
  TicketRunnable ticketrunnable = new TicketRunnable();
  new Thread(ticketrunnable).start();
  new Thread(ticketrunnable).start();
  new Thread(ticketrunnable).start();
  new Thread(ticketrunnable).start();
 }
}

而继承继承Thread类,并重载run方法不能达到此目的
如下所示:
public class TicketThread extends Thread {

 private int tickets = 100;
 
 public void run(){
  while(true){
   if(tickets>0){
    System.out.println(Thread.currentThread().getName() +
      "is saling ticket " + tickets--);
   }
  }
 }
}

public class TicketThreadDemo {

 public static void main(String[] args){
  //我们希望的是四个线程共售100,我们创建四个线程
  //但结果与我们希望的相反,各个线程各售100张票
  new TicketThread().start();
  new TicketThread().start();
  new TicketThread().start();
  new TicketThread().start();
  
//  如果我们象下面这样写的话,只有一个线程在售票,没有实现四个线程并发售票
//  TicketThread ticketThread = new TicketThread();
//  ticketThread.start();
//  ticketThread.start();
//  ticketThread.start();
//  ticketThread.start();
 }
}

3.后台线程

public class DaemonTest {
 public static void main(String[] args){
  TicketRunnable ticketRunnable = new TicketRunnable();
  Thread thread = new Thread(ticketRunnable);
  thread.setDaemon(true);//将此线程设置为后台线程,当进程中只有后台线程时,进程就会结束
  thread.start();
 }
}

4.联合线程
(还没搞懂)

5. 线程同步

在上面的售票程序中,线程可能是不安全的,

比如我们修改一下:

public class ThreadRunnable implements Runnable{
 
 private int tickets = 100;
 public void run() {
   while(true){
    if(tickets > 0){
     try{
      Thread.sleep(10);//使该线程暂停,
     }catch(Exception e){
      System.out.println(e.getMessage());
     }
     System.out.println(Thread.currentThread().getName() +
       " is saling ticket " + tickets--);
    }
   }
 }
}

再次测试时,发现可能会打印-1 -2 这样的ticket

所以我们要修改一下,使该程序是线程安全的,

public class TicketSynch implements Runnable{
 
 private int tickets = 100;
 String str = new String("");//此对象放在run函数之外
 
 public void run(){
  while(true){
   synchronized(str){//同步代码块
    if(tickets > 0){
     try{
      Thread.sleep(10);
     }
     catch(Exception e){
      System.out.println(e.getMessage());
     }
     System.out.println(Thread.currentThread().getName()
       +" is saling ticket " + tickets--);
    }
   }
  }
 }

}

但是 同步以牺牲程序的性能为代价

5. 同步函数

public class SynchMethod implements Runnable{

 private int tickets = 100;
 public void run(){
  while(true){
   sale();
  }
 }
 
 public synchronized void sale(){
  if(tickets > 0){
   try{
    Thread.sleep(10);
   }catch(Exception e){
    System.out.println(e.getMessage());
   }
   System.out.println(Thread.currentThread().getName()
     + "is saling ticket " + tickets--);
  }
 }
}

 public static void main(String[] args){
  //创建了四个线程,每个线程调用了同一各TicketRunnable对象中run方法,
  //访问的是同一个对象中的变量tickets,从而满足了我们的要求。
  //实现Runnable接口适合多个相同的程序代码访问处理同一资源
  SynchMethod synchMethod = new SynchMethod();
  new Thread(synchMethod).start();
  new Thread(synchMethod).start();
  new Thread(synchMethod).start();
  new Thread(synchMethod).start();
 }

6.代码块和函数间的同步
public class MethodAndBlockSyn implements Runnable{

 private int tickets = 100;
 String str = new String("");
 
 public void run(){
  if(str.equals("method")){
   while(true){
    sale();
   }
  }
  else{
   synchronized(str){//同步代码块
    if(tickets > 0){
     try{
      Thread.sleep(10);
     }
     catch(Exception e){
      System.out.println(e.getMessage());
     }
     System.out.println(Thread.currentThread().getName()
       +" is saling ticket " + tickets--);
    }
   }
  }
 }
 
 
 public synchronized void sale(){
  if(tickets > 0){
   try{
    Thread.sleep(10);
   }catch(Exception e){
    System.out.println(e.getMessage());
   }
   System.out.println(Thread.currentThread().getName()
     + "is saling ticket " + tickets--);
  }
 }
}


 public static void main(String[] args){
  MethodAndBlockSyn test = new MethodAndBlockSyn();
  new Thread(test).start();// 这个线程调用同步代码块
  
  try{
   Thread.sleep(1);//让线程等待100ms 是为了让该线程得到CPU
   //避免该线程未得到CPU就将str设置为method,从而使此线程也是调用同步函数
  }catch(Exception e)
  {
  }
  test.str = new String("method");
  new Thread(test).start();//这个线程调用同步函数
 }

 

7. 线程间通讯

wait: 告诉当前线程放弃监视器并进入睡眠状态,知道有其他线程进入同一监视器并调用notify为止。

notify:唤醒同一对象监视器中调用wait的第一个线程。类似饭馆有一个空位置后通知所有等待就餐顾客中的第一位可以入座的情况

notifyAll:唤醒同一对象监视器中调用wait的所有线程,具有最高优先级的程序先被唤醒并执行。

以上三个方法只能在synchronized方法中调用

public class Record {

 private String name = "陈世美";
 private String sex = "女";
 boolean bFull = false;
 
 public synchronized void put(String name, String sex)
 {
  if(bFull)
   try {
    wait();
   } catch (InterruptedException e1) {
    // TODO Auto-generated catch block
    e1.printStackTrace();
   }
  
  this.name = name;
  
  try{
   Thread.sleep(10);
  }
  catch(Exception e){
   System.out.println(e.getMessage());
  }
  
  this.sex = sex;
  
  bFull = true;
  notify();
 }
 
 public synchronized void get(){
  if(!bFull)
   try {
    wait();
   } catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   }
  System.out.println(name + "--->" + sex);
  
  bFull = false;
  notify();
 }
}




public class Productor implements Runnable {

 Record record = null;
 
 public Productor(Record record){
  this.record = record;
 }
 
 public void run(){
  int i = 0;
  
  while(true){
   if(i==0)
    record.put("陈陈陈", "男");
   else
    record.put("陈世美", "女");
   i = (i+1)%2;
  }
 }
}

public class Consumer implements Runnable {

 Record record = null;
 
 public Consumer(Record record){
  this.record = record;
 }
 
 public void run(){
  while(true){
   record.get();
  }
 }
}

 public static void main(String[] args) {
  // TODO Auto-generated method stub

  Record record = new Record();
  new Thread(new Productor(record)).start();
  new Thread(new Consumer(record)).start();
 }



 

 

posted @ 2007-04-08 17:14 chenweicai 阅读(184) | 评论 (0)编辑 收藏

 我们知道,Java利用ClassLoader将类载入内存,并且在同一应用中,可以有很多个ClassLoader,通过委派机制,把装载的任务传递给上级的装载器的,依次类推,直到启动类装载器(没有上级类装载器)。如果启动类装载器能够装载这个类,那么它会首先装载。如果不能,则往下传递。当父类为null时,JVM内置的类(称为:bootstrap class loader)就会充当父类。想想眼下的越来越多用XML文件做配置文件或者是描述符、部署符。其实这些通过XML文档描述的配置信息最终都要变成Java类,基实都是通过ClassLoader来完成的。URLClassLoader是ClassLoader的子类,它用于从指向 JAR 文件和目录的 URL 的搜索路径加载类和资源。也就是说,通过URLClassLoader就可以加载指定jar中的class到内存中。

下面来看一个例子,在该例子中,我们要完成的工作是利用URLClassLoader加载jar并运行其中的类的某个方法。

首先我们定义一个接口,使所有继承它的类都必须实现action方法,如下:

public   interface  ActionInterface  {
    
public  String action();
}

完成后将其打包为testInterface.jar文件。

接下来新建一工程,为了编译通过,引入之前打好的testInterface.jar包。并创建TestAction类,使它实现ActionInterface接口。如下:

 

public   class  TestAction  implements  ActionInterface  {
    
public  String action()  {
        
return   " com.mxjava.TestAction.action " ;
    }

}

完成后将其打包为test.jar,放在c盘根目录下。下面要做的就是利用URLClassLoader加载并运行TestAction的action方法,并将返回的值打印在控制台上。

新建一工程,引入testInterface.jar包。并创建一可执行类(main方法),在其中加入如下代码:

URL url  =   new  URL(“file:C: / test.jar”);
URLClassLoader myClassLoader 
=   new  URLClassLoader( new  URL[]  { url } );
Class myClass 
=  myClassLoader.loadClass(“com.mxjava.TestAction”);
ActionInterface action 
=  (ActionInterface)myClass.newInstance();
System.out.println(action.action());

  在上面的例子中,首先利用URLClassLoader加载了C:\test.jar包,将其中的com.mxjava.TestAction类载入内存,将其强制转型为testInterface包中的ActionInterface类型,最后调用其action方法,并打印到控制台中。

  执行程序后,在控制台上如期打印出我们想要的内容。但是,事情并没有那么简单,当我们将该代码移动web应用中时,就会抛出异常。原来,Java为我们提供了三种可选择的ClassLoader:
1. 系统类加载器或叫作应用类加载器 (system classloader or application classloader)
2. 当前类加载器
3. 当前线程类加载器

  在上例中我们使用javac命令来运行该程序,这时候使用的是系统类加载器 (system classloader)。这个类加载器处理 -classpath下的类加载工作,可以通过ClassLoader.getSystemClassLoader()方法调用。 ClassLoader 下所有的 getSystemXXX()的静态方法都是通过这个方法定义的。在代码中,应该尽量少地调用这个方法,以其它的类加载器作为代理。否则代码将只能工作在简单的命令行应用中。当在web应用中时,服务器也是利用ClassLoader来加载class的,由于ClassLoader的不同,所以在强制转型时JVM认定不是同一类型。(在JAVA中,一个类用其完全匹配类名(fully qualified class name)作为标识,这里指的完全匹配类名包括包名和类名。但在JVM中一个类用其全名和一个加载类ClassLoader的实例作为唯一标识。因此,如果一个名为Pg的包中,有一个名为Cl的类,被类加载器KlassLoader的一个实例kl1加载,Cl的实例,即C1.class在JVM中表示为(Cl, Pg, kl1)。这意味着两个类加载器的实例(Cl, Pg, kl1) 和 (Cl, Pg, kl2)是不同的,被它们所加载的类也因此完全不同,互不兼容的。)为了能够使程序正确运行,我们首要解决的问题就是,如何将URLClassLoader加载的类,同当前ClassLoader保持在同一类加载器中。解决方法很简单,利用java提供的第三种ClassLoader—当前线程类加载器即可。jdk api文档就会发现,URLClassLoader提供了三种构造方式:

// 使用默认的委托父 ClassLoader 为指定的 URL 构造一个新 URLClassLoader。 
URLClassLoader(URL[] urls) 
// 为给定的 URL 构造新 URLClassLoader。 
URLClassLoader(URL[] urls, ClassLoader parent) 
// 为指定的 URL、父类加载器和 URLStreamHandlerFactory 创建新 URLClassLoader。
URLClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) 

接下来要做的就是,在构造URLClassLoader时,将当前线程类加载器置入即可。如下:

URLClassLoader myClassLoader  =   new  URLClassLoader( new  URL[]  { url } , Thread.currentThread().getContextClassLoader());

总结:
  
Java是利用ClassLoader来加载类到内存的,ClassLoader本身是用java语言写的,所以我们可以扩展自己的ClassLoader。利用URLClassLoader可以加载指定jar包中的类到内存。在命行上利用URLClassLoader加载jar时,是使用系统类加载器来加载class的,所以在web环境下,就会出错。这是因为JVM中一个类用其全名和一个加载类ClassLoader的实例作为唯一标识的。我们只要利用URLClassLoader的第二种构造方法并传入当前线程类加载器即可解决。

posted @ 2007-04-06 16:55 chenweicai 阅读(307) | 评论 (0)编辑 收藏

 

 不变模式(Immutable Pattern)顾名思义,它的状态在它的生命周期内是永恒的(晕,永恒的日月星晨,对象如人,
太渺小,谈不上永恒!
),不会改变的.对于其中的不变类(Immutable Class),它的实例可以在运行期间保持状态永远不会被
改变,所以不需要采取共享互斥机制来保护,如果运用得当可以节省大量的时间成本.

 请注意上面这段话,不变模式其中的不变类,说明不变类只是不变模式中一个组成部分,不变类和与之相辅的可变
类,以及它们之间的关系才共同构成不变模式!所以在涉及不变模式的时候一定要研究一个类是不变的还是可变的(Mutable).
在jdk中的String类和StringBuffer类就组成了一个不变模式.

还是先看具体的例子:

final class Dog{
    private final String name;
    private final int age;
    public Dog(String name,int age){
        this.name = name;
        this.age = age;
    }
   
    public String getName(){return this.name;}
    public int getAge(){return this.age;}
   
    public String toString(){
        return "Dog's name = " + this.name + ",age = " + this.age;
    }
}

1.Dog类本身被声明为final,可以保证它本身的状态不会被子类扩展方法所改变.
2.Dog类的所有成员变量都是final的,保证它在构造后不会被重新赋值.而且Dog类所有属性是private的,只提供getter访问.
3.Dog类的能传入的参数本身是Immutable的.这一点非常重要将在下面具体说明.
以上条件都是必要条件,而不是充要条件.

class DisplayDog extends Thread{
    private Dog dog;
    public DisplayDog(Dog dog){
        this.dog = dog;
    }
   
    public void run(){
        while(true){
            System.out.println(this.getName() + " display: " + dog);
        }
    }
}

DisplayDog类是把一个Dog对象传入后,不断显示这个dog的属性.我们会同时用多个线程来显示同一dog对象,看看它们在共享
同一对象时对象的状态:
public class Test {
    public static void main(String[] args) throws Exception {
        Dog dog = new Dog("Sager",100);
        new DisplayDog(dog).start();
        new DisplayDog(dog).start();
        new DisplayDog(dog).start();
    }
}
运行这个例子你可以等上一个月,虽然运行一年都正常并不能说明第366天不出现异常,但我们可以把这样的结果认为是一种
说明.多个线程共享一个不变类的实例时,这个实例的状态不会发生改变.事实上它没有地方让你去改变.
在临界区模式中有些操作必须只允许有一个线程操作,而这个类本身以及对它的访问类中并不需要进行临界区保护,这就让多
个线程不必等待从而提高了性能.

既然有这么好的优势,那我们在需要临界区保护的对象为什么不都设计成不变类呢?

1.不变类设计起来有一定难度.对于上面这个用来示例的Dog,由于其本身的属性,方法都很简单,我们还可以充分地考虑到可
以改变它状态的各种情况.但对于复杂的类,要保证它的不变性,是一个非常吃力的工作.
 
不变类中,任何一个必要都件都不是充要条件,虽然连老骨灰都没有这么说过,但我还是要真诚地目光深邃语气凝重地告诉你.
没有任何条件是充要条件的意思就是如果任何一个必要条件你没考虑到,那它就会无法保证类的不可变性.没有规范,没有模
板,完全看一人设计人员的经验和水平.也许你自以为考虑很全面的一个"不变类"在其他高手面前轻而易举地就"可变"了.

2.不变类的种种必要条件限制了类设计的全面性,灵活性.这点不用多说,简单说因为是不变类,所以你不能A,因为是不变类,你
不能B.

当然,如果你是一人很有经验的设计者,你能成功地设计一个不变类,但因为它的限制而失去一些功能,你就要以使用与之相辅
的可变类.并且它们之间可以相互转换,在需要不变性操作的时候以不变类提供给用户,在需要可变性操作的时候以可变类提供
给用户.

在jdk中String被设计为不可变类,一旦生成一个String对象,它的所有属性就不会被变,任何方法要么返回这个对象本身的原
始状态,要么抛弃原来的字符串返回一个新字符串,而绝对不会返回被修改了的字符串对象.
但是很多时候返回新字符串抛弃原来的字符串对象这样的操作太浪费资源了.特别是在循环地操作的时候:

 String s = "Axman";
 for(int i=0;i<1000*1000;i++) s += "x";这样的操作是致命的.
那么这种时候需要将原始的不变的s包装成可变的StringBuffer来操作,性能的改变可能是成千上万倍:

        StringBuffer sb = new StringBuffer(s); //将不变的String包装成可变的String;
        for(int i=0;i<1000*1000;i++)
            sb.append("x");
        s = new String(sb); //将可变类封装成不变类.虽然可以调用toString(),但那不是可变到不变的转换.

在将可变类封装到不变类的时候要特别小心.因为你传入的引用在外面是可以被修改的.所以即使你不变类本身不能去改变属
性但属性有一个外部引用.可以在外面修改:

final class MutableDog{
    private String name;
    private int age;
    public MutableDog(String name,int age){
        this.name = name;
        this.age = age;
    }
    public synchronized void setDog(String name,int age){
        this.name = name;
        this.age = age;
    }
    public String getName(){return this.name;}
    public int getAge(){return this.age;}

    public synchronized String toString(){
        return "Dog's name = " + this.name + ",age = " + this.age;
    }
   
     public synchronized ImmatableDog getImmatableDog(){
         return new ImmatableDog(this);
     }
}

final class ImmatableDog{
    private final String name;
    private final int age;
    public ImmatableDog(String name,int age){
        this.name = name;
        this.age = age;
    }
   
    public ImmatableDog(MutableDog dog){
        this.name = dog.getName();
        this.age = dog.getAge();
    }   
   
    public String getName(){return this.name;}
    public int getAge(){return this.age;}
   
    public String toString(){
        return "Dog's name = " + this.name + ",age = " + this.age;
    }
}


MutableDog类是可变的,可以满足我们利用对象的缓冲来让对象成为表示另一个实体的功能.但它们之间
随时可以根据需要相互转换,但是我们发现:
    public ImmatableDog(MutableDog dog){
        this.name = dog.getName();
        this.age = dog.getAge();
    }
这个方法是不安全的.当一个属性为"Sager",100的dog被传进来后,执行this.name = dog.getName();后,
如果线程切换到其它线程执行,那么dog的属性就可能是"p4",80,这时再执行this.age = dog.getAge();
我们就会得到一个属性为"Sager",80的这样一个错误的不可变对象.这是一个非常危险的陷井.在这里我们
可以通过同上来解决:
    public ImmatableDog(MutableDog dog){
        synchronized(dog){
            this.name = dog.getName();
            this.age = dog.getAge();
        }
    }
注意这里同步的MutableDog,它将会和MutableDog的setDog产生互斥.它们都需要获取同一MutableDog对象的
锁,如果MutableDog的setDog不是方法同步(synchronized(this)),即使ImmatableDog(MutableDog dog)中同步
了dog,也不能保证安全,它们需要在同一对象上互斥.

posted @ 2007-03-29 15:58 chenweicai 阅读(117) | 评论 (0)编辑 收藏

简单工厂模式(缺点:每增加一个具体产品时 ,就要修改工厂方法,工厂方法负责了所有具体产品的创建)

举个例子:
------------------
public interface Fruit {

 void grow();
 
 void harvest();
 
 void plant();
}
-------------------
public class Apple implements Fruit {

 private int treeAge;
 
 public void grow() {
  // TODO Auto-generated method stub
  System.out.println("Apple is growing...");
  
 }

 public void harvest() {
  // TODO Auto-generated method stub
  System.out.println("Apple has been harvested.");
 }

 public void plant() {
  // TODO Auto-generated method stub
  System.out.println("Apple has been planted.");
  
 }

 public int getTreeAge() {
  return treeAge;
 }

 public void setTreeAge(int treeAge) {
  this.treeAge = treeAge;
 }
 
}
-------------------
public class Grape implements Fruit {

 private boolean seedless;
 
 
 
 public boolean isSeedless() {
  return seedless;
 }

 public void setSeedless(boolean seedless) {
  this.seedless = seedless;
 }

 public void grow() {
  // TODO Auto-generated method stub
  System.out.println("Grape is growing...");
 }

 public void harvest() {
  // TODO Auto-generated method stub
  System.out.println("Grape has been harvested.");
 }

 public void plant() {
  // TODO Auto-generated method stub
  System.out.println("Grape has been planted.");
  
 }

 
}


---------------------------
public class Strawberry implements Fruit {

 public void grow() {
  // TODO Auto-generated method stub
  System.out.println("Strawberry is growing...");
 }

 public void harvest() {
  // TODO Auto-generated method stub
  System.out.println("Strawberry has been harvested.");
 }

 public void plant() {
  // TODO Auto-generated method stub
  System.out.println("Strawberry has been planted.");
 }
}

-------------------------
public class FruitGardener {

 //静态工厂方法
 public static Fruit factory(String which) throws BadFruitException{
  if(which.equalsIgnoreCase("apple")){
   return new Apple();
  }
  else if(which.equalsIgnoreCase("strawberry")){
   return new Strawberry();
  }
  else if(which.equalsIgnoreCase("grape")){
   return new Grape();
  }
  else{
   throw new BadFruitException("Bad fruit request");
  }
 }
}

---------------------------
public class BadFruitException extends Exception {

 public BadFruitException(String msg){
  super(msg);
 }
}


--------------------------
public class Client {

 public static void main(String[] args){
  
  try{
   Fruit apple = (Fruit)FruitGardener.factory("Apple");
   System.out.println("apple is class: " + apple.getClass().getName());
   apple.plant();
   apple.grow();
   apple.harvest();
   System.out.println();
   
   Fruit grape = (Fruit)FruitGardener.factory("grape");
   System.out.println("grape is class: " + grape.getClass().getName());
   grape.plant();
   grape.grow();
   grape.harvest();
   System.out.println();
   
   Fruit strawberry = (Fruit)FruitGardener.factory("strawberry");
   System.out.println("strawberry is class: " + strawberry.getClass().getName());
   strawberry.plant();
   strawberry.grow();
   strawberry.harvest();
   
  }catch(BadFruitException e){
   e.printStackTrace();
  }
 }
}


2 工厂方法模式:解决了简单工厂模式的缺点, 将每一个具体产品的创建工作交给对应的具体工厂角色去解决

举个例子:
---------------------
public interface FruitGardener {

 public Fruit factory();
}


---------------------

public class AppleGardener implements FruitGardener{

 public Fruit factory() {
  return new Apple();
 }
}

-----------------

public class GrapeGardener implements FruitGardener{

 public Fruit factory() {
  return new Grape();
 }
}


----------------------

public class StrawGardener implements FruitGardener{

 public Fruit factory() {
  return new Strawberry();
 }
}

-----------------
public class Client {

 private static FruitGardener applegardener, grapegardener, strawgardener;
 private static Fruit apple, grape, strawberry;
 
 public static void main(String[] args){
  applegardener = new AppleGardener();
  apple = applegardener.factory();
  System.out.println("apple is class: " + apple.getClass().getName());
  apple.plant();
  apple.grow();
  apple.harvest();
  System.out.println();
  
  grapegardener = new GrapeGardener();
  grape = grapegardener.factory();
  System.out.println("grape is class: " + grape.getClass().getName());
  grape.plant();
  grape.grow();
  grape.harvest();
  System.out.println();

  strawgardener = new StrawGardener();
  strawberry = strawgardener.factory();
  System.out.println("strawberry is class: " + strawberry.getClass().getName());
  strawberry.plant();
  strawberry.grow();
  strawberry.harvest();
 }
}


3 抽象工厂模式:解决多产品簇问题

举个例子:

-------------------
public interface Fruit {

// public String getName();
}

public class NorthernFruit implements Fruit{

 private String name;

 public String getName() {
  return name;
 }

 public void setName(String name) {
  this.name = name;
 }

 public NorthernFruit(String name) {
  super();
  // TODO Auto-generated constructor stub
  this.name = name;
 }
 
 
}



public class TropicalFruit implements Fruit{

 private String name;

 public String getName() {
  return name;
 }

 public void setName(String name) {
  this.name = name;
 }

 public TropicalFruit(String name) {
  super();
  // TODO Auto-generated constructor stub
  this.name = name;
 }
 
 
}



public interface Veggie {

}


public class TropicalVeggie implements Veggie{
 
 private String name;

 public String getName() {
  return name;
 }

 public void setName(String name) {
  this.name = name;
 }

 public TropicalVeggie(String name) {
  super();
  // TODO Auto-generated constructor stub
  this.name = name;
 }
 
 

}


public class NorthernVeggie implements Veggie{

 private String name;

 public String getName() {
  return name;
 }

 public void setName(String name) {
  this.name = name;
 }

 public NorthernVeggie(String name) {
  super();
  // TODO Auto-generated constructor stub
  this.name = name;
 }
 
 
}

-------------------------

public interface Gardener {

 public Fruit createFruit(String name);
 
 public Veggie createVeggie(String name);
}

public class NorthernGardener implements Gardener {

 public Fruit createFruit(String name) {
  return new NorthernFruit(name);
 }

 public Veggie createVeggie(String name) {
  return new NorthernVeggie(name);
 }

 
}



public class TropicalGardener implements Gardener {

 public Fruit createFruit(String name) {
  return new TropicalFruit(name);
 }

 public Veggie createVeggie(String name) {
  return new TropicalVeggie(name);
 }

 
}


public class Client {

 private static Gardener tropicalgardener ,northerngardener;
 private static Fruit northernfruit, tropicalfruit;
 private static Veggie northernveggie, tropicalveggie;
 
 public static void main(String[] args){
  tropicalgardener = new TropicalGardener();
  tropicalfruit = tropicalgardener.createFruit("tropicalfruit");
  //System.out.println(tropicalfruit.getName());
  tropicalveggie = tropicalgardener.createVeggie("tropicalveggie");
  
  northerngardener = new NorthernGardener();
  northernfruit = northerngardener.createFruit("northernfruit");
  northernveggie = northerngardener.createVeggie("northernveggie");
 }
 
}

posted @ 2007-03-29 13:18 chenweicai 阅读(285) | 评论 (0)编辑 收藏

假设在你的应用中使用一些对象,你如何拷贝你的对象呢?最明显的方法是讲一个对象简单的赋值给另一个,就像这样:

    obj2 = obj1;
    但是这个方法实际上没有拷贝对象而仅仅是拷贝了一个对象引用,换换言之,在你执行这个操作后仍然只有一个对象,但是多出了一个对该对象的引用。

    如果这个看似明显的方法不能正常工作,那么如何实际的拷贝一个对象呢?为什么不试试Object.clone呢?这个方法对Object的所有子类都是可用的。例如:

package clone.clone1;

public class ClassA implements Cloneable{//要继承Cloneable接口,否则会抛出异常
 private int x;

 public ClassA(int x) {
  super();
  // TODO Auto-generated constructor stub
  this.x = x;
 }

 //要覆盖clone方法,因为Object.clone()是protect类型
    //Object.clone完成的是对象的“浅”拷贝,即简单的成员到成员的拷贝。
 //它不做“深度”拷贝,即成员或者数组指向的对象的递归拷贝
 public Object clone(){
  // TODO Auto-generated method stub
  try{
   return super.clone();
  }catch(CloneNotSupportedException e){
   throw new InternalError(e.toString());
  }
 }

 public int getX() {
  return x;
 }

 public void setX(int x) {
  this.x = x;
 }
 
 
}


package clone.clone1;

public class Test {

 /**
  * @param args
  */
 public static void main(String[] args) {
  // TODO Auto-generated method stub
   ClassA a = new ClassA(12);
   ClassA b = (ClassA)a.clone();
   ClassA c = a;//此处多出了一个对该对象的引用,并没有实现克隆原对象
   System.out.println("Object A's X is :" + a.getX());
   System.out.println("Object B's X is :" + b.getX());
   System.out.println("Object C's X is :" + c.getX());
   a.setX(16);
   b.setX(18);
   System.out.println("After set,Object A's X is :" + a.getX());
   System.out.println("After set,Object B's X is :" + b.getX());
   System.out.println("After set,Object C's X is :" + c.getX());
 }
}


Object.clone完成的是对象的“浅”拷贝,即简单的成员到成员的拷贝。它不做“深度”拷贝,即成员或者数组指向的对象的递归拷贝。
package clone.clone2;

import java.util.HashMap;

public class ClassA implements Cloneable{
 
 public HashMap hashmap;//成员对象不是简单的数据,而是复杂的数据类型

 public ClassA() {
  hashmap = new HashMap();
  hashmap.put("key1", "value1");
  hashmap.put("key2", "value2");
 }
 
 public Object clone(){//不能简单的调用super.clone方法,
  //否则在新的对象中的复杂数据成员只是原对象的一个引用
  try{
   ClassA obj = (ClassA)super.clone();
   obj.hashmap = (HashMap)hashmap.clone();
   return obj;
  }catch(CloneNotSupportedException e){
  throw new InternalError(e.toString());
  }
 }

}


package clone.clone2;

public class test {

 public static void main(String[] args) {
  // TODO Auto-generated method stub

  ClassA a = new ClassA();
  ClassA b = (ClassA)a.clone();
  a.hashmap.remove("key1");
  System.out.println(b.hashmap.get("key1"));
  System.out.println(a.hashmap.get("key1"));
 }

}

posted @ 2007-03-27 19:37 chenweicai 阅读(452) | 评论 (1)编辑 收藏

 观察者模式是对象的行为模式,顾名思义,即存在观察者和被观察者。 观察者模式可以让多个观察者同时监听同一个被观察对象,当被观察对象发生变化时,并通知所有观察者,使各个观察者能作出相应的响应。适当地运用观察者模式,能提高自身代码的设计水平。

  观察者模式理解和编码都比较简单,通常包括以下步骤:

  1. 设计观察者接口类;
  2. 观察者类实现该接口;
  3. 设计被观察者抽象类,该类中提供一些方法,如:添加观察者对象,删除观察者对象,把事件通知给各个观察者对象;
  4. 设计被观察者类,继承被观察者抽象类,在该类中,可以根据需要在该类中,可以定义方法:被观察者是否发生变化

  以上四步,即完成了观察者模式的设计。下面代码分类进行描述以上步骤:

package Observer;

import java.util.Enumeration;
import java.util.Vector;

abstract public class Subject {

 private Vector observersVector = new java.util.Vector();
 
 public void attach(Observer observer){
  observersVector.addElement(observer);
  System.out.println("Attached an observer.");
 }
 
 public void detach(Observer observer){
  observersVector.removeElement(observer);
 }
 
 public void notifyObservers(){
  java.util.Enumeration enumeration = observers();
  while(enumeration.hasMoreElements()){
   System.out.println("Before notifying");
   ((Observer)enumeration.nextElement()).update();
  }
 }
 
 public Enumeration observers(){
  return ((java.util.Vector)observersVector.clone()).elements();
 }
}


package Observer;

public class ConcreteSubject extends Subject{

 private String state;
 
 public void change(String newState){
  state = newState;
  this.notifyObservers();
 }
}


package Observer;

public interface Observer {

 void update();
}


package Observer;

public class ConcreteObserver implements Observer{
 
 public void update(){
  System.out.println("I am notified.");
 }

}


package Observer;

public class Client {

 private static ConcreteSubject subject;
 private static Observer observer1;
 private static Observer observer2;
 
 public static void main(String[] args){
  subject = new ConcreteSubject();
  observer1 = new ConcreteObserver();
  observer2 = new ConcreteObserver();
  
  subject.attach(observer1);
  subject.attach(observer2);
  
  subject.change("new state");
 }
}


运行结果是:

Attached an observer.
Attached an observer.
Before notifying
I am notified.
Before notifying
I am notified.


在java中提供了Observerable类和Observer接口来实现观察者模式

public class Observable
extends Object

此类表示模型视图范例中的 observable 对象,或者说“数据”。可将其子类化,表示应用程序想要观察的对象(即被观察者)。

一个 observable 对象可以有一个或多个观察者。观察者可以是实现了 Observer 接口的任意对象。一个 observable 实例改变后,调用 ObservablenotifyObservers 方法的应用程序会通过调用观察者的 update 方法来通知观察者该实例发生了改变。

未指定发送通知的顺序。Observable 类中所提供的默认实现将按照其注册的重要性顺序来通知 Observers,但是子类可能改变此顺序,从而使用非固定顺序在单独的线程上发送通知,或者也可能保证其子类遵从其所选择的顺序。

注意,此通知机制与线程无关,并且与 Object 类的 waitnotify 机制完全独立。

新创建一个 observable 对象时,其观察者集合是空的。当且仅当 equals 方法为两个观察者返回 true 时,才认为它们是相同的。

public interface Observer

一个可在观察者要得到 observable 对象更改通知时可实现 Observer 接口的类。 
 给个用java机制来实现观察者模式的例子
在java机制中Observable类相当于抽象主题角色,我们定义具体主题角色来继承该类
Observer 接口相当于抽象观察者角色,我们定义具体观察者角色来实现该接口
所以这两个抽象角色我们不用编写了 只需编写两个具体角色

//----具体观察对象角色
package Observer.javaObserver;

import java.util.Observable;

public class Product extends Observable{

 private String name;
 private float price;
 
 public String getName(){
  return name;
 }
 
 public void setName(String name){
  this.name = name;
  // 设置变化点
  setChanged();
  notifyObservers(name);//通知观察者
 }

 public float getPrice() {
  return price;
 }

 public void setPrice(float price) {
  this.price = price;
  //设置变化点
  setChanged();
  notifyObservers(new Float(price));
 }
 
 public void saveToDb(){
  System.out.println("saveToDb");
 }
}

//------------ 两个具体观察者角色
package Observer.javaObserver;

import java.util.Observable;
import java.util.Observer;

public class NameObserver implements Observer {

 private String name = null;
 
 public void update(Observable obj, Object arg){
  if(arg instanceof String){
   name = (String)arg;
   System.out.println("NameObserver:name changed to " + name);
  }
 }
}

package Observer.javaObserver;

import java.util.Observable;
import java.util.Observer;

public class PriceObserver implements Observer{

 private float price=0;
 
 public void update(Observable obj, Object arg){
  if(arg instanceof Float){
   price = ((Float)arg).floatValue();
   System.out.println("PriceObserver: price change to" + price);
  }
 }
}

//----测试程序

package Observer.javaObserver;

import java.util.Observer;

public class Test {

 public static void main(String[] args){
  Product product = new Product();
  Observer nameObs = new NameObserver();
  Observer priceObs = new PriceObserver();
  
  product.addObserver(nameObs);
  product.addObserver(priceObs);
  
  product.setName("apple");
  product.setPrice(9.22f);
  
  product.setName("Apple");
  product.setPrice(9.88f);
  }
}


//---运行结果

NameObserver:name changed to apple
PriceObserver: price change to9.22
NameObserver:name changed to Apple
PriceObserver: price change to9.88



posted @ 2007-03-24 17:03 chenweicai 阅读(871) | 评论 (0)编辑 收藏

对于这个系列里的问题,每个学Java的人都应该搞懂。当然,如果只是学Java玩玩就无所谓了。如果你认为自己已经超越初学者了,却不很懂这些问题,请将你自己重归初学者行列。内容均来自于CSDN的经典老贴。

问题一:我声明了什么!

String s = "Hello world!";

许多人都做过这样的事情,但是,我们到底声明了什么?回答通常是:一个String,内容是“Hello world!”。这样模糊的回答通常是概念不清的根源。如果要准确的回答,一半的人大概会回答错误。
这个语句声明的是一个指向对象的引用,名为“s”,可以指向类型为String的任何对象,目前指向"Hello world!"这个String类型的对象。这就是真正发生的事情。我们并没有声明一个String对象,我们只是声明了一个只能指向String对象的引用变量。所以,如果在刚才那句语句后面,如果再运行一句:

String string = s;

我们是声明了另外一个只能指向String对象的引用,名为string,并没有第二个对象产生,string还是指向原来那个对象,也就是,和s指向同一个对象。

问题二:"=="和equals方法究竟有什么区别?

==操作符专门用来比较变量的值是否相等。比较好理解的一点是:
int a=10;
int b=10;
则a==b将是true。
但不好理解的地方是:
String a=new String("foo");
String b=new String("foo");
则a==b将返回false。

根据前一帖说过,对象变量其实是一个引用,它们的值是指向对象所在的内存地址,而不是对象本身。a和b都使用了new操作符,意味着将在内存中产生两个内容为"foo"的字符串,既然是“两个”,它们自然位于不同的内存地址。a和b的值其实是两个不同的内存地址的值,所以使用"=="操作符,结果会是false。诚然,a和b所指的对象,它们的内容都是"foo",应该是“相等”,但是==操作符并不涉及到对象内容的比较。
对象内容的比较,正是equals方法做的事。

看一下Object对象的equals方法是如何实现的:
boolean equals(Object o){

return this==o;

}
Object对象默认使用了==操作符。所以如果你自创的类没有覆盖equals方法,那你的类使用equals和使用==会得到同样的结果。同样也可以看出,Object的equals方法没有达到equals方法应该达到的目标:比较两个对象内容是否相等。因为答案应该由类的创建者决定,所以Object把这个任务留给了类的创建者。

看一下一个极端的类:
Class Monster{
private String content;
...
boolean equals(Object another){ return true;}

}
我覆盖了equals方法。这个实现会导致无论Monster实例内容如何,它们之间的比较永远返回true。

所以当你是用equals方法判断对象的内容是否相等,请不要想当然。因为可能你认为相等,而这个类的作者不这样认为,而类的equals方法的实现是由他掌握的。如果你需要使用equals方法,或者使用任何基于散列码的集合(HashSet,HashMap,HashTable),请察看一下java doc以确认这个类的equals逻辑是如何实现的。

问题三:String到底变了没有?

没有。因为String被设计成不可变(immutable)类,所以它的所有对象都是不可变对象。请看下列代码:

String s = "Hello";
s = s + " world!";

s所指向的对象是否改变了呢?从本系列第一篇的结论很容易导出这个结论。我们来看看发生了什么事情。在这段代码中,s原先指向一个String对象,内容是"Hello",然后我们对s进行了+操作,那么s所指向的那个对象是否发生了改变呢?答案是没有。这时,s不指向原来那个对象了,而指向了另一个String对象,内容为"Hello world!",原来那个对象还存在于内存之中,只是s这个引用变量不再指向它了。
通过上面的说明,我们很容易导出另一个结论,如果经常对字符串进行各种各样的修改,或者说,不可预见的修改,那么使用String来代表字符串的话会引起很大的内存开销。因为String对象建立之后不能再改变,所以对于每一个不同的字符串,都需要一个String对象来表示。这时,应该考虑使用StringBuffer类,它允许修改,而不是每个不同的字符串都要生成一个新的对象。并且,这两种类的对象转换十分容易。
同时,我们还可以知道,如果要使用内容相同的字符串,不必每次都new一个String。例如我们要在构造器中对一个名叫s的String引用变量进行初始化,把它设置为初始值,应当这样做:
public class Demo {
private String s;
...
public Demo {
s = "Initial Value";
}
...
}
而非
s = new String("Initial Value");
后者每次都会调用构造器,生成新对象,性能低下且内存开销大,并且没有意义,因为String对象不可改变,所以对于内容相同的字符串,只要一个String对象来表示就可以了。也就说,多次调用上面的构造器创建多个对象,他们的String类型属性s都指向同一个对象。
上面的结论还基于这样一个事实:对于字符串常量,如果内容相同,Java认为它们代表同一个String对象。而用关键字new调用构造器,总是会创建一个新的对象,无论内容是否相同。
至于为什么要把String类设计成不可变类,是它的用途决定的。其实不只String,很多Java标准类库中的类都是不可变的。在开发一个系统的时候,我们有时候也需要设计不可变类,来传递一组相关的值,这也是面向对象思想的体现。不可变类有一些优点,比如因为它的对象是只读的,所以多线程并发访问也不会有任何问题。当然也有一些缺点,比如每个不同的状态都要一个对象来代表,可能会造成性能上的问题。所以Java标准类库还提供了一个可变版本,即StringBuffer。

问题四:final关键字到底修饰了什么?

final使得被修饰的变量"不变",但是由于对象型变量的本质是“引用”,使得“不变”也有了两种含义:引用本身的不变,和引用指向的对象不变。

引用本身的不变:
final StringBuffer a=new StringBuffer("immutable");
final StringBuffer b=new StringBuffer("not immutable");
a=b;//编译期错误

引用指向的对象不变:
final StringBuffer a=new StringBuffer("immutable");
a.append(" broken!"); //编译通过

可见,final只对引用的“值”(也即它所指向的那个对象的内存地址)有效,它迫使引用只能指向初始指向的那个对象,改变它的指向会导致编译期错误。至于它所指向的对象的变化,final是不负责的。这很类似==操作符:==操作符只负责引用的“值”相等,至于这个地址所指向的对象内容是否相等,==操作符是不管的。

理解final问题有很重要的含义。许多程序漏洞都基于此----final只能保证引用永远指向固定对象,不能保证那个对象的状态不变。在多线程的操作中,一个对象会被多个线程共享或修改,一个线程对对象无意识的修改可能会导致另一个使用此对象的线程崩溃。一个错误的解决方法就是在此对象新建的时候把它声明为final,意图使得它“永远不变”。其实那是徒劳的。

问题五:到底要怎么样初始化!

本问题讨论变量的初始化,所以先来看一下Java中有哪些种类的变量。
1. 类的属性,或者叫值域
2. 方法里的局部变量
3. 方法的参数

对于第一种变量,Java虚拟机会自动进行初始化。如果给出了初始值,则初始化为该初始值。如果没有给出,则把它初始化为该类型变量的默认初始值。

int类型变量默认初始值为0
float类型变量默认初始值为0.0f
double类型变量默认初始值为0.0
boolean类型变量默认初始值为false
char类型变量默认初始值为0(ASCII码)
long类型变量默认初始值为0
所有对象引用类型变量默认初始值为null,即不指向任何对象。注意数组本身也是对象,所以没有初始化的数组引用在自动初始化后其值也是null

对于两种不同的类属性,static属性与instance属性,初始化的时机是不同的。instance属性在创建实例的时候初始化,static属性在类加载,也就是第一次用到这个类的时候初始化,对于后来的实例的创建,不再次进行初始化。这个问题会在以后的系列中进行详细讨论。

对于第二种变量,必须明确地进行初始化。如果再没有初始化之前就试图使用它,编译器会抗议。如果初始化的语句在try块中或if块中,也必须要让它在第一次使用前一定能够得到赋值。也就是说,把初始化语句放在只有if块的条件判断语句中编译器也会抗议,因为执行的时候可能不符合if后面的判断条件,如此一来初始化语句就不会被执行了,这就违反了局部变量使用前必须初始化的规定。但如果在else块中也有初始化语句,就可以通过编译,因为无论如何,总有至少一条初始化语句会被执行,不会发生使用前未被初始化的事情。对于try-catch也是一样,如果只有在try块里才有初始化语句,编译部通过。如果在catch或finally里也有,则可以通过编译。总之,要保证局部变量在使用之前一定被初始化了。所以,一个好的做法是在声明他们的时候就初始化他们,如果不知道要出事化成什么值好,就用上面的默认值吧!

其实第三种变量和第二种本质上是一样的,都是方法中的局部变量。只不过作为参数,肯定是被初始化过的,传入的值就是初始值,所以不需要初始化。

问题六:instanceof是什么东东?

instanceof是Java的一个二元操作符,和==,>,<是同一类东东。由于它是由字母组成的,所以也是Java的保留关键字。它的作用是测试它左边的对象是否是它右边的类的实例,返回boolean类型的数据。举个例子:

String s = "I AM an Object!";
boolean isObject = s instanceof Object;

我们声明了一个String对象引用,指向一个String对象,然后用instancof来测试它所指向的对象是否是Object类的一个实例,显然,这是真的,所以返回true,也就是isObject的值为True。
instanceof有一些用处。比如我们写了一个处理账单的系统,其中有这样三个类:

public class Bill {//省略细节}
public class PhoneBill extends Bill {//省略细节}
public class GasBill extends Bill {//省略细节}

在处理程序里有一个方法,接受一个Bill类型的对象,计算金额。假设两种账单计算方法不同,而传入的Bill对象可能是两种中的任何一种,所以要用instanceof来判断:

public double calculate(Bill bill) {
if (bill instanceof PhoneBill) {
//计算电话账单
}
if (bill instanceof GasBill) {
//计算燃气账单
}
...
}
这样就可以用一个方法处理两种子类。

然而,这种做法通常被认为是没有好好利用面向对象中的多态性。其实上面的功能要求用方法重载完全可以实现,这是面向对象变成应有的做法,避免回到结构化编程模式。只要提供两个名字和返回值都相同,接受参数类型不同的方法就可以了:

public double calculate(PhoneBill bill) {
//计算电话账单
}

public double calculate(GasBill bill) {
//计算燃气账单
}

所以,使用instanceof在绝大多数情况下并不是推荐的做法,应当好好利用多态。

 

posted @ 2007-03-23 20:17 chenweicai 阅读(144) | 评论 (0)编辑 收藏

Flyweight模式定义:
避免大量拥有相同内容的小类的开销(如耗费内存),使大家共享一个类(元类).

为什么使用?
面向对象语言的原则就是一切都是对象,但是如果真正使用起来,有时对象数可能显得很庞大,比如,字处理软件,如果以每个文字都作为一个对象,几千个字,对象数就是几千,无疑耗费内存,那么我们还是要"求同存异",找出这些对象群的共同点,设计一个元类,封装可以被共享的类,另外,还有一些特性是取决于应用(context),是不可共享的,这也Flyweight中两个重要概念内部状态intrinsic和外部状态extrinsic之分.

说白点,就是先捏一个的原始模型,然后随着不同场合和环境,再产生各具特征的具体模型,很显然,在这里需要产生不同的新对象,所以Flyweight模式中常出现Factory模式.Flyweight的内部状态是用来共享的,Flyweight factory负责维护一个Flyweight pool(模式池)来存放内部状态的对象.

Flyweight模式是一个提高程序效率和性能的模式,会大大加快程序的运行速度.应用场合很多:比如你要从一个数据库中读取一系列字符串,这些字符串中有许多是重复的,那么我们可以将这些字符串储存在Flyweight池(pool)中.

如何使用?

我们先从Flyweight抽象接口开始:

public interface Flyweight
{
  public void operation( ExtrinsicState state );
}

//用于本模式的抽象数据类型(自行设计)
public interface ExtrinsicState { }

下面是接口的具体实现(ConcreteFlyweight) ,并为内部状态增加内存空间, ConcreteFlyweight必须是可共享的,它保存的任何状态都必须是内部(intrinsic),也就是说,ConcreteFlyweight必须和它的应用环境场合无关.

public class ConcreteFlyweight implements Flyweight {
  private IntrinsicState state;
  
  public void operation( ExtrinsicState state )
  {
      //具体操作
  }

}

当然,并不是所有的Flyweight具体实现子类都需要被共享的,所以还有另外一种不共享的ConcreteFlyweight:

public class UnsharedConcreteFlyweight implements Flyweight {

  public void operation( ExtrinsicState state ) { }

}

Flyweight factory负责维护一个Flyweight池(存放内部状态),当客户端请求一个共享Flyweight时,这个factory首先搜索池中是否已经有可适用的,如果有,factory只是简单返回送出这个对象,否则,创建一个新的对象,加入到池中,再返回送出这个对象.池

public class FlyweightFactory {
  //Flyweight pool
  private Hashtable flyweights = new Hashtable();

  public Flyweight getFlyweight( Object key ) {

    Flyweight flyweight = (Flyweight) flyweights.get(key);

    if( flyweight == null ) {
      //产生新的ConcreteFlyweight
      flyweight = new ConcreteFlyweight();
      flyweights.put( key, flyweight );
    }

    return flyweight;
  }
}

至此,Flyweight模式的基本框架已经就绪,我们看看如何调用:

FlyweightFactory factory = new FlyweightFactory();
Flyweight fly1 = factory.getFlyweight( "Fred" );
Flyweight fly2 = factory.getFlyweight( "Wilma" );
......

从调用上看,好象是个纯粹的Factory使用,但奥妙就在于Factory的内部设计上.

Flyweight模式在XML等数据源中应用
我们上面已经提到,当大量从数据源中读取字符串,其中肯定有重复的,那么我们使用Flyweight模式可以提高效率,以唱片CD为例,在一个XML文件中,存放了多个CD的资料.

每个CD有三个字段:
1.出片日期(year)
2.歌唱者姓名等信息(artist)
3.唱片曲目 (title)

其中,歌唱者姓名有可能重复,也就是说,可能有同一个演唱者的多个不同时期 不同曲目的CD.我们将"歌唱者姓名"作为可共享的ConcreteFlyweight.其他两个字段作为UnsharedConcreteFlyweight.

首先看看数据源XML文件的内容:


<?xml version="1.0"?>
<collection>

<cd>
<title>Another Green World</title>
<year>1978</year>
<artist>Eno, Brian</artist>
</cd>

<cd>
<title>Greatest Hits</title>
<year>1950</year>
<artist>Holiday, Billie</artist>
</cd>

<cd>
<title>Taking Tiger Mountain (by strategy)</title>
<year>1977</year>
<artist>Eno, Brian</artist>
</cd>

.......

</collection>


虽然上面举例CD只有3张,CD可看成是大量重复的小类,因为其中成分只有三个字段,而且有重复的(歌唱者姓名).

CD就是类似上面接口 Flyweight:


public class CD {

  private String title;
  private int year;
  private Artist artist;

  public String getTitle() {  return title; }
  public int getYear() {    return year;  }
  public Artist getArtist() {    return artist;  }

  public void setTitle(String t){    title = t;}
  public void setYear(int y){year = y;}
  public void setArtist(Artist a){artist = a;}

}

将"歌唱者姓名"作为可共享的ConcreteFlyweight:

public class Artist {

  //内部状态
  private String name;

  // note that Artist is immutable.
  String getName(){return name;}

  Artist(String n){
    name = n;
  }

}

再看看Flyweight factory,专门用来制造上面的可共享的ConcreteFlyweight:Artist

public class ArtistFactory {

  Hashtable pool = new Hashtable();

  Artist getArtist(String key){

    Artist result;
    result = (Artist)pool.get(key);
    ////产生新的Artist
    if(result == null) {
      result = new Artist(key);
      pool.put(key,result);
      
    }
    return result;
  }

}

当你有几千张甚至更多CD时,Flyweight模式将节省更多空间,共享的flyweight越多,空间节省也就越大.


给个例子,coffee商店

package FlyWeight.coffeeshop;

public class Table {
 
 private int number;

 public int getNumber() {
  return number;
 }

 public void setNumber(int number) {
  this.number = number;
 }

 public Table(int number) {
  super();
  // TODO Auto-generated constructor stub
  this.number = number;
 }

}


package FlyWeight.coffeeshop;

public abstract class Order {
 
 public abstract void serve(Table table);
 
 public abstract String getFlavor();

}


package FlyWeight.coffeeshop;

public class Flavor extends Order {

 private String flavor;

 public Flavor(String flavor) {
  super();
  // TODO Auto-generated constructor stub
  this.flavor = flavor;
 }

 public String getFlavor() {
  return flavor;
 }

 public void setFlavor(String flavor) {
  this.flavor = flavor;
 }

 public void serve(Table table) {
  System.out.println("Serving table " + table.getNumber() + " with flavor " + flavor );
 } 
}

package FlyWeight.coffeeshop;

public class FlavorFactory {

 private Order[] flavors = new Flavor[10];
 private int ordersMade = 0;//已经处理好的订单数
 private int totalFlavors = 0;//已购买的coffee风味种类数
 
 public Order getOrder(String flavorToGet){
  if(ordersMade > 0){
   for(int i=0; i<ordersMade; i++){
    if(flavorToGet.equalsIgnoreCase(flavors[i].getFlavor()))
     return flavors[i];
   }
  }
  flavors[ordersMade] = new Flavor(flavorToGet);
  totalFlavors++;
  return flavors[ordersMade++];
 }
 
 public int getTotalFlavorsMade(){
  return totalFlavors;
 }
}


package FlyWeight.coffeeshop;

public class Client {
 
 private static Order[] flavors = new Flavor[100];
 
 private static int ordersMade = 0;
 private static FlavorFactory flavorFactory;
 
 private static void takeOrders(String aFlavor){
  flavors[ordersMade++] = flavorFactory.getOrder(aFlavor);
 }
 
 public static void main(String[] args){
  flavorFactory = new FlavorFactory();
  
     takeOrders("Black Coffee");
     takeOrders("Capucino");
     takeOrders("Espresso");
     takeOrders("Espresso");
     takeOrders("Capucino");
     takeOrders("Capucino");
     takeOrders("Black Coffee");
     takeOrders("Espresso");
     takeOrders("Capucino");
     takeOrders("Black Coffee");
     takeOrders("Espresso");
    
     for(int i=0; i<ordersMade; i++){
      flavors[i].serve(new Table(i));
     }
    
     System.out.println("\nTotal Flavor objrcts made: " +
       flavorFactory.getTotalFlavorsMade());
 }
 
}

//-------------------------------------------------------------------

运行结果:

Serving table 0 with flavor Black Coffee
Serving table 1 with flavor Capucino
Serving table 2 with flavor Espresso
Serving table 3 with flavor Espresso
Serving table 4 with flavor Capucino
Serving table 5 with flavor Capucino
Serving table 6 with flavor Black Coffee
Serving table 7 with flavor Espresso
Serving table 8 with flavor Capucino
Serving table 9 with flavor Black Coffee
Serving table 10 with flavor Espresso

Total Flavor objrcts made: 3

posted @ 2007-03-23 19:45 chenweicai 阅读(291) | 评论 (0)编辑 收藏

关 键 词: Java  模式
阅读提示:装饰模式是在不必改变原类文件和使用继承的情况下,动态的扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。

说明:

装饰模式是在不必改变原类文件和使用继承的情况下,动态的扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。

装饰模式的特点;

(1) 装饰对象和真实对象有相同的接口。这样客户端对象就可以以和真实对象相同的方式和装饰对象交互。

(2) 装饰对象包含一个真实对象的索引(reference)

(3) 装饰对象接受所有的来自客户端的请求。它把这些请求转发给真实的对象。

(4) 装饰对象可以在转发这些请求以前或以后增加一些附加功能。这样就确保了在运行时,不用修改给定对象的结构就可以在外部增加附加的功能。在面向对象的设计中,通常是通过继承来实现对给定类的功能扩展。

下表格列举了装饰模式和继承的不同:

装饰模式 VS 继承

装饰模式 继承

用来扩展特定对象的功能 用来扩展一类对象的功能

不需要子类 需要子类

动态地 静态地

运行时分配职责 编译时分派职责

防止由于子类而导致的复杂和混乱 导致很多子类产生,在一些场合,报漏类的层次

更多的灵活性 缺乏灵活性

对于一个给定的对象,同时可能有不同的装饰对象,客户端可以通过它的需要选择合适的装饰对象发送消息。 对于所有可能的联合,客户期望

很容易增加任何的 困难

给个实例,打印发票



package Decorator.printorder;
import java.text.NumberFormat;
public class OrderLine {
 private String itemName;
 private int units;
 private double unitPrice;
 
 public String getItemName() {
  return itemName;
 }
 
 public void setItemName(String itemName) {
  this.itemName = itemName;
 }
 
 public double getUnitPrice() {
  return unitPrice;
 }
 
 public void setUnitPrice(double unitPrice) {
  this.unitPrice = unitPrice;
 }
 
 public int getUnits() {
  return units;
 }
 
 public void setUnits(int units) {
  this.units = units;
 }
 
 public void printLine(){
  System.out.println(itemName + "\t" + units
    + "\t" + formatCurrency(unitPrice)
    + "\t\t" + formatCurrency(getSubtotal()));
 }
 
 public double getSubtotal(){
  return units * unitPrice;
 }
 
 private String formatCurrency(double amnt){
  return NumberFormat.getCurrencyInstance().format(amnt);
 }
}


package Decorator.printorder;
import java.text.NumberFormat;
import java.util.Date;
import java.util.Vector;
abstract public class Order {
 
 private OrderLine lnkOrderLine;
 protected String customerName;
 protected Date salesDate;
 protected Vector items = new Vector(10);
 
 public void print(){
  for(int i=0; i<items.size(); i++){
   OrderLine item = (OrderLine) items.get(i);
   item.printLine();
  }
 }
 public String getCustomerName() {
  return customerName;
 }
 public void setCustomerName(String customerName) {
  this.customerName = customerName;
 }
 public Date getSalesDate() {
  return salesDate;
 }
 public void setSalesDate(Date salesDate) {
  this.salesDate = salesDate;
 }
 
 public void addItem(OrderLine item){
  items.add(item);
 }
 
 public void remove(OrderLine item){
  items.remove(item);
 }
 
 public double getGrandTotal(){
  double amnt = 0.0D;
  for(int i=0; i<items.size(); i++){
   OrderLine item = (OrderLine) items.get(i);
   amnt += item.getSubtotal();
  }
  return amnt;
 }
 
 protected String formatCurrency(double amnt){
  return NumberFormat.getCurrencyInstance().format(amnt);
 }
}


package Decorator.printorder;
public class SalesOrder extends Order {
 public SalesOrder() {
  super();
  // TODO Auto-generated constructor stub
 }
 public void print() {
  // TODO Auto-generated method stub
  super.print();
 }
 
}


package Decorator.printorder;
public abstract class OrderDecorator extends Order{
 protected Order order;
 
 public OrderDecorator(Order order){
  this.order = order;
  this.setSalesDate(this.order.getSalesDate());
  this.setCustomerName(this.order.getCustomerName());
 }
}


package Decorator.printorder;
public class HeaderDecorator extends OrderDecorator {
 public HeaderDecorator(Order order) {
  super(order);
  // TODO Auto-generated constructor stub
 }
 public void print(){
  this.printHeader();
  super.order.print();
 }
 
 private void printHeader(){
  System.out.println("\t***\tI N V O I C E\t***\nXYZ Incorporated\nDate of Sale: "
    + order.getSalesDate());
        System.out.println("========================================================");
        System.out.println("Item\t\tUnits\tUnit Price\tSubtotal");
 }
}


package Decorator.printorder;
public class FooterDecorator extends OrderDecorator {
 public FooterDecorator(Order order) {
  super(order);
  // TODO Auto-generated constructor stub
 }
 public void print(){
  super.order.print();
  printFooter();
 }
 
 private void printFooter(){
        System.out.println("========================================================");
        System.out.println("Total\t\t\t\t\t" +
            formatCurrency(super.order.getGrandTotal()));
 }
}


package Decorator.printorder;
import java.util.Date;

public class Client {
 private static Order order;
 
 public static void main(String[] args){
  order = new SalesOrder();
  order.setSalesDate(new Date());
  order.setCustomerName("XYZ Repair Shop");
  
        OrderLine line1 = new OrderLine();
        line1.setItemName("FireWheel Tire");
        line1.setUnitPrice(154.23);
        line1.setUnits(4);
        order.addItem(line1);
        OrderLine line2 = new OrderLine();
        line2.setItemName("Front Fender");
        line2.setUnitPrice(300.45);
        line2.setUnits(1);
        order.addItem(line2);
       
        order = new HeaderDecorator(new FooterDecorator(order));
       
        order.print();
       
 }
}


posted @ 2007-03-23 15:56 chenweicai 阅读(959) | 评论 (0)编辑 收藏

小结:适配器模式用插座的适配器最为形象,插头是2口的,插座是3口的,中间的适配器就是同时支持2口和三口的。从对象的角度就是一般继承一个实现一个,总之,前方百计把两者都关联起来 。


通常,客户类(clients of class)通过类的接口访问它提供的服务。有时,现有的类(existing class)可以提供客户类的功能需要,但是它所提供的接口不一定是客户类所期望的。这是由于现有的接口太详细或者缺乏详细或接口的名称与客户类所查找的不同等诸多不同原因导致的。

  在这种情况下,现有的接口需要转化(convert)为客户类期望的接口,这样保证了对现有类的重用。如果不进行这样的转化,客户类就不能利用现有类所提供的功能。适配器模式(Adapter Pattern)可以完成这样的转化。适配器模式建议定义一个包装类,包装有不兼容接口的对象。这个包装类指的就是适配器(Adapter),它包装的对象就是适配者(Adaptee)。适配器提供客户类需要的接口,适配器接口的实现是把客户类的请求转化为对适配者的相应接口的调用。换句话说:当客户类调用适配器的方法时,在适配器类的内部调用适配者类的方法,这个过程对客户类是透明的,客户类并不直接访问适配者类。因此,适配器可以使由于借口不兼容而不能交互的类可以一起工作(work together)。

  在上面讨论的接口:

  (1)    不是指在JAVA编程语言中接口的概念,虽然类的接口可以通过JAVA借扩来定义。

  (2)    不是指由窗体和GUI控件所组成的GUI应用程序的用户接口。

  (3)    而是指类所报漏的,被其他类调用的编程接口,

  类适配器(Class Adapter)VS对象适配器(Object Adapter)

  适配器总体上可以分为两类??类适配器(Class Adapter)VS对象适配器(Object Adapter)
    

 类适配器:


  类适配器是通过继承类适配者类(Adaptee Class)实现的,另外类适配器实现客户类所需要的接口。当客户对象调用适配器类方法的时候,适配器内部调用它所继承的适配者的方法。
    

 对象适配器:

  对象适配器包含一个适配器者的引用(reference),与类适配器相同,对象适配器也实现了客户类需要的接口。当客户对象调用对象适配器的方法的时候,对象适配器调它所包含的适配器者实例的适当方法。

  下表是类适配器(Class Adapter)和对象适配器(Object Adapter)的详细不同

  
    
  例子:

  让我们建立一个验证给定客户地址的应用。这个应用是作为大的客户数据管理应用的一部分。

  让我们定义一个Customer类:

Customer 



Figure 20.1: Customer Class 
Listing 20.1: Customer Class 
  1. class Customer { 
  2.   public static final String US = "US"
  3.   public static final String CANADA = "Canada"
  4.   private String address; 
  5.   private String name; 
  6.   private String zip, state, type; 
  7.   public boolean isValidAddress() { 
  8.           … 
  9.           … 
  10.   } 
  11.   public Customer(String inp_name, String inp_address, 
  12.                   String inp_zip, String inp_state, 
  13.                   String inp_type) { 
  14.     name = inp_name; 
  15.     address = inp_address; 
  16.     zip = inp_zip; 
  17.     state = inp_state; 
  18.     type = inp_type; 
  19.   } 
  20. }//end of class 
  不同的客户对象创建Customer对象并调用(invoke)isValidAddress方法验证客户地址的有效性。为了验证客户地址的有效性, Customer类期望利用一个地址验证类(address validator class),这个验证类提供了在接口 AddressValidator中声明的接口。

  Listing 20.2: AddressValidator as an Interface 
  1. public interface AddressValidator { 
  2.   public boolean isValidAddress(String inp_address, 
  3.      String inp_zip, String inp_state); 
  4. }//end of class 

  让我们定义一个USAddress的验证类,来验证给定的U.S地址。

  Listing 20.3: USAddress Class 
  1. class USAddress implements AddressValidator { 
  2.   public boolean isValidAddress(String inp_address, 
  3.      String inp_zip, String inp_state) { 
  4.    if (inp_address.trim().length() < 10) 
  5.      return false
  6.    if (inp_zip.trim().length() < 5) 
  7.      return false
  8.    if (inp_zip.trim().length() > 10) 
  9.      return false
  10.    if (inp_state.trim().length() != 2) 
  11.      return false
  12.    return true
  13.   } 
  14. }//end of class 

  USAddress类实现AddressValidator接口,因此Customer对象使用USAddress实例作为验证客户地址过程的一部分是没有任何问题的。

  Listing 20.4: Customer Class Using the USAddress Class 
  1. class Customer { 
  2.           … 
  3.           … 
  4.  public boolean isValidAddress() { 
  5.    //get an appropriate address validator 
  6.    AddressValidator validator = getValidator(type); 
  7.    //Polymorphic call to validate the address 
  8.    return validator.isValidAddress(address, zip, state); 
  9.  } 
  10.  private AddressValidator getValidator(String custType) { 
  11.    AddressValidator validator = null
  12.    if (custType.equals(Customer.US)) { 
  13.      validator = new USAddress(); 
  14.    } 
  15.    return validator; 
  16.  } 
  17. }//end of class 
 


Figure 20.2: Customer/USAddress Validator?Class Association 

  但是当验证来自加拿大的客户时,就要对应用进行改进。这需要一个验证加拿大客户地址的验证类。让我们假设已经存在一个用来验证加拿大客户地址的使用工具类CAAddress。

从下面的CAAdress类的实现,可以发现CAAdress提供了客户类Customer类所需要的验证服务。但是它所提供的接口不用于客户类Customer所期望的。

  Listing 20.5: CAAdress Class with Incompatible Interface 
  1. class CAAddress { 
  2.   public boolean isValidCanadianAddr(String inp_address, 
  3.      String inp_pcode, String inp_prvnc) { 
  4.    if (inp_address.trim().length() < 15) 
  5.      return false
  6.    if (inp_pcode.trim().length() != 6) 
  7.      return false
  8.    if (inp_prvnc.trim().length() < 6) 
  9.      return false
  10.    return true
  11.   } 
  12. }//end of class 

  CAAdress类提供了一个isValidCanadianAddr方法,但是Customer期望一个声明在AddressValidator接口中的isValidAddress方法。

  接口的不兼容使得Customer对象利用现有的CAAdress类是困难的。一种意见是改变CAAdress类的接口,但是可能会有其他的应用正在使用CAAdress类的这种形式。改变CAAdress类接口会影响现在使用CAAdress类的客户。

  应用适配器模式,类适配器CAAdressAdapter可以继承CAAdress类实现AddressValidator接口。

 


  Figure 20.3: Class Adapter for the CAAddress Class 
Listing 20.6: CAAddressAdapter as a Class Adapter 
  1. public class CAAddressAdapter extends CAAddress 
  2.   implements AddressValidator { 
  3.   public boolean isValidAddress(String inp_address, 
  4.      String inp_zip, String inp_state) { 
  5.     return isValidCanadianAddr(inp_address, inp_zip, 
  6.            inp_state); 
  7.   } 
  8. }//end of class 

  因为适配器CAAdressAdapter实现了AddressValidator接口,客户端对象访问适配器CAAdressAdapter对象是没有任何问题的。当客户对象调用适配器实例的isValidAddress方法的时候,适配器在内部把调用传递给它继承的 isValidCanadianAddr方法。

  在Customer类内部,getValidator私有方法需要扩展,以至于它可以在验证加拿大客户的时候返回一个CAAdressAdapter实例。返回的对象是多态的,USAddress和CAAddressAdapter都实现了AddressValidator接口,所以不用改变。

Listing 20.7: Customer Class Using the CAAddressAdapter Class 
  1. class Customer { 
  2.           … 
  3.           … 
  4.   public boolean isValidAddress() { 
  5.     //get an appropriate address validator 
  6.     AddressValidator validator = getValidator(type); 
  7.     //Polymorphic call to validate the address 
  8.     return validator.isValidAddress(address, zip, state); 
  9.   } 
  10.   private AddressValidator getValidator(String custType) { 
  11.     AddressValidator validator = null
  12.     if (custType.equals(Customer.US)) { 
  13.       validator = new USAddress(); 
  14.     } 
  15.     if (type.equals(Customer.CANADA)) { 
  16.       validator = new CAAddressAdapter(); 
  17.     } 
  18.     return validator; 
  19.   } 
  20. }//end of class 
  CAAddressAdapter设计和对AddressValidator(声明期望的接口)对象的多态调用使Customer可以利用接口不兼容CAAddress类提供的服务。

 


  Figure 20.4: Address Validation Application?Using Class Adapter 

 


  Figure 20.5: Address Validation Message Flow?Using Class Adapter 

  作为对象适配器的地址适配器

  当讨论以类适配器来实现地址适配器时,我们说客户类期望的AddressValidator接口是Java接口形式。现在,让我们假设客户类期望 AddressValidator接口是抽象类而不是java接口。因为适配器CAAdapter必须提供抽象类AddressValidatro中声明的接口,适配器必须是AddressValidator抽象类的子类、实现抽象方法。
  1. Listing 20.8: AddressValidator as an Abstract Class 
  2. public abstract class AddressValidator { 
  3.   public abstract boolean isValidAddress(String inp_address, 
  4.      String inp_zip, String inp_state); 
  5. }//end of class 
  6. Listing 20.9: CAAddressAdapter Class 
  7. class CAAddressAdapter extends AddressValidator { 
  8.           … 
  9.           … 
  10.   public CAAddressAdapter(CAAddress address) { 
  11.     objCAAddress = address; 
  12.   } 
  13.   public boolean isValidAddress(String inp_address, 
  14.      String inp_zip, String inp_state) { 
  15.           … 
  16.           … 
  17.   } 
  18. }//end of class 

  因为多继承在JAVA中不支持,现在适配器CAAddressAdapter不能继承现有的CAAddress类,它已经使用了唯一一次继承其他类的机会。

  应用对象适配器模式,CAAddressAdapter可以包含一个适配者CAAddress的一个实例。当适配器第一次创建的时候,这个适配者的实例通过客户端传递给适配器。通常,适配者实例可以通过下面两种方式提供给包装它的适配器。

  (1)    对象适配器的客户端可以传递一个适配者的实例给适配器。这种方式在选择类的形式上有很大的灵活性,但是客户端感知了适配者或者适配过程。这种方法在适配器不但需要适配者对象行为而且需要特定状态时很适合。

  (2)    适配器可以自己创建适配者实例。这种方法相对来说缺乏灵活性。适用于适配器只需要适配者对象的行为而不需要适配者对象的特定状态的情况。

 


  Figure 20.6: Object Adapter for the CAAddress Class 

  Listing 20.10: CAAddressAdapter as an Object Adapter 
  1. class CAAddressAdapter extends AddressValidator { 
  2.   private CAAddress objCAAddress; 
  3.   public CAAddressAdapter(CAAddress address) { 
  4.     objCAAddress = address; 
  5.   } 
  6.   public boolean isValidAddress(String inp_address, 
  7.      String inp_zip, String inp_state) { 
  8.     return objCAAddress.isValidCanadianAddr(inp_address, 
  9.            inp_zip, inp_state); 
  10.   } 
  11. }//end of class 

  当客户对象调用CAAddressAdapter(adapter)上的isValidAddress方法时, 适配器在内部调用CAAddress(adaptee)上的isValidCanadianAddr方法。


 


  Figure 20.7: Address Validation Application?Using Object Adapter 

  从这个例子可以看出,适配器可以使Customer(client)类访问借口不兼容的CAAddress(adaptee)所提供的服务!

 



  Figure 20.8: Address Validation Message Flow?Using Object Adapter

posted @ 2007-03-23 13:12 chenweicai 阅读(333) | 评论 (0)编辑 收藏

Spring的模块化是很强的,各个功能模块都是独立的,我们可以选择的使用。这一章先从Spring的IoC开始。所谓IoC就是一个用XML来定义生成对象的模式,我们看看如果来使用的。
1、数据模型。
1、如下图所示有三个类,Human(人类)是接口,Chinese(中国人)是一个子类,American(美国人)是另外一个子类。


源代码如下:
package cn.com.chengang.spring;
public interface Human {
       void eat();
       void walk();
}
 
package cn.com.chengang.spring;
public class Chinese implements Human {
    /* (非 Javadoc)
     * @see cn.com.chengang.spring.Human#eat()
     */
    public void eat() {
        System.out.println("中国人对吃很有一套");
    }
 
    /* (非 Javadoc)
     * @see cn.com.chengang.spring.Human#walk()
     */
    public void walk() {
        System.out.println("中国人行如飞");
    }
}
 
package cn.com.chengang.spring;
public class American implements Human {
    /* (非 Javadoc)
     * @see cn.com.chengang.spring.Human#eat()
     */
    public void eat() {
        System.out.println("美国人主要以面包为主");
    }
 
    /* (非 Javadoc)
     * @see cn.com.chengang.spring.Human#walk()
     */
    public void walk() {
        System.out.println("美国人以车代步,有四肢退化的趋势");
    }
}
 
2、对以上对象采用工厂模式的用法如下
创建一个工厂类Factory,如下。这个工厂类里定义了两个字符串常量,所标识不同的人种。getHuman方法根据传入参数的字串,来判断要生成什么样的人种。
package cn.com.chengang.spring;
public class Factory {
    public final static String CHINESE = "Chinese";
    public final static String AMERICAN = "American";
 
    public Human getHuman(String ethnic) {
        if (ethnic.equals(CHINESE))
            return new Chinese();
        else if (ethnic.equals(AMERICAN))
            return new American();
        else
            throw new IllegalArgumentException("参数(人种)错误");
    }
}
 
下面是一个测试的程序,使用工厂方法来得到了不同的“人种对象”,并执行相应的方法。
package cn.com.chengang.spring;
public class ClientTest {
    public static void main(String[] args) {
        Human human = null;
        human = new Factory().getHuman(Factory.CHINESE);
        human.eat();
        human.walk();
        human = new Factory().getHuman(Factory.AMERICAN);
        human.eat();
        human.walk();
    }
}
 
控制台的打印结果如下:
 
3、采用Spring的IoC的用法如下:
1、在项目根目录下创建一个bean.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
       <bean id="Chinese" class="cn.com.chengang.spring.Chinese"/>
       <bean id="American" class="cn.com.chengang.spring.American"/>
</beans>
bean.xml的位置如下图,注意不要看花眼把它看成是lib目录下的了,它是在myspring目录下的。
 
2、修改ClientTest程序如下:
package cn.com.chengang.spring;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
public class ClientTest {
    public final static String CHINESE = "Chinese";
    public final static String AMERICAN = "American";
 
    public static void main(String[] args) {
        //        Human human = null;
        //        human = new Factory().getHuman(Factory.CHINESE);
        //        human.eat();
        //        human.walk();
        //        human = new Factory().getHuman(Factory.AMERICAN);
        //        human.eat();
        //        human.walk();
 
        ApplicationContext ctx = new FileSystemXmlApplicationContext("bean.xml");
        Human human = null;
        human = (Human) ctx.getBean(CHINESE);
        human.eat();
        human.walk();
        human = (Human) ctx.getBean(AMERICAN);
        human.eat();
        human.walk();
    }
}
从这个程序可以看到,ctx就相当于原来的Factory工厂,原来的Factory就可以删除掉了。然后又把Factory里的两个常量移到了ClientTest类里,整个程序结构基本一样。
再回头看原来的bean.xml文件的这一句
<bean id="Chinese" class="cn.com.chengang.spring.Chinese"/>
id就是ctx.getBean的参数值,一个字符串。class就是一个类(包名+类名)。然后在ClientTest类里获得Chinese对象就是这么一句
human = (Human) ctx.getBean(CHINESE);
因为getBean方法返回的是Object类型,所以前面要加一个类型转换。
 
4、总结
(1)也许有人说,IoC和工厂模式不是一样的作用吗,用IoC好象还麻烦一点。
       举个例子,如果用户需求发生变化,要把Chinese类修改一下。那么前一种工厂模式,就要更改Factory类的方法,并且重新编译布署。而IoC只需 要将class属性改变一下,并且由于IoC利用了Java反射机制,这些对象是动态生成的,这时我们就可以热插拨Chinese对象(不必把原程序停止 下来重新编译布署)
 
       (2)也许有人说,即然IoC这么好,那么我把系统所有对象都用IoC方式来生成。
       注意,IoC的灵活性是有代价的:设置步骤麻烦、生成对象的方式不直观、反射比正常生成对象在效率上慢一点。因此使用IoC要看有没有必要,我认为比较通用的判断方式是:用到工厂模式的地方都可以考虑用IoC模式。
 
       (3)在上面的IoC的方式里,还有一些可以变化的地方。比如,bean.xml不一定要放在项目录下,也可以放在其他地方,比如cn.com.chengang.spring包里。不过在使用时也要变化一下,如下所示:
new FileSystemXmlApplicationContext("src/cn/com/chengang/spring/bean.xml");
另外,bean.xml也可以改成其他名字。这样我们在系统中就可以分门别类的设置不同的bean.xml。
 
(4)关于IoC的低侵入性。
什 么是低侵入性?如果你用过Struts或EJB就会发现,要继承一些接口或类,才能利用它们的框架开发。这样,系统就被绑定在Struts、EJB上了, 对系统的可移植性产生不利的影响。如果代码中很少涉及某一个框架的代码,那么这个框架就可以称做是一个低侵入性的框架。
Spring的侵入性很低,Humen.java、Chinese.java等几个类都不必继承什么接口或类。但在ClientTest里还是有一些Spring的影子:FileSystemXmlApplicationContext类和ctx.getBean方式等。
现在,低侵入性似乎也成了判定一个框架的实现技术好坏的标准之一。
 
(5)关于bean.xml的用法
bean.xml 的用法还有很多,其中内容是相当丰富的。假设Chinese类里有一个humenName属性(姓名),那么原的bean.xml修改如下。此后生成 Chinese对象时,“陈刚”这个值将自动设置到Chinese类的humenName属性中。而且由于singleton为true这时生成 Chinese对象将采用单例模式,系统仅存在一个Chinese对象实例。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
       <bean id="Chinese" class="cn.com.chengang.spring.Chinese"  singleton="true">
              <property name="humenName">
                     <value>陈刚</value>
              </property>
       </bean>
       <bean id="American" class="cn.com.chengang.spring.American"/>
</beans>
 
关于bean.xml的其它用法,不再详细介绍了,大家自己拿Spring的文档一看就明白了。

posted @ 2007-03-21 21:15 chenweicai 阅读(153) | 评论 (0)编辑 收藏

  Action类是用户请求和业务逻辑之间的桥梁。每个Action充当用户的一项业务代理。在   RequestProcessor类预处理请求时,在创建了Action的实例后,就调用自身的processActionPerform()方法,该方法再调用Action类的execute()方法。Action的execute()方法调用模型的业务方法,完成用户请求的业务逻辑,然后根据执行结果把请求转发给其他合适的Web组件。在实际的应用中,主要有以下几种比较常见的使用方法:  
   
  1.普通的Action应用  
   
  <action   path="/normalAction"  
  type="package.OneActionClass">  
  name="oneForm"  
  input="page.jsp"  
  <forward   name="success"   path="success.jsp"/>  
  <forward   name="failure"   path="failure.jsp"/>  
  </action>  
   
          Struts的ActionServlet接收到一个请求,然后根据struts-config.xml的配置定位到相应的mapping(映射);接下来如果form的范围是request或者在定义的范围中找不到这个form,创建一个新的form实例,如果找到则重用;取得form实例以后,调用其   reset()方法,然后将表单中的参数放入form,如果validate属性不为false,调用validate()方法;如果validate   ()返回非空的ActionErrors,将会被转到input属性指定的URI,如果返回空的ActionErrors,那么执行Action的   execute()方法,根据返回的ActionForward确定目标URI。即execute()仅当validate()成功以后才执行;   input属性指定的是一个URI。  
   
   
  2.有Form的Action应用  
   
  <action   path="/formAction"  
  type="org.apache.struts.actions.ForwardAction"  
  name="oneForm"  
  input="page.jsp"  
  parameter="another.jsp"  
  />  
   
          Struts会在定义的scope搜寻oneForm,如果找到则重用,如果找不到则新建一个实例;取得form实例以后,调用其reset()方法,然后将表单中的参数放入form,如果validate属性不为false,调用validate()方法;如果validate()返回非空的   ActionErrors,将会被转到input属性指定的URI,如果返回空的ActionErrors,那么转到parameter属性指定的目标   URI。  
   
          这种方法使得没有action类可以存放我们的业务逻辑,所以所有需要写入的逻辑都只能写到form的reset()或者validate()方法中。   validate()的作用是验证和访问业务层。因为这里的action映射不包括forward,所以不能重定向,只能用默认的那个forward。这种仅有form的action可以用来处理数据获取并forward到另一个JSP来显示。  
   
   
  3.仅有Action的Action应用  
   
  <action   path="/actionAction"  
  type="package.OneActionClass">  
  input="page.jsp"  
  <forward   name="success"   path="success.jsp"/>  
  <forward   name="failure"   path="failure.jsp"/>  
  </action>  
   
          ActionServlet接收到请求后,取得action类实例,调用execute()方法;然后根据返回的ActionForward在配置中找   forward,forward到指定的URI或action。这样就没有form实例被传入execute()方法,于是execute()必须自己从请求中获取参数。Action可以被forward或者重定向。这种action不能处理通过HTML   FORM提交的请求,只能处理链接式的请求。  
   
   
  4.仅有JSP的Action应用  
   
  <action   path="/jspAction"  
  type="org.apache.struts.actions.ForwardAction"  
  parameter="another.jsp"  
  />  
   
          ActionServlet接到请求后调用ForwardAction的execute()方法,execute()根据配置的parameter属性值来forward到那个URI。这种情况下,没有任何form被实例化,比较现实的情形可能是form在request更高级别的范围中定义;或者这个   action被用作在应用程序编译好后充当系统参数,只需要更改这个配置文件而不需要重新编译系统。  
   
   
  5.两个Action对应一个Form(和第四种方式部分的Action作用相近)  
   
  <action   path="/oneAction"  
  type="package.OneActionClass">  
  name="oneForm"  
  input="one.jsp"  
  <forward   name="success"   path="/anotherAction.do"/>  
  </action>  
  <action   path="/anotherAction"  
  type="package.AnotherActionClass">  
  name="oneForm"  
  input="another.jsp"  
  <forward   name="success"   path="success.jsp"/>  
  </action>  
   
          这个组合模式可以被用来传递form对象,就每个单独的action来讲,处理上并没有和完整的action有什么实质的区别。需要注意的是在后一个   action中同样会调用form的reset()和validate()方法,因此我们必须确保form中的信息不被重写。这种情况分两种方式处理:   a)   在request中放入一个指示器表明前一个action有意向后一个action传递form,从而在后一个action可以保留那个form中的值,这一方式只能在使用forward时使用。b)   当使用redirect而不是forward时,可以把指示器放在session或更高的级别,在命令链的最后一环将这个指示器清除。  
   
   
  6.两个Action对应两个form  
   
  <action   path="/oneAction"  
  type="package.oneActionClass">  
  name="oneForm"  
  input="one.jsp"  
  <forward   name="successful"   path="/anotherAction.do"   redirect="true"/>  
  </action>  
  <action   path="/anotherAction"  
  type="package.AnotherActionClass">"  
  name="anotherForm"  
  input="another.jsp"  
  <forward   name="success"   path="success.jsp"/>  
  </action>  
   
          这个组合方式跟前一种在流程上没有太大区别,只是我们现在对于两个action分别提供了form,于是代码看上去更加清晰。于是我们可以分别处理WEB   应用程序的输入和输出。值得注意的是,后一个action同样会尝试往form中写入那些参数,不过我们可以这样处理:a)   在后一个form中使用另一套属性名;b)   只提供getter而不提供setter。  
   
  基本处理过程:  
          前一个action接收输入、验证、然后将数据写入业务层或持久层,重定向到后一个action,后一个action手动的从业务层/持久层取出数据,写入form(通过其他方式),交给前台JSP显示。这样做的好处是不必保留输入form中的值,因此可以使用redirect而不是forward。这样就降低了两个action之间的耦合度,同时也避免了不必要的重复提交。  
   
  注明:文中所提及的“仅有Form”指的是没有继承Struts提供的Action类,而是直接使用了Struts自身提供的Action类;“仅有Action”指的是仅继承了Struts提供的Action类而没有使用Form。

posted @ 2007-03-20 15:30 chenweicai 阅读(199) | 评论 (0)编辑 收藏

使用Struts有一段时间了,但也仅仅涉及到一部分内容,比如Action做逻辑控制、FormBean对象化用户提交的Form数据、国际化资源文件,Struts很重要的一部分Struts Tag却一直没有使用到,一是因为要熟练的使用Struts Tag需要一定的时间来,二也是因为自己有一套比较好的Tag可用。JSP页面上Tag使用的较多虽然能让页面看起来比较整洁,但可读性会相对降低,开发时的灵活性也会降低。相对来说还是比较看中Action的逻辑控制部分。

重新翻看Struts书本,特别注意了Tag部分,也还是觉得有些眩晕,或许使用熟练是才能体验出其中的玄妙。摘录几个Tag放在这里。

1、<bean:message key="hello.jsp.page.heading"/>
用于输出资源文件中的内容

2、<html:errors/>或者写成<html:error property="xxx"/>
用于输出错误信息,当指定peoperty时,则只显示对应的错误信息,如:
ActionErrors errors = new ActionErrors();
errors.add("xxx",new ActionError("username.null"));

3、<html:form action="/Helloworld">
Form表单

4、<html:text property="userName" size="16"/>
表单中的输入域

5、<html:submit property="submit" value="Submit"/>
提交按钮

6、<html:reset/>
重置按钮

7、<bean:write name="bitiliu" property="userName"/>
从request中或者session中获得bitiliu对象,并输出userName属性的值,可指定scope

8、非空逻辑判断
<logic:notEmpty name="bitiliu" property="userName" scope="request">
 do something...  
</logic:notEmpty>

9、<html:link>
用于生成链接,可以增加参数
<%
 pageContext.setAttribute("name","I am a boy!");

 HashMap myMap = new HashMap(); 
 myMap.put("name","bitiliu");
 myMap.put("password",new String[]{"1","2","3"});
 pageContext.setAttribute("myMap",myMap);
%>
<html:link page="/Test.jsp" paramId="haha" paramName="name" name="myMap">Test</html:link>
生成的超链接为:
<a href="/Struts2/Test.jsp?password=1&amp;password=2&amp;password=3&amp;haha=I+am+a+boy%21&amp;name=bitiliu">Test</a>

10、<html:img>
生成图片标记,如<html:img page="/logo.gif"/>,生成的HTML为<img src="/web/logo.gif">
<html:img>也可以包含请求参数,可参考<html:link>

11、<html:cancel>
增加取消按钮,点击取消按钮也会请求form的action事件,可以在execute方法中通过方法isCancelled(request)来判断是否点击了取消按钮。

12、<html:hidden>
生成隐藏域,如<html:hidden property="name"/>,生成的HTML为<input type="hidden" name="name"/>

13、<html:checkbox property="check">
对应的Form中应该有一对应属性check类型为boolean

14、<html:multibox property="strArray" value="value1">
对应的Form中对应属性strArray类型为String

15、<html:radio property="strArray" value="value1">
同<html:multibox>,但为单选框

16、<html:select>
下拉选择框,可指定property、size和multiple(true\false),标签内可包含<html:option>、<html:options>和<html:optionCollections>

17、<html:option>
可写成:<html:option value="color.orange" key="color.orange"/>或<html:option value="color.orange">Orange</html:option>

18、<html:options>
一次生成多个option,使用示例:
Vector vec = new Vector();
vec.add(new org.apache.struts.util.LabelValueBean("label1","value1"));
vec.add(new org.apache.struts.util.LabelValueBean("label2","value2"));
pageContext.setAttribute("vec",vec);

<html:options collection="vec" property="value" labelProperty="label"/>

19、<html:file>
用于实现文件上传,其中<html:form>的enctype="multipart/form-date",Form的对应属性类型应为File类型,

逻辑判断标签:
1、<logic:equal>
相等,示例如下:
<logic:equal name="strValue" value="112" scope="request">equal</logic:equal>
标签从request中获得strValue对象,然后和112比较,如果相等,则输入字符串equal,否则不输出。下面标签相同。

2、<logic:greateEqual>
大于等于

3、<logic:greaterThan>
大于

4、<logic:lessEqual>
小于等于

5、<logic:lessThan>
小于

6、<logic:notEqual>
不等于

7、<logic:match>
指定的值是变量的子串

8、<logic:notMatch>
指定的值不是变量的子串

9、<logic:iterate>
叠代标签

其它:
1、在国际化文件中添加两个errors.header和errors.footer用于指定错误信息显示时前后追加的内容。

posted @ 2007-03-17 14:32 chenweicai 阅读(635) | 评论 (1)编辑 收藏

struts标签bean

<!-- ################################################################ -->
<!--
访问cookie的信息内容
此标签主要是用于在jsp中将一个cookie附给一个指定的变量
以便于在jsp中应用
-->
<bean:cookie id="唯一标识符" name="存在的cookie属性名"/>
<!-- ################################################################ -->
<!--
访问cookie的信息内容
此标签主要是用于在jsp中将scop中一个的属性附给一个指定的变量
以便于在jsp中应用
-->
<bean:define
  id="唯一标识符"
  name="在page|request|response|session中存在的标识符"
  property="对象中的成员变量"
  scope="page|request|response|session"
  toScope="存放此对象的范围page|request|response|session"
  type="此对象的数据类型(权限定类名)"
  value="默认初始化值"/>
<!-- ################################################################ -->
<!--
访问头部元素的信息内容
<bean:header>标签是用于将头部信息中的一个元素属性附给一个指定的变量
以便于在jsp中应用
-->
<bean:header id="唯一标识符" name="头部元素中存在的属性名"/>
<!-- ################################################################ -->
<!--
访问头部元素的信息内容
<bean:include>标签是用于web应用程序中的一个资源引进当前jsp中,
并且将指向它的一个地址附给指定的变量
以便于在jsp中应用
-->
<bean:include id="唯一标识符" page="包含的web组件的uri路径,以 / 开头" />
<!-- ################################################################ -->
<!--
<bean: page>标签主要用于访问jsp中的隐含对象,
  page|request|response|session|application
  将此属性附给一个指定变量
-->
<bean: page id="唯一标识符" name="jsp中的隐含对象"/>
<!-- ################################################################ -->
<!--
<bean: parameter>标签
  用于访问请求参数 ,
  将此属性附给一个指定的变量,便于在当前jsp中应用
-->
<bean: parameter
  id="唯一标识符"
  name="参数名"
  value="默认值"
  multiple="??????"/>
<!-- ################################################################ -->
<!--
<bean:resource>
访问系统配置中的资源绑定信息 Resource Bundle
此标签的作用是,将指向系统配置中的某个资源的指针,附给指定变量,以便于在当前页中调用
-->
<bean:resource
  id="唯一标识符"
  name="包含的 web uri 路径,以 / 开头"
  input="?????" />
<!-- ################################################################ -->
<!--
<bean:size> 用于取得某个指定数据容器的深度大小 ,并且将此值附给一个指定变量名
-->
<bean:size
  id="唯一标识符"
  name="page|request|response|session中存在的属性变量名"
  property="变量中的成员变量名"
  scope="作用范围page|request|response|session"
  collection="java.util.Collection类变量" />
<!-- ################################################################ -->
<!--
<bean:write> 用于输出指定变量的内容值
-->
<bean:write
  name="page|request|response|session中存在的属性变量名"
  property="变量中的成员变量名"
  filter="true|false"
  format="书写格式"
  formatKey="索引主键,此主键与系统配置文件中的主键区配"
  scope="作用范围page|request|response|session"
  bundle="??????" ignore="??????" locale="??????"
  />
<!-- ################################################################ -->
<!--
<bean:message> 用于输出资源配置中的信息内容
-->
<bean:message
  bundle="系统配置文件中的绑定参数"
  key="与系统配置中的资源文件中的主键区配" />
<bean:message
  bundle="系统配置文件中的绑定参数"
  name="scope中存在的属性名"
  scope="page|request|response|session"
  property="属性对象中的成员变量名,并且此属性变量的值与key的值相同" />

struts标签html

<!--######################################################################-->
<!-- 不带参数的page连接方式 -->
<html:link [page="/XXX.do"|action="/XXX"]>连接内容</html:link>
<!--带参数的一种连接方式-->
<html:link
  [page="/XXX.do?paramName1=value1&amp;paramName2=value2"|
  page="/XXX?paramName1=value1&amp;paramName2=value2"]>
        连接内容
      </html:link>
<html:link [page="/XXX.do"|action="/XXX"]
           paramId="参数名"
           paramName="在page|request|response|session中存在的属性名">
        连接内容
      </html:link>
<!--带参数的一种连接方式-->
<html:link [page="/XXX.do"|action="/XXX"]
           paramId="参数名"
           paramName="配置文件中的BEAN的配置名称"
           paramProperty="配置对象中的成员变量">
        连接内容
      </html:link>
<html:link action="/XXX"
           name="在page|request|response|session中存在的属性名">
        连接内容
      </html:link>
<html:link href="完整的url路径">
        连接内容
      </html:link>
<html:link page="相对于当前操作路径的url">
        连接内容
      </html:link>
<html:link forward="struts配置文件中存在的<global-forwards>元素的子元素<forwar>的name值">
        连接内容
      </html:link>
<!--######################################################################-->
<html:img page="相对于当前操作路径的url" />
<!--######################################################################-->
<html:img src="完整的uri路径"
  paramId="参数名"
  paramName="page|request|response|session中存在的属性名"/>
<!--######################################################################-->
<html:form
  action="xxx.do"
  focus="焦点"
  method="GET|POST|DELETE|PUT|HEAD|OPTIONS" >
<!--######################################################################-->
  <!-- 单行输入框 -->
  <html:text property="prptName">初始值</html:text>
<!--######################################################################-->
  <!-- 隐藏字段 -->
  <html:hidden property="prptName" />
<!--######################################################################-->
  <!-- 密码输入框 -->
  <html: password property="prptName"></html: password>
<!--######################################################################-->
  <!-- 文件获取输入框 -->
  <html:file property="prptName">初始值</html:file>
<!--######################################################################-->
  <!-- 按钮 -->
  <html:button property="prptName">初始值</html:button>
<!--######################################################################-->
  <!-- 提交按钮 -->
  <html:submit property="prptName">初始值</html:submit>
<!--######################################################################-->
  <!-- 取消按钮 -->
  <html:cancel property="prptName">初始值</html:cancel>
<!--######################################################################-->
  <!-- 重置按钮 -->
  <html:reset property="prptName">初始值</html:reset>
<!--######################################################################-->
  <!-- 图片按钮 -->
  <html:image onclick="" src="url" >初始值</html:image>
<!--######################################################################-->
  <!-- 复选框 -->
  <html:checkbox property="prptName">初始值</html:checkbox>
<!--######################################################################-->
  <!--多选框的表达方式一-->
  <html:multibox property="属性名" value="初始值"/>
<!--######################################################################-->
  <!--多选框的表达方式二-->
  <html:multibox property="属性名">初始值</html:multibox>
<!--######################################################################-->
  <!-- 选择列表 -->
  <html:select
    property="prptName"
    size="指定在网页上显示的可选的数目"
    multiple="true|false,此属性用于指定列表是否允许多选">
<!--######################################################################-->
    <html:option
      value="绑定的属性名"
      bundle="系统文件中绑定的属性名"
      key="资源文件中绑定的属性" >lable</html:option>
<!--######################################################################-->
    <html:options
      collection="Vector对象,此对象中放置org.apache.struts.util.LabelValueBean对象"
      property="网页中的value值其值一般是value"
      labelProperty="网页中显示的标签,其值一般是lable"/>
<!--######################################################################-->
    <html:optionsCollection
      name="page|request|response|session中存在的属性名称"
      property="属性对象中的成员变量"
      label="成员变量中的成员变量"
      value="成员变量中的成员变量" />
  </html:select>
</html:form>

struts标签logic

<!--#################################################################-->
<!-- <logic:equal>和<logic:notEqual>判断变量的值与指定常量是否相等 -->
<logic:equal
  name="request,page,response,session中存在的以此名称命名的变量"
  property="此变量中的成员变量"
  parameter="将要取得的变量的标识符,此变量存在于request|page|response|session中"
  scope="取得变量的范围 request|page|response|session"
  value=" 参加比对的值 ">
  若判断成立,则输出此处的内容!
  </logic:equal>
<!--=====================================================================-->
<logic:notEqual
  name="request,page,response,session中存在的以此名称命名的变量"
  property="此变量中的成员变量"
  parameter="将要取得的变量的标识符,此变量存在于request|page|response|session中"
  scope="取得变量的范围 request|page|response|session"
  value=" 参加比对的值 ">
  若判断成立,则输出此处的内容!
  </logic:notEqual>
<!--#################################################################-->
<logic:iterate
  id="唯一标识符"
  name="在request|response|session|page中的标识符"
  property="若是自定义类对象,此处用语表示此对象中的属性名"
  type="取得的对象的权限定类名"
  indexId="indexid"
  offset="起始位置"
  length="循环的长度">
  <li><em><bean:write name="与logic:iterate的属性id的内容一致" /></em>&nbsp;[<bean:write name="index"/>]</li>
</logic:iterate>

<!--#################################################################-->
<!--<logic:empty>与<logic:notEmpty>用于判断指定参数的属性值是否是null值或是空字符串""-->
<!--判断scope中存在的指定参数名的变量值是否是null值或是空字符串,若是null或空字符串则输出标签之间的内容-->
<logic:empty
  name="对象的唯一标识符"
  scope="page|request|response|session"
  property="对象中存在的成员变量">
empty
</logic:empty>
<!--判断scope中存在的指定参数名的变量值是否不是null值或是空字符串,若不是null或空字符串则输出标签之间的内容-->
<logic:notEmpty
  name="对象的唯一标识符"
  scope="page|request|response|session"
  property="对象中存在的成员变量">
notEmpty
</logic:notEmpty>
<!--#################################################################-->
<!--<logic: present>与<logic:notPresent>用于判断指定参数的属性是否存在-->
<!--判断cookie中是否存在指定参数名的变量若存在则输出标签之间的内容-->
<logic: present
  cookie="cookie中存在的变量名">
  此处是输出内容!
  </logic: present>
<!--判断header中是否存在指定参数名的变量若存在则输出标签之间的内容-->
<logic: present
  header="取得头部元素中存在的变量">
  此处是输出内容!
  </logic: present>
<!--判断scope中是否存在指定参数名的变量若存在则输出标签之间的内容-->
<logic: present
  name="属性名"
  property="对象中存在的成员变量"
  scope="page|request|response|session">
  此处是输出内容!
  </logic: present>
<!--=================================================================-->
<!--判断cookie中是否存在指定参数名的变量若存在则输出标签之间的内容-->
<logic:notPresent
  cookie="cookie中可能存在的变量名">
  此处是输出内容!
  </logic:notPresent>
<!--判断header中是否存在指定参数名的变量若存在则输出标签之间的内容-->
<logic:notPresent
  header="头部元素中存在的变量">
  此处是输出内容!
  </logic:notPresent>
<!--判断scope中是否存在指定参数名的变量若存在则输出标签之间的内容-->
<logic:notPresent
  name="属性名"
  property="对象中可能存在的成员变量"
  scope="page|request|response|session">
  此处是输出内容!
  </logic:notPresent>
<!--#################################################################-->
<!--<logic:match>与<logic:notMatch>用于判断指定参数的字符串是否区配某个给定标准-->
<!--判断cookie中存在指定参数名的变量的值,是否与指定的字符格式区配若区配则输出此内容-->
<logic:match
  cookie="cookie中可能存在的变量名"
  location="contains|start|end"
  value="要区配的字符格式">
</logic:match>
<!--判断header中存在指定参数名的变量的值,是否与指定的字符格式区配若区配则输出此内容-->
<logic:match
  header="header中可能存在的变量名"
  location="contains|start|end"
  value="要区配的字符格式">
</logic:match>
<!--判断scope中存在指定参数名的变量的值,是否与指定的字符格式区配若区配则输出此内容-->
<logic:match
  name="存在的属性名"
  property="属性对象中的成员变量"
  scope="page|request|response|session"
  location="contains|start|end"
  value="要区配的字符格式">
</logic:match>
<!--===================================================================-->
<!--判断cookie中存在指定参数名的变量的值,是否与指定的字符格式区配若区配则输出此内容-->
<logic:notMatch
  cookie="cookie中可能存在的变量名"
  location="contains|start|end"
  value="要区配的字符格式">
</logic:notMatch>
<!--判断header中存在指定参数名的变量的值,是否与指定的字符格式区配若区配则输出此内容-->
<logic:notMatch
  header="header中可能存在的变量名"
  location="contains|start|end"
  value="要区配的字符格式">
</logic:notMatch>
<!--判断scope中存在指定参数名的变量的值,是否与指定的字符格式区配若区配则输出此内容-->
<logic:notMatch
  name="存在的属性名"
  property="属性对象中的成员变量"
  scope="page|request|response|session"
  location="contains|start|end"
  value="要区配的字符格式">
</logic:notMatch>
<!--#################################################################-->
<!--<logic:forward>用于地址转向到指定位置-->
<logic:forward name="与系统配置文件中的<global-forward>元素中的子元素<forward>区配"/>
<!--#################################################################-->
<!-- <logic:redirect>用于地址重定向到指定位置 -->
<logic:redirect
  anchor=""
  forward=""
  href=""
  name=""
  page=""
  paramId=""
  paramName=""
  paramProperty=""
  paramScope=""
  property=""
  scope=""
  transaction="">连接内容</logic:redirect>

posted @ 2007-03-17 14:29 chenweicai 阅读(1328) | 评论 (0)编辑 收藏

当序列化遇到继承…

当一个父类实现Serializable接口后,他的子类都将自动的实现序列化。

以下验证了这一点:
package InherSerialTest;

import java.io.Serializable;

public class SuperA implements Serializable {

 private int supervalue;

 public SuperA(int supervalue) {
  super();
  // TODO Auto-generated constructor stub
  this.supervalue = supervalue;
 }
 
 public int getSupervalue() {
  return supervalue;
 }

 public void setSupervalue(int supervalue) {
  this.supervalue = supervalue;
 }

 public String toString(){
  return "supervalue is :" + supervalue;
 }
}

package InherSerialTest;

public class SubB extends SuperA {

 private int subvalue;

 public SubB(int supervalue, int subvalue) {
  super(supervalue);
  // TODO Auto-generated constructor stub
  this.subvalue = subvalue;
 }

 public int getSubvalue() {
  return subvalue;
 }

 public void setSubvalue(int subvalue) {
  this.subvalue = subvalue;
 }

 public String toString() {
  // TODO Auto-generated method stub
  return super.toString() + " ,subvalue " + subvalue;
 }
 
}

package InherSerialTest;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class InherSerialTest {

 /**
  * @param args
  */
 public static void main(String[] args) {
  // TODO Auto-generated method stub

  SubB sub = new SubB(100,200);
  
  try{
   ObjectOutputStream oos = new ObjectOutputStream(
     new FileOutputStream("c:\\InherSerialTest.txt"));
   oos.writeObject(sub);
   oos.close();
   
   ObjectInputStream ois = new ObjectInputStream(
     new FileInputStream("c:\\InherSerialTest.txt"));
   SubB sub2 = (SubB)ois.readObject();
   System.out.println(sub2);
   ois.close();
  }catch(Exception e){
   e.printStackTrace();
  }
 }

}


结果是:supervalue is :100 ,subvalue 200

怎管让子类实现序列化看起来是一件很简单的事情,但有的时候,往往我们不能够让父类实现Serializable接口,原因是有时候父类是抽象的(这并没有关系),并且父类不能够强制每个子类都拥有序列化的能力。换句话说父类设计的目的仅仅是为了被继承。

要为一个没有实现Serializable接口的父类,编写一个能够序列化的子类是一件很麻烦的事情。java docs中提到:

“To allow subtypes of non-serializable classes to be serialized, the subtype may assume responsibility for saving and restoring the state of the supertype's public, protected, and (if accessible) package fields. The subtype may assume this responsibility only if the class it extends has an accessible no-arg constructor to initialize the class's state. It is an error to declare a class Serializable if this is not the case. The error will be detected at runtime. ”

也就是说,要为一个没有实现Serializable接口的父类,编写一个能够序列化的子类要做两件事情:

其一,父类要有一个无参的constructor;

其二,子类要负责序列化(反序列化)父类的域。
如果我们将上列中的SuperA没有实现Serializable接口,而是在SubB类中实现Serializable接口的话,即:

public class SuperA  {}
public class SubB extends SuperA implements Serializable{}

我们再次运行时,将会产生错误:

java.io.InvalidClassException: InherSerialTest.SubB; no valid constructor
 at java.io.ObjectStreamClass.<init>(Unknown Source)
 at java.io.ObjectStreamClass.lookup(Unknown Source)
 at java.io.ObjectOutputStream.writeObject0(Unknown Source)
 at java.io.ObjectOutputStream.writeObject(Unknown Source)
 at InherSerialTest.InherSerialTest.main(InherSerialTest.java:21)

果真如docs中所说的一样,父类缺少无参构造函数是不行的。



接下来,按照docs中的建议我们改写这个例子:
package InherSerialTest2;

import java.io.Serializable;

public abstract class SuperC {

 int supervalue;

 public SuperC(int supervalue) {
  super();
  // TODO Auto-generated constructor stub
  this.supervalue = supervalue;
 }
 
// 父类没有实现Serializable接口,子类要可序列化的话,父类必须要有无参构造函数
 public SuperC(){
  
 }

 public int getSupervalue() {
  return supervalue;
 }

 public void setSupervalue(int supervalue) {
  this.supervalue = supervalue;
 }

 public String toString() {
  // TODO Auto-generated method stub
  return "supervalue : " + supervalue;
 }

}

package InherSerialTest2;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;

import InherSerialTest.SuperA;

public class SubC extends SuperC implements Serializable{
 
 private int subvalue;

 public SubC(int supervalue, int subvalue) {
  super(supervalue);
  // TODO Auto-generated constructor stub
  this.subvalue = subvalue;
 }

 public int getSubvalue() {
  return subvalue;
 }

 public void setSubvalue(int subvalue) {
  this.subvalue = subvalue;
 }

 public String toString() {
  // TODO Auto-generated method stub
  return super.toString() + " ,subvalue :" + subvalue;
 }
 
 private void writeObject(java.io.ObjectOutputStream oos)throws IOException{
  
  //先序列化对象
  oos.defaultWriteObject();
  //在序列化父类的域
  oos.writeInt(supervalue);
 }
 
 private void readObject(ObjectInputStream ois)throws IOException, ClassNotFoundException{
  
  //先反序列化对象
  ois.defaultReadObject();
  //再反序列化父类的域
  supervalue = ois.readInt();
 }
}

测试成功!

posted @ 2007-03-09 22:44 chenweicai 阅读(458) | 评论 (0)编辑 收藏

序列化概述

简单来说序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化,流的概念这里不用多说(就是I/O),我们可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间(注:要想将对象传输于网络必须进行流化)!在对对象流进行读写操作时会引发一些问题,而序列化机制正是用来解决这些问题的!

问题的引出:

如上所述,读写对象会有什么问题呢?比如:我要将对象写入一个磁盘文件而后再将其读出来会有什么问题吗?别急,其中一个最大的问题就是对象引用!举个例子来说:假如我有两个类,分别是A和B,B类中含有一个指向A类对象的引用,现在我们对两个类进行实例化{ A a = new A(); B b = new B(); },这时在内存中实际上分配了两个空间,一个存储对象a,一个存储对象b,接下来我们想将它们写入到磁盘的一个文件中去,就在写入文件时出现了问题!因为对象b包含对对象a的引用,所以系统会自动的将a的数据复制一份到b中,这样的话当我们从文件中恢复对象时(也就是重新加载到内存中)时,内存分配了三个空间,而对象a同时在内存中存在两份,想一想后果吧,如果我想修改对象a的数据的话,那不是还要搜索它的每一份拷贝来达到对象数据的一致性,这不是我们所希望的!

以下序列化机制的解决方案:

1.保存到磁盘的所有对象都获得一个序列号(1, 2, 3等等)

2.当要保存一个对象时,先检查该对象是否被保存了。

3.如果以前保存过,只需写入"与已经保存的具有序列号x的对象相同"的标记,否则,保存该对象

通过以上的步骤序列化机制解决了对象引用的问题!

序列化的实现

将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。
package serializable;

import java.io.Serializable;

public class Employee implements Serializable {

 private String name;
 
 private double salary;

 public Employee(String name, double salary) {
  super();
  // TODO Auto-generated constructor stub
  this.name = name;
  this.salary = salary;
 }
 
 public void raiseSalary(double byPercent){
  double temp = salary * byPercent / 100;
  salary += temp;
 }

 public String toString() {
  // TODO Auto-generated method stub
  return getClass().getName() +
   "[ Name = " + name + ", salary = " + salary +"]";
 }

package serializable;

public class Manager extends Employee {
 
 private Employee secretary;

 public Manager(String name, double salary) {
  super(name, salary);
  // TODO Auto-generated constructor stub
  secretary = null;
 }

 public Employee getSecretary() {
  return secretary;
 }

 public void setSecretary(Employee secretary) {
  this.secretary = secretary;
 }

 public String toString() {
  // TODO Auto-generated method stub
  return super.toString() + "[ secretary = " + secretary +"]";
 }
 
}

package serializable;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Test {
 
 public static void main(String[] args){
  
  Employee employee = new Employee("LiLei", 1000);
  Manager manager1 = new Manager("Jim", 20000);
  manager1.setSecretary(employee);
  
  Employee[] staff = new Employee[2];
  staff[0] = employee;
  staff[1] = manager1;
  
  try{
   ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("employee.dat"));
   oos.writeObject(staff);
   oos.close();
   
   ObjectInputStream ois = new ObjectInputStream(new FileInputStream("employee.dat"));
   Employee[] newStaff = (Employee[])ois.readObject();
   ois.close();
   
   newStaff[0].raiseSalary(1000);
   
   for(int i=0; i<newStaff.length; i++)
    System.out.println(newStaff[i]);
   
  }catch(Exception e)
  {
   e.printStackTrace();
  }
 }
}

修改默认的序列化机制 

在序列化的过程中,有些数据字段我们不想将其序列化,对于此类字段我们只需要在定义时给它加上transient关键字即可,对于transient字段序列化机制会跳过不会将其写入文件,当然也不可被恢复。但有时我们想将某一字段序列化,但它在SDK中的定义却是不可序列化的类型,这样的话我们也必须把他标注为transient,可是不能写入又怎么恢复呢?好在序列化机制为包含这种特殊问题的类提供了如下的方法定义:

private void readObject(ObjectInputStream in) throws

IOException, ClassNotFoundException;

private void writeObject(ObjectOutputStream out) throws

IOException;

(注:这些方法定义时必须是私有的,因为不需要你显示调用,序列化机制会自动调用的)

使用以上方法我们可以手动对那些你又想序列化又不可以被序列化的数据字段进行写出和读入操作。

下面是一个典型的例子,java.awt.geom包中的Point2D.Double类就是不可序列化的,因为该类没有实现Serializable接口,在我的例子中将把它当作LabeledPoint类中的一个数据字段,并演示如何将其序列化
package transientTest;

import java.awt.geom.Point2D;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;


public class LabeledPoint implements Serializable {

 private String label;
 transient private Point2D.Double point;
 
 public LabeledPoint(String label, double x, double y) {
  super();
  // TODO Auto-generated constructor stub
  this.label = label;
  this.point = new Point2D.Double(x,y);
 }
 
 private void writeObject(ObjectOutputStream oos)throws IOException{
  
  oos.defaultWriteObject();
  oos.writeDouble(point.getX());
  oos.writeDouble(point.getY());
 }
 
 private void readObject(ObjectInputStream ois)throws IOException, ClassNotFoundException{
  
  ois.defaultReadObject();
  double x = ois.readDouble() + 1.0;
  double y = ois.readDouble() + 1.0;
  point = new Point2D.Double(x,y);
 }

 public String toString() {
  // TODO Auto-generated method stub
  return getClass().getName() + "[ Label = " + label + ", point.getX() = "
   + point.getX() + ", point.getY() = " + point.getY() + "]";
 }
}

package transientTest;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class transientTest {

 public static void main(String[] args){
  LabeledPoint label = new LabeledPoint("Book", 5.0, 5.0);
  
  try{
   System.out.println("before:\n" + label);
   
   ObjectOutputStream oos = new ObjectOutputStream(
     new FileOutputStream("c:\\label.txt"));
   oos.writeObject(label);
   oos.close();
   
   System.out.println("after:\n" + label);
   ObjectInputStream ois = new ObjectInputStream(
     new FileInputStream("c:\\label.txt"));
   LabeledPoint label1 = (LabeledPoint)ois.readObject();
   ois.close();
   
   System.out.println("after add 1.0:\n" + label);
  }catch(Exception e)
  {
   e.printStackTrace();
  }
 }
}

posted @ 2007-03-09 20:13 chenweicai 阅读(369) | 评论 (0)编辑 收藏

摘要:本文介绍了J2EE的分层结构,深入研究了如何使用Session Facade模式和ValueObject 模式设计EJB,并对其开发过程做了较详细的说明。

关键字:EJB ;值对象模式;会话外观模式
一、概述

  与传统的二层体系结构相比,J2EE有两个特点:

  1、定义了一套标准化组件,通过为这些组件提供完整的服务。

  2、使用多层分布式的应用程序模型。应用程序的逻辑根据其实现的不同功能被封装到不同的组件中。如图1所示。





  这种多层结构使企业级应用具有很强的伸缩性,允许各层专注于某种特定的角色:

  1、Client Tier用于显示。

  2、Web Tier用于生成动态显示。

  3、Business Tier用于实现业务逻辑。

  4、EIS Tier用于数据库服务。

  其中,用于实现业务逻辑的EJB组件架构是J2EE的基础和最重要的部分。

  正是认识到J2EE平台作为一种可扩展的、全功能的平台,可以将关键的企业应用扩展到任何Web浏览器上并可适合多种不同的Internet数据流、可连接到几乎任何一种传统数据库和解决方案,J2EE已经成为开发电子商务应用的事实标准。

  为了使开发者开发出规范的、可重用的应用程序,J2EE为我们提供了大量的模式。模式尽管有时不易理解,但使用却非常简单,它提供了强大的可重用机制,避免了开发者和设计者的重复投资。

  可是,面对如此多的模式,初学者往往不知如何下手,为此,作者结合以往的开发经验,详细介绍如何使用模式完成EJB的设计。
二、设计与实现

  1.值对象模式

  J2EE应用程序把服务器端业务组件实现为会话Bean和实体Bean。对于实体Bean的创建,开发人员通常采用CMP(容器管理持久性)模式,其好处在于容器提供公共的服务,例如目录服务、事务管理、安全性、持久性、资源缓冲池以及容错性等,使开发人员不必维护将会集成到业务逻辑中的系统级代码,只需专注于商业逻辑。

  一般来说,有了实体bean,就可以通过调用业务组件的一些方法向客户端返回数据。初学者往往会认为既然客户端可以与服务器通信,那么任务就算完成了。可是,问题恰恰出在这里。业务组件的get方法只能返回一个属性值,这就导致需要获得所有属性值的客户端需要多次调用业务对象的get方法,如图2-1所示。每次调用都是一次网络调用,都会造成系统性能的退化,当调用次数增多时,系统性能就会严重下降。

  这就要求有一种方法使客户端可以一次调用得到所需的大量数据,这种方法就是Value Object(值对象)模式。值对象是任意的可串行化的Java对象,也被称为值的对象,它在一次网络传输中包含和封装了大量的数据并被保存在内存中。这样,当客户端需要再次使用数据的时候,不用再次到数据库中查询,而是直接在内存中读取值对象,节省了大量的时间和系统开销,如图2-2。

  值对象模式有两种策略――可更新的值对象策略和多值对象策略。

  可更新的值对象策略中,业务对象负责创建值对象,并且在客户端请求时把该值对象返回给客户端;同时,业务对象也可以从客户端接收数据,形成值对象,并使用该对象来完成更新。

  例如,在银行系统的例子中,Account 中提供一个以AccountValue为参数的setAccountValueObject方法,这样客户端可以通过这个方法来设置值对象的值,而不采用实体bean--Account中设置每个属性的方法(setBalance()),因为后一种方法会导致大量的网络负载。由于值对象的易变性,所以值对象类必须给每个可以被客户端更新的属性提供设置方法。例如,AccountValue中的setBalance()方法。这样,一旦某客户端拥有来自业务对象的值对象,客户端就可以在本地调用必要的设置方法来更改属性值,然后调用业务对象的setAccountValueObject()方法更新业务对象。

  多值对象策略

  一些应用程序业务对象往往比较复杂,在这种情况下,根据客户端请求不同,有可能单个业务对象会产生多个不同的值对象。在这种情况下,可以考虑采用多值对象策略。这种策略的实现比较简单,就是在entity bean中增加不同的Get×××ValueObject()方法和set×××ValueObject()方法。

  2.Session Facade 模式

  有了实体Bean,客户端就可以直接调用它以获得数据。也就是说实体Bean封装了业务数据,并把他们的接口暴露给客户,因而也就把分布式服务的复杂性暴露给客户。在对J2EE 应用程序环境下,一般会产生如下问题:

  1、紧密耦合,这回导致客户端和业务对象的直接依赖关系

  2、客户端和服务器之间的网络方法调用太多,容易导致网络性能问题

  3、缺乏统一的客户访问策略,容易误用业务对象

  4、如果实体bean的API改动,那么用户端的一些代码也要修改,扩展性很差



  解决这些问题的方法就是把客户端和实体bean分割开。本文采用Session Facade模式,如图3-2所示。该模式通过一个Session Bean,为一系列的实体bean提供统一的接口来实现流程。事实上,客户端只是使用这个接口来触发流程。这样,所有关于实体bean实现流程所需要的改变,都和客户端无关。当实体bean改变时,我们不用改变客户端的代码,只要对Session Bean做出相应的改变即可,大大提高了系统的可维护性。

  通过实体bean来表示业务对象是session facade的最常见用法。但多个实体bean参与某用例时,不必向客户暴露所有实体bean。相反的,用session bean 包装这些实体bean ,并且提供粗粒度方法来执行所需的业务功能,从而隐藏了实体bean交互的复杂性。

  但是千万不要以为Facade模式就是简单的用Session Bean把Entity Bean的所有方法统统封装起来,而不提供任何额外的抽象。其实这是对Facade模式的滥用。这样做并不是降低整个系统的复杂性,而是把复杂性转移到另一个对象上。

  正确应用Facade模式应遵循三条基本原则:

  1、他们自己不作实际工作,而是委派其他对象作实际工作。

  2、他们提供简单的接口。

  3、他们是底层系统的客户端接口。他们应该把特定于子系统的信息封装起来,并且不应该在不必要的情况下公开它。
三、具体代码

  下面用一个简单的银行系统的例子来解释Facade模式和Value Object模式的具体应用。

  创建Entity Bean。其中对每个属性的get和set方法是自动生成的,我们不去管它。

public interface Account extends javax.ejb.EJBObject {
private AccountValue creaeAccountValueObject();
void setAccountVauleObject(AccountValue v);
AccountValue getAccountValueObject();
……}

  其中

private AccountValue createAccountValueObject(){
AccountValue vo=new AccountValue();
vo. accountNumber=accountNumber;
Vo.balance=balance;
……}
public AccountValue getAccountValueObject(){
return createAccountValueObject();
}
public void setAccountValueObject(AccountValue v){
accountNumber=v. accountNumber;
balance=v.balance;
……}

  用值对象封装Entity Bean数据。

public class AccountValue implements java.io.Serializable {
private java.lang.String accountNumber;
private double balance;
void setBalance(double newValue)
……}

  用Factory或者是Action类逻辑方法,涉及到数据的地方使用值对象。

public class AccountFactory {
 private static AccountHome accountHome = null;
  ……
 public java.util.Vector getAccounts(String userid) throws FactoryException {
  try { Vector vect = new Vector();
   AccountHome home = getAccountHome();
   Enumeration accountRefs = home.findByUserid(userid);
   while (accountRefs.hasMoreElements()) {
    Account acc = (Account)accountRefs.nextElement();
    AccountValue valueObject =acc.getAccountValueObjcet();
    vect.addElement(valueObject);}
    return vect; }
  ……}

  在Session Bean的get方法中调用Factory或者是Action对象。

public java.util.Vector getAccounts(String userid) throws FactoryException {
 AccountFactory fact = new AccountFactory();
 Vector result = fact.getAccounts(userid);
 return result;
}

  正如代码所示,使用session facade模式,可以

  1、提供统一的接口:会话外观抽象了业务组件交互的复杂性,并且向客户端提供一个更简单的接口。

  2、减少耦合提高可管理性:会话外观分离了业务对象和客户端,这样可以减少紧密耦合,以及客户端对业务对象的依赖性。

  3、提供粗粒度访问:会话外观减少客户端和服务器之间的网络负载。客户端与业务数据的说有交互都是通过会话外观以粗粒度的发拿过是进行的。

  通过使用

  从上述代码可以看出,使用模式之后,大大改善了系统性能,也提高了代码的可重用性。此外,开发者也可以采用其他的小模式来提高系统性能,比如服务器定位模式,在此不作进一步介绍。

  四、总结

  综上所述,本文详细地介绍了使用值对象模式和会话模式设计商业逻辑层的方法,很好的实现了数据封装和合理分层,大大提高了系统的可维护性和可伸缩性,也显著的简化了具有可伸缩性和高度负责的企业级应用的开发。

  注:航天自然基金赞助支持

posted @ 2007-03-03 15:33 chenweicai 阅读(144) | 评论 (0)编辑 收藏

引言

  期待已久的EJB3.0规范在最近发布了它的初稿。在本文中将对新的规范进行一个概要性的介绍,包括新增的元数据支持,EJBQL的修改,实体Bean模型访问bean上下文的新方法和运行时环境等等。作者还讨论了EJB在未来要作出的调整以及EJB3.0与其他开发规范之间的关系。

  开始

  无论如何由于EJB的复杂性使之在J2EE架构中的表现一直不是很好。EJB大概是J2EE架构中唯一一个没有兑现其能够简单开发并提高生产力的组建。 EJB3.0规范正尝试在这方面作出努力以减轻其开发的复杂性。EJB3.0减轻了开发人员进行底层开发的工作量,它取消或最小化了很多(以前这些是必须实现)回调方法的实现,并且降低了实体Bean及O/R映射模型的复杂性。

  在本文中,我首先会介绍EJB3.0中几个主要的改变。它对进一步深入了解EJB3.0是非常重要的。随后,我会从更高的层面来描述已经被提交到EJB3.0规范中的细节,并一个个的讲解新的规范中的改变:实体 Bean,O/R映射模型,实体关系模型和EJB QL(EJB查询语言)等等。

  背景

  EJB3.0中两个重要的变更分别是:使用了Java5中的程序注释工具和基于Hibernate的O/R映射模型。

  Java5中的元数据工具。

  Java5 (以前叫J2SE1.5或Tiger)中加入了一种新的程序注释工具。通过这个工具你可以自定义注释标记,通过这些自定义标记来注释字段、方法、类等等。这些注释并不会影响程序的语义,但是可以通过工具(编译时或运行时)来解释这些标记并产生附加的内容(比如部署描述文件),或者强制某些必须的运行时行为(比如EJB组件的状态特性)。注释的解析可以通过源文件的解析(比如编译器或这IDE工具)或者使用Java5中的APIs反射机制。注释只能被定义在源代码层。由于所有被提交到EJB3.0草案中的注释标记都有一个运行时的RetentionPolicy,因此会增加类文件占用的存储空间,但这却给容器制造商和工具制造商带来了方便。

  Hibernate

  目前Hibernate非常受欢迎,它是开发源代码的Java O/R映射框架,目的是把开发人员从繁琐的数据持久化编程中解脱出来。它也有一个标准的HQL(Hibernate 查询语言)语言,你可以在新的EJB QL中看到它的影子。Hibernate在处理如数据查询、更新、连接池、事务处理、实体关系处理等方面非常简单。

  概览

  在已经提交的EJB3.0规范中主要涉及两个方面的改变:

  1. 一套以注释为基础的EJB编程模型,再加上EJB2.1中定义的通过部署描述符和几个接口定义的应用程序行为。

  2. 新的实体Bean持久化模型,EJBQL也有许多重要的改变。

  还有一些有关上述的提议,比如:一个新的客户端编程模型,业务接口的使用以及实体Bean的生命周期。请注意EJB2.1编程模型(包括部署描述符和home/remote接口)仍然是有效的。新的简化模型并没有完全取代EJB2.1模型。

  EJB注释

  EJB 规范组织一个重要的目标是减轻原始代码的数量,并且他们为此给出了一个完美而简介的办法。在EJB3.0的里,任何类型的企业级Bean只是一个加了适当注释的简单Java对象(POJO)。注释可以用于定义bean的业务接口、O/R映射信息、资源引用信息,效果与在EJB2.1中定义部署描述符和接口是一样的。在EJB3.0中部署描述符不再是必须的了;home接口也没有了,你也不必实现业务接口(容器可以为你完成这些事情)。

  比如,你可以使用@Stateless注释标记类把Java类声明为一个无状态回话bean。对于有状态回话bean来说,@Remove注释可以用来标记一个特定的方法,通过这个注释来说明在调用这个方法之后bean的实例将被清除掉。

  为了减少描述组件的说明信息,规范组织还采纳了由异常进行配置(configuration-by-exception)的手段,意思是你可以为所有的注释提供一个明确的缺省值,这样多数常规信息就可以据此推断得出。

  新的持久化模型

  新的实体bean也是一个加了注释的简单Java对象(POJO)。一旦它被EntityManager访问它就成为了一个持久化对象,并且成为了持久化上下文(context)的一部分。一个持久化上下文与一个事务上下文是松耦合的;严格的讲,它隐含的与一个事务会话共存。

  实体关系也是通过注释来定义的,O/R映射也是,并提供几种不同的数据库规范操作,在EJB2.1中这些要通过开发人员自己的设计模式或者其它技术来完成的(比如,自增长主键策略)。

  深入研究

  现在是时候详细了解EJB3.0草案了。让我们开始探讨所有EJB中四种企业级bean,并看看他们在新的规范中是什么样子。

  无状态回话bean

  在EJB3.0规范中,写一个无状态回话bean(SLSB)只需要一个简单的Java文件并在类层加上@Stateless注释就可以了。这个bean可以扩展javax.ejb.SessionBean接口,但这些不是必须的。

  一个SLSB不再需要home接口,没有哪类EJB再需要它了。Bean类可以实现业务接口也可以不实现它。如果没有实现任何业务接口,业务接口会由任意 public的方法产生。如果只有几个业务方法会被暴露在业务接口中,这些方法可以使用@BusinessMethod注释。缺省情况下所有产生的接口都是local(本地)接口,你也可以使用@Remote注释来声明这个接口为remote(远程)接口。

  下面的几行代码就可以定义一个HelloWorldbean了。而在EJB2.1中同样的bean至少需要两个接口,一个实现类和几个空的实现方法,再加上部署描述符。

import javax.ejb.*;

/**
* A stateless session bean requesting that a remote business
* interface be generated for it.
*/
@Stateless
@Remote
public class HelloWorldBean {
public String sayHello() {
return "Hello World!!!";
}
}

  有状态回话bean

  除了几个SFSB的特别说明之外,有状态回话bean(SFSB)和SLSB一样精简:

  ·一个SFSB应该有一个方法来初始化自己(在EJB2.1中是通过ejbCreate()来实现的)。在EJB3.0的规范中建议这些初始化操作可以通过自定义方法完成,并把他们暴露在业务接口中。在使用这个bean之前由客户端来调用相应的初始化方法。目前规范组织就是否提供一个注释来标记某个方法用于初始化还存在争议。

  ·Bean的提供者可以用@Remove注释来标记任何SFSB的方法,以说明这个方法被调用之后bean的实例将被移除。同样,规范组织仍然在讨论是否要有一种机制来处理这种特殊的情况,即当这个方法出现异常的情况下bean的实例是否被移除。

  下面是对以上问题我个人的观点:

  ·是否应该有一个注释来标明一个方法进行初始化呢?我的观点是——应该有,这样容器就可以在调用其他方法之前至少调用一个方法来进行初始化。这不仅可以避免不必要的错误(由于没有调用初始化方法)而且可以使容器更明确的判断是否可以重用SFSB实例。我暂且把这个问题放一放,规范组织只考虑为一个方法提供一个注释来声明它是一个初始化方法。

  ·对于第二个问题我的观点也是肯定的。这有利于Bean的提供者合客户端程序对其进行控制。只有一个遗留的问题:那就是一旦调用这个方法失败,是否能移除这个bean 的实例?答案是不能,但是它将会在回话结束的时候被移除。
消息驱动Bean

  消息驱动Bean是唯一一种必须实现一个业务接口的Bean。这个接口指出bean支持的是哪一种消息系统。对于以JMS为基础的MDB来说,这个接口是 javax.jms.MessageListener。注意MDB业务接口不是一个真正意义上的业务接口,它只是一个消息接口。

  实体Bean

  ·实体Bean使用@Entity注释来标记,所有实体bean中的属性/字段不必使用@Transient注释来标记。实体bean的持久化字段可以通过JavaBean-style机制或者声明为public/protected字段来实现。

  ·实体bean可以使用助手类来描述其状态,但是这些类的实例并没有持久化唯一性(persistent identity)的特性(即,唯一标识这个bean的字段等),实际上这些助手类与他们的实体bean实例是紧密结合的;并且这些对象还是以非共享方式来访问实体对象的。

  实体关联

  EJB3.0同时支持Bean之间双向的合单向的关联,它们可以是一对一、一对多、多对一或者是多对多的关联。然而双向关联的两端还要分为自身端(owning side)和对方端(inverse side)不同的端。自身端负责向数据库通告关联的变更。对于多对多的关联自身端必须明确的声明。实际上对方端通过isInverse=true进行注释(由此自身端就不必说明了而是由另一段推断出)。看来上面的描述,规范组织还能说让EJB变的简单了吗?

  O/R映射

  EJB3.0 中的O/R映射模型也有了重要的改变,它从原来的abstract-persistence-schema-based变成了现在的Hibernate- inspired模式。尽管目前规范组织还在就此进行讨论但是一个明确的模型将会出现在下一个版本的草案中。

  举例来说,O/R映射模型将通过bean类中的注释来声明。而且此方法还会指出对应的具体表和字段。O/R映射模型提供了一套自有的SQL;而且除了提供一些基本的SQL外还支持某些高层开发的功能。比如,有一个通过@Column注释声明的字段columnDefinition,那么可以写这样的SQL: columnDefinition="BLOB NOT NULL"

  客户端程序模型

  一个EJB客户端可以通过 @Inject注释以一种“注入”的方式获得一个bean的业务接口引用。你也可以使用另一个注释 @javax.ejb.EJBContext.lookup()来完成上面的操作,但是规范中没有告诉我们一个普通的Java客户端怎样获得一个Bean 的实例,因为这个普通的Java客户端是运行在一个客户端容器中,它无法访问@javax.ejb.EJBContex对象。现在还有另外一种机制来完成上面的工作那就是使用一个超级上下文环境对象:@javax.ejb.Context()。但是规范中没有指出该如何在客户端中使用这个对象。

  EJB QL

  EJB QL可以通过@NamedQuery来注释。这个注释有两个成员属性分别是name和queryString.一旦定义了这些属性,就可以通过 EntityManager.createNamedQuery(name)来指向这个查询。你也可以创建一个标准的JDBC风格的查询并使用 EntityManager.createQuery(ejbqlString)或EntityManager.createNativeQuery (nativeSqlString)(这个方法用于执行一个本地查询)来执行查询。

  EJB QL有两个地方可以定义其参数。javax.ejb.Query接口提供了定义参数、指向查询、更新数据等等方法。下面是一个EJBQL指向查询的例子:

.. ..
@NamedQuery(
name="findAllCustomersWithName",
queryString="SELECT c FROM Customer c WHERE c.name LIKE :custName"
)
.. ..
@Inject public EntityManager em;
customers = em.createNamedQuery("findAllCustomersWithName")
.setParameter("custName", "Smith")
.listResults();

  下面列出了一些EJB QL的增强特性:

  ·支持批量更新和删除。

  ·直接支持内连接和外连接。FETCH JOIN运行你指出关联的实体,Order可以指定只查询某个字段。

  ·查询语句可以返回一个以上的结果值。实际上,你可以返回一个依赖的类比如下面这样:

SELECT new CustomerDetails(c.id, c.status, o.count)
FROM Customer c JOIN c.orders o
WHERE o.count > 100

  ·支持group by 和having。

  ·支持where子句的嵌套子查询。

  在提交的EJB3.0草案中,EJB QL与标准SQL非常的接近。实际上规范中甚至直接支持本地的SQL(就像我们上面提到的那样)。这一点对某些程序员来说也许有些不是很清楚,我们将在下面进行更详细的讲解。

  多样性

  方法许可(Method permissions)可以通过@MethodPermissions或@Unchecked注释来声明;同样的,事务属性也可以通过 @TransactionAttribute注释来声明。规范中仍然保留资源引用和资源环境引用。这些一样可以通过注释来声明,但是有一些细微的差别。比如,上下文(context)环境要通过注入工具控制。容器根据bean对外部环境引用自动初始化一个适当的已经声明的实例变量。比如,你可以象下面这样获得一个数据源(DataSource):

@Resource(name="myDataSource") //Type is inferred from variable
public DataSource customerDB;

  在上面的例子中如果你不指定引用资源的名称(name)那么其中的customerDB会被认为是默认值。当所有的引用属性都可得到时,@Injec注释就可以这样写:

@Inject public DataSource customerDB;

  容器负责在运行时初始化customerDB数据源实例。部署人员必须在此之前在容器中定义好这些资源属性。

  更好的消息是:那些以前必须检测的异常将一去不复返。你可以声明任意的应用程序异常,而不必在再抛出或捕获其他类似CreateException和 FinderException这样的异常。容器会抛出封装在javax.ejb.EJBException中的系统级异常或者只在必要时候抛出 IllegalArgumentException或IllegalStateException异常。
 EJB文件处理模式

  在我们结束本节之前,让我的快速的浏览一下容器提供商在EJB处理模式方面可能的变更。规范中对此并没有明确的表态,但我可以想到至少两种模式。

  ·一种办法是首先利用EJB文件生成类似于EJB2.1部署模式的文件(包括必要的接口和部署描述符)然后再用类似于EJB2.1的方式来部署这个EJB组件。当然,这样产生的部署描述符可能并不标准但是它可以解决同一个容器对EJB2.1和EJB3.0兼容的问题。

  ·另一种方法是一种类似于JSP托放的部署模式。你可以把一个EJB文件放到一个预先定义的目录下,然后容器会识别这个EJB并处理它,然后部署并使之可以使用。这种方法可以建立于上面那种方法之上,在支持反复部署时有很大的帮助。考虑到部署的简单性也是EJB3.0规范的目的之一,我真诚的希望在下一个草案出来时能够确定一个模式(至少能有一个非正式的)。

  你有什么想法

  EJB3.0规范的制定正在有序的进行,为了使 EJB的开发变得更加容易,EJB规范组织作出的努力是有目共睹的。就像他们说的那样,一切对会变得简单,但做到这一点并不容易。目前已经定义了50个注释标记(还有几个将在下一个草案中发布),每一个都有自己的缺省规则和其他的操作。当然,我真的不希望EJB3.0变成EJB2.1的一个翻版"EJB 3.0 = EJB 2.1 for dummies"(希望这个等式不要成立)。最后,我还是忍不住要提一些我自己的观点:

  ·首先,规范确实使反复部署变得容易了,并且有一个简单的模式来访问运行时环境。我还是觉得home接口应该放弃。

  ·在早期的EJB规范中,实体bean用于映射一个持久化存储。理论上(也许只是理论上)可能需要把实体bean映射到一个遗留的EIS (enterprise information system)系统中。出于将来扩展的考虑这样作是有好处的,并且可以使更多的业务数据模型采用实体bean。也因此其伴随的复杂性使得实体bean不被看好。在本次提交的草案中,一个实体bean只是一个数据库的映射。并且是基于非抽象持久化模式和简单的数据访问模式的更加简单开发。

  ·我对模型变更持保留态度,我认为在EJB中包含SQL脚本片断并不是个好注意。一些开发人员完全反对包含某些“SQL片段(SQLness)”(比如 @Table 和 @Column注释)。我的观点是这些SQLness是好的,据此我们可以清楚的知道我们到底要数据库作些什么。但是某些SQL段我看来并不是很好,比如 columnDefinition="BLOB NOT NULL",这使得EJB代码和SQL之间的耦合太过紧密了。

  ·尽管对于本地SQL的支持看似很诱人,其实在EJB代码中嵌入SQL是一个非常糟糕的主意。当然,有些办法可以避免在EJB中硬编码SQL,但是这应该在规范中说明,而不能是某些开发人员自己定义的模式。

  ·假设@Table注释只用于类。在运行时通过@Table注释的name属性定义的表名称将必须对应一个实际的数据库表。规范对此应该给予清楚的说明和一致的模式。

  ·规范还需要更清楚的说明客户端编程模型,尤其是普通java客户端。规范中所有的参考都假设或者隐含的使用EJB客户端。而且规范中对客户端的向后兼容方面也没有给出明确的说法。

  ·Transient注释应该重新命名以避免和已有的transient关键字发生冲突。事实上,在这一点上我们更乐于稍微的背离一下 configuration-by-exception原则并且定义一个@Persistent注释来明确的定义持久化字段。@Persistent注释可以仅仅是一个标记注释或者它可以有几个属性来关联O/R映射注释。

  与其他规范的关联

  目前可能影响到EJB3.0的JSR有JSR175(java语言元数据工具)和JSR181(Java Web服务元数据)

  JSR175已经初步完成并且不会和EJB3.0有太大的冲突;但是JSR181与EJB3.0有两个关联的地方:

  ·Web service接口:EJB规范将采用一种机制适应JSR181以便可以把一个bean实现为一个Web service并告诉Web service如何被客户端调用。

  ·JSR 181计划采用不同的机制来处理安全问题。在早期的规范中EJB建议使用一个一致的机制(MethodPermissions),但是JSR 181计划使用一个稍微不同的方式(SecurityRoles和SecurityIdentity注释)。同样的RunAs注释的定义也存在这些许差别。这一问题还在解决中最终会在J2EE层的规范中维持其一致性。

  在J2EE 1.5中的一些开发规范可能与EJB3.0有关联。除了上面说到的几个关联之外现在没有其他的开发规范与EJB3.0有冲突。

  结束语

  在使EJB的开发变得简单高效之前,我们还有很长一段路要走。规范组织在降低EJB的开发难度方面起了个好头。O/R映射模型的提议还处在早期阶段,规范组织正在完善它。我希望它不要太复杂也不要与SQL过分的耦合。让我们不要只是停留在期望、希望、思考和请求中:提出你的想法并把你的建议发送给规范组织 ejb3-feedback@sun.com。JCP并不是很民主的组织,但是你的建议一定是有价值的。

posted @ 2007-03-03 11:12 chenweicai 阅读(153) | 评论 (0)编辑 收藏

1、EJB2.0有哪些内容?分别用在什么场合? EJB2.0和EJB1.1的区别?

  答:规范内容包括Bean提供者,应用程序装配者,EJB容器,EJB配置工具,EJB服务提供者,系统管理员。这里面,EJB容器是EJB之所以能够运行的核心。EJB容器管理着EJB的创建,撤消,激活,去活,与数据库的连接等等重要的核心工作。JSP,Servlet,EJB,JNDI,JDBC,JMS.....

  2、EJB与JAVA BEAN的区别?

  答:Java Bean 是可复用的组件,对Java Bean并没有严格的规范,理论上讲,任何一个Java类都可以是一个Bean。但通常情况下,由于Java Bean是被容器所创建(如Tomcat)的,所以Java Bean应具有一个无参的构造器,另外,通常Java Bean还要实现Serializable接口用于实现Bean的持久性。Java Bean实际上相当于微软COM模型中的本地进程内COM组件,它是不能被跨进程访问的。Enterprise Java Bean 相当于DCOM,即分布式组件。它是基于Java的远程方法调用(RMI)技术的,所以EJB可以被远程访问(跨进程、跨计算机)。但EJB必须被布署在诸如Webspere、WebLogic这样的容器中,EJB客户从不直接访问真正的EJB组件,而是通过其容器访问。EJB容器是EJB组件的代理,EJB组件由容器所创建和管理。客户通过容器来访问真正的EJB组件。

  2、EJB是基于哪些技术实现的?并说出SessionBean和EntityBean的区别,StatefulBean和StatelessBean的区别。

  答:EJB包括Session Bean、Entity Bean、Message Driven Bean,基于JNDI、RMI、JAT等技术实现。

  SessionBean在J2EE应用程序中被用来完成一些服务器端的业务操作,例如访问数据库、调用其他EJB组件。EntityBean被用来代表应用系统中用到的数据。

  对于客户机,SessionBean是一种非持久性对象,它实现某些在服务器上运行的业务逻辑。

  对于客户机,EntityBean是一种持久性对象,它代表一个存储在持久性存储器中的实体的对象视图,或是一个由现有企业应用程序实现的实体。

  Session Bean 还可以再细分为 Stateful Session Bean 与 Stateless Session Bean ,这两种的 Session Bean都可以将系统逻辑放在 method之中执行,不同的是 Stateful Session Bean 可以记录呼叫者的状态,因此通常来说,一个使用者会有一个相对应的 Stateful Session Bean 的实体。Stateless Session Bean 虽然也是逻辑组件,但是他却不负责记录使用者状态,也就是说当使用者呼叫 Stateless Session Bean 的时候,EJB Container 并不会找寻特定的 Stateless Session Bean 的实体来执行这个 method。换言之,很可能数个使用者在执行某个 Stateless Session Bean 的 methods 时,会是同一个 Bean 的 Instance 在执行。从内存方面来看, Stateful Session Bean 与 Stateless Session Bean 比较, Stateful Session Bean 会消耗 J2EE Server 较多的内存,然而 Stateful Session Bean 的优势却在于他可以维持使用者的状态。

  3、EJB与JAVA BEAN的区别?

  答:Java Bean 是可复用的组件,对Java Bean并没有严格的规范,理论上讲,任何一个Java类都可以是一个Bean。但通常情况下,由于Java Bean是被容器所创建(如Tomcat)的,所以Java Bean应具有一个无参的构造器,另外,通常Java Bean还要实现Serializable接口用于实现Bean的持久性。Java Bean实际上相当于微软COM模型中的本地进程内COM组件,它是不能被跨进程访问的。Enterprise Java Bean 相当于DCOM,即分布式组件。它是基于Java的远程方法调用(RMI)技术的,所以EJB可以被远程访问(跨进程、跨计算机)。但EJB必须被布署在诸如Webspere、WebLogic这样的容器中,EJB客户从不直接访问真正的EJB组件,而是通过其容器访问。EJB容器是EJB组件的代理,EJB组件由容器所创建和管理。客户通过容器来访问真正的EJB组件。

  EJB包括(SessionBean,EntityBean)说出他们的生命周期,及如何管理事务的?

  SessionBean:Stateless Session Bean 的生命周期是由容器决定的,当客户机发出请求要建立一个Bean的实例时,EJB容器不一定要创建一个新的Bean的实例供客户机调用,而是随便找一个现有的实例提供给客户机。当客户机第一次调用一个Stateful Session Bean 时,容器必须立即在服务器中创建一个新的Bean实例,并关联到客户机上,以后此客户机调用Stateful Session Bean 的方法时容器会把调用分派到与此客户机相关联的Bean实例。

  EntityBean:Entity Beans能存活相对较长的时间,并且状态是持续的。只要数据库中的数据存在,Entity beans就一直存活。而不是按照应用程序或者服务进程来说的。即使EJB容器崩溃了,Entity beans也是存活的。Entity Beans生命周期能够被容器或者 Beans自己管理。

  EJB通过以下技术管理实务:对象管理组织(OMG)的对象实务服务(OTS),Sun Microsystems的Transaction Service(JTS)、Java Transaction API(JTA),开发组(X/Open)的XA接口。

  4、EJB的角色和三个对象

  答:一个完整的基于EJB的分布式计算结构由六个角色组成,这六个角色可以由不同的开发商提供,每个角色所作的工作必须遵循Sun公司提供的EJB规范,以保证彼此之间的兼容性。这六个角色分别是EJB组件开发者(Enterprise Bean Provider) 、应用组合者(Application Assembler)、部署者(Deployer)、EJB 服务器提供者(EJB Server Provider)、EJB 容器提供者(EJB Container Provider)、系统管理员(System Administrator)

  三个对象是Remote(Local)接口、Home(LocalHome)接口,Bean类
5、EJB容器提供的服务

  答:主要提供声明周期管理、代码产生、持续性管理、安全、事务管理、锁和并发行管理等服务。

  6、EJB规范规定EJB中禁止的操作有哪些?

  答:1.不能操作线程和线程API(线程API指非线程对象的方法如notify,wait等),2.不能操作awt,3.不能实现服务器功能,4.不能对静态属生存取,5.不能使用IO操作直接存取文件系统,6.不能加载本地库.,7.不能将this作为变量和返回,8.不能循环调用。

  7、remote接口和home接口主要作用

  答:remote接口定义了业务方法,用于EJB客户端调用业务方法。

  home接口是EJB工厂用于创建和移除查找EJB实例

  8、bean 实例的生命周期

  答:对于Stateless Session Bean、Entity Bean、Message Driven Bean一般存在缓冲池管理,而对于Entity Bean和Statefull Session Bean存在Cache管理,通常包含创建实例,设置上下文、创建EJB Object(create)、业务方法调用、remove等过程,对于存在缓冲池管理的Bean,在create之后实例并不从内存清除,而是采用缓冲池调度机制不断重用实例,而对于存在Cache管理的Bean则通过激活和去激活机制保持Bean的状态并限制内存中实例数量。

  9、EJB的激活机制

  答:以Stateful Session Bean 为例:其Cache大小决定了内存中可以同时存在的Bean实例的数量,根据MRU或NRU算法,实例在激活和去激活状态之间迁移,激活机制是当客户端调用某个EJB实例业务方法时,如果对应EJB Object发现自己没有绑定对应的Bean实例则从其去激活Bean存储中(通过序列化机制存储实例)回复(激活)此实例。状态变迁前会调用对应的ejbActive和ejbPassivate方法。

  10、EJB的几种类型

  答:会话(Session)Bean ,实体(Entity)Bean 消息驱动的(Message Driven)Bean

  会话Bean又可分为有状态(Stateful)和无状态(Stateless)两种

  实体Bean可分为Bean管理的持续性(BMP)和容器管理的持续性(CMP)两种

  11、客服端调用EJB对象的几个基本步骤

  答:设置JNDI服务工厂以及JNDI服务地址系统属性,查找Home接口,从Home接口调用Create方法创建Remote接口,通过Remote接口调用其业务方法

posted @ 2007-03-03 10:37 chenweicai 阅读(154) | 评论 (0)编辑 收藏

JBoss,作为J2EE应用服务器,以其EJB容器卓越的性能、技术的潮流性、开发部署J2EE应用的方便性赢得了很多J2EE开发者的信赖。其中,免安装、基于JMX构架、热部署(Hot Deploy)、快速开发EJB应用等几项特征与其他商用服务器相比,显得有些得意忘形的样子。尽管其本身没有重大的缺陷,但毕竟是Open Source的开发模式,文档很少,因此要很好的掌握、精通开发基于JBoss的应用还是显得有些力不从心。
本文结合自己的开发经验,给出在JBoss 3.2.1下开发J2EE一些相关的注意事项和规则。其中,读者一定要知道JBoss 3.2.1作为JBoss的过渡产品(与JBoss 3.0.x、JBoss 4.x相比),自然有些东西和JBoss 3.0.x、JBoss 4.x有很大差别。但是,一般情况下,本文介绍的内容,大体上都适合JBoss各个版本。
下载完JBoss 3.2.1后,解压到一个没有空格的目录路径下面就可以运行JBoss,所以很方便,但前提是目标机器安装了Java 2 Standard Edition。一切就绪后,开始我们的旅程。
(假设JBoss 3.2.1安装在:C:\jboss-3.2.1_tomcat-4.1.24,本使用default配置)
一,    相关配置文件的设置
为开发J2EE应用,操作数据库成了必不可少的内容;调节日志输出的详细程度成了调试J2EE应用的关键;EJB应用的调优过程是J2EE应用的核心。等等,这些内容都是我们需要知道的。

(1)数据源的配置:

在JBoss 3.2.1中,配置数据源的步骤很简单,JBoss 3.2.1本身带了主流数据库的配置实例,于目录下:C:\jboss-3.2.1_tomcat-4.1.24\docs\examples\jca。具体使用那个配置文件取决于目标用户的数据库。如果是SQL Server 2000,则需要使用mssql-ds.xml文件(支持本地事务)或者mssql-xa-ds.xml文件(支持全局事务);如果是Oracle 9i数据库,则需要使用oracle-ds.xml文件或者oracle-xa-ds.xml文件。等等。这里以SQL Server 2000为例。
首先将mssql-ds.xml文件拷贝到目录:C:\jboss-3.2.1_tomcat-4.1.24\server\default\deploy下。然后打开文件,并作如下修改:
<datasources>
    <local-tx-datasource>
        <jndi-name>VSSDB</jndi-name>
        <connection-url>jdbc:microsoft:sqlserver://125.16.45.158:1433;DatabaseName=DDD
</connection-url>
        <driver-class>com.microsoft.jdbc.sqlserver.SQLServerDriver</driver-class>
        <user-name>sa</user-name>
        <password>sa</password>
        <min-pool-size>50</min-pool-size>
        <max-pool-size>200</max-pool-size>
    </local-tx-datasource>
</datasources>
如果目标J2EE应用只需要本地事务,则上述过程已经完成了Datasource的配置,同时这个配置将用于JDBC以及EJB通过JNDI使用。如果要实现EJB使用Datasource,则还需要修改位于目录:C:\jboss-3.2.1_tomcat-4.1.24\server\default\conf下的standardjbosscmp-jdbc.xml文件。比如,
<jbosscmp-jdbc>

   <defaults>
      <datasource>java:/VSSDB1</datasource>
      <datasource-mapping>MS SQLSERVER2000</datasource-mapping>

      <create-table>true</create-table>
      <remove-table>false</remove-table>
      <read-only>false</read-only>
      <time-out>300</time-out>
      <pk-constraint>true</pk-constraint>
      <fk-constraint>false</fk-constraint>
。。。。。。。。
其中,<datasource>java:/VSSDB</datasource>中的VSSDB就是mssql-ds.xml配置的数据源;而“java:/”前缀表明该命名空间只是对JBoss本身可见,即运行于JBoss外的应用是不能够使用这里定义的数据源,这一点希望读者注意。
其次,<datasource-mapping>MS SQLSERVER2000</datasource-mapping>中的MS SQLSERVER2000可以在该文件的其他地方找到。(如果是其他数据库,情况都是类似的,希望读者加以思考!)

(2)日志的输出详细程度配置:
    
由于JBoss 3.2.1开发采用了Log4j管理其日志信息(严格地讲,它扩展了Log4j),因此了解Log4j的机理,有助于理解JBoss 3.2.1管理日志的方式。
JBoss 3.2.1采用JMX架构的同时,且以.xml文件类型为配置文件,因此可以找到位于目录:C:\jboss-3.2.1_tomcat-4.1.24\server\default\conf下的log4j.xml文件。比如,其中一段配置示例如下:
      <appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
    <param name="Target" value="System.out"/>
    <param name="Threshold" value="INFO"/>

    <layout class="org.apache.log4j.PatternLayout">
      <!-- The default pattern: Date Priority [Category] Message\n -->
      <param name="ConversionPattern" value="%d{ABSOLUTE} %-5p [%c{1}] %m%n"/>
    </layout>
  </appender>
比如,为了调节JBoss 3.2.1控制台日志输出的详细程度(调整为DEBUG级别),我们需要修改value=”INFO”,将INFO改为DEBUG。
如果目标读者在开发Entity Beans,可以调节位于与log4j.xml文件同一目录下的standardjboss.xml文件(该文件主要是提供修改EJB相关的调试、运行、调优、部署参数)。如果目标读者Entity Beans采用的<container-name>为Standard CMP 2.x EntityBean,则将其中的<call-logging>属性的取值改为true。
<container-configuration>
        <container-name>Standard CMP 2.x EntityBean</container-name>
        <call-logging>false</call-logging>
<invoker-proxy-binding-name>entity-rmi-invoker</invoker-proxy-binding-name>
        <sync-on-commit-only>false</sync-on-commit-only>
。。。。。。。。。
完成上述两步后,读者在调试Entity Beans时通过控制台,可以看到Entity Beans发出的JDBC调用细节。

(3)Tomcat容器相关参数的配置:

如果目标读者使用JBoss 3.2.1与Tomcat 4.1.24的集成版本,则可以通过调节分别位于目录:C:\jboss-3.2.1_tomcat-4.1.24\server\default\deploy\jbossweb-tomcat.sar下的web.xml和目录:C:\jboss-3.2.1_tomcat-4.1.24\server\default\deploy\jbossweb-tomcat.sar\META-INF下的jboss-service.xml文件来达到目标读者特定需求。
比如,如果想将HTTP服务端口改为80,则可以修改jboss-service.xml文件;如果想使目标J2EE应用处理更多的文件类型,可以修改web.xml文件。

(4)相关类库的放置:

如果您的应用涉及到第三方类库,比如JDBC Driver,则可以将这些JDBC Driver存放到目录下:C:\jboss-3.2.1_tomcat-4.1.24\server\default\lib。注意,不是目录:C:\jboss-3.2.1_tomcat-4.1.24\lib下。
如果是与目标J2EE应用相关,则可以存放到目标.war(或者.ear)里面,或者xxx.war目录中的WEB-INFO\lib下。无论那种情形,都需要遵循J2EE规范。

当然,JBoss 3.2.1的配置文件有很多,比如提供邮件服务的mail-service.xml文件,等等。在这里只是给读者一些信息,如果您有相关问题,都可以试着本文介绍的一些内容解决您的问题。谢谢。

二,开发EJB应用
如果开发EJB应用,建议采用JBoss作为开发服务器,因为开发、调试、部署速度快。如果采用其他商用服务器,由于实现机理的不同,其编译的速度很慢。
如果采用Entity Beans技术,则您需要知道这么几点。第一,您目标系统的数据源有多少个操作入口,即是否存在Entity Beans之外的方式来操作数据库。如果有,则需要调节相应<container-name>的<commit-option>提交策略以及<locking-policy>策略。
比如,JBoss 3.2.1采用的<commit-option>方式有4种:A、B、C、D。当然,如果除了Entity Beans访问数据库外,别无它出,采用A是很理智的。如果有,则需要取决于具体的情况使用<commit-option>方式。同时,<commit-option>方式的选择与<locking-policy>策略有关系。
能够采用<read-only>的Entity Beans或Entity Beans Methods,则尽量采用,这样会减少或消除死锁发生的可能性。
尽量采用1:n的关系来操作n方的数据表结构,这样能够提高EJB Container的效率。

待续。。。。。。

posted @ 2007-03-01 10:47 chenweicai 阅读(170) | 评论 (0)编辑 收藏