﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-飞翔吧-随笔分类-Java</title><link>http://www.blogjava.net/ytfei/category/3100.html</link><description /><language>zh-cn</language><lastBuildDate>Sat, 30 Jan 2010 18:42:14 GMT</lastBuildDate><pubDate>Sat, 30 Jan 2010 18:42:14 GMT</pubDate><ttl>60</ttl><item><title>ant 的使用</title><link>http://www.blogjava.net/ytfei/archive/2006/10/20/76397.html</link><dc:creator>飞翔</dc:creator><author>飞翔</author><pubDate>Fri, 20 Oct 2006 07:48:00 GMT</pubDate><guid>http://www.blogjava.net/ytfei/archive/2006/10/20/76397.html</guid><wfw:comment>http://www.blogjava.net/ytfei/comments/76397.html</wfw:comment><comments>http://www.blogjava.net/ytfei/archive/2006/10/20/76397.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ytfei/comments/commentRss/76397.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ytfei/services/trackbacks/76397.html</trackback:ping><description><![CDATA[
		<blockquote>转自:http://www.csbus.com/forum/ShowDetail.asp?Ici_ID=849&amp;ForumID=5&amp;ForumName=%D4%B1%B9%A4%D6%AE%BC%D2<br /><br /><br />本文主要介绍 Ant 在多用户开发的情况下，如何用 Eclipse，并且根据不同的目标环境编译不同的部署包。文中首先介绍一个场景，给出在开发、编译、部署过程中遇到的问题；然后介绍如何用 Eclipse 来简化你的 Ant 开发过程；文章的最后解释如何通过编写有效的 Ant 脚本来提高你的工作效率。</blockquote>
		<!--START RESERVED FOR FUTURE USE INCLUDE FILES-->
		<!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters -->
		<!--END RESERVED FOR FUTURE USE INCLUDE FILES-->
		<p>读者定位为具有 Java 和 Ant 使用经验的开发人员。</p>
		<p>读者可以学习到如何使用 Ant 解决一些多用户开发环境中，根据不同的目标环境编译成不同部署包的问题。</p>
		<p>
				<a name="N10061">
						<span class="atitle">
								<font face="Arial" size="4">工作场景</font>
						</span>
				</a>
		</p>
		<p>现在有一个 web 项目，是多人进行开发的，通过 CVS 来管理。另外该项目还有一些测试人员，他们测试部署在测试服务器上的应用程序，发现 bug 后通过 bug 管理系统通知开发人员，在开发人员修复 bug 并经过本地测试后，由专门的人负责检出（check out）代码，编译打包后部署到测试服务器上。</p>
		<p>该项目的成员小A就是负责检出代码、编译打包，并部署到服务器上的人。除了这个任务之外，他还是该项目的编程人员。在项目进入测试阶段后，小A在得到组中别的成员修复了 bug 并且检入（check in）了代码的消息后（也有可能是小A自己检入了代码），小A首先更新本地的代码，先在本地做测试，确认修复了 bug 后打成 WAR 包部署到测试服务器上，并通知测试人员已经修复了 bug，让测试人员继续进行测试。</p>
		<p>
				<a name="N1006C">
						<span class="smalltitle">
								<strong>
										<font face="Arial" size="3">小A的烦恼</font>
								</strong>
						</span>
				</a>
		</p>
		<p>在该项目中，有一些为测试方便开发而写的代码和页面，比如跳过用户认证，但是在部署到测试机环境的时候，需要删除这些代码和页面；另外作为一个具有灵活性和扩展性的应用程序，又有一些配置文件，配置文件中的值会根据环境的改变而变动。例如，在项目中使用了 Log4j 记录日志，需要给 Log4j 指定日志文件的保存路径，本地程序员开发的时候用的是 Windows 系统，给 Log4j 指定的也是 Windows 的文件系统，在测试阶段的时候，需要部署到 Linux 系统中，那么日志的保存路径也需要做相应的改动。部署到测试服务器上的时候，除了 Log4j 需要改之外，还有很多别的配置项目也需要变动，并且分散在各个 package 中。小A的烦恼也随之而来，每次他在做完本地测之后，就根据测试机的需要逐个找配置文件，更改相应的值，并删除那些为测试方便写的代码和页面，每天可能根据需要做好几次这样的事情，最烦的是他在快做完对测试机环境更改的时候，某开发人员突然通知小A说：“我又改了一点代码，刚检入，你再重打一个包吧。”，小A又不得不从头开始做新一轮的检出代码、本地测试、更改配置文件、删除不需要的文件、打包部署的工作。另外小A在测试阶段的后期被通知要求除了每次生成一个测试环境的 WAR 包外还必须生成一个在产品环境下的 WAR 包，他做的事情就更多了。</p>
		<p>从上面的场景可以看出，小A的工作效率低而且容易出错，甚至有可能导致整个项目的工作效率低下。其实可以通过 Ant 来帮助小A快速而且有效地完成这个工作。在 Ant 中，根据目标环境的需要，可以把所有要更改的配置文件的项目集中写到某个配置文件中。这样根据不同的目标环境得到不同的配置文件，Ant 在编译包时根据不同的目标环境切换不同的配置文件即可。比如小A现在碰到的有 3 中环境：开发环境、测试环境以及产品环境，根据这三种环境可以生成三个不同的配置文件：develop_deploy.property、test_deploy. property和product_deploy. property，当小A想生成不同的包时只需在这三个配置文件之间切换就可以了。</p>
		<p>在正式开始编写脚本之前，我们需要下载安装相应的软件。</p>
		<ul>
				<li>Eclipse：为了使 Ant 的开发更加简单，我们选择了 Eclipse3.1 作为开发环境。当然你可以使用任何你喜欢的文本编辑工具来开发你的 Ant。Eclipse 的最新版本可以在 <a href="http://eclipse.org/"><u><font color="#5c81a7">http://eclipse.org/</font></u></a> 上下载。 
</li>
				<li>Ant：Ant 是基于 Java 的编译工具，它就像 C/C++ 中的 make，但是没有 make 那样难用。Ant 的最新版本可以在 <a href="http://ant.apache.org/bindownload.cgi"><u><font color="#5c81a7">http://ant.apache.org/bindownload.cgi</font></u></a> 上下载。如果你用 Eclipse 来开发 Ant，则不用去单独下载 Ant，因为在 Eclipse 中已经集成了 Ant。 
</li>
				<li>CVS 客户端（cvs.exe）：CVS 可以在 <a href="http://www.nongnu.org/cvs/"><u><font color="#5c81a7">http://www.nongnu.org/cvs/</font></u></a> 上下载 </li>
		</ul>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="500" onload="javascript:if(this.width&gt;500)this.width=500" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" onload="javascript:if(this.width&gt;500)this.width=500" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" onload="javascript:if(this.width&gt;500)this.width=500" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" onload="javascript:if(this.width&gt;500)this.width=500" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-lo-ant-eclipse/index.html#main">
																				<b>
																						<font color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="N10092">
						<span class="atitle">
								<font face="Arial" size="4">用 Eclipse3.1 来创建 Ant 脚本</font>
						</span>
				</a>
		</p>
		<p>如果你使用 Eclipse 来编写 Ant，建议使用 Eclipse 3.1 以后的版本。除了以前 Ant 编辑器提供的语法高亮，提示语法错误等功能外，Eclipse3.1 版本增加了许多新的功能。比如：脚本代码的折叠；快速定位某属性或者目标（target）段的定义；在同一 builder 文件中重构属性名和目标名（快捷方式Alt + Shift +R）；调试 Ant 脚本等。</p>
		<p>下面我们就来看看 Eclipse 3.1 中对 Ant 的支持</p>
		<ul>
				<li>
						<p>打开“File”－“New”－“Project”－“Simple”－“Project”，点击“Next”，输入工程名“Ant”，然后点击直到“Finish”</p>
				</li>
				<li>
						<p>在新建的 Ant 工程中，新建 Test.xml，并且拷贝下面的脚本。该段脚本的内容就不做介绍了，我们主要看 Eclipse 提供了哪些功能。注意这时候打开的并不是 Ant 编辑器，将内容拷贝进去之后，关掉打开的“Test.xml”，然后再重新打开它，这样 Eclipse 就会用 Ant 编辑器打开它，并且也语法高亮了。</p>
						<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
								<tbody>
										<tr>
												<td>
														<code>
																<pre class="section">&lt;?xml version='1.0' encoding='UTF-8'?&gt;
&lt;project name='Test' default='init' basedir='.'&gt;
&lt;property name='test' value='test'/&gt;
&lt;target name='init'&gt;
	&lt;echo&gt;${test}&lt;/echo&gt;
&lt;/target&gt;
&lt;/project&gt;
</pre>
														</code>
												</td>
										</tr>
								</tbody>
						</table>
						<br />
				</li>
				<li>
						<p>自动提示和代码折叠功能。如果是 Ant 内置的命令或者前面已经定义的变量，编辑器会自动提示；编辑器右边的加/减号可以代码折叠。如下所示：</p>
						<img alt="图1：编辑器右边的加/减号可以代码折叠" src="http://www-128.ibm.com/developerworks/cn/java/j-lo-ant-eclipse/image001.jpg" width="500" onload="javascript:if(this.width&gt;500)this.width=500" />
				</li>
				<li>
						<p>快速定位目标（target）或者定义变量处。在上图中，将鼠标移至 default=”init” 中的 init 上之后，按下 ctrl 键，鼠标变成手状，单击就可以定位到定义该目标的地方</p>
				</li>
				<li>
						<p>快速重构目标名或者属性名。选中目标/属性名，按下 Alt + Shift + R，然后键入你要修改后的值，所有引用到的地方都会随之改动。如下图所示，选中 init 后，按下快捷键，改成 initial：</p>
						<img alt="图2：改成 initial" src="http://www-128.ibm.com/developerworks/cn/java/j-lo-ant-eclipse/image002.jpg" onload="javascript:if(this.width&gt;500)this.width=500" />
				</li>
				<li>
						<p>调试 Ant 脚本。在标签“&lt;target name=..”的左边设置一断点，然后在编辑器中右击，出现的菜单中选“Debug As”－“Ant Build”，出现后的窗口与调试 Java 程序的界面差不多。</p>
						<p>这是调试窗口，这里可以选择单步跟进、跳出等：</p>
						<img alt="图3：选择单步跟进、跳出等" src="http://www-128.ibm.com/developerworks/cn/java/j-lo-ant-eclipse/image003.jpg" onload="javascript:if(this.width&gt;500)this.width=500" />
						<p>下面是运行时变量窗口。可以看到 test 变量的值是“test”：</p>
						<img alt="图4：test 变量的值是 test" src="http://www-128.ibm.com/developerworks/cn/java/j-lo-ant-eclipse/image004.jpg" onload="javascript:if(this.width&gt;500)this.width=500" />
						<p>运行结果窗口：</p>
						<img alt="图5：运行结果窗口" src="http://www-128.ibm.com/developerworks/cn/java/j-lo-ant-eclipse/image005.jpg" width="500" onload="javascript:if(this.width&gt;500)this.width=500" />
				</li>
		</ul>
		<p>由此可见，如果使用好 Eclipse Ant 编辑器所提供强大的功能的话能大大提高写 Ant 脚本的效率。</p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="500" onload="javascript:if(this.width&gt;500)this.width=500" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" onload="javascript:if(this.width&gt;500)this.width=500" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" onload="javascript:if(this.width&gt;500)this.width=500" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" onload="javascript:if(this.width&gt;500)this.width=500" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-lo-ant-eclipse/index.html#main">
																				<b>
																						<font color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="N100DD">
						<span class="atitle">
								<font face="Arial" size="4">Ant 中使用 property（属性）文件</font>
						</span>
				</a>
		</p>
		<p>刚开始写 Ant 的初学者可能会把所有的信息都放在 build.xml 中，下面就是这样的一个例子。</p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<code>
												<pre class="section">&lt;project name='testBuild' default='compile' basedir='.'&gt;
&lt;target name='init'&gt;
	&lt;mkdir dir='c:/temp/dest/testProj' /&gt;
&lt;/target&gt;
&lt;target name='compile' depends='init'&gt;
	&lt;javac srcdir='c:/temp/src/testProj' destdir='c:/temp/dest/testProj '/&gt;
&lt;echo&gt;Build into c:/temp/dest/testProj, successfully.&lt;/echo&gt;
&lt;/target&gt;
&lt;/project&gt;
</pre>
										</code>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>在上面的例子中，所有的路径信息都是写在 build.xml 中。但是 Ant 脚本可能在不同的机器或者不同的系统上运行，也有可能一些值需要根据环境的不同而变化，在 Ant 中可以把所有这些可能变化的地方都放到一个配置文件中，然后在 Ant 脚本中引用这个配置文件就可以了，针对上面的例子，如下所示：</p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<code>
												<pre class="section">&lt;project name='testBuild' default='compile' basedir='.'&gt;
	&lt;property file='build.properties'/&gt;
	&lt;target name='init'&gt;
		&lt;mkdir dir='${dest.dir}' /&gt;
	&lt;/target&gt;
	&lt;target name='compile' depends='init'&gt;
		&lt;javac srcdir='${src.dir}' destdir='${dest.dir}'/&gt;
		&lt;echo&gt;Build into ${dest.dir}, successfully.&lt;/echo&gt;
	&lt;/target&gt;
&lt;/project&gt;
build.properties的内容：
dest.dir=c:/temp/dest/testProj
src.dir=c:/temp/src/testProj
</pre>
										</code>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>如果想在 Ant 脚本中引用值的话，只需用$符号开头，在一对'{}'中写入要引用的键值。如上例中，需要引用编译的目标路径用'${dest.dir}'。</p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="500" onload="javascript:if(this.width&gt;500)this.width=500" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" onload="javascript:if(this.width&gt;500)this.width=500" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" onload="javascript:if(this.width&gt;500)this.width=500" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" onload="javascript:if(this.width&gt;500)this.width=500" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-lo-ant-eclipse/index.html#main">
																				<b>
																						<font color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="N100F3">
						<span class="atitle">
								<font face="Arial" size="4">使用 Ant 任务从 CVS 中检出（check out）源代码，并编译打包</font>
						</span>
				</a>
		</p>
		<p>Ant 中提供了 cvs 任务（Task）可以从 CVS 服务器中检出资源（注意：在使用 Ant 的 cvs 任务之前，请先将 cvs.exe 下载到你的机器，并且将它添加到你本地的 PATH 环境变量中，然后重新启动 Eclipse。否则在执行下面脚本的时候就会得到 error=2 的错误）。cvs 的可选用属性很多，在这里介绍经常使用到的几个属性。从 CVS 中检出资源一般需要指定：</p>
		<ul>
				<li>CVS 所在的服务器地址：目标 CVS 服务器地址 
</li>
				<li>用户名：登录该 CVS 服务器你指定的用户名 
</li>
				<li>密码：登录该 CVS 服务器需要的密码 
</li>
				<li>库路径（Repository Path）：服务器中的库路径 
</li>
				<li>模块名：当前需要检出的模块名，一般都是以工程的名字作为模块名 
</li>
				<li>标签名：需要从 CVS 中检出哪个标签 </li>
		</ul>
		<p>在介绍使用 Ant 的 cvs 之前，先说一下本地的目录结构。在 C 盘的 temp 目录下，分别有四个目录，如下所示：</p>
		<ul>
				<li>build 目录：放编译后的类以及资源文件 
</li>
				<li>dist 目录：放生成的 jar 文件或者 war 文件 
</li>
				<li>lib 目录：放在编译过程中需要用到的 jar 文件 
</li>
				<li>src 目录：放从 cvs 中检出的源文件（包括 JSP 等）<br /><img alt="图6：src 目录" src="http://www-128.ibm.com/developerworks/cn/java/j-lo-ant-eclipse/image006.jpg" onload="javascript:if(this.width&gt;500)this.width=500" /></li>
		</ul>
		<p>在 Ant 中这样写就可以从中检出资源：</p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<code>
												<pre class="section">  &lt;cvs cvsRoot=':pserver:username:pwd@cvs.server:/home/testPath' 
package='TestProj' dest=' c:/temp/src/testProj ' failonerror='true' /&gt;
</pre>
										</code>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>这段脚本片断的意思就是从叫'cvs.server'的服务器中，用用户名是 username、密码为 pwd 的用户检出在库路径是 /home/testPath 下的 TestProj 模块（项目），检出后的资源放入本地目录 c:/temp/src/testProj 中。在上面这段脚本中，可以看到有很多值可能会根据不同的环境或者用户随之改变的，比如用户名和密码等；而且从脚本的重复可利用性来说，需要把有些值抽出来放到配置文件中，如服务器的地址和库路径等。因此把这些可能需要更改的地方放到 property 文件中，效果会更好。改完后的完整 Ant 脚本如下所示：</p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<code>
												<pre class="section">  &lt;?xml version='1.0'?&gt;
&lt;project name='testWeb' default='checkout' basedir='.'&gt;
	&lt;target name='checkout'&gt;
		&lt;property file='TestWeb.properties' /&gt;
		&lt;cvs cvsRoot='${cvs.root}' package='${cvs.projectName}' 
tag='${cvs.tag}' dest='${src.dir}' failonerror='true' /&gt;
	&lt;/target&gt;
&lt;/project&gt;
</pre>
										</code>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>对应的 TestWeb.properties 文件内容如下所示：</p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<code>
												<pre class="section">base.dir=c:/temp/
src.dir=${base.dir}/src

cvs.server=cvs.server
cvs.user=username
cvs.pw=pwd
cvs.repositoryPath=/home/testPath
cvs.projectName=TestProj
cvs.root=:pserver:${cvs.user}:${cvs.pw}@${cvs.server}:${cvs.repositoryPath}
cvs.tag=
</pre>
										</code>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>在检出了资源后，需要对其进行编译打包。为了使 Ant 脚本更加具有可读性和灵活性，我们需要对上面的 Ant 脚本进行一些改动。首先将 Ant 脚本中进行分段，如下所示：</p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<code>
												<pre class="section">&lt;?xml version='1.0'?&gt;
&lt;project name='testWeb' default='all' basedir='.'&gt;
	&lt;target name='all' depends='init,clean,checkout,build'&gt;
    &lt;!--脚本的入口点--&gt;
	&lt;/target&gt;
	&lt;target name='init'&gt;
    &lt;!--做初始化属性文件和设置classpath等设置初始条件--&gt;
	&lt;/target&gt;
	&lt;target name='clean'&gt;
     &lt;!--删除上一次留下的没用的目录和文件--&gt;
	&lt;/target&gt;
	&lt;target name='checkout'&gt;	
     &lt;!--从CVS中检出资源--&gt;
	&lt;/target&gt;
	&lt;target name='build'&gt;
     &lt;!--编译源文件并打包到指定的目录--&gt;
	&lt;/target&gt;
&lt;/project&gt;
</pre>
										</code>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>上面的脚本中，总共分成了5个目标（target），脚本的入口点是'all'，all 按顺序调用 init，clean，checkout，build。其中：</p>
		<ul>
				<li>init 是用来做初始化属性文件和设置 classpath 等设置初始条件的事情 
</li>
				<li>clean 用来删除上一次留下的没用的目录和文件 
</li>
				<li>checkout 已经介绍过了，是用来从 CVS 中检出资源 
</li>
				<li>build 用来编译源文件并打 WAR 包到指定的目录 </li>
		</ul>
		<p>详细的 Ant 脚本可以参见随本文所附的 TestWeb.xml 和 TestWeb.properties。</p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="500" onload="javascript:if(this.width&gt;500)this.width=500" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" onload="javascript:if(this.width&gt;500)this.width=500" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" onload="javascript:if(this.width&gt;500)this.width=500" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" onload="javascript:if(this.width&gt;500)this.width=500" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-lo-ant-eclipse/index.html#main">
																				<b>
																						<font color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="N10159">
						<span class="atitle">
								<font face="Arial" size="4">编译过程与产生不同目标环境的脚本分开执行</font>
						</span>
				</a>
		</p>
		<p>在前面介绍的 Ant 脚本中，根据从 CVS 服务器中检出的资源打成了一个默认的 war 包，并没有考虑根据不同的目标环境来生成不同的包，从下一节开始介绍如何根据不同的环境来生成不同的部署包。</p>
		<p>还有一个问题是：为什么需要把从 CVS 中检出资源、编译的过程跟根据目标环境打包的过程分开？</p>
		<p>这是因为本身 CVS 检出资源是需要花一定的时间，如果资源比较多这个过程就会花费挺长时间；另外，在多人开发的情况下必须保证在生成不同的部署包的时候是用的是同一套代码生成的，否则会出现各个服务器上运行的版本不一致，如果检出资源、编译的过程跟生成包的脚本一起执行的话就会出现这个问题（比如小A在测试服务器测试通过的时候之后，再生成一个在产品环境下的部署包，如果分两次从 CVS 服务器中检出资源的话，在此期间可能会有开发人员往 CVS 服务器中检入代码，导致生成的版本不一致），因此，必须将这两个过程分开执行。现在我们开始建立另外一个 Ant 脚本文件，叫 deploy.xml，专门用来生成包；另外与 deploy.xml 相对应的还有一个 deploy.properties 文件。在 deploy.xml 中会引用 deploy.properties 文件。另外根据在前面的场景中碰到的环境，创建三个不同的属性文件, develop_deploy.property、test_deploy. property 和 product_deploy. Property，在打包的时候，根据不同的目标环境，将相应属性文件中的内容拷贝至 deploy.properties 文件中（或者也可以直接在 deploy.xml 中直接切换不同的属性文件），然后在 Eclipse 中直接执行 deploy.xml；如果在命令行中，可以用下面的命令来执行：</p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<code>
												<pre class="section">  ant –f deploy.xml
</pre>
										</code>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="500" onload="javascript:if(this.width&gt;500)this.width=500" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" onload="javascript:if(this.width&gt;500)this.width=500" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" onload="javascript:if(this.width&gt;500)this.width=500" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" onload="javascript:if(this.width&gt;500)this.width=500" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-lo-ant-eclipse/index.html#main">
																				<b>
																						<font color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="N1016B">
						<span class="atitle">
								<font face="Arial" size="4">解开 WAR 包</font>
						</span>
				</a>
		</p>
		<p>我们首先得建立一个目录（这里是 unpack）用来存放解压后的文件。Ant 中提供了 unzip 命令用来解压 ear/war/jar 包。除了这个目录外，根据不同的目标环境，在运行目录下建立一个与目标环境相对应的目录，重新打好的 war 包就放在这个目录下，比如针对场景中的情况，如果需要创建一个产品环境下的部署包，我们可以建立一个 TestWebProduct 目录，目录名写在配置文件中（${pack.base.dir}）。</p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<code>
												<pre class="section">  &lt;target name='init'&gt;
		&lt;echo&gt;init in deploy&lt;/echo&gt;
		&lt;property file='deploy.properties' /&gt;
		&lt;delete dir='${unpack.base.dir}' failonerror='false' /&gt;
		&lt;delete dir='${pack.base.dir}' failonerror='false' /&gt;
		&lt;mkdir dir='${unpack.base.dir}' /&gt;
		&lt;mkdir dir='${pack.base.dir}' /&gt;
	&lt;/target&gt;

	&lt;target name='unpack'&gt;
		&lt;echo&gt;unpack the ${war.name}&lt;/echo&gt;
		&lt;copy file='${dest.dir}/${war.name}' todir='${unpack.base.dir}' /&gt;
		&lt;unzip src='${unpack.base.dir}/${war.name}' dest='${unpack.base.dir}/${projectName}' /&gt;
		&lt;delete file='${unpack.base.dir}/${war.name}' /&gt;
	&lt;/target&gt;
