﻿<?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-dwys0343-文章分类-J2EE</title><link>http://www.blogjava.net/dwys0343/category/19023.html</link><description>技术整理</description><language>zh-cn</language><lastBuildDate>Wed, 28 Feb 2007 03:18:45 GMT</lastBuildDate><pubDate>Wed, 28 Feb 2007 03:18:45 GMT</pubDate><ttl>60</ttl><item><title>装饰Servlet  Request对象</title><link>http://www.blogjava.net/dwys0343/articles/92941.html</link><dc:creator>特兰克斯</dc:creator><author>特兰克斯</author><pubDate>Wed, 10 Jan 2007 08:12:00 GMT</pubDate><guid>http://www.blogjava.net/dwys0343/articles/92941.html</guid><wfw:comment>http://www.blogjava.net/dwys0343/comments/92941.html</wfw:comment><comments>http://www.blogjava.net/dwys0343/articles/92941.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/dwys0343/comments/commentRss/92941.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/dwys0343/services/trackbacks/92941.html</trackback:ping><description><![CDATA[
		<div class="content">
				<font color="#deb887">版权声明：可以任意转载，转载时请务必以超链接形式标明文章原始出处和作者信息及本声明<br />英文原文地址：<br />http://dev2dev.bea.com/pub/a/2005/05/decorators.html<br /><br />中文地址<br />http://www.matrix.org.cn/resource/article/43/43603_Servlet_Request.html<br /><br />关键词：Servlet  Request    filter  Decorator</font>
				<br />
				<br />
				<br />
				<font color="#0000ff">摘要</font>
				<br />装饰模式是Erich  Gamma等人所著的《设计模式：可利用面向对象软件的基础》一书中众多模式之一。一般来说，此模式在设计Swing的程序员中比较流行，他们用它来改进软件。今天，即使有许多程序是基于Web应用的，装饰模式仍有用武之地，在J2EE的环境下也有使用的价值。<br />本文说明了如何将装饰模式应用到servlet  request对象上。首先，提出了一个与servlet  filter有关的问题，并解释了随之而引入的装饰模式。然后，讨论了如何在servlet环境下使用此模式，并列出了使用此模式的几个比较有名的基于servlet的项目。最后，文章通过实现一个删除空白符的filter例子，演示了装饰模式在servlet中的使用。<br /><br /><font color="#0000ff">简介<br /></font>       Servlet规范中所引入的filter令人心动不已，因为它引入了一个功能强大的拦截模式。Filter是这样一种Java对象，它能在request到达servlet的服务方法之前拦截HttpServletRequest对象，而在服务方法转移控制后又能拦截HttpServletResponse对象。你可以使用filter来实现特定的任务，比如验证用户输入，以及压缩web内容。但你拟富有成效地使用过滤器的念头却被你不能改变HttpServletRequest对象的参数的现实扫了兴，因为java.util.Map所包装的HttpServletRequest对象的参数是不可改变的。这极大地缩减了filter的应用范围。至少在一半的时间里，你希望可以改变准备传送给filter的对象。如果在HttpServletRequest对象到达Struts的action  servlet之前，我们可以通过一个filter将用户输入的多余空格去掉，难道不是更美妙吗？这样的话，你就不必等到在Struts的action表单验证方法中才进行这项工作了。<br />幸运的是，尽管你不能改变不变对象本身，但你却可以通过使用装饰模式来改变其状态。<br /><br />装饰模式<br />在继承中，你可以通过继承一个父类并覆盖你希望改变的方法来改变对象状态。然而，如果这个对象是由程序的另一个子模块，例如对象工厂  (这里所说的工厂是工厂模式中的术语，下同。译者注)  或是servlet容器所产生的，继承就无能为力了。<br />装饰模式可用来增加一个现有对象的功能，或是改变其状态。与其使用继承方式来扩展此类，这个模式将一个对象包装成另外一个对象。图1是装饰模式的UML类图。<br /><br /><br /><br /><a href="http://www.matrix.org.cn/resource/upload/content/2005_08_18_001741_vyvXqHTQoN.jpg" target="_blank"><img onerror="javascript:this.src='/club/GVimgs/imgErr.gif'" src="http://www.matrix.org.cn/resource/upload/content/2005_08_18_001741_vyvXqHTQoN.jpg" onload="javascript:if(this.width &gt; screen.width-350){this.width = screen.width-350};" border="0" /></a><br /><br /><br />图1：装饰模式<br /><br /><br />在图1中，Component是一个接口，其具体实现是ConcreteComponent。要改变Component的状态，你可以修改ConcreteComponent或是扩展它  (通过继承或实现接口的方式，译者注)。然而，如果ConcreteComponent来自于一个工厂，你却无计可施。你所能做的，就是创建一个同为实现了Component接口的装饰类。在图1中，这个装饰类的角色就由Decorator来扮演，在程序中通常表现为接口或抽象类。Decorator类的一个特性就是，它有一个接收Component对象的构造方法。你将拟装饰的对象传递给这个构造方法。在本例中，这个对象就是从工厂获得的ConcreteComponent对象。通过将此装饰对象传递给Decorator的一个类变量，你可以访问Decorator中的任何方法。这就使你得以改变对象的状态了。<br />图1中的Decorator类不一定是接口或抽象类。如果你的程序不是很复杂，你可以将其转化为一个具体的Decorator类。<br />举个例子，考虑这样一个简单的消息传递程序，其主要部分是Messenger接口及其实现类MessengerImpl。让我们假设MessengerImpl对象来自于一个工厂，因此你不能改变其状态。如果你准备增加或改变Messenger对象的功能，你可以创建一个MessengerDecorator类。图2是此例子的类图。<br /><br /><br /><a href="http://www.matrix.org.cn/resource/upload/content/2005_08_18_001820_mmjkgDUOWJ.jpg" target="_blank"><img onerror="javascript:this.src='/club/GVimgs/imgErr.gif'" src="http://www.matrix.org.cn/resource/upload/content/2005_08_18_001820_mmjkgDUOWJ.jpg" onload="javascript:if(this.width &gt; screen.width-350){this.width = screen.width-350};" border="0" /></a><br /><br />图2：Messenger装饰类<br /><br /><br />我们来看程序的代码。列表1给出了Messenger接口的代码，列表2是MessengerImpl类的代码。<br /><br />列表1：Messenger接口<br /><br /><br />public  interface  Messenger  {    public  String  getMessage();}<br /><br /><br />列表2：MessengerImpl类<br /><br /><br /><table cellspacing="1" cellpadding="4" width="98%" align="center" bgcolor="#bad5ef" border="0"><form><tbody><tr><td style="FONT-SIZE: 12px" bgcolor="#e6eef7" height="25">程序代码：</td></tr><tr><td style="FONT-SIZE: 12px" bgcolor="#ffffff"><span style="FONT-WEIGHT: bold; COLOR: #7f0055">public </span>class MessengerImpl implements Messenger {  <span style="FONT-WEIGHT: bold; COLOR: #7f0055">private </span>String message;  <span style="FONT-WEIGHT: bold; COLOR: #7f0055">public </span>MessengerImpl(String message) {    <span style="FONT-WEIGHT: bold; COLOR: #7f0055">this</span>.message = message;  }  <span style="FONT-WEIGHT: bold; COLOR: #7f0055">public </span>String getMessage() {    <span style="FONT-WEIGHT: bold; COLOR: #7f0055">return </span>message;  }}</td></tr></tbody></form></table><br /><br /><br />Messenger对象由一个名为MessengerFactory的工厂创建，如列表3所示。<br /><br />列表3：MessengerFactory类<br /><br />public  class  MessengerFactory  {    public  static  Messenger  getMessenger()  {        return  new  MessengerImpl("secrets");    }}<br /><br /><br />对每一个所创建的Messenger对象，此工厂通过某个未知的操作，初始化了getMessage()方法所返回的字符串。换句话说，你不能自己创建Messenger对象。<br />在程序中，Messenger对象的主要用途是被传递给一个名为Util的类中的broadcast()静态方法。列表4是Util类的代码。<br /><br />列表4：Util类<br /><br />public  class  Util  {    public  static  void  broadcast(Messenger  messenger)  {        System.out.print(messenger.getMessage());    }    //  other  methods  here}<br /><br /><br />在你自己的类中，你可能会有这样的代码：<br /><br />Messenger  messenger  =  MessengerFactory.getMessenger();Util.broadcast(messenger);<br /><br /><br />假设你希望对broadcast()方法所打印出的消息做一小改动。你拟将其转为大写，怎么做？表面上看，你可以继承Messenger，实例化其子类，并将返回的对象传给Util.broadcast()。但是，这种做法毫无意义，因为只有工厂才知道如何初始化Messenger对象，并通过其getMessage()方法返回正确的值。<br />使用装饰模式，你可以创建一个MessengerDecorator类，如列表5所示。<br /><br />列表5：MessengerDecorator类<br /><br />public  class  MessengerDecorator  implements  Messenger  {    private  Messenger  messenger;    public  MessengerDecorator(Messenger  messenger)  {        this.messenger  =  messenger;    }    public  String  getMessage()  {        return  messenger.getMessage().toUpperCase();    }}<br /><br /><br />因为MessengerDecorator实现了Messenger，Util.broadcast()将接受一个MessengerDecorator的实例。然而，MessengerDecorator不仅仅是一个接口的实现，它还是一个MessengerImpl对象的装饰器。正因如此，MessengerDecorator就必须有一个接收拟被装饰的Messenger对象的构造方法。<br />如列表5所示，这个构造方法将参数传给变量。你现在可以覆盖MessengerDecorator中的getMessage()方法，以便将消息转为大写后再打印出来。因为你持有原来Messenger对象的引用，你可以这样写getMessage()方法：<br /><br />public  String  getMessage()  {    return  this.messenger.getMessage().toUpperCase();}<br /><br /><br />MessengerDecorator中的getMessage()方法返回原始消息的大写版本。<br />在你的类中，就像往常一样，你得到一个Messenger对象，并将Decorator传给Util.broadcast()。<br /><br />Messenger  messenger  =  factory.getMessenger();Util.broadcast(new  MessengerDecorator(messenger));<br /><br /><br />你并不将原始对象传给原先的目标，相反，你将其传给了该对象的装饰器。<br /><br />应用装饰模式于Servlet<br />以上Messenger类的例子与servlet容器所构造的ServletRequest对象是一样的。当收到一个HTTP请求时，servlet容器就会创建ServletRequest对象及ServletResponse对象（分别是ServletRequestImpl及ServletResponseImpl的实例），并将这两个对象传递给特定的servlet服务方法。现在，如果你为ServletRequest创建一个装饰角色，并将其传给servlet服务方法，你就应用了装饰模式。<br />对ServletRequest很容易应用装饰模式，因为servlet  API已经为其提供了一个包装类：ServletRequestWrapper。图3是一个servlet装饰模式的类图。<br /><br /><br /><a href="http://www.matrix.org.cn/resource/upload/content/2005_08_18_002024_arBYHnEZLh.jpg" target="_blank"><img onerror="javascript:this.src='/club/GVimgs/imgErr.gif'" src="http://www.matrix.org.cn/resource/upload/content/2005_08_18_002024_arBYHnEZLh.jpg" onload="javascript:if(this.width &gt; screen.width-350){this.width = screen.width-350};" border="0" /></a><br /><br /><br />图3：Servlet  API中的装饰模式<br /><br /><br />图3中的HTTP版本的类图如图4所示。别为过多的类搞晕了头，只管注意虚线框中的三个类就行了：HttpServletRequest,  HttpServletRequestImpl,  HttpServletRequestWrapper。<br /><br /><br /><br /> <br /><a href="http://www.matrix.org.cn/resource/upload/content/2005_08_18_002101_OOtoGFiRMH.jpg" target="_blank"><img onerror="javascript:this.src='/club/GVimgs/imgErr.gif'" src="http://www.matrix.org.cn/resource/upload/content/2005_08_18_002101_OOtoGFiRMH.jpg" onload="javascript:if(this.width &gt; screen.width-350){this.width = screen.width-350};" border="0" /></a><br /><br />图4：Servlet  API  (HTTP)的装饰模式<br /><br /><br />情况与前面所举例子类似。你拥有一个ServletRequest的实现，而它是由servlet容器产生的。你可以使用所提供的ServletRequestWrapper来装饰这些ServletRequest对象。<br />这个模式很简单，在实际应用中可以派上用场。实际上，一些很有名的应用就使用了此模式。这些应用包括：<br />               Struts  -  Struts是当前开发Java  Web应用最受欢迎的基于MVC(模型-视图-控制)模式的框架。Struts提供了相当于ServletRequest包装类的org.apache.struts.upload.MultipartRequestWrapper类。  MultipartRequestWrapper覆盖了getParameter()，getParameterNames()，及getParameterValues()等方法来实现文件上传。<br />               Apache  Beehive  ?C  这个源于BEA的WebLogic专题小组的开源项目，构建于Struts之上，并简化了web应用及web服务的开发。与ServletRequest包装类一样，org.apache.beehive.netui.pageflow.internal包中的PageFlowRequestWrapper类有助于任意Apache  Beehive应用的页流处理。<br />现在，让我们来看看，如何编写自己的HttpServletRequest装饰类。<br /><br />一个删除空白字符的Filter<br />本节将以上的理论投入实际使用，通过实现一个删除空白字符的filter，来演示如何使用javax.servlet.http.HttpServletRequestWrapper类来装饰HttpServletRequest对象。在本例中，这个filter将删除所传来的参数中多余的空白字符。<br />这在许多servlet/JSP应用中是很有用的，包括Struts及JavaServer  Faces等应用。例如，Struts通过调用HttpServletRequest对象的getParameterValues()对象来处理action表单。通过覆盖装饰类中此方法，你可以改变当前HttpServletRequest对象的状态。<br />要创建HttpServletRequest的装饰类，你需要继承HttpServletRequestWrapper并且覆盖你希望改变的方法。列表5中，MyRequestWrapper类将删除getParameterValues()方法返回值的多余空白字符。<br /><br />列表5：HttpServerletRequest装饰类<br /><br /><table cellspacing="1" cellpadding="4" width="98%" align="center" bgcolor="#bad5ef" border="0"><form><tbody><tr><td style="FONT-SIZE: 12px" bgcolor="#e6eef7" height="25">程序代码：</td></tr><tr><td style="FONT-SIZE: 12px" bgcolor="#ffffff"><span style="FONT-WEIGHT: bold; COLOR: #7f0055">package</span> trimmer.filter;<br /><br />import javax.servlet.http.HttpServletRequest;<br />import javax.servlet.http.HttpServletRequestWrapper;<br /><br /><span style="FONT-WEIGHT: bold; COLOR: #7f0055">public </span><span style="FONT-WEIGHT: bold; COLOR: #7f0055">final </span>class MyRequestWrapper extends HttpServletRequestWrapper {<br /><span style="FONT-WEIGHT: bold; COLOR: #7f0055">public </span>MyRequestWrapper(HttpServletRequest servletRequest) {<br />super(servletRequest);<br />}<br /><br /><span style="FONT-WEIGHT: bold; COLOR: #7f0055">public </span>String[] getParameterValues(String parameter) {<br />String[] results = super.getParameterValues(parameter);<br />if (results == <span style="FONT-WEIGHT: bold; COLOR: #7f0055">null</span>)<br /><span style="FONT-WEIGHT: bold; COLOR: #7f0055">return </span><span style="FONT-WEIGHT: bold; COLOR: #7f0055">null</span>;<br /><span style="FONT-WEIGHT: bold; COLOR: #7f0055">int </span>count = results.length;<br />String[] trimResults = <span style="FONT-WEIGHT: bold; COLOR: #7f0055">new </span>String[count];<br />for (<span style="FONT-WEIGHT: bold; COLOR: #7f0055">int </span>i = 0; i &lt; count; i++) {<br />trimResults[i] = results[i].trim();<br />}<br /><span style="FONT-WEIGHT: bold; COLOR: #7f0055">return </span>trimResults;<br />}<br />}</td></tr></tbody></form></table><br /><br />列表6演示了如何载获Http请求并装饰HttpServletRequest对象。[i]列表6：删除空白符的filter<br /><br /><br />列表6演示了如何载获Http请求并装饰HttpServletRequest对象。<br /><br />[i]列表6：删除空白符的filter<br /><br /><table cellspacing="1" cellpadding="4" width="98%" align="center" bgcolor="#bad5ef" border="0"><form><tbody><tr><td style="FONT-SIZE: 12px" bgcolor="#e6eef7" height="25">程序代码：</td></tr><tr><td style="FONT-SIZE: 12px" bgcolor="#ffffff"><span style="FONT-WEIGHT: bold; COLOR: #7f0055">package</span> trimmer.filter;<br /><br />import java.io.IOException;<br />import javax.servlet.Filter;<br />import javax.servlet.FilterChain;<br />import javax.servlet.FilterConfig;<br />import javax.servlet.ServletException;<br />import javax.servlet.ServletRequest;<br />import javax.servlet.ServletResponse;<br />import javax.servlet.http.HttpServletRequest;<br /><br /><span style="FONT-WEIGHT: bold; COLOR: #7f0055">public </span>class MyFilter implements Filter {<br /><span style="FONT-WEIGHT: bold; COLOR: #7f0055">private </span>FilterConfig filterConfig;<br /><br /><span style="FONT-WEIGHT: bold; COLOR: #7f0055">public </span>void init(FilterConfig filterConfig) throws ServletException {<br />System.out.println(<span style="COLOR: #2a00ff">"Filter initialized"</span>);<br /><span style="FONT-WEIGHT: bold; COLOR: #7f0055">this</span>.filterConfig = filterConfig;<br />}<br /><br /><span style="FONT-WEIGHT: bold; COLOR: #7f0055">public </span>void destroy() {<br />System.out.println(<span style="COLOR: #2a00ff">"Filter destroyed"</span>);<br /><span style="FONT-WEIGHT: bold; COLOR: #7f0055">this</span>.filterConfig = <span style="FONT-WEIGHT: bold; COLOR: #7f0055">null</span>;<br />}<br /><br /><span style="FONT-WEIGHT: bold; COLOR: #7f0055">public </span>void doFilter(ServletRequest request, ServletResponse response,<br />FilterChain chain) throws IOException, ServletException {<br />chain.doFilter(<span style="FONT-WEIGHT: bold; COLOR: #7f0055">new </span>MyRequestWrapper((HttpServletRequest) request),<br />response);<br />}<br />}</td></tr></tbody></form></table><br /><br />这个程序使用了列表6所示的filter来修整用户输入。要使用这个filter，你需要在web.xml文件中如下设置filter及filter-mapping的元素。<br />   <br /> &lt;filter&gt;<br />       &lt;filter-name&gt;TrimmerFilter&lt;/filter-name&gt;<br />       &lt;filter-class&gt;trimmer.filter.MyFilter&lt;/filter-class&gt;<br />   &lt;/filter&gt;<br />   &lt;filter-mapping&gt;<br />       &lt;filter-name&gt;TrimmerFilter&lt;/filter-name&gt;<br />       &lt;url-pattern&gt;*.do&lt;/url-pattern&gt;<br />   &lt;/filter-mapping&gt;<br /><br /><br />要测试这个filter，启动这个应用后，在表单中输入一些值，提交表单，看看这个filter是如何修整输入数值的。这是一个实用的装饰模式的应用。<br /><br /><font color="#0000ff">小结</font><br />Servlet  filter可以在调用一个servlet的服务方法后，拦载或加工HTTP请求。尽管这非常诱人，但其实际使用却有所限制，因为你不能改变HttpServletRequest对象。<br />这时候装饰模式派上了用场。本文演示了如何通过应用装饰模式来“修改”HttpServletRequest对象，从而使你的servlet  filter更加有用。在上面filter例子中，filter改了request参数中的用户输入，而这一点，如果没有装饰request对象，你是无论如何也不可能做到的。<br /><br />Budi  Kurniawan是一个高级J2EE的架构师。他还是《Tomcat如何工作：教你如何开发自己的Servlet容器》  (”How  Tomcat  Works:  A  Guide  to  Developing  Your  Own  Servlet  Container”)  以及《Struts设计与编程指南》(”Struts  Design  and  Programming  :A  Tutorial”)  这两本书的作者，它们均由BrainySoftwar.com出版。</div>
<img src ="http://www.blogjava.net/dwys0343/aggbug/92941.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/dwys0343/" target="_blank">特兰克斯</a> 2007-01-10 16:12 <a href="http://www.blogjava.net/dwys0343/articles/92941.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>关于Java Servlet的Filter 技术</title><link>http://www.blogjava.net/dwys0343/articles/92933.html</link><dc:creator>特兰克斯</dc:creator><author>特兰克斯</author><pubDate>Wed, 10 Jan 2007 07:58:00 GMT</pubDate><guid>http://www.blogjava.net/dwys0343/articles/92933.html</guid><wfw:comment>http://www.blogjava.net/dwys0343/comments/92933.html</wfw:comment><comments>http://www.blogjava.net/dwys0343/articles/92933.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/dwys0343/comments/commentRss/92933.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/dwys0343/services/trackbacks/92933.html</trackback:ping><description><![CDATA[
		<p class="fontsize10">
				<font color="#deb887">作者: 李文军 <br /></font>
				<br />    Filter 技术是servlet 2.3 新增加的功能.servlet2.3是sun公司与2000年10月发布的,它的开发者包括许多个人和公司团体,充分体现了sun公司所倡导的代码开放性原则.由于众多的参与者的共同努力,servlet2.3比以往功能都强大了许多,而且性能也有了大幅提高. <br />它新增加的功能包括: <br />1. 应用程序生命周期事件控制; <br />2. 新的国际化; <br />3. 澄清了类的装载规则; <br />4. 新的错误及安全属性; <br />5. 不赞成使用HttpUtils 类; <br />6. 各种有用的方法; <br />7. 阐明并扩展了几个servlet DTD; <br />8. filter功能. <br />其中最重要的就是filter功能.它使用户可以改变一个request和修改一个response. Filter 不是一个servlet,它不能产生一个response,它能够在一个request到达servlet之前预处理request,也可以在离开servlet时处理response.换种说法,filter其实是一个”servlet chaining”(servlet 链).一个filter 包括: <br />1. 在servlet被调用之前截获; <br />2. 在servlet被调用之前检查servlet request; <br />3. 根据需要修改request头和request数据; <br />4. 根据需要修改response头和response数据; <br />5. 在servlet被调用之后截获. <br />你能够配置一个filter 到一个或多个servlet;单个servlet或servlet组能够被多个filter 使用.几个实用的filter 包括:用户辨认filter,日志filter,审核filter,加密filter,符号filter,能改变xml内容的XSLT filter等. <br />一个filter必须实现javax.servlet.Filter接口并定义三个方法: <br />1.void setFilterConfig(FilterConfig config) //设置filter 的配置对象; <br />2. FilterConfig getFilterConfig() //返回filter的配置对象; <br />3. void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) //执行filter 的工作. <br />服务器每次只调用setFilterConfig方法一次准备filter 的处理;调用doFilter方法多次以处理不同的请求.FilterConfig接口有方法可以找到filter名字及初始化参数信息.服务器可以设置FilterConfig为空来指明filter已经终结. <br />每一个filter从doFilter()方法中得到当前的request及response.在这个方法里,可以进行任何的针对request及response的操作.(包括收集数据,包装数据等).filter调用chain.doFilter()方法把控制权交给下一个filter.一个filter在doFilter()方法中结束.如果一个filter想停止request处理而获得对response的完全的控制,那它可以不调用下一个filter. <br />一个filter可以包装request 或response以改变几个方法和提供用户定制的属性.Api2.3提供了HttpServletRequestWrapper 和HttpServletResponseWrapper来实现.它们能分派最初的request和response.如果要改变一个方法的特性,必须继承wapper和重写方法.下面是一段简单的日志filter用来记录所有request的持续时间. <br />public class LogFilter implements Filter { <br />FilterConfig config; <br /><br />public void setFilterConfig(FilterConfig config) { <br />this.config = config; <br />} <br /><br />public FilterConfig getFilterConfig() { <br />return config; <br />} <br /><br />public void doFilter(ServletRequest req, <br />ServletResponse res, <br />FilterChain chain) { <br />ServletContext context = getFilterConfig().getServletContext(); <br />long bef = System.currentTimeMillis(); <br />chain.doFilter(req, res); // no chain parameter needed here <br />long aft = System.currentTimeMillis(); <br />context.log("Request to " + req.getRequestURI() <br />+ ": " + (aft-bef)); <br />} <br />} <br />当server调用setFilterConfig(),filter保存config信息.在doFilter()方法中通过config信息得到servletContext.如果要运行这个filter,必须去配置到web.xml中.以tomcat4.01为例: <br />&lt;filter&gt; <br />&lt;filter-name&gt; <br />log //filter 名字 <br />&lt;/filter-name&gt; <br />&lt;filter-class&gt; <br />LogFilter //filter class(上例的servlet) <br />&lt;/filter-class&gt; <br />&lt;/filter&gt; <br />&lt;filter-mapping&gt; <br />&lt;filter-name&gt;log&lt;/filter-name&gt; <br />&lt;servletname&gt;servletname&lt;/servlet-name&gt; <br />&lt;/filter-mapping&gt; <br />&lt;servlet&gt; <br />&lt;servlet-name&gt;servletname&lt;/servletname&gt; <br />&lt;servletclass&gt;servletclass&lt;/servlet-class&gt; <br />&lt;/servlet&gt; <br />&lt;servlet-mapping&gt; <br />&lt;servlet-name&gt;servletname&lt;/servlet-name&gt; <br />&lt;url-pattern&gt;*&lt;/url-pattern&gt; <br />&lt;/servlet-mapping&gt; <br /><br />把这个web.xml放到web-inf中(详请参考tomcat帮助文档). <br />当每次请求一个request时(如index.jsp),先到LogFilter中去并调用doFilter()方法,然后才到各自的servlet中去.如果是一个简单的servlet(只是一个页面,无任何输出语句),那么可能的输出是: <br />Request to /index.jsp: 10<br /><br /><br /></p>
		<h4 class="TextColor1" id="subjcns!fbb255b0fa07bfa4!769" style="MARGIN-BOTTOM: 0px">Servlet和Filter的url匹配以及url-pattern详解</h4>
		<div id="msgcns!fbb255b0fa07bfa4!769">
				<div>
						<p>一，servlet容器对url的匹配过程：<br />当一个请求发送到servlet容器的时候，容器先会将请求的url减去当前应用上下文的路径作为servlet的映射url，比如我访问的是<a href="http://localhost/test/aaa.html"><font color="#669932">http://localhost/test/aaa.html</font></a>，我的应用上下文是test，容器会将<a href="http://localhost/test"><font color="#669932">http://localhost/test</font></a>去掉，剩下的/aaa.html部分拿来做servlet的映射匹配。这个映射匹配过程是有顺序的，而且当有一个servlet匹配成功以后，就不会去理会剩下的servlet了（filter不同，后文会提到）。其匹配规则和顺序如下：<br />1.     精确路径匹配。例子：比如servletA 的url-pattern为 /test，servletB的url-pattern为 /* ，这个时候，如果我访问的url为<a href="http://localhost/test"><font color="#669932">http://localhost/test</font></a> ，这个时候容器就会先 进行精确路径匹配，发现/test正好被servletA精确匹配，那么就去调用servletA，也不会去理会其他的servlet了。<br />2.     最长路径匹配。例子：servletA的url-pattern为/test/*，而servletB的url-pattern为/test/a/*，此时访问<a href="http://localhost/test/a"><font color="#669932">http://localhost/test/a</font></a>时，容器会选择路径最长的servlet来匹配，也就是这里的servletB。<br />3.     扩展匹配，如果url最后一段包含扩展，容器将会根据扩展选择合适的servlet。例子：servletA的url-pattern：*.action<br />4.     如果前面三条规则都没有找到一个servlet，容器会根据url选择对应的请求资源。如果应用定义了一个default servlet，则容器会将请求丢给default servlet（什么是default servlet？后面会讲）。<br />     根据这个规则表，就能很清楚的知道servlet的匹配过程，所以定义servlet的时候也要考虑url-pattern的写法，以免出错。<br />       对于filter，不会像servlet那样只匹配一个servlet，因为filter的集合是一个链，所以只会有处理的顺序不同，而不会出现只选择一个filter。Filter的处理顺序和filter-mapping在web.xml中定义的顺序相同。<br />    二，url-pattern详解<br />         在web.xml文件中，以下语法用于定义映射：<br />l  以”/’开头和以”/*”结尾的是用来做路径映射的。<br />l  以前缀”*.”开头的是用来做扩展映射的。<br />l  “/” 是用来定义default servlet映射的。<br />l  剩下的都是用来定义详细映射的。比如： /aa/bb/cc.action<br />所以，为什么定义”/*.action”这样一个看起来很正常的匹配会错？因为这个匹配即属于路径映射，也属于扩展映射，导致容器无法判断。</p>
				</div>
		</div>
<img src ="http://www.blogjava.net/dwys0343/aggbug/92933.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/dwys0343/" target="_blank">特兰克斯</a> 2007-01-10 15:58 <a href="http://www.blogjava.net/dwys0343/articles/92933.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Session详解</title><link>http://www.blogjava.net/dwys0343/articles/92336.html</link><dc:creator>特兰克斯</dc:creator><author>特兰克斯</author><pubDate>Mon, 08 Jan 2007 04:26:00 GMT</pubDate><guid>http://www.blogjava.net/dwys0343/articles/92336.html</guid><wfw:comment>http://www.blogjava.net/dwys0343/comments/92336.html</wfw:comment><comments>http://www.blogjava.net/dwys0343/articles/92336.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/dwys0343/comments/commentRss/92336.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/dwys0343/services/trackbacks/92336.html</trackback:ping><description><![CDATA[
		<font color="#808080">发表于 2006-03-13 02:58:27<br />作者:郎云鹏;hippiewolf     来源:bea</font>
		<br />
		<br />
		<h4>
				<font color="#a52a2a">摘要:</font>
		</h4>虽然session机制在web应用程序中被采用已经很长时间了，但是仍然有很多人不清楚session机制的本质，以至不能正确的应用这一技术。本文将详细讨论session的工作机制并且对在Java web application中应用session机制时常见的问题作出解答。 <br /><br /><strong><font color="#0000ff">一、术语session</font><br /></strong>在我的经验里，session这个词被滥用的程度大概仅次于transaction，更加有趣的是transaction与session在某些语境下的含义是相同的。<br /><br />session，中文经常翻译为会话，其本来的含义是指有始有终的一系列动作/消息，比如打电话时从拿起电话拨号到挂断电话这中间的一系列过程可以称之为一个 session。有时候我们可以看到这样的话“在一个浏览器会话期间，...”，这里的会话一词用的就是其本义，是指从一个浏览器窗口打开到关闭这个期间 ①。最混乱的是“用户（客户端）在一次会话期间”这样一句话，它可能指用户的一系列动作（一般情况下是同某个具体目的相关的一系列动作，比如从登录到选购商品到结账登出这样一个网上购物的过程，有时候也被称为一个transaction），然而有时候也可能仅仅是指一次连接，也有可能是指含义①，其中的差别只能靠上下文来推断②。<br /><br />然而当session一词与网络协议相关联时，它又往往隐含了“面向连接”和/或“保持状态”这样两个含义， “面向连接”指的是在通信双方在通信之前要先建立一个通信的渠道，比如打电话，直到对方接了电话通信才能开始，与此相对的是写信，在你把信发出去的时候你并不能确认对方的地址是否正确，通信渠道不一定能建立，但对发信人来说，通信已经开始了。“保持状态”则是指通信的一方能够把一系列的消息关联起来，使得消息之间可以互相依赖，比如一个服务员能够认出再次光临的老顾客并且记得上次这个顾客还欠店里一块钱。这一类的例子有“一个TCP session”或者 “一个POP3 session”③。<br /><br />而到了web服务器蓬勃发展的时代，session在web开发语境下的语义又有了新的扩展，它的含义是指一类用来在客户端与服务器之间保持状态的解决方案④。有时候session也用来指这种解决方案的存储结构，如“把xxx保存在session 里”⑤。由于各种用于web开发的语言在一定程度上都提供了对这种解决方案的支持，所以在某种特定语言的语境下，session也被用来指代该语言的解决方案，比如经常把Java里提供的javax.servlet.http.HttpSession简称为session⑥。<br /><br />鉴于这种混乱已不可改变，本文中session一词的运用也会根据上下文有不同的含义，请大家注意分辨。<br />在本文中，使用中文“浏览器会话期间”来表达含义①，使用“session机制”来表达含义④，使用“session”表达含义⑤，使用具体的“HttpSession”来表达含义⑥<br /><br /><b><font color="#0000ff">二、HTTP协议与状态保持</font></b><br />HTTP 协议本身是无状态的，这与HTTP协议本来的目的是相符的，客户端只需要简单的向服务器请求下载某些文件，无论是客户端还是服务器都没有必要纪录彼此过去的行为，每一次请求之间都是独立的，好比一个顾客和一个自动售货机或者一个普通的（非会员制）大卖场之间的关系一样。<br /><br />然而聪明（或者贪心？）的人们很快发现如果能够提供一些按需生成的动态信息会使web变得更加有用，就像给有线电视加上点播功能一样。这种需求一方面迫使HTML逐步添加了表单、脚本、DOM等客户端行为，另一方面在服务器端则出现了CGI规范以响应客户端的动态请求，作为传输载体的HTTP协议也添加了文件上载、 cookie这些特性。其中cookie的作用就是为了解决HTTP协议无状态的缺陷所作出的努力。至于后来出现的session机制则是又一种在客户端与服务器之间保持状态的解决方案。<br /><br />让我们用几个例子来描述一下cookie和session机制之间的区别与联系。笔者曾经常去的一家咖啡店有喝5杯咖啡免费赠一杯咖啡的优惠，然而一次性消费5杯咖啡的机会微乎其微，这时就需要某种方式来纪录某位顾客的消费数量。想象一下其实也无外乎下面的几种方案：<br />1、该店的店员很厉害，能记住每位顾客的消费数量，只要顾客一走进咖啡店，店员就知道该怎么对待了。这种做法就是协议本身支持状态。<br />2、发给顾客一张卡片，上面记录着消费的数量，一般还有个有效期限。每次消费时，如果顾客出示这张卡片，则此次消费就会与以前或以后的消费相联系起来。这种做法就是在客户端保持状态。<br />3、发给顾客一张会员卡，除了卡号之外什么信息也不纪录，每次消费时，如果顾客出示该卡片，则店员在店里的纪录本上找到这个卡号对应的纪录添加一些消费信息。这种做法就是在服务器端保持状态。<br /><br />由于HTTP协议是无状态的，而出于种种考虑也不希望使之成为有状态的，因此，后面两种方案就成为现实的选择。具体来说cookie机制采用的是在客户端保持状态的方案，而session机制采用的是在服务器端保持状态的方案。同时我们也看到，由于采用服务器端保持状态的方案在客户端也需要保存一个标识，所以session机制可能需要借助于cookie机制来达到保存标识的目的，但实际上它还有其他选择。<br /><br /><b><font color="#0000ff">三、理解cookie机制</font></b><br />cookie机制的基本原理就如上面的例子一样简单，但是还有几个问题需要解决：“会员卡”如何分发；“会员卡”的内容；以及客户如何使用“会员卡”。<br /><br />正统的cookie分发是通过扩展HTTP协议来实现的，服务器通过在HTTP的响应头中加上一行特殊的指示以提示浏览器按照指示生成相应的cookie。然而纯粹的客户端脚本如JavaScript或者VBScript也可以生成cookie。<br /><br />而cookie 的使用是由浏览器按照一定的原则在后台自动发送给服务器的。浏览器检查所有存储的cookie，如果某个cookie所声明的作用范围大于等于将要请求的资源所在的位置，则把该cookie附在请求资源的HTTP请求头上发送给服务器。意思是麦当劳的会员卡只能在麦当劳的店里出示，如果某家分店还发行了自己的会员卡，那么进这家店的时候除了要出示麦当劳的会员卡，还要出示这家店的会员卡。<br /><br />cookie的内容主要包括：名字，值，过期时间，路径和域。<br />其中域可以指定某一个域比如.google.com，相当于总店招牌，比如宝洁公司，也可以指定一个域下的具体某台机器比如www.google.com或者froogle.google.com，可以用飘柔来做比。<br />路径就是跟在域名后面的URL路径，比如/或者/foo等等，可以用某飘柔专柜做比。<br />路径与域合在一起就构成了cookie的作用范围。<br />如果不设置过期时间，则表示这个cookie的生命期为浏览器会话期间，只要关闭浏览器窗口，cookie就消失了。这种生命期为浏览器会话期的 cookie被称为会话cookie。会话cookie一般不存储在硬盘上而是保存在内存里，当然这种行为并不是规范规定的。如果设置了过期时间，浏览器就会把cookie保存到硬盘上，关闭后再次打开浏览器，这些cookie仍然有效直到超过设定的过期时间。<br /><br />存储在硬盘上的cookie 可以在不同的浏览器进程间共享，比如两个IE窗口。而对于保存在内存里的cookie，不同的浏览器有不同的处理方式。对于IE，在一个打开的窗口上按 Ctrl-N（或者从文件菜单）打开的窗口可以与原窗口共享，而使用其他方式新开的IE进程则不能共享已经打开的窗口的内存cookie；对于 Mozilla Firefox0.8，所有的进程和标签页都可以共享同样的cookie。一般来说是用javascript的window.open打开的窗口会与原窗口共享内存cookie。浏览器对于会话cookie的这种只认cookie不认人的处理方式经常给采用session机制的web应用程序开发者造成很大的困扰。<br /><br />下面就是一个goolge设置cookie的响应头的例子<br /><pre class="overflow"><font color="#006400">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</font></pre><br /><br /><img style="DISPLAY: inline" onclick="javascript:imgClick(this);" alt="image" src="http://www.matrix.org.cn/resource/upload/forum/2006_03_12_235649_MdbyYrtKVN.jpg" onload="javascript:imgLoad(this);" border="0" resized="0" /><br />这是使用HTTPLook这个HTTP Sniffer软件来俘获的HTTP通讯纪录的一部分<br /><br /><img style="DISPLAY: inline" onclick="javascript:imgClick(this);" alt="image" src="http://www.matrix.org.cn/resource/upload/forum/2006_03_12_235718_CZMAULRUCN.jpg" onload="javascript:imgLoad(this);" border="0" resized="0" /><br />浏览器在再次访问goolge的资源时自动向外发送cookie<br /><br /><img style="DISPLAY: inline" onclick="javascript:imgClick(this);" alt="image" src="http://www.matrix.org.cn/resource/upload/forum/2006_03_12_235746_TgiVaUuZME.jpg" onload="javascript:imgLoad(this);" border="0" resized="0" /><br />用Firefox可以很容易的观察现有的cookie的值<br />使用HTTPLook配合Firefox可以很容易的理解cookie的工作原理。<br /><br /><img style="DISPLAY: inline" onclick="javascript:imgClick(this);" alt="image" src="http://www.matrix.org.cn/resource/upload/forum/2006_03_12_235812_qtRvksXzxP.jpg" onload="javascript:imgLoad(this);" border="0" resized="0" /><br />IE也可以设置在接受cookie前询问<br /><br /><b><font color="#0000ff">四、理解session机制</font></b><br /><br />session机制是一种服务器端的机制，服务器使用一种类似于散列表的结构（也可能就是使用散列表）来保存信息。<br /><br />当程序需要为某个客户端的请求创建一个session的时候，服务器首先检查这个客户端的请求里是否已包含了一个session标识 - 称为 session id，如果已包含一个session id则说明以前已经为此客户端创建过session，服务器就按照session id把这个 session检索出来使用（如果检索不到，可能会新建一个），如果客户端请求不包含session id，则为此客户端创建一个session并且生成一个与此session相关联的session id，session id的值应该是一个既不会重复，又不容易被找到规律以仿造的字符串，这个 session id将被在本次响应中返回给客户端保存。<br /><br />保存这个session id的方式可以采用cookie，这样在交互过程中浏览器可以自动的按照规则把这个标识发挥给服务器。一般这个cookie的名字都是类似于SEEESIONID，而。比如weblogic对于web应用程序生成的cookie，JSESSIONID= ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764，它的名字就是 JSESSIONID。<br /><br />由于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。<br /><br />另一种技术叫做表单隐藏字段。就是服务器会自动修改表单，添加一个隐藏字段，以便在表单提交时能够把session id传递回服务器。比如下面的表单<br /><pre class="overflow"><font color="#006400">&lt;form name="testform" action="/xxx"&gt;<br />&lt;input type="text"&gt;<br />&lt;/form&gt;</font></pre><br /><br />在被传递给客户端之前将被改写成<br /><pre class="overflow"><font color="#006400">&lt;form name="testform" action="/xxx"&gt;<br />&lt;input type="hidden" name="jsessionid" value="ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764"&gt;<br />&lt;input type="text"&gt;<br />&lt;/form&gt;</font></pre><br /><br />这种技术现在已较少应用，笔者接触过的很古老的iPlanet6(SunONE应用服务器的前身)就使用了这种技术。<br />实际上这种技术可以简单的用对action应用URL重写来代替。<br /><br />在谈论session机制的时候，常常听到这样一种误解“只要关闭浏览器，session就消失了”。其实可以想象一下会员卡的例子，除非顾客主动对店家提出销卡，否则店家绝对不会轻易删除顾客的资料。对session来说也是一样的，除非程序通知服务器删除一个session，否则服务器会一直保留，程序一般都是在用户做log off的时候发个指令去删除session。然而浏览器从来不会主动在关闭之前通知服务器它将要关闭，因此服务器根本不会有机会知道浏览器已经关闭，之所以会有这种错觉，是大部分session机制都使用会话cookie来保存session id，而关闭浏览器后这个 session id就消失了，再次连接服务器时也就无法找到原来的session。如果服务器设置的cookie被保存到硬盘上，或者使用某种手段改写浏览器发出的HTTP请求头，把原来的session id发送给服务器，则再次打开浏览器仍然能够找到原来的session。<br /><br />恰恰是由于关闭浏览器不会导致session被删除，迫使服务器为seesion设置了一个失效时间，当距离客户端上一次使用session的时间超过这个失效时间时，服务器就可以认为客户端已经停止了活动，才会把session删除以节省存储空间。<br /><br /><b><font color="#0000ff">五、理解javax.servlet.http.HttpSession</font></b><br />HttpSession是Java平台对session机制的实现规范，因为它仅仅是个接口，具体到每个web应用服务器的提供商，除了对规范支持之外，仍然会有一些规范里没有规定的细微差异。这里我们以BEA的Weblogic Server8.1作为例子来演示。<br /><br />首先，Weblogic Server提供了一系列的参数来控制它的HttpSession的实现，包括使用cookie的开关选项，使用URL重写的开关选项，session持久化的设置，session失效时间的设置，以及针对cookie的各种设置，比如设置cookie的名字、路径、域， cookie的生存时间等。<br /><br />一般情况下，session都是存储在内存里，当服务器进程被停止或者重启的时候，内存里的session也会被清空，如果设置了session的持久化特性，服务器就会把session保存到硬盘上，当服务器进程重新启动或这些信息将能够被再次使用， Weblogic Server支持的持久性方式包括文件、数据库、客户端cookie保存和复制。<br /><br />复制严格说来不算持久化保存，因为session实际上还是保存在内存里，不过同样的信息被复制到各个cluster内的服务器进程中，这样即使某个服务器进程停止工作也仍然可以从其他进程中取得session。<br /><br />cookie生存时间的设置则会影响浏览器生成的cookie是否是一个会话cookie。默认是使用会话cookie。有兴趣的可以用它来试验我们在第四节里提到的那个误解。<br /><br />cookie的路径对于web应用程序来说是一个非常重要的选项，Weblogic Server对这个选项的默认处理方式使得它与其他服务器有明显的区别。后面我们会专题讨论。<br /><br />关于session的设置参考[5] http://e-docs.bea.com/wls/docs70/webapp/weblogic_xml.html#1036869<br /><br /><b><font color="#0000ff">六、HttpSession常见问题</font></b><br />（在本小节中session的含义为⑤和⑥的混合）<br /><br />1、session在何时被创建<br />一个常见的误解是以为session在有客户端访问时就被创建，然而事实是直到某server端程序调用 HttpServletRequest.getSession(true)这样的语句时才被创建，注意如果JSP没有显示的使用 &lt;% @page session="false"%&gt; 关闭session，则JSP文件在编译成Servlet时将会自动加上这样一条语句 HttpSession session = HttpServletRequest.getSession(true);这也是JSP中隐含的 session对象的来历。<br /><br />由于session会消耗内存资源，因此，如果不打算使用session，应该在所有的JSP中关闭它。<br /><br />2、session何时被删除<br />综合前面的讨论，session在下列情况下被删除a.程序调用HttpSession.invalidate();或b.距离上一次收到客户端发送的session id时间间隔超过了session的超时设置;或c.服务器进程被停止（非持久session）<br /><br />3、如何做到在浏览器关闭时删除session<br />严格的讲，做不到这一点。可以做一点努力的办法是在所有的客户端页面里使用javascript代码window.oncolose来监视浏览器的关闭动作，然后向服务器发送一个请求来删除session。但是对于浏览器崩溃或者强行杀死进程这些非常规手段仍然无能为力。<br /><br />4、有个HttpSessionListener是怎么回事<br />你可以创建这样的listener去监控session的创建和销毁事件，使得在发生这样的事件时你可以做一些相应的工作。注意是session的创建和销毁动作触发listener，而不是相反。类似的与HttpSession有关的listener还有 HttpSessionBindingListener，HttpSessionActivationListener和 HttpSessionAttributeListener。<br /><br />5、存放在session中的对象必须是可序列化的吗<br />不是必需的。要求对象可序列化只是为了session能够在集群中被复制或者能够持久保存或者在必要时server能够暂时把session交换出内存。在 Weblogic Server的session中放置一个不可序列化的对象在控制台上会收到一个警告。我所用过的某个iPlanet版本如果 session中有不可序列化的对象，在session销毁时会有一个Exception，很奇怪。<br /><br />6、如何才能正确的应付客户端禁止cookie的可能性<br />对所有的URL使用URL重写，包括超链接，form的action，和重定向的URL，具体做法参见[6]<br />http://e-docs.bea.com/wls/docs70/webapp/sessions.html#100770<br /><br />7、开两个浏览器窗口访问应用程序会使用同一个session还是不同的session<br />参见第三小节对cookie的讨论，对session来说是只认id不认人，因此不同的浏览器，不同的窗口打开方式以及不同的cookie存储方式都会对这个问题的答案有影响。<br /><br />8、如何防止用户打开两个浏览器窗口操作导致的session混乱<br />这个问题与防止表单多次提交是类似的，可以通过设置客户端的令牌来解决。就是在服务器每次生成一个不同的id返回给客户端，同时保存在session里，客户端提交表单时必须把这个id也返回服务器，程序首先比较返回的id与保存在session里的值是否一致，如果不一致则说明本次操作已经被提交过了。可以参看《J2EE核心模式》关于表示层模式的部分。需要注意的是对于使用javascript window.open打开的窗口，一般不设置这个id，或者使用单独的id，以防主窗口无法操作，建议不要再window.open打开的窗口里做修改操作，这样就可以不用设置。<br /><br />9、为什么在Weblogic Server中改变session的值后要重新调用一次session.setValue<br />做这个动作主要是为了在集群环境中提示Weblogic Server session中的值发生了改变，需要向其他服务器进程复制新的session值。<br /><br />10、为什么session不见了<br />排除session正常失效的因素之外，服务器本身的可能性应该是微乎其微的，虽然笔者在iPlanet6SP1加若干补丁的Solaris版本上倒也遇到过；浏览器插件的可能性次之，笔者也遇到过3721插件造成的问题；理论上防火墙或者代理服务器在cookie处理上也有可能会出现问题。<br />出现这一问题的大部分原因都是程序的错误，最常见的就是在一个应用程序中去访问另外一个应用程序。我们在下一节讨论这个问题。<br /><br /><b><font color="#0000ff">七、跨应用程序的session共享</font></b><br /><br />常常有这样的情况，一个大项目被分割成若干小项目开发，为了能够互不干扰，要求每个小项目作为一个单独的web应用程序开发，可是到了最后突然发现某几个小项目之间需要共享一些信息，或者想使用session来实现SSO(single sign on)，在session中保存login的用户信息，最自然的要求是应用程序间能够访问彼此的session。<br /><br />然而按照Servlet规范，session的作用范围应该仅仅限于当前应用程序下，不同的应用程序之间是不能够互相访问对方的session的。各个应用服务器从实际效果上都遵守了这一规范，但是实现的细节却可能各有不同，因此解决跨应用程序session共享的方法也各不相同。<br /><br />首先来看一下Tomcat是如何实现web应用程序之间session的隔离的，从 Tomcat设置的cookie路径来看，它对不同的应用程序设置的cookie路径是不同的，这样不同的应用程序所用的session id是不同的，因此即使在同一个浏览器窗口里访问不同的应用程序，发送给服务器的session id也可以是不同的。<br /><br /><img style="DISPLAY: inline" onclick="javascript:imgClick(this);" alt="image" src="http://www.matrix.org.cn/resource/upload/forum/2006_03_12_235848_ApHWDSAInh.jpg" onload="javascript:imgLoad(this);" border="0" resized="0" /><br /><img style="DISPLAY: inline" onclick="javascript:imgClick(this);" alt="image" src="http://www.matrix.org.cn/resource/upload/forum/2006_03_12_235854_enWszyCQxX.jpg" onload="javascript:imgLoad(this);" border="0" resized="0" /><br /><br />根据这个特性，我们可以推测Tomcat中session的内存结构大致如下。<br /><img style="DISPLAY: inline" onclick="javascript:imgClick(this);" alt="image" src="http://www.matrix.org.cn/resource/upload/forum/2006_03_12_235909_sNDpkQLtDg.jpg" onload="javascript:imgLoad(this);" border="0" resized="0" /><br /><br />笔者以前用过的iPlanet也采用的是同样的方式，估计SunONE与iPlanet之间不会有太大的差别。对于这种方式的服务器，解决的思路很简单，实际实行起来也不难。要么让所有的应用程序共享一个session id，要么让应用程序能够获得其他应用程序的session id。<br /><br />iPlanet中有一种很简单的方法来实现共享一个session id，那就是把各个应用程序的cookie路径都设为/（实际上应该是/NASApp，对于应用程序来讲它的作用相当于根）。<br /><pre class="overflow"><font color="#006400">&lt;session-info&gt;<br />&lt;path&gt;/NASApp&lt;/path&gt;<br />&lt;/session-info&gt;</font></pre><br /><br />需要注意的是，操作共享的session应该遵循一些编程约定，比如在session attribute名字的前面加上应用程序的前缀，使得 setAttribute("name", "neo")变成setAttribute("app1.name", "neo")，以防止命名空间冲突，导致互相覆盖。<br /><br />在Tomcat中则没有这么方便的选择。在Tomcat版本3上，我们还可以有一些手段来共享session。对于版本4以上的Tomcat，目前笔者尚未发现简单的办法。只能借助于第三方的力量，比如使用文件、数据库、JMS或者客户端cookie，URL参数或者隐藏字段等手段。<br /><br />我们再看一下Weblogic Server是如何处理session的。<br /><img style="DISPLAY: inline" onclick="javascript:imgClick(this);" alt="image" src="http://www.matrix.org.cn/resource/upload/forum/2006_03_12_235920_OFuZWKrUSy.jpg" onload="javascript:imgLoad(this);" border="0" resized="0" /><br /><img style="DISPLAY: inline" onclick="javascript:imgClick(this);" alt="image" src="http://www.matrix.org.cn/resource/upload/forum/2006_03_12_235929_VqyJhESxZq.jpg" onload="javascript:imgLoad(this);" border="0" resized="0" /><br /><br />从截屏画面上可以看到Weblogic Server对所有的应用程序设置的cookie的路径都是/，这是不是意味着在Weblogic Server中默认的就可以共享session了呢？然而一个小实验即可证明即使不同的应用程序使用的是同一个session，各个应用程序仍然只能访问自己所设置的那些属性。这说明Weblogic Server中的session的内存结构可能如下<br /><img style="DISPLAY: inline" onclick="javascript:imgClick(this);" alt="image" src="http://www.matrix.org.cn/resource/upload/forum/2006_03_12_235953_LogterrCRh.jpg" onload="javascript:imgLoad(this);" border="0" resized="0" /><br /><br />对于这样一种结构，在 session机制本身上来解决session共享的问题应该是不可能的了。除了借助于第三方的力量，比如使用文件、数据库、JMS或者客户端 cookie，URL参数或者隐藏字段等手段，还有一种较为方便的做法，就是把一个应用程序的session放到ServletContext中，这样另外一个应用程序就可以从ServletContext中取得前一个应用程序的引用。示例代码如下，<br /><br />应用程序A<br /><pre class="overflow"><font color="#006400">context.setAttribute("appA", session); </font></pre><br /><br />应用程序B<br /><pre class="overflow"><font color="#006400">contextA = context.getContext("/appA");<br />HttpSession sessionA = (HttpSession)contextA.getAttribute("appA");</font></pre><br /><br />值得注意的是这种用法不可移植，因为根据ServletContext的JavaDoc，应用服务器可以处于安全的原因对于context.getContext("/appA");返回空值，以上做法在Weblogic Server 8.1中通过。<br /><br />那么Weblogic Server为什么要把所有的应用程序的cookie路径都设为/呢？原来是为了SSO，凡是共享这个session的应用程序都可以共享认证的信息。一个简单的实验就可以证明这一点，修改首先登录的那个应用程序的描述符weblogic.xml，把cookie路径修改为/appA 访问另外一个应用程序会重新要求登录，即使是反过来，先访问cookie路径为/的应用程序，再访问修改过路径的这个，虽然不再提示登录，但是登录的用户信息也会丢失。注意做这个实验时认证方式应该使用FORM，因为浏览器和web服务器对basic认证方式有其他的处理方式，第二次请求的认证不是通过 session来实现的。具体请参看[7] secion 14.8 Authorization，你可以修改所附的示例程序来做这些试验。<br /><br /><b><font color="#0000ff">八、总结</font></b><br />session机制本身并不复杂，然而其实现和配置上的灵活性却使得具体情况复杂多变。这也要求我们不能把仅仅某一次的经验或者某一个浏览器，服务器的经验当作普遍适用的经验，而是始终需要具体情况具体分析。<br />摘要：虽然session机制在web应用程序中被采用已经很长时间了，但是仍然有很多人不清楚session机制的本质，以至不能正确的应用这一技术。本文将详细讨论session的工作机制并且对在Java web application中应用session机制时常见的问题作出解答。<img src ="http://www.blogjava.net/dwys0343/aggbug/92336.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/dwys0343/" target="_blank">特兰克斯</a> 2007-01-08 12:26 <a href="http://www.blogjava.net/dwys0343/articles/92336.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>