﻿<?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-wml-文章分类-技术类文章</title><link>http://www.blogjava.net/wml/category/12663.html</link><description /><language>zh-cn</language><lastBuildDate>Tue, 27 Feb 2007 12:07:43 GMT</lastBuildDate><pubDate>Tue, 27 Feb 2007 12:07:43 GMT</pubDate><ttl>60</ttl><item><title>JSP 2.0 特性</title><link>http://www.blogjava.net/wml/articles/59379.html</link><dc:creator>wml</dc:creator><author>wml</author><pubDate>Fri, 21 Jul 2006 03:08:00 GMT</pubDate><guid>http://www.blogjava.net/wml/articles/59379.html</guid><description><![CDATA[
		<p align="left">期待已久的日子即将到来: 最新版<a href="http://java.sun.com/products/jsp/">JavaServer Pages (JSP)</a>2.0规范即将和其他的J2EE 1.4一同发布。新的JSP版本有一个新的飞跃，采用了新的方式：由于新的语言表达式（Expression Language，以下简称为EL）和JSP标准标签库（JSP Standard Tag Library ,以下简称为JSTL）这两种新的方式，在页面中不需要用java,对于开发一般的应用来说，重用代码变得更加容易。更具体来说，JSP 2.0带来了以下的优点：</p>
		<ul>
				<li>首次被JSTL 1.0引入的EL现在被合并到JSP规范中，就像应用template text一样地使用所有的标准的和定制的组件。 
</li>
				<li>新的EL已经被扩展，具备一个函数调用机制，JSTL1.1整合了一系列经常需要使用的函数。 
</li>
				<li>新增加的变量和servlet 规范定义的错误处理机制被更好地组织起来。通过新增加的变量，JSP error pages 现在可以提供更多的错误信息。 
</li>
				<li>容器因为更加严格的语法检查可以更容易地找出发生的错误。 
</li>
				<li>所有的J2EE 1.4规范（包括JSP 2.0 和 Servlet 2.4），为了声明部署的规则描述而应用了XML schema。这样的好处之一是你现在可以通过任何顺序列出web.xml文件中的描述。JSP 2.0也增加了一些新的配置选项用于部署描述，允许通过全局的配置来代替基于每页的配置。 
</li>
				<li>由于更具伸缩性的规则和新的自定义action element，现在就像编写XML文件一样，编写JSP页面变得更加容易。 