</pre>
										</code>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>在 init 段中首先删除掉上次解压的目录和目标打包目录，然后重新建立目录；在 unpack 中，首先将编译好的默认 war 包拷贝至 unpack 定义的目录，解压之后把 unpack 下的 war 包删除。下面是这时候对应的属性文件。</p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<code>
												<pre class="section">projectName=MTSWeb
war.name=MTSWeb.war

#根目录
base.dir=c:/temp
#默认的war包所在的目录
dest.dir=${base.dir}/dist
#解压后的目录
unpack.base.dir=${base.dir}/unpack
#目标环境相对应的目录
pack.base.dir=${base.dir}/TestWebProduct
</pre>
										</code>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="500" onload="javascript:if(this.width&gt;500)this.width=500" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" onload="javascript:if(this.width&gt;500)this.width=500" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" onload="javascript:if(this.width&gt;500)this.width=500" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" onload="javascript:if(this.width&gt;500)this.width=500" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-lo-ant-eclipse/index.html#main">
																				<b>
																						<font color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="N1017E">
						<span class="atitle">
								<font face="Arial" size="4">利用 Ant 提供的 filter 任务替换属性值</font>
						</span>
				</a>
		</p>
		<p>现在根据不同环境的需要，对某些配置文件的值做一些替换。在 Ant 中，提供了 filter 任务，使得替换值很方便。当然也可以使用下面介绍的正则表达式来替换属性值。filter 主要用来在同一行内容中的替换，而正则表达式一下子可以替换多行内容。filter 的使用例子：</p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<code>
												<pre class="section">  &lt;filter token=' log4j.logger' value='INFO'/&gt;
  &lt;copy todir='${dest.dir}' filtering='true'&gt;
    &lt;fileset dir='${src.dir}'/&gt;
  &lt;/copy&gt;
</pre>
										</code>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>这段脚本的意思就是在 src.dir 目录下的所有文件中，如果有预先定义好的'@log4j.logger@'占位符的话，在拷贝到 dest.dir 目录后，所有的占位符都被替换成了'INFO'。</p>
		<p>你也可以将所有被替换的值放到某个属性文件中，filter 任务将属性文件中的每一个条目读出来并且设置成一个 Filter。如下所示：</p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<code>
												<pre class="section">  &lt;filter filtersfile='deploy_env.properties'/&gt;
  &lt;copy todir='${dest.dir}' filtering='true'&gt;
    &lt;fileset dir='${src.dir}'/&gt;
  &lt;/copy&gt;
</pre>
										</code>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>上面的脚本表示所有在 deploy_env 中出现的条目将被作为一个 filter，在拷贝到 dest.dir 目录后，所有 src.dir 目录中存在的占位符将被替换成 deploy_env 中的值。具体的例子可以参见随本文附带的 deploy.xml， deploy_env.properties 和 Test.properties。 </p>
		<p>其中 deploy.xml 是 ant 脚本，deploy_env.properties 中包含所有要替换的值，在 Test.properties 中是包含有占位符的资源文件。</p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="500" onload="javascript:if(this.width&gt;500)this.width=500" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" onload="javascript:if(this.width&gt;500)this.width=500" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" onload="javascript:if(this.width&gt;500)this.width=500" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" onload="javascript:if(this.width&gt;500)this.width=500" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-lo-ant-eclipse/index.html#main">
																				<b>
																						<font color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="N1019A">
						<span class="atitle">
								<font face="Arial" size="4">利用正则表达式替换属性值</font>
						</span>
				</a>
		</p>
		<p>Ant 中支持多种正则表达式，在运行 Ant 的时候用哪种正则表达式可以通过设置 ant.regexp.regexpimpl 的值来切换，Ant 支持的的正则表达式有：</p>
		<ul>
				<li>java.util.regex package of JDK 1.4 
</li>
				<li>jakarta-regexp 
</li>
				<li>installation dependencies </li>
		</ul>
		<p>正则表达式的例子：</p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<code>
												<pre class="section">&lt;replaceregexp byline='true'&gt;
&lt;regexp pattern='正则表达式'/&gt;
&lt;substitution expression='将要替换的值'/&gt;
&lt;fileset dir='${unpack.war.dir}/WEB-INF' includes='web.xml'/&gt;
&lt;/replaceregexp&gt;
</pre>
										</code>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>byline 属性用来确认被替换的时候是一次替换一行还是多行；pattern 属性用来指明正则表达式；substitution expression 中是替换的值，替换的值都定义在相对应的配置文件中；fileset 属性中的 dir 用来指定被替换文件所在的目录，includes 用来指定要替换哪个文件。需要注意的是，如果在正则表达式或者替换的值中出现'&lt;'的话，需要用转义符'&lt;'。</p>
		<p>在 Eclipse3.1 中已经内置了对正则表达式的支持；但是如果你在命令行中运行需要正则表达式支持的脚本的话，则需要自己将正则表达式的包下载下来加到 classpath 中。在随文章的 deploy.xml 中提供了一个简单的替换属性文件的值的例子。正则表达式的例子可以在本文所带的 deploy.xml 中找到。</p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="500" onload="javascript:if(this.width&gt;500)this.width=500" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" onload="javascript:if(this.width&gt;500)this.width=500" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" onload="javascript:if(this.width&gt;500)this.width=500" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" onload="javascript:if(this.width&gt;500)this.width=500" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-lo-ant-eclipse/index.html#main">
																				<b>
																						<font color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="N101BB">
						<span class="atitle">
								<font face="Arial" size="4">Ant 使用条件表达式，根据属性值删除不需要的文件</font>
						</span>
				</a>
		</p>
		<p>在生成部署包的时候，还有可能会根据目标环境的不同，删除一些不同的文件。比如在产品环境中那些作为测试需要的代码往往需要删除，但是测试环境中并不需要。因此就需要条件表达式来做判断。如下所示：</p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<code>
												<pre class="section">&lt;target name='checkTestEnv'&gt;
	&lt;condition property='isTestEnv'&gt;
		&lt;istrue value='${deploy.isTestEnv}' /&gt;
	&lt;/condition&gt;
	&lt;antcall target='trueCondition' /&gt;
	&lt;antcall target='falseCondition' /&gt;
&lt;/target&gt;
&lt;target name='trueCondition' if='isTestEnv'&gt;
	&lt;echo message='true condition in ${projectName}' /&gt;
&lt;/target&gt;
&lt;target name='falseCondition' unless='isTestEnv'&gt;
	&lt;echo message='false condition in ${projectName}' /&gt;
&lt;/target&gt;
</pre>
										</code>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>在上面的例子中，先读出 ${deploy.isTestEnv} 的值（在配置文件 deloy.properties 中），根据读出的值对属性 isTestEnv 设值，如果 ${deploy.isTestEnv} 的值是 true，isTestEnv 的值也是 true；否则是 false。然后分别调用目标段 trueCondition 和 falseCondition。在这里，表达式值的判断是用'istrue'，在 Ant 中还提供了很多别的表达式，比如 not/and/or，equals 等等，具体关于条件表达式的信息可以参考：<a href="http://ant.apache.org/manual/CoreTasks/condition.html" target="_blank"><u><font color="#5c81a7">http://ant.apache.org/manual/CoreTasks/condition.html</font></u></a> ，该页也可以在随下载下来的文档中找到。</p>
		<p>在段 trueCondition 中，判断 isTestEnv，如果是真的话就运行，否则不运行；在段 falseCondition 中，也判断 isTestEnv，如果是假就运行，否则不运行，在段中可以根据情况做相应的事情。条件判断式的例子可以在本文的 deploy.xml 中找到。</p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="500" onload="javascript:if(this.width&gt;500)this.width=500" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" onload="javascript:if(this.width&gt;500)this.width=500" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" onload="javascript:if(this.width&gt;500)this.width=500" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" onload="javascript:if(this.width&gt;500)this.width=500" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-lo-ant-eclipse/index.html#main">
																				<b>
																						<font color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="N101D2">
						<span class="atitle">
								<font face="Arial" size="4">重新打包，并拷贝到不同的目录</font>
						</span>
				</a>
		</p>
		<p>在上面的替换过程完成后，调用 war 将 unpack 目录下的内容重新打包。</p>
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<code>
												<pre class="section">&lt;target name='repack'&gt;
	&lt;war destfile='${pack.base.dir}/${projectName}.war' 
		basedir='${unpack.base.dir}/${projectName}'
		webxml='${unpack.base.dir}/${projectName}/WEB-INF/web.xml' 
		manifest='${unpack.base.dir}/${projectName}/META-INF/MANIFEST.MF'&gt;
	&lt;/war&gt;
&lt;/target&gt;
</pre>
										</code>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>详细的例子可以参见随本文的附件 deploy.xml 和 deploy.properties。</p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="500" onload="javascript:if(this.width&gt;500)this.width=500" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" onload="javascript:if(this.width&gt;500)this.width=500" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" onload="javascript:if(this.width&gt;500)this.width=500" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" onload="javascript:if(this.width&gt;500)this.width=500" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-lo-ant-eclipse/index.html#main">
																				<b>
																						<font color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="N101E1">
						<span class="atitle">
								<font face="Arial" size="4">结论</font>
						</span>
				</a>
		</p>
		<p>通过本文可以看出编写好有效的 Ant 脚本能很好的解决在编译部署包的时候出现的问题，将那些变化的内容放到配置文件中，在部署的时候切换不同的配置文件就可以实现生成不同的部署包。文中也介绍了如何使用 Eclipse 来提高你编写/调试 Ant 脚本的效率。</p>
<img src ="http://www.blogjava.net/ytfei/aggbug/76397.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ytfei/" target="_blank">飞翔</a> 2006-10-20 15:48 <a href="http://www.blogjava.net/ytfei/archive/2006/10/20/76397.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>c++学习笔记</title><link>http://www.blogjava.net/ytfei/archive/2006/06/25/54983.html</link><dc:creator>飞翔</dc:creator><author>飞翔</author><pubDate>Sun, 25 Jun 2006 05:35:00 GMT</pubDate><guid>http://www.blogjava.net/ytfei/archive/2006/06/25/54983.html</guid><wfw:comment>http://www.blogjava.net/ytfei/comments/54983.html</wfw:comment><comments>http://www.blogjava.net/ytfei/archive/2006/06/25/54983.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ytfei/comments/commentRss/54983.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ytfei/services/trackbacks/54983.html</trackback:ping><description><![CDATA[class Teacher : public People <BR>Teacher * t,tobj; People * p,pobj;<BR><BR>1.使用基类指针操作派生类对象。 p = &amp;tobj;0只能访问定义于基类的共有成员。 必须将基类指针强制转换为派生类指针，才能访问派生类的成员。 (Teacher *)&nbsp;p-&gt; ...<BR>2.使用派生类指针操作基类对象 。 t = (Teacher *) &amp;pobj ; 并且如此只能访问基类中的成员，不能访问派生类成员！<BR><BR>***********************************<BR>虚函数（实现多态）<BR>在基类中给方法加上virtual 标记，派生类实现该方法。 <BR>则使用时，采用基类指针指向具体子类对象的时候，即可以实现多台。通过指针调用的将是具体子类的改方法。<BR><BR>纯虚函数：<BR>virtual double&nbsp; method&nbsp; const=0;&nbsp;&nbsp; 包含纯虚函数的类称为 抽象类！<BR><BR>类的析构函数应该被设置为虚析构函数<BR><BR><BR>const Teacher t;//常量对象<BR><BR>void method const(){} //常量函数<BR><BR>常量对象不能调用非常量成员函数<BR>常量函数不允许调用非常量成员函数。<BR><BR><BR>**********<BR><BR>文件写对象<BR>ofstream outFile("filename",ios::out| ios::binary);<BR>outFile.write( (char *) &amp;st,sizeof(st)); //st是一个类的对象<BR>infile.read((char *)&amp;st,sizeof(st)); //读取出对象<BR><BR>*******<BR>throw 可以抛出任意类型的异常,比如 int a=0; throw a;<BR>catch(...){ //捕获任意类型的异常<BR>; <BR>}<BR><BR>catch(int){<BR>//捕获整型异常<BR>}<BR><BR>********<BR>函数模板<BR>template &lt;class T&gt; <BR>void sortArray(T b[],int len);<BR>类模板:<BR>template &lt;class T&gt;<BR>class Test{<BR>..<BR>}<BR>所以在类模板外面定义的成员函数都要以template &lt;class T&gt;开头<BR><BR>实例化类对象. Test &lt;int&gt; test;<BR><img src ="http://www.blogjava.net/ytfei/aggbug/54983.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ytfei/" target="_blank">飞翔</a> 2006-06-25 13:35 <a href="http://www.blogjava.net/ytfei/archive/2006/06/25/54983.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>memcached 的使用</title><link>http://www.blogjava.net/ytfei/archive/2006/06/10/51787.html</link><dc:creator>飞翔</dc:creator><author>飞翔</author><pubDate>Sat, 10 Jun 2006 02:44:00 GMT</pubDate><guid>http://www.blogjava.net/ytfei/archive/2006/06/10/51787.html</guid><wfw:comment>http://www.blogjava.net/ytfei/comments/51787.html</wfw:comment><comments>http://www.blogjava.net/ytfei/archive/2006/06/10/51787.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.blogjava.net/ytfei/comments/commentRss/51787.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ytfei/services/trackbacks/51787.html</trackback:ping><description><![CDATA[
		<ul>
				<li>用户访问网页时，查看 memcached 中是否有当前用户的 SESSION 数据，使用 session_id() 作为唯一标识符；如果数据存在，则直接返回，如果不存在，再进行数据库连接，获取 SESSION 数据，并将此数据保存到 memcached 中，供下次使用； 
</li>
				<li>当前的 PHP 运行结束（或使用了 <a href="http://php.liukang.com/manual/en/function.session-write-close.php">session_write_close()</a>）时，会调用 My_Sess::write() 方法，将数据写入数据库，这样的话，每次仍然会有数据库操作，对于这个方法，也需要进行优化。使用一个全局变量，记录用户进入页面时的 SESSION 数据，然后在 write() 方法内比较此数据与想要写入的 SESSION 数据是否相同，不同才进行数据库连接、写入数据库，同时将 memcached 中对应的对象删除，如果相同的话，则表示 SESSION 数据未改变，那么就可以不做任何操作，直接返回了； 
</li>
				<li>那么用户 SESSION 过期时间怎么解决呢？记得 memcached 的 add() 方法有个过期时间参数 $exp 吗？把这个参数值设置成小于 SESSION 最大存活时间即可。另外别忘了给那些一直在线的用户延续 SESSION 时长，这个可以在 write() 方法中解决，通过判断时间，符合条件则更新数据库数据。 </li>
		</ul>
<img src ="http://www.blogjava.net/ytfei/aggbug/51787.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ytfei/" target="_blank">飞翔</a> 2006-06-10 10:44 <a href="http://www.blogjava.net/ytfei/archive/2006/06/10/51787.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>转:千万级邮件系统的设计及数据库中间件部分实现</title><link>http://www.blogjava.net/ytfei/archive/2006/06/09/51742.html</link><dc:creator>飞翔</dc:creator><author>飞翔</author><pubDate>Fri, 09 Jun 2006 09:27:00 GMT</pubDate><guid>http://www.blogjava.net/ytfei/archive/2006/06/09/51742.html</guid><wfw:comment>http://www.blogjava.net/ytfei/comments/51742.html</wfw:comment><comments>http://www.blogjava.net/ytfei/archive/2006/06/09/51742.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ytfei/comments/commentRss/51742.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ytfei/services/trackbacks/51742.html</trackback:ping><description><![CDATA[
		<!--开始头部-->
		<script language="javascript" src="Huihoo Power! - 千万级邮件系统的设计及数据库中间件部分实现.files/header.js">
		</script>
 
