<?xml version="1.0" encoding="gb2312"?>

<!-- RSS generated by oioj.net on 4/16/2004 ; 感谢LeXRus提供 RSS 2.0 文档; 此文件可自由使用，但请保留此行信息 --> 
<!-- Source download URL: http://blogger.org.cn/blog/rss2.asp       -->
<rss version="2.0">

<channel>
<title>happysearch的博客</title>
<link>http://blogger.org.cn/blog/blog.asp?name=happysearch</link>
<description>happysearch的博客</description>
<copyright>blogger.org.cn</copyright>
<generator>W3CHINA Blog</generator>
<webMaster>webmaster@blogger.org.cn</webMaster>
<item>
<title><![CDATA[From: http://ms.mblogger.cn/viking/posts/13437.aspx]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=happysearch&amp;id=7180</link>
<author>happysearch</author>
<pubDate>2005/7/13 11:14:31</pubDate>
<description><![CDATA[转载自: <A href="http://ms.mblogger.cn/viking/posts/13437.aspx" target=_blank>http://ms.mblogger.cn/viking/posts/13437.aspx</A><BR><BR>]]></description>
</item><item>
<title><![CDATA[DOM4J 使用简介]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=happysearch&amp;id=6044</link>
<author>happysearch</author>
<pubDate>2005/5/24 15:39:54</pubDate>
<description><![CDATA[转载自: <A href="http://dev.csdn.net/article/22/22753.shtm" target=_blank>http://dev.csdn.net/article/22/22753.shtm</A><BR><BR>]]></description>
</item><item>
<title><![CDATA[Session详解]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=happysearch&amp;id=2964</link>
<author>happysearch</author>
<pubDate>2005/2/21 10:07:30</pubDate>
<description><![CDATA[转载自: <A href="http://yhe.dalian.cn/showlog.jspe?log_id=112#31" target=_blank>http://yhe.dalian.cn/showlog.jspe?log_id=112#31</A><BR><BR><TR><TD noWrap><FONT class=log_title>Session详解</FONT>&nbsp; [<FONT class=smalltext>05-2-2</FONT> | <A href="http://dev2dev.bea.com.cn/bbs/jishudata/Article.jsp" target=_blank>bea</A>] </TD><TD align="right"><IMG src="http://yhe.dalian.cn/images/hn2_t_sunny.gif"> <IMG src="http://yhe.dalian.cn/images/hn2_sunny.gif" align=absMiddle>&nbsp; <IMG src="http://yhe.dalian.cn/images/face3.gif"> </TD></TR><TR><TD colSpan="2" height="1">
<HR color=#cccccc noShade SIZE=1>
</TD></TR><TR><TD style="WORD-BREAK: break-all" colSpan="2">
<P>作者：郎云鹏（dev2dev ID: hippiewolf）</P>
<P>摘要：虽然session机制在web应用程序中被采用已经很长时间了，但是仍然有很多人不清楚session机制的本质，以至不能正确的应用这一技术。本文将详细讨论session的工作机制并且对在Java web application中应用session机制时常见的问题作出解答。</P>
<P>目录：<BR><A href="#1">一、术语session</A><BR><A href="#2">二、HTTP协议与状态保持</A><BR><A href="#3">三、理解cookie机制</A><BR><A href="#4">四、理解session机制</A><BR><A href="#5">五、理解javax.servlet.http.HttpSession</A><BR><A href="#6">六、HttpSession常见问题</A><BR><A href="#7">七、跨应用程序的session共享</A><BR><A href="#8">八、总结</A><BR><A href="#9">参考文档</A></P>
<P id=#1><STRONG>一、术语session</STRONG><BR>在我的经验里，session这个词被滥用的程度大概仅次于transaction，更加有趣的是transaction与session在某些语境下的含义是相同的。</P>
<P>session，中文经常翻译为会话，其本来的含义是指有始有终的一系列动作/消息，比如打电话时从拿起电话拨号到挂断电话这中间的一系列过程可以称之为一个session。有时候我们可以看到这样的话“在一个浏览器会话期间，...”，这里的会话一词用的就是其本义，是指从一个浏览器窗口打开到关闭这个期间①。最混乱的是“用户（客户端）在一次会话期间”这样一句话，它可能指用户的一系列动作（一般情况下是同某个具体目的相关的一系列动作，比如从登录到选购商品到结账登出这样一个网上购物的过程，有时候也被称为一个transaction），然而有时候也可能仅仅是指一次连接，也有可能是指含义①，其中的差别只能靠上下文来推断②。</P>
<P>然而当session一词与网络协议相关联时，它又往往隐含了“面向连接”和/或“保持状态”这样两个含义，“面向连接”指的是在通信双方在通信之前要先建立一个通信的渠道，比如打电话，直到对方接了电话通信才能开始，与此相对的是写信，在你把信发出去的时候你并不能确认对方的地址是否正确，通信渠道不一定能建立，但对发信人来说，通信已经开始了。“保持状态”则是指通信的一方能够把一系列的消息关联起来，使得消息之间可以互相依赖，比如一个服务员能够认出再次光临的老顾客并且记得上次这个顾客还欠店里一块钱。这一类的例子有“一个TCP session”或者“一个POP3 session”③。</P>
<P>而到了web服务器蓬勃发展的时代，session在web开发语境下的语义又有了新的扩展，它的含义是指一类用来在客户端与服务器之间保持状态的解决方案④。有时候session也用来指这种解决方案的存储结构，如“把xxx保存在session里”⑤。由于各种用于web开发的语言在一定程度上都提供了对这种解决方案的支持，所以在某种特定语言的语境下，session也被用来指代该语言的解决方案，比如经常把Java里提供的javax.servlet.http.HttpSession简称为session⑥。</P>
<P>鉴于这种混乱已不可改变，本文中session一词的运用也会根据上下文有不同的含义，请大家注意分辨。<BR>在本文中，使用中文“浏览器会话期间”来表达含义①，使用“session机制”来表达含义④，使用“session”表达含义⑤，使用具体的“HttpSession”来表达含义⑥</P>
<P id=#2><STRONG>二、HTTP协议与状态保持</STRONG><BR>HTTP协议本身是无状态的，这与HTTP协议本来的目的是相符的，客户端只需要简单的向服务器请求下载某些文件，无论是客户端还是服务器都没有必要纪录彼此过去的行为，每一次请求之间都是独立的，好比一个顾客和一个自动售货机或者一个普通的（非会员制）大卖场之间的关系一样。</P>
<P>然而聪明（或者贪心？）的人们很快发现如果能够提供一些按需生成的动态信息会使web变得更加有用，就像给有线电视加上点播功能一样。这种需求一方面迫使HTML逐步添加了表单、脚本、DOM等客户端行为，另一方面在服务器端则出现了CGI规范以响应客户端的动态请求，作为传输载体的HTTP协议也添加了文件上载、cookie这些特性。其中cookie的作用就是为了解决HTTP协议无状态的缺陷所作出的努力。至于后来出现的session机制则是又一种在客户端与服务器之间保持状态的解决方案。</P>
<P>让我们用几个例子来描述一下cookie和session机制之间的区别与联系。笔者曾经常去的一家咖啡店有喝5杯咖啡免费赠一杯咖啡的优惠，然而一次性消费5杯咖啡的机会微乎其微，这时就需要某种方式来纪录某位顾客的消费数量。想象一下其实也无外乎下面的几种方案：<BR>1、该店的店员很厉害，能记住每位顾客的消费数量，只要顾客一走进咖啡店，店员就知道该怎么对待了。这种做法就是协议本身支持状态。<BR>2、发给顾客一张卡片，上面记录着消费的数量，一般还有个有效期限。每次消费时，如果顾客出示这张卡片，则此次消费就会与以前或以后的消费相联系起来。这种做法就是在客户端保持状态。<BR>3、发给顾客一张会员卡，除了卡号之外什么信息也不纪录，每次消费时，如果顾客出示该卡片，则店员在店里的纪录本上找到这个卡号对应的纪录添加一些消费信息。这种做法就是在服务器端保持状态。</P>
<P>由于HTTP协议是无状态的，而出于种种考虑也不希望使之成为有状态的，因此，后面两种方案就成为现实的选择。具体来说cookie机制采用的是在客户端保持状态的方案，而session机制采用的是在服务器端保持状态的方案。同时我们也看到，由于采用服务器端保持状态的方案在客户端也需要保存一个标识，所以session机制可能需要借助于cookie机制来达到保存标识的目的，但实际上它还有其他选择。</P>
<P id=#3><STRONG>三、理解cookie机制</STRONG> <BR>cookie机制的基本原理就如上面的例子一样简单，但是还有几个问题需要解决：“会员卡”如何分发；“会员卡”的内容；以及客户如何使用“会员卡”。</P>
<P>正统的cookie分发是通过扩展HTTP协议来实现的，服务器通过在HTTP的响应头中加上一行特殊的指示以提示浏览器按照指示生成相应的cookie。然而纯粹的客户端脚本如JavaScript或者VBScript也可以生成cookie。</P>
<P>而cookie的使用是由浏览器按照一定的原则在后台自动发送给服务器的。浏览器检查所有存储的cookie，如果某个cookie所声明的作用范围大于等于将要请求的资源所在的位置，则把该cookie附在请求资源的HTTP请求头上发送给服务器。意思是麦当劳的会员卡只能在麦当劳的店里出示，如果某家分店还发行了自己的会员卡，那么进这家店的时候除了要出示麦当劳的会员卡，还要出示这家店的会员卡。</P>
<P>cookie的内容主要包括：名字，值，过期时间，路径和域。<BR>其中域可以指定某一个域比如.google.com，相当于总店招牌，比如宝洁公司，也可以指定一个域下的具体某台机器比如www.google.com或者froogle.google.com，可以用飘柔来做比。<BR>路径就是跟在域名后面的URL路径，比如/或者/foo等等，可以用某飘柔专柜做比。<BR>路径与域合在一起就构成了cookie的作用范围。<BR>如果不设置过期时间，则表示这个cookie的生命期为浏览器会话期间，只要关闭浏览器窗口，cookie就消失了。这种生命期为浏览器会话期的cookie被称为会话cookie。会话cookie一般不存储在硬盘上而是保存在内存里，当然这种行为并不是规范规定的。如果设置了过期时间，浏览器就会把cookie保存到硬盘上，关闭后再次打开浏览器，这些cookie仍然有效直到超过设定的过期时间。</P>
<P>存储在硬盘上的cookie可以在不同的浏览器进程间共享，比如两个IE窗口。而对于保存在内存里的cookie，不同的浏览器有不同的处理方式。对于IE，在一个打开的窗口上按Ctrl-N（或者从文件菜单）打开的窗口可以与原窗口共享，而使用其他方式新开的IE进程则不能共享已经打开的窗口的内存cookie；对于Mozilla Firefox0.8，所有的进程和标签页都可以共享同样的cookie。一般来说是用javascript的window.open打开的窗口会与原窗口共享内存cookie。浏览器对于会话cookie的这种只认cookie不认人的处理方式经常给采用session机制的web应用程序开发者造成很大的困扰。</P>
<P>下面就是一个goolge设置cookie的响应头的例子<BR>HTTP/1.1 302 Found<BR>Location: http://www.google.com/intl/zh-CN/<BR>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<BR>Content-Type: text/html</P>
<P align=center><IMG height=293 alt="" src="http://dev2dev.bea.com.cn/images/paihang_article/041020/image002.jpg" width=408></P>
<P><BR>这是使用HTTPLook这个HTTP Sniffer软件来俘获的HTTP通讯纪录的一部分</P>
<P align=center><IMG height=344 alt="" src="http://dev2dev.bea.com.cn/images/paihang_article/041020/image004.jpg" width=432></P>
<P><BR>浏览器在再次访问goolge的资源时自动向外发送cookie</P>
<P align=center><IMG height=305 alt="" src="http://dev2dev.bea.com.cn/images/paihang_article/041020/image006.jpg" width=421> </P>
<P><BR>使用Firefox可以很容易的观察现有的cookie的值<BR>使用HTTPLook配合Firefox可以很容易的理解cookie的工作原理。</P>
<P align=center><IMG height=248 alt="" src="http://dev2dev.bea.com.cn/images/paihang_article/041020/image008.jpg" width=324></P>
<P><BR>IE也可以设置在接受cookie前询问</P>
<P align=center><IMG height=249 alt="" src="http://dev2dev.bea.com.cn/images/paihang_article/041020/image010.jpg" width=239> </P>
<P><BR>这是一个询问接受cookie的对话框。</P>
<P id=#4><STRONG>四、理解session机制</STRONG><BR>session机制是一种服务器端的机制，服务器使用一种类似于散列表的结构（也可能就是使用散列表）来保存信息。</P>
<P>当程序需要为某个客户端的请求创建一个session的时候，服务器首先检查这个客户端的请求里是否已包含了一个session标识 - 称为session id，如果已包含一个session id则说明以前已经为此客户端创建过session，服务器就按照session id把这个session检索出来使用（如果检索不到，可能会新建一个），如果客户端请求不包含session id，则为此客户端创建一个session并且生成一个与此session相关联的session id，session id的值应该是一个既不会重复，又不容易被找到规律以仿造的字符串，这个session id将被在本次响应中返回给客户端保存。</P>
<P>保存这个session id的方式可以采用cookie，这样在交互过程中浏览器可以自动的按照规则把这个标识发挥给服务器。一般这个cookie的名字都是类似于SEEESIONID，而。比如weblogic对于web应用程序生成的cookie，JSESSIONID=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764，它的名字就是JSESSIONID。</P>
<P>由于cookie可以被人为的禁止，必须有其他机制以便在cookie被禁止时仍然能够把session id传递回服务器。经常被使用的一种技术叫做URL重写，就是把session id直接附加在URL路径的后面，附加方式也有两种，一种是作为URL路径的附加信息，表现形式为http://...../xxx;jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764<BR>另一种是作为查询字符串附加在URL后面，表现形式为http://...../xxx?jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764<BR>这两种方式对于用户来说是没有区别的，只是服务器在解析的时候处理的方式不同，采用第一种方式也有利于把session id的信息和正常程序参数区分开来。<BR>为了在整个交互过程中始终保持状态，就必须在每个客户端可能请求的路径后面都包含这个session id。</P>
<P>另一种技术叫做表单隐藏字段。就是服务器会自动修改表单，添加一个隐藏字段，以便在表单提交时能够把session id传递回服务器。比如下面的表单<BR>
<FORM name=testform action=/xxx><BR><INPUT><BR></FORM><BR>在被传递给客户端之前将被改写成<BR>
<FORM name=testform action=/xxx><BR><INPUT type=hidden value=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764 name=jsessionid><BR><INPUT><BR></FORM><BR>这种技术现在已较少应用，笔者接触过的很古老的iPlanet6(SunONE应用服务器的前身)就使用了这种技术。<BR>实际上这种技术可以简单的用对action应用URL重写来代替。</P>
<P>在谈论session机制的时候，常常听到这样一种误解“只要关闭浏览器，session就消失了”。其实可以想象一下会员卡的例子，除非顾客主动对店家提出销卡，否则店家绝对不会轻易删除顾客的资料。对session来说也是一样的，除非程序通知服务器删除一个session，否则服务器会一直保留，程序一般都是在用户做log off的时候发个指令去删除session。然而浏览器从来不会主动在关闭之前通知服务器它将要关闭，因此服务器根本不会有机会知道浏览器已经关闭，之所以会有这种错觉，是大部分session机制都使用会话cookie来保存session id，而关闭浏览器后这个session id就消失了，再次连接服务器时也就无法找到原来的session。如果服务器设置的cookie被保存到硬盘上，或者使用某种手段改写浏览器发出的HTTP请求头，把原来的session id发送给服务器，则再次打开浏览器仍然能够找到原来的session。</P>
<P>恰恰是由于关闭浏览器不会导致session被删除，迫使服务器为seesion设置了一个失效时间，当距离客户端上一次使用session的时间超过这个失效时间时，服务器就可以认为客户端已经停止了活动，才会把session删除以节省存储空间。</P>
<P id=#5><STRONG>五、理解javax.servlet.http.HttpSession</STRONG><BR>HttpSession是Java平台对session机制的实现规范，因为它仅仅是个接口，具体到每个web应用服务器的提供商，除了对规范支持之外，仍然会有一些规范里没有规定的细微差异。这里我们以BEA的Weblogic Server8.1作为例子来演示。</P>
<P>首先，Weblogic Server提供了一系列的参数来控制它的HttpSession的实现，包括使用cookie的开关选项，使用URL重写的开关选项，session持久化的设置，session失效时间的设置，以及针对cookie的各种设置，比如设置cookie的名字、路径、域，cookie的生存时间等。</P>
<P>一般情况下，session都是存储在内存里，当服务器进程被停止或者重启的时候，内存里的session也会被清空，如果设置了session的持久化特性，服务器就会把session保存到硬盘上，当服务器进程重新启动或这些信息将能够被再次使用，Weblogic Server支持的持久性方式包括文件、数据库、客户端cookie保存和复制。</P>
<P>复制严格说来不算持久化保存，因为session实际上还是保存在内存里，不过同样的信息被复制到各个cluster内的服务器进程中，这样即使某个服务器进程停止工作也仍然可以从其他进程中取得session。</P>
<P>cookie生存时间的设置则会影响浏览器生成的cookie是否是一个会话cookie。默认是使用会话cookie。有兴趣的可以用它来试验我们在第四节里提到的那个误解。</P>
<P>cookie的路径对于web应用程序来说是一个非常重要的选项，Weblogic Server对这个选项的默认处理方式使得它与其他服务器有明显的区别。后面我们会专题讨论。</P>
<P>关于session的设置参考[5] http://e-docs.bea.com/wls/docs70/webapp/weblogic_xml.html#1036869</P>
<P id=#6><STRONG>六、HttpSession常见问题</STRONG><BR>（在本小节中session的含义为⑤和⑥的混合）</P>
<P><BR>1、session在何时被创建<BR>一个常见的误解是以为session在有客户端访问时就被创建，然而事实是直到某server端程序调用HttpServletRequest.getSession(true)这样的语句时才被创建，注意如果JSP没有显示的使用 <!--age session="false-->关闭session，则JSP文件在编译成Servlet时将会自动加上这样一条语句HttpSession session = HttpServletRequest.getSession(true);这也是JSP中隐含的session对象的来历。</P>
<P>由于session会消耗内存资源，因此，如果不打算使用session，应该在所有的JSP中关闭它。</P>
<P>2、session何时被删除<BR>综合前面的讨论，session在下列情况下被删除a.程序调用HttpSession.invalidate();或b.距离上一次收到客户端发送的session id时间间隔超过了session的超时设置;或c.服务器进程被停止（非持久session）</P>
<P>3、如何做到在浏览器关闭时删除session<BR>严格的讲，做不到这一点。可以做一点努力的办法是在所有的客户端页面里使用javascript代码window.oncolose来监视浏览器的关闭动作，然后向服务器发送一个请求来删除session。但是对于浏览器崩溃或者强行杀死进程这些非常规手段仍然无能为力。</P>
<P>4、有个HttpSessionListener是怎么回事<BR>你可以创建这样的listener去监控session的创建和销毁事件，使得在发生这样的事件时你可以做一些相应的工作。注意是session的创建和销毁动作触发listener，而不是相反。类似的与HttpSession有关的listener还有HttpSessionBindingListener，HttpSessionActivationListener和HttpSessionAttributeListener。</P>
<P>5、存放在session中的对象必须是可序列化的吗<BR>不是必需的。要求对象可序列化只是为了session能够在集群中被复制或者能够持久保存或者在必要时server能够暂时把session交换出内存。在Weblogic Server的session中放置一个不可序列化的对象在控制台上会收到一个警告。我所用过的某个iPlanet版本如果session中有不可序列化的对象，在session销毁时会有一个Exception，很奇怪。</P>
<P>6、如何才能正确的应付客户端禁止cookie的可能性<BR>对所有的URL使用URL重写，包括超链接，form的action，和重定向的URL，具体做法参见[6]<BR>http://e-docs.bea.com/wls/docs70/webapp/sessions.html#100770</P>
<P>7、开两个浏览器窗口访问应用程序会使用同一个session还是不同的session<BR>参见第三小节对cookie的讨论，对session来说是只认id不认人，因此不同的浏览器，不同的窗口打开方式以及不同的cookie存储方式都会对这个问题的答案有影响。</P>
<P>8、如何防止用户打开两个浏览器窗口操作导致的session混乱<BR>这个问题与防止表单多次提交是类似的，可以通过设置客户端的令牌来解决。就是在服务器每次生成一个不同的id返回给客户端，同时保存在session里，客户端提交表单时必须把这个id也返回服务器，程序首先比较返回的id与保存在session里的值是否一致，如果不一致则说明本次操作已经被提交过了。可以参看《J2EE核心模式》关于表示层模式的部分。需要注意的是对于使用javascript window.open打开的窗口，一般不设置这个id，或者使用单独的id，以防主窗口无法操作，建议不要再window.open打开的窗口里做修改操作，这样就可以不用设置。</P>
<P>9、为什么在Weblogic Server中改变session的值后要重新调用一次session.setValue<BR>做这个动作主要是为了在集群环境中提示Weblogic Server session中的值发生了改变，需要向其他服务器进程复制新的session值。</P>
<P>10、为什么session不见了<BR>排除session正常失效的因素之外，服务器本身的可能性应该是微乎其微的，虽然笔者在iPlanet6SP1加若干补丁的Solaris版本上倒也遇到过；浏览器插件的可能性次之，笔者也遇到过3721插件造成的问题；理论上防火墙或者代理服务器在cookie处理上也有可能会出现问题。<BR>出现这一问题的大部分原因都是程序的错误，最常见的就是在一个应用程序中去访问另外一个应用程序。我们在下一节讨论这个问题。</P>
<P id=#7>七、跨应用程序的session共享<BR><BR>常常有这样的情况，一个大项目被分割成若干小项目开发，为了能够互不干扰，要求每个小项目作为一个单独的web应用程序开发，可是到了最后突然发现某几个小项目之间需要共享一些信息，或者想使用session来实现SSO(single sign on)，在session中保存login的用户信息，最自然的要求是应用程序间能够访问彼此的session。</P>
<P>然而按照Servlet规范，session的作用范围应该仅仅限于当前应用程序下，不同的应用程序之间是不能够互相访问对方的session的。各个应用服务器从实际效果上都遵守了这一规范，但是实现的细节却可能各有不同，因此解决跨应用程序session共享的方法也各不相同。</P>
<P>首先来看一下Tomcat是如何实现web应用程序之间session的隔离的，从Tomcat设置的cookie路径来看，它对不同的应用程序设置的cookie路径是不同的，这样不同的应用程序所用的session id是不同的，因此即使在同一个浏览器窗口里访问不同的应用程序，发送给服务器的session id也可以是不同的。<BR></P>
<P align=center><IMG height=219 alt="" src="http://dev2dev.bea.com.cn/images/paihang_article/041020/image012.jpg" width=288> <IMG height=215 alt="" src="http://dev2dev.bea.com.cn/images/paihang_article/041020/image014.jpg" width=257> </P>
<P>根据这个特性，我们可以推测Tomcat中session的内存结构大致如下。<BR></P>
<P align=center><IMG height=278 alt="" src="http://dev2dev.bea.com.cn/images/paihang_article/041020/image016.jpg" width=444> </P>
<P>笔者以前用过的iPlanet也采用的是同样的方式，估计SunONE与iPlanet之间不会有太大的差别。对于这种方式的服务器，解决的思路很简单，实际实行起来也不难。要么让所有的应用程序共享一个session id，要么让应用程序能够获得其他应用程序的session id。</P>
<P>iPlanet中有一种很简单的方法来实现共享一个session id，那就是把各个应用程序的cookie路径都设为/（实际上应该是/NASApp，对于应用程序来讲它的作用相当于根）。<BR><SESSION-INFO></SESSION-INFO><BR><PATH></PATH>/NASApp<BR></P>
<P>需要注意的是，操作共享的session应该遵循一些编程约定，比如在session attribute名字的前面加上应用程序的前缀，使得setAttribute("name", "neo")变成setAttribute("app1.name", "neo")，以防止命名空间冲突，导致互相覆盖。</P>
<P><BR>在Tomcat中则没有这么方便的选择。在Tomcat版本3上，我们还可以有一些手段来共享session。对于版本4以上的Tomcat，目前笔者尚未发现简单的办法。只能借助于第三方的力量，比如使用文件、数据库、JMS或者客户端cookie，URL参数或者隐藏字段等手段。</P>
<P>我们再看一下Weblogic Server是如何处理session的。<BR></P>
<P align=center><IMG height=208 alt="" src="http://dev2dev.bea.com.cn/images/paihang_article/041020/image018.jpg" width=288> <IMG height=207 alt="" src="http://dev2dev.bea.com.cn/images/paihang_article/041020/image020.jpg" width=269> </P>
<P>从截屏画面上可以看到Weblogic Server对所有的应用程序设置的cookie的路径都是/，这是不是意味着在Weblogic Server中默认的就可以共享session了呢？然而一个小实验即可证明即使不同的应用程序使用的是同一个session，各个应用程序仍然只能访问自己所设置的那些属性。这说明Weblogic Server中的session的内存结构可能如下<BR></P>
<P align=center><IMG height=290 alt="" src="http://dev2dev.bea.com.cn/images/paihang_article/041020/image022.jpg" width=420> </P>
<P>对于这样一种结构，在session机制本身上来解决session共享的问题应该是不可能的了。除了借助于第三方的力量，比如使用文件、数据库、JMS或者客户端cookie，URL参数或者隐藏字段等手段，还有一种较为方便的做法，就是把一个应用程序的session放到ServletContext中，这样另外一个应用程序就可以从ServletContext中取得前一个应用程序的引用。示例代码如下，</P>
<P>应用程序A<BR>context.setAttribute("appA", session); </P>
<P>应用程序B<BR>contextA = context.getContext("/appA");<BR>HttpSession sessionA = (HttpSession)contextA.getAttribute("appA"); </P>
<P>值得注意的是这种用法不可移植，因为根据ServletContext的JavaDoc，应用服务器可以处于安全的原因对于context.getContext("/appA");返回空值，以上做法在Weblogic Server 8.1中通过。</P>
<P>那么Weblogic Server为什么要把所有的应用程序的cookie路径都设为/呢？原来是为了SSO，凡是共享这个session的应用程序都可以共享认证的信息。一个简单的实验就可以证明这一点，修改首先登录的那个应用程序的描述符weblogic.xml，把cookie路径修改为/appA访问另外一个应用程序会重新要求登录，即使是反过来，先访问cookie路径为/的应用程序，再访问修改过路径的这个，虽然不再提示登录，但是登录的用户信息也会丢失。注意做这个实验时认证方式应该使用FORM，因为浏览器和web服务器对basic认证方式有其他的处理方式，第二次请求的认证不是通过session来实现的。具体请参看[7] secion 14.8 Authorization，你可以修改所附的示例程序来做这些试验。</P>
<P id=#8>八、总结<BR>session机制本身并不复杂，然而其实现和配置上的灵活性却使得具体情况复杂多变。这也要求我们不能把仅仅某一次的经验或者某一个浏览器，服务器的经验当作普遍适用的经验，而是始终需要具体情况具体分析。</P>
<P>关于作者：<BR>郎云鹏（dev2dev ID: hippiewolf），软件工程师，从事J2EE开发<BR>电子邮件：langyunpeng@yahoo.com.cn<BR>地址：大连软件园路31号科技大厦A座大连博涵咨询服务有限公司</P>
<P id=#9>参考文档：<BR>[1] Preliminary Specification http://wp.netscape.com/newsref/std/cookie_spec.html<BR>[2] RFC2109 http://www.rfc-editor.org/rfc/rfc2109.txt<BR>[3] RFC2965 http://www.rfc-editor.org/rfc/rfc2965.txt<BR>[4] The Unofficial Cookie FAQ http://www.cookiecentral.com/faq/<BR>[5] http://e-docs.bea.com/wls/docs70/webapp/weblogic_xml.html#1036869<BR>[6] http://e-docs.bea.com/wls/docs70/webapp/sessions.html#100770<BR>[7] RFC2616 http://www.rfc-editor.org/rfc/rfc2616.txt<BR></P>
<P>代码下载：<A href="http://dev2dev.bea.com.cn/images/paihang_article/041020/sampleApp.zip">sampleApp.zip</A></P></TD></TR>]]></description>
</item><item>
<title><![CDATA[hibernate要点]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=happysearch&amp;id=2931</link>
<author>happysearch</author>
<pubDate>2005/2/18 14:56:06</pubDate>
<description><![CDATA[转载自: <A href="http://www.donews.net/dyhcn/archive/2004/12/15/203729.aspx" target=_blank>http://www.donews.net/dyhcn/archive/2004/12/15/203729.aspx</A><BR><BR>
<DIV class=postTitle><A id=viewpost.ascx_TitleUrl href="http://www.donews.net/dyhcn/archive/2004/12/15/203729.aspx">hibernate要点</A> </DIV>
<DIV class=postText>
<P>1.两种配置文件： <BR>&nbsp; A.hibernate.cfg.xml&nbsp;&nbsp; 和&nbsp;&nbsp; B.hibernate.properties</P>
<P>&nbsp; A中可含映射文件的配置，而B中hard codes加映射文件。</P>
<P>&nbsp; A。Configuration config=new Configuration().config();<BR>&nbsp; B. Configuration config=new Configuration();<BR>&nbsp;&nbsp;&nbsp;&nbsp; config.addClass(TUser.class);</P>
<P><BR>2.你不必一定用hibernate.cfg.xml或hibernate.properties这两文件名，<BR>&nbsp;&nbsp; 你也不一定非得把配置文件放在Classes下，<BR>&nbsp;&nbsp; File file=new File("c:\\sample\\myhibernate.xml");<BR>&nbsp;&nbsp; Configuration config=new Configuration().config(file);</P>
<P>3. session.Flush()<BR>&nbsp;&nbsp; 强制数据库立即同步，当用事务时，不必用flush,事务提交自动调用flush<BR>&nbsp;&nbsp; 在session关闭时也会调用flush</P>
<P><BR>4. Hibernate总是使用对象类型作为字段类型</P>
<P>5. XDoclet专门建立了hibernate doclet,就是在java代码上加上一些<BR>&nbsp;&nbsp; java docTag，后来再让XDoclet分析该java代码，生成映射文件;</P>
<P>6.HQL子句本身大小写无关，但是其中出现的类名和属性名必须注意大小写区分。</P>
<P>7.关系：<BR>&nbsp; Constrained : 约束，表明主控表的主键上是否存在一个外键（foreigh key）<BR>&nbsp;&nbsp; 对其进行约束。</P>
<P>&nbsp; property-ref:关联类中用于与主控类相关联的属性名，默认为关联类的主键属性名</P>
<P>&nbsp; 单向一对多需在一方配置，双向一对多需在双方进行配置</P>
<P>8.lazy=false:被动方的记录由hibernate负责记取，之后存放在主控方指定的<BR>&nbsp; Collection类型属性中</P>
<P>9. java.util.Set或net.sof.hibernate.collecton.Bag类型的Collection</P>
<P><BR>10.重要：inverse:用于标识双向关联中的被动方一端。<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; inverse=false的一方（主控方）负责维护关联关系.默认值：false</P>
<P>11.batch-size:采用延迟加载特征时，一次读入的数据数昨。</P>
<P>12.一对多通过主控方更新（主控方为一方时）</P>
<P>&nbsp;&nbsp; user.getAddresses().add(addr);<BR>&nbsp;&nbsp; session.save(user);//通过主控对象级联更新 &nbsp;</P>
<P>13.在one-to-many 关系中，将many 一方设为主动方（inverse=false）将有助性能<BR>&nbsp;&nbsp; 的改善。在一方设置关系时，inverse=true,即将主控权交给多方，<BR>&nbsp;&nbsp; 这样多方可主动从一方获得foreign key,然后一次insert即可完工。</P>
<P><BR>&nbsp;&nbsp; addr.setUser(user);//设置关联的TUser对象<BR>&nbsp;&nbsp; user.getAddresses().add(addr);<BR>&nbsp;&nbsp; session.save(user);//级联更新&nbsp;</P>
<P>14.只有设为主控方的一方才关心（访问）对方的属性，被动方是不关心对方的属性的。</P>
<P>15.one-to-many与many-to-one节点的配置属性不同：<BR>&nbsp;&nbsp; 一对多关系多了lazy和inverse两个属性<BR>&nbsp;&nbsp; 多对多节点属性：column:中间映射表中，关联目标表的关联字段<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; class:类名，关联目标类<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; outer-join:是否使用外联接&nbsp;&nbsp; &nbsp;</P>
<P>&nbsp;&nbsp; 注意:access是设置属性值的读取方式。</P>
<P><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; column是设置关联字段。</P>
<P><BR>16.多对多，注意两方都要设置inverse和lazy,cascade只能设为insert-update<BR>&nbsp;&nbsp; 多对多关系中，由于关联关系是两张表相互引用，因此在保存关系状态时必须对双方同时保存。</P>
<P>&nbsp;&nbsp; group1.getRoles().add(role1);<BR>&nbsp;&nbsp; role1.getGroups().add(group1);</P>
<P>&nbsp;&nbsp; session.save(role1);<BR>&nbsp;&nbsp; session.save(group1);</P>
<P>17.关于vo和po<BR>&nbsp;&nbsp; vo经过hibernate容量处理，就变成了po(该vo的引用将被容器保存，并且在session关闭时flush,因此po如果再传到其它地方改变了，就危险了)<BR>&nbsp;<BR>&nbsp;&nbsp; vo和po相互转换：BeanUtils.copyProperties(anotherUser,user);</P>
<P>18.对于save操作而言，如果对象已经与Session相关联（即已经被加入Session的实体容器中），则无需进行具体的操作。因为之后的Session.flush过程中，Hibernate<BR>会对此实体容器中的对象进行遍历，查找出发生变化的实体，生成并执行相应的update<BR>语句。</P>
<P>19.如果我们采用了延迟加载机制，但希望在一些情况下，实现非延迟加<BR>载时的功能，也就是说，我们希望在Session关闭后，依然允许操作user的addresses<BR>属性<BR>Hibernate.initialize方法可以通过强制加载关联对象实现这一功能：<BR>这也正是我们为什么在编写POJO时，必须用JDK Collection接口（如Set,Map）,<BR>而非特定的JDK Collection实现类（如HashSet、HashMap）申明Collection属性的<BR>原因。</P>
<P>20.事务：从sessionFactory获得session,其自动提交属性就已经关闭(AutoCommit=false),此时若执行了jdbc操作，如果不显式调用session.BeginTransaction(),是不会执行事务操作的。</P>
<P>&nbsp;&nbsp; jdbc transaction:基于同一个session(就是同一个connection)的事务;<BR>&nbsp;&nbsp; jta&nbsp; transaction:跨session（跨connection）事务.</P>
<P>&nbsp;&nbsp; 对于jta事务，有三种实现方法：<BR>&nbsp;&nbsp;&nbsp;&nbsp; A。UserTransaction tx=new InitialContext().lookup("...");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tx.commit();<BR>&nbsp;&nbsp;&nbsp;&nbsp; B. 使用hibernate封装的方法：(不推荐)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Transaction tx=session.beginTransaction();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tx.commit();<BR>&nbsp;&nbsp;&nbsp;&nbsp; C. 使用ejb之sessionBean的事务技持方法，你只要在把需要在发布描述符中，把需要jta事务的方法声明为require即可</P>
<P>21.悲观锁，乐观锁：<BR>&nbsp;&nbsp; 乐观锁一般通过version来实现，注意version节点必须出现在id后。</P>
<P><BR>22.Hibernate中，可以通过Criteria.setFirstResult和Criteria.setFetchSize方法设定分页范围。<BR>&nbsp;&nbsp; Query接口中也提供了与其一致的方法，hibernate主要在dialect类中实现在这个功能。</P>
<P><BR>23.cache<BR>&nbsp;&nbsp; <HIBERNATE-CONFIGURATION><BR>&nbsp;<SESSION-FACTORY><BR>&nbsp;……<BR>&nbsp;&nbsp;<property name="hibernate.cache.provider_class"><BR>&nbsp;&nbsp;&nbsp;net.sf.ehcache.hibernate.Provider<BR>&nbsp;&nbsp;</property>&nbsp;</P>
<P>&nbsp; 还需对ecache本身进配置<BR>&nbsp; <EHCACHE><BR>&nbsp;<DISKSTORE path="java.io.tmpdir" /><BR>&nbsp;<DEFAULTCACHE<BR>&nbsp;maxElementsInMemory="10000" //Cache中最大允许保存的数据数量<BR>&nbsp;eternal="false" //Cache中数据是否为常量<BR>&nbsp;timeToIdleSeconds="120" //缓存数据钝化时间<BR>&nbsp;timeToLiveSeconds="120" //缓存数据的生存时间<BR>&nbsp;overflowToDisk="true" //内存不足时，是否启用磁盘缓存<BR>&nbsp;/&gt;<BR>&nbsp; </EHCACHE></P>
<P>&nbsp; 之后在映射文件中指定各个映射实体的cache策略<BR>&nbsp; <CLASS name=" &#13;&#10;org.hibernate.sample.TUser" ....><BR>&nbsp;<CACHE usage="read-write" /><BR>&nbsp;....<BR>&nbsp;<SET name="addresses" ....><BR>&nbsp;&nbsp;<CACHE usage="read-only" /><BR>&nbsp;....<BR>&nbsp;</SET><BR>&nbsp;</CLASS></P>
<P>&nbsp;*****************************************************<BR>&nbsp;Query.list()跟Query.iterate()的不同：<BR>&nbsp;对于query.list()总是通过一条sql语句获取所有记录,然后将其读出，填入pojo返回;<BR>&nbsp;但是query.iterate()，则是首先通过一条Select SQL 获取所有符合查询条件的记录的<BR>id，再对这个id 集合进行循环操作，通过单独的Select SQL 取出每个id 所对应的记<BR>录，之后填入POJO中返回。</P>
<P>&nbsp;也就是说，对于list 操作，需要一条SQL 完成。而对于iterate 操作，需要n+1<BR>条SQL。，list方法将不会从Cache中读取数据。iterator却会。</P>
<P><BR>24.ThreadLocal:它会为每个线程维护一个私有的变量空间。实际上，<BR>其实现原理是在JVM 中维护一个Map，这个Map的key 就是当前的线程对象，而value则是<BR>线程通过ThreadLocal.set方法保存的对象实例。当线程调用ThreadLocal.get方法时，<BR>ThreadLocal会根据当前线程对象的引用，取出Map中对应的对象返回。</P>
<P>&nbsp;这样，ThreadLocal通过以各个线程对象的引用作为区分，从而将不同线程的变量隔离开<BR>来。</P>
<P><BR>25.Hibernate官方开发手册标准示例:<BR>&nbsp; public class HibernateUtil {<BR>private static SessionFactory sessionFactory;<BR>static {<BR>try {<BR>// Create the SessionFactory<BR>sessionFactory = new<BR>Configuration().configure().buildSessionFactory();<BR>} catch (HibernateException ex) {<BR>throw new RuntimeException(<BR>"Configuration problem: " + ex.getMessage(),<BR>ex<BR>);<BR>}<BR>}<BR>public static final ThreadLocal session = new ThreadLocal();<BR>public static Session currentSession() throws HibernateException<BR>{<BR>Session s = (Session) session.get();<BR>// Open a new Session, if this Thread has none yet<BR>if (s == null) {<BR>s = sessionFactory.openSession();<BR>session.set(s);<BR>}<BR>return s;<BR>}<BR>public static void closeSession() throws HibernateException {<BR>Session s = (Session) session.get();<BR>session.set(null);<BR>if (s != null)<BR>s.close();<BR>}<BR>}&nbsp;</P>
<P><BR>&nbsp;&nbsp;&nbsp;&nbsp; <BR>26.通过filter实现session的重用：<BR>&nbsp;&nbsp; public class PersistenceFilter implements Filter<BR>{<BR>protected static ThreadLocal hibernateHolder = new ThreadLocal();<BR>public void doFilter(ServletRequest request, ServletResponse<BR>response, FilterChain chain)<BR>throws IOException, ServletException<BR>{<BR>hibernateHolder.set(getSession());<BR>try<BR>{<BR>……<BR>chain.doFilter(request, response);<BR>……<BR>}<BR>finally<BR>{<BR>Session sess = (Session)hibernateHolder.get();<BR>if (sess != null)<BR>{<BR>hibernateHolder.set(null);<BR>try<BR>{<BR>sess.close();<BR>}<BR>catch (HibernateException ex) {<BR>throw new ServletException(ex);<BR>}<BR>}<BR>}<BR>}<BR>……}</P>
<P>27.Spring的参数化事务管理功能相当强大，笔者建议在基于Spring Framework的应用<BR>开发中，尽量使用容器管理事务，以获得数据逻辑代码的最佳可读性。<BR>&nbsp;<BR>public class UserDAO extends HibernateDaoSupport implements IUserDAO<BR>{<BR>public void insertUser(User user) {<BR>getHibernateTemplate().saveOrUpdate(user);<BR>}<BR>}</P>
<P><BR>&nbsp; 上面的UserDAO实现了自定义的IUserDAO接口，并扩展了抽象类：<BR>HibernateDaoSupport<BR>HibernateSupport实现了HibernateTemplate和SessionFactory实例的关联。<BR>HibernateTemplate对Hibernate Session操作进行了封装，而<BR>HibernateTemplate.execute方法则是一封装机制的核心 </P>
<P>&nbsp; *在spring的配置文件里，移植了整个hibernate.cfg.xml的内容。<BR></P></DIV>]]></description>
</item><item>
<title><![CDATA[TextRange 的常用方法]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=happysearch&amp;id=2762</link>
<author>happysearch</author>
<pubDate>2005/2/5 10:45:00</pubDate>
<description><![CDATA[<P>collapse([bStart])<BR>移动Range的插入点<BR>bStart true(移到开头) false(移到末尾)<BR><BR>findText(sText [, iSearchScope] [, iFlags])<BR>在Range中查找sText<BR>iSearchScope 开始位置，负数方向搜索<BR>iFlags 2(整词匹配) 4(区别大小写)<BR><BR>moveStart(sUnit [, iCount])<BR>moveEnd(sUnit [, iCount])<BR>移动Range的开头或结尾<BR>sUnit character(字) word(词) sentence(句) textedit(Range)<BR>iCount 移动数量，默认为1<BR><BR>moveToPoint(iX, iY)<BR>移动光标到坐标(iX,iY)<BR><BR>pasteHTML(sHTMLText)<BR>替换Range中的html<BR><BR>scrollIntoView([bAlignToTop])<BR>滚动使之在当前窗口显示<BR>bAlignToTop true(Range在窗口开头) false(Range在窗口底部)<BR><BR>select()<BR>选中Range</P>
<P>Code:</P>
<P>设置光标</P>
<DIV class=HtmlCode title=点击运行该代码！ style="CURSOR: pointer" onclick="preWin=window.open('','','');preWin.document.open();preWin.document.write(this.innerText);preWin.document.close();" ;>
<P>&lt;textarea id=demo cols=50 rows=5&gt;关于光标定位的补充.abcdefghijklmnopqrstuvwxyz&lt;/textarea&gt;<BR>&lt;br&gt;&lt;input type=checkbox id=collapse&gt;倒数 第&lt;input id=s value=4 size=4&gt;位 &lt;input value="设置光标" type=button onclick=setCursor()&gt;<BR>&lt;script&gt;<BR>function setCursor(){<BR>&nbsp;var num=parseInt(document.all.s.value)<BR>&nbsp;range=document.all.demo.createTextRange(); <BR>&nbsp;if(document.all.collapse.checked){<BR>&nbsp;&nbsp;range.collapse(false); <BR>&nbsp;&nbsp;range.moveEnd('character',-1*num); <BR>&nbsp;}else{<BR>&nbsp;&nbsp;range.collapse(true); <BR>&nbsp;&nbsp;range.moveStart('character',-1+num); <BR>&nbsp;}<BR>&nbsp;range.select();<BR>}<BR>&lt;/script&gt;</P></DIV>]]></description>
</item>
</channel>
</rss>