</li>
				<li>定制的标签库现在可以开发成一系列的标签文件（具有JSP元素的文本文件），标签处理器可以使用新的、简化的标签处理器的API。与此同时，新规范加入了一些新的特性，比如：支持在jsp页面上显示动态属性列表和可执行片断属性。 </li>
		</ul>
		<p align="left">在众多的书籍中，这是头一个讲解JSP 2.0新特性的文章。在这一部分，我们将看到和EL相关的信息，其他的新特性留到后面。在这里我假定读者已经熟悉JSP 1.2，而且至少听说过JSTL。</p>
		<p align="left">你可能对这本第三版的《JavaServer Pages》感兴趣。这本书中，我尽可能在细节上讲述所有的内容，而且并不认为你对JSP或者JSTL了解一切。这本书预计在2003年12月 出版，但是你现在可以在<a href="http://www.amazon.com/">http://www.amazon.com</a>、Barnes&amp;Noble，或者其他在线书店预订。</p>
		<p align="left">
				<strong>EL(</strong>
				<strong>The Expression Language</strong>
				<strong>)</strong>
				<strong>
				</strong>
		</p>
		<p align="left">如果过去使用过JSTL，那么你可能已经熟悉了EL。EL在JSTL 1.0规范中被引入，用来在运行期间对Java表达式中action element属性赋值提供另一种选择。当JSTL EL已经非常迅速的流行起来情况下，还是存在一个问题： JSTL EL 表达式仅仅可以与JSTL和custom action一起使用，怎样才能使用非标准API对EL表达式求值？</p>
		<p align="left">JSP 2.0中,JSP容器自己可以理解EL表达式。这使你在所有过去只能应用Java表达式的地方应用EL表达式成为可能，比如：标准和定制action的属性值，模板文本。</p>
		<p align="left">在我们看具体的例子前，让我们更进一步的看看什么是EL。EL是从JavaScript中获得启发的一种语言，XPath(一种用来访问XML文档的语言)，但是EL在对变量的null值和执行更多数据类型的自动类型转换的处理上更加宽松。这些新特性对于web应用非常重要，在这些应用中输入通常通过html表单的request parameter来得到。这些参数可能仅仅在某些请求下才能体现出来，而且浏览器经常将request parameter作为文本发送，然而应用程序经常需要把他们作为数字类型、布尔类型（true 或者 false）来使用。通过EL，你根本就很少需要关心缺少某些参数的值或者类型转换。</p>
		<p align="left">一个EL表达式包含变量和操作符。任何存储在某个JSP作用范围(如：page、 request、session、application)的bean能被作为一个EL变量来使用。另外，EL支持以下预定义的变量：</p>
		<table cellspacing="0" cellpadding="0" border="1">
				<tbody>
						<tr>
								<td>
										<p align="center">
												<strong>变量名称</strong>
										</p>
								</td>
								<td>
										<p align="center">
												<strong>说明</strong>
										</p>
								</td>
						</tr>
						<tr>
								<td valign="top">
										<p align="left">pageScope</p>
								</td>
								<td>
										<p align="left">一个包含所有page scope范围的变量集合 (a java.util.Map)</p>
								</td>
						</tr>
						<tr>
								<td valign="top">
										<p align="left">requestScope</p>
								</td>
								<td>
										<p align="left">一个包含所有request scope范围的变量集合 (a java.util.Map)</p>
								</td>
						</tr>
						<tr>
								<td valign="top">
										<p align="left">sessionScope</p>
								</td>
								<td>
										<p align="left">一个包含所有session scope范围的变量集合 (a java.util.Map)</p>
								</td>
						</tr>
						<tr>
								<td valign="top">
										<p align="left">applicationScope</p>
								</td>
								<td>
										<p align="left">一个包含所有application scope范围的变量集合 (a java.util.Map)</p>
								</td>
						</tr>
						<tr>
								<td valign="top">
										<p align="left">param</p>
								</td>
								<td>
										<p align="left">一个包含所有请求参数的集合 (a java.util.Map)，通过每个参数对应一个String值的方式赋值</p>
								</td>
						</tr>
						<tr>
								<td valign="top">
										<p align="left">paramValues</p>
								</td>
								<td>
										<p align="left">一个包含所有请求参数的集合 (a java.util.Map)，通过每个参数对应一个String数组的方式赋值</p>
								</td>
						</tr>
						<tr>
								<td valign="top">
										<p align="left">header</p>
								</td>
								<td>
										<p align="left">一个包含所有请求的头信息的集合， (a java.util.Map) ,通过每个头信息对应一个String值的方式赋值</p>
								</td>
						</tr>
						<tr>
								<td valign="top">
										<p align="left">headerValues</p>
								</td>
								<td>
										<p align="left">一个包含所有请求的头信息的集合 (a java.util.Map) ，通过每个头信息的值都保存在一个String数组的方式赋值</p>
								</td>
						</tr>
						<tr>
								<td valign="top">
										<p align="left">cookie</p>
								</td>
								<td>
										<p align="left">一个包含所有请求的 cookie集合 (a java.util.Map)，   通过每一个cookie（javax.servlet.http.Cookie）对应一个cookie值的方式赋值</p>
								</td>
						</tr>
						<tr>
								<td valign="top">
										<p align="left">initParam</p>
								</td>
								<td>
										<p align="left">一个包含所有应用程序初始化参数的集合(a java.util.Map) ，通过每个参数分别对应一个String值的方式赋值</p>
								</td>
						</tr>
						<tr>
								<td valign="top">
										<p align="left">pageContext</p>
								</td>
								<td>
										<p align="left">一个javax.servlet.jsp.PageContext类的实例, 用来提供访问不同的请求数据</p>
								</td>
						</tr>
				</tbody>
		</table>
		<p align="left">操作符描述了你对变量所期望的操作。如果你之前曾经使用过任何编程语言的话，在EL表达式中所使用的操作符对你来说可能看起来很熟悉。因为它们和那些在大多数语言中所支持的操作符一样。</p>
		<table cellspacing="0" cellpadding="0" border="1">
				<tbody>
						<tr>
								<td>
										<p align="center">
												<strong>Operator</strong>
										</p>
								</td>
								<td>
										<p align="center">
												<strong>Description</strong>
										</p>
								</td>
						</tr>
						<tr>
								<td valign="top">
										<p align="left">.</p>
								</td>
								<td>
										<p align="left">访问一个bean属性或者 Map entry</p>
								</td>
						</tr>
						<tr>
								<td valign="top">
										<p align="left">[]</p>
								</td>
								<td>
										<p align="left">访问一个数组或者链表元素</p>
								</td>
						</tr>
						<tr>
								<td valign="top">
										<p align="left">()</p>
								</td>
								<td>
										<p align="left">对子表达式分组，用来改变赋值顺序</p>
								</td>
						</tr>
						<tr>
								<td valign="top">
										<p align="left">? :</p>
								</td>
								<td>
										<p align="left">条件语句，比如: <em>条件</em> ? <em>ifTrue</em> : <em>ifFalse</em>.如果条件为真，表达式值为前者，反之为后者</p>
								</td>
						</tr>
						<tr>
								<td valign="top">
										<p align="left">+</p>
								</td>
								<td>
										<p align="left">数学运算符，加操作</p>
								</td>
						</tr>
						<tr>
								<td valign="top">
										<p align="left">-</p>
								</td>
								<td>
										<p align="left">数学运算符，减操作或者对一个值取反</p>
								</td>
						</tr>
						<tr>
								<td valign="top">
										<p align="left">*</p>
								</td>
								<td>
										<p align="left">数学运算符，乘操作</p>
								</td>
						</tr>
						<tr>
								<td valign="top">
										<p align="left">/ or div</p>
								</td>
								<td>
										<p align="left">数学运算符，除操作</p>
								</td>
						</tr>
						<tr>
								<td valign="top">
										<p align="left">% or mod</p>
								</td>
								<td>
										<p align="left">数学运算符，模操作(取余)</p>
								</td>
						</tr>
						<tr>
								<td valign="top">
										<p align="left">== or eq</p>
								</td>
								<td>
										<p align="left">逻辑运算符，判断符号左右两端是否相等，如果相等返回true，否则返回false</p>
								</td>
						</tr>
						<tr>
								<td valign="top">
										<p align="left">!= or ne</p>
								</td>
								<td>
										<p align="left">逻辑运算符，判断符号左右两端是否不相等，如果不相等返回true，否则返回false</p>
								</td>
						</tr>
						<tr>
								<td valign="top">
										<p align="left">&lt; or lt</p>
								</td>
								<td>
										<p align="left">逻辑运算符，判断符号左边是否小于右边，如果小于返回true，否则返回false</p>
								</td>
						</tr>
						<tr>
								<td valign="top">
										<p align="left">&gt; or gt</p>
								</td>
								<td>
										<p align="left">逻辑运算符，判断符号左边是否大于右边，如果大于返回true，否则返回false</p>
								</td>
						</tr>
						<tr>
								<td valign="top">
										<p align="left">&lt;= or le</p>
								</td>
								<td>
										<p align="left">逻辑运算符，判断符号左边是否小于或者等于右边，如果小于或者等于返回true，否则返回false</p>
								</td>
						</tr>
						<tr>
								<td valign="top">
										<p align="left">&gt;= or ge</p>
								</td>
								<td>
										<p align="left">逻辑运算符，判断符号左边是否大于或者等于右边，如果大于或者等于返回true，否则返回false</p>
								</td>
						</tr>
						<tr>
								<td valign="top">
										<p align="left">&amp;&amp; or and</p>
								</td>
								<td>
										<p align="left">逻辑运算符，与操作赋。如果左右两边同为true返回true，否则返回false</p>
								</td>
						</tr>
						<tr>
								<td valign="top">
										<p align="left">|| or or</p>
								</td>
								<td>
										<p align="left">逻辑运算符，或操作赋。如果左右两边有任何一边为true返回true，否则返回false</p>
								</td>
						</tr>
						<tr>
								<td valign="top">
										<p align="left">! or not</p>
								</td>
								<td>
										<p align="left">逻辑运算符，非操作赋。如果对true取运算返回false，否则返回true</p>
								</td>
						</tr>
						<tr>
								<td valign="top">
										<p align="left">empty</p>
								</td>
								<td>
										<p align="left">用来对一个空变量值进行判断: null、一个空String、空数组、 空Map、没有条目的Collection集合</p>
								</td>
						</tr>
						<tr>
								<td valign="top">
										<p align="left">
												<em>func</em>(<em>args</em>)</p>
								</td>
								<td>
										<p align="left">调用方法, <em>func</em>是方法名，<em>args</em>是参数，可以没有，或者有一个、多个参数.参数间用逗号隔开</p>
								</td>
						</tr>
				</tbody>
		</table>
		<p align="left">一个EL表达式可以包含：数字、文本（在单引号或者双引号之间）、布尔值、null值。</p>
		<p align="left">因为一个EL表达式可以出现在静态文本出现的地方，因此你必须告诉JSP容器它应该被当作一个EL表达式来处理。你可以通过使用定界符来做到这一点。一个EL表达式总是以”${ }”来标记（一个“$”符号和一个左花括号,右花括号）。这里有一个EL表达式，它将一个命名为amount的变量加5：</p>
		<p align="left">${amount + 5}</p>
		<p align="left">如果你想要将5加到一个bean的property上，可以使用property访问操作符：</p>
		<p align="left">${order.amount + 5}</p>
		<p align="left">在当前这个指定的bean或者collection集合中，Property访问操作符（一个“.“符号）告诉EL去寻找名字为amount的property。</p>
		<p align="left">${order['amount'] + 5}</p>
		<p align="left">在[]之间的值必须是一个property的名字（就像上面的例子中那样）或者是一个保存property名字的变量（或者是一个完整的EL子表达式）。</p>
		<p align="left">EL表达式可以被用来赋值给任何标准的或者定制的JSP行为属性（action attribute），这些行为属性被标记为可以接受动态值（或者请求期间的属性值，就象它被正式调用一样）：</p>
		<p align="left">&lt;c:out value="${order.amount + 5}"/&gt;</p>
		<p align="left">在JSP 2.0之前，你不得不使用Java表达式去给一个属性动态赋值。在过去的很多年中，这已经成为语法混乱的一个普遍根源。</p>
		<p align="left">最后，EL表达式可以在页面中和模板直接混合使用。当你生成HTML并且需要设置一个动态值给一个属性的时候，这非常方便：</p>
		<p align="left">&lt;input name="firstName" value="${customer.firstName}"&gt;</p>
		<p align="left">JSP 1.2中，你不得不使用JSTL的&lt;c:out&gt;来实现同样的事情，最后把各种不同类型的元素混合起来，这导致程序理解起来非常的困难：</p>
		<p align="left">&lt;input name="firstName" </p>
		<p align="left">value="&lt;c:out value="${customer.firstName}"/&gt;" &gt;</p>
		<p align="left"> </p>
		<h3>新JSTL 1.1 Tag Library 标识符</h3>
		<p>JSTL1.1发布的是一个初级的版本，主要目的是用来整合JSTL和JSP2.0 。最明显的变化是JSTL1.0 “孪生函数库”（一组库用来接受EL表达式，另外一组用来接受JAVA表达式），而它们已经被一组既可以用于EL表达式也可以用于JAVA表达式的函数库所代替。</p>
		<p>在JSTL 1.1中使用以下标识符:</p>
		<table cellspacing="0" cellpadding="0" border="1">
				<tbody>
						<tr>
								<td>
										<p align="center">
												<strong>库</strong>
												<strong>
												</strong>
										</p>
								</td>
								<td>
										<p align="center">
												<strong>URI</strong>
												<strong>
												</strong>
										</p>
								</td>
								<td>
										<p align="center">
												<strong>前缀</strong>
												<strong>
												</strong>
										</p>
								</td>
						</tr>
						<tr>
								<td>
										<p>Core</p>
								</td>
								<td>
										<p>
												<em>http://java.sun.com/jsp/jstl/core</em>
										</p>
								</td>
								<td>
										<p>c</p>
								</td>
						</tr>
						<tr>
								<td>
										<p>XML processing</p>
								</td>
								<td>
										<p>
												<em>http://java.sun.com/jsp/jstl/xml</em>
										</p>
								</td>
								<td>
										<p>x</p>
								</td>
						</tr>
						<tr>
								<td>
										<p>I18N formatting</p>
								</td>
								<td>
										<p>
												<em>http://java.sun.com/jsp/jstl/fmt</em>
										</p>
								</td>
								<td>
										<p>fmt</p>
								</td>
						</tr>
						<tr>
								<td>
										<p>Database access</p>
								</td>
								<td>
										<p>
												<em>http://java.sun.com/jsp/jstl/sql</em>
										</p>
								</td>
								<td>
										<p>sql</p>
								</td>
						</tr>
						<tr>
								<td>
										<p>Functions</p>
								</td>
								<td>
										<p>
												<em>http://java.sun.com/jsp/jstl/functions</em>
										</p>
								</td>
								<td>
										<p>fn</p>
								</td>
						</tr>
				</tbody>
		</table>
		<p>如果你曾经使用过JSTL1.0，你可能会注意到新的标识符和旧的EL库标试符一模一样，除了加入了“/jsp path” element。你也可能注意到在JSTL1.1中有一个库，包含了EL的函数。我们稍后就会看到。</p>
		<h3>一个新的EL操作符</h3>
		<p>在JSP页面中一个非常普遍的需求就是：当某个条件为真时，要在网页中包含一些文字。在JSP1.2和JSTL1.1中，用具有代表性的&lt;c:if&gt;来实现，但是这样做非常繁琐。JSP2.0增加了一个新的条件操作符用于EL，以更加优雅的方式来处理这样的情况。这个条件操作符存在于很多编程语言中（比如：Java,C,JavaScript）,因此你可能以前就见过它。它判断一个布尔的条件，当条件为真或者假时，分别取不同的结果。</p>
		<p>一个能清楚说明它如何工作的例子：</p>
		<p>&lt;select name="artist"&gt;<br />&lt;option value="1" ${param.artist == 1 ? 'selected' : ''}&gt;<br />Vesica Pisces<br />&lt;option value="2" ${param.artist == 2 ? 'selected' : ''}&gt;<br />Cortical Control<br />&lt;option value="3" ${param.artist == 3 ? 'selected' : ''}&gt;<br />Vida Vierra<br />&lt;/select&gt; </p>
		<p>在这里，我使用了EL表达式和条件操作符来选择是否包含 html 中的 “selected”属性，只有符合条件的 “option” 才被添加 “selected” 属性。如果条件（param.artist==1）为真时，前面的“selected” 才被添加到网页中；否则就添加后面的（在这里是空字符串 ‘’）到页面中。</p>
		<h3>EL函数</h3>
		<p>当EL从JSTL规范中移到JSP规范中，它使用了一个如何进行函数调用的技巧。这个EL函数语法非常简单：方法名，紧接着在圆括号中有一组参数：</p>&lt;%@ taglib prefix="fn"<br />uri="http://java.sun.com/jsp/jstl/functions" %&gt;<br />${fn:length(myCollection)} 