<table cellspacing="0" cellpadding="0" width="760" align="center" border="0"><tbody><tr><td width="86"><a href="http://www.huihoo.com/index.html"><img height="23" src="http://www.huihoo.com/i/a01.gif" width="86" border="0" /></a></td><td width="91"><a href="http://www.huihoo.com/document/index.html"><img height="23" src="http://www.huihoo.com/i/a02.gif" width="91" border="0" /></a></td><td width="90"><a href="http://www.huihoo.org/index.html" target="_blank"><img height="23" src="http://www.huihoo.com/i/a03.gif" width="90" border="0" /></a></td><td width="92"><a href="http://www.huihoo.com/bbs/index.html"><img height="23" src="http://www.huihoo.com/i/a04.gif" width="92" border="0" /></a></td><td width="92"><a href="http://www.huihoo.com/about/index.html"><img height="23" src="http://www.huihoo.com/i/a05.gif" width="92" border="0" /></a></td><td width="91"><a href="http://www.huihoo.com/services/index.html"><img height="23" src="http://www.huihoo.com/i/a06.gif" width="91" border="0" /></a></td><td width="93"><a href="http://www.huihoo.com/about/index.html"><img height="23" src="http://www.huihoo.com/i/a07.gif" width="93" border="0" /></a></td><td><img height="23" src="http://www.huihoo.com/i/title.gif" width="125" /></td></tr></tbody></table><table cellspacing="0" cellpadding="0" width="760" align="center" border="0"><tbody><tr><td width="525"><img height="99" src="http://www.huihoo.com/i/banner-001.gif" width="525" /></td><td><img height="99" src="http://www.huihoo.com/i/logo-001.gif" width="235" /></td></tr></tbody></table><table cellspacing="0" cellpadding="0" width="760" align="center" border="0"><tbody><tr><td><img height="3" src="http://www.huihoo.com/i/kong.gif" width="4" /></td></tr><tr><td>  <a href="http://www.huihoo.com/os/index.html">OS</a>  <a href="http://www.huihoo.com/java/index.html">Java</a>  <a href="http://www.huihoo.com/corba/index.html">CORBA</a>  <a href="http://www.huihoo.com/com/index.html">COM+</a>  <a href="http://www.huihoo.com/document/middleware.html">Middleware</a>  <a href="http://www.huihoo.com/xml/index.html">XML&amp;WebService</a>  <a href="http://www.huihoo.com/patterns/index.html">Patterns</a>  <a href="http://www.huihoo.com/one_and_net/index.html">ONE&amp;NET</a>  <a href="http://www.huihoo.com/p2p/index.html">P2P</a>  <a href="http://www.huihoo.com/development/index.html">Development</a>  <a href="http://www.huihoo.com/database/index.html">Database</a>  <a href="http://www.huihoo.com/download/index.html">Download</a>  <a href="http://www.huihoo.com/document/index.html">Doc</a></td></tr></tbody></table><table cellspacing="0" cellpadding="0" width="760" align="center" border="0"><tbody><tr><td><img height="3" src="http://www.huihoo.com/i//bar-001.gif" width="760" /></td></tr></tbody></table><!--结束头部--><!--开始中部--><table cellspacing="0" cellpadding="3" width="760" align="center" border="0"><tbody><tr width="100%"><td width="1%"></td><!--开始正文--><td width="98%"><br /><center><h4>千万级邮件系统的设计及数据库中间件部分实现</h4></center><br />(作者：艺龙公司技术研发经理 高鹏) <br /><br /><p>　　近日，艺龙公司推出了“艺龙五星级企业邮箱系统”和“艺龙个人5秒邮”的收费邮件系统，这两套系统都采用的是艺龙公司与全世界最大的邮件运营商mail.com合作设计和开发的千万级邮件系统：“ComConnect”。笔者是该系统的主要设计人员，本文将介绍这套系统的技术特色，以及数据库中间件部分实现。</p><p>　　艺龙公司的“ComConnect”系统可以运行在大部分的unix系统平台，包括solaris、linux等，采用模块化的设计，整个系统分为下面八大模块：</p><p>　　<b>1．数据库中间件模块＋缓存模块</b></p><p>　　　 这是整套系统的主线，贯穿于系统的其他所有模块。</p><p>　　在大规模邮件系统中，为了管理的方便和系统的分布式部署，都需要采用SQL数据库保存一些信息，例如帐号信息等。例如，当用户通过POP3登陆时，服务器端会启动一个程序，与SQL数据库连接，并查询该用户身份的合法性，当用户退出时，该程序会断开与SQL数据库的连接。这样，大量用户的登陆、退出，都会对应于SQL数据库的连接、断开操作，降低了系统的效率，也增加了SQL数据库的负载。</p><p><br />　　在ComConnect系统中，所有数据库的操作，都是通过“数据库中间件”模块完成的。该模块会保持与SQL数据库的连接，永不断开。当有程序需要查询数据库时，会通过unix domain socket与本地的数据库中间件服务器相连接，中间件会取得查询需求，并进行实际的SQL查询，并将结果通过unix domain socket返回给应用程序。同时，对于所得的查询，中间件服务器会进行本地缓存，保存到内存数据库BerkeleyDB3(http://www.sleepycat.com)中，这样下次同样的查询请求，就不必SQL查询了，因为结果已经被保存在本地缓存了。这种设计，一方面，避免了频繁的SQL数据库的连接、断开的操作，另一方面，由于本地缓存的存在，也大大降低了SQL数据库的负载，提高了系统的响应效率。</p><p>　　<b> 2．收信模块 </b></p><p><br />　　该模块对应于DNS中的MX记录。</p><p>　　传统的收信模块运行方式是，在得到一封邮件后，保存到本地，或通过SMTP协议进行转发。而在ComConnect系统中，采用LMTP(local mail transport protocol)协议连接“收信模块”和“邮件存储模块”，在这两个模块之间，永久保持若干个通道，当收信模块得到新的邮件时，就会利用这些已经存在的通道将邮件内容传输给后台的邮件存储模块。这种设计，大大减少了SMTP服务器频繁的fork、exit过程，从而提高了效率。</p><p>　　 <b>3．发信模块</b></p><p>　　 该模块是整个系统中最独立的模块。为了提高安全性，ComConnect系统通过cyrus-sasl实现了发信服务器的用户身份认证功能。认证部分通过“认证模块”完成。</p><p>　　<b> 4．中央数据库模块</b></p><p><br />　　与其他大规模邮件系统一样，ComConnect系统也需要中央数据库(支持SQL标准)模块。但与其他系统不同的是，ComConnect系统的中央数据库保存的内容非常少，仅仅保存用户帐号信息。而且，由于“数据库中间件”和“缓存”模块的存在，在理想状态下，中央数据库根本不需要存在。</p><p>　　这种设计，极大地提高了系统性能，更提高了系统的可扩展性。与此相对应，其他的邮件系统将尽可能多的信息都保存在中央数据库中，随着用户数目的增大，效率会越来越低，最终成为系统的瓶颈。 <br /><br />　<b>　 5．HTTP session模块</b></p><p>　　由于HTTP协议的无状态性质，所以当用户登陆后需要保存用户的session(会话)信息。传统的session模块都是通过数据库完成的，这样，如果用户量非常大，会对数据库造成很大的负载，最终形成整套系统的瓶颈。ComConnect系统对session的处理，采用了自行开发的专门的session server处理，系统拿出一台单独的服务器充当session server，内部定义了一套session协议来维护每个session的状态，并在一定时间客户端没有访问时自动删除session记录以实现session垃圾回收机制。该模块的数据库采用了高效的内存数据库：BerkeleyDB3。在这种设计下，web服务器作为session模块的客户端，session server作为session模块的服务器端。当用户登陆时，Web服务器会通过session协议访问后台的session server，以记录该次session的信息。</p><p>　　与传统的session管理机制相比，这种方式减少了中央数据库的负载，又由于内存数据库的高效性，以及session协议的简单性，大大提高系统的响应速度。</p><p><b>　　 6．认证模块</b></p><p>　　系统需要认证的部分有：Web登陆、发信时的用户身份认证、POP3登陆。这些认证都通过本地的认证模块实现，该模块工作机制与“数据库中间件”类似，通过unix domain socket进行进程间通讯，并与数据库持续连接，以及维护本地的缓存。</p><p>　　<b> 7．Web模块</b></p><p>　　这是用户使用的Web界面部分。该模块通过IMAP协议，与后台的“邮件存储模块”进行通讯。其中的session，是通过“HTTP session模块”完成的。</p><p><br />　　<b> 8．邮件存储模块</b></p><p>　　该部分是整个系统最为核心的模块，所有用户的邮件最终都保存在该模块上。每个用户的邮箱就是一个目录，每封邮件就是一个文件。</p><p><br />　　有些大规模邮件系统将邮件的信头(header)保存到数据库中，而邮件的正文(body)保存到文件中。这种设计，可以提高用户在访问邮箱时的速度，尤其是邮箱中有很多封邮件的时候，另外，在实际实现的时候也有代码简单的优点。但它的缺点也是突出的，首先，“收信模块”要对进入的每封邮件进行处理，以提取出信头和正文，这会降低收信的效率；另一方面，由于每封邮件都会引发对数据库的INSERT操作，因此会加大数据库的负担；还有，随着系统接收邮件数目的增大，数据库中的记录数也会相应地增大，最终可能出现瓶颈。</p><p>　　与上面的解决办法不同，ComConnect系统在文件系统中建立了索引机制，而不采用数据库的索引。每个用户的邮箱(对应一个目录)下，都有该用户所有邮件的索引。这样，当用户操作邮箱时，速度可以非常快；另外，对每封进入的邮件，“收信模块”不需要做任何处理，可以直接通过LMTP通道传输给“邮件存储模块”，而“邮件存储模块”直接将内容写入对应用户的邮箱目录下。</p><p><br />　　这八大模块构成了ComConnect整个系统，其中每个模块都具有很好的扩展机制，可以通过增加计算机数目来提高性能。更详细的信息，请访问： http://bms.elong.com/ads/</p><p><br />下面是数据库中间件的部分代码，读者可以从中了解其运行机制：</p><p><br />dbServer dbserver = db_unknown;</p><p>int midInited = 0;</p><p><br />/*</p><p>*--------------------------------------------------------------</p><p>*</p><p>* mid_init</p><p>*</p><p>*--------------------------------------------------------------</p><p>*/</p><p>void</p><p>mid_init(const char *conf)</p><p>{</p><p>　　const char fname[] = "mid_init";</p><p>　　const char *server ; </p><p><br />　　if ( !conf || !*conf ) {</p><p>　　　　myconfig_read(MIDWARE_CONFIG_FIENAME);</p><p>　　} else {</p><p>　　　　myconfig_read(conf);</p><p>　　}</p><p>　　server = myconfig_getstring("backend_server",NULL);</p><p>　　if ( !server || !*server ) {</p><p>　　　　syslog(LOG_ERR,"[%s]invalid 'backend_server' in conf file",fname);</p><p>exit(1); /* yes, we exit directly */</p><p>　　}</p><p><br />　　/* judge what backend server is */</p><p>　　if ( !strcasecmp(server,BACKEND_MYSQL) ) {</p><p>　　　　syslog(LOG_DEBUG3,"[%s]backend server is %s",fname,BACKEND_MYSQL);</p><p>　　　　dbserver = db_mysql;</p><p>　　} else if ( !strcasecmp(server,BACKEND_ORACLE) ) {</p><p>　　　　syslog(LOG_DEBUG3,"[%s]backend server is %s",fname,BACKEND_ORACLE);</p><p>　　　　dbserver = db_oracle;</p><p>　　} else if ( !strcasecmp(server,BACKEND_BERKELEYDB) ) {</p><p>　　　　syslog(LOG_DEBUG3,"[%s]backend server is %s",fname,BACKEND_BERKELEYDB);</p><p>　　　　dbserver = db_berkeleydb;</p><p>　　} else {</p><p>　　　　dbserver = db_unknown;</p><p>　　　　syslog(LOG_CRIT,"[%s]unknown backend server: %s",fname,server);</p><p>exit(1);</p><p>　　} </p><p><br />　　midInited = 1;</p><p><br />} /* mid_init */</p><p><br />/*</p><p>*------------------------------------------------------------------------</p><p>*</p><p>* sendCmd</p><p>*</p><p>* -- midware protocol implementation on midwared client side</p><p>*</p><p>* ask midware server something via unix domain socket</p><p>*</p><p>* RET:</p><p>* CMD_ERR_SYS: system error</p><p>* CMD_INVALID: null cmd sent to midwared server</p><p>* CMD_OK : ok</p><p>*</p><p>*------------------------------------------------------------------------</p><p>*/</p><p>static cmdResult</p><p>sendCmd(char *cmd,char *sqlcmd,const char *param2,const char *param3,</p><p>　　　　　　const char *param4,char **reply)</p><p>{</p><p>　　const char fname[]="sendCmd";</p><p>　　int s; int r,iovcount=0;</p><p>　　struct sockaddr_un srvaddr;</p><p>　　struct iovec iov[6];</p><p>　　static char response[MAX_REP_LEN];</p><p>　　char sockfile[1024];</p><p>　　int start, n;</p><p></p><p>　　if (reply) *reply = NULL;</p><p>　　if ( !cmd || !*cmd ) {</p><p>　　　　syslog(LOG_ERR,"[%s]null cmd specified",fname);</p><p>　　　　return CMD_INVALID;</p><p>　　}</p><p></p><p>　　/* create socket and connect to midware server */</p><p>　　s = socket(AF_UNIX, SOCK_STREAM, 0);</p><p>　　if (s == -1) {</p><p>　　　　syslog(LOG_CRIT,"[%s]create socket failed: %m",fname);</p><p>return CMD_ERR_SYS;</p><p>　　}</p><p>　　memset(sockfile,0,sizeof(sockfile));</p><p>　　strncpy(sockfile, myconfig_getstring("unixsock_dir",DEFAULT_MIDWARED_DIR) ,</p><p>　　　　sizeof(sockfile));</p><p>　　strcat(sockfile,"/");</p><p>　　strcat(sockfile,SOCKET_FILENAME);</p><p>　　syslog(LOG_DEBUG3,"[%s]socket filename: %s",fname,sockfile);</p><p>　　memset((char *)&amp;srvaddr, 0, sizeof(srvaddr));</p><p>　　srvaddr.sun_family = AF_UNIX;</p><p>　　strncpy(srvaddr.sun_path, sockfile, sizeof(srvaddr.sun_path));</p><p>　　r = connect(s, (struct sockaddr *)&amp;srvaddr, sizeof(srvaddr));</p><p>　　if (r == -1) {</p><p>　　　　if (reply) *reply="Cannot connect to midwared server";</p><p>　　　　syslog(LOG_ERR,"[%s]:connect: %m",fname);</p><p>　　　　return CMD_ERR_SYS;</p><p>　　} </p><p></p><p>　　/* </p><p>　　 * connected with the unix-domain socket ,next </p><p>　　 * prepare the parameters for midwared server </p><p>　　 */</p><p>　　iov[iovcount].iov_base = (char *)cmd;</p><p>　　iov[iovcount].iov_len = strlen(cmd)+1;</p><p>　　/* check sqlcmd */</p><p>　　if ( sqlcmd &amp;&amp; *sqlcmd ){</p><p>iovcount++;</p><p>　　　　iov[iovcount].iov_base = (char *)sqlcmd;</p><p>　　　　iov[iovcount].iov_len = strlen(sqlcmd)+1;</p><p>　　} else {</p><p>　　　　goto startsend;</p><p>　　}</p><p>　　/* check parameter2 */</p><p>　　if ( param2 &amp;&amp; *param2 ){</p><p>iovcount++;</p><p>　　　　iov[iovcount].iov_base = (char *)param2;</p><p>　　　　iov[iovcount].iov_len = strlen(param2)+1;</p><p>　　} else {</p><p>　　　　goto startsend;</p><p>　　}</p><p>　　/* check parameter3 */</p><p>　　if ( param3 &amp;&amp; *param3 ){</p><p>iovcount++;</p><p>　　　　iov[iovcount].iov_base = (char *)param3;</p><p>　　　　iov[iovcount].iov_len = strlen(param3)+1;</p><p>　　} else { </p><p>　　　　goto startsend;</p><p>　　}</p><p>　　/* check parameter4 */</p><p>　　if ( param4 &amp;&amp; *param4 ){</p><p>iovcount++;</p><p>　　　　iov[iovcount].iov_base = (char *)param4;</p><p>　　　　iov[iovcount].iov_len = strlen(param4)+1;</p><p>　　}</p><p><br />startsend:</p><p>　　retry_writev(s, iov, iovcount+1);</p><p></p><p>　　/* get reply from midwared server */</p><p>　　start = 0;</p><p>　　while (start 　　　　n = read(s, response+start, sizeof(response) - 1 - start);</p><p>　　　　if (n &lt;1) break; </p><p>　　　　start += n;</p><p>　　} /* while */</p><p>　　close(s);</p><p></p><p>　　/* marshell the reply for client call */</p><p>　　if ( start &lt;) =1 { </p><p>　　　　/* failurely got the reply */</p><p>　　　　if ( reply ) {</p><p>*reply=response;</p><p>}</p><p>　　　　return CMD_ERR_SYS;</p><p>　　} else {</p><p>　　　　/* successfully got the reply */</p><p>　　　　response[start] = '\0';</p><p>　　　　if (reply) { </p><p>*reply=response;</p><p>}</p><p>　　　　return CMD_OK;</p><p>　　}</p><p><br />} /* sendCmd */</p><p><br />/*</p><p>*--------------------------------------------------------------</p><p>*</p><p>* mid_query</p><p>* - exported for end user directly</p><p>*</p><p>* ARG -</p><p>* key: used for cache</p><p>*</p><p>* PRECON:</p><p>* argument 'result' must be initialized before this call</p><p>*</p><p>*--------------------------------------------------------------</p><p>*/</p><p>midQueryResult</p><p>mid_query(char *sqlcmd,const char *key,int useCache,char result[])</p><p>{</p><p>　　extern char *cache_mapstr(int);</p><p>　　const char fname[]="mid_query";</p><p>　　char *reply;</p><p>　　int r = 0;</p><p><br />　　if ( midInited == 0 ) {</p><p>　　　　mid_init(NULL);</p><p>　　}</p><p>　　　　　　　　　　　　　　　　</p><p>　　switch ( dbserver ) {</p><p>　　　　case db_mysql:</p><p>　　　　　　r = sendCmd(REQ_MYSQL_QUERY,sqlcmd,key,cache_mapstr(useCache),</p><p>　　　　　　　 NULL,(char **)&amp;reply);</p><p>　　　　　　break;</p><p></p><p>　　　　case db_oracle:</p><p>　　　　　　r = sendCmd(REQ_ORACLEL_QUERY,sqlcmd,key,cache_mapstr(useCache),</p><p>　　　　　　　 NULL,(char **)&amp;reply);</p><p>　　　　　　break;</p><p></p><p>　　　　case db_berkeleydb:</p><p>　　　　　　break;</p><p></p><p>　　　　default:</p><p>　　　　　　syslog(LOG_CRIT,"[%s]unknown backend server",fname);</p><p>　　　　　　break;</p><p>　　} /* switch */</p><p><br />　　/* let midware client know what happened */</p><p>　　if ( result &amp;&amp; *result ) strcpy(result,reply);</p><p></p><p>　　if ( r != CMD_OK ) {</p><p>　　　　syslog(LOG_DEBUG3,"[%s]no reply,system error",fname);</p><p>return QUERY_SYS_ERR;</p><p>　　}</p><p><br />　　if ( !strcmp(reply,REP_DB_QUERY_FAIL) ) {</p><p>　　　　return QUERY_FAIL;</p><p>　　} else if ( !strcmp(reply,REP_DB_QUERY_NOTFOUND) ) {</p><p>　　　　return QUERY_NOTFOUND;</p><p>　　} else if ( !strcmp(reply,REP_DB_QUERY_MANYRECORD) ) {</p><p>　　　　return QUERY_FOUNDMANY;</p><p>　　} else {</p><p>　　　　return QUERY_OK;</p><p>　　}</p><p><br />} /* mid_query */</p><p><br />/*</p><p>*--------------------------------------------------------------</p><p>*</p><p>* mid_insert</p><p>*</p><p>*--------------------------------------------------------------</p><p>*/</p><p>midInsertResult</p><p>mid_insert(char *sqlcmd)</p><p>{</p><p>　　const char fname[]="mid_insert";</p><p>　　char *reply;</p><p>　　int r = 0;</p><p>　　　　　　　　　　　　　　　　</p><p>　　if ( midInited == 0 ) {</p><p>　　　　mid_init(NULL);</p><p>　　}</p><p>　　　　　　　　　　　　　　　　</p><p>　　switch ( dbserver ) {</p><p>　　　　case db_mysql:</p><p>　　　　　　r = sendCmd(REQ_MYSQL_INSERT,sqlcmd,NULL,NULL,NULL,(char **)&amp;reply);</p><p>　　　　　　break;</p><p></p><p>　　　　case db_oracle:</p><p>　　　　　　r = sendCmd(REQ_ORACLE_INSERT,sqlcmd,NULL,NULL,NULL,</p><p>(char **)&amp;reply);</p><p>　　　　　　break;</p><p></p><p>　　　　case db_berkeleydb:</p><p>　　　　　　break;</p><p></p><p>　　　　default:</p><p>　　　　　　syslog(LOG_CRIT,"[%s]unknown backend server",fname);</p><p>　　　　　　break;</p><p>　　} /* switch */</p><p></p><p>　　if ( r != CMD_OK ) {</p><p>　　　　syslog(LOG_DEBUG3,"[%s]no reply,system error",fname);</p><p>return INSERT_SYS_ERR;</p><p>　　}</p><p><br />　　if ( !strcmp(reply,REP_DB_INSERT_FAIL) ) {</p><p>　　　　return INSERT_FAIL;</p><p>　　} else {</p><p>　　　　return INSERT_OK;</p><p>　　}</p><p><br />} /* mid_insert */</p><p></p></td><!--结束正文--><td width="1%"></td></tr></tbody></table><!--结束中部--><!--开始底部--><script language="javascript" src="Huihoo Power! - 千万级邮件系统的设计及数据库中间件部分实现.files/bottom.js"></script><table cellspacing="0" cellpadding="0" width="760" align="center" border="0"><tbody><tr><td align="middle"><font size="2">© 2006 Huihoo</font><br /></td></tr></tbody></table><!--结束底部--><img src ="http://www.blogjava.net/ytfei/aggbug/51742.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ytfei/" target="_blank">飞翔</a> 2006-06-09 17:27 <a href="http://www.blogjava.net/ytfei/archive/2006/06/09/51742.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>转:在Java程序中截获控制台输出</title><link>http://www.blogjava.net/ytfei/archive/2006/05/21/47293.html</link><dc:creator>飞翔</dc:creator><author>飞翔</author><pubDate>Sun, 21 May 2006 07:52:00 GMT</pubDate><guid>http://www.blogjava.net/ytfei/archive/2006/05/21/47293.html</guid><wfw:comment>http://www.blogjava.net/ytfei/comments/47293.html</wfw:comment><comments>http://www.blogjava.net/ytfei/archive/2006/05/21/47293.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ytfei/comments/commentRss/47293.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ytfei/services/trackbacks/47293.html</trackback:ping><description><![CDATA[
		<table cellspacing="0" cellpadding="0" width="96%" align="center" border="0">
				<tbody>
						<tr>
								<td>
										<br />
										<h1>在Java程序中截获控制台输出</h1>文档提供者：<a href="http://www.chinadir.net/viewuser.aspx?u=admin">newebug</a> () 于 2005-3-15<br /><br /><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr valign="top"><td><span class="atitle">在Java程序中截获控制台输出</span></td><td width="8"><img height="1" alt="" src="file:///E:/developerWorks/cn/i/c.gif" width="8" border="0" /></td><td></td><td width="6"><img height="1" alt="" src="file:///E:/developerWorks/cn/i/c.gif" width="6" border="0" /></td></tr><!-- Black line separator --><tr valign="top"><td bgcolor="#000000" colspan="5"><img height="1" alt="" src="file:///E:/developerWorks/cn/i/c.gif" width="100" border="0" /></td></tr><tr valign="top"><td bgcolor="#ffffff" colspan="5"><img height="8" alt="" src="file:///E:/developerWorks/cn/i/c.gif" width="100" border="0" /></td></tr></tbody></table><!-- END HEADER AREA --><!-- START BODY AREA --><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr valign="top"><td width="10"><img height="1" alt="" src="file:///E:/developerWorks/cn/i/c.gif" width="10" border="0" /></td><td width="100%"><table cellspacing="0" cellpadding="0" width="168" align="right" border="0"><tbody><tr><!-- Sidebar Gutter --><td width="8"><img height="21" alt="" src="file:///E:/developerWorks/cn/i/c.gif" width="5" /></td><td width="160"><!-- Start TOC --><table cellspacing="0" cellpadding="0" width="160" border="0"><tbody><tr><td width="160" bgcolor="#000000" height="1"><img height="1" alt="" src="file:///E:/developerWorks/cn/i/c.gif" width="160" /></td></tr><tr><td align="middle" background="/developerWorks/cn/i/bg-gold.gif" height="5"></td></tr><tr><td width="160" bgcolor="#666666" height="1"><img height="1" alt="" src="file:///E:/developerWorks/cn/i/c.gif" width="160" /></td></tr><tr><td align="right"><table cellspacing="0" cellpadding="3" width="98%" border="0"><tbody><tr><td></td></tr><tr><td>　　</td></tr><tr><td>　　</td></tr><tr><td>　　</td></tr><tr><td>　　</td></tr><tr><td> </td></tr><tr><td></td></tr><tr><td><a href="file:///E:/helpDoc/java/在Java程序中截获控制台输出.htm#resources"></a></td></tr><tr><td></td></tr></tbody></table></td></tr></tbody></table><!-- End TOC --><!-- Start Related Content Area ==
        <table width="160" border="0" cellspacing="0" cellpadding="0">
        <tr><td bgcolor="#000000" height="1" width="160"><img src="/developerWorks/cn/i/c.gif" width="160" height="1" alt=""/></td></tr>
        <tr><td height="5" background="/developerWorks/cn/i/bg-gold.gif" align="center"><b>相关内容：</b></td></tr>
        <tr><td bgcolor="#666666" height="1" width="160"><img src="/developerWorks/cn/i/c.gif" width="160" height="1" alt=""/></td></tr>
        <tr><td align=right>
            <table width="98%" border="0" cellspacing="0" cellpadding="3">
            <tr><td><a target="_blank" rel="nofollow" href=#1>TCP/IP 介绍</a></td></tr>
            <tr><td><a target="_blank" rel="nofollow" href=#1>TCP/IP 介绍</a></td></tr>
            </table>
        </td></tr></table>
        <!== End TOC --><table cellspacing="0" cellpadding="0" width="160" border="0"><tbody><tr><td width="150" bgcolor="#000000" colspan="2" height="2"></td></tr><tr><td width="150" bgcolor="#ffffff" colspan="2" height="2"><img height="2" alt="" src="file:///E:/developerWorks/cn/i/c.gif" width="160" /></td></tr></tbody></table><!-- END STANDARD SIDEBAR AREA --></td></tr></tbody></table><p><a href="file:///E:/helpDoc/java/在Java程序中截获控制台输出.htm#author1">俞良松</a> (<a href="mailto:javaman@163.net">javaman@163.net</a>)<br /><!-- <a target="_blank" rel="nofollow" href="#author2">Author name</a> (<a target="_blank" rel="nofollow" href="mailto:userid@us.ibm.com">email address</a>)<br />-->软件工程师，独立顾问和自由撰稿人<br />2001 年 10 月</p><blockquote>在Java开发中，控制台输出仍是一个重要的工具，但默认的控制台输出有着各种各样的局限。本文介绍如何用Java管道流截取控制台输出，分析管道流应用中应该注意的问题，提供了截取Java程序和非Java程序控制台输出的实例。</blockquote><p>即使在图形用户界面占统治地位的今天，控制台输出仍旧在Java程序中占有重要地位。控制台不仅是Java程序默认的堆栈跟踪和错误信息输出窗口，而且还是一种实用的调试工具（特别是对习惯于使用println()的人来说）。然而，控制台窗口有着许多局限。例如在Windows 9x平台上，DOS控制台只能容纳50行输出。如果Java程序一次性向控制台输出大量内容，要查看这些内容就很困难了。</p><p>对于使用javaw这个启动程序的开发者来说，控制台窗口尤其宝贵。因为用javaw启动java程序时，根本不会有控制台窗口出现。如果程序遇到了问题并抛出异常，根本无法查看Java运行时环境写入到System.out或System.err的调用堆栈跟踪信息。为了捕获堆栈信息，一些人采取了用try/catch()块封装main()的方式，但这种方式不一定总是有效，在Java运行时的某些时刻，一些描述性错误信息会在抛出异常之前被写入System.out和System.err；除非能够监测这两个控制台流，否则这些信息就无法看到。</p><p>因此，有些时候检查Java运行时环境（或第三方程序）写入到控制台流的数据并采取合适的操作是十分必要的。本文讨论的主题之一就是创建这样一个输入流，从这个输入流中可以读入以前写入Java控制台流（或任何其他程序的输出流）的数据。我们可以想象写入到输出流的数据立即以输入的形式“回流”到了Java程序。</p><p>本文的目标是设计一个基于Swing的文本窗口显示控制台输出。在此期间，我们还将讨论一些和Java管道流（PipedInputStream和PipedOutputStream）有关的重要注意事项。图一显示了用来截取和显示控制台文本输出的Java程序，用户界面的核心是一个JTextArea。最后，我们还要创建一个能够捕获和显示其他程序（可以是非Java的程序）控制台输出的简单程序。</p><center><br />图一：多线程的控制台输出截取程序</center><br /><br /><p><a id="1" name="1"><span class="atitle2">一、Java管道流</span></a><br />要在文本框中显示控制台输出，我们必须用某种方法“截取”控制台流。换句话说，我们要有一种高效地读取写入到System.out和System.err所有内容的方法。如果你熟悉Java的管道流PipedInputStream和PipedOutputStream，就会相信我们已经拥有最有效的工具。</p><p>写入到PipedOutputStream输出流的数据可以从对应的PipedInputStream输入流读取。Java的管道流极大地方便了我们截取控制台输出。Listing 1显示了一种非常简单的截取控制台输出方案。</p><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#cccccc" border="1"><tbody><tr><td><pre><code>【Listing 1：用管道流截取控制台输出】
PipedInputStream pipedIS = new PipedInputStream();
PipedOutputStream pipedOS = new PipedOutputStream();
try {
   pipedOS.connect(pipedIS);
}
catch(IOException e) {
   System.err.println("连接失败");
   System.exit(1);
}
PrintStream ps = new PrintStream(pipedOS);
System.setOut(ps);
System.setErr(ps);
</code></pre></td></tr></tbody></table><br /><br /><p>可以看到，这里的代码极其简单。我们只是建立了一个PipedInputStream，把它设置为所有写入控制台流的数据的最终目的地。所有写入到控制台流的数据都被转到PipedOutputStream，这样，从相应的PipedInputStream读取就可以迅速地截获所有写入控制台流的数据。接下来的事情似乎只剩下在Swing JTextArea中显示从pipedIS流读取的数据，得到一个能够在文本框中显示控制台输出的程序。遗憾的是，在使用Java管道流时有一些重要的注意事项。只有认真对待所有这些注意事项才能保证Listing 1的代码稳定地运行。下面我们来看第一个注意事项。</p><p><a id="1-1" name="1-1"><b>1.1 注意事项一</b></a><br />PipedInputStream运用的是一个1024字节固定大小的循环缓冲区。写入PipedOutputStream的数据实际上保存到对应的PipedInputStream的内部缓冲区。从PipedInputStream执行读操作时，读取的数据实际上来自这个内部缓冲区。如果对应的PipedInputStream输入缓冲区已满，任何企图写入PipedOutputStream的线程都将被阻塞。而且这个写操作线程将一直阻塞，直至出现读取PipedInputStream的操作从缓冲区删除数据。</p><p>这意味着，向PipedOutputStream写数据的线程不应该是负责从对应PipedInputStream读取数据的唯一线程。从图二可以清楚地看出这里的问题所在：假设线程t是负责从PipedInputStream读取数据的唯一线程；另外，假定t企图在一次对PipedOutputStream的write()方法的调用中向对应的PipedOutputStream写入2000字节的数据。在t线程阻塞之前，它最多能够写入1024字节的数据（PipedInputStream内部缓冲区的大小）。然而，一旦t被阻塞，读取PipedInputStream的操作就再也不会出现，因为t是唯一读取PipedInputStream的线程。这样，t线程已经完全被阻塞，同时，所有其他试图向PipedOutputStream写入数据的线程也将遇到同样的情形。</p><center><br />图二：管道流工作过程</center><br /><br /><p>这并不意味着在一次write()调用中不能写入多于1024字节的数据。但应当保证，在写入数据的同时，有另一个线程从PipedInputStream读取数据。</p><p>Listing 2示范了这个问题。这个程序用一个线程交替地读取PipedInputStream和写入PipedOutputStream。每次调用write()向PipedInputStream的缓冲区写入20字节，每次调用read()只从缓冲区读取并删除10个字节。内部缓冲区最终会被写满，导致写操作阻塞。由于我们用同一个线程执行读、写操作，一旦写操作被阻塞，就不能再从PipedInputStream读取数据。</p><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#cccccc" border="1"><tbody><tr><td><pre><code>【Listing 2：用同一个线程执行读/写操作导致线程阻塞】

import java.io.*;
public class Listing2 {
    static PipedInputStream pipedIS = new PipedInputStream();
    static PipedOutputStream pipedOS = 
        new PipedOutputStream();

    public static void main(String[] a){
        try {
            pipedIS.connect(pipedOS);
        }
        catch(IOException e) {
            System.err.println("连接失败");
                System.exit(1);
            }

        byte[] inArray    = new byte[10];
        byte[] outArray = new byte[20];
        int bytesRead = 0;

        try {
            // 向pipedOS发送20字节数据
            pipedOS.write(outArray, 0, 20);
            System.out.println("     已发送20字节...");

           // 在每一次循环迭代中，读入10字节
           // 发送20字节
            bytesRead = pipedIS.read(inArray, 0, 10);
            int i=0;
            while(bytesRead != -1) {
                pipedOS.write(outArray, 0, 20);
                System.out.println("     已发送20字节..."+i);
                i++;
                bytesRead = pipedIS.read(inArray, 0, 10);
            }
        }
        catch(IOException e) {
                System.err.println("读取pipedIS时出现错误: " + e);
                System.exit(1);
        }
    } // main()
}</code></pre></td></tr></tbody></table><br /><br /><p>只要把读/写操作分开到不同的线程，Listing 2的问题就可以轻松地解决。Listing 3是Listing 2经过修改后的版本，它在一个单独的线程中执行写入PipedOutputStream的操作（和读取线程不同的线程）。为证明一次写入的数据可以超过1024字节，我们让写操作线程每次调用PipedOutputStream的write()方法时写入2000字节。那么，在startWriterThread()方法中创建的线程是否会阻塞呢？按照Java运行时线程调度机制，它当然会阻塞。写操作在阻塞之前实际上最多只能写入1024字节的有效载荷（即PipedInputStream缓冲区的大小）。但这并不会成为问题，因为主线程（main）很快就会从PipedInputStream的循环缓冲区读取数据，空出缓冲区空间。最终，写操作线程会从上一次中止的地方重新开始，写入2000字节有效载荷中的剩余部分。</p><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#cccccc" border="1"><tbody><tr><td><pre><code>【Listing 3：把读/写操作分开到不同的线程】
import java.io.*;

public class Listing3 {
    static PipedInputStream pipedIS =
        new PipedInputStream();
    static PipedOutputStream pipedOS =
        new PipedOutputStream();

    public static void main(String[] args) {
        try {
            pipedIS.connect(pipedOS);
        }
        catch(IOException e) {
            System.err.println("连接失败");
            System.exit(1);
        }

        byte[] inArray = new byte[10];
        int bytesRead = 0;

        // 启动写操作线程
        startWriterThread();

        try {
            bytesRead = pipedIS.read(inArray, 0, 10);
            while(bytesRead != -1) {
                System.out.println("已经读取" +
                    bytesRead + "字节...");
                bytesRead = pipedIS.read(inArray, 0, 10);
            }
        }
        catch(IOException e) {
            System.err.println("读取输入错误.");
            System.exit(1);
        }
    } // main()

    // 创建一个独立的线程
    // 执行写入PipedOutputStream的操作
    private static void startWriterThread() {
        new Thread(new Runnable() {
            public void run() {
                byte[] outArray = new byte[2000];

                while(true) { // 无终止条件的循环
                    try {
                        // 在该线程阻塞之前，有最多1024字节的数据被写入
                        pipedOS.write(outArray, 0, 2000);
                    }
                    catch(IOException e) {
                        System.err.println("写操作错误");
                        System.exit(1);
                    }
                    System.out.println("     已经发送2000字节...");
                }
            }
        }).start();
    } // startWriterThread()
} // Listing3</code></pre></td></tr></tbody></table><br /><br /><p>也许我们不能说这个问题是Java管道流设计上的缺陷，但在应用管道流时，它是一个必须密切注意的问题。下面我们来看看第二个更重要（更危险的）问题。</p><p><a id="1-2" name="1-2"><b>1.2 注意事项二</b></a><br />从PipedInputStream读取数据时，如果符合下面三个条件，就会出现IOException异常：</p><ol class="n01"><li>试图从PipedInputStream读取数据， 
</li><li>PipedInputStream的缓冲区为“空”（即不存在可读取的数据）， 
</li><li>最后一个向PipedOutputStream写数据的线程不再活动（通过Thread.isAlive()检测）。 </li></ol><br /><br /><p>这是一个很微妙的时刻，同时也是一个极其重要的时刻。假定有一个线程w向PipedOutputStream写入数据；另一个线程r从对应的PipedInputStream读取数据。下面一系列的事件将导致r线程在试图读取PipedInputStream时遇到IOException异常：</p><ol class="n01"><li>w向PipedOutputStream写入数据。 
</li><li>w结束（w.isAlive()返回false）。 
</li><li>r从PipedInputStream读取w写入的数据，清空PipedInputStream的缓冲区。 
</li><li>r试图再次从PipedInputStream读取数据。这时PipedInputStream的缓冲区已经为空，而且w已经结束，从而导致在读操作执行时出现IOException异常。 </li></ol><br /><br /><p>构造一个程序示范这个问题并不困难，只需从Listing 3的startWriterThread()方法中，删除while(true)条件。这个改动阻止了执行写操作的方法循环执行，使得执行写操作的方法在一次写入操作之后就结束运行。如前所述，此时主线程试图读取PipedInputStraem时，就会遇到一个IOException异常。</p><p>这是一种比较少见的情况，而且不存在直接修正它的方法。请不要通过从管道流派生子类的方法修正该问题——在这里使用继承是完全不合适的。而且，如果Sun以后改变了管道流的实现方法，现在所作的修改将不再有效。</p><p>最后一个问题和第二个问题很相似，不同之处在于，它在读线程（而不是写线程）结束时产生IOException异常。</p><p><a id="1-3" name="1-3"><b>1.3 注意事项三</b></a><br />如果一个写操作在PipedOutputStream上执行，同时最近从对应PipedInputStream读取的线程已经不再活动（通过Thread.isAlive()检测），则写操作将抛出一个IOException异常。假定有两个线程w和r，w向PipedOutputStream写入数据，而r则从对应的PipedInputStream读取。下面一系列的事件将导致w线程在试图写入PipedOutputStream时遇到IOException异常：</p><ol class="n01"><li>写操作线程w已经创建，但r线程还不存在。 
</li><li>w向PipedOutputStream写入数据。 
</li><li>读线程r被创建，并从PipedInputStream读取数据。 
</li><li>r线程结束。 
</li><li>w企图向PipedOutputStream写入数据，发现r已经结束，抛出IOException异常。 </li></ol><br /><br /><p>实际上，这个问题不象第二个问题那样棘手。和多个读线程/单个写线程的情况相比，也许在应用中有一个读线程（作为响应请求的服务器）和多个写线程（发出请求）的情况更为常见。</p><p><a id="1-4" name="1-4"><b>1.4 解决问题</b></a><br />要防止管道流前两个局限所带来的问题，方法之一是用一个ByteArrayOutputStream作为代理或替代PipedOutputStream。Listing 4显示了一个LoopedStreams类，它用一个ByteArrayOutputStream提供和Java管道流类似的功能，但不会出现死锁和IOException异常。这个类的内部仍旧使用管道流，但隔离了本文介绍的前两个问题。我们先来看看这个类的公用方法（参见图3）。构造函数很简单，它连接管道流，然后调用startByteArrayReaderThread()方法（稍后再讨论该方法）。getOutputStream()方法返回一个OutputStream（具体地说，是一个ByteArrayOutputStream）用以替代PipedOutputStream。写入该OutputStream的数据最终将在getInputStream()方法返回的流中作为输入出现。和使用PipedOutputStream的情形不同，向ByteArrayOutputStream写入数据的线程的激活、写数据、结束不会带来负面效果。</p><center><br />图三：ByteArrayOutputStream原理</center><br /><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#cccccc" border="1"><tbody><tr><td><pre><code>【Listing 4：防止管道流应用中出现的常见问题】
import java.io.*;

public class LoopedStreams {
    private PipedOutputStream pipedOS = 
        new PipedOutputStream();
    private boolean keepRunning = true;
    private ByteArrayOutputStream byteArrayOS =
        new ByteArrayOutputStream() {
        public void close() {
            keepRunning = false;
            try {
                super.close();
                pipedOS.close();
            }
            catch(IOException e) {
                // 记录错误或其他处理
                // 为简单计，此处我们直接结束
                System.exit(1);
            }
        }
    };


    private PipedInputStream pipedIS = new PipedInputStream() {
        public void close() {
            keepRunning = false;
            try    {
                super.close();
            }
            catch(IOException e) {
                // 记录错误或其他处理
                // 为简单计，此处我们直接结束
                System.exit(1);
            }
        }
    };


    public LoopedStreams() throws IOException {
        pipedOS.connect(pipedIS);
        startByteArrayReaderThread();
    } // LoopedStreams()


    public InputStream getInputStream() {
        return pipedIS;
    } // getInputStream()


    public OutputStream getOutputStream() {
        return byteArrayOS;
    } // getOutputStream()


    private void startByteArrayReaderThread() {
        new Thread(new Runnable() {
            public void run() {
                while(keepRunning) {
                    // 检查流里面的字节数
                    if(byteArrayOS.size() &gt; 0) {
                        byte[] buffer = null;
                        synchronized(byteArrayOS) {
                            buffer = byteArrayOS.toByteArray();
                            byteArrayOS.reset(); // 清除缓冲区
                        }
                        try {
                            // 把提取到的数据发送给PipedOutputStream
                            pipedOS.write(buffer, 0, buffer.length);
                        }
                        catch(IOException e) {
                            // 记录错误或其他处理
                            // 为简单计，此处我们直接结束
                            System.exit(1);
                        }
                    }
                    else // 没有数据可用，线程进入睡眠状态
                        try {
                            // 每隔1秒查看ByteArrayOutputStream检查新数据
                            Thread.sleep(1000);
                        }
                        catch(InterruptedException e) {}
                    }
             }
        }).start();
    } // startByteArrayReaderThread()
} // LoopedStreams</code></pre></td></tr></tbody></table><br /><br /><p>startByteArrayReaderThread()方法是整个类真正的关键所在。这个方法的目标很简单，就是创建一个定期地检查ByteArrayOutputStream缓冲区的线程。缓冲区中找到的所有数据都被提取到一个byte数组，然后写入到PipedOutputStream。由于PipedOutputStream对应的PipedInputStream由getInputStream()返回，从该输入流读取数据的线程都将读取到原先发送给ByteArrayOutputStream的数据。前面提到，LoopedStreams类解决了管道流存在的前二个问题，我们来看看这是如何实现的。</p><p>ByteArrayOutputStream具有根据需要扩展其内部缓冲区的能力。由于存在“完全缓冲”，线程向getOutputStream()返回的流写入数据时不会被阻塞。因而，第一个问题不会再给我们带来麻烦。另外还要顺便说一句，ByteArrayOutputStream的缓冲区永远不会缩减。例如，假设在能够提取数据之前，有一块500 K的数据被写入到流，缓冲区将永远保持至少500 K的容量。如果这个类有一个方法能够在数据被提取之后修正缓冲区的大小，它就会更完善。</p><p>第二个问题得以解决的原因在于，实际上任何时候只有一个线程向PipedOutputStream写入数据，这个线程就是由startByteArrayReaderThread()创建的线程。由于这个线程完全由LoopedStreams类控制，我们不必担心它会产生IOException异常。</p><p>LoopedStreams类还有一些细节值得提及。首先，我们可以看到byteArrayOS和pipedIS实际上分别是ByteArrayOutputStream和PipedInputStream的派生类的实例，也即在它们的close()方法中加入了特殊的行为。如果一个LoopedStreams对象的用户关闭了输入或输出流，在startByteArrayReaderThread()中创建的线程必须关闭。覆盖后的close()方法把keepRunning标记设置成false以关闭线程。另外，请注意startByteArrayReaderThread()中的同步块。要确保在toByteArray()调用和reset()调用之间ByteArrayOutputStream缓冲区不被写入流的线程修改，这是必不可少的。由于ByteArrayOutputStream的write()方法的所有版本都在该流上同步，我们保证了ByteArrayOutputStream的内部缓冲区不被意外地修改。</p><p>注意LoopedStreams类并不涉及管道流的第三个问题。该类的getInputStream()方法返回PipedInputStream。如果一个线程从该流读取，一段时间后终止，下次数据从ByteArrayOutputStream缓冲区传输到PipedOutputStream时就会出现IOException异常。</p><p><a id="1" name="1"><span class="atitle2">二、捕获Java控制台输出</span></a><br />Listing 5的ConsoleTextArea类扩展Swing JTextArea捕获控制台输出。不要对这个类有这么多代码感到惊讶，必须指出的是，ConsoleTextArea类有超过50%的代码用来进行测试。</p><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#cccccc" border="1"><tbody><tr><td><pre><code>【Listing 5：截获Java控制台输出】
import java.io.*;
import java.util.*;
import javax.swing.*;
import javax.swing.text.*;

public class ConsoleTextArea extends JTextArea {
    public ConsoleTextArea(InputStream[] inStreams) {
        for(int i = 0; i &lt; inStreams.length; ++i)
            startConsoleReaderThread(inStreams[i]);
    } // ConsoleTextArea()

    public ConsoleTextArea() throws IOException {
        final LoopedStreams ls = new LoopedStreams();

        // 重定向System.out和System.err
        PrintStream ps = new PrintStream(ls.getOutputStream());
        System.setOut(ps);
        System.setErr(ps);

        startConsoleReaderThread(ls.getInputStream());
    } // ConsoleTextArea()


    private void startConsoleReaderThread(
        InputStream inStream) {
        final BufferedReader br =
            new BufferedReader(new InputStreamReader(inStream));
        new Thread(new Runnable() {
            public void run() {
                StringBuffer sb = new StringBuffer();
                try {
                    String s;
                    Document doc = getDocument();
                    while((s = br.readLine()) != null) {
                        boolean caretAtEnd = false;
                        caretAtEnd = getCaretPosition() == doc.getLength() ?
                            true : false;
                        sb.setLength(0);
                        append(sb.append(s).append('\n').toString());
                        if(caretAtEnd)
                            setCaretPosition(doc.getLength());
                    }
                }
                catch(IOException e) {
                    JOptionPane.showMessageDialog(null,
                        "从BufferedReader读取错误：" + e);
                    System.exit(1);
                }
            }
        }).start();
    } // startConsoleReaderThread()


    // 该类剩余部分的功能是进行测试
    public static void main(String[] args) {
        JFrame f = new JFrame("ConsoleTextArea测试");
        ConsoleTextArea consoleTextArea = null;

        try {
            consoleTextArea = new ConsoleTextArea();
        }
        catch(IOException e) {
            System.err.println(
                "不能创建LoopedStreams：" + e);
            System.exit(1);
        }

        consoleTextArea.setFont(java.awt.Font.decode("monospaced"));
        f.getContentPane().add(new JScrollPane(consoleTextArea),
            java.awt.BorderLayout.CENTER);
        f.setBounds(50, 50, 300, 300);
        f.setVisible(true);

        f.addWindowListener(new java.awt.event.WindowAdapter() {
            public void windowClosing(
                java.awt.event.WindowEvent evt) {
                System.exit(0);
            }
        });

        // 启动几个写操作线程向
        // System.out和System.err输出
        startWriterTestThread(
            "写操作线程 #1", System.err, 920, 50);
        startWriterTestThread(
            "写操作线程 #2", System.out, 500, 50);
        startWriterTestThread(
            "写操作线程 #3", System.out, 200, 50);
        startWriterTestThread(
            "写操作线程 #4", System.out, 1000, 50);
        startWriterTestThread(
            "写操作线程 #5", System.err, 850,    50);
    } // main()


    private static void startWriterTestThread(
        final String name, final PrintStream ps, 
        final int delay, final int count) {
        new Thread(new Runnable() {
            public void run() {
                for(int i = 1; i &lt;= count; ++i) {
                    ps.println("***" + name + ", hello !, i=" + i);
                    try {
                        Thread.sleep(delay);
                    }
                    catch(InterruptedException e) {}
                }
            }
        }).start();
    } // startWriterTestThread()
} // ConsoleTextArea</code></pre></td></tr></tbody></table><br /><br /><p>main()方法创建了一个JFrame，JFrame包含一个ConsoleTextArea的实例。这些代码并没有什么特别之处。Frame显示出来之后，main()方法启动一系列的写操作线程，写操作线程向控制台流输出大量信息。ConsoleTextArea捕获并显示这些信息，如图一所示。</p><p>ConsoleTextArea提供了两个构造函数。没有参数的构造函数用来捕获和显示所有写入到控制台流的数据，有一个InputStream[]参数的构造函数转发所有从各个数组元素读取的数据到JTextArea。稍后将有一个例子显示这个构造函数的用处。首先我们来看看没有参数的ConsoleTextArea构造函数。这个函数首先创建一个LoopedStreams对象；然后请求Java运行时环境把控制台输出转发到LoopedStreams提供的OutputStream；最后，构造函数调用startConsoleReaderThread()，创建一个不断地把文本行追加到JTextArea的线程。注意，把文本追加到JTextArea之后，程序小心地保证了插入点的正确位置。</p><p>一般来说，Swing部件的更新不应该在AWT事件分派线程（AWT Event Dispatch Thread，AEDT）之外进行。对于本例来说，这意味着所有把文本追加到JTextArea的操作应该在AEDT中进行，而不是在startConsoleReaderThread()方法创建的线程中进行。然而，事实上在Swing中向JTextArea追加文本是一个线程安全的操作。读取一行文本之后，我们只需调用JText.append()就可以把文本追加到JTextArea的末尾。</p><p><a id="1" name="1"><span class="atitle2">三、捕获其他程序的控制台输出</span></a><br />在JTextArea中捕获Java程序自己的控制台输出是一回事，去捕获其他程序（甚至包括一些非Java程序）的控制台数据又是另一回事。ConsoleTextArea提供了捕获其他应用的输出时需要的基础功能，Listing 6的AppOutputCapture利用ConsoleTextArea，截取其他应用的输出信息然后显示在ConsoleTextArea中。</p><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#cccccc" border="1"><tbody><tr><td><pre><code>【Listing 6：截获其他程序的控制台输出】
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import javax.swing.*;

public class AppOutputCapture {
        private static Process process;

        public static void main(String[] args) {
                if(args.length == 0) {
             System.err.println("用法：java AppOutputCapture " +
                 "&lt;程序名字&gt; {参数1 参数2 ...}");
             System.exit(0);
                }

                try {
                        // 启动命令行指定程序的新进程
                        process = Runtime.getRuntime().exec(args);
                }
                catch(IOException e) {
                        System.err.println("创建进程时出错...\n" + e);
                        System.exit(1);
                }

                // 获得新进程所写入的流
                InputStream[] inStreams =
                        new InputStream[] {
                    process.getInputStream(),process.getErrorStream()};
                ConsoleTextArea cta = new
    ConsoleTextArea(inStreams);
                cta.setFont(java.awt.Font.decode("monospaced"));

                JFrame frame = new JFrame(args[0] +
                        "控制台输出");

                frame.getContentPane().add(new JScrollPane(cta),
                    BorderLayout.CENTER);
                frame.setBounds(50, 50, 400, 400);
                frame.setVisible(true);

                frame.addWindowListener(new WindowAdapter() {
                        public void windowClosing(WindowEvent evt) {
                                process.destroy();
                                try {
                                        process.waitFor(); // 在Win98下可能被挂起
                                }
                                catch(InterruptedException e) {}
                                        System.exit(0);
                                }
                        });
        } // main()
} // AppOutputCapture
</code></pre></td></tr></tbody></table><br /><br /><p>AppOutputCapture的工作过程如下：首先利用Runtime.exec()方法启动指定程序的一个新进程。启动新进程之后，从结果Process对象得到它的控制台流。之后，把这些控制台流传入ConsoleTextArea(InputStream[])构造函数（这就是带参数ConsoleTextArea构造函数的用处）。使用AppOutputCapture时，在命令行上指定待截取其输出的程序名字。例如，如果在Windows 2000下执行javaw.exe AppOutputCapture ping.exe www.yahoo.com，则结果如图四所示。</p><center><br />图四：截取其他程序的控制台输出</center><br /><br /><p>使用AppOutputCapture时应该注意，被截取输出的应用程序最初输出的一些文本可能无法截取。因为在调用Runtime.exec()和ConsoleTextArea初始化完成之间存在一小段时间差。在这个时间差内，应用程序输出的文本会丢失。当AppOutputCapture窗口被关闭，process.destory()调用试图关闭Java程序开始时创建的进程。测试结果显示出，destroy()方法不一定总是有效（至少在Windows 98上是这样的）。似乎当待关闭的进程启动了额外的进程时，则那些进程不会被关闭。此外，在这种情况下AppOutputCapture程序看起来未能正常结束。但在Windows NT下，一切正常。如果用JDK v1.1.x运行AppOutputCapture，关闭窗口时会出现一个NullPointerException。这是一个JDK的Bug，JDK 1.2.x和JDK 1.3.x下就不会出现问题。</p></td></tr></tbody></table></td>
						</tr>
				</tbody>
		</table>
<img src ="http://www.blogjava.net/ytfei/aggbug/47293.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ytfei/" target="_blank">飞翔</a> 2006-05-21 15:52 <a href="http://www.blogjava.net/ytfei/archive/2006/05/21/47293.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>想作个静态网页生成器</title><link>http://www.blogjava.net/ytfei/archive/2006/05/20/47215.html</link><dc:creator>飞翔</dc:creator><author>飞翔</author><pubDate>Sat, 20 May 2006 12:48:00 GMT</pubDate><guid>http://www.blogjava.net/ytfei/archive/2006/05/20/47215.html</guid><wfw:comment>http://www.blogjava.net/ytfei/comments/47215.html</wfw:comment><comments>http://www.blogjava.net/ytfei/archive/2006/05/20/47215.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.blogjava.net/ytfei/comments/commentRss/47215.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ytfei/services/trackbacks/47215.html</trackback:ping><description><![CDATA[网上总查不到可以用的，没有人愿意公开源代码要么就是局限于项目相关的。没有一种比较通用的解决方案<br />        我想实现这样的功能，<br />1，与开发代码分离。开发的时候不用考虑静态的网页生成，完全可以按照普通的动态网站开发方法开发<br />2，可以局部调整生成优先权。<br />3，可以调整生成速度。 即调整生成器的数量，和时间间隔。<br />4，本静态网页生成系统实现时因充分考虑今后的调整，随着自己对系统及技术了解的深入，可以方便更改以前的实现。即要求模块分离，一定要符合设计模式！<br />目前的局限性：<br />1，当然不能实时，因为，是采用第三方的工具，因此只局限在实时性要求不高的系统，但根据以上2，3点及通过添加硬件设施，尽量满足生成的实时性。<img src ="http://www.blogjava.net/ytfei/aggbug/47215.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ytfei/" target="_blank">飞翔</a> 2006-05-20 20:48 <a href="http://www.blogjava.net/ytfei/archive/2006/05/20/47215.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>调试JSP解决TOMCAT缓存问题</title><link>http://www.blogjava.net/ytfei/archive/2006/05/12/45799.html</link><dc:creator>飞翔</dc:creator><author>飞翔</author><pubDate>Fri, 12 May 2006 03:10:00 GMT</pubDate><guid>http://www.blogjava.net/ytfei/archive/2006/05/12/45799.html</guid><wfw:comment>http://www.blogjava.net/ytfei/comments/45799.html</wfw:comment><comments>http://www.blogjava.net/ytfei/archive/2006/05/12/45799.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.blogjava.net/ytfei/comments/commentRss/45799.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ytfei/services/trackbacks/45799.html</trackback:ping><description><![CDATA[
		<div id="body_main_L1_top2">
				<strong class="title">调试JSP解决TOMCAT缓存问题</strong>
				<br />
				<span class="zhushi">
						<strong>来源:</strong> <a class="zhushi" href="http://www.gamvan.com/" target="_blank">今晚在线</a>   <strong>作者:</strong><a class="zhushi" href="mailto:Noteasy@gamvan.com">我容易么我</a>   <strong>类别:</strong> 原创   <strong>时间:</strong> 2005-4-18   <strong>浏览:</strong><script language="javascript" src="/gvScript/ArticleViewCount.asp?aID=325"></script><div align="right">【<a class="zhushi" href="javascript:doZoom(16)">大</a>    <a class="zhushi" href="javascript:doZoom(14)">中</a>    <a class="zhushi" href="javascript:doZoom(12)">小</a>】   <a class="zhushi" href="http://www.gamvan.com/doPost.asp?aID=325">依我之见</a>    <a class="zhushi" href="http://www.gamvan.com/SendFriends.asp?aID=325">推荐给朋友</a>    <a class="zhushi" onclick="window.print();" href="http://www.gamvan.com/web/jsp/2005/4/325.html#">打印文档</a></div></span>
		</div>
		<div id="content">
				<font id="zoom">jsp里的bean，每次生成 class后 刷新jsp叶面，显示的还是老的bean的信息!非要重起tomcat经过多方打探总结几点经验分享给大家。<br /><br />1。让JSP不缓存方法网页头部加上<br /><table style="BORDER-TOP-WIDTH: 1px; BORDER-LEFT-WIDTH: 1px; BORDER-BOTTOM-WIDTH: 1px; BORDER-RIGHT-WIDTH: 1px" bordercolor="#e0e0e0" cellspacing="1" cellpadding="4" width="95%" align="center" border="1" fixed="" table-layout:=""><tbody><tr><td style="HEIGHT: 25px; WORD-WRAP: break-word" valign="top" bgcolor="#f6f6f6"><font style="COLOR: #b0b0b0">代码内容</font><br />&lt;%  <br />response.setHeader("Pragma","No-cache");//HTTP 1.1 <br />response.setHeader("Cache-Control","no-cache");//HTTP 1.0 <br />response.setHeader("Expires","0");//防止被proxy <br />%&gt;</td></tr></tbody></table><p><br />2。删除work目录下的缓存文件。</p><p>3。conf/server.xml 文件Context path 中间加上 reloadable="true" </p><p>例如：&lt;Context path="" docBase="E:\MYJSP\" debug="0" <font color="#f70968">reloadable="true"</font> /&gt;</p><p>这样基本可以解决缓存遗留问题。</p></font>
		</div>
<img src ="http://www.blogjava.net/ytfei/aggbug/45799.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ytfei/" target="_blank">飞翔</a> 2006-05-12 11:10 <a href="http://www.blogjava.net/ytfei/archive/2006/05/12/45799.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>JSTL 入门: 表达式语言</title><link>http://www.blogjava.net/ytfei/archive/2006/05/10/45550.html</link><dc:creator>飞翔</dc:creator><author>飞翔</author><pubDate>Wed, 10 May 2006 14:33:00 GMT</pubDate><guid>http://www.blogjava.net/ytfei/archive/2006/05/10/45550.html</guid><wfw:comment>http://www.blogjava.net/ytfei/comments/45550.html</wfw:comment><comments>http://www.blogjava.net/ytfei/archive/2006/05/10/45550.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ytfei/comments/commentRss/45550.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ytfei/services/trackbacks/45550.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: JSTL 入门: 表达式语言				通过避免使用脚本编制元素来简化对 JSP 应用程序的软件维护				级别: 初级																		Mark A. Kolb						, 软件工程师				2003 年 5 月 27 日				JSP 标准标记库（JSP Standard Tag Library，JSTL）是一个实现 Web 应用程序中常见的通用...&nbsp;&nbsp;<a href='http://www.blogjava.net/ytfei/archive/2006/05/10/45550.html'>阅读全文</a><img src ="http://www.blogjava.net/ytfei/aggbug/45550.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ytfei/" target="_blank">飞翔</a> 2006-05-10 22:33 <a href="http://www.blogjava.net/ytfei/archive/2006/05/10/45550.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>改造jpetstore4.0 (struts+ibatis) ===&gt; (struts+spring+ibatis) 的轻量级J2EE开发</title><link>http://www.blogjava.net/ytfei/archive/2006/05/02/44329.html</link><dc:creator>飞翔</dc:creator><author>飞翔</author><pubDate>Mon, 01 May 2006 18:53:00 GMT</pubDate><guid>http://www.blogjava.net/ytfei/archive/2006/05/02/44329.html</guid><wfw:comment>http://www.blogjava.net/ytfei/comments/44329.html</wfw:comment><comments>http://www.blogjava.net/ytfei/archive/2006/05/02/44329.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.blogjava.net/ytfei/comments/commentRss/44329.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ytfei/services/trackbacks/44329.html</trackback:ping><description><![CDATA[
		<p>级别: 初级</p>
		<p>
				<a href="http://www-128.ibm.com/developerworks/cn/java/j-s-s-i/index.html#author">
						<font color="#996699">吴高峰</font>
				</a>常德卷烟厂信息技术部<br /></p>
		<p>2005 年 2 月 01 日</p>
		<blockquote>大多数IT 组织都必须解决三个主要问题：1．帮助组织减少成本 2．增加并且保持客户 3．加快业务效率。完成这些问题一般都需要实现对多个业务系统的数据和业务逻辑的无缝访问，也就是说，要实施系统集成工程，以便联结业务流程、实现数据的访问与共享。</blockquote>
		<!--START RESERVED FOR FUTURE USE INCLUDE FILES-->
		<!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters -->
		<!--END RESERVED FOR FUTURE USE INCLUDE FILES-->
		<p>JpetStore 4.0是ibatis的最新示例程序，基于Struts MVC框架（注：非传统Struts开发模式），以ibatis作为持久化层。该示例程序设计优雅，层次清晰，可以学习以及作为一个高效率的编程模型参考。本文是在其基础上，采用Spring对其中间层（业务层）进行改造。使开发量进一步减少，同时又拥有了Spring的一些好处…</p>
		<p>
				<a name="N1003A">
						<span class="atitle">
								<font face="Arial" size="4">1. 前言</font>
						</span>
				</a>
		</p>
		<p>JpetStore 4.0是ibatis的最新示例程序。ibatis是开源的持久层产品，包含SQL Maps 2.0 和 Data Access Objects 2.0 框架。JpetStore示例程序很好的展示了如何利用ibatis来开发一个典型的J2EE web应用程序。JpetStore有如下特点：</p>
		<ul>
				<li>ibatis数据层 
</li>
				<li>POJO业务层 
</li>
				<li>POJO领域类 
</li>
				<li>Struts MVC 
</li>
				<li>JSP 表示层 </li>
		</ul>
		<p>以下是本文用到的关键技术介绍，本文假设您已经对Struts，SpringFramewok，ibatis有一定的了解，如果不是，请首先查阅附录中的参考资料。</p>
		<ul>
				<li>Struts 是目前Java Web MVC框架中不争的王者。经过长达五年的发展，Struts已经逐渐成长为一个稳定、成熟的框架，并且占有了MVC框架中最大的市场份额。但是Struts某些技术特性上已经落后于新兴的MVC框架。面对Spring MVC、Webwork2 这些设计更精密，扩展性更强的框架，Struts受到了前所未有的挑战。但站在产品开发的角度而言，Struts仍然是最稳妥的选择。本文的原型例子JpetStore 4.0就是基于Struts开发的，但是不拘泥于Struts的传统固定用法，例如只用了一个自定义Action类，并且在form bean类的定义上也是开创性的，令人耳目一新，稍后将具体剖析一下。 
</li>
				<li>Spring Framework 实际上是Expert One-on-One J2EE Design and Development 一书中所阐述的设计思想的具体实现。Spring Framework的功能非常多。包含AOP、ORM、DAO、Context、Web、MVC等几个部分组成。Web、MVC暂不用考虑，JpetStore 4.0用的是更成熟的Struts和JSP；DAO由于目前Hibernate、JDO、ibatis的流行，也不考虑，JpetStore 4.0用的就是ibatis。因此最需要用的是AOP、ORM、Context。Context中，最重要的是Beanfactory，它能将接口与实现分开，非常强大。目前AOP应用最成熟的还是在事务管理上。 
</li>
				<li>ibatis 是一个功能强大实用的SQL Map工具，不同于其他ORM工具（如hibernate），它是将SQL语句映射成Java对象，而对于ORM工具，它的SQL语句是根据映射定义生成的。ibatis 以SQL开发的工作量和数据库移植性上的让步，为系统设计提供了更大的自由空间。有ibatis代码生成的工具，可以根据DDL自动生成ibatis代码，能减少很多工作量。 </li>
		</ul>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-s-s-i/index.html#main">
																				<b>
																						<font color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="N10064">
						<span class="atitle">
								<font face="Arial" size="4">2. JpetStore简述</font>
						</span>
				</a>
		</p>
		<p>
				<a name="N1006A">
						<span class="smalltitle">
								<strong>
										<font face="Arial">2.1. 背景</font>
								</strong>
						</span>
				</a>
		</p>
		<p>最初是Sun公司的J2EE petstore，其最主要目的是用于学习J2EE，但是其缺点也很明显，就是过度设计了。接着Oracle用J2EE petstore来比较各应用服务器的性能。微软推出了基于.Net平台的 Pet shop，用于竞争J2EE petstore。而JpetStore则是经过改良的基于struts的轻便框架J2EE web应用程序，相比来说，JpetStore设计和架构更优良，各层定义清晰，使用了很多最佳实践和模式，避免了很多"反模式"，如使用存储过程，在java代码中嵌入SQL语句，把HTML存储在数据库中等等。最新版本是JpetStore 4.0。</p>
		<p>
				<a name="N10073">
						<span class="smalltitle">
								<strong>
										<font face="Arial">2.2. JpetStore开发运行环境的建立</font>
								</strong>
						</span>
				</a>
		</p>
		<p>1、开发环境</p>
		<ul>
				<li>Java SDK 1.4.2 
</li>
				<li>Apache Tomcat 4.1.31 
</li>
				<li>Eclipse-SDK-3.0.1-win32 
</li>
				<li>HSQLDB 1.7.2 </li>
		</ul>
		<p>2、Eclipse插件</p>
		<ul>
				<li>EMF SDK 2.0.1：Eclipse建模框架，lomboz插件需要，可以使用runtime版本。 
</li>
				<li>lomboz 3.0：J2EE插件，用来在Eclipse中开发J2EE应用程序 
</li>
				<li>Spring IDE 1.0.3：Spring Bean配置管理插件 
</li>
				<li>xmlbuddy_2.0.10：编辑XML，用免费版功能即可 
</li>
				<li>tomcatPluginV3：tomcat管理插件 
</li>
				<li>Properties Editor：编辑java的属性文件,并可以预览以及自动存盘为Unicode格式。免去了手工或者ANT调用native2ascii的麻烦。 </li>
		</ul>
		<p>3、示例源程序</p>
		<ul>
				<li>ibatis示例程序JpetStore 4.0 http://www.ibatis.com/jpetstore/jpetstore.html 
</li>
				<li>改造后的源程序（+spring）（源码链接） </li>
		</ul>
		<p>
				<a name="N100AF">
						<span class="smalltitle">
								<strong>
										<font face="Arial">2.3. 架构</font>
								</strong>
						</span>
				</a>
		</p>
		<br />
		<a name="N100B7">
				<strong>图1 JpetStore架构图</strong>
		</a>
		<br />
		<img height="279" alt="图1 JpetStore架构图" src="http://www-128.ibm.com/developerworks/cn/java/j-s-s-i/images/image002.jpg" width="553" border="0" />
		<br />
		<p>图1 是JPetStore架构图，更详细的内容请参见JPetStore的白皮书。参照这个架构图，让我们稍微剖析一下源代码，得出JpetStore 4.0的具体实现图（见图2），思路一下子就豁然开朗了。前言中提到的非传统的struts开发模式，关键就在struts Action类和form bean类上。</p>
		<p>struts Action类只有一个：BeanAction。没错，确实是一个！与传统的struts编程方式很不同。再仔细研究BeanAction类，发现它其实是一个通用类，利用反射原理，根据URL来决定调用formbean的哪个方法。BeanAction大大简化了struts的编程模式，降低了对struts的依赖（与struts以及WEB容器有关的几个类都放在com.ibatis.struts包下，其它的类都可以直接复用）。利用这种模式，我们会很容易的把它移植到新的框架如JSF，spring。</p>
		<p>这样重心就转移到form bean上了，它已经不是普通意义上的form bean了。查看源代码，可以看到它不仅仅有数据和校验/重置方法，而且已经具有了行为，从这个意义上来说，它更像一个BO(Business Object)。这就是前文讲到的，BeanAction类利用反射原理，根据URL来决定调用form bean的哪个方法（行为）。form bean的这些方法的签名很简单，例如：</p>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console"> public String myActionMethod() {
   //..work
   return "success";
 }
 </font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>方法的返回值直接就是字符串，对应的是forward的名称，而不再是ActionForward对象，创建ActionForward对象的任务已经由BeanAction类代劳了。</p>
		<p>另外，程序还提供了ActionContext工具类，该工具类封装了request 、response、form parameters、request attributes、session attributes和 application attributes中的数据存取操作，简单而线程安全，form bean类使用该工具类可以进一步从表现层框架解耦。</p>
		<p>在这里需要特别指出的是，BeanAction类是对struts扩展的一个有益尝试，虽然提供了非常好的应用开发模式，但是它还非常新，一直在发展中。</p>
		<br />
		<a name="N100E4">
				<b>图2 JpetStore 4.0具体实现</b>
		</a>
		<br />
		<img height="588" alt="图2 JpetStore 4.0具体实现" src="http://www-128.ibm.com/developerworks/cn/java/j-s-s-i/images/image004.gif" width="582" border="0" />
		<br />
		<p>
				<a name="N100F4">
						<span class="smalltitle">
								<strong>
										<font face="Arial">2.4. 代码剖析</font>
								</strong>
						</span>
				</a>
		</p>
		<p>下面就让我们开始进一步分析JpetStore4.0的源代码，为下面的改造铺路。</p>
		<ul>
				<li>BeanAction.java是唯一一个Struts action类，位于com.ibatis.struts包下。正如上文所言，它是一个通用的控制类，利用反射机制，把控制转移到form bean的某个方法来处理。详细处理过程参考其源代码，简单明晰。 
