﻿<?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-花香蝶自来-随笔分类-J2EE</title><link>http://www.blogjava.net/ltc603/category/6946.html</link><description>&lt;font size="3"&gt;学无止境&lt;/font&gt;
&lt;br&gt;

&lt;script type="text/javascript" src="http://wujunlove.googlepages.com/bigstaticeyes.js"&gt;&lt;/script&gt;</description><language>zh-cn</language><lastBuildDate>Tue, 27 Feb 2007 21:18:29 GMT</lastBuildDate><pubDate>Tue, 27 Feb 2007 21:18:29 GMT</pubDate><ttl>60</ttl><item><title>结合struts和hibernate谈J2EE架构的数据表示（vo,po,formbean）</title><link>http://www.blogjava.net/ltc603/archive/2006/08/16/63888.html</link><dc:creator>阿成</dc:creator><author>阿成</author><pubDate>Wed, 16 Aug 2006 05:51:00 GMT</pubDate><guid>http://www.blogjava.net/ltc603/archive/2006/08/16/63888.html</guid><wfw:comment>http://www.blogjava.net/ltc603/comments/63888.html</wfw:comment><comments>http://www.blogjava.net/ltc603/archive/2006/08/16/63888.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ltc603/comments/commentRss/63888.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ltc603/services/trackbacks/63888.html</trackback:ping><description><![CDATA[
		<strong>  </strong>关于VO、PO的理解
		<p><br />O/R Mapping 是 Object Relational Mapping（对象关系映射）的缩写。通俗点讲，就是将对象与关系数据库绑定，用对象来表示关系数据。在O/R Mapping的世界里，有两个基本的也是重要的东东需要了解，即VO，PO。<br />　　VO，值对象(Value Object)，PO，持久对象(Persisent Object)，它们是由一组属性和属性的get和set方法组成。从结构上看，它们并没有什么不同的地方。但从其意义和本质上来看是完全不同的。<br /><br />１．VO是用new关键字创建，由GC回收的。 <br />　　PO则是向数据库中添加新数据时创建，删除数据库中数据时削除的。并且它只能存活在一个数据库连接中，断开连接即被销毁。 <br /><br />２．VO是值对象，精确点讲它是业务对象，是存活在业务层的，是业务逻辑使用的，它存活的目的就是为数据提供一个生存的地方。 <br />　　PO则是有状态的，每个属性代表其当前的状态。它是物理数据的对象表示。使用它，可以使我们的程序与物理数据解耦，并且可以简化对象数据与物理数据之间的转换。<br /><br />３．VO的属性是根据当前业务的不同而不同的，也就是说，它的每一个属性都一一对应当前业务逻辑所需要的数据的名称。 <br />　　PO的属性是跟数据库表的字段一一对应的。<br /><br />PO对象需要实现序列化接口。<br /><br />（转载：<a href="http://www.matrix.org.cn/resource/article/43/43869.html">http://www.matrix.org.cn/resource/article/43/43869.html</a>）<br />－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－<br />在 struts+ hibernate 这种结构中，是不应该把Hibernate产生的PO直接传递给JSP的，不管他是</p><p>Iterator，还是List，这是一个设计错误。 </p><p>我来谈谈在J2EE架构中各层的数据表示方法： </p><p>Web层的数据表示是FormBean，数据来源于HTML Form POST <br />业务层的数据表示是VO <br />持久层的数据表示是PO，其数据来源于数据库，持久层的数据表示例如CMP </p><p>在一个规范的J2EE架构中，不同层的数据表示应该被限制在层内，而不应该扩散到其它层，这样可以降</p><p>低层间的耦合性，提高J2EE架构整体的可维护性和可扩展性。比如说Web层的逻辑进行了修改，那么只需</p><p>要修改FormBean的结构，而不需要触动业务层和持久层的代码修改。同样滴，当数据库表进行了小的调</p><p>整，那么也只需要修改持久层数据表示，而不需要触动业务层代码和Web层代码。 </p><p>不过由于Hibernate的强大功能，例如动态生成PO，PO的状态管理可以脱离Session，使得在应用了</p><p>Hibernate的J2EE框架中，PO完全可以充当VO，因此我们下面把PO和VO合并，统称为PO。 </p><p>先来谈谈ActionFormBean和持久层的PO之间的重大区别。 </p><p>在简单的应用中，ActionFormBean和PO几乎是没有区别，所以很多人干脆就是用ActionFormBean来充当</p><p>PO，于是ActionFormBean从JSP页面到Servlet控制层再到业务层，然后穿过持久层，最后一直映射到数</p><p>据库表。真是一竿子捅到了底！ </p><p>但是在复杂的应用中，ActionFormBean和PO是分离的，他们也不可能一样。ActionFormBean是和网页里</p><p>面的Form表单一一对应的，Form里面有什么元素，Bean里面就有什么属性。而PO和数据库表对应，因此</p><p>如果数据库表不修改，那么PO也不会修改，如果页面的流程和数据库表字段对应关系不一致，那么你又</p><p>如何能够使用ActionFormBean来取代PO呢？ </p><p>比如说吧，用户注册页面要求注册用户的基本信息，因此HTML Form里面包含了基本信息属性，于是你需</p><p>要一个ActionFormBean来一一对应(注意：是一一对应)，每个Bean属性对应一个文本框或者选择框什么</p><p>的。 </p><p>而用户这个持久对象呢？他的属性和ActionFormBean有什么明显不同呢？他会有一些ActionFormBean所</p><p>没有的集合属性，比如说用户的权限属性，用户的组属性，用户的帖子等等。另外还有可能的是在</p><p>ActionFormBean里面有3个属性，分别是用户的First Name, Middle Name, Last Name，而在我的User这</p><p>个持久对象中就是一个 Name 对象属性。 </p><p>假设我的注册页面原来只要你提供First Name，那么ActionFormBean就这一个属性，后来我要你提供全</p><p>名，你要改ActionFormBean，加两个属性。但是这个时候PO是不应该修改滴，因为数据库没有改。 </p><p>那么在一个完整的J2EE系统中应该如何进行合理的设计呢？ </p><p>JSP(View) ---&gt; ActionFormBean(Module) ---&gt; Action(Control) </p><p>ActionFormBean是Web层的数据表示，它和HTML页面Form对应，只要Web页面的操作流程发生改变，它就</p><p>要相应的进行修改，它不应该也不能被传递到业务层和持久层，否则一旦页面修改，会一直牵连到业务</p><p>层和持久层的大面积的代码进行修改，对于软件的可维护性和可扩展性而言，是一个灾难，Actiont就是</p><p>他的边界，到此为止！ </p><p>Action(Web Control) ---&gt; Business Bean ---&gt; DAO ---&gt; ORM ---&gt;DB </p><p>而PO则是业务层和持久层的数据表示，它在业务层和持久层之间进行流动，他不应该也不能被传递到Web</p><p>层的View中去，而ActionServlet就是他的边界，到此为止！ </p><p>然后来看一看整个架构的流程： </p><p>当用户通过浏览器访问网页，提交了一个页面。于是Action拿到了这个FormBean，他会把FormBean属性</p><p>读出来，然后构造一个PO对象，再调用业务层的Bean类，完成了注册操作，重定向到成功页面。而业务</p><p>层Bean收到这个PO对象之后，调用DAO接口方法，进行持久对象的持久化操作。 </p><p>当用户查询某个会员的信息的时候，他用全名进行查询，于是Action得到一个UserNameFormBean包括了3</p><p>个属性，分别是first name, middle name, last name，然后Action把UserNameFormBean的3个属性读出</p><p>来，构造Name对象，再调用业务Bean，把Name对象传递给业务Bean，进行查询。 </p><p>业务Bean取得Name(注意: Name对象只是User的一个属性)对象之后调用DAO接口，返回一个User的PO对象</p><p>，注意这个User不同于在Web层使用的UserFormBean，他有很多集合属性滴。然后业务Bean把User对象返</p><p>回给Action。 </p><p>Action拿到User之后，把User的基本属性取出(集合属性如果不需要就免了)，构造UserFormBean，然后</p><p>把UserFormBean request.setAttribute(...)，然后重定向到查询结果页面。 </p><p>查询页面拿到request对象里面的ActionFormBean，自动调用tag显示之。 </p><p>总结： </p><p>FormBean是Web层的数据表示，他不能被传递到业务层；PO是持久层的数据表示，在特定情况下，例如</p><p>Hibernate中，他可以取代VO出现在业务层，但是不管PO还是VO都必须限制在业务层内使用，最多到达</p><p>Web层的Control，绝不能被扩散到View去。 </p><p>FormBean和PO之间的数据转化是在Action中进行滴。 </p><p>BTW: </p><p>JDO1.x还不能像Hibernate功能这样强大，PO不能脱离持久层，所以必须在业务层使用VO，因此必须在业</p><p>务层进行大量的VO和PO的转化操作，相对于Hibernate来说，编程比较烦琐。 </p><p>当然咯，理论是一回事，实际操作也不一定非要这样干，你可以自行取舍，在实际项目中灵活一点，增</p><p>加一点bad smell，提高开发效率。只不过在大型项目中最好还是严丝合缝，不然的话，改版的时候会痛</p><p>苦的很滴。<br />（转载：<a href="http://forum.hibernate.org.cn/viewtopic.php?t=627">http://forum.hibernate.org.cn/viewtopic.php?t=627</a>）</p><img src ="http://www.blogjava.net/ltc603/aggbug/63888.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ltc603/" target="_blank">阿成</a> 2006-08-16 13:51 <a href="http://www.blogjava.net/ltc603/archive/2006/08/16/63888.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>J2EE WEB程序员应该掌握的技术</title><link>http://www.blogjava.net/ltc603/archive/2006/06/20/54027.html</link><dc:creator>阿成</dc:creator><author>阿成</author><pubDate>Tue, 20 Jun 2006 09:25:00 GMT</pubDate><guid>http://www.blogjava.net/ltc603/archive/2006/06/20/54027.html</guid><wfw:comment>http://www.blogjava.net/ltc603/comments/54027.html</wfw:comment><comments>http://www.blogjava.net/ltc603/archive/2006/06/20/54027.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ltc603/comments/commentRss/54027.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ltc603/services/trackbacks/54027.html</trackback:ping><description><![CDATA[html ，css ，js ，jsp， jdbc， sql ，会java，懂web框架， 数据库 ,会用google<img src ="http://www.blogjava.net/ltc603/aggbug/54027.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ltc603/" target="_blank">阿成</a> 2006-06-20 17:25 <a href="http://www.blogjava.net/ltc603/archive/2006/06/20/54027.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>学习使用J2EE Web应用的事件功能</title><link>http://www.blogjava.net/ltc603/archive/2006/01/25/29174.html</link><dc:creator>阿成</dc:creator><author>阿成</author><pubDate>Wed, 25 Jan 2006 02:11:00 GMT</pubDate><guid>http://www.blogjava.net/ltc603/archive/2006/01/25/29174.html</guid><wfw:comment>http://www.blogjava.net/ltc603/comments/29174.html</wfw:comment><comments>http://www.blogjava.net/ltc603/archive/2006/01/25/29174.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ltc603/comments/commentRss/29174.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ltc603/services/trackbacks/29174.html</trackback:ping><description><![CDATA[在Servlet2.3规范中，Web应用事件是新增加的部分。它让你能最大程度地控制你的Web应用。在本文中，我们将学习两个很重要的应用事件：<br /><br />应用的启动和停止<br /><br />Session的创建和失效如它们的名字那样，应用启动事件发生在你的应用第一次被servlet容器装载和启动的时候；停止事件发生在Web应用停止的时候。<br /><br />Session创建事件发生在每次一个新的session创建的时候，类似地Session失效事件发生在每次一个Session失效的时候。为了使用这些Web应用事件为你做些有用的事情，我们必须创建和使用一些特殊的“监听”类。下面，我们将研究这些监听类到地是什么以及我们如何去使用它们。<br /><br />监听类：<br /><br />它们是实现了下边两个接口中任何一个接口的简单的java类：<br /><br /><center><ccid_nobr></ccid_nobr><table bordercolordark="#ffffff" bordercolorlight="black" align="center" border="1" cellpadding="2" cellspacing="0" width="400"><tbody><tr><td class="code" style="font-size: 9pt;" bgcolor="#e6e6e6"><pre><ccid_code>javax.servlet.ServletContextListener 
