﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-yqin-文章分类-java</title><link>http://www.blogjava.net/yqin/category/45114.html</link><description /><language>zh-cn</language><lastBuildDate>Wed, 11 Aug 2010 16:07:30 GMT</lastBuildDate><pubDate>Wed, 11 Aug 2010 16:07:30 GMT</pubDate><ttl>60</ttl><item><title>session详解</title><link>http://www.blogjava.net/yqin/articles/328302.html</link><dc:creator>爱运动 爱生活</dc:creator><author>爱运动 爱生活</author><pubDate>Mon, 09 Aug 2010 03:48:00 GMT</pubDate><guid>http://www.blogjava.net/yqin/articles/328302.html</guid><wfw:comment>http://www.blogjava.net/yqin/comments/328302.html</wfw:comment><comments>http://www.blogjava.net/yqin/articles/328302.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/yqin/comments/commentRss/328302.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/yqin/services/trackbacks/328302.html</trackback:ping><description><![CDATA[<span style="font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; font-size: 13px; color: #4b4b4b; line-height: 19px; ">
<div id="msgcns!38BC563D902DC3A1!125">
<div><strong>一、术语session<br />
<br />
</strong>在我的经验里，session这个词被滥用的程度大概仅次于transaction，更加有趣的是transaction与session在某些语境下的含义是相同的。
<p>&nbsp;&nbsp;&nbsp; session，中文经常翻译为会话，其本来的含义是指有始有终的一系列动作/消息，比如打电话时从拿起电话拨号到挂断电话这中间的一系列过程可以称之为一个 session.有时候我们可以看到这样的话&#8220;在一个浏览器会话期间，&#8230;&#8230;&#8221;，这里的会话一词用的就是其本义，是指从一个浏览器窗口打开到关闭这个期间 ①。最混乱的是&#8220;用户（客户端）在一次会话期间&#8221;这样一句话，它可能指用户的一系列动作（一般情况下是同某个具体目的相关的一系列动作，比如从登录到选购商品到结账登出这样一个网上购物的过程，有时候也被称为一个transaction），然而有时候也可能仅仅是指一次连接，也有可能是指含义①，其中的差别只能靠上下文来推断②。</p>
<p>&nbsp;&nbsp;&nbsp; 然而当session一词与网络协议相关联时，它又往往隐含了&#8220;面向连接&#8221;和/或&#8220;保持状态&#8221;这样两个含义， &#8220;面向连接&#8221;指的是在通信双方在通信之前要先建立一个通信的渠道，比如打电话，直到对方接了电话通信才能开始，与此相对的是写信，在你把信发出去的时候你并不能确认对方的地址是否正确，通信渠道不一定能建立，但对发信人来说，通信已经开始了。&#8220;保持状态&#8221;则是指通信的一方能够把一系列的消息关联起来，使得消息之间可以互相依赖，比如一个服务员能够认出再次光临的老顾客并且记得上次这个顾客还欠店里一块钱。这一类的例子有&#8220;一个TCP session&#8221;或者 &#8220;一个POP3 session&#8221;③。</p>
<p>&nbsp;&nbsp;&nbsp; 而到了web服务器蓬勃发展的时代，session在web开发语境下的语义又有了新的扩展，它的含义是指一类用来在客户端与服务器之间保持状态的解决方案④。有时候session也用来指这种解决方案的存储结构，如&#8220;把xxx保存在session 里&#8221;⑤。由于各种用于web开发的语言在一定程度上都提供了对这种解决方案的支持，所以在某种特定语言的语境下，session也被用来指代该语言的解决方案，比如经常把Java里提供的javax.servlet.http.HttpSession简称为session⑥。</p>
<p>&nbsp;&nbsp;&nbsp; 鉴于这种混乱已不可改变，本文中session一词的运用也会根据上下文有不同的含义，请大家注意分辨。</p>
<p>&nbsp;&nbsp;&nbsp; 在本文中，使用中文&#8220;浏览器会话期间&#8221;来表达含义①，使用&#8220;session机制&#8221;来表达含义④，使用&#8220;session&#8221;表达含义⑤，使用具体的&#8220;HttpSession&#8221;来表达含义⑥</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;<strong>二、HTTP协议与状态<br />
<br />
</strong>保持HTTP 协议本身是无状态的，这与HTTP协议本来的目的是相符的，客户端只需要简单的向服务器请求下载某些文件，无论是客户端还是服务器都没有必要纪录彼此过去的行为，每一次请求之间都是独立的，好比一个顾客和一个自动售货机或者一个普通的（非会员制）大卖场之间的关系一样。</p>
<p>&nbsp;&nbsp;&nbsp; 然而聪明（或者贪心？）的人们很快发现如果能够提供一些按需生成的动态信息会使web变得更加有用，就像给有线电视加上点播功能一样。这种需求一方面迫使HTML逐步添加了表单、脚本、DOM等客户端行为，另一方面在服务器端则出现了CGI规范以响应客户端的动态请求，作为传输载体的HTTP协议也添加了文件上载、 cookie这些特性。其中cookie的作用就是为了解决HTTP协议无状态的缺陷所作出的努力。至于后来出现的session机制则是又一种在客户端与服务器之间保持状态的解决方案。</p>
<p>&nbsp;&nbsp;&nbsp; 让我们用几个例子来描述一下cookie和session机制之间的区别与联系。笔者曾经常去的一家咖啡店有喝5杯咖啡免费赠一杯咖啡的优惠，然而一次性消费5杯咖啡的机会微乎其微，这时就需要某种方式来纪录某位顾客的消费数量。想象一下其实也无外乎下面的几种方案：1、该店的店员很厉害，能记住每位顾客的消费数量，只要顾客一走进咖啡店，店员就知道该怎么对待了。这种做法就是协议本身支持状态。</p>
<p>&nbsp;&nbsp;&nbsp; 2、发给顾客一张卡片，上面记录着消费的数量，一般还有个有效期限。每次消费时，如果顾客出示这张卡片，则此次消费就会与以前或以后的消费相联系起来。这种做法就是在客户端保持状态。</p>
<p>&nbsp;&nbsp;&nbsp; 3、发给顾客一张会员卡，除了卡号之外什么信息也不纪录，每次消费时，如果顾客出示该卡片，则店员在店里的纪录本上找到这个卡号对应的纪录添加一些消费信息。这种做法就是在服务器端保持状态。</p>
<p>&nbsp;&nbsp;&nbsp; 由于HTTP协议是无状态的，而出于种种考虑也不希望使之成为有状态的，因此，后面两种方案就成为现实的选择。具体来说cookie机制采用的是在客户端保持状态的方案，而session机制采用的是在服务器端保持状态的方案。同时我们也看到，由于采用服务器端保持状态的方案在客户端也需要保存一个标识，所以session机制可能需要借助于cookie机制来达到保存标识的目的，但实际上它还有其他选择。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;<strong>三、理解cookie机制<br />
<br />
</strong>cookie机制的基本原理就如上面的例子一样简单，但是还有几个问题需要解决：&#8220;会员卡&#8221;如何分发：&#8220;会员卡&#8221;的内容；以及客户如何使用&#8220;会员卡&#8221;。</p>
<p>&nbsp;&nbsp;&nbsp; 正统的cookie分发是通过扩展HTTP协议来实现的，服务器通过在HTTP的响应头中加上一行特殊的指示以提示浏览器按照指示生成相应的cookie.然而纯粹的客户端脚本如JavaScript或者VBScript也可以生成cookie.</p>
<p>&nbsp;&nbsp;&nbsp; 而cookie 的使用是由浏览器按照一定的原则在后台自动发送给服务器的。浏览器检查所有存储的cookie，如果某个cookie所声明的作用范围大于等于将要请求的资源所在的位置，则把该cookie附在请求资源的HTTP请求头上发送给服务器。意思是麦当劳的会员卡只能在麦当劳的店里出示，如果某家分店还发行了自己的会员卡，那么进这家店的时候除了要出示麦当劳的会员卡，还要出示这家店的会员卡。</p>
<p>&nbsp;&nbsp;&nbsp; cookie的内容主要包括：名字，值，过期时间，路径和域。</p>
<p>&nbsp;&nbsp;&nbsp; 其中域可以指定某一个域比如。google.com，相当于总店招牌，比如宝洁公司，也可以指定一个域下的具体某台机器比如<a href="http://www.google.com/" style="color: #1a8bc8; text-decoration: none; "><u><font color="#0000ff">www.google.com</font></u></a>或者froogle.google.com，可以用飘柔来做比。</p>
<p>&nbsp;&nbsp;&nbsp; 路径就是跟在域名后面的URL路径，比如/或者/foo等等，可以用某飘柔专柜做比。</p>
<p>&nbsp;&nbsp;&nbsp; 路径与域合在一起就构成了cookie的作用范围。</p>
<p>&nbsp;&nbsp;&nbsp; 如果不设置过期时间，则表示这个cookie的生命期为浏览器会话期间，只要关闭浏览器窗口，cookie就消失了。这种生命期为浏览器会话期的 cookie被称为会话cookie.会话cookie一般不存储在硬盘上而是保存在内存里，当然这种行为并不是规范规定的。如果设置了过期时间，浏览器就会把cookie保存到硬盘上，关闭后再次打开浏览器，这些cookie仍然有效直到超过设定的过期时间。</p>
<p>&nbsp;&nbsp;&nbsp; 存储在硬盘上的cookie 可以在不同的浏览器进程间共享，比如两个IE窗口。而对于保存在内存里的cookie，不同的浏览器有不同的处理方式。对于IE，在一个打开的窗口上按 Ctrl-N（或者从文件菜单）打开的窗口可以与原窗口共享，而使用其他方式新开的IE进程则不能共享已经打开的窗口的内存cookie；对于 Mozilla Firefox0.8，所有的进程和标签页都可以共享同样的cookie.一般来说是用javascript的window.open打开的窗口会与原窗口共享内存cookie.浏览器对于会话cookie的这种只认cookie不认人的处理方式经常给采用session机制的web应用程序开发者造成很大的困扰。</p>
<p>&nbsp;&nbsp;&nbsp; 下面就是一个goolge设置cookie的响应头的例子HTTP/1.1 302 FoundLocation：&nbsp;<a href="http://www.google.com/intl/zh-CN/Set-Cookie" style="color: #1a8bc8; text-decoration: none; "><u><font color="#0000ff">http://www.google.com/intl/zh-CN/Set-Cookie</font></u></a>： PREF=ID=0565f77e132de138：NW=1：TM=1098082649：LM=1098082649：S=KaeaCFPo49RiA_d8； expires=Sun， 17-Jan-2038 19：14：07 GMT； path=/； domain=.google.comContent-Type： text/html</p>
<p align="center"><img alt="" src="http://java.chinaitlab.com/UploadFiles_8734/200603/20060314125320274.jpg" border="0" style="border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-style: initial; border-color: initial; display: inline; " /></p>
<p>&nbsp;&nbsp;&nbsp; 这是使用HTTPLook这个HTTP Sniffer软件来俘获的HTTP通讯纪录的一部分</p>
<p align="center"><img alt="" src="http://java.chinaitlab.com/UploadFiles_8734/200603/20060314125320533.jpg" border="0" style="border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-style: initial; border-color: initial; display: inline; " /></p>
<p>&nbsp;&nbsp;&nbsp; 浏览器在再次访问goolge的资源时自动向外发送cookie</p>
<p align="center"><img alt="" src="http://java.chinaitlab.com/UploadFiles_8734/200603/20060314125322468.jpg" border="0" style="border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-style: initial; border-color: initial; display: inline; " /></p>
<p>&nbsp;&nbsp;&nbsp; 用Firefox可以很容易的观察现有的cookie的值使用HTTPLook配合Firefox可以很容易的理解cookie的工作原理。</p>
<p align="center"><img alt="" src="http://java.chinaitlab.com/UploadFiles_8734/200603/20060314125322207.jpg" border="0" style="border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-style: initial; border-color: initial; display: inline; " /></p>
<p>&nbsp;&nbsp;&nbsp; IE也可以设置在接受cookie前询问</p>
<p>&nbsp;&nbsp;&nbsp;<strong>&nbsp;四、理解session机制</strong></p>
<p>&nbsp;&nbsp;&nbsp; session机制是一种服务器端的机制，服务器使用一种类似于散列表的结构（也可能就是使用散列表）来保存信息。</p>
<p>&nbsp;&nbsp;&nbsp; 当程序需要为某个客户端的请求创建一个session的时候，服务器首先检查这个客户端的请求里是否已包含了一个session标识 - 称为 session id，如果已包含一个session id则说明以前已经为此客户端创建过session，服务器就按照session id把这个 session检索出来使用（如果检索不到，可能会新建一个），如果客户端请求不包含session id，则为此客户端创建一个session并且生成一个与此session相关联的session id，session id的值应该是一个既不会重复，又不容易被找到规律以仿造的字符串，这个 session id将被在本次响应中返回给客户端保存。</p>
<p>&nbsp;&nbsp;&nbsp; 保存这个session id的方式可以采用cookie，这样在交互过程中浏览器可以自动的按照规则把这个标识发挥给服务器。一般这个cookie的名字都是类似于SEEESIONID，而。比如weblogic对于web应用程序生成的cookie，JSESSIONID= ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng！-145788764，它的名字就是 JSESSIONID.</p>
<p>&nbsp;&nbsp;&nbsp; 由于cookie可以被人为的禁止，必须有其他机制以便在cookie被禁止时仍然能够把session id传递回服务器。经常被使用的一种技术叫做URL重写，就是把session id直接附加在URL路径的后面，附加方式也有两种，一种是作为URL路径的附加信息，表现形式为<a href="http://....../xxx" style="color: #1a8bc8; text-decoration: none; "><u><font color="#0000ff">http://&#8230;&#8230;/xxx</font></u></a>；jsessionid= ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng！-145788764另一种是作为查询字符串附加在URL后面，表现形式为<a href="http://....../xxx" style="color: #1a8bc8; text-decoration: none; "><u><font color="#0000ff">http://&#8230;&#8230;/xxx</font></u></a>？jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng！-145788764这两种方式对于用户来说是没有区别的，只是服务器在解析的时候处理的方式不同，采用第一种方式也有利于把session id的信息和正常程序参数区分开来。</p>
<p>&nbsp;&nbsp;&nbsp; 为了在整个交互过程中始终保持状态，就必须在每个客户端可能请求的路径后面都包含这个session id.</p>
<p>&nbsp;&nbsp;&nbsp; 另一种技术叫做表单隐藏字段。就是服务器会自动修改表单，添加一个隐藏字段，以便在表单提交时能够把session id传递回服务器。比如下面的表单&lt;form name="testform" action="/xxx"&gt;&lt;input type="text"&gt;&lt;/form&gt;</p>
<p>&nbsp;&nbsp;&nbsp; 在被传递给客户端之前将被改写成&lt;form name="testform" action="/xxx"&gt;&lt;input type="hidden" name="jsessionid" value="ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng！-145788764"&gt;&lt;input type="text"&gt;&lt;/form&gt;</p>
<p>&nbsp;&nbsp;&nbsp; 这种技术现在已较少应用，笔者接触过的很古老的iPlanet6（SunONE应用服务器的前身）就使用了这种技术。</p>
<p>&nbsp;&nbsp;&nbsp; 实际上这种技术可以简单的用对action应用URL重写来代替。</p>
<p>&nbsp;&nbsp;&nbsp; 在谈论session机制的时候，常常听到这样一种误解&#8220;只要关闭浏览器，session就消失了&#8221;。其实可以想象一下会员卡的例子，除非顾客主动对店家提出销卡，否则店家绝对不会轻易删除顾客的资料。对session来说也是一样的，除非程序通知服务器删除一个session，否则服务器会一直保留，程序一般都是在用户做log off的时候发个指令去删除session.然而浏览器从来不会主动在关闭之前通知服务器它将要关闭，因此服务器根本不会有机会知道浏览器已经关闭，之所以会有这种错觉，是大部分session机制都使用会话cookie来保存session id，而关闭浏览器后这个 session id就消失了，再次连接服务器时也就无法找到原来的session.如果服务器设置的cookie被保存到硬盘上，或者使用某种手段改写浏览器发出的HTTP请求头，把原来的session id发送给服务器，则再次打开浏览器仍然能够找到原来的session.</p>
<p>&nbsp;&nbsp;&nbsp; 恰恰是由于关闭浏览器不会导致session被删除，迫使服务器为seesion设置了一个失效时间，当距离客户端上一次使用session的时间超过这个失效时间时，服务器就可以认为客户端已经停止了活动，才会把session删除以节省存储空间。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;<strong>五、理解javax.servlet.http.HttpSession<br />
<br />
</strong>&nbsp;HttpSession是Java平台对session机制的实现规范，因为它仅仅是个接口，具体到每个web应用服务器的提供商，除了对规范支持之外，仍然会有一些规范里没有规定的细微差异。这里我们以BEA的Weblogic Server8.1作为例子来演示。</p>
<p>&nbsp;&nbsp;&nbsp; 首先，Weblogic Server提供了一系列的参数来控制它的HttpSession的实现，包括使用cookie的开关选项，使用URL重写的开关选项，session持久化的设置，session失效时间的设置，以及针对cookie的各种设置，比如设置cookie的名字、路径、域， cookie的生存时间等。</p>
<p>&nbsp;&nbsp;&nbsp; 一般情况下，session都是存储在内存里，当服务器进程被停止或者重启的时候，内存里的session也会被清空，如果设置了session的持久化特性，服务器就会把session保存到硬盘上，当服务器进程重新启动或这些信息将能够被再次使用， Weblogic Server支持的持久性方式包括文件、数据库、客户端cookie保存和复制。</p>
<p>&nbsp;&nbsp;&nbsp; 复制严格说来不算持久化保存，因为session实际上还是保存在内存里，不过同样的信息被复制到各个cluster内的服务器进程中，这样即使某个服务器进程停止工作也仍然可以从其他进程中取得session.</p>
<p>&nbsp;&nbsp;&nbsp; cookie生存时间的设置则会影响浏览器生成的cookie是否是一个会话cookie.默认是使用会话cookie.有兴趣的可以用它来试验我们在第四节里提到的那个误解。</p>
<p>&nbsp;&nbsp;&nbsp; cookie的路径对于web应用程序来说是一个非常重要的选项，Weblogic Server对这个选项的默认处理方式使得它与其他服务器有明显的区别。后面我们会专题讨论。</p>
<p>&nbsp;&nbsp;&nbsp; 关于session的设置参考[5]&nbsp;<a href="http://e-docs.bea.com/wls/docs70/webapp/weblogic_xml.html#1036869" style="color: #1a8bc8; text-decoration: none; "><u><font color="#0000ff">http://e-docs.bea.com/wls/docs70/webapp/weblogic_xml.html#1036869</font></u></a></p>
<p>&nbsp;&nbsp;&nbsp;<strong>&nbsp;六、HttpSession常见问题</strong>（在本小节中session的含义为⑤和⑥的混合）</p>
<p>&nbsp;&nbsp;&nbsp; 1、session在何时被创建一个常见的误解是以为session在有客户端访问时就被创建，然而事实是直到某server端程序调用 HttpServletRequest.getSession（true）这样的语句时才被创建，注意如果JSP没有显示的使用 &lt;% @page session="false"%&gt; 关闭session，则JSP文件在编译成Servlet时将会自动加上这样一条语句 HttpSession session = HttpServletRequest.getSession（true）；这也是JSP中隐含的 session对象的来历。</p>
<p>&nbsp;&nbsp;&nbsp; 由于session会消耗内存资源，因此，如果不打算使用session，应该在所有的JSP中关闭它。</p>
<p>&nbsp;&nbsp;&nbsp; 2、session何时被删除综合前面的讨论，session在下列情况下被删除a.程序调用HttpSession.invalidate（）；或b.距离上一次收到客户端发送的session id时间间隔超过了session的超时设置；或c.服务器进程被停止（非持久session）</p>
<p>&nbsp;&nbsp;&nbsp; 3、如何做到在浏览器关闭时删除session严格的讲，做不到这一点。可以做一点努力的办法是在所有的客户端页面里使用javascript代码window.oncolose来监视浏览器的关闭动作，然后向服务器发送一个请求来删除session.但是对于浏览器崩溃或者强行杀死进程这些非常规手段仍然无能为力。</p>
<p>&nbsp;&nbsp;&nbsp; 4、有个HttpSessionListener是怎么回事你可以创建这样的listener去监控session的创建和销毁事件，使得在发生这样的事件时你可以做一些相应的工作。注意是session的创建和销毁动作触发listener，而不是相反。类似的与HttpSession有关的listener还有 HttpSessionBindingListener，HttpSessionActivationListener和 HttpSessionAttributeListener.</p>
<p>&nbsp;&nbsp;&nbsp; 5、存放在session中的对象必须是可序列化的吗不是必需的。要求对象可序列化只是为了session能够在集群中被复制或者能够持久保存或者在必要时server能够暂时把session交换出内存。在 Weblogic Server的session中放置一个不可序列化的对象在控制台上会收到一个警告。我所用过的某个iPlanet版本如果 session中有不可序列化的对象，在session销毁时会有一个Exception，很奇怪。</p>
<p>&nbsp;&nbsp;&nbsp; 6、如何才能正确的应付客户端禁止cookie的可能性对所有的URL使用URL重写，包括超链接，form的action，和重定向的URL，具体做法参见[6]&nbsp;<a href="http://e-docs.bea.com/wls/docs70/webapp/sessions.html#100770" style="color: #1a8bc8; text-decoration: none; "><u><font color="#0000ff">http://e-docs.bea.com/wls/docs70/webapp/sessions.html#100770</font></u></a></p>
<p>&nbsp;&nbsp;&nbsp; 7、开两个浏览器窗口访问应用程序会使用同一个session还是不同的session参见第三小节对cookie的讨论，对session来说是只认id不认人，因此不同的浏览器，不同的窗口打开方式以及不同的cookie存储方式都会对这个问题的答案有影响。</p>
<p>&nbsp;&nbsp;&nbsp; 8、如何防止用户打开两个浏览器窗口操作导致的session混乱这个问题与防止表单多次提交是类似的，可以通过设置客户端的令牌来解决。就是在服务器每次生成一个不同的id返回给客户端，同时保存在session里，客户端提交表单时必须把这个id也返回服务器，程序首先比较返回的id与保存在session里的值是否一致，如果不一致则说明本次操作已经被提交过了。可以参看《J2EE核心模式》关于表示层模式的部分。需要注意的是对于使用javascript window.open打开的窗口，一般不设置这个id，或者使用单独的id，以防主窗口无法操作，建议不要再window.open打开的窗口里做修改操作，这样就可以不用设置。</p>
<p>&nbsp;&nbsp;&nbsp; 9、为什么在Weblogic Server中改变session的值后要重新调用一次session.setValue做这个动作主要是为了在集群环境中提示Weblogic Server session中的值发生了改变，需要向其他服务器进程复制新的session值。</p>
<p>&nbsp;&nbsp;&nbsp; 10、为什么session不见了排除session正常失效的因素之外，服务器本身的可能性应该是微乎其微的，虽然笔者在iPlanet6SP1加若干补丁的Solaris版本上倒也遇到过；浏览器插件的可能性次之，笔者也遇到过3721插件造成的问题；理论上防火墙或者代理服务器在cookie处理上也有可能会出现问题。</p>
<p>&nbsp;&nbsp;&nbsp; 出现这一问题的大部分原因都是程序的错误，最常见的就是在一个应用程序中去访问另外一个应用程序。我们在下一节讨论这个问题。</p>
<p>&nbsp;<strong>&nbsp;&nbsp; 七、跨应用程序的session共享</strong></p>
<p>&nbsp;&nbsp;&nbsp; 常常有这样的情况，一个大项目被分割成若干小项目开发，为了能够互不干扰，要求每个小项目作为一个单独的web应用程序开发，可是到了最后突然发现某几个小项目之间需要共享一些信息，或者想使用session来实现SSO（single sign on），在session中保存login的用户信息，最自然的要求是应用程序间能够访问彼此的session.</p>
<p>&nbsp;&nbsp;&nbsp; 然而按照Servlet规范，session的作用范围应该仅仅限于当前应用程序下，不同的应用程序之间是不能够互相访问对方的session的。各个应用服务器从实际效果上都遵守了这一规范，但是实现的细节却可能各有不同，因此解决跨应用程序session共享的方法也各不相同。</p>
<p>&nbsp;&nbsp;&nbsp; 首先来看一下Tomcat是如何实现web应用程序之间session的隔离的，从 Tomcat设置的cookie路径来看，它对不同的应用程序设置的cookie路径是不同的，这样不同的应用程序所用的session id是不同的，因此即使在同一个浏览器窗口里访问不同的应用程序，发送给服务器的session id也可以是不同的。</p>
<p align="center"><img alt="" src="http://java.chinaitlab.com/UploadFiles_8734/200603/20060314125323841.jpg" border="0" style="border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-style: initial; border-color: initial; display: inline; " /></p>
<p align="center"><img alt="" src="http://java.chinaitlab.com/UploadFiles_8734/200603/20060314125323233.jpg" border="0" style="border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-style: initial; border-color: initial; display: inline; " /></p>
<p>&nbsp;&nbsp;&nbsp; 根据这个特性，我们可以推测Tomcat中session的内存结构大致如下。</p>
<p align="center"><img alt="" src="http://java.chinaitlab.com/UploadFiles_8734/200603/20060314125323802.jpg" border="0" style="border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-style: initial; border-color: initial; display: inline; " /></p>
<p>&nbsp;&nbsp;&nbsp; 笔者以前用过的iPlanet也采用的是同样的方式，估计SunONE与iPlanet之间不会有太大的差别。对于这种方式的服务器，解决的思路很简单，实际实行起来也不难。要么让所有的应用程序共享一个session id，要么让应用程序能够获得其他应用程序的session id.</p>
<p>&nbsp;&nbsp;&nbsp; iPlanet中有一种很简单的方法来实现共享一个session id，那就是把各个应用程序的cookie路径都设为/（实际上应该是/NASApp，对于应用程序来讲它的作用相当于根）。</p>
<p>&nbsp;&nbsp;&nbsp; &lt;session-info&gt;&lt;path&gt;/NASApp&lt;/path&gt;&lt;/session-info&gt;</p>
<p>&nbsp;&nbsp;&nbsp; 需要注意的是，操作共享的session应该遵循一些编程约定，比如在session attribute名字的前面加上应用程序的前缀，使得 setAttribute（"name"， "neo"）变成setAttribute（"app1.name"， "neo"），以防止命名空间冲突，导致互相覆盖。</p>
<p>&nbsp;&nbsp;&nbsp; 在Tomcat中则没有这么方便的选择。在Tomcat版本3上，我们还可以有一些手段来共享session.对于版本4以上的Tomcat，目前笔者尚未发现简单的办法。只能借助于第三方的力量，比如使用文件、数据库、JMS或者客户端cookie，URL参数或者隐藏字段等手段。</p>
<p>&nbsp;&nbsp;&nbsp; 我们再看一下Weblogic Server是如何处理session的。</p>
<p align="center"><img alt="" src="http://java.chinaitlab.com/UploadFiles_8734/200603/20060314125323420.jpg" border="0" style="border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-style: initial; border-color: initial; display: inline; " /></p>
<p align="center"><img alt="" src="http://java.chinaitlab.com/UploadFiles_8734/200603/20060314125324813.jpg" border="0" style="border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-style: initial; border-color: initial; display: inline; " /></p>
<p>&nbsp;&nbsp;&nbsp; 从截屏画面上可以看到Weblogic Server对所有的应用程序设置的cookie的路径都是/，这是不是意味着在Weblogic Server中默认的就可以共享session了呢？然而一个小实验即可证明即使不同的应用程序使用的是同一个session，各个应用程序仍然只能访问自己所设置的那些属性。这说明Weblogic Server中的session的内存结构可能如下</p>
<p align="center"><img alt="" src="http://java.chinaitlab.com/UploadFiles_8734/200603/20060314125324805.jpg" border="0" style="border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-style: initial; border-color: initial; display: inline; " /></p>
<p>&nbsp;&nbsp;&nbsp; 对于这样一种结构，在 session机制本身上来解决session共享的问题应该是不可能的了。除了借助于第三方的力量，比如使用文件、数据库、JMS或者客户端 cookie，URL参数或者隐藏字段等手段，还有一种较为方便的做法，就是把一个应用程序的session放到ServletContext中，这样另外一个应用程序就可以从ServletContext中取得前一个应用程序的引用。示例代码如下，</p>
<p>&nbsp;&nbsp;&nbsp; 应用程序A context.setAttribute（"appA"， session）；</p>
<p>&nbsp;&nbsp;&nbsp; 应用程序B contextA = context.getContext（"/appA"）；HttpSession sessionA = （HttpSession）contextA.getAttribute（"appA"）；</p>
<p>&nbsp;&nbsp;&nbsp; 值得注意的是这种用法不可移植，因为根据ServletContext的JavaDoc，应用服务器可以处于安全的原因对于context.getContext（"/appA"）；返回空值，以上做法在Weblogic Server 8.1中通过。</p>
<p>&nbsp;&nbsp;&nbsp; 那么Weblogic Server为什么要把所有的应用程序的cookie路径都设为/呢？原来是为了SSO，凡是共享这个session的应用程序都可以共享认证的信息。一个简单的实验就可以证明这一点，修改首先登录的那个应用程序的描述符weblogic.xml，把cookie路径修改为/appA 访问另外一个应用程序会重新要求登录，即使是反过来，先访问cookie路径为/的应用程序，再访问修改过路径的这个，虽然不再提示登录，但是登录的用户信息也会丢失。注意做这个实验时认证方式应该使用FORM，因为浏览器和web服务器对basic认证方式有其他的处理方式，第二次请求的认证不是通过 session来实现的。具体请参看[7] secion 14.8 Authorization，你可以修改所附的示例程序来做这些试验。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;<strong>八、总结<br />
<br />
</strong>session机制本身并不复杂，然而其实现和配置上的灵活性却使得具体情况复杂多变。这也要求我们不能把仅仅某一次的经验或者某一个浏览器，服务器的经验当作普遍适用的经验，而是始终需要具体情况具体分析。</p>
<p>&nbsp;&nbsp;&nbsp; 摘要：虽然session机制在web应用程序中被采用已经很长时间了，但是仍然有很多人不清楚session机制的本质，以至不能正确的应用这一技术。本文将详细讨论session的工作机制并且对在Java web application中应用session机制时常见的问题作出解答。</p>
</div>
<div><br />
</div>
</div>
</span>
<img src ="http://www.blogjava.net/yqin/aggbug/328302.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/yqin/" target="_blank">爱运动 爱生活</a> 2010-08-09 11:48 <a href="http://www.blogjava.net/yqin/articles/328302.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>深刻理解Java中的堆和栈</title><link>http://www.blogjava.net/yqin/articles/324921.html</link><dc:creator>爱运动 爱生活</dc:creator><author>爱运动 爱生活</author><pubDate>Wed, 30 Jun 2010 13:06:00 GMT</pubDate><guid>http://www.blogjava.net/yqin/articles/324921.html</guid><wfw:comment>http://www.blogjava.net/yqin/comments/324921.html</wfw:comment><comments>http://www.blogjava.net/yqin/articles/324921.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/yqin/comments/commentRss/324921.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/yqin/services/trackbacks/324921.html</trackback:ping><description><![CDATA[<span style="font-family: 宋体; font-size: 14px; line-height: 25px; ">
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　 &nbsp;栈与堆都是<a href="http://java.chinaitlab.com/" target="_blank" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; font-size: 14px; font-family: 宋体; text-decoration: none; color: #0000ff; line-height: 22px; ">Java</a>用来在RAM中存放数据的地方。与<a href="http://c.chinaitlab.com/" target="_blank" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; font-size: 14px; font-family: 宋体; text-decoration: none; color: #0000ff; line-height: 22px; ">C++</a>不同，<a href="http://java.chinaitlab.com/" target="_blank" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; font-size: 14px; font-family: 宋体; text-decoration: none; color: #0000ff; line-height: 22px; ">Java</a>自动管理栈和堆，程序员不能直接地设置栈或堆。</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　Java的堆是一个运行时数据区,类的对象从中分配空间。这些对象通过new、newarray、anewarray和 multianewarray等指令建立，它们不需要程序代码来显式的释放。堆是由垃圾回收来负责的，堆的优势是可以动态地分配内存大小，生存期也不必事 先告诉编译器，因为它是在运行时动态分配内存的，Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是，由于要在运行时动态分配内存，存取速度较 慢。</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　栈的优势是，存取速度比堆要快，仅次于寄存器，栈数据可以共享。但缺点是，存在栈中的数据大小与生存期必须是确定的，缺乏灵活性。栈中主要存放一些基本类 型的变量(,int, short, long, byte, float, double, boolean, char)和对象句柄。</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　栈有一个很重要的特殊性，就是存在栈中的数据可以共享。假设我们同时定义：</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　int a = 3;</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　int b = 3;</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用，然后查找栈中是否有3这个值，如果没找到，就将3存放进来，然后将a指向3。接着处理int b = 3;在创建完b的引用变量后，因为在栈中已经有3这个值，便将b直接指向3。这样，就出现了a与b同时均指向3的情况。</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　这时，如果再令a=4;那么编译器会重新搜索栈中是否有4值，如果没有，则将4存放进来，并令a指向4;如果已经有了，则直接将a指向这个地 址。因此a值的改变不会影响到b的值。</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　要注意这种数据的共享与两个对象的引用同时指向一个对象的这种共享是不同的，因为这种情况a的修改并不会影响到b, 它是由编译器完成的，它有利于节省空间。而一个对象引用变量修改了这个对象的内部状态，会影响到另一个对象引用变量。</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　String是一个特殊的包装类数据。可以用：</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　String str = new String("abc");</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　String str = "abc";</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　两种的形式来创建，第一种是用new()来新建对象的，它会在存放于堆中。每调用一次就会创建一个新的对象。</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　而第二种是先在栈中创建一个对String类的对象引用变量str，然后查找栈中有没有存放"abc"，如果没有，则将"abc"存放进栈，并 令str指向&#8221;abc&#8221;，如果已经有&#8221;abc&#8221; 则直接令str指向&#8220;abc&#8221;。</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　比较类里面的数值是否相等时，用equals()方法;当<a href="http://softtest.chinaitlab.com/" target="_blank" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; font-size: 14px; font-family: 宋体; text-decoration: none; color: #0000ff; line-height: 22px; ">测试</a>两个包装类的引用是否指向同一个对象时，用==，下面用例子说明上面的理论。</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　String　str1　=　"abc";</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　String　str2　=　"abc";</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　System.out.println(str1==str2);　//true</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　可以看出str1和str2是指向同一个对象的。</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　String　str1　=new　String　("abc");</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　String　str2　=new　String　 ("abc");</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　System.out.println(str1==str2);　//　false</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　用new的方式是生成不 同的对象。每一次生成一个。</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　因此用第一种方式创建多个&#8221;abc&#8221;字符串,在内存中其实只存在一个对象而已. 这种写法有利与节省内存空间. 同时它可以在一定程度上提高程序的运行速度，因为JVM会自动根据栈中数据的实际情况来决定是否有必要创建新对象。而对于String str = new String("abc");的代码，则一概在堆中创建新对象，而不管其字符串值是否相等，是否有必要创建新对象，从而加重了程序的负担。</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　另一方面, 要注意: 我们在使用诸如String str = "abc";的格式定义类时，总是想当然地认为，创建了String类的对象str。担心陷阱!对象可能并没有被创建!而可能只是指向一个先前已经创建的 对象。只有通过new()方法才能保证每次都创建一个新的对象。</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　由于String类的immutable性质，当String变量需要经常变换其值时，应该考虑使用StringBuffer类，以提高程序效 率。</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　<strong style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">申请后系统的响应</strong></p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　栈：只要栈的剩余空间大于所申请空间，系统将为程序提供内存，否则将报异常提示栈溢出。</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　堆： 首先应该知道操作系统有一个记录空闲内存地址的链表，当系统收到程序的申请时，会遍历该链表，寻找第一个空间大于所申请空间的堆结点，然后将该结点从空闲 结点链表中删除，并将该结点的空间分配给程序，另外，对于大多数系统，会在这块内存空间中的首地址处记录本次分配的大小，这样，代码中的delete语句 才能正确的释放本内存空间。另外，由于找到的堆结点的大小不一定正好等于申请的大小，系统会自动的将多余的那部分重新放入空闲链表中。</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　<strong style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">申请大小的限制</strong></p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　栈：在<a href="http://windows.chinaitlab.com/" target="_blank" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; font-size: 14px; font-family: 宋体; text-decoration: none; color: #0000ff; line-height: 22px; ">Windows</a>下,栈是向低地址扩展的数据结构，是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的， 在WINDOWS下，栈的大小是2M(也可能是1M，它是一个编译时就确定的常数)，如果申请的空间超过栈的剩余空间时，将提示overflow。因此， 能从栈获得的空间较小。</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　堆：堆是向高地址扩展的数据结构，是不连续的内存区域。这是由于系统是用链表来<a href="http://www.storworld.com/" target="_blank" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; font-size: 14px; font-family: 宋体; text-decoration: none; color: #0000ff; line-height: 22px; ">存储</a>的空闲内存地址的，自然是不连续的，而链表的遍历方向是由低 地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见，堆获得的空间比较灵活，也比较大。</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　<strong style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">申请效率的比较：</strong></p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　栈由系统自动分配，速度较快。但程序员是无法控制的。</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　堆是由new分配的内存，一般速度比较慢，而且容易产生内存碎片,不过用起来最方便.</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　另外，在WINDOWS下，最好的方式是用VirtualAlloc分配内存，他不是在堆，也不是在栈是直接在进程的地址空间中保留一快内存， 虽然用起来最不方便。但是速度快，也最灵活。</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　<strong style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">堆和栈中的<a href="http://www.storworld.com/" target="_blank" style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; font-size: 14px; font-family: 宋体; text-decoration: none; color: #0000ff; line-height: 22px; ">存储</a>内容</strong></p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　栈：在函数调用时，第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址，然后是函数的各个参数，在大多数的C编译 器中，参数是由右往左入栈的，然后是函数中的局部变量。注意静态变量是不入栈的。</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　当本次函数调用结束后，局部变量先出栈，然后是参数，最后栈顶指针指向最开始存的地址，也就是主函数中的下一条指令，程序由该点继续运行。</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　堆：一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　<strong style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">存取效率的比较</strong></p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　char s1[] = "aaaaaaaaaaaaaaa";</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　char *s2 = "bbbbbbbbbbbbbbbbb";</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　aaaaaaaaaaa是在运行时刻赋值的;</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　而bbbbbbbbbbb是在编译时就确定的;</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　但是，在以后的存取中，在栈上的数组比指针所指向的字符串(例如堆)快。</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　比如：</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　void　main()</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　{</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　char　a　=　1;</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　char　c[]　=　 "1234567890";</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　char　*p　="1234567890";</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　a　=　c[1];</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　a　=　 p[1];</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　return;</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　}</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　对应的汇编代码</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　10:　a　=　c[1];</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　00401067　8A　4D　F1　mov　cl,byte　ptr　[ebp-0Fh]</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　0040106A　88　4D　FC　mov　byte　ptr　[ebp-4],cl</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　11:　a　=　p[1];</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　0040106D　8B　55　EC　mov　edx,dword　ptr　[ebp-14h]</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　00401070　8A　42　01　 mov　al,byte　ptr　[edx+1]</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　00401073　88　45　FC　mov　byte　ptr　[ebp-4],al</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　第一种在读取时直接就把字符串中的元素读到寄存器cl中，而第二种则要先把指针值读到edx中，在根据</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　edx读取字符，显然慢了。</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　<strong style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">小结：</strong></p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　堆和栈的区别可以用如下的比喻来看出：</p>
<p style="margin-top: 10px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; word-wrap: break-word; ">　　使用栈就象我们去饭馆里吃饭，只管点菜(发出申请)、付钱、和吃(使用)，吃饱了就走，不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作， 他的好处是快捷，但是自由度小。</p>
</span>
<img src ="http://www.blogjava.net/yqin/aggbug/324921.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/yqin/" target="_blank">爱运动 爱生活</a> 2010-06-30 21:06 <a href="http://www.blogjava.net/yqin/articles/324921.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>HTTP中的重定向和请求转发的区别</title><link>http://www.blogjava.net/yqin/articles/322952.html</link><dc:creator>爱运动 爱生活</dc:creator><author>爱运动 爱生活</author><pubDate>Mon, 07 Jun 2010 02:56:00 GMT</pubDate><guid>http://www.blogjava.net/yqin/articles/322952.html</guid><wfw:comment>http://www.blogjava.net/yqin/comments/322952.html</wfw:comment><comments>http://www.blogjava.net/yqin/articles/322952.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/yqin/comments/commentRss/322952.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/yqin/services/trackbacks/322952.html</trackback:ping><description><![CDATA[<p><strong><span style="font-size:14.0pt;font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">一、调用方式</span></strong></p>
<p><span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">我们知道，在</span>servlet<span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">中调用转发、重定向的语句如下：</span></p>
<p><span>request.getRequestDispatcher("new.jsp").forward(request,
response);&nbsp;&nbsp; //</span><span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">转发到</span>new.jsp</p>
<p>response.sendRedirect("new.jsp");&nbsp;&nbsp; //<span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">重定向到</span>new.jsp</p>
<p><span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">在</span>jsp<span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">页面中你也会看到通过下面的方式实现转发：</span></p>
<p><span>&lt;jsp:forward page="apage.jsp"
/&gt;</span></p>
<p><span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">当然也可以在</span>jsp<span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">页面中实现重定向：</span></p>
<p><span>&lt;%response.sendRedirect("new.jsp");
%&gt;&nbsp;//</span><span style="font-family:
宋体;Times New Roman&quot;;Times New Roman&quot;">重定向到</span>new.jsp</p>
<p><strong><span style="font-size:14.0pt;font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">二、本质区别</span></strong></p>
<p><strong><span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">解释一</span></strong></p>
<p><span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">一句话，<span style="color:red">转发是服务器行为，重定向是客户端行为。</span>为什么这样说呢，这就要看两个动作的工作流程：</span></p>
<p><span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;;color:red">转发过程：</span><span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">客户浏览器发送</span>http<span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">请求——》</span>web<span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">服务器接受此请求——》调用内部的一个方法在容器内部完成请求处理和转发动作——》将目标资源发送给客户；在这里，转发的路径必须是同一个</span>web<span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">容器下的</span>url<span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">，其不能转向到其他的</span>web<span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">路径上去，中间传递的是自己的容器内的</span>request<span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">。在客户浏览器路径栏显示的仍然是其第一次访问的路径，也就是说客户是感觉不到服务器做了转发的。转发行为是浏览器只做了一次访问请求。</span></p>
<p><span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;;color:red">重定向过程：</span><span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">客户浏览器发送</span>http<span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">请求——》</span>web<span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">服务器接受后发送</span>302<span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">状态码响应及对应新的</span>location<span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">给客户浏览器——》客户浏览器发现是</span>302<span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">响应，则<strong>自动</strong>再发送一个新的</span>http<span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">请求，请求</span>url<span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">是新的</span>location<span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">地址——》服务器根据此请求寻找资源并发送给客户。在这里</span>location<span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">可以重定向到任意</span>URL<span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">，既然是浏览器重新发出了请求，则就没有什么</span>request<span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">传递的概念了。在客户浏览器路径栏显示的是其重定向的路径，客户可以观察到地址的变化的。重定向行为是浏览器做了至少两次的访问请求的。</span></p>
<p><strong><span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">解释二</span></strong></p>
<p><span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">重定向，其实是两次</span>request</p>
<p><span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">第一次，客户端</span>request&nbsp;&nbsp; A,<span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">服务器响应，并</span>response<span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">回来，告诉浏览器，你应该去</span>B<span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">。这个时候</span>IE<span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">可以看到地址变了，而且历史的回退按钮也亮了。重定向可以访问自己</span>web<span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">应用以外的资源。<span style="color:red">在重定向的过程中，传输的信息会被丢失。</span></span></p>
<p><span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">例子：</span></p>
<p style="text-indent:21.0pt">response.sendRedirect("loginsuccess.jsp");</p>
<p><span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">请求转发是服务器内部把对一个</span>request/response<span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">的处理权，移交给另外一个</span></p>
<p><span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">对于客户端而言，它只知道自己最早请求的那个</span>A<span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">，而不知道中间的</span>B<span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">，甚至</span>C<span style="font-family:
宋体;Times New Roman&quot;;Times New Roman&quot;">、</span>D<span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">。</span><span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;;color:red">传输的信息不会丢失。</span></p>
<p><span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">例子：</span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; RequestDispatcher
dis=request.getRequestDispatcher(&#8220;loginsuccess.jsp&#8221;);</span></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Dis.forward(request,response);</p>
<p><strong><span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">解释三</span></strong></p>
<p><span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">假设你去办理某个执照</span></p>
<p><span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">重定向：你先去了</span>A<span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">局，</span>A<span style="font-family:
宋体;Times New Roman&quot;;Times New Roman&quot;">局的人说：&#8220;这个事情不归我们管，去</span>B<span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">局&#8221;，然后，你就从</span>A<span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">退了出来，自己乘车去了</span>B<span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">局。</span></p>
<p><span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">转发：你先去了</span>A<span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">局，</span>A<span style="font-family:
宋体;Times New Roman&quot;;Times New Roman&quot;">局看了以后，知道这个事情其实应该</span>B<span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">局来管，但是他没有把你退回来，而是让你坐一会儿，自己到后面办公室联系了</span>B<span style="font-family:宋体;Times New Roman&quot;;Times New Roman&quot;">的人，让他们办好后，送了过来。</span></p>
<img src ="http://www.blogjava.net/yqin/aggbug/322952.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/yqin/" target="_blank">爱运动 爱生活</a> 2010-06-07 10:56 <a href="http://www.blogjava.net/yqin/articles/322952.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java 反射（Reflection） 经典实用例子</title><link>http://www.blogjava.net/yqin/articles/322164.html</link><dc:creator>爱运动 爱生活</dc:creator><author>爱运动 爱生活</author><pubDate>Fri, 28 May 2010 09:42:00 GMT</pubDate><guid>http://www.blogjava.net/yqin/articles/322164.html</guid><wfw:comment>http://www.blogjava.net/yqin/comments/322164.html</wfw:comment><comments>http://www.blogjava.net/yqin/articles/322164.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/yqin/comments/commentRss/322164.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/yqin/services/trackbacks/322164.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: Java提供了一套机制来动态执行方法和构造方法，以及数组操作等，这套机制就叫——反射。反射机制是如今很多流行框架的实现基础，其中包括Spring、Hibernate等。原理性的问题不是本文的重点，接下来让我们在实例中学习这套精彩的机制。1. 得到某个对象的属性    public&nbsp;Object&nbsp;getProperty(Object&nbsp;owner,&n...&nbsp;&nbsp;<a href='http://www.blogjava.net/yqin/articles/322164.html'>阅读全文</a><img src ="http://www.blogjava.net/yqin/aggbug/322164.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/yqin/" target="_blank">爱运动 爱生活</a> 2010-05-28 17:42 <a href="http://www.blogjava.net/yqin/articles/322164.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>