</li>
				<li>
						<p>Form bean类位于com.ibatis.jpetstore.presentation包下，命名规则为***Bean。Form bean类全部继承于BaseBean类，而BaseBean类实际继承于ActionForm，因此，Form bean类就是Struts的 ActionForm，Form bean类的属性数据就由struts框架自动填充。而实际上，JpetStore4.0扩展了struts中ActionForm的应用： Form bean类还具有行为，更像一个BO,其行为（方法）由BeanAction根据配置（struts-config.xml）的URL来调用。虽然如此，我们还是把Form bean类定位于表现层。</p>
						<p>Struts-config.xml的配置里有3种映射方式，来告诉BeanAction把控制转到哪个form bean对象的哪个方法来处理。</p>
						<p>以这个请求连接为例http://localhost/jpetstore4/shop/viewOrder.do</p>
						<p>1. URL Pattern</p>
						<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
								<tbody>
										<tr>
												<td>
														<pre>
																<code class="section">
																		<font face="Lucida Console">    &lt;action path="/shop/viewOrder" type="com.ibatis.struts.BeanAction"
    name="orderBean" scope="session"
    validate="false"&gt;
    &lt;forward name="success" path="/order/ViewOrder.jsp"/&gt;
  &lt;/action&gt;
  </font>
																</code>
														</pre>
												</td>
										</tr>
								</tbody>
						</table>
						<br />
						<p>此种方式表示，控制将被转发到"orderBean"这个form bean对象 的"viewOrder"方法（行为）来处理。方法名取"path"参数的以"/"分隔的最后一部分。</p>
						<p>2. Method Parameter</p>
						<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
								<tbody>
										<tr>
												<td>
														<pre>
																<code class="section">
																		<font face="Lucida Console">    &lt;action path="/shop/viewOrder" type="com.ibatis.struts.BeanAction"
    name="orderBean" parameter="viewOrder" scope="session"
    validate="false"&gt;
    &lt;forward name="success" path="/order/ViewOrder.jsp"/&gt;
  &lt;/action&gt;
  </font>
																</code>
														</pre>
												</td>
										</tr>
								</tbody>
						</table>
						<br />
						<p>此种方式表示，控制将被转发到"orderBean"这个form bean对象的"viewOrder"方法（行为）来处理。配置中的"parameter"参数表示form bean类上的方法。"parameter"参数优先于"path"参数。</p>
						<p>3. No Method call</p>
						<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
								<tbody>
										<tr>
												<td>
														<pre>
																<code class="section">
																		<font face="Lucida Console">    &lt;action path="/shop/viewOrder" type="com.ibatis.struts.BeanAction"
    name="orderBean" parameter="*" scope="session"
    validate="false"&gt;
    &lt;forward name="success" path="/order/ViewOrder.jsp"/&gt;
  &lt;/action&gt;
  </font>
																</code>
														</pre>
												</td>
										</tr>
								</tbody>
						</table>
						<br />
						<p>此种方式表示，form bean上没有任何方法被调用。如果存在"name"属性，则struts把表单参数等数据填充到form bean对象后，把控制转发到"success"。否则，如果name为空，则直接转发控制到"success"。</p>
						<p>这就相当于struts内置的org.apache.struts.actions.ForwardAction的功能</p>
						<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
								<tbody>
										<tr>
												<td>
														<pre>
																<code class="section">
																		<font face="Lucida Console"> &lt;action path="/shop/viewOrder" type="org.apache.struts.actions.ForwardAction"
    parameter="/order/ViewOrder.jsp " scope="session" validate="false"&gt;
 &lt;/action&gt;
 </font>
																</code>
														</pre>
												</td>
										</tr>
								</tbody>
						</table>
						<br />
				</li>
				<li>Service类位于com.ibatis.jpetstore.service包下，属于业务层。这些类封装了业务以及相应的事务控制。Service类由form bean类来调用。 
