﻿<?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/sgsoft/category/6.html</link><description>JAVA站</description><language>zh-cn</language><lastBuildDate>Tue, 27 Feb 2007 08:49:38 GMT</lastBuildDate><pubDate>Tue, 27 Feb 2007 08:49:38 GMT</pubDate><ttl>60</ttl><item><title>提升 JSP 应用程序的七大秘籍绝招</title><link>http://www.blogjava.net/sgsoft/articles/2378.html</link><dc:creator>海天一鸥</dc:creator><author>海天一鸥</author><pubDate>Wed, 23 Mar 2005 09:21:00 GMT</pubDate><guid>http://www.blogjava.net/sgsoft/articles/2378.html</guid><wfw:comment>http://www.blogjava.net/sgsoft/comments/2378.html</wfw:comment><comments>http://www.blogjava.net/sgsoft/articles/2378.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/sgsoft/comments/commentRss/2378.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/sgsoft/services/trackbacks/2378.html</trackback:ping><description><![CDATA[你时常被客户抱怨JSP页面响应速度很慢吗？你想过当客户访问次数剧增时，你的WEB应用能承受日益增加的访问量吗？本文讲述了调整JSP和servlet的一些非常实用的方法，它可使你的servlet和JSP页面响应更快，扩展性更强。而且在用户数增加的情况下，系统负载会呈现出平滑上长的趋势。在本文中，我将通过一些实际例子和配置方法使得你的应用程序的性能有出人意料的提升。<BR><BR>其中，某些调优技术是在你的编程工作中实现的。而另一些技术是与应用服务器的配置相关的。在本文中，我们将详细地描述怎样通过调整servlet和JSP页面，来提高你的应用程序的总体性能。在阅读本文之前，假设你有基本的servlet和JSP的知识。<BR><BR>方法一：在servlet的init()方法中缓存数据<BR><BR>当应用服务器初始化servlet实例之后，为客户端请求提供服务之前，它会调用这个servlet的init()方法。在一个servlet的生命周期中，init()方法只会被调用一次。通过在init()方法中缓存一些静态的数据或完成一些只需要执行一次的、耗时的操作，就可大大地提高系统性能。<BR><BR>例如，通过在init()方法中建立一个JDBC连接池是一个最佳例子，假设我们是用jdbc2.0的DataSource接口来取得数据库连接，在通常的情况下，我们需要通过JNDI来取得具体的数据源。我们可以想象在一个具体的应用中，如果每次SQL请求都要执行一次JNDI查询的话，那系统性能将会急剧下降。解决方法是如下代码，它通过缓存DataSource，使得下一次SQL调用时仍然可以继续利用它：<BR><BR>
<CENTER><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><CCID_CODE>public class ControllerServlet extends HttpServlet
{
　private javax.sql.DataSource testDS = null; 
　public void init(ServletConfig config) throws ServletException
　{
　　super.init(config); 
　　Context ctx = null;
　　try
　　{ 
　　　ctx = new InitialContext();
　　　testDS = (javax.sql.DataSource)ctx.lookup("jdbc/testDS");
　　}
　　catch(NamingException ne)
　　{
　　　ne.printStackTrace(); 
　　}
　　catch(Exception e)
　　{
　　　e.printStackTrace();
　　}
　}

　public javax.sql.DataSource getTestDS()
　{
　　return testDS;
　}
　...
　... 
}</CCID_CODE></PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER><BR><BR>方法 2:禁止servlet和JSP 自动重载(auto-reloading) <BR><BR>Servlet/JSP提供了一个实用的技术，即自动重载技术，它为开发人员提供了一个好的开发环境，当你改变servlet和JSP页面后而不必重启应用服务器。然而，这种技术在产品运行阶段对系统的资源是一个极大的损耗，因为它会给JSP引擎的类装载器(classloader)带来极大的负担。因此关闭自动重载功能对系统性能的提升是一个极大的帮助。 <BR><BR>方法 3: 不要滥用HttpSession <BR><BR>在很多应用中，我们的程序需要保持客户端的状态，以便页面之间可以相互联系。但不幸的是由于HTTP具有天生无状态性，从而无法保存客户端的状态。因此一般的应用服务器都提供了session来保存客户的状态。在JSP应用服务器中，是通过HttpSession对像来实现session的功能的，但在方便的同时，它也给系统带来了不小的负担。因为每当你获得或更新session时，系统者要对它进行费时的序列化操作。你可以通过对HttpSession的以下几种处理方式来提升系统的性能。<BR><BR>如果没有必要，就应该关闭JSP页面中对HttpSession的缺省设置。 如果你没有明确指定的话，每个JSP页面都会缺省地创建一个HttpSession。如果你的JSP中不需要使用session的话，那可以通过如下的JSP页面指示符来禁止它：<BR><BR>
<CENTER><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><CCID_CODE>＜%@ page session="false"%＞</CCID_CODE></PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER><BR><BR>不要在HttpSession中存放大的数据对像：如果你在HttpSession中存放大的数据对像的话，每当对它进行读写时，应用服务器都将对其进行序列化，从而增加了系统的额外负担。你在HttpSession中存放的数据对像越大，那系统的性能就下降得越快。 <BR><BR>当你不需要HttpSession时，尽快地释放它：当你不再需要session时，你可以通过调用HttpSession.invalidate()方法来释放它。尽量将session的超时时间设得短一点：在JSP应用服务器中，有一个缺省的session的超时时间。当客户在这个时间之后没有进行任何操作的话，系统会将相关的session自动从内存中释放。超时时间设得越大，系统的性能就会越低，因此最好的方法就是尽量使得它的值保持在一个较低的水平。 <BR><BR>方法 4: 将页面输出进行压缩 <BR><BR>压缩是解决数据冗余的一个好的方法，特别是在网络带宽不够发达的今天。有的浏览器支持gzip(GNU zip)进行来对HTML文件进行压缩，这种方法可以戏剧性地减少HTML文件的下载时间。因此，如果你将servlet或JSP页面生成的HTML页面进行压缩的话，那用户就会觉得页面浏览速度会非常快。但不幸的是，不是所有的浏览器都支持gzip压缩，但你可以通过在你的程序中检查客户的浏览器是否支持它。下面就是关于这种方法实现的一个代码片段： <BR><BR>
<CENTER><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><CCID_CODE>public void doGet(HttpServletRequest request, 
HttpServletResponse response)
throws IOException, ServletException 
{
　OutputStream out = null
　String encoding = request.getHeader("Accept-Encoding"); 
　if (encoding != null &amp;&amp; encoding.indexOf("gzip") != -1)
　{
　　request.setHeader("Content-Encoding" , "gzip");
　　out = new GZIPOutputStream(request.getOutputStream());
　}
　else if (encoding != null &amp;&amp; encoding.indexOf("compress") != -1)
　{
　　request.setHeader("Content-Encoding" , "compress");
　　out = new ZIPOutputStream(request.getOutputStream());
　} 
　else
　{
　　out = request.getOutputStream();
　}
　...
　... 
}</CCID_CODE></PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER><BR><BR></CCID_NOBR>方法 5: 使用线程池<BR><BR>应用服务器缺省地为每个不同的客户端请求创建一个线程进行处理，并为它们分派service()方法，当service()方法调用完成后，与之相应的线程也随之撤消。由于创建和撤消线程会耗费一定的系统资源，这种缺省模式降低了系统的性能。但所幸的是我们可以通过创建一个线程池来改变这种状况。<BR><BR>另外，我们还要为这个线程池设置一个最小线程数和一个最大线程数。在应用服务器启动时，它会创建数量等于最小线程数的一个线程池，当客户有请求时，相应地从池从取出一个线程来进行处理，当处理完成后，再将线程重新放入到池中。如果池中的线程不够地话，系统会自动地增加池中线程的数量，但总量不能超过最大线程数。通过使用线程池，当客户端请求急剧增加时，系统的负载就会呈现的平滑的上升曲线，从而提高的系统的可伸缩性。<BR><BR>方法 6: 选择正确的页面包含机制<BR><BR>在JSP中有两种方法可以用来包含另一个页面：<BR><BR>1、使用include指示符<BR><BR>
<CENTER><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><CCID_CODE>＜%@ includee file=”test.jsp” %＞</CCID_CODE></PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER><BR><BR>2、使用jsp指示符 <BR><BR>
<CENTER><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><CCID_CODE>＜jsp:includee page=”test.jsp” flush=”true”/＞</CCID_CODE></PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER><BR><BR>在实际中发现，如果使用第一种方法的话，可以使得系统性能更高。 <BR><BR>方法 7:正确地确定javabean的生命周期 <BR><BR>JSP的一个强大的地方就是对javabean的支持。通过在JSP页面中使用＜jsp:useBean＞标签，可以将javabean直接插入到一个JSP页面中。它的使用方法如下： <BR><BR>
<CENTER><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><CCID_CODE>＜jsp:useBean id="name" scope="page|request|session|application" class=
"package.className" type="typeName"＞
＜/jsp:useBean＞</CCID_CODE></PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER><BR><BR>其中scope属性指出了这个bean的生命周期。缺省的生命周期为page。如果你没有正确地选择bean的生命周期的话，它将影响系统的性能。 <BR><BR>举例来说，如果你只想在一次请求中使用某个bean，但你却将这个bean的生命周期设置成了session，那当这次请求结束后，这个bean将仍然保留在内存中，除非session超时或用户关闭浏览器。这样会耗费一定的内存，并无谓的增加了JVM垃圾收集器的工作量。因此为bean设置正确的生命周期，并在bean的使命结束后尽快地清理它们，会使用系统性能有一个提高。 <BR><BR>其它一些有用的方法 <BR><BR>1、在字符串连接操作中尽量不使用“＋”操作符：在java编程中，我们常常使用“＋”操作符来将几个字符串连接起来，但你或许从来没有想到过它居然会对系统性能造成影响吧？由于字符串是常量，因此JVM会产生一些临时的对像。你使用的“＋”越多，生成的临时对像就越多，这样也会给系统性能带来一些影响。解决的方法是用StringBuffer对像来代替“＋”操作符。 <BR><BR>2、避免使用System.out.println()方法：由于System.out.println()是一种同步调用，即在调用它时，磁盘I/O操作必须等待它的完成，因此我们要尽量避免对它的调用。但我们在调试程序时它又是一个必不可少的方便工具，为了解决这个矛盾，我建议你最好使用Log4j工具(http://Jakarta.apache.org )，它既可以方便调试，而不会产生System.out.println()这样的方法。 <BR><BR>3、ServletOutputStream 与 PrintWriter的权衡：使用PrintWriter可能会带来一些小的开销，因为它将所有的原始输出都转换为字符流来输出，因此如果使用它来作为页面输出的话，系统要负担一个转换过程。而使用ServletOutputStream作为页面输出的话就不存在一个问题，但它是以二进制进行输出的。因此在实际应用中要权衡两者的利弊。 <BR><BR>总结 <BR><BR>本文的目的是通过对servlet和JSP的一些调优技术来极大地提高你的应用程序的性能，并因此提升整个J2EE应用的性能。通过这些调优技术，你可以发现其实并不是某种技术平台（比如J2EE和.NET之争）决定了你的应用程序的性能，重要是你要对这种平台有一个较为深入的了解，这样你才能从根本上对自己的应用程序做一个优化。 <BR><BR><img src ="http://www.blogjava.net/sgsoft/aggbug/2378.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/sgsoft/" target="_blank">海天一鸥</a> 2005-03-23 17:21 <a href="http://www.blogjava.net/sgsoft/articles/2378.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>如何编出健壮的代码 java编程30条规则</title><link>http://www.blogjava.net/sgsoft/articles/2260.html</link><dc:creator>海天一鸥</dc:creator><author>海天一鸥</author><pubDate>Sun, 20 Mar 2005 09:08:00 GMT</pubDate><guid>http://www.blogjava.net/sgsoft/articles/2260.html</guid><wfw:comment>http://www.blogjava.net/sgsoft/comments/2260.html</wfw:comment><comments>http://www.blogjava.net/sgsoft/articles/2260.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/sgsoft/comments/commentRss/2260.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/sgsoft/services/trackbacks/2260.html</trackback:ping><description><![CDATA[<TABLE class=content cellSpacing=1 cellPadding=1 width="96%" border=0>
<TBODY>
<TR>
<TD class=p9 align=middle width=60 bgColor=#fb4f03>关键字：</TD>
<TD>java</TD></TR>
<TR>
<TD class=p9 align=middle bgColor=#fb4f03>来&nbsp;&nbsp;源：</TD>
<TD>www.cn-java.com</TD></TR></TBODY></TABLE><BR>这是一些相当不错的忠告！每个规则都很有分量！都是长期经验积累的总结，希望能对您有所帮助，使您编出高质量的JAVA代码。<BR><BR>(1)类名首字母应该大写。字段、方法以及对象（句柄）的首字母应小写。对于所有标识符，其中包含的所有单词都应紧靠在一起，而且大写中间单词的首字母。例如：<BR>ThisIsAClassName<BR>thisIsMethodOrFieldName<BR>若在定义中出现了常数初始化字符，则大写static final基本类型标识符中的所有字母。这样便可标志出它们属于编译期的常数。<BR>Java包（Package）属于一种特殊情况：它们全都是小写字母，即便中间的单词亦是如此。对于域名扩展名称，如com，org，net或者edu等，全部都应小写（这也是Java 1.1和Java 1.2的区别之一）。<BR><BR>(2) 为了常规用途而创建一个类时，请采取“经典形式”，并包含对下述元素的定义：<BR><BR>equals()<BR>hashCode()<BR>toString()<BR>clone()（implement Cloneable）<BR>implement Serializable<BR><BR>(3) 对于自己创建的每一个类，都考虑置入一个main()，其中包含了用于测试那个类的代码。为使用一个项目中的类，我们没必要删除测试代码。若进行了任何形式的改动，可方便地返回测试。这些代码也可作为如何使用类的一个示例使用。<BR><BR>(4) 应将方法设计成简要的、功能性单元，用它描述和实现一个不连续的类接口部分。理想情况下，方法应简明扼要。若长度很大，可考虑通过某种方式将其分割成较短的几个方法。这样做也便于类内代码的重复使用（有些时候，方法必须非常大，但它们仍应只做同样的一件事情）。<BR><BR>(5) 设计一个类时，请设身处地为客户程序员考虑一下（类的使用方法应该是非常明确的）。然后，再设身处地为管理代码的人考虑一下（预计有可能进行哪些形式的修改，想想用什么方法可把它们变得更简单）。<BR>(6) 使类尽可能短小精悍，而且只解决一个特定的问题。下面是对类设计的一些建议：<BR>■一个复杂的开关语句：考虑采用“多形”机制<BR>■数量众多的方法涉及到类型差别极大的操作：考虑用几个类来分别实现<BR>■许多成员变量在特征上有很大的差别：考虑使用几个类<BR><BR>(7) 让一切东西都尽可能地“私有”——private。可使库的某一部分“公共化”（一个方法、类或者一个字段等等），就永远不能把它拿出。若强行拿出，就可能破坏其他人现有的代码，使他们不得不重新编写和设计。若只公布自己必须公布的，就可放心大胆地改变其他任何东西。在多线程环境中，隐私是特别重要的一个因素——只有private字段才能在非同步使用的情况下受到保护。<BR><BR>(8) 谨惕“巨大对象综合症”。对一些习惯于顺序编程思维、且初涉OOP领域的新手，往往喜欢先写一个顺序执行的程序，再把它嵌入一个或两个巨大的对象里。根据编程原理，对象表达的应该是应用程序的概念，而非应用程序本身。<BR><BR>(9) 若不得已进行一些不太雅观的编程，至少应该把那些代码置于一个类的内部。<BR><BR>(10) 任何时候只要发现类与类之间结合得非常紧密，就需要考虑是否采用内部类，从而改善编码及维护工作（参见第14章14.1.2小节的“用内部类改进代码”）。<BR><BR>(11) 尽可能细致地加上注释，并用javadoc注释文档语法生成自己的程序文档。<BR><BR>(12) 避免使用“魔术数字”，这些数字很难与代码很好地配合。如以后需要修改它，无疑会成为一场噩梦，因为根本不知道“100”到底是指“数组大小”还是“其他全然不同的东西”。所以，我们应创建一个常数，并为其使用具有说服力的描述性名称，并在整个程序中都采用常数标识符。这样可使程序更易理解以及更易维护。<BR><BR>(13) 涉及构建器和异常的时候，通常希望重新丢弃在构建器中捕获的任何异常——如果它造成了那个对象的创建失败。这样一来，调用者就不会以为那个对象已正确地创建，从而盲目地继续。<BR><BR>(14) 当客户程序员用完对象以后，若你的类要求进行任何清除工作，可考虑将清除代码置于一个良好定义的方法里，采用类似于cleanup()这样的名字，明确表明自己的用途。除此以外，可在类内放置一个boolean（布尔）标记，指出对象是否已被清除。在类的finalize()方法里，请确定对象已被清除，并已丢弃了从RuntimeException继承的一个类（如果还没有的话），从而指出一个编程错误。在采取象这样的方案之前，请确定finalize()能够在自己的系统中工作（可能需要调用System.runFinalizersOnExit(true)，从而确保这一行为）。<BR><BR>(15) 在一个特定的作用域内，若一个对象必须清除（非由垃圾收集机制处理），请采用下述方法：初始化对象；若成功，则立即进入一个含有finally从句的try块，开始清除工作。<BR><BR>(16) 若在初始化过程中需要覆盖（取消）finalize()，请记住调用super.finalize()（若Object属于我们的直接超类，则无此必要）。在对finalize()进行覆盖的过程中，对super.finalize()的调用应属于最后一个行动，而不应是第一个行动，这样可确保在需要基础类组件的时候它们依然有效。<BR><BR>(17) 创建大小固定的对象集合时，请将它们传输至一个数组（若准备从一个方法里返回这个集合，更应如此操作）。这样一来，我们就可享受到数组在编译期进行类型检查的好处。此外，为使用它们，数组的接收者也许并不需要将对象“造型”到数组里。<BR><BR>(18) 尽量使用interfaces，不要使用abstract类。若已知某样东西准备成为一个基础类，那么第一个选择应是将其变成一个interface（接口）。只有在不得不使用方法定义或者成员变量的时候，才需要将其变成一个abstract（抽象）类。接口主要描述了客户希望做什么事情，而一个类则致力于（或允许）具体的实施细节。<BR><BR>(19) 在构建器内部，只进行那些将对象设为正确状态所需的工作。尽可能地避免调用其他方法，因为那些方法可能被其他人覆盖或取消，从而在构建过程中产生不可预知的结果（参见第7章的详细说明）。<BR><BR>(20) 对象不应只是简单地容纳一些数据；它们的行为也应得到良好的定义。<BR><BR>(21) 在现成类的基础上创建新类时，请首先选择“新建”或“创作”。只有自己的设计要求必须继承时，才应考虑这方面的问题。若在本来允许新建的场合使用了继承，则整个设计会变得没有必要地复杂。<BR><BR>(22) 用继承及方法覆盖来表示行为间的差异，而用字段表示状态间的区别。一个非常极端的例子是通过对不同类的继承来表示颜色，这是绝对应该避免的：应直接使用一个“颜色”字段。<BR><BR>(23) 为避免编程时遇到麻烦，请保证在自己类路径指到的任何地方，每个名字都仅对应一个类。否则，编译器可能先找到同名的另一个类，并报告出错消息。若怀疑自己碰到了类路径问题，请试试在类路径的每一个起点，搜索一下同名的.class文件。<BR><BR>(24) 在Java 1.1 AWT中使用事件“适配器”时，特别容易碰到一个陷阱。若覆盖了某个适配器方法，同时拼写方法没有特别讲究，最后的结果就是新添加一个方法，而不是覆盖现成方法。然而，由于这样做是完全合法的，所以不会从编译器或运行期系统获得任何出错提示——只不过代码的工作就变得不正常了。<BR><BR>(25) 用合理的设计方案消除“伪功能”。也就是说，假若只需要创建类的一个对象，就不要提前限制自己使用应用程序，并加上一条“只生成其中一个”注释。请考虑将其封装成一个“独生子”的形式。若在主程序里有大量散乱的代码，用于创建自己的对象，请考虑采纳一种创造性的方案，将些代码封装起来。<BR><BR>(26) 警惕“分析瘫痪”。请记住，无论如何都要提前了解整个项目的状况，再去考察其中的细节。由于把握了全局，可快速认识自己未知的一些因素，防止在考察细节的时候陷入“死逻辑”中。<BR><BR>(27) 警惕“过早优化”。首先让它运行起来，再考虑变得更快——但只有在自己必须这样做、而且经证实在某部分代码中的确存在一个性能瓶颈的时候，才应进行优化。除非用专门的工具分析瓶颈，否则很有可能是在浪费自己的时间。性能提升的隐含代价是自己的代码变得难于理解，而且难于维护。<BR><BR>(28) 请记住，阅读代码的时间比写代码的时间多得多。思路清晰的设计可获得易于理解的程序，但注释、细致的解释以及一些示例往往具有不可估量的价值。无论对你自己，还是对后来的人，它们都是相当重要的。如对此仍有怀疑，那么请试想自己试图从联机Java文档里找出有用信息时碰到的挫折，这样或许能将你说服。<BR><BR>(29) 如认为自己已进行了良好的分析、设计或者实施，那么请稍微更换一下思维角度。试试邀请一些外来人士——并不一定是专家，但可以是来自本公司其他部门的人。请他们用完全新鲜的眼光考察你的工作，看看是否能找出你一度熟视无睹的问题。采取这种方式，往往能在最适合修改的阶段找出一些关键性的问题，避免产品发行后再解决问题而造成的金钱及精力方面的损失。<BR><BR>(30) 良好的设计能带来最大的回报。简言之，对于一个特定的问题，通常会花较长的时间才能找到一种最恰当的解决方案。但一旦找到了正确的方法，以后的工作就轻松多了，再也不用经历数小时、数天或者数月的痛苦挣扎。我们的努力工作会带来最大的回报（甚至无可估量）。而且由于自己倾注了大量心血，最终获得一个出色的设计方案，成功的快感也是令人心动的。坚持抵制草草完工的诱惑——那样做往往得不偿失。<BR><img src ="http://www.blogjava.net/sgsoft/aggbug/2260.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/sgsoft/" target="_blank">海天一鸥</a> 2005-03-20 17:08 <a href="http://www.blogjava.net/sgsoft/articles/2260.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>值得思考的 J2EE 架构的6个最佳实践 </title><link>http://www.blogjava.net/sgsoft/articles/2259.html</link><dc:creator>海天一鸥</dc:creator><author>海天一鸥</author><pubDate>Sun, 20 Mar 2005 09:06:00 GMT</pubDate><guid>http://www.blogjava.net/sgsoft/articles/2259.html</guid><wfw:comment>http://www.blogjava.net/sgsoft/comments/2259.html</wfw:comment><comments>http://www.blogjava.net/sgsoft/articles/2259.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/sgsoft/comments/commentRss/2259.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/sgsoft/services/trackbacks/2259.html</trackback:ping><description><![CDATA[<TABLE class=content cellSpacing=1 cellPadding=1 width="96%" border=0>
<TBODY>
<TR>
<TD class=p9 align=middle width=60 bgColor=#fb4f03>关键字：</TD>
<TD>Java;J2EE</TD></TR>
<TR>
<TD class=p9 align=middle bgColor=#fb4f03>来&nbsp;&nbsp;源：</TD>
<TD>http://www.fawcette.com</TD></TR></TBODY></TABLE><BR><BR>作为一位软件顾问，我曾有机会不但设计并实现了Web应用程序，而且还评估/审核了许多Web应用程序。在复杂的、并且用JavaScript客户端封装的应用程序内，我经常遇到对用户输入信息执行大量检查的Web页面。即使HTML元素具有数据有效性的属性也如此，例如MAXLENGTH。只有在成功验证所有输入信息后，才能提交HTML表单。结果，一旦服务器端收到通知表单（请求），便恰当地执行业务逻辑。<BR><BR>在此，您发现问题了么？开发人员已经做了许多重要的假设。例如，他们假设所有的Web应用程序用户都同样诚实。开发人员还假设所有用户将总是使用他们测试过的浏览器访问Web应用程序。还有很多其他的假设。这些开发人员忘记了利用可以免费得到的工具，通过命令行很容易地模拟类似浏览器的行为。事实上，通过在浏览器窗口中键入适当的URL，您可以发送任何“posted”表单，尽管如此，通过禁用这些页面的GET请求，您很容易地阻止这样的“表单发送”。但是，您不能阻止人们模拟甚至创建他们自己的浏览器来入侵您的系统。<BR><BR>根本的问题在于开发人员不能确定客户端验证与服务器端验证的主要差别。两者的主要差别不在于验证究竟发生在哪里，例如在客户端或者在服务器端。主要的差别在于验证背后的目的不同。客户端验证仅仅是方便。执行它可为用户提供快速反馈，使应用程序似乎做出响应，给人一种运行桌面应用程序的错觉。<BR><BR>另一方面，服务器端验证是构建安全Web应用程序必需的。不管在客户端一侧输入的是什么，它可以确保客户端送往服务器的所有数据都是有效的。因而，只有服务器端验证才可以提供真正应用程序级的安全。许多开发人员陷入了错误感觉的圈套：只有在客户端进行所有数据的验证才能确保安全。下面是说明此观点的一个常见的示例：<BR><BR>一个典型的登录页面拥有一个用来输入用户名的文本框和一个输入密码的文本框。在服务器端，某人在接收servlet中可能遇到一些代码，这些代码构成了下面形式的SQL查询：<BR><BR>
<CENTER><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><CCID_CODE>"SELECT * FROM SecurityTable 
WHERE username = '" +
form.getParameter("username") 
+ "' AND password = 
'" + form.getParameter("password") + "';"</CCID_CODE></PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER><BR><BR>并执行这些代码。如果查询在结果集的某一行返回，则用户登录成功，否则用户登录失败。第一个问题是构造SQL的方式，但现在让我们暂时忽略它。如果用户在用户名中输入“Alice'--”会怎样呢？假设名为“Alice”的用户已经在SecurityTable中，这时此用户（更恰当的说法是黑客）成功地登录。我将把找出为什么会出现这种情况的原因做为留给您的一道习题。 <BR><BR>许多创造性的客户端验证可以阻止一般的用户从浏览器中这样登录。但对于已经禁用了JavaScript的客户端，或者那些能够使用其他类似浏览器程序直接发送命令（HTTP POST和GET命令）的高级用户（或者说黑客）来说，我们又有什么办法呢？服务器端验证是防止这种漏洞类型所必须的。这时，SSL、防火墙等都派不上用场了。 <BR><BR>安全并非是附加物 <BR><BR>我发现所有的JavaServer Page（JSP）都有一个共同的主题，那就是具有类似下面伪代码的布局： <BR><BR>
<CENTER><CCID_NOBR>
<TABLE cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=400 align=center borderColorLight=black border=1>
<TBODY>
<TR>
<TD class=code style="FONT-SIZE: 9pt" bgColor=#e6e6e6><PRE><CCID_CODE>&lt;%
User user = 
session.getAttribute("User");
if(user == null)
{
// redirect to 
// the logon page…
} 
if(!user.role.equals("manager"))
{
// redirect to the
// "unauthorized" page…
}
%&gt;

&lt;!-
HTML, JavaScript, and JSP
code to display data and
allow user interaction --&gt;</CCID_CODE></PRE></TD></TR></TBODY></TABLE></CCID_NOBR></CENTER><BR><BR>如果项目使用诸如Struts这样的MVC框架，所有的Action Bean都会具有类似的代码。尽管最后这些代码可能运行得很好，但如果您发现一个bug，或者您必须添加一个新的角色（例如，“guest”或者“admin”），这就会代表一场维护恶梦。 <BR><BR>此外，所有的开发人员，不管您多年轻，都需要熟悉这种编码模式。当然，您可以用一些JSP标签来整理JSP代码，可以创建一个清除派生Action Bean的基本Action Bean。尽管如此，由于与安全相关的代码会分布到多个地方，所以维护时的恶梦仍旧存在。由于Web应用程序的安全是强迫建立在应用程序代码的级别上（由多个开发人员），而不是建立在架构级别上，所以Web应用程序还是很可能存在弱点。 <BR><BR>很可能，根本的问题是在项目接近完成时才处理安全性问题。最近作为一名架构师，我曾在一年多的时间里亲历了某一要实现项目的6个版本，而直到第四版时我们才提到了安全性。即使该项目会将高度敏感的个人数据暴露于Web上，我们也没有注意到安全性。为了更改发布计划，我们卷入了与项目资助人及其管理人员的争斗中，以便在第一版中包含所有与安全相关的功能，并将一些“业务”功能放在后续的版本中。最终，我们赢得了胜利。而且由于应用程序的安全性相当高，能够保护客户的私有数据，这一点我们引以为荣，我们的客户也非常高兴。 <BR><BR>遗憾的是，在大多数应用程序中，安全性看起来并未增加任何实际的商业价值，所以直到最后才解决。发生这种情况时，人们才匆忙开发与安全相关的代码，而丝毫没有考虑解决方案的长期可维护性或者健壮性。忽视该安全性的另一个征兆是缺乏全面的服务器端验证，这一点是安全Web应用程序的一个重要组成部分。 <BR><BR>记住：J2EE Web应用程序的安全性并非仅仅是在Web.xml 和ejb-jar.xml文件中使用合适的声明，也不是使用J2EE技术，如Java 认证和授权服务（Java Authentication and Authorization Service，JAAS）。而是经过深思熟虑后的设计，且实现一个支持它的架构。 <BR><BR>国际化（I18N）不再是纸上谈兵 <BR><BR>当今世界的事实是许多英语非母语的人们将访问您的公共Web应用程序。随着电子政务的实行，由于它允许人们（某个国家的居民）在线与政府机构交互，所以这一点特别真实。这样的例子包括换发驾照或者车辆登记证。许多第一语言不是英语的人们很可能将访问这样的应用程序。国际化（即：“i18n”，因为在“internationalization”这个单词中，字母i和字母n之间一共有18个字母）使得您的应用程序能够支持多种语言。 <BR><BR>显然，如果您的JSP页面中有硬编码的文本，或者您的Java代码返回硬编码的错误消息，那么您要花费很多时间开发此Web应用程序的西班牙语版本。然而，在Web应用程序中，为了支持多种语言，文本不是惟一必须“具体化”的部分。因为许多图像中嵌有文字，所以图形和图像也应该是可配置的。在极端的情况下，图像（或者颜色）在不同的文化背景中可能有完全不同的意思。类似地，任何格式化数字和日期的Java代码也必须本地化。但问题是：您的页面布局可能也需要更改。 <BR><BR>例如，如果您使用HTML表格来格式化和显示菜单选项、应用程序题头或注脚，则您可能必须为每一种支持的语言更改每一栏的最小宽度和表格其他可能的方面。为了适应不同的字体和颜色，您可能必须为每一种语言使用单独的样式表。<BR>显然，现在创建一个国际化的Web应用程序面临的是架构挑战而不是应用程序方面的挑战。一个架构良好的Web应用程序意味着您的JSP页面和所有与业务相关的（应用程序特有的）Java代码都不知不觉地选择了本地化。要记住的教训是：不要因为Java、J2EE支持国际化而不考虑国际化。您必须从第一天起就记住设计具有国际化的解决方案。<BR><BR>在MVC表示中避免共同的错误 <BR><BR>J2EE开发已经足够成熟，在表示层，大多数项目使用MVC架构的某些形式，例如Struts。在这样的项目中，我常见到的现象是对MVC模式的误用。下面是几个示例。常见的误用是在模型层（例如，在Struts的Action Bean中）实现了所有的业务逻辑。不要忘了，表示层的模型层仍然是表示层的一部分。使用该模型层的正确方法是调用适当的业务层服务（或对象）并将结果发送到视图层（view layer）。用设计模式术语来说，MVC表示层的模型应该作为业务层的外观（Fa?ade）来实现。更好的方法是，使用核心J2EE模式（Core J2EE Patterns）中论述到的Business Delegate模式。这段自书中摘录的内容精彩地概述了将您的模型作为Business Delegate来实现的要点和优点：<BR><BR>Business Delegate起到客户端业务抽象化的作用。它抽象化，进而隐藏业务服务的实现。使用Business Delegate，可以降低表示层客户端和系统的业务服务.之间的耦合程度。根据实现策略不同，Business Delegate可以在业务服务API的实现中，保护客户端不受可能的变动性影响。这样，在业务服务API或其底层实现变化时，可以潜在地减少必须修改表示层客户端代码的次数。<BR><BR>另一个常见的错误是在模型层中放置许多表示类型的逻辑。例如，如果JSP页面需要以指定方式格式化的日期或者以指定方式排序的数据，某些人可能将该逻辑放置在模型层，对该逻辑来说，这是错误的地方。实际上，它应该在JSP页面使用的一组helper类中。当业务层返回数据时，Action Bean应该将数据转发给视图层。这样，无需创建模型和视图之间多余的耦合，就能够灵活支持多个视图层（JSP、Velocity、XML等）。也使视图能够确定向用户显示数据的最佳方式。<BR><BR>最后，我见过的大多数MVC应用程序都有未充分应用的控制器。例如，绝大多数的Struts应用程序将创建一个基本的Action类，并完成所有与安全相关的功能。其他所有的Action Bean都是此基类的派生类。这种功能应该是控制器的一部分，因为如果没有满足安全条件，则首先调用不应该到达Action Bean（即：模型）。记住，一个设计良好的MVC架构的最强大功能之一是存在一个健壮的、可扩展的控制器。您应该利用该能力以加强自己的优势。<BR><BR>不要被JOPO束缚住手脚<BR><BR>我曾目睹许多项目为了使用Enterprise JavaBean而使用Enterprise JavaBean。因为EJB似乎给项目带来优越感和妄自尊大的表现，所以有时它是显酷的要素（coolness factor）。而其他时候，它会使J2EE和EJB引起混淆。记住，J2EE和EJB不是同意词。EJB只是J2EE 的一部分，J2EE 是包含JSP、servlet、Java 消息服务（JMS）、Java数据库连接（JDBC）、JAAS、 Java管理扩展（JMX）和EJB在内的一系列技术，同样也是有关如何共同使用这些技术建立解决方案的一组指导原则和模式。<BR><BR>如果在不需要使用EJB的情况下使用EJB，它们可能会影响程序的性能。与老的Web服务器相比，EJB一般对应用服务器有更多的需求。EJB提供的所有增值服务一般需要消耗更大的内存和更多的CPU时间。许多应用程序不需要这些服务，因此应用服务器要与应用程序争夺资源。<BR><BR>在某些情况下，不必要地使用EJB可能使应用程序崩溃。例如，最近我遇到了一个在开源应用服务器上开发的应用程序。业务逻辑封装在一系列有状态会话bean（EJB）中。开发人员为了在应用服务器中完全禁用这些bean的“钝化”费了很大的劲。客户端要求应用程序部署在某一商用应用服务器上，而该服务器是客户端技术栈的一部分。该应用服务器却不允许关闭“钝化”功能。事实上，客户端不想改变与其合作的应用服务器的设任何置。结果，开发商碰到了很大的麻烦。（似乎）有趣的事情是开发商自己都不能给出为什么将代码用EJB（而且还是有状态会话bean）实现的好理由。不仅仅是开发商会遇到性能问题，他们的程序在客户那里也无法工作。<BR><BR>在Web应用程序中，无格式普通Java 对象（POJO）是EJB强有力的竞争者。POJO是轻量级的，不像EJB那样负担额外的负担。在我看来，对许多EJB的优点，例如对象入池，估计过高。POJO是您的朋友，不要被它束缚住手脚。<BR><BR>数据访问并不能托管O/R映射 <BR><BR>我曾参与过的所有Web应用程序都向用户提供从其他地方存取的数据，并且因此需要一个数据访问层。这并不是说所有的项目都需要标识并建立这样一个层，这仅仅说明这样层的存在不是隐含的就是明确的。如果是隐含的数据层，数据层是业务对象（即：业务服务）层的一部分。这适用于小型应用程序，但通常与大一些项目所接受的架构指导原则相抵触。<BR><BR>总之，数据访问层必须满足或超出以下四个标准：<BR><BR>具有透明性 <BR><BR>业务对象在不知道数据源实现的具体细节情况下，可以使用数据源。由于实现细节隐藏在数据访问层的内部，所以访问是透明的。 <BR><BR>易于迁移 <BR><BR>数据访问层使应用程序很容易迁移到其他数据库实现。业务对象不了解底层的数据实现，所以迁移仅仅涉及到修改数据访问层。进一步地说，如果您正在部署某种工厂策略，您可以为每个底层的存储实现提供具体的工厂实现。如果是那样的话，迁移到不同的存储实现意味着为应用程序提供一个新的工厂实现。 <BR><BR>尽量减少业务对象中代码复杂性 <BR><BR>因为数据访问层管理着所有的数据访问复杂性，所以它可以简化业务对象和使用数据访问层的其他数据客户端的代码。数据访问层，而不是业务对象，含有许多与实现相关的代码（例如SQL语句）。这样给开发人员带来了更高的效率、更好的可维护性、提高了代码的可读性等一系列好处。 <BR><BR>把所有的数据访问集中在单独的层上，由于所有的数据访问操作现在都委托给数据访问层，所以您可以将这个单独的数据访问层看做能够将应用程序的其他部分与数据访问实现相互隔离的层。这种集中化可以使应用程序易于维护和管理。 <BR><BR>注意：这些标准都不能明确地调出对O/R（对象到关系）映射层的需求。O/R映射层一般用O/R映射工具创建，它提供对象对关系数据结构的查看和感知（look-and-feel）。在我看来，在项目中使用O/R映射与使用EJB类似。在大多数情况下，并不要求它。对于包含中等规模的联合以及多对多关系的关系型数据库来说，O/R映射会变得相当复杂。由于增加O/R 映射解决方案本身的内在复杂性，例如延迟加载（lazy loading）、高速缓冲等，您将为您的项目带来更大的复杂性（和风险）。 <BR><BR>为了进一步支持我的观点，我将指出按照Sun Microsystem所普及的实体Bean（O/R映射的一种实现）的许多失败的尝试，这是自1.0版以来一直折磨人的难题。在SUN的防卫措施中，一些早期的问题是有关EJB规范的开发商实现的。这依次证明了实体Bean规范自身的复杂性。结果，大多数J2EE架构师一般认为从实体Bean中脱离出来是一个好主意。 <BR><BR>大多数应用程序在处理他们的数据时，只能进行有限次数的查询。在这样的应用程序中，访问数据的一种有效方法是实现一个数据访问层，该层实现执行这些查询的一系列服务（或对象、或API）。如上所述，在这种情况下，不需要O/R映射。当您要求查询灵活性时，O/R映射正合适，但要记住：这种附加的灵活性并不是没有代价的。 <BR><img src ="http://www.blogjava.net/sgsoft/aggbug/2259.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/sgsoft/" target="_blank">海天一鸥</a> 2005-03-20 17:06 <a href="http://www.blogjava.net/sgsoft/articles/2259.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Struts 培训教程</title><link>http://www.blogjava.net/sgsoft/articles/463.html</link><dc:creator>海天一鸥</dc:creator><author>海天一鸥</author><pubDate>Tue, 18 Jan 2005 13:15:00 GMT</pubDate><guid>http://www.blogjava.net/sgsoft/articles/463.html</guid><wfw:comment>http://www.blogjava.net/sgsoft/comments/463.html</wfw:comment><comments>http://www.blogjava.net/sgsoft/articles/463.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/sgsoft/comments/commentRss/463.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/sgsoft/services/trackbacks/463.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: Struts 培训教程1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 概述在这份教程中，将介绍struts框架和与struts框架有关的技术。该教程主要分为以下几部分：§&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; MVC模式§&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n...&nbsp;&nbsp;<a href='http://www.blogjava.net/sgsoft/articles/463.html'>阅读全文</a><img src ="http://www.blogjava.net/sgsoft/aggbug/463.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/sgsoft/" target="_blank">海天一鸥</a> 2005-01-18 21:15 <a href="http://www.blogjava.net/sgsoft/articles/463.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>教您如何成为 EJB 专家详解系列连载之一</title><link>http://www.blogjava.net/sgsoft/articles/462.html</link><dc:creator>海天一鸥</dc:creator><author>海天一鸥</author><pubDate>Tue, 18 Jan 2005 13:10:00 GMT</pubDate><guid>http://www.blogjava.net/sgsoft/articles/462.html</guid><wfw:comment>http://www.blogjava.net/sgsoft/comments/462.html</wfw:comment><comments>http://www.blogjava.net/sgsoft/articles/462.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/sgsoft/comments/commentRss/462.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/sgsoft/services/trackbacks/462.html</trackback:ping><description><![CDATA[<STRONG><FONT color=#330099>一、Server方组件结构<BR><BR></FONT></STRONG>EJB是一种Server方的组件结构，它可以非常简单的开发基于Java的企业级的分布式对象应用。使用EJB可以开发出易升级的、可靠的、安全的应用程序，而不用独立开发复杂的分布式对象框架；EJB可以迅速开发服务方应用程序，快速建立基于Java的服务方组件。EJB被设计用来实现企业中间件服务的可移植和可重用性。<BR><BR>如果你对企业计算很陌生，这些概念对你很有用，EJB是个很复杂的主题，应该被充分的解释。在这一章我们讨论有关EJB的主要概念。首先，我们先讨论开发企业级软件是为了干什么？为什么说像EJB的预包装的分布式对象体系可以简化你的生活？在讨论中，你将会对服务端的组件结构有宏观的了解。<BR><BR>服务端组件结构的需要，我们必须首先了解开发者在建立和配置服务端环境下的组件时通常需要什么？同时，我们将解决围绕服务端开发所出现的问题，将看到建立一套像EJB标准体系结构的必要性。<BR><BR>软件组件是一个软件组件是一段代码，它用来实现一系列定义好的接口。组件不是完整的。应用程序，它们不能被独立运行。更贴切的说，它们是看作是许多大型问题分割成的小问题。软件组件的思想非常有用。公司可以买来定义好的可用来解决某一问题的模块，将它和其他组件一起编译用以解决大型问题。<BR><BR>组件结构是为了使组件开发过程更加容易，需要为建立、管理、维持组件建立规范。开发组件的开发工具，是在建立组件时，应该允许开发者集中精力在组件的背后开发核心逻辑，使开发者不需要考虑太多的标准细节问题，从而快速开发应用程序。例如IDE：Symantec的Visual Cafe，IBM的VisualAge for Java，Inprise的Jbuilder 2，这些工具可以帮助你快速的建立和调试组件。<BR><BR>管理配置好的组件容器，组件容器为你的组件运行提供了一个运行时环境。同时也提供了一套供大多数组件使用的通用服务。配置和维持组件工具，从组件提供商购买了组件后，还需要有一套工具来帮助配置和维持这些组件。<BR><BR>Java：完美实现组件结构<BR><BR>对于成功运用在解决商业问题的一个组件，无论是组件开发商还是使用组件的客户都必须遵守调用组件方法的语法和语义。开发商出版有关调用组件的规范，客户代码必须遵守它们。为了防止当开发商提供了一个新版本的组件，或更改了组件规范，客户方就必须重新编写或编译它们的代码，因此面向对象设计提出了一种新的设计方法：通过从组件的实现中分离出接口。<BR><BR>组件接口和组件逻辑，为了使接口/实现分离这种方式变得更有效，开发者必须为组件接口写客户代码（这被称为基于接口的程序设计），通过这种分离，可以改变服务方逻辑而不用更改客户方代码。<BR><BR>Java中的组件结构<BR><BR>现在让我们了解什么是组件结构，看看在Java世界里存在那些组件结构。首先，你应该了解什么是JavaBeans，JavaBeans组件是小的应用程序块，可以使用JavaBean去集合成大型的组件，从而编译完整的应用程序。然而，你不能配置一个JavaBean，因为一个JavaBean不是一个完全的应用程序。JavaBean可以帮助你构建更大的可配置的软件。因为不需要配置，JavaBean不需要运行时环境，也不需要容器来对它进行实例化、破坏、提供其他服务的操作。应用程序本身是由许多JavaBean构成的。<BR><BR>相比较，EJB标准定义了一个组件结构来配置组件，被称为企业级的Beans。企业级的Beans是比较大的、粗糙的被用来配置的应用程序组件。他们能被破坏，也能被用来和其他组件组合成更大的应用程序系统。可配置组件在容器内被配置，容器提供了对组件的运行时服务。例如实例化。<BR><BR>企业级Beans和两种其他的Java组件十分相似：applets和servlets。Applets可以在Web页中配置，浏览器的Appletviewer为其提供了运行时的容器。Servlets可以在Web Server中被配置，Webserver的servlet engine为提供运行时的容器。企业级Beans可以在应用程序服务器中被配置，应用服务器为其提供了运行时的容器。<BR><BR>它们三者之间真正的不同是每个组件类型可以延伸的域大小。 Applets是轻便Java程序，它能够被任意的下载和运行。例如它可以从Web Server中下载到浏览器。 Servlets是可以被用来延伸Web server功能的网络组件。它是面向请求/回答的，从许多客户端获得请求，再给它们发应答。这样使得它被广泛用于执行Web任务。 Applets和servlets适用于客户方操作。而企业级Bean不扩展客户端操作，它是服务端组件，执行服务端操作；例如执行复杂运算、执行大量的商业传输。<BR><BR>服务端所需一个完整的组件结构遵循以下方式：<BR><BR>开发者写可重用组件；<BR>提供商写组件容器：用以给组件提供运行时环境和服务；<BR>提供商提供开发、配置和维持工具；<BR>这些方式保证了可重用性；<BR><BR>多层结构<BR><BR>服务方配置是用来支持用户同时执行并发、安全、可靠、有效的操作的软件。服务方配置一般被分成多层。每层实现不同的功能，每层有一个或多个组件。注意：层是个抽象的概念，在物理上它并不存在。下面有个分层的例子：<BR><BR>代表层：这些容器组件处理用户接口和用户交互。例如，代表层是一个单独的应用程序，可以用VB来写。基于Web配置的代表层可以使用Java servlets，Java server pages或Java applets。<BR><BR>业务逻辑层：是用来解决业务问题的容器组件的集合。这些组件常被用来处理高执行度的工作，它们常用安全性的语言来写，例如：Java、C。<BR><BR>数据层：被业务逻辑层用来保持状态的持久性。数据层的中心是一个或多个数据库。分层的优点是尽量隔离各层。<BR><BR>两层结构，通常，大多数配置是两层结构。将业务逻辑层和另外两层的一个合并：可能和代表层合并。可能和数据层合并。代表层和业务逻辑层合并。如把代表层和业务逻辑层这一层作为客户端，数据层作为服务端，则形成fat客户端，而thin服务端的情况。在两层结构中，客户端应用程序通过数据库桥API与数据层相连。例如：ODBC、JDBC。<BR><BR>这样的两层结构有以下特征：<BR><BR>配置代价非常高。数据库驱动必须在每一个客户层上安装和配置。<BR>数据库驱动交换代价高。转接一个数据库驱动，需要在各个客户端重新安装客户端驱动。<BR>数据库类型转型代价高。<BR>业务逻辑移植代价高。<BR>数据库连接代价高。<BR>网络性能发挥低。<BR>将业务逻辑层部分并入数据层。<BR>形成客户端thin，而服务端fat的情况。<BR><BR>N层结构，其中将代表层、业务逻辑层、数据层各自为一层。<BR>特点：<BR>配置代价低。<BR>数据库交换代价低。<BR>业务逻辑移植代价低。<BR>可以和防火墙结合配置安全部分。<BR>资源可以被重用。<BR>每层都有较大的灵活性。<BR>J2EE技术：<BR>J2EE是中间件服务套件，对于服务端应用的开发者来说，使开发变得更容易。它包含：<BR>EJB：它定义了怎样去写服务方组件、提供了一个在组件和管理它们的应用服务器之间的标准。EJB是J2EE的核心。<BR>RMI和RMI-IIOP：RMI??远程过程调用；RMI-IIOP是RMI的扩展，它可以使用IIOP协议，可以被CORBA整合使用。<BR>JNDI：JNDI用来在网络上区分组件和其他资源的位置。<BR>JDBC：是联系数据库的桥梁。<BR>推出EJB1.0后的几个月，第一个基于EJB的应用服务BEA’s WebLogic就诞生了。<BR><B><FONT color=#330099>二、EJB总揽</FONT></B><BR><BR>EJB采用divide-and-conquer的方式来实现服务方的计算。事实上，EJB标准定义了6个不同的部分协同工作。每个部分都被作为EJB配置成功的关键。在这儿，我们分别讨论它们的作用。<BR><BR>在EJB的世界里，业务解决方案被分为四个阶段发展：<BR><BR>1、业务逻辑模块化。ERP、金融、电子商务提供商将产品模块化，集成在可重用EJB组件中，这样就意味着任何有EJB和J2EE知识的人都可以使用这些组件。<BR>2、业务组件需要运行在分布式的、企业级配置的多层环境中。这样就需要不同的中间件，推动了中间件的发展。<BR>3、应用服务器和可重用组件捆绑。<BR>4、完整的系统配置。<BR><BR>EJB有好的可移植性，它被分为完全不同的6个部分，6个部分之间的关系：<BR><BR>EJB规范定义了完成一个基于EJB组件的分布式应用所需要的六个角色，这六个角色可以由不同的厂商来担当，也可以某个厂商担当多个角色。这六个角色是：<BR><BR>Enterprise Bean Provider： EJB组件开发者，负责编写EJB组件，EJB组件开发者是应用领域的专家。<BR>Application Assembler： 应用组合者，负责将各种EJB组合成一个完整的应用系统。<BR>Deployer 部署者：负责将包含EJB组件的eJb-Jar文件部署到应用服务器中。<BR>EJB Server Provider： EJB服务器提供者，负责实现一些底层的系统服务，如交易管理等。EJB服务器提供者是系统领域的专家。<BR>EJB Container Provider： EJB容器提供者，负责提供EJB组件的运行环境，EJB容器提供者和EJB服务器提供者一般是由相同的厂商担当，提供的产品叫应用服务器。<BR>System Administrator： 系统管理员负责为EJB服务器和容器提供一个企业级的计算和网络环境。<BR><BR>EJB配置的物理部分分为以下几部分：<BR><BR>EJB Container：是装载Enterprise Beans及Enterprise Beans生存的空间，是Beans在运行时相互联接的接口，Container必须实现与Beans之间的约定，Continer提供者也应该提供配置工具以便能方便地配置Beans，使其适合各种运行环境。<BR><BR>EJB Server： 主要处理复杂的底层任务，如分布式对象、分布式事务处理的管理、系统OS级的访问、网络、数据库访问等。EJB Server与EJB Container之间的合约在EJB 1.0 规范中有详细说明。大多EJB Server提供者也是EJB Container提供者。<BR><img src ="http://www.blogjava.net/sgsoft/aggbug/462.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/sgsoft/" target="_blank">海天一鸥</a> 2005-01-18 21:10 <a href="http://www.blogjava.net/sgsoft/articles/462.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Introducing to Spring Framework </title><link>http://www.blogjava.net/sgsoft/articles/315.html</link><dc:creator>海天一鸥</dc:creator><author>海天一鸥</author><pubDate>Fri, 14 Jan 2005 12:54:00 GMT</pubDate><guid>http://www.blogjava.net/sgsoft/articles/315.html</guid><wfw:comment>http://www.blogjava.net/sgsoft/comments/315.html</wfw:comment><comments>http://www.blogjava.net/sgsoft/articles/315.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/sgsoft/comments/commentRss/315.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/sgsoft/services/trackbacks/315.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 我对http://xglw.51.net/5team/springframework/viewtopic.php?t=18的翻译进行了一些修订，并且接着翻译了未完成的60%。 —————————————————————— Introducing to Spring Framework 作者：Rod Johnson 译者：yanger，taowen 校对：taowen 关于Spring Framew...&nbsp;&nbsp;<a href='http://www.blogjava.net/sgsoft/articles/315.html'>阅读全文</a><img src ="http://www.blogjava.net/sgsoft/aggbug/315.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/sgsoft/" target="_blank">海天一鸥</a> 2005-01-14 20:54 <a href="http://www.blogjava.net/sgsoft/articles/315.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Spring中IOC的实现 </title><link>http://www.blogjava.net/sgsoft/articles/314.html</link><dc:creator>海天一鸥</dc:creator><author>海天一鸥</author><pubDate>Fri, 14 Jan 2005 12:30:00 GMT</pubDate><guid>http://www.blogjava.net/sgsoft/articles/314.html</guid><wfw:comment>http://www.blogjava.net/sgsoft/comments/314.html</wfw:comment><comments>http://www.blogjava.net/sgsoft/articles/314.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/sgsoft/comments/commentRss/314.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/sgsoft/services/trackbacks/314.html</trackback:ping><description><![CDATA[<p>&nbsp;</p> <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><img id="Codehighlighter1_21_435_Open_Image" onclick="this.style.display='none'; Codehighlighter1_21_435_Open_Text.style.display='none'; Codehighlighter1_21_435_Closed_Image.style.display='inline'; Codehighlighter1_21_435_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedBlockStart.gif" align="top" /><img id="Codehighlighter1_21_435_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_21_435_Closed_Text.style.display='none'; Codehighlighter1_21_435_Open_Image.style.display='inline'; Codehighlighter1_21_435_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedBlock.gif" align="top" /><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">class</span><span style="COLOR: #000000">&nbsp;RefBean&nbsp;</span><span id="Codehighlighter1_21_435_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.blogjava.net/images/dot.gif" /></span><span id="Codehighlighter1_21_435_Open_Text"><span style="COLOR: #000000">{<br /><img id="Codehighlighter1_57_96_Open_Image" onclick="this.style.display='none'; Codehighlighter1_57_96_Open_Text.style.display='none'; Codehighlighter1_57_96_Closed_Image.style.display='inline'; Codehighlighter1_57_96_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockStart.gif" align="top" /><img id="Codehighlighter1_57_96_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_57_96_Closed_Text.style.display='none'; Codehighlighter1_57_96_Open_Image.style.display='inline'; Codehighlighter1_57_96_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedSubBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000">&nbsp;String&nbsp;getAddress()&nbsp;</span><span id="Codehighlighter1_57_96_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.blogjava.net/images/dot.gif" /></span><span id="Codehighlighter1_57_96_Open_Text"><span style="COLOR: #000000">{<br /><img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000">&nbsp;address;<br /><img src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span></span><span style="COLOR: #000000"><br /><img id="Codehighlighter1_144_191_Open_Image" onclick="this.style.display='none'; Codehighlighter1_144_191_Open_Text.style.display='none'; Codehighlighter1_144_191_Closed_Image.style.display='inline'; Codehighlighter1_144_191_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockStart.gif" align="top" /><img id="Codehighlighter1_144_191_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_144_191_Closed_Text.style.display='none'; Codehighlighter1_144_191_Open_Image.style.display='inline'; Codehighlighter1_144_191_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedSubBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;setAddress(String&nbsp;address)&nbsp;</span><span id="Codehighlighter1_144_191_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.blogjava.net/images/dot.gif" /></span><span id="Codehighlighter1_144_191_Open_Text"><span style="COLOR: #000000">{<br /><img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">this</span><span style="COLOR: #000000">.address&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;address;<br /><img src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span></span><span style="COLOR: #000000"><br /><img id="Codehighlighter1_227_266_Open_Image" onclick="this.style.display='none'; Codehighlighter1_227_266_Open_Text.style.display='none'; Codehighlighter1_227_266_Closed_Image.style.display='inline'; Codehighlighter1_227_266_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockStart.gif" align="top" /><img id="Codehighlighter1_227_266_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_227_266_Closed_Text.style.display='none'; Codehighlighter1_227_266_Open_Image.style.display='inline'; Codehighlighter1_227_266_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedSubBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000">&nbsp;String&nbsp;getZipcode()&nbsp;</span><span id="Codehighlighter1_227_266_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.blogjava.net/images/dot.gif" /></span><span id="Codehighlighter1_227_266_Open_Text"><span style="COLOR: #000000">{<br /><img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000">&nbsp;zipcode;<br /><img src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span></span><span style="COLOR: #000000"><br /><img id="Codehighlighter1_314_361_Open_Image" onclick="this.style.display='none'; Codehighlighter1_314_361_Open_Text.style.display='none'; Codehighlighter1_314_361_Closed_Image.style.display='inline'; Codehighlighter1_314_361_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockStart.gif" align="top" /><img id="Codehighlighter1_314_361_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_314_361_Closed_Text.style.display='none'; Codehighlighter1_314_361_Open_Image.style.display='inline'; Codehighlighter1_314_361_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedSubBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;setZipcode(String&nbsp;zipcode)&nbsp;</span><span id="Codehighlighter1_314_361_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.blogjava.net/images/dot.gif" /></span><span id="Codehighlighter1_314_361_Open_Text"><span style="COLOR: #000000">{<br /><img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">this</span><span style="COLOR: #000000">.zipcode&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;zipcode;<br /><img src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span></span><span style="COLOR: #000000"><br /><img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">private</span><span style="COLOR: #000000">&nbsp;String&nbsp;zipcode</span><span style="COLOR: #000000">=</span><span style="COLOR: #0000ff">null</span><span style="COLOR: #000000">;<br /><img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">private</span><span style="COLOR: #000000">&nbsp;String&nbsp;address</span><span style="COLOR: #000000">=</span><span style="COLOR: #0000ff">null</span><span style="COLOR: #000000">;<br /><img src="http://www.blogjava.net/images/OutliningIndicators/ExpandedBlockEnd.gif" align="top" />}</span></span></div></div> <p>Spring中IOC贯穿了其整个框架，但正如martinflower所说：“saying that these lightweight containers are special because they use inversion of control is like saying my car is special because it has wheels”，IOC已经称为框架设计中必不可少的部分。就实现上来讲Spring采取了配置文件的形式来实现依赖的注射，并且支持Type2 IOC（Setter Injection）以及Type3 IOC（Constructor Injection）。 <br /><br />&nbsp;&nbsp;&nbsp;&nbsp;Spring中IOC的实现的核心是其Core Bean Factory，它将框架内部的组件以一定的耦合度组装起来，并对使用它的应用提供一种面向服务的编程模式(SOP：Service-Orient Programming)，比如Spring中的AOP、以及持久化（Hibernate、ibatics）的实现。 <br /><br />&nbsp;&nbsp;&nbsp;&nbsp;首先从最底层最基础的factory Bean开始，先来看org.springframework.beans.factory.Bean <br /><br />&nbsp;&nbsp;&nbsp;&nbsp;Factory接口，它是一个非常简单的接口，getBean方法是其中最重要的方法，Spring通常是使用xml来populate Bean，所以比较常用的是XMLFactoryBean。 <br /><br />&nbsp;&nbsp;&nbsp;&nbsp;用一个简单的示例看一下其用法。首先写下两个Bean类： <br /><br />&nbsp;&nbsp;&nbsp;&nbsp;ExampleBean 类： <br /></p> <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><img id="Codehighlighter1_25_691_Open_Image" onclick="this.style.display='none'; Codehighlighter1_25_691_Open_Text.style.display='none'; Codehighlighter1_25_691_Closed_Image.style.display='inline'; Codehighlighter1_25_691_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedBlockStart.gif" align="top" /><img id="Codehighlighter1_25_691_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_25_691_Closed_Text.style.display='none'; Codehighlighter1_25_691_Open_Image.style.display='inline'; Codehighlighter1_25_691_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedBlock.gif" align="top" /><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">class</span><span style="COLOR: #000000">&nbsp;ExampleBean&nbsp;</span><span id="Codehighlighter1_25_691_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.blogjava.net/images/dot.gif" /></span><span id="Codehighlighter1_25_691_Open_Text"><span style="COLOR: #000000">{<br /><img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">private</span><span style="COLOR: #000000">&nbsp;String&nbsp;psnName</span><span style="COLOR: #000000">=</span><span style="COLOR: #0000ff">null</span><span style="COLOR: #000000">;<br /><img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">private</span><span style="COLOR: #000000">&nbsp;RefBean&nbsp;refbean</span><span style="COLOR: #000000">=</span><span style="COLOR: #0000ff">null</span><span style="COLOR: #000000">;<br /><img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">private</span><span style="COLOR: #000000">&nbsp;String&nbsp;addinfo</span><span style="COLOR: #000000">=</span><span style="COLOR: #0000ff">null</span><span style="COLOR: #000000">;<br /><img id="Codehighlighter1_170_253_Open_Image" onclick="this.style.display='none'; Codehighlighter1_170_253_Open_Text.style.display='none'; Codehighlighter1_170_253_Closed_Image.style.display='inline'; Codehighlighter1_170_253_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockStart.gif" align="top" /><img id="Codehighlighter1_170_253_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_170_253_Closed_Text.style.display='none'; Codehighlighter1_170_253_Open_Image.style.display='inline'; Codehighlighter1_170_253_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedSubBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000">&nbsp;String&nbsp;getAddinfo()&nbsp;</span><span id="Codehighlighter1_170_253_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.blogjava.net/images/dot.gif" /></span><span id="Codehighlighter1_170_253_Open_Text"><span style="COLOR: #000000">{<br /><img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000">&nbsp;getRefbean().getAddress()</span><span style="COLOR: #000000">+</span><span style="COLOR: #000000">getRefbean().getZipcode();<br /><img src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span></span><span style="COLOR: #000000"><br /><img id="Codehighlighter1_289_328_Open_Image" onclick="this.style.display='none'; Codehighlighter1_289_328_Open_Text.style.display='none'; Codehighlighter1_289_328_Closed_Image.style.display='inline'; Codehighlighter1_289_328_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockStart.gif" align="top" /><img id="Codehighlighter1_289_328_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_289_328_Closed_Text.style.display='none'; Codehighlighter1_289_328_Open_Image.style.display='inline'; Codehighlighter1_289_328_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedSubBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000">&nbsp;String&nbsp;getPsnName()&nbsp;</span><span id="Codehighlighter1_289_328_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.blogjava.net/images/dot.gif" /></span><span id="Codehighlighter1_289_328_Open_Text"><span style="COLOR: #000000">{<br /><img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000">&nbsp;psnName;<br /><img src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span></span><span style="COLOR: #000000"><br /><img id="Codehighlighter1_376_423_Open_Image" onclick="this.style.display='none'; Codehighlighter1_376_423_Open_Text.style.display='none'; Codehighlighter1_376_423_Closed_Image.style.display='inline'; Codehighlighter1_376_423_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockStart.gif" align="top" /><img id="Codehighlighter1_376_423_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_376_423_Closed_Text.style.display='none'; Codehighlighter1_376_423_Open_Image.style.display='inline'; Codehighlighter1_376_423_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedSubBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;setPsnName(String&nbsp;psnName)&nbsp;</span><span id="Codehighlighter1_376_423_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.blogjava.net/images/dot.gif" /></span><span id="Codehighlighter1_376_423_Open_Text"><span style="COLOR: #000000">{<br /><img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">this</span><span style="COLOR: #000000">.psnName&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;psnName;<br /><img src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span></span><span style="COLOR: #000000"><br /><img id="Codehighlighter1_472_519_Open_Image" onclick="this.style.display='none'; Codehighlighter1_472_519_Open_Text.style.display='none'; Codehighlighter1_472_519_Closed_Image.style.display='inline'; Codehighlighter1_472_519_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockStart.gif" align="top" /><img id="Codehighlighter1_472_519_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_472_519_Closed_Text.style.display='none'; Codehighlighter1_472_519_Open_Image.style.display='inline'; Codehighlighter1_472_519_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedSubBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;setRefbean(RefBean&nbsp;refbean)&nbsp;</span><span id="Codehighlighter1_472_519_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.blogjava.net/images/dot.gif" /></span><span id="Codehighlighter1_472_519_Open_Text"><span style="COLOR: #000000">{<br /><img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">this</span><span style="COLOR: #000000">.refbean&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;refbean;<br /><img src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span></span><span style="COLOR: #000000"><br /><img id="Codehighlighter1_555_594_Open_Image" onclick="this.style.display='none'; Codehighlighter1_555_594_Open_Text.style.display='none'; Codehighlighter1_555_594_Closed_Image.style.display='inline'; Codehighlighter1_555_594_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockStart.gif" align="top" /><img id="Codehighlighter1_555_594_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_555_594_Closed_Text.style.display='none'; Codehighlighter1_555_594_Open_Image.style.display='inline'; Codehighlighter1_555_594_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedSubBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000">&nbsp;RefBean&nbsp;getRefbean()&nbsp;</span><span id="Codehighlighter1_555_594_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.blogjava.net/images/dot.gif" /></span><span id="Codehighlighter1_555_594_Open_Text"><span style="COLOR: #000000">{<br /><img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000">&nbsp;refbean;<br /><img src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span></span><span style="COLOR: #000000"><br /><img id="Codehighlighter1_642_689_Open_Image" onclick="this.style.display='none'; Codehighlighter1_642_689_Open_Text.style.display='none'; Codehighlighter1_642_689_Closed_Image.style.display='inline'; Codehighlighter1_642_689_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockStart.gif" align="top" /><img id="Codehighlighter1_642_689_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_642_689_Closed_Text.style.display='none'; Codehighlighter1_642_689_Open_Image.style.display='inline'; Codehighlighter1_642_689_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedSubBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;setAddinfo(String&nbsp;addinfo)&nbsp;</span><span id="Codehighlighter1_642_689_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.blogjava.net/images/dot.gif" /></span><span id="Codehighlighter1_642_689_Open_Text"><span style="COLOR: #000000">{<br /><img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">this</span><span style="COLOR: #000000">.addinfo&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;addinfo;<br /><img src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span></span><span style="COLOR: #000000"><br /><img src="http://www.blogjava.net/images/OutliningIndicators/ExpandedBlockEnd.gif" align="top" />}</span></span><span style="COLOR: #000000"><br /><img src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align="top" />&nbsp;<br /><img src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align="top" /></span></div></div> <p>&nbsp;RefBean类： <br /></p> <p>&nbsp;</p> <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><img id="Codehighlighter1_21_435_Open_Image" onclick="this.style.display='none'; Codehighlighter1_21_435_Open_Text.style.display='none'; Codehighlighter1_21_435_Closed_Image.style.display='inline'; Codehighlighter1_21_435_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedBlockStart.gif" align="top" /><img id="Codehighlighter1_21_435_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_21_435_Closed_Text.style.display='none'; Codehighlighter1_21_435_Open_Image.style.display='inline'; Codehighlighter1_21_435_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedBlock.gif" align="top" /><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">class</span><span style="COLOR: #000000">&nbsp;RefBean&nbsp;</span><span id="Codehighlighter1_21_435_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.blogjava.net/images/dot.gif" /></span><span id="Codehighlighter1_21_435_Open_Text"><span style="COLOR: #000000">{<br /><img id="Codehighlighter1_57_96_Open_Image" onclick="this.style.display='none'; Codehighlighter1_57_96_Open_Text.style.display='none'; Codehighlighter1_57_96_Closed_Image.style.display='inline'; Codehighlighter1_57_96_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockStart.gif" align="top" /><img id="Codehighlighter1_57_96_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_57_96_Closed_Text.style.display='none'; Codehighlighter1_57_96_Open_Image.style.display='inline'; Codehighlighter1_57_96_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedSubBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000">&nbsp;String&nbsp;getAddress()&nbsp;</span><span id="Codehighlighter1_57_96_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.blogjava.net/images/dot.gif" /></span><span id="Codehighlighter1_57_96_Open_Text"><span style="COLOR: #000000">{<br /><img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000">&nbsp;address;<br /><img src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span></span><span style="COLOR: #000000"><br /><img id="Codehighlighter1_144_191_Open_Image" onclick="this.style.display='none'; Codehighlighter1_144_191_Open_Text.style.display='none'; Codehighlighter1_144_191_Closed_Image.style.display='inline'; Codehighlighter1_144_191_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockStart.gif" align="top" /><img id="Codehighlighter1_144_191_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_144_191_Closed_Text.style.display='none'; Codehighlighter1_144_191_Open_Image.style.display='inline'; Codehighlighter1_144_191_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedSubBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;setAddress(String&nbsp;address)&nbsp;</span><span id="Codehighlighter1_144_191_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.blogjava.net/images/dot.gif" /></span><span id="Codehighlighter1_144_191_Open_Text"><span style="COLOR: #000000">{<br /><img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">this</span><span style="COLOR: #000000">.address&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;address;<br /><img src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span></span><span style="COLOR: #000000"><br /><img id="Codehighlighter1_227_266_Open_Image" onclick="this.style.display='none'; Codehighlighter1_227_266_Open_Text.style.display='none'; Codehighlighter1_227_266_Closed_Image.style.display='inline'; Codehighlighter1_227_266_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockStart.gif" align="top" /><img id="Codehighlighter1_227_266_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_227_266_Closed_Text.style.display='none'; Codehighlighter1_227_266_Open_Image.style.display='inline'; Codehighlighter1_227_266_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedSubBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000">&nbsp;String&nbsp;getZipcode()&nbsp;</span><span id="Codehighlighter1_227_266_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.blogjava.net/images/dot.gif" /></span><span id="Codehighlighter1_227_266_Open_Text"><span style="COLOR: #000000">{<br /><img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000">&nbsp;zipcode;<br /><img src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span></span><span style="COLOR: #000000"><br /><img id="Codehighlighter1_314_361_Open_Image" onclick="this.style.display='none'; Codehighlighter1_314_361_Open_Text.style.display='none'; Codehighlighter1_314_361_Closed_Image.style.display='inline'; Codehighlighter1_314_361_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockStart.gif" align="top" /><img id="Codehighlighter1_314_361_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_314_361_Closed_Text.style.display='none'; Codehighlighter1_314_361_Open_Image.style.display='inline'; Codehighlighter1_314_361_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedSubBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;setZipcode(String&nbsp;zipcode)&nbsp;</span><span id="Codehighlighter1_314_361_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.blogjava.net/images/dot.gif" /></span><span id="Codehighlighter1_314_361_Open_Text"><span style="COLOR: #000000">{<br /><img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">this</span><span style="COLOR: #000000">.zipcode&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;zipcode;<br /><img src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span></span><span style="COLOR: #000000"><br /><img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">private</span><span style="COLOR: #000000">&nbsp;String&nbsp;zipcode</span><span style="COLOR: #000000">=</span><span style="COLOR: #0000ff">null</span><span style="COLOR: #000000">;<br /><img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">private</span><span style="COLOR: #000000">&nbsp;String&nbsp;address</span><span style="COLOR: #000000">=</span><span style="COLOR: #0000ff">null</span><span style="COLOR: #000000">;<br /><img src="http://www.blogjava.net/images/OutliningIndicators/ExpandedBlockEnd.gif" align="top" />}</span></span></div></div>其xml配置文件 Bean.xml <br /> <p>&nbsp;</p> <p>&nbsp;</p> <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><img src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align="top" /><span style="COLOR: #0000ff">&lt;?</span><span style="COLOR: #ff00ff">xml&nbsp;version="1.0"&nbsp;encoding="UTF-8"</span><span style="COLOR: #0000ff">?&gt;</span><span style="COLOR: #000000"><br /><img src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align="top" /></span><span style="COLOR: #0000ff">&lt;!</span><span style="COLOR: #ff00ff">DOCTYPE&nbsp;beans&nbsp;PUBLIC&nbsp;"-//SPRING//DTD&nbsp;BEAN//EN"<br /><img src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align="top" />"http://www.springframework.org/dtd/spring-beans.dtd"</span><span style="COLOR: #0000ff">&gt;</span><span style="COLOR: #000000"><br /><img src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align="top" /></span><span style="COLOR: #0000ff">&lt;</span><span style="COLOR: #800000">beans</span><span style="COLOR: #0000ff">&gt;</span><span style="COLOR: #000000"><br /><img src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align="top" />&nbsp;&nbsp;</span><span style="COLOR: #0000ff">&lt;</span><span style="COLOR: #800000">bean&nbsp;</span><span style="COLOR: #ff0000">id</span><span style="COLOR: #0000ff">="exampleBean"</span><span style="COLOR: #ff0000">&nbsp;class</span><span style="COLOR: #0000ff">="test.ExampleBean"</span><span style="COLOR: #0000ff">&gt;</span><span style="COLOR: #000000"><br /><img src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">&lt;</span><span style="COLOR: #800000">property&nbsp;</span><span style="COLOR: #ff0000">name</span><span style="COLOR: #0000ff">="psnName"</span><span style="COLOR: #0000ff">&gt;&lt;</span><span style="COLOR: #800000">value</span><span style="COLOR: #0000ff">&gt;</span><span style="COLOR: #000000">xkf</span><span style="COLOR: #0000ff">&lt;/</span><span style="COLOR: #800000">value</span><span style="COLOR: #0000ff">&gt;&lt;/</span><span style="COLOR: #800000">property</span><span style="COLOR: #0000ff">&gt;</span><span style="COLOR: #000000"><br /><img src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">&lt;</span><span style="COLOR: #800000">property&nbsp;</span><span style="COLOR: #ff0000">name</span><span style="COLOR: #0000ff">="refbean"</span><span style="COLOR: #0000ff">&gt;</span><span style="COLOR: #000000"><br /><img src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">&lt;</span><span style="COLOR: #800000">ref&nbsp;</span><span style="COLOR: #ff0000">bean</span><span style="COLOR: #0000ff">="refBean"</span><span style="COLOR: #0000ff">/&gt;</span><span style="COLOR: #000000"><br /><img src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">&lt;/</span><span style="COLOR: #800000">property</span><span style="COLOR: #0000ff">&gt;</span><span style="COLOR: #000000"><br /><img src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align="top" />&nbsp;&nbsp;</span><span style="COLOR: #0000ff">&lt;/</span><span style="COLOR: #800000">bean</span><span style="COLOR: #0000ff">&gt;</span><span style="COLOR: #000000"><br /><img src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align="top" />&nbsp;&nbsp;</span><span style="COLOR: #0000ff">&lt;</span><span style="COLOR: #800000">bean&nbsp;</span><span style="COLOR: #ff0000">id</span><span style="COLOR: #0000ff">="refBean"</span><span style="COLOR: #ff0000">&nbsp;class</span><span style="COLOR: #0000ff">="test.RefBean"</span><span style="COLOR: #0000ff">&gt;</span><span style="COLOR: #000000"><br /><img src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align="top" />&nbsp;&nbsp;</span><span style="COLOR: #0000ff">&lt;</span><span style="COLOR: #800000">property&nbsp;</span><span style="COLOR: #ff0000">name</span><span style="COLOR: #0000ff">="address"</span><span style="COLOR: #0000ff">&gt;&lt;</span><span style="COLOR: #800000">value</span><span style="COLOR: #0000ff">&gt;</span><span style="COLOR: #000000">BeiJing</span><span style="COLOR: #0000ff">&lt;/</span><span style="COLOR: #800000">value</span><span style="COLOR: #0000ff">&gt;&lt;/</span><span style="COLOR: #800000">property</span><span style="COLOR: #0000ff">&gt;</span><span style="COLOR: #000000"><br /><img src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align="top" />&nbsp;&nbsp;</span><span style="COLOR: #0000ff">&lt;</span><span style="COLOR: #800000">property&nbsp;</span><span style="COLOR: #ff0000">name</span><span style="COLOR: #0000ff">="zipcode"</span><span style="COLOR: #0000ff">&gt;&lt;</span><span style="COLOR: #800000">value</span><span style="COLOR: #0000ff">&gt;</span><span style="COLOR: #000000">100085</span><span style="COLOR: #0000ff">&lt;/</span><span style="COLOR: #800000">value</span><span style="COLOR: #0000ff">&gt;&lt;/</span><span style="COLOR: #800000">property</span><span style="COLOR: #0000ff">&gt;</span><span style="COLOR: #000000"><br /><img src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align="top" />&nbsp;&nbsp;</span><span style="COLOR: #0000ff">&lt;/</span><span style="COLOR: #800000">bean</span><span style="COLOR: #0000ff">&gt;</span><span style="COLOR: #000000"><br /><img src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align="top" /></span><span style="COLOR: #0000ff">&lt;/</span><span style="COLOR: #800000">beans</span><span style="COLOR: #0000ff">&gt;</span></div></div> <p>然后可以写个测试类来测试，当然，需要Spring中的Spring-core.jar以及commons-logging.jar，当然在elipse中可以通过安装spring-ide插件来轻松实现。 <br /></p> <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><img id="Codehighlighter1_18_549_Open_Image" onclick="this.style.display='none'; Codehighlighter1_18_549_Open_Text.style.display='none'; Codehighlighter1_18_549_Closed_Image.style.display='inline'; Codehighlighter1_18_549_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedBlockStart.gif" align="top" /><img id="Codehighlighter1_18_549_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_18_549_Closed_Text.style.display='none'; Codehighlighter1_18_549_Open_Image.style.display='inline'; Codehighlighter1_18_549_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedBlock.gif" align="top" /><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">class</span><span style="COLOR: #000000">&nbsp;Test&nbsp;</span><span id="Codehighlighter1_18_549_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.blogjava.net/images/dot.gif" /></span><span id="Codehighlighter1_18_549_Open_Text"><span style="COLOR: #000000">{<br /><img id="Codehighlighter1_65_548_Open_Image" onclick="this.style.display='none'; Codehighlighter1_65_548_Open_Text.style.display='none'; Codehighlighter1_65_548_Closed_Image.style.display='inline'; Codehighlighter1_65_548_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockStart.gif" align="top" /><img id="Codehighlighter1_65_548_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_65_548_Closed_Text.style.display='none'; Codehighlighter1_65_548_Open_Image.style.display='inline'; Codehighlighter1_65_548_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedSubBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">static</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;main(String[]&nbsp;args)</span><span id="Codehighlighter1_65_548_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.blogjava.net/images/dot.gif" /></span><span id="Codehighlighter1_65_548_Open_Text"><span style="COLOR: #000000">{<br /><img id="Codehighlighter1_84_475_Open_Image" onclick="this.style.display='none'; Codehighlighter1_84_475_Open_Text.style.display='none'; Codehighlighter1_84_475_Closed_Image.style.display='inline'; Codehighlighter1_84_475_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockStart.gif" align="top" /><img id="Codehighlighter1_84_475_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_84_475_Closed_Text.style.display='none'; Codehighlighter1_84_475_Open_Image.style.display='inline'; Codehighlighter1_84_475_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedSubBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">try</span><span id="Codehighlighter1_84_475_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.blogjava.net/images/dot.gif" /></span><span id="Codehighlighter1_84_475_Open_Text"><span style="COLOR: #000000">{<br /><img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Resource&nbsp;input&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">new</span><span style="COLOR: #000000">&nbsp;ClassPathResource(</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">test/Bean.xml</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">);<br /><img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />&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">resource&nbsp;is:</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">+</span><span style="COLOR: #000000">input);<br /><img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;BeanFactory&nbsp;factory&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">new</span><span style="COLOR: #000000">&nbsp;XmlBeanFactory(input);<br /><img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ExampleBean&nbsp;eb&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"><br /><img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(ExampleBean)factory.getBean(</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">exampleBean</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">);<br /><img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />&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(eb.getPsnName());<br /><img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />&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(eb.getAddinfo());<br /><img src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span></span><span style="COLOR: #000000"><br /><img id="Codehighlighter1_502_546_Open_Image" onclick="this.style.display='none'; Codehighlighter1_502_546_Open_Text.style.display='none'; Codehighlighter1_502_546_Closed_Image.style.display='inline'; Codehighlighter1_502_546_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockStart.gif" align="top" /><img id="Codehighlighter1_502_546_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_502_546_Closed_Text.style.display='none'; Codehighlighter1_502_546_Open_Image.style.display='inline'; Codehighlighter1_502_546_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedSubBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">catch</span><span style="COLOR: #000000">(Exception&nbsp;e)</span><span id="Codehighlighter1_502_546_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.blogjava.net/images/dot.gif" /></span><span id="Codehighlighter1_502_546_Open_Text"><span style="COLOR: #000000">{<br /><img src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e.printStackTrace();<br /><img src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span></span><span style="COLOR: #000000"><br /><img src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top" />}</span></span></div></div>这样，通过BeanFactory的getBean方法，以及xml配置文件，避免了在test类中直接实例化ExampleBean，消除了应用程序(Test)与服务(ExampleBean)之间的耦合，实现了IOC（控制反转）或者说实现了依赖的注射（Dependency Injection）。 <p></span>&nbsp;</p> <p>&nbsp;</p><img src ="http://www.blogjava.net/sgsoft/aggbug/314.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/sgsoft/" target="_blank">海天一鸥</a> 2005-01-14 20:30 <a href="http://www.blogjava.net/sgsoft/articles/314.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>IOC和Dependency Injection </title><link>http://www.blogjava.net/sgsoft/articles/313.html</link><dc:creator>海天一鸥</dc:creator><author>海天一鸥</author><pubDate>Fri, 14 Jan 2005 12:25:00 GMT</pubDate><guid>http://www.blogjava.net/sgsoft/articles/313.html</guid><wfw:comment>http://www.blogjava.net/sgsoft/comments/313.html</wfw:comment><comments>http://www.blogjava.net/sgsoft/articles/313.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/sgsoft/comments/commentRss/313.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/sgsoft/services/trackbacks/313.html</trackback:ping><description><![CDATA[<div class="postText"> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt"><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">介绍</span></p> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21.75pt"><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">其实</span><span lang="EN">IOC</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">模式并不是什么新的东西，它是一种很普遍的概念（或者说结构），</span><span lang="EN">GoF</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">中的</span><span lang="EN">Template Method </span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">就是</span><span lang="EN">IOC</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">的结构。顾名思义，</span><span lang="EN">IOC</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">即控制反转。著名的好莱坞原则：“</span><span lang="EN">Don’t Call us, We will call you</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">”，以及</span><span lang="EN">Robert C. Martin</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">在其敏捷软件开发中所描述的依赖倒置原则（</span><span lang="EN">Dependency Inversion Principle, DIP</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">）都是这一思想的体现。</span><span lang="EN">Dependency Injection</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">是</span><span lang="EN">Martin Flower</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">对</span><span lang="EN">IOC</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">模式的一种扩展的解释，下面我们从一个简单的实例开始。</span></p> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt"><span lang="EN">&nbsp;<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /><o:p></o:p></span></p> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt"><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">考虑一个</span><span lang="EN">Button</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">来控制</span><span lang="EN">Lamp</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">的开和关。</span></p> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt"><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">如下的类图，并写下了代码。</span></p> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt"><span lang="EN"><?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" /><v:shapetype id=_x0000_t75 stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><v:stroke joinstyle="miter"></v:stroke><v:formulas><v:f eqn="if lineDrawn pixelLineWidth 0"></v:f><v:f eqn="sum @0 1 0"></v:f><v:f eqn="sum 0 0 @1"></v:f><v:f eqn="prod @2 1 2"></v:f><v:f eqn="prod @3 21600 pixelWidth"></v:f><v:f eqn="prod @3 21600 pixelHeight"></v:f><v:f eqn="sum @0 0 1"></v:f><v:f eqn="prod @6 1 2"></v:f><v:f eqn="prod @7 21600 pixelWidth"></v:f><v:f eqn="sum @8 21600 0"></v:f><v:f eqn="prod @7 21600 pixelHeight"></v:f><v:f eqn="sum @10 21600 0"></v:f></v:formulas><v:path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></v:path><o:lock aspectratio="t" v:ext="edit"></o:lock></v:shapetype></span></p> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt"><span lang="EN">&nbsp;<o:p></o:p></span></p> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt"><span lang="EN">public class Button {</span></p> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt"><span lang="EN"><span style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>private Lamp lnkLamp;</span></p> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt"><span lang="EN"><span style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>public void poll() {</span></p> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt"><span lang="EN"><span style="mso-tab-count: 2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>lnkLamp.Turnon();</span></p> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt"><span lang="EN"><span style="mso-tab-count: 1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>}</span></p> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt"><span lang="EN">}</span></p> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21pt; mso-char-indent-count: 2.0; mso-char-indent-size: 10.5pt"><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">但是马上发现这个设计的问题，</span><span lang="EN">Button</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">类直接依赖于</span><span lang="EN">Lamp</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">类，这个依赖关系意味着当</span><span lang="EN">Lamp</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">类修改时，</span><span lang="EN">Button</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">类会受到影响。此外，想重用</span><span lang="EN">Button</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">类来控制类似与</span><span lang="EN">Lamp</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">的另外一个对象则是不可能的。即</span><span lang="EN">Button</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">控制</span><span lang="EN">Lamp</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">，并且只能控制</span><span lang="EN">Lamp</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">。</span></p> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt"><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">显然，我违反了“高层模块不应该依赖于底层模块，两者都应该依赖于抽象；抽象不应该依赖于具体实现，细节应该依赖于抽象”</span> <span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">这一原则（</span><span lang="EN">DIP</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">原则）。</span></p> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21.75pt"><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">考虑到上述问题，自然地想到应该抽象出一个接口，来消除</span><span lang="EN">Button</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">对</span><span lang="EN">Lamp</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">的依赖，于是设计如下：<br /></span></p> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21.75pt"><span lang="EN"></span></p> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21.75pt"><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'"><br />&nbsp;&nbsp;&nbsp; 这样，我们倒置了</span><span lang="EN">Button</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">对</span><span lang="EN">Lamp</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">的依赖关系，使得</span><span lang="EN">Lamp</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">依赖于</span><span lang="EN">SwitchableDevice</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">接口，</span><span lang="EN">SwitchableDevice</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">并没有依赖于</span><span lang="EN">Button</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">类，任何知道如何操纵该接口的对象都可以控制</span><span lang="EN">Lamp</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">。同时</span><span lang="EN">Button</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">不只是可以控制</span><span lang="EN">Lamp</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">，还可以控制同样实现</span><span lang="EN">SwitchableDevice</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">接口的如</span><span lang="EN">Computer</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">、</span><span lang="EN">Cell Phone</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">等等。回头想想，这种做法好像似曾相识，拍拍脑袋，哦！这不是</span><span lang="EN">GoF</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">策略（</span><span lang="EN">Strategy</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">）模式吗？！正是，不经意间我就应用了设计模式（有点得意哦</span><span lang="EN">~~~~</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">）。</span></p> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21.75pt"><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">现在再来考虑一个问题，刚才的做法虽然倒置了依赖关系，但是如果将</span><span lang="EN">Button</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">作为一个应用程序来控制</span><span lang="EN">Lamp</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">或者同样实现</span><span lang="EN">SwitchableDevice</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">的</span><span lang="EN">Computer</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">、</span><span lang="EN">Cell Phone</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">等，则代码可能如下：</span></p> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt"><span lang="EN">public class Button {</span></p> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21.75pt"><span lang="EN">private SwitchableDevice lnkLamp;</span></p> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21.75pt"><span lang="EN">public Button(){</span></p> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21.75pt"><span lang="EN"><span style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp; </span>lnkLamp= new Lamp();</span></p> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21.75pt"><span lang="EN">}</span></p> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21.75pt"><span lang="EN">…</span></p> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt"><span lang="EN">}</span></p> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21pt; mso-char-indent-count: 2.0; mso-char-indent-size: 10.5pt"><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">也就是说</span><span lang="EN">Button</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">和</span><span lang="EN">Lamp</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">之间仍然存在《</span><span lang="EN">creates</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">》这样的依赖关系。</span></p> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21pt; mso-char-indent-count: 2.0; mso-char-indent-size: 10.5pt"><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">为了解除这种依赖关系，首先看</span><span lang="EN">GoF</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">能作些什么。显然，这个地方应该用</span><span lang="EN">Factory</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">模式，将对象的创建交给</span><span lang="EN">Factory Class</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">来处理，这样虽然解开了</span><span lang="EN">Lamp</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">组件与我们应用程序</span><span lang="EN">Button</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">之间的耦合关系，但是组件的创建仍然是显式的（</span><span lang="EN">explicitly</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">），在组件更改时仍需要重新编译。</span></p> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21pt; mso-char-indent-count: 2.0; mso-char-indent-size: 10.5pt"><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">另外，通过一个</span><span lang="EN">ServiceLocator</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">去</span><span lang="EN">look up</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">实现类也是一种解除耦合的办法，看到这儿，你不禁会想</span><span lang="EN">EJB</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">不就是这么实现的嘛，</span><span lang="EN">U are Right! <span class="b24">Rod Johnson</span>&nbsp;</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">在其大作</span><span lang="EN">J2EE without EJB</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">中称这种方式为</span><span lang="EN">Dependency Look up</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">，但这种方式也有其弊端，比如无法脱离容器环境，以及不利于</span><span lang="EN">Unit test</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">等。</span></p> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21pt; mso-char-indent-count: 2.0; mso-char-indent-size: 10.5pt"><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">“</span><span lang="EN">Don’t Call us, We will Call you</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">”，这个原则启示我们应该换一个思路，不应该在应用类中创建具体对象的实例，而是应该将具体对象实例的创建插入</span><span lang="EN">(plug)</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">或者说注射（</span><span lang="EN">inject</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">）到应用类中，这大概是依赖注射名称的由来吧。</span></p> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21pt; mso-char-indent-count: 2.0; mso-char-indent-size: 10.5pt"><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">这种实现方式需要在应用类以及调用组件之间建立一个</span><span lang="EN">assembler</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">来解除两者之间的依赖，看起来与前面的方式没有太大区别，来看一下结构：</span></p> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21pt; mso-char-indent-count: 2.0; mso-char-indent-size: 10.5pt"><span lang="EN"></span></p> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21pt; mso-char-indent-count: 2.0; mso-char-indent-size: 10.5pt"><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'"><br />&nbsp;&nbsp;&nbsp; 仔细查看会发现还是有比较大的不同，依赖关系是相反的，也就是说这个过程中依然倒置了依赖关系。</span><span lang="EN">Lamp</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">通过</span><span lang="EN">Assembler</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">将其创建过程注射到了</span><span lang="EN">Button</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">中，从而消除了两者之间的耦合，增加了灵活性。</span></p> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21pt; mso-char-indent-count: 2.0; mso-char-indent-size: 10.5pt"><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">下面我们看一下具体的实现，在</span><span lang="EN">PicoContainer</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">以及</span><span lang="EN">Spring</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">中有着其不同的实现，分别代表了两种类型的</span><span lang="EN">Dependency Injection, </span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">即</span><span lang="EN">Constructor Injection </span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">和</span><span lang="EN">Setter Injection</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">。</span></p> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt"><span lang="EN">private MutablePicoContainer configureContainer() {</span></p> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt"><span lang="EN"><span style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>MutablePicoContainer pico = new DefaultPicoContainer();</span></p> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt"><span lang="EN"><span style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>pico.registerComponentImplementation(SwitchableDevice.class, Lamp.class);</span></p> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt"><span lang="EN"><span style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>pico.registerComponentImplementation(Button.class);</span></p> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt"><span lang="EN"><span style="mso-spacerun: yes">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>return pico;</span></p> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21.75pt"><span lang="EN">}</span></p> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt"><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">然后可以通过</span><span lang="EN">MutablePicoContainer</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">的</span><span lang="EN">getComponentImplementation</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">方法获得实现类，调用其</span><span lang="EN">poll</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">方法控制</span><span lang="EN">Lamp</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">的开关，这样一来，两者之间的耦合通过</span><span lang="EN">PicoContainer</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">提供的</span><span lang="EN">Assembler</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">完全消除了。</span></p> <p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21.75pt"><span lang="EN">Spring</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">则通过一个</span><span lang="EN">XML</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">格式的配置文件，将两者联系起来，使用时，通过</span><span lang="EN">ApplicationContext</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">获得</span><span lang="EN">Button Bean</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">，再调用其方法实现，同样也消除了耦合关系</span></p></div></span><img src ="http://www.blogjava.net/sgsoft/aggbug/313.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/sgsoft/" target="_blank">海天一鸥</a> 2005-01-14 20:25 <a href="http://www.blogjava.net/sgsoft/articles/313.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>探讨Spring框架使用真相</title><link>http://www.blogjava.net/sgsoft/articles/312.html</link><dc:creator>海天一鸥</dc:creator><author>海天一鸥</author><pubDate>Fri, 14 Jan 2005 12:23:00 GMT</pubDate><guid>http://www.blogjava.net/sgsoft/articles/312.html</guid><wfw:comment>http://www.blogjava.net/sgsoft/comments/312.html</wfw:comment><comments>http://www.blogjava.net/sgsoft/articles/312.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/sgsoft/comments/commentRss/312.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/sgsoft/services/trackbacks/312.html</trackback:ping><description><![CDATA[<table cellspacing="0" cellpadding="0" width="760" align="center" border="0"> <tbody> <tr> <td class="title" valign="center" align="middle" height="56"><b><font color="#ff0000" size="3">探讨Spring框架使用真相<!-- #BeginEditable "1" --><!-- #EndEditable --></font></b></td></tr> <tr> <td class="formtitle" align="middle" height="40"><!-- #BeginEditable "2" -->板桥里人<!-- #EndEditable --></td></tr></tbody></table> <table height="65" cellspacing="0" cellpadding="0" width="760" align="center" border="0"> <tbody> <tr> <td class="content" height="65"><!-- #BeginEditable "3" --> <table width="85%" align="center" border="0"> <tbody> <tr> <td> <p class="content">最近，Spring很热闹，因为实现IoC模式和AOP（见本站专栏），然后又成立公司，吸取上次JBoss的教训，文档不敢收费，结果迎来了一片祝贺声。</p> <p class="content">　　Spring真正的精华是它的Ioc模式实现的BeanFactory和AOP，它自己在这个基础上延伸的功能有些画蛇添足。</p> <p class="content">　　其实说白了，大家"惊奇"的是它的IoC模式(使用AOP功能需要了解AOP，比较难)，那么，Spring之类的Ioc模式是什么？ 就是：你在编制程序时，只要写被调用者的接口代码，具体子类实例可通过配置实现。</p> <p class="content">　　Ioc模式是什么知道的人不多，但是，当他知道生成对象不用再使用new了，只要在配置文件里配置一下，他感到新鲜，其实这就是Ioc模式的实现，PicoContainer是另外一种真正轻量的Ioc模式实现，PicoContainer还是采取代码将对象注射一个小容器中，而Spring采取配置文件。</p> <p class="content">　　配置式编码其实有利有弊，编码本来可通过开发工具或编译器检查错误，但是过分依赖配置时，就会经常出现因为粗心导致的小错误，如果调试程序出错经常是因为配置文件中小写字母写成大写字母，不知道你是怎么心情？</p> <p class="content">　　Spring最近还发表了Spring without EJB的书，这倒是说了实话，Spring和EJB其实是相竞争，如同黑与白，如果硬是将两者搭配使用，显得不协调，更不是一些人所谓优化EJB调用的谣言，原因下面将会分析。既然Spring+EJB有缺陷，那么就直接使用Spring+Hibernate架构，但是又带来了新问题：无集群分布式计算性能，只能在一台机器上运行啊，具体分析见：可伸缩性和重/轻量，谁是实用系统的架构主选？</p> <p class="content">　　下面来分析所谓Spring+EJB的不协调性，正如水和油搅拌在一起使用一样。<br />　　目前，Spring+EJB有两种应用方式：</p> <p class="content">　　1. Spring不介入EJB容器，只做Web与EJB之间的接口，这个位置比较尴尬，Web层直接调用EJB的方法比较直接快捷，为什么要中间加个Spring？可实现Web缓存？使用性能更好的AOP框架aspectwerkz啊；实现Web和EJB解耦？这样的工具更多，自己都可以做个小框架实现，就不必打扰背着AOP和IOC双重重担的Spring了吧。</p> <p class="content">　　2. Spring介入EJB容器，这时，需要在你的ejb-jar.xml中配置beanFactoryPath值指向你为EJB配置的applicationContext.xml，那么你的EJB还需要继承Spring的SimpleRemoteStatelessSessionProxyFactoryBean。</p> <p class="content">　　好了，现在你的SLSB（无状态Session Bean）成为下面这个样子：</p> <p class="content">　　void updateUser(){<br />　　　　<br />　　　　　　myService.updateUser(); //委托给一个POJO的方法，真正业务逻辑封装在这个POJO中</p> <p class="content">　　} </p> <p class="content">　　这样做有很多“优点”，当然最大“优点”是：</p> <p class="content">　　由于真正业务核心在POJO中实现，因此，只要改一下applicationContext.xml配置，这样，调试时，前台就可以直接调用POJO，不必通过EJB，调试起来方便了，这有一个前提：他们认为调试EJB复杂，其实不然，在JBuilder中，结合Junit，测试EJB如同测试POJO一样方便，这是其他分支，不在此讨论。当部署使用时，再改一下applicationContext.xml配置，指引前台调用到EJB。</p> <p class="content">　　似乎很巧妙，这里有两个疑问，首先，指引到EJB的改变是什么时候做？持续集成前还是后，在前在后都有问题，这里不仔细分析。</p> <p class="content">　　这种表面巧妙的优点带来最大的问题是：粗粒度事务机制。所谓粗粒度事务机制，最早见于Petstore的WEB调用EJB Command模式，在这个帖子中有讨论。 </p> <p class="content">　　下面以代码描述什么是粗粒度事务机制：</p> <p class="content">　　ejb方法：<br />　　public void updateUser(){<br />　　　　service.updateUser();<br />　　}</p> <p class="content">　　service是一个POJO，具体方法可能是更新两个表：<br />　　public void updateUser(){<br />　　　　updateTabel1();//更新table1<br />　　　　updateTable2(); //更新table2<br />　　}</p> <p class="content">　　当updateTable2()抛出异常，updateTable1()是不回滚的。这样，table1中就有一条错误的多余的记录，而table2则没有这条记录。</p> <p class="content">　　那么，怎么做才能使两个表记录一致，采取事务机制，只有下面这样书写才能实现真正事务：<br />在EJB方法中写两个方法，因为EJB方法体缺省是一个事务。<br />　　public void updateUser(){<br />　　　　updateTabel1();//更新table1<br />　　　　updateTable2(); //更新table2<br />　　}</p> <p class="content">　　关于EJB自动的事务机制，最近也有一个道友做了测试，对于JBoss中容器管理的事务的疑惑。</p> <p class="content">　　如果你从事关键事务，就是带money相关操作的事务，这种粗粒度机制可能害苦你，那么，似乎有一种办法可弥补事务，不使用EJB容器事务(CMT)，在service中使用Spring的事务机制。</p> <p class="content">　　如果使用Spring事务机制，业务核心又在POJO中实现，那么我有一个疑问：还要套上EJB干什么？至此，你终于明白，Spring本质是和EJB竞争的，如果硬套上EJB使用，只是相借助其集群分布式功能，而这个正是Spring目前所缺少的。</p> <p class="content">　　我太惊异Spring精巧的诡异了，他和EJB关系，正如异型和人的关系一样。</p> <p class="content">　　为了避免你每次使用Spring时想到粘糊糊的异型，不如Spring without EJB，这也正是Spring的初衷，也是它的一个畅销书名。</p></td></tr></tbody></table></td></tr></tbody></table><img src ="http://www.blogjava.net/sgsoft/aggbug/312.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/sgsoft/" target="_blank">海天一鸥</a> 2005-01-14 20:23 <a href="http://www.blogjava.net/sgsoft/articles/312.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Ioc模式解析 </title><link>http://www.blogjava.net/sgsoft/articles/311.html</link><dc:creator>海天一鸥</dc:creator><author>海天一鸥</author><pubDate>Fri, 14 Jan 2005 12:22:00 GMT</pubDate><guid>http://www.blogjava.net/sgsoft/articles/311.html</guid><wfw:comment>http://www.blogjava.net/sgsoft/comments/311.html</wfw:comment><comments>http://www.blogjava.net/sgsoft/articles/311.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/sgsoft/comments/commentRss/311.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/sgsoft/services/trackbacks/311.html</trackback:ping><description><![CDATA[分离关注（ Separation of Concerns : SOC）是Ioc模式和AOP产生最原始动力，通过功能分解可得到关注点，这些关注可以是 组件Components, 方面Aspects或服务Services。 <br /><br />　　从GoF设计模式中，我们已经习惯一种思维编程方式：Interface Driven Design 接口驱动，接口驱动有很多好处，可以提供不同灵活的子类实现，增加代码稳定和健壮性等等，但是接口一定是需要实现的，也就是如下语句迟早要执行：<br />AInterface a = new AInterfaceImp(); <br /><br />　　AInterfaceImp是接口AInterface的一个子类，Ioc模式可以延缓接口的实现，根据需要实现，有个比喻：接口如同空的模型套，在必要时，需要向模型套注射石膏，这样才能成为一个模型实体，因此，我们将人为控制接口的实现成为“注射”。 <br /><br />　　Ioc英文为 Inversion of Control，即反转模式，这里有著名的好莱坞理论：你呆着别动，到时我会找你。 <br /><br />　　其实Ioc模式也是解决调用者和被调用者之间的一种关系，上述AInterface实现语句表明当前是在调用被调用者AInterfaceImp，由于被调用者名称写入了调用者的代码中，这产生了一个接口实现的原罪：彼此联系，调用者和被调用者有紧密联系，在UML中是用依赖 Dependency 表示。 <br /><br />　　但是这种依赖在分离关注的思维下是不可忍耐的，必须切割，实现调用者和被调用者解耦，新的Ioc模式 Dependency Injection 模式由此产生了， Dependency Injection模式是依赖注射的意思，也就是将依赖先剥离，然后在适当时候再注射进入。 <br /><br />&nbsp;&nbsp;&nbsp;&nbsp;Ioc模式（Dependency Injection模式）有三种： <br /><br /><ccid_nobr> <table border="1"> <tbody> <tr> <td>第一种类型 </td> <td>从JNDI或ServiceManager等获得被调用者，这里类似ServiceLocator模式。</td> <td>1. EJB/J2EE<br />2. Avalon（Apache的一个复杂使用不多的项目）</td></tr> <tr> <td>第二种类型 </td> <td>使用JavaBeans的setter方法</td> <td>1. Spring Framework,<br />2. WebWork/XWork </td></tr> <tr> <td>第三种类型</td> <td>在构造方法中实现依赖</td> <td>1. PicoContainer,<br />2. HiveMind </td></tr></tbody></table></ccid_nobr><br /><br />　　有过EJB开发经验的人都知道，每个EJB的调用都需要通过JNDI寻找到工厂性质的Home接口，在我的教程EJB是什么章节中，我也是从依赖和工厂模式角度来阐述EJB的使用。 <br /><br />　　在通常传统情况下，为了实现调用者和被调用者解耦，分离，一般是通过工厂模式实现的，下面将通过比较工厂模式和Ioc模式不同，加深理解Ioc模式。 <br /><br />&nbsp;&nbsp;&nbsp;&nbsp;<b>工厂模式和Ioc</b> <br /><br />　　假设有两个类B 和 C：B作为调用者，C是被调用者，在B代码中存在对C的调用： <br /><br /><ccid_nobr> <table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1"> <tbody> <tr> <td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>public class B{ 　　 private C comp; 　　...... }</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br />　　实现comp实例有两种途径：单态工厂模式和Ioc。 <br /><br />&nbsp;&nbsp;&nbsp;&nbsp;工厂模式实现如下： <br /><br /><ccid_nobr> <table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1"> <tbody> <tr> <td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>public class B{ 　　 private C comp; 　　private final static MyFactory myFactory = MyFactory.getInstance(); 　　public B(){ 　　　　this.comp = myFactory.createInstanceOfC(); 　　} 　　 public void someMethod(){ 　　　　this.comp.sayHello(); 　 } 　　...... }</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br />&nbsp;&nbsp;&nbsp;&nbsp;特点：<br />&nbsp;&nbsp;&nbsp;&nbsp;每次运行时，MyFactory可根据配置文件XML中定义的C子类实现，通过createInstanceOfC()生成C的具体实例。 <br /><br />&nbsp;&nbsp;&nbsp;&nbsp;使用Ioc依赖性注射( Dependency Injection )实现Picocontainer如下，B类如同通常POJO类，如下： <br /><br /><ccid_nobr> <table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1"> <tbody> <tr> <td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>public class B{ 　　 private C comp; 　　public B(C comp){ 　　　　this.comp = comp; 　　 } 　　 public void someMethod(){ 　　　　this.comp.sayHello(); 　　 } 　　...... }</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br />&nbsp;&nbsp;&nbsp;&nbsp;假设C接口/类有有一个具体实现CImp类。当客户端调用B时，使用下列代码： <br /><br /><ccid_nobr> <table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1"> <tbody> <tr> <td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>public class client{ 　　 public static void main( String[] args ) { 　　　　DefaultPicoContainer container = new DefaultPicoContainer(); 　　　　container.registerComponentImplementation(CImp.class); 　　　　container.registerComponentImplementation(B.class); 　　　　B b = (B) container.getComponentInstance(B.class); 　　　　b.someMethod(); 　　 } }</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br />　　因此，当客户端调用B时，分别使用工厂模式和Ioc有不同的特点和区别： <br /><br />　　主要区别体现在B类的代码，如果使用Ioc，在B类代码中将不需要嵌入任何工厂模式等的代码，因为这些工厂模式其实还是与C有些间接的联系，这样，使用Ioc彻底解耦了B和C之间的联系。 <br /><br />　　使用Ioc带来的代价是：需要在客户端或其它某处进行B和C之间联系的组装。 <br /><br />　　所以，Ioc并没有消除B和C之间这样的联系，只是转移了这种联系。 <br /><br />　　这种联系转移实际也是一种分离关注，它的影响巨大，它提供了AOP实现的可能。 <br /><br />&nbsp;&nbsp;&nbsp;&nbsp;<b>Ioc和AOP</b> <br /><br />　　AOP我们已经知道是一种面向切面的编程方式，由于Ioc解放自由了B类，而且可以向B类实现注射C类具体实现，如果把B类想像成运行时的横向动作，无疑注入C类子类就是AOP中的一种Advice，如下图： <br /><br /> <center><img src="http://tech.ccidnet.com/pub/attachment/2004/11/353053.png" /></center><br /><br />　　通过下列代码说明如何使用Picocontainer实现AOP，该例程主要实现是记录logger功能，通过Picocontainer可以使用简单一行，使所有的应用类的记录功能激活。 <br /><br />&nbsp;&nbsp;&nbsp;&nbsp;首先编制一个记录接口： <br /><br /><ccid_nobr> <table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1"> <tbody> <tr> <td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>public interface Logging { 　　public void enableLogging(Log log); }</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br />&nbsp;&nbsp;&nbsp;&nbsp;有一个LogSwitcher类，主要用来激活具体应用中的记录功能： <br /><br /><ccid_nobr> <table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1"> <tbody> <tr> <td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>import org.apache.commons.logging.Log; public class LogSwitcher { 　 protected Log m_log; 　 public void enableLogging(Log log) { 　　　　m_log = log; 　　　　m_log.info("Logging Enabled"); 　　} }</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br />&nbsp;&nbsp;&nbsp;&nbsp;一般的普通应用JavaBeans都可以继承这个类，假设PicoUserManager是一个用户管理类，代码如下： <br /><br /><ccid_nobr> <table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1"> <tbody> <tr> <td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>public class PicoUserManager extends LogSwitcher { 　　..... //用户管理功能 } public class PicoXXXX1Manager extends LogSwitcher { 　　..... //业务功能 } public class PicoXXXX2Manager extends LogSwitcher { 　　..... //业务功能 }</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br />&nbsp;&nbsp;&nbsp;&nbsp;注意LogSwitcher中Log实例是由外界赋予的，也就是说即将被外界注射进入，下面看看使用Picocontainer是如何注射Log的具体实例的。 <br /><br /><ccid_nobr> <table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1"> <tbody> <tr> <td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>DefaultPicoContainer container = new DefaultPicoContainer(); container.registerComponentImplementation(PicoUserManager.class); container.registerComponentImplementation(PicoXXXX1Manager.class); container.registerComponentImplementation(PicoXXXX2Manager.class); ..... Logging logging = (Logging) container.getComponentMulticaster(); logging.enableLogging(new SimpleLog("pico"));//激活log</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br />　　由上代码可见，通过使用简单一行logging.enableLogging()方法使所有的应用类的记录功能激活。这是不是类似AOP的advice实现？ <br /><br />　　总之，使用Ioc模式，可以不管将来具体实现，完全在一个抽象层次进行描述和技术架构，因此，Ioc模式可以为容器、框架之类的软件实现提供了具体的实现手段，属于架构技术中一种重要的模式应用。J道的JdonSD框架也使用了Ioc模式。<img src ="http://www.blogjava.net/sgsoft/aggbug/311.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/sgsoft/" target="_blank">海天一鸥</a> 2005-01-14 20:22 <a href="http://www.blogjava.net/sgsoft/articles/311.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>简单直观-实战体会Java多线程编程的精要</title><link>http://www.blogjava.net/sgsoft/articles/309.html</link><dc:creator>海天一鸥</dc:creator><author>海天一鸥</author><pubDate>Fri, 14 Jan 2005 11:30:00 GMT</pubDate><guid>http://www.blogjava.net/sgsoft/articles/309.html</guid><wfw:comment>http://www.blogjava.net/sgsoft/comments/309.html</wfw:comment><comments>http://www.blogjava.net/sgsoft/articles/309.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.blogjava.net/sgsoft/comments/commentRss/309.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/sgsoft/services/trackbacks/309.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 为什么会排队等待？使用 Java 实现线程Java 高级多线程支持避免不提倡使用的方法调试线程化的程序调试大量的线程限制线程优先级和调度小结 在 Java 程序中使用多线程要比在 C 或 C++ 中容易得多，这是因为 Java 编程语言提供了语言级的支持。本文通过简单的编程示例来说明 Java 程序中的多线程是多么直观。读完本文以后，用户应该能够编写简单的多线程程序。 为什么会排队等待？下面的这个...&nbsp;&nbsp;<a href='http://www.blogjava.net/sgsoft/articles/309.html'>阅读全文</a><img src ="http://www.blogjava.net/sgsoft/aggbug/309.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/sgsoft/" target="_blank">海天一鸥</a> 2005-01-14 19:30 <a href="http://www.blogjava.net/sgsoft/articles/309.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>　Jive论坛与Spring框架     选择自 duoshanx 的 Blog </title><link>http://www.blogjava.net/sgsoft/articles/245.html</link><dc:creator>海天一鸥</dc:creator><author>海天一鸥</author><pubDate>Wed, 12 Jan 2005 12:55:00 GMT</pubDate><guid>http://www.blogjava.net/sgsoft/articles/245.html</guid><wfw:comment>http://www.blogjava.net/sgsoft/comments/245.html</wfw:comment><comments>http://www.blogjava.net/sgsoft/articles/245.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/sgsoft/comments/commentRss/245.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/sgsoft/services/trackbacks/245.html</trackback:ping><description><![CDATA[Jive论坛与Spring框架 <p align="center"><a href="http://www.jdon.com/aboutme.htm"><font color="#002c99">板桥里人</font></a> http://www.jdon.com 2004/07/01</p> <p>　　没有一种新技术是凭空诞生的，它的萌芽或胚胎总是或多或少显现于以前的技术中，Jive论坛是大家潜心研究的设计型应用程序，其相关解析可见本栏的GoF设计模式专栏。</p> <p>　　Jive和Spring同为由JavaBeans组成的J2EE Web系统，Jive作为早期成功设计案例，其主要架构成为大多数纯JavaBeans系统的流行架构，Spring也不例外。</p> <p>　　Spring框架除了是一种Web层应用框架，还提供了访问EJB层的接口，也有JDBC/ORM的直接操作。Spring框架主要魅力是使用IoC模式和AOP实现了Jive系统的通用功能，从而使得Jive这样的纯JavaBeans架构设计可以重用在其它系统中。</p> <p>　　如果你感慨于Jive的设计理念，但是又苦于无法重用其设计时，Spring框架已经帮你实现了。</p> <p>　　同时也要注意到：Spring框架类似“杂烩”，它包含了很多J2EE应用的工具，类如对EJB的调用，它的MVC与Struts JSF也是相竞争的，以纯Ioc和AOP设计来说，Spring框架也是一种很重的(Heavy、Weight)框架。Spring框架是复杂的，如果想以Spring替代EJB，那么无疑按了葫芦浮起瓢。</p> <p>　　将Jive论坛和Spring框架联系起来，会帮助更多理解设计模式的程序员迅速掌握最新的设计思潮，而不是一种跳跃式的强迫接受。如果你对Jive有很好的研究，将会发现Spring框架是Jive设计的更加通用的提升。</p> <p>　　在Jive中,ForumFactory是整个系统的入口和突破点，Jive通过ForumFactory将整个系统掌控在一个工厂模式下，这样做的好处是：便于控制系统的JavaBeans，例如，客户端通过ForumFactory可创建一个Forum或访问一个Forum，但是是否有权限访问呢？如下图：</p> <p align="center"><img height="155" src="http://www.jdon.com/AOPdesign/images/jivespring.jpg" width="272" /><br />　　<br />　　 Jive通过ForumFactory将这种访问引导到相应的Proxy类去，如ForumFactoryProxy类等，通过代理模式对这些类进行权限控制访问。这是代理模式的一个主要用处，但是研读Jive的代理模式会发现，要为每个类实现一个Proxy类，非常琐碎，有没有更优雅的方式呢？ 当然使用动态代理。</p> <p>　　Spring框架基本是抽象上述设计，Spring框架对所有JavaBeans的管理也是基于一个总入口Bean Factory机制，不同的是，BeanFactory可以 管理所有应用的JavaBeans，使用者只要将自己的JavaBeans通过配置文件告诉BeanFactory，那么BeanFactory将会加载这些JavaBeans，例如：</p> <p>　　&lt;beans&gt;<br />　　　　&lt;bean id="exampleBean" class="eg.ExampleBean"/&gt;<br />　　　　&lt;bean id="anotherExample" class="eg.ExampleBeanTwo"/&gt;<br />　　&lt;/beans&gt;<br /><br />　　在Jive中，ForumFactory加载Jive自己的JavaBeans是通过工厂实现DbForumFactory实现的，如下代码，DbForumFactory引发了后台一系列功能实现，这是纵向，而return new ForumFactoryProxy这个语句则类似引来一个切面，从一个横向方面实现了权限访问等功能：</p> <table width="90%" bgcolor="#cccccc" border="0"> <tbody> <tr> <td> <p>private static String className = "com.jivesoftware.forum.database.DbForumFactory"; </p> <p>public static ForumFactory getInstance(Authorization authorization) { <br />　　　　 //If no valid authorization passed in, return null. <br />　　　　 if (authorization == null) { <br />　　　　　　 return null; <br />　　　　 } <br />　　　　 //以下使用了Singleton 单态模式 <br />　　　　 if (factory == null) { <br />　　　　　　 synchronized(initLock) { <br />　　　　　　　　 if (factory == null) { <br />　　　　　　　　　　　　 ...... </p> <p>　　　　 　　　　 try { <br />　　　　　　　　　　　　　　 //动态转载类 <br />　　　　　　　　　　　　　　 Class c = Class.forName(className); <br />　　　　　　　　　　　　　　 factory = (ForumFactory)c.newInstance(); <br />　　　　　　　　　　 } <br />　　　　　　　　　　 catch (Exception e) { <br />　　　　　　　　　　　　　　 return null; <br />　　　　　　　　　　 } <br />　　　　　　　　 } <br />　　　　　　 } <br />　　　　 } </p> <p>　　　　 //Now, 返回 proxy.用来限制授权对forum的访问 <br />　　　　 return new ForumFactoryProxy(authorization, factory, 　　　　　　　　　　　　　　　　　　　 factory.getPermissions(authorization)); <br />　　 } </p></td></tr></tbody></table> <p>　　既然Spring框架也是通过一个Bean Factory加载所有的类，那么它是如何加载的？通过IoC模式，也就是依赖性注射模式。在我以前文章“<a href="http://www.jdon.com/AOPdesign/Ioc.htm" target="_blank"><font color="#002c99">IoC模式</font></a>”中，我比较了Factory工厂模式创建对象和Ioc模式的注射对象实现之间的异同，Ioc相比工厂模式则更加解耦了调用者和被调用者之间关系，使用Ioc模式，无需在调用者代码中涉及被调用者的具体实现。</p> <p>　　Spring框架不但可以向自己容器中注射应用者自己定义的JavaBeans（也就是创建它们），而且也可以向这些JavaBeans通过set方法实现数据赋值。</p> <p>　　一旦Bean Factory运行时刻掌管这些激活的对象，Spring通过AOP方式，从一个横切面为这些JavaBeans提供了权限访问、事务锁等通用功能的实现，这种实现是基于动态代理模式，而动态代理是AOP实现的一种方式。</p> <p>　　前面提到，Jive中使用代理模式实现权限访问，比代理模式更加简洁和抽象的是动态代理，使用动态代理将使得调用者无需指定被调用者的代理类，这是动态代理区别代理模式的本质。</p> <p>　　动态代理这一优势，又可以体现在另外一句话语上：动态代理拦截了调用者对被调用者的调用，正是这一功能符合了AOP的拦截器功能，为AOP实现提供了可能。</p> <p>　　Spring框架使用了动态代理实现的AOP，正是通过动态代理机制拦截了外界对Bean Factory管理下的对象的调用。如下图：</p> <p align="center"><img height="166" src="http://www.jdon.com/AOPdesign/images/springfwk.jpg" width="300" /></p> <p align="center">&nbsp;</p> <p align="left">　　以上只是大体解构了Spring的架构，Spring框架在这个架构下，还顺带了很多其它功能，如Web MVC、 DAO JDBC、 DAO ORM 、以及remote，后者类似我设计的EJB方法调用框架。</p> <p align="left">　　总之，Spring确实是Ioc和AOP的完美应用，Ioc用来装载JavaBeans，创建这些对象；AOP用来拦截这些对象的使用，这才是框架设计的必然经典方式。<br /></p><img src ="http://www.blogjava.net/sgsoft/aggbug/245.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/sgsoft/" target="_blank">海天一鸥</a> 2005-01-12 20:55 <a href="http://www.blogjava.net/sgsoft/articles/245.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Spring Framework之最佳实践</title><link>http://www.blogjava.net/sgsoft/articles/241.html</link><dc:creator>海天一鸥</dc:creator><author>海天一鸥</author><pubDate>Wed, 12 Jan 2005 12:45:00 GMT</pubDate><guid>http://www.blogjava.net/sgsoft/articles/241.html</guid><wfw:comment>http://www.blogjava.net/sgsoft/comments/241.html</wfw:comment><comments>http://www.blogjava.net/sgsoft/articles/241.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/sgsoft/comments/commentRss/241.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/sgsoft/services/trackbacks/241.html</trackback:ping><description><![CDATA[<table cellspacing="0" cellpadding="0" width="100%" border="0"> <tbody> <tr> <td> <h1>&nbsp;</h1></td></tr> <tr background="/images/point.gif"> <td colspan="2"></td></tr> <tr> <td>&nbsp;</td> <td> <p>Spring Framework从诞生之日起，受到了越来越多的关注。最近，新的开源项目大多支持Spring Framework。国内目前也有专门的网站（<a href="http://spring.jactiongroup.net/"><font color="#000080">http://spring.jactiongroup.net/</font></a>）。那它为什么如此受欢迎呢？</p> <p>我想最重要的是，EJB让每个人都痛恨。要编写一个EJB，需要写LocalHome, RemoteHome, Bean, LocalInterface, RemoteInterface，需要一个标准描述符，一个特殊厂商描述符（Weblogic、WebSphere都不一样），如果是Entity Bean，还需要Mapping文件。如此之多，实在麻烦。但EJB最重要的是解决Transaction问题，没有Spring之前，没有其他方法能够描述式的解决它。每个人、每个公司为了解决Transaction的问题，编程的写法都不一样，百花齐放。于是，在最需要它的时候，Spring出现了。</p> <p>Spring的功能非常多。但对于一个产品，最重要的是如何用好它的精华。Spring包含AOP、ORM、DAO、Context、Web、MVC几个部分组成。Web、MVC暂不用考虑，用成熟的Struts、JSP或Webwork更好。DAO由于目前Hibernate、JDO的流行，也可不考虑。因此最需要用的是AOP、ORM、Context。</p> <p>Context中，最重要的是Beanfactory，它是将接口与实现分开，非常重要。以前我们写程序，如一个接口IDocument，一个实现类Document1。在写程序时，需写成IDocument doc = new Document1()，一旦我们的实现类需改变时，变为Document2，则程序需写成IDocument doc = new Document2()，所有用到的地方全需改。Beanfactory帮我们解决了这个问题，用context后，写法变为IDocument doc=(IDocument)beanFactory.getBean("doc")。如果实现类从Document1改为Document2，直接在配置文件改就可以了。Context是Bean factory的进一步抽象。很多人都喜欢用ApplicationConext，用Servlet把它Load。这样就把Bean Factory与Web绑定在一起。如果是Fat Client或Remote调用，则这些Bean factory就很难调用，实际是将表现层与业务层绑定的太紧。推荐的方法是SingletonBeanFactoryLocator。具体为：</p> <p>&nbsp;&nbsp; BeanFactoryLocator bfLocator = SingletonBeanFactoryLocator.getInstance();<br />&nbsp;&nbsp;&nbsp;BeanFactoryReference bf = bfLocator.useBeanFactory("beanFactory");<br />&nbsp;&nbsp;&nbsp;// now use some bean from factory<br />&nbsp;&nbsp;&nbsp;return bf.getFactory().getBean(name);</p> <p>&nbsp;</p> <p>&nbsp;&lt;beans&gt;</p> <p>&nbsp;&nbsp;&nbsp;&nbsp; &lt;bean id="beanFactory" class="org.springframework.context.support.ClassPathXmlApplicationContext"&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp; &lt;constructor-arg&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;list&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;value&gt;dataAccessContext.xml&lt;/value&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;value&gt;securityContext.xml&lt;/value&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;value&gt;...&lt;/value&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/list&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp; &lt;/constructor-arg&gt;<br />&nbsp;&nbsp;&nbsp; &lt;/bean&gt;</p> <p>&lt;/beans&gt;</p> <p><br />这样，就可随时动态扩展，实现组件式的开发。</p> <p>Spring Framework最得以出名的是与Hibernate的无缝链接，基本上用Spring，就会用Hibernate。可惜的是Spring提供的HibernateTemplate功能显得不够，使用起来也不是很方便。我们编程序时，一般先写BusinessService，由BusinessService调DAO来执行存储，在这方面Spring没有很好的例子，造成真正想用好它，并不容易。</p> <p>我们的思路是先写一个BaseDao，仿照HibernateTemplate，将基本功能全部实现：</p> <p>public class BaseDao extends HibernateDaoSupport{</p> <p>&nbsp;&nbsp;&nbsp; private Log log = LogFactory.getLog(getClass());</p> <p>&nbsp;&nbsp;&nbsp; public Session openSession() {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return SessionFactoryUtils.getSession(getSessionFactory(), false);<br />&nbsp;&nbsp;&nbsp; }</p> <p>&nbsp;&nbsp;&nbsp; public Object get(Class entityClass, Serializable id) throws DataAccessException {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Session session = openSession();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return session.get(entityClass, id);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; catch (HibernateException ex) {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; throw SessionFactoryUtils.convertHibernateAccessException(ex);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; }</p> <p>&nbsp;&nbsp;&nbsp; public Serializable create(Object entity) throws DataAccessException {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Session session = openSession();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return session.save(entity);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; catch (HibernateException ex) {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; throw SessionFactoryUtils.convertHibernateAccessException(ex);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; }</p> <p>...</p> <p>其它的DAO，从BaseDao继承出来，这样写其他的DAO，代码就会很少。</p> <p>从BaseDao继承出来EntityDao，专门负责一般实体的基本操作，会更方便。</p> <p>public interface EntityDao {</p> <p>&nbsp;&nbsp;&nbsp; public Object get(Class entityClass, Serializable id) throws DataAccessException;</p> <p>&nbsp;&nbsp;&nbsp; public Object load(Class entityClass, Serializable id) throws DataAccessException;</p> <p>&nbsp;&nbsp;&nbsp; public Serializable create(Object entity) throws DataAccessException;<br />...}</p> <p>/**<br />&nbsp;* Base class for Hibernate DAOs.&nbsp; This class defines common CRUD methods for<br />&nbsp;* child classes to inherit. User Sping AOP Inteceptor<br />&nbsp;*/<br />public class EntityDaoImpl extends BaseDao implements EntityDao{</p> <p>}</p> <p>为了Transaction的控制，采用AOP的方式：</p> <p>public interface EntityManager {</p> <p>&nbsp;&nbsp;&nbsp; public Object get(Class entityClass, Serializable id);</p> <p>&nbsp;&nbsp;&nbsp; public Object load(Class entityClass, Serializable id);</p> <p>&nbsp;&nbsp;&nbsp; public Serializable create(Object entity);<br />...</p> <p>}</p> <p>/**<br />&nbsp;* Base class for Entity Service. User Sping AOP Inteceptor<br />&nbsp;*/<br />public class EntityManagerImpl implements EntityManager {</p> <p>&nbsp;&nbsp;&nbsp; private EntityDao entityDao;</p> <p>&nbsp;&nbsp;&nbsp; public void setEntityDao(EntityDao entityDao) {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.entityDao = entityDao;<br />&nbsp;&nbsp;&nbsp; }</p> <p>&nbsp;&nbsp;&nbsp; public Object get(Class entityClass, Serializable id) {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return entityDao.get(entityClass, id);<br />&nbsp;&nbsp;&nbsp; }</p> <p>&nbsp;&nbsp;&nbsp; public Object load(Class entityClass, Serializable id) {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return entityDao.load(entityClass, id);<br />&nbsp;&nbsp;&nbsp; }<br />...</p> <p>}</p> <p>这样我们就有了一个通用的Hibernate实体引擎，可以对任何Hibernate实体实现基本的增加、修改、删除、查询等。</p> <p>其它的BusinessService就可以继承EntityManager，快速实现业务逻辑。</p> <p>具体XML配置如下：</p> <p>&nbsp;&lt;!-- Oracle JNDI DataSource for J2EE environments --&gt;<br />&nbsp;&lt;bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean"&gt;<br />&nbsp;&nbsp;&lt;property name="jndiName"&gt;&lt;value&gt;java:comp/env/jdbc/testPool&lt;/value&gt;&lt;/property&gt;<br />&nbsp;&lt;/bean&gt;</p> <p>&nbsp;&lt;!-- Hibernate SessionFactory for Oracle --&gt;<br />&nbsp;&lt;!-- Choose the dialect that matches your "dataSource" definition --&gt;<br />&nbsp;&lt;bean id="sessionFactory" class="org.springframework.orm.hibernate.LocalSessionFactoryBean"&gt;<br />&nbsp;&nbsp;&lt;property name="dataSource"&gt;&lt;ref local="dataSource"/&gt;&lt;/property&gt;<br />&nbsp;&nbsp;&lt;property name="mappingResources"&gt;<br />&nbsp;&nbsp;&nbsp;&lt;value&gt;user-hbm.xml&lt;/value&gt;<br />&nbsp;&nbsp;&lt;/property&gt;<br />&nbsp;&nbsp;&lt;property name="hibernateProperties"&gt;<br />&nbsp;&nbsp;&nbsp;&lt;props&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&lt;prop key="hibernate.dialect"&gt;net.sf.hibernate.dialect.OracleDialect&lt;/prop&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&lt;prop key="hibernate.cache.provider_class"&gt;net.sf.ehcache.hibernate.Provider&lt;/prop&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&lt;prop key="hibernate.cache.use_query_cache"&gt;true&lt;/prop&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;prop key="hibernate.show_sql"&gt;false&lt;/prop&gt;<br />&nbsp;&nbsp;&nbsp;&lt;/props&gt;<br />&nbsp;&nbsp;&lt;/property&gt;<br />&nbsp;&lt;/bean&gt;</p> <p>&nbsp;&lt;!-- AOP DAO Intecepter --&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;bean id="hibernateInterceptor" class="org.springframework.orm.hibernate.HibernateInterceptor"&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;property name="sessionFactory"&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;ref bean="sessionFactory"/&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/property&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/bean&gt;</p> <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;bean id="entityDaoTarget" class="com.gpower.services.entity.dao.EntityDaoImpl"&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;property name="sessionFactory"&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;ref bean="sessionFactory"/&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/property&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/bean&gt;</p> <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;bean id="entityDao" class="org.springframework.aop.framework.ProxyFactoryBean"&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;property name="proxyInterfaces"&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;value&gt;com.gpower.services.entity.dao.EntityDao&lt;/value&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/property&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;property name="interceptorNames"&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;list&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;value&gt;hibernateInterceptor&lt;/value&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;value&gt;entityDaoTarget&lt;/value&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/list&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/property&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/bean&gt;</p> <p>&nbsp;&lt;!-- Transaction manager for a single Hibernate SessionFactory (alternative to JTA) --&gt;<br />&nbsp;&lt;bean id="transactionManager" class="org.springframework.orm.hibernate.HibernateTransactionManager"&gt;<br />&nbsp;&nbsp;&lt;property name="sessionFactory"&gt;&lt;ref local="sessionFactory"/&gt;&lt;/property&gt;<br />&nbsp;&lt;/bean&gt;</p> <p>&nbsp;&lt;!-- Transaction manager that delegates to JTA (for a transactional JNDI DataSource) --&gt;<br />&nbsp;&lt;!--<br />&nbsp;&lt;bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"/&gt;<br />&nbsp;--&gt;</p> <p>&nbsp;&lt;!-- Transactional proxy for the Application primary business object --&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;bean id="entityManagerTarget" class="com.gpower.services.entity.EntityManagerImpl"&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;property name="entityDao"&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;ref bean="entityDao"/&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/property&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/bean&gt;</p> <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;bean id="entityManager" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;property name="transactionManager"&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;ref bean="transactionManager"/&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/property&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;property name="target"&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;ref bean="entityManagerTarget"/&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/property&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;property name="transactionAttributes"&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp; &lt;props&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;prop key="get*"&gt;PROPAGATION_SUPPORTS&lt;/prop&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;prop key="*"&gt;PROPAGATION_REQUIRED&lt;/prop&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp; &lt;/props&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/property&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/bean&gt;</p> <p>资源：<a href="http://www.gpowersoft.com/tech/Spring/47.htm">Spring+Hibernate培训ppt</a> </p> <p>&nbsp;</p> <p>&nbsp;</p> <p>&nbsp;</p> <p>&nbsp;</p></td></tr></tbody></table><img src ="http://www.blogjava.net/sgsoft/aggbug/241.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/sgsoft/" target="_blank">海天一鸥</a> 2005-01-12 20:45 <a href="http://www.blogjava.net/sgsoft/articles/241.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Domain Model Design</title><link>http://www.blogjava.net/sgsoft/articles/239.html</link><dc:creator>海天一鸥</dc:creator><author>海天一鸥</author><pubDate>Wed, 12 Jan 2005 12:23:00 GMT</pubDate><guid>http://www.blogjava.net/sgsoft/articles/239.html</guid><wfw:comment>http://www.blogjava.net/sgsoft/comments/239.html</wfw:comment><comments>http://www.blogjava.net/sgsoft/articles/239.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/sgsoft/comments/commentRss/239.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/sgsoft/services/trackbacks/239.html</trackback:ping><description><![CDATA[<p><span class="postbody">一直想系统的整理一下自己有关Domain Model实践的尝试。但总觉得自己的想法还不够系统而作罢。 <br />然而从另一方面看“系统的东西”也许永远做不到，失去了目标的生活该会多乏味。 <br />因此我决定将自己有关Domain Model设计的有关实践和思考和盘托出，也算是抛砖引玉。欢迎大家 <br />参与讨论，遇到同你的观点相左的地方，希望能以包容的态度来面对，我们是朝同一方向走的伙伴而不是 <br />相互对视的敌人。：） <br /><br />在深入讨论之前我先抛出一些原则和概念，最后你会看到这些概念和原则的威力。 <br />1.按照概念依赖的原则来组织业务层。 <br />2.将业务活动（业务流程）建模成类。 <br />3.用业务活动（业务流程）作为关联整个业务层各种对象的骨架。 <br />4.在业务活动中凿出扩展点，使用不同接口分离不同性质业务对象。 <br />5.将对象的存储理解为业务层概念。 <br />...... <br /><br />概念依赖 <br /><br />这是我认为能否得到良好业务层最重要的概念。 <br />在我系统框架设计将要完成，开始涉及业务层设计时，我脑袋一片空白，书上，大家讨论的大多是整个系统的结构从UI层 <br />到服务层到数据访问层到数据库。到底业务层该如何组织？Martin Fowler的POEAA的书中没有回答。找到的相关 <br />书籍也都过于空泛。Martin Fowler的分析模式有些用处，但不够系统。透过Martin fowler网站，我拿到了 <br />Domain Driven Design的发行前版本。该书给了我很大的启示。其中的要点有： <br />关于关联： <br />1.Imposing a traversal direction (强制一个关联的导航方向) <br />...... <br />关于Responsibility Layers（业务职责层）的划分： <br />作者给出了三个指导原则：Conceptual dependency.(概念依赖)为其中一项。 <br />书中给出的描述的是业务职责层上层的对象需要通过下层对象才能在概念上完整， <br />相反下层对象则可独立于上层对象存在含义。这样天然的下层对象相对于上层对象 <br />会更稳定。并且在今后演变的过程中，使同扩展的方式来完善系统，而不是改变对象 <br />的方式。 <br />通过实践，我觉得这条原则可以应用在任何两个有关联的业务对象上。通常可以通过 <br />概念依赖先建立一个导航方向。这能够满足大多数的需求。当确实需要反向导航时， <br />只要理由充分可以随时加上，并且如果先前将这两个对象放入不同包中，这时需要 <br />将他们合并到同一个包中。 <br />我见过一个不好的设计。Customer具有很多Flag分别标记该客户是否挂失，冻结，注销等等。 <br />通常叫做客户状态,然而这是不对的，这违背了单一职责原则。事实上除了注销外 <br />挂失和冻结都不应该算作Customer的本质属性。相反我把他们看作某种约束，进而把挂失看作 <br />一种协议.....因为Customer的概念可以不依赖于挂失和冻结的概念，相反挂失和冻结却要依赖 <br />Customer的概念，应为这是他们动作的主体。 <br />同样的一开始就让Customer有GetAccount的方法同样不好。因为Customer的概念确实不依赖Account <br />XXXAccount却可以有Customer的属性，Account在概念上依赖Customer。 <br /></span><span class="postbody"><span class="postbody">按照概念依赖原则我们能更好的理解业务职责层的划分。DDD中建议了如下的职责层。 <br />按从高到低分别为： <br /><br />依赖方向 <br />| Decision <br />| Policy <br />| Commitment <br />| Operation <br />V Potential <br /><br />Potential中包括类似Customer,Employee等Party方面的类。对应支持业务。 <br />Operation中包括了核心业务如存取款，买卖以及同这些业务关联的Account，Product等等。 <br />Commmitment对于客户包括同客户签订的协议等。对于员工来说包括授权等。 <br />Policy包括计算某种收费的策略，比如电信收费的算法。对应支持业务。 <br />Decision包括主要对应于管理业务。监控系统多在这层。 <br />从上到下观察，很好的遵循了概念依赖的原则。 <br />从另一方面来看，可以根据概念随着时间发展的顺序来建立对象之间的关系。这样会天然的满足概念依赖原则。 <br />后面发展起来的概念可以依赖前面的已经存在的概念，而反过来这不可。这是系统稳定的关键。 <br />同客户签订的各种协议可以不断的发展，但是客户对象是稳定的。 <br />同理收费的策略可以变化但是最终反映到帐户上的都只是对Balance的变更。所以收费策略比 <br />帐户更不稳定。 <br />客户对象也要比帐户对象稳定。 <br /><br />从按照稳定的方向依赖的原则出发，我们可以得到对象间的单向依赖。当然也会存在双向关联 <br />的对象，然而这种情况在我的实践中不是很多。而且一旦你懂得了单向关联的好处后，你就会 <br />谨慎的使用双向关联。滥用关联会使得整个业务层象DDD中说的，变成一大块“果冻”，你随便触动 <br />果冻某一块，整个果冻都会颤动。 <br />同样为了简化设计，对象的关系中多对多的关系尽量避免。如果可以 <br />则通过限定角色转化为一对多或一对一的关系。 <br /></span></span></p> <p><span class="postbody"><span class="postbody"><span class="postbody">以上是关于概念依赖的观念，下面让我们看看如何建模业务中的活动。 <br />有一种做法是使用分析模型中的控制类直接映射到设计中类中。我看来这不是好的做法。 <br />这里谈谈分析与设计的区别。 <br />从分析的角度来看，业务实体总是被动的。业务是通过控制对象操作业务实体来完成的。 <br />分析时我们是关注是什么问题。这要求我们客观的来描述现实。 <br />进入设计阶段我们关注的是如何解决的问题。控制对象施加与业务实体的操作加入不涉及 <br />第三者，则这个操作可以并入被操作的实体类中。然而分析中控制对象的概念是如此的 <br />深刻，以至于只涉及Customer的ChangePassword方法该放到哪里都成了问题。类不是 <br />“某概念 + 所关心该概念的属性 + 最终施加与这些属性上的操作” 的封装，又是什么呢？ <br />下面的问题是如何建模跨越多个业务实体的操作？ <br />举个例子：银行开户。 <br />现在假设开户时涉及到下面的一些操作对象。 <br />创建一个Customer对象。 <br />创建一个CapitalAccount对象。 <br />存入一定数额的现金。 <br />记录一笔开户流水。 <br />整个业务活动，我可以建模为OpenCustomerAct对象。伪码如下： <br /><br />public class OpenCustomerAct extends CustomerAct <br />{ <br />... <br />public void override doRun() <br />{ <br />Customer customer = Customer.create(...); <br />CapitalAccount capitalAccount = CapitalAccount.create(customer,...); <br />capitalAccount.deposit(...); <br />OpenCustomerLog.create(this); <br />} <br />... <br />} <br /><br />所需的参数通过构造函数得到。 <br />将所有的业务活动都建模成一个Act，这非常重要。甚至你可以在Session中放入一个Act来 <br />表示当前正在进行的业务。所有的扩展都是从Act开始的。 <br />假如你要对Act施加某种检查，那么对doRun方法进行拦截可以达到该目的。 <br />用例能够简化到只剩下流程，同样道理Act也可以做到这点。 <br />对于象RichClient的交互模式，通常只在最后才提交业务，中间的交互都是在准备提交的数据。 <br />那么在中间调用的方法中可以只new XXXAct而不执行doRun操作。这样做是因为中间的调用 <br />可能会用到XXXAct来作为上下文。现在我还没有想好在这样的中间过程中，如何能够触发 <br />植入到donRun前的检查？或许可以创建一个空doRun的子类覆盖掉父类实际的操作？ <br /></span></span></span></p> <p><span class="postbody"><span class="postbody"><span class="postbody"><span class="postbody">Act <br /><br />public interface Act <br />{ <br />Operator getOperator();//谁 <br />Date getOccurDate();//在什么时间 <br />String getOccurPlace();//什么地点 <br />BusinessType getBusinessType();//做什么业务 <br />ActState getActState();//业务运行的当前状态 <br />} <br />“谁在什么时间什么地点做了什么业务。” <br />这描述了任何业务的基本方面。从哲学的角度来看，“我们得到了Act，我们就得到了事物的基础”。 <br />当我们具体的描述某项业务时，假如需要向调用方暴露特定的属性。 <br />我们可以随时添加到Act的子接口中。 <br />例如同Customer相关的Act可定义为： <br />public interface CustomerAct extends Act <br />{ <br />Cutomer getCustomer();//针对哪个客户 <br />} <br />在复杂一点的情况下，如业务需要多人协作完成，可以通过组合模式达到目的。 <br /><br />public interface CompositeAct extends Act <br />{ <br />Act[] getActs(); <br />} <br />涉及到一段时间有中间状态的工作流也应该可以作为Act的子接口进行扩展。 <br />不过我没有做过这方面的尝试。 <br /><br />将Act放入Session <br /><br />将Act放入Session使得可以方便得到业务运行的上下文。而且通过扩展Act。 <br />可以从Act或其子接口中得到想得到的任何东西,这使得任何扩展都成为可能。 <br /><br />这里说明一下Act类的位置应当放入Potential层中，并且与Operator在一起。 <br />因为Potential层的业务对象也需要业务活动来维护。 <br />如果你的框架中Sesion在更基础的包中，则可以给Act提供一个空内容的父接口,放入Session所在的包中。 <br />public interface AbstractAct <br />{ <br />} <br /><br />public interface Act extends AbstractAct <br />{ <br />... <br />} <br />Session提供得到AbstractAct的入口。 <br />public class Session <br />{ <br />... <br />static public AbstractAct getAbstractAct() <br />{ <br />return Instance().abstractAct; <br />} <br />... <br />} <br /><br /></span></span></span></span></p> <p><span class="postbody"><span class="postbody"><span class="postbody"><span class="postbody"><span class="postbody">Act上的扩展点 <br /><br />按照分层的观点，下层不允许依赖上层，然而业务对象却是协作完成某个目的的。 <br />而且只要业务对象需要维护，就需要相关的Act。 <br />例如：银行中的存钱业务，参考上面的分层，我们把它放入Operation层。 <br />在存钱的业务中，我们需要检查该客户是否做了挂失。而挂失协议我们是放在Commitment层。 <br />显然，Operation层不能直接调用Commitment层的协议。 <br />DIP模式发话了“用我”。 <br />在Operation层中定义Commitment层接口，和一个工厂，使用反射实现这种调用。在Act中调用。 <br />abstract public class ActImpl <br />extends abstractActImpl <br />implements Act <br />{ <br />public virtual void run() <br />{ <br />doPreprocess(); <br />doRun(); <br />doPostprocess(); <br />} <br />abstract public doPreprocess(); <br />abstract public doRun(); <br />abstract public doPostprocess(); <br />} <br /><br />public interface CustomerCommitment <br />{ <br />void affirmCanDo(); <br />} <br /><br />abstract public class CustomerActImpl <br />extends ActImpl <br />implements CustomerAct <br />{ <br />... <br />public override void doPreprocess() <br />{ <br />... <br />//扩展点 <br />CustomerCommitment customerCommitment = CustomerCommitmentFactory.create(this); <br />customerCommitment.affirmCanDo(); <br />... <br />} <br />... <br />} <br /><br />public interface InnerCustomerCommitment <br />{ <br />void affirmCanDo(CustomerAct customerAct); <br />} <br /><br />public class CustomerCommitmentImpl implements CustomerCommitment <br />{ <br />private CustomerAct customerAct; <br /><br />public CustomerCommitmentImpl(CustomerAct customerAct) <br />{ <br />this.customerAct = customerAct; <br />} <br /><br />public void affirmCanDo() <br />{ <br />... <br />//通过配置得到该customerAct对应需要检查的客户约束，包括协议,逐一检查。 <br />DomainObjectCollection commitmentTypes = CustomerCommimentRepository.findByBusinessType(customerAct.getBusinessType()); <br /><br />... <br />foreach( CommitmentType typeItem in commitmentTypes ) <br />{ <br />InnerCustomerCommitment commitment = getCommitment(typeItem); <br />commitmentItem.affirmCanDo(customerAct); <br />} <br />... <br />} <br />} <br /><br />public class CustomerLostReportAgreementChecker implements InnerCustomerCommitment <br />{ <br />public void affirmCanDo(CustomerAct customerAct) <br />{ <br />Check.require(customerAct.getCustomer() != null,"客户不存在"); <br /><br />CustomerLostReportAgreement customerLostReportAgreement = <br />CustomerLostReportAgreementRepository.find(customerAct.getCustomer()); <br /><br />if(customerLostReportAgreement != null) <br />{ <br />agreement.affirmCanDo(customerAct); <br />} <br /><br />} <br />} <br /><br />public class CustomerLostReportAgreement <br />{ <br />... <br />public void AffirmCanDo(CustomerAct customerAct) <br />{ <br />if(customerAct.getOccurDate &lt;= expiringDate) <br />throw new CustomerLossReportedException(customer); <br />} <br />... <br />} <br /><br />同样道理，可以对其他上层的对象使用DIP使依赖倒置。 <br />比如：电信计算费用。就可以通过在CustomerAct的doRun中插入扩展点来实现。 <br />这样复杂的计费算法就被封装在接口之后了。可以分配另外的人员来开发。 <br />业务活动的流程仍然清晰可见。 <br />是啊，这正是接口的威力，大多数的设计模式不也是基于这种原理吗？ <br /><br />还有在Act上的扩展点可以分为两类，显式的和隐式的。 <br />电信费用的计算就是显式的，因为CustomerAct需要知道计算的结果，用来从帐户中扣除金额。 <br />而检查挂失协议是隐式的，CustomerAct可以对此一无所知。 <br /><br />通过在Act上的扩展点，我们可以向上扩展。 <br />这仿佛是在树枝上种木耳，呵呵。 <br /></span></span></span></span></span></p> <p><span class="postbody"><span class="postbody"><span class="postbody"><span class="postbody"><span class="postbody"><span class="postbody">DIP VS Facade <br /><br />对于上面的情况，另外一种方法是使用Facade。 <br />让我们比较一下两者。 <br />简要说明一下Facade的做法： <br />abstract public class CustomerActImpl <br />extends ActImpl <br />implements CustomerAct <br />{ <br />... <br />public override void doPreprocess() <br />{ <br />... <br />//注意：这里传递的参数，会使得用Facade方式的人大伤脑筋。 <br />//按照挂失的要求目前传递getBusinessType(),getCustomer(),getOccurDate()就够了 <br />//但是对于所有的CustomerCommitment这些参数就不一定够了。 <br />//比如：客户可能签订指定员工协议。（指只允许协议中指明的员工能操作的业务） <br />//那么该接口需要添加getOperator()参数。 <br />//接口变得不稳定。 <br />CustomerCommitmentManager.affirmCanDo(getBusinessType(),getCustomer(),getOccurDate(),?,...); <br />... <br />} <br />... <br />} <br /><br />Facade可以使得在Act中也是只提供一个调用点，但是因为不是依赖倒置的关系，不得不显示的说明需要用到的参数。 <br />相反使用DIP模式，接口中定义的是Act的接口，而Act是可以扩展的。(是否扩展全部看上层的对象是否需要)。 <br />而正是因为相应的CustomerCommitment总是处于需要检查的XXXAct的上层。这样具体的CustomerCommitment <br />总是可以依赖XXXAct。因此可以获得任何想要得到的信息。 <br /><br />同样对于电信计算费用的例子，因为传递的参数是CustomerAct接口。所以对于今后任何可能的扩展该接口都是不会变化的。 <br />能够做到这一点，完全要归功于将计算费用放入Operation的上层Policy中，你能体会到其中的要领吗？ <br /><br />形象一点来说，使用DIP模式，采取的是一种专家模式。 <br />DIP的Act说的是：“CustomerCommitment你看看我现在的情况，还能运行吗？” <br />相反Facade模式，则是令人厌烦的唠叨模式。 <br />Facade的Act说的是：“CustomerCommitment，现在执行的客户是XXX,业务是XXX,时间是XXX,...你能告诉我还能运行下去吗？” <br />显然DIP要潇洒得多。 <br /></span></span></span></span></span></span></p> <p><span class="postbody"><span class="postbody"><span class="postbody"><span class="postbody"><span class="postbody"><span class="postbody"><span class="postbody">实现接口　VS　继承父类 　 <br /><br />这里稍稍偏离一下主题，讨论一下接口同继承的问题。 <br />什么时候使用接口？什么时候使用继承？ <br />这似乎是个感觉和经验问题。或者我们会倾向于多使用接口，少使用继承。 <br />可不可以再进一步呢？ <br />以下是我的观点： <br /><br />“接口是调用方要求的结果，而继承则是实现方思考的产物。” <br /><br />毕竟如果我们定义的接口没有被用到，那它就没有任何用处。 <br />接口的目的在于制定虚的标准，从而使调用方不依赖于实现方。 <br />而继承某个父类则多半是基于“偷懒“的考虑，已经存在的东西，我为什么不利用一下？ <br />当然这样说是忽略了继承的真正用意－－单点维护。 <br /><br />所以在定义XXXAct的接口时，需要多考虑一下，上层对象需要Act中的提供什么特性，会如何使用它。 <br /><br />接口属于调用方。 <br /></span></span></span></span></span></span></span></p> <p><span class="postbody"><span class="postbody"><span class="postbody"><span class="postbody"><span class="postbody"><span class="postbody"><span class="postbody"><span class="postbody">业务对象的持久化 <br /><br />一个会引起争议的问题，是业务层是否会涉及业务对象持久化的概念。 <br />答案是肯定的。 <br />DDD中在描述The life cycle of a domain object时，给出了两种形式的持久化。 <br />Store和Archive。我们使用的较多是Store。 <br /><br />但是这不代表业务层要依赖数据访问层。相反依赖关系应该倒过来。数据访问层依赖 <br />业务层。通常我们使用Mapper实现，在hibernate中通过配置达到该目的。 <br />要做到业务层不依赖于数据访问层，同样借助接口来完成。 <br />在业务层定义数据访问的接口，为了方便，可以使用一个类来封装这些操作。 <br /><br />public interface CustomerFinder <br />{ <br />Customer findByID(ID id); <br />Customer findByCode(String code); <br />DomainObjectCollection findByName(String name); <br />... <br />} <br /><br />public class CustomerRepository <br />{ <br />private static CustomerFinder finder = null; <br />private static CustomerFinder getFinderInstance() <br />{ <br />if (finder == null) <br />{ <br />finder = (CustomerFinder)FinderRegistry.getFinder("CustomerFinder"); <br />} <br />return finder; <br />} <br /><br />public static Customer findByID(ID id) <br />{ <br />Customer obj = getFinderInstance().findByID(id); <br />Check.require(obj != null, <br />"未找到ID为：" + id.toString() + <br />"对应的 Customer。"); <br />return obj; <br />} <br />... <br />} <br /><br />在数据访问层实现这些接口。因为是数据访问层依赖业务层，所以你可以采用多种技术来实现， <br />使用hibernate这样的开源项目，或者手工编写Mapper。 <br /><br />ID id <br /><br />另外一个有争议的问题是Domain层是否要引入与业务无关的ID来标识不同的对象呢？ <br />我的经验是在业务层引入ID的概念会使很多事情变得方便些。 <br />如：Lazyload。 <br />这是否不属于业务的范畴？是在概念上不属于业务。但在业务上 <br />不是没有对应的概念。 <br />例如：保存客户定购信息的订单，作为标识的就是订单号，这是给人使用的。 <br />在使用电脑后，我们也给对象一个它能理解的统一标识，这就是ID。 <br />另外不要使用业务上的概念作为主键和外键，因为它们本来就不是数据库的概念。 <br />否则，会使得业务概念同数据库的概念混淆起来。 <br /><br />ID的使用通常会选择效率较高的long类型。 <br />不过我们的实现走得更远，我们将其封装为ID对象。 <br /></span></span></span></span></span></span></span></span></p> <p><span class="postbody"><span class="postbody"><span class="postbody"><span class="postbody"><span class="postbody"><span class="postbody"><span class="postbody"><span class="postbody"><span class="postbody">Service层 <br /><br />现在我们向上看看将业务层包裹的服务层。 <br />服务层是架设在应用层和业务层的桥梁，用来封装对业务层的访问，因此 <br />可以把服务层看作中介，充当两个角色： <br />1.实现应用层接口要求的接口; <br />2.作为业务层的外观。 <br />服务层的典型调用如下： <br /><br />public interface CustomerServices <br />{ <br />void openCustomer(CustomerInfo cutomerInfo); <br />void customerLostReport(String customerCode,Date expiringDate,String remark); <br />CutomerBasicInfo getCutomerBasicInfo(String customerCode); <br />... <br />} <br /><br />public class CustomerServicesImpl <br />extends ServiceFacade <br />implements CustomerServices <br />{ <br />... <br />public void openCustomer(CustomerInfo cutomerInfo) <br />{ <br />try <br />{ <br />init(); <br /><br />OpenCustomerAct openCustomerAct = <br />new OpenCustomerAct(customerInfo.name, <br />customerInfo.code, <br />customerInfo.address, <br />customerInfo.plainpassword <br />... <br />); <br />openCustomerAct.run(); <br /><br />commit(); <br />} <br />catch(Exception e) <br />{ <br />throw ExceptionPostprocess(e); <br />} <br />} <br /><br />public void customerLostReport(String customerCode,Date expiringDate,String remark) <br />{ <br />try <br />{ <br />Check.require(customerCode != null &amp;&amp; customerCode != "", <br />"无效的客户代码：" + customerCode); <br />init(); <br /><br />CustomerLostReportAct customerLostReportAct = <br />new CustomerLostReportAct(customerCode, <br />expiringDate, <br />remark); <br />customerLostReportAct.run(); <br /><br />commit(); <br />} <br />catch(Exception e) <br />{ <br />throw ExceptionPostprocess(e); <br />} <br />} <br /><br />public CutomerBasicInfo getCutomerBasicInfo(String customerCode) <br />{ <br />try <br />{ <br />Check.require(customerCode != null &amp;&amp; customerCode != "", <br />"无效的客户代码：" + customerCode); <br />init(); <br />Customer customer = CustomerRepository.findByCode(customerCode); <br /><br />//这里选择的是在CustomerRepository外抛出CustomerNotFoundException异常, <br />//另一种方法是在CustomerRepository中抛出CustomerNotFoundException异常。 <br />//因为CustomerRepository在于通过客户代码查找对应的客户。至于是否应该抛出 <br />//异常则交给业务层或服务层来处理。 <br />//这里有很微妙的区别,抛出CustomerNotFoundException应该是谁的职责呢？ <br />//你的想法是什么？<img alt="Smile" src="http://forum.javaeye.com/images/smiles/icon_smile.gif" border="0" /> <br />if(customer == null) <br />throw new CustomerNotFoundException(customerCode); <br /><br />CutomerBasicInfo cutomerBasicInfo = CutomerBasicInfoAssembler.create(customer); <br />return cutomerBasicInfo; <br />} <br />catch(Exception e) <br />{ <br />throw ExceptionPostprocess(e); <br />} <br />} <br /><br />... <br />} <br /><br />服务层的代码很简单，不是吗？ <br /><br />上面的代码可以通过AOP进一步的简化。使用AOP实现我希望代码象下面这样简单。 <br />public class CustomerServicesImpl <br />implements CustomerServices <br />{ <br />... <br />public void openCustomer(CustomerInfo cutomerInfo) <br />{ <br />OpenCustomerAct openCustomerAct = <br />new OpenCustomerAct(customerInfo.name, <br />customerInfo.code, <br />customerInfo.address, <br />customerInfo.plainpassword <br />... <br />); <br />openCustomerAct.run(); <br />} <br /><br />public void customerLostReport(String customerCode,Date expiringDate,String remark) <br />{ <br />Check.require(customerCode != null &amp;&amp; customerCode != "", <br />"无效的客户代码：" + customerCode); <br />CustomerLostReportAct customerLostReportAct = <br />new CustomerLostReportAct(customerCode, <br />expiringDate, <br />remark); <br />customerLostReportAct.run(); <br />} <br /><br />public CutomerBasicInfo getCutomerBasicInfo(String customerCode) <br />{ <br />Customer customer = CustomerRepository.findByCode(customerCode); <br />if(customer == null) <br />throw new CustomerNotFoundException(customerCode); <br /><br />CutomerBasicInfo cutomerBasicInfo = CutomerBasicInfoAssembler.create(customer); <br />return cutomerBasicInfo; <br />} <br /><br />DTO or Not <br /><br />我认为是否使用DTO取决于项目的大小，开发团队的结构,以及对项目演变预期的评估结果。 <br />不使用DTO而直接使用PO传递到应用层适用于一个人同时负责应用层和业务层的短期简单项目; <br />一旦采用该模式作为构架，我不知道业务层是否还能叫做面向对象。 <br />原因如下： <br />1.使用PO承担DTO的职责传递到应用层，迫使PO不能包含业务逻辑，这样业务逻辑会暴露给应用层。 <br />业务逻辑将由类似于XXXManager的类承担，这样看来似乎PO有了更多的复用机会，因为PO只包含getXXX同setXXX类似的属性。 <br />然而这正类似面向过程模式的范例，使用方法操作结构，程序多少又回到了面向过程的方式。 <br />2.将PO直接传递到应用层，迫使应用层依赖于业务层，如果一个人同时负责应用层和业务层那么问题不大； <br />如果是分别由不同的人开发，将使得应用层开发人员必须了解业务层对象结构的细节，增加了应用层开发人员的知识范围。 <br />同时因为这种耦合，开发的并行受到影响，相互交流增多。 <br />3.此外这也会使得业务层在构建PO时要特别小心，因为需要考虑传递到应用层效率问题，在构建业务层时需要 <br />考虑应用层的需要解决的问题是不是有些奇怪？ <br /><br />有人会抱怨写XXXAssember太麻烦，我的经验是XXXAssembler都很简单。 <br /><br />我们使用手机，会发现大多数手机提供给的接口都是相同的，这包括0-9的数字键，绿色的接听键，红色的挂机键，还有一块显示屏。 <br />无论我是拿到NOkIA,还是MOTO的手机，我都能使用，作为手机使用者我没有必要知道手机界面下的结构，不用关心 <br />使用的是SmartPhone还是Symbian。 <br /><br />确实,应用层将服务层和业务层看作黑箱要比看作白箱好得多。 <br /></span></p></span></span></span></span></span></span></span></span><img src ="http://www.blogjava.net/sgsoft/aggbug/239.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/sgsoft/" target="_blank">海天一鸥</a> 2005-01-12 20:23 <a href="http://www.blogjava.net/sgsoft/articles/239.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java XML API 漫谈 </title><link>http://www.blogjava.net/sgsoft/articles/235.html</link><dc:creator>海天一鸥</dc:creator><author>海天一鸥</author><pubDate>Wed, 12 Jan 2005 12:12:00 GMT</pubDate><guid>http://www.blogjava.net/sgsoft/articles/235.html</guid><wfw:comment>http://www.blogjava.net/sgsoft/comments/235.html</wfw:comment><comments>http://www.blogjava.net/sgsoft/articles/235.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/sgsoft/comments/commentRss/235.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/sgsoft/services/trackbacks/235.html</trackback:ping><description><![CDATA[<span class="postbody">在IBM的developerWorks上有几篇非常优秀的关于Java XML API的评测文章，它们是： <br /><br /><a class="postlink" href="http://www-900.ibm.com/developerWorks/cn/xml/x-injava/index.shtml" target="_blank">http://www-900.ibm.com/developerWorks/cn/xml/x-injava/index.shtml</a> <br /><br /><a class="postlink" href="http://www-900.ibm.com/developerWorks/cn/xml/x-injava2/index.shtml" target="_blank">http://www-900.ibm.com/developerWorks/cn/xml/x-injava2/index.shtml</a> <br /><br /><a class="postlink" href="http://www-900.ibm.com/developerWorks/cn/xml/x-databdopt/part2/index.shtml" target="_blank">http://www-900.ibm.com/developerWorks/cn/xml/x-databdopt/part2/index.shtml</a> <br /><br /><a class="postlink" href="http://www-900.ibm.com/developerWorks/cn/xml/x-databdopt/part1/index.shtml" target="_blank">http://www-900.ibm.com/developerWorks/cn/xml/x-databdopt/part1/index.shtml</a> <br /><br />对这几篇文章我想说的就是<span style="FONT-WEIGHT: bold"><span style="COLOR: red">“吐血推荐”</span></span> <br /><br />Java的XML API这几篇文章该讲的都讲到了，我只想补充几点： <br /><br />一、Crimson和Xerces恩仇录 <br /><br />Crimson来自于Sun捐赠给Apache的ProjectX项目，Xerces来自IBM捐赠给Apache的XML4J项目，结果Xerces胜出，成了Apache XML小组全力开发的XML API，而Crimon已经早就不做了，如今Xerces名满天下，到处都是在用Xerces DOM和SAX解析器，只有Sun不服气，非要在JDK1.4里面使用过时的Crimson，让人感觉像是在赌气一样，真是让人可怜又可气！不过IBM发行JDK用的XML 解析器自然是Xerces。 <br /><br />由于JDK的Class Loader的优先级关系，当你采用JAXP编写XML程序的时候，即使把Xerces包引入CLASSPATH，JDK还是会顽固的使用Crimson，这一点通过打开JVM的verbose参数可以观察到。不过JDK也允许你采用其它的解析器，因此我们可以通过在JRE\lib\目录下建一个jaxp.properties的文件，来替换解析器，jaxp.properties内容如下： <br /><br /></span> <table cellspacing="1" cellpadding="3" width="90%" align="center" border="0"> <tbody> <tr> <td><span class="genmed"><b>引用:</b></span></td></tr> <tr> <td class="quote">javax.xml.parsers.DocumentBuilderFactory=org.apache.xerces.jaxp.DocumentBuilderFactoryImpl <br />javax.xml.parsers.SAXParserFactory=org.apache.xerces.jaxp.SAXParserFactoryImpl</td></tr></tbody></table><span class="postbody"><br />这样就可以使用Xerces，当然你必须还是要把Xerces包放到CLASSPATH下。 <br /><br />二、JAXP的姗姗来迟 <br /><br />Sun在XML领域总是后知后觉，等到Sun重视XML的时候，XML的API早就满天 飞了，尤其是IBM具有非常大的领先优势。不过Sun是规范的制订者，于是参考W3C的标准制订了JAXP规范。JAXP不像Xerces和Crimon那样，它只是一个spec，本身是不做任何事情的，它的作用就是提出一个统一的接口，让其它的XML API都来遵循JAXP编程，那么用JAXP写出来的程序，底层的API可以任意切换。 <br /><br />具体来说JAXP包括了几个工厂类，这就是JDK1.4里面的javax.xml.parsers 包，用来寻找符合DOM标准的XML API实现类的位置；此外JAXP还包括一整套interface，这就是JDK1.4里面的org.w3c.dom那几个包。工厂类负责加载DOM的实现类。那么加载的规则是什么呢？ <br /><br />我是通过阅读JAXP的源代码知道的，工厂类首先会根据java命令行传入的参数进行寻找，然后在根据JRE\lib\jaxp.properties中定义的实现类寻找，最后什么都找不到的话，就用Crimson。注意Crimons是由Bootstrap Class Loader来load的，如果你不通过上面两个方法来改变工厂的寻找顺序，那么铁定用Crimson了 <img alt="Sad" src="http://forum.javaeye.com/images/smiles/icon_sad.gif" border="0" /> <br /><br />三、 DOM解析器和DOM API <br /><br />当你严格采用JAXP编程的时候，是遵循W3C的DOm标准的，那么在JAXP底层你实际上可以任意切换不同的DOM实现，例如Xerces，或者Crimon，再或者其它，切换方法就是配置jaxp.properties。因此JAXP就是一些标准接口而已。 <br /><br />而Xerces和Crimon也不单单是一个DOM实现那么简单，他们本身实际上也包含SAX解析器和DOM解析器。所以一个JAXP程序下面有如下层次： <br /><br /></span> <table cellspacing="1" cellpadding="3" width="90%" align="center" border="0"> <tbody> <tr> <td><span class="genmed"><b>引用:</b></span></td></tr> <tr> <td class="quote">JAXP应用程序 -&gt; JAXP接口 -&gt; Xerces DOM实现 -&gt; Xerces DOM/SAX 解析器</td></tr></tbody></table><span class="postbody"><br /><br />只要你用JAXP编程，那么你就可以切换到Crimson上来 <br /><br /></span> <table cellspacing="1" cellpadding="3" width="90%" align="center" border="0"> <tbody> <tr> <td><span class="genmed"><b>引用:</b></span></td></tr> <tr> <td class="quote">JAXP应用程序 -&gt; JAXP接口 -&gt; Crimson DOM实现 -&gt; Crimson DOM/SAX 解析器</td></tr></tbody></table><span class="postbody"><br /><br />另外你也可以这样来做： <br /><br /></span> <table cellspacing="1" cellpadding="3" width="90%" align="center" border="0"> <tbody> <tr> <td><span class="genmed"><b>引用:</b></span></td></tr> <tr> <td class="quote">JAXP应用程序 -&gt; JAXP接口 -&gt; Crimson DOM实现 -&gt; Xerces DOM/SAX 解析器</td></tr></tbody></table><span class="postbody"><br /><br />不过如果你的程序不安装JAXP来写，那么就没有办法切换不同的DOM实现了。 <br /><br />四、不是标准的dom4j和jdom <br /><br />W3C的DOM标准API难用的让人想撞墙，于是有一帮人开发Java专用的XML API目的是为了便于使用，这就是jdom的由来，开发到一半的时候，另一部分人又分了出来，他们有自己的想法，于是他们就去开发dom4j，形成了今天这样两个API，至于他们之间的性能，功能之比较看看上面我推荐的文章就知道了，jdom全面惨败。 <br /><br />jdom 相当于上面的 JAXP接口 ＋ Xerces DOM实现部分，它本身没有解析器，它可以使用Xerces或者Crimson的解析器，就是这样： <br /><br /></span> <table cellspacing="1" cellpadding="3" width="90%" align="center" border="0"> <tbody> <tr> <td><span class="genmed"><b>引用:</b></span></td></tr> <tr> <td class="quote">jdom应用程序 -&gt; jdom API -&gt; Xerces/Crimson解析器</td></tr></tbody></table><span class="postbody"><br /><br />dom4j 和jdom类似，不过他自己绑定了一个叫做Alfred2的解析器，功能不是很全，但是速度很快，当没有其它的解析器的时候，dom4j将使用Alfred2解析器，如下： <br /><br /></span> <table cellspacing="1" cellpadding="3" width="90%" align="center" border="0"> <tbody> <tr> <td><span class="genmed"><b>引用:</b></span></td></tr> <tr> <td class="quote">dom4j应用程序 -&gt; dom4j API -&gt; Xerces/Crimson解析器</td></tr></tbody></table><span class="postbody"><br /><br />或者 <br /><br /></span> <table cellspacing="1" cellpadding="3" width="90%" align="center" border="0"> <tbody> <tr> <td><span class="genmed"><b>引用:</b></span></td></tr> <tr> <td class="quote">dom4j应用程序 -&gt; dom4j API -&gt; Alfred2解析器</td></tr></tbody></table><span class="postbody"><br /><br />你在SF上下载的dom4j.jar是不含 Alfred2解析器的，而dom4j-full.jar包含了 Alfred2解析器，在这种情况下，实际上你什么也不需要，光是一个dom4j-full.jar就全部都包括了。 <br /><br />因此可以看出采用dom4j/jdom编写的应用程序，已经不具备可移植性了。 <br /><br />五、小插曲 <br /><br />Sun是JAXP标准的制订者，甚至很执著的在JDK1.4里面绑定Crimson DOM实现和解析器，然后可笑的是，Sun自己的JAXM RI竟然不是用JAXP写出来的，而是dom4j，制订标准让大家遵守，自己却监守自盗，这未免太说不过去了吧！ <br /><br />BTW: Hibernate也用的是dom4j来读取XML配置文件，如今已经越来越多的程序纷纷采用dom4j，如果你不是那么在乎可移植性，我强烈建议你采用dom4j。</span><img src ="http://www.blogjava.net/sgsoft/aggbug/235.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/sgsoft/" target="_blank">海天一鸥</a> 2005-01-12 20:12 <a href="http://www.blogjava.net/sgsoft/articles/235.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>对象/关系映射--关联模式</title><link>http://www.blogjava.net/sgsoft/articles/234.html</link><dc:creator>海天一鸥</dc:creator><author>海天一鸥</author><pubDate>Wed, 12 Jan 2005 12:03:00 GMT</pubDate><guid>http://www.blogjava.net/sgsoft/articles/234.html</guid><wfw:comment>http://www.blogjava.net/sgsoft/comments/234.html</wfw:comment><comments>http://www.blogjava.net/sgsoft/articles/234.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/sgsoft/comments/commentRss/234.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/sgsoft/services/trackbacks/234.html</trackback:ping><description><![CDATA[<blockquote class="sumblk"> <p><strong>摘要</strong><br />这一章节描述了两种用于映射对象间关系的模式：Foreign Key Association和Association Table。注意，1:n关联背后隐藏的问题和Representing Collections in a Relational Database [Bro+96]中描述的问题是一致的。(2002-08-20 12:29:17)</p> <p><strong>By </strong><a href="mailto:lxaxing@263.net"><strong>axing</strong></a><br /><br /><span class="contentblk"></p> <h4>将对象关联映射到表的模式</h4> <p>这一章节描述了两种用于映射对象间关系的模式：Foreign Key Association和Association Table。注意，1:n关联背后隐藏的问题和Representing Collections in a Relational Database [Bro+96]中描述的问题是一致的。 </p> <h4>模式：Foreign Key Association</h4> <p><b>摘要</b></p> <p>该模式展示了如何将对象间的1:n的关系映射到关系型数据库表中。</p> <p><b>示例</b></p> <p>考虑经典的订单/订单明细（Order / OrderItem）的例子，一张有效的订单将包含0个以上的订单明细。</p> <p><img alt="" src="http://www.linuxaid.com.cn/articles/8/0/802053421/2002080117.jpg" width="450" /> </p> <p><b>问题</b></p> <p>如何把1:n的关系映射到关系型数据库表中？</p> <p><b>约束</b></p> <p>参见一般约束</p> <p><b>解决方案</b></p> <p>在依赖（dependent）对象的表中插入所属（owner）对象的OID。这个OID可以是数据库的关键字或一个Synthetic Object Identity。</p> <p><b>结构</b></p> <p><a href="http://www.linuxaid.com.cn/articles/8/0/802053421/2002080118.jpg" target="_blank"><img alt="" src="http://www.linuxaid.com.cn/articles/8/0/802053421/2002080118.jpg" width="450" border="0" /></a> </p> <p><b>结论</b></p> <ol> <li><b>读性能：</b>读取一个订单对象将需要一个连接操作或两次读操作。可以在订单对象中加入到订单明细的引用集合。 <li><b>写性能：</b>该模式将按照1:n的关联写入依赖对象，如果写入操作不写入未改变的对象的话，那么写入的成本依赖于改变的依赖对象的个数。 <li><b>性能和冗余 VS 维护成本和普通窗体：</b>该映射模式是关系数据库中最常见的模式，因此它和普通的窗体并没有任何的冲突，因此它的维护成本是比较合理的。 <li><b>所占空间接近于最优</b>－－除了在依赖对象表中需要的外键字段以外。 <li><b>特殊查询：</b>由于映射是关系数据库应用最为常见的形式，因此特殊的查询也是不难实现的。 <li><b>应用程序类型：</b>这种映射模式最适合于关系型应用程序。它不适合用于CAD或CASE应用系统中。因为它是基于以外键形式连接关系表的。实现一个关联需要一次的连接操作或两次的数据库访问。基于页面的存储系统，例如OODBMS，能够更快的处理类似的问题。 <li><b>和旧有系统的集成：</b>因为大多数的旧有系统正式使用这种映射关系的，把1:n的关联转换为对象不会出现任何新的问题。 </li></ol> <p><b>实现</b></p> <ol> <li><b>一般性能：</b>如果性能上出现低效的情况，你可以考虑在对象/关系映射层下面再增加一个关系型数据库访问层，并应用性能改进模式，例如Controlled Redundancy、Denormalization、或Overflow Tables [Kel+97]。这些模式的目的是为了让你在不影响逻辑映射模式的前提下优化物理表模式。 <li><b>更新性能：</b>当更新订单明细对象（依赖对象）时，你应该只更新那些已经被修改的对象。更新和插入操作都是较为昂贵的操作。 <li><b>预取依赖对象：</b>在这个例子中，大多数的用例都需要读入所有的依赖对象（如订单明细），你可以使用连接操作来获得所有的数据，并从单个数据库操作的返回值中构建所有者对象和依赖对象： <p><br />select * from Order O, OrderItem I<br />where O.key = ‘YourOrderKey’ and<br />O.key = I.OrderKey</p></li></ol> <p><b>相关模式</b></p> <p>在实践中，1:n的关联总是难以和聚合分离开来。因此在研究本模式时也需要同时参考聚合模式Single Table Aggregation和Foreign Key Aggregation。后者和该模式同样的解决方案，只有稍许的不同。</p> <p>使用外键的备选方案，包括Controlled Redundancy、Denormalization和Overflow Tables [Kel+97]。</p> <p>该模式和Association Table模式非常相近，后者是用户解决n:m映射的问题。参看Representing Object Relationships as Tables [Bro+96]。</p> <p>&nbsp;</p> <h4>模式Association Table</h4> <p><b>摘要</b></p> <p>该模式展示了怎样把对象间n:m的关系映射到关系型数据库表中。 <p><b>示例</b></p> <p>员工对象和部门对象间存在着n:m的关系。一个员工可以在多个部门中工作，一个部门通常也包含了不只一个的员工。</p> <p><a href="http://www.linuxaid.com.cn/articles/8/0/802053421/2002080119.jpg" target="_blank"><img alt="" src="http://www.linuxaid.com.cn/articles/8/0/802053421/2002080119.jpg" width="450" border="0" /></a> </p> <p><b>问题</b></p> <p>怎样把n:m的关系映射到关系型数据库？</p> <p><b>约束</b></p> <p>参见通用约束</p> <p><b>解决方案</b></p> <p>创建一张单独的表，来存放两种对象类型的关系中的对象标志（或外键）。其它的对象类型到表的映射可以使用另外适当的模式来处理。</p> <p><b>结构</b></p> <p><a href="http://www.linuxaid.com.cn/articles/8/0/802053421/2002080120.jpg" target="_blank"><img alt="" src="http://www.linuxaid.com.cn/articles/8/0/802053421/2002080120.jpg" width="450" border="0" /></a> </p> <p><b>结论</b></p> <p>这里的结论和Foreign Key Association中是类似的，只是在环境上有些许的不同，因此这里我们就不重复了。</p> <p><b>实现</b></p> <ol> <li><b>一般性能：</b>如果性能没有达到预期效果，可以考虑使用数据库优化模式，例如Controlled Redundancy、Denormalization、or Overflow Tables [Kel+97]。我们的例子中处理了n:m的关系，这会使关系变得更为复杂。因此可以把它打散，以便于实行数据库优化。 <li><b>预取对象：</b>如果在上面的例子中，你预先知道大多数的用例都需要读出所有的依赖对象，例如部门中的员工。那就可以使用连接操作获取所有的数据，并从数据集中构建部门对象和员工对象。如： <p>select * from DepartmentTable D, EmployeeDepartmentTable ED,EmployeeTable E<br />where D.SyntheticOID = ‘YourDepartment’<br />D.SyntheticOID = ED.DepartmentKey and<br />ED.EmployeeKey = E.SyntheticOID</p></li></ol> <p><b>相关模式</b></p> <p>该模式，非常接近于Foreign Key Association。参看Representing Object Relationships as Tables [Bro+96]。</p> <p><b>已知应用</b></p> <p>Single Table Aggregation, Foreign Key Aggregation, Foreign Key Association, 和Association Table已用于Persistence [www.persistence.com]和TopLink的Smalltalk框架[www.objectpeople.com/toplink/]，以及其它的大多数持久性框架中。这些模式还被用于HYPO-Bank [Col+96,Kel+96]的对象/关系访问层，和POET [POE96]的对象/关系网关。</p> <p>One Inheritance Tree One Table and One Class One Table曾在POET [POE96]的对象/关系网关被讨论过，同时被提及的还包括One Inheritance Path One Table。后者还被Champs和HYPO的项目中[Col+96, Hah+95, Kel+96]。</p> <p>Objects in BLOBs曾用在SMRC搜索原型上[Rei+94, Rei+96]。</p></span></blockquote><img src ="http://www.blogjava.net/sgsoft/aggbug/234.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/sgsoft/" target="_blank">海天一鸥</a> 2005-01-12 20:03 <a href="http://www.blogjava.net/sgsoft/articles/234.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>对象/关系映射--继承模式</title><link>http://www.blogjava.net/sgsoft/articles/233.html</link><dc:creator>海天一鸥</dc:creator><author>海天一鸥</author><pubDate>Wed, 12 Jan 2005 11:58:00 GMT</pubDate><guid>http://www.blogjava.net/sgsoft/articles/233.html</guid><wfw:comment>http://www.blogjava.net/sgsoft/comments/233.html</wfw:comment><comments>http://www.blogjava.net/sgsoft/articles/233.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/sgsoft/comments/commentRss/233.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/sgsoft/services/trackbacks/233.html</trackback:ping><description><![CDATA[<blockquote class="sumblk"><strong>摘要</strong><br />把继承层次映射到关系型数据库表有很多种做法。以下展示的模式是单种的映射方式，在实际中，你可以混合使用不同的映射方式。(2002-08-20 12:28:52)</blockquote> <p><strong>By </strong><a href="mailto:lxaxing@263.net"><strong>axing</strong></a><br /><br /><span class="contentblk"></p> <h4>继承映射模式</h4> <p>把继承层次映射到关系型数据库表有很多种做法。以下展示的模式是单种的映射方式，在实际中，你可以混合使用不同的映射方式。</p> <p>以下的讨论没有涉及多重继承。在领域层中很少会遇到有多重继承的情况。大多数多重继承的使用都可以使用协议继承来实现，一个类从虚基类中继承多种协议。协议类很少有需要持久性的，因此我们只讨论简单的继承。</p> <p><b>运行实例</b></p> <p>我们选用了一个称为合伙人（partner）的系统中的一部分。一个群体（Party）由任何形式的人（person）组成，可以是自然人（natural person）或法人（institution）。客户和员工都是群体。对于员工，我们将之区分为专职员工（SalariedEmployee）和兼职员工（FreelanceEmployee）。见下图：</p> <p><a href="http://www.linuxaid.com.cn/articles/4/9/492565019/2002080108.jpg" target="_blank"><img alt="" src="http://www.linuxaid.com.cn/articles/4/9/492565019/2002080108.jpg" width="450" border="0" /></a> </p> <p>图中使用的属性比较少，现实中的系统中会有更多的属性，但是我们这里只是列出了可以表示我们所讨论的模式不同之处的属性。因此它们之中都没有涉及到读咱的属性或关系。并且我们假设以上层次中每一个类都不是虚基类，所有的五个类都可以生成实例。</p> <h4>模式: One Inheritance Tree One Table</h4> <p><b>摘要</b></p> <p>该模式展示了一种方法，将一个完整的继承层次映射到单个的数据库表中。</p> <p><b>问题</b></p> <p>怎样把一个完整的继承层次映射到单个的数据库表中？</p> <p><b>约束</b></p> <p>除了文章一开始列出的通用约束，还包括了如下的约束：</p> <ol> <li><b>多态读取、存储空间和写/更新性能的对比：</b>在一个继承层次中，你需要支持这样的查询：在给定的条件下，查找所有匹配的Party对象。结果集还必须是多态的。在该例中，包括Employee、FreelanceEmployee或SalariedEmployee。这种方案虽然能够支持多态查询，但是既浪费空间，也影响了写/更新性能。 <li><b>数据库的锁模式：</b>有些数据库只是实现了页面锁，这样的话，你就需要注意数据库的流量，不要让一个表中的锁超出了限制的数量而影响性能。如果把过多的类映射到单个表中，很明显，你必须注意流量的问题。 <li><b>继承层次的深度：</b>某些方案适合于处理扁平性的继承层次，但难于处理纵深的继承层次。 <li><b>维护负担：</b>把单个的对象的数据跨越多个表来存放，这样的做法可以加快多态读取的速度，但缺点是增加了维护量，一旦对象需要新增或删除属性，模式的发展也需要考虑物理数据模型中数据的重复问题，这极易导致维护的噩梦。其它的维护实例还包括在一个继承层次中插入类和删除类。 <li><b>用户自定义的查询：</b>如果你希望给用户赋予自定义查询的权利，那么你需要从用户的角度考虑该映射是否能为用户所理解。 </li></ol> <p><b>解决方案</b></p> <p>把继承层次中的所有对象的属性全集对应到数据库的单个表中。每条记录中那些无用的字段使用Null值。</p> <p><b>结构</b></p> <p><a href="http://www.linuxaid.com.cn/articles/4/9/492565019/2002080109.jpg" target="_blank"><img alt="" src="http://www.linuxaid.com.cn/articles/4/9/492565019/2002080109.jpg" width="450" border="0" /></a> </p> <p><b>解决方案示例</b></p> <p>上例中的表设计如下图：</p> <p><img alt="" src="http://www.linuxaid.com.cn/articles/4/9/492565019/2002080110.jpg" /></a> </p> <p><b>结论</b></p> <ol> <li><b>写/更新性能：</b>One Inheritance Tree One Table允许在单词的数据库操作中对任何基类的任何一个子类读取和写入。 <li><b>多态读性能：</b>象全部的基类的子类都可以在一张表中找到一样，多态读也很简单。唯一的难点就是为一条选中的记录构造正确的对象类型。处理这种情况的模式有很多，例如Abstract Interface[Col96]。 <li><b>所占空间：</b>就像你在上面的映射中所描述的，存储对象的属性会浪费一些空间，浪费空间的多少取决于继承层次的深度。层次越深，不同的属性越多，属性的全集就越大，也就越浪费空间。 <li><b>通过多个表来均衡数据库负载：</b>将过多的类映射到单个表中会导致低性能。可以在如下的数据库行为中发现这些问题的来源： <li>如果数据库使用页面级锁，一张表上的过量的传输量会大大降低数据库的访问速度。巧妙的安排簇可以抵消部分的开销。如果单个表上的传输量超过限制，性能将继续恶化，甚至引起死锁。 <li>在单个表上太多的锁还会导致锁扩散。锁的数量是关系型数据库系统中锁扩散问题的罪魁祸首。 <li>某些类需要次级索引来提高搜索速度。如果你在一个单独的数据库表中实现了很多类，相应的你也需要在表中加入索引。一张表上过多的索引会使得更新的时候需要同时更新所有的索引，降低了速度。 <li><b>维护成本：</b>当映射比较直接、比较简单的时候，只要继承的层次不会很多，模式的改进相对也会比较直接和简单。 <li><b>特殊查询：</b>由于映射和直观，因此一些特殊的查询也会相对较容易。 </li></ol> <p><b>实现</b></p> <ol> <li>考虑将所有的对象都映射到单个的表：你可以把所有的对象类型都放在单个表中－－这将导致表上的大传输量。对于小型的应用系统类说，这不失为切实可行且灵活的方法。 <li><b>空间的浪费：</b>你需要检查关系数据库是否允许空值压缩。如果允许的话，那么这种映射方法无疑将更具吸引力，因为空值也不会浪费空间。类型标识：表中需要插入类型信息。可以在Synthetic Object Identities中包含类型信息。 </li></ol> <p><b>相关模式</b></p> <p>参看Representing Inheritance in a Relational Database [Bro+96]</p> <p>&nbsp;</p> <h4>模式：One Class One Table</h4> <p><b>摘要</b></p> <p>该模式讨论了如何把一个继承层次中各个类映射到不同的数据库表中。</p> <p><b>问题</b></p> <p>如何把继承层次中的类映射到数据库表中？</p> <p><b>约束</b></p> <p>参见One Inheritance Tree One Table模式。</p> <p><b>解决方案</b></p> <p>将每个类的属性放到不同的表中。在每个表中插入一个Synthetic OID，将子类的行和父类所在表的行关联起来。</p> <p><b>结构</b></p> <p><a href="http://www.linuxaid.com.cn/articles/4/9/492565019/2002080111.jpg" target="_blank"><img alt="" src="http://www.linuxaid.com.cn/articles/4/9/492565019/2002080111.jpg" width="450" border="0" /></a> </p> <p><b>解决方案示例</b></p> <p>将前例中的类映射到数据库后产生五张表－－每个类一张。单个的SalariedEmployee将存储在五张表其中的三张中：</p> <p><a href="http://www.linuxaid.com.cn/articles/4/9/492565019/2002080112.jpg" target="_blank"><img alt="" src="http://www.linuxaid.com.cn/articles/4/9/492565019/2002080112.jpg" width="450" border="0" /></a> </p> <p><b>结论</b></p> <ol> <li><b>写/更新性能：</b>该模式提供了非常灵活的映射机制，但缺乏一个较好的性能。考虑在上例中读取FreelanceEmployee的一个实例。它将会包括三次的数据库读操作：一次读FreelanceEmployee表， 一次读Employee表，一次读Party表。写同样需要三次操作，并更新一个或多个索引。在读写相关的任务上，这种映射方式相当费资源，这个代价还随着继承层次的增多而增大。 <li><b>多态读性能：</b>在我们的例子中，FreelanceEmployee的实例分别对应于不同表的Employee实例和Party实例。因此，多态读只需要读取一张表。这是该模式除去节约空间的优点之外吸引人的另一大优点。 <li><b>所占空间：</b>这种映射几乎有最佳的空间节约性。唯一的容易就是额外的synthetic OID，来连接不同的层级层次。 <li><b>维护成本：</b>由于这种映射比较直接，也易于了解，模式的改进也相应较为直接、容易。 <li><b>特殊查询：</b>由于映射需要访问多个表来获取一个对象实例的数据，特殊的查询比较难以组织，尤其是对于无经验的用户。 <li><b>根表上的重负载：</b>该模式将会导致根对象类型表上的重负载。在该例中，持有FreelanceEmployee表的写入锁的事务将同时持有对Party表和Employee表的写入锁。参看One Inheritance Tree One Table的结论部分对数据库表瓶颈的负面影响的讨论。 </li></ol> <p><b>实现</b></p> <ol> <li><b>虚类：</b>注意虚类也需要映射到单独的表中。 <li><b>类型标识：</b>从前一个模式的例子中，我们假设Synthetic Object Identity已经包含了类型信息。为了从多态读取查询返回的结果中构建正确的类型，需要了解某些类型信息。 </li></ol> <p><b>相关模式</b></p> <p>参看Representing Inheritance in a Relational Database [Bro+96]。</p> <h4>模式：One Inheritance Path One Table</h4> <p><b>摘要</b></p> <p>该模式显式了一种将继承路径中的所有属性放到单个的数据库表中的方法。</p> <p><b>问题</b></p> <p>如何把一个继承层次中的所有类映射到数据库的表中？</p> <p><b>约束</b></p> <p>和One Inheritance Tree One Table模式的约束相同。</p> <p><b>解决方案</b></p> <p>将每个类的属性映射到不同的表中。并在表中加入该类的父类的所有属性。</p> <p><b>结构</b></p> <p><a href="http://www.linuxaid.com.cn/articles/4/9/492565019/2002080113.jpg" target="_blank"><img alt="" src="http://www.linuxaid.com.cn/articles/4/9/492565019/2002080113.jpg" width="450" border="0" /></a> </p> <p><b>解决方案示例</b></p> <p>上例中映射的结果将会产生五张表－－每个类一张。SalariedEmployee用其中的一张表就可以表示，SalariedEmployee的映射方法如下：</p> <p><img alt="" src="http://www.linuxaid.com.cn/articles/4/9/492565019/2002080114.jpg" /> </p> <p><b>结论</b></p> <ol> <li><b>写/更新性能：</b>映射需要单次的数据库操作来完成对一个对象的读写。 <li><b>多态读性能：</b>本例中，对所有的Party对象的查询意味着要访问五张表。和One Class One Table模式移机One Inheritance Tree One Table模式比起来是不小的开销。 <li><b>所占空间：</b>这种映射提供了最佳的空间占用。没有任何的冗余属性，甚至连额外的synthetic OID都没有。 <li><b>维护成本：</b>产物一个新的子类意味着需要更新所有的多态查询。增加或删除父类的属性将会导致对所有子类的表结构的修改。 <li><b>特殊查询：</b>由于映射需要访问不只一张表来完成多态搜索，特殊的多态查询对无经验的用户来说难以编写。对叶结点类的查询将会非常的复杂。 </li></ol> <p><b>实现</b></p> <ol> <li><b>虚类：</b>注意虚类不要映射到表。 <li><b>类型标识：</b>不需要在表中插入类型信息，因为可以通过表的名称来区分对象类型。因为Synthetic Object Identities包含了类型信息，可以考虑减少它的长度来节省部分的空间。 </li></ol> <p><b>相关模式</b></p> <p>参见Representing Inheritance in a Relational Database [Bro+96]。</p> <p>&nbsp;</p> <h4>模式：Objects in BLOBs</h4> <p><b>摘要</b></p> <p>该模式展示了一种使用BLOB，将对象映射到单个数据库表的方法。该模式覆盖了继承、聚合和关联。</p> <p><b>问题</b></p> <p>怎样把对象映射到关系型数据库？</p> <p><b>约束</b></p> <p>参见One Inheritance Tree One Table模式。</p> <p><b>解决方案</b></p> <p>表包括两个字段：一个是synthetic OID，另一个是变长的BLOB，后者囊括了一个对象内的所有数据。使用流把对象的数据存放到BLOB中。</p> <p><b>结构</b></p> <p><a href="http://www.linuxaid.com.cn/articles/4/9/492565019/2002080115.jpg" target="_blank"><img alt="" src="http://www.linuxaid.com.cn/articles/4/9/492565019/2002080115.jpg" width="450" border="0" /></a> </p> <p><b>解决方案示例</b></p> <p>任何一种的设计方案都和上图相似。</p> <p><b>结论</b></p> <ol> <li><b>写/更新性能：</b>Objects in BLOBs可以在单次的数据库操作中读取、写入基类的任何子类。注意，在很多RDBMS中，BLOBs都不是最快的访问数据的类型。 <li><b>多态读：</b>搜索类来获得属性是非常困难的。由于你没有办法访问BLOB的内部结构，因此就需要在数据库中注册函数，以便于访问属性。这种函数的实现可以参考[Loh+91]。定义和维护这些函数也需要付出客观的代价。 <li><b>特殊查询：</b>由于搜索类中的属性难以实现，因此特殊查询也同样难于表达。还必须要额外的定义函数。 <li><b>所占空间：</b>如果你的数据库可以实现变长的BLOBs，所占空间就是最小的。 <li><b>维护成本：</b>数据库模式的演进将会和面向对象数据库一样容易。 </li></ol> <p><b>实现</b></p> <ol> <li><b>相似实现的起源：</b>Objects in BLOBs已用于原型的研究，该原型正尽可能的像OODBMS靠近，只不过它是使用一个关系型数据库作为存储管理。因此该模式的实现机会等于是在一个已存在的存储管理器的基础上实现OODBMS。 <li><b>表间的数据库负载均衡：</b>把过多的类映射到一个单独的表会造成低性能。具体的讨论可以参见One Inheritance Tree One Table模式的相关章节。 <li><b>把OODB的特点和关系型数据库相结合：</b>把这个模式同其它类的对象/关系映射相结合是切实可行的。在这个例子中，BLOB存储了复杂的对象的结构，类似于一个项目计划图一样。可以设置其它的字段来存储信息，以供那些访问已结构化的数据的特殊查询使用。（见图3，这种用BLOBs来存储对象的方式已用于SMRC原型研究[Rei+94, Rei+96]。 SMRC中的BLOBs不仅存储单个类的属性，还包含了一个对象网，这个对象网采用流方式存储在BLOBs中。流方式存储在图3中描述）。此外，除了我们上面介绍的纯粹的使用这种方法外，它还允许关系型数据和OODBMS共存。 </li></ol> <p><a href="http://www.linuxaid.com.cn/articles/4/9/492565019/2002080116.jpg" target="_blank"><img alt="对象数据和关系型数据的共存" src="http://www.linuxaid.com.cn/articles/4/9/492565019/2002080116.jpg" width="450" border="0" /></a> </p> <p><b>图3：对象数据和关系型数据的共存。</b></p> <p><b>相关模式</b></p> <p>当只使用该模式的时候，这个模式类似于One Inheritance Tree One Table映射，参看Representing Inheritance in a Relational Database [Bro+96]。</p> <p>&nbsp;</p></span><img src ="http://www.blogjava.net/sgsoft/aggbug/233.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/sgsoft/" target="_blank">海天一鸥</a> 2005-01-12 19:58 <a href="http://www.blogjava.net/sgsoft/articles/233.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>对象/关系映射--聚合模式 </title><link>http://www.blogjava.net/sgsoft/articles/232.html</link><dc:creator>海天一鸥</dc:creator><author>海天一鸥</author><pubDate>Wed, 12 Jan 2005 11:53:00 GMT</pubDate><guid>http://www.blogjava.net/sgsoft/articles/232.html</guid><wfw:comment>http://www.blogjava.net/sgsoft/comments/232.html</wfw:comment><comments>http://www.blogjava.net/sgsoft/articles/232.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/sgsoft/comments/commentRss/232.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/sgsoft/services/trackbacks/232.html</trackback:ping><description><![CDATA[<span class="titleblk">对象/关系映射--聚合模式</span><br /> <blockquote class="sumblk"> <p><strong>摘要</strong><br />在面向对象建模期间（OOA）最难回答的问题是，何时使用聚合，何时使用关联。性能和灵活性的权衡将会影响到这个问题的答案。你可以使用Single Table Aggregation。这是一种最自然的聚合映射方式。你也可以使用Foreign Key Aggregation，它常常用于处理1:n聚合的映射，我们将在Foreign Key Aggregation的相关章节中讨论它。(2002-08-20 12:28:18)</p> <p><strong>By </strong><a href="mailto:lxaxing@263.net"><strong>axing</strong></a><br /><br /><span class="contentblk"></p> <h4>聚合映射的模式</h4> <p>在面向对象建模期间（OOA）最难回答的问题是，何时使用聚合，何时使用关联。性能和灵活性的权衡将会影响到这个问题的答案。你可以使用Single Table Aggregation。这是一种最自然的聚合映射方式。你也可以使用Foreign Key Aggregation，它常常用于处理1:n聚合的映射，我们将在Foreign Key Aggregation的相关章节中讨论它。</p> <h4>模式：Single Table Aggregation</h4> <p><b>摘要：</b></p> <p>该模式展示了如何通过把所有的聚合的对象属性集成到单个的表中的方法把聚合映射到一个关系数据模型。</p> <p><b>示例：</b></p> <p>考虑下列的对象模型：</p> <p><a href="http://www.linuxaid.com.cn/articles/4/5/458698163/2002080103.jpg" target="_blank"><img alt="一个AddressType" src="http://www.linuxaid.com.cn/articles/4/5/458698163/2002080103.jpg" width="450" border="0" /></a> </p> <p><b>图1.一个AddressType，为其它的对象所聚合。</b></p> <p><b>问题：</b></p> <p>如何将聚合映射到关系表中？</p> <p><b>约束：</b></p> <ol> <li><b>性能：</b>为了得到最佳的性能，该方案应该在一次的数据库访问中，不用Join操作获取一个对象。数据库访问应该只获取最少的页面，以节省IO带宽。 <li><b>可维护性：</b>为了获得最佳的可维护性，那些为多个对象所聚合的聚合类型，应该映射到一组表中，而不是分散到物理数据模型的各个点上。在数据模型层次上实行标准化，这样可以使维护简单。 <li><b>数据库的一致性：</b>聚合意味着被聚合对象的生命周期和需要聚合的对象的生命周期是相互耦合的。这一点需要由数据库或应用程序代码来保证。 </li></ol> <p><b>解决方案：</b></p> <p>把被聚合对象的属性和使用聚合对象的属性放在同一张表中。</p> <p><b>结构：</b></p> <p><a href="http://www.linuxaid.com.cn/articles/4/5/458698163/2002080104.jpg" target="_blank"><img alt="" src="http://www.linuxaid.com.cn/articles/4/5/458698163/2002080104.jpg" width="450" border="0" /></a> </p> <p>使用聚合的对象被转换为物理数据模型的一张表中，被聚合对象的数据集成到该表中。</p> <p><b>解决方法示例：</b></p> <p>我们为Customer对象创建Customer表。InvoiceAddress和DeliveryAddress都集成到Customer表中。</p> <p><a href="http://www.linuxaid.com.cn/articles/4/5/458698163/2002080105.jpg" target="_blank"><img alt="将一个被聚合的对象类型映射到使用聚合的对象的数据库表中" src="http://www.linuxaid.com.cn/articles/4/5/458698163/2002080105.jpg" width="450" border="0" /></a> </p> <p><b>图2.将一个被聚合的对象类型映射到使用聚合的对象的数据库表中。</b></p> <p>我们使用前缀来区分同类的属性。这有点类似于C++中的命名空间的概念，例如Customer.DeliveryAddress.Street。</p> <p><b>结论：</b></p> <ol> <li><b>性能：</b>在性能方面，该方案是最佳的，因为只需要访问一张表就能够获取一个带聚合的对象，并读入所有聚合对象。另一方面，由于聚合对象的字段的增多，一次读取将会增大数据库读入的页面数，导致IO带宽的浪费。 <li><b>可维护性和灵活性：</b>如果聚合的对象类型被多个对象所引用，那么将会降低可维护性，因为每一次对聚合对象类型的修改都会导致对所有引用聚合对象的修改。 <li><b>数据库的一致性：</b>删除使用聚合的对象时，聚合对象将会自动删除。不需要任何其它的程序或数据库触发器来控制。 <li><b>特殊查询：</b>类似查询数据库中所有的AddressType对象之类的查询，都会变得很难处理。 </li></ol> <p><b>实现：</b></p> <ol> <li><b>命名规则：</b>需要为聚合的对象的属性考虑前缀或其它的命名规则。在上面的例子中，我们使用属性名称的缩写形式作为前缀。 <li><b>物理数据库的页面大小：</b>将聚合对象和使用聚合对象放在同一张表中，在一定程度上弥补了由于对象的部分属性存储在另一个数据库页面上的性能损失。这种情况下，读入的是两个页面，而不是一个。 </li></ol> <p><b>变化：</b></p> <p>我们已经讨论了使用聚合对象类型和聚合对象类型之间最简单的1:1关系。Foreign Key Association模式描述了两种对象类型间1:n的关系，Overflow Table则展示了在1:n的关系下避免采用外键的技巧。</p> <p><b>相关模式：</b></p> <p>Foreign Key Aggregation模式是Single Table Aggregation模式的备选方案。参考Representing Collections in a Relational Database [Bro+96]。当应用到普通的关系型数据库访问层的时候，还可以对比Denormalization [Kel+97]。</p> <p><b>参考：</b></p> <p>《Mainstream Objects》（Ed Yourdon [You+95] ）在第21章完整的讨论了在建模时期何时使用聚合和关联、以及如何使用它们的问题。</p> <p>&nbsp;</p> <h4>聚合映射的模式</h4> <p>在面向对象建模期间（OOA）最难回答的问题是，何时使用聚合，何时使用关联。性能和灵活性的权衡将会影响到这个问题的答案。你可以使用Single Table Aggregation。这是一种最自然的聚合映射方式。你也可以使用Foreign Key Aggregation，它常常用于处理1:n聚合的映射，我们将在Foreign Key Aggregation的相关章节中讨论它。</p> <h4>模式：Single Table Aggregation</h4> <p><b>摘要：</b></p> <p>该模式展示了如何通过把所有的聚合的对象属性集成到单个的表中的方法把聚合映射到一个关系数据模型。</p> <p><b>示例：</b></p> <p>考虑下列的对象模型：</p> <p><a href="http://www.linuxaid.com.cn/articles/4/5/458698163/2002080103.jpg" target="_blank"><img alt="一个AddressType" src="http://www.linuxaid.com.cn/articles/4/5/458698163/2002080103.jpg" width="450" border="0" /></a> </p> <p><b>图1.一个AddressType，为其它的对象所聚合。</b></p> <p><b>问题：</b></p> <p>如何将聚合映射到关系表中？</p> <p><b>约束：</b></p> <ol> <li><b>性能：</b>为了得到最佳的性能，该方案应该在一次的数据库访问中，不用Join操作获取一个对象。数据库访问应该只获取最少的页面，以节省IO带宽。 <li><b>可维护性：</b>为了获得最佳的可维护性，那些为多个对象所聚合的聚合类型，应该映射到一组表中，而不是分散到物理数据模型的各个点上。在数据模型层次上实行标准化，这样可以使维护简单。 <li><b>数据库的一致性：</b>聚合意味着被聚合对象的生命周期和需要聚合的对象的生命周期是相互耦合的。这一点需要由数据库或应用程序代码来保证。 </li></ol> <p><b>解决方案：</b></p> <p>把被聚合对象的属性和使用聚合对象的属性放在同一张表中。</p> <p><b>结构：</b></p> <p><a href="http://www.linuxaid.com.cn/articles/4/5/458698163/2002080104.jpg" target="_blank"><img alt="" src="http://www.linuxaid.com.cn/articles/4/5/458698163/2002080104.jpg" width="450" border="0" /></a> </p> <p>使用聚合的对象被转换为物理数据模型的一张表中，被聚合对象的数据集成到该表中。</p> <p><b>解决方法示例：</b></p> <p>我们为Customer对象创建Customer表。InvoiceAddress和DeliveryAddress都集成到Customer表中。</p> <p><a href="http://www.linuxaid.com.cn/articles/4/5/458698163/2002080105.jpg" target="_blank"><img alt="将一个被聚合的对象类型映射到使用聚合的对象的数据库表中" src="http://www.linuxaid.com.cn/articles/4/5/458698163/2002080105.jpg" width="450" border="0" /></a> </p> <p><b>图2.将一个被聚合的对象类型映射到使用聚合的对象的数据库表中。</b></p> <p>我们使用前缀来区分同类的属性。这有点类似于C++中的命名空间的概念，例如Customer.DeliveryAddress.Street。</p> <p><b>结论：</b></p> <ol> <li><b>性能：</b>在性能方面，该方案是最佳的，因为只需要访问一张表就能够获取一个带聚合的对象，并读入所有聚合对象。另一方面，由于聚合对象的字段的增多，一次读取将会增大数据库读入的页面数，导致IO带宽的浪费。 <li><b>可维护性和灵活性：</b>如果聚合的对象类型被多个对象所引用，那么将会降低可维护性，因为每一次对聚合对象类型的修改都会导致对所有引用聚合对象的修改。 <li><b>数据库的一致性：</b>删除使用聚合的对象时，聚合对象将会自动删除。不需要任何其它的程序或数据库触发器来控制。 <li><b>特殊查询：</b>类似查询数据库中所有的AddressType对象之类的查询，都会变得很难处理。 </li></ol> <p><b>实现：</b></p> <ol> <li><b>命名规则：</b>需要为聚合的对象的属性考虑前缀或其它的命名规则。在上面的例子中，我们使用属性名称的缩写形式作为前缀。 <li><b>物理数据库的页面大小：</b>将聚合对象和使用聚合对象放在同一张表中，在一定程度上弥补了由于对象的部分属性存储在另一个数据库页面上的性能损失。这种情况下，读入的是两个页面，而不是一个。 </li></ol> <p><b>变化：</b></p> <p>我们已经讨论了使用聚合对象类型和聚合对象类型之间最简单的1:1关系。Foreign Key Association模式描述了两种对象类型间1:n的关系，Overflow Table则展示了在1:n的关系下避免采用外键的技巧。</p> <p><b>相关模式：</b></p> <p>Foreign Key Aggregation模式是Single Table Aggregation模式的备选方案。参考Representing Collections in a Relational Database [Bro+96]。当应用到普通的关系型数据库访问层的时候，还可以对比Denormalization [Kel+97]。</p> <p><b>参考：</b></p> <p>《Mainstream Objects》（Ed Yourdon [You+95] ）在第21章完整的讨论了在建模时期何时使用聚合和关联、以及如何使用它们的问题。</p> <h4>模式：Foreign Key Aggregation</h4> <p><b>摘要：</b></p> <p>该模式显示了如何使用外键将聚合映射到一个关系数据模型中。</p> <p><b>环境：</b></p> <p>重新考虑Single Table Aggregation模式中的例子（见图1）。假设你需要把AddressType视为单个的对象，并希望获得比Single Table Aggregation更好的可维护性。</p> <p><b>问题：</b></p> <p>如何将聚合映射到关系表？</p> <p><b>约束：</b></p> <p>见Single Table Aggregation模式。</p> <p><b>解决方案</b></p> <p>为聚合类型使用单独的表。在表中插入Synthetic Object Identity，并使用使用聚合对象表中的对象标记做一个外键，连接到聚合对象。</p> <p><b>结构：</b></p> <p><a href="http://www.linuxaid.com.cn/articles/4/5/458698163/2002080106.jpg" target="_blank"><img alt="" src="http://www.linuxaid.com.cn/articles/4/5/458698163/2002080106.jpg" width="450" border="0" /></a> </p> <p>AggregatingObject（使用聚合对象）映射到一张表，而AggregatedObject（聚合对象）映射到另一张表。聚合对象的表中包含了Synthetic Object Identity。SyntheticOID字段被使用聚合表中的AggregatedObjectsOID外键字段引用。</p> <p><b>解决方案示例</b></p> <p>我们使用Single Table Aggregation模式中的例子。Customer表中包含了两个外键引用，指向AddressType表。AddressType表包含了Synthetic Object Identity字段，用于连接两张表。</p> <p><a href="http://www.linuxaid.com.cn/articles/4/5/458698163/2002080107.jpg" target="_blank"><img alt="" src="http://www.linuxaid.com.cn/articles/4/5/458698163/2002080107.jpg" width="450" border="0" /></a> </p> <p>现在从数据库中获取一个客户对象需要三次访问数据库（一次是对Customer表，另两次是对AddressType：InvoiceAddress和DeliveryAddress），这要比Single Table Aggregation中多两次。</p> <p>如果AddressType表中增加指回Customer表中Synthetic Object Identity字段的回指字段的话，你就可以用连接来减少访问次数。这样做的代价是在取客户属性时同时取回两个地址。</p> <p><b>结论：</b></p> <ol> <li><b>性能：</b>Foreign Key Aggregation需要连接操作或至少两次的数据库访问，而Single Table Aggregation只需要一次的数据库操作。如果访问聚合对象的概率较小，这个性能还是可以接受的，如果聚合对象总是需要和使用聚合对象一同返回，那么就需要慎重的思考性能问题了。 <li><b>可维护性：</b>将对象做分解，例如单独存放AddressType，这将使得维护更加容易，并提高映射的灵活性。 <li><b>数据库的一致性：</b>聚合对象不会随着使用聚合对象的删除而自动删除。为做到这一点，需要编程或使用数据库触发器来实现。 <li><b>特殊查询：</b>将聚合对象放到单独的表中可以简化对这些对象的查询。 </li></ol> <p><b>实现</b></p> <ol> <li>可以考虑使用领域逻辑中的关键字来代替Synthetic Object Identitie。使用领域关键字的缺点是它们无法指回owner对象。 <li>考虑在聚合对象中插入一个指回使用聚合对象的连接。在地址这个例子中，就是在地址表中插入一个字段，来表明AddressType对象的所有者。这个所有者可能是一个员工、一个客户，或是其它需要使用AddressType的对象。双向的关联在查询、一致性检查和其它方面提供了方便。为了得到聚合对象的所有者，你不需要再次搜索使用聚合对象的表。另一方面，返回连接的开销比较昂贵。 </li></ol> <p><b>相关模式</b></p> <p>其备选方案为Single Table Aggregation。Foreign Key Association模式的工作方式和该模式非常接近。见Representing Collections in a Relational Database [Bro+96]。</p></span></blockquote><img src ="http://www.blogjava.net/sgsoft/aggbug/232.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/sgsoft/" target="_blank">海天一鸥</a> 2005-01-12 19:53 <a href="http://www.blogjava.net/sgsoft/articles/232.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>分页 &amp; QueryKey &amp; 预取 </title><link>http://www.blogjava.net/sgsoft/articles/231.html</link><dc:creator>海天一鸥</dc:creator><author>海天一鸥</author><pubDate>Wed, 12 Jan 2005 11:43:00 GMT</pubDate><guid>http://www.blogjava.net/sgsoft/articles/231.html</guid><wfw:comment>http://www.blogjava.net/sgsoft/comments/231.html</wfw:comment><comments>http://www.blogjava.net/sgsoft/articles/231.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/sgsoft/comments/commentRss/231.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/sgsoft/services/trackbacks/231.html</trackback:ping><description><![CDATA[<span class="postbody"><font size="2">分页 &amp; QueryKey &amp; 预取 <br /><br />数据库分页查询一般分为两步， <br />(1)根据查询条件，count 记录总数 <br />(2)根据当前页的数据范围(起始位置offset, 每页数据个数span)，从符合查询条件的记录集 取出对应范围的数据。 <br /><br />一、根据范围取数据的方法 <br />如果单纯用JDBC从ResultSet中取出一个指定范围（offset, span）的数据，可以采用这样的方法。 <br /></font></span> <table cellspacing="1" cellpadding="3" width="90%" align="center" border="0"> <tbody> <tr> <td><span class="genmed"><b><font size="2">java代码:&nbsp;</font></b></span></td></tr> <tr> <td class="code"> <div style="FONT-FAMILY: 'Courier New', Courier, monospace"><br /><br /><font size="2">ps = con.<span style="COLOR: #000000">prepareStatement</span><span style="COLOR: #000000">(</span>sql, <span style="COLOR: #aaaadd">ResultSet</span>.<span style="COLOR: #000000">TYPE_SCROLL_INSENSITIVE</span>, <span style="COLOR: #aaaadd">ResultSet</span>.<span style="COLOR: #000000">CONCUR_READ_ONLY</span><span style="COLOR: #000000">)</span>； <br />ps.<span style="COLOR: #000000">setMaxRows</span><span style="COLOR: #000000">(</span>offset + span<span style="COLOR: #000000">)</span>； <br />rs = ps.<span style="COLOR: #000000">executeQuery</span><span style="COLOR: #000000">(</span><span style="COLOR: #000000">)</span>; <br />rs.<span style="COLOR: #000000">absolute</span><span style="COLOR: #000000">(</span>offset<span style="COLOR: #000000">)</span>; <br /><span style="FONT-WEIGHT: bold; COLOR: #990066">while</span><span style="COLOR: #000000">(</span>rs.<span style="COLOR: #000000">next</span><span style="COLOR: #000000">(</span><span style="COLOR: #000000">)</span><span style="COLOR: #000000">)</span>... <br /></font></div><br /></td></tr></tbody></table><span class="postbody"><br /><br /><font size="2">数据量大的时候，页数很多，offset很大，这种方法不太适合。这时候，需要使用各数据库的native SQL特性。 <br />我们来看Hibernate dialect package的类，支持了各种数据库的getLimitString方法。这里举Mysql和Oracle的例子。假设查询语句为 <br /></font></span> <table cellspacing="1" cellpadding="3" width="90%" align="center" border="0"> <tbody> <tr> <td><span class="genmed"><b><font size="2">java代码:&nbsp;</font></b></span></td></tr> <tr> <td class="code"> <div style="FONT-FAMILY: 'Courier New', Courier, monospace"><br /><br /><font size="2">Select * from message where forum_id = ? and created_time &gt; ? order by created_time desc <br /></font></div><br /></td></tr></tbody></table><span class="postbody"><br /><br /><font size="2">Mysql 的limit SQL为 <br /></font></span> <table cellspacing="1" cellpadding="3" width="90%" align="center" border="0"> <tbody> <tr> <td><span class="genmed"><b><font size="2">java代码:&nbsp;</font></b></span></td></tr> <tr> <td class="code"> <div style="FONT-FAMILY: 'Courier New', Courier, monospace"><br /><br /><font size="2">Select * from message where forum_id = ? and created_time &gt; ? order by created_time desc <br />limit ?, ? <br /></font></div><br /></td></tr></tbody></table><span class="postbody"><br /><font size="2">后面的两个limit ?, ? 分别为 offset, span。 <br /><br />Oracle的limit SQL为 <br /></font></span> <table cellspacing="1" cellpadding="3" width="90%" align="center" border="0"> <tbody> <tr> <td><span class="genmed"><b><font size="2">java代码:&nbsp;</font></b></span></td></tr> <tr> <td class="code"> <div style="FONT-FAMILY: 'Courier New', Courier, monospace"><br /><br /><font size="2">select * from <span style="COLOR: #000000">(</span> select row_.*, rownum rownum_ from <span style="COLOR: #000000">(</span> <br />Select * from message where forum_id = ? and created_time &gt; ? order by created_time desc <br /><span style="COLOR: #000000">)</span> row_ where rownum &lt;= ?<span style="COLOR: #000000">)</span> where rownum_ &gt; ? <br /></font></div><br /></td></tr></tbody></table><span class="postbody"><br /><font size="2">后面的两个limit ?, ? 分别为 offset + span, offset。 <br /><br />二、缓存 &amp; QueryKey <br />count语句可以根据查询语句自动生成，比如 <br /></font></span> <table cellspacing="1" cellpadding="3" width="90%" align="center" border="0"> <tbody> <tr> <td><span class="genmed"><b><font size="2">java代码:&nbsp;</font></b></span></td></tr> <tr> <td class="code"> <div style="FONT-FAMILY: 'Courier New', Courier, monospace"><br /><br /><font size="2">Select count<span style="COLOR: #000000">(</span>*<span style="COLOR: #000000">)</span> from <span style="COLOR: #000000">(</span> <br />Select * from message where forum_id = ? and created_time &gt; ? order by created_time desc <br /><span style="COLOR: #000000">)</span> <br /></font></div><br /></td></tr></tbody></table><span class="postbody"><br /><br /><font size="2">这样的自动count语句有些浪费，用了子查询不说，还保留了没有必要的order by。最好还是另外提供一个count语句。 <br /></font></span> <table cellspacing="1" cellpadding="3" width="90%" align="center" border="0"> <tbody> <tr> <td><span class="genmed"><b><font size="2">java代码:&nbsp;</font></b></span></td></tr> <tr> <td class="code"> <div style="FONT-FAMILY: 'Courier New', Courier, monospace"><br /><br /><font size="2">Select count<span style="COLOR: #000000">(</span>*<span style="COLOR: #000000">)</span> from message where forum_id = ? and created_time &gt; ? <br /></font></div><br /></td></tr></tbody></table><span class="postbody"><br /><br /><font size="2">在多页翻动的情况下，这个count语句要被反复执行。为了提高效率，我把这个count结果保存在全局缓存中，不仅本Session用户可以重复使用，其他用户在根据同样条件翻找message的时候，也可以重复使用这个结果。 <br /><br />我在持久层中使用通用的QueryKey做为缓存键值。 <br />QueryKey分成三个部分，SQL, Parameters, Range。比如： <br /></font></span> <table cellspacing="1" cellpadding="3" width="90%" align="center" border="0"> <tbody> <tr> <td><span class="genmed"><b><font size="2">java代码:&nbsp;</font></b></span></td></tr> <tr> <td class="code"> <div style="FONT-FAMILY: 'Courier New', Courier, monospace"><br /><br /><font size="2">Query <span style="COLOR: #aaaadd">Key</span>: <br />SQL : Select count<span style="COLOR: #000000">(</span>*<span style="COLOR: #000000">)</span> from message where forum_id = ? and created_time &gt; ? <br />Parameters : <span style="COLOR: #000000">[</span>buaawhl, time long value<span style="COLOR: #000000">]</span> <br />Range: <span style="COLOR: #000000">(</span><span style="COLOR: #000000">0</span>, <span style="COLOR: #000000">1</span><span style="COLOR: #000000">)</span> <br /></font></div><br /></td></tr></tbody></table><span class="postbody"><br /><br /><font size="2">这个QueryKey的效率很关键。主要是hashCode和equals两个方法的效率。 <br />我们知道，当key放在Map等Hash数据结构中，首先hashCode，然后用equals比较hashCode后面的一串key。 <br />举个例子。Key1和key2 的hashCode一样，都和key3的hashCode不一样。 <br /></font></span> <table cellspacing="1" cellpadding="3" width="90%" align="center" border="0"> <tbody> <tr> <td><span class="genmed"><b><font size="2">java代码:&nbsp;</font></b></span></td></tr> <tr> <td class="code"> <div style="FONT-FAMILY: 'Courier New', Courier, monospace"><br /><br /><font size="2">… <br /><span style="COLOR: #000000">[</span> <span style="COLOR: #000000">101</span> <span style="COLOR: #000000">]</span> -&gt; key1 -&gt; key2 <br />… <br /><span style="COLOR: #000000">[</span> <span style="COLOR: #000000">666</span> <span style="COLOR: #000000">]</span> -&gt; key3 <br />… <br /></font></div><br /></td></tr></tbody></table><span class="postbody"><br /><br /><font size="2">可以看到，hashCode，equals，这两个方法都是每次查找缓存都要调用的方法。尤其是equals方法更是重中之重，很可能需要被调用多次。 <br />hashCode的优化实现相对来说比较简单，只要根据QueryKey中各部分的不同，尽量实现hashCode取值的扩散化，降低hashCode的重复率就可以了。 <br />关键是equals的实现方案。这里有个原则，越小的结构越先比较，可以提高比较速度。 <br />QueryKey中的parameters和range比较好办。每次equals比较的时候，先比较range，如果不相等，返回false; 如果相等，再比较Parameters，如果有一个parameter value不相等，返回false。这样，我们可以用很短的时间开销 过滤掉一大批不相等的QueryKey。 <br />但是parameters和range都相等的时候，我们还是无可避免的要比较SQL。String的equals方法如下： <br /></font></span> <table cellspacing="1" cellpadding="3" width="90%" align="center" border="0"> <tbody> <tr> <td><span class="genmed"><b><font size="2">java代码:&nbsp;</font></b></span></td></tr> <tr> <td class="code"> <div style="FONT-FAMILY: 'Courier New', Courier, monospace"><br /><font size="2"><span style="COLOR: #6666ff">// from jdk src </span><br /><span style="COLOR: #6666ff">//这个方法没有比较hashCode，直接比较长度和字符</span> <br />&nbsp; &nbsp; <span style="FONT-WEIGHT: bold; COLOR: #990066">public</span> <span style="FONT-WEIGHT: bold; COLOR: #990066">boolean</span> equals<span style="COLOR: #000000">(</span><span style="COLOR: #aaaadd">Object</span> anObject<span style="COLOR: #000000">)</span> <span style="COLOR: #000000">{</span> <br />&nbsp; &nbsp; &nbsp; &nbsp; <span style="FONT-WEIGHT: bold; COLOR: #990066">if</span> <span style="COLOR: #000000">(</span>this == anObject<span style="COLOR: #000000">)</span> <span style="COLOR: #000000">{</span> <br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="FONT-WEIGHT: bold; COLOR: #990066">return</span> <span style="FONT-WEIGHT: bold; COLOR: #990066">true</span>; <br />&nbsp; &nbsp; &nbsp; &nbsp; <span style="COLOR: #000000">}</span> <br />&nbsp; &nbsp; &nbsp; &nbsp; <span style="FONT-WEIGHT: bold; COLOR: #990066">if</span> <span style="COLOR: #000000">(</span>anObject instanceof <span style="COLOR: #aaaadd">String</span><span style="COLOR: #000000">)</span> <span style="COLOR: #000000">{</span> <br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="COLOR: #aaaadd">String</span> anotherString = <span style="COLOR: #000000">(</span><span style="COLOR: #aaaadd">String</span><span style="COLOR: #000000">)</span>anObject; <br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="FONT-WEIGHT: bold; COLOR: #990066">int</span> n = count; <br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="FONT-WEIGHT: bold; COLOR: #990066">if</span> <span style="COLOR: #000000">(</span>n == anotherString.<span style="COLOR: #000000">count</span><span style="COLOR: #000000">)</span> <span style="COLOR: #000000">{</span> <br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; char v1<span style="COLOR: #000000">[</span><span style="COLOR: #000000">]</span> = value; <br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; char v2<span style="COLOR: #000000">[</span><span style="COLOR: #000000">]</span> = anotherString.<span style="COLOR: #000000">value</span>; <br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="FONT-WEIGHT: bold; COLOR: #990066">int</span> i = offset; <br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="FONT-WEIGHT: bold; COLOR: #990066">int</span> j = anotherString.<span style="COLOR: #000000">offset</span>; <br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="FONT-WEIGHT: bold; COLOR: #990066">while</span> <span style="COLOR: #000000">(</span>n-- != <span style="COLOR: #000000">0</span><span style="COLOR: #000000">)</span> <span style="COLOR: #000000">{</span> <br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="FONT-WEIGHT: bold; COLOR: #990066">if</span> <span style="COLOR: #000000">(</span>v1<span style="COLOR: #000000">[</span>i++<span style="COLOR: #000000">]</span> != v2<span style="COLOR: #000000">[</span>j++<span style="COLOR: #000000">]</span><span style="COLOR: #000000">)</span> <br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="FONT-WEIGHT: bold; COLOR: #990066">return</span> <span style="FONT-WEIGHT: bold; COLOR: #990066">false</span>; <br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="COLOR: #000000">}</span> <br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="FONT-WEIGHT: bold; COLOR: #990066">return</span> <span style="FONT-WEIGHT: bold; COLOR: #990066">true</span>; <br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="COLOR: #000000">}</span> <br />&nbsp; &nbsp; &nbsp; &nbsp; <span style="COLOR: #000000">}</span> <br />&nbsp; &nbsp; &nbsp; &nbsp; <span style="FONT-WEIGHT: bold; COLOR: #990066">return</span> <span style="FONT-WEIGHT: bold; COLOR: #990066">false</span>; <br />&nbsp; &nbsp; <span style="COLOR: #000000">}</span> <br /></font></div><br /></td></tr></tbody></table><span class="postbody"><br /><font size="2">我们看到，当SQL String很长的时候，长度相等，前面大部分字符相同的时候，(最极端的情况下，两个不同reference的String的字符完全相等)，这个比较是相当消耗时间的。比如， <br /></font></span> <table cellspacing="1" cellpadding="3" width="90%" align="center" border="0"> <tbody> <tr> <td><span class="genmed"><b><font size="2">java代码:&nbsp;</font></b></span></td></tr> <tr> <td class="code"> <div style="FONT-FAMILY: 'Courier New', Courier, monospace"><br /><br /><font size="2">Select * from message where forum_id = ? and created_time &gt; ? order by created_time desc <br /></font></div><br /></td></tr></tbody></table><span class="postbody"><br /><font size="2">和 <br /></font></span> <table cellspacing="1" cellpadding="3" width="90%" align="center" border="0"> <tbody> <tr> <td><span class="genmed"><b><font size="2">java代码:&nbsp;</font></b></span></td></tr> <tr> <td class="code"> <div style="FONT-FAMILY: 'Courier New', Courier, monospace"><br /><br /><font size="2">Select * from message where forum_id = ? and created_time &gt; ? order by updated_time desc <br /></font></div><br /></td></tr></tbody></table><span class="postbody"><br /><font size="2">两个String的长度相等，前面大部分也相等，只有走到cre 和 upd 的时候，才能比较出不相同。如果两个字符串内容一样，那更是要走到头，才能判断出两个字符串完全一样了。 <br /><br />我的第一个做法就是，尽量使用static final String做为QueryKey的SQL。这样两个SQL的reference如果相等，那么可以迅速判断出两个SQL相同。 <br />这个做法只能处理事先定义好的SQL语句，但实际需求中，存在很多需要动态拼接SQL的情况，不可能做到所有相同的SQL具有相同的reference。 <br />当然大部分不同的SQL都具有不同的长度，即使长度相同，前面走不了几个字符，就可以判断出不相同。所以做法一已经能够解决95%以上的SQL效率问题。 <br /><br />不过，为了解决这剩下的5%情况，我又采取了第二个做法：分而治之，把一个SQL String拆分成多个SQL常量的数组;泛化SQL的类型，SQL不限制为String类型，也可以是String[]类型。 <br />比如。 <br /></font></span> <table cellspacing="1" cellpadding="3" width="90%" align="center" border="0"> <tbody> <tr> <td><span class="genmed"><b><font size="2">java代码:&nbsp;</font></b></span></td></tr> <tr> <td class="code"> <div style="FONT-FAMILY: 'Courier New', Courier, monospace"><br /><br /><font size="2"><span style="COLOR: #aaaadd">String</span><span style="COLOR: #000000">[</span><span style="COLOR: #000000">]</span> sql1 = <span style="COLOR: #000000">{</span> <br />“Select * from message where forum_id = ?”， <br />“ and created_time &gt; ?”, <br />“ order by ”, <br />“created_time”, <br />“desc” <br /><span style="COLOR: #000000">}</span>; <br /></font></div><br /></td></tr></tbody></table><span class="postbody"><br /><font size="2">和 <br /></font></span> <table cellspacing="1" cellpadding="3" width="90%" align="center" border="0"> <tbody> <tr> <td><span class="genmed"><b><font size="2">java代码:&nbsp;</font></b></span></td></tr> <tr> <td class="code"> <div style="FONT-FAMILY: 'Courier New', Courier, monospace"><br /><br /><font size="2"><span style="COLOR: #aaaadd">String</span><span style="COLOR: #000000">[</span><span style="COLOR: #000000">]</span> sql2 = <span style="COLOR: #000000">{</span> <br />“Select * from message where forum_id = ?”， <br />“ and created_time &gt; ?”, <br />“ order by ”, <br />“created_time”, <br />“desc” <br /><span style="COLOR: #000000">}</span>; <br /></font></div><br /></td></tr></tbody></table><span class="postbody"><br /><font size="2">和 <br /></font></span> <table cellspacing="1" cellpadding="3" width="90%" align="center" border="0"> <tbody> <tr> <td><span class="genmed"><b><font size="2">java代码:&nbsp;</font></b></span></td></tr> <tr> <td class="code"> <div style="FONT-FAMILY: 'Courier New', Courier, monospace"><br /><br /><font size="2"><span style="COLOR: #aaaadd">String</span><span style="COLOR: #000000">[</span><span style="COLOR: #000000">]</span> sql3 = <span style="COLOR: #000000">{</span> <br />“Select * from message where forum_id = ?”， <br />“ and created_time &gt; ?”, <br />“ order by ”, <br />“updated_time”, <br />“desc” <br /><span style="COLOR: #000000">}</span>; <br /></font></div><br /></td></tr></tbody></table><span class="postbody"> <p><br /><br /><font size="2">这个时候，比较sql1和sql2和sql3的效率就会大大提高，虽然sql1 和 sql2两个数组的长度相等，还是要一个元素一个元素的比较，但由于里面大量用到了String常量，相同的String常量具有相同的reference，所以5步下来，就可以判断出sql1和sql2数组的元素是完全相等的；4步下来，加上第一个字符的比较，就可以判断sql1和sql3的第4个元素是不相等的。 <br /><br />我们看到，做法1和做法2，能够100%的提高SQL的比较效率，大部分情况下，也许比parameters的比较还快。 <br /><br />三、定长预取 <br />多用户访问同一页面的可能性比较大的情况下，比如，论坛的某些热门话题，很可能被多人同时翻阅。这时候，如果把根据范围取出的数据对象List也按照QueryKey存入缓存中，那么就可以大大提高响应速度，减轻数据服务器负担，当然，你的Web Server的内存负担也大大增加了。<img alt="Smile" src="http://forum.javaeye.com/images/smiles/icon_smile.gif" border="0" /> <br />我们进一步考虑下面两种情况： <br />1. 用户自定义页面记录数 <br />一般来说，用户可以自定义自己的每页显示记录个数，比如，有些用户喜欢每页20条，有的喜欢每页10条。 <br />假设用户A翻到一个论坛的第一页，显示1 – 20条信息；用户B翻到同一个论坛的第一页，显示1 – 10条信息。这个时候，缓存的命中率是很低的。用户A和用户B无法共享缓存信息。因为他们的range(的span)总是不同，QueryKey永远不可能相同。 <br /><br />2. 记录很多、每页记录数过少 <br />假设一个论坛里面有1000条信息，每页显示10条，那么共有100页。如果用户一页一页的翻动，每次程序发出一个span大小为10的Query请求，取出10条记录，根据QueryKey缓存起来。由于页面记录数过少，每次数据库查询的效率很低，缓存命中率也很低。 <br /><br />为了提高缓存命中率，并且顺便实现数据预取功能，我们可以采取 同一定长Span的方案。比如，还是上面的例子，我们在程序中设定统一Span大小为100。 <br />当用户A请求1 – 10的记录的时候，程序判断这个落在 1 – 100的范围内，那么用range (1, 100)获取100条记录，把前面的10条返回给用户。当用户A翻了一页，请求11 – 20的记录的时候，程序判断还是落在 1 – 100的范围内，而且已经存在于缓存中，那么直接把对应的11 – 20条返回给用户A就可以。 <br />当用户B 请求1 – 20的记录的时候，程序判断这个落在 1 – 100的范围内，而且已经存在于缓存中，那么直接把对应的1 – 20条返回给用户B就可以。 <br /><br />可以看到，这种定长预取方案能够大大提高数据库查询的效率和缓存的命中率。</font></p> <p><font size="2"></font>&nbsp;</p> <p><span class="postbody"><font size="2">关于Cache &amp; QueryKey 部分，偶有1个问题：你是如何做到cache的自动清理和聪明地清理？ <br /><br />举个例子 <br />假设有这样的查询语句：select * from message where message_to = ? <br />执行了2次值不同的操作：'buaawhl' 和 'Readonly' <br />那么就有2个不同QueryKey对应到Cache里的对象。 <br /><br />这个时候再执行一个write的操作：往message表里面插入了一条message_to等于‘buaawhl’的记录，那么之前在Cache里QueryKey为'buaawhl'的对象会不会自动失效？而QueryKey为'Readonly'的对象是否还能保持有效呢？</font></span></p> <p><span class="postbody"><font size="2"></font></span>&nbsp;</p> <p><span class="postbody"><font size="2">没有这么智能。我现在无法做到 cache的自动清理和聪明地清理。 <br />我现在做的持久层本身是不做cache的清理管理工作，这个工作交给 调用程序自己去做。cache也需要用户自己实现，并且明确提供。 <br />查询的时候，需要指定cache <br /></font></p> <p> <table cellspacing="1" cellpadding="3" width="90%" align="center" border="0"> <tbody> <tr> <td><span class="genmed"><b><font size="2">java代码:&nbsp;</font></b></span></td></tr> <tr> <td class="code"> <div style="FONT-FAMILY: 'Courier New', Courier, monospace"><br /><br /><font size="2">finder.<span style="COLOR: #000000">setRowClass</span><span style="COLOR: #000000">(</span>Message.<span style="COLOR: #000000">class</span><span style="COLOR: #000000">)</span>; <br />finder.<span style="COLOR: #000000">setCache</span><span style="COLOR: #000000">(</span>cache<span style="COLOR: #000000">)</span>; <br />finder.<span style="COLOR: #000000">queryRowsRange</span><span style="COLOR: #000000">(</span>conP, sql, params, offset, span<span style="COLOR: #000000">)</span>; <br /></font></div><br /></td></tr></tbody></table></p><span class="postbody"> <p><br /><br /><font size="2">这个时候，finder会用 QueryKey(sql, params, offset, span) 作为Key, <br />从指定的cache里面，查找对应的记录集合。 <br />如果查不到，从conP真正获取一个connection，连接并查找数据库，把结果放到cache里面。 <br />这里有个优化，如果指定了缓存，而且只有当缓存中不存在目标数据的时候，才真正地从连接池中获取connection。主要是考虑到连接池的大小总是有限的，如果并发用户多的话，这样就可以节省连接池里的connection的分配。 <br /><br />--- <br />update, delete, update的时候，用户需要手动自己清理cache的内容。 <br /></font></p></span> <p> <table cellspacing="1" cellpadding="3" width="90%" align="center" border="0"> <tbody> <tr> <td><span class="genmed"><b><font size="2">java代码:&nbsp;</font></b></span></td></tr> <tr> <td class="code"> <div style="FONT-FAMILY: 'Courier New', Courier, monospace"><br /><br /><font size="2">persister.<span style="COLOR: #000000">insertRow</span><span style="COLOR: #000000">(</span>con, message<span style="COLOR: #000000">)</span>; <br />cache.<span style="COLOR: #000000">clear</span><span style="COLOR: #000000">(</span><span style="COLOR: #000000">)</span>; <br /><span style="COLOR: #6666ff">// 或者更智能的操作, 比如, 根据message的message_to value,</span> <br /><span style="COLOR: #6666ff">// 清理Cache里面的符合下列条件的QueryKey</span> <br /><span style="COLOR: #6666ff">// (sql 包含 message_to = ?,&nbsp; 并且 params[0] = buaawhl)</span> <br /></font></div><br /></td></tr></tbody></table></p><span class="postbody"> <p><br /><br /><font size="2">我做的持久层，由于直接使用SQL，在 自动智能过滤缓存数据 方面，具有先天的缺陷。 <br />因为SQL可以写的很复杂，比HQL复杂很多。而且还有各种 Native SQL特性，没有一个统一的中间语言（比如HQL）， 解析起来也相当复杂。 <br />即使有这么一个中间语言，解析处理的代价和难度也相当大。相当于实现了一个小型的HSQL级别的内存数据库。 <br /><br />而对于用户来说，定义DAO方法的时候，很清楚自己SQL的语义，实现智能缓存处理更容易一些，所以干脆把cache的管理交给用户自己。 <br />这样做的另一个目的是，我想把 对应的页面缓存也放到同一个cache中去。 <br />还有一种情况，比如，user, group, group user, 三个不同的Data Object类，由于相互关联，那么用户可以指定这三个类使用同一个cache，简化管理。 <br />还有一种情况，比如，我想用message类，同时对应 站内短信，和论坛帖子两个对象，我也可以为这两种不同的情况，指定不同cache。 <br /><br />--- <br />Hibernate如果想实现 自动智能过滤缓存数据 方面，那么具有天生的优势。 <br />因为本来Hibernate就是要 解析HQL的，而且Hibernate管理数据类本身及其之间的关联。什么信息都有了，做起来也相对容易很多。 <br />当然Hibernate并没有做这个工作。Hibernate只提供了一个evictQueries()方法，不分类型地清理所有的cached query. <br />Hibernate的QueryKey也是直接使用结果SQL，而不是HQL。 <br /><br /></font></p></span> <p> <table cellspacing="1" cellpadding="3" width="90%" align="center" border="0"> <tbody> <tr> <td><span class="genmed"><b><font size="2">java代码:&nbsp;</font></b></span></td></tr> <tr> <td class="code"> <div style="FONT-FAMILY: 'Courier New', Courier, monospace"><br /><font size="2"><span style="COLOR: #6666ff">// from hibernate 2.7</span> <br /><span style="FONT-WEIGHT: bold; COLOR: #990066">public</span> <span style="FONT-WEIGHT: bold; COLOR: #990066">class</span> QueryKey <span style="FONT-WEIGHT: bold; COLOR: #990066">implements</span> <span style="COLOR: #aaaadd">Serializable</span> <span style="COLOR: #000000">{</span> <br /><span style="FONT-WEIGHT: bold; COLOR: #990066">private</span> <span style="FONT-WEIGHT: bold; COLOR: #990066">final</span> <span style="COLOR: #aaaadd">String</span> sqlQueryString; <br /><span style="FONT-WEIGHT: bold; COLOR: #990066">private</span> <span style="FONT-WEIGHT: bold; COLOR: #990066">final</span> Type<span style="COLOR: #000000">[</span><span style="COLOR: #000000">]</span> types; <br /><span style="FONT-WEIGHT: bold; COLOR: #990066">private</span> <span style="FONT-WEIGHT: bold; COLOR: #990066">final</span> <span style="COLOR: #aaaadd">Object</span><span style="COLOR: #000000">[</span><span style="COLOR: #000000">]</span> values; <br /><span style="FONT-WEIGHT: bold; COLOR: #990066">private</span> <span style="FONT-WEIGHT: bold; COLOR: #990066">final</span> <span style="COLOR: #aaaadd">Integer</span> firstRow; <br /><span style="FONT-WEIGHT: bold; COLOR: #990066">private</span> <span style="FONT-WEIGHT: bold; COLOR: #990066">final</span> <span style="COLOR: #aaaadd">Integer</span> maxRows; <br /><span style="FONT-WEIGHT: bold; COLOR: #990066">private</span> <span style="FONT-WEIGHT: bold; COLOR: #990066">final</span> <span style="COLOR: #aaaadd">Map</span> namedParameters; <br />... <br /><span style="FONT-WEIGHT: bold; COLOR: #990066">public</span> <span style="FONT-WEIGHT: bold; COLOR: #990066">boolean</span> equals<span style="COLOR: #000000">(</span><span style="COLOR: #aaaadd">Object</span> other<span style="COLOR: #000000">)</span> <span style="COLOR: #000000">{</span> <br />&nbsp; &nbsp; &nbsp; &nbsp; QueryKey that = <span style="COLOR: #000000">(</span>QueryKey<span style="COLOR: #000000">)</span> other; <br />&nbsp; &nbsp; &nbsp; &nbsp; <span style="FONT-WEIGHT: bold; COLOR: #990066">if</span> <span style="COLOR: #000000">(</span> !sqlQueryString.<span style="COLOR: #000000">equals</span><span style="COLOR: #000000">(</span>that.<span style="COLOR: #000000">sqlQueryString</span><span style="COLOR: #000000">)</span> <span style="COLOR: #000000">)</span> <span style="FONT-WEIGHT: bold; COLOR: #990066">return</span> <span style="FONT-WEIGHT: bold; COLOR: #990066">false</span>; <br />&nbsp; &nbsp; &nbsp; &nbsp; <span style="FONT-WEIGHT: bold; COLOR: #990066">if</span> <span style="COLOR: #000000">(</span> !EqualsHelper.<span style="COLOR: #000000">equals</span><span style="COLOR: #000000">(</span>firstRow, that.<span style="COLOR: #000000">firstRow</span><span style="COLOR: #000000">)</span> || !EqualsHelper.<span style="COLOR: #000000">equals</span><span style="COLOR: #000000">(</span>maxRows, that.<span style="COLOR: #000000">maxRows</span><span style="COLOR: #000000">)</span> <span style="COLOR: #000000">)</span> <span style="FONT-WEIGHT: bold; COLOR: #990066">return</span> <span style="FONT-WEIGHT: bold; COLOR: #990066">false</span>; <br />... <br />&nbsp; &nbsp; &nbsp; &nbsp; <span style="FONT-WEIGHT: bold; COLOR: #990066">return</span> <span style="FONT-WEIGHT: bold; COLOR: #990066">true</span>; <br /><span style="COLOR: #000000">}</span> <br /></font></div><br /></td></tr></tbody></table></p><span class="postbody"> <p><br /><br /><font size="2">可以看到，hibernate query key 直接比较结果SQL。而且我们知道，这个SQL是HQL转换过来的结果，reference一定不会相等。 <br />假设这个SQL很长的时候，而两个SQL又相同，这个比较就会比较消耗时间。而且，这个QueryKey占的空间也比较大。 <br />（在我的持久层里面，SQL尽量采用常量字符串、或常量字符串数组，在一定程度上解决这个问题。当然，这需要用户在使用的时候，有意识的支持） <br /><br />我想了一下，为什么hibernate QueryKey直接采用结果SQL，而不用HQL。 <br />这个SQL是HQL的解析结果，直接使用结果，节省了解析时间。比如，From A where id = 1 和 from A WHERE ID = '1'，两个HQL字符串不相同，但语义相同，转换出来的SQL一定相同。 <br /><br />如果Hibernate要加入智能管理QueryCache的功能，需要在QueryKey里面加入更多的信息（比如，HQL的解析结果的条件过滤部分），这样QueryKey的占用空间就会进一步加大。</font></p> <p><span class="postbody"><font size="2">有一个hibernate cache讨论。 <br /></font><a href="http://forum.javaeye.com/viewtopic.php?t=6593" target="_blank"><font size="2">http://forum.javaeye.com/viewtopic.php?t=6593</font></a><font size="2"> <br /><br />我的另一个帖子里面也有介绍。 <br /></font><a href="http://forum.javaeye.com/viewtopic.php?t=9706" target="_blank"><font size="2">http://forum.javaeye.com/viewtopic.php?t=9706</font></a><font size="2"> <br /><br />Hibernate主要的缓存是ID缓存（二级缓存），而QueryCache缓存的支持非常初级。 <br />ID缓存的Key非常简单，就是persistent class + entity identifier。 <br />具体来说，每个不同的persistent class有独立的ID缓存，该独立ID缓存的key就是 entity identifier。 <br /><br />ID缓存的管理是不是在 Method Interceptor里面管理的。我从hibernate代码中看不出这一点。也许在Hibernate自定义的Event中处理。 <br />我大致猜测一下，Hibernate把updated, inserted, deleted Entities(对于用户来说，就是PO；对于Hibernate来说，就是Entity, Proxy)都保存在内部的一个Collection结构里面。在最后Session.flush()的时候，统一处理，同步更新数据库状态，和ID缓存状态。</font></span></span></span></p> <p><font size="2"></font>&nbsp;</p> <p><font size="2"></font></span>&nbsp;</p><img src ="http://www.blogjava.net/sgsoft/aggbug/231.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/sgsoft/" target="_blank">海天一鸥</a> 2005-01-12 19:43 <a href="http://www.blogjava.net/sgsoft/articles/231.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title> JDBC中文处理－－解答集合</title><link>http://www.blogjava.net/sgsoft/articles/230.html</link><dc:creator>海天一鸥</dc:creator><author>海天一鸥</author><pubDate>Wed, 12 Jan 2005 11:20:00 GMT</pubDate><guid>http://www.blogjava.net/sgsoft/articles/230.html</guid><wfw:comment>http://www.blogjava.net/sgsoft/comments/230.html</wfw:comment><comments>http://www.blogjava.net/sgsoft/articles/230.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/sgsoft/comments/commentRss/230.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/sgsoft/services/trackbacks/230.html</trackback:ping><description><![CDATA[<p><small>发信人: sailorc (ZZ), 信区: Java<br />标 题: ● ● JDBC中文处理：方法与问题<br />发信站: BBS 水木清华站 (Sun May 17 21:34:19 1998)<br /><br />● ● JDBC中文处理：方法与问题<br /><br />我们在做一个JAVA的应用，不可避免地要处理中文。经过<br />艰苦的探索，目前有一些进展，找到了一些解决方法，但仍然<br />面临着无法解决的问题。在此作一整理，希望对大家有所帮助，<br />同时请各位高手帮忙考虑我们的问题。<br />Email: sailor@mailserv.stu.edu.cn<br /><br />背景：<br />JDK 1.15<br />VCafe 2.0<br />JPadPro<br />SERVER：<br />NT IIS<br />Sybase System 10 <br />JDBC: Jconnect<br />CLIENT：<br />Browser: Netscape 4.04 + Patch<br />PWin95 &amp; Pwin98 Beta3<br /><br />CLASS文件存放在 SERVER，由BROWSER 运行APPLET，APPLET只<br />起调入FRAME类主程序的作用。界面包括Text field, Text Area,<br />List, Choice 等。<br /><br />一，取中文<br />用JDBC执行SELECT语句从SERVER取数据（中文）后，将数据<br />用APPEND方法加到TEXT AREA（TA），不能正确显示。但加到<br />LIST中时，则大部分汉字可正确显示。<br /><br />处理：将数据按“ISO-8859-1”格式转为字节数组，再按系统<br />缺省编码格式（default character encoding）转为STRING，即可在TA和LIST中正确显示。<br />程序段如下：<br /><br />dbstr2 = results.getString(1);<br />//*********************************************************************<br />// After read result from Database server, Convert the result string.<br /><br />dbbyte1 = dbstr2.getBytes("iso-8859-1");<br />dbstr1 = new String(dbbyte1);<br />//*********************************************************************<br /><br />二，写中文到DB<br />处理方式与以上相逆，先将SQL语句按DEFAULT CHARACTER ENCODING<br />转为字节数组，再按ISO-8859-1转为STRING，然后送执行，<br />则中文信息可正确写入DB。<br /><br />sqlstmt = tf_input.getText();<br /><br />//***************************************************************************** <br />// Before send statement to Database server, Convert sql statement.<br /><br />dbbyte1 = sqlstmt.getBytes();<br />sqlstmt = new String(dbbyte1,"iso-8859-1");<br />//***************************************************************************** <br /><br />_stmt = _con.createStatement();<br />_stmt.executeUpdate(sqlstmt);<br />。。。。。。<br /><br />问题：<br />以上方法当本地客户机上存在CLASSPATH指向JDK的CLASSES。ZIP<br />时（称为A情况），可正确运行。<br />但如果客户机只有Browser，没有JDK和CLASSPATH时<br />（称为B情况），则汉字无法正确转换。<br /><br />我们的分析：<br />1,<br />经过测试，在A情况下，程序运行时系统的default character<br />encoding = "GBK" or "GB2312".<br />在B情况下，程序启动时，Browser 的JAVA CONSOLE中出现<br />如下信息：<br />can't find resource for <br />sun.awt.windows.awtLocalization_zh_CN<br />然后系统的<br />default characterencoding = "8859-1".<br /><br />2,<br />如果在转换字符串时不采用default character encoding，<br />而是直接采用“GBK”或“GB2312”，则在A情况下仍然可正常，<br />在B情况下，系统出现错误：UnsupportedEncodingException。<br /><br />3，<br />在本地客户机上，我把JDK的CLASSES。ZIP解压后，放在另一个<br />目录中，CLASSPATH只包含该目录。然后逐步删除目录中的CLASS<br />文件，一边运行测试程序，最后发现在一千多个CLASS文件中，<br />只有一个是不可缺少的，该文件是：<br />sun.io.CharToByteDoubleByte.class<br />我将该文件拷到SERVER端和其它的类放在一起，并在程序的开头<br />IMPORT它，仍然在B情况下无法正常。<br /><br />4，<br />在A情况下，如果在CLASSPTH中去掉<br />sun.io.CharToByteDoubleByte.class，则程序运行时，<br />测得default character encoding为“8859-1”，否则为<br />GBK 或GB2312。<br /><br />5，<br />分析BROWSER程序NETSCAPE目录下的文件<br />/program/java/classes/java40.jar, 发现其中没有包括<br />sun.io.CharToByteDoubleByte.class,<br />不知这是需要升级，还是有其它方法可以解决？<br /><br />盼望各位高手指导！Email: sailor@mailserv.stu.edu.cn<br /><br />--<br />※ 来源:·BBS 水木清华站 bbs.net.tsinghua.edu.cn·[FROM: DHCP159_158.STU]</small></p> <p><small>发信人: barebell (小心), 信区: Java<br />标 题: Re: ● ● JDBC中文处理：方法与问题<br />发信站: BBS 水木清华站 (Tue May 19 22:38:19 1998) WWW-POST<br /><br />现在我们取得的一点小小进展，在转换字符串时不采用default character <br />encoding，而是直接采用“GBK”或“GB2312”，在情况A和B底下，从DB取数据<br />都没有问题，但是写中文到DB也采用“GBK”或“GB2312”时，情况B仍是出错的。<br /><br />发信人: mah (chip), 信区: Java<br />标 题: 通过jdbc driver获取数据库中文信息揭密<br />发信站: BBS 水木清华站 (Tue Aug 11 20:42:16 1998) WWW-POST<br /><br />当我们使用老外公司开发的jdbc第四类driver获取数据库中文信息时，常会出现乱码现象<br />，如????D.<br />解决办法1：<br />使用interface ResultSet的方法getBytes()得到一byte[]，然后由此byte[]数组产生一<br />新的<br />String,可获得正确的汉字，但此方法有一定的局限性，在某些driver上可以实现，如<br />weblogic公司<br />开发的fastforward产品。另此种方法不规范，根据sun jdbc的标准varchar和var推荐用<br />getString()<br />方法来获取。<br />解决办法2:<br />使用interface ResultSet的方法getString(),这时我们得到的String一定是乱码，如何<br />解决，<br />String temp = result.getString (s);<br />if (temp != null) {<br />byte[] b = temp.getBytes ("8859_1");<br />temp = new String (b);<br />此时的temp一定是正确的中文，，，，，，此种方法我在sybase公司开发的jconnect4上<br />实验成功，在fastforward<br />上也成功。</small></p><img src ="http://www.blogjava.net/sgsoft/aggbug/230.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/sgsoft/" target="_blank">海天一鸥</a> 2005-01-12 19:20 <a href="http://www.blogjava.net/sgsoft/articles/230.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>基于J2EE的Blog平台 </title><link>http://www.blogjava.net/sgsoft/articles/226.html</link><dc:creator>海天一鸥</dc:creator><author>海天一鸥</author><pubDate>Wed, 12 Jan 2005 05:36:00 GMT</pubDate><guid>http://www.blogjava.net/sgsoft/articles/226.html</guid><wfw:comment>http://www.blogjava.net/sgsoft/comments/226.html</wfw:comment><comments>http://www.blogjava.net/sgsoft/articles/226.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/sgsoft/comments/commentRss/226.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/sgsoft/services/trackbacks/226.html</trackback:ping><description><![CDATA[<hr noshade="" size="1" /> <br />作者：xuefengl(dev2dev ID) <br /><strong>摘要：<br /></strong><br />　　本文讲述如何在J2EE平台上创建一个Blog系统，以.Text的功能和界面为原型，Springframework为框架，实现一个运行在WebLogic Server上的灵活的多层结构的Blog平台。 <a id="0" name="0"></a> <p><strong>目录：</strong><br /><br /><a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#1">1. 设计目标</a><br /><a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#2">2. 开发环境</a><br />2.1. 选择平台和框架<br /><a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#2_2">2.2. 配置服务器</a><br /><a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#2_3">2.3. 编写Ant脚本</a><br /><a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#3">3. 系统设计</a><br />3.1. 持久层设计<br /><a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#3_1_1">3.1.1. 设计Domain对象</a><br /><a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#3_1_2">3.1.2. 配置iBatis</a><br /><a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#3_1_3">3.1.3. 使用DAO模式</a><br /><a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#3_2">3.2. 逻辑层设计</a><br /><a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#3_3">3.3. Web层设计</a><br /><a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#3_3_1">3.3.1. 使用MVC模式</a><br /><a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#3_3_2">3.3.2. 实现Skin</a><br /><a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#4">4. 附加功能</a><br /><a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#4_1_1">4.1.1. 实现图片上传</a><br /><a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#4_1_2">4.1.2. 生成缩略图</a><br /><a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#4_1_3">4.1.3. 实现RSS</a><br /><a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#4_1_4">4.1.4. 实现全文搜索</a><br /><a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#4_1_5">4.1.5. 发送Email</a><br /><a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#5">5. 测试</a><br /><a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#6">6. 中文支持</a><br /><a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#7">7. 总结</a><br /><a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#8">8. 源代码下载</a><br /><a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#9">9. 相关资源下载</a><br /><a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#10">10. 参考</a><br /><a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#11">11. 关于作者</a><a id="1" name="1"></a></p> <p><strong id="#1">设计目标</strong> <a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#0">(目录)</a><br /><br />　　Blog（WebLog）在Internet上越来越流行。许多网友都有了自己的Blog，通过Blog展示自己，结识更过的网友。比较著名的Blog平台是基于ASP.net的开源项目.Text。但是它的逻辑全部以存储过程的形式放在数据库中。虽然存储过程能大大提高数据操作的效率，但是存储过程本身是结构化的程序，无法发挥面向对象的威力，也不便于实现代码复用。因此，我决定实现一个基于J2EE体系的多层结构的Blog平台，功能和界面和.Text非常类似，暂命名为Crystal Blog。实现的功能有：发表和编辑文章；多用户支持；全文检索；RSS支持；图片管理；SMTP邮件发送等常见功能。界面如下：</p> <p align="center"><img height="384" src="http://dev2dev.bea.com.cn/images/paihang_article/041020/pic07.jpg" width="450" /><br /></p> <p><b>选择平台和框架</b> <a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#0">(目录)</a><br />　　由于使用J2EE平台，我们准备采用WebLogic Server 8.1作为运行平台，使用WebLogic Workshop8.1这个强大的集成化IDE作为开发工具。 <br />　　数据库选择MS SQL Server 2000 SP3，建立一个名为blog的数据库存储所有的用户数据。由于我们并没有针对特定数据库编码，稍后我们会使用其他数据库测试。在系统设计之前，选择一个优秀的框架能大大提高开发效率。Spring是一个轻量级的J2EE框架。它覆盖了从后台数据库的JDBC封装到前台Web框架的几乎所有方?面。并且，Spring的各个模块耦合非常松散，我们既可以用它作为整个应用程序的框架，也可以仅仅使用它的某一个模块。此外，Spring非常强大的集成功能使我们可以轻易地集成Struts编写的Web端，或者使用Hibernate作为后端的O/R Mapping方案。<br />　　Spring的核心思想便是IoC和AOP，Spring本身是一个轻量级容器，和EJB容器不同，Spring的组件就是普通的Java Bean，这使得单元测试可以不再依赖容器，编写更加容易。Spring负责管理所有的Java Bean组件，同样支持声明式的事务管理。我们只需要编写好Java Bean组件，然后将它们“装配”起来就可以了，组件的初始化和管理均由Spring完成，只需在配置文件中声明即可。这种方式最大的优点是各组件的耦合极为松散，并且无需我们自己实现Singleton模式。 <br />　　由于后台要使用关系数据库存储数据，使用O/R Mapping必不可少。iBatis是又一个类似于Hibernate的O/R Mapping方案，特点是小巧，配置简单，查询灵活，完全符合我们的要求。<br />　　除了Spring和iBatis，用到的第三方组件还有：用于全文搜索的Lucene引擎，用于文件上传的common-file-upload 1.0，用于输出RSS的RSSLibJ1.0 RC2。<br />　　由于使用Spring这个轻量级框架，就无需EJB服务器，只需要Web服务器即可。因此，系统可以运行在WebLogic Server，Tomcat和Resin等支持Servlet和JSP的Web服务器上。<a id="2" name="2"></a></p> <p><b>系统设计</b> <a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#0">(目录)</a><br />　　很显然，多层结构的J2EE架构能保证系统的灵活性和可扩展性。我们仍然采用表示层/逻辑层/持久层三层设计。</p> <p align="center"><img height="422" src="http://dev2dev.bea.com.cn/images/image2004111676.gif" width="700" /></p> <p align="left">　　整个系统以Spring为基础，持久层采用DAO模式和iBatis O/R Mapping，封装所有数据库操作；中间层是由Spring管理的普通的JavaBean，采用Fa?ade模式；表示层使用Spring提供的MVC框架。由于Spring对其他框架的良好集成，我们采用Velocity作为View。由于Velocity不能调用Java代码，从而强制使用MVC模式而不是在View中嵌入逻辑代码。<a id="2_2" name="2_2"></a></p> <p><b>配置服务器</b> <a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#0">(目录)</a><br />　　在WebLogic中新建一个Configuration，命名为blog，添加一个数据源，命名为jdbc/blog：<br /></p> <p align="center"><img height="466" src="http://dev2dev.bea.com.cn/images/image2004111678.gif" width="644" /></p> <p align="left">　　整个应用程序的目录结构如下：</p> <p align="left"><font color="#339966">crystalblog/<br />+ doc/ （存放API文档）<br />+ report/ （存放JUnit测试结果）<br />+ src/ （存放java源程序）<br />+ web/ （web目录）<br />| + manage/ （存放blog管理页）<br />| + skin/ （存放blog界面页）<br />| + upload/ （存放用户上传的图片）<br />| + WEB-INF/<br />|&nbsp;&nbsp; + classes/ （存放编译的class文件）<br />|&nbsp;&nbsp; + lib/ （存放用到的所有jar文件）<br />|&nbsp;&nbsp; + search/ （存放Lucene的index）<br />|&nbsp;&nbsp; + c.tld （使用jstl必须的文件）<br />|&nbsp;&nbsp; + dispatcher-servlet.xml （Spring配置文件）<br />|&nbsp;&nbsp; + web.xml （标准web配置文件）<br />+ blog.war （打包的可部署应用）<br />+ build.xml （ant脚本）</font></p> <p align="left"><font color="#339900"><a id="2_3" name="2_3"></a></font></p> <p align="left"><b>编写Ant?脚本</b> <a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#0">(目录)</a><br />　　Ant是一个非常棒的执行批处理任务的工具。使用Ant能使编译、测试、打包、部署和生成文档等一系列任务全自动化，从而大大节省开发时间。<br />　　首先我们把用到的所有.jar文件放到/web/WEB-INF/lib中，然后编写compile任务，生成的class文件直接放到web/WEB-INF/classes目录下。如果编译成功，就进行单元测试，单元测试的结果以文本文件存放在report目录中。如果测试通过，下一步便是打包成blog.war文件。接着把应用部署到服务器上，直接将web目录的内容复制到%BEA_HOME%/user_projects/domains/blogdomain/applications/blog/目录下即可。如果要在Tomcat上部署，直接将整个web目录复制到%TOMCAT%/webapps/blog/下。 <br />　　最后，如果需要，可以用javadoc生成api文档。<a id="3" name="3"></a></p> <p><b>系统设计</b> <a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#0">(目录)</a><br />　　Crystal Blog共分成三层结构：后台数据持久层，采用DAO模式；中间逻辑层，采用Facade模式；前端Web层，采用MVC结构，使用JSP作为视图。以下是Rational Rose的UML图：</p> <p align="center"></p><a id="3_1_1" name="3_1_1"></a><b>设计Domain对象</b><b></b> <a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#0">(目录)</a><br /><br />　　设计Domain对象<br />　　Domain层是抽象出的实体。根据我们要实现的功能，设计以下实体，它们都是普通的Java Bean： <br />　　<b>Account：</b>封装一个用户，包括用户ID，用户名，口令，用户设置等等。 <br />　　<b>Category：</b>封装一个分类，一共有3种Category，分别用来管理Article，Image和Link，一个Account对应多个Category。 <br />　　<b>Article：</b>封装一篇文章，包括Title，Summary，Content等等，一个Category对应多个Article。 <br />　　<b>Feedback：</b>封装一个回复，包括Title，Username，Url和Content，一个Article对应多个Feedback。 <br />　　<b>Image：</b>封装一个图片，Image只包含图片信息（ImageId，Type），具体的图片是以用户上传到服务器的文件的形式存储的。一个Category对应多个Image。 <br />　　<b>Link：</b>封装一个链接，和Category是多对一的关系。有Title，Url，Rss等属性。 <br />　　<b>Message：</b>封装一个消息，使其他用户在不知道Email地址的情况下能够通过系统发送邮件给某个用户。<br /><br />　　最后，为了唯一标识每条数据库记录，我们需要一个主键。在MS SQL Server和Oracle中可以使用自动递增的主键生成方式。但是很多数据库不支持自动递增的主键，考虑到移植性，我们自己定义一个Sequence表，用于生成递增的主键。Sequence表有且仅有7条记录，分别记录Account到Message对象的当前最大主键值。系统启动时，由SqlConfig负责初始化Sequence表。 <br />　　SequenceDao负责提供下一个主键，为了提高效率，一次缓存10个主键。 <a id="3_1_2" name="3_1_2"></a> <p><b>配置iBatis</b><b></b> <a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#0">(目录)</a><br />　　接下来，使用iBatis实现O/R Mapping。首先从<a href="http://www.ibatis.com/" target="_blank">http://www.ibatis.com</a>下载iBatis 2.0，将所需的jar文件复制到web/WEB-INF/lib/目录下。iBatis使用XML配置数据库表到Java对象的映射，先编写一个sql-map-config.xml：<br /><font color="#339966"></font></p> <p align="left"><font color="#339966">&lt;?xml version="1.0" encoding="utf-8" ?&gt;<br />&lt;!DOCTYPE sqlMapConfig<br />&nbsp; PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN"<br />&nbsp; "http://www.ibatis.com/dtd/sql-map-config-2.dtd"&gt;<br />&lt;sqlMapConfig&gt;<br />&nbsp; &lt;settings cacheModelsEnabled="false" enhancementEnabled="true"<br />&nbsp;&nbsp;&nbsp; lazyLoadingEnabled="true" maxRequests="32"<br />&nbsp;&nbsp;&nbsp; maxSessions="10" maxTransactions="5"<br />&nbsp;&nbsp;&nbsp; useStatementNamespaces="false"<br />&nbsp; /&gt;<br />&nbsp; &lt;transactionManager type="JDBC"&gt;<br />&nbsp;&nbsp;&nbsp; &lt;dataSource type="JNDI"&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;property name="DataSource" value="jdbc/blog" /&gt;<br />&nbsp;&nbsp;&nbsp; &lt;/dataSource&gt;<br />&nbsp; &lt;/transactionManager&gt;<br />&nbsp; &lt;!-- 如果有其他xml配置文件，可以包含进来 --&gt;<br />&nbsp; &lt;sqlMap resource="Account.xml" /&gt;<br />&lt;/sqlMapConfig&gt;</font></p> <p>　　将sql-map-config.xml放到web/WEB-INF/classes/目录下，iBatis就能搜索到这个配置文件，然后编写一个初始化类：<br /><br /><font color="#339966">public class SqlConfig {<br />&nbsp;&nbsp;&nbsp; private SqlConfig() {}<br />&nbsp;&nbsp;&nbsp; private static final SqlMapClient sqlMap;<br />&nbsp;&nbsp;&nbsp; static {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; java.io.Reader reader = Resources.getResourceAsReader ("sql-map-config.xml");<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sqlMap = SqlMapClientBuilder.buildSqlMapClient(reader);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } catch (Exception e) {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; e.printStackTrace();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; throw new RuntimeException("Error initializing SqlConfig. Cause: " + e);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; public static SqlMapClient getSqlMapInstance () {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return sqlMap;<br />&nbsp;&nbsp;&nbsp; }<br />}</font><br /><br />　　SqlMapClient封装了访问数据库的大部分操作，可以直接使用SqlConfig.getSqlMapInstance()获得这个唯一实例。<a id="3_1_3" name="3_1_3"></a></p> <p><b>使用DAO模１ 式</b><b></b> <a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#0">(目录)</a><br />　　为了分离逻辑层和数据库持久层，定义一系列DAO接口：AccountDao，CategoryDao，ArticleDao……其实现类对应为SqlMapAccountDao，SqlMapCategoryDao，SqlMapArticleDao……这样就使得逻辑层完全脱离了数据库访问代码。如果将来需要使用其它的O/R Mapping方案，直接实现新的DAO接口替代现有的SqlMapXxxDao即可。 <br />　　以SqlMapAccountDao为例，实现一个login()方法是非常简单的：</p> <p align="left"><font color="#339966">public int login(String username, String password) throws AuthorizationException {<br />&nbsp;&nbsp;&nbsp; try {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Map map = new HashMap();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; map.put("username", username);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; map.put("password", password);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Integer I = (Integer)sqlMap.queryForObject("login", map);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(I==null)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; throw new RuntimeException("Failed: Invalid username or password.");<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return I.intValue();<br />&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; catch(SQLException sqle) {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; throw new RuntimeException("Sql Exception: " + sqle);<br />&nbsp;&nbsp;&nbsp; }<br />}</font></p> <p>　　在Account.xml配置文件中定义login查询：</p> <p align="left"><font color="#339966">&lt;select id="login" parameterClass="java.util.Map" resultClass="int"&gt;<br />&nbsp; select [accountId] from [Account] where<br />&nbsp; [username] = #username# and password = #password#<br />&lt;/select&gt;<a id="3_2" name="3_2"></a></font></p> <p><b>逻辑层设计</b><b></b> <a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#0">(目录)</a><br />　　由于DAO模式已经实现了所有的数据库操作，业务逻辑主要是检查输入，调用DAO接口，因此业务逻辑就是一个简单的Facade接口： </p> <p align="left"><font color="#339966">public class FacadeImpl implements Facade {<br />&nbsp;&nbsp;&nbsp; private AccountDao&nbsp; accountDao;<br />&nbsp;&nbsp;&nbsp; private ArticleDao&nbsp; articleDao;<br />&nbsp;&nbsp;&nbsp; private CategoryDao categoryDao;<br />&nbsp;&nbsp;&nbsp; private FeedbackDao feedbackDao;<br />&nbsp;&nbsp;&nbsp; private ImageDao&nbsp;&nbsp;&nbsp; imageDao;<br />&nbsp;&nbsp;&nbsp; private LinkDao&nbsp;&nbsp;&nbsp;&nbsp; linkDao;<br />&nbsp;&nbsp;&nbsp; private SequenceDao sequenceDao;<br />}<br /></font><br />　　对于普通的getArticle()等方法，Facade仅仅简单地调用对应的DAO接口：<br /><br /><font color="#339966">public Article getArticle(int articleId) throws QueryException {<br />&nbsp;&nbsp;&nbsp; return articleDao.getArticle(articleId);<br />}<br /></font><br />　　对于需要身份验证的操作，如deleteArticle()方法，Facade需要首先验证用户身份：<br /><br /><font color="#339966">public void deleteArticle(Identity id, int articleId) throws DeleteException {<br />&nbsp;&nbsp;&nbsp; Article article = getArticleInfo(articleId);<br />&nbsp;&nbsp;&nbsp; if(article.getAccountId()!=id.getAccountId())<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; throw new AuthorizationException("Permission denied.");<br />&nbsp;&nbsp;&nbsp; articleDao.deleteArticle(articleId);<br />}</font></p> <p align="left">　　要分离用户验证逻辑，可以使用Proxy模式，或者使用Spring的AOP，利用MethodInterceptor实现，不过，由于逻辑很简单，完全可以直接写在一块，不必使用过于复杂的设计。 <br />　　至此，我们的Blog已经实现了所有的后台业务逻辑，并且提供统一的Facade接口。前台Web层仅仅依赖这个Facade接口，这样，Web层和后台耦合非常松散，即使替换整个Web层也非常容易。<a id="3_3" name="3_3"></a></p> <p><b>Web层设计</b> <a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#0">(目录)</a><a id="3_3_1" name="3_3_1"></a><br /><b>使用MVC模式 </b><a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#0">(目录)</a><br />　　对于复杂的Web层，使用MVC模式是必不可少的。虽然Spring能轻易集成Struts，WebWorks等Web框架，但Spring本身就提供了一个非常好的Web框架，能完全实现MVC模式。 <br />　　Spring使用一个DispatcherServlet，所有的特定请求都被转发到DispatcherServlet，然后由相应的Controller处理，Controller返回一个ModelAndView对象（因为Java语言的方法调用只能返回一个结果，而且不支持ref参数，所以将Model和View对象合在一起返回），Model是一个Java对象，通常是Map，View是视图的逻辑名字，通常是JSP文件名，但也可以使用Velocity等作为视图。返回的View通过viewResolver得到真正的文件名。<br />　　首先配置Spring的MVC，在web.xml中声明DispatcherServlet，处理所有以<font color="#ff0000">.c</font>结尾的请求：<font color="#339966"><web-app> <servlet></servlet></web-app><web-app><servlet></servlet></web-app><beans></beans></font></p> <p align="left"><font color="#339966">&lt;web-app&gt;<br />&nbsp;&nbsp;&nbsp; &lt;servlet&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;servlet-name&gt;dispatcher&lt;/servlet-name&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;servlet-class&gt;org.springframework.web.servlet.DispatcherServlet&lt;/servlet-class&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;load-on-startup&gt;1&lt;/load-on-startup&gt;<br />&nbsp;&nbsp;&nbsp; &lt;/servlet&gt;<br />&nbsp;&nbsp;&nbsp; &lt;servlet-mapping&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;servlet-name&gt;dispatcher&lt;/servlet-name&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;url-pattern&gt;*.c&lt;/url-pattern&gt;<br />&nbsp;&nbsp;&nbsp; &lt;/servlet-mapping&gt;<br />&lt;/web-app&gt;</font><br /><font color="#339966"><beans></beans></font><br />　　Spring会在WEB-INF下查找一个名为dispatcher-servlet.xml的文件，我们需要创建这个文件：<br /><br /><font color="#339966">&lt;?xml version="1.0" encoding="utf-8"?&gt;<br />&lt;!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"<br />&nbsp;"http://www.springframework.org/dtd/spring-beans.dtd"&gt;<br />&lt;beans&gt;<br />&lt;/beans&gt;</font><br /><br /><font color="#339966"></font><property name="mappings">　用到的所有的Java Bean组件都要在这个文件中声明和配置，以下是配置URL映射的Bean：</property><font color="#339966"><property name="mappings"><br /><props></props></property><br />&lt;bean id="urlMapping"<br />&nbsp;class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"&gt;<br />&nbsp;&nbsp;&nbsp; &lt;property name="mappings"&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;props&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;prop key="/article.c"&gt;articleController&lt;/prop&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/props&gt;<br />&nbsp;&nbsp;&nbsp; &lt;/property&gt;<br />&lt;/bean&gt;</font></p> <p align="left">　　凡是匹配/article.c的Request都会被名为articleController的Bean处理，同样需要声明这个articleController：<br /><font color="#339966"><bean class="example" id="articleController"><br /></bean>&lt;bean id="articleController" class="example.ViewArticleController"&gt;<br />&lt;/bean&gt;</font><br /><font color="#339966"><bean class="example"></bean><br /></font>　ViewArticleController处理请求，然后生成Model，并选择一个View：<br /><br /><font color="#339966">public class ViewArticleController implements Controller {<br />&nbsp; &nbsp;&nbsp;private Facade facade;<br />&nbsp;&nbsp;&nbsp; public void setFacade(Facade facade) { this.facade = facade; }<br />public ModelAndView handleRequest(HttpServletRequest request,<br />&nbsp; &nbsp;&nbsp;HttpServletResponse response) throws Exception {<br /></font>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;// 获得参数：<font color="#339966"><br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;int articleId = Integer.parseInt(request.getParameter("articleId"));<br /></font>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;// 使用facade处理请求：<font color="#339966"><br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;Article article = facade.getArticle(articleId);<br /></font>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;// 生成Model：<font color="#339966"><br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;Map map = new HashMap();<br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;map.put("article", article);<br /></font>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;// 返回Model和视图名“skin/blueskysimple/article”：<font color="#339966"><br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;return new ModelAndView("skin/blueskysimple/article", map);<br />&nbsp;&nbsp;&nbsp; }<br />}</font><br /><br />　　最后，skin/bluesky/article视图会将结果显示给用户。 <br />　　我们注意到，ViewArticleController并不自己查找或者创建Facade，而是由容器通过setFacade(Facade)方法设置的，这就是所谓的IoC（Inversion of Control）或者Dependency Injection。容器通过配置文件完成所有组件的初始化工作：<br /><br /><font color="#339966">&lt;!-- 声明一个Facade --&gt;<br />&lt;bean id="facade" class="example.Facade" /&gt;<br />&lt;!-- 声明一个Controller --&gt;<br />&lt;bean id="articleController" class="example.ViewArticleController"&gt;<br />&nbsp;&nbsp;&nbsp; &lt;!-- 为articleController设置facade属性 --&gt;<br />&lt;property name="facade"&gt;<br />&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&lt;!-- 将名为facade的Bean的引用传进去 --&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;ref bean="facade" /&gt;<br />&nbsp;&nbsp;&nbsp; &lt;/property&gt;<br />&lt;/bean&gt;</font></p> <p align="left">　　以上配置文件实现的功能大致为：<br /><br /><font color="#339966">Facade facade = new Facade();<br />ViewArticleController articleController = new ViewArticleController();<br />articleController.setFacade(facade);<br /></font><br />　　但是我们不必编写以上代码，只需在xml文件中装配好我们的组件就可以了。所有组件由Spring管理，并且，缺省的创建模式是Singleton，确保了一个组件只有一个实例。 <br />　　此外，所有自定义异常都是RuntimeException，Spring提供的AOP使我们能非常方便地实现异常处理。在Web层定义ExceptionHandler，处理所有异常并以统一的error页面把出错信息显示给用户，因此，在代码中只需抛出异常，完全不必在Controller中处理异常：<br /><br /><font color="#339966">&lt;bean id="handlerExceptionResolver" class="org.crystalblog.web.ExceptionHandler" /&gt;</font></p> <p><b>使用Velocity作为View</b><br />　　采用Velocity作为View的最大的优点是简洁，能通过简单明了的语法直接在Html中输出Java变量。Velocity本身是一个模板引擎，通过它来渲染Model输出Html非常简单，比如显示用户名： <br /><br /><font color="#339966">&lt;b&gt;${account.username}&lt;/b&gt;<br /></font>　　换成JSP不得不写成：<font color="#339966"><br />&lt;b&gt;&lt;%=account.getUsername()%&gt;&lt;/b&gt;<br /></font>而<font color="#339966">&lt;%...%&gt;</font>标记在Dreamwaver中无法直接看到其含义。如果需要在标签中嵌入JSP就更晦涩了：<font color="#339966"><br />&lt;a href="somelink?id=&lt;%=id %&gt;"&gt;Link&lt;/a&gt;</font></p> <p>　　这种嵌套的标签往往使得可视化Html编辑器难以正常显示，而Velocity用直接嵌入的语法解决了“ <%...%>”的问题。 <br />　　在Spring中集成Velocity是非常简单的事情，甚至比单独使用Velocity更简单，只需在dispatcher-servlet.xml中申明：</p> <p align="left"><font color="#339966">&lt;bean id="viewResolver" class="org.springframework.web.servlet.view.velocity.VelocityViewResolver"&gt;<br />&lt;/bean&gt;<br />&lt;bean id="velocityConfig" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer"&gt;<br />&nbsp;&nbsp;&nbsp; &lt;property name="resourceLoaderPath"&gt;&lt;value&gt;/&lt;/value&gt;&lt;/property&gt;<br />&nbsp;&nbsp;&nbsp; &lt;property name="configLocation"&gt;&lt;value&gt;/WEB-INF/velocity.properties&lt;/value&gt;&lt;/property&gt;<br />&lt;/bean&gt;</font></p> <p>　　虽然标准的Velocity模板以.vm命名，但我们的所有Velocity模板页均以.html作为扩展名，不但用Dreamwaver编辑极为方便，甚至可以直接用IE观看页面效果。<a id="3_3_2" name="3_3_2"></a></p> <p><b>实现Skin</b> <a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#0">(目录)</a><br />　　许多Blog系统都允许用户选择自己喜欢的界面风格。要实现Skin功能非常简单，为每个Skin编写Velocity模板，存放在web/skin/目录下，然后在Controller中返回对应的视图：</p> <p align="left"><font color="#339966">String viewpath = skin.getSkin(account.getSkinId()) + "viewArticle";</font><br /><br />　　由于使用了MVC模式，我们已经为每个页面定义好了Model，添加一个新的skin非常容易，只需要编写几个必须的.html文件即可，可以参考现有的skin。 <br />　　SkinManager的作用是在启动时自动搜索/skin/目录下的所有子目录并管理这些skin，将用户设定的skinId映射到对应的目录。目前只有一个skin，您可以直接用可视化Html编辑器如Dreamwaver改造这个skin。<a id="4" name="4"></a></p> <p><b>附加功能</b> <a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#0">(目录)</a><a id="4_1_1" name="4_1_1"></a><br /><b>实现图片上传</b> <a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#0">(目录)</a><br />　　用户必须能够上传图片，因此需要文件上传的功能。比较常见的文件上传组件有Commons FileUpload（<a href="http://jakarta.apache.org/commons/fileupload" target="_blank">http://jakarta.apache.org/commons/fileupload/a&gt;）和COS FileUpload</a>（<a href="http://www.servlets.com/cos" target="_blank">http://www.servlets.com/cos</a>），Spring已经完全集成了这两种组件，这里我们选择Commons FileUpload。 <br />　　由于Post一个包含文件上传的Form会以multipart/form-data请求发送给服务器，必须明确告诉DispatcherServlet如何处理MultipartRequest。首先在dispatcher-servlet.xml中声明一个MultipartResolver：</p> <p align="left"><font color="#339966">&lt;bean id="multipartResolver"<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; class="org.springframework.web.multipart.commons.CommonsMultipartResolver"&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;!-- 设置上传文件的最大尺寸为1MB --&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;property name="maxUploadSize"&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;value&gt;1048576&lt;/value&gt;<br />&nbsp;&nbsp;&nbsp; &lt;/property&gt;<br />&lt;/bean&gt;</font></p> <p>　　这样一旦某个Request是一个MultipartRequest，它就会首先被MultipartResolver处理，然后再转发相应的Controller。 <br />　　在UploadImageController中，将HttpServletRequest转型为MultipartHttpServletRequest，就能非常方便地得到文件名和文件内容： </p> <p align="left"><font color="#339966">public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {<br /></font>&nbsp;&nbsp;&nbsp; // 转型为MultipartHttpRequest：<font color="#339966"><br />&nbsp;&nbsp;&nbsp; MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;<br /></font>&nbsp;&nbsp;&nbsp; // 获得文件：<font color="#339966"><br />&nbsp;&nbsp;&nbsp; MultipartFile file = multipartRequest.getFile("file");<br /></font>&nbsp;&nbsp;&nbsp; // 获得文件名：<font color="#339966"><br />&nbsp;&nbsp;&nbsp; String filename = file.getOriginalFilename();<br /></font>&nbsp;&nbsp;&nbsp; // 获得输入流：<font color="#339966"><br />&nbsp;&nbsp;&nbsp; InputStream input = file.getInputStream();<br /></font>&nbsp;&nbsp;&nbsp; // 写入文件...<font color="#339966"><br />}<a id="4_1_2" name="4_1_2"></a></font></p> <p><b>生成缩略图</b> <a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#0">(目录)</a><br />　　当用户上传了图片后，必须生成缩略图以便用户能快速浏览。我们不需借助第三方软件，JDK标准库就包含了图像处理的API。我们把一张图片按比例缩放到120X120大小，以下是关键代码：</p> <p align="left"><font color="#339966">public static void createPreviewImage(String srcFile, String destFile) {<br />&nbsp;&nbsp;&nbsp; try {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; File fi = new File(srcFile);</font> // src<font color="#339966"><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; File fo = new File(destFile);</font> // dest<font color="#339966"><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; BufferedImage bis = ImageIO.read(fi);<br /></font></p> <p align="left"><font color="#339966">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int w = bis.getWidth();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int h = bis.getHeight();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; double scale = (double)w/h;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int nw = IMAGE_SIZE;</font> // final int IMAGE_SIZE = 120;<font color="#339966"><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int nh = (nw * h) / w;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if( nh&gt;IMAGE_SIZE ) {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; nh = IMAGE_SIZE;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; nw = (nh * w) / h;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; double sx = (double)nw / w;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; double sy = (double)nh / h;<br /></font></p> <p align="left"><font color="#339966">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; transform.setToScale(sx,sy);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; AffineTransformOp ato = new AffineTransformOp(transform, null);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; BufferedImage bid = new BufferedImage(nw, nh, BufferedImage.TYPE_3BYTE_BGR);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ato.filter(bis,bid);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ImageIO.write(bid, "jpeg", fo);<br />&nbsp;&nbsp;&nbsp; } catch(Exception e) {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; e.printStackTrace();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; throw new RuntimeException("Failed in create preview image. Error: " + e.getMessage());<br />&nbsp;&nbsp;&nbsp; }<br />}<a id="4_1_3" name="4_1_3"></a></font></p> <p><b>实现RSS</b> <a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#0">(目录)</a><br />　　RSS是一个标准的XML文件，Rss阅读器可以读取这个XML文件获得文章的信息，使用户可以通过Rss阅读器而非浏览器阅读Blog，我们只要动态生成这个XML文件便可以了。RSSLibJ是一个专门读取和生成RSS的小巧实用的Java库，大小仅25k，可以从<a href="http://sourceforge.net/projects/rsslibj/" target="_blank">http://sourceforge.net/projects/rsslibj/</a>下载rsslibj-1_0RC2.jar和它需要的EXMLjar两个文件，然后复制到web/WEB-INF/lib/下。 <br />　　使用RSSLibJ异常简单，我们先设置好HttpServletResponse的Header，然后通过RSSLibJ输出XML即可：</p> <p align="left"><font color="#339966">Channel channel = new Channel();<br />channel.setDescription(account.getDescription());<br />baseUrl = baseUrl.substring(0, n);<br />channel.setLink("http://server-name/home.c?accountId=" + accountId);<br />channel.setTitle(account.getTitle());<br />List articles = facade.getArticles(accountId, account.getMaxPerPage(), 1);<br />Iterator it = articles.iterator();<br />while(it.hasNext()) {<br />&nbsp;&nbsp;&nbsp; Article article = (Article)it.next();<br />&nbsp;&nbsp;&nbsp; channel.addItem("http://server-name/article.c?articleId=" + article.getArticleId(),<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; article.getSummary(), article.getTitle()<br />&nbsp;&nbsp;&nbsp; );<br />}<br />// 输出xml:<br />response.setContentType("text/xml");<br />PrintWriter pw = response.getWriter();<br />pw.print(channel.getFeed("rss"));<br />pw.close();<a id="4_1_4" name="4_1_4"></a></font></p> <p align="left"><b>实现全文搜索</b> <a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#0">(目录)</a><br />　　全文搜索能大大方便用户快速找到他们希望的文章，为blog增加一个全文搜索功能是非常必要的。然而，全文搜索不等于SQL的LIKE语句，因为关系数据库的设计并不是为全文搜索设计的，数据库索引对全文搜索无效，在一个几百万条记录中检索LIKE ＇%A%＇可能会耗时几分钟，这是不可接受的。幸运的是，我们能使用免费并且开源的纯Java实现的Lucene全文搜索引擎，Lucene可以非常容易地集成到我们的blog中。 <br />　　Lucene不提供直接对文件，数据库的索引，只提供一个高性能的引擎，但接口却出人意料地简单。我们只需要关心以下几个简单的接口： <br />　　<b>Document：</b>代表Lucene数据库的一条记录，也代表搜索的一条结果。 <br />　　<b>Field：</b>一个Document包含一个或多个Field，类似关系数据库的字段。 <br />　　<b>IndexWriter：</b>用于创建新的索引，也就是向数据库添加新的可搜索的大段字符串。 <br />　　<b>Analyzer：</b>将字符串拆分成单词（Token），不同的文本对应不同的Analyzer，如HtmlAnalyzer，PDFAnalyzer。 <br />　　<b>Query：</b>封装一个查询，用于解析用户输入。例如，将“bea blog”解析为“同时包含bea和blog的文章”。 <br />　　<b>Searcher：</b>搜索一个Query，结果将以Hits返回。 <br />　　<b>Hits：</b>封装一个搜索结果，包含Document集合，能非常容易地输出结果。 <br />　　下一步，我们需要为Article表的content字段建立全文索引。首先为Lucene新建一个数据库，请注意这个数据库是Lucene专用的，我们不能也不必知道它的内部结构。Lucene的每个数据库对应一个目录，只需要指定目录即可：<br /><br /><font color="#339966">String indexDir = "C:/search/blog";<br />IndexWriter indexWriter = new IndexWriter(indexDir, new StandardAnalyzer(), true);<br />indexWriter.close();</font><br /><br />　　然后添加文章，让Lucene对其索引：<br /><br /><font color="#339966">String title = "文章标题" // 从数据库读取<br />String content = "文章内容" // 从数据库读取<br />// 打开索引：<br />IndexWriter indexWriter = new IndexWriter(indexDir, new StandardAnalyzer(), false);<br />// 添加一个新记录：<br />Document doc = new Document();<br />doc.add(Field.Keyword("title", title));<br />doc.add(Field.Text("content", content));<br />// 建立索引：<br />indexWriter.addDocument(doc);<br />// 关闭：<br />indexWriter.close();<br /><br /></font>　要搜索文章非常简单：<br /><font color="#339966"></font>　然后添加文章，让对其索引：</p> <p align="left"><font color="#339966">String title = "文章标题" </font>// 从数据库读取<br /><font color="#339966">String content = "文章内容" </font>// 从数据库读取<br />// 打开索引：<br /><font color="#339966">IndexWriter indexWriter = new IndexWriter(indexDir, new StandardAnalyzer(), false);<br /></font>// 添加一个新记录：<br /><font color="#339966">Document doc = new Document();<br /></font><font color="#339966">doc.add(Field.Keyword("title", title));<br /></font><font color="#339966">doc.add(Field.Text("content", content));<br /></font>// 建立索引：<br /><font color="#339966">indexWriter.addDocument(doc);<br /></font>// 关闭：<br /><font color="#339966">indexWriter.close();<br /><br /></font>　　要搜索文章非常简单：<br /><br /><font color="#339966">Searcher searcher = new IndexSearcher(dir);<br /></font><font color="#339966">Query query = QueryParser.parse(keyword, "content", new StandardAnalyzer());<br /></font><font color="#339966">Hits hits = searcher.search(query);<br /></font><font color="#339966">if(hits != null){<br /></font><font color="#339966">&nbsp;&nbsp;&nbsp; for(int i = 0;i &lt; hits.length(); i++){<br /></font><font color="#339966">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;Document doc = hits.doc(i);<br /></font><font color="#339966">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("found in " + doc.get("title"));<br /></font><font color="#339966">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(doc.get("content"));<br /></font><font color="#339966">&nbsp;&nbsp;&nbsp; }<br /></font><font color="#339966">}<br /></font><font color="#339966">searcher.close();</font></p> <p>　　我们设计一个LuceneSearcher类封装全文搜索功能，由于必须锁定数据库所在目录，我们把数据库设定在/WEB-INF/search/下，确保用户不能访问，并且在配置文件中初始化目录：</p> <p align="left"><font color="#339966">&lt;bean id="luceneSearcher" class="org.crystalblog.search.LuceneSearcher"&gt;<br /></font><font color="#339966">&nbsp;&nbsp;&nbsp; &lt;property name="directory"&gt;<br /></font><font color="#339966">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;value&gt;/WEB-INF/search/&lt;/value&gt;<br /></font><font color="#339966">&nbsp;&nbsp;&nbsp; &lt;/property&gt;<br /></font><font color="#339966">&lt;/bean&gt;</font><br /><br />效果如下：</p> <p align="center"><img height="467" src="http://dev2dev.bea.com.cn/images/image2004111677.gif" width="700" /> <br />（图4：search）<a id="4_1_5" name="4_1_5"></a></p> <p><b>发送Email</b> <a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#0">(目录)</a><br />　　Blog用户可以让系统将来访用户的留言发送到注册的Email地址，为了避免使用SMTP发信服务器，我们自己手动编写一个SendMail组件，直接通过SMTP协议将Email发送到用户信箱。 <br />　　SendMail组件只需配置好DNS服务器的IP地址，即可向指定的Email信箱发送邮件。并且，SendMail使用缓冲队列和多线程在后台发送Email，不会中断正常的Web服务。具体代码请看SendMail.java。<a id="5" name="5"></a></p> <p><b>测试</b> <a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#0">(目录)</a><br />　　服务器配置为：P4 1.4G，512M DDR，100M Ethernet，Windows XP Professional SP2。 <br />　　测试服务器分别为WebLogic Server 8.1，Tomcat 4.1/5.0，Resin 2.1.1。 <br />　　测试数据库为MS SQL Server 2000 SP3。如果你使用Oracle或者DB2，MySQL等其他数据库并测试成功，请将SQL初始化脚本和详细配置过程发一份给我，谢谢。 <br />　　由于时间有限，没有作进一步的调优。WebLogic Server和iBatis有很多优化选项，详细配置可以参考相关文档。<a id="6" name="6"></a></p> <p><b>中文支持</b> <a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#0">(目录)</a><br />　　测试发现，中文不能在页面中正常显示，为了支持中文，首先在web.xml加入Filter，用于将输入编码设置为gb2312：<br /><br /><font color="#339966">&lt;filter&gt;<br />&nbsp;&nbsp;&nbsp; &lt;filter-name&gt;encodingFilter&lt;/filter-name&gt;<br />&nbsp;&nbsp;&nbsp; &lt;filter-class&gt;org.crystalblog.web.filter.EncodingFilter&lt;/filter-class&gt;<br />&nbsp;&nbsp;&nbsp; &lt;init-param&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;param-name&gt;encoding&lt;/param-name&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;param-value&gt;gb2312&lt;/param-value&gt;<br />&nbsp;&nbsp;&nbsp; &lt;/init-param&gt;<br />&lt;/filter&gt;<br />&lt;filter-mapping&gt;<br />&nbsp;&nbsp;&nbsp; &lt;filter-name&gt;encodingFilter&lt;/filter-name&gt;<br />&nbsp;&nbsp;&nbsp; &lt;url-pattern&gt;/*&lt;/url-pattern&gt;<br />&lt;/filter-mapping&gt;</font><br /><br />　　然后用文本工具搜索所有的.htm，.html，.properties文件，将“iso-8859-1”替换为“gb2312”，现在页面中文已经能正常显示，但是Lucene仍不能正常解析中文，原因是标准的StandardA?nalyzer只能解析英文，可以从网上下载一个支持中文的Analyzer。<a id="7" name="7"></a></p> <p><b>总结</b> <a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#0">(目录)</a><br />　　Spring的确是一个优秀的J2EE框架，通过Spring强大的集成和配置能力，我们能轻松设计出灵活的多层J2EE应用而无需复杂的EJB组件支持。由于时间仓促，水平有限，文中难免有不少错误，恳请读者指正。<a id="8" name="8"></a></p> <p><b>源代码下载 </b><a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#0">(目录)</a><br />　　源代码可以从<a href="http://www.javasprite.com/crystal/download.asp" target="_blank">http://www.javasprite.com/crystal/download.asp</a>下载。<a id="9" name="9"></a></p> <p><b>相关资源下载</b> <a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#0">(目录)</a><br />JDK 1.4.2可以从<a href="http://java.sun.com/" target="_blank">http://java.sun.com</a>下载。<br />Spring framework 1.1可以从<a href="http://www.springframework.org/" target="_blank">http://www.springframework.org</a>下载。<br />iBatis 2.0可以从<a href="http://www.ibatis.com/" target="_blank">http://www.ibatis.com</a>下载。<br />Tomcat 4.1/5.0、Ant 1.6可以从<a href="http://www.apache.org/" target="_blank">http://www.apache.org</a>下载。<br />Resin 2.1.1可以从<a href="http://www.caucho.com/" target="_blank">http://www.caucho.com</a>下载。<br />WebLogic Server / Workshop 8.1可以从<a href="http://commerce.bea.com/" target="_blank">http://commerce.bea.com</a>下载。<br />JUnit 3.8可以从<a href="http://www.junit.org/" target="_blank">http://www.junit.org</a>下载。<br />MySQL 4及其JDBC驱动可以从<a href="http://www.mysql.com/" target="_blank">http://www.mysql.com</a>下载。<br />MS SQL Server 2000 JDBC驱动可以从<a href="http://www.microsoft.com/downloads/details.aspx?FamilyID=07287b11-0502-461a-b138-2aa54bfdc03a&amp;DisplayLang=en" target="_blank">http://www.microsoft.com/downloads/details.aspx?FamilyID=07287b11-0502-461a-b138-2aa54bfdc03a&amp;DisplayLang=en</a>下载。<br />RSSLibJ 1.0可以从<a href="http://sourceforge.net/projects/rsslibj/" target="_blank">http://sourceforge.net/projects/rsslibj/</a>下载。<a id="10" name="10"></a></p> <p><b>参考</b> <a href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=12#0">(目录)</a><br />“Spring Reference”，Rod Johnson等。<br />“iBatis SQL Maps Guide”。<br />“Apache Ant 1.6.2 Manual”。<br />“Lucene Getting Started”。<br />Springframework的JPetStore示例是非常棒的设计，本文参考了JPetStore的许多设计模式。<a id="11" name="11"></a></p> <p><b>关于作者</b><br />廖雪峰，北京邮电大学本科毕业，对J2EE/J2ME有浓厚兴趣，欢迎交流：<a href="mailto:asklxf@163.com">asklxf@163.com</a>。<br />个人网站：<a href="http://www.javasprite.com/" target="_blank">http://www.javasprite.com</a>，欢迎访问。<br />个人Blog站点：<a href="http://blog.csdn.net/asklxf/" target="_blank">http://blog.csdn.net/asklxf/</a>，欢迎访问。<br /></p><img src ="http://www.blogjava.net/sgsoft/aggbug/226.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/sgsoft/" target="_blank">海天一鸥</a> 2005-01-12 13:36 <a href="http://www.blogjava.net/sgsoft/articles/226.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Struts动态控制表格大小(作者：作者：James M. Turner。译者：陈姣姣 ) </title><link>http://www.blogjava.net/sgsoft/articles/198.html</link><dc:creator>海天一鸥</dc:creator><author>海天一鸥</author><pubDate>Tue, 11 Jan 2005 07:59:00 GMT</pubDate><guid>http://www.blogjava.net/sgsoft/articles/198.html</guid><wfw:comment>http://www.blogjava.net/sgsoft/comments/198.html</wfw:comment><comments>http://www.blogjava.net/sgsoft/articles/198.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/sgsoft/comments/commentRss/198.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/sgsoft/services/trackbacks/198.html</trackback:ping><description><![CDATA[<span class="myp111"><font id="zoom">在Succeeding with Struts的前面安装部分，我间接提到了DynaForms在运行期内可以动态的控制表格大小。换句话说，就是能够根据需要得到5行、或者10行、或者15行长的表格。可能有点不明智，我把这种策略的实际实现作为一种练习留给了读者自己。在接下来的几个月内，我收到了几十个读者的请求，他们请求给出详细的实现细节，所以这个月我将用两种不同的方法来实现动态调整的表格。 <br />第一个方法就是我在前面的栏目中提到的那个方法，将尺寸参数留给DynaForm 的form-property 属性来实现。为了演示详细过程，我们来看看一个非常简单的应用：添加关于不同Star Wars 演员的注释。在这个应用中我们感兴趣的关键事实是：演员的数量在表格配置中动态设定，而不是在struts-config.xml文件中动态设定。 <br />首先，我们先来看看struts-config.xml 文件： <br /><br /><ccid_nobr></ccid_nobr> <table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="#000000" border="1"> <tbody> <tr> <td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code></ccid_code>&lt;?xml version="1.0" encoding="UTF-8"?&gt; &lt;!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd"&gt; &lt;struts-config&gt; &lt;form-beans&gt; &lt;form-bean name="dynamicArrayForm" type="org.apache.struts.validator.DynaValidatorForm"&gt; &lt;form-property name="people" type="demo.Person[]"/&gt; &lt;/form-bean&gt; &lt;/form-beans&gt; &lt;action-mappings&gt; &lt;action path="/setupForm" type="demo.SetupFormAction" name="dynamicArrayForm" scope="session" validate="false"&gt; &lt;forward name="success" path="/displayForm.jsp"/&gt; &lt;/action&gt; &lt;action path="/processActorComments" type="demo.ProcessFormAction" name="dynamicArrayForm" scope="session" validate="false"&gt; &lt;forward name="success" path="/displayForm.jsp"/&gt; &lt;/action&gt; &lt;/action-mappings&gt; &lt;/struts-config&gt;</pre></td></tr></tbody></table><br /><br />如你所见，这是一个相当简单的配置文件，只定义了一个表格和两个动作。第一个动作，/setupForm，用来在初始显示之前配置表格；另一个动作，/processActorComments 用来处理用户输入的注释。 <br />在这个文件中有两个重要的事情需要注意，它们对于事态的发展很关键： <br />1. people 表格属性定义为demo.Person[] 类型(即demo.Person的一个排列)，但不给出任何size 参数。这就为要创建的排列产生了一个占位符，但是没有任何例示的实排列。 <br />2. 这两个动作将表格定义在会话期范围内。这是很关键的，因为用户在填写数值之后提交表格时，数值在动作执行之前已经填充到表格内了。这就意味着没有机会手动创建具有恰当空位数的排列，正如你在表格显示之前在SetupFormAction 类中看到的情况一样。换句话说，当表格提交时，必须已经有恰当的空位来接受表格值，唯一能保证这个的方法就是在会话期范围内就已经有了这个表格。 <br />基本上在Person bean 中是没有值的，他只是一个具有lastName、 firstName、 dateOfBirth、gender 和comment字段的普通bean。源文件包括在WAR 文件内。 <br />现在我们来看看SetupFormAction 类，它在表格第一次显示之前调用。 <br /><br /><br /><ccid_nobr></ccid_nobr> <table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="#000000" border="1"> <tbody> <tr> <td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code></ccid_code>package demo; /** * Copyright 2004, James M. Turner. * All Rights Reserved * * A Struts action that sets up a DynaForm which is globally scoped */ import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.*; import org.apache.struts.action.*; import org.apache.struts.validator.DynaValidatorForm; public class SetupFormAction extends Action { public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { DynaValidatorForm df = (DynaValidatorForm) form; Person[] p = new Person[3]; p[0] = new Person(); p[0].setDateOfBirth("07/13/1942"); p[0].setLastName("Ford"); p[0].setFirstName("Harrison"); p[0].setGender("M"); p[1] = new Person(); p[1].setDateOfBirth("10/21/1956"); p[1].setLastName("Fisher"); p[1].setFirstName("Carrie"); p[1].setGender("F"); p[2] = new Person(); p[2].setDateOfBirth("09/25/1951"); p[2].setLastName("Hamill"); p[2].setFirstName("Mark"); p[2].setGender("M"); df.set("people", p); return mapping.findForward("success"); } }</pre></td></tr></tbody></table><br /><br />这一次也没有许多东西要看的。execute 方法要做的第一件事情，和任何基于DynaForm的动作所做的一样，就是将泛型ActionForm 类放到DynaValidatorForm内。这就使得我们可以在表格上使用get和set 方法。第二件事情就是，创建一个具有三个元素的类型Person 的排列。在这个方法中，尺寸是硬布线的，在实际应用中可以从数据库中选择一个尺寸。我们需要考虑的重要事情是排列应该在代码中创建，而不是由Struts引擎自己创建。这样行数可根据应用要求由代码随意指定。 <br />一旦排列已经确定，方法将创建三个Person 类实例并赋与数值。同样，在实际的应用中可通过一个循环来实现，这个循环不断地从数据库中读取行和填充表格行。最后，动作返回成功，导致Struts转移控制到displayForm.jsp 页。 <br /><br /><ccid_nobr></ccid_nobr> <table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="#000000" border="1"> <tbody> <tr> <td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code></ccid_code>&lt;!-- Copyright 2004, James M Turner. All Rights Reserved --&gt; &lt;%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %&gt; &lt;%@ taglib uri="/WEB-INF/c.tld" prefix="c" %&gt; &lt;head&gt; &lt;title&gt;Star Wars Actor Fact Page&lt;/title&gt; &lt;/head&gt; &lt;H1&gt;&lt;center&gt;Start Wars Actor Fact Page&lt;/title&gt; &lt;html:form action="/processActorComments" &gt; &lt;table border="1" width="80%"&gt; &lt;tr&gt;&lt;th&gt;Last Name&lt;/th&gt;&lt;th&gt;First Name&lt;/th&gt;&lt;th&gt;Date of Birth&lt;/th&gt;&lt;th&gt;Comment&lt;/th&gt;&lt;/tr&gt; &lt;c:forEach var="people" items="${dynamicArrayForm.map.people}"&gt; &lt;tr&gt;&lt;td&gt;&lt;c:out value="${people.lastName}"/&gt;&lt;/td&gt; &lt;td&gt;&lt;c:out value="${people.firstName}"/&gt;&lt;/td&gt; &lt;td&gt;&lt;c:out value="${people.dateOfBirth}"/&gt;&lt;/td&gt; &lt;td&gt;&lt;html:text name="people" indexed="true" property="comment"/&gt;&lt;/td&gt; &lt;/tr&gt; &lt;/c:forEach&gt; &lt;/table&gt; &lt;P/&gt; &lt;html:submit value="Update Comments"/&gt; &lt;/html:form&gt;</pre></td></tr></tbody></table><br /><br />同样，这里也没有很多东西要看的，他与我们上一篇文章查看固定长度的行时的代码完全一样。该页迭代行（记住在JSTL中我们必须使用map 属性来获得到DynaForm 属性的访问），显示演员的姓、名和出生日期，并提供文本域以便输入注释。 <br />当我们聚焦我们的浏览器合请求时，http://localhost:8080/struts/setupForm.do (假设你把struts.war 文件放在你本地机器的Tomcat 内)，将会出现下列页面： <br /><br /><ccid_nobr></ccid_nobr> <h3>Start Wars Actor Fact Page <table width="40%" border="1"> <tbody> <tr> <th>Last Name</th> <th>First Name</th> <th>Date of Birth</th> <th>Comment</th></tr> <tr> <td>Ford</td> <td>Harrison</td> <td>07/13/1942</td> <td><input name="people" height="95507296"> </td></tr> <tr> <td>Fisher</td> <td>Carrie</td> <td>10/21/1956</td> <td><input name="people" height="95506144"> </td></tr> <tr> <td>Hamill</td> <td>Mark</td> <td>09/25/1951</td> <td><input name="people" height="95141832"> </td></tr></tbody></table> <p><input type="submit" value="Update Comments" height="95505840"> </p></h3><br /></font></span>一旦表格提供，另一个简单的Struts动作来处理结果： <br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><ccid_nobr></ccid_nobr> <table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="#000000" border="1"> <tbody> <tr> <td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code></ccid_code>package demo; /** * Copyright 2004, James M. Turner. * All Rights Reserved * * A Struts action that sends the new comments to the console */ import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.*; import org.apache.struts.action.*; import org.apache.struts.validator.DynaValidatorForm; public class ProcessFormAction extends Action { public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { DynaValidatorForm df = (DynaValidatorForm) form; Person[] p = (Person[]) df.get("people"); for (int i = 0; i &lt; p.length; i++) { System.out.println(p[i].getFirstName() + " " + p[i]. getLastName() + ":" + p[i].getComment()); } return mapping.findForward("success"); } }</pre></td></tr></tbody></table><br /><br />在实际的应用中，这就是数据写回到数据库的地方。在这种情况下，他只将数据倒在控制台上所以我们可以看到他是正确收到的。假设我们为每个演员都填充了恰当的值，我们在控制台上会看到下列内容： <br /><br /><ccid_nobr></ccid_nobr> <table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="#000000" border="1"> <tbody> <tr> <td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code></ccid_code>Harrison Ford:Indiana Jones Carrie Fisher:Postcards from the Edge Mark Hamill:Wing Commander</pre></td></tr></tbody></table><br /><br />正如我在文章开头提到的一样，还有另一个方法可以解决这个问题，而且它不需要使用会话期范围内的表格。这个方法就是使用HashMaps 来存储行。我们来看看使用HashMaps编写的同一段代码： <br />首先，我们添加一个新表格到struts-config.xml： <br /><br /><ccid_nobr></ccid_nobr> <table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="#000000" border="1"> <tbody> <tr> <td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code></ccid_code>&lt;form-bean name="dynamicHashmapForm" type="org.apache.struts.validator.DynaValidatorForm"&gt; &lt;form-property name="people" type="java.util.HashMap"/&gt; &lt;form-property name="comments" type="java.util.HashMap"/&gt; &lt;/form-bean&gt;</pre></td></tr></tbody></table><br /><br />现在，我们不使用beans的排列，改为使用HashMap 来存储每个人的数据。另外，我们需要一个新的HashMap 来存储注释，原因我稍后再解释。我们也需要一个新的动作来填充数据： <br /><br /><ccid_nobr></ccid_nobr> <table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="#000000" border="1"> <tbody> <tr> <td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code></ccid_code>package demo; /** * Copyright 2004, James M. Turner. * All Rights Reserved * * A Struts action that sets up a DynaForm which is globally scoped */ import java.io.IOException; import java.util.HashMap; import javax.servlet.ServletException; import javax.servlet.http.*; import org.apache.struts.action.*; import org.apache.struts.validator.DynaValidatorForm; public class SetupHashFormAction extends Action { public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { DynaValidatorForm df = (DynaValidatorForm) form; HashMap hm = (HashMap) df.get("people"); Person p = new Person(); p = new Person(); p.setDateOfBirth("07/13/1942"); p.setLastName("Ford"); p.setFirstName("Harrison"); p.setGender("M"); hm.put("1", p); p = new Person(); p.setDateOfBirth("10/21/1956"); p.setLastName("Fisher"); p.setFirstName("Carrie"); p.setGender("F"); hm.put("2", p); p = new Person(); p.setDateOfBirth("09/25/1951"); p.setLastName("Hamill"); p.setFirstName("Mark"); p.setGender("M"); hm.put("3", p); return mapping.findForward("success"); } }</pre></td></tr></tbody></table><br /><br />基本上，这段代码与前面的代码相同，除了我们将Person 对象存储到HashMap 中，而不是排列中之外。我们也不需要创建HashMap，因为它可以作为表格初始化的一部分来动态实现。 <br />在JSP本身中相应的技巧部分为： <br /><br /><ccid_nobr></ccid_nobr> <table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="#000000" border="1"> <tbody> <tr> <td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code></ccid_code>&lt;!-- Copyright 2004, James M Turner. All Rights Reserved --&gt; &lt;%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %&gt; &lt;%@ taglib uri="/WEB-INF/struts-html-el.tld" prefix="html-el" %&gt; &lt;%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %&gt; &lt;%@ taglib uri="/WEB-INF/c.tld" prefix="c" %&gt; &lt;%@ taglib prefix="fmt" uri="/WEB-INF/fmt.tld" %&gt; &lt;head&gt; &lt;title&gt;Star Wars Actor Fact Page&lt;/title&gt; &lt;/head&gt; &lt;H1&gt;&lt;center&gt;Start Wars Actor Fact Page&lt;/title&gt; &lt;html:form action="/processHashActorComments" &gt; &lt;table border="1" width="80%"&gt; &lt;tr&gt;&lt;th&gt;Last Name&lt;/th&gt;&lt;th&gt;First Name&lt;/th&gt; &lt;th&gt;Date of Birth&lt;/th&gt;&lt;th&gt;Comment&lt;/th&gt;&lt;/tr&gt; &lt;c:forEach var="people" items="${dynamicHashmapForm.map.people}"&gt; &lt;tr&gt;&lt;td&gt;&lt;c:out value="${people.value.lastName}"/&gt;&lt;/td&gt; &lt;td&gt;&lt;c:out value="${people.value.firstName}"/&gt;&lt;/td&gt; &lt;td&gt;&lt;c:out value="${people.value.dateOfBirth}"/&gt;&lt;/td&gt; &lt;td&gt;&lt;html-el:text property="comments(${people.value.lastName}, ${people.value.firstName})" /&gt;&lt;/td&gt; &lt;/tr&gt; &lt;/c:forEach&gt; &lt;/table&gt; &lt;P/&gt; &lt;html:submit value="Update Comments"/&gt; &lt;/html:form&gt;</pre></td></tr></tbody></table><br /><br />记住：在初始化时填充的HashMap 值，只要表格显示就会消失，因为表格是请求范围的，而不是会话期范围的。特别是对于我们来说这就意味着所有的Person 对象都会消失。所以，如果我们粘贴文本域到Person bean 的注释属性上，在提交表格时我们将得到一个空的指针异常，因为Person 对象不再位于HashMap 内(实际上，我们得到的是一个全新的空的HashMap.)。所以，我们需要将注释存储在一个单独的并行HashMap 内，它将注释当作简单的字符串来存储。 <br />在上述的代码中还须注意几件事情。首先，因为现在正迭代HashMap条，来自c:forEach 标记的值实际上是用于堆栈条的占位符，同时具有两个属性。key 属性的值用来访问堆栈（在我们的例子中如字符"1", "2", "3"等等），value 属性的值存储在关键字之下。所以，在这种情况下，我们必须使用value 属性来得到Person bean 的实属性。 <br />而且，我们需要构造一个用于文本框的有效的Struts属性域。在html-el 标记库中使用JSTL 扩展就可以实现。在这种情况下，我们通过一个由演员的最后一个名字、逗号和第一个名字组成的字符串来存储注释。 <br />最后，我们需要一个新的动作来处理结果： <br /><br /><ccid_nobr></ccid_nobr> <table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="#000000" border="1"> <tbody> <tr> <td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code></ccid_code>package demo; /** * Copyright 2004, James M. Turner. * All Rights Reserved * * A Struts action that sends the new comments to the console */ import java.io.IOException; import java.util.HashMap; import java.util.Iterator; import javax.servlet.ServletException; import javax.servlet.http.*; import org.apache.struts.action.*; import org.apache.struts.validator.DynaValidatorForm; public class ProcessHashFormAction extends Action { public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { DynaValidatorForm df = (DynaValidatorForm) form; HashMap hm = (HashMap) df.get("comments"); Iterator it = hm.keySet().iterator(); while (it.hasNext()) { String key = (String) it.next(); String comment = (String) hm.get(key); System.out.println(key + ":" + comment); } return mapping.findForward("success"); } }</pre></td></tr></tbody></table> <p><br /><br />同样，这里最大的差别是数据都是作为HashMaps 来存储的。代码获取关键字(lastname,firstname)，然后显示关键字和在控制台注释: <br /><br />Fisher,Carrie:Leia <br />Ford,Harrison:Han <br />Hamill,Mark:Luke <br /><br />请注意，当控制返回到JSP页时，打印一个空白表格。这是因为我们在初始化操作中创建的HashMap 已经没有了，在处理结果时我们不能重新创建它。你可以将该数据保存在会话期变量中，但是接着你要返回到你使用第一个方案的地方。最好是选择一个关键字，在表格提交时它可以允许你在后台对象上获得，并且能够总是重新创建需要的任何其他的表格数据。 <br />哪种方式更好？基于排列的方案允许你将所有的数据都保存在一个bean 内，而基于堆栈的方法避免了任何会话期范围的数据。你觉得哪种方案最好就采用哪种。 <br />注意：包含运行这些例子所需的所有代码和库的WAR 文件在http://www.blackbear.com/struts.war.上可以找到。 <br /><br /><br /><b>关于作者</b>:James Turner 是Benefit Systems有限公司软件开发总监。他对Apache Struts 项目颇有贡献。他已经出版了两本面向WEB的JAVA技术的书：MySQL and JSP Web Applications, 和Struts Kick Start。他的第三本书，Java Server Faces Kick Start，在2003年冬季由Sams出版发行 </p><img src ="http://www.blogjava.net/sgsoft/aggbug/198.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/sgsoft/" target="_blank">海天一鸥</a> 2005-01-11 15:59 <a href="http://www.blogjava.net/sgsoft/articles/198.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>