<p>这是一个属于标签库中的函数,并且函数名字在页面中所包含的前缀要指定taglib库。在这个例子中，我使用了前缀fn,这是JSTL function库默认的前缀。</p><p>标签库描述符（Tag Library Descriptor,TLD）将函数名称映射到一个由JAVA实现的静态方法中：</p>&lt;function&gt;<br />&lt;description&gt;<br />Returns the number of items in a collection or the number of characters in a string.<br />&lt;/description&gt;<br />&lt;name&gt;length&lt;/name&gt;<br />&lt;function-class&gt;<br />org.apache.taglibs.standard.functions.Functions<br />&lt;/function-class&gt;<br />&lt;function-signature&gt;<br />int length(java.lang.Object)<br />&lt;/function-signature&gt;<br />&lt;/function&gt; 
<p>在这里最有趣的element是&lt;function-signature&gt;。它包含一个函数返回类型的声明，静态的方法的名字，在圆括号中声明该方法所有参数的类型（可以没有参数或者有多个，参数间用逗号间隔开）。返回值类型和参数类型必须是java的原始类型（Object）或者是其他合法类型。</p><p>这个静态方法 length()在Jakarta Taglibs标准库中用类似于下面的代码实现的：</p>public static int length(Object obj)<br />throws JspTagException {<br />if (obj == null)<br />return 0;<br />if (obj instanceof String)<br />return ((String)obj).length();<br />if (obj instanceof Collection)<br />return ((Collection)obj).size();<br />if (obj instanceof Map)<br />return ((Map)obj).size(); 
<p>int count = 0;<br />if (obj instanceof Iterator) {<br />Iterator iter = (Iterator) obj;<br />count = 0;<br />while (iter.hasNext()) {<br />count++;<br />iter.next();<br />}<br />return count;<br />}<br />if (obj instanceof Enumeration) {<br />Enumeration enum = (Enumeration) obj;<br />count = 0;<br />while (enum.hasMoreElements()) {<br />count++;<br />enum.nextElement();<br />}<br />return count;<br />}<br />try {<br />count = Array.getLength(obj);<br />return count;<br />} catch (IllegalArgumentException ex) {}<br />throw new JspTagException("Unsupported type"));<br />}</p><p>就像你所看到的，在那里没有什么出奇的地方。它是一个常规的静态方法，这个函数中通过对运行期中的参数类别的判断，找出参数的长度。</p><p>除了在这个方法中使用的length()方法，JSTL1.1标签库还包含了许多其它经常使用的函数：</p><table cellspacing="0" cellpadding="0" border="1"><tbody><tr><td><p align="center"><strong>函数</strong><strong></strong></p></td><td><p align="center"><strong>描述</strong><strong></strong></p></td></tr><tr><td valign="top"><p>fn:contains(string, substring)</p></td><td><p>如果参数string中包含参数substring，返回true</p></td></tr><tr><td valign="top"><p>fn:containsIgnoreCase(string, substring)</p></td><td><p>如果参数string中包含参数substring（忽略大小写），返回true</p></td></tr><tr><td valign="top"><p>fn:endsWith(string, suffix)</p></td><td><p>如果参数 string 以参数suffix结尾，返回true</p></td></tr><tr><td valign="top"><p>fn:escapeXml(string)</p></td><td><p>将有特殊意义的XML (和HTML)转换为对应的XML character entity code，并返回</p></td></tr><tr><td valign="top"><p>fn:indexOf(string, substring)</p></td><td><p>返回参数substring在参数string中第一次出现的位置</p></td></tr><tr><td valign="top"><p>fn:join(array, separator)</p></td><td><p>将一个给定的数组array用给定的间隔符separator串在一起，组成一个新的字符串并返回。</p></td></tr><tr><td valign="top"><p>fn:length(item)</p></td><td><p>返回参数item中包含元素的数量。参数Item类型是数组、collection或者String。如果是String类型,返回值是String中的字符数。</p></td></tr><tr><td valign="top"><p>fn:replace(string, before, after)</p></td><td valign="top"><p>返回一个String对象。用参数after字符串替换参数string中所有出现参数before字符串的地方，并返回替换后的结果</p></td></tr><tr><td valign="top"><p>fn:split(string, separator)</p></td><td><p>返回一个数组，以参数separator 为分割符分割参数string，分割后的每一部分就是数组的一个元素</p></td></tr><tr><td valign="top"><p>fn:startsWith(string, prefix)</p></td><td><p>如果参数string以参数prefix开头，返回true</p></td></tr><tr><td valign="top"><p>fn:substring(string, begin, end)</p></td><td><p>返回参数string部分字符串, 从参数begin开始到参数end位置，包括end位置的字符</p></td></tr><tr><td valign="top"><p>fn:substringAfter(string, substring)</p></td><td><p>返回参数substring在参数string中后面的那一部分字符串</p></td></tr><tr><td valign="top"><p>fn:substringBefore(string, substring)</p></td><td><p>返回参数substring在参数string中前面的那一部分字符串</p></td></tr><tr><td valign="top"><p>fn:toLowerCase(string)</p></td><td><p>将参数string所有的字符变为小写，并将其返回</p></td></tr><tr><td valign="top"><p>fn:toUpperCase(string)</p></td><td><p>将参数string所有的字符变为大写，并将其返回</p></td></tr><tr><td valign="top"><p>fn:trim(string)</p></td><td><p>去除参数string 首尾的空格，并将其返回</p></td></tr></tbody></table><h3>结束语：</h3><p>在这篇文章中，我从EL讲到JSTL1.1规范、EL新特色和JSTL 1.1函数库。接下来的部分我将要告诉你：关于JSP error-page的改进和增强； jsp:id 属性带来的益处；新的配置属性描述符；JSP2.0如何使JSP操作XML变得更加容易；自定义标签库的新特性。</p><p>如果你想要尝试JSP2.0的新特性，我建议你使用Apache Tomcat 5。它是最早实现了JSP新规范的容器之一。在公布最终版本的JSP 2.0规范之前，一个被标记为“stable”版本的Tomcat是不能被发布的。但是最新的beta版已经被证实是非常稳定的，不要理会beta版的标记。Tomcat 5在<a href="http://jakarta.apache.org/tomcat/tomcat-5.0/">the Jakarta Project site</a>可以下载。</p><img src ="http://www.blogjava.net/wml/aggbug/59379.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/wml/" target="_blank">wml</a> 2006-07-21 11:08 <a href="http://www.blogjava.net/wml/articles/59379.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>转：Javascript--文件操作</title><link>http://www.blogjava.net/wml/articles/58033.html</link><dc:creator>wml</dc:creator><author>wml</author><pubDate>Thu, 13 Jul 2006 09:49:00 GMT</pubDate><guid>http://www.blogjava.net/wml/articles/58033.html</guid><description><![CDATA[Javascript----文件操作<br /><br />一、功能实现核心：FileSystemObject 对象 <br />要在javascript中实现文件操作功能，主要就是依靠FileSystemobject对象。<br />二、FileSystemObject编程 <br />使用FileSystemObject 对象进行编程很简单，一般要经过如下的步骤： 创建FileSystemObject对象、应用相关方法、访问对象相关属性 。 <br />（一）创建FileSystemObject对象 <br />创建FileSystemObject对象的代码只要1行： <br />var fso = new ActiveXObject("Scripting.FileSystemObject"); <br />上述代码执行后，fso就成为一个FileSystemObject对象实例。 <br />（二）应用相关方法 <br />创建对象实例后，就可以使用对象的相关方法了。比如，使用CreateTextFile方法创建一个文本文件： <br />var fso = new ActiveXObject("Scripting.FileSystemObject"); <br />var f1 = fso.createtextfile("c:\\myjstest.txt",true"); <br />（三）访问对象相关属性 <br />要访问对象的相关属性，首先要建立指向对象的句柄，这就要通过get系列方法实现：GetDrive负责获取驱动器信息，GetFolder负责获取文件夹信息，GetFile负责获取文件信息。比如，指向下面的代码后，f1就成为指向文件c:\test.txt的句柄： <br />var fso = new ActiveXObject("Scripting.FileSystemObject"); <br />var f1 = fso.GetFile("c:\\myjstest.txt"); <br />然后，使用f1访问对象的相关属性。比如： <br />var fso = new ActiveXObject("Scripting.FileSystemObject"); <br />var f1 = fso.GetFile("c:\\myjstest.txt"); <br />alert("File last modified: " + f1.DateLastModified); <br />执行上面最后一句后，将显示c:\myjstest.txt的最后修改日期属性值。 <br />但有一点请注意：对于使用create方法建立的对象，就不必再使用get方法获取对象句柄了，这时直接使用create方法建立的句柄名称就可以： <br />var fso = new ActiveXObject("Scripting.FileSystemObject"); <br />var f1 = fso.createtextfile("c:\\myjstest.txt",true"); <br />alert("File last modified: " + f1.DateLastModified); <br />三、操作驱动器（Drives） <br />使用FileSystemObject对象来编程操作驱动器（Drives）和文件夹（Folders）很容易，这就象在Windows文件浏览器中对文件进行交互操作一样，比如：拷贝、移动文件夹，获取文件夹的属性。 <br />（一）Drives对象属性 <br />Drive对象负责收集系统中的物理或逻辑驱动器资源内容，它具有如下属性： <br />l TotalSize：以字节（byte）为单位计算的驱动器大小。 <br />l AvailableSpace或FreeSpace：以字节（byte）为单位计算的驱动器可用空间。 <br />l DriveLetter：驱动器字母。 <br />l DriveType：驱动器类型，取值为：removable（移动介质）、fixed（固定介质）、network（网络资源）、CD-ROM或者RAM盘。 <br />l SerialNumber：驱动器的系列码。 <br />l FileSystem：所在驱动器的文件系统类型，取值为FAT、FAT32和NTFS。 <br />l IsReady：驱动器是否可用。 <br />l ShareName：共享名称。 <br />l VolumeName：卷标名称。 <br />l Path和RootFolder：驱动器的路径或者根目录名称。 <br />（二）Drive对象操作例程 <br />下面的例程显示驱动器C的卷标、总容量和可用空间等信息： <br />var fso, drv, s =""; <br />fso = new ActiveXObject("Scripting.FileSystemObject"); <br />drv = fso.GetDrive(fso.GetDriveName("c:\\")); <br />s += "Drive C:" + " - "; <br />s += drv.VolumeName + "\n"; <br />s += "Total Space: " + drv.TotalSize / 1024; <br />s += " Kb" + "\n"; <br />s += "Free Space: " + drv.FreeSpace / 1024; <br />s += " Kb" + "\n"; <br />alert(s); <br />四、操作文件夹（Folders） <br />涉及到文件夹的操作包括创建、移动、删除以及获取相关属性。 <br />Folder对象操作例程 :<br />下面的例程将练习获取父文件夹名称、创建文件夹、删除文件夹、判断是否为根目录等操作： <br />var fso, fldr, s = ""; <br />// 创建FileSystemObject对象实例 <br />fso = new ActiveXObject("Scripting.FileSystemObject"); <br />// 获取Drive 对象 <br />fldr = fso.GetFolder("c:\\"); <br />// 显示父目录名称 <br />alert("Parent folder name is: " + fldr + "\n"); <br />// 显示所在drive名称 <br />alert("Contained on drive " + fldr.Drive + "\n"); <br />// 判断是否为根目录 <br />if (fldr.IsRootFolder) <br />alert("This is the root folder."); <br />else <br />alert("This folder isn't a root folder."); <br />alert("\n\n"); <br />// 创建新文件夹 <br />fso.CreateFolder ("C:\\Bogus"); <br />alert("Created folder C:\\Bogus" + "\n"); <br />// 显示文件夹基础名称，不包含路径名 <br />alert("Basename = " + fso.GetBaseName("c:\\bogus") + "\n"); <br />// 删除创建的文件夹 <br />fso.DeleteFolder ("C:\\Bogus"); <br />alert("Deleted folder C:\\Bogus" + "\n"); <br />五、操作文件（Files） <br />对文件进行的操作要比以上介绍的驱动器（Drive）和文件夹（Folder）操作复杂些，基本上分为以下两个类别：对文件的创建、拷贝、移动、删除操作和对文件内容的创建、添加、删除和读取操作。下面分别详细介绍。 <br />（一）创建文件 <br />一共有3种方法可用于创建一个空文本文件，这种文件有时候也叫做文本流（text stream）。 <br />第一种是使用CreateTextFile方法。代码如下： <br />var fso, f1; <br />fso = new ActiveXObject("Scripting.FileSystemObject"); <br />f1 = fso.CreateTextFile("c:\\testfile.txt", true); <br />第二种是使用OpenTextFile方法，并添加上ForWriting属性，ForWriting的值为2。代码如下： <br />var fso, ts; <br />var ForWriting= 2; <br />fso = new ActiveXObject("Scripting.FileSystemObject"); <br />ts = fso.OpenTextFile("c:\\test.txt", ForWriting, true); <br />第三种是使用OpenAsTextStream方法，同样要设置好ForWriting属性。代码如下： <br />var fso, f1, ts; <br />var ForWriting = 2; <br />fso = new ActiveXObject("Scripting.FileSystemObject"); <br />fso.CreateTextFile ("c:\\test1.txt"); <br />f1 = fso.GetFile("c:\\test1.txt"); <br />ts = f1.OpenAsTextStream(ForWriting, true); <br />（二）添加数据到文件 <br />当文件被创建后，一般要按照“打开文件－&gt;填写数据－&gt;关闭文件”的步骤实现添加数据到文件的目的。 <br />打开文件可使用FileSystemObject对象的OpenTextFile方法，或者使用File对象的OpenAsTextStream方法。 <br />填写数据要使用到TextStream对象的Write、WriteLine或者WriteBlankLines方法。在同是实现写入数据的功能下，这3者的区别在于：Write方法不在写入数据末尾添加新换行符，WriteLine方法要在最后添加一个新换行符，而WriteBlankLines则增加一个或者多个空行。 <br />关闭文件可使用TextStream对象的Close方法。 <br />（三）创建文件及添加数据例程 <br />下面的代码将创建文件、添加数据、关闭文件几个步骤结合起来进行应用： <br />var fso, tf; <br />fso = new ActiveXObject("Scripting.FileSystemObject"); <br />// 创建新文件 <br />tf = fso.CreateTextFile("c:\\testfile.txt", true); <br />// 填写数据，并增加换行符 <br />tf.WriteLine("Testing 1, 2, 3.") ; <br />// 增加3个空行 <br />tf.WriteBlankLines(3) ; <br />// 填写一行，不带换行符 <br />tf.Write ("This is a test."); <br />// 关闭文件 <br />tf.Close(); <br />（四）读取文件内容 <br />从文本文件中读取数据要使用TextStream对象的Read、ReadLine或ReadAll 方法。Read方法用于读取文件中指定数量的字符；ReadLine方法读取一整行，但不包括换行符；ReadAll方法则读取文本文件的整个内容。读取的内容存放于字符串变量中，用于显示、分析。在使用Read或ReadLine方法读取文件内容时，如果要跳过一些部分，就要用到Skip或SkipLine方法。 <br />下面的代码演示打开文件、填写数据，然后读取数据： <br />var fso, f1, ts, s; <br />var ForReading = 1; <br />fso = new ActiveXObject("Scripting.FileSystemObject"); <br />// 创建文件 <br />f1 = fso.CreateTextFile("c:\\testfile.txt", true); <br />// 填写一行数据 <br />f1.WriteLine("Hello World"); <br />f1.WriteBlankLines(1); <br />// 关闭文件 <br />f1.Close(); <br />// 打开文件 <br />ts = fso.OpenTextFile("c:\\testfile.txt", ForReading); <br />// 读取文件一行内容到字符串 <br />s = ts.ReadLine(); <br />// 显示字符串信息 <br />alert("File contents = '" + s + "'"); <br />// 关闭文件 <br />ts.Close(); <br />（五）移动、拷贝和删除文件 <br />对于以上三种文件操作，javascript各有两种对应的方法：File.Move 或 FileSystemObject.MoveFile用于移动文件；File.Copy 或 FileSystemObject.CopyFile用于拷贝文件；File.Delete 或 FileSystemObject.DeleteFile用于删除文件。 <br />下面的代码演示在驱动器C的根目录下创建一个文本文件，填写一些内容，然后将文件移动到\tmp目录下，再在目录\temp下面建立一个文件拷贝，最后删除这两个目录的文件： <br />var fso, f1, f2, s; <br />fso = new ActiveXObject("Scripting.FileSystemObject"); <br />f1 = fso.CreateTextFile("c:\\testfile.txt", true); <br />// 写一行 <br />f1.Write("This is a test."); <br />// 关闭文件 <br />f1.Close(); <br />// 获取C:\根目录下的文件句柄 <br />f2 = fso.GetFile("c:\\testfile.txt"); <br />// 移动文件到\tmp目录下 <br />f2.Move ("c:\\tmp\\testfile.txt"); <br />// 拷贝文件到\temp目录下 <br />f2.Copy ("c:\\temp\\testfile.txt"); <br />// 获取文件句柄 <br />f2 = fso.GetFile("c:\\tmp\\testfile.txt"); <br />f3 = fso.GetFile("c:\\temp\\testfile.txt"); <br />// 删除文件 <br />f2.Delete(); <br />f3.Delete(); <br />六、结 语 <br />通过以上对FileSystemObject的各种对象、属性和方法的介绍和示例，相信你已经对如何使用javascript语言在页面中操作驱动器、文件和文件夹有了清晰的认识。但是上述提及的例程都非常简单，要全面、灵活地掌握javascript文件操作技术，还需要大量的实践练习。而且还有一点提醒大家，由于涉及到在浏览器中进行文件读写这样的高级操作，对于默认的浏览器安全级别而言，在代码运行前都会有一个信息提示，这点请在实际环境中提示访问者注意。 <br /><img src ="http://www.blogjava.net/wml/aggbug/58033.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/wml/" target="_blank">wml</a> 2006-07-13 17:49 <a href="http://www.blogjava.net/wml/articles/58033.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>转：session机制</title><link>http://www.blogjava.net/wml/articles/55999.html</link><dc:creator>wml</dc:creator><author>wml</author><pubDate>Fri, 30 Jun 2006 09:16:00 GMT</pubDate><guid>http://www.blogjava.net/wml/articles/55999.html</guid><description><![CDATA[session机制的本质，以至不能正确的应用这一技术。本文将详细讨论session的工作机制并且对在Java web application中应用session机制时常见的问题作出解答。<br /><br />目录：<br />一、术语session<br />二、HTTP协议与状态保持<br />三、理解cookie机制<br />四、理解session机制<br />五、理解javax.servlet.http.HttpSession<br />六、HttpSession常见问题<br />七、跨应用程序的session共享<br />八、总结<br />参考文档<br /><br />一、术语session<br />在我的经验里，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 />二、HTTP协议与状态保持<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 />三、理解cookie机制 <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 />HTTP/1.1 302 Found<br />Location: <a href="http://www.google.com/intl/zh-CN/"></a><a href="http://www.google.com/intl/zh-CN/" target="_blank"><font color="#223355">http://www.google.com/intl/zh-CN/</font></a><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<br /><br /><br /><br /><br />这是使用HTTPLook这个HTTP Sniffer软件来俘获的HTTP通讯纪录的一部分<br /><br /><br /><br /><br />浏览器在再次访问goolge的资源时自动向外发送cookie<br /><br /><br /><br /><br />使用Firefox可以很容易的观察现有的cookie的值<br />使用HTTPLook配合Firefox可以很容易的理解cookie的工作原理。<br /><br /><br /><br /><br />IE也可以设置在接受cookie前询问<br /><br /><br /><br /><br />这是一个询问接受cookie的对话框。<br /><br />四、理解session机制<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路径的附加信息，表现形式为<a href="http://...../xxx;jsessionid="></a><a href="http://...../xxx;jsessionid=" target="_blank"><font color="#223355">http://...../xxx;jsessionid=</font></a> ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764<br />另一种是作为查询字符串附加在URL后面，表现形式为<a href="http://...../xxx?jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764"></a><a href="http://...../xxx?jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764" target="_blank"><font color="#223355">http://...../xxx?jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764</font></a><br />这两种方式对于用户来说是没有区别的，只是服务器在解析的时候处理的方式不同，采用第一种方式也有利于把session id的信息和正常程序参数区分开来。<br />为了在整个交互过程中始终保持状态，就必须在每个客户端可能请求的路径后面都包含这个session id。<br /><br />另一种技术叫做表单隐藏字段。就是服务器会自动修改表单，添加一个隐藏字段，以便在表单提交时能够把session id传递回服务器。比如下面的表单<br /><a href="http://e-docs.bea.com/wls/docs70/webapp/weblogic_xml.html#1036869" target="_blank"><font color="#223355">http://e-docs.bea.com/wls/docs70/webapp/weblogic_xml.html#1036869</font></a><br /><br />六、HttpSession常见问题<br />（在本小节中session的含义为⑤和⑥的混合）<br /><br /><br />1、session在何时被创建<br />一个常见的误解是以为session在有客户端访问时就被创建，然而事实是直到某server端程序调用 HttpServletRequest.getSession(true)这样的语句时才被创建，注意如果JSP没有显示的使用 <!--page&nbsp;session="false--> 关闭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 /><a href="http://e-docs.bea.com/wls/docs70/webapp/sessions.html#100770"></a><a href="http://e-docs.bea.com/wls/docs70/webapp/sessions.html#100770" target="_blank"><font color="#223355">http://e-docs.bea.com/wls/docs70/webapp/sessions.html#100770</font></a><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 />七、跨应用程序的session共享<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 />根据这个特性，我们可以推测Tomcat中session的内存结构大致如下。<br /><br />笔者以前用过的iPlanet也采用的是同样的方式，估计SunONE与iPlanet之间不会有太大的差别。对于这种方式的服务器，解决的思路很简单，实际实行起来也不难。要么让所有的应用程序共享一个session id，要么让应用程序能够获得其他应用程序的session id。<br /><br />iPlanet中有一种很简单的方法来实现共享一个session id，那就是把各个应用程序的cookie路径都设为/（实际上应该是/NASApp，对于应用程序来讲它的作用相当于根）。<br /><br />/NASApp<br /><br />需要注意的是，操作共享的session应该遵循一些编程约定，比如在session attribute名字的前面加上应用程序的前缀，使得 setAttribute("name", "neo")变成setAttribute("app1.name", "neo")，以防止命名空间冲突，导致互相覆盖。<br /><br /><br />在Tomcat中则没有这么方便的选择。在Tomcat版本3上，我们还可以有一些手段来共享session。对于版本4以上的Tomcat，目前笔者尚未发现简单的办法。只能借助于第三方的力量，比如使用文件、数据库、JMS或者客户端cookie，URL参数或者隐藏字段等手段。<br /><br />我们再看一下Weblogic Server是如何处理session的。<br /><br /><br />  <br /><br />从截屏画面上可以看到Weblogic Server对所有的应用程序设置的cookie的路径都是/，这是不是意味着在Weblogic Server中默认的就可以共享session了呢？然而一个小实验即可证明即使不同的应用程序使用的是同一个session，各个应用程序仍然只能访问自己所设置的那些属性。这说明Weblogic Server中的session的内存结构可能如下<br /><br /><br /><br /><br />对于这样一种结构，在 session机制本身上来解决session共享的问题应该是不可能的了。除了借助于第三方的力量，比如使用文件、数据库、JMS或者客户端 cookie，URL参数或者隐藏字段等手段，还有一种较为方便的做法，就是把一个应用程序的session放到ServletContext中，这样另外一个应用程序就可以从ServletContext中取得前一个应用程序的引用。示例代码如下，<br /><br />应用程序A<br />context.setAttribute("appA", session); <br /><br />应用程序B<br />contextA = context.getContext("/appA");<br />HttpSession sessionA = (HttpSession)contextA.getAttribute("appA"); <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 />八、总结<br />session机制本身并不复杂，然而其实现和配置上的灵活性却使得具体情况复杂多变。这也要求我们不能把仅仅某一次的经验或者某一个浏览器，服务器的经验当作普遍适用的经验，而是始终需要具体情况具体分析。<br />摘要：虽然session机制在web应用程序中被采用已经很长时间了，但是仍然有很多人不清楚session机制的本质，以至不能正确的应用这一技术。本文将详细讨论session的工作机制并且对在Java web application中应用session机制时常见的问题作出解答。 <img src ="http://www.blogjava.net/wml/aggbug/55999.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/wml/" target="_blank">wml</a> 2006-06-30 17:16 <a href="http://www.blogjava.net/wml/articles/55999.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>转：JAVA字符集</title><link>http://www.blogjava.net/wml/articles/55985.html</link><dc:creator>wml</dc:creator><author>wml</author><pubDate>Fri, 30 Jun 2006 08:49:00 GMT</pubDate><guid>http://www.blogjava.net/wml/articles/55985.html</guid><description><![CDATA[
		<p align="left">
				<strong>1. 概述</strong>
		</p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 22pt">本文主要包括以下几个方面：编码基本知识，java，系统软件，url，工具软件等。 </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 22pt">在下面的描述中，将以"中文"两个字为例，经查表可以知道其GB2312编码是"<u>d6d0 cec4</u>"，Unicode编码为"<u>4e2d 6587</u>"，UTF编码就是"<u>e4b8ad e69687</u>"。注意，这两个字没有iso8859-1编码，但可以用iso8859-1编码来"表示"。 </p>
		<p class="1">
				<strong>2. 编码基本知识</strong>
		</p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">最早的编码是iso8859-1，和ascii编码相似。但为了方便表示各种各样的语言，逐渐出现了很多标准编码，重要的有如下几个。 </p>
		<p class="2">
				<strong>2.1. iso8859-1</strong>
		</p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">属于单字节编码，最多能表示的字符范围是0-255，应用于英文系列。比如，字母'a'的编码为0x61=97。 </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">很明显，iso8859-1编码表示的字符范围很窄，无法表示中文字符。但是，由于是单字节编码，和计算机最基础的表示单位一致，所以很多时候，仍旧使用iso8859-1编码来表示。而且在很多协议上，默认使用该编码。比如，虽然"中文"两个字不存在iso8859-1编码，以gb2312编码为例，应该是"<u>d6d0 cec4</u>"两个字符，使用iso8859-1编码的时候则将它拆开为4个字节来表示："<u>d6 d0 ce c4</u>"（事实上，在进行存储的时候，也是以字节为单位处理的）。而如果是UTF编码，则是6个字节"<u>e4 b8 ad e6 96 87</u>"。很明显，这种表示方法还需要以另一种编码为基础。 </p>
		<p class="2">
				<strong>2.2. GB2312/GBK</strong>
		</p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">这就是汉子的国标码，专门用来表示汉字，是双字节编码，而英文字母和iso8859-1一致（兼容iso8859-1编码）。其中gbk编码能够用来同时表示繁体字和简体字，而gb2312只能表示简体字，gbk是兼容gb2312编码的。 </p>
		<p class="2">
				<strong>2.3. unicode</strong>
		</p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">这是最统一的编码，可以用来表示所有语言的字符，而且是定长双字节（也有四字节的）编码，包括英文字母在内。所以可以说它是不兼容iso8859-1编码的，也不兼容任何编码。不过，相对于iso8859-1编码来说，uniocode编码只是在前面增加了一个0字节，比如字母'a'为"<u>00 61</u>"。 </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 22pt">需要说明的是，定长编码便于计算机处理（注意GB2312/GBK不是定长编码），而unicode又可以用来表示所有字符，所以在很多软件内部是使用unicode编码来处理的，比如java。 </p>
		<p class="2">
				<strong>2.4. UTF</strong>
		</p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">考虑到unicode编码不兼容iso8859-1编码，而且容易占用更多的空间：因为对于英文字母，unicode也需要两个字节来表示。所以unicode不便于传输和存储。因此而产生了utf编码，utf编码兼容iso8859-1编码，同时也可以用来表示所有语言的字符，不过，utf编码是不定长编码，每一个字符的长度从1-6个字节不等。另外，utf编码自带简单的校验功能。一般来讲，英文字母都是用一个字节表示，而汉字使用三个字节。 </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">注意，虽然说utf是为了使用更少的空间而使用的，但那只是相对于unicode编码来说，如果已经知道是汉字，则使用GB2312/GBK无疑是最节省的。不过另一方面，值得说明的是，虽然utf编码对汉字使用3个字节，但即使对于汉字网页，utf编码也会比unicode编码节省，因为网页中包含了很多的英文字符。 </p>
		<p class="1">
				<strong>3. java对字符的处理</strong>
		</p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">在java应用软件中，会有多处涉及到字符集编码，有些地方需要进行正确的设置，有些地方需要进行一定程度的处理。 </p>
		<p class="2">
				<strong>3.1. getBytes(charset)</strong>
		</p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">这是java字符串处理的一个标准函数，其作用是将字符串所表示的字符按照charset编码，并以字节方式表示。注意字符串在java内存中总是按unicode编码存储的。比如"中文"，正常情况下（即没有错误的时候）存储为"<u>4e2d 6587</u>"，如果charset为"gbk"，则被编码为"<u>d6d0 cec4</u>"，然后返回字节"<u>d6 d0 ce c4</u>"。如果charset为"utf8"则最后是"<u>e4 b8 ad e6 96 87</u>"。如果是"iso8859-1"，则由于无法编码，最后返回 "<u>3f 3f</u>"（两个问号）。 </p>
		<p class="2">
				<strong>3.2. new String(charset)</strong>
		</p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">这是java字符串处理的另一个标准函数，和上一个函数的作用相反，将字节数组按照charset编码进行组合识别，最后转换为unicode存储。参考上述getBytes的例子，"gbk" 和"utf8"都可以得出正确的结果"<u>4e2d 6587</u>"，但iso8859-1最后变成了"<u>003f 003f</u>"（两个问号）。 </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 22pt">因为utf8可以用来表示/编码所有字符，所以new String( str.getBytes( "utf8" ), "utf8" ) === str，即完全可逆。 </p>
		<p class="2">
				<strong>3.3. setCharacterEncoding()</strong>
		</p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">该函数用来设置http请求或者相应的编码。 </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">对于request，是指提交内容的编码，指定后可以通过getParameter()则直接获得正确的字符串，如果不指定，则默认使用iso8859-1编码，需要进一步处理。参见下述"表单输入"。值得注意的是在执行setCharacterEncoding()之前，不能执行任何getParameter()。java doc上说明：This method must be called prior to reading request parameters or reading input using getReader()。而且，该指定只对POST方法有效，对GET方法无效。分析原因，应该是在执行第一个getParameter()的时候，java将会按照编码分析所有的提交内容，而后续的getParameter()不再进行分析，所以setCharacterEncoding()无效。而对于GET方法提交表单是，提交的内容在URL中，一开始就已经按照编码分析所有的提交内容，setCharacterEncoding()自然就无效。 </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">对于response，则是指定输出内容的编码，同时，该设置会传递给浏览器，告诉浏览器输出内容所采用的编码。 </p>
		<p class="2">
				<strong>3.4. 处理过程</strong>
		</p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">下面分析两个有代表性的例子，说明java对编码有关问题的处理方法。 </p>
		<p class="3">
				<strong>3.4.1. 表单输入</strong>
		</p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 22pt">User input<u>  *(gbk:d6d0 cec4)  </u>browser<u>  *(gbk:d6d0 cec4)  </u>web server<u>  iso8859-1(00d6 00d 000ce 00c4)  </u>class，需要在class中进行处理：getbytes("iso8859-1")为<u>d6 d0 ce c4</u>，new String("gbk")为<u>d6d0 cec4</u>，内存中以unicode编码则为<u>4e2d 6587</u>。 </p>
		<p class="MsoBodyTextIndent" style="MARGIN-LEFT: 43pt; TEXT-INDENT: -21pt; tab-stops: list 43.0pt">
				<span style="FONT-SIZE: 11pt; FONT-FAMILY: Wingdings">l</span> 用户输入的编码方式和页面指定的编码有关，也和用户的操作系统有关，所以是不确定的，上例以gbk为例。 </p>
		<p class="MsoBodyTextIndent" style="MARGIN-LEFT: 43pt; TEXT-INDENT: -21pt; tab-stops: list 43.0pt">
				<span style="FONT-SIZE: 11pt; FONT-FAMILY: Wingdings">l</span> 从browser到web server，可以在表单中指定提交内容时使用的字符集，否则会使用页面指定的编码。而如果在url中直接用?的方式输入参数，则其编码往往是操作系统本身的编码，因为这时和页面无关。上述仍旧以gbk编码为例。 </p>
		<p class="MsoBodyTextIndent" style="MARGIN-LEFT: 43pt; TEXT-INDENT: -21pt; tab-stops: list 43.0pt">
				<span style="FONT-SIZE: 11pt; FONT-FAMILY: Wingdings">l</span> Web server接收到的是字节流，默认时（getParameter）会以iso8859-1编码处理之，结果是不正确的，所以需要进行处理。但如果预先设置了编码（通过request. setCharacterEncoding ()），则能够直接获取到正确的结果。 </p>
		<p class="MsoBodyTextIndent" style="MARGIN-LEFT: 43pt; TEXT-INDENT: -21pt; tab-stops: list 43.0pt">
				<span style="FONT-SIZE: 11pt; FONT-FAMILY: Wingdings">l</span> 在页面中指定编码是个好习惯，否则可能失去控制，无法指定正确的编码。 </p>
		<p class="3">
				<strong>3.4.2. 文件编译</strong>
		</p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 22pt">假设文件是gbk编码保存的，而编译有两种编码选择：gbk或者iso8859-1，前者是中文windows的默认编码，后者是linux的默认编码，当然也可以在编译时指定编码。 </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 22pt">Jsp<u>  *(gbk:d6d0 cec4)  </u>java file<u>  *(gbk:d6d0 cec4)  </u>compiler read<u>  uincode(gbk: 4e2d 6587; iso8859-1: 00d6 00d 000ce 00c4)  </u>compiler write<u>  utf(gbk: e4b8ad e69687; iso8859-1: *)  </u>compiled file<u>  unicode(gbk: 4e2d 6587; iso8859-1: 00d6 00d 000ce 00c4)  </u>class。所以用gbk编码保存，而用iso8859-1编译的结果是不正确的。 </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 22pt">class<u>  unicode(4e2d 6587)  </u>system.out / jsp.out<u>  gbk(d6d0 cec4)  </u>os console / browser。 </p>
		<p class="MsoBodyTextIndent" style="MARGIN-LEFT: 43pt; TEXT-INDENT: -21pt; tab-stops: list 43.0pt">
				<span style="FONT-SIZE: 11pt; FONT-FAMILY: Wingdings">l</span> 文件可以以多种编码方式保存，中文windows下，默认为ansi/gbk。 </p>
		<p class="MsoBodyTextIndent" style="MARGIN-LEFT: 43pt; TEXT-INDENT: -21pt; tab-stops: list 43.0pt">
				<span style="FONT-SIZE: 11pt; FONT-FAMILY: Wingdings">l</span> 编译器读取文件时，需要得到文件的编码，如果未指定，则使用系统默认编码。一般class文件，是以系统默认编码保存的，所以编译不会出问题，但对于jsp文件，如果在中文windows下编辑保存，而部署在英文linux下运行/编译，则会出现问题。所以需要在jsp文件中用pageEncoding指定编码。 </p>
		<p class="MsoBodyTextIndent" style="MARGIN-LEFT: 43pt; TEXT-INDENT: -21pt; tab-stops: list 43.0pt">
				<span style="FONT-SIZE: 11pt; FONT-FAMILY: Wingdings">l</span> Java编译的时候会转换成统一的unicode编码处理，最后保存的时候再转换为utf编码。 </p>
		<p class="MsoBodyTextIndent" style="MARGIN-LEFT: 43pt; TEXT-INDENT: -21pt; tab-stops: list 43.0pt">
				<span style="FONT-SIZE: 11pt; FONT-FAMILY: Wingdings">l</span> 当系统输出字符的时候，会按指定编码输出，对于中文windows下，System.out将使用gbk编码，而对于response（浏览器），则使用jsp文件头指定的contentType，或者可以直接为response指定编码。同时，会告诉browser网页的编码。如果未指定，则会使用iso8859-1编码。对于中文，应该为browser指定输出字符串的编码。 </p>
		<p class="MsoBodyTextIndent" style="MARGIN-LEFT: 43pt; TEXT-INDENT: -21pt; tab-stops: list 43.0pt">
				<span style="FONT-SIZE: 11pt; FONT-FAMILY: Wingdings">l</span> browser显示网页的时候，首先使用response中指定的编码（jsp文件头指定的contentType最终也反映在response上），如果未指定，则会使用网页中meta项指定中的contentType。 </p>
		<p class="2">
				<strong>3.5. 几处设置</strong>
		</p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">对于web应用程序，和编码有关的设置或者函数如下。 </p>
		<p class="3">
				<strong>3.5.1. jsp编译</strong>
		</p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">指定文件的存储编码，很明显，该设置应该置于文件的开头。例如：&lt;%@page pageEncoding="GBK"%&gt;。另外，对于一般class文件，可以在编译的时候指定编码。 </p>
		<p class="3">
				<strong>3.5.2. jsp输出</strong>
		</p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">指定文件输出到browser是使用的编码，该设置也应该置于文件的开头。例如：&lt;%@ page contentType="text/html; charset= GBK" %&gt;。该设置和response.setCharacterEncoding("GBK")等效。 </p>
		<p class="3">
				<strong>3.5.3. meta设置</strong>
		</p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">指定网页使用的编码，该设置对静态网页尤其有作用。因为静态网页无法采用jsp的设置，而且也无法执行response.setCharacterEncoding()。例如：&lt;META http-equiv="Content-Type" content="text/html; charset=GBK" /&gt; </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">如果同时采用了jsp输出和meta设置两种编码指定方式，则jsp指定的优先。因为jsp指定的直接体现在response中。 </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">需要注意的是，apache有一个设置可以给无编码指定的网页指定编码，该指定等同于jsp的编码指定方式，所以会覆盖静态网页中的meta指定。所以有人建议关闭该设置。 </p>
		<p class="3">
				<strong>3.5.4. form设置</strong>
		</p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">当浏览器提交表单的时候，可以指定相应的编码。例如：&lt;form accept-charset= "gb2312"&gt;。一般不必不使用该设置，浏览器会直接使用网页的编码。 </p>
		<p class="1">
				<strong>4. 系统软件</strong>
		</p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">下面讨论几个相关的系统软件。 </p>
		<p class="2">
				<strong>4.1. mysql数据库</strong>
		</p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 22pt">很明显，要支持多语言，应该将数据库的编码设置成utf或者unicode，而utf更适合与存储。但是，如果中文数据中包含的英文字母很少，其实unicode更为适合。 </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 22pt">数据库的编码可以通过mysql的配置文件设置，例如default-character-set=utf8。还可以在数据库链接URL中设置，例如： useUnicode=true&amp;characterEncoding=UTF-8。注意这两者应该保持一致，在新的sql版本里，在数据库链接URL里可以不进行设置，但也不能是错误的设置。 </p>
		<p class="2">
				<strong>4.2. apache</strong>
		</p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">appache和编码有关的配置在httpd.conf中，例如AddDefaultCharset UTF-8。如前所述，该功能会将所有静态页面的编码设置为UTF-8，最好关闭该功能。 </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">另外，apache还有单独的模块来处理网页响应头，其中也可能对编码进行设置。 </p>
		<p class="2">
				<strong>4.3. linux默认编码</strong>
		</p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">这里所说的linux默认编码，是指运行时的环境变量。两个重要的环境变量是LC_ALL和LANG，默认编码会影响到java URLEncode的行为，下面有描述。 </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">建议都设置为"zh_CN.UTF-8"。 </p>
		<p class="2">
				<strong>4.4. 其它</strong>
		</p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">为了支持中文文件名，linux在加载磁盘时应该指定字符集，例如：mount /dev/hda5 /mnt/hda5/ -t ntfs -o iocharset=gb2312。 </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">另外，如前所述，使用GET方法提交的信息不支持request.setCharacterEncoding()，但可以通过tomcat的配置文件指定字符集，在tomcat的server.xml文件中，形如：&lt;Connector ... URIEncoding="GBK"/&gt;。这种方法将统一设置所有请求，而不能针对具体页面进行设置，也不一定和browser使用的编码相同，所以有时候并不是所期望的。 </p>
		<p class="1">
				<strong>5. URL地址</strong>
		</p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">URL地址中含有中文字符是很麻烦的，前面描述过使用GET方法提交表单的情况，使用GET方法时，参数就是包含在URL中。 </p>
		<p class="2">
				<strong>5.1. URL编码</strong>
		</p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">对于URL中的一些特殊字符，浏览器会自动进行编码。这些字符除了"/?&amp;"等外，还包括unicode字符，比如汉子。这时的编码比较特殊。 </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">IE有一个选项"总是使用UTF-8发送URL"，当该选项有效时，IE将会对特殊字符进行UTF-8编码，同时进行URL编码。如果改选项无效，则使用默认编码"GBK"，并且不进行URL编码。但是，对于URL后面的参数，则总是不进行编码，相当于UTF-8选项无效。比如"中文.html?a=中文"，当UTF-8选项有效时，将发送链接"%<u>e4%b8%ad%e6%96%87.html?a=\x4e\x2d\x65\x87</u>"；而UTF-8选项无效时，将发送链接"<u>\x4e\x2d\x65\x87.html?a=\x4e\x2d\x65\x87</u>"。注意后者前面的"中文"两个字只有4个字节，而前者却有18个字节，这主要时URL编码的原因。 </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 22pt">当web server（tomcat）接收到该链接时，将会进行URL解码，即去掉"%"，同时按照ISO8859-1编码（上面已经描述，可以使用URLEncoding来设置成其它编码）识别。上述例子的结果分别是"<u>\ue4\ub8\uad\ue6\u96\u87.html?a=\u4e\u2d\u65\u87</u>"和"<u>\u4e\u2d\u65\u87.html?a=\u4e\u2d\u65\u87</u>"，注意前者前面的"中文"两个字恢复成了6个字符。这里用"\u"，表示是unicode。 </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 22pt">所以，由于客户端设置的不同，相同的链接，在服务器上得到了不同结果。这个问题不少人都遇到，却没有很好的解决办法。所以有的网站会建议用户尝试关闭UTF-8选项。不过，下面会描述一个更好的处理办法。 </p>
		<p class="2">
				<strong>5.2. rewrite</strong>
		</p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">熟悉的人都知道，apache有一个功能强大的rewrite模块，这里不描述其功能。需要说明的是该模块会自动将URL解码（去除%），即完成上述web server（tomcat）的部分功能。有相关文档介绍说可以使用[NE]参数来关闭该功能，但我试验并未成功，可能是因为版本（我使用的是apache 2.0.54）问题。另外，当参数中含有"?&amp; "等符号的时候，该功能将导致系统得不到正常结果。 </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">rewrite本身似乎完全是采用字节处理的方式，而不考虑字符串的编码，所以不会带来编码问题。 </p>
		<p class="2">
				<strong>5.3. URLEncode.encode()</strong>
		</p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">这是Java本身提供对的URL编码函数，完成的工作和上述UTF-8选项有效时浏览器所做的工作相似。值得说明的是，java已经不赞成不指定编码来使用该方法（deprecated）。应该在使用的时候增加编码指定。 </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">当不指定编码的时候，该方法使用系统默认编码，这会导致软件运行结果得不确定。比如对于"中文"，当系统默认编码为"gb2312"时，结果是"%<u>4e%2d%65%87</u>"，而默认编码为"UTF-8"，结果却是"%<u>e4%b8%ad%e6%96%87</u>"，后续程序将难以处理。另外，这儿说的系统默认编码是由运行tomcat时的环境变量LC_ALL和LANG等决定的，曾经出现过tomcat重启后就出现乱码的问题，最后才郁闷的发现是因为修改修改了这两个环境变量。 </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">建议统一指定为"UTF-8"编码，可能需要修改相应的程序。 </p>
		<p class="2">
				<strong>5.4. 一个解决方案</strong>
		</p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">上面说起过，因为浏览器设置的不同，对于同一个链接，web server收到的是不同内容，而软件系统有无法知道这中间的区别，所以这一协议目前还存在缺陷。 </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">针对具体问题，不应该侥幸认为所有客户的IE设置都是UTF-8有效的，也不应该粗暴的建议用户修改IE设置，要知道，用户不可能去记住每一个web server的设置。所以，接下来的解决办法就只能是让自己的程序多一点智能：根据内容来分析编码是否UTF-8。 </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">比较幸运的是UTF-8编码相当有规律，所以可以通过分析传输过来的链接内容，来判断是否是正确的UTF-8字符，如果是，则以UTF-8处理之，如果不是，则使用客户默认编码（比如"GBK"），下面是一个判断是否UTF-8的例子，如果你了解相应规律，就容易理解。 </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">public static boolean isValidUtf8(byte[] b,int aMaxCount){ </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">       int lLen=b.length,lCharCount=0; </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">       for(int i=0;i&lt;lLen &amp;&amp; lCharCount&lt;aMaxCount;++lCharCount){ </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">              byte lByte=b[i++];//to fast operation, ++ now, ready for the following for(;;) </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">              if(lByte&gt;=0) continue;//&gt;=0 is normal ascii </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">              if(lByte&lt;(byte)0xc0 || lByte&gt;(byte)0xfd) return false; </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">              int lCount=lByte&gt;(byte)0xfc?5:lByte&gt;(byte)0xf8?4 </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">                     :lByte&gt;(byte)0xf0?3:lByte&gt;(byte)0xe0?2:1; </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">              if(i+lCount&gt;lLen) return false; </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">              for(int j=0;j&lt;lCount;++j,++i) if(b[i]&gt;=(byte)0xc0) return false; </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">       } </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">       return true; </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">} </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">相应地，一个使用上述方法的例子如下： </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">public static String getUrlParam(String aStr,String aDefaultCharset) </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">throws UnsupportedEncodingException{ </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">       if(aStr==null) return null; </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">       byte[] lBytes=aStr.getBytes("ISO-8859-1"); </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">       return new String(lBytes,StringUtil.isValidUtf8(lBytes)?"utf8":aDefaultCharset); </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">} </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">不过，该方法也存在缺陷，如下两方面： </p>
		<p class="MsoBodyTextIndent" style="MARGIN-LEFT: 42pt; TEXT-INDENT: -21pt; tab-stops: list 42.0pt">
				<span style="FONT-FAMILY: Wingdings">l</span> 没有包括对用户默认编码的识别，这可以根据请求信息的语言来判断，但不一定正确，因为我们有时候也会输入一些韩文，或者其他文字。 </p>
		<p class="MsoBodyTextIndent" style="MARGIN-LEFT: 42pt; TEXT-INDENT: -21pt; tab-stops: list 42.0pt">
				<span style="FONT-FAMILY: Wingdings">l</span> 可能会错误判断UTF-8字符，一个例子是"学习"两个字，其GBK编码是" <u>\xd1\xa7\xcf\xb0</u>"，如果使用上述isValidUtf8方法判断，将返回true。可以考虑使用更严格的判断方法，不过估计效果不大。 </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">有一个例子可以证明google也遇到了上述问题，而且也采用了和上述相似的处理方法，比如，如果在地址栏中输入"<a href="http://www.google.com/search?hl=zh-CN&amp;newwindow=1&amp;q=学习"><font color="#002c99">http://www.google.com/search?hl=zh-CN&amp;newwindow=1&amp;q=学习</font></a>"，google将无法正确识别，而其他汉字一般能够正常识别。 </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">最后，应该补充说明一下，如果不使用rewrite规则，或者通过表单提交数据，其实并不一定会遇到上述问题，因为这时可以在提交数据时指定希望的编码。另外，中文文件名确实会带来问题，应该谨慎使用。 </p>
		<p class="1">
				<strong>6. 其它</strong>
		</p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">下面描述一些和编码有关的其他问题。 </p>
		<p class="2">
				<strong>6.1. SecureCRT</strong>
		</p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">除了浏览器和控制台与编码有关外，一些客户端也很有关系。比如在使用SecureCRT连接linux时，应该让SecureCRT的显示编码（不同的session，可以有不同的编码设置）和linux的编码环境变量保持一致。否则看到的一些帮助信息，就可能是乱码。 </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">另外，mysql有自己的编码设置，也应该保持和SecureCRT的显示编码一致。否则通过SecureCRT执行sql语句的时候，可能无法处理中文字符，查询结果也会出现乱码。 </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">对于Utf-8文件，很多编辑器（比如记事本）会在文件开头增加三个不可见的标志字节，如果作为mysql的输入文件，则必须要去掉这三个字符。（用linux的vi保存可以去掉这三个字符）。一个有趣的现象是，在中文windows下，创建一个新txt文件，用记事本打开，输入"连通"两个字，保存，再打开，你会发现两个字没了，只留下一个小黑点。 </p>
		<p class="2">
				<strong>6.2. 过滤器</strong>
		</p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">如果需要统一设置编码，则通过filter进行设置是个不错的选择。在filter class中，可以统一为需要的请求或者回应设置编码。参加上述setCharacterEncoding()。这个类apache已经给出了可以直接使用的例子SetCharacterEncodingFilter。 </p>
		<p class="2">
				<strong>6.3. POST和GET</strong>
		</p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">很明显，以POST提交信息时，URL有更好的可读性，而且可以方便的使用setCharacterEncoding()来处理字符集问题。但GET方法形成的URL能够更容易表达网页的实际内容，也能够用于收藏。 </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">从统一的角度考虑问题，建议采用GET方法，这要求在程序中获得参数是进行特殊处理，而无法使用setCharacterEncoding()的便利，如果不考虑rewrite，就不存在IE的UTF-8问题，可以考虑通过设置URIEncoding来方便获取URL中的参数。 </p>
		<p class="2">
				<strong>6.4. 简繁体编码转换</strong>
		</p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">GBK同时包含简体和繁体编码，也就是说同一个字，由于编码不同，在GBK编码下属于两个字。有时候，为了正确取得完整的结果，应该将繁体和简体进行统一。可以考虑将UTF、GBK中的所有繁体字，转换为相应的简体字，BIG5编码的数据，也应该转化成相应的简体字。当然，仍旧以UTF编码存储。 </p>
		<p class="MsoBodyTextIndent" style="TEXT-INDENT: 21pt">例如，对于"语言 語言"，用UTF表示为"<u>\x</u><u>E8\xAF\xAD\xE8\xA8\x80 \xE8\xAA\x9E\xE8\xA8\x80</u>"，进行简繁体编码转换后应该是两个相同的 "<u>\x</u><u>E8\xAF\xAD\xE8\xA8\x80&gt;</u>"。 </p>
<img src ="http://www.blogjava.net/wml/aggbug/55985.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/wml/" target="_blank">wml</a> 2006-06-30 16:49 <a href="http://www.blogjava.net/wml/articles/55985.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>