</li>
				<li>com.ibatis.jpetstore.persistence.iface包下的类是DAO接口，属于业务层，其屏蔽了底层的数据库操作，供具体的Service类来调用。DaoConfig类是工具类（DAO工厂类），Service类通过DaoConfig类来获得相应的DAO接口，而不用关心底层的具体数据库操作，实现了如图2中{耦合2}的解耦。 
</li>
				<li>com.ibatis.jpetstore.persistence.sqlmapdao包下的类是对应DAO接口的具体实现，在JpetStore4.0中采用了ibatis来实现ORM。这些实现类继承BaseSqlMapDao类，而BaseSqlMapDao类则继承ibatis DAO 框架中的SqlMapDaoTemplate类。ibatis的配置文件存放在com.ibatis.jpetstore.persistence.sqlmapdao.sql目录下。这些类和配置文件位于数据层 
</li>
				<li>Domain类位于com.ibatis.jpetstore.domain包下，是普通的javabean。在这里用作数据传输对象（DTO），贯穿视图层、业务层和数据层，用于在不同层之间传输数据。 </li>
		</ul>
		<p>剩下的部分就比较简单了，请看具体的源代码，非常清晰。</p>
		<p>
				<a name="N10141">
						<span class="smalltitle">
								<strong>
										<font face="Arial">2.5. 需要改造的地方</font>
								</strong>
						</span>
				</a>
		</p>
		<p>JpetStore4.0的关键就在struts Action类和form bean类上，这也是其精华之一（虽然该实现方式是试验性，待扩充和验证），在此次改造中我们要保留下来，即控制层一点不变，表现层获取相应业务类的方式变了（要加载spring环境），其它保持不变。要特别关注的改动是业务层和持久层，幸运的是JpetStore4.0设计非常好，需要改动的地方非常少，而且由模式可循，如下：</p>
		<p>1. 业务层和数据层用Spring BeanFactory机制管理。</p>
		<p>2. 业务层的事务由spring 的aop通过声明来完成。</p>
		<p>3. 表现层（form bean）获取业务类的方法改由自定义工厂类来实现（加载spring环境）。</p>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-s-s-i/index.html#main">
																				<b>
																						<font color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="N10153">
						<span class="atitle">
								<font face="Arial" size="4">3. JPetStore的改造</font>
						</span>
				</a>
		</p>
		<p>
				<a name="N10159">
						<span class="smalltitle">
								<strong>
										<font face="Arial">3.1. 改造后的架构</font>
								</strong>
						</span>
				</a>
		</p>
		<br />
		<strong>
				<font face="Arial">
						<img height="640" alt="" src="http://www-128.ibm.com/developerworks/cn/java/j-s-s-i/images/image005.gif" width="582" border="0" />
				</font>
		</strong>
		<br />
		<p>其中红色部分是要增加的部分，蓝色部分是要修改的部分。下面就让我们逐一剖析。</p>
		<p>
				<a name="N10173">
						<span class="smalltitle">
								<strong>
										<font face="Arial">3.2. Spring Context的加载</font>
								</strong>
						</span>
				</a>
		</p>
		<p>为了在Struts中加载Spring Context，一般会在struts-config.xml的最后添加如下部分：</p>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">&lt;plug-in className="org.springframework.web.struts.ContextLoaderPlugIn"&gt;
