﻿<?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-JAVA流通桥-文章分类-java文章</title><link>http://www.blogjava.net/zhuyan/category/21039.html</link><description>JAVA启发者</description><language>zh-cn</language><lastBuildDate>Tue, 10 Jul 2007 18:01:13 GMT</lastBuildDate><pubDate>Tue, 10 Jul 2007 18:01:13 GMT</pubDate><ttl>60</ttl><item><title>Java Map 集合类简介</title><link>http://www.blogjava.net/zhuyan/articles/129331.html</link><dc:creator>朱岩</dc:creator><author>朱岩</author><pubDate>Tue, 10 Jul 2007 05:58:00 GMT</pubDate><guid>http://www.blogjava.net/zhuyan/articles/129331.html</guid><wfw:comment>http://www.blogjava.net/zhuyan/comments/129331.html</wfw:comment><comments>http://www.blogjava.net/zhuyan/articles/129331.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/zhuyan/comments/commentRss/129331.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/zhuyan/services/trackbacks/129331.html</trackback:ping><description><![CDATA[<p><span class=topstoryhead>Java Map 集合类简介</span><br><span class=italicbodycopy>作者：Jack Shirazi</span> </p>
<p><span class=boldbodycopy>了解最常用的集合类型之一 Map 的基础知识以及如何针对您应用程序特有的数据优化 Map。</span></p>
<p>
<table cellPadding=5 width="25%" bgColor=#dddddd border=0>
    <tbody>
        <tr>
            <td><span class=boldbodycopy>本文相关下载：</span><br><span class=boldbodycopy>&#183;</span> <a href="http://www.oracle.com/technology/pub/files/hashmaps_part1.zip" target=_blank><span class=bodylink><u><font color=#0000ff>Jack 的 HashMap 测试</font></u></span></a><br><span class=boldbodycopy>&#183;</span> <a href="http://www.oracle.com/go/?&amp;Src=1952635&amp;Act=33" target=_blank><span class=bodylink><u><font color=#0000ff>Oracle JDeveloper 10<em>g</em></font></u></span></a> </td>
        </tr>
    </tbody>
</table>
<br clear=all>
<p>&#160;</p>
<p><span class=bodycopy>java.util 中的集合类包含 Java 中某些最常用的类。 最常用的集合类是 List 和 Map。 List 的具体实现包括 ArrayList 和 Vector，它们是可变大小的列表，比较适合构建、存储和操作任何类型对象的元素列表。 List 适用于按数值索引访问元素的情形。</span> </p>
<p><span class=bodycopy>Map 提供了一个更通用的元素存储方法。 Map 集合类用于存储元素对（称作&#8220;键&#8221;和&#8220;值&#8221;），其中每个键映射到一个值。 从概念上而言，您可以将 List 看作是具有数值键的 Map。 而实际上，除了 List 和 Map 都在定义 java.util 中外，两者并没有直接的联系。本文将着重介绍核心 Java 发行套件中附带的 Map，同时还将介绍如何采用或实现更适用于您应用程序特定数据的专用 Map。</span> </p>
<p><span class=parahead1>了解 Map 接口和方法</span> </p>
<p><span class=bodycopy>Java 核心类中有很多预定义的 Map 类。 在介绍具体实现之前，我们先介绍一下 Map 接口本身，以便了解所有实现的共同点。 Map 接口定义了四种类型的方法，每个 Map 都包含这些方法。 下面，我们从两个普通的方法（<a href="http://www.oracle.com/technology/global/cn/pub/articles/maps1.html#T1"><span class=bodylink><u><font color=#800080>表 1</font></u></span></a>）开始对这些方法加以介绍。</span> </p>
<p><span class=italicbodycopy><a name=T1></a>表 1： 覆盖的方法。 我们将这 Object 的这两个方法覆盖，以正确比较 Map 对象的等价性。</span>
<table cellPadding=5 width="100%" align=center bgColor=#dddddd border=1 vspace="5" hspace="5">
    <tbody>
        <tr>
            <td><span class=bodycopy>equals(Object o)</span></td>
            <td><span class=bodycopy>比较指定对象与此 Map 的等价性</span></td>
        </tr>
        <tr>
            <td><span class=bodycopy>hashCode()</span></td>
            <td><span class=bodycopy>返回此 Map 的哈希码</span></td>
        </tr>
    </tbody>
</table>
<p>&#160;</p>
<p><span class=boldbodycopy>Map 构建</span> </p>
<p><span class=bodycopy>Map 定义了几个用于插入和删除元素的变换方法（<a href="http://www.oracle.com/technology/global/cn/pub/articles/maps1.html#T2"><span class=bodylink><u><font color=#800080>表 2</font></u></span></a>）。</span> </p>
<p><span class=italicbodycopy><a name=T2></a>表 2： Map 更新方法： 可以更改 Map 内容。</span>
<table cellPadding=5 width="100%" align=center bgColor=#dddddd border=1 vspace="5" hspace="5">
    <tbody>
        <tr>
            <td><span class=bodycopy>clear()</span></td>
            <td><span class=bodycopy>从 Map 中删除所有映射</span></td>
        </tr>
        <tr>
            <td><span class=bodycopy>remove(Object key)</span></td>
            <td><span class=bodycopy>从 Map 中删除键和关联的值</span></td>
        </tr>
        <tr>
            <td><span class=bodycopy>put(Object key, Object value)</span></td>
            <td><span class=bodycopy>将指定值与指定键相关联</span></td>
        </tr>
        <tr>
            <td><span class=bodycopy>clear()</span></td>
            <td><span class=bodycopy>从 Map 中删除所有映射</span></td>
        </tr>
        <tr>
            <td><span class=bodycopy>putAll(Map t)</span></td>
            <td><span class=bodycopy>将指定 Map 中的所有映射复制到此 map</span></td>
        </tr>
    </tbody>
</table>
<p>&#160;</p>
<p><span class=bodycopy>尽管您可能注意到，纵然假设忽略构建一个需要传递给 putAll() 的 Map 的开销，使用 putAll() 通常也并不比使用大量的 put() 调用更有效率，但 putAll() 的存在一点也不稀奇。 这是因为，putAll() 除了迭代 put() 所执行的将每个键值对添加到 Map 的算法以外，还需要迭代所传递的 Map 的元素。 但应注意，putAll() 在添加所有元素之前可以正确调整 Map 的大小，因此如果您未亲自调整 Map 的大小（我们将对此进行简单介绍），则 putAll() 可能比预期的更有效。</span> </p>
<p><span class=boldbodycopy>查看 Map</span> </p>
<p><span class=bodycopy>迭代 Map 中的元素不存在直接了当的方法。 如果要查询某个 Map 以了解其哪些元素满足特定查询，或如果要迭代其所有元素（无论原因如何），则您首先需要获取该 Map 的&#8220;视图&#8221;。 有三种可能的视图（参见<a href="http://www.oracle.com/technology/global/cn/pub/articles/maps1.html#T3"><span class=bodylink><u><font color=#800080>表 3</font></u></span></a>）</span>
<ul>
    <li><span class=bodycopy>所有键值对 — 参见 entrySet()</span>
    <li><span class=bodycopy>所有键 — 参见 keySet()</span>
    <li><span class=bodycopy>所有值 — 参见 values()</span> </li>