javax.servlet.http.HttpSessionListener</ccid_code></pre></td></tr></tbody></table></center><br /><br />如果你想让你的类监听应用的启动和停止事件，你就得实现ServletContextListener接口;如果你想让你的类去监听Session的创建和失效事件，那你就得实现HttpSessionListener接口。 让我们看看在这些接口中你必须要实现的方法。 <br /><br />1.ServletContextListener : <br /><br />接口包括如下两个方法： <br /><br /><center><ccid_nobr></ccid_nobr><table bordercolordark="#ffffff" bordercolorlight="black" align="center" border="1" cellpadding="2" cellspacing="0" width="400"><tbody><tr><td class="code" style="font-size: 9pt;" bgcolor="#e6e6e6"><pre><ccid_code>public void contextInitialized
(ServletContextEvent sce); 

public void contextDestroyed
(ServletContextEvent sce);</ccid_code></pre></td></tr></tbody></table></center><br /><br />如果你实现了一个接口，那你就必须实现它所有的方法。因此，如果你想利用应用的启动和停止事件，你就需要创建一个Java类并实现ServletContextListener接口。下边是这样的一个类的例子： <br /><br /><center><ccid_nobr></ccid_nobr><table bordercolordark="#ffffff" bordercolorlight="black" align="center" border="1" cellpadding="2" cellspacing="0" width="400"><tbody><tr><td class="code" style="font-size: 9pt;" bgcolor="#e6e6e6"><pre><ccid_code>/*File : ApplicationWatch.java*/
import javax.servlet.ServletContextListener;
import javax.servlet.ServletContextEvent;
public class ApplicationWatch implements 
ServletContextListener 
{
public static long
applicationInitialized = 0L;
/* 应用启动事件 */
public void contextInitialized
(ServletContextEvent ce)
{
applicationInitialized =
System.currentTimeMillis();
}
/*应用停止事件 */
public void contextDestroyed
(ServletContextEvent ce) {}
}</ccid_code></pre></td></tr></tbody></table></center><br /><br />在上边的代码中，ApplicationWatch类实现了ServletContextListener接口。它实现了接口中的两个方法，但只用了其中的一个方法，另一个方法中没有写任何代码。这个类把应用启动的时间记录在一个可以从其它应用类中存取应用启动时间的public static变量中。 <br /><br />我将很快解释如何告诉服务器我们有这个监听类，但首先让我们看看HttpSessionListener接口有什么不同的方法。 <br /><br />2.HttpSessionListener : <br /><br />这个接口也只包含两个方法，分别对应于Session的创建和失效： <br /><br /><center><ccid_nobr></ccid_nobr><table bordercolordark="#ffffff" bordercolorlight="black" align="center" border="1" cellpadding="2" cellspacing="0" width="400"><tbody><tr><td class="code" style="font-size: 9pt;" bgcolor="#e6e6e6"><pre><ccid_code>public void sessionCreated
(HttpSessionEvent se); 