&lt;set-property property="contextConfigLocation"
value="/WEB-INF/applicationContext.xml" /&gt;
&lt;/plug-in&gt;
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>Spring在设计时就充分考虑到了与Struts的协同工作，通过内置的Struts Plug-in在两者之间提供了良好的结合点。但是，因为在这里我们一点也不改动JPetStore的控制层(这是JpetStore4.0的精华之一)，所以本文不准备采用此方式来加载ApplicationContext。我们利用的是spring framework 的BeanFactory机制,采用自定义的工具类（bean工厂类）来加载spring的配置文件，从中可以看出Spring有多灵活，它提供了各种不同的方式来使用其不同的部分/层次，您只需要用你想用的，不需要的部分可以不用。</p>
		<p>具体的来说，就是在com.ibatis.spring包下创建CustomBeanFactory类，spring的配置文件applicationContext.xml也放在这个目录下。以下就是该类的全部代码，很简单：</p>
		<br />
		<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1">
				<tbody>
						<tr>
								<td>
										<pre>
												<code class="section">
														<font face="Lucida Console">public final class CustomBeanFactory {
	static XmlBeanFactory factory = null;
	static {
		Resource is = new
InputStreamResource( CustomBeanFactory.class.getResourceAsStream("applicationContext.xml"));
		factory = new XmlBeanFactory(is);			
	}
	public static Object getBean(String beanName){
		return factory.getBean(beanName);
	}
}
</font>
												</code>
										</pre>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<p>实际上就是封装了Spring 的XMLBeanFactory而已，并且Spring的配置文件只需要加载一次，以后就可以直接用CustomBeanFactory.getBean("someBean")来获得需要的对象了(例如someBean)，而不需要知道具体的类。CustomBeanFactory类用于{耦合1}的解耦。</p>
		<p>CustomBeanFactory类在本文中只用于表现层的form bean对象获得service类的对象，因为我们没有把form bean对象配置在applicationContext.xml中。但是，为什么不把表现层的form bean类也配置起来呢，这样就用不着这CustomBeanFactory个类了，Spring会帮助我们创建需要的一切？问题的答案就在于form bean类是struts的ActionForm类！如果大家熟悉struts，就会知道ActionForm类是struts自动创建的：在一次请求中，struts判断，如果ActionForm实例不存在，就创建一个ActionForm对象，把客户提交的表单数据保存到ActionForm对象中。因此formbean类的对象就不能由spring来创建，但是service类以及数据层的DAO类可以，所以只有他们在spring中配置。</p>
		<p>所以，很自然的，我们就创建了CustomBeanFactory类，在表现层来衔接struts和spring。就这么简单，实现了另一种方式的{耦合一}的解耦。</p>
		<p>
				<a name="N1019D">
						<span class="smalltitle">
								<strong>
										<font face="Arial">3.3. 表现层</font>
								</strong>
						</span>
				</a>
		</p>上 
<p>面分析到，struts和spring是在表现层衔接起来的，那么表现层就要做稍微的更改，即所需要的service类的对象创建上。以表现层的AccountBean类为例：</p><p>原来的源代码如下</p><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">    private static final AccountService accountService = AccountService.getInstance();
  private static final CatalogService catalogService = CatalogService.getInstance();
  </font></code></pre></td></tr></tbody></table><br /><p>改造后的源代码如下</p><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">  private static final AccountService accountService = (AccountService)CustomBeanFactory.getBean("AccountService");
  private static final CatalogService catalogService = (CatalogService)CustomBeanFactory.getBean("CatalogService");