</ul>
<p>&#160;</p>
<p><span class=bodycopy>前两个视图均返回 Set 对象，第三个视图返回 Collection 对象。 就这两种情况而言，问题到这里并没有结束，这是因为您无法直接迭代 Collection 对象或 Set 对象。要进行迭代，您必须获得一个 Iterator 对象。 因此，要迭代 Map 的元素，必须进行比较烦琐的编码</span> </p>
<p>
<pre>Iterator keyValuePairs = aMap.entrySet().iterator();
Iterator keys = aMap.keySet().iterator();
Iterator values = aMap.values().iterator();
</pre>
<p>&#160;</p>
<p><span class=bodycopy>值得注意的是，这些对象（Set、Collection 和 Iterator）实际上是基础 Map 的视图，而不是包含所有元素的副本。 这使它们的使用效率很高。 另一方面，Collection 或 Set 对象的 toArray() 方法却创建包含 Map 所有元素的数组对象，因此除了确实需要使用数组中元素的情形外，其效率并不高。</span> </p>
<p><span class=bodycopy>我运行了一个小测试（随附文件中的 <a href="http://www.oracle.com/technology/pub/listing/Jack_Test1.java" target=_blank><span class=bodylink><u><font color=#0000ff>Test1</font></u></span></a>），该测试使用了 HashMap，并使用以下两种方法对迭代 Map 元素的开销进行了比较：</span> </p>
<p>
<pre>int mapsize = aMap.size();
<strong>Iterator keyValuePairs1 = aMap.entrySet().iterator();</strong>
for (int i = 0; i &lt; mapsize; i++)
{
Map.Entry entry = (Map.Entry) keyValuePairs1.next();
Object key = entry.getKey();
Object value = entry.getValue();
...
}
<strong>Object[] keyValuePairs2 = aMap.entrySet().toArray();</strong>
for (int i = 0; i &lt; rem; i++) {
{
Map.Entry entry = (Map.Entry) keyValuePairs2[i];
Object key = entry.getKey();
<table cellPadding=5 width="40%" align=right bgColor=#dddddd border=1 vspace="5" hspace="5">
    <tbody>
        <tr>
            <td><center><span class=parahead1>Profilers in Oracle JDeveloper</span></center>
            <p>&#160;</p>
            <p><span class=bodycopy>Oracle JDeveloper 包含一个嵌入的监测器，它测量内存和执行时间，使您能够快速识别代码中的瓶颈。 我曾使用 Jdeveloper 的执行监测器监测 HashMap 的 containsKey() 和 containsValue() 方法，并很快发现 containsKey() 方法的速度比 containsValue() 方法慢很多（实际上要慢几个数量级！）。 （参见<a href="http://www.oracle.com/technology/global/cn/pub/articles/maps1.html#F1"><span class=bodylink><u><font color=#800080>图 1</font></u></span></a> 和<a href="http://www.oracle.com/technology/global/cn/pub/articles/maps1.html#F2"><span class=bodylink><u><font color=#800080>图 2</font></u></span></a>，以及随附文件中的 <a href="http://www.oracle.com/technology/pub/listing/Jack_Test2.java" target=_blank><span class=bodylink><u><font color=#0000ff>Test2</font></u></span></a> 类）。</span> </p>
            </td>
        </tr>
    </tbody>
</table>
Object value = entry.getValue();
...
}
</pre>
<span class=bodycopy>此测试使用了两种测量方法： 一种是测量迭代元素的时间，另一种测量使用 toArray 调用创建数组的其他开销。 第一种方法（忽略创建数组所需的时间）表明，使用已从 toArray 调用中创建的数组迭代元素的速度要比使用 Iterator 的速度大约快 30%-60%。 但如果将使用 toArray 方法创建数组的开销包含在内，则使用 Iterator 实际上要快 10%-20%。 因此，如果由于某种原因要创建一个集合元素的数组而非迭代这些元素，则应使用该数组迭代元素。 但如果您不需要此中间数组，则不要创建它，而是使用 Iterator 迭代元素。</span>
<p>&#160;</p>
<p><span class=italicbodycopy><a name=T3></a>表 3： 返回视图的 Map 方法： 使用这些方法返回的对象，您可以遍历 Map 的元素，还可以删除 Map 中的元素。</span>
<table cellPadding=5 width="100%" align=center bgColor=#dddddd border=1 vspace="5" hspace="5">
    <tbody>
        <tr>
            <td><span class=bodycopy>entrySet()</span></td>
            <td><span class=bodycopy>返回 Map 中所包含映射的 Set 视图。 Set 中的每个元素都是一个 Map.Entry 对象，可以使用 getKey() 和 getValue() 方法（还有一个 setValue() 方法）访问后者的键元素和值元素</span></td>
        </tr>
        <tr>
            <td><span class=bodycopy>keySet()</span></td>
            <td><span class=bodycopy>返回 Map 中所包含键的 Set 视图。 删除 Set 中的元素还将删除 Map 中相应的映射（键和值）</span></td>
        </tr>
        <tr>
            <td><span class=bodycopy>values()</span></td>
            <td><span class=bodycopy>返回 map 中所包含值的 Collection 视图。 删除 Collection 中的元素还将删除 Map 中相应的映射（键和值）</span></td>
        </tr>
    </tbody>
</table>
<p>&#160;</p>
<p><span class=boldbodycopy>访问元素</span> </p>
<p><span class=bodycopy>表 4 中列出了 Map 访问方法。Map 通常适合按键（而非按值）进行访问。 Map 定义中没有规定这肯定是真的，但通常您可以期望这是真的。 例如，您可以期望 containsKey() 方法与 get() 方法一样快。 另一方面，containsValue() 方法很可能需要扫描 Map 中的值，因此它的速度可能比较慢。</span> </p>
<p><span class=italicbodycopy><a name=T4></a>表 4： Map 访问和测试方法： 这些方法检索有关 Map 内容的信息但不更改 Map 内容。</span>
<table cellPadding=5 width="100%" align=center bgColor=#dddddd border=1 vspace="5" hspace="5">
    <tbody>
        <tr>
            <td><span class=bodycopy>get(Object key)</span></td>
            <td><span class=bodycopy>返回与指定键关联的值</span></td>
        </tr>
        <tr>
            <td><span class=bodycopy>containsKey(Object key)</span></td>
            <td><span class=bodycopy>如果 Map 包含指定键的映射，则返回 true</span></td>
        </tr>
        <tr>
            <td><span class=bodycopy>containsValue(Object value)</span></td>
            <td><span class=bodycopy>如果此 Map 将一个或多个键映射到指定值，则返回 true</span></td>
        </tr>
        <tr>
            <td><span class=bodycopy>isEmpty()</span></td>
            <td><span class=bodycopy>如果 Map 不包含键-值映射，则返回 true</span></td>
        </tr>
        <tr>
            <td><span class=bodycopy>size()</span></td>
            <td><span class=bodycopy>返回 Map 中的键-值映射的数目</span></td>
        </tr>
    </tbody>
</table>
<p>&#160;</p>
<p><span class=bodycopy>对使用 containsKey() 和 containsValue() 遍历 HashMap 中所有元素所需时间的测试表明，containsValue() 所需的时间要长很多。 实际上要长几个数量级！ （参见<a href="http://www.oracle.com/technology/global/cn/pub/articles/maps1.html#F1"><span class=bodylink><u><font color=#800080>图 1</font></u></span></a> 和<a href="http://www.oracle.com/technology/global/cn/pub/articles/maps1.html#F2"><span class=bodylink><u><font color=#800080>图 2</font></u></span></a>，以及随附文件中的 <a href="http://www.oracle.com/technology/pub/listing/Jack_Test2.java" target=_blank><span class=bodylink><u><font color=#0000ff>Test2</font></u></span></a>）。 因此，如果 containsValue() 是应用程序中的性能问题，它将很快显现出来，并可以通过监测您的应用程序轻松地将其识别。 这种情况下，我相信您能够想出一个有效的替换方法来实现 containsValue() 提供的等效功能。 但如果想不出办法，则一个可行的解决方案是再创建一个 Map，并将第一个 Map 的所有值作为键。 这样，第一个 Map 上的 containsValue() 将成为第二个 Map 上更有效的 containsKey()。</span> </p>
<p><a name=F1></a>
<table width="100%" align=center>
    <tbody>
        <tr>
            <td align=middle><img height=468 alt="图 1" src="http://www.oracle.com/technology/pub/images/map_test.gif" width=750 border=0> </td>
        </tr>
        <tr>
            <td>
            <center><span class=bodycopy>图 1： 使用 JDeveloper 创建并运行 Map 测试类</span></center></td>
        </tr>
    </tbody>
</table>
<p>&#160;</p>
<p><a name=F2></a>
<table width="100%" align=center>
    <tbody>
        <tr>
            <td align=middle><img height=448 alt="图 2" src="http://www.oracle.com/technology/pub/images/map_test2.gif" width=750 border=0> </td>
        </tr>
        <tr>
            <td>
            <center><span class=bodycopy>图 2： 在 JDeveloper 中使用执行监测器进行的性能监测查出应用程序中的瓶颈</span></center></td>
        </tr>
    </tbody>
</table>
<p>&#160;</p>
<p><span class=parahead1>核心 Map</span> </p>
<p><span class=bodycopy>Java 自带了各种 Map 类。 这些 Map 类可归为三种类型： </span></p>
<p>
<ol>
    <li><span class=bodycopy>通用 Map，用于在应用程序中管理映射，通常在 java.util 程序包中实现</span>
    <ul>
        <li><span class=bodycopy>HashMap</span>
        <li><span class=bodycopy>Hashtable</span>
        <li><span class=bodycopy>Properties</span>
        <li><span class=bodycopy>LinkedHashMap</span>
        <li><span class=bodycopy>IdentityHashMap</span>
        <li><span class=bodycopy>TreeMap</span>
        <li><span class=bodycopy>WeakHashMap</span>
        <li><span class=bodycopy>ConcurrentHashMap</span> </li>
    </ul>
    <li><span class=bodycopy>专用 Map，您通常不必亲自创建此类 Map，而是通过某些其他类对其进行访问</span>
    <ul>
        <li><span class=bodycopy>java.util.jar.Attributes</span>
        <li><span class=bodycopy>javax.print.attribute.standard.PrinterStateReasons</span>
        <li><span class=bodycopy>java.security.Provider</span>
        <li><span class=bodycopy>java.awt.RenderingHints</span>
        <li><span class=bodycopy>javax.swing.UIDefaults</span> </li>
    </ul>
    <li><span class=bodycopy>一个用于帮助实现您自己的 Map 类的抽象类</span>
    <ul>
        <li><span class=bodycopy>AbstractMap</span> </li>
    </ul>
    </li>
</ol>
<p>&#160;</p>
<p><span class=parahead1>内部哈希： 哈希映射技术</span> </p>
<p><span class=bodycopy>几乎所有通用 Map 都使用哈希映射。 这是一种将元素映射到数组的非常简单的机制，您应了解哈希映射的工作原理，以便充分利用 Map。</span> </p>
<p><span class=bodycopy>哈希映射结构由一个存储元素的内部数组组成。 由于内部采用数组存储，因此必然存在一个用于确定任意键访问数组的索引机制。 实际上，该机制需要提供一个小于数组大小的整数索引值。 该机制称作哈希函数。 在 Java 基于哈希的 Map 中，哈希函数将对象转换为一个适合内部数组的整数。 您不必为寻找一个易于使用的哈希函数而大伤脑筋： 每个对象都包含一个返回整数值的 hashCode() 方法。 要将该值映射到数组，只需将其转换为一个正值，然后在将该值除以数组大小后取余数即可。 以下是一个简单的、适用于任何对象的 Java 哈希函数</span> </p>
<p>
<pre>int hashvalue = Maths.abs(key.hashCode()) % table.length;
</pre>
<p>&#160;</p>
<p><span class=bodycopy>（% 二进制运算符（称作模）将左侧的值除以右侧的值，然后返回整数形式的余数。）</span> </p>
<p><span class=bodycopy>实际上，在 1.4 版发布之前，这就是各种基于哈希的 Map 类所使用的哈希函数。 但如果您查看一下代码，您将看到</span> </p>
<p>
<pre>int hashvalue = (key.hashCode() &amp; 0x7FFFFFFF) % table.length;
</pre>
<p>&#160;</p>
<p><span class=bodycopy>它实际上是使用更快机制获取正值的同一函数。 在 1.4 版中，HashMap 类实现使用一个不同且更复杂的哈希函数，该函数基于 Doug Lea 的 util.concurrent 程序包（稍后我将更详细地再次介绍 Doug Lea 的类）。</span> </p>
<p><a name=F3></a>
<table width="100%" align=center>
    <tbody>
        <tr>
            <td align=middle><img height=446 alt="图 3" src="http://www.oracle.com/technology/pub/images/jave_maps.gif" width=260 border=0> </td>
        </tr>
        <tr>
            <td>
            <center><span class=bodycopy>图 3： 哈希工作原理</span></center></td>
        </tr>
    </tbody>
</table>
<p>&#160;</p>
<p><span class=bodycopy>该图介绍了哈希映射的基本原理，但我们还没有对其进行详细介绍。 我们的哈希函数将任意对象映射到一个数组位置，但如果两个不同的键映射到相同的位置，情况将会如何？ 这是一种必然发生的情况。 在哈希映射的术语中，这称作冲突。 Map 处理这些冲突的方法是在索引位置处插入一个链接列表，并简单地将元素添加到此链接列表。 因此，一个基于哈希的 Map 的基本 put() 方法可能如下所示</span> </p>
<p>
<pre>public Object put(Object key, Object value) {
//我们的内部数组是一个 Entry 对象数组
//Entry[] table;
//获取哈希码，并映射到一个索引
int hash = key.hashCode();
int index = (hash &amp; 0x7FFFFFFF) % table.length;
//循环遍历位于 table[index] 处的链接列表，以查明
//我们是否拥有此键项 — 如果拥有，则覆盖它
for (Entry e = table[index] ; e != null ; e = e.next) {
//必须检查键是否相等，原因是不同的键对象
//可能拥有相同的哈希
if ((e.hash == hash) &amp;&amp; e.key.equals(key)) {
//这是相同键，覆盖该值
//并从该方法返回 old 值
Object old = e.value;
e.value = value;
return old;
}
}
//仍然在此处，因此它是一个新键，只需添加一个新 Entry
//Entry 对象包含 key 对象、 value 对象、一个整型的 hash、
//和一个指向列表中的下一个 Entry 的 next Entry
//创建一个指向上一个列表开头的新 Entry，
//并将此新 Entry 插入表中
Entry e = new Entry(hash, key, value, table[index]);
table[index] = e;
return null;
}
</pre>
<p>&#160;</p>
<p><span class=bodycopy>如果看一下各种基于哈希的 Map 的源代码，您将发现这基本上就是它们的工作原理。 此外，还有一些需要进一步考虑的事项，如处理空键和值以及调整内部数组。 此处定义的 put() 方法还包含相应 get() 的算法，这是因为插入包括搜索映射索引处的项以查明该键是否已经存在。 （即 get() 方法与 put() 方法具有相同的算法，但 get() 不包含插入和覆盖代码。） 使用链接列表并不是解决冲突的唯一方法，某些哈希映射使用另一种&#8220;开放式寻址&#8221;方案，本文对其不予介绍。</span> </p>
<p><span class=parahead1>优化 Hasmap</span> </p>
<p><span class=bodycopy>如果哈希映射的内部数组只包含一个元素，则所有项将映射到此数组位置，从而构成一个较长的链接列表。 由于我们的更新和访问使用了对链接列表的线性搜索，而这要比 Map 中的每个数组索引只包含一个对象的情形要慢得多，因此这样做的效率很低。 访问或更新链接列表的时间与列表的大小线性相关，而使用哈希函数访问或更新数组中的单个元素则与数组大小无关 — 就渐进性质（Big-O 表示法）而言，前者为 O(n)，而后者为 O(1)。 因此，使用一个较大的数组而不是让太多的项聚集在太少的数组位置中是有意义的。</span> </p>
<p><span class=boldbodycopy>调整 Map 实现的大小</span> </p>
<p><span class=bodycopy>在哈希术语中，内部数组中的每个位置称作&#8220;存储桶&#8221;(bucket)，而可用的存储桶数（即内部数组的大小）称作容量 (capacity)。 为使 Map 对象有效地处理任意数目的项，Map 实现可以调整自身的大小。 但调整大小的开销很大。 调整大小需要将所有元素重新插入到新数组中，这是因为不同的数组大小意味着对象现在映射到不同的索引值。 先前冲突的键可能不再冲突，而先前不冲突的其他键现在可能冲突。 这显然表明，如果将 Map 调整得足够大，则可以减少甚至不再需要重新调整大小，这很有可能显著提高速度。</span> </p>
<p><span class=bodycopy>使用 1.4.2 JVM 运行一个简单的测试，即用大量的项（数目超过一百万）填充 HashMap。 表 5 显示了结果，并将所有时间标准化为已预先设置大小的服务器模式（关联文件中的 <a href="http://www.oracle.com/technology/pub/listing/Jack_Test3.java" target=_blank><span class=bodylink><u><font color=#0000ff>Test3</font></u></span></a>）。 对于已预先设置大小的 JVM，客户端和服务器模式 JVM 运行时间几乎相同（在放弃 JIT 编译阶段后）。 但使用 Map 的默认大小将引发多次调整大小操作，开销很大，在服务器模式下要多用 50% 的时间，而在客户端模式下几乎要多用两倍的时间！</span> </p>
<p><span class=italicbodycopy><a name=T5></a>表 5： 填充已预先设置大小的 HashMap 与填充默认大小的 HashMap 所需时间的比较</span>
<table cellPadding=5 width="100%" align=center bgColor=#dddddd border=1 vspace="5" hspace="5">
    <tbody>
        <tr>
            <td></td>
            <td><span class=boldbodycopy>客户端模式</span></td>
            <td><span class=boldbodycopy>服务器模式</span></td>
        </tr>
        <tr>
            <td><span class=bodycopy>预先设置的大小</span></td>
            <td><span class=bodycopy>100%</span>
            <td><span class=bodycopy>100%</span></td>
            </span>
        </tr>
        <tr>
            <td><span class=bodycopy>默认大小</span></td>
            <td><span class=bodycopy>294%</span></td>
            <td><span class=bodycopy>157%</span></td>
        </tr>
    </tbody>
</table>
<p>&#160;</p>
<p><span class=boldbodycopy>使用负载因子</span> </p>
<p><span class=bodycopy>为确定何时调整大小，而不是对每个存储桶中的链接列表的深度进行记数，基于哈希的 Map 使用一个额外参数并粗略计算存储桶的密度。 Map 在调整大小之前，使用名为&#8220;负载因子&#8221;的参数指示 Map 将承担的&#8220;负载&#8221;量，即它的负载程度。 负载因子、项数（Map 大小）与容量之间的关系简单明了：</span> </p>
<p>
<ul>
    <li><span class=bodycopy>如果（负载因子）x（容量）&gt;（Map 大小），则调整 Map 大小</span> </li>
</ul>
<p>&#160;</p>
<p><span class=bodycopy>例如，如果默认负载因子为 0.75，默认容量为 11，则 11 x 0.75 = 8.25，该值向下取整为 8 个元素。 因此，如果将第 8 个项添加到此 Map，则该 Map 将自身的大小调整为一个更大的值。 相反，要计算避免调整大小所需的初始容量，用将要添加的项数除以负载因子，并向上取整，例如，</span> </p>
<p>
<ul>
    <li><span class=bodycopy>对于负载因子为 0.75 的 100 个项，应将容量设置为 100/0.75 = 133.33，并将结果向上取整为 134（或取整为 135 以使用奇数）</span> </li>
</ul>
<p>&#160;</p>
<p><span class=bodycopy>奇数个存储桶使 map 能够通过减少冲突数来提高执行效率。 虽然我所做的测试（关联文件中的<a href="http://www.oracle.com/technology/pub/listing/Jack_Test4.java" target=_blank><span class=bodylink><u><font color=#0000ff>Test4</font></u></span></a>）并未表明质数可以始终获得更好的效率，但理想情形是容量取质数。 1.4 版后的某些 Map（如 HashMap 和 LinkedHashMap，而非 Hashtable 或 IdentityHashMap）使用需要 2 的幂容量的哈希函数，但下一个最高 2 的幂容量由这些 Map 计算，因此您不必亲自计算。</span> </p>
<p><span class=bodycopy>负载因子本身是空间和时间之间的调整折衷。 较小的负载因子将占用更多的空间，但将降低冲突的可能性，从而将加快访问和更新的速度。 使用大于 0.75 的负载因子可能是不明智的，而使用大于 1.0 的负载因子肯定是不明知的，这是因为这必定会引发一次冲突。 使用小于 0.50 的负载因子好处并不大，但只要您有效地调整 Map 的大小，应不会对小负载因子造成性能开销，而只会造成内存开销。 但较小的负载因子将意味着如果您未预先调整 Map 的大小，则导致更频繁的调整大小，从而降低性能，因此在调整负载因子时一定要注意这个问题。</span> </p>
<p><span class=parahead1>选择适当的 Map</span> </p>
<p><span class=bodycopy>应使用哪种 Map？ 它是否需要同步？ 要获得应用程序的最佳性能，这可能是所面临的两个最重要的问题。 当使用通用 Map 时，调整 Map 大小和选择负载因子涵盖了 Map 调整选项。</span> </p>
<p><span class=bodycopy>以下是一个用于获得最佳 Map 性能的简单方法</span>
<ol>
    <li><span class=bodycopy>将您的所有 Map 变量声明为 Map，而不是任何具体实现，即不要声明为 HashMap 或 Hashtable，或任何其他 Map 类实现。</span>
    <p>&#160;</p>
    <p>
    <pre>Map criticalMap = new HashMap(); //好
    HashMap criticalMap = new HashMap(); //差
    </pre>
    <p>&#160;</p>
    <p><span class=bodycopy>这使您能够只更改一行代码即可非常轻松地替换任何特定的 Map 实例。</span> </p>
    <li><span class=bodycopy>下载 Doug Lea 的 util.concurrent 程序包 (<a href="http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html" target=_blank><span class=bodylink><u><font color=#0000ff>http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html</font></u></span></a>)。 将 ConcurrentHashMap 用作默认 Map。 当移植到 1.5 版时，将 java.util.concurrent.ConcurrentHashMap 用作您的默认 Map。 不要将 ConcurrentHashMap 包装在同步的包装器中，即使它将用于多个线程。 使用默认大小和负载因子。</span>
    <li><span class=bodycopy>监测您的应用程序。 如果发现某个 Map 造成瓶颈，则分析造成瓶颈的原因，并部分或全部更改该 Map 的以下内容： Map 类；Map 大小；负载因子；关键对象 equals() 方法实现。 专用的 Map 的基本上都需要特殊用途的定制 Map 实现，否则通用 Map 将实现您所需的性能目标。</span> </li>
</ol>
<p>&#160;</p>
<p><span class=boldbodycopy>Map 选择</span> </p>
<p><span class=bodycopy>也许您曾期望更复杂的考量，而这实际上是否显得太容易？ 好的，让我们慢慢来。 首先，您应使用哪种 Map？ 答案很简单： 不要为您的设计选择任何特定的 Map，除非实际的设计需要指定一个特殊类型的 Map。 设计时通常不需要选择具体的 Map 实现。 您可能知道自己需要一个 Map，但不知道使用哪种。 而这恰恰就是使用 Map 接口的意义所在。 直到需要时再选择 Map 实现 — 如果随处使用&#8220;Map&#8221;声明的变量，则更改应用程序中任何特殊 Map 的 Map 实现只需要更改一行，这是一种开销很少的调整选择。 是否要使用默认的 Map 实现？ 我很快将谈到这个问题。</span> </p>
<p><span class=boldbodycopy>同步 Map</span> </p>
<p><span class=bodycopy>同步与否有何差别？ （对于同步，您既可以使用同步的 Map，也可以使用 Collections.synchronizedMap() 将未同步的 Map 转换为同步的 Map。 后者使用&#8220;同步的包装器&#8221;）这是一个异常复杂的选择，完全取决于您如何根据多线程并发访问和更新使用 Map，同时还需要进行维护方面的考虑。 例如，如果您开始时未并发更新特定 Map，但它后来更改为并发更新，情况将如何？ 在这种情况下，很容易在开始时使用一个未同步的 Map，并在后来向应用程序中添加并发更新线程时忘记将此未同步的 Map 更改为同步的 Map。 这将使您的应用程序容易崩溃（一种要确定和跟踪的最糟糕的错误）。 但如果默认为同步，则将因随之而来的可怕性能而序列化执行多线程应用程序。 看起来，我们需要某种决策树来帮助我们正确选择。</span> </p>
<p><span class=bodycopy>Doug Lea 是纽约州立大学奥斯威戈分校计算机科学系的教授。 他创建了一组公共领域的程序包（统称 util.concurrent），该程序包包含许多可以简化高性能并行编程的实用程序类。 这些类中包含两个 Map，即 ConcurrentReaderHashMap 和 ConcurrentHashMap。 这些 Map 实现是线程安全的，并且不需要对并发访问或更新进行同步，同时还适用于大多数需要 Map 的情况。 它们还远比同步的 Map（如 Hashtable）或使用同步的包装器更具伸缩性，并且与 HashMap 相比，它们对性能的破坏很小。 util.concurrent 程序包构成了 JSR166 的基础；JSR166 已经开发了一个包含在 Java 1.5 版中的并发实用程序，而 Java 1.5 版将把这些 Map 包含在一个新的 java.util.concurrent 程序包中。</span> </p>
<p>
<table cellPadding=5 width="40%" align=right bgColor=#dddddd border=1 vspace="5" hspace="5">
    <tbody>
        <tr>
            <td>
            <center><span class=parahead1>后续步骤</span></center>
            <p>&#160;</p>
            <p><a href="http://www.oracle.com/go/?&amp;Src=1952635&amp;Act=33" target=_blank><span class=bodylink><u><font color=#0000ff>下载 Oracle JDeveloper 10<em>g</em></font></u></span></a>：<span class=bodycopy>改变您对 Java 开发的看法</span> </p>
            <p><a href="http://helponline.oracle.com/jdeveloper/help/state/content/navSetId.jdeveloper/navId.4/vtTopicFile.jdeveloper%7Ctuning%7Coptimizing%7Cprf_about%7Ehtml/" target=_blank><span class=bodylink><u><font color=#0000ff>Oracle JDeveloper 10<em>g</em> 中的监测器</font></u></span></a>： <span class=bodycopy>该监测器利用 Java 虚拟机中的某些特性，使您能够发现应用程序代码中的编程缺陷、性能问题以及内存泄漏。 可以将监测器与调试器和 CodeCoach 一起使用来进行功能强大且有效的应用程序代码故障排除。 了解更多有关事件监测、执行监测以及内存监测的信息。 </span></p>
            </td>
        </tr>
    </tbody>
</table>
<span class=bodycopy>所有这一切意味着您不需要一个决策树来决定是使用同步的 Map 还是使用非同步的 Map， 而只需使用 ConcurrentHashMap。 当然，在某些情况下，使用 ConcurrentHashMap 并不合适。 但这些情况很少见，并且应具体情况具体处理。 这就是监测的用途。</span>
<p>&#160;</p>
<p><span class=parahead1>结束语</span> </p>
<p><span class=bodycopy>通过 Oracle JDeveloper 可以非常轻松地创建一个用于比较各种 Map 性能的测试类。 更重要的是，集成良好的监测器可以在开发过程中快速、轻松地识别性能瓶颈 - 集成到 IDE 中的监测器通常被较频繁地使用，以便帮助构建一个成功的工程。 现在，您已经拥有了一个监测器并了解了有关通用 Map 及其性能的基础知识，可以开始运行您自己的测试，以查明您的应用程序是否因 Map 而存在瓶颈以及在何处需要更改所使用的 Map。 </span></p>
<p><span class=bodycopy>以上内容介绍了通用 Map 及其性能的基础知识。 当然，有关特定 Map 实现以及如何根据不同的需求使用它们还存在更多复杂和值得关注的事项，这些将在本文第 2 部分中介绍。</span> </p>
<hr>
<span class=boldbodycopy>Jack Shirazi</span><span class=bodycopy> 是 O'Reilly 的&#8220;<span class=italicbodycopy>Java 性能调整</span><span class=bodycopy>&#8221;的作者，以及受欢迎的 JavaPerformanceTuning.com 网站（提供 Java 性能信息的全球知名站点）的总监。 Jack 在 Java 性能领域提供咨询并著书立说。 他还监督 JavaPerformanceTuning.com 提供的信息，其中包括每年大约发布 1000 条性能技巧以及许多有关性能工具、讨论组等内容的文章。 Jack 早年还曾发布有关蛋白质结构预测以及黑洞热力学方面的文章，而且在其空闲时还对某些 Perl5 核心模块作出了贡献。</span> </span>
<img src ="http://www.blogjava.net/zhuyan/aggbug/129331.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/zhuyan/" target="_blank">朱岩</a> 2007-07-10 13:58 <a href="http://www.blogjava.net/zhuyan/articles/129331.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>JSTL入门</title><link>http://www.blogjava.net/zhuyan/articles/107266.html</link><dc:creator>朱岩</dc:creator><author>朱岩</author><pubDate>Thu, 29 Mar 2007 09:07:00 GMT</pubDate><guid>http://www.blogjava.net/zhuyan/articles/107266.html</guid><wfw:comment>http://www.blogjava.net/zhuyan/comments/107266.html</wfw:comment><comments>http://www.blogjava.net/zhuyan/articles/107266.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/zhuyan/comments/commentRss/107266.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/zhuyan/services/trackbacks/107266.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: JSTL 入门: 表达式语言										通过避免使用脚本编制元素来简化对 JSP 应用程序的软件维护																																																																																																																...&nbsp;&nbsp;<a href='http://www.blogjava.net/zhuyan/articles/107266.html'>阅读全文</a><img src ="http://www.blogjava.net/zhuyan/aggbug/107266.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/zhuyan/" target="_blank">朱岩</a> 2007-03-29 17:07 <a href="http://www.blogjava.net/zhuyan/articles/107266.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>关于web.xml配置的详细说明</title><link>http://www.blogjava.net/zhuyan/articles/106763.html</link><dc:creator>朱岩</dc:creator><author>朱岩</author><pubDate>Tue, 27 Mar 2007 13:08:00 GMT</pubDate><guid>http://www.blogjava.net/zhuyan/articles/106763.html</guid><wfw:comment>http://www.blogjava.net/zhuyan/comments/106763.html</wfw:comment><comments>http://www.blogjava.net/zhuyan/articles/106763.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/zhuyan/comments/commentRss/106763.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/zhuyan/services/trackbacks/106763.html</trackback:ping><description><![CDATA[form:javaresearch<br />1 定义头和根元素<br /><br />部署描述符文件就像所有XML文件一样，必须以一个XML头开始。这个头声明可以使用的XML版本并给出文件的字符编码。<br />DOCYTPE声明必须立即出现在此头之后。这个声明告诉服务器适用的servlet规范的版本（如2.2或2.3）并指定管理此文件其余部分内容的语法的DTD(Document Type Definition，文档类型定义)。<br />所有部署描述符文件的顶层（根）元素为web-app。请注意，XML元素不像HTML，他们是大小写敏感的。因此，web-App和WEB-APP都是不合法的，web-app必须用小写。<br /><br />2 部署描述符文件内的元素次序<br /><br />XML 元素不仅是大小写敏感的，而且它们还对出现在其他元素中的次序敏感。例如，XML头必须是文件中的第一项，DOCTYPE声明必须是第二项，而web- app元素必须是第三项。在web-app元素内，元素的次序也很重要。服务器不一定强制要求这种次序，但它们允许（实际上有些服务器就是这样做的）完全拒绝执行含有次序不正确的元素的Web应用。这表示使用非标准元素次序的web.xml文件是不可移植的。<br />下面的列表给出了所有可直接出现在web-app元素内的合法元素所必需的次序。例如，此列表说明servlet元素必须出现在所有servlet-mapping元素之前。请注意，所有这些元素都是可选的。因此，可以省略掉某一元素，但不能把它放于不正确的位置。<br />l icon icon元素指出IDE和GUI工具用来表示Web应用的一个和两个图像文件的位置。<br />l display-name display-name元素提供GUI工具可能会用来标记这个特定的Web应用的一个名称。<br />l description description元素给出与此有关的说明性文本。<br />l context-param context-param元素声明应用范围内的初始化参数。<br />l filter 过滤器元素将一个名字与一个实现javax.servlet.Filter接口的类相关联。<br />l filter-mapping 一旦命名了一个过滤器，就要利用filter-mapping元素把它与一个或多个servlet或JSP页面相关联。<br />l listener servlet API的版本2.3增加了对事件监听程序的支持，事件监听程序在建立、修改和删除会话或servlet环境时得到通知。Listener元素指出事件监听程序类。<br />l servlet 在向servlet或JSP页面制定初始化参数或定制URL时，必须首先命名servlet或JSP页面。Servlet元素就是用来完成此项任务的。<br />l servlet-mapping 服务器一般为servlet提供一个缺省的URL：http://host/webAppPrefix/servlet/ServletName。但是，常常会更改这个URL，以便servlet可以访问初始化参数或更容易地处理相对URL。在更改缺省URL时，使用servlet-mapping元素。<br />l session -config 如果某个会话在一定时间内未被访问，服务器可以抛弃它以节省内存。可通过使用HttpSession的 setMaxInactiveInterval方法明确设置单个会话对象的超时值，或者可利用session-config元素制定缺省超时值。<br />l mime-mapping 如果Web应用具有想到特殊的文件，希望能保证给他们分配特定的MIME类型，则mime-mapping元素提供这种保证。<br />l welcom-file-list welcome-file-list元素指示服务器在收到引用一个目录名而不是文件名的URL时，使用哪个文件。<br />l error-page error-page元素使得在返回特定HTTP状态代码时，或者特定类型的异常被抛出时，能够制定将要显示的页面。<br />l taglib taglib元素对标记库描述符文件（Tag Libraryu Descriptor file）指定别名。此功能使你能够更改TLD文件的位置，而不用编辑使用这些文件的JSP页面。<br />l resource-env-ref resource-env-ref元素声明与资源相关的一个管理对象。<br />l resource-ref resource-ref元素声明一个资源工厂使用的外部资源。<br />l security-constraint security-constraint元素制定应该保护的URL。它与login-config元素联合使用<br />l login-config 用login-config元素来指定服务器应该怎样给试图访问受保护页面的用户授权。它与sercurity-constraint元素联合使用。<br />l security-role security-role元素给出安全角色的一个列表，这些角色将出现在servlet元素内的security-role-ref元素的role-name子元素中。分别地声明角色可使高级IDE处理安全信息更为容易。<br />l env-entry env-entry元素声明Web应用的环境项。<br />l ejb-ref ejb-ref元素声明一个EJB的主目录的引用。<br />l ejb-local-ref ejb-local-ref元素声明一个EJB的本地主目录的应用。<br /><br />3 分配名称和定制的UL<br /><br />在web.xml中完成的一个最常见的任务是对servlet或JSP页面给出名称和定制的URL。用servlet元素分配名称，使用servlet-mapping元素将定制的URL与刚分配的名称相关联。<br />3.1 分配名称<br />为了提供初始化参数，对servlet或JSP页面定义一个定制URL或分配一个安全角色，必须首先给servlet或JSP页面一个名称。可通过 servlet元素分配一个名称。最常见的格式包括servlet-name和servlet-class子元素（在web-app元素内），如下所示：<br />&lt;servlet&gt;<br />&lt;servlet-name&gt;Test&lt;/servlet-name&gt;<br />&lt;servlet-class&gt;moreservlets.TestServlet&lt;/servlet-class&gt;<br />&lt;/servlet&gt; <br />这表示位于WEB-INF/classes/moreservlets/TestServlet的servlet已经得到了注册名Test。给 servlet一个名称具有两个主要的含义。首先，初始化参数、定制的URL模式以及其他定制通过此注册名而不是类名引用此servlet。其次,可在 URL而不是类名中使用此名称。因此，利用刚才给出的定义，URL <a href="http://host/webAppPrefix/servlet/Test" target="_blank">http://host/webAppPrefix/servlet/Test</a> 可用于 <a href="http://host/webAppPrefix/servlet/moreservlets.TestServlet" target="_blank">http://host/webAppPrefix/servlet/moreservlets.TestServlet</a> 的场所。<br />请记住：XML元素不仅是大小写敏感的，而且定义它们的次序也很重要。例如，web-app元素内所有servlet元素必须位于所有servlet- mapping元素（下一小节介绍）之前，而且还要位于5.6节和5.11节讨论的与过滤器或文档相关的元素（如果有的话）之前。类似地， servlet 的servlet-name子元素也必须出现在servlet-class之前。5.2节"部署描述符文件内的元素次序"将详细介绍这种必需的次序。<br />例如，程序清单5-1给出了一个名为TestServlet的简单servlet，它驻留在moreservlets程序包中。因为此servlet是扎根在一个名为deployDemo的目录中的Web应用的组成部分，所以TestServlet.class放在 deployDemo/WEB- INF/classes/moreservlets中。程序清单5-2给出将放置在deployDemo/WEB- INF/内的web.xml文件的一部分。此web.xml文件使用servlet-name和servlet-class元素将名称Test与 TestServlet.class相关联。图 5-1和图5-2分别显示利用缺省URL和注册名调用TestServlet时的结果。<br /><br />程序清单5-1 TestServlet.java<br />package moreservlets;<br /><br />import java.io.*;<br />import javax.servlet.*;<br />import javax.servlet.http.*;<br /><br />/** Simple servlet used to illustrate servlet naming<br />* and custom URLs.<br />* 
<p><br />* Taken from More Servlets and JavaServer Pages<br />* from Prentice Hall and Sun Microsystems Press,<br />* <a href="http://www.moreservlets.com/." target="_blank">http://www.moreservlets.com/.</a><br />* © 2002 Marty Hall; may be freely used or adapted.<br />*/<br /><br />public class TestServlet extends HttpServlet {<br />public void doGet(HttpServletRequest request,<br />HttpServletResponse response)<br />throws ServletException, IOException {<br />response.setContentType("text/html");<br />PrintWriter out = response.getWriter();<br />String uri = request.getRequestURI();<br />out.println(ServletUtilities.headWithTitle("Test Servlet") +<br />"&lt;BODY BGCOLOR=\"#FDF5E6\"&gt;\n" +<br />" 
</p><h2>URI: " + uri + "</h2>\n" +<br />"&lt;/BODY&gt;&lt;/HTML&gt;");<br />}<br />}<br /><br /><br />程序清单5-2 web.xml（说明servlet名称的摘录）<br />&lt;?xml version="1.0" encoding="ISO-8859-1"?&gt;<br />&lt;!DOCTYPE web-app<br />PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"<br />"http://java.sun.com/dtd/web-app_2_3.dtd"&gt;<br /><br />&lt;web-app&gt;<br />&lt;!-- … --&gt;<br />&lt;servlet&gt;<br />&lt;servlet-name&gt;Test&lt;/servlet-name&gt;<br />&lt;servlet-class&gt;moreservlets.TestServlet&lt;/servlet-class&gt;<br />&lt;/servlet&gt;<br />&lt;!-- … --&gt;<br />&lt;/web-app&gt;<br /><br />3.2 定义定制的URL<br />大多数服务器具有一个缺省的serlvet URL：<br />http: //host/webAppPrefix/servlet/packageName.ServletName。虽然在开发中使用这个URL很方便，但是我们常常会希望另一个URL用于部署。例如，可能会需要一个出现在Web应用顶层的URL（如，http: //host/webAppPrefix/Anyname），并且在此URL中没有servlet项。位于顶层的URL简化了相对URL的使用。此外，对许多开发人员来说，顶层URL看上去比更长更麻烦的缺省URL更简短。<br />事实上，有时需要使用定制的URL。比如，你可能想关闭缺省URL映射，以便更好地强制实施安全限制或防止用户意外地访问无初始化参数的servlet。如果你禁止了缺省的URL，那么你怎样访问servlet呢？这时只有使用定制的URL了。<br />为了分配一个定制的URL，可使用servlet-mapping元素及其servlet-name和url-pattern子元素。Servlet- name元素提供了一个任意名称，可利用此名称引用相应的servlet；url-pattern描述了相对于Web应用的根目录的URL。url- pattern元素的值必须以斜杠（/）起始。<br />下面给出一个简单的web.xml摘录，它允许使用URL <a href="http://host/webAppPrefix/UrlTest而不是http://host/webAppPrefix/servlet/Test或" target="_blank">http://host/webAppPrefix/UrlTest而不是http://host/webAppPrefix/servlet/Test或</a><br />http: //host/webAppPrefix/servlet/moreservlets.TestServlet。请注意，仍然需要XML头、 DOCTYPE声明以及web-app封闭元素。此外，可回忆一下，XML元素出现地次序不是随意的。特别是，需要把所有servlet元素放在所有 servlet-mapping元素之前。<br />&lt;servlet&gt;<br />&lt;servlet-name&gt;Test&lt;/servlet-name&gt;<br />&lt;servlet-class&gt;moreservlets.TestServlet&lt;/servlet-class&gt;<br />&lt;/servlet&gt;<br />&lt;!-- ... --&gt;<br />&lt;servlet-mapping&gt;<br />&lt;servlet-name&gt;Test&lt;/servlet-name&gt;<br />&lt;url-pattern&gt;/UrlTest&lt;/url-pattern&gt;<br />&lt;/servlet-mapping&gt;<br />URL模式还可以包含通配符。例如，下面的小程序指示服务器发送所有以Web应用的URL前缀开始，以..asp结束的请求到名为BashMS的servlet。<br />&lt;servlet&gt;<br />&lt;servlet-name&gt;BashMS&lt;/servlet-name&gt;<br />&lt;servlet-class&gt;msUtils.ASPTranslator&lt;/servlet-class&gt;<br />&lt;/servlet&gt;<br />&lt;!-- ... --&gt;<br />&lt;servlet-mapping&gt;<br />&lt;servlet-name&gt;BashMS&lt;/servlet-name&gt;<br />&lt;url-pattern&gt;/*.asp&lt;/url-pattern&gt;<br />&lt;/servlet-mapping&gt;<br />3.3 命名JSP页面<br />因为JSP页面要转换成sevlet，自然希望就像命名servlet一样命名JSP页面。毕竟，JSP页面可能会从初始化参数、安全设置或定制的URL中受益，正如普通的serlvet那样。虽然JSP页面的后台实际上是servlet这句话是正确的，但存在一个关键的猜疑：即，你不知道JSP页面的实际类名（因为系统自己挑选这个名字）。因此，为了命名JSP页面，可将jsp-file元素替换为servlet-calss元素，如下所示：<br />&lt;servlet&gt;<br />&lt;servlet-name&gt;Test&lt;/servlet-name&gt;<br />&lt;jsp-file&gt;/TestPage.jsp&lt;/jsp-file&gt;<br />&lt;/servlet&gt;<br />命名JSP页面的原因与命名servlet的原因完全相同：即为了提供一个与定制设置（如，初始化参数和安全设置）一起使用的名称，并且，以便能更改激活 JSP页面的URL（比方说，以便多个URL通过相同页面得以处理，或者从URL中去掉.jsp扩展名）。但是，在设置初始化参数时，应该注意，JSP 页面是利用jspInit方法，而不是init方法读取初始化参数的。<br />例如，程序清单5-3给出一个名为TestPage.jsp的简单JSP页面，它的工作只是打印出用来激活它的URL的本地部分。TestPage.jsp放置在deployDemo应用的顶层。程序清单5-4给出了用来分配一个注册名PageName，然后将此注册名与http://host/webAppPrefix/UrlTest2/anything 形式的URL相关联的web.xml文件（即，deployDemo/WEB-INF/web.xml）的一部分。<br /><br />程序清单5-3 TestPage.jsp<br />&lt;!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"&gt;<br />&lt;HTML&gt;<br />&lt;HEAD&gt;<br /><br />&lt;/HEAD&gt;<br />&lt;BODY BGCOLOR="#FDF5E6"&gt;<br /><h2>URI: &lt;%= request.getRequestURI() %&gt;</h2><br />&lt;/BODY&gt;<br />&lt;/HTML&gt;<br /><br /><br />程序清单5-4 web.xml（说明JSP页命名的摘录）<br />&lt;?xml version="1.0" encoding="ISO-8859-1"?&gt;<br />&lt;!DOCTYPE web-app<br />PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"<br />"http://java.sun.com/dtd/web-app_2_3.dtd"&gt;<br /><br />&lt;web-app&gt;<br />&lt;!-- ... --&gt;<br />&lt;servlet&gt;<br />&lt;servlet-name&gt;PageName&lt;/servlet-name&gt;<br />&lt;jsp-file&gt;/TestPage.jsp&lt;/jsp-file&gt;<br />&lt;/servlet&gt;<br />&lt;!-- ... --&gt;<br />&lt;servlet-mapping&gt;<br />&lt;servlet-name&gt; PageName &lt;/servlet-name&gt;<br />&lt;url-pattern&gt;/UrlTest2/*&lt;/url-pattern&gt;<br />&lt;/servlet-mapping&gt;<br />&lt;!-- ... --&gt;<br />&lt;/web-app&gt;<br /><br /><br />4 禁止激活器servlet<br /><br />对servlet 或JSP页面建立定制URL的一个原因是，这样做可以注册从 init（servlet）或jspInit（JSP页面）方法中读取得初始化参数。但是，初始化参数只在是利用定制URL模式或注册名访问 servlet或JSP页面时可以使用，用缺省URL http: //host/webAppPrefix/servlet/ServletName 访问时不能使用。因此，你可能会希望关闭缺省URL，这样就不会有人意外地调用初始化servlet了。这个过程有时称为禁止激活器servlet，因为多数服务器具有一个用缺省的servlet URL注册的标准 servlet，并激活缺省的URL应用的实际servlet。<br />有两种禁止此缺省URL的主要方法：<br />l 在每个Web应用中重新映射/servlet/模式。<br />l 全局关闭激活器servlet。<br />重要的是应该注意到，虽然重新映射每个Web应用中的/servlet/模式比彻底禁止激活servlet所做的工作更多，但重新映射可以用一种完全可移植的方式来完成。相反，全局禁止激活器servlet完全是针对具体机器的，事实上有的服务器（如ServletExec）没有这样的选择。下面的讨论对每个Web应用重新映射/servlet/ URL模式的策略。后面提供在Tomcat中全局禁止激活器servlet的详细内容。<br />4.1 重新映射/servlet/URL模式<br />在一个特定的Web应用中禁止以http://host/webAppPrefix/servlet/ 开始的URL的处理非常简单。所需做的事情就是建立一个错误消息servlet，并使用前一节讨论的url-pattern元素将所有匹配请求转向该 servlet。只要简单地使用：<br />&lt;url-pattern&gt;/servlet/*&lt;/url-pattern&gt;<br />作为servlet-mapping元素中的模式即可。<br />例如，程序清单5-5给出了将SorryServlet servlet（程序清单5-6）与所有以http://host/webAppPrefix/servlet/ 开头的URL相关联的部署描述符文件的一部分。<br /><br />程序清单5-5 web.xml（说明JSP页命名的摘录）<br />&lt;?xml version="1.0" encoding="ISO-8859-1"?&gt;<br />&lt;!DOCTYPE web-app<br />PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"<br />"http://java.sun.com/dtd/web-app_2_3.dtd"&gt;<br /><br />&lt;web-app&gt;<br />&lt;!-- ... --&gt;<br />&lt;servlet&gt;<br />&lt;servlet-name&gt;Sorry&lt;/servlet-name&gt;<br />&lt;servlet-class&gt;moreservlets.SorryServlet&lt;/servlet-class&gt;<br />&lt;/servlet&gt;<br />&lt;!-- ... --&gt;<br />&lt;servlet-mapping&gt;<br />&lt;servlet-name&gt; Sorry &lt;/servlet-name&gt;<br />&lt;url-pattern&gt;/servlet/*&lt;/url-pattern&gt;<br />&lt;/servlet-mapping&gt;<br />&lt;!-- ... --&gt;<br />&lt;/web-app&gt;<br /><br /><br />程序清单5-6 SorryServlet.java<br />package moreservlets;<br /><br />import java.io.*;<br />import javax.servlet.*;<br />import javax.servlet.http.*;<br /><br />/** Simple servlet used to give error messages to<br />* users who try to access default servlet URLs<br />* (i.e., <a href="http://host/webAppPrefix/servlet/ServletName)" target="_blank">http://host/webAppPrefix/servlet/ServletName)</a><br />* in Web applications that have disabled this<br />* behavior.<br />* 
<p><br />* Taken from More Servlets and JavaServer Pages<br />* from Prentice Hall and Sun Microsystems Press,<br />* <a href="http://www.moreservlets.com/." target="_blank">http://www.moreservlets.com/.</a><br />* © 2002 Marty Hall; may be freely used or adapted.<br />*/<br /><br />public class SorryServlet extends HttpServlet {<br />public void doGet(HttpServletRequest request,<br />HttpServletResponse response)<br />throws ServletException, IOException {<br />response.setContentType("text/html");<br />PrintWriter out = response.getWriter();<br />String title = "Invoker Servlet Disabled.";<br />out.println(ServletUtilities.headWithTitle(title) +<br />"&lt;BODY BGCOLOR=\"#FDF5E6\"&gt;\n" +<br />" 
</p><h2>" + title + "</h2>\n" +<br />"Sorry, access to servlets by means of\n" +<br />"URLs that begin with\n" +<br />"http://host/webAppPrefix/servlet/\n" +<br />"has been disabled.\n" + <br />"&lt;/BODY&gt;&lt;/HTML&gt;");<br />}<br /><br />public void doPost(HttpServletRequest request,<br />HttpServletResponse response)<br />throws ServletException, IOException {<br />doGet(request, response);<br />}<br />}<br /><br /><br />4.2 全局禁止激活器：Tomcat<br />Tomcat 4中用来关闭缺省URL的方法与Tomcat 3中所用的很不相同。下面介绍这两种方法：<br />1．禁止激活器： Tomcat 4<br />Tomcat 4 用与前面相同的方法关闭激活器servlet，即利用web.xml中的url-mapping元素进行关闭。不同之处在于Tomcat使用了放在 install_dir/conf中的一个服务器专用的全局web.xml文件，而前面使用的是存放在每个Web应用的WEB-INF目录中的标准 web.xml文件。<br />因此，为了在Tomcat 4中关闭激活器servlet，只需在install_dir/conf/web.xml中简单地注释出/servlet/* URL映射项即可，如下所示：<br />&lt;!-- <br />&lt;servlet-mapping&gt;<br />&lt;servlet-name&gt;invoker&lt;/servlet-name&gt;<br />&lt;url-pattern&gt;/servlet/*&lt;/url-pattern&gt;<br />&lt;/servlet-mapping&gt;<br />--&gt;<br />再次提醒，应该注意这个项是位于存放在install_dir/conf的Tomcat专用的web.xml文件中的，此文件不是存放在每个Web应用的WEB-INF目录中的标准web.xml。<br />2．禁止激活器：Tomcat3<br />在Apache Tomcat 的版本3中，通过在install_dir/conf/server.xml中注释出InvokerInterceptor项全局禁止缺省 servlet URL。例如，下面是禁止使用缺省servlet URL的server.xml文件的一部分。<br />&lt;!-- <br />&lt;RequsetInterceptor <br />className="org.apache.tomcat.request.InvokerInterceptor"<br />debug="0" prefix="/servlet/" /&gt;<br />--&gt;<br /><br />5 初始化和预装载servlet与JSP页面<br /><br />这里讨论控制servlet和JSP页面的启动行为的方法。特别是，说明了怎样分配初始化参数以及怎样更改服务器生存期中装载servlet和JSP页面的时刻。<br />5.1 分配servlet初始化参数<br />利用init-param元素向servlet提供初始化参数，init-param元素具有param-name和param-value子元素。例如，在下面的例子中，如果initServlet servlet是利用它的注册名（InitTest）访问的，它将能够从其方法中调用 getServletConfig(). getInitParameter("param1")获得"Value 1"，调用 getServletConfig().getInitParameter("param2")获得"2"。<br />&lt;servlet&gt;<br />&lt;servlet-name&gt;InitTest&lt;/servlet-name&gt;<br />&lt;servlet-class&gt;moreservlets.InitServlet&lt;/servlet-class&gt;<br />&lt;init-param&gt;<br />&lt;param-name&gt;param1&lt;/param-name&gt;<br />&lt;param-value&gt;value1&lt;/param-value&gt;<br />&lt;/init-param&gt;<br />&lt;init-param&gt;<br />&lt;param-name&gt;param2&lt;/param-name&gt;<br />&lt;param-value&gt;2&lt;/param-value&gt;<br />&lt;/init-param&gt;<br />&lt;/servlet&gt;<br />在涉及初始化参数时，有几点需要注意：<br />l 返回值。GetInitParameter的返回值总是一个String。因此，在前一个例子中，可对param2使用Integer.parseInt获得一个int。<br />l JSP中的初始化。JSP页面使用jspInit而不是init。JSP页面还需要使用jsp-file元素代替servlet-class。<br />l 缺省URL。初始化参数只在通过它们的注册名或与它们注册名相关的定制URL模式访问Servlet时可以使用。因此，在这个例子中，param1和 param2初始化参数将能够在使用URL <a href="http://host/webAppPrefix/servlet/InitTest" target="_blank">http://host/webAppPrefix/servlet/InitTest</a> 时可用，但在使用 URL <a href="http://host/webAppPrefix/servlet/myPackage.InitServlet" target="_blank">http://host/webAppPrefix/servlet/myPackage.InitServlet</a> 时不能使用。<br />例如，程序清单5-7给出一个名为InitServlet的简单servlet，它使用init方法设置firstName和emailAddress字段。程序清单5-8给出分配名称InitTest给servlet的web.xml文件。<br />程序清单5-7 InitServlet.java<br />package moreservlets;<br /><br />import java.io.*;<br />import javax.servlet.*;<br />import javax.servlet.http.*;<br /><br />/** Simple servlet used to illustrate servlet<br />* initialization parameters.<br />* 
<p><br />* Taken from More Servlets and JavaServer Pages<br />* from Prentice Hall and Sun Microsystems Press,<br />* <a href="http://www.moreservlets.com/." target="_blank">http://www.moreservlets.com/.</a><br />* © 2002 Marty Hall; may be freely used or adapted.<br />*/<br /><br />public class InitServlet extends HttpServlet {<br />private String firstName, emailAddress;<br /><br />public void init() {<br />ServletConfig config = getServletConfig();<br />firstName = config.getInitParameter("firstName");<br />emailAddress = config.getInitParameter("emailAddress");<br />}<br /><br />public void doGet(HttpServletRequest request,<br />HttpServletResponse response)<br />throws ServletException, IOException {<br />response.setContentType("text/html");<br />PrintWriter out = response.getWriter();<br />String uri = request.getRequestURI();<br />out.println(ServletUtilities.headWithTitle("Init Servlet") +<br />"&lt;BODY BGCOLOR=\"#FDF5E6\"&gt;\n" +<br />" 
</p><h2>Init Parameters:</h2>\n" +<br />" 
<ul>\n" +<br />" 
<li>First name: " + firstName + "\n" +<br />" 
</li><li>Email address: " + emailAddress + "\n" +<br />"</li></ul>\n" + <br />"&lt;/BODY&gt;&lt;/HTML&gt;");<br />}<br />}<br /><br /><br />程序清单5-8 web.xml（说明初始化参数的摘录）<br />&lt;?xml version="1.0" encoding="ISO-8859-1"?&gt;<br />&lt;!DOCTYPE web-app<br />PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"<br />"http://java.sun.com/dtd/web-app_2_3.dtd"&gt;<br /><br />&lt;web-app&gt;<br />&lt;!-- ... --&gt;<br />&lt;servlet&gt;<br />&lt;servlet-name&gt;InitTest&lt;/servlet-name&gt;<br />&lt;servlet-class&gt;moreservlets.InitServlet&lt;/servlet-class&gt;<br />&lt;init-param&gt;<br />&lt;param-name&gt;firstName&lt;/param-name&gt;<br />&lt;param-value&gt;Larry&lt;/param-value&gt;<br />&lt;/init-param&gt;<br />&lt;init-param&gt;<br />&lt;param-name&gt;emailAddress&lt;/param-name&gt;<br />&lt;param-value&gt;Ellison@Microsoft.com&lt;/param-value&gt;<br />&lt;/init-param&gt;<br />&lt;/servlet&gt;<br />&lt;!-- ... --&gt;<br />&lt;/web-app&gt;<br /><br />5.2 分配JSP初始化参数<br />给JSP页面提供初始化参数在三个方面不同于给servlet提供初始化参数。<br />1）使用jsp-file而不是servlet-class。因此，WEB-INF/web.xml文件的servlet元素如下所示：<br />&lt;servlet&gt;<br />&lt;servlet-name&gt;PageName&lt;/servlet-name&gt;<br />&lt;jsp-file&gt;/RealPage.jsp&lt;/jsp-file&gt;<br />&lt;init-param&gt;<br />&lt;param-name&gt;...&lt;/param-name&gt;<br />&lt;param-value&gt;...&lt;/param-value&gt;<br />&lt;/init-param&gt;<br />...<br />&lt;/servlet&gt;<br />2) 几乎总是分配一个明确的URL模式。对servlet，一般相应地使用以http://host/webAppPrefix/servlet/ 开始的缺省URL。只需记住，使用注册名而不是原名称即可。这对于JSP页面在技术上也是合法的。例如，在上面给出的例子中，可用URL http: //host/webAppPrefix/servlet/PageName 访问RealPage.jsp的对初始化参数具有访问权的版本。但在用于 JSP页面时，许多用户似乎不喜欢应用常规的servlet的URL。此外，如果 JSP页面位于服务器为其提供了目录清单的目录中（如，一个既没有 index.html也没有index.jsp文件的目录），则用户可能会连接到此 JSP页面，单击它，从而意外地激活未初始化的页面。因此，好的办法是使用url-pattern（5.3节）将JSP页面的原URL与注册的 servlet名相关联。这样，客户机可使用JSP页面的普通名称，但仍然激活定制的版本。例如，给定来自项目1的servlet定义，可使用下面的 servlet-mapping定义：<br />&lt;servlet-mapping&gt;<br />&lt;servlet-name&gt;PageName&lt;/servlet-name&gt;<br />&lt;url-pattern&gt;/RealPage.jsp&lt;/url-pattern&gt;<br />&lt;/servlet-mapping&gt;<br />3）JSP页使用jspInit而不是init。自动从JSP页面建立的servlet或许已经使用了inti方法。因此，使用JSP声明提供一个init方法是不合法的，必须制定jspInit方法。<br />为了说明初始化JSP页面的过程，程序清单5-9给出了一个名为InitPage.jsp的JSP页面，它包含一个jspInit方法且放置于 deployDemo Web应用层次结构的顶层。一般，http://host/deployDemo/InitPage.jsp 形式的URL将激活此页面的不具有初始化参数访问权的版本，从而将对firstName和emailAddress变量显示null。但是， web.xml文件（程序清单5-10）分配了一个注册名，然后将该注册名与URL模式/InitPage.jsp相关联。<br /><br />程序清单5-9 InitPage.jsp<br />&lt;!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"&gt;<br />&lt;HTML&gt;<br />&lt;HEAD&gt;&lt;/HEAD&gt;<br />&lt;BODY BGCOLOR="#FDF5E6"&gt;<br /><h2>Init Parameters:</h2><br /><ul><br /><li>First name: &lt;%= firstName %&gt;<br /></li><li>Email address: &lt;%= emailAddress %&gt;<br /></li></ul><br />&lt;/BODY&gt;&lt;/HTML&gt;<br />&lt;%!<br />private String firstName, emailAddress;<br /><br />public void jspInit() {<br />ServletConfig config = getServletConfig();<br />firstName = config.getInitParameter("firstName");<br />emailAddress = config.getInitParameter("emailAddress");<br />}<br />%&gt;<br /><br /><br />程序清单5-10 web.xml（说明JSP页面的init参数的摘录）<br />&lt;?xml version="1.0" encoding="ISO-8859-1"?&gt;<br />&lt;!DOCTYPE web-app<br />PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"<br />"http://java.sun.com/dtd/web-app_2_3.dtd"&gt;<br /><br />&lt;web-app&gt;<br />&lt;!-- ... --&gt;<br />&lt;servlet&gt;<br />&lt;servlet-name&gt;InitPage&lt;/servlet-name&gt;<br />&lt;jsp-file&gt;/InitPage.jsp&lt;/jsp-file&gt;<br />&lt;init-param&gt;<br />&lt;param-name&gt;firstName&lt;/param-name&gt;<br />&lt;param-value&gt;Bill&lt;/param-value&gt;<br />&lt;/init-param&gt;<br />&lt;init-param&gt;<br />&lt;param-name&gt;emailAddress&lt;/param-name&gt;<br />&lt;param-value&gt;gates@oracle.com&lt;/param-value&gt;<br />&lt;/init-param&gt;<br />&lt;/servlet&gt;<br />&lt;!-- ... --&gt; <br />&lt;servlet-mapping&gt;<br />&lt;servlet-name&gt; InitPage&lt;/servlet-name&gt;<br />&lt;url-pattern&gt;/InitPage.jsp&lt;/url-pattern&gt;<br />&lt;/servlet-mapping&gt;<br />&lt;!-- ... --&gt;<br />&lt;/web-app&gt;<br /><br /><br />5.3 提供应用范围内的初始化参数<br />一般，对单个地servlet或JSP页面分配初始化参数。指定的servlet或JSP页面利用ServletConfig的 getInitParameter方法读取这些参数。但是，在某些情形下，希望提供可由任意servlet或JSP页面借助ServletContext 的getInitParameter方法读取的系统范围内的初始化参数。<br />可利用context-param元素声明这些系统范围内的初始化值。context-param元素应该包含param-name、param-value以及可选的description子元素，如下所示：<br />&lt;context-param&gt;<br />&lt;param-name&gt;support-email&lt;/param-name&gt;<br />&lt;param-value&gt;blackhole@mycompany.com&lt;/param-value&gt;<br />&lt;/context-param&gt;<br />可回忆一下，为了保证可移植性，web.xml内的元素必须以正确的次序声明。但这里应该注意，context-param元素必须出现任意与文档有关的元素（icon、display-name或description）之后及filter、filter-mapping、listener或 servlet元素之前。<br />5.4 在服务器启动时装载servlet<br />假如servlet或JSP页面有一个要花很长时间执行的 init （servlet）或jspInit（JSP）方法。例如，假如init或jspInit方法从某个数据库或ResourceBundle查找产量。这种情况下，在第一个客户机请求时装载servlet的缺省行为将对第一个客户机产生较长时间的延迟。因此，可利用servlet的load-on - startup元素规定服务器在第一次启动时装载servlet。下面是一个例子。<br />&lt;servlet&gt;<br />&lt;servlet-name&gt; … &lt;/servlet-name&gt;<br />&lt;servlet-class&gt; … &lt;/servlet-class&gt; &lt;!-- Or jsp-file --&gt;<br />&lt;load-on-startup/&gt;<br />&lt;/servlet&gt;<br />可以为此元素体提供一个整数而不是使用一个空的load-on-startup。想法是服务器应该在装载较大数目的servlet或JSP页面之前装载较少数目的servlet或JSP页面。例如，下面的servlet项（放置在Web应用的WEB-INF目录下的web.xml文件中的web-app元素内）将指示服务器首先装载和初始化SearchServlet，然后装载和初始化由位于Web应用的result目录中的index.jsp文件产生的 servlet。<br />&lt;servlet&gt;<br />&lt;servlet-name&gt;Search&lt;/servlet-name&gt;<br />&lt;servlet-class&gt;myPackage.SearchServlet&lt;/servlet-class&gt; &lt;!-- Or jsp-file --&gt;<br />&lt;load-on-startup&gt;1&lt;/load-on-startup&gt;<br />&lt;/servlet&gt;<br />&lt;servlet&gt;<br />&lt;servlet-name&gt;Results&lt;/servlet-name&gt;<br />&lt;servlet-class&gt;/results/index.jsp&lt;/servlet-class&gt; &lt;!-- Or jsp-file --&gt;<br />&lt;load-on-startup&gt;2&lt;/load-on-startup&gt;<br />&lt;/servlet&gt;<br /><br />6 声明过滤器<br /><br />servlet版本2.3引入了过滤器的概念。虽然所有支持servlet API版本2.3的服务器都支持过滤器，但为了使用与过滤器有关的元素，必须在web.xml中使用版本2.3的DTD。<br />过滤器可截取和修改进入一个servlet或JSP页面的请求或从一个servlet或JSP页面发出的相应。在执行一个servlet或JSP页面之前，必须执行第一个相关的过滤器的doFilter方法。在该过滤器对其FilterChain对象调用doFilter时，执行链中的下一个过滤器。如果没有其他过滤器，servlet或JSP页面被执行。过滤器具有对到来的ServletRequest对象的全部访问权，因此，它们可以查看客户机名、查找到来的cookie等。为了访问servlet或JSP页面的输出，过滤器可将响应对象包裹在一个替身对象（stand-in object）中，比方说把输出累加到一个缓冲区。在调用FilterChain对象的doFilter方法之后，过滤器可检查缓冲区，如有必要，就对它进行修改，然后传送到客户机。<br />例如，程序清单5-11帝国难以了一个简单的过滤器，只要访问相关的servlet或JSP页面，它就截取请求并在标准输出上打印一个报告（开发过程中在桌面系统上运行时，大多数服务器都可以使用这个过滤器）。<br /><br />程序清单5-11 ReportFilter.java<br />package moreservlets;<br /><br />import java.io.*;<br />import javax.servlet.*;<br />import javax.servlet.http.*;<br />import java.util.*;<br /><br />/** Simple filter that prints a report on the standard output <br />* whenever the associated servlet or JSP page is accessed.<br />* 
<p><br />* Taken from More Servlets and JavaServer Pages<br />* from Prentice Hall and Sun Microsystems Press,<br />* <a href="http://www.moreservlets.com/." target="_blank">http://www.moreservlets.com/.</a><br />* © 2002 Marty Hall; may be freely used or adapted.<br />*/<br /><br />public class ReportFilter implements Filter {<br />public void doFilter(ServletRequest request,<br />ServletResponse response,<br />FilterChain chain)<br />throws ServletException, IOException {<br />HttpServletRequest req = (HttpServletRequest)request;<br />System.out.println(req.getRemoteHost() +<br />" tried to access " +<br />req.getRequestURL() +<br />" on " + new Date() + ".");<br />chain.doFilter(request,response);<br />}<br /><br />public void init(FilterConfig config)<br />throws ServletException {<br />}<br /><br />public void destroy() {}<br />}<br /><br />一旦建立了一个过滤器，可以在web.xml中利用filter元素以及filter-name（任意名称）、file-class（完全限定的类名）和（可选的）init-params子元素声明它。请注意，元素在web.xml的web-app元素中出现的次序不是任意的；允许服务器（但不是必需的）强制所需的次序，并且实际中有些服务器也是这样做的。但这里要注意，所有filter元素必须出现在任意filter-mapping元素之前， filter-mapping元素又必须出现在所有servlet或servlet-mapping元素之前。<br />例如，给定上述的ReportFilter类，可在web.xml中作出下面的filter声明。它把名称Reporter与实际的类ReportFilter（位于moreservlets程序包中）相关联。<br />&lt;filter&gt;<br />&lt;filter-name&gt;Reporter&lt;/filter-name&gt;<br />&lt;filter-class&gt;moresevlets.ReportFilter&lt;/filter-class&gt;<br />&lt;/filter&gt;<br />一旦命名了一个过滤器，可利用filter-mapping元素把它与一个或多个servlet或JSP页面相关联。关于此项工作有两种选择。<br />首先，可使用filter-name和servlet-name子元素把此过滤器与一个特定的servlet名（此servlet名必须稍后在相同的 web.xml文件中使用servlet元素声明）关联。例如，下面的程序片断指示系统只要利用一个定制的URL访问名为 SomeServletName 的servlet或JSP页面，就运行名为Reporter的过滤器。<br />&lt;filter-mapping&gt;<br />&lt;filter-name&gt;Reporter&lt;/filter-name&gt;<br />&lt;servlet-name&gt;SomeServletName&lt;/servlet-name&gt;<br />&lt;/filter-mapping&gt;<br />其次，可利用filter-name和url-pattern子元素将过滤器与一组servlet、JSP页面或静态内容相关联。例如，相面的程序片段指示系统只要访问Web应用中的任意URL，就运行名为Reporter的过滤器。<br />&lt;filter-mapping&gt;<br />&lt;filter-name&gt;Reporter&lt;/filter-name&gt;<br />&lt;url-pattern&gt;/*&lt;/url-pattern&gt;<br />&lt;/filter-mapping&gt;<br />例如，程序清单5-12给出了将ReportFilter过滤器与名为PageName的servlet相关联的web.xml文件的一部分。名字 PageName依次又与一个名为TestPage.jsp的JSP页面以及以模式http: //host/webAppPrefix/UrlTest2/ 开头的URL相关联。TestPage.jsp的源代码已经JSP页面命名的谈论在前面的3节"分配名称和定制的URL"中给出。事实上，程序清单5- 12中的servlet和servlet-name项从该节原封不动地拿过来的。给定这些web.xml项，可看到下面的标准输出形式的调试报告（换行是为了容易阅读）。<br />audit.irs.gov tried to access <br /><a href="http://mycompany.com/deployDemo/UrlTest2/business/tax-plan.html" target="_blank">http://mycompany.com/deployDemo/UrlTest2/business/tax-plan.html</a><br />on Tue Dec 25 13:12:29 EDT 2001.<br /><br />程序清单5-12 Web.xml（说明filter用法的摘录）<br />&lt;?xml version="1.0" encoding="ISO-8859-1"?&gt;<br />&lt;!DOCTYPE web-app<br />PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"<br />"http://java.sun.com/dtd/web-app_2_3.dtd"&gt;<br /><br />&lt;web-app&gt;<br />&lt;filter&gt;<br />&lt;filter-name&gt;Reporter&lt;/filter-name&gt;<br />&lt;filter-class&gt;moresevlets.ReportFilter&lt;/filter-class&gt;<br />&lt;/filter&gt;<br />&lt;!-- ... --&gt;<br />&lt;filter-mapping&gt;<br />&lt;filter-name&gt;Reporter&lt;/filter-name&gt;<br />&lt;servlet-name&gt;PageName&lt;/servlet-name&gt;<br />&lt;/filter-mapping&gt;<br />&lt;!-- ... --&gt;<br />&lt;servlet&gt;<br />&lt;servlet-name&gt;PageName&lt;/servlet-name&gt;<br />&lt;jsp-file&gt;/RealPage.jsp&lt;/jsp-file&gt;<br />&lt;/servlet&gt;<br />&lt;!-- ... --&gt;<br />&lt;servlet-mapping&gt;<br />&lt;servlet-name&gt; PageName &lt;/servlet-name&gt;<br />&lt;url-pattern&gt;/UrlTest2/*&lt;/url-pattern&gt;<br />&lt;/servlet-mapping&gt;<br />&lt;!-- ... --&gt;<br />&lt;/web-app&gt;<br /><br /><br />7 指定欢迎页<br /><br />假如用户提供了一个像http: //host/webAppPrefix/directoryName/ 这样的包含一个目录名但没有包含文件名的 URL，会发生什么事情呢？用户能得到一个目录表？一个错误？还是标准文件的内容？如果得到标准文件内容，是 index.html、 index.jsp、default.html、default.htm或别的什么东西呢？<br />Welcome-file-list 元素及其辅助的 welcome-file元素解决了这个模糊的问题。例如，下面的web.xml项指出，如果一个URL给出一个目录名但未给出文件名，服务器应该首先试用index.jsp，然后再试用index.html。如果两者都没有找到，则结果有赖于所用的服务器（如一个目录列表）。<br />&lt;welcome-file-list&gt;<br />&lt;welcome-file&gt;index.jsp&lt;/welcome-file&gt;<br />&lt;welcome-file&gt;index.html&lt;/welcome-file&gt;<br />&lt;/welcome-file-list&gt;<br />虽然许多服务器缺省遵循这种行为，但不一定必须这样。因此，明确地使用welcom-file-list保证可移植性是一种良好的习惯。<br /><br />8 指定处理错误的页面<br /><br />现在我了解到，你在开发servlet和JSP页面时从不会犯错误，而且你的所有页面是那样的清晰，一般的程序员都不会被它们的搞糊涂。但是，是人总会犯错误的，用户可能会提供不合规定的参数，使用不正确的URL或者不能提供必需的表单字段值。除此之外，其它开发人员可能不那么细心，他们应该有些工具来克服自己的不足。<br />error-page元素就是用来克服这些问题的。它有两个可能的子元素，分别是：error-code和exception- type。第一个子元素error-code指出在给定的HTTP错误代码出现时使用的URL。第二个子元素excpetion-type指出在出现某个给定的Java异常但未捕捉到时使用的URL。error-code和exception-type都利用location元素指出相应的URL。此 URL必须以/开始。location所指出的位置处的页面可通过查找HttpServletRequest对象的两个专门的属性来访问关于错误的信息，这两个属性分别是：javax.servlet.error.status_code和javax.servlet.error.message。<br />可回忆一下，在web.xml内以正确的次序声明web-app的子元素很重要。这里只要记住，error-page出现在web.xml文件的末尾附近，servlet、servlet-name和welcome-file-list之后即可。<br /><br />8.1 error-code元素<br />为了更好地了解error-code元素的值，可考虑一下如果不正确地输入文件名，大多数站点会作出什么反映。这样做一般会出现一个404错误信息，它表示不能找到该文件，但几乎没提供更多有用的信息。另一方面，可以试一下在www.microsoft.com、www.ibm.com 处或者特别是在 www.bea.com 处输出未知的文件名。这是会得出有用的消息，这些消息提供可选择的位置，以便查找感兴趣的页面。提供这样有用的错误页面对于 Web应用来说是很有价值得。事实上rm-error-page子元素）。由form-login-page给出的HTML表单必须具有一个 j_security_check的 ACTION属性、一个名为j_username的用户名文本字段以及一个名为j_password的口令字段。<br />例如，程序清单5-19指示服务器使用基于表单的验证。Web应用的顶层目录中的一个名为login.jsp的页面将收集用户名和口令，并且失败的登陆将由相同目录中名为login-error.jsp的页面报告。<br /><br />程序清单5-19 web.xml（说明login-config的摘录）<br />&lt;?xml version="1.0" encoding="ISO-8859-1"?&gt;<br />&lt;!DOCTYPE web-app<br />PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"<br />"http://java.sun.com/dtd/web-app_2_3.dtd"&gt;<br /><br />&lt;web-app&gt;<br />&lt;!-- ... --&gt;<br />&lt;security-constraint&gt; ... &lt;/security-constraint&gt;<br />&lt;login-config&gt;<br />&lt;auth-method&gt; FORM &lt;/auth-method&gt;<br />&lt;form-login-config&gt;<br />&lt;form-login-page&gt;/login.jsp&lt;/form-login-page&gt;<br />&lt;form-error-page&gt;/login-error.jsp&lt;/form-error-page&gt;<br />&lt;/form-login-config&gt;<br />&lt;/login-config&gt;<br />&lt;!-- ... --&gt;<br />&lt;/web-app&gt;<br /><br /><br />9.2 限制对Web资源的访问<br />现在，可以指示服务器使用何种验证方法了。"了不起，"你说道，"除非我能指定一个来收到保护的 URL，否则没有多大用处。"没错。指出这些URL并说明他们应该得到何种保护正是security-constriaint元素的用途。此元素在 web.xml中应该出现在login-config的紧前面。它包含是个可能的子元素，分别是：web-resource-collection、 auth-constraint、user-data- constraint和display-name。下面各小节对它们进行介绍。<br />l web-resource-collection<br />此元素确定应该保护的资源。所有security-constraint元素都必须包含至少一个web-resource-collection项。此元素由一个给出任意标识名称的web-resource-name元素、一个确定应该保护的URL的url-pattern元素、一个指出此保护所适用的 HTTP命令（GET、POST等，缺省为所有方法）的http-method元素和一个提供资料的可选description元素组成。例如，下面的 Web-resource-collection项（在security-constratint元素内）指出Web应用的proprietary目录中所有文档应该受到保护。<br />&lt;security-constraint&gt;<br />&lt;web-resource-coolection&gt;<br />&lt;web-resource-name&gt;Proprietary&lt;/web-resource-name&gt;<br />&lt;url-pattern&gt;/propritary/*&lt;/url-pattern&gt;<br />&lt;/web-resource-coolection&gt;<br />&lt;!-- ... --&gt;<br />&lt;/security-constraint&gt;<br />重要的是应该注意到，url-pattern仅适用于直接访问这些资源的客户机。特别是，它不适合于通过MVC体系结构利用 RequestDispatcher来访问的页面，或者不适合于利用类似jsp:forward的手段来访问的页面。这种不匀称如果利用得当的话很有好处。例如，servlet可利用MVC体系结构查找数据，把它放到bean中，发送请求到从bean中提取数据的JSP页面并显示它。我们希望保证决不直接访问受保护的JSP页面，而只是通过建立该页面将使用的bean的servlet来访问它。url-pattern和auth-contraint元素可通过声明不允许任何用户直接访问JSP页面来提供这种保证。但是，这种不匀称的行为可能让开发人员放松警惕，使他们偶然对应受保护的资源提供不受限制的访问。 <br />l auth-constraint<br />尽管web-resource-collention元素质出了哪些URL应该受到保护，但是auth-constraint元素却指出哪些用户应该具有受保护资源的访问权。此元素应该包含一个或多个标识具有访问权限的用户类别role- name元素，以及包含（可选）一个描述角色的description元素。例如，下面web.xml中的security-constraint元素部门规定只有指定为Administrator或Big Kahuna（或两者）的用户具有指定资源的访问权。<br />&lt;security-constraint&gt;<br />&lt;web-resource-coolection&gt; ... &lt;/web-resource-coolection&gt;<br />&lt;auth-constraint&gt;<br />&lt;role-name&gt;administrator&lt;/role-name&gt;<br />&lt;role-name&gt;kahuna&lt;/role-name&gt;<br />&lt;/auth-constraint&gt;<br />&lt;/security-constraint&gt;<br />重要的是认识到，到此为止，这个过程的可移植部分结束了。服务器怎样确定哪些用户处于任何角色以及它怎样存放用户的口令，完全有赖于具体的系统。<br />例如，Tomcat使用install_dir/conf/tomcat-users.xml将用户名与角色名和口令相关联，正如下面例子中所示，它指出用户joe（口令bigshot）和jane（口令enaj）属于administrator和kahuna角色。<br />&lt;tomcat-users&gt;<br />&lt;user name="joe" password="bigshot" roles="administrator,kahuna" /&gt;<br />&lt;user name="jane" password="enaj" roles="kahuna" /&gt;<br />&lt;/tomcat-users&gt;<br />l user-data-constraint<br />这个可选的元素指出在访问相关资源时使用任何传输层保护。它必须包含一个transport-guarantee子元素（合法值为NONE、 INTEGRAL或CONFIDENTIAL），并且可选地包含一个description元素。transport-guarantee为NONE值将对所用的通讯协议不加限制。INTEGRAL值表示数据必须以一种防止截取它的人阅读它的方式传送。虽然原理上（并且在未来的HTTP版本中），在 INTEGRAL和CONFIDENTIAL之间可能会有差别，但在当前实践中，他们都只是简单地要求用SSL。例如，下面指示服务器只允许对相关资源做 HTTPS连接：<br />&lt;security-constraint&gt;<br />&lt;!-- ... --&gt;<br />&lt;user-data-constraint&gt;<br />&lt;transport-guarantee&gt;CONFIDENTIAL&lt;/transport-guarantee&gt;<br />&lt;/user-data-constraint&gt;<br />&lt;/security-constraint&gt;<br />l display-name<br />security-constraint的这个很少使用的子元素给予可能由GUI工具使用的安全约束项一个名称。<br />9.3 分配角色名<br />迄今为止，讨论已经集中到完全由容器（服务器）处理的安全问题之上了。但servlet以及JSP页面也能够处理它们自己的安全问题。<br />例如，容器可能允许用户从bigwig或bigcheese角色访问一个显示主管人员额外紧贴的页面，但只允许bigwig用户修改此页面的参数。完成这种更细致的控制的一种常见方法是调用HttpServletRequset的isUserInRole方法，并据此修改访问。<br />Servlet的 security-role-ref子元素提供出现在服务器专用口令文件中的安全角色名的一个别名。例如，假如编写了一个调用 request.isUserInRole（"boss"）的servlet，但后来该servlet被用在了一个其口令文件调用角色manager而不是boss的服务器中。下面的程序段使该servlet能够使用这两个名称中的任何一个。<br />&lt;servlet&gt;<br />&lt;!-- ... --&gt;<br />&lt;security-role-ref&gt;<br />&lt;role-name&gt;boss&lt;/role-name&gt; &lt;!-- New alias --&gt;<br />&lt;role-link&gt;manager&lt;/role-link&gt; &lt;!-- Real name --&gt;<br />&lt;/security-role-ref&gt;<br />&lt;/servlet&gt;<br />也可以在web-app内利用security-role元素提供将出现在role-name元素中的所有安全角色的一个全局列表。分别地生命角色使高级IDE容易处理安全信息。<br /><br />10 控制会话超时<br /><br />如果某个会话在一定的时间内未被访问，服务器可把它扔掉以节约内存。可利用HttpSession的setMaxInactiveInterval方法直接设置个别会话对象的超时值。如果不采用这种方法，则缺省的超时值由具体的服务器决定。但可利用session-config和session- timeout元素来给出一个适用于所有服务器的明确的超时值。超时值的单位为分钟，因此，下面的例子设置缺省会话超时值为三个小时（180分钟）。<br />&lt;session-config&gt;<br />&lt;session-timeout&gt;180&lt;/session-timeout&gt;<br />&lt;/session-config&gt;<br /><br />11 Web应用的文档化<br /><br />越来越多的开发环境开始提供servlet和JSP的直接支持。例子有Borland Jbuilder Enterprise Edition、 Macromedia UltraDev、Allaire JRun Studio（写此文时，已被Macromedia收购）以及 IBM VisuaAge for Java等。<br />大量的web.xml元素不仅是为服务器设计的，而且还是为可视开发环境设计的。它们包括icon、display-name和discription等。<br />可回忆一下，在web.xml内以适当地次序声明web-app子元素很重要。不过，这里只要记住icon、display-name和description是web.xml的web-app元素内的前三个合法元素即可。<br />l icon<br />icon元素指出GUI工具可用来代表Web应用的一个和两个图像文件。可利用small-icon元素指定一幅16 x 16的GIF或JPEG图像，用large-icon元素指定一幅32 x 32的图像。下面举一个例子： <br />&lt;icon&gt;<br />&lt;small-icon&gt;/images/small-book.gif&lt;/small-icon&gt;<br />&lt;large-icon&gt;/images/tome.jpg&lt;/large-icon&gt;<br />&lt;/icon&gt;<br />l display-name<br />display-name元素提供GUI工具可能会用来标记此Web应用的一个名称。下面是个例子。<br />&lt;display-name&gt;Rare Books&lt;/display-name&gt;<br />l description<br />description元素提供解释性文本，如下所示：<br />&lt;description&gt;<br />This Web application represents the store developed for<br />rare-books.com, an online bookstore specializing in rare<br />and limited-edition books.<br />&lt;/description&gt;<br /><br />12 关联文件与MIME类型<br /><br />服务器一般都具有一种让Web站点管理员将文件扩展名与媒体相关联的方法。例如，将会自动给予名为mom.jpg的文件一个image/jpeg的 MIME 类型。但是，假如你的Web应用具有几个不寻常的文件，你希望保证它们在发送到客户机时分配为某种MIME类型。mime-mapping元素（具有 extension和mime-type子元素）可提供这种保证。例如，下面的代码指示服务器将application/x-fubar的 MIME类型分配给所有以.foo结尾的文件。<br />&lt;mime-mapping&gt;<br />&lt;extension&gt;foo&lt;/extension&gt;<br />&lt;mime-type&gt;application/x-fubar&lt;/mime-type&gt;<br />&lt;/mime-mapping&gt;<br />或许，你的Web应用希望重载（override）标准的映射。例如，下面的代码将告诉服务器在发送到客户机时指定.ps文件作为纯文本（text/plain）而不是作为PostScript（application/postscript）。<br />&lt;mime-mapping&gt;<br />&lt;extension&gt;ps&lt;/extension&gt;<br />&lt;mime-type&gt;application/postscript&lt;/mime-type&gt;<br />&lt;/mime-mapping&gt;<br /><br /><br />13 定位TLD<br /><br />JSP taglib 元素具有一个必要的uri属性，它给出一个TLD（Tag Library Descriptor）文件相对于Web应用的根的位置。TLD文件的实际名称在发布新的标签库版本时可能会改变，但我们希望避免更改所有现有JSP页面。此外，可能还希望使用保持taglib元素的简练性的一个简短的uri。这就是部署描述符文件的taglib元素派用场的所在了。Taglib包含两个子元素：taglib-uri和taglib-location。 taglib-uri元素应该与用于JSP taglib元素的uri属性的东西相匹配。Taglib-location元素给出TLD文件的实际位置。例如，假如你将文件chart-tags- 1.3beta.tld放在WebApp/WEB-INF/tlds中。现在，假如web.xml在web- app元素内包含下列内容。<br />&lt;taglib&gt;<br />&lt;taglib-uri&gt;/charts.tld&lt;/taglib-uri&gt;<br />&lt;taglib-location&gt;<br />/WEB-INF/tlds/chart-tags-1.3beta.tld<br />&lt;/taglib-location&gt;<br />&lt;/taglib&gt;<br />给出这个说明后，JSP页面可通过下面的简化形式使用标签库。<br />&lt;%@ taglib uri="/charts.tld" prefix="somePrefix" %&gt;<br /><br />14 指定应用事件监听程序<br /><br />应用事件监听器程序是建立或修改servlet环境或会话对象时通知的类。它们是servlet规范的版本2.3中的新内容。这里只简单地说明用来向Web应用注册一个监听程序的web.xml的用法。<br />注册一个监听程序涉及在web.xml的web-app元素内放置一个listener元素。在listener元素内，listener-class元素列出监听程序的完整的限定类名，如下所示：<br />&lt;listener&gt;<br />&lt;listener-class&gt;package.ListenerClass&lt;/listener-class&gt;<br />&lt;/listener&gt;<br />虽然listener元素的结构很简单，但请不要忘记，必须正确地给出web-app元素内的子元素的次序。listener元素位于所有的 servlet 元素之前以及所有filter-mapping元素之后。此外，因为应用生存期监听程序是serlvet规范的2.3版本中的新内容，所以必须使用 web.xml DTD的2.3版本，而不是2.2版本。<br />例如，程序清单5-20给出一个名为ContextReporter的简单的监听程序，只要Web应用的Servlet-Context建立（如装载Web应用）或消除（如服务器关闭）时，它就在标准输出上显示一条消息。程序清单5-21给出此监听程序注册所需要的web.xml文件的一部分。<br /><br />程序清单5-20 ContextReporterjava<br />package moreservlets;<br /><br />import javax.servlet.*;<br />import java.util.*;<br /><br />/** Simple listener that prints a report on the standard output <br />* when the ServletContext is created or destroyed.<br />* 
</p><p><br />* Taken from More Servlets and JavaServer Pages<br />* from Prentice Hall and Sun Microsystems Press,<br />* <a href="http://www.moreservlets.com/." target="_blank">http://www.moreservlets.com/.</a><br />* © 2002 Marty Hall; may be freely used or adapted.<br />*/<br /><br />public class ContextReporter implements ServletContextListener {<br />public void contextInitialized(ServletContextEvent event) {<br />System.out.println("Context created on " +<br />new Date() + ".");<br />}<br /><br />public void contextDestroyed(ServletContextEvent event) {<br />System.out.println("Context destroyed on " +<br />new Date() + ".");<br />}<br />}<br /><br /><br />程序清单5-21 web.xml（声明一个监听程序的摘录）<br />&lt;?xml version="1.0" encoding="ISO-8859-1"?&gt;<br />&lt;!DOCTYPE web-app<br />PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"<br />"http://java.sun.com/dtd/web-app_2_3.dtd"&gt;<br /><br />&lt;web-app&gt;<br />&lt;!-- ... --&gt;<br />&lt;filter-mapping&gt; … &lt;/filter-mapping&gt;<br />&lt;listener&gt;<br />&lt;listener-class&gt;package.ListenerClass&lt;/listener-class&gt;<br />&lt;/listener&gt;<br />&lt;servlet&gt; ... &lt;/servlet&gt;<br />&lt;!-- ... --&gt;<br />&lt;/web-app&gt;<br /><br /><br />15 J2EE元素<br /><br />本节描述用作J2EE环境组成部分的Web应用的web.xml元素。这里将提供一个简明的介绍，详细内容可以参阅http: //java.sun.com/j2ee/j2ee-1_3-fr-spec.pdf的 Java 2 Plantform Enterprise Edition版本1.3规范的第5章。<br />l distributable<br />distributable 元素指出，Web应用是以这样的方式编程的：即，支持集群的服务器可安全地在多个服务器上分布Web应用。例如，一个可分布的应用必须只使用 Serializable对象作为其HttpSession对象的属性，而且必须避免用实例变量（字段）来实现持续性。distributable元素直接出现在discription元素之后，并且不包含子元素或数据，它只是一个如下的标志。<br />&lt;distributable /&gt;<br />l resource-env-ref<br />resource -env-ref元素声明一个与某个资源有关的管理对象。此元素由一个可选的description元素、一个resource-env-ref- name元素（一个相对于java:comp/env环境的JNDI名）以及一个resource-env-type元素（指定资源类型的完全限定的类），如下所示：<br />&lt;resource-env-ref&gt;<br />&lt;resource-env-ref-name&gt;<br />jms/StockQueue<br />&lt;/resource-env-ref-name&gt;<br />&lt;resource-env-ref-type&gt;<br />javax.jms.Queue<br />&lt;/resource-env-ref-type&gt;<br />&lt;/resource-env-ref&gt;<br />l env-entry<br />env -entry元素声明Web应用的环境项。它由一个可选的description元素、一个env-entry-name元素（一个相对于java: comp/env环境JNDI名）、一个env-entry-value元素（项值）以及一个env-entry-type元素（java.lang程序包中一个类型的完全限定类名，java.lang.Boolean、java.lang.String等）组成。下面是一个例子：<br />&lt;env-entry&gt;<br />&lt;env-entry-name&gt;minAmout&lt;/env-entry-name&gt;<br />&lt;env-entry-value&gt;100.00&lt;/env-entry-value&gt;<br />&lt;env-entry-type&gt;minAmout&lt;/env-entry-type&gt;<br />&lt;/env-entry&gt;<br />l ejb-ref<br />ejb -ref元素声明对一个EJB的主目录的应用。它由一个可选的description元素、一个ejb-ref-name元素（相对于java: comp/env的EJB应用）、一个ejb-ref-type元素（bean的类型，Entity或Session）、一个home元素（bean的主目录接口的完全限定名）、一个remote元素（bean的远程接口的完全限定名）以及一个可选的ejb-link元素（当前bean链接的另一个 bean的名称）组成。<br />l ejb-local-ref<br />ejb-local-ref元素声明一个EJB的本地主目录的引用。除了用local-home代替home外，此元素具有与ejb-ref元素相同的属性并以相同的方式使用</p><img src ="http://www.blogjava.net/zhuyan/aggbug/106763.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/zhuyan/" target="_blank">朱岩</a> 2007-03-27 21:08 <a href="http://www.blogjava.net/zhuyan/articles/106763.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>