public void sessionDestroyed
(HttpSessionEvent se);</ccid_code></pre></td></tr></tbody></table></center><br /><br />如上边的ApplicationWatch例子那样，我们也创建了一个实现HttpSessionListener接口的类。如下： <br /><br /><center><ccid_nobr></ccid_nobr><table bordercolordark="#ffffff" bordercolorlight="black" align="center" border="1" cellpadding="2" cellspacing="0" width="400"><tbody><tr><td class="code" style="font-size: 9pt;" bgcolor="#e6e6e6"><pre><ccid_code>/*File : SessionCounter.java*/
import javax.servlet.http.HttpSessionListener;
import javax.servlet.http.HttpSessionEvent;
public class SessionCounter
implements HttpSessionListener 
{
private static int activeSessions =0;
/* Session创建事件 */
public void sessionCreated
(HttpSessionEvent se)
{
       activeSessions++;
}
/* Session失效事件 */
public void sessionDestroyed
(HttpSessionEvent se)
{
if(activeSessions&gt;0)activeSessions--;
}

public static int getActiveSessions()
{
return activeSessions;
}
}</ccid_code></pre></td></tr></tbody></table></center><br /><br />在上边的代码中，SessionCounter类实现了HttpSessionListener接口，其目的是计算活动会话的数量。 <br /><br />好了，我们已经学习了什么是Web应用事件，有什么接口可以用以及看到了一些实现这些接口的例子。让我们看看如何告诉应用服务器我们有这些监听类。 <br /><br />Web.xml : <br /><br />我们通过把类路径加入/WEB-INF/web.xml文件的标签<listener>中来告诉服务器我们的监听类。下边是一个web.xml文件的例子： <br /><br /></listener><center><ccid_nobr></ccid_nobr><table bordercolordark="#ffffff" bordercolorlight="black" align="center" border="1" cellpadding="2" cellspacing="0" width="400"><tbody><tr><td class="code" style="font-size: 9pt;" bgcolor="#e6e6e6"><pre><ccid_code>&lt;!-- Web.xml --&gt;
&lt;?xml version="1.0" encoding="ISO-8859-1"?&gt;
&lt;!DOCTYPE web-appPUBLIC "-//Sun Microsystems,
Inc.//DTD Web Application 2.3
//EN""http://java.sun.com
/j2ee/dtds/web-app_2.3.dtd"&gt;
&lt;web-app&gt;
&lt;!-- Listeners --&gt;
&lt;listener&gt;
&lt;listener-class&gt;
com.stardeveloper.web.listener.SessionCounter
&lt;/listener-class&gt;
&lt;/listener&gt;
&lt;listener&gt;
&lt;listener-class&gt;
com.stardeveloper.web.listener.
ApplicationWatch&lt;/listener-class&gt;
&lt;/listener&gt;
&lt;/web-app&gt;</ccid_code></pre></td></tr></tbody></table></center><br /><br />如上所示，在web.xml文件中声明监听类是非常简单的。现在，每次的服务器的启动和停止，会话的创建和失效，配置好的监听类的相应的方法就会被调用。<img src ="http://www.blogjava.net/ltc603/aggbug/29174.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ltc603/" target="_blank">阿成</a> 2006-01-25 10:11 <a href="http://www.blogjava.net/ltc603/archive/2006/01/25/29174.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Struts+Spring+Hibernate实现上传下载</title><link>http://www.blogjava.net/ltc603/archive/2006/01/13/27966.html</link><dc:creator>阿成</dc:creator><author>阿成</author><pubDate>Fri, 13 Jan 2006 12:31:00 GMT</pubDate><guid>http://www.blogjava.net/ltc603/archive/2006/01/13/27966.html</guid><wfw:comment>http://www.blogjava.net/ltc603/comments/27966.html</wfw:comment><comments>http://www.blogjava.net/ltc603/archive/2006/01/13/27966.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ltc603/comments/commentRss/27966.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ltc603/services/trackbacks/27966.html</trackback:ping><description><![CDATA[
		<p>
				<strong>引言<br /><br /></strong>　　文件的上传和下载在J2EE编程已经是一个非常古老的话题了，也许您马上就能掰着指头数出好几个著名的大件：如SmartUpload、Apache的FileUpload。但如果您的项目是构建在Struts+Spring+Hibernate（以下称SSH）框架上的，这些大件就显得笨重而沧桑了，SSH提供了一个简捷方便的文件上传下载的方案，我们只需要通过一些配置并辅以少量的代码就可以完好解决这个问题了。<br /><br />　　本文将围绕SSH文件上传下载的主题，向您详细讲述如何开发基于SSH的Web程序。SSH各框架的均为当前最新版本：<br /><br />　　·Struts 1.2<br /><br />　　·Spring 1.2.5<br /><br />　　·Hibernate 3.0<br /><br />　　本文选用的数据库为Oracle 9i，当然你可以在不改动代码的情况下，通过配置文件的调整将其移植到任何具有Blob字段类型的数据库上，如MySQL，SQLServer等。<br /><br />　　<b>总体实现</b><br /><br />　　上传文件保存到T_FILE表中，T_FILE表结构如下：<br /><br /></p>
		<table align="center" border="0" width="90%">
				<tbody>
						<tr>
								<td>
										<div align="center">
												<img src="http://java.chinaitlab.com/UploadFiles_8734/200512/20051223095916710.gif" border="0" />
												<br />图 1 T_FILE表结构</div>
								</td>
						</tr>
				</tbody>
		</table>
		<br />　　其中：<br /><br />　　·FILE_ID：文件ID，32个字符，用Hibernate的uuid.hex算法生成。<br /><br />　　·FILE_NAME：文件名。<br /><br />　　·FILE_CONTENT：文件内容，对应Oracle的Blob类型。<br /><br />　　·REMARK：文件备注。<br /><br />　　文件数据存储在Blob类型的FILE_CONTENT表字段上，在Spring中采用OracleLobHandler来处理Lob字段（包括Clob和Blob），由于在程序中不需要引用到oracle数据驱动程序的具体类且屏蔽了不同数据库处理Lob字段方法上的差别，从而撤除程序在多数据库移植上的樊篱。 <br /><br />　　1．首先数据表中的Blob字段在Java领域对象中声明为byte[]类型，而非java.sql.Blob类型。<br /><br />　　2．数据表Blob字段在Hibernate持久化映射文件中的type为org.springframework.orm.hibernate3.support.BlobByteArrayType，即Spring所提供的用户自定义的类型，而非java.sql.Blob。 <br /><br />　　3．在Spring中使用org.springframework.jdbc.support.lob.OracleLobHandler处理Oracle数据库的Blob类型字段。<br /><br />　　通过这样的设置和配置，我们就可以象持久化表的一般字段类型一样处理Blob字段了。<br /><br />　　以上是Spring＋Hibernate将文件二进制数据持久化到数据库的解决方案，而Struts通过将表单中file类型的组件映射为ActionForm中类型为org.apache.struts.upload. FormFile的属性来获取表单提交的文件数据。<br /><br />　　综上所述，我们可以通过图 2，描绘出SSH处理文件上传的方案：<br /><br /><table align="center" border="0" width="90%"><tbody><tr><td><div align="center"><img src="http://java.chinaitlab.com/UploadFiles_8734/200512/20051223095916406.jpg" border="0" /><br />图 2 SSH处理文件上传技术方案</div></td></tr></tbody></table><br />　　文件上传的页面如图 3所示：<br /><br /><table align="center" border="0" width="90%"><tbody><tr><td><div align="center"><img src="http://java.chinaitlab.com/UploadFiles_8734/200512/20051223095917560.jpg" border="0" /><br />图 3 文件上传页面</div></td></tr></tbody></table><br />　　文件下载的页面如图 4所示：<br /><br /><table align="center" border="0" width="90%"><tbody><tr><td><div align="center"><img src="http://java.chinaitlab.com/UploadFiles_8734/200512/20051223095919264.jpg" border="0" /><br />图 4 文件下载页面</div></td></tr></tbody></table><br />　　该工程的资源结构如图 5所示：<br /><br /><table align="center" border="0" width="90%"><tbody><tr><td><div align="center"><img src="http://java.chinaitlab.com/UploadFiles_8734/200512/20051223095919770.gif" border="0" /><br />图 5 工程资源结构</div></td></tr></tbody></table><br />　　工程的类按SSH的层次结构划分为数据持久层、业务层和Web层；WEB-INF下的applicationContext.xml为Spring的配置文件，struts-config.xml为Struts的配置文件，file-upload.jsp为文件上传页面，file-list.jsp为文件列表页面。<br /><br />　　本文后面的章节将从数据持久层-＞业务层-＞Web层的开发顺序，逐层讲解文件上传下载的开发过程。<br /><br />　　<b>数据持久层</b><br /><br />　　1、领域对象及映射文件<br /><br />　　您可以使用Hibernate Middlegen、HIbernate Tools、Hibernate Syhchronizer等工具或手工的方式，编写Hibernate的领域对象和映射文件。其中对应T_FILE表的领域对象Tfile.java为：<br /><br />　　代码 1 领域对象Tfile<br /><br /><table align="center" bgcolor="#e3e3e3" border="1" bordercolor="#cccccc" width="90%"><tbody><tr><td>1. package sshfile.model;<br />2. public class Tfile<br />3.{<br />4. private String fileId;<br />5. private String fileName;<br />6. private byte[] fileContent;<br />7. private String remark;<br />8. …//getter and setter<br />9. }</td></tr></tbody></table><br />　　特别需要注意的是：数据库表为Blob类型的字段在Tfile中的fileContent类型为byte[]。Tfile的Hibernate映射文件Tfile.hbm.xml放在Tfile .java类文件的相同目录下：<br /><br />　　代码 2 领域对象映射文件<br /><br /><table align="center" bgcolor="#e3e3e3" border="1" bordercolor="#cccccc" width="90%"><tbody><tr><td>1. ＜?xml version="1.0"?＞<br />2. ＜!DOCTYPE hibernate-mapping PUBLIC<br />3. "-//Hibernate/Hibernate Mapping DTD 3.0//EN"<br />4. "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" ＞<br />5. ＜hibernate-mapping＞<br />6. ＜class name="sshfile.model.Tfile" table="T_FILE"＞<br />7. ＜id name="fileId" type="java.lang.String" column="FILE_ID"＞<br />8. ＜generator class="uuid.hex"/＞<br />9. ＜/id＞<br />10. ＜property name="fileContent"<br />11. type="org.springframework.orm.hibernate3.support.BlobByteArrayType"<br />12. column="FILE_CONTENT" lazy="true"/＞<br />13. …//其它一般字段的映射<br />14. ＜/class＞<br />15. ＜/hibernate-mapping＞</td></tr></tbody></table><br />　　fileContent字段映射为Spring所提供的BlobByteArrayType类型，BlobByteArrayType是用户自定义的数据类型，它实现了Hibernate 的org.hibernate.usertype.UserType接口。BlobByteArrayType使用从sessionFactory获取的Lob操作句柄lobHandler将byte[]的数据保存到Blob数据库字段中。这样，我们就再没有必要通过硬编码的方式，先insert然后再update来完成Blob类型数据的持久化，这个原来难伺候的老爷终于被平民化了。关于lobHandler的配置请见本文后面的内容。<br /><br />　　此外lazy="true"说明地返回整个Tfile对象时，并不返回fileContent这个字段的数据，只有在显式调用tfile.getFileContent()方法时才真正从数据库中获取fileContent的数据。这是Hibernate3引入的新特性，对于包含重量级大数据的表字段，这种抽取方式提高了对大字段操作的灵活性，否则加载Tfile对象的结果集时如果总是返回fileContent，这种批量的数据抽取将可以引起数据库的"洪泛效应"。<br /><br />　　2、DAO编写和配置<br /><br />　　Spring强调面向接口编程，所以我们将所有对Tfile的数据操作的方法定义在TfileDAO接口中，这些接口方法分别是：<br /><br />　　·findByFildId(String fileId)<br /><br />　　·save(Tfile tfile)<br /><br />　　·List findAll()<br /><br />　　TfileDAOHibernate提供了对TfileDAO接口基于Hibernate的实现，如代码 3所示：<br /><br />　　代码 3 基于Hibernate 的fileDAO实现类<br /><br /><table align="center" bgcolor="#e3e3e3" border="1" bordercolor="#cccccc" width="90%"><tbody><tr><td>1. package sshfile.dao;<br />2.<br />3. import sshfile.model.*;<br />4. import org.springframework.orm.hibernate3.support.HibernateDaoSupport;<br />5. import java.util.List;<br />6.<br />7. public class TfileDAOHibernate<br />8. extends HibernateDaoSupport implements TfileDAO<br />9. {<br />10. public Tfile findByFildId(String fileId)<br />11. {<br />12. return (Tfile) getHibernateTemplate().get(Tfile.class, fileId);<br />13. }<br />14. public void save(Tfile tfile)<br />15. {<br />16. getHibernateTemplate().save(tfile);<br />17. getHibernateTemplate().flush();<br />18. }<br />19. public List findAll()<br />20. {<br />21. return getHibernateTemplate().loadAll(Tfile.class);<br />22. }<br />23. }</td></tr></tbody></table><br />　　TfileDAOHibernate通过扩展Spring提供的Hibernate支持类HibernateDaoSupport而建立，HibernateDaoSupport封装了HibernateTemplate，而HibernateTemplate封装了Hibernate所提供几乎所有的的数据操作方法，如execute(HibernateCallback action)，load(Class entityClass, Serializable id)，save(final Object entity)等等。<br /><br />　　所以我们的DAO只需要简单地调用父类的HibernateTemplate就可以完成几乎所有的数据库操作了。<br /><br />　　由于Spring通过代理Hibernate完成数据层的操作，所以原Hibernate的配置文件hibernate.cfg.xml的信息也转移到Spring的配置文件中：<br /><br />　　代码 4 Spring中有关Hibernate的配置信息<br /><br /><table align="center" bgcolor="#e3e3e3" border="1" bordercolor="#cccccc" width="90%"><tbody><tr><td>1. ＜beans＞<br />2. ＜!-- 数据源的配置 //--＞<br />3. ＜bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"<br />4. destroy-method="close"＞<br />5. ＜property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/＞<br />6. ＜property name="url" value="jdbc:oracle:thin:@localhost:1521:ora9i"/＞<br />7. ＜property name="username" value="test"/＞<br />8. ＜property name="password" value="test"/＞<br />9. ＜/bean＞<br />10. ＜!-- Hibernate会话工厂配置 //--＞<br />11. ＜bean id="sessionFactory"<br />12. class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"＞<br />13. ＜property name="dataSource" ref="dataSource"/＞<br />14. ＜property name="mappingDirectoryLocations"＞<br />15. ＜list＞<br />16. ＜value＞classpath:/sshfile/model＜/value＞<br />17. ＜/list＞<br />18. ＜/property＞<br />19. ＜property name="hibernateProperties"＞<br />20. ＜props＞<br />21. ＜prop key="hibernate.dialect"＞org.hibernate.dialect.OracleDialect＜/prop＞<br />22. ＜prop key="hibernate.cglib.use_reflection_optimizer"＞true＜/prop＞<br />23. ＜/props＞<br />24. ＜/property＞<br />25. ＜/bean＞<br />26. ＜!-- Hibernate 模板//--＞<br />27. ＜bean id="hibernateTemplate"<br />28. class="org.springframework.orm.hibernate3.HibernateTemplate"＞<br />29. ＜property name="sessionFactory" ref="sessionFactory"/＞<br />30. ＜/bean＞<br />31. ＜!--DAO配置 //--＞<br />32. ＜bean id="tfileDAO" class="sshfile.dao.TfileDAOHibernate"＞<br />33. ＜property name="hibernateTemplate" ref="hibernateTemplate" /＞<br />34. ＜/bean＞<br />35. …<br />36. ＜/beans＞</td></tr></tbody></table><br />　　第3~9行定义了一个数据源，其实现类是apache的BasicDataSource，第11~25行定义了Hibernate的会话工厂，会话工厂类用Spring提供的LocalSessionFactoryBean维护，它注入了数据源和资源映射文件，此外还通过一些键值对设置了Hibernate所需的属性。<br /><br />　　其中第16行通过类路径的映射方式，将sshfile.model类包目录下的所有领域对象的映射文件装载进来，在本文的例子里，它将装载进Tfile.hbm.xml映射文件。如果有多个映射文件需要声明，使用类路径映射方式显然比直接单独指定映射文件名的方式要简便。 <br /><br />　　第27~30行定义了Spring代理Hibernate数据操作的HibernateTemplate模板，而第32~34行将该模板注入到tfileDAO中。<br /><br />　　需要指定的是Spring 1.2.5提供了两套Hibernate的支持包，其中Hibernate 2相关的封装类位于org.springframework.orm.hibernate2.*包中，而Hibernate 3.0的封装类位于org.springframework.orm.hibernate3.*包中，需要根据您所选用Hibernate版本进行正确选择。<br /><br />　　3、Lob字段处理的配置<br /><br />　　我们前面已经指出Oracle的Lob字段和一般类型的字段在操作上有一个明显的区别--那就是你必须首先通过Oracle的empty_blob()/empty_clob()初始化Lob字段，然后获取该字段的引用，通过这个引用更改其值。所以要完成对Lob字段的操作，Hibernate必须执行两步数据库访问操作，先Insert再Update。<br /><br />　　使用BlobByteArrayType字段类型后，为什么我们就可以象一般的字段类型一样操作Blob字段呢？可以确定的一点是：BlobByteArrayType不可能逾越Blob天生的操作方式，原来是BlobByteArrayType数据类型本身具体数据访问的功能，它通过LobHandler将两次数据访问的动作隐藏起来，使Blob字段的操作在表现上和其他一般字段业类型无异，所以LobHandler即是那个"苦了我一个，幸福十亿人"的那位幕后英雄。<br /><br />　　LobHandler必须注入到Hibernate会话工厂sessionFactory中，因为sessionFactory负责产生与数据库交互的Session。LobHandler的配置如代码 5所示：<br /><br />　　代码 5 Lob字段的处理句柄配置<br /><br /><table align="center" bgcolor="#e3e3e3" border="1" bordercolor="#cccccc" width="90%"><tbody><tr><td>1. ＜beans＞<br />2. …<br />3. ＜bean id="nativeJdbcExtractor"<br />4. class="org.springframework.jdbc.support.nativejdbc.CommonsDbcpNativeJdbcExtractor"<br />5. lazy-init="true"/＞<br />6. ＜bean id="lobHandler"<br />7. class="org.springframework.jdbc.support.lob.OracleLobHandler" lazy-init="true"＞<br />8. ＜property name="nativeJdbcExtractor"＞<br />9. ＜ref local="nativeJdbcExtractor"/＞<br />10. ＜/property＞<br />11. ＜/bean＞<br />12. …<br />13. ＜/beans＞</td></tr></tbody></table><br />　　首先，必须定义一个能够从连接池中抽取出本地数据库JDBC对象（如OracleConnection，OracleResultSet等）的抽取器：nativeJdbcExtractor，这样才可以执行一些特定数据库的操作。对于那些仅封装了Connection而未包括Statement的简单数据连接池，SimpleNativeJdbcExtractor是效率最高的抽取器实现类，但具体到apache的BasicDataSource连接池，它封装了所有JDBC的对象，这时就需要使用CommonsDbcpNativeJdbcExtractor了。Spring针对几个著名的Web服务器的数据源提供了相应的JDBC抽取器：<br /><br />　　·WebLogic：WebLogicNativeJdbcExtractor<br /><br />　　·WebSphere：WebSphereNativeJdbcExtractor<br /><br />　　·JBoss：JBossNativeJdbcExtractor<br /><br />　　在定义了JDBC抽取器后，再定义lobHandler。Spring 1.2.5提供了两个lobHandler：<br /><br />　　·DefaultLobHandler：适用于大部分的数据库，如SqlServer，MySQL，对Oracle 10g也适用，但不适用于Oracle 9i（看来Oracle 9i确实是个怪胎，谁叫Oracle 公司自己都说Oracle 9i是一个过渡性的产品呢）。<br /><br />　　·OracleLobHandler：适用于Oracle 9i和Oracle 10g。<br /><br />　　由于我们的数据库是Oracle9i，所以使用OracleLobHandler。<br /><br />　　在配置完LobHandler后， 还需要将其注入到sessionFactory的Bean中，下面是调用后的sessionFactory Bean的配置：<br /><br />　　代码 6 将lobHandler注入到sessionFactory中的配置<br /><br /><table align="center" bgcolor="#e3e3e3" border="1" bordercolor="#cccccc" width="90%"><tbody><tr><td>1. ＜beans＞<br />2. …<br />3. ＜bean id="sessionFactory"<br />4. class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"＞<br />5. ＜property name="dataSource" ref="dataSource"/＞<br />6. ＜!-- 为处理Blob类型字段的句柄声明 //--＞<br />7. ＜property name="lobHandler" ref="lobHandler"/＞<br />8. …<br />9. ＜/bean＞<br />10. …<br />11. ＜/beans＞</td></tr></tbody></table><br />　　如第7所示，通过sessionFactory的lobHandler属性进行注入。<br /><br />　　<b>业务层</b><br /><br />　　1、业务层接口<br /><br />　　"面向接口而非面向类编程"是Spring不遗余力所推荐的编程原则，这条原则也已经为大部开发者所接受；此外，JDK的动态代理只对接口有效，否则必须使用CGLIB生成目标类的子类。我们依从于Spring的倡导为业务类定义一个接口：<br /><br />　　代码 7 业务层操作接口<br /><br /><table align="center" bgcolor="#e3e3e3" border="1" bordercolor="#cccccc" width="90%"><tbody><tr><td>1. public interface FileService<br />2. {<br />3. void save(FileActionForm fileForm);//将提交的上传文件保存到数据表中<br />4. List getAllFile();//得到T_FILE所示记录<br />5. void write(OutputStream os,String fileId);//将某个文件的文件数据写出到输出流中<br />6. String getFileName(String fileId);//获取文件名<br />7. }</td></tr></tbody></table><br />　　其中save(FileActionForm fileForm)方法，将封装在fileForm中的上传文件保存到数据库中，这里我们使用FileActionForm作为方法入参，FileActionForm是Web层的表单数据对象，它封装了提交表单的数据。将FileActionForm直接作为业务层的接口入参，相当于将Web层传播到业务层中去，即将业务层绑定在特定的Web层实现技术中，按照分层模型学院派的观点，这是一种反模块化的设计，但在"一般"的业务系统并无需提供多种UI界面，系统Web层将来切换到另一种实现技术的可能性也微乎其微，所以笔者觉得没有必要为了这个业务层完全独立于调用层的过高目标而去搞一个额外的隔离层，浪费了原材料不说，还将系统搞得过于复杂，相比于其它原则，"简单"始终是最大的一条原则。<br /><br />　　getAllFile()负责获取T_FILE表所有记录，以便在网页上显示出来。<br /><br />　　而getFileName(String fileId)和write(OutputStream os,String fileId)则用于下载某个特定的文件。具体的调用是将Web层将response.getOutputStream()传给write(OutputStream os,String fileId)接口，业务层直接将文件数据输出到这个响应流中。具体实现请参见错误！未找到引用源。节下载文件部分。<br /><br />　　2、业务层接口实现类<br /><br />　　FileService的实现类为FileServiceImpl，其中save(FileActionForm fileForm)的实现如下所示：<br /><br />　　代码 8 业务接口实现类之save()<br /><br /><table align="center" bgcolor="#e3e3e3" border="1" bordercolor="#cccccc" width="90%"><tbody><tr><td>1. …<br />2. public class FileServiceImpl<br />3. implements FileService<br />4. {<br />5. private TfileDAO tfileDAO;<br />6. public void save(FileActionForm fileForm)<br />7. {<br />8. Tfile tfile = new Tfile();<br />9. try<br />10. {<br />11. tfile.setFileContent(fileForm.getFileContent().getFileData());<br />12. }<br />13. catch (FileNotFoundException ex)<br />14. {<br />15. throw new RuntimeException(ex);<br />16. }<br />17. catch (IOException ex)<br />18. {<br />19. throw new RuntimeException(ex);<br />20. }<br />21. tfile.setFileName(fileForm.getFileContent().getFileName());<br />22. tfile.setRemark(fileForm.getRemark());<br />23. tfileDAO.save(tfile);<br />24. }<br />25. …<br />26. }</td></tr></tbody></table><br />　　在save(FileActionForm fileForm)方法里，完成两个步骤：<br /><br />　　其一，象在水桶间倒水一样，将FileActionForm对象中的数据倒入到Tfile对象中；<br /><br />　　其二，调用TfileDAO保存数据。<br /><br />　　需要特别注意的是代码的第11行，FileActionForm的fileContent属性为org.apache.struts.upload.FormFile类型，FormFile提供了一个方便的方法getFileData()，即可获取文件的二进制数据。通过解读FormFile接口实现类DiskFile的原码，我们可能知道FormFile本身并不缓存文件的数据，只有实际调用getFileData()时，才从磁盘文件输入流中获取数据。由于FormFile使用流读取方式获取数据，本身没有缓存文件的所有数据，所以对于上传超大体积的文件，也是没有问题的；但是，由于数据持久层的Tfile使用byte[]来缓存文件的数据，所以并不适合处理超大体积的文件（如100M），对于超大体积的文件，依然需要使用java.sql.Blob类型以常规流操作的方式来处理。<br /><br />　　此外，通过FileForm的getFileName()方法就可以获得上传文件的文件名，如第21行代码所示。<br /><br />　　write(OutputStream os,String fileId)方法的实现，如代码 9所示：<br /><br />　　代码 9 业务接口实现类之write()<br /><br /><table align="center" bgcolor="#e3e3e3" border="1" bordercolor="#cccccc" width="90%"><tbody><tr><td>1. …<br />2. public class FileServiceImpl<br />3. implements FileService<br />4. {<br />5.<br />6. public void write(OutputStream os, String fileId)<br />7. {<br />8. Tfile tfile = tfileDAO.findByFildId(fileId);<br />9. try<br />10. {<br />11. os.write(tfile.getFileContent());<br />12. os.flush();<br />13. }<br />14. catch (IOException ex)<br />15. {<br />16. throw new RuntimeException(ex);<br />17. }<br />18. }<br />19. …<br />20. }</td></tr></tbody></table><br />　　write(OutputStream os,String fileId)也简单地分为两个操作步骤，首先，根据fileId加载表记录，然后将fileContent写入到输出流中。<br /><br />　　3、Spring事务配置<br /><br />　　下面，我们来看如何在Spring配置文件中为FileService配置声明性的事务<br /><br /><table align="center" bgcolor="#e3e3e3" border="1" bordercolor="#cccccc" width="90%"><tbody><tr><td>1. ＜beans＞<br />2. … <br />3. ＜bean id="transactionManager"<br />4. class="org.springframework.orm.hibernate3.HibernateTransactionManager"＞<br />5. ＜property name="sessionFactory" ref="sessionFactory"/＞<br />6. ＜/bean＞<br />7. ＜!-- 事务处理的AOP配置 //--＞<br />8. ＜bean id="txProxyTemplate" abstract="true"<br />9. class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"＞<br />10. ＜property name="transactionManager" ref="transactionManager"/＞<br />11. ＜property name="transactionAttributes"＞<br />12. ＜props＞<br />13. ＜prop key="get*"＞PROPAGATION_REQUIRED,readOnly＜/prop＞<br />14. ＜prop key="find*"＞PROPAGATION_REQUIRED,readOnly＜/prop＞<br />15. ＜prop key="save"＞PROPAGATION_REQUIRED＜/prop＞<br />16. ＜prop key="write"＞PROPAGATION_REQUIRED,readOnly＜/prop＞<br />17. ＜/props＞<br />18. ＜/property＞<br />19. ＜/bean＞<br />20. ＜bean id="fileService" parent="txProxyTemplate"＞<br />21. ＜property name="target"＞<br />22. ＜bean class="sshfile.service.FileServiceImpl"＞<br />23. ＜property name="tfileDAO" ref="tfileDAO"/＞<br />24. ＜/bean＞<br />25. ＜/property＞<br />26. ＜/bean＞<br />27. ＜/beans＞</td></tr></tbody></table><br />　　Spring的事务配置包括两个部分：<br /><br />　　其一，定义事务管理器transactionManager，使用HibernateTransactionManager实现事务管理；<br /><br />　　其二，对各个业务接口进行定义，其实txProxyTemplate和fileService是父子节点的关系，本来可以将txProxyTemplate定义的内容合并到fileService中一起定义，由于我们的系统仅有一个业务接口需要定义，所以将其定义的一部分抽象到父节点txProxyTemplate中意义确实不大，但是对于真实的系统，往往拥有为数众多的业务接口需要定义，将这些业务接口定义内容的共同部分抽取到一个父节点中，然后在子节点中通过parent进行关联，就可以大大简化业务接口的配置了。<br /><br />　　父节点txProxyTemplate注入了事务管理器，此外还定义了业务接口事务管理的方法（允许通过通配符的方式进行匹配声明，如前两个接口方法），有些接口方法仅对数据进行读操作，而另一些接口方法需要涉及到数据的更改。对于前者，可以通过readOnly标识出来，这样有利于操作性能的提高，需要注意的是由于父类节点定义的Bean仅是子节点配置信息的抽象，并不能具体实现化一个Bean对象，所以需要特别标注为abstract="true"，如第8行所示。<br /><br />　　fileService作为一个目标类被注入到事务代理器中，而fileService实现类所需要的tfileDAO实例，通过引用3.2节中定义的tfileDAO Bean注入。<br /><br />　　<b>Web层实现</b><br /><br />　　1、Web层的构件和交互流程<br /><br />　　Web层包括主要3个功能：<br /><br />　　·上传文件。<br /><br />　　·列出所有已经上传的文件列表，以供点击下载。<br /><br />　　·下载文件。<br /><br />　　Web层实现构件包括与2个JSP页面，1个ActionForm及一个Action：<br /><br />　　·file-upload.jsp：上传文件的页面。<br /><br />　　·file-list.jsp：已经上传文件的列表页面。<br /><br />　　·FileActionForm：file-upload.jsp页面表单对应的ActionForm。<br /><br />　　·FileAction：继承org.apache.struts.actions.DispatchAction的Action，这样这个Action就可以通过一个URL参数区分中响应不同的请求。<br /><br />　　Web层的这些构件的交互流程如图 6所示：<br /><br /><table align="center" border="0" width="90%"><tbody><tr><td><div align="center"><img src="http://java.chinaitlab.com/UploadFiles_8734/200512/20051223095919215.jpg" border="0" /><br />图 6 Web层Struts流程图</div></td></tr></tbody></table><br />　　其中，在执行文件上传的请求时，FileAction在执行文件上传后，forward到loadAllFile出口中，loadAllFile加载数据库中所有已经上传的记录，然后forward到名为fileListPage的出口中，调用file-list.jsp页面显示已经上传的记录。<br /><br />　　2、FileAction功能<br /><br />　　Struts 1.0的Action有一个弱项：一个Action只能处理一种请求，Struts 1.1中引入了一个DispatchAction，允许通过URL参数指定调用Action中的某个方法，如http://yourwebsite/fileAction.do?method=upload即调用FileAction中的upload方法。通过这种方式，我们就可以将一些相关的请求集中到一个Action当中编写，而没有必要为某个请求操作编写一个Action类。但是参数名是要在struts-config.xml中配置的：<br /><br /><table align="center" bgcolor="#e3e3e3" border="1" bordercolor="#cccccc" width="90%"><tbody><tr><td>1. ＜struts-config＞<br />2. ＜form-beans＞<br />3. ＜form-bean name="fileActionForm" type="sshfile.web.FileActionForm" /＞<br />4. ＜/form-beans＞<br />5. ＜action-mappings＞<br />6. ＜action name="fileActionForm" parameter="method" path="/fileAction"<br />7. type="sshfile.web.FileAction"＞<br />8. ＜forward name="fileListPage" path="/file-list.jsp" /＞<br />9. ＜forward name="loadAllFile" path="/fileAction.do?method=listAllFile" /＞<br />10. ＜/action＞<br />11. ＜/action-mappings＞<br />12. ＜/struts-config＞</td></tr></tbody></table><br />　　第6行的parameter="method"指定了承载方法名的参数，第9行中，我们还配置了一个调用FileAction不同方法的Action出口。<br /><br />　　FileAction共有3个请求响应的方法，它们分别是：<br /><br />　　·upload(…)：处理上传文件的请求。<br /><br />　　·listAllFile(…)：处理加载数据库表中所有记录的请求。<br /><br />　　·download（…）：处理下载文件的请求。<br /><br />　　下面我们分别对这3个请求处理方法进行讲解。<br /><br />　　2.1 上传文件<br /><br />　　上传文件的请求处理方法非常简单，简之言之，就是从Spring容器中获取业务层处理类FileService，调用其save(FileActionForm form)方法上传文件，如下所示：<br /><br /><table align="center" bgcolor="#e3e3e3" border="1" bordercolor="#cccccc" width="90%"><tbody><tr><td>1. public class FileAction<br />2. extends DispatchAction<br />3. {<br />4. //将上传文件保存到数据库中<br />5. public ActionForward upload(ActionMapping mapping, ActionForm form,<br />6. HttpServletRequest request,<br />7. HttpServletResponse response)<br />8. {<br />9. FileActionForm fileForm = (FileActionForm) form;<br />10. FileService fileService = getFileService();<br />11. fileService.save(fileForm);<br />12. return mapping.findForward("loadAllFile");<br />13. }<br />14. //从Spring容器中获取FileService对象<br />15. private FileService getFileService()<br />16. {<br />17. ApplicationContext appContext = WebApplicationContextUtils.<br />18. getWebApplicationContext(this.getServlet().getServletContext());<br />19. return (FileService) appContext.getBean("fileService");<br />20. }<br />21. …<br />22. }</td></tr></tbody></table><br />　　由于FileAction其它两个请求处理方法也需要从Spring容器中获取FileService实例，所以我们特别提供了一个getFileService()方法（第15~21行）。重构的一条原则就是："发现代码中有重复的表达式，将其提取为一个变量；发现类中有重复的代码段，将其提取为一个方法；发现不同类中有相同的方法，将其提取为一个类"。在真实的系统中，往往拥有多个Action和多个Service类，这时一个比较好的设置思路是，提供一个获取所有Service实现对象的工具类，这样就可以将Spring 的Service配置信息屏蔽在一个类中，否则Service的配置名字散落在程序各处，维护性是很差的。<br /><br />　　2.2 列出所有已经上传的文件<br /><br />　　listAllFile方法调用Servie层方法加载T_FILE表中所有记录，并将其保存在Request域中，然后forward到列表页面中：<br /><br /><table align="center" bgcolor="#e3e3e3" border="1" bordercolor="#cccccc" width="90%"><tbody><tr><td>1. public class FileAction<br />2. extends DispatchAction<br />3. {<br />4. …<br />5. public ActionForward listAllFile(ActionMapping mapping, ActionForm form,<br />6. HttpServletRequest request,<br />7. HttpServletResponse response)<br />8. throws ModuleException<br />9. {<br />10. FileService fileService = getFileService();<br />11. List fileList = fileService.getAllFile();<br />12. request.setAttribute("fileList",fileList);<br />13. return mapping.findForward("fileListPage");<br />14. }<br />15. }</td></tr></tbody></table><br />　　file-list.jsp页面使用Struts标签展示出保存在Request域中的记录：<br /><br /><table align="center" bgcolor="#e3e3e3" border="1" bordercolor="#cccccc" width="90%"><tbody><tr><td>1. ＜%@page contentType="text/html; charset=GBK"%＞<br />2. ＜%@taglib uri="/WEB-INF/struts-logic.tld" prefix="logic"%＞<br />3. ＜%@taglib uri="/WEB-INF/struts-bean.tld" prefix="bean"%＞<br />4. ＜html＞<br />5. ＜head＞<br />6. ＜title＞file-download＜/title＞<br />7. ＜/head＞<br />8. ＜body bgcolor="#ffffff"＞<br />9. ＜ol＞<br />10. ＜logic:iterate id="item" name="fileList" scope="request"＞<br />11. ＜li＞<br />12. ＜a href='fileAction.do?method=download&amp;fileId=<br />13. ＜bean:write name="item"property="fileId"/＞'＞<br />14. ＜bean:write name="item" property="fileName"/＞<br />15. ＜/a＞<br />16. ＜/li＞<br />17. ＜/logic:iterate＞<br />18. ＜/ol＞<br />19. ＜/body＞<br />20. ＜/html＞</td></tr></tbody></table><br />　　展现页面的每条记录挂接着一个链接地址，形如：fileAction.do?method=download&amp;fileId=xxx，method参数指定了这个请求由FileAction的download方法来响应，fileId指定了记录的主键。<br /><br />　　由于在FileActionForm中，我们定义了fileId的属性，所以在download响应方法中，我们将可以从FileActionForm中取得fileId的值。这里涉及到一个处理多个请求Action所对应的ActionForm的设计问题，由于原来的Action只能对应一个请求，那么原来的ActionForm非常简单，它仅需要将这个请求的参数项作为其属性就可以了，但现在一个Action对应多个请求，每个请求所对应的参数项是不一样的，此时的ActionForm的属性就必须是多请求参数项的并集了。所以，除了文件上传请求所对应的fileContent和remark属性外还包括文件下载的fileId属性：<br /><br /><table align="center" border="0" width="90%"><tbody><tr><td><div align="center"><img src="http://java.chinaitlab.com/UploadFiles_8734/200512/20051223095919511.jpg" border="0" /><br />图 7 FileActionForm</div></td></tr></tbody></table><br />　　当然这样会造成属性的冗余，比如在文件上传的请求中，只会用到fileContent和remark属性，而在文件下载的请求时，只会使用到fileId属性。但这种冗余是会带来好处的--它使得一个Action可以处理多个请求。<br /><br />　　2.3 下载文件<br /><br />　　在列表页面中点击一个文件下载，其请求由FileAction的download方法来响应，download方法调用业务层的FileService方法，获取文件数据并写出到response的响应流中。通过合理设置HTTP响应头参数，将响应流在客户端表现为一个下载文件对话框，其代码如下所示：<br /><br />　　代码 10 业务接口实现类之download<br /><br /><table align="center" bgcolor="#e3e3e3" border="1" bordercolor="#cccccc" width="90%"><tbody><tr><td>1. public class FileAction<br />2. extends DispatchAction<br />3. {<br />4. …<br />5. public ActionForward download(ActionMapping mapping, ActionForm form,<br />6. HttpServletRequest request,<br />7. HttpServletResponse response)<br />8. throws ModuleException<br />9. {<br />10. FileActionForm fileForm = (FileActionForm) form;<br />11. FileService fileService = getFileService();<br />12. String fileName = fileService.getFileName(fileForm.getFileId());<br />13. try<br />14. {<br />15. response.setContentType("application/x-msdownload");<br />16. response.setHeader("Content-Disposition",<br />17. "attachment;" + " filename="+<br />18. new String(fileName.getBytes(), "ISO-8859-1"));<br />19. fileService.write(response.getOutputStream(), fileForm.getFileId());<br />20. }<br />21. catch (Exception e)<br />22. {<br />23. throw new ModuleException(e.getMessage());<br />24. }<br />25. return null;<br />26. }<br />27. }</td></tr></tbody></table><br />　　第15~18行，设置HTTP响应头，将响应类型设置为application/x-msdownload MIME类型，则响应流在IE中将弹出一个文件下载的对话框，如图 4所示。IE所支持的MIME类型多达26种，您可以通过这个网址查看其他的MIME类型：<br /><br />http://msdn.microsoft.com/workshop/networking/moniker/overview/appendix_a.asp。<br /><br />　　如果下载文件的文件名含有中文字符，如果不对其进行硬编码，如第18行所示，客户文件下载对话框中出现的文件名将会发生乱码。<br />第19行代码获得response的输出流，作为FileServie write(OutputStream os,String fileId)的入参，这样文件的内容将写到response的输出流中。<br /><br />　　3、web.xml文件的配置<br /><br />　　Spring容器在何时启动呢？我可以在Web容器初始化来执行启动Spring容器的操作，Spring提供了两种方式启动的方法：<br /><br />　　·通过org.springframework.web.context .ContextLoaderListener容器监听器，在Web容器初始化时触发初始化Spring容器，在web.xml中通过＜listener＞＜/listener＞对其进行配置。<br /><br />　　·通过Servlet org.springframework.web.context.ContextLoaderServlet，将其配置为自动启动的Servlet，在Web容器初始化时，通过这个Servlet启动Spring容器。<br /><br />　　在初始化Spring容器之前，必须先初始化log4J的引擎，Spring也提供了容器监听器和自动启动Servlet两种方式对log4J引擎进行初始化：<br /><br />　　·org.springframework.web.util .Log4jConfigListener<br /><br />　　·org.springframework.web.util.Log4jConfigServlet<br /><br />　　下面我们来说明如何配置web.xml启动Spring容器：<br /><br />　　代码 11 web.xml中对应Spring的配置内容<br /><br /><table align="center" bgcolor="#e3e3e3" border="1" bordercolor="#cccccc" width="90%"><tbody><tr><td>1. ＜web-app＞<br />2. ＜context-param＞<br />3. ＜param-name＞contextConfigLocation＜/param-name＞<br />4. ＜param-value＞/WEB-INF/applicationContext.xml＜/param-value＞<br />5. ＜/context-param＞<br />6. ＜context-param＞<br />7. ＜param-name＞log4jConfigLocation＜/param-name＞<br />8. ＜param-value＞/WEB-INF/log4j.properties＜/param-value＞<br />9. ＜/context-param＞<br />10. ＜servlet＞<br />11. ＜servlet-name＞log4jInitServlet＜/servlet-name＞<br />12. ＜servlet-class＞org.springframework.web.util.Log4jConfigServlet＜/servlet-class＞<br />13. ＜load-on-startup＞1＜/load-on-startup＞<br />14. ＜/servlet＞<br />15. ＜servlet＞<br />16. ＜servlet-name＞springInitServlet＜/servlet-name＞<br />17. ＜servlet-class＞org.springframework.web.context.ContextLoaderServlet＜/servlet-class＞<br />18. ＜load-on-startup＞2＜/load-on-startup＞<br />19. ＜/servlet＞<br />20. …<br />21. ＜/web-app＞</td></tr></tbody></table><br />　　启动Spring容器时，需要得到两个信息：Spring配置文件的地址和Log4J属性文件，这两上信息分别通过contextConfigLocationWeb和log4jConfigLocation容器参数指定，如果有多个Spring配置文件，则用逗号隔开，如：<br /><br />/WEB-INF/applicationContext_1.xml, /WEB-INF/applicationContext_1.xm2<br /><br />　　由于在启动ContextLoaderServlet之前，必须事先初始化Log4J的引擎，所以Log4jConfigServlet必须在ContextLoaderServlet之前启动，这通过＜load-on-startup＞来指定它们启动的先后顺序。<br /><br />　　乱码是开发Web应用程序一个比较老套又常见问题，由于不同Web应用服务器的默认编码是不一样的，为了方便Web应用在不同的Web应用服务器上移植，最好的做法是Web程序自身来处理编码转换的工作。经典的作法是在web.xml中配置一个编码转换过滤器，Spring就提供了一个编码过滤器类CharacterEncodingFilter，下面，我们为应用配置上这个过滤器：<br /><br /><table align="center" bgcolor="#e3e3e3" border="1" bordercolor="#cccccc" width="90%"><tbody><tr><td>1. ＜web-app＞<br />2. …<br />3. ＜filter＞<br />4. ＜filter-name＞encodingFilter＜/filter-name＞<br />5. ＜filter-class＞org.springframework.web.filter.CharacterEncodingFilter＜/filter-class＞<br />6. ＜init-param＞<br />7. ＜param-name＞encoding＜/param-name＞<br />8. ＜param-value＞GBK＜/param-value＞<br />9. ＜/init-param＞<br />10. ＜/filter＞<br />11. ＜filter-mapping＞<br />12. ＜filter-name＞encodingFilter＜/filter-name＞<br />13. ＜url-pattern＞/*＜/url-pattern＞<br />14. ＜/filter-mapping＞<br />15. …<br />16. ＜/web-app＞</td></tr></tbody></table><br />　　Spring的过滤器类是org.springframework.web.filter.CharacterEncodingFilter，通过encoding参数指定编码转换类型为GBK，＜filter-mapping＞的配置使该过滤器截获所有的请示。<br /><br />　　Struts的框架也需要在web.xml中配置，想必读者朋友对Struts的配置都很熟悉，故在此不再提及，请参见本文所提供的源码。<br /><br />　　<b>总结</b><br /><br />　　本文通过一个文件上传下载的Web应用，讲解了如何构建基于SSH的Web应用，通过Struts和FormFile，Spring的LobHandler以及Spring为HibernateBlob处理所提供的用户类BlobByteArrayType ，实现上传和下载文件的功能仅需要廖廖数行的代码即告完成。读者只需对程序作稍许的调整，即可处理Clob字段：<br /><br />　　·领域对象对应Clob字段的属性声明为String类型；<br /><br />　　·映射文件对应Clob字段的属性声明为org.springframework.orm.hibernate3.support.ClobStringType类型。<br /><br />　　本文通过SSH对文件上传下载简捷完美的实现得以管中窥豹了解SSH强强联合构建Web应用的强大优势。在行文中，还穿插了一些分层的设计经验，配置技巧和Spring所提供的方便类，相信这些知识对您的开发都有所裨益。<img src ="http://www.blogjava.net/ltc603/aggbug/27966.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ltc603/" target="_blank">阿成</a> 2006-01-13 20:31 <a href="http://www.blogjava.net/ltc603/archive/2006/01/13/27966.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>用struts+spring+hibernate组装web应用</title><link>http://www.blogjava.net/ltc603/archive/2006/01/12/27763.html</link><dc:creator>阿成</dc:creator><author>阿成</author><pubDate>Thu, 12 Jan 2006 07:20:00 GMT</pubDate><guid>http://www.blogjava.net/ltc603/archive/2006/01/12/27763.html</guid><wfw:comment>http://www.blogjava.net/ltc603/comments/27763.html</wfw:comment><comments>http://www.blogjava.net/ltc603/archive/2006/01/12/27763.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ltc603/comments/commentRss/27763.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ltc603/services/trackbacks/27763.html</trackback:ping><description><![CDATA[　其实，就算用Java建造一个不是很烦琐的web应用程序，也不是件轻松的事情。当为一个应用程序建造一个构架时有许多事情需要考虑。从高层来说，开发者需要考虑：怎样建立用户接口（user interfaces）？在哪里处理业务逻辑？和怎样持久化应用数据。这三层每一层都有它们各自的问题需要回答。 各个层次应该使用什么技术？怎样才能把应用程序设计得松耦合和能灵活改变？构架允许层的替换不会影响到其它层吗？应用程序怎样处理容器级的服务（container level services），比如事务处理（transactions）？ <br /><br />　　当为你的web应用程序创建一个构架时，需要涉及到相当多的问题。幸运的是，已经有不少开发者已经遇到过这类重复发生的问题，并且建立了处理这类问题的框架。一个好框架具备以下几点： 减轻开发者处理复杂的问题的负担（“不重复发明轮子”）；内部定义为可扩展的；有一个强大的用户群支持。框架通常能够很好的解决一方面的问题。然而，你的应用程序有几个层可能都需要它们各自的框架。就如解决你的用户接口（UI）问题时你就不应该把事务逻辑和持久化逻辑掺杂进来。例如，你不应该在控制器（controller）里面写jdbc代码，使它包含有业务逻辑，这不是控制器应该提供的功能。它应该是轻量级的，代理来自用户接口（UI）外的调用请求给其它服务于这些请求的应用层。好的框架自然的形成代码如何分布的指导。更重要的是，框架减轻开发者从头开始写像持久层这样的代码的痛苦，使他们专注于对客户来说很重要的应用逻辑。 <br /><br />　　这篇文章将讨论怎样组合几个著名的框架去做到松耦合的目的，怎样建立你的构架，怎样让你的各个应用层保持一致。富于挑战的是：组合这些框架使得每一层都以一种松耦合的方式彼此沟通，而与底层的技术无关。这篇文章将使用３种流行的开源框架来讨论组合框架的策略。表现层我们将使用Struts（http://jakarta.apache.org/struts）;业务层我们将使用Spring（http://www.springframework.org/）;持久层使用Hibrenate（http://www.hibernate.org/）.你也可以在你的应用程序中替换这些框架中的任何一种而得到同样的效果。图１展示了当这些框架组合在一起时从高层看是什么样子。 <br /><br /><img src="http://www.onjava.com/onjava/2004/04/07/graphics/wiring.gif" border="0" /><br />图１用Struts, Spring, 和 Hibernate框架构建的概览 <br /><br />应用程序的分层 （Application Layering） <br /><br />大多数不复杂的web应用都能被分成至少４个各负其责的层次。这些层次是：表现层（presentation）、持久层（persistence）、业务层（business）、领域模型层（domain model）。每层在应用程序中都有明确的责任，不应该和其它层混淆功能。每一应用层应该彼此独立但要给他们之间放一个通讯接口。让我们从审视各个层开始，讨论这些层应该提供什么和不应该提供什么。 <br /><br />表现层 (The Presentation Layer) <br /><br />　　在一个典型的web应用的一端是表现层。很多Java开发者也理解Struts所提供的。然而，太常见的是，他们把像业务逻辑之类的耦合的代码放进了一个org.apache.struts.Action。所以，让我们在像Struts这样一个框架应该提供什么上取得一致意见。这儿是Struts负责的： <br /><br />为用户管理请求和响应； <br />提供一个控制器（controller）代理调用业务逻辑和其它上层处理； <br />处理从其它层掷出给一个Struts Action的异常； <br />为显示提供一个模型； <br />执行用户接口（UI）验证。 <br /><br />这儿是一些经常用Struts编写的但是却不应该和Struts表现层相伴的项目： <br />直接和数据库通讯，比如JDBC调用； <br />业务逻辑和与你的应用程序相关的验证； <br />事务管理； <br />在表现层中引入这种代码将导致典型耦合（type coupling）和讨厌的维护。 <br /><br />持久层 （The Persistence Layer ） <br /><br />　　在典型web应用的另一端是持久层。这通常是使事情迅速失控的地方。开发者低估了构建他们自己的持久层框架的挑战性。一般来说，机构内部自己写的持久层不仅需要大量的开发时间，而且还经常缺少功能和变得难以控制。有几个开源的“对象－关系映射”（ORM）框架非常解决问题。尤其是，Hibernate框架为java提供了＂对象－关系持久化＂（object-to-relational persistence）机制和查询服务。Hibernate对那些已经熟悉了SQL和JDBC API的Java开发者有一个适中的学习曲线。Hibernate持久对象是基于简单旧式Java对象（POJO）和Java集合（Java collections）。此外，使用Hibernate并不妨碍你正在使用的IDE。下面的列表包含了你该写在一个持久层框架里的代码类型： <br /><br />　　查询相关的信息成为对象。Hibernate通过一种叫作HQL的面向对象（OO）的查询语言或者使用条件表达式API（expressive criteria API）来做这个事情。 HQL非常类似于SQL-- 只是把SQL里的table和columns用Object和它的fields代替。有一些新的专用的HQL语言成分要学；不过，它们容易理解而且文档做得好。HQL是一种使用来查询对象的自然语言，花很小的代价就能学习它。 <br /><br />保存、更新、删除储存在数据库中的信息。 <br /><br />像Hibernate这样的高级“对象－关系”映射（object-to-relational mapping）框架提供对大多数主流SQL数据库的支持，它们支持“父/子”（parent/child）关系、事务处理、继承和多态。 <br /><br /><br />这儿是一些应该在持久层里被避免的项目： <br /><br /><br />业务逻辑应该在你的应用的一个高一些的层次里。持久层里仅仅允许数据存取操作。 <br /><br />　　你不应该把持久层逻辑（persistence logic）和你的表现层逻辑（presentation logic）搅在一起。避免像JSPs或基于servlet的类这些表现层组件里的逻辑和数据存取直接通讯。通过把持久层逻辑隔离进它自己的层，应用程序变得易于修改而不会影响在其它层的代码。例如：Hebernate能够被其它持久层框架或者API代替而不会修改在其它任何层的代码。 <br /><br /><br />业务层（The Business Layer） <br /><br />　　在一个典型的web应用程序的中间的组件是业务层或服务层。从编码的视角来看，这个服务层是最容易被忽视的一层。不难在用户接口（UI）层或者持久层里找到散布在其中的这种类型的代码。这不是正确的地方，因为这导致了应用程序的紧耦合，这样一来，随着时间推移代码将很难维护。幸好，针对这一问题有好几种Frameworks存在。在这个领域两个最流行的框架是Spring和PicoContainer，它们叫作微容器（microcontainers），你可以不费力不费神的把你的对象连在一起。所有这些框架都工作在一个简单的叫作“依赖注入”（dependency injection）（也通称“控制反转”（inversion of control））的概念上。这篇文章将着眼于Spring的为指定的配置参数通过bean属性的setter注入（setter injection）的使用。Spring也提供了一个构建器注入（constructor injection）的复杂形式作为setter注入的一个替代。对象们被一个简单的XML文件连在一起，这个XML文件含有到像事务管理器（transaction management handler）、对象工厂（object factories）、包含业务逻辑的服务对象（service objects）、和数据存取对象（DAO）这些对象的引用（references）。 <br /><br />　　这篇文章的后面将用例子来把Spring使用这些概念的方法说得更清楚一些。业务层应该负责下面这些事情： <br />处理应用程序的业务逻辑和业务验证； <br />管理事务； <br />预留和其它层交互的接口； <br />管理业务层对象之间的依赖； <br />增加在表现层和持久层之间的灵活性，使它们互不直接通讯； <br />从表现层中提供一个上下文（context）给业务层获得业务服务（business services ）； <br />管理从业务逻辑到持久层的实现。 <br /><br />领域模型层 （The Domain Model Layer ） <br />　　最后，因为我们讨论的是一个不是很复杂的、基于web的应用程序，我们需要一组能在不同的层之间移动的对象。领域对象层由那些代表现实世界中的业务对象的对象们组成，比如：一份订单（Order）、订单项（OrderLineItem）、产品（Product）等等。这个层让开发者停止建立和维护不必要的数据传输对象（或者叫作DTOs）,来匹配他们的领域对象。例如，Hibernate允许你把数据库信息读进领域对象（domain objects）的一个对象图，这样你可以在连接断开的情况下把这些数据显示到UI层。那些对象也能被更新和送回到持久层并在数据库里更新。而且，你不必把对象转化成DTOs，因为DTOs在不同的应用层间移动，可能在转换中丢失。这个模型使得Java开发者自然地以一种面向对象的风格和对象打交道，没有附加的编码。 <br /><br />结合一个简单的例子 <br /><br />　　既然我们已经从一个高的层次上理解了这些组件， 现在就让我们开始实践吧。在这个例子中，我们还是将合并Struts、Spring、Hibernate框架。每一个这些框架在一篇文章中都有太多的细节覆盖到。这篇文章将用一个简单的例子代码展示怎样把它们结合在一起，而不是进入每个框架的许多细节。示例应用程序将示范一个请求怎样跨越每一层被服务的。这个示例应用程序的一个用户能保存一个订单到数据库中和查看一个在数据库中存在的订单。进一步的增强可以使用户更新或删除一个存在的订单。　　 <br /><br />你可以下载这个应用的源码（http://www.onjava.com/onjava/2004/04/07/examples/wiring.zip）。 <br /><br />　　因为领域对象（domain objects）将和每一层交互，我们将首先创建它们。这些对象将使我们定义什么应该被持久化，什么业务逻辑应该被提供，和哪种表现接口应该被设计。然后，我们将配置持久层和用Hibernate为我们的领域对象（domain objects）定义“对象-关系”映射（object-to-relational mappings）。然后，我们将定义和配置我们的业务对象（business objects）。在有了这些组件后，我们就能讨论用Spring把这些层连在一起。最后，我们将提供一个表现层（presentation layer），它知道怎样和业务服务层（business service layer）交流和知道怎样处理从其它层产生的异常（exceptions）。 <br /><br />领域对象层（Domain Object Layer） <br /><br />　　因为这些对象将和所有层交互，这也许是一个开始编码的好地方。这个简单的领域模型将包括一个代表一份订单（order）的对象和一个代表一个订单项（line item for an order）的对象。订单（order）对象将和一组订单项（a collection of line item）对象有一对多（one-to-many）的关系。例子代码在领域层有两个简单的对象： <br />com.meagle.bo.Order.java: 包括一份订单（oder）的概要信息； <br />com.meagle.bo.OrderLineItem.java: 包括一份订单（order）的详细信息； <br />考虑一下为你的对象选择包名，它将反映你的应用程序是怎样分层的。例如：简单应用的领域对象（domain objects）可以放进com.meagle.bo包［译者注：bo-business object？］。更多专门的领域对象将放入在com.meagle.bo下面的子包里。业务逻辑在com.meagle.service包里开始打包，DAO对象放进com.meagle.service.dao.hibernate包。对于forms和actions的表现类（presentation classes）分别放入com.meagle.action 和 com.meagle.forms包。准确的包命名为你的类提供的功能提供一个清楚的区分，使当故障维护时更易于维护，和当给应用程序增加新的类或包时提供一致性。 <br /><br /><br />持久层配置（Persistence Layer Configuration） <br /><br />　　用Hibernate设置持久层涉及到几个步骤。第一步是进行配置持久化我们的领域业务对象（domain business objects ）。因为我们用于领域对象（domain objects ）持久化的Hibernate和POJOs一起工作（ 此句原文：Since Hibernate works with POJOs we will use our domain objects for persistence.），因此，订单和订单项对象包括的所有的字段的都需要提供getter和setter方法。订单对象将包括像ID、用户名、合计、和订单项这样一些字段的标准的JavaBean格式的setter和getter方法。订单项对象将同样的用JavaBean的格式为它的字段设置setter和getter方法。 <br />　　Hibernate在XML文件里映射领域对象到关系数据库。订单和订单项对象将有两个映射文件来表达这种映射。有像XDoclet（http://xdoclet.sourceforge.net/）这样的工具来帮助这种映射。Hibernate将映射领域对象到这些文件： <br />Order.hbm.xml <br />OrderLineItem.hbm.xml <br />你可以在WebContent/WEB-INF/classes/com/meagle/bo目录里找到这些生成的文件。配置Hibernate SessionFactory（http://www.hibernate.org/hib_docs/api/net/sf/hibernate/SessionFactory.html）使它知道是在和哪个数据库通信，使用哪个数据源或连接池，加载哪些持久对象。SessionFactory提供的Session（http://www.hibernate.org/hib_docs/api/net/sf/hibernate/Session.html）对象是Java对象和像选取、保存、更新、删除对象这样一些持久化功能间的翻译接口。我们将在后面的部分讨论Hibernate操作Session对象需要的SessionFactory配置。 <br /><br />业务层配置（Business Layer Configuration ） <br /><br />　　既然我们已经有了领域对象（domain objects），我们需要有业务服务对象来执行应用逻辑、执行向持久层的调用、获得从用户接口层（UI layer）的请求、处理事务、处理异常。为了将所有这些连接起来并且易于管理，我们将使用Spring框架的bean管理方面（bean management aspect）。Spring使用“控制反转”（IoC）,或者“setter依赖注入”来把这些对象连好，这些对象在一个外部的XML文件中被引用。“控制反转”是一个简单的概念，它允许对象接受其它的在一个高一些的层次被创建的对象。使用这种方法，你的对象从必须创建其它对象中解放出来并降低对象耦合。 <br /><br />　　这儿是个不使用IoC的对象创建它的从属对象（ object creating its dependencies without IoC）的例子，这导致紧的对象耦合： <br /><br /><img src="http://www.onjava.com/onjava/2004/04/07/graphics/nonioc.gif" border="0" /><br />图２：没有使用IoC的对象组织。对象Ａ创建对象Ｂ和Ｃ。 <br /><br />　　这儿是一个使用IoC的例子，它允许对象在一个高一些层次被创建和传进另外的对象，所以另外的对象能直接使用现成的对象·［译者注：另外的对象不必再亲自创建这些要使用的对象］（allows objects to be created at higher levels and passed into objects so that they can use the implementations directly）： <br /><br /><img src="http://www.onjava.com/onjava/2004/04/07/graphics/ioc.gif" border="0" /><br />　图３：对象使用IoC组织。对象Ａ包含setter方法，它们接受到对象Ｂ和Ｃ的接口。这也可以用对象Ａ里的接受对象Ｂ和Ｃ的构建器完成。 <br /><br />建立我们的业务服务对象（Building Our Business Service Objects） <br />　　我们将在我们的业务对象中使用的setter方法接受的是接口，这些接口允许对象的松散定义的实现，这些对象将被设置或者注入。在我们这个例子里我们将使我们的业务服务对象接受一个DAO去控制我们的领域对象的持久化。当我们在这篇文章的例子中使用Hibernate（ While the examples in this article use Hibernate），我们可以容易的转换到一个不同的持久框架的实现，通知Spring使用新的实现的DAO对象。你能明白编程到接口和使用“依赖注入”模式是怎样宽松耦合你的业务逻辑和你的持久化机制的。 <br />　　这儿是业务服务对象的接口，它是一个DAO对象依赖的桩。（Here is the interface for the business service object that is stubbed for a DAO object dependency: ） <br /><br /><br /><br />public interface IOrderService { public abstract Order saveNewOrder(Order order) throws OrderException, OrderMinimumAmountException; public abstract List findOrderByUser( String user) throws OrderException; public abstract Order findOrderById(int id) throws OrderException; public abstract void setOrderDAO( IOrderDAO orderDAO); } <br /><br /><br />　　注意上面的代码有一个为DAO对象准备的setter方法。这儿没有一个getOrderDAO方法因为它不是必要的，因为不太有从外面访问连着的OrderDAO对象的需要。DAO对象将被用来和我们的持久层沟通。我们将用Spring把业务服务对象和DAO对象连在一起。因为我们编码到接口，我们不会紧耦合实现。 <br /><br />下一步是写我们的DAO实现对象。因为Spring有内建的对Hibernate的支持，这个例子DAO将继承HibernateDaoSupport（http://www.springframework.org/docs/api/org/springframework/ <br />orm/hibernate/support/HibernateDaoSupport.html）类，这使得我们容易取得一个到HibernateTemplate（http://www.springframework.org/docs/api/org/springframework/ <br />orm/hibernate/HibernateTemplate.html）类的引用，HibernateTemplate是一个帮助类，它能简化Hibernate Session的编码和处理HibernateExceptions。这儿是DAO的接口： <br /><br /><br /><br />public interface IOrderDAO { public abstract Order findOrderById( final int id); public abstract List findOrdersPlaceByUser( final String placedBy); public abstract Order saveOrder( final Order order); } <br /><br /><br />　　我们还有两个对象要和我们的业务层连在一起。这包括HibernateSessionFactory和一个TransactionManager对象。这在Spring配置文件里直接完成。Spring提供一个HibernateTransactionManager（http://www.springframework.org/docs/api/org/springframework/ <br />orm/hibernate/HibernateTransactionManager.html），它将从工厂绑定一个Hibernate Session到一个线程来支持事务（见ThreadLocal（http://java.sun.com/j2se/1.4.2/docs/api/java/lang/ThreadLocal.html）获取更多的信息）。这儿是HibernateSessionFactory和HibernateTransactionManager的Spring配置。 <br /><br />class="org.springframework.orm.hibernate. LocalSessionFactoryBean"&gt; com/meagle/bo/Order.hbm.xml com/meagle/bo/OrderLineItem.hbm.xml net.sf.hibernate.dialect.MySQLDialect false C:/MyWebApps/.../WEB-INF/proxool.xml spring class="org. springframework. orm. hibernate. HibernateTransactionManager"&gt; <br /><br />　　每一个对象能被Spring配置里的一个标记引用。在这个例子里，bean “mySessionFactory”代表一个HibernateSessionFactory，bean “myTransactionManager”代表一个Hibernate transaction manager。注意transactionManger bean有一个叫作sessionFactory的属性元素。HibernateTransactionManager有一个为sessionFactory准备的setter和getter方法，它们是用来当Spring容器启动时的依赖注入。sessionFactory属性引用mySessionFactory bean。这两个对象现在当Spring容器初始化时将被连在一起。这种连接把你从为引用和创建这些对象而创建singleton对象和工厂中解放出来，这减少了你应用程序中的代码维护。mySessionFactory bean有两个属性元素,它们翻译成为mappingResources 和 hibernatePropertes准备的setter方法。通常，如果你在Spring之外使用Hibernate,这个配置将被保存在hibernate.cfg.xml文件中。不管怎样,Spring提供了一个便捷的方式--在Spring配置文件中合并Hibernate的配置。获得更多的信息查阅Spring API（http://www.springframework.org/docs/api/index.html）。 <br /><br />既然我们已经配置了我们的容器服务beans和把它们连在了一起，我们需要把我们的业务服务对象和我们的DAO对象连在一起。然后，我们需要把这些对象连接到事务管理器。 <br /><br />这是在Spring配置文件里的样子： <br /><br /><br />class="org. springframework. transaction. interceptor. TransactionProxyFactoryBean"&gt; PROPAGATION_REQUIRED,readOnly,-OrderException PROPAGATION_REQUIRED,-OrderException class="com. meagle. service. spring. OrderServiceSpringImpl"&gt; class="com. meagle. service. dao. hibernate. OrderHibernateDAO"&gt; <br /><br /><br />图４是我们已经连在一起的东西的一个概览。它展示了每个对象是怎样相关联的和怎样被Spring设置进其它对象中。把这幅图和示例应用中的Spring配置文件对比查看它们之间的关系。 <br /><br /><img src="http://www.onjava.com/onjava/2004/04/07/graphics/spring_wiring.gif" border="0" /><br />图４：这是Spring怎样将在这个配置的基础上装配beans。 <br /><br />　　这个例子使用一个TransactionProxyFactoryBean，它有一个为我们已经定义了的事务管理者准备的setter方法。这是一个有用的对象，它知道怎样处理声明的事务操作和你的服务对象。你可以通过transactionAttributes属性定义事务怎样被处理，transactionAttributes属性为方法名定义模式和它们怎样参与进一个事务。获得更多的关于在一个事务上配置隔离层和提交或回滚查阅TransactionAttributeEditor（http://www.springframework.org/docs/api/org/springframework/ <br />transaction/interceptor/TransactionAttributeEditor.html）。 <br /><br />　　TransactionProxyFactoryBean（http://www.springframework.org/docs/api/org/springframework/ <br />transaction/interceptor/TransactionProxyFactoryBean.html）类也有一个为一个target准备的setter,target将是一个到我们的叫作orderTarget的业务服务对象的引用（a reference）。 orderTarget bean定义使用哪个业务服务对象并有一个指向setOrderDAO()的属性。orderDAO bean将居于这个属性中，orderDAO bean是我们的和持久层交流的DAO对象。 <br /><br />　　还有一个关于Spring和bean要注意的是bean能以两种模式工作。这两种模式被定义为singleton和prototype。一个bean默认的模式是singleton，意味着一个共享的bean的实例将被管理。这是用于无状态操作--像一个无状态会话bean将提供的那样。当bean由Spring提供时，prototype模式允许创建bean的新实例。你应当只有在每一个用户都需要他们自己的bean的拷贝时才使用prototype模式。 <br /><br />提供一个服务定位器（Providing a Service Locator） <br />　　既然我们已经把我们的服务和我们的DAO连起来了，我们需要把我们的服务暴露给其它层。通常是一个像使用Struts或Swing这样的用户接口层里的代码来使用这个服务。一个简单的处理方法是使用一个服务定位器模式的类从一个Spring上下文中返回资源。这也可以靠引用bean ID通过Spring来直接完成。 <br />　　这儿是一个在Struts Action中怎样配置一个服务定位器的例子： <br /><br /><br /><br />public abstract class BaseAction extends Action { private IOrderService orderService; public void setServlet(ActionServlet actionServlet) { super.setServlet(actionServlet); ServletContext servletContext = actionServlet.getServletContext(); WebApplicationContext wac = WebApplicationContextUtils. getRequiredWebApplicationContext( servletContext); this.orderService = (IOrderService) wac.getBean("orderService"); } protected IOrderService getOrderService() { return orderService; } } <br />用户接口层配置 （UI Layer Configuration） 　　示例应用的用户接口层使用Struts框架。这儿我们将讨论当为一个应用分层时和Struts相关的部分。让我们从在struts-config.xml文件里检查一个Action配置开始。 <br />type="com.meagle.action.SaveOrderAction" name="OrderForm" scope="request" validate="true" input="/NewOrder.jsp"&gt; Save New Order path="/NewOrder.jsp" scope="request" type="com.meagle.exception.OrderException"/&gt; path="/NewOrder.jsp" scope="request" type="com. meagle. exception. OrderMinimumAmountException"/&gt; <br /><br /><br />　　SaveNewOrder Action被用来持久化一个用户从用户接口层提交的订单。这是一个典型的Struts Action；然而，注意这个action的异常配置。这些Exceptions为我们的业务服务对象也在Spring 配置文件(applicationContext-hibernate.xml)中配置了（在transactionAttributes属性里）。当这些异常被从业务层掷出我们能在我们的用户接口里恰当的处理它们。第一个异常，OrderException，当在持久层里保存订单对象失败时将被这个action使用。这将引起事务回滚和通过业务对象传递把异常传回给Struts层。OrderMinimumAmountException，在业务对象逻辑里的一个事务因为提交的订单达不到最小订单数量而失败也将被处理。然后，事务将回滚和这个异常能被用户接口层恰当的处理。 <br /><br />　　最后一个连接步骤是使我们的表现层和我们的业务层交互。这已经通过使用前面讨论的服务定位器来完成了。服务层充当一个到我们的业务逻辑和持久层的接口。这儿是 Struts中的SaveNewOrder Action可能怎样使用一个服务定位器调用一个业务方法： <br /><br /><br /><br />public ActionForward execute( ActionMapping mapping, ActionForm form, javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws java.lang.Exception { OrderForm oForm = (OrderForm) form; // Use the form to build an Order object that // can be saved in the persistence layer. // See the full source code in the sample app. // Obtain the wired business service object // from the service locator configuration // in BaseAction. // Delegate the save to the service layer and // further upstream to save the Order object. getOrderService().saveNewOrder(order); oForm.setOrder(order); ActionMessages messages = new ActionMessages(); messages.add( ActionMessages.GLOBAL_MESSAGE, new ActionMessage( "message.order.saved.successfully")); saveMessages(request, messages); return mapping.findForward("success"); } <br /><br /><br />结论 <br />　　这篇文章按照技术和架构覆盖了许多话题。从中而取出的主要思想是怎样更好的给你的应用程序分层：用户接口层、持久逻辑层、和其它任何你需要的应用层。这样可以解耦你的代码，允许添加新的代码组件，使你的应用在将来更易维护。这里覆盖的技术能很好的解决这类的问题。不管怎样，使用这样的构架可以让你用其他技术代替现在的层。<span class="postbody"><br /></span><img src ="http://www.blogjava.net/ltc603/aggbug/27763.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ltc603/" target="_blank">阿成</a> 2006-01-12 15:20 <a href="http://www.blogjava.net/ltc603/archive/2006/01/12/27763.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>