</font></code></pre></td></tr></tbody></table><br /><p>其他的几个presentation类以同样方式改造。这样，表现层就完成了。关于表现层的其它部分如JSP等一概不动。也许您会说，没有看出什么特别之处的好处啊？你还是额外实现了一个工厂类。别着急，帷幕刚刚开启，spring是在表现层引入，但您发没发现：</p><ul><li>presentation类仅仅面向service类的接口编程，具体"AccountService"是哪个实现类，presentation类不知道，是在spring的配置文件里配置。（本例中，为了最大限度的保持原来的代码不作变化，没有抽象出接口）。Spring鼓励面向接口编程，因为是如此的方便和自然，当然您也可以不这么做。 
</li><li>CustomBeanFactory这个工厂类为什么会如此简单，因为其直接使用了Spring的BeanFactory。Spring从其核心而言，是一个DI容器，其设计哲学是提供一种无侵入式的高扩展性的框架。为了实现这个目标，Spring 大量引入了Java 的Reflection机制，通过动态调用的方式避免硬编码方式的约束，并在此基础上建立了其核心组件BeanFactory，以此作为其依赖注入机制的实现基础。org.springframework.beans包中包括了这些核心组件的实现类，核心中的核心为BeanWrapper和BeanFactory类。 </li></ul><p><a name="N101CA"><span class="smalltitle"><strong><font face="Arial">3.4. 持久层</font></strong></span></a></p><p>在讨论业务层之前，我们先看一下持久层，如下图所示：</p><br /><img height="416" alt="" src="http://www-128.ibm.com/developerworks/cn/java/j-s-s-i/images/image007.jpg" width="320" border="0" /><br /><p>在上文中，我们把iface包下的DAO接口归为业务层，在这里不需要做修改。ibatis的sql配置文件也不需要改。要改的是DAO实现类，并在spring的配置文件中配置起来。</p><p>1、修改基类</p><p>所有的DAO实现类都继承于BaseSqlMapDao类。修改BaseSqlMapDao类如下：</p><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">public class BaseSqlMapDao extends SqlMapClientDaoSupport {
  protected static final int PAGE_SIZE = 4;
  protected SqlMapClientTemplate smcTemplate = this.getSqlMapClientTemplate();
  public BaseSqlMapDao() { 
	}
}
</font></code></pre></td></tr></tbody></table><br /><p>使BaseSqlMapDao类改为继承于Spring提供的SqlMapClientDaoSupport类，并定义了一个保护属性smcTemplate，其类型为SqlMapClientTemplate。关于SqlMapClientTemplate类的详细说明请参照附录中的"Spring中文参考手册"</p><p>2、修改DAO实现类</p><p>所有的DAO实现类还是继承于BaseSqlMapDao类，实现相应的DAO接口，但其相应的DAO操作委托SqlMapClientTemplate来执行，以AccountSqlMapDao类为例，部分代码如下：</p><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">    public List getUsernameList() {
    return smcTemplate.queryForList("getUsernameList", null);
  }
  public Account getAccount(String username, String password) {
    Account account = new Account();
    account.setUsername(username);
    account.setPassword(password);
    return (Account) smcTemplate.queryForObject("getAccountByUsernameAndPassword", account);
  }
  public void insertAccount(Account account) {
  	smcTemplate.update("insertAccount", account);
  	smcTemplate.update("insertProfile", account);
  	smcTemplate.update("insertSignon", account);
  }
  </font></code></pre></td></tr></tbody></table><br /><p>就这么简单，所有函数的签名都是一样的，只需要查找替换就可以了！</p><p>3、除去工厂类以及相应的配置文件</p><p>除去DaoConfig.java这个DAO工厂类和相应的配置文件dao.xml，因为DAO的获取现在要用spring来管理。</p><p>4、DAO在Spring中的配置（applicationContext.xml）</p><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">    &lt;bean id="dataSource" 
        class="org.springframework.jdbc.datasource.DriverManagerDataSource"&gt;
        &lt;property name="driverClassName"&gt;
            &lt;value&gt;org.hsqldb.jdbcDriver&lt;/value&gt;
        &lt;/property&gt;
        &lt;property name="url"&gt;
            &lt;value&gt;jdbc:hsqldb:hsql://localhost/xdb&lt;/value&gt;
        &lt;/property&gt;
        &lt;property name="username"&gt;
            &lt;value&gt;sa&lt;/value&gt;
        &lt;/property&gt;
        &lt;property name="password"&gt;
            &lt;value&gt;&lt;/value&gt;
        &lt;/property&gt;
    &lt;/bean&gt;    
    &lt;!-- ibatis sqlMapClient config --&gt;
    &lt;bean id="sqlMapClient" 
        class="org.springframework.orm.ibatis.SqlMapClientFactoryBean"&gt;
        &lt;property name="configLocation"&gt;
            &lt;value&gt; 
                classpath:com\ibatis\jpetstore\persistence\sqlmapdao\sql\sql-map-config.xml
            &lt;/value&gt;
        &lt;/property&gt;
        &lt;property name="dataSource"&gt;
            &lt;ref bean="dataSource"/&gt;
        &lt;/property&gt;    
    &lt;/bean&gt;
    &lt;!-- Transactions --&gt;
    &lt;bean id="TransactionManager" 
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager"&gt;
        &lt;property name="dataSource"&gt;
            &lt;ref bean="dataSource"/&gt;
        &lt;/property&gt;
    &lt;/bean&gt;
    &lt;!-- persistence layer --&gt;
    &lt;bean id="AccountDao" 
        class="com.ibatis.jpetstore.persistence.sqlmapdao.AccountSqlMapDao"&gt;
        &lt;property name="sqlMapClient"&gt;
            &lt;ref local="sqlMapClient"/&gt;
        &lt;/property&gt;
    &lt;/bean&gt;
    </font></code></pre></td></tr></tbody></table><br /><p>具体的语法请参照附录中的"Spring中文参考手册"。在这里只简单解释一下：</p><p>1. 我们首先创建一个数据源dataSource，在这里配置的是hsqldb数据库。如果是ORACLE数据库，driverClassName的值是"oracle.jdbc.driver.OracleDriver"，URL的值类似于"jdbc:oracle:thin:@wugfMobile:1521:cdcf"。数据源现在由spring来管理，那么现在我们就可以去掉properties目录下database.properties这个配置文件了；还有不要忘记修改sql-map-config.xml，去掉&lt;properties resource="properties/database.properties"/&gt;对它的引用。</p><p>2. sqlMapClient节点。这个是针对ibatis SqlMap的SqlMapClientFactoryBean配置。实际上配置了一个sqlMapClient的创建工厂类。configLocation属性配置了ibatis映射文件的名称。dataSource属性指向了使用的数据源，这样所有使用sqlMapClient的DAO都默认使用了该数据源，除非在DAO的配置中另外显式指定。</p><p>3. TransactionManager节点。定义了事务，使用的是DataSourceTransactionManager。</p><p>4. 下面就可以定义DAO节点了，如AccountDao，它的实现类是com.ibatis.jpetstore.persistence.sqlmapdao.AccountSqlMapDao，使用的SQL配置从sqlMapClient中读取，数据库连接没有特别列出，那么就是默认使用sqlMapClient配置的数据源datasource。</p><p>这样，我们就把持久层改造完了，其他的DAO配置类似于AccountDao。怎么样？简单吧。这次有接口了：） AccountDao接口－&gt;AccountSqlMapDao实现。</p><p><a name="N1022F"><span class="smalltitle"><strong><font face="Arial">3.5. 业务层</font></strong></span></a></p><p>业务层的位置以及相关类，如下图所示：</p><br /><img height="240" alt="" src="http://www-128.ibm.com/developerworks/cn/java/j-s-s-i/images/image009.jpg" width="259" border="0" /><br /><p>在这个例子中只有3个业务类，我们以OrderService类为例来改造，这个类是最复杂的，其中涉及了事务。</p><p>1、在ApplicationContext配置文件中增加bean的配置：</p><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">    &lt;bean id="OrderService" 
        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"&gt;
        &lt;property name="transactionManager"&gt;
            &lt;ref local="TransactionManager"&gt;&lt;/ref&gt;
        &lt;/property&gt;
        &lt;property name="target"&gt;
            &lt;bean class="com.ibatis.jpetstore.service.OrderService"&gt;
                &lt;property name="itemDao"&gt;
                    &lt;ref bean="ItemDao"/&gt;
                &lt;/property&gt;
                &lt;property name="orderDao"&gt;
                    &lt;ref bean="OrderDao"/&gt;
                &lt;/property&gt;
                &lt;property name="sequenceDao"&gt;
                    &lt;ref bean="SequenceDao"/&gt;
                &lt;/property&gt;
            &lt;/bean&gt;
        &lt;/property&gt;
        &lt;property name="transactionAttributes"&gt;
            &lt;props&gt;
                &lt;prop key="insert*"&gt;PROPAGATION_REQUIRED&lt;/prop&gt;
            &lt;/props&gt;
        &lt;/property&gt;
    &lt;/bean&gt;
    </font></code></pre></td></tr></tbody></table><br /><p>定义了一个OrderService，还是很容易懂的。为了简单起见，使用了嵌套bean，其实现类是com.ibatis.jpetstore.service.OrderService，分别引用了ItemDao，OrderDao，SequenceDao。该bean的insert*实现了事务管理(AOP方式)。TransactionProxyFactoryBean自动创建一个事务advisor， 该advisor包括一个基于事务属性的pointcut,因此只有事务性的方法被拦截。</p><p>2、业务类的修改</p><p>以OrderService为例：</p><br /><table cellspacing="0" cellpadding="5" width="100%" bgcolor="#eeeeee" border="1"><tbody><tr><td><pre><code class="section"><font face="Lucida Console">public class OrderService {

   /* Private Fields */
  private ItemDao itemDao;
  private OrderDao orderDao;
  private SequenceDao sequenceDao;

  /* Constructors */

  public OrderService() {
  }

/**
 * @param itemDao 要设置的 itemDao。
 */
public final void setItemDao(ItemDao itemDao) {
	this.itemDao = itemDao;
}
/**
 * @param orderDao 要设置的 orderDao。
 */
public final void setOrderDao(OrderDao orderDao) {
	this.orderDao = orderDao;
}
/**
 * @param sequenceDao 要设置的 sequenceDao。
 */
public final void setSequenceDao(SequenceDao sequenceDao) {
	this.sequenceDao = sequenceDao;
}
//剩下的部分
…….
}
</font></code></pre></td></tr></tbody></table><br /><p>红色部分为修改部分。Spring采用的是Type2的设置依赖注入，所以我们只需要定义属性和相应的设值函数就可以了，ItemDao，OrderDao，SequenceDao的值由spring在运行期间注入。构造函数就可以为空了，另外也不需要自己编写代码处理事务了（事务在配置中声明），daoManager.startTransaction();等与事务相关的语句也可以去掉了。和原来的代码比较一下，是不是处理精简了很多！可以更关注业务的实现。</p><br /><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr><td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br /><img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></td></tr></tbody></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tbody><tr align="right"><td><img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /><br /><table cellspacing="0" cellpadding="0" border="0"><tbody><tr><td valign="center"><img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" /><br /></td><td valign="top" align="right"><a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-s-s-i/index.html#main"><b><font color="#996699">回页首</font></b></a></td></tr></tbody></table></td></tr></tbody></table><br /><br /><p><a name="N1026D"><span class="atitle"><font face="Arial" size="4">4. 结束语</font></span></a></p><p>ibatis是一个功能强大实用的SQL Map工具，可以直接控制SQL,为系统设计提供了更大的自由空间。其提供的最新示例程序JpetStore 4.0,设计优雅，应用了迄今为止很多最佳实践和设计模式，非常适于学习以及在此基础上创建轻量级的J2EE WEB应用程序。JpetStore 4.0是基于struts的，本文在此基础上，最大程度保持了原有设计的精华以及最小的代码改动量，在业务层和持久化层引入了Spring。在您阅读了本文以及改造后的源代码后，会深切的感受到Spring带来的种种好处：自然的面向接口的编程，业务对象的依赖注入，一致的数据存取框架和声明式的事务处理，统一的配置文件…更重要的是Spring既是全面的又是模块化的，Spring有分层的体系结构，这意味着您能选择仅仅使用它任何一个独立的部分，就像本文，而它的架构又是内部一致。</p><br /><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr><td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br /><img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></td></tr></tbody></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tbody><tr align="right"><td><img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /><br /><table cellspacing="0" cellpadding="0" border="0"><tbody><tr><td valign="center"><img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" /><br /></td><td valign="top" align="right"><a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-s-s-i/index.html#main"><b><font color="#996699">回页首</font></b></a></td></tr></tbody></table></td></tr></tbody></table><br /><br /><p><a name="resources"><span class="atitle"><font face="Arial" size="4">参考资料 </font></span></a></p><ul><li>jpetstore相关各种资料和源程序 <a href="http://www.ibatis.com/jpetstore/jpetstore.html"><font color="#5c81a7">http://www.ibatis.com/jpetstore/jpetstore.html</font></a><br /><br /></li><li>Spring中文参考手册<a href="http://www.jactiongroup.net/reference/html/index.html"><font color="#5c81a7">http://www.jactiongroup.net/reference/html/index.html</font></a><br /><br /></li><li>Spring 开发指南 夏昕<br /><br /></li><li>Struts <a href="http://struts.apache.org/"><font color="#5c81a7">http://struts.apache.org/</font></a><br /></li></ul><br /><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr><td><font color="#5c81a7"><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br /><img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></font></td></tr></tbody></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tbody><tr align="right"><td><font color="#5c81a7"><img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /><br /></font><table cellspacing="0" cellpadding="0" border="0"><tbody><tr><td valign="center"><font color="#5c81a7"><img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" /><br /></font></td><td valign="top" align="right"><a class="fbox" href="http://www-128.ibm.com/developerworks/cn/java/j-s-s-i/index.html#main"><b><font color="#996699">回页首</font></b></a></td></tr></tbody></table></td></tr></tbody></table><br /><br /><p><a name="author"><span class="atitle"><font face="Arial" size="4">关于作者</font></span></a></p><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr><td colspan="3"><font face="Arial" size="4"><img height="5" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /></font></td></tr><tr valign="top" align="left"><td><p><font face="Arial" size="4"></font></p></td><td><font face="Arial" size="4"><img height="5" alt="" src="http://www.ibm.com/i/c.gif" width="4" /></font></td><td width="100%"><p>吴高峰，原一直在中兴通讯从事运营支撑产品的研发工作，对J2EE以及各种开源项目感兴趣。现在常德卷烟厂信息技术部从事EAI的建设。联系方式：<a href="mailto:shuwgf@21cn.com"><font color="#5c81a7">shuwgf@21cn.com</font></a></p></td></tr></tbody></table><img src ="http://www.blogjava.net/ytfei/aggbug/44329.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ytfei/" target="_blank">飞翔</a> 2006-05-02 02:53 <a href="http://www.blogjava.net/ytfei/archive/2006/05/02/44329.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>动态编译JAVA程序</title><link>http://www.blogjava.net/ytfei/archive/2005/09/06/12194.html</link><dc:creator>飞翔</dc:creator><author>飞翔</author><pubDate>Tue, 06 Sep 2005 03:53:00 GMT</pubDate><guid>http://www.blogjava.net/ytfei/archive/2005/09/06/12194.html</guid><wfw:comment>http://www.blogjava.net/ytfei/comments/12194.html</wfw:comment><comments>http://www.blogjava.net/ytfei/archive/2005/09/06/12194.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ytfei/comments/commentRss/12194.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ytfei/services/trackbacks/12194.html</trackback:ping><description><![CDATA[<P><STRONG>[转贴者注]对于很多应用系统，常常需要动态装载和执行类和代码片断，这有利于部署的简易性和系统设计上的灵活性。本文给出了一个比较全面的介绍，值得参考。<BR><BR></STRONG>&nbsp;&nbsp;&nbsp;&nbsp;在Sun&nbsp;JDK&nbsp;1.2及后续版本中，包含了一组可在程序运行时刻编译和执行Java代码的API。这些API被包含在tools.jar类库中。这个功能允许Java程序在运行时动态编译、执行小的代码块，在有些情况下这个功能会让Java应用程序的架构更加灵活、开放。&nbsp;<BR><BR>　　本文假定读者已经在计算机中安装并配置好了Sun&nbsp;JDK&nbsp;1.2或更高的版本，并对javac编译器命令有所了解。&nbsp;<BR><BR>　　<B>在Java程序中使用编译器&nbsp;</B><BR>　　假定要使用javac命令编译&nbsp;/home/mytest目录下Test.java文件，并设定class文件存放在/home/mytest/classes路径下，输入下面命令：&nbsp;<BR><BR>　　javac&nbsp;-d&nbsp;/home/mytest/classes&nbsp;Test.java&nbsp;<BR><BR>　　达到同样的目的，也可以使用Sun提供的一个Java编译器的API来实现。它的使用也很简单，核心代码段如下：&nbsp;<BR><BR></P>
<DIV class=codeStyle>
<OL>
<LI>　　…&nbsp; 
<LI>
<LI>　　<B><A href="http://www.javaresearch.org/source/jdk142/java/lang/String.java.html" target=_blank><FONT class=classLink><U>String</U></FONT></A></B>[]&nbsp;args&nbsp;=&nbsp;<B><FONT color=#0000ff>new</FONT></B>&nbsp;<B><A href="http://www.javaresearch.org/source/jdk142/java/lang/String.java.html" target=_blank><FONT class=classLink><U>String</U></FONT></A></B>[]&nbsp;{“-d”，&nbsp;“/homemytestclasses”，&nbsp;“Test.java”};&nbsp; 
<LI>
<LI>　　Int&nbsp;status&nbsp;=&nbsp;javac.compile(args);&nbsp; 
<LI>
<LI>　　…&nbsp; </LI></OL></DIV>
<P><BR><BR>　　javac编译工具被安装在JDK根目录的/bin目录下，负责将源代码编译成运行于JVM的字节码。事实上，我们经常使用/bin目录下的javac编译工具来编译Java源文件。如果在Java程序中动态编译任意制定的Java语句，使用这个外部的javac编译器就显得不够灵活了。虽然有时可使用Runtime类来执行一个外部命令，但如果想知道代码是否被编译通过、编译时发生了什么错误，用Runtime类的exec()方法就很难实现了。&nbsp;<BR><BR>　　在Sun的JDK&nbsp;1.2及后续版本中，JDK安装路径的/lib路径下包含了一个tools.jar文件，这个类库包含了一个完整的编译器包。com.sun.tools.javac.Main是编译器的主类入口，如果已经熟悉了javac编译器命令行的使用方法，很容易理解这个类的使用方法。方法compile(String[]&nbsp;p)执行编译动作，参数p是一个String数组，用来存放javac命令的参数选项，编译后的状态返回一个Int值，其对应值参考如下表所示：&nbsp;<BR><BR>　　表&nbsp;状态参数与对应值&nbsp;<BR><BR>　　EXIT_OK&nbsp;0&nbsp;<BR><BR>　　EXIT_ERROR&nbsp;1&nbsp;<BR><BR>　　EXIT_CMDERR&nbsp;2&nbsp;<BR><BR>　　EXIT_SYSERR&nbsp;3&nbsp;<BR><BR>　　EXIT_ABNORMAL&nbsp;4&nbsp;<BR><BR>　　&nbsp;<BR><BR>　　<B>在程序执行时编译和执行Java语句&nbsp;</B><BR><BR>　　从上面一段中，我们已经基本了解了动态编译一个Java文件的方法。那么，如何运行时动态编译指定的Java语句呢？这里需要一个技巧。&nbsp;<BR><BR>　　假设要动态编译的Java条语句如下：&nbsp;<BR><BR></P>
<DIV class=codeStyle>
<OL>
<LI>　　<B><A href="http://www.javaresearch.org/source/jdk142/java/lang/System.java.html" target=_blank><FONT class=classLink><U>System</U></FONT></A></B>.out.println(“Hello，This&nbsp;runtime&nbsp;code！”);&nbsp; </LI></OL></DIV>
<P><BR><BR>　　编译器不支持编译单个Java语句，被编译的对象必须是一个以.java为后缀的、结构合法的类源程序文件，所以需要对这个语句进行改造，变成一个完整的类，并把这条语句置入main方法中，便于测试。&nbsp;<BR><BR></P>
<DIV class=codeStyle>
<OL>
<LI>　　<B><FONT color=#0000ff>public</FONT></B>&nbsp;<B><FONT color=#0000ff>class</FONT></B>&nbsp;&lt;临时类文件名&gt;&nbsp;{&nbsp; 
<LI>
<LI>　　<B><FONT color=#0000ff>public</FONT></B>&nbsp;<B><FONT color=#0000ff>static</FONT></B>&nbsp;<B><FONT color=#0000ff>void</FONT></B>&nbsp;main(<B><A href="http://www.javaresearch.org/source/jdk142/java/lang/String.java.html" target=_blank><FONT class=classLink><U>String</U></FONT></A></B>[]&nbsp;args)&nbsp;<B><FONT color=#0000ff>throws</FONT></B>&nbsp;<B><A href="http://www.javaresearch.org/source/jdk142/java/lang/Exception.java.html" target=_blank><FONT class=classLink><U>Exception</U></FONT></A></B>&nbsp;{&nbsp; 
<LI>
<LI>　　<B><A href="http://www.javaresearch.org/source/jdk142/java/lang/System.java.html" target=_blank><FONT class=classLink><U>System</U></FONT></A></B>.out.println(“Hello，This&nbsp;runtime&nbsp;code！”);&nbsp; 
<LI>
<LI>　　}&nbsp; 
<LI>
<LI>　　}&nbsp; </LI></OL></DIV>
<P><BR><BR>　　这样，欲动态编译的代码已经被程序动态拼装成了上面那段代码，准备工作还没有结束，不过看起来工作在趋向稍微的复杂化。因为上述代码当前还存放在内存中，编译器似乎对一个硬盘文件更感兴趣。我们需要引用java.io.File类（JDK&nbsp;1.2以上），创建一个临时的文件来存放上述代码的内容。java.io.File类的静态方法createTempFile()方法保证所创建的文件名是不重复的，这样会增大这段程序的灵活性。灵活性取决于真正应用到系统架构中的策略。&nbsp;<BR><BR>　　System.getProperty(“user.dir”)用来获得当前路径，在这里作为临时文件的存放目录。&nbsp;<BR><BR></P>
<DIV class=codeStyle>
<OL>
<LI>　　<FONT color=#ff0000>File</FONT>&nbsp;file;&nbsp; 
<LI>
<LI>　　file&nbsp;=&nbsp;<FONT color=#ff0000>File</FONT>.createTempFile(“JavaRuntime”，&nbsp;“.java”，&nbsp;<B><FONT color=#0000ff>new</FONT></B>&nbsp;<FONT color=#ff0000>File</FONT>(<B><A href="http://www.javaresearch.org/source/jdk142/java/lang/System.java.html" target=_blank><FONT class=classLink><U>System</U></FONT></A></B>.getProperty(“user.dir”)));&nbsp; 
<LI>
<LI>　　<B><A href="http://www.javaresearch.org/source/jdk142/java/lang/String.java.html" target=_blank><FONT class=classLink><U>String</U></FONT></A></B>&nbsp;filename&nbsp;=&nbsp;file.getName();&nbsp; 
<LI>
<LI>　　<B><A href="http://www.javaresearch.org/source/jdk142/java/lang/String.java.html" target=_blank><FONT class=classLink><U>String</U></FONT></A></B>&nbsp;classname&nbsp;=&nbsp;getClassName(filename);&nbsp; 
<LI>
<LI>　　<I><FONT color=#339900>//将代码输出到文件&nbsp;</FONT></I> 
<LI>
<LI>　　<FONT color=#ff0000>PrintWriter</FONT>&nbsp;out&nbsp;=&nbsp;<B><FONT color=#0000ff>new</FONT></B>&nbsp;<FONT color=#ff0000>PrintWriter</FONT>(<B><FONT color=#0000ff>new</FONT></B>&nbsp;<FONT color=#ff0000>FileOutputStream</FONT>(file));&nbsp; 
<LI>
<LI>　　out.println(“<B><FONT color=#0000ff>public</FONT></B>&nbsp;<B><FONT color=#0000ff>class</FONT></B>”&nbsp;+&nbsp;classname&nbsp;+&nbsp;“&nbsp;{”};&nbsp; 
<LI>
<LI>　　out.println(“..代码..”);&nbsp; 
<LI>
<LI>　　out.println(“}”);&nbsp; 
<LI>
<LI>　　<I><FONT color=#339900>//关闭文件流&nbsp;</FONT></I> 
<LI>
<LI>　　out.flush();&nbsp; 
<LI>
<LI>　　out.close();&nbsp; </LI></OL></DIV>
<P><BR>　　我们约定被创建的临时文件名以“JavaRuntime”为头缀（可任意命名），后缀名以“.java”结尾。一个待编译的Java源文件已被动态生成。下一步要从com.sun.tools.javac包中创建一个Main实例，调用javac.compile()方法编译这个临时文件：&nbsp;<BR><BR></P>
<DIV class=codeStyle>
<OL>
<LI>　　Private&nbsp;<B><FONT color=#0000ff>static</FONT></B>&nbsp;com.sun.tools.javac.Main&nbsp;javac&nbsp;=&nbsp;<B><FONT color=#0000ff>new</FONT></B>&nbsp;com.sun.tools.javac.Main();&nbsp; 
<LI>
<LI>　　<B><A href="http://www.javaresearch.org/source/jdk142/java/lang/String.java.html" target=_blank><FONT class=classLink><U>String</U></FONT></A></B>[]&nbsp;args&nbsp;=&nbsp;<B><FONT color=#0000ff>new</FONT></B>&nbsp;<B><A href="http://www.javaresearch.org/source/jdk142/java/lang/String.java.html" target=_blank><FONT class=classLink><U>String</U></FONT></A></B>[]&nbsp;{“-d”，&nbsp;<B><A href="http://www.javaresearch.org/source/jdk142/java/lang/System.java.html" target=_blank><FONT class=classLink><U>System</U></FONT></A></B>.getProperty(“user.dir”)，filename&nbsp;};&nbsp; 
<LI>
<LI>　　Int&nbsp;status&nbsp;=&nbsp;javac.compile(args);&nbsp; </LI></OL></DIV>
<P><BR><BR>　　假定临时文件通过了编译器文法验证等验证，编译成功（status值等于0，参看前表），在当前程序的运行目录下就会多了一个Java类文件。我们将通过执行这个Java&nbsp;类文件，来模拟执行欲动态编译代码的结果。&nbsp;<BR><BR>　　Java提供在运行时刻加载类的特性，可动态识别和调用类构造方法、类字段和类方法。java.lang.reflect.Method实现了Member接口，可以调用接口的方法来获得方法类的名称、修饰词等。方法getRuturnType()、getParameterTypes()、getExeptionTypess()等返回被表示方法的构造信息。Method另一个重要的特性是可以调用invoke()执行这个方法（详细使用方法可以查看java.lang.reflect包文档）。下面这段代码中创建一个java.lang.reflect.Method类方法，调用getMethod()方法获得被拼装的main方法的映射，这段代码如下：&nbsp;<BR><BR></P>
<DIV class=codeStyle>
<OL>
<LI>　　<B><FONT color=#0000ff>try</FONT></B>&nbsp;{&nbsp; 
<LI>
<LI>　　<I><FONT color=#339900>//&nbsp;访问这个类&nbsp;</FONT></I> 
<LI>
<LI>　　<B><A href="http://www.javaresearch.org/source/jdk142/java/lang/Class.java.html" target=_blank><FONT class=classLink><U>Class</U></FONT></A></B>&nbsp;cls&nbsp;=&nbsp;<B><A href="http://www.javaresearch.org/source/jdk142/java/lang/Class.java.html" target=_blank><FONT class=classLink><U>Class</U></FONT></A></B>.forName(classname);&nbsp; 
<LI>
<LI>　　<I><FONT color=#339900>//调用main方法&nbsp;</FONT></I> 
<LI>
<LI>　　<FONT color=#ff0000>Method</FONT>&nbsp;main&nbsp;=&nbsp;cls.getMethod(“main”，&nbsp;<B><FONT color=#0000ff>new</FONT></B>&nbsp;<B><A href="http://www.javaresearch.org/source/jdk142/java/lang/Class.java.html" target=_blank><FONT class=classLink><U>Class</U></FONT></A></B>[]&nbsp;{&nbsp;<B><A href="http://www.javaresearch.org/source/jdk142/java/lang/String.java.html" target=_blank><FONT class=classLink><U>String</U></FONT></A></B>[].<B><FONT color=#0000ff>class</FONT></B>&nbsp;});&nbsp; 
<LI>
<LI>　　main.invoke(<B><FONT color=#0000ff>null</FONT></B>，&nbsp;<B><FONT color=#0000ff>new</FONT></B>&nbsp;<B><A href="http://www.javaresearch.org/source/jdk142/java/lang/Object.java.html" target=_blank><FONT class=classLink><U>Object</U></FONT></A></B>[]&nbsp;{&nbsp;<B><FONT color=#0000ff>new</FONT></B>&nbsp;<B><A href="http://www.javaresearch.org/source/jdk142/java/lang/String.java.html" target=_blank><FONT class=classLink><U>String</U></FONT></A></B>[0]&nbsp;});&nbsp; 
<LI>
<LI>　　}<B><FONT color=#0000ff>catch</FONT></B>&nbsp;(<B><A href="http://www.javaresearch.org/source/jdk142/java/lang/SecurityException.java.html" target=_blank><FONT class=classLink><U>SecurityException</U></FONT></A></B>&nbsp;se)&nbsp;{&nbsp; 
<LI>
<LI>　　debug(“access&nbsp;to&nbsp;the&nbsp;information&nbsp;is&nbsp;denied:”&nbsp;+&nbsp;se.toString());&nbsp; 
<LI>
<LI>　　}<B><FONT color=#0000ff>catch</FONT></B>&nbsp;(<B><A href="http://www.javaresearch.org/source/jdk142/java/lang/NoSuchMethodException.java.html" target=_blank><FONT class=classLink><U>NoSuchMethodException</U></FONT></A></B>&nbsp;nme)&nbsp;{&nbsp; 
<LI>
<LI>　　debug(“a&nbsp;matching&nbsp;method&nbsp;is&nbsp;not&nbsp;found&nbsp;or&nbsp;<B><FONT color=#0000ff>if</FONT></B>&nbsp;then&nbsp;name&nbsp;is&nbsp;or&nbsp;:&nbsp; 
<LI>
<LI>　　”&nbsp;+&nbsp;nme.toString());&nbsp; 
<LI>
<LI>　　}<B><FONT color=#0000ff>catch</FONT></B>&nbsp;(<B><A href="http://www.javaresearch.org/source/jdk142/java/lang/reflect/InvocationTargetException.java.html" target=_blank><FONT class=classLink><U>InvocationTargetException</U></FONT></A></B>&nbsp;ite)&nbsp;{&nbsp; 
<LI>
<LI>　　debug(“<B><A href="http://www.javaresearch.org/source/jdk142/java/lang/Exception.java.html" target=_blank><FONT class=classLink><U>Exception</U></FONT></A></B>&nbsp;in&nbsp;main:&nbsp;”&nbsp;+&nbsp;ite.getTargetException());&nbsp; 
<LI>
<LI>　　}<B><FONT color=#0000ff>catch</FONT></B>&nbsp;(<B><A href="http://www.javaresearch.org/source/jdk142/java/lang/Exception.java.html" target=_blank><FONT class=classLink><U>Exception</U></FONT></A></B>&nbsp;e){&nbsp; 
<LI>
<LI>　　debug(e.toString());&nbsp; 
<LI>
<LI>　　}&nbsp; </LI></OL></DIV>
<P><BR>　　运行结果参如下：&nbsp;<BR><BR>　　Hello，This&nbsp;runtime&nbsp;code！&nbsp;<BR><BR>　　&nbsp;<BR><BR>　　<B>示范程序&nbsp;</B><BR><BR>　　下面给出了一个简单的Java程序，这个程序说明了如何利用Sun的javac编译器完成动态编译Java语句。运行该程序需要计算机安装JDK&nbsp;1.2以上版本，并在classpath中或运行时指定tools.jar文件位置。&nbsp;<BR><BR>　　程序结构：&nbsp;<BR><BR>　　◆&nbsp;compile()&nbsp;编译Java代码，返回生成的临时文件；&nbsp;<BR><BR>　　◆&nbsp;run()运行编译的class文件；&nbsp;<BR><BR>　　◆&nbsp;debug()输出调试信息；&nbsp;<BR><BR>　　◆&nbsp;getClassName()从一个Java源文件获得类名；&nbsp;<BR><BR>　　◆&nbsp;readLine()从控制台读取用户输入的Java&nbsp;Code。&nbsp;<BR><BR></P>
<DIV class=codeStyle>
<OL>
<LI>　　Import&nbsp;java.io.<B><A href="http://www.javaresearch.org/source/jdk142/java/io/File.java.html" target=_blank><FONT class=classLink><U>File</U></FONT></A></B>;&nbsp; 
<LI>
<LI>　　…&nbsp; 
<LI>
<LI>　　Public&nbsp;<B><FONT color=#0000ff>class</FONT></B>&nbsp;RuntimeCode{&nbsp; 
<LI>
<LI>　　<I><FONT color=#339900>/**编译器*/</FONT></I>&nbsp; 
<LI>
<LI>　　<B><FONT color=#0000ff>private</FONT></B>&nbsp;<B><FONT color=#0000ff>static</FONT></B>&nbsp;com.sun.tools.javac.Main&nbsp;javac&nbsp;=&nbsp;<B><FONT color=#0000ff>new</FONT></B>&nbsp;com.sun.tools.javac.Main();&nbsp; 
<LI>
<LI>　　<I><FONT color=#339900>/**等待用户输入JavaCode，然后编译、执行*/</FONT></I>&nbsp; 
<LI>
<LI>　　<B><FONT color=#0000ff>public</FONT></B>&nbsp;<B><FONT color=#0000ff>static</FONT></B>&nbsp;<B><FONT color=#0000ff>void</FONT></B>&nbsp;main(<B><A href="http://www.javaresearch.org/source/jdk142/java/lang/String.java.html" target=_blank><FONT class=classLink><U>String</U></FONT></A></B>[]&nbsp;args)&nbsp;<B><FONT color=#0000ff>throws</FONT></B>&nbsp;<B><A href="http://www.javaresearch.org/source/jdk142/java/lang/Exception.java.html" target=_blank><FONT class=classLink><U>Exception</U></FONT></A></B>{&nbsp; 
<LI>
<LI>　　…&nbsp; 
<LI>
<LI>　　run(compile(code));&nbsp; 
<LI>
<LI>　　}&nbsp; 
<LI>
<LI>　　<I><FONT color=#339900>/**编译JavaCode，返回临时文件对象*/</FONT></I>&nbsp; 
<LI>
<LI>　　<B><FONT color=#0000ff>private</FONT></B>&nbsp;<B><FONT color=#0000ff>synchronized</FONT></B>&nbsp;<B><FONT color=#0000ff>static</FONT></B>&nbsp;<FONT color=#ff0000>File</FONT>&nbsp;compile(<B><A href="http://www.javaresearch.org/source/jdk142/java/lang/String.java.html" target=_blank><FONT class=classLink><U>String</U></FONT></A></B>&nbsp;code)&nbsp; 
<LI>
<LI>　　<B><FONT color=#0000ff>throws</FONT></B>&nbsp;<FONT color=#ff0000>IOException</FONT>，<B><A href="http://www.javaresearch.org/source/jdk142/java/lang/Exception.java.html" target=_blank><FONT class=classLink><U>Exception</U></FONT></A></B>&nbsp;{&nbsp; 
<LI>
<LI>　　<FONT color=#ff0000>File</FONT>&nbsp;file;&nbsp; 
<LI>
<LI>　　<I><FONT color=#339900>//在用户当前文件目录创建一个临时代码文件&nbsp;</FONT></I> 
<LI>
<LI>　　file&nbsp;=&nbsp;<FONT color=#ff0000>File</FONT>.createTempFile(“JavaRuntime”，&nbsp;“.java”，&nbsp; 
<LI>
<LI>　　<B><FONT color=#0000ff>new</FONT></B>&nbsp;<FONT color=#ff0000>File</FONT>(<B><A href="http://www.javaresearch.org/source/jdk142/java/lang/System.java.html" target=_blank><FONT class=classLink><U>System</U></FONT></A></B>.getProperty(“user.dir”)));&nbsp; 
<LI>
<LI>　　<I><FONT color=#339900>//当虚拟机退出时，删除此临时java源文件&nbsp;</FONT></I> 
<LI>
<LI>　　file.deleteOnExit();&nbsp; 
<LI>
<LI>　　<I><FONT color=#339900>//获得文件名和类名字&nbsp;</FONT></I> 
<LI>
<LI>　　<B><A href="http://www.javaresearch.org/source/jdk142/java/lang/String.java.html" target=_blank><FONT class=classLink><U>String</U></FONT></A></B>&nbsp;filename&nbsp;=&nbsp;file.getName();&nbsp; 
<LI>
<LI>　　<B><A href="http://www.javaresearch.org/source/jdk142/java/lang/String.java.html" target=_blank><FONT class=classLink><U>String</U></FONT></A></B>&nbsp;classname&nbsp;=&nbsp;getClassName(filename);&nbsp; 
<LI>
<LI>　　<I><FONT color=#339900>//将代码输出到文件&nbsp;</FONT></I> 
<LI>
<LI>　　<FONT color=#ff0000>PrintWriter</FONT>&nbsp;out&nbsp;=&nbsp;<B><FONT color=#0000ff>new</FONT></B>&nbsp;<FONT color=#ff0000>PrintWriter</FONT>(<B><FONT color=#0000ff>new</FONT></B>&nbsp;<FONT color=#ff0000>FileOutputStream</FONT>(file));&nbsp; 
<LI>
<LI>　　out.println(“<I><FONT color=#339900>/**”);&nbsp;</FONT></I> 
<LI>
<LI><I><FONT color=#339900>　　…&nbsp;</FONT></I> 
<LI>
<LI><I><FONT color=#339900>　　//关闭文件流&nbsp;</FONT></I> 
<LI>
<LI><I><FONT color=#339900>　　out.flush();&nbsp;</FONT></I> 
<LI>
<LI><I><FONT color=#339900>　　out.close();&nbsp;</FONT></I> 
<LI>
<LI><I><FONT color=#339900>　　//编译代码文件&nbsp;</FONT></I> 
<LI>
<LI><I><FONT color=#339900>　　String[]&nbsp;args&nbsp;=&nbsp;new&nbsp;String[]&nbsp;{“-d”，&nbsp;System.getProperty(“user.dir”)，filename&nbsp;};&nbsp;</FONT></I> 
<LI>
<LI><I><FONT color=#339900>　　//返回编译的状态代码&nbsp;</FONT></I> 
<LI>
<LI><I><FONT color=#339900>　　int&nbsp;status&nbsp;=&nbsp;javac.compile(args);&nbsp;</FONT></I> 
<LI>
<LI><I><FONT color=#339900>　　//处理编译状态&nbsp;</FONT></I> 
<LI>
<LI><I><FONT color=#339900>　　…&nbsp;</FONT></I> 
<LI>
<LI><I><FONT color=#339900>　　}&nbsp;</FONT></I> 
<LI>
<LI><I><FONT color=#339900>　　/**执行刚刚编译的类文件*/</FONT></I>&nbsp; 
<LI>
<LI>　　<B><FONT color=#0000ff>private</FONT></B>&nbsp;<B><FONT color=#0000ff>static</FONT></B>&nbsp;<B><FONT color=#0000ff>synchronized</FONT></B>&nbsp;<B><FONT color=#0000ff>void</FONT></B>&nbsp;run(<FONT color=#ff0000>File</FONT>&nbsp;file)&nbsp; 
<LI>
<LI>　　…&nbsp; 
<LI>
<LI>　　<I><FONT color=#339900>//当虚拟机退出时，删除此临时编译的类文件&nbsp;</FONT></I> 
<LI>
<LI>　　<B><FONT color=#0000ff>new</FONT></B>&nbsp;<FONT color=#ff0000>File</FONT>(file.getParent()，&nbsp;classname&nbsp;+&nbsp;“.<B><FONT color=#0000ff>class</FONT></B>”).deleteOnExit();&nbsp; 
<LI>
<LI>　　<B><FONT color=#0000ff>try</FONT></B>&nbsp;{&nbsp; 
<LI>
<LI>　　<I><FONT color=#339900>//&nbsp;访问这个类&nbsp;</FONT></I> 
<LI>
<LI>　　<B><A href="http://www.javaresearch.org/source/jdk142/java/lang/Class.java.html" target=_blank><FONT class=classLink><U>Class</U></FONT></A></B>&nbsp;cls&nbsp;=&nbsp;<B><A href="http://www.javaresearch.org/source/jdk142/java/lang/Class.java.html" target=_blank><FONT class=classLink><U>Class</U></FONT></A></B>.forName(classname);&nbsp; 
<LI>
<LI>　　<I><FONT color=#339900>//映射main方法&nbsp;</FONT></I> 
<LI>
<LI>　　<FONT color=#ff0000>Method</FONT>&nbsp;main&nbsp;=&nbsp;cls.getMethod(“main”，&nbsp;<B><FONT color=#0000ff>new</FONT></B>&nbsp;<B><A href="http://www.javaresearch.org/source/jdk142/java/lang/Class.java.html" target=_blank><FONT class=classLink><U>Class</U></FONT></A></B>[]&nbsp;{&nbsp;<B><A href="http://www.javaresearch.org/source/jdk142/java/lang/String.java.html" target=_blank><FONT class=classLink><U>String</U></FONT></A></B>[].<B><FONT color=#0000ff>class</FONT></B>&nbsp;});&nbsp; 
<LI>
<LI>　　<I><FONT color=#339900>//执行main方法&nbsp;</FONT></I> 
<LI>
<LI>　　main.invoke(<B><FONT color=#0000ff>null</FONT></B>，&nbsp;<B><FONT color=#0000ff>new</FONT></B>&nbsp;<B><A href="http://www.javaresearch.org/source/jdk142/java/lang/Object.java.html" target=_blank><FONT class=classLink><U>Object</U></FONT></A></B>[]&nbsp;{&nbsp;<B><FONT color=#0000ff>new</FONT></B>&nbsp;<B><A href="http://www.javaresearch.org/source/jdk142/java/lang/String.java.html" target=_blank><FONT class=classLink><U>String</U></FONT></A></B>[0]&nbsp;});&nbsp; 
<LI>
<LI>　　}<B><FONT color=#0000ff>catch</FONT></B>&nbsp;(<B><A href="http://www.javaresearch.org/source/jdk142/java/lang/SecurityException.java.html" target=_blank><FONT class=classLink><U>SecurityException</U></FONT></A></B>&nbsp;se)&nbsp;{&nbsp; 
<LI>
<LI>　　…&nbsp; 
<LI>
<LI>　　}&nbsp; 
<LI>
<LI>　　}&nbsp; 
<LI>
<LI>　　<I><FONT color=#339900>/**打印调试信息*/</FONT></I>&nbsp; 
<LI>
<LI>　　<B><FONT color=#0000ff>private</FONT></B>&nbsp;<B><FONT color=#0000ff>static</FONT></B>&nbsp;<B><FONT color=#0000ff>void</FONT></B>&nbsp;debug(<B><A href="http://www.javaresearch.org/source/jdk142/java/lang/String.java.html" target=_blank><FONT class=classLink><U>String</U></FONT></A></B>&nbsp;msg)&nbsp;{&nbsp; 
<LI>
<LI>　　<B><A href="http://www.javaresearch.org/source/jdk142/java/lang/System.java.html" target=_blank><FONT class=classLink><U>System</U></FONT></A></B>.err.println(msg);&nbsp; 
<LI>
<LI>　　}&nbsp; 
<LI>
<LI>　　<I><FONT color=#339900>/**根据一个java源文件名获得类名*/</FONT></I>&nbsp; 
<LI>
<LI>　　<B><FONT color=#0000ff>private</FONT></B>&nbsp;<B><FONT color=#0000ff>static</FONT></B>&nbsp;<B><A href="http://www.javaresearch.org/source/jdk142/java/lang/String.java.html" target=_blank><FONT class=classLink><U>String</U></FONT></A></B>&nbsp;getClassName(<B><A href="http://www.javaresearch.org/source/jdk142/java/lang/String.java.html" target=_blank><FONT class=classLink><U>String</U></FONT></A></B>&nbsp;filename){&nbsp; 
<LI>
<LI>　　<B><FONT color=#0000ff>return</FONT></B>&nbsp;filename.substring(0，filename.<B><FONT color=#0000ff>length</FONT></B>()-5);&nbsp; 
<LI>
<LI>　　}&nbsp; 
<LI>
<LI>　　<I><FONT color=#339900>/**从控制台获得用户输入的Java代码段*/</FONT></I>&nbsp; 
<LI>
<LI>　　…&nbsp; 
<LI>
<LI>　　}&nbsp; </LI></OL></DIV>
<P><BR>　　编译运行上述代码，在please&nbsp;input&nbsp;java&nbsp;code提示下输入以下代码：&nbsp;<BR><BR>　　for(int&nbsp;i=0;i&lt;10;i++){System.out.println(“this&nbsp;is:”+i);}&nbsp;<BR><BR>　　运行结果如下所示：&nbsp;<BR><BR>　　Please&nbsp;input&nbsp;java&nbsp;code：&nbsp;<BR><BR>　　for(int&nbsp;i=0;i&lt;10;i++){System.out.println(“this&nbsp;is:”+i);}&nbsp;<BR><BR>　　wait....&nbsp;<BR><BR>　　--------------------&nbsp;<BR><BR>　　this&nbsp;is:0&nbsp;<BR><BR>　　this&nbsp;is:1&nbsp;<BR><BR>　　this&nbsp;is:2&nbsp;<BR><BR>　　this&nbsp;is:3&nbsp;<BR><BR>　　this&nbsp;is:4&nbsp;<BR><BR>　　this&nbsp;is:5&nbsp;<BR><BR>　　this&nbsp;is:6&nbsp;<BR><BR>　　this&nbsp;is:7&nbsp;<BR><BR>　　this&nbsp;is:8&nbsp;<BR><BR>　　this&nbsp;is:9&nbsp;<BR><BR>　　&nbsp;<BR><BR>　　总结&nbsp;<BR><BR>　　在大中型企业应用系统平台中，使用代码动态编译技术结合OO编程模型，可在系统不菪机条件下保证系统的可扩展性和伸缩性。如果你是一个Java程序员，稍加调整以上代码，还可以帮助调试小段的Java代码．&nbsp;<BR></P><img src ="http://www.blogjava.net/ytfei/aggbug/12194.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ytfei/" target="_blank">飞翔</a> 2005-09-06 11:53 <a href="http://www.blogjava.net/ytfei/archive/2005/09/06/12194.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Hibernate In Action Note</title><link>http://www.blogjava.net/ytfei/archive/2005/09/05/12119.html</link><dc:creator>飞翔</dc:creator><author>飞翔</author><pubDate>Mon, 05 Sep 2005 14:14:00 GMT</pubDate><guid>http://www.blogjava.net/ytfei/archive/2005/09/05/12119.html</guid><wfw:comment>http://www.blogjava.net/ytfei/comments/12119.html</wfw:comment><comments>http://www.blogjava.net/ytfei/archive/2005/09/05/12119.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ytfei/comments/commentRss/12119.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ytfei/services/trackbacks/12119.html</trackback:ping><description><![CDATA[1.(Hibernate with a connection pool in a non-managed environment)<BR>Using hibernate.properties for C3P0 connection pool settings:<BR>hibernate.connection.driver_class = org.postgresql.Driver<BR>hibernate.connection.url = jdbc:postgresql://localhost/auctiondb<BR>hibernate.connection.username = auctionuser<BR>hibernate.connection.password = secret<BR>hibernate.dialect = net.sf.hibernate.dialect.PostgreSQLDialect<BR>hibernate.c3p0.min_size=5<BR>hibernate.c3p0.max_size=20<BR>hibernate.c3p0.timeout=300<BR>hibernate.c3p0.max_statements=50<BR>hibernate.c3p0.idle_test_period=3000<BR>2.(Hibernate in a managed environment with an application server) <BR>hibernate.connection.datasource = java:/comp/env/jdbc/AuctionDB<BR>hibernate.transaction.factory_class = \<BR>net.sf.hibernate.transaction.JTATransactionFactory<BR>hibernate.transaction.manager_lookup_class = \<BR>net.sf.hibernate.transaction.JBossTransactionManagerLookup<BR>hibernate.dialect = net.sf.hibernate.dialect.PostgreSQLDialect<BR><BR>结合spring 后就不是这样了，可以右spring 控制transaction &amp; datasource &amp;...<img src ="http://www.blogjava.net/ytfei/aggbug/12119.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ytfei/" target="_blank">飞翔</a> 2005-09-05 22:14 <a href="http://www.blogjava.net/ytfei/archive/2005/09/05/12119.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Hibernate In Action</title><link>http://www.blogjava.net/ytfei/archive/2005/09/01/11794.html</link><dc:creator>飞翔</dc:creator><author>飞翔</author><pubDate>Thu, 01 Sep 2005 15:59:00 GMT</pubDate><guid>http://www.blogjava.net/ytfei/archive/2005/09/01/11794.html</guid><wfw:comment>http://www.blogjava.net/ytfei/comments/11794.html</wfw:comment><comments>http://www.blogjava.net/ytfei/archive/2005/09/01/11794.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ytfei/comments/commentRss/11794.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ytfei/services/trackbacks/11794.html</trackback:ping><description><![CDATA[<FONT color=#000000>log4j.logger.net.sf.hibernate.ps.PreparedStatementCache=info<BR><BR>inverse="false" 则本类为主控方，hibernate会自动查找关联的对象（即使不给完全的对象）<BR>inverse = "true" 为被控方 <BR><BR>cascade = "delete"或其他&nbsp;&nbsp; 必须是个完整的对象hibernate才会去删除关联的对象，否则不予删除<BR><BR><FONT color=#0000ff>&lt;set name="members" table="USERGROUP" lazy="true"<BR>&nbsp;&nbsp;&nbsp;inverse="false" cascade="delete" sort="unsorted"&gt;<BR>&nbsp;&nbsp;&nbsp;&lt;key column="groupid"&gt;&lt;/key&gt;<BR>&nbsp;&nbsp;&nbsp;&lt;many-to-many class="Sysuser" column="userid"&gt;<BR>&nbsp;&nbsp;&nbsp;&lt;/many-to-many&gt;<BR>&nbsp;&nbsp;&lt;/set&gt;</FONT><BR>现对inverse 和cascade 属性在不同条件下测试，结果如下：<BR>(&nbsp;&nbsp;&nbsp;全为完整对象，否则cascade delete 不会级联删除,除非inverse= false 而不出错）<BR><BR>inverse&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cascade&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; result<BR>-------------------------------------<BR>true&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;delete&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;delete sysgroup,sysuser<BR>true&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;none&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;delete sysgroup<BR>false&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;delete&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;delete sysgroup,sysuser,user-group<BR>false&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;none&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;delete sysgroup,user-group<BR><FONT color=#0000ff>&lt;set name="permissions" table="group_permissions" lazy="true"<BR>&nbsp;&nbsp;&nbsp;inverse="true" cascade="delete" sort="unsorted"&gt;<BR>&nbsp;&nbsp;&nbsp;&lt;key column="GROUPID"&gt;&lt;/key&gt;<BR>&nbsp;&nbsp;&nbsp;&lt;one-to-many class="GroupPermissions" /&gt;<BR>&nbsp;&nbsp;&lt;/set&gt;<BR></FONT>(完整的对象）<BR>one-to-many : <BR>inverse&nbsp;&nbsp; cascade&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; result<BR>------------------------------------<BR>true&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; delete&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;删除&nbsp;&nbsp;&nbsp; sysgroup,grouppermissions<BR>true&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;none&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;只删除 sysgroup<BR>false&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;两种情况&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;出错，不能更新grouppermissions=null</FONT><img src ="http://www.blogjava.net/ytfei/aggbug/11794.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ytfei/" target="_blank">飞翔</a> 2005-09-01 23:59 <a href="http://www.blogjava.net/ytfei/archive/2005/09/01/11794.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Struts menu的使用（动态数据）</title><link>http://www.blogjava.net/ytfei/archive/2005/08/30/12079.html</link><dc:creator>飞翔</dc:creator><author>飞翔</author><pubDate>Tue, 30 Aug 2005 03:42:00 GMT</pubDate><guid>http://www.blogjava.net/ytfei/archive/2005/08/30/12079.html</guid><wfw:comment>http://www.blogjava.net/ytfei/comments/12079.html</wfw:comment><comments>http://www.blogjava.net/ytfei/archive/2005/08/30/12079.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ytfei/comments/commentRss/12079.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ytfei/services/trackbacks/12079.html</trackback:ping><description><![CDATA[&nbsp;以下是我在实际项目中使用了struts-menu，现将他们写出来已供参考<BR>1.<BR><SPAN>&nbsp;&nbsp; 拷贝必要的</SPAN><SPAN lang=EN-US><FONT face="Times New Roman">jar</FONT></SPAN><SPAN>库文件到</SPAN><SPAN lang=EN-US><FONT face="Times New Roman">dbMenu\WEB-INF\lib</FONT></SPAN> <BR><FONT face="Times New Roman"><FONT size=2><SPAN lang=EN-US>log4j-</SPAN><?xml:namespace prefix = st1 /><st1:chsdate Year="1899" Month="12" Day="30" IsLunarDate="False" IsROCDate="False"><SPAN lang=EN-US>1.2.8</SPAN></st1:chsdate><SPAN lang=EN-US>.jar<BR></SPAN></FONT></FONT><FONT size=2><SPAN lang=EN-US><FONT face="Times New Roman">struts-menu-2.3.jar</FONT></SPAN><SPAN lang=EN-US><FONT face="Times New Roman">&nbsp;<BR>拷贝menu-config.xml&nbsp;,struts-menu.tld 到WEB-INF目录下,<BR>及一些必须的图片和jscript,css到相应的目录<BR>Images/*.*&nbsp;<SPAN lang=EN-US><FONT face="Times New Roman"><FONT size=2><SPAN>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR></SPAN>Scripts/<SPAN>&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>menuExpandable.js xtree.js<BR></FONT></FONT></SPAN><SPAN lang=EN-US><FONT face="Times New Roman"><FONT size=2>Styles/<SPAN>&nbsp;&nbsp;&nbsp;&nbsp; </SPAN>menuExpandable.css<SPAN>&nbsp;&nbsp; </SPAN>global.css<SPAN>&nbsp;&nbsp; </SPAN>xtree.css</FONT></FONT></SPAN><BR>2.<BR><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">修改</SPAN><SPAN lang=EN-US>struts-config.xml </SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">文件</SPAN><SPAN lang=EN-US>,</SPAN><SPAN style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'"><FONT size=2>增加如下部分</FONT> 
<DIV style="BORDER-RIGHT: windowtext 0.5pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: windowtext 0.5pt solid; PADDING-LEFT: 5.4pt; BACKGROUND: #e6e6e6; PADDING-BOTTOM: 4px; BORDER-LEFT: windowtext 0.5pt solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: windowtext 0.5pt solid">
<DIV><FONT size=2><SPAN style="COLOR: #000000">&nbsp;&nbsp;&nbsp;</SPAN><SPAN style="COLOR: #0000ff">&lt;</SPAN><SPAN style="COLOR: #800000">plug-in&nbsp;</SPAN><SPAN style="COLOR: #ff0000">className</SPAN><SPAN style="COLOR: #0000ff">="net.sf.navigator.menu.MenuPlugIn"</SPAN><SPAN style="COLOR: #0000ff">&gt;</SPAN></FONT><SPAN style="COLOR: #000000"><BR><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</FONT></SPAN><FONT size=2><SPAN style="COLOR: #0000ff">&lt;</SPAN><SPAN style="COLOR: #800000">set-property&nbsp;</SPAN><SPAN style="COLOR: #ff0000">property</SPAN><SPAN style="COLOR: #0000ff">="menuConfig"</SPAN></FONT><SPAN style="COLOR: #ff0000"><BR><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;value</FONT></SPAN><FONT size=2><SPAN style="COLOR: #0000ff">="/WEB-INF/menu-config.xml"</SPAN><SPAN style="COLOR: #0000ff">/&gt;</SPAN></FONT><SPAN style="COLOR: #000000"><BR><FONT size=2>&nbsp;&nbsp;&nbsp;</FONT></SPAN><FONT size=2><SPAN style="COLOR: #0000ff"></< SPAN><SPAN style="COLOR: #800000">plug-in</SPAN><SPAN style="COLOR: #0000ff">&gt;</SPAN></FONT><SPAN style="COLOR: #000000"><BR></SPAN></DIV></DIV>
<P></SPAN></FONT></SPAN></FONT>3.获得数据,构造树状菜单：<BR>&nbsp; 无论何种方法获得数据（JDBC，HIBERNATE。。。），<BR>&nbsp;&nbsp;&nbsp; 我们要构造的树状菜单需要2个必须元素: 1.显示的名称(name) 2.连接到的目标位置(location)<BR>&nbsp; TreeBuilder.java<BR>===========================================================================<BR><BR>/*<BR>&nbsp;* 创建日期 2005-8-6<BR>&nbsp;*/<BR>import ....</P>
<P>import net.sf.navigator.menu.MenuComponent;<BR>import net.sf.navigator.menu.MenuRepository;</P>
<P>/**<BR>&nbsp;* @author yutengfei<BR>&nbsp;*/<BR>public class TreeBuilder {<BR>&nbsp;protected final static Log log = LogFactory.getLog("TreeBuilder");&nbsp;<BR>&nbsp;public TreeBuilder() {&nbsp;}</P>
<P>&nbsp;// 以下代码生成树结构，并以"treerepository"名储存在session&nbsp;中<BR>&nbsp;&nbsp;&nbsp;public static void initialTree(HttpServletRequest request, HttpServlet servlet){<BR>&nbsp;&nbsp;try {<BR>&nbsp;&nbsp;&nbsp;MenuRepository repository = new MenuRepository();<BR>&nbsp;&nbsp; ServletContext context = servlet.getServletContext()<BR>&nbsp;&nbsp;&nbsp;// Get the repository from the application scope - and copy the<BR>&nbsp;&nbsp;&nbsp;// DisplayerMappings from it.<BR>&nbsp;&nbsp;&nbsp;MenuRepository defaultRepository = (MenuRepository) context.getAttribute(MenuRepository.MENU_REPOSITORY_KEY);</P>
<P>&nbsp;&nbsp;&nbsp;repository.setDisplayers(defaultRepository.getDisplayers());&nbsp;<BR><BR>//your&nbsp;code begin <BR>List tree = getTreeDataFromDataBase(); //<BR>//&nbsp;产生从数据库中获得的数据列表(一个树节点是一个<FONT size=2>NoteBean </FONT>对象)&nbsp;，获得数据的方法这里没有提供<BR>for(Iterator it = tree.iterator();it.hasNext();)<BR>{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;MenuComponent&nbsp;mc&nbsp;<SPAN style="COLOR: #000000">=</SPAN><SPAN style="COLOR: #000000">&nbsp;</SPAN><SPAN style="COLOR: #0000ff">new</SPAN><FONT size=2><SPAN style="COLOR: #000000">&nbsp;MenuComponent();&nbsp;&nbsp; //建立一个menu对象(树节点)，然后设置他的属性<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;NoteBean row&nbsp;</SPAN><SPAN style="COLOR: #000000">=(NoteBean) it.next()</SPAN></FONT><FONT size=2><SPAN style="COLOR: #000000"><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;name&nbsp;</SPAN><SPAN style="COLOR: #000000">=</SPAN><SPAN style="COLOR: #000000">&nbsp;(String)&nbsp;row.</SPAN><SPAN style="COLOR: #0000ff">getN</SPAN><SPAN style="COLOR: #000000">ame()</SPAN></FONT><FONT size=2><SPAN style="COLOR: #000000">;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mc.setName(name);&nbsp; //设置显示的节点名称<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;parent&nbsp;</SPAN><SPAN style="COLOR: #000000">=</SPAN><SPAN style="COLOR: #000000">&nbsp;(String)&nbsp;row.</SPAN><SPAN style="COLOR: #0000ff">getP</SPAN><SPAN style="COLOR: #000000">arentname()</SPAN></FONT><FONT size=2><SPAN style="COLOR: #000000">;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.</SPAN><SPAN style="COLOR: #0000ff">out</SPAN><SPAN style="COLOR: #000000">.println(name&nbsp;</SPAN><SPAN style="COLOR: #000000">+</SPAN><SPAN style="COLOR: #000000">&nbsp;</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">,&nbsp;parent&nbsp;is:&nbsp;</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">&nbsp;</SPAN><SPAN style="COLOR: #000000">+</SPAN></FONT><FONT size=2><SPAN style="COLOR: #000000">&nbsp;parent);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN><SPAN style="COLOR: #0000ff">if</SPAN><SPAN style="COLOR: #000000">&nbsp;(parent&nbsp;</SPAN><SPAN style="COLOR: #000000">!=</SPAN><SPAN style="COLOR: #000000">&nbsp;</SPAN><SPAN style="COLOR: #0000ff">null</SPAN></FONT><FONT size=2><SPAN style="COLOR: #000000">)&nbsp;{//存在“父”节点<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;MenuComponent&nbsp;parentMenu&nbsp;</SPAN><SPAN style="COLOR: #000000">=</SPAN></FONT><FONT size=2><SPAN style="COLOR: #000000">&nbsp;repository.getMenu(parent);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN><SPAN style="COLOR: #0000ff">if</SPAN><SPAN style="COLOR: #000000">&nbsp;(parentMenu&nbsp;</SPAN><SPAN style="COLOR: #000000">==</SPAN><SPAN style="COLOR: #000000">&nbsp;</SPAN><SPAN style="COLOR: #0000ff">null</SPAN></FONT><FONT size=2><SPAN style="COLOR: #000000">)&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.</SPAN><SPAN style="COLOR: #0000ff">out</SPAN><SPAN style="COLOR: #000000">.println(</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">parentMenu&nbsp;'</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">&nbsp;</SPAN><SPAN style="COLOR: #000000">+</SPAN><SPAN style="COLOR: #000000">&nbsp;parent&nbsp;</SPAN><SPAN style="COLOR: #000000">+</SPAN><SPAN style="COLOR: #000000">&nbsp;</SPAN><SPAN style="COLOR: #000000">"</SPAN><SPAN style="COLOR: #000000">'&nbsp;doesn't&nbsp;exist!</SPAN><SPAN style="COLOR: #000000">"</SPAN></FONT><FONT size=2><SPAN style="COLOR: #000000">);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN><SPAN style="COLOR: #008000">//</SPAN><SPAN style="COLOR: #008000">&nbsp;create&nbsp;a&nbsp;temporary&nbsp;parentMenu</SPAN></FONT><SPAN style="COLOR: #008000"><BR></SPAN><FONT size=2><SPAN style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;parentMenu&nbsp;</SPAN><SPAN style="COLOR: #000000">=</SPAN><SPAN style="COLOR: #000000">&nbsp;</SPAN><SPAN style="COLOR: #0000ff">new</SPAN></FONT><FONT size=2><SPAN style="COLOR: #000000">&nbsp;MenuComponent();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;parentMenu.setName(parent);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;repository.addMenu(parentMenu);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mc.setParent(parentMenu);&nbsp;&nbsp; //设置“父”节点<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;title&nbsp;</SPAN><SPAN style="COLOR: #000000">=</SPAN><SPAN style="COLOR: #000000">&nbsp;(String)&nbsp;row.</SPAN><SPAN style="COLOR: #0000ff">getT</SPAN><SPAN style="COLOR: #000000">itle()</SPAN></FONT><FONT size=2><SPAN style="COLOR: #000000">;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mc.setTitle(title);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;location&nbsp;</SPAN><SPAN style="COLOR: #000000">=</SPAN><SPAN style="COLOR: #000000">&nbsp;(String)&nbsp;row.</SPAN><SPAN style="COLOR: #0000ff">getL</SPAN><SPAN style="COLOR: #000000">ocation()</SPAN></FONT><FONT size=2><SPAN style="COLOR: #000000">;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mc.setLocation(location);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;repository.addMenu(mc);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</SPAN></FONT>&nbsp;&nbsp;&nbsp;<BR>request.getSession().setAttribute("treerepository", repository);&nbsp;&nbsp; //整个树结构储存在session中<BR>&nbsp;&nbsp;} catch (Exception ex) {<BR>&nbsp;&nbsp;&nbsp;ex.printStackTrace();<BR>&nbsp;&nbsp;}</P>
<P>&nbsp;}</P>
<P>}<BR><BR>4.前台jsp显示树菜单<BR><BR><%try{%><BR><%<BR>if (session.getAttribute("treerepository") == null) {<BR>TreeBuilder .initialTree(request, (HttpServlet) pageContext.getPage());<BR> }%><BR><%List list = new ArrayList();<BR>MenuRepository repository = (MenuRepository) session.getAttribute("treerepository");<BR>list = repository.getTopMenus();<BR>String name = "";%><BR>
<DIV class=dynamicMenu><?xml:namespace prefix = menu /><menu:useMenuDisplayer repository="treerepository" name="ListMenu"><BR>&nbsp;<%for (Iterator it = list.iterator(); it.hasNext();) {<BR> name = ((MenuComponent) it.next()).getName();%><BR>&nbsp;<menu:displayMenu name="<%=name%>"></menu:displayMenu><BR>&nbsp;<%}%><BR></menu:useMenuDisplayer></DIV><BR><%}catch(Exception e){<BR>e.printStackTrace();<BR>}<BR>%><BR><BR>5 over <BR>
<P></P><IMG height=1 src="http://ytfei.cnblogs.com/aggbug/225943.html" width=1><BR><BR>
<DIV align=right>飞翔 2005-08-30 11:42 </DIV><BR>文章来源:<A href="http://ytfei.cnblogs.com/archive/2005/08/30/225943.html">http://ytfei.cnblogs.com/archive/2005/08/30/225943.html</A></SPAN><img src ="http://www.blogjava.net/ytfei/aggbug/12079.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ytfei/" target="_blank">飞翔</a> 2005-08-30 11:42 <a href="http://www.blogjava.net/ytfei/archive/2005/08/30/12079.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Struts Validator验证器使用指南</title><link>http://www.blogjava.net/ytfei/archive/2005/08/04/12081.html</link><dc:creator>飞翔</dc:creator><author>飞翔</author><pubDate>Thu, 04 Aug 2005 08:20:00 GMT</pubDate><guid>http://www.blogjava.net/ytfei/archive/2005/08/04/12081.html</guid><wfw:comment>http://www.blogjava.net/ytfei/comments/12081.html</wfw:comment><comments>http://www.blogjava.net/ytfei/archive/2005/08/04/12081.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ytfei/comments/commentRss/12081.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ytfei/services/trackbacks/12081.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: Struts Validator验证器使用指南（根据Struts Validator Guide）作者：David Winterfeldt大卫James Turner詹姆斯Rob Leland罗伯特翻译：       侯思超验证器：从0.5版，验证器在一些form中就已经实现了，他最初包含在开发人员包中，后来核心代码挪到Jakarta Commons包中和Struts特...&nbsp;&nbsp;<a href='http://www.blogjava.net/ytfei/archive/2005/08/04/12081.html'>阅读全文</a><img src ="http://www.blogjava.net/ytfei/aggbug/12081.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ytfei/" target="_blank">飞翔</a> 2005-08-04 16:20 <a href="http://www.blogjava.net/ytfei/archive/2005/08/04/12081.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>