<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>杰克，快跑！ &#187; J2EE</title>
	<atom:link href="http://blog.jackrun.com/archives/category/j2ee/feed" rel="self" type="application/rss+xml" />
	<link>http://blog.jackrun.com</link>
	<description>busy to live or busy to die</description>
	<lastBuildDate>Fri, 18 Jun 2010 09:15:30 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.2</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>正则表达式基础</title>
		<link>http://blog.jackrun.com/archives/50.html</link>
		<comments>http://blog.jackrun.com/archives/50.html#comments</comments>
		<pubDate>Fri, 08 Aug 2008 07:35:12 +0000</pubDate>
		<dc:creator>Peltason</dc:creator>
				<category><![CDATA[J2EE]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[正则表达式]]></category>

		<guid isPermaLink="false">http://www.jackrun.com/?p=50</guid>
		<description><![CDATA[一个正则表达式就是由普通字符（例如字符 a 到 z）以及特殊字符（称为元字符）组成的文字模式。该模式描述在查找文字主体时待匹配的一个或多个字符串。正则表达式作为一个模板，将某个字符模式与所搜索的字符串进行匹配。如：




JScript
VBScript
匹配


/^\[ \t]*$/
&#8220;^\[ \t]*$&#8221;
匹配一个空白行。


/\d{2}-\d{5}/
&#8220;\d{2}-\d{5}&#8221;
验证一个ID 号码是否由一个2位数字，一个连字符以及一个5位数字组成。


/&#60;(.*)&#62;.*&#60;\/\1&#62;/
&#8220;&#60;(.*)&#62;.*&#60;\/\1&#62;&#8221;
匹配一个 HTML 标记。





下表是元字符及其在正则表达式上下文中的行为的一个完整列表：



字符
描述


\
将下一个字符标记为一个特殊字符、或一个原义字符、或一个 向后引用、或一个八进制转义符。例如，&#8217;n&#8217; 匹配字符 &#8220;n&#8221;。&#8217;\n&#8217; 匹配一个换行符。序列 &#8216;\\&#8217; 匹配 &#8220;\&#8221; 而 &#8220;\(&#8221; 则匹配 &#8220;(&#8220;。


^
匹配输入字符串的开始位置。如果设置了 RegExp 对象的 Multiline 属性，^ 也匹配 &#8216;\n&#8217; 或 &#8216;\r&#8217; 之后的位置。


$
匹配输入字符串的结束位置。如果设置了RegExp 对象的 Multiline 属性，$ 也匹配 &#8216;\n&#8217; 或 &#8216;\r&#8217; 之前的位置。


*
匹配前面的子表达式零次或多次。例如，zo* 能匹配 &#8220;z&#8221; 以及 &#8220;zoo&#8221;。* 等价于{0,}。


+
匹配前面的子表达式一次或多次。例如，&#8217;zo+&#8217; 能匹配 &#8220;zo&#8221; 以及 &#8220;zoo&#8221;，但不能匹配 &#8220;z&#8221;。+ 等价于 {1,}。


?
匹配前面的子表达式零次或一次。例如，&#8221;do(es)?&#8221; 可以匹配 &#8220;do&#8221; 或 &#8220;does&#8221; 中的&#8221;do&#8221; 。? [...]]]></description>
			<content:encoded><![CDATA[<p><span>一个正则表达式就是由普通字符（例如字符 a 到 z）以及特殊字符（称为元字符）组成的文字模式。该模式描述在查找文字主体时待匹配的一个或多个字符串。正则表达式作为一个模板，将某个字符模式与所搜索的字符串进行匹配。如：</span></p>
<div class="tablediv">
<table border="1" cellspacing="0">
<tbody>
<tr valign="top">
<th width="30%">JScript</th>
<th width="30%">VBScript</th>
<th width="40%">匹配</th>
</tr>
<tr valign="top">
<td width="30%">/^\[ \t]*$/</td>
<td width="30%">&#8220;^\[ \t]*$&#8221;</td>
<td width="40%">匹配一个空白行。</td>
</tr>
<tr valign="top">
<td width="30%">/\d{2}-\d{5}/</td>
<td width="30%">&#8220;\d{2}-\d{5}&#8221;</td>
<td width="40%">验证一个ID 号码是否由一个2位数字，一个连字符以及一个5位数字组成。</td>
</tr>
<tr valign="top">
<td width="30%">/&lt;(.*)&gt;.*&lt;\/\1&gt;/</td>
<td width="30%">&#8220;&lt;(.*)&gt;.*&lt;\/\1&gt;&#8221;</td>
<td width="40%">匹配一个 HTML 标记。</td>
</tr>
</tbody>
</table>
</div>
<p><span id="more-50"></span></p>
<p>下表是元字符及其在正则表达式上下文中的行为的一个完整列表：</p>
<table border="1" cellspacing="0">
<tbody>
<tr valign="top">
<th width="16%">字符</th>
<th width="84%">描述</th>
</tr>
<tr valign="top">
<td width="16%">\</td>
<td width="84%">将下一个字符标记为一个特殊字符、或一个原义字符、或一个 向后引用、或一个八进制转义符。例如，&#8217;n&#8217; 匹配字符 &#8220;n&#8221;。&#8217;\n&#8217; 匹配一个换行符。序列 &#8216;\\&#8217; 匹配 &#8220;\&#8221; 而 &#8220;\(&#8221; 则匹配 &#8220;(&#8220;。</td>
</tr>
<tr valign="top">
<td width="16%">^</td>
<td width="84%">匹配输入字符串的开始位置。如果设置了 <strong>RegExp</strong> 对象的 <strong>Multiline</strong> 属性，^ 也匹配 &#8216;\n&#8217; 或 &#8216;\r&#8217; 之后的位置。</td>
</tr>
<tr valign="top">
<td width="16%">$</td>
<td width="84%">匹配输入字符串的结束位置。如果设置了<strong>RegExp</strong> 对象的 <strong>Multiline</strong> 属性，$ 也匹配 &#8216;\n&#8217; 或 &#8216;\r&#8217; 之前的位置。</td>
</tr>
<tr valign="top">
<td width="16%">*</td>
<td width="84%">匹配前面的子表达式零次或多次。例如，zo* 能匹配 &#8220;z&#8221; 以及 &#8220;zoo&#8221;。* 等价于{0,}。</td>
</tr>
<tr valign="top">
<td width="16%">+</td>
<td width="84%">匹配前面的子表达式一次或多次。例如，&#8217;zo+&#8217; 能匹配 &#8220;zo&#8221; 以及 &#8220;zoo&#8221;，但不能匹配 &#8220;z&#8221;。+ 等价于 {1,}。</td>
</tr>
<tr valign="top">
<td width="16%">?</td>
<td width="84%">匹配前面的子表达式零次或一次。例如，&#8221;do(es)?&#8221; 可以匹配 &#8220;do&#8221; 或 &#8220;does&#8221; 中的&#8221;do&#8221; 。? 等价于 {0,1}。</td>
</tr>
<tr valign="top">
<td width="16%">{<em>n</em>}</td>
<td width="84%"><em>n</em> 是一个非负整数。匹配确定的 <em>n</em> 次。例如，&#8217;o{2}&#8217; 不能匹配 &#8220;Bob&#8221; 中的 &#8216;o&#8217;，但是能匹配 &#8220;food&#8221; 中的两个 o。</td>
</tr>
<tr valign="top">
<td width="16%">{<em>n</em>,}</td>
<td width="84%"><em>n</em> 是一个非负整数。至少匹配<em>n</em> 次。例如，&#8217;o{2,}&#8217; 不能匹配 &#8220;Bob&#8221; 中的 &#8216;o&#8217;，但能匹配 &#8220;foooood&#8221; 中的所有 o。&#8217;o{1,}&#8217; 等价于 &#8216;o+&#8217;。&#8217;o{0,}&#8217; 则等价于 &#8216;o*&#8217;。</td>
</tr>
<tr valign="top">
<td width="16%">{<em>n</em>,<em>m</em>}</td>
<td width="84%"><em>m</em> 和 <em>n</em> 均为非负整数，其中<em>n</em> &lt;= <em>m</em>。最少匹配 <em>n</em> 次且最多匹配 <em>m</em> 次。例如，&#8221;o{1,3}&#8221; 将匹配 &#8220;fooooood&#8221; 中的前三个 o。&#8217;o{0,1}&#8217; 等价于 &#8216;o?&#8217;。请注意在逗号和两个数之间不能有空格。</td>
</tr>
<tr valign="top">
<td width="16%">?</td>
<td width="84%">当该字符紧跟在任何一个其他限制符 (*, +, ?, {<em>n</em>}, {<em>n</em>,}, {<em>n</em>,<em>m</em>}) 后面时，匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串，而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如，对于字符串 &#8220;oooo&#8221;，&#8217;o+?&#8217; 将匹配单个 &#8220;o&#8221;，而 &#8216;o+&#8217; 将匹配所有 &#8216;o&#8217;。</td>
</tr>
<tr valign="top">
<td width="16%">.</td>
<td width="84%">匹配除 &#8220;\n&#8221; 之外的任何单个字符。要匹配包括 &#8216;\n&#8217; 在内的任何字符，请使用象 &#8216;[.\n]&#8216; 的模式。</td>
</tr>
<tr valign="top">
<td width="16%">(<em>pattern</em>)</td>
<td width="84%">匹配 <em>pattern</em> 并获取这一匹配。所获取的匹配可以从产生的 Matches 集合得到，在VBScript 中使用 <strong>SubMatches</strong> 集合，在JScript 中则使用 <strong>$0</strong>…<strong>$9</strong> 属性。要匹配圆括号字符，请使用 &#8216;\(&#8216; 或 &#8216;\)&#8217;。</td>
</tr>
<tr valign="top">
<td width="16%">(?:<em>pattern</em>)</td>
<td width="84%">匹配 <em>pattern</em> 但不获取匹配结果，也就是说这是一个非获取匹配，不进行存储供以后使用。这在使用 &#8220;或&#8221; 字符 (|) 来组合一个模式的各个部分是很有用。例如， &#8216;industr(?:y|ies) 就是一个比 &#8216;industry|industries&#8217; 更简略的表达式。</td>
</tr>
<tr valign="top">
<td width="16%">(?=<em>pattern</em>)</td>
<td width="84%">正向预查，在任何匹配 <em>pattern</em> 的字符串开始处匹配查找字符串。这是一个非获取匹配，也就是说，该匹配不需要获取供以后使用。例如，&#8217;Windows (?=95|98|NT|2000)&#8217; 能匹配 &#8220;Windows 2000&#8243; 中的 &#8220;Windows&#8221; ，但不能匹配 &#8220;Windows 3.1&#8243; 中的 &#8220;Windows&#8221;。预查不消耗字符，也就是说，在一个匹配发生后，在最后一次匹配之后立即开始下一次匹配的搜索，而不是从包含预查的字符之后开始。</td>
</tr>
<tr valign="top">
<td width="16%">(?!<em>pattern</em>)</td>
<td width="84%">负向预查，在任何不匹配 <em>pattern</em> 的字符串开始处匹配查找字符串。这是一个非获取匹配，也就是说，该匹配不需要获取供以后使用。例如&#8217;Windows (?!95|98|NT|2000)&#8217; 能匹配 &#8220;Windows 3.1&#8243; 中的 &#8220;Windows&#8221;，但不能匹配 &#8220;Windows 2000&#8243; 中的 &#8220;Windows&#8221;。预查不消耗字符，也就是说，在一个匹配发生后，在最后一次匹配之后立即开始下一次匹配的搜索，而不是从包含预查的字符之后开始</td>
</tr>
<tr valign="top">
<td width="16%"><em>x</em>|<em>y</em></td>
<td width="84%">匹配 <em>x</em> 或 <em>y</em>。例如，&#8217;z|food&#8217; 能匹配 &#8220;z&#8221; 或 &#8220;food&#8221;。&#8217;(z|f)ood&#8217; 则匹配 &#8220;zood&#8221; 或 &#8220;food&#8221;。</td>
</tr>
<tr valign="top">
<td width="16%">[<em>xyz</em>]</td>
<td width="84%">字符集合。匹配所包含的任意一个字符。例如， &#8216;[abc]&#8216; 可以匹配 &#8220;plain&#8221; 中的 &#8216;a&#8217;。</td>
</tr>
<tr valign="top">
<td width="16%">[^<em>xyz</em>]</td>
<td width="84%">负值字符集合。匹配未包含的任意字符。例如， &#8216;[^abc]&#8216; 可以匹配 &#8220;plain&#8221; 中的&#8217;p'。</td>
</tr>
<tr valign="top">
<td width="16%">[<em>a-z</em>]</td>
<td width="84%">字符范围。匹配指定范围内的任意字符。例如，&#8217;[a-z]&#8216; 可以匹配 &#8216;a&#8217; 到 &#8216;z&#8217; 范围内的任意小写字母字符。</td>
</tr>
<tr valign="top">
<td width="16%">[^<em>a-z</em>]</td>
<td width="84%">负值字符范围。匹配任何不在指定范围内的任意字符。例如，&#8217;[^a-z]&#8216; 可以匹配任何不在 &#8216;a&#8217; 到 &#8216;z&#8217; 范围内的任意字符。</td>
</tr>
<tr valign="top">
<td width="16%">\b</td>
<td width="84%">匹配一个单词边界，也就是指单词和空格间的位置。例如， &#8216;er\b&#8217; 可以匹配&#8221;never&#8221; 中的 &#8216;er&#8217;，但不能匹配 &#8220;verb&#8221; 中的 &#8216;er&#8217;。</td>
</tr>
<tr valign="top">
<td width="16%">\B</td>
<td width="84%">匹配非单词边界。&#8217;er\B&#8217; 能匹配 &#8220;verb&#8221; 中的 &#8216;er&#8217;，但不能匹配 &#8220;never&#8221; 中的 &#8216;er&#8217;。</td>
</tr>
<tr valign="top">
<td width="16%">\c<em>x</em></td>
<td width="84%">匹配由 <em>x</em> 指明的控制字符。例如， \cM 匹配一个 Control-M 或回车符。<em>x</em> 的值必须为 A-Z 或 a-z 之一。否则，将 c 视为一个原义的 &#8216;c&#8217; 字符。</td>
</tr>
<tr valign="top">
<td width="16%">\d</td>
<td width="84%">匹配一个数字字符。等价于 [0-9]。</td>
</tr>
<tr valign="top">
<td width="16%">\D</td>
<td width="84%">匹配一个非数字字符。等价于 [^0-9]。</td>
</tr>
<tr valign="top">
<td width="16%">\f</td>
<td width="84%">匹配一个换页符。等价于 \x0c 和 \cL。</td>
</tr>
<tr valign="top">
<td width="16%">\n</td>
<td width="84%">匹配一个换行符。等价于 \x0a 和 \cJ。</td>
</tr>
<tr valign="top">
<td width="16%">\r</td>
<td width="84%">匹配一个回车符。等价于 \x0d 和 \cM。</td>
</tr>
<tr valign="top">
<td width="16%">\s</td>
<td width="84%">匹配任何空白字符，包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。</td>
</tr>
<tr valign="top">
<td width="16%">\S</td>
<td width="84%">匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。</td>
</tr>
<tr valign="top">
<td width="16%">\t</td>
<td width="84%">匹配一个制表符。等价于 \x09 和 \cI。</td>
</tr>
<tr valign="top">
<td width="16%">\v</td>
<td width="84%">匹配一个垂直制表符。等价于 \x0b 和 \cK。</td>
</tr>
<tr valign="top">
<td width="16%">\w</td>
<td width="84%">匹配包括下划线的任何单词字符。等价于&#8217;[A-Za-z0-9_]&#8216;。</td>
</tr>
<tr valign="top">
<td width="16%">\W</td>
<td width="84%">匹配任何非单词字符。等价于 &#8216;[^A-Za-z0-9_]&#8216;。</td>
</tr>
<tr valign="top">
<td width="16%">\x<em>n</em></td>
<td width="84%">匹配 <em>n</em>，其中 <em>n</em> 为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如，&#8217;\x41&#8242; 匹配 &#8220;A&#8221;。&#8217;\x041&#8242; 则等价于 &#8216;\x04&#8242; &amp; &#8220;1&#8243;。正则表达式中可以使用 ASCII 编码。.</td>
</tr>
<tr valign="top">
<td width="16%">\<em>num</em></td>
<td width="84%">匹配 <em>num</em>，其中 <em>num</em> 是一个正整数。对所获取的匹配的引用。例如，&#8217;(.)\1&#8242; 匹配两个连续的相同字符。</td>
</tr>
<tr valign="top">
<td width="16%">\<em>n</em></td>
<td width="84%">标识一个八进制转义值或一个向后引用。如果 \<em>n</em> 之前至少 <em>n</em> 个获取的子表达式，则 <em>n</em> 为向后引用。否则，如果 <em>n</em> 为八进制数字 (0-7)，则 <em>n</em> 为一个八进制转义值。</td>
</tr>
<tr valign="top">
<td width="16%">\<em>nm</em></td>
<td width="84%">标识一个八进制转义值或一个向后引用。如果 \<em>nm</em> 之前至少有 <em>nm</em> 个获得子表达式，则 <em>nm</em> 为向后引用。如果 \<em>nm</em> 之前至少有 <em>n</em> 个获取，则 <em>n</em> 为一个后跟文字 <em>m</em> 的向后引用。如果前面的条件都不满足，若 <em>n</em> 和 <em>m</em> 均为八进制数字 (0-7)，则 \<em>nm</em> 将匹配八进制转义值 <em>nm</em>。</td>
</tr>
<tr valign="top">
<td width="16%">\<em>nml</em></td>
<td width="84%">如果 <em>n</em> 为八进制数字 (0-3)，且 <em>m</em> 和 <em>l</em> 均为八进制数字 (0-7)，则匹配八进制转义值 <em>nml。</em></td>
</tr>
<tr valign="top">
<td width="16%">\u<em>n</em></td>
<td width="84%">匹配 <em>n</em>，其中 <em>n</em> 是一个用四个十六进制数字表示的 Unicode 字符。例如， \u00A9 匹配版权符号 (©)。</td>
</tr>
</tbody>
</table>
<p>下面看几个例子：<br />
&#8220;^The&#8221;：表示所有以&#8221;The&#8221;开始的字符串（&#8221;There&#8221;，&#8221;The cat&#8221;等）；<br />
&#8220;of despair$&#8221;：表示所以以&#8221;of despair&#8221;结尾的字符串；<br />
&#8220;^abc$&#8221;：表示开始和结尾都是&#8221;abc&#8221;的字符串——呵呵，只有&#8221;abc&#8221;自己了；<br />
&#8220;notice&#8221;：表示任何包含&#8221;notice&#8221;的字符串。</p>
<p>&#8216;*&#8217;，&#8217;+'和&#8217;?'这三个符号，表示一个或一序列字符重复出现的次数。它们分别表示“没有或<br />
更多”，“一次或更多”还有“没有或一次”。下面是几个例子：</p>
<p>&#8220;ab*&#8221;：表示一个字符串有一个a后面跟着零个或若干个b。（&#8221;a&#8221;, &#8220;ab&#8221;, &#8220;abbb&#8221;,……）；<br />
&#8220;ab+&#8221;：表示一个字符串有一个a后面跟着至少一个b或者更多；<br />
&#8220;ab?&#8221;：表示一个字符串有一个a后面跟着零个或者一个b；<br />
&#8220;a?b+$&#8221;：表示在字符串的末尾有零个或一个a跟着一个或几个b。</p>
<p>也可以使用范围，用大括号括起，用以表示重复次数的范围。</p>
<p>&#8220;ab{2}&#8221;：表示一个字符串有一个a跟着2个b（&#8221;abb&#8221;）；<br />
&#8220;ab{2,}&#8221;：表示一个字符串有一个a跟着至少2个b；<br />
&#8220;ab{3,5}&#8221;：表示一个字符串有一个a跟着3到5个b。</p>
<p>请注意，你必须指定范围的下限（如：&#8221;{0,2}&#8221;而不是&#8221;{,2}&#8221;）。还有，你可能注意到了，&#8217;*'，&#8217;+'和<br />
&#8216;?&#8217;相当于&#8221;{0,}&#8221;，&#8221;{1,}&#8221;和&#8221;{0,1}&#8221;。<br />
还有一个&#8217;¦&#8217;，表示“或”操作：</p>
<p>&#8220;hi¦hello&#8221;：表示一个字符串里有&#8221;hi&#8221;或者&#8221;hello&#8221;；<br />
&#8220;(b¦cd)ef&#8221;：表示&#8221;bef&#8221;或&#8221;cdef&#8221;；<br />
&#8220;(a¦b)*c&#8221;：表示一串&#8221;a&#8221;"b&#8221;混合的字符串后面跟一个&#8221;c&#8221;；</p>
<p>&#8216;.&#8217;可以替代任何字符：</p>
<p>&#8220;a.[0-9]&#8220;：表示一个字符串有一个&#8221;a&#8221;后面跟着一个任意字符和一个数字；<br />
&#8220;^.{3}$&#8221;：表示有任意三个字符的字符串（长度为3个字符）；</p>
<p>方括号表示某些字符允许在一个字符串中的某一特定位置出现：</p>
<p>&#8220;[ab]&#8220;：表示一个字符串有一个&#8221;a&#8221;或&#8221;b&#8221;（相当于&#8221;a¦b&#8221;）；<br />
&#8220;[a-d]&#8220;：表示一个字符串包含小写的&#8217;a'到&#8217;d'中的一个（相当于&#8221;a¦b¦c¦d&#8221;或者&#8221;[abcd]&#8220;）；<br />
&#8220;^[a-zA-Z]&#8220;：表示一个以字母开头的字符串；<br />
&#8220;[0-9]%&#8221;：表示一个百分号前有一位的数字；<br />
&#8220;,[a-zA-Z0-9]$&#8221;：表示一个字符串以一个逗号后面跟着一个字母或数字结束。</p>
<p>你也可以在方括号里用&#8217;^'表示不希望出现的字符，&#8217;^'应在方括号里的第一位。（如：&#8221;%[^a-zA-Z]%&#8221;表<br />
示两个百分号中不应该出现字母）。</p>
<p>为了逐字表达，必须在&#8221;^.$()¦*+?{\&#8221;这些字符前加上转移字符&#8217;\'。</p>
<p>请注意在方括号中，不需要转义字符。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.jackrun.com/archives/50.html/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>使用XStream对Java Object和XML进行转换</title>
		<link>http://blog.jackrun.com/archives/53.html</link>
		<comments>http://blog.jackrun.com/archives/53.html#comments</comments>
		<pubDate>Wed, 16 Jul 2008 06:32:13 +0000</pubDate>
		<dc:creator>Peltason</dc:creator>
				<category><![CDATA[J2EE]]></category>
		<category><![CDATA[软件]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[XML]]></category>
		<category><![CDATA[XStream]]></category>

		<guid isPermaLink="false">http://www.jackrun.com/?p=53</guid>
		<description><![CDATA[官方网站:http://xstream.codehaus.org/
将xstream和xpp3的jar包加入lib中
XStream x = new XStream() ;
List userList = new ArrayList() ;
User user = new User() ;
user.setUserName(&#8220;peltason&#8221;) ;
user.setPassword(&#8220;123&#8243;) ;
User user2 = new User() ;
user2.setUserName(&#8220;jack&#8221;) ;
user2.setPassword(&#8220;321&#8243;) ;
x.alias(&#8220;user&#8221;, User.class) ;
userList.add(user) ;
userList.add(user2) ;
System.out.println( x.toXML(userList) ) ;
生成的XML文件内容:
xml 代码
peltason
123
jack
321
]]></description>
			<content:encoded><![CDATA[<p>官方网站:<a href="http://xstream.codehaus.org/" target="_blank">http://xstream.codehaus.org/</a></p>
<p>将xstream和xpp3的jar包加入lib中<br />
XStream x = new XStream() ;<br />
List userList = new ArrayList() ;<br />
User user = new User() ;<br />
user.setUserName(&#8220;peltason&#8221;) ;<br />
user.setPassword(&#8220;123&#8243;) ;<br />
User user2 = new User() ;<br />
user2.setUserName(&#8220;jack&#8221;) ;<br />
user2.setPassword(&#8220;321&#8243;) ;<br />
x.alias(&#8220;user&#8221;, User.class) ;<br />
userList.add(user) ;<br />
userList.add(user2) ;<br />
System.out.println( x.toXML(userList) ) ;</p>
<p>生成的XML文件内容:<br />
xml 代码<br />
peltason</p>
<p>123</p>
<p>jack</p>
<p>321</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.jackrun.com/archives/53.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>getOutputStream() has already been called for this response的问题</title>
		<link>http://blog.jackrun.com/archives/69.html</link>
		<comments>http://blog.jackrun.com/archives/69.html#comments</comments>
		<pubDate>Fri, 09 May 2008 03:09:07 +0000</pubDate>
		<dc:creator>Peltason</dc:creator>
				<category><![CDATA[J2EE]]></category>
		<category><![CDATA[Exception]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[ouputStream]]></category>

		<guid isPermaLink="false">http://www.jackrun.com/?p=69</guid>
		<description><![CDATA[今天做一个jsp的验证码程序，把验证码的绘制写在一个jsp里，发现在调用时总是出现getOutputStream() has already been called for this response异常，搞得一头雾水，看似自己重复调用了，因为在程序最后是这样输出的
ImageIO.write(image, &#8220;JPEG&#8221;, response.getOutputStream());
但是仔细检查了程序，并没有问题，不过最后还是解决了，问题出在%&#62;与&#60;%之间的空行，把换行都去掉就OK了。
因为Application Server在处理编译jsp时对于％&#62;和&#60;％之间的内容一般是原样输出，而且默认是PrintWriter，而你却要进行流输出：ServletOutputStream，这样做相当于试图在Servlet中使用两种输出机制，就会发生getOutputStream() has already been called for this response的错误
详细请见《More Java Pitfill》一书的第二部分 Web层Item 33：试图在Servlet中使用两种输出机制 270
而且如果有换行，对于文本文件没有什么问题，但是对于其它格式，比如AutoCAD、Word、Excel等文件
下载下来的文件中就会多出一些换行符0&#215;0d和0&#215;0a，这样可能导致某些格式的文件无法打开，有些也可以正常打开
]]></description>
			<content:encoded><![CDATA[<p>今天做一个jsp的验证码程序，把验证码的绘制写在一个jsp里，发现在调用时总是出现getOutputStream() has already been called for this response异常，搞得一头雾水，看似自己重复调用了，因为在程序最后是这样输出的<br />
ImageIO.write(image, &#8220;JPEG&#8221;, response.getOutputStream());<br />
但是仔细检查了程序，并没有问题，不过最后还是解决了，问题出在%&gt;与&lt;%之间的空行，把换行都去掉就OK了。<br />
因为Application Server在处理编译jsp时对于％&gt;和&lt;％之间的内容一般是原样输出，而且默认是PrintWriter，而你却要进行流输出：ServletOutputStream，这样做相当于试图在Servlet中使用两种输出机制，就会发生getOutputStream() has already been called for this response的错误<br />
详细请见《More Java Pitfill》一书的第二部分 Web层Item 33：试图在Servlet中使用两种输出机制 270<br />
而且如果有换行，对于文本文件没有什么问题，但是对于其它格式，比如AutoCAD、Word、Excel等文件<br />
下载下来的文件中就会多出一些换行符0&#215;0d和0&#215;0a，这样可能导致某些格式的文件无法打开，有些也可以正常打开</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.jackrun.com/archives/69.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Guice 1.0 用户指南</title>
		<link>http://blog.jackrun.com/archives/72.html</link>
		<comments>http://blog.jackrun.com/archives/72.html#comments</comments>
		<pubDate>Fri, 04 Apr 2008 09:20:40 +0000</pubDate>
		<dc:creator>Peltason</dc:creator>
				<category><![CDATA[J2EE]]></category>
		<category><![CDATA[Guice]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[Spring]]></category>
		<category><![CDATA[依赖注入]]></category>

		<guid isPermaLink="false">http://www.jackrun.com/?p=72</guid>
		<description><![CDATA[Guice 1.0 用户指南
（20070326 王咏刚 译自http://docs.google.com/Doc?id=dd2fhx4z_5df5hw8）
(非常酷的Google果汁——Guice 发布一段时间了，今天非常快速地把它的用户指南翻译成了中文，分享给大家。因为只花了3个小时翻译，也没有更多时间校对，肯定有不少错误，希望有心人指正。)
Guice (读作&#8221;juice&#8221;)是超轻量级的，下一代的，为Java 5及后续版本设计的依赖注入容器。

简介
Java 企业应用开发社区在连接对象方面花了很大功夫。你的Web应用如何访问中间层服务？你的服务如何连接到登录用户和交易管理器？关于这个问题你会发现很多通 用的和特定的解决方案。有一些方案依赖于模式，另一些则使用框架。所有这些方案都会不同程度地引入一些难于测试或者程式化代码重复的问题。你马上就会看 到，Guice 在这方面是全世界做得最好的：非常容易进行单元测试，最大程度的灵活性和可维护性，以及最少的代码重复。
我们使 用一个假想的、简单的例子来展示 Guice 优于其他一些你可能已经熟悉的经典方法的地方。下面的例子过于简单，尽管它展示了许多显而易见的优点，但其实它还远没有发挥出 Guice 的全部潜能。我们希望，随着你的应用开发的深入，Guice 的优越性也会更多地展现出来。
在这个例子中，一个客户对象依赖于一个服务接口。服务接口可以提供任意的服务。我们只是调用这个服务而已。
public interface Service {
void go();
}
对于这个服务接口，我们有一个缺省的实现，但客户对象不应该直接依赖于这个缺省实现。如果我们将来打算使用一个不同的服务实现，我们不希望回过头来修改所有的客户代码。
public class ServiceImpl implements Service {
public void go() {
&#8230;
}
}
我们还有一个虚拟的、可用于单元测试的服务实现。
public class MockService implements Service {
private boolean gone = false;
public void go() {
gone = true;
}
public boolean isGone() {
return gone;
}
}
简单工厂模式
在发现依赖注入之前，最常用的是工厂模式。除了服务接口之外，你还有一个既可以向客户提供服务对象，也可以向测试程序提供模拟对象的工厂接口。在这里我们会将服务实现为一个单件模式，以便让示例尽量简化。
public class ServiceFactory {
private ServiceFactory() {}
private static Service [...]]]></description>
			<content:encoded><![CDATA[<p>Guice 1.0 用户指南</p>
<p>（20070326 王咏刚 译自<a href="http://docs.google.com/Doc?id=dd2fhx4z_5df5hw8" target="_blank">http://docs.google.com/Doc?id=dd2fhx4z_5df5hw8</a>）</p>
<p>(非常酷的Google果汁——Guice 发布一段时间了，今天非常快速地把它的用户指南翻译成了中文，分享给大家。因为只花了3个小时翻译，也没有更多时间校对，肯定有不少错误，希望有心人指正。)</p>
<p>Guice (读作&#8221;juice&#8221;)是超轻量级的，下一代的，为Java 5及后续版本设计的依赖注入容器。</p>
<p><span id="more-72"></span><br />
简介</p>
<p>Java 企业应用开发社区在连接对象方面花了很大功夫。你的Web应用如何访问中间层服务？你的服务如何连接到登录用户和交易管理器？关于这个问题你会发现很多通 用的和特定的解决方案。有一些方案依赖于模式，另一些则使用框架。所有这些方案都会不同程度地引入一些难于测试或者程式化代码重复的问题。你马上就会看 到，Guice 在这方面是全世界做得最好的：非常容易进行单元测试，最大程度的灵活性和可维护性，以及最少的代码重复。</p>
<p>我们使 用一个假想的、简单的例子来展示 Guice 优于其他一些你可能已经熟悉的经典方法的地方。下面的例子过于简单，尽管它展示了许多显而易见的优点，但其实它还远没有发挥出 Guice 的全部潜能。我们希望，随着你的应用开发的深入，Guice 的优越性也会更多地展现出来。</p>
<p>在这个例子中，一个客户对象依赖于一个服务接口。服务接口可以提供任意的服务。我们只是调用这个服务而已。<br />
public interface Service {<br />
void go();<br />
}</p>
<p>对于这个服务接口，我们有一个缺省的实现，但客户对象不应该直接依赖于这个缺省实现。如果我们将来打算使用一个不同的服务实现，我们不希望回过头来修改所有的客户代码。<br />
public class ServiceImpl implements Service {</p>
<p>public void go() {<br />
&#8230;<br />
}</p>
<p>}</p>
<p>我们还有一个虚拟的、可用于单元测试的服务实现。<br />
public class MockService implements Service {</p>
<p>private boolean gone = false;</p>
<p>public void go() {<br />
gone = true;<br />
}</p>
<p>public boolean isGone() {<br />
return gone;<br />
}<br />
}</p>
<p>简单工厂模式<br />
在发现依赖注入之前，最常用的是工厂模式。除了服务接口之外，你还有一个既可以向客户提供服务对象，也可以向测试程序提供模拟对象的工厂接口。在这里我们会将服务实现为一个单件模式，以便让示例尽量简化。<br />
public class ServiceFactory {</p>
<p>private ServiceFactory() {}</p>
<p>private static Service instance = new ServiceImpl();</p>
<p>public static Service getInstance() {<br />
return instance;<br />
}</p>
<p>public static void setInstance(Service service) {<br />
instance = service;<br />
}<br />
}</p>
<p>客户程序每次需要服务对象时就直接从工厂获取。<br />
public class Client {</p>
<p>public void go() {<br />
Service service = ServiceFactory.getInstance();<br />
service.go();<br />
}<br />
}</p>
<p>客 户程序足够简单。但客户程序的单元测试代码必须将一个虚拟对象传入工厂，同时要记得在测试后清理。在我们这个简单的例子里，这不算什么难事儿。但当你增加 了越来越多的客户和服务代码后，所有这些虚拟代码和清理代码会让单元测试的开发一团糟。此外，如果你忘记在测试后清理，其他测试可能会得到与预期不符的结 果。更糟的是，测试的成功与失败可能取决于他们被执行的顺序。<br />
public void testClient() {<br />
Service previous = ServiceFactory.getInstance();<br />
try {<br />
final MockService mock = new MockService();<br />
ServiceFactory.setInstance(mock);<br />
Client client = new Client();<br />
client.go();<br />
assertTrue(mock.isGone());<br />
}finally {<br />
ServiceFactory.setInstance(previous);<br />
}<br />
}</p>
<p>最后，注意服务工厂的API把我们限制在了单件这一种应用模式上。即便 getInstance() 可以返回多个实例， setInstance() 也会束缚我们的手脚。转换到非单件模式也意味着转换到了一套更复杂的API。</p>
<p>手工依赖注入</p>
<p>依赖注入模式的目标之一是使单元测试更简单。我们不需要特殊的框架就可以实践依赖注入模式。依靠手工编写代码，你可以得到该模式大约80%的好处。</p>
<p>当上例中的客户代码向工厂对象请求一个服务时，根据依赖注入模式，客户代码希望它所依赖的对象实例可以被传入自己。也就是说：不要调用我，我会调用你。</p>
<p>public class Client {</p>
<p>private final Service service;</p>
<p>public Client(Service service) {<br />
this.service = service;<br />
}</p>
<p>public void go() {<br />
service.go();<br />
}<br />
}</p>
<p>这让我们的单元测试简化了不少。我们可以只传入一个虚拟服务对象，在结束后也不需要多做什么。</p>
<p>public void testClient() {<br />
MockService mock = new MockService();<br />
Client client = new Client(mock);<br />
client.go();<br />
assertTrue(mock.isGone());<br />
}</p>
<p>我们也可以精确地说出客户代码依赖于哪些API。</p>
<p>现在，我们如何把客户代码连接到服务对象呢？手工实现依赖注入的时候，我们可以将所有依赖逻辑都移动到工厂类中。也就是说，我们还需要有一个工厂类来创建客户对象。</p>
<p>public static class ClientFactory {</p>
<p>private ClientFactory() {}</p>
<p>public static Client getInstance() {<br />
Service service = ServiceFactory.getInstance();<br />
return new Client(service);<br />
}<br />
}</p>
<p>手工实现依赖注入需要的代码行数和简单工厂模式差不多。</p>
<p>用 Guice 实现依赖注入<br />
手工为每一个服务与客户实现工厂类和依赖注入逻辑是一件很麻烦的事情。其他一些依赖注入框架甚至需要你显式将服务映射到每一个需要注入的地方。</p>
<p>Guice 希望在不牺牲可维护性的情况下去除所有这些程式化的代码。</p>
<p>使用 Guice，你只需要实现模块类。Guice 将一个绑定器传入你的模块，你的模块使用绑定器来连接接口和实现。以下模块代码告诉 Guice 将 Service 映射到单件模式的 ServiceImpl：</p>
<p>public class MyModule implements Module {<br />
public void configure(Binder binder) {<br />
binder.bind(Service.class)<br />
.to(ServiceImpl.class)<br />
.in(Scopes.SINGLETON);<br />
}<br />
}</p>
<p>模块类告诉 Guice 我们想注入什么东西。那么，我们该如何告诉 Guice 我们想注入到哪里呢？使用 Guice，你可以使用 @Inject 标注你的构造器，方法或字段：</p>
<p>public class Client {</p>
<p>private final Service service;</p>
<p>@Inject<br />
public Client(Service service) {<br />
this.service = service;<br />
}</p>
<p>public void go() {<br />
service.go();<br />
}<br />
}</p>
<p>@Inject 标注使程序员可以很容易地在类中指明哪些方法需要被注入。</p>
<p>为了让 Guice 向 Client 中注入，我们必须直接让 Guice 帮我们创建 Client 的实例，或者，其他类必须包含被注入的 Client 实例。</p>
<p>Guice vs. 手工依赖注入<br />
如你所见，Guice 省去了写工厂类的麻烦。你不需要编写将客户连接到它们所依赖的对象的代码。如果你忘了提供一个依赖关系，Guice 在启动时就会失败。Guice 也会自动处理循环依赖关系。</p>
<p>Guice 允许你通过声明指定对象的作用域。例如，你不用为了将对象存入 HttpSession 而反复编写同样的代码。</p>
<p>在现实世界，不到运行的时候，你通常并不知道具体要使用哪一个实现类。你需要元工厂类或是服务定位器来增强你的工厂模式。Guice 用最少的代价解决了这些问题。</p>
<p>手工实现依赖注入时，你很容易退回到使用直接依赖的旧习惯，特别是当你对依赖注入的概念还不那么熟悉的时候。使用 Guice 可以避免这种问题，可以让你更容易地把事情做对。Guice 使你保持正确的方向。</p>
<p>更多的标注<br />
只要有可能，Guice 就允许你使用标注来绑定对象，减少重复的程式化代码。回到我们的例子，如果你需要一个接口来简化单元测试，但你又不介意编译时的依赖，你可以直接从你的接口指向一个缺省的实现。</p>
<p>@ImplementedBy(ServiceImpl.class)<br />
public interface Service {<br />
void go();<br />
}</p>
<p>这时，如果客户需要一个 Service 对象，且 Guice 无法找到显式绑定，Guice 就会注入一个 ServiceImpl 的实例。</p>
<p>缺省情况下，Guice 每次都注入一个新的实例。如果你想指定不同的作用域规则，你也可以对实现类进行标注。</p>
<p>@Singleton<br />
public class ServiceImpl implements Service {<br />
public void go() {<br />
&#8230;<br />
}<br />
}</p>
<p>架构概览<br />
我们可以将 Guice 的架构分成两个不同的阶段：启动和运行。你在启动时创建一个注入器 Injector，在运行时用它来注入对象。</p>
<p>启动<br />
你通过实现 Module 来配置 Guice。你传给 Guice 一个模块对象，Guice 则将一个绑定器 Binder 对象传入你的模块，然后，你的模块使用绑定器来配置绑定。一个绑定通常包含一个从接口到具体实现的映射。例如：</p>
<p>public class MyModule implements Module {<br />
public void configure(Binder binder) {<br />
// Bind Foo to FooImpl. Guice will create a new<br />
// instance of FooImpl for every injection.<br />
binder.bind(Foo.class).to(FooImpl.class);</p>
<p>// Bind Bar to an instance of Bar.<br />
Bar bar = new Bar();<br />
binder.bind(Bar.class).toInstance(bar);<br />
}<br />
}</p>
<p>在这个阶段，Guice 会察看你告诉它的所有类，以及与这些类有关系的类，然后通知你是否有依赖关系的缺失。例如，在一个 Struts 2 应用中，Guice 知道你所有的动作类。Guice 会检查你的动作类以及所有它们所依赖的类，如果有问题会及早报错。</p>
<p>创建一个 Injector 涉及以下步骤：</p>
<p><img src="http://docs.google.com/File?id=dd2fhx4z_29f8h7x7" border="0" alt="" width="696" /></p>
<p>首先创建你的模块类的实例，并将其传入 Guice.createInjector().<br />
Guice 创建一个绑定器 Binder 并将其传入你的模块。<br />
你的模块使用绑定器来定义绑定。<br />
基于你所定义的绑定，Guice 创建一个注入器 Injector 并将其返回给你。<br />
你使用注入器来注入对象。<br />
运行<br />
现在你可以使用第一阶段创建的注入器来注入对象并内省（introspect）我们的绑定了。Guice 的运行时模型包含了一个管理一定数量绑定的注入器。</p>
<p><img src="http://docs.google.com/File?id=dd2fhx4z_30f3dppc" border="0" alt="" /></p>
<p>键 Key 唯一地指定每一个绑定。 Key 包含了客户代码所依赖的类型以及一个可选的标注。你可以使用标注来区分指向同一类型的绑定。 Key 的类型和标注对应于注入时的类型和标注。</p>
<p>每个绑定有一个提供者 provider，它提供所需类型的实例。你可以提供一个类，Guice 会帮你创建它的实例。你也可以给 Guice 一个你要绑定的实例。你还可以实现你自己的 provider，Guice 可以向其中注入依赖关系。</p>
<p>每一个绑定还有一个可选的作用域。缺省情况下绑定没有作用域，Guice 为每一次注入创建一个新的对象。一个定制的作用域可以使你控制 Guice 创建在何时创建新对象。例如，你可以为每一个  HttpSession 创建一个实例。<br />
自举（Bootstrapping）你的应用</p>
<p>自举（bootstrapping）对于依赖注入非常重要。总是显式地向 Injector 索要依赖，这就将 Guice 用作了服务定位器，而不是一个依赖注入框架。</p>
<p>你 的代码应该尽量少地和 Injector 直接打交道。相反，你应该通过注入一个根对象来自举你的应用。容器可以更进一步地将依赖注入根对象所依赖的对象，并如此迭代下去。最终，在理想情况下，你 的应用中应该只有一个类知道 Injector，每个其他类都应该使用注入的依赖关系。</p>
<p>例如，一个诸如 Struts 2 的 Web 应用框架通过注入所有你的动作类来自举你的应用。你可以通过注入你的服务实现类来自举一个 Web 服务框架。</p>
<p>依赖注入是传染性的。如果你重构一个有大量静态方法的已有代码，你可能会觉得你正在试图拉扯一根没有尽头的线。这是好事情。它表明依赖注入正在帮助你改进代码的灵活性和可测试性。</p>
<p>如 果重构工作太复杂，你不想一次性地整理完所有代码，你可以暂时将一个 Injector 的引用存入某个类的一个静态的字段，或是使用静态注入。这时，请清楚地命名包含该字段的类：比如 InjectorHack 和 GodKillsAKittenEveryTimeYouUseMe。记住你将来可能不得不为这些类提供虚拟类，你的单元测试则不得不手工安装一个注入 器。记住，你将来需要清理这些代码。</p>
<p>绑定依赖关系<br />
Guice 是如何知道要注入什么东西的呢？对启动器来说，一个包含了类型和可选的标注的 Key 唯一地指明了一个依赖关系。Guice 将 key 和实现之间的映射记为一个绑定。一个实现可以包含一个单独的对象，一个需要由 Guice 注入的类，或一个定制的 provider。</p>
<p>当注入依赖关系时，Guice 首先查看显式绑定，即你通过绑定器 Binder 指明的绑定。Binder API 使用生成器（Builder）模式来创建一种领域相关的描述语言。根据约束适用方法的上下文的不同，不同方法返回不同的对象。</p>
<p>例如，为了将接口 Service 绑定到一个具体的实现 ServiceImpl，调用：</p>
<p>binder.bind(Service.class).to(ServiceImpl.class);</p>
<p>该绑定与下面的方法匹配：</p>
<p>@Inject<br />
void injectService(Service service) {<br />
&#8230;<br />
}</p>
<p>注: 与某些其他的框架相反，Guice 并没有给 &#8220;setter&#8221; 方法任何特殊待遇。不管方法有几个参数，只要该方法含有 @Inject 标注，Guice 就会实施注入，甚至对基类中实现的方法也不例外。</p>
<p>不要重复自己</p>
<p>对 每个绑定不断地重复调用 &#8220;binder&#8221; 似乎有些乏味。Guice 提供了一个支持 Module 的类，名为 AbstractModule，它隐含地赋予你访问 Binder 的方法的权力。例如，我们可以用扩展 AbstractModule 类的方式改写上述绑定：</p>
<p>bind(Service.class).to(ServiceImpl.class);</p>
<p>在本手册的余下部分中我们会一直使用这样的语法。</p>
<p>标注绑定</p>
<p>如果你需要指向同一类型的多个绑定，你可以用标注来区分这些绑定。例如，将接口 Service 和标注 @Blue 绑定到具体的实现类 BlueService 的代码如下：</p>
<p>bind(Service.class)<br />
.annotatedWith(Blue.class)<br />
.to(BlueService.class);</p>
<p>这个绑定会匹配以下方法：</p>
<p>@Inject<br />
void injectService(@Blue Service service) {<br />
&#8230;<br />
}</p>
<p>注意，标注 @Inject 出现在方法前，而绑定标注，如 @Blue 则出现在参数前。对构造器也是如此。使用字段注入时，两种标注都直接应用于字段，如以下代码：</p>
<p>@Inject @Blue Service service;</p>
<p>创建绑定标注</p>
<p>刚才提到的标注 @Blue 是从哪里来的？你可以很容易地创建这种标注，但不幸的是，你必须使用略显复杂的标准语法：</p>
<p>/**<br />
* Indicates we want the blue version of a binding.<br />
*/<br />
@Retention(RetentionPolicy.RUNTIME)<br />
@Target({ElementType.FIELD, ElementType.PARAMETER})<br />
@BindingAnnotation<br />
public @interface Blue {}</p>
<p>幸运的是，我们不需要理解这些代码，只要会用就可以了。对于好奇心强的朋友，下面是这些程式化代码的含义：</p>
<p>@Retention(RUNTIME) 使得你的标注在运行时可见。<br />
@Target({FIELD, PARAMETER}) 是对用户使用的说明；它不允许 @Blue 被用于方法、类型、局部变量和其他标注。<br />
@BindingAnnotation 是 Guice 特定的信号，表示你希望该标注被用于绑定标注。当用户将多于一个的绑定标注应用于同一个可注入元素时，Guice 会报错。<br />
有属性的标注<br />
如果你已经会写有属性的标注了，请跳到下一节。</p>
<p>你也可以绑定到标注实例，即，你可以有多个绑定指向同样的类型和标注类型，但每个绑定拥有不同的标注属性值。如果 Guice 找不到拥有特定属性值的标注实例，它会去找一个绑定到该标注类型的绑定。</p>
<p>例如，我们有一个绑定标注 @Named，它有一个字符属性值。<br />
@Retention(RUNTIME)@Target({ FIELD, PARAMETER })@BindingAnnotationpublic @interface Named {  String value();}<br />
如果我们希望绑定到 @Named(&#8220;Bob&#8221;)，我们首先需要一个 Named 的实现。我们的实现必须遵守关于 Annotation 的约定，特别是 hashCode() 和 equals() 的实现。</p>
<p>class NamedAnnotation implements Named { final String value; public NamedAnnotation(String value) { this.value = value; } public String value() { return this.value; } public int hashCode() { // This is specified in java.lang.Annotation. return 127 * &#8220;value&#8221;.hashCode() ^ value.hashCode(); } public boolean equals(Object o) { if (!(o instanceof Named)) return false; Named other = (Named) o; return value.equals(other.value()); } public String toString() { return &#8220;@&#8221; + Named.class.getName() + &#8220;(value=&#8221; + value + &#8220;)&#8221;; } public Class&lt;? extends Annotation&gt; annotationType() { return Named.class; }}<br />
现在我们可以使用这个标注实现来创建一个指向 @Named 的绑定。</p>
<p>bind(Person.class)<br />
.annotatedWith(new NamedAnnotation(&#8220;Bob&#8221;))<br />
.to(Bob.class);</p>
<p>与其它框架使用基于字符串的标识符相比，这显得有些繁琐，但记住，使用基于字符串的标识符，你根本无法这样做。而且，你会发现你可以大量复用已有的绑定标注。</p>
<p>因为通过名字标记一个绑定非常普遍，以至于 Guice 在 com.google.inject.name 中提供了一个十分有用的 @Named 的实现。</p>
<p>隐式绑定<br />
正 如我们在简介中看到的那样，你并不总需要显式声明邦定。如果缺少显式绑定，Guice 会试图注入并创建一个你所依赖的类的新实例。如果你依赖于一个实例，Guice 会寻找一个指向具体实现的 @ImplementedBy 标注。例如，下例中的代码显式绑定到一个具体的、可注入的名为 Concrete 的类。它的含义是，将 Concrete 绑定到 Concrete。这是显式的声明方式，但也有些冗余。</p>
<p>bind(Concrete.class);</p>
<p>删除上述绑定语句不会影响下面这个类的行为：</p>
<p>class Mixer {</p>
<p>@Inject<br />
Mixer(Concrete concrete) {<br />
&#8230;<br />
}<br />
}</p>
<p>好吧，你自己来选择：显式的或简略的。无论何种方式，Guice 在遇到错误时都会生成有用的信息。<br />
注入提供者<br />
有 时对于每次注入，客户代码需要一个依赖的多个实例。其它时候，客户可能不想在一开始就真地获取对象，而是等到注入后的某个时候再获取。对于任意绑定类型 T，你可以不直接注入 T 的实例，而是注入一个 Provider&lt;T&gt;，然后在需要的时候调用 Provider&lt;T&gt;.get()，例如：</p>
<p>@Inject<br />
void injectAtm(Provider&lt;Money&gt; atm) {<br />
Money one = atm.get();<br />
Money two = atm.get();<br />
&#8230;<br />
}</p>
<p>正如你所看到的那样， Provider 接口简单得不能再简单了，它不会为简单的单元测试添加任何麻烦。</p>
<p>注入常数值</p>
<p>对于常数值，Guice 对以下几种类型做了特殊处理：</p>
<p>基本类型(int, char, &#8230;)<br />
基本 wrapper 类型(Integer, Character, &#8230;)<br />
Strings<br />
Enums<br />
Classes</p>
<p>首先，当绑定到这些类型的常数值的时候，你不需要指定你要绑定到的类型。Guice 可以根据值判断类型。例如，一个绑定标注名为 TheAnswer：</p>
<p>bindConstant().annotatedWith(TheAnswer.class).to(42);</p>
<p>它的效果等价于：</p>
<p>bind(int.class).annotatedWith(TheAnswer.class).toInstance(42);</p>
<p>当需要注入这些类型的数值时，如果 Guice 找不到显式指向基本数据类型的绑定，它会找一个指向相应的 wrapper 类型的绑定，反之亦然。</p>
<p>转换字符串</p>
<p>如果 Guice 仍然无法找到一个上述类型的显式绑定，它会去找一个拥有相同绑定标识的常量 String 绑定，并试图将字符串转换到相应的值。例如：</p>
<p>bindConstant().annotatedWith(TheAnswer.class).to(&#8220;42&#8243;); // String!</p>
<p>会匹配：</p>
<p>@Inject @TheAnswer int answer;</p>
<p>转换时，Guice 会用名字去查找枚举和类。Guice 在启动时转换一次，这意味着它提前做了类型检查。这个特性特别有用，例如，当绑定值来自一个属性文件的时候。<br />
定制的提供者<br />
有时你需要手工创建你自己的对象，而不是让 Guice 创建它们。例如，你可能不能为来自第三方的实现类添加 @Inject 标注。在这种情况下，你可以实现一个定制的 Provider。Guice 甚至可以注入你的提供者类。例如：</p>
<p>class WidgetProvider implements Provider&lt;Widget&gt; {</p>
<p>final Service service;</p>
<p>@Inject<br />
WidgetProvider(Service service) {<br />
this.service = service;<br />
}</p>
<p>public Widget get() {<br />
return new Widget(service);<br />
}<br />
}</p>
<p>你可以向这样把 Widget 绑定到 WidgetProvider：</p>
<p>bind(Widget.class).toProvider(WidgetProvider.class);</p>
<p>注 入定制的提供者可以使 Guice 提前检查类型和依赖关系。定制的提供者可以在任意作用域中使用，而不依赖于他们所创建的类的作用域。缺省情况下，Guice 为每一次注入创建一个新的提供者实例。在上例中，如果每个 Widget 需要它自己的 Service 实例，我们的代码也没有问题。通过在工厂类上使用作用域标注，或为工厂类创建单独的绑定，你可以为定制的工厂指定不同的作用域。<br />
示例：与 JNDI 集成<br />
例如我们需要绑定从 JNDI 得到的对象。我们可以仿照下面的代码实现一个可复用的定制的提供者。注意我们注入了 JNDI Context：</p>
<p>package mypackage;</p>
<p>import com.google.inject.*;<br />
import javax.naming.*;</p>
<p>class JndiProvider&lt;T&gt; implements Provider&lt;T&gt; {</p>
<p>@Inject Context context;<br />
final String name;<br />
final Class&lt;T&gt; type;</p>
<p>JndiProvider(Class&lt;T&gt; type, String name) {<br />
this.name = name;<br />
this.type = type;<br />
}</p>
<p>public T get() {<br />
try {<br />
return type.cast(context.lookup(name));<br />
}<br />
catch (NamingException e) {<br />
throw new RuntimeException(e);<br />
}<br />
}</p>
<p>/**<br />
* Creates a JNDI provider for the given<br />
* type and name.<br />
*/<br />
static &lt;T&gt; Provider&lt;T&gt; fromJndi(<br />
Class&lt;T&gt; type, String name) {<br />
return new JndiProvider&lt;T&gt;(type, name);<br />
}<br />
}</p>
<p>感谢范型擦除（generic type erasure）技术。我们必须在运行时将依赖传入类中。你可以省略这一步，但在今后跟踪类型转换错误会比较棘手（当 JNDI 返回错误的类型的时候）。</p>
<p>我们可以使用定制的 JndiProvider 来将 DataSource 绑定到来自 JNDI 的一个对象：</p>
<p>import com.google.inject.*;<br />
import static mypackage.JndiProvider.fromJndi;<br />
import javax.naming.*;<br />
import javax.sql.DataSource;</p>
<p>&#8230;</p>
<p>// Bind Context to the default InitialContext.<br />
bind(Context.class).to(InitialContext.class);</p>
<p>// Bind to DataSource from JNDI.<br />
bind(DataSource.class)<br />
.toProvider(fromJndi(DataSource.class, &#8220;&#8230;&#8221;));</p>
<p>限制绑定的作用域<br />
缺省情况下，Guice 为每次注入创建一个新的对象。我们把它称为“无作用域”。你可以在配制绑定时指明作用域。例如，每次注入相同的实例：</p>
<p>bind(MySingleton.class).in(Scopes.SINGLETON);</p>
<p>另一种做法是，你可以在实现类中使用标注来指明作用域。Guice 缺省支持 @Singleton：</p>
<p>@Singleton<br />
class MySingleton {<br />
&#8230;<br />
}</p>
<p>是 用标注的方法对于隐式绑定也同样有效，但需要 Guice 来创建你的对象。另一方面，调用 in() 适用于几乎所有绑定类型（显然，绑定到一个单独的实例是个例外）并且会忽略已有的作用域标注。如果你不希望引入对于作用域实现的编译时依赖，in() 还可以接受标注。</p>
<p>可以使用 Binder.bindScope() 为定制的作用域指定标注。例如，对于标注 @SessionScoped 和一个 Scope 的实现 ServletScopes.SESSION：</p>
<p>binder.bindScope(SessionScoped.class, ServletScopes.SESSION);</p>
<p>创建作用域标注</p>
<p>用于指定作用域的标注必须：</p>
<p>有一个 @Retention(RUNTIME) 标注，从而使我们可以在运行时看到该标注。<br />
有一个 @Target({TYPE}) 标注。作用域标注只用于实现类。<br />
有一个 @ScopeAnnotation 元标注。一个类只能使用一个此类标注。</p>
<p>例如：</p>
<p>/**<br />
* Scopes bindings to the current transaction.<br />
*/<br />
@Retention(RUNTIME)<br />
@Target({TYPE})<br />
@ScopeAnnotation<br />
public @interface TransactionScoped {}<br />
尽早加载绑定<br />
Guice 可以等到你实际使用对象时再加载单件对象。这有助于开发，因为你的应用程序可以快速启动，只初始化你需要的对象。但是，有时你总是希望在启动时加载一个对象。你可以告诉 Guice，让它总是尽早加载一个单件对象，例如：</p>
<p>bind(StartupTask.class).asEagerSingleton();</p>
<p>我们经常在我们的程序中使用这个方法实现初始化逻辑。你可以通过在 Guice 必须首先初始化的单件上创建依赖关系来控制初始化顺序。<br />
在不同作用域间注入<br />
你 可以安全地将来自大作用域的对象注入到来自小作用域或相同作用域的对象中。例如，你可以将一个作用域为 HTTP 会话的对象注入到作用域为 HTTP 请求的对象中。但是，在来自较大作用域的对象中注入就是另一件事了。例如，如果你把一个作用域为 HTTP 请求的对象注入到一个单件对象中，最好情况下，你会得到无法在 HTTP 请求中运行的错误信息，最坏情况下，你的单件对象总会引用一个来自第一次请求的对象。在这些时候，你应该注入一个 Provider&lt;T&gt;，然后在需要的时候使用它从较小的作用域中获取对象。这时，你必须确保，在 T 的作用域之外，永远不要调用这个提供者（例如，当目前没有 HTTP 请求且 T 的作用域为请求的时候）。<br />
开发阶段<br />
Guice 明白你的应用开发需要经历不同的阶段。你可以在创建容器时告诉它应用程序运行在哪一个阶段。Guice 目前支持“开发”和“产品”两个阶段。我们发现测试通常属于其中某一个阶段。</p>
<p>在开发阶段，Guice 会在需要时加载单件对象。这样，你的应用可以快速启动，只加载你正在测试的部分。</p>
<p>在产品阶段，Guice 会在启动时加载全部单件对象。这帮助你尽早捕获错误，提前优化性能。</p>
<p>你的模块也可以使用方法拦截和其他基于当前阶段的绑定。例如，一个拦截器可能会在开发阶段检查你是否在作用域之外使用对象。</p>
<p>拦截方法<br />
Guice 使用 AOP Alliance API 支持简单的方法拦截。你可以在模块中使用 Binder 绑定拦截器。例如，对标注有 @Transactional 的方法使用交易拦截器：</p>
<p>import static com.google.inject.matcher.Matchers.*;</p>
<p>&#8230;</p>
<p>binder.bindInterceptor(<br />
any(),                              // Match classes.<br />
annotatedWith(Transactional.class), // Match methods.<br />
new TransactionInterceptor()        // The interceptor.<br />
);</p>
<p>尽量让匹配代码多做些过滤工作，而不是在拦截器中过滤。因为匹配代码只在启动时运行一次。</p>
<p>静态注入<br />
静态字段和方法会增加测试和复用的难度，但有的时候你唯一的选择就是保留一个静态的指向 Injector 的引用。</p>
<p>在 这些情况下，Guice 支持注入可访问性较少的静态方法。例如，HTTP 会话对象经常需要被串行化，以支持复制机制。但是，如果你的会话对象依赖于一个作用域为容器生命周期的对象，该怎么办呢？我们可以保留一个易变的引用指向 该对象，但在反串行化的时候，我们该如何再次找到该对象呢？</p>
<p>我们发现更实用的解决方案是使用静态注入：</p>
<p>@SessionScoped<br />
class User {</p>
<p>@Inject<br />
static AuthorizationService authorizationService;<br />
&#8230;<br />
}</p>
<p>Guice 从不自动实施静态注入。你必须使用 Binder 显式请求 Injector 在启动后注入你的静态成员：</p>
<p>binder.requestStaticInjection(User.class);</p>
<p>静态注入是一个需要保留的祸害，它会使测试难度加大。如果有办法避开它，你多半会很高兴的。</p>
<p>可选注入<br />
有时你的代码应该在无论绑定是否存在的时候都能工作。在这些情况下，你可以使用 @Inject(optional=true)，Guice 会在有绑定可用时，用一个绑定的实现覆盖你的缺省实现。例如：</p>
<p>@Inject(optional=true) Formatter formatter = new DefaultFormatter();</p>
<p>如果谁为 Formatter 创建了一个绑定，Guice 会基于该绑定注入实例。否则，如果 Formatter 不能被注入(参见隐式绑定)，Guice 会忽略可选成员。</p>
<p>可选注入只能应用于字段和方法，而不能用于构造器。对于方法，如果一个参数的绑定找不到，Guice 就不会注入该方法，即便其他参数的绑定是可用的。</p>
<p>绑定到字符串<br />
只要有可能，我们就尽量避免使用字符串，因为它们容易受错误拼写的影响，对工具不友好，等等。但使用字符串而不是创建定制的标注对于快而脏的代码来说仍是有用的。在这些情况下，Guice 提供了@Named 和 Names。例如，一个到字符串名字的绑定：</p>
<p>import static com.google.inject.name.Names.*;</p>
<p>&#8230;</p>
<p>bind(named(&#8220;bob&#8221;)).to(10);</p>
<p>会匹配下面的注入点：</p>
<p>@Inject @Named(&#8220;bob&#8221;) int score;</p>
<p>Struts 2支持<br />
要 在 Struts 2.0.6 或更高版本中安装 Guice Struts 2 插件，只要将 guice-struts2-plugin-1.0.jar 包含在你的 Web 应用的 classpath 中，并在 struts.xml 中选择 Guice 作为你的 ObjectFactory 实现即可：</p>
<p>&lt;constant name=&#8221;struts.objectFactory&#8221; value=&#8221;guice&#8221; /&gt;</p>
<p>Guice 会注入所有你的 Struts 2 对象，包括动作和拦截器。你甚至可以设置动作类的作用域。你也可以在你的 struts.xml 文件中指定 Guice 的 Module：</p>
<p>&lt;constant name=&#8221;guice.module&#8221; value=&#8221;mypackage.MyModule&#8221;/&gt;</p>
<p>如果你的所有绑定都是隐式的，你就根本不用定义模块了。</p>
<p>一个计数器的例子<br />
例如，我们试图统计一个会话中的请求数目。定义一个在会话中存活的 Counter 对象：</p>
<p>@SessionScoped<br />
public class Counter {</p>
<p>int count = 0;</p>
<p>/** Increments the count and returns the new value. */<br />
public synchronized int increment() {<br />
return count++;<br />
}<br />
}</p>
<p>接下来，我们可以将我们的计数器注入到动作中：</p>
<p>public class Count {</p>
<p>final Counter counter;</p>
<p>@Inject<br />
public Count(Counter counter) {<br />
this.counter = counter;<br />
}</p>
<p>public String execute() {<br />
return SUCCESS;<br />
}</p>
<p>public int getCount() {<br />
return counter.increment();<br />
}<br />
}</p>
<p>然后在 struts.xml 文件中为动作类创建映射：</p>
<p>&lt;action name=&#8221;Count&#8221;<br />
class=&#8221;mypackage.Count&#8221;&gt;<br />
&lt;result&gt;/WEB-INF/Counter.jsp&lt;/result&gt;<br />
&lt;/action&gt;</p>
<p>以及一个用于显示结果的 JSP 页面：</p>
<p>&lt;%@ taglib prefix=&#8221;s&#8221; uri=&#8221;/struts-tags&#8221; %&gt;</p>
<p>&lt;html&gt;<br />
&lt;body&gt;<br />
&lt;h1&gt;Counter Example&lt;/h1&gt;<br />
&lt;h3&gt;&lt;b&gt;Hits in this session:&lt;/b&gt;<br />
&lt;s:property value=&#8221;count&#8221;/&gt;&lt;/h3&gt;<br />
&lt;/body&gt;<br />
&lt;/html&gt;</p>
<p>我们实际上把这个例子做得比需求更复杂，以便展示更多的概念。在现实中，我们不需要使用单独的 Counter 对象，只要把 @SessionScoped 应用于我们的动作类即可。</p>
<p>JMX 集成<br />
参见 com.google.inject.tools.jmx.</p>
<p>附录：注入器如何实施注入<br />
注入器响应注入请求的过程依赖于已有的绑定和相关类型中的标注。这里是关于注入请求如何被实施的一个概要描述：</p>
<p>观察被注入元素的 Java 类型和可选的“绑定标注”。如果类型是 com.google.inject.Provider&lt;T&gt;，就对类型 T 实施。对（类型，标注）对，寻找一个绑定。如果没有，则跳到步骤4。<br />
沿着绑定链检查。如果绑定连接到另一个绑定，则走过这条边，并继续检查，直到到达一个没有连接到后续绑定的绑定为止。我们现在为注入请求找到了最明确的显式绑定。<br />
如果绑定指明一个实例或一个 Provider 实例，所有事情都做完了；使用这个实例来满足请求即可。<br />
此时，如果注入请求使用了标注类型或值，我们就报告错误。<br />
否则，检查绑定的 Java 类型；如果找到了 @ImplementedBy 标注，就实例化该类型。 如果找到了 @ProvidedBy 标注，就实例化提供者类并用它来获取想要的对象。否则试图实例化类型本身。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.jackrun.com/archives/72.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>JAVA Calendar详解</title>
		<link>http://blog.jackrun.com/archives/55.html</link>
		<comments>http://blog.jackrun.com/archives/55.html#comments</comments>
		<pubDate>Tue, 01 Apr 2008 02:05:44 +0000</pubDate>
		<dc:creator>Peltason</dc:creator>
				<category><![CDATA[J2EE]]></category>
		<category><![CDATA[Calendar]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[日期]]></category>

		<guid isPermaLink="false">http://www.jackrun.com/?p=55</guid>
		<description><![CDATA[（在文章的最后，将会介绍Date类，如果有兴趣，可以直接翻到最后去阅读）
究竟什么是一个 Calendar 呢？中文的翻译就是日历，那我们立刻可以想到我们生活中有阳(公)历、阴(农)历之分。它们的区别在哪呢？
比如有：
月份的定义 &#8211; 阳`(公)历 一年12 个月，每个月的天数各不同；阴(农)历，每个月固定28天
每周的第一天 &#8211; 阳(公)历星期日是第一天；阴(农)历，星期一是第一天

实 际上，在历史上有着许多种纪元的方法。它们的差异实在太大了，比如说一个人的生日是&#8221;八月八日&#8221; 那么一种可能是阳(公)历的八月八日，但也可以是阴(农)历的日期。所以为了计时的统一，必需指定一个日历的选择。那现在最为普及和通用的日历就是 &#8220;Gregorian Calendar&#8221;。也就是我们在讲述年份时常用 &#8220;公元几几年&#8221;。Calendar 抽象类定义了足够的方法，让我们能够表述日历的规则。Java 本身提供了对 &#8220;Gregorian Calendar&#8221; 规则的实现。我们从 Calendar.getInstance() 中所获得的实例就是一个 &#8220;GreogrianCalendar&#8221; 对象(与您通过 new GregorianCalendar() 获得的结果一致)。
下面的代码可以证明这一点：
import java.io.*;
import java.util.*;
public class WhatIsCalendar{
public static void main(String[] args) {
Calendar calendar = Calendar.getInstance();
if (calendar instanceof GregorianCalendar)
System.out.println(&#8220;It is an instance of GregorianCalendar&#8221;t;
}
}
Calendar 在 Java 中是一个抽象类(Abstract Class)，GregorianCalendar 是它的一个具体实现。
我们也可以自己的 Calendar 实现类，然后将它作为 Calendar 对象返回(面向对象的特性)。在 [...]]]></description>
			<content:encoded><![CDATA[<p>（在文章的最后，将会介绍Date类，如果有兴趣，可以直接翻到最后去阅读）</p>
<p>究竟什么是一个 Calendar 呢？中文的翻译就是日历，那我们立刻可以想到我们生活中有阳(公)历、阴(农)历之分。它们的区别在哪呢？<br />
比如有：<br />
月份的定义 &#8211; 阳`(公)历 一年12 个月，每个月的天数各不同；阴(农)历，每个月固定28天<br />
每周的第一天 &#8211; 阳(公)历星期日是第一天；阴(农)历，星期一是第一天</p>
<p><span id="more-55"></span><br />
实 际上，在历史上有着许多种纪元的方法。它们的差异实在太大了，比如说一个人的生日是&#8221;八月八日&#8221; 那么一种可能是阳(公)历的八月八日，但也可以是阴(农)历的日期。所以为了计时的统一，必需指定一个日历的选择。那现在最为普及和通用的日历就是 &#8220;Gregorian Calendar&#8221;。也就是我们在讲述年份时常用 &#8220;公元几几年&#8221;。Calendar 抽象类定义了足够的方法，让我们能够表述日历的规则。Java 本身提供了对 &#8220;Gregorian Calendar&#8221; 规则的实现。我们从 Calendar.getInstance() 中所获得的实例就是一个 &#8220;GreogrianCalendar&#8221; 对象(与您通过 new GregorianCalendar() 获得的结果一致)。<br />
下面的代码可以证明这一点：</p>
<p>import java.io.*;<br />
import java.util.*;<br />
public class WhatIsCalendar{<br />
public static void main(String[] args) {<br />
Calendar calendar = Calendar.getInstance();<br />
if (calendar instanceof GregorianCalendar)<br />
System.out.println(&#8220;It is an instance of GregorianCalendar&#8221;t;<br />
}<br />
}</p>
<p>Calendar 在 Java 中是一个抽象类(Abstract Class)，GregorianCalendar 是它的一个具体实现。<br />
我们也可以自己的 Calendar 实现类，然后将它作为 Calendar 对象返回(面向对象的特性)。在 IBM alphaWorks 上，IBM 的开发人员实现了多种日历(http://www.alphaworks.ibm.com/tech/calendars)。同样在 Internet 上，也有对中国农历的实现。本文对如何扩展 Calendar 不作讨论，大家可以通过察看上述 Calendar 的源码来学习。<br />
Calendar 与 Date 的转换非常简单：</p>
<p>Calendar calendar = Calendar.getInstance();<br />
// 从一个 Calendar 对象中获取 Date 对象<br />
Date date = calendar.getTime();<br />
// 将 Date 对象反应到一个 Calendar 对象中，<br />
// Calendar/GregorianCalendar 没有构造函数可以接受 Date 对象<br />
// 所以我们必需先获得一个实例，然后设置 Date 对象<br />
calendar.setTime(date);</p>
<p>Calendar 对象在使用时，有一些值得注意的事项：<br />
1. Calendar 的 set() 方法<br />
set(int field, int value) &#8211; 是用来设置&#8221;年/月/日/小时/分钟/秒/微秒&#8221;等值<br />
field 的定义在 Calendar 中<br />
set(int year, int month, int day, int hour, int minute, int second) 但没有<br />
set(int year, int month, int day, int hour, int minute, int second, int millisecond) 前面 set(int,int,int,int,int,int) 方法不会自动将 MilliSecond 清为 0。<br />
另外，月份的起始值为０而不是１，所以要设置八月时，我们用７而不是8。<br />
calendar.set(Calendar.MONTH, 7);<br />
我们通常需要在程序逻辑中将它清为 0，否则可能会出现下面的情况：</p>
<p>import java.io.*;<br />
import java.util.*;<br />
public class WhatIsCalendarWrite{<br />
public static void main(String[] args) throws Exception{<br />
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(&#8220;calendar.out&#8221;t);<br />
Calendar cal1 = Calendar.getInstance();<br />
cal1.set(2000, 7, 1, 0, 0, 0);<br />
out.writeObject(cal1);<br />
Calendar cal2 = Calendar.getInstance();<br />
cal2.set(2000, 7, 1, 0, 0, 0);<br />
cal2.set(Calendar.MILLISECOND, 0);<br />
out.writeObject(cal2);<br />
out.close();<br />
}<br />
}</p>
<p>我们将 Calendar 保存到文件中</p>
<p>import java.io.*;<br />
import java.util.*;<br />
public class WhatIsCalendarRead{<br />
public static void main(String[] args) throws Exception{<br />
ObjectInputStream in = new ObjectInputStream(new FileInputStream(&#8220;calendar.out&#8221;t);<br />
Calendar cal2 = (Calendar)in.readObject();<br />
Calendar cal1 = Calendar.getInstance();<br />
cal1.set(2000, 7, 1, 0, 0, 0);<br />
if (cal1.equals(cal2))<br />
System.out.println(&#8220;Equals&#8221;t;<br />
else<br />
System.out.println(&#8220;NotEqual&#8221;t;<br />
System.out.println(&#8220;Old calendar &#8220;+cal2.getTime().getTime());<br />
System.out.println(&#8220;New calendar &#8220;+cal1.getTime().getTime());<br />
cal1.set(Calendar.MILLISECOND, 0);<br />
cal2 = (Calendar)in.readObject();<br />
if (cal1.equals(cal2))<br />
System.out.println(&#8220;Equals&#8221;t;<br />
else<br />
System.out.println(&#8220;NotEqual&#8221;t;<br />
System.out.println(&#8220;Processed Old calendar &#8220;+cal2.getTime().getTime());<br />
System.out.println(&#8220;Processed New calendar &#8220;+cal1.getTime().getTime());<br />
}<br />
}</p>
<p>然后再另外一个程序中取回来（模拟对数据库的存储），但是执行的结果是：<br />
NotEqual<br />
Old calendar 965113200422 &lt;&#8212;&#8212;&#8212;&#8212; 最后三位的MilliSecond与当前时间有关<br />
New calendar 965113200059 &lt;&#8212;&#8212;&#8212;&#8211;/<br />
Equals<br />
Processed Old calendar 965113200000<br />
Processed New calendar 965113200000<br />
另外我们要注意的一点是，Calendar 为了性能原因对 set() 方法采取延缓计算的方法。在 JavaDoc 中有下面的例子来说明这个问题：<br />
Calendar cal1 = Calendar.getInstance();<br />
cal1.set(2000, 7, 31, 0, 0 , 0); //2000-8-31<br />
cal1.set(Calendar.MONTH, Calendar.SEPTEMBER); //应该是 2000-9-31，也就是 2000-10-1<br />
cal1.set(Calendar.DAY_OF_MONTH, 30); //如果 Calendar 转化到 2000-10-1，那么现在的结果就该是 2000-10-30<br />
System.out.println(cal1.getTime()); //输出的是2000-9-30，说明 Calendar 不是马上就刷新其内部的记录<br />
在 Calendar 的方法中，get() 和 add() 会让 Calendar 立刻刷新。Set() 的这个特性会给我们的开发带来一些意想不到的结果。我们后面会看到这个问题。<br />
2. Calendar 对象的容错性，Lenient 设置<br />
我们知道特定的月份有不同的日期，当一个用户给出错误的日期时，Calendar 如何处理的呢？</p>
<p>import java.io.*;<br />
import java.util.*;<br />
public class WhatIsCalendar{<br />
public static void main(String[] args) throws Exception{<br />
Calendar cal1 = Calendar.getInstance();<br />
cal1.set(2000, 1, 32, 0, 0, 0);<br />
System.out.println(cal1.getTime());<br />
cal1.setLenient(false);<br />
cal1.set(2000, 1, 32, 0, 0, 0);<br />
System.out.println(cal1.getTime());<br />
}<br />
}</p>
<p>它的执行结果是：<br />
Tue Feb 01 00:00:00 PST 2000<br />
Exception in thread &#8220;main&#8221; java.lang.IllegalArgumentException<br />
at java.util.GregorianCalendar.computeTime(GregorianCalendar.java:1368)<br />
at java.util.Calendar.updateTime(Calendar.java:1508)<br />
at java.util.Calendar.getTimeInMillis(Calendar.java:890)<br />
at java.util.Calendar.getTime(Calendar.java:871)<br />
at WhatIsCalendar.main(WhatIsCalendar.java:12)<br />
当我们设置该 Calendar 为 Lenient false 时，它会依据特定的月份检查出错误的赋值。<br />
3. 不稳定的 Calendar<br />
我们知道 Calendar 是可以被 serialize 的，但是我们要注意下面的问题</p>
<p>import java.io.*;<br />
import java.util.*;<br />
public class UnstableCalendar implements Serializable{<br />
public static void main(String[] args) throws Exception{<br />
Calendar cal1 = Calendar.getInstance();<br />
cal1.set(2000, 7, 1, 0, 0 , 0);<br />
cal1.set(Calendar.MILLISECOND, 0);<br />
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(&#8220;newCalendar.out&#8221;t);<br />
out.writeObject(cal1);<br />
out.close();<br />
ObjectInputStream in = new ObjectInputStream(new FileInputStream(&#8220;newCalendar.out&#8221;t);<br />
Calendar cal2 = (Calendar)in.readObject();<br />
cal2.set(Calendar.MILLISECOND, 0);<br />
System.out.println(cal2.getTime());<br />
}<br />
}</p>
<p>运行的结果竟然是: Thu Jan 01 00:00:00 PST 1970<br />
它 被复原到 EPOC 的起始点，我们称该 Calendar 是处于不稳定状态。这个问题的根本原因是 Java 在 serialize GregorianCalendar 时没有保存所有的信息，所以当它被恢复到内存中，又缺少足够的信息时，Calendar 会被恢复到 EPOCH 的起始值。Calendar 对象由两部分构成：字段和相对于 EPOC 的微秒时间差。字段信息是由微秒时间差计算出的，而 set() 方法不会强制 Calendar 重新计算字段。这样字段值就不对了。<br />
下面的代码可以解决这个问题：</p>
<p>import java.io.*;<br />
import java.util.*;<br />
public class StableCalendar implements Serializable{<br />
public static void main(String[] args) throws Exception{<br />
Calendar cal1 = Calendar.getInstance();<br />
cal1.set(2000, 7, 1, 0, 0 , 0);<br />
cal1.set(Calendar.MILLISECOND, 0);<br />
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(&#8220;newCalendar.out&#8221;t);<br />
out.writeObject(cal1);<br />
out.close();<br />
ObjectInputStream in =new ObjectInputStream(new FileInputStream(&#8220;newCalendar.out&#8221;t);<br />
Calendar cal2 = (Calendar)in.readObject();<br />
cal2.get(Calendar.MILLISECOND); //先调用 get()，强制 Calendar 刷新<br />
cal2.set(Calendar.MILLISECOND, 0);//再设值<br />
System.out.println(cal2.getTime());<br />
}<br />
}</p>
<p>运行的结果是: Tue Aug 01 00:00:00 PDT 2000<br />
这个问题主要会影响到在 EJB 编程中，参数对象中包含 Calendar 时。经过 Serialize/Deserialize 后，直接操作 Calendar 会产生不稳定的情况。<br />
4. add() 与 roll() 的区别<br />
add() 的功能非常强大，add 可以对 Calendar 的字段进行计算。如果需要减去值，那么使用负数值就可以了，如 add(field, -value)。<br />
add() 有两条规则：<br />
当被修改的字段超出它可以的范围时，那么比它大的字段会自动修正。如：</p>
<p>Calendar cal1 = Calendar.getInstance();<br />
cal1.set(2000, 7, 31, 0, 0 , 0); //2000-8-31<br />
cal1.add(Calendar.MONTH, 1); //2000-9-31 =&gt; 2000-10-1，对吗？<br />
System.out.println(cal1.getTime()); //结果是 2000-9-30</p>
<p>另一个规则是，如果比它小的字段是不可变的（由 Calendar 的实现类决定），那么该小字段会修正到变化最小的值。<br />
以上面的例子，9-31 就会变成 9-30，因为变化最小。<br />
Roll() 的规则只有一条：<br />
当被修改的字段超出它可以的范围时，那么比它大的字段不会被修正。如：</p>
<p>Calendar cal1 = Calendar.getInstance();<br />
cal1.set(1999, 5, 6, 0, 0, 0); //1999-6-6, 周日<br />
cal1.roll(Calendar.WEEK_OF_MONTH, -1); //1999-6-1, 周二<br />
cal1.set(1999, 5, 6, 0, 0, 0); //1999-6-6, 周日<br />
cal1.add(Calendar.WEEK_OF_MONTH, -1); //1999-5-30, 周日</p>
<p>WEEK_OF_MONTH 比 MONTH 字段小，所以 roll 不能修正 MONTH 字段。<br />
Date类介绍<br />
Data和Calendar类：<br />
一、创建一个日期对象r<br />
让我们看一个使用系统的当前日期和时间创建一个日期对象并返回一个长整数的简<br />
单例子. 这个时间通常被称为Java 虚拟机(JVM)主机环境的系统时间.</p>
<p>import java.util.Date;<br />
public class DateExample1 {<br />
public static void main(String[] args) {<br />
// Get the system date/time<br />
Date date = new Date();<br />
System.out.println(date.getTime());<br />
}<br />
}</p>
<p>在星期六, 2001年9月29日, 下午大约是6:50的样子, 上面的例子在系统输出设备上<br />
显示的结果是 1001803809710. 在这个例子中,值得注意的是我们使用了Date 构造<br />
函数创建一个日期对象, 这个构造函数没有接受任何参数. 而这个构造函数在内部<br />
使用了System.currentTimeMillis() 方法来从系统获取日期.如果用<br />
System.out.println(new Date());<br />
则输出形式为：Tue Nov 08 14:28:07 CST 2005<br />
那么, 现在我们已经知道了如何获取从1970年1月1日开始经历的毫秒数了. 我们如<br />
何才能以一种用户明白的格式来显示这个日期呢? 在这里类java.text.<br />
SimpleDateFormat 和它的抽象基类 java.text.DateFormat 就派得上用场了.<br />
二、日期数据的定制格式<br />
假如我们希望定制日期数据的格式, 比方星期六-9月-29日-2001年. 下面的例子展<br />
示了如何完成这个工作:</p>
<p>import java.text.SimpleDateFormat;<br />
import java.util.Date;<br />
public class DateExample2 {<br />
public static void main(String[] args) {<br />
SimpleDateFormat bartDateFormat =<br />
new SimpleDateFormat(&#8220;EEEE-MMMM-dd-yyyy&#8221;);<br />
Date date = new Date();<br />
System.out.println(bartDateFormat.format(date));<br />
}<br />
}</p>
<p>只要通过向SimpleDateFormat 的构造函数传递格式字符串&#8221;EEE-MMMM-dd-yyyy&#8221;,<br />
我们就能够指明自己想要的格式. 你应该可以看见, 格式字符串中的ASCII 字符<br />
告诉格式化函数下面显示日期数据的哪一个部分. EEEE是星期, MMMM是月, dd是日<br />
, yyyy是年. 字符的个数决定了日期是如何格式化的.传递&#8221;EE-MM-dd-yy&#8221;会显示<br />
Sat-09-29-01. 请察看Sun 公司的Web 站点获取日期格式化选项的完整的指示.<br />
三、将文本数据解析成日期对象r<br />
假设我们有一个文本字符串包含了一个格式化了的日期对象, 而我们希望解析这个<br />
字符串并从文本日期数据创建一个日期对象. 我们将再次以格式化字符串<br />
&#8220;MM-dd-yyyy&#8221; 调用SimpleDateFormat类, 但是这一次, 我们使用格式化解析而不<br />
是生成一个文本日期数据. 我们的例子, 显示在下面, 将解析文本字符串<br />
&#8220;9-29-2001&#8243;并创建一个值为001736000000 的日期对象.<br />
例子程序:</p>
<p>import java.text.SimpleDateFormat;<br />
import java.util.Date;<br />
public class DateExample3 {<br />
public static void main(String[] args) {<br />
// Create a date formatter that can parse dates of<br />
// the form MM-dd-yyyy.<br />
SimpleDateFormat bartDateFormat = new SimpleDateFormat(&#8220;MM-dd-yyyy&#8221;);<br />
// Create a string containing a text date to be parsed.<br />
String dateStringToParse = &#8220;9-29-2001&#8243;;<br />
try {<br />
// Parse the text version of the date.<br />
// We have to perform the parse method in a<br />
// try-catch construct in case dateStringToParse<br />
// does not contain a date in the format we are expecting.<br />
Date date = bartDateFormat.parse(dateStringToParse);<br />
// Now send the parsed date as a long value<br />
// to the system output.<br />
System.out.println(date.getTime());<br />
} catch (Exception ex) {<br />
System.out.println(ex.getMessage());<br />
}<br />
}<br />
}</p>
<p>五、使用标准的日期格式化过程<br />
既然我们已经可以生成和解析定制的日期格式了, 让我们来看一看如何使用内建的<br />
格式化过程. 方法 DateFormat.getDateTimeInstance() 让我们得以用几种不同的<br />
方法获得标准的日期格式化过程. 在下面的例子中, 我们获取了四个内建的日期格<br />
式化过程. 它们包括一个短的, 中等的, 长的, 和完整的日期格式.</p>
<p>import java.text.DateFormat;<br />
import java.util.Date;<br />
public class DateExample4 {<br />
public static void main(String[] args) {<br />
Date date = new Date();<br />
DateFormat shortDateFormat =  DateFormat.getDateTimeInstance(DateFormat.SHORT,DateFormat.SHORT);<br />
DateFormat mediumDateFormat =<br />
DateFormat.getDateTimeInstance(DateFormat.MEDIUM,DateFormat.MEDIUM);<br />
DateFormat longDateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG,DateFormat.LONG);<br />
DateFormat fullDateFormat = DateFormat.getDateTimeInstance(DateFormat.FULL,DateFormat.FULL);<br />
System.out.println(shortDateFormat.format(date));<br />
System.out.println(mediumDateFormat.format(date));<br />
System.out.println(longDateFormat.format(date));<br />
System.out.println(fullDateFormat.format(date));<br />
}<br />
}</p>
<p>注意我们在对 getDateTimeInstance的每次调用中都传递了两个值. 第一个参数<br />
是日期风格, 而第二个参数是时间风格. 它们都是基本数据类型int(整型). 考虑<br />
到可读性, 我们使用了DateFormat 类提供的常量: SHORT, MEDIUM, LONG, 和<br />
FULL. 要知道获取时间和日期格式化过程的更多的方法和选项, 请看Sun 公司Web<br />
站点上的解释.<br />
运行我们的例子程序的时候, 它将向标准输出设备输出下面的内容:<br />
9/29/01 8:44 PM<br />
Sep 29, 2001 8:44:45 PM<br />
September 29, 2001 8:44:45 PM EDT<br />
Saturday, September 29, 2001 8:44:45 PM EDT</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.jackrun.com/archives/55.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>FreeMarker设计指南(完整整理)</title>
		<link>http://blog.jackrun.com/archives/74.html</link>
		<comments>http://blog.jackrun.com/archives/74.html#comments</comments>
		<pubDate>Mon, 12 Mar 2007 15:42:31 +0000</pubDate>
		<dc:creator>Peltason</dc:creator>
				<category><![CDATA[J2EE]]></category>
		<category><![CDATA[freemark]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[视图层工具]]></category>

		<guid isPermaLink="false">http://www.jackrun.com/?p=74</guid>
		<description><![CDATA[快速入门
（1）模板 + 数据模型 = 输出
FreeMarker基于设计者和程序员是具有不同专业技能的不同个体的观念他们是分工劳动的：
设计者专注于表示——创建HTML文件、图片、Web页面的其它可视化方面；
程序员创建系统，生成设计页面要显示的数据。
经常会遇到的问题是：在Web页面（或其它类型的文档）中显示的信息在设计页面时是无效的，是基于动态数据的。在这里，你可以在HTML（或其它要输出的文本）中加入一些特定指令，FreeMarker会在输出页面给最终用户时，用适当的数据替代这些代码。

先来解释一下freemaker的基本语法了，
&#60;# &#8230; &#62; 中存放所有freemaker的内容，之外的内容全部原样输出。
&#60;@ &#8230; /&#62; 是函数调用
两个定界符内的内容中，第一个符号表示指令或者函数名，其后的跟随参数。freemaker提供的控制包括如下：
&#60;#if condition&#62;&#60;#elseif condition&#62;&#60;#else&#62; 条件判断
&#60;#list hash_or_seq as var&#62; 遍历hash表或者collection（freemaker称作sequence）的成员
&#60;#macro name param1 param2 &#8230; &#62;&#60;#nested param&#62; 宏，无返回参数
&#60;#function name param1 param2&#62;&#60;#return val&#62;函数，有返回参数
var?member_function(&#8230;) 用函数对var进行转换，freemaker称为build-ins。实际内部实现类似member_function(var, &#8230;)
stringA[M .. N] 取子字符串，类似substring(stringA, M, N)
{key:value, key2:value2 &#8230;} 直接定义一个hash表
[item0, item1, item2 ...] 直接定义一个序列
hash0[key0] 存取hash表中key对应的元素
seq0[5] 存取序列指定下标的元素
&#60;@function1 param0 param1 &#8230; /&#62; 调用函数function1
&#60;@macro0 param0 param1 ; nest_param0 nest_param1 [...]]]></description>
			<content:encoded><![CDATA[<h2>快速入门</h2>
<h3>（1）模板 + 数据模型 = 输出</h3>
<p>FreeMarker基于设计者和程序员是具有不同专业技能的不同个体的观念他们是分工劳动的：<br />
设计者专注于表示——创建HTML文件、图片、Web页面的其它可视化方面；<br />
程序员创建系统，生成设计页面要显示的数据。<br />
经常会遇到的问题是：在Web页面（或其它类型的文档）中显示的信息在设计页面时是无效的，是基于动态数据的。在这里，你可以在HTML（或其它要输出的文本）中加入一些特定指令，FreeMarker会在输出页面给最终用户时，用适当的数据替代这些代码。</p>
<p><span id="more-74"></span></p>
<p>先来解释一下freemaker的基本语法了，<br />
<span style="font-family: Courier New;"><span style="color: #0000ff;">&lt;# &#8230; &gt;</span> 中存放所有freemaker的内容，之外的内容全部原样输出。<br />
<span style="color: #0000ff;">&lt;@ &#8230; /&gt;</span> 是函数调用<br />
两个定界符内的内容中，第一个符号表示指令或者函数名，其后的跟随参数。freemaker提供的控制包括如下：<br />
<span style="color: #0000ff;">&lt;#if condition&gt;&lt;#elseif condition&gt;&lt;#else&gt;</span> 条件判断<br />
<span style="color: #0000ff;">&lt;#list hash_or_seq as var&gt;</span> 遍历hash表或者collection（freemaker称作sequence）的成员<br />
<span style="color: #0000ff;">&lt;#macro name param1 param2 &#8230; &gt;&lt;#nested param&gt;</span> 宏，无返回参数<br />
<span style="color: #0000ff;">&lt;#function name param1 param2&gt;&lt;#return val&gt;</span>函数，有返回参数<br />
<span style="color: #0000ff;">var?member_function(&#8230;)</span> 用函数对var进行转换，freemaker称为build-ins。实际内部实现类似member_function(var, &#8230;)<br />
<span style="color: #0000ff;">stringA[M .. N]</span> 取子字符串，类似substring(stringA, M, N)<br />
<span style="color: #0000ff;">{key:value, key2:value2 &#8230;}</span> 直接定义一个hash表<br />
<span style="color: #0000ff;">[item0, item1, item2 ...]</span> 直接定义一个序列<br />
<span style="color: #0000ff;">hash0[key0]</span> 存取hash表中key对应的元素<br />
<span style="color: #0000ff;">seq0[5]</span> 存取序列指定下标的元素<br />
<span style="color: #0000ff;">&lt;@function1 param0 param1 &#8230; /&gt;</span> 调用函数function1<br />
<span style="color: #0000ff;">&lt;@macro0 param0 param1 ; nest_param0 nest_param1 &#8230;&gt; nest_body &lt;</span></span><a><span style="font-family: Courier New; color: #0000ff;">/@macro</span></a><span style="font-family: Courier New;"><span style="color: #0000ff;">&gt;</span> 调用宏，并处理宏的嵌套<br />
<span style="color: #0000ff;">&lt;#assign var = value &gt;</span> 定义变量并初始化<br />
<span style="color: #0000ff;">&lt;#local var = value&gt;</span> 在 macro 或者 function 中定义局部变量并初始化<br />
<span style="color: #0000ff;">&lt;#global var = value &gt;</span> 定义全局变量并初始化<br />
<span style="color: #0000ff;">${var}</span> 输出并替换为表达式的值<br />
<span style="color: #0000ff;">&lt;#visit xmlnode&gt;</span> 调用macro匹配xmlnode本身及其子节点<br />
<span style="color: #0000ff;">&lt;#recurse xmlnode&gt;</span> 调用macro匹配xmlnode的子节点</span></p>
<p>下面是一个例子：</p>
<pre>&lt;html&gt;
&lt;head&gt;
  &lt;title&gt;Welcome!&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;h1&gt;Welcome ${user}!&lt;/h1&gt;
  &lt;p&gt;Our latest product:
  &lt;a href="${latestProduct.url}"&gt;${latestProduct.name}&lt;/a&gt;!
&lt;/body&gt;
&lt;/html&gt;</pre>
<p>这个例子是在简单的HTML中加入了一些由${…}包围的特定代码，这些特定代码是FreeMarker的指令，而包含FreeMarker的指令的文件就称为模板（Template）。<br />
至于user、latestProduct.url和latestProduct.name来自于数据模型（data model）。<br />
数据模型由程序员编程来创建，向模板提供变化的信息，这些信息来自于数据库、文件，甚至于在程序中直接生成。<br />
模板设计者不关心数据从那儿来，只知道使用已经建立的数据模型。</p>
<p>下面是一个可能的数据模型：</p>
<pre>(root)
  |
  +- user = "Big Joe"
  |
  +- latestProduct
      |
      +- url = "products/greenmouse.html"
      |
      +- name = "green mouse"</pre>
<p>数据模型类似于计算机的文件系统，latestProduct可以看作是目录。</p>
<h3>2、数据模型</h3>
<h4>（1）基础</h4>
<p>在快速入门中介绍了在模板中使用的三种基本对象类型：scalars、hashes 和sequences，其实还可以有其它更多的能力：</p>
<ul>
<li>scalars：存储单值</li>
</ul>
<ul>
<li>hashes：充当其它对象的容器，每个都关联一个唯一的查询名字</li>
</ul>
<ul>
<li>sequences：充当其它对象的容器，按次序访问</li>
</ul>
<ul>
<li>方法：通过传递的参数进行计算，以新对象返回结果</li>
</ul>
<ul>
<li>用户自定义FTL标记：宏和变换器</li>
</ul>
<p>通常每个变量只具有上述的一种能力，但一个变量可以具有多个上述能力，如下面的例子：</p>
<pre>(root)
 |
 +- mouse = "Yerri"
     |
     +- age = 12
     |
     +- color = "brown"&gt;</pre>
<p>mouse既是scalars又是hashes，将上面的数据模型合并到下面的模板：</p>
<pre>${mouse}       &lt;#-- use mouse as scalar --&gt;
${mouse.age}   &lt;#-- use mouse as hash --&gt;
${mouse.color} &lt;#-- use mouse as hash --&gt;</pre>
<p>输出结果是：</p>
<pre>Yerri
12
brown</pre>
<h4>（2）Scalar变量</h4>
<p>Scalar变量存储单值，可以是：</p>
<ul>
<li>字符串：简单文本，在模板中使用引号（单引号或双引号）括起</li>
</ul>
<ul>
<li>数字：在模板中直接使用数字值</li>
</ul>
<ul>
<li>日期：存储日期/时间相关的数据，可以是日期、时间或日期-时间（Timestamp）；通常情况，日期值由程序员加到数据模型中，设计者只需要显示它们</li>
</ul>
<ul>
<li>布尔值：true或false，通常在&lt;#if …&gt;标记中使用</li>
</ul>
<h4>（3）hashes 、sequences和集合</h4>
<p>有些变量不包含任何可显示的内容，而是作为容器包含其它变量，者有两种类型：</p>
<ul>
<li>hashes：具有一个唯一的查询名字和它包含的每个变量相关联</li>
</ul>
<ul>
<li>sequences：使用数字和它包含的每个变量相关联，索引值从0开始</li>
</ul>
<p>集合变量通常类似sequences，除非无法访问它的大小和不能使用索引来获得它的子变量；集合可以看作只能由&lt;#list …&gt;指令使用的受限sequences</p>
<h4>（4）方法</h4>
<p>方法变量通常是基于给出的参数计算值。</p>
<p>下面的例子假设程序员已经将方法变量avg放到数据模型中，用来计算数字平均值：</p>
<pre>The average of 3 and 5 is: ${avg(3, 5)}
The average of 6 and 10 and 20 is: ${avg(6, 10, 20)}
The average of the price of python and elephant is:
    ${avg(animals.python.price, animals.elephant.price)}</pre>
<h4>（5）宏和变换器</h4>
<p>宏和变换器变量是用户自定义指令（自定义FTL标记），会在后面讲述这些高级特性</p>
<h4>（6）节点</h4>
<p>节点变量表示为树型结构中的一个节点，通常在XML处理中使用，会在后面的专门章节中讲</p>
<h3>3、模板</h3>
<h4>（1）整体结构</h4>
<p>模板使用FTL（FreeMarker模板语言）编写，是下面各部分的一个组合：</p>
<ul>
<li>文本：直接输出</li>
<li>Interpolation：由${和}，或#{和}来限定，计算值替代输出</li>
<li>FTL标记：FreeMarker指令，和HTML标记类似，名字前加#予以区分，不会输出</li>
<li>注释：由&lt;#&#8211;和&#8211;&gt;限定，不会输出</li>
</ul>
<p>下面是以一个具体模板例子：</p>
<pre>&lt;html&gt;
&lt;head&gt;
  &lt;title&gt;Welcome!&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;#-- Greet the user with his/her name --&gt;
  &lt;h1&gt;Welcome ${user}!&lt;/h1&gt;
  &lt;p&gt;We have these animals:
  &lt;ul&gt;
  &lt;#list animals as being&gt;
    &lt;li&gt;${being.name} for ${being.price} Euros
  &lt;/#list&gt;
  &lt;/ul&gt;
&lt;/body&gt;
&lt;/html&gt;</pre>
<p>注意事项：</p>
<ul>
<li>FTL区分大小写，所以list是正确的FTL指令，而List不是；${name}和${NAME}是不同的</li>
</ul>
<ul>
<li>Interpolation只能在文本中使用</li>
</ul>
<ul>
<li>FTL标记不能位于另一个FTL标记内部，例如：</li>
</ul>
<pre>&lt;#if &lt;#include 'foo'&gt;='bar'&gt;...&lt;/if&gt;</pre>
<ul>
<li>注释可以位于FTL标记和Interpolation内部，如下面的例子：</li>
</ul>
<pre>&lt;h1&gt;Welcome ${user &lt;#-- The name of user --&gt;}!&lt;/h1&gt;
&lt;p&gt;We have these animals:
&lt;ul&gt;
&lt;#list &lt;#-- some comment... --&gt; animals as &lt;#-- again... --&gt; being&gt;
...</pre>
<ul>
<li>余的空白字符会在模板输出时移除</li>
</ul>
<h4>（2）指令</h4>
<p>在FreeMarker中，使用FTL标记引用指令。有三种FTL标记，这和HTML标记是类似的：</p>
<ul>
<li>开始标记：&lt;#directivename parameters&gt;</li>
</ul>
<ul>
<li>结束标记：&lt;/#directivename&gt;</li>
</ul>
<ul>
<li>空内容指令标记：&lt;#directivename parameters/&gt;</li>
</ul>
<p>有两种类型的指令：预定义指令和用户定义指令。</p>
<p>用户定义指令要使用@替换#，如&lt;@mydirective&gt;&#8230;&lt;/@mydirective&gt;（会在后面讲述）。</p>
<p>FTL标记不能够交叉，而应该正确的嵌套，如下面的代码是错误的：</p>
<pre>&lt;ul&gt;
&lt;#list animals as being&gt;
  &lt;li&gt;${being.name} for ${being.price} Euros
  &lt;#if use = "Big Joe"&gt;
     (except for you)
&lt;/#list&gt;
&lt;/#if&gt; &lt;#-- WRONG! --&gt;
&lt;/ul&gt;</pre>
<p>如果使用不存在的指令，FreeMarker不会使用模板输出，而是产生一个错误消息。</p>
<p>FreeMarker会忽略FTL标记中的空白字符，如下面的例子：</p>
<pre>&lt;#list
  animals       as
     being
&gt;
${being.name} for ${being.price} Euros
&lt;/#list    &gt;</pre>
<p>但是，&lt;、&lt;/和指令之间不允许有空白字符。</p>
<h4>（3）表达式</h4>
<p><strong>直接指定值</strong></p>
<ul>
<li>字符串</li>
</ul>
<p>使用单引号或双引号限定</p>
<p>如果包含特殊字符需要转义，如下面的例子：</p>
<pre>${"It's \"quoted\" and
this is a backslash: \\"}

${'It\'s "quoted" and
this is a backslash: \\'}</pre>
<p>输出结果是：</p>
<pre>It's "quoted" and
this is a backslash: \

It's "quoted" and
this is a backslash: \</pre>
<p>下面是支持的转义序列：</p>
<table class="wikitable" border="1">
<tbody>
<tr>
<th>转义序列</th>
<th>含义</th>
</tr>
<tr>
<td>\&#8221;</td>
<td>双引号(u0022)</td>
</tr>
<tr>
<td>\&#8217;</td>
<td>单引号(u0027)</td>
</tr>
<tr>
<td></td>
<td>反斜杠(u005C)</td>
</tr>
<tr>
<td>\n</td>
<td>换行(u000A)</td>
</tr>
<tr>
<td>\r</td>
<td>Return (u000D)</td>
</tr>
<tr>
<td>\t</td>
<td>Tab (u0009)</td>
</tr>
<tr>
<td>\b</td>
<td>Backspace (u0008)</td>
</tr>
<tr>
<td>\f</td>
<td>Form feed (u000C)</td>
</tr>
<tr>
<td>\l</td>
<td>&lt;</td>
</tr>
<tr>
<td>\g</td>
<td>&gt;</td>
</tr>
<tr>
<td>\a</td>
<td>&amp;</td>
</tr>
<tr>
<td>\{</td>
<td>{</td>
</tr>
<tr>
<td>\xCode</td>
<td>4位16进制Unicode代码</td>
</tr>
</tbody>
</table>
<p>有一类特殊的字符串称为raw字符串，被认为是纯文本，其中的\和{等不具有特殊含义，该类字符串在引号前面加r，下面是一个例子：</p>
<pre>${r"${foo}"}

${r"C:\foo\bar"}</pre>
<p>输出的结果是：</p>
<pre>${foo}

C:\foo\bar</pre>
<ul>
<li>数字</li>
</ul>
<p>直接输入，不需要引号</p>
<p>精度数字使用“.”分隔，不能使用分组符号</p>
<p>目前版本不支持科学计数法，所以“1E3”是错误的</p>
<p>不能省略小数点前面的0，所以“.5”是错误的</p>
<p>数字8、+8、08和8.00都是相同的</p>
<ul>
<li>布尔值</li>
</ul>
<p>true和false，不使用引号</p>
<ul>
<li>序列</li>
</ul>
<p>由逗号分隔的子变量列表，由方括号限定，下面是一个例子：</p>
<pre>&lt;#list ["winter", "spring", "summer", "autumn"] as x&gt;
${x}
&lt;/#list&gt;</pre>
<p>输出的结果是：</p>
<pre>winter
spring
summer
autumn</pre>
<p>列表的项目是表达式，所以可以有下面的例子：</p>
<pre>[2 + 2, [1, 2, 3, 4], "whatnot"]</pre>
<p>可以使用数字范围定义数字序列，例如2..5等同于[2, 3, 4, 5]，但是更有效率，注意数字范围没有方括号</p>
<p>可以定义反递增的数字范围，如5..2</p>
<ul>
<li>散列（hash）</li>
</ul>
<p>由逗号分隔的键/值列表，由大括号限定，键和值之间用冒号分隔，下面是一个例子：</p>
<pre>{"name":"green mouse", "price":150}</pre>
<p>键和值都是表达式，但是键必须是字符串</p>
<p><strong>获取变量</strong></p>
<ul>
<li>顶层变量： ${variable}，变量名只能是字母、数字、下划线、$、@和#的组合，且不能以数字开头</li>
</ul>
<ul>
<li>从散列中获取数据</li>
</ul>
<p>可以使用点语法或方括号语法，假设有下面的数据模型：</p>
<pre>(root)
 |
 +- book
 |   |
 |   +- title = "Breeding green mouses"
 |   |
 |   +- author
 |       |
 |       +- name = "Julia Smith"
 |       |
 |       +- info = "Biologist, 1923-1985, Canada"
 |
 +- test = "title"</pre>
<p>下面都是等价的：</p>
<pre>book.author.name
book["author"].name
book.author.["name"]
book["author"]["name"]</pre>
<p>使用点语法，变量名字有顶层变量一样的限制，但方括号语法没有该限制，因为名字是任意表达式的结果</p>
<ul>
<li>从序列获得数据：和散列的方括号语法语法一样，只是方括号中的表达式值必须是数字；注意：第一个项目的索引是0</li>
</ul>
<p>序列片断：使用[startIndex..endIndex]语法，从序列中获得序列片断（也是序列）；startIndex和endIndex是结果为数字的表达式</p>
<ul>
<li>特殊变量：FreeMarker内定义变量，使用.variablename语法访问</li>
</ul>
<p><strong>字符串操作 </strong></p>
<ul>
<li>Interpolation（或连接操作）</li>
</ul>
<p>可以使用${..}（或#{..}）在文本部分插入表达式的值，例如：</p>
<pre>${"Hello ${user}!"}

${"${user}${user}${user}${user}"}</pre>
<p>可以使用+操作符获得同样的结果</p>
<pre>${"Hello " + user + "!"}

${user + user + user + user}</pre>
<p>${..}只能用于文本部分，下面的代码是错误的：</p>
<pre>&lt;#if ${isBig}&gt;Wow!&lt;/#if&gt;

&lt;#if "${isBig}"&gt;Wow!&lt;/#if&gt;</pre>
<p>应该写成：</p>
<pre>&lt;#if isBig&gt;Wow!&lt;/#if&gt;</pre>
<ul>
<li>子串</li>
</ul>
<p>例子（假设user的值为“Big Joe”）：</p>
<pre>${user[0]}${user[4]}

${user[1..4]}</pre>
<p>结果是（注意第一个字符的索引是0）：</p>
<pre>BJ

ig J</pre>
<p><strong>序列操作 </strong></p>
<ul>
<li>连接操作：和字符串一样，使用+，下面是一个例子：</li>
</ul>
<pre>&lt;#list ["Joe", "Fred"] + ["Julia", "Kate"] as user&gt;
- ${user}
&lt;/#list&gt;</pre>
<p>输出结果是：</p>
<pre>- Joe
- Fred
- Julia
- Kate</pre>
<p><strong>散列操作 </strong></p>
<ul>
<li>连接操作：和字符串一样，使用+，如果具有相同的key，右边的值替代左边的值，例如：</li>
</ul>
<pre>&lt;#assign ages = {"Joe":23, "Fred":25} + {"Joe":30, "Julia":18}&gt;
- Joe is ${ages.Joe}
- Fred is ${ages.Fred}
- Julia is ${ages.Julia}</pre>
<p>输出结果是：</p>
<pre>- Joe is 30
- Fred is 25
- Julia is 18</pre>
<p><strong>算术运算 </strong></p>
<ul>
<li>＋、－、×、／、％，下面是一个例子：</li>
</ul>
<pre>${x * x - 100}
${x / 2}
${12 % 10}</pre>
<p>输出结果是（假设x为5）：</p>
<pre>-75
2.5
2</pre>
<p>操作符两边必须是数字，因此下面的代码是错误的：</p>
<pre>${3 * "5"} &lt;#-- WRONG! --&gt;</pre>
<p>使用+操作符时，如果一边是数字，一边是字符串，就会自动将数字转换为字符串，例如：</p>
<pre>${3 + "5"}</pre>
<p>输出结果是：</p>
<pre>35</pre>
<p>使用内建的int（后面讲述）获得整数部分，例如：</p>
<pre>${(x/2)?int}
${1.1?int}
${1.999?int}
${-1.1?int}
${-1.999?int}</pre>
<p>输出结果是（假设x为5）：</p>
<pre>2
1
1
-1
-1</pre>
<ul>
<li>比较操作符</li>
</ul>
<p>使用=（或==，完全相等）测试两个值是否相等，使用!= 测试两个值是否不相等</p>
<p>=和!=两边必须是相同类型的值，否则会产生错误，例如&lt;#if 1 = &#8220;1&#8243;&gt;会引起错误</p>
<p>Freemarker是精确比较，所以对&#8221;x&#8221;、&#8221;x  &#8220;和&#8221;X&#8221;是不相等的</p>
<p>对数字和日期可以使用&lt;、&lt;=、&gt;和&gt;=，但不能用于字符串</p>
<p>由于Freemarker会将&gt;解释成FTL标记的结束字符，所以对于&gt;和&gt;=可以使用括号来避免这种情况，例如&lt;#if (x &gt; y)&gt;</p>
<p>另一种替代的方法是，使用lt、lte、gt和gte来替代&lt;、&lt;=、&gt;和&gt;=</p>
<ul>
<li>逻辑操作符</li>
</ul>
<p>&amp;&amp;（and）、||（or）、!（not），只能用于布尔值，否则会产生错误</p>
<p>例子：</p>
<pre>&lt;#if x &lt; 12 &amp;&amp; color = "green"&gt;
  We have less than 12 things, and they are green.
&lt;/#if&gt;
&lt;#if !hot&gt; &lt;#-- here hot must be a boolean --&gt;
  It's not hot.
&lt;/#if&gt;</pre>
<ul>
<li>内建函数</li>
</ul>
<p>内建函数的用法类似访问散列的子变量，只是使用“?”替代“.”，下面列出常用的一些函数</p>
<ul>
<li>
<ul>
<li>字符串使用的：</li>
</ul>
</li>
</ul>
<p>html：对字符串进行HTML编码</p>
<p>cap_first：使字符串第一个字母大写</p>
<p>lower_case：将字符串转换成小写</p>
<p>upper_case：将字符串转换成大写</p>
<p>trim：去掉字符串前后的空白字符</p>
<ul>
<li>
<ul>
<li>序列使用的：</li>
</ul>
</li>
</ul>
<p>size：获得序列中元素的数目</p>
<ul>
<li>
<ul>
<li>数字使用的：</li>
</ul>
</li>
</ul>
<p>int：取得数字的整数部分（如-1.9?int的结果是-1）</p>
<p>例子（假设test保存字符串&#8221;Tom &amp; Jerry&#8221;）：</p>
<pre>${test?html}
${test?upper_case?html}</pre>
<p>输出结果是：</p>
<pre>Tom &amp;amp; Jerry
TOM &amp;amp; JERRY</pre>
<ul>
<li>操作符优先顺序</li>
</ul>
<table class="wikitable" border="1">
<tbody>
<tr>
<th>操作符组</th>
<th> 操作符</th>
</tr>
<tr>
<td>后缀</td>
<td>[subvarName] [subStringRange] . (methodParams)</td>
</tr>
<tr>
<td>一元</td>
<td>+expr、-expr、!</td>
</tr>
<tr>
<td>内建</td>
<td>?</td>
</tr>
<tr>
<td>乘法</td>
<td>*、 / 、%</td>
</tr>
<tr>
<td>加法</td>
<td>+、-</td>
</tr>
<tr>
<td>关系</td>
<td>&lt;、&gt;、&lt;=、&gt;=（lt、lte、gt、gte）</td>
</tr>
<tr>
<td>相等</td>
<td>==（=）、!=</td>
</tr>
<tr>
<td>逻辑and</td>
<td>&amp;&amp;</td>
</tr>
<tr>
<td>逻辑or</td>
<td>双竖线</td>
</tr>
<tr>
<td>数字范围</td>
<td>..</td>
</tr>
</tbody>
</table>
<h4>（4）Interpolation</h4>
<p>Interpolation有两种类型：</p>
<ol>
<li>通用Interpolation：${expr}</li>
</ol>
<ol>
<li>数字Interpolation：#{expr}或#{expr; format}</li>
</ol>
<p>注意：Interpolation只能用于文本部分</p>
<ul>
<li>通用Interpolation</li>
</ul>
<p>插入字符串值：直接输出表达式结果</p>
<p>插入数字值：根据缺省格式（由#setting指令设置）将表达式结果转换成文本输出；可以使用内建函数string格式化单个Interpolation，下面是一个例子：</p>
<pre>&lt;#setting number_format="currency"/&gt;
&lt;#assign answer=42/&gt;
${answer}
${answer?string}  &lt;#-- the same as ${answer} --&gt;
${answer?string.number}
${answer?string.currency}
${answer?string.percent}</pre>
<p>输出结果是：</p>
<pre>$42.00
$42.00
42
$42.00
4,200%</pre>
<p>插入日期值：根据缺省格式（由#setting指令设置）将表达式结果转换成文本输出；可以使用内建函数string格式化单个Interpolation，下面是一个使用格式模式的例子：</p>
<pre>${lastUpdated?string("yyyy-MM-dd HH:mm:ss zzzz")}
${lastUpdated?string("EEE, MMM d, ''yy")}
${lastUpdated?string("EEEE, MMMM dd, yyyy, hh:mm:ss a '('zzz')'")}</pre>
<p>输出的结果类似下面的格式：</p>
<pre>2003-04-08 21:24:44 Pacific Daylight Time
Tue, Apr 8, '03
Tuesday, April 08, 2003, 09:24:44 PM (PDT)</pre>
<p>插入布尔值：根据缺省格式（由#setting指令设置）将表达式结果转换成文本输出；可以使用内建函数string格式化单个Interpolation，下面是一个例子：</p>
<pre>&lt;#assign foo=true/&gt;
${foo?string("yes", "no")}</pre>
<p>输出结果是：</p>
<pre>yes</pre>
<ul>
<li>数字Interpolation的#{expr; format}形式可以用来格式化数字，format可以是：</li>
</ul>
<p>mX：小数部分最小X位</p>
<p>MX：小数部分最大X位</p>
<p>例子：</p>
<pre>&lt;#-- If the language is US English the output is: --&gt;
&lt;#assign x=2.582/&gt;
&lt;#assign y=4/&gt;
#{x; M2}   &lt;#-- 2.58 --&gt;
#{y; M2}   &lt;#-- 4    --&gt;
#{x; m1}   &lt;#-- 2.6 --&gt;
#{y; m1}   &lt;#-- 4.0 --&gt;
#{x; m1M2} &lt;#-- 2.58 --&gt;
#{y; m1M2} &lt;#-- 4.0  --&gt;</pre>
<h3>4、杂项</h3>
<h4>（1）用户定义指令</h4>
<p>宏和变换器变量是两种不同类型的用户定义指令，它们之间的区别是宏是在模板中使用macro指令定义，而变换器是在模板外由程序定义，这里只介绍宏</p>
<ul>
<li>基本用法</li>
</ul>
<p>宏是和某个变量关联的模板片断，以便在模板中通过用户定义指令使用该变量，下面是一个例子：</p>
<pre>&lt;#macro greet&gt;
  &lt;font size="+2"&gt;Hello Joe!&lt;/font&gt;
&lt;/#macro&gt;</pre>
<p>作为用户定义指令使用宏变量时，使用@替代FTL标记中的#</p>
<pre>&lt;@greet&gt;&lt;/@greet&gt;</pre>
<p>如果没有体内容，也可以使用：</p>
<pre>&lt;@greet/&gt;</pre>
<ul>
<li>参数</li>
</ul>
<p>在macro指令中可以在宏变量之后定义参数，如：</p>
<pre>&lt;#macro greet person&gt;
  &lt;font size="+2"&gt;Hello ${person}!&lt;/font&gt;
&lt;/#macro&gt;</pre>
<p>可以这样使用这个宏变量：</p>
<pre>&lt;@greet person="Fred"/&gt; and &lt;@greet person="Batman"/&gt;</pre>
<p>输出结果是：</p>
<pre>  &lt;font size="+2"&gt;Hello Fred!&lt;/font&gt;

 and   &lt;font size="+2"&gt;Hello Batman!&lt;/font&gt;</pre>
<p>宏的参数是FTL表达式，所以下面的代码具有不同的意思：</p>
<pre>&lt;@greet person=Fred/&gt;</pre>
<p>这意味着将Fred变量的值传给person参数，该值不仅是字符串，还可以是其它类型，甚至是复杂的表达式</p>
<p>可以有多参数，下面是一个例子：</p>
<pre>&lt;#macro greet person color&gt;
  &lt;font size="+2" color="${color}"&gt;Hello ${person}!&lt;/font&gt;
&lt;/#macro&gt;</pre>
<p>可以这样使用该宏变量：</p>
<pre>&lt;@greet person="Fred" color="black"/&gt;</pre>
<p>其中参数的次序是无关的，因此下面是等价的：</p>
<pre>&lt;@greet color="black" person="Fred"/&gt;</pre>
<p>只能使用在macro指令中定义的参数，并且对所有参数赋值，所以下面的代码是错误的：</p>
<pre>&lt;@greet person="Fred" color="black" background="green"/&gt;
&lt;@greet person="Fred"/&gt;</pre>
<p>可以在定义参数时指定缺省值，如：</p>
<pre>&lt;#macro greet person color="black"&gt;
  &lt;font size="+2" color="${color}"&gt;Hello ${person}!&lt;/font&gt;
&lt;/#macro&gt;</pre>
<p>这样&lt;@greet person=&#8221;Fred&#8221;/&gt;就正确了</p>
<p>宏的参数是局部变量，只能在宏定义中有效</p>
<ul>
<li>嵌套内容</li>
</ul>
<p>用户定义指令可以有嵌套内容，使用&lt;#nested&gt;指令执行指令开始和结束标记之间的模板片断</p>
<p>例子：</p>
<pre>&lt;#macro border&gt;
  &lt;table border=4 cellspacing=0 cellpadding=4&gt;&lt;tr&gt;&lt;td&gt;
    &lt;#nested&gt;
  &lt;/tr&gt;&lt;/td&gt;&lt;/table&gt;
&lt;/#macro&gt;</pre>
<p>这样使用该宏变量：</p>
<pre>&lt;@border&gt;The bordered text&lt;/@border&gt;</pre>
<p>输出结果：</p>
<pre>  &lt;table border=4 cellspacing=0 cellpadding=4&gt;&lt;tr&gt;&lt;td&gt;
    The bordered text
  &lt;/tr&gt;&lt;/td&gt;&lt;/table&gt;</pre>
<p>&lt;#nested&gt;指令可以被多次调用，例如：</p>
<pre>&lt;#macro do_thrice&gt;
  &lt;#nested&gt;
  &lt;#nested&gt;
  &lt;#nested&gt;
&lt;/#macro&gt;
&lt;@do_thrice&gt;
  Anything.
&lt;/@do_thrice&gt;</pre>
<p>输出结果：</p>
<pre>  Anything.
  Anything.
  Anything.</pre>
<p>嵌套内容可以是有效的FTL，下面是一个有些复杂的例子：  <tt> &lt;@border&gt; &lt;ul&gt; &lt;@do_thrice&gt; &lt;li&gt;&lt;@greet person="Joe"/&gt; &lt;/@do_thrice&gt; &lt;/ul&gt; &lt;/@border&gt; }}} 输出结果：</tt></p>
<pre><tt>  &lt;table border=4 cellspacing=0 cellpadding=4&gt;&lt;tr&gt;&lt;td&gt;
      &lt;ul&gt;
    &lt;li&gt;&lt;font size="+2"&gt;Hello Joe!&lt;/font&gt;
    &lt;li&gt;&lt;font size="+2"&gt;Hello Joe!&lt;/font&gt;
    &lt;li&gt;&lt;font size="+2"&gt;Hello Joe!&lt;/font&gt;
  &lt;/ul&gt;
  &lt;/tr&gt;&lt;/td&gt;&lt;/table&gt;</tt></pre>
<p><tt>宏定义中的局部变量对嵌套内容是不可见的，例如：</tt></p>
<pre><tt>&lt;#macro repeat count&gt;
  &lt;#local y = "test"&gt;
  &lt;#list 1..count as x&gt;
    ${y} ${count}/${x}: &lt;#nested&gt;
  &lt;/#list&gt;
&lt;/#macro&gt;
&lt;@repeat count=3&gt;${y?default("?")} ${x?default("?")} ${count?default("?")}&lt;/@repeat&gt;</tt></pre>
<p><tt>输出结果：</tt></p>
<pre><tt>    test 3/1: ? ? ?
    test 3/2: ? ? ?
    test 3/3: ? ? ?</tt></pre>
<ul>
<li><tt>在宏定义中使用循环变量</tt></li>
</ul>
<p><tt>用户定义指令可以有循环变量，通常用于重复嵌套内容，基本用法是：作为nested指令的参数传递循环变量的实际值，而在调用用户定义指令时，在&lt;@…&gt;开始标记的参数后面指定循环变量的名字</tt></p>
<p><tt>例子：</tt></p>
<pre><tt>&lt;#macro repeat count&gt;
  &lt;#list 1..count as x&gt;
    &lt;#nested x, x/2, x==count&gt;
  &lt;/#list&gt;
&lt;/#macro&gt;
&lt;@repeat count=4 ; c, halfc, last&gt;
  ${c}. ${halfc}&lt;#if last&gt; Last!&lt;/#if&gt;
&lt;/@repeat&gt;</tt></pre>
<p><tt>输出结果：</tt></p>
<pre><tt>  1. 0.5
  2. 1
  3. 1.5
  4. 2 Last!</tt></pre>
<p><tt>指定的循环变量的数目和用户定义指令开始标记指定的不同不会有问题</tt></p>
<p><tt>调用时少指定循环变量，则多指定的值不可见</tt></p>
<p><tt>调用时多指定循环变量，多余的循环变量不会被创建</tt></p>
<h4><tt>（2）在模板中定义变量</tt></h4>
<p><tt>在模板中定义的变量有三种类型：</tt></p>
<ul>
<li><tt>plain变量：可以在模板的任何地方访问，包括使用include指令插入的模板，使用assign指令创建和替换</tt></li>
</ul>
<ul>
<li><tt>局部变量：在宏定义体中有效，使用local指令创建和替换</tt></li>
</ul>
<ul>
<li><tt>循环变量：只能存在于指令的嵌套内容，由指令（如list）自动创建</tt></li>
</ul>
<p><tt>宏的参数是局部变量，而不是循环变量；局部变量隐藏（而不是覆盖）同名的plain变量；循环变量隐藏同名的局部变量和plain变量，下面是一个例子：</tt></p>
<pre><tt>&lt;#assign x = "plain"&gt;
1. ${x}  &lt;#-- we see the plain var. here --&gt;
&lt;@test/&gt;
6. ${x}  &lt;#-- the value of plain var. was not changed --&gt;
&lt;#list ["loop"] as x&gt;
  7. ${x}  &lt;#-- now the loop var. hides the plain var. --&gt;
&lt;#assign x = "plain2"&gt; &lt;#-- replace the plain var, hiding does not mater here --&gt;
 8. ${x}  &lt;#-- it still hides the plain var. --&gt;
&lt;/#list&gt;
   9. ${x}  &lt;#-- the new value of plain var. --&gt;
&lt;#macro test&gt;
  2. ${x}  &lt;#-- we still see the plain var. here --&gt;
  &lt;#local x = "local"&gt;
  3. ${x}  &lt;#-- now the local var. hides it --&gt;
  &lt;#list ["loop"] as x&gt;
    4. ${x}  &lt;#-- now the loop var. hides the local var. --&gt;
  &lt;/#list&gt;
  5. ${x}  &lt;#-- now we see the local var. again --&gt;
&lt;/#macro&gt;</tt></pre>
<p><tt>输出结果：</tt></p>
<pre><tt>1. plain
  2. plain
  3. local
    4. loop
  5. local
6. plain
    7. loop
    8. loop
9. plain2</tt></pre>
<p><tt>内部循环变量隐藏同名的外部循环变量，如：</tt></p>
<pre><tt>&lt;#list ["loop 1"] as x&gt;
  ${x}
  &lt;#list ["loop 2"] as x&gt;
    ${x}
    &lt;#list ["loop 3"] as x&gt;
      ${x}
    &lt;/#list&gt;
    ${x}
  &lt;/#list&gt;
  ${x}
&lt;/#list&gt;</tt></pre>
<p><tt>输出结果：</tt></p>
<pre><tt>  loop 1
    loop 2
      loop 3
    loop 2
  loop 1</tt></pre>
<p><tt>模板中的变量会隐藏（而不是覆盖）数据模型中同名变量，如果需要访问数据模型中的同名变量，使用特殊变量global，下面的例子假设数据模型中的user的值是Big Joe：</tt></p>
<pre><tt>&lt;#assign user = "Joe Hider"&gt;
${user}          &lt;#-- prints: Joe Hider --&gt;
${.globals.user} &lt;#-- prints: Big Joe --&gt;</tt></pre>
<h4><tt>（3）名字空间</tt></h4>
<p><tt>通常情况，只使用一个名字空间，称为主名字空间</tt></p>
<p><tt>为了创建可重用的宏、变换器或其它变量的集合（通常称库），必须使用多名字空间，其目的是防止同名冲突</tt></p>
<ul>
<li><tt>创建库</tt></li>
</ul>
<p><tt>下面是一个创建库的例子（假设保存在lib/my_test.ftl中）：</tt></p>
<pre><tt>&lt;#macro copyright date&gt;
  &lt;p&gt;Copyright (C) ${date} Julia Smith. All rights reserved.
  &lt;br&gt;Email: ${mail}&lt;/p&gt;
&lt;/#macro&gt;
&lt;#assign mail = "jsmith@acme.com"&gt;</tt></pre>
<p><tt>使用import指令导入库到模板中，Freemarker会为导入的库创建新的名字空间，并可以通过import指令中指定的散列变量访问库中的变量：</tt></p>
<pre><tt>&lt;#import "/lib/my_test.ftl" as my&gt;
&lt;#assign mail="fred@acme.com"&gt;
&lt;@my.copyright date="1999-2002"/&gt;
${my.mail}
${mail}</tt></pre>
<p><tt>输出结果：</tt></p>
<pre><tt>  &lt;p&gt;Copyright (C) 1999-2002 Julia Smith. All rights reserved.
  &lt;br&gt;Email: jsmith@acme.com&lt;/p&gt;
jsmith@acme.com
fred@acme.com</tt></pre>
<p><tt>可以看到例子中使用的两个同名变量并没有冲突，因为它们位于不同的名字空间可以使用assign指令在导入的名字空间中创建或替代变量，下面是一个例子：</tt></p>
<pre><tt>&lt;#import "/lib/my_test.ftl" as my&gt;
${my.mail}
&lt;#assign mail="jsmith@other.com" in my&gt;
${my.mail}</tt></pre>
<p><tt>输出结果：</tt></p>
<pre><tt>jsmith@acme.com
jsmith@other.com</tt></pre>
<p><tt>数据模型中的变量任何地方都可见，也包括不同的名字空间，下面是修改的库：</tt></p>
<pre><tt>&lt;#macro copyright date&gt;
  &lt;p&gt;Copyright (C) ${date} ${user}. All rights reserved.&lt;/p&gt;
&lt;/#macro&gt;
&lt;#assign mail = "${user}@acme.com"&gt;</tt></pre>
<p><tt>假设数据模型中的user变量的值是Fred，则下面的代码：</tt></p>
<pre><tt>&lt;#import "/lib/my_test.ftl" as my&gt;
&lt;@my.copyright date="1999-2002"/&gt;
${my.mail}</tt></pre>
<p><tt>输出结果：</tt></p>
<pre><tt>  &lt;p&gt;Copyright (C) 1999-2002 Fred. All rights reserved.&lt;/p&gt;
Fred@acme.com   

<strong>
补充（静态方法的调用）：</strong>

<strong>方法1：</strong>
<span class="comment">##定义配置文件 freemarkerstatic.properties</span>
<span class="ident">_Validator</span><span class="punct">=</span><span class="ident">com</span><span class="punct">.</span><span class="ident">longyou</span><span class="punct">.</span><span class="ident">util</span><span class="punct">.</span><span class="ident">Validator</span>
<span class="ident">_Functions</span><span class="punct">=</span><span class="ident">com</span><span class="punct">.</span><span class="ident">longyou</span><span class="punct">.</span><span class="ident">util</span><span class="punct">.</span><span class="ident">Functions</span>
<span class="ident">_EscapeUtils</span><span class="punct">=</span><span class="ident">com</span><span class="punct">.</span><span class="ident">longyou</span><span class="punct">.</span><span class="ident">util</span><span class="punct">.</span><span class="ident">EscapeUtils</span>
<span class="punct">/调用代码</span>
<span class="global">${</span><span class="ident">_Functions</span><span class="punct">.</span><span class="ident">toUpperCase</span><span class="punct">("</span><span class="string">Hello</span><span class="punct">")}&lt;</span><span class="ident">br</span><span class="punct">&gt;</span>
<span class="global">${</span><span class="ident">_EscapeUtils</span><span class="punct">.</span><span class="ident">escape</span><span class="punct">("</span><span class="string">狼的原野</span><span class="punct">")}

<strong>方法2：</strong>
</span>${stack.findValue("@package.ClassName@method")}

<span style="font-size: large;"><strong>补充：常用语法</strong></span></tt></pre>
<p><tt></tt></p>
<p>EG.一个对象BOOK</p>
<p>1.输出 ${book.name}</p>
<p>空值判断：${book.name?if_exists },</p>
<p>${book.name?default(‘xxx’)}//默认值xxx</p>
<p>${ book.name!&#8221;xxx&#8221;}//默认值xxx</p>
<p>日期格式：${book.date?string(&#8216;yyyy-MM-dd&#8217;)}</p>
<p>数字格式：${book?string.number}&#8211;20</p>
<p>${book?string.currency}&#8211;&lt;#&#8211; $20.00 &#8211;&gt;</p>
<p>${book?string.percent}—&lt;#&#8211; 20% &#8211;&gt;</p>
<p>插入布尔值：</p>
<p>&lt;#assign foo=ture /&gt;</p>
<p>${foo?string(&#8220;yes&#8221;,&#8221;no&#8221;)} &lt;#&#8211; yes &#8211;&gt;</p>
<p>2．逻辑判断</p>
<p>a:</p>
<p>&lt;#if condition&gt;&#8230;</p>
<p>&lt;#elseif condition2&gt;&#8230;</p>
<p>&lt;#elseif condition3&gt;&#8230;&#8230;</p>
<p>&lt;#else&gt;&#8230;</p>
<p>其中空值判断可以写成&lt;#if book.name?? &gt;</p>
<p>b:</p>
<p>&lt;#switch value&gt;</p>
<p>&lt;#case refValue1&gt;</p>
<p>&#8230;</p>
<p>&lt;#break&gt;</p>
<p>&lt;#case refValue2&gt;</p>
<p>&#8230;</p>
<p>&lt;#break&gt;</p>
<p>&#8230;</p>
<p>&lt;#case refValueN&gt;</p>
<p>&#8230;</p>
<p>&lt;#break&gt;</p>
<p>&lt;#default&gt;</p>
<p>&#8230;</p>
<p>3．循环读取</p>
<p>&lt;#list sequence as item&gt;</p>
<p>&#8230;</p>
<p>空值判断&lt;#if bookList?size = 0&gt;</p>
<p>e.g.</p>
<p>&lt;#list employees as e&gt;</p>
<p>${e_index}. ${e.name}</p>
<p>输出:</p>
<p>1. Readonly</p>
<p>2. Robbin</p>
<p><strong><br />
freemarker中Map的使用</strong><br />
<span style="font-size: x-small; color: #999900;"> <span>&lt;</span><span>#list testMap?keys as testKey&gt; </span><br />
<span> &lt;</span> <span>option</span> <span>value</span><span>=&#8221;</span><span>${testKey}</span><span>&#8220;</span> <span>&gt;</span></span></p>
<div><span style="font-size: x-small; color: #999900;"><span> </span><span>${</span><span>testMap</span><span>[</span><span>testKey</span><span>]}</span> </span></div>
<div><span style="font-size: x-small;"><span><span style="color: #999900;"> &lt;/</span></span></span><span style="font-size: x-small; color: #999900;"><span>option&gt;<br />
&lt;</span><span>/</span><span>#list&gt;</span></span></div>
<p><tt></tt></p>
<pre><tt>freemarker的Eclipse插件</tt></pre>
<p><tt></tt></p>
<ul>
<li>If you use Eclipse 2.x:
<ol>
<li> Open the <strong>Window</strong> menu, then <strong>Open Perspective</strong> -&gt; <strong>Install/Update</strong></li>
<li> Click with the right mouse button on the <strong>Feature Updates</strong> view,             then select <strong>New</strong> -&gt; <strong>Site Bookmark</strong></li>
<li> In the displayed dialog box, type <strong>&#8220;FreeMarker&#8221;</strong> for Name and             <strong>&#8220;http://www.freemarker.org/eclipse/update&#8221;</strong> for URL.             Leave the &#8220;Bookmark type&#8221; radio buttons on &#8220;Eclipse update site&#8221;.</li>
<li> Click <strong>Finish</strong></li>
<li> Open the tree node under the newly created update site named             &#8220;FreeMarker&#8221;, select the <strong>&#8220;FreeMarker <em>X.Y.Z</em>&#8220;</strong> feature, and install it using             the <strong>Install now</strong> button in the preview pane.</li>
</ol>
</li>
<li>If you use Eclipse 3.x:
<ol>
<li><strong>Help</strong> -&gt; <strong>Software updates</strong> -&gt; <strong>Find and install&#8230;</strong>.</li>
<li>Choose &#8220;Search for new features to install&#8221;.</li>
<li>Click <strong>Add Update Site&#8230;</strong>, and type <strong>&#8220;FreeMarker&#8221;</strong> for Name and               <strong>&#8220;http://www.freemarker.org/eclipse/update&#8221;</strong> for URL.</li>
<li>Check the box of the &#8220;FreeMarker&#8221; feature.</li>
<li>&#8220;Next&#8221;-s until it is installed&#8230;</li>
</ol>
</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://blog.jackrun.com/archives/74.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>让WebWork遍历Map</title>
		<link>http://blog.jackrun.com/archives/77.html</link>
		<comments>http://blog.jackrun.com/archives/77.html#comments</comments>
		<pubDate>Tue, 09 Jan 2007 10:38:09 +0000</pubDate>
		<dc:creator>Peltason</dc:creator>
				<category><![CDATA[J2EE]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[webwork]]></category>
		<category><![CDATA[遍历Map]]></category>

		<guid isPermaLink="false">http://www.jackrun.com/?p=77</guid>
		<description><![CDATA[用Webwork的标签遍历，是一件很爽的事情。例如遍历一个List。你可以做得比之前任何一套标签更优雅和简单。当遇到Map的时候，可能你会束手无策。因为我也刚好有这样的经历。
如果知道Map里面的Key，使用Key去得到Value那不难做，只需要在遍历的标签里面写上&#60;ww:property value=&#8221;yourMap['key']&#8220;/&#62;即可。但是要把Map里面所有的无素都遍历出来，我查了好多网页都没有答案。最后查了下Webwork in action，发现只有一句话提到Map的遍历，但这句话足以让我找到答案了。原文是：
When iterating over a Map, it
iterates over the Set returned by Map.entrySet(), which is a set of Map.Entry
objects, which in turn has the methods getKey() and getValue() to retrieve the
associated key/value pairs.
译文如下：
当遍历一个Map的时候，它调用Map.entrySet()方法返回一个Set，这个Set是一个Map.Entry对象的集合。这个返回的对象有一个getKey(),getValue()的方法供取得相关的键及值。
发现了有Get方法了。这意味着可以在页面上使用EL直接把Map的键及值取出来了。爽死了。那么，在遍历一个Map的时候，键及值的获得可以这样简单：
&#60;ww:iterator value=&#8221;yourMap&#8221;&#62;
&#60;ww:property value=&#8221;key&#8221;/&#62;&#60;ww:property value=&#8221;value&#8221;/&#62;
&#60;/ww:iterator&#62;
]]></description>
			<content:encoded><![CDATA[<p>用Webwork的标签遍历，是一件很爽的事情。例如遍历一个List。你可以做得比之前任何一套标签更优雅和简单。当遇到Map的时候，可能你会束手无策。因为我也刚好有这样的经历。<br />
如果知道Map里面的Key，使用Key去得到Value那不难做，只需要在遍历的标签里面写上&lt;ww:property value=&#8221;yourMap['key']&#8220;/&gt;即可。但是要把Map里面所有的无素都遍历出来，我查了好多网页都没有答案。最后查了下Webwork in action，发现只有一句话提到Map的遍历，但这句话足以让我找到答案了。原文是：<br />
When iterating over a Map, it<br />
iterates over the Set returned by Map.entrySet(), which is a set of Map.Entry<br />
objects, which in turn has the methods getKey() and getValue() to retrieve the<br />
associated key/value pairs.<br />
译文如下：<br />
当遍历一个Map的时候，它调用Map.entrySet()方法返回一个Set，这个Set是一个Map.Entry对象的集合。这个返回的对象有一个getKey(),getValue()的方法供取得相关的键及值。<br />
发现了有Get方法了。这意味着可以在页面上使用EL直接把Map的键及值取出来了。爽死了。那么，在遍历一个Map的时候，键及值的获得可以这样简单：<br />
&lt;ww:iterator value=&#8221;yourMap&#8221;&gt;<br />
&lt;ww:property value=&#8221;key&#8221;/&gt;&lt;ww:property value=&#8221;value&#8221;/&gt;<br />
&lt;/ww:iterator&gt;</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.jackrun.com/archives/77.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>JDBC性能技巧</title>
		<link>http://blog.jackrun.com/archives/84.html</link>
		<comments>http://blog.jackrun.com/archives/84.html#comments</comments>
		<pubDate>Fri, 22 Dec 2006 04:38:07 +0000</pubDate>
		<dc:creator>Peltason</dc:creator>
				<category><![CDATA[J2EE]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[JDBC]]></category>
		<category><![CDATA[性能]]></category>

		<guid isPermaLink="false">http://www.jackrun.com/?p=84</guid>
		<description><![CDATA[介绍
开发一个注重性能的JDBC应用程序不是一件容易的事. 当你的代码运行很慢的时候JDBC驱动程序并不会抛出异常告诉你.
本系列的性能提示将为改善JDBC应用程序的性能介绍一些基本的指导原则，这其中的原则已经被许多现有的JDBC应用程序编译运行并验证过。 这些指导原则包括:
* 正确的使用数据库MetaData方法
* 只获取需要的数据
* 选用最佳性能的功能
* 管理连接和更新
以下这些一般性原则可以帮助你解决一些公共的JDBC系统的性能问题.
选用JDBC对象和方法
本节的指导原则将帮助你在选用JDBC的对象和方法时哪些会有最好的性能.
在存储过程中使用参数标记作为参数
当 调用存储过程时, 应总是使用参数标记(?)来代替字面上的参数. JDBC驱动能调用数据库的存储过程, 也能被其它的SQL查询执行, 或者直接通过远程进程调用(RPC)的方式执行. 当你将存储过程作为一个SQL查询执行时, 数据库要解析这个查询语句, 校验参数并将参数转换为正确的数据类型.
要记住, SQL语句总是以字符串的形式送到数据库, 例如, “{call getCustName (12345)}”. 在这里, 即使程序中将参数作为整数赋给了getSustName, 而实现上参数还是以字符串的形式传给了服务器. 数据库会解析这个SQL查询, 并且根据metadata来决定存储过程的参数类型, 然后分解出参数&#8221;12345&#8243;, 然后在最终将存储过程作为一个SQL查询执行之前将字串&#8217;12345’转换为整型数.
按RPC方式调用时, 之前那种SQL字符串的方式要避免使用. 取而代之, JDBC驱动会构造一个网络packet, 其中包含了本地数据类型的参数，然后执行远程调用.
案例 1
在这个例子中, 存储过程不能最佳的使用RPC. 数据库必须将这作为一个普通的语言来进行解析，校验参数类型并将参数转换为正确的数据类型，最后才执行这个存储过程.
CallableStatement cstmt = conn.prepareCall (
&#8220;{call getCustName (12345)}&#8221;);
ResultSet rs = cstmt.executeQuery ();
案例 2
在这个例子中, 存储过程能最佳的执行RPC. 因为程序避免了字面的的参数, 使用特殊的参数来调用存储过程, JDBC驱动能最好以RPC方式直接来执行存储过程. SQL语言上的处理在这里被避免并且执行也得到很大的改善.
CallableStatement cstmt &#8211; conn.prepareCall (
&#8220;{call getCustName [...]]]></description>
			<content:encoded><![CDATA[<p>介绍<br />
开发一个注重性能的JDBC应用程序不是一件容易的事. 当你的代码运行很慢的时候JDBC驱动程序并不会抛出异常告诉你.<br />
本系列的性能提示将为改善JDBC应用程序的性能介绍一些基本的指导原则，这其中的原则已经被许多现有的JDBC应用程序编译运行并验证过。 这些指导原则包括:<br />
* 正确的使用数据库MetaData方法<br />
* 只获取需要的数据<br />
* 选用最佳性能的功能<br />
* 管理连接和更新<br />
以下这些一般性原则可以帮助你解决一些公共的JDBC系统的性能问题.<br />
选用JDBC对象和方法<br />
本节的指导原则将帮助你在选用JDBC的对象和方法时哪些会有最好的性能.<br />
在存储过程中使用参数标记作为参数<br />
当 调用存储过程时, 应总是使用参数标记(?)来代替字面上的参数. JDBC驱动能调用数据库的存储过程, 也能被其它的SQL查询执行, 或者直接通过远程进程调用(RPC)的方式执行. 当你将存储过程作为一个SQL查询执行时, 数据库要解析这个查询语句, 校验参数并将参数转换为正确的数据类型.<br />
要记住, SQL语句总是以字符串的形式送到数据库, 例如, “{call getCustName (12345)}”. 在这里, 即使程序中将参数作为整数赋给了getSustName, 而实现上参数还是以字符串的形式传给了服务器. 数据库会解析这个SQL查询, 并且根据metadata来决定存储过程的参数类型, 然后分解出参数&#8221;12345&#8243;, 然后在最终将存储过程作为一个SQL查询执行之前将字串&#8217;12345’转换为整型数.<br />
按RPC方式调用时, 之前那种SQL字符串的方式要避免使用. 取而代之, JDBC驱动会构造一个网络packet, 其中包含了本地数据类型的参数，然后执行远程调用.</p>
<p><span id="more-84"></span>案例 1<br />
在这个例子中, 存储过程不能最佳的使用RPC. 数据库必须将这作为一个普通的语言来进行解析，校验参数类型并将参数转换为正确的数据类型，最后才执行这个存储过程.<br />
CallableStatement cstmt = conn.prepareCall (<br />
&#8220;{call getCustName (12345)}&#8221;);<br />
ResultSet rs = cstmt.executeQuery ();<br />
案例 2<br />
在这个例子中, 存储过程能最佳的执行RPC. 因为程序避免了字面的的参数, 使用特殊的参数来调用存储过程, JDBC驱动能最好以RPC方式直接来执行存储过程. SQL语言上的处理在这里被避免并且执行也得到很大的改善.<br />
CallableStatement cstmt &#8211; conn.prepareCall (<br />
&#8220;{call getCustName (?)}&#8221;);<br />
cstmt.setLong (1,12345);<br />
ResultSet rs = cstmt.executeQuery();<br />
使用Statement而不是PreparedStatement对象<br />
JDBC 驱动的最佳化是基于使用的是什么功能. 选择PreparedStatement还是Statement取决于你要怎么使用它们. 对于只执行一次的SQL语句选择Statement是最好的. 相反, 如果SQL语句被多次执行选用PreparedStatement是最好的.<br />
PreparedStatement 的第一次执行消耗是很高的. 它的性能体现在后面的重复执行. 例如, 假设我使用Employee ID, 使用prepared的方式来执行一个针对Employee表的查询. JDBC驱动会发送一个网络请求到数据解析和优化这个查询. 而执行时会产生另一个网络请求. 在JDBC驱动中，减少网络通讯是最终的目的. 如果我的程序在运行期间只需要一次请求, 那么就使用Statement. 对于Statement, 同一个查询只会产生一次网络到数据库的通讯.<br />
对于使用 PreparedStatement池的情况下, 本指导原则有点复杂. 当使用PreparedStatement池时, 如果一个查询很特殊, 并且不太会再次执行到, 那么可以使用Statement. 如果一个查询很少会被执行,但连接池中的Statement池可能被再次执行, 那么请使用PreparedStatement. 在不是Statement池的同样情况下, 请使用Statement.<br />
使用PreparedStatement的Batch功能<br />
Update 大量的数据时, 先Prepare一个INSERT语句再多次的执行, 会导致很多次的网络连接. 要减少JDBC的调用次数改善性能, 你可以使用PreparedStatement的AddBatch()方法一次性发送多个查询给数据库. 例如, 让我们来比较一下下面的例子.<br />
例 1: 多次执行Prepared Statement<br />
PreparedStatement ps = conn.prepareStatement(<br />
&#8220;INSERT into employees values (?, ?, ?)&#8221;);<br />
for (n = 0; n &lt; 100; n++) {<br />
ps.setString(name[n]);<br />
ps.setLong(id[n]);<br />
ps.setInt(salary[n]);<br />
ps.executeUpdate();<br />
}<br />
例 2: 使用Batch<br />
PreparedStatement ps = conn.prepareStatement(<br />
&#8220;INSERT into employees values (?, ?, ?)&#8221;);<br />
for (n = 0; n &lt; 100; n++) {<br />
ps.setString(name[n]);<br />
ps.setLong(id[n]);<br />
ps.setInt(salary[n]);<br />
ps.addBatch();<br />
}<br />
ps.executeBatch();<br />
在 例 1中, PreparedStatement被用来多次执行INSERT语句. 在这里, 执行了100次INSERT操作, 共有101次网络往返. 其中,1次往返是预储statement, 另外100次往返执行每个迭代. 在例2中, 当在100次INSERT操作中使用addBatch()方法时, 只有两次网络往返. 1次往返是预储statement, 另一次是执行batch命令. 虽然Batch命令会用到更多的数据库的CPU周期, 但是通过减少网络往返，性能得到提高. 记住, JDBC的性能最大的增进是减少JDBC驱动与数据库之间的网络通讯.<br />
选择合适的光标类型<br />
选择适用的光标类型以最大限度的适用你的应用程序. 本节主要讨论三种光标类型的性能问题.<br />
对于从一个表中顺序读取所有记录的情况来说, Forward-Only型的光标提供了最好的性能. 获取表中的数据时, 没有哪种方法比使用Forward-Only型的光标更快. 但不管怎样, 当程序中必须按无次序的方式处理数据行时, 这种光标就无法使用了.<br />
对 于程序中要求与数据库的数据同步以及要能够在结果集中前后移动光标, 使用JDBC的Scroll-Insensitive型光标是较理想的选择. 此类型的光标在第一次请求时就获取了所有的数据(当JDBC驱动采用&#8217;lazy&#8217;方式获取数据时或许是很多的而不是全部的数据)并且储存在客户端. 因此, 第一次请求会非常慢, 特别是请求长数据时会理严重. 而接下来的请求并不会造成任何网络往返(当使用&#8217;lazy&#8217;方法时或许只是有限的网络交通) 并且处理起来很快. 因为第一次请求速度很慢, Scroll-Insensitive型光标不应该被使用在单行数据的获取上. 当有要返回长数据时, 开发者也应避免使用Scroll-Insensitive型光标, 因为这样可能会造成内存耗尽. 有些Scroll-Insensitive型光标的实现方式是在数据库的临时表中缓存数据来避免性能问题, 但多数还是将数据缓存在应用程序中.<br />
Scroll -Sensitive型光标, 有时也称为Keyset-Driven光标, 使用标识符, 像数据库的ROWID之类. 当每次在结果集移动光标时, 会重新该标识符的数据. 因为每次请求都会有网络往返, 性能可能会很慢. 无论怎样, 用无序方式的返回结果行对性能的改善是没有帮助的.<br />
现 在来解释一下这个, 来看这种情况. 一个程序要正常的返回1000行数据到程序中. 在执行时或者第一行被请求时, JDBC驱动不会执行程序提供的SELECT语句. 相反, 它会用键标识符来替换SELECT查询, 例如, ROWID. 然后修改过的查询都会被驱动程序执行，跟着会从数据库获取所有1000个键值. 每一次对一行结果的请求都会使JDBC驱动直接从本地缓存中找到相应的键值, 然后构造一个包含了&#8217;WHERE ROWID=？&#8217;子句的最佳化查询, 再接着执行这个修改过的查询, 最后从服务器取得该数据行.<br />
当程序无法像Scroll-Insensitive型光标一样提供足够缓存时, Scroll-Sensitive型光标可以被替代用来作为动态的可滚动的光标.<br />
使用有效的getter方法<br />
JDBC 提供多种方法从ResultSet中取得数据, 像getInt(), getString(), 和getObject()等等. 而getObject()方法是最泛化了的, 提供了最差的性能。 这是因为JDBC驱动必须对要取得的值的类型作额外的处理以映射为特定的对象. 所以就对特定的数据类型使用相应的方法.<br />
要更进一步的改善性能, 应在取得数据时提供字段的索引号, 例如, getString(1), getLong(2), 和getInt(3)等来替代字段名. 如果没有指定字段索引号, 网络交通不会受影响, 但会使转换和查找的成本增加. 例如, 假设你使用getString(&#8220;foo&#8221;) &#8230; JDBC驱动可能会将字段名转为大写(如果需要), 并且在到字段名列表中逐个比较来找到&#8221;foo&#8221;字段. 如果可以, 直接使用字段索引, 将为你节省大量的处理时间.<br />
例如, 假设你有一个100行15列的ResultSet, 字段名不包含在其中. 你感兴趣的是三个字段 EMPLOYEENAME (字串型), EMPLOYEENUMBER (长整型), 和SALARY (整型). 如果你指定getString(“EmployeeName”), getLong(“EmployeeNumber”), 和getInt(“Salary”), 查询旱每个字段名必须被转换为metadata中相对应的大小写, 然后才进行查找. 如果你使用getString(1), getLong(2), 和getInt(15). 性能就会有显著改善.<br />
获取自动生成的键值<br />
有许多数据库提供了隐藏列为表中的每行记录分配一 个唯一键值. 很典型, 在查询中使用这些字段类型是取得记录值的最快的方式, 因为这些隐含列通常反应了数据在磁盘上的物理位置. 在JDBC3.0之前, 应用程序只可在插入数据后通过立即执行一个SELECT语句来取得隐含列的值.<br />
例如:<br />
//插入行<br />
int rowcount = stmt.executeUpdate (<br />
&#8220;insert into LocalGeniusList (name) values (&#8216;Karen&#8217;)&#8221;);<br />
// 现在为新插入的行取得磁盘位置 &#8211; rowid<br />
ResultSet rs = stmt.executeQuery (<br />
&#8220;select rowid from LocalGeniusList where name = &#8216;Karen&#8217;&#8221;);<br />
这 种取得隐含列的方式有两个主要缺点. 第一, 取得隐含列是在一个独立的查询中, 它要透过网络送到服务器后再执行. 第二, 因为不是主键, 查询条件可能不是表中的唯一性ID. 在后面一个例子中, 可能返回了多个隐含列的值, 程序无法知道哪个是最后插入的行的值.<br />
(译者：由于不同的数据库支持的程度不同，返回rowid的方式各有差异。在SQL Server中，返回最后插入的记录的id可以用这样的查询语句：SELECT @IDENTITY )<br />
JDBC3.0规范中的一个可选特性提供了一种能力, 可以取得刚刚插入到表中的记录的自动生成的键值.<br />
例如:<br />
int rowcount = stmt.executeUpdate (<br />
&#8220;insert into LocalGeniusList (name) values (&#8216;Karen&#8217;)&#8221;,<br />
// 插入行并返回键值<br />
Statement.RETURN_GENERATED_KEYS);<br />
ResultSet rs = stmt.getGeneratedKeys ();<br />
// 得到生成的键值<br />
现在, 程序中包含了一个唯一性ID, 可以用来作为查询条件来快速的存取数据行, 甚至于表中没有主键的情况也可以.<br />
这种取得自动生成的键值的方式给JDBC的开发者提供了灵活性, 并且使存取数据的性能得到提升.<br />
选择合适的数据类型<br />
接 收和发送某些数据可能代价昂贵. 当你设计一个schema时, 应选择能被最有效地处理的数据类型. 例如, 整型数就比浮点数或实数处理起来要快一些. 浮点数的定义是按照数据库的内部规定的格式, 通常是一种压缩格式. 数据必须被解压和转换到另外种格式, 这样它才能被数据的协议处理.<br />
获取ResultSet<br />
由于数据库系统对可滚动光标的支持有 限, 许多JDBC驱动程序并没有实现可滚动光标. 除非你确信数据库支持可滚动光标的结果集, 否则不要调用rs.last()和rs.getRow()方法去找出数据集的最大行数. 因为JDBC驱动程序模拟了可滚动光标, 调用rs.last()导致了驱动程序透过网络移到了数据集的最后一行. 取而代之, 你可以用ResultSet遍历一次计数或者用SELECT查询的COUNT函数来得到数据行数.<br />
通常情况下，请不要写那种依赖于结果集行数的代码, 因为驱动程序必须获取所有的数据集以便知道查询会返回多少行数据.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.jackrun.com/archives/84.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Hibernate Native SQL查询常用的2种方法及对返回结果处理</title>
		<link>http://blog.jackrun.com/archives/87.html</link>
		<comments>http://blog.jackrun.com/archives/87.html#comments</comments>
		<pubDate>Tue, 14 Nov 2006 01:24:00 +0000</pubDate>
		<dc:creator>Peltason</dc:creator>
				<category><![CDATA[J2EE]]></category>
		<category><![CDATA[hibernate]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[sql]]></category>

		<guid isPermaLink="false">http://www.jackrun.com/?p=87</guid>
		<description><![CDATA[本文使用Hibernate3.2 cr2
1.直接返回List结果集，不包括列名信息
List ls = session.createSQLQuery(querySql).list();
如果当前查询SQL里包括多列，则List里每行存放的是Object数组，如果直接查询的是一列，则每行存放的则直接就是查询的哪一列的数据。
也可以在返回的结果数据里指定每一列的类型
List ls2 = session.createSQLQuery(querySql).addScalar(&#8220;column_name&#8221;, Hibernate.DATE).list();
这样返回的数据的时候Hiberante就会对返回的数据转换成你指定的数据类型.这个是在3.1版本就支持的操作。

2.返回包括列名信息，可以是数据每行转换成一个Map形式。则KEY对应列名，VALUE对应当前列的值。
实现方法如下：
List ls3 = session.createSQLQuery(querySql).setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP).list();
这样返回的结果集每行就是Map(key:column,value:columnValue);还可以使用其他的一些Hibernate定义好的 一些ResultTransformer方法的实现类。具体可以参考 org.hibernate.criterion.CriteriaSpecification里定义的一些。
当然列可以实现自己的数据组装方法，只需要实现ResultTransformer接口就行了。
注意：ResultTransformer实现新版本的Hibernate才支持
以上例子说明的两种方法是没有Hibernate的mapping对象的概念。直接和JDBC返回的结果差不多。
如果想支持返回数据为Hibernate的Entity可以具体参考Hibernate参考文档里的 Native SQL查询一章的实现方法。
]]></description>
			<content:encoded><![CDATA[<p>本文使用Hibernate3.2 cr2</p>
<p>1.直接返回List结果集，不包括列名信息</p>
<p>List ls = session.createSQLQuery(querySql).list();</p>
<p>如果当前查询SQL里包括多列，则List里每行存放的是Object数组，如果直接查询的是一列，则每行存放的则直接就是查询的哪一列的数据。<br />
也可以在返回的结果数据里指定每一列的类型</p>
<p>List ls2 = session.createSQLQuery(querySql).addScalar(&#8220;column_name&#8221;, Hibernate.DATE).list();<br />
这样返回的数据的时候Hiberante就会对返回的数据转换成你指定的数据类型.这个是在3.1版本就支持的操作。</p>
<p><span id="more-87"></span></p>
<p>2.返回包括列名信息，可以是数据每行转换成一个Map形式。则KEY对应列名，VALUE对应当前列的值。<br />
实现方法如下：</p>
<p>List ls3 = session.createSQLQuery(querySql).setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP).list();</p>
<p>这样返回的结果集每行就是Map(key:column,value:columnValue);还可以使用其他的一些Hibernate定义好的 一些ResultTransformer方法的实现类。具体可以参考 org.hibernate.criterion.CriteriaSpecification里定义的一些。<br />
当然列可以实现自己的数据组装方法，只需要实现ResultTransformer接口就行了。<br />
注意：ResultTransformer实现新版本的Hibernate才支持<br />
以上例子说明的两种方法是没有Hibernate的mapping对象的概念。直接和JDBC返回的结果差不多。<br />
如果想支持返回数据为Hibernate的Entity可以具体参考Hibernate参考文档里的 Native SQL查询一章的实现方法。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.jackrun.com/archives/87.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
