﻿<?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-kapok-随笔分类-HibernateAndSpring</title><link>http://www2.blogjava.net/kapok/category/649.html</link><description>垃圾桶,嘿嘿，我藏的这么深你们还能找到啊，真牛！</description><language>zh-cn</language><lastBuildDate>Wed, 28 Feb 2007 03:23:11 GMT</lastBuildDate><pubDate>Wed, 28 Feb 2007 03:23:11 GMT</pubDate><ttl>60</ttl><item><title>为大型项目提供的 Ant 1.6 新特性</title><link>http://www.blogjava.net/kapok/archive/2005/06/14/6106.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Tue, 14 Jun 2005 04:22:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/06/14/6106.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/6106.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/06/14/6106.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/6106.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/6106.html</trackback:ping><description><![CDATA[<P><SPAN class=topstoryhead><A href="http://www.oracle.com/technology/global/cn/pub/articles/bodewig_ant1.6.html">http://www.oracle.com/technology/global/cn/pub/articles/bodewig_ant1.6.html</A><BR><BR>为大型项目提供的 Ant 1.6 新特性</SPAN><BR><SPAN class=italicbodycopy>作者：Stefan Bodewig</SPAN> </P>
<P><SPAN class=boldbodycopy>了解 Ant 1.6 的新特性以及它们如何影响您组织编译过程的方式。</SPAN> </P>
<P><SPAN class=bodycopy>虽然 Ant 版本的 1.5.x 系列在任务级方面有很大的改善，但它没有改变人们使用 Ant 的方式。而 Ant 1.6 却有所不同。它增加了几个新特性，以支持大型或非常复杂的编译情况。但是，要充分利用它们的功能，用户可能需要稍微调整它们的编译过程。</SPAN> </P>
<P><SPAN class=bodycopy>本文重点介绍了其中的三种新特性 — &lt;macrodef&gt;、&lt;import&gt;、&lt;subant&gt; 任务，表明使用它们可以有什么收获，以及它们如何影响您组织编译设置的方式。</SPAN> </P>
<P><SPAN class=parahead1>宏</SPAN> </P>
<P><SPAN class=bodycopy>大多数编译工程师迟早会面临必须执行相同的任务组合但在几个地方配置稍微有点不同的情况。一个常见的例子是创建一个web 应用程序存档，对于开发系统、测试系统和生产系统有着不同的配置。</SPAN> </P>
<P><SPAN class=bodycopy>让我们假设 web 应用程序拥有依赖于目标系统的不同的 web 部署描述符，并为开发环境使用了一个不同的 JSP 集合以及一个不同的资料库集合。配置信息将放在属性中，创建 web 存档的任务看起来将类似于</SPAN> </P>
<P><PRE>  &lt;target name="war" depends="jar"&gt;
    &lt;war destfile="${war.name}"
         webxml="${web.xml}"&gt;
      &lt;lib refid="support-libraries"/&gt;
      &lt;lib file="${jar.name}"/&gt;
      &lt;fileset dir="${jsps}"/&gt;
    &lt;/war&gt;
&lt;/target&gt;
</PRE>
<P></P>
<P><SPAN class=bodycopy>其中 support-libraries 是引用一个在其它位置定义的 </SPAN><TT>&lt;fileset&gt;</TT><SPAN class=bodycopy> ，该引用指向您的应用程序所需的附加资料库的一个公共集合。</SPAN> </P>
<P><SPAN class=bodycopy>如果您只想一次创建一个 web 存档，那么您只需要正确地设置属性。比如说，您可以从一个您的目标专有的属性文件中加载它们。</SPAN> </P>
<P><SPAN class=boldbodycopy>利用 Ant 1.5 创建存档</SPAN> </P>
<P><SPAN class=bodycopy>现在，假定您想为测试系统和生产系统同时创建存档，以确保您真正为两个系统打包了相同的应用程序。利用 Ant 1.5，您可能使用 &lt;antcall&gt; 来调用拥有不同属性设置的 "war" 目标，类似：</SPAN> </P>
<P><PRE>  &lt;target name="production-wars"&gt;
    &lt;antcall target="war"&gt;
      &lt;param name="war.name" value="${staging.war.name}"/&gt;
      &lt;param name="web.xml" value="${staging.web.xml}"/&gt;
    &lt;/antcall&gt;
    &lt;antcall target="war"&gt;
      &lt;param name="war.name" value="${production.war.name}"/&gt;
      &lt;param name="web.xml" value="${production.web.xml}"/&gt;
    &lt;/antcall&gt;
&lt;/target&gt;
</PRE>
<P></P>
<P><SPAN class=bodycopy>当然，这假定两个目标系统都将使用相同的 jar 和 JSP。</SPAN> </P>
<P><SPAN class=bodycopy>但这种方法有一个主要缺点 — 就是速度慢。</SPAN><TT>&lt;antcall&gt;</TT><SPAN class=bodycopy> 重新分析编译文件，并为每一次调用重新运行调用的目标所依赖的所有目标。在上面的例子中，"jar" 目标将被运行两次。我们希望这对第二次调用没有影响，因为 "war" 目标依赖于它。</SPAN> </P>
<P><SPAN class=boldbodycopy>利用 Ant 1.6 创建存档</SPAN> </P>
<P><SPAN class=bodycopy>使用 Ant 1.6，您可以忘掉用 </SPAN><TT>&lt;antcall&gt;</TT><SPAN class=bodycopy> 来实现宏的方法，相反您可以通过参数化现有的任务来创建一个新的任务。因而上面的例子将变为：</SPAN> </P>
<P><PRE>  &lt;macrodef name="makewar"&gt;
    &lt;attribute name="webxml"/&gt;
    &lt;attribute name="destfile"/&gt;
    &lt;sequential&gt;
      &lt;war destfile="@{destfile}"
           webxml="@{webxml}"&gt;
        &lt;lib refid="support-libraries"/&gt;
        &lt;lib file="${jar.name}"/&gt;
        &lt;fileset dir="${jsps}"/&gt;
      &lt;/war&gt;
    &lt;/sequential&gt;
  &lt;/macrodef&gt;
</PRE>
<P></P>
<P><SPAN class=bodycopy>这定义了一个名称为 makewar 的任务，该任务可以和任何其它的任务一样使用。该任务有两个必需的属性，webxml 和 destfile。要使属性可选，我们必需在任务定义中提供一个默认值。这个示例假定 </SPAN><TT>${jar.name}</TT><SPAN class=bodycopy> 和 </SPAN><TT>${jsps}</TT><SPAN class=bodycopy> 在编译期间为常量，从而它们仍然作为属性指定。注意，属性在使用任务时展开而不是在定义宏的地方展开。</SPAN> </P>
<P><SPAN class=bodycopy>所用任务的特性几乎完全和属性一样，它们通过 </SPAN><TT>@{}</TT><SPAN class=bodycopy> 而不是 </SPAN><TT>${}</TT> 展开<SPAN class=bodycopy>。与属性不同，它们是可变的，也就是说，它们的值可以（并将）随着每一次调用而改变。它们也只在您的宏定义程序块内部可用。这意味着如果您的宏定义还包含了另一个定义了宏的任务，那么您内部的宏将看不到包含的宏的属性。</SPAN> </P>
<P><SPAN class=bodycopy>于是新的 production-wars 目标将类似于：</SPAN> </P>
<P><PRE>  &lt;target name="production-wars"&gt;
    &lt;makewar destfile="${staging.war.name}"
             webxml="${staging.web.xml}"/&gt;
    &lt;makewar destfile="${production.war.name}"
             webxml="${production.web.xml}"/&gt;
&lt;/target&gt;
</PRE>
<P></P>
<P><SPAN class=bodycopy>这个新的代码段不仅执行得快一些，而且也更易读，因为属性名称提供了更多的信息。</SPAN> </P>
<P><SPAN class=bodycopy>宏任务还可以定义嵌套的元素。<TT>&lt;makewar&gt;</TT><SPAN class=bodycopy> 定义中的 </SPAN><TT>&lt;war&gt; </TT>任务的嵌套 </SPAN><TT>&lt;fileset&gt;</TT><SPAN class=bodycopy> 可以是这种嵌套元素的一种。可能开发目标需要一些额外的文件或想从不同的位置中挑选 JSP 或资源。以下代码段将一个可选的嵌套 &lt;morefiles&gt; 元素添加到了 &lt;makewar&gt; 任务中</SPAN> </P>
<P><PRE>  &lt;macrodef name="makewar"&gt;
    &lt;attribute name="webxml"/&gt;
    &lt;attribute name="destfile"/&gt;
    &lt;element name="morefiles" optional="true"/&gt;
    &lt;sequential&gt;
      &lt;war destfile="@{destfile}"
           webxml="@{webxml}"&gt;
        &lt;lib refid="support-libraries"/&gt;
        &lt;lib file="${jar.name}"/&gt;
        &lt;fileset dir="${jsps}"/&gt;
        &lt;morefiles/&gt;
      &lt;/war&gt;
    &lt;/sequential&gt;
  &lt;/macrodef&gt;
</PRE>
<P></P>
<P><SPAN class=bodycopy>调用将类似于：</SPAN> </P>
<P><PRE>  &lt;makewar destfile="${development.war.name}"
           webxml="${development.web.xml}"&gt;
    &lt;morefiles&gt;
      &lt;fileset dir="${development.resources}"/&gt;
      &lt;lib refid="development-support-libraries"/&gt;
    &lt;/morefiles&gt;
  &lt;/makewar&gt;
</PRE>
<P></P>
<P><SPAN class=bodycopy>这就像 &lt;morefiles&gt; 的嵌套元素直接在 &lt;war&gt; 任务内部使用的效果一样。</SPAN> </P>
<P><SPAN class=bodycopy>即使迄今为止的示例仅显示了包装单个任务的 &lt;macrodef&gt;，但它不限于此。</SPAN> </P>
<P><SPAN class=bodycopy>下面的宏不仅将创建 web 存档，还将确保包含最终存档的目录在试图写入之前存在。在一个实际的编译文件中，您可能在调用任务之前使用一个设置目标来完成这个操作。</SPAN> </P>
<P><PRE>  &lt;macrodef name="makewar"&gt;
    &lt;attribute name="webxml"/&gt;
    &lt;attribute name="destfile"/&gt;
    &lt;element name="morefiles" optional="true"/&gt;
    &lt;sequential&gt;
      &lt;dirname property="@{destfile}.parent"
               file="@{destfile}"/&gt;
      &lt;mkdir dir="${@{destfile}.parent}"/&gt;
      &lt;war destfile="@{destfile}"
           webxml="@{webxml}"&gt;
        &lt;lib refid="support-libraries"/&gt;
        &lt;lib file="${jar.name}"/&gt;
        &lt;fileset dir="${jsps}"/&gt;
        &lt;morefiles/&gt;
      &lt;/war&gt;
    &lt;/sequential&gt;
  &lt;/macrodef&gt;
</PRE>
<P></P>
<P><SPAN class=bodycopy>这里注意两件事情：</SPAN> </P>
<P><SPAN class=bodycopy>首先，特性在属性展开之前展开，因此结构 ${@{destfile}.parent} 将展开一个名称包含了 destfile 特性的值和 ".parent" 后缀的属性。这意味着您可以将特性展开嵌入到属性展开中，而不是将属性展开嵌入特性展开中。</SPAN> </P>
<P><SPAN class=bodycopy>其次，这个宏定义了属性，该属性的名称基于一个特性的值，因为 Ant 中的属性是全局的并且不可改变。第一次尝试使用</SPAN> </P>
<P><PRE>      &lt;dirname property="parent"
               file="@{destfile}"/&gt;
</PRE>
<P></P>
<P><SPAN class=bodycopy>相反将不会在 "production-wars" 目标中的第二次 &lt;makewar&gt; 调用产生期望的结果。第一次调用将定义一个新的名称为 parent 的属性，该属性指向父目录 ${staging.war.name}。第二次调用将查看这个属性但不会修改它的值。</SPAN> </P>
<P><SPAN class=bodycopy>预期 Ant 未来的版本将支持某些类型的限定范围的属性，这种属性只在宏执行期间定义。在此之前，使用特性的名称来构建属性名称是一种变通办法，潜在的副作用是要创建大量的属性。</SPAN> </P>
<P>
<TABLE cellPadding=5 width="30%" align=right bgColor=#cccccc border=1 vspace="5" hspace="5">
<TBODY>
<TR>
<TD><SPAN class=boldbodycopy>提示：</SPAN><SPAN class=bodycopy>如果您查看您的编译文件时发现使用了 &lt;antcall&gt; 代替宏，那么强烈建议您考虑使用 macrodef 将其转换成真正的宏。性能影响可能非常显著，并且还可能产生更易读和更易于维护的编译文件。</SPAN> </TD></TR></TBODY></TABLE><SPAN class=parahead1>导入</SPAN> </P>
<P><SPAN class=bodycopy>将一个编译文件分成多个文件有几个原因。 </SPAN>
<OL>
<LI><SPAN class=bodycopy>文件可能变得太大，需要分成几个单独的部分，以便更易于维护。 </SPAN>
<LI><SPAN class=bodycopy>您有某个功能集是多个编译文件公用的，您想共享它。</SPAN> </LI></OL>
<P></P>
<P><SPAN class=boldbodycopy>共享公用功能/在 Ant 1.6 之前包含文件</SPAN> </P>
<P><SPAN class=bodycopy>在 Ant 1.6 之前，您唯一的选择是实体包含的 XML 方法，类似于：</SPAN> </P>
<P><PRE>  &lt;!DOCTYPE project [
      &lt;!ENTITY common SYSTEM "file:./common.xml"&gt;
  ]&gt;
  
  &lt;project name="test" default="test" basedir="."&gt;
  
    &lt;target name="setup"&gt;
      ...
&lt;/target&gt;
  
    &amp;common;
  
    ...
  
&lt;/project&gt;
</PRE>
<P></P>
<P><SPAN class=bodycopy>摘自 Ant 常见问题解答。</SPAN> </P>
<P><SPAN class=bodycopy>这种方法有两个主要的缺点。您不能使用 Ant 属性指向您想包含的文件，因此被迫在您的编译文件中对位置进行硬编码。您想包含的文件只是一个 XML 文件的一部分，它可能没有一个根元素，因而使用支持 XML 的工具进行维护更加困难。</SPAN> </P>
<P><SPAN class=boldbodycopy>共享公用功能/使用 Ant 1.6 包含文件</SPAN> </P>
<P><SPAN class=bodycopy>Ant 1.6 自带了一个名称为 import 的新任务，您现在可以使用它。上面的示例将变为</SPAN> </P>
<P><PRE>  &lt;project name="test" default="test" basedir="."&gt;
  
    &lt;target name="setup"&gt;
      ...
&lt;/target&gt;
  
    &lt;import file="common.xml"/&gt;
  
    ...
  
&lt;/project&gt;
</PRE>
<P></P>
<P><SPAN class=bodycopy>因为它是一个任务，因此您可以使用 Ant 所有的特性来指定文件位置。主要的差异是被导入的文件本身必须是一个有效的 Ant 编译文件，因而必须有一个名称为 project 的根元素。如果您想从实体包含转换到导入，那么您必须在导入的文件的内容首尾放上 &lt;project&gt; 标记；然后 Ant 将在读取文件时再次划分它们。</SPAN> </P>
<P><SPAN class=bodycopy>注意文件名称由 Ant 任务根据编译文件的位置（而不是指定的基本目录）确定。如果您没有设置项目的 basedir 属性或将其设为 "."，那么您将不会注意到任何差异。如果您需要根据基本目录解析一个文件，那么您可以使用一个属性作为变通办法，类似于：</SPAN> </P>
<P><PRE>  &lt;property name="common.location" location="common.xml"/&gt;
  &lt;import file="${common.location}"/&gt;
</PRE>
<P></P>
<P><SPAN class=bodycopy>属性 common.location 将包含文件 common.xml 的绝对路径，并已根据导入项目的基本目录解析。</SPAN> </P>
<P><SPAN class=bodycopy>使用 Ant 1.6，所有的任务都可能放在目标之外或之内，除了两个例外。</SPAN><TT>&lt;import&gt;</TT><SPAN class=bodycopy> 一定不能嵌入到目标中，</SPAN><TT>&lt;antcall&gt;</TT><SPAN class=bodycopy> 一定不能在目标外使用（否则它将创建一个无限循环）。</SPAN> </P>
<P><SPAN class=bodycopy>而 &lt;import&gt; 可做的不仅仅是导入另一个文件。</SPAN> </P>
<P><SPAN class=bodycopy>首先，它定义了名称为 ant.file.NAME 的特殊属性，其中 NAME 替换为每一个导入文件的 &lt;project&gt; 标记的名称属性。这个属性包含了导入文件的绝对路径，导入文件可用来根据它自己的位置（而不是导入文件的基本目录）定位文件和资源。</SPAN> </P>
<P><SPAN class=bodycopy>这意味着 </SPAN><TT>&lt;project&gt;</TT><SPAN class=bodycopy> 的名称属性在 &lt;import&gt; 任务环境中变得更加重要。它还用来为在被导入的编译文件中定义的目标提供别名。如果导入了以下文件</SPAN> </P>
<P><PRE>&lt;project name="share"&gt;
    &lt;target name="setup"&gt;
      &lt;mkdir dir="${dest}"/&gt;
&lt;/target&gt;
&lt;/project&gt;
</PRE>
<P></P>
<P><SPAN class=bodycopy>导入编译文件可以查看作为 "setup" 或 "share.setup" 的目标。后者在目标覆盖的上下文中变得非常重要。</SPAN> </P>
<P><SPAN class=bodycopy>让我们假定有一个包含了多个独立的组件（每个组件拥有它自己的编译文件）的编译系统。这些编译文件几乎相同，因此我们决定将公用功能转移到一个共享和已导入的文件中。为了简单起见，我们只介绍 Java 文件的编译和创建结果的一个 JAR 存档。共享的文件将类似于</SPAN> </P>
<P><PRE>  &lt;project name="share"&gt;
    &lt;target name="setup" depends="set-properties"&gt;
      &lt;mkdir dir="${dest}/classes"/&gt;
      &lt;mkdir dir="${dest}/lib"/&gt;
&lt;/target&gt;
    &lt;target name="compile" depends="setup"&gt;
      &lt;javac srcdir="${src}" destdir="${dest}/classes"&gt;
        &lt;classpath refid="compile-classpath"/&gt;
&lt;/javac&gt;
&lt;/target&gt;
    &lt;target name="jar" depends="compile"&gt;
      &lt;jar destfile="${dest}/lib/${jar.name}" basedir="${dest}/classes"/&gt;
&lt;/target&gt;
&lt;/project&gt;
</PRE>
<P></P>
<P><SPAN class=bodycopy>这个文件不会作为一个独立的 Ant 编译文件进行工作，因为它没有定义 "setup" 所依赖的 "set-properties" 目标。</SPAN> </P>
<P><SPAN class=bodycopy>组件 A 的编译文件可能类似于</SPAN> </P>
<P><PRE>  &lt;project name="A" default="jar"&gt;
    &lt;target name="set-properties"&gt;
      &lt;property name="dest" location="../dest/A"/&gt;
      &lt;property name="src" location="src"/&gt;
      &lt;property name="jar.name" value="module-A.jar"/&gt;
      &lt;path id="compile-classpath"/&gt;
&lt;/target&gt;
    &lt;import file="../share.xml"/&gt;
&lt;/project&gt;
</PRE>
<P></P>
<P><SPAN class=bodycopy>它仅设置适当的环境，然后将全部的编译逻辑交给被导入的文件负责。注意该编译文件创建了一个空的路径作为编译 CLASSPATH，因为它是自包含的。模块 B 依赖于 A，它的编译文件将类似于</SPAN> </P>
<P><PRE>  &lt;project name="B" default="jar"&gt;
    &lt;target name="set-properties"&gt;
      &lt;property name="dest" location="../dest/B"/&gt;
      &lt;property name="src" location="src"/&gt;
      &lt;property name="jar.name" value="module-B.jar"/&gt;
      &lt;path id="compile-classpath"&gt;
        &lt;pathelement location="../dest/A/module-A.jar"/&gt;
      &lt;/path&gt;
&lt;/target&gt;
    &lt;import file="../share.xml"/&gt;
&lt;/project&gt;
</PRE>
<P></P>
<P><SPAN class=bodycopy>您将注意到该编译文件与 A 的编译文件几乎一样，因此似乎有可能将大多数的 set-properties 目标也推送到 shared.xml 中。实际上，我们可以假定有一个对 dest 和 src 目标一致的命名惯例，以实现这一目的。</SPAN> </P>
<P><PRE>  &lt;project name="share"&gt;
    &lt;target name="set-properties"&gt;
      &lt;property name="dest" location="../dest/${ant.project.name}"/&gt;
      &lt;property name="src" location="src"/&gt;
      &lt;property name="jar.name" value="module-${ant.project.name}.jar"/&gt;
&lt;/target&gt;

    ... contents of first example above ...
&lt;/project&gt;
</PRE>
<P></P>
<P><SPAN class=bodycopy>ant.project.name 是一个内置的属性，它包含了最外面的 &lt;project&gt; 标记的名称属性的值。因此，如果模块 A 的编译文件导入了 share.xml，那么它将拥有值 A。</SPAN> </P>
<P><SPAN class=bodycopy>注意，所有的文件都与导入编译文件的基本目录相关，因此 scr 属性的实际值依赖于导入文件。</SPAN> </P>
<P><SPAN class=bodycopy>为此，A 的编译文件将简单地变为</SPAN> </P>
<P><PRE>&lt;project name="A" default="jar"&gt;
    &lt;path id="compile-classpath"/&gt;
    &lt;import file="../share.xml"/&gt;
&lt;/project&gt;
</PRE>
<P></P>
<P><SPAN class=bodycopy>B 的编译文件将变为</SPAN> </P>
<P><PRE>  &lt;project name="B" default="jar"&gt;
    &lt;path id="compile-classpath"&gt;
      &lt;pathelement location="../dest/A/module-A.jar"/&gt;
    &lt;/path&gt;
    &lt;import file="../share.xml"/&gt;
&lt;/project&gt;
</PRE>
<P></P>
<P><SPAN class=bodycopy>现在假定 B 增加了一些 RMI 接口，需要在编译类之后但在创建 jar 之前运行 &lt;rmic&gt;。这就是目标覆盖能派上用场的地方。如果我们在导入编译文件中定义了一个目标，该目标与被导入的编译文件中的一个目标名称相同，那么将使用导入编译文件中的目标。例如，B 可以使用：</SPAN> </P>
<P><PRE>  &lt;project name="B" default="jar"&gt;
    &lt;path id="compile-classpath"&gt;
      &lt;pathelement location="../dest/A/module-A.jar"/&gt;
    &lt;/path&gt;
    &lt;import file="../share.xml"/&gt;

    &lt;target name="compile" depends="setup"&gt;
      &lt;javac srcdir="${src}" destdir="${dest}/classes"&gt;
        &lt;classpath refid="compile-classpath"/&gt;
&lt;/javac&gt;
      &lt;rmic base="${dest}/classes" includes="**/Remote*.class"/&gt;
&lt;/target&gt;
&lt;/project&gt;
</PRE>
<P></P>
<P><SPAN class=bodycopy>在上面的示例中将使用 "compile" 目标，而不是 share.xml 中的目标；然而，不幸的是，这只是从共享那里复制 &lt;javac&gt; 任务。一种更好的解决方案是：</SPAN> </P>
<P><PRE>  &lt;project name="B" default="jar"&gt;
    &lt;path id="compile-classpath"&gt;
      &lt;pathelement location="../dest/A/module-A.jar"/&gt;
    &lt;/path&gt;
    &lt;import file="../share.xml"/&gt;

    &lt;target name="compile" depends="share.compile"&gt;
      &lt;rmic base="${dest}/classes" includes="**/Remote*.class"/&gt;
&lt;/target&gt;
&lt;/project&gt;
</PRE>
<P></P>
<P><SPAN class=bodycopy>这只是使 B 的 "compile" 在原来的 "compile" 目标使用之后运行 &lt;rmic&gt;。</SPAN> </P>
<P><SPAN class=bodycopy>如果我们想在编译之前生成一些 Java 源代码（例如通过 XDoclet)，我们可以使用类似下面的方法：</SPAN> </P>
<P><PRE>    &lt;import file="../share.xml"/&gt;

    &lt;target name="compile" depends="setup,xdoclet,share.compile"/&gt;
    &lt;target name="xdoclet"&gt;
       .. details of XDoclet invocation omitted ..
&lt;/target&gt;
</PRE>
<P></P>
<P><SPAN class=bodycopy>因此您可以完全覆盖一个目标或通过在原始目标之前或之后运行任务来增强它。</SPAN> </P>
<P><SPAN class=bodycopy>这里要注意一个危险。目标覆盖机制使导入编译文件依赖于在导入文件中使用的名称属性。如果任何人修改了导入文件的名称属性，那么导入编译文件将被破坏。Ant 开发社区目前正在讨论在 Ant 的一个未来的版本中为此提供一个解决方案。</SPAN> </P>
<P>
<TABLE cellPadding=5 width="30%" align=right bgColor=#cccccc border=1 vspace="5" hspace="5">
<TBODY>
<TR>
<TD><SPAN class=boldbodycopy>提示：</SPAN><SPAN class=bodycopy>如果您在编译文件中发现了非常常见的结构，那么值得尝试将文件重构为一个（一些）共享文件，并在必要时使用目标覆盖。这可以使您的编译系统更加一致，并让您能够重用编译逻辑。</SPAN> </TD></TR></TBODY></TABLE><SPAN class=parahead1>Subant</SPAN> </P>
<P><SPAN class=bodycopy>在某种意义上，subant 是两种任务合二为一，因为它了解操作的两种模式。</SPAN> </P>
<P><SPAN class=bodycopy>如果您使用 &lt;subant&gt; 的 genericantfile 属性，那么它的工作方式和 &lt;antcall&gt; 一样，调用包含任务的同一个编译文件中的目标。与 &lt;antcall&gt; 不同，&lt;subant&gt; 获取目录的列表或集合，并将为每一个目录调用一次目标，以设定项目的基本目录。如果您想在任意数量的目录中执行完全一样的操作，那么这非常有用。</SPAN> </P>
<P><SPAN class=bodycopy>第二种模式不使用 genericantfile 属性，而获取一个编译文件的列表和集合进行迭代，以在每一个编译文件中调用目标。这种工作方式类似于在一个循环中使用 &lt;ant&gt; 任务。</SPAN> </P>
<P><SPAN class=bodycopy>第二种形式的典型情景是几个能够独立编译的模块的一个编译系统，但是该系统需要一个主编译文件来一次性编译所有的模块。</SPAN> </P>
<TABLE cellPadding=5 width="30%" align=right bgColor=#cccccc border=1 vspace="5" hspace="5">
<TBODY>
<TR>
<TD>
<CENTER><SPAN class=parahead1>接下来的步骤</SPAN> </CENTER>
<P></P>
<P><SPAN class=bodycopy>使用以下资源了解关于 Ant 的更多信息，并开始编译和部署 Java 项目。</SPAN> 
<P><SPAN class=boldbodycopy>Ant 业界趋势</SPAN><BR><SPAN class=bodycopy>对这个跨平台编译工具的形成进行<A href="http://oracle.com/global/cn/oramag/oracle/02-jul/o42industry.html"><SPAN class=bodylink>幕后观察</SPAN></A>。</SPAN></P>
<P><SPAN class=boldbodycopy>阅读关于 JDeveloper 中的 Ant 集成的更多信息</SPAN> <BR><SPAN class=bodycopy>Oracle JDeveloper 中的 Ant <A href="http://helponline.oracle.com/jdeveloper/help/state?oldNavSetId=jdeveloper&amp;oldNavId=1&amp;navId=1&amp;vtTopicFile=&amp;navSetId=jdeveloper&amp;kwValue=212&amp;tpValue=1&amp;accesstype=topics&amp;action=&amp;locale=&amp;keywordInput=Ant&amp;event=update&amp;source=&amp;value=1&amp;size=&amp;topicid=213" target=_blank><SPAN class=bodylink>集成</SPAN></A>是通过在 JDeveloper 项目中添加一个 Ant 编译文件或通过从一个现有的 JDeveloper 项目中创建一个新的 Ant 编译文件来实现的。</SPAN></P>
<P><SPAN class=boldbodycopy>下载 Oracle JDeveloper 10g</SPAN><BR><SPAN class=bodycopy><A href="http://www.oracle.com/technology/global/cn/software/products/jdev/index.html" target=_blank><SPAN class=bodylink>Oracle JDeveloper 10<I>g</I></SPAN></A> 是一个集成开发环境，它提供了对建模、开发、调试、优化和部署 Java 应用程序及 Web 服务的端到端支持。</SPAN> </P>
<P><SPAN class=boldbodycopy>测试驱动：将 Ant 用于编译</SPAN><BR><SPAN class=bodycopy>这个 <A href="http://www.oracle.com/technology/products/jdev/collateral/papers/10g/reviewer/viewlets/reviewer_ant_viewlet_swf.html" target=_blank><SPAN class=bodylink>viewlet</SPAN></A> 演示了已拥有 Ant 项目的用户如何能够在 JDeveloper 内部使用这些项目。</SPAN></P>
<P><SPAN class=boldbodycopy>Ant 入门第 1 部分</SPAN><BR><SPAN class=bodycopy><A href="http://oracle.com/global/cn/oramag/oracle/02-nov/o62odev_ant.html" target=_blank><SPAN class=bodylink>这里</SPAN></A>开始将这个非常有用的工具用于构建和部署 Java 项目。本文介绍了您可能在 Java 开发过程期间执行的一些基本的 Ant 任务。</SPAN> </P>
<P><SPAN class=boldbodycopy>Ant 入门第 2 部分</SPAN><BR><SPAN class=bodycopy><A href="http://oracle.com/global/cn/oramag/oracle/03-jan/o13ant.html" target=_blank><SPAN class=bodylink>这里</SPAN></A>是我们的系列中的第 2 部分，这个系列介绍用于构建和部署 Java 项目的一个非常有用的工具。本文讨论在 Ant 的两个任务程序包（核心的任务程序包和可选的任务程序包）中提供的一些 Ant 的更高级的特性。</SPAN></P>
<P><SPAN class=boldbodycopy>在 Linux 上创建 Java 应用程序的命令行方法</SPAN><BR><SPAN class=bodycopy><A href="" target=_blank><SPAN class=bodylink>本文</SPAN></A>可用作使用 Ant 在 Linux 上开发和部署 Java 客户端应用程序的一个不错的上机操作指南。</SPAN> </P>
<P><SPAN class=boldbodycopy>阅读关于 Ant 的更多信息</SPAN><BR><SPAN class=bodycopy>访问<A href="http://ant.apache.org/" target=_blank><SPAN class=bodylink>官方 Apache Ant 站点</SPAN></A>，获取更多的项目详细信息。</SPAN> </P>
<P><SPAN class=boldbodycopy>相关文章与下载</SPAN> </P>
<P><A href="http://radio.weblogs.com/0132383/stories/2004/03/16/antDeploymentToOc4j.html" target=_blank><SPAN class=bodylink>Blog：到 OC4J 的 Ant 部署</SPAN></A></P>
<P><A href="http://radio.weblogs.com/0132383/stories/2004/03/16/antDeploymentToOc4j.html" target=_blank><SPAN class=bodylink>Blog： 如何从一个 JDeveloper 项目文件中将属性动态检索到一个 ant 编译文件中？</SPAN></A></P>
<P><A href="http://www.oracle.com/technology/products/jdev/collateral/papers/10g/reviewer/viewlets/reviewer_CVS_viewlet_swf.html" target=_blank a><SPAN class=bodylink>Viewlet：将 CVS 用于软件配置</SPAN></A></P></TD></TR></TBODY></TABLE>
<P><SPAN class=boldbodycopy>在 Ant 1.6 之前构建主编译文件</SPAN> </P>
<P><SPAN class=bodycopy>在导入部分中讨论的例子使用了这样一个主编译文件。</SPAN> </P>
<P><PRE>  &lt;target name="build-all"&gt;
    &lt;ant dir="module-A" target="jar"/&gt;
    &lt;ant dir="module-B" target="jar"/&gt;
&lt;/target&gt;
</PRE>
<P></P>
<P><SPAN class=bodycopy>在 Ant 1.6 之前的 Ant 中。</SPAN> </P>
<P><SPAN class=boldbodycopy>使用 Ant 1.6 构建主编译文件</SPAN> </P>
<P><SPAN class=bodycopy>在 Ant 1.6 中使用 &lt;subant&gt;，这可以重写为</SPAN> </P>
<P><PRE> 
  &lt;target name="build-all"&gt;
    &lt;subant target="jar"&gt;
      &lt;filelist dir="."&gt;
        &lt;file name="module-A/build.xml"/&gt;
        &lt;file name="module-B/build.xml"/&gt;
      &lt;/filelist&gt;
    &lt;/subant&gt;
&lt;/target&gt;
</PRE>
<P></P>
<P><SPAN class=bodycopy>这看起来并没有很大的改善，因为您仍然必须单独指定每一个子编译文件。相反如果您转用 &lt;fileset&gt;，那么情况将有所改观。</SPAN> </P>
<P><PRE>  &lt;target name="build-all"&gt;
    &lt;subant target="jar"&gt;
      &lt;fileset dir="." includes="module-*/build.xml"/&gt;
    &lt;/subant&gt;
&lt;/target&gt;
</PRE>
<P></P>
<P><SPAN class=bodycopy>这将自动发现所有模块的编译文件。如果您增加了一个模块 C，主编译文件中的目标不需要修改。</SPAN> </P>
<P><SPAN class=bodycopy>但小心。与 &lt;filelist&gt; 或 &lt;path&gt;（也被 &lt;subant&gt; 支持）不同，&lt;fileset&gt; 是无序的。在我们的例子中，模块 B 依赖于模块 A，因此我们需要确保首先编译模块 A，而使用 &lt;fileset&gt; 没有办法这么做。</SPAN> </P>
<P><SPAN class=bodycopy>如果编译完全彼此独立或者它们对于一个给定的操作彼此独立，那么 &lt;fileset&gt; 仍然有用。模块 B 的文档目标可能完全不依赖于模块 A，同样还有从您的 SCM 系统中更新源代码的目标。</SPAN> </P>
<P><SPAN class=bodycopy>如果您想将编译文件的自动发现与根据编译的相互依赖性对编译进行排序结合在一起，那么您将必须编写一个定制的 Ant 任务。基本的想法是编写一个使用 &lt;fileset&gt; 的任务（让我们目前称之为 &lt;buildlist&gt;），确定依赖关系并计算 &lt;subant&gt; 必须使用的顺序。然后它创建一个以正确的顺序包含编译文件的 &lt;path&gt;，然后将对这个路径的一个引用放到项目中。调用将类似于</SPAN> </P>
<P><PRE>  &lt;target name="build-all"&gt;
    &lt;buildlist reference="my-build-path"&gt;
      &lt;fileset dir="." includes="module-*/build.xml"/&gt;
    &lt;/buildlist&gt;
    &lt;subant target="jar"&gt;
      &lt;buildpath refid="my-build-path"/&gt;
    &lt;/subant&gt;
&lt;/target&gt;</PRE>
<P><SPAN class=bodycopy>这个假想的 buildlist 任务已经在 Ant 用户邮件列表和 bug 跟踪系统中进行了讨论。很有可能 Ant 的一个将来的版本中将包含这样的一个任务。</SPAN> </P>
<P><SPAN class=bodycopy>在 Ant 1.6 中已经增加了大量的新特性。这些新功能中的许多功能使得编译模板易于创建、构造和定制。特别是 &lt;import&gt; 和 &lt;target&gt; 进行了覆盖。&lt;import&gt;、&lt;macrodef&gt; 和 &lt;subant&gt; 特性很有可能使得 Ant 编译可高度重用。&lt;scriptdef&gt;（本文中未讨论）对于需要一些脚本但不想用 Java 编写定制任务的人而言可能非常有吸引力。</SPAN> </P><img src ="http://www.blogjava.net/kapok/aggbug/6106.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-06-14 12:22 <a href="http://www.blogjava.net/kapok/archive/2005/06/14/6106.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>记录</title><link>http://www.blogjava.net/kapok/archive/2005/06/13/6084.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Mon, 13 Jun 2005 15:03:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/06/13/6084.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/6084.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/06/13/6084.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/6084.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/6084.html</trackback:ping><description><![CDATA[不知道这辈子还有没有空做:<BR>1.<BR>page里面的tag换成valuelist, 修改valuelist的分页做法<BR><BR>2.<BR>BaseDAO换成AbstractService,取消DAOImpl对spring和hibernate的直接依赖.<BR>加入Query和Filter功能以及对NamedQuery的支持和分页的支持.<BR><BR>3.<BR>自带的user之类的太烦, 删?<BR><BR>4. <BR>Menu数据库化并实现授权, 实现对产品树的支持, 实现右键菜单的Displayer.<BR><BR>5.<BR>Acegi适配器编写<BR><BR>6.<BR>想办法更容易集成统一认证和LDAP<BR><BR>7.<BR>实现对model的子模块的支持和分开部署<BR><BR>8.<BR>嵌套model的衍生问题<BR><BR>9.<BR>看着不爽的地方,删.<BR><BR><BR>10.<BR>用户管理部分, 集成osaccess和osuser.<BR><BR>11.<BR>再研究一下sitemesh<BR><BR><img src ="http://www.blogjava.net/kapok/aggbug/6084.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-06-13 23:03 <a href="http://www.blogjava.net/kapok/archive/2005/06/13/6084.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Ioc吵架</title><link>http://www.blogjava.net/kapok/archive/2005/06/05/5577.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Sun, 05 Jun 2005 14:13:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/06/05/5577.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/5577.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/06/05/5577.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/5577.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/5577.html</trackback:ping><description><![CDATA[<SPAN class=postbody><A href="http://forum.javaeye.com/viewtopic.php?t=11418&amp;postdays=0&amp;postorder=asc&amp;highlight=%CF%B5%CD%B3%BC%DC%B9%B9&amp;start=75">http://forum.javaeye.com/viewtopic.php?t=11418&amp;postdays=0&amp;postorder=asc&amp;highlight=%CF%B5%CD%B3%BC%DC%B9%B9&amp;start=75</A><BR><BR>其实frankensteinlin说的一句话很对： <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>这不是推卸责任吗？ <BR></TD></TR></TBODY></TABLE><SPAN class=postbody><BR>太对了！ioc，推而广之，OO，就是一个推卸责任的艺术。 <IMG alt=Smile src="http://forum.javaeye.com/images/smiles/icon_smile.gif" border=0> <BR><BR>我曾经说OO是一种政治挂帅的设计方法，就是指这种责任分配。 <BR><BR>不能越俎代庖，能推卸责任就推卸责任，这是每个参与到这个政治游戏中的模块都应该遵守的。 <BR><BR>do one thing and do one thing well. <BR><BR>就是说，你要做最好只作一件事，不是两件，也不是半件。 <BR><BR>当你写A的时候，你先要明确A的责任是什么。如果A唯一负责的就是创建B，好吧，你那么作没错。 <BR><BR>但是，我的假设是，A的职责是做另外一件事（比如出差），而买票只不过是要达到这个目标的一种实现方法所需要的一个前提条件。你要是自己也负责买票，你就是做了两件事。 <BR>当然，什么是“一件”事的定义并不明确。如果你的买票这个动作可以被完全封装进A，外界看不到，也不想看到，并且你也可以预知永远不会有其它的搞到票的方法（比如，抢票，弯腰在地上拣票，不要发票打折，中关村买假车票，买电脑附赠车票等等），你可以选择自己买票。（我好像曾经就什么时候正向控制，什么时候反向给了一个大致的标准吧？） <BR><BR><BR><BR><BR><BR><BR>试着从frankensteinlin的角度理解一下。 <BR><BR>假设X是A的使用者，本来如果A这么设计： <BR><BR></SPAN>
<TABLE cellSpacing=1 cellPadding=3 width="90%" align=center border=0>
<TBODY>
<TR>
<TD><SPAN class=genmed><B>java代码:&nbsp;</B></SPAN></TD></TR>
<TR>
<TD class=code>
<DIV style="FONT-FAMILY: 'Courier New', Courier, monospace"><BR>A: <BR>&nbsp; A<SPAN style="COLOR: #000000">(</SPAN><SPAN style="COLOR: #000000">)</SPAN><SPAN style="COLOR: #000000">{</SPAN> <BR>&nbsp; &nbsp; b = <SPAN style="FONT-WEIGHT: bold; COLOR: #990066" ?>new</SPAN> B<SPAN style="COLOR: #000000">(</SPAN><SPAN style="COLOR: #000000">)</SPAN>; <BR>&nbsp; <SPAN style="COLOR: #000000">}</SPAN></DIV><BR></TD></TR></TBODY></TABLE><SPAN class=postbody><BR><BR>那么，我的X的代码就是： <BR><BR></SPAN>
<TABLE cellSpacing=1 cellPadding=3 width="90%" align=center border=0>
<TBODY>
<TR>
<TD><SPAN class=genmed><B>java代码:&nbsp;</B></SPAN></TD></TR>
<TR>
<TD class=code>
<DIV style="FONT-FAMILY: 'Courier New', Courier, monospace"><BR>X: <BR>&nbsp; A a = <SPAN style="FONT-WEIGHT: bold; COLOR: #990066" ?>new</SPAN> A<SPAN style="COLOR: #000000">(</SPAN><SPAN style="COLOR: #000000">)</SPAN>; <BR>&nbsp; ......</DIV><BR></TD></TR></TBODY></TABLE><SPAN class=postbody><BR><BR><BR>这样，只有A依赖B，X依赖A。 <BR><BR><BR>但是如果你要ioc，A变成这样： <BR><BR></SPAN>
<TABLE cellSpacing=1 cellPadding=3 width="90%" align=center border=0>
<TBODY>
<TR>
<TD><SPAN class=genmed><B>java代码:&nbsp;</B></SPAN></TD></TR>
<TR>
<TD class=code>
<DIV style="FONT-FAMILY: 'Courier New', Courier, monospace"><BR><BR>A: <BR>&nbsp; A<SPAN style="COLOR: #000000">(</SPAN>B b<SPAN style="COLOR: #000000">)</SPAN><SPAN style="COLOR: #000000">{</SPAN> <BR>&nbsp; &nbsp; this.<SPAN style="COLOR: #000000">b</SPAN> = b; <BR>&nbsp; <SPAN style="COLOR: #000000">}</SPAN></DIV><BR></TD></TR></TBODY></TABLE><SPAN class=postbody><BR><BR>此时，我的X岂不是要变成： <BR><BR></SPAN>
<TABLE cellSpacing=1 cellPadding=3 width="90%" align=center border=0>
<TBODY>
<TR>
<TD><SPAN class=genmed><B>java代码:&nbsp;</B></SPAN></TD></TR>
<TR>
<TD class=code>
<DIV style="FONT-FAMILY: 'Courier New', Courier, monospace"><BR>A a = <SPAN style="FONT-WEIGHT: bold; COLOR: #990066" ?>new</SPAN> A<SPAN style="COLOR: #000000">(</SPAN><SPAN style="FONT-WEIGHT: bold; COLOR: #990066" ?>new</SPAN> B<SPAN style="COLOR: #000000">(</SPAN><SPAN style="COLOR: #000000">)</SPAN><SPAN style="COLOR: #000000">)</SPAN>;</DIV><BR></TD></TR></TBODY></TABLE><SPAN class=postbody><BR><BR>如果这样，不是说明A要用到B这样一个实现细节暴露给X了？ <BR>而且X要使用的接口也复杂了。 <BR><BR>如果X是老板，那么</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>老板不应该事事躬亲！我交给你办至于你怎么办我就不管了</TD></TR></TBODY></TABLE><SPAN class=postbody><BR>为什么·还要我老板来给你小兵买票？到底谁是老板啊？ <BR><BR>而且现在是X依赖A, X依赖B，A依赖B但不依赖B的创建。依赖也没有减少啊！ <BR><BR><BR><BR>好。对frankensteinlin的理解叙述完毕。 <BR><BR>下面·我来回答， <BR><BR><BR>1。首先，还是要根据我前面给的标准分析。B是否是一个对它要实现的功能的一个100％标准的实现？它是否可能有B1, B2, ..., Bn等等不同的实现竞争对手？ <BR><BR>2。如果1是true，那么A能否自己对选择那一个B做决定？A选择B1和B2是否都能够给出合法的语义，这两个合法的语义是否可能不同？ <BR>A的设计者如何在不知道使用者意图的时候决定采用哪个语义？ <BR>比如说出差买票。中关村买假票便宜，但是风险高；去代理点预定价格适中，但是要提前定购，有点麻烦和不灵活；想走了拍拍屁股现场买票最潇洒，但是可能价格非常高，bill gates无所谓，小业务员就别想这么奢侈了。 <BR>其它还有买卧铺？买软卧？买头等舱飞机票？ 等等等等。 <BR>这些，你A同志能否都自己决定？假如你买头等舱，结果X穷，不买单怎么办？ <BR>还是说你A只给bill gates专职服务，换个老板你就辞职？ <BR><BR><BR>3。说到ioc把A的实现细节暴露给X，这也不好说。 <BR>假设B就是一个买票的策略或者是一张票。 <BR>首先，X是否有可能本身就想控制这个买票的策略呢？它真是A的实现细节吗？ <BR>比如bill gates同志，他老人家对下属关怀备至，一人为本，一定要买最爽的头等舱，那么这样： <BR></SPAN>
<TABLE cellSpacing=1 cellPadding=3 width="90%" align=center border=0>
<TBODY>
<TR>
<TD><SPAN class=genmed><B>java代码:&nbsp;</B></SPAN></TD></TR>
<TR>
<TD class=code>
<DIV style="FONT-FAMILY: 'Courier New', Courier, monospace"><BR><BR>BillGates: <BR>&nbsp; A = <SPAN style="FONT-WEIGHT: bold; COLOR: #990066" ?>new</SPAN> A<SPAN style="COLOR: #000000">(</SPAN><SPAN style="FONT-WEIGHT: bold; COLOR: #990066" ?>new</SPAN> BuyMostComfortableFirstClassWithStrippers<SPAN style="COLOR: #000000">(</SPAN><SPAN style="COLOR: #000000">)</SPAN><SPAN style="COLOR: #000000">)</SPAN>; <BR></DIV><BR></TD></TR></TBODY></TABLE><SPAN class=postbody><BR>不是很自然？ <BR><BR><BR>其次，是有可能X不想控制这个买票策略。象你说的“老板”的情况。这个老板不在乎买票怎么买，花多少钱，就是不想过问实现细节的。它就是希望你把事情做好。 <BR><BR>但是，请注意，在你说new A()的时候，你等于让老板自己寻找一个能够出差的人再下命令。老板必须认识A，必须自己构造A。这，这难道就爽了？ <BR>为什么老板自己不能直接说：“给我一个能出差的家伙”。这不是更爽？ <BR><BR>看看你的X的代码，它难道不能继续ioc？不能继续推诿责任？它为什么要new A()？ <BR><BR></SPAN>
<TABLE cellSpacing=1 cellPadding=3 width="90%" align=center border=0>
<TBODY>
<TR>
<TD><SPAN class=genmed><B>java代码:&nbsp;</B></SPAN></TD></TR>
<TR>
<TD class=code>
<DIV style="FONT-FAMILY: 'Courier New', Courier, monospace"><BR>X: <BR>&nbsp; A a; <BR>&nbsp; X<SPAN style="COLOR: #000000">(</SPAN>A a<SPAN style="COLOR: #000000">)</SPAN><SPAN style="COLOR: #000000">{</SPAN> <BR>&nbsp; &nbsp; this.<SPAN style="COLOR: #000000">a</SPAN> = a; <BR>&nbsp; <SPAN style="COLOR: #000000">}</SPAN> <BR></DIV><BR></TD></TR></TBODY></TABLE><SPAN class=postbody><BR><BR>如此，不就没有什么实现细节暴露？接口不是比new A()还要简单？ <BR>这个老板做的不是更彻底？ <BR><BR><BR>至于用xml编程，我是不太喜欢的。xml配置的好处在于可以随时修改，不用重编译系统。这是operation上的好处，从软件结构上，依赖关系上，它和把依赖写在java里没什么不同。而且往往比java还要繁琐，易错，难维护。 <BR><BR>只不过，ioc并不必然意味着xml配置。把组装代码写在java里也一样是ioc。 <BR><BR>比如，在main()函数里，我可以自己手工组装如下： <BR><BR><BR></SPAN>
<TABLE cellSpacing=1 cellPadding=3 width="90%" align=center border=0>
<TBODY>
<TR>
<TD><SPAN class=genmed><B>java代码:&nbsp;</B></SPAN></TD></TR>
<TR>
<TD class=code>
<DIV style="FONT-FAMILY: 'Courier New', Courier, monospace"><BR><BR>A a1 = <SPAN style="FONT-WEIGHT: bold; COLOR: #990066" ?>new</SPAN> A<SPAN style="COLOR: #000000">(</SPAN><SPAN style="FONT-WEIGHT: bold; COLOR: #990066" ?>new</SPAN> B1<SPAN style="COLOR: #000000">(</SPAN><SPAN style="FONT-WEIGHT: bold; COLOR: #990066" ?>new</SPAN> C1<SPAN style="COLOR: #000000">(</SPAN><SPAN style="COLOR: #000000">)</SPAN>, <SPAN style="FONT-WEIGHT: bold; COLOR: #990066" ?>new</SPAN> D1<SPAN style="COLOR: #000000">(</SPAN><SPAN style="COLOR: #000000">)</SPAN><SPAN style="COLOR: #000000">)</SPAN><SPAN style="COLOR: #000000">)</SPAN>; <BR>A a2 = <SPAN style="FONT-WEIGHT: bold; COLOR: #990066" ?>new</SPAN> A<SPAN style="COLOR: #000000">(</SPAN><SPAN style="FONT-WEIGHT: bold; COLOR: #990066" ?>new</SPAN> B2<SPAN style="COLOR: #000000">(</SPAN><SPAN style="FONT-WEIGHT: bold; COLOR: #990066" ?>new</SPAN> C2<SPAN style="COLOR: #000000">(</SPAN><SPAN style="COLOR: #000000">)</SPAN>, <SPAN style="FONT-WEIGHT: bold; COLOR: #990066" ?>new</SPAN> D2<SPAN style="COLOR: #000000">(</SPAN><SPAN style="COLOR: #000000">)</SPAN><SPAN style="COLOR: #000000">)</SPAN><SPAN style="COLOR: #000000">)</SPAN>; <BR>... <BR></DIV><BR></TD></TR></TBODY></TABLE><SPAN class=postbody><BR><BR><BR><BR><BR>这样，程序其它地方都避免了不必要的依赖。 <BR><BR>确实，依赖必须存在，就象你再program against interface，最终也必须new一个class一样。ioc也不是魔术，不可能把依赖变没。 <BR>但是依赖放在什么地方就是学问了。 <BR><BR>通过让main()或者是任何一个其它的组装模块来单独处理程序所有的依赖，我们实现了职责单一化。 <BR><BR>另外，如果要改动系统的行为（比如从B1变成B2，或者D2改用D1），直接改动组装者就可。 <BR>更重要的是，我们可以通过注射不同的零件来让同一个组件展现不同的行为。 <BR>比如，上面的main()代码构造了两个A对象，这两个A对象行为不同。但是都同时存在于程序之中。 <BR><BR>请问，你如果完全把new B()这种东西固定到A中去，怎么达到这个效果？ <BR><BR><BR>至于说配置着出错，就全完蛋了之类的话，就有点不知所云了。 <BR><BR>一个软件系统，如果一个模块有bug，那么整个系统就是有bug。 <BR>如果你的main()函数不工作了，那么整个程序就不工作了。 <BR><BR>你这里难道要转移话题谈fail over之类的事情了吗？</SPAN><img src ="http://www.blogjava.net/kapok/aggbug/5577.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-06-05 22:13 <a href="http://www.blogjava.net/kapok/archive/2005/06/05/5577.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>[JSP/Servlet入門]自訂EL函式 [精華] </title><link>http://www.blogjava.net/kapok/archive/2005/06/03/5537.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Fri, 03 Jun 2005 11:48:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/06/03/5537.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/5537.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/06/03/5537.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/5537.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/5537.html</trackback:ping><description><![CDATA[<SPAN class=javascript id=text56702><A href="http://www.javaworld.com.tw/jute/post/view?bid=6&amp;id=56702&amp;sty=3&amp;keywords=el+function">http://www.javaworld.com.tw/jute/post/view?bid=6&amp;id=56702&amp;sty=3&amp;keywords=el+function</A><BR><BR>對於一些常用的函式，我們可以將之撰寫為一個函式庫，之後結合<FONT style="BACKGROUND-COLOR: #ffff00"><B>EL</B></FONT>中對函式使用的支援即可重複使用該函式，例如我們可以這樣使用<FONT style="BACKGROUND-COLOR: #ffff00"><B>EL</B></FONT>函式:<BR><PRE><DIV class=codeStyle>${ math:gcd(10, 20) }</DIV></PRE><BR><BR>要能夠自訂<FONT style="BACKGROUND-COLOR: #ffff00"><B>EL</B></FONT>函式並使用之，我們必須完成四個步驟: 撰寫函式類別、撰寫標籤函式描述（Tag Library Descriptor）、在web.xml中說明class與tld的位置資訊、在JSP網頁中指定標籤函式位置與前置文字。<BR><BR>我們一個一個來完成，首先我們編寫下面的程式: <BR><PRE><DIV class=codeStyle>package demo.<FONT style="BACKGROUND-COLOR: #ffff00"><B>el</B></FONT>;<BR><BR>public class MathTools {<BR>    public static int gcd(int m, int n) {<BR>        int r = 0;<BR>        while(n != 0) {<BR>            r = m % n;<BR>            m = n;<BR>            n = r;<BR>        }<BR>        return m;<BR>    }<BR>    <BR>    public static double pi() {<BR>        return Math.PI;<BR>    }<BR>}</DIV></PRE><BR><BR>注意所有的函式都是公開且靜態的，編譯完成之後，將之放置在WEB-INF\classes\下即可，然後我們撰寫標籤函式描述（Tag Library Descriptor），這是個XML格式的檔案，注意副檔名要是.tld而不是.xml，假設我們的檔名是mathtools.tld: <BR><PRE><DIV class=codeStyle>&lt;?xml version="1.0" encoding="UTF-8" ?&gt;<BR><BR>&lt;taglib xmlns="http://java.sun.com/xml/ns/j2ee"<BR>    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"<BR>    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee web-jsptaglibrary_2_0.xsd"<BR>    version="2.0"&gt;<BR>    <BR>    &lt;description&gt;Math Tools&lt;/description&gt;<BR>    &lt;tlib-version&gt;1.0&lt;/tlib-version&gt;<BR>    &lt;short-name&gt;SimpleMathTools&lt;/short-name&gt;<BR>    &lt;uri&gt;/SimpleMathTools&lt;/uri&gt;<BR><BR>    &lt;<FONT style="BACKGROUND-COLOR: #00ff00"><B>function</B></FONT>&gt;<BR>        &lt;description&gt;GCD Tool&lt;/description&gt;<BR>        &lt;name&gt;gcd&lt;/name&gt;<BR>        &lt;<FONT style="BACKGROUND-COLOR: #00ff00"><B>function</B></FONT>-class&gt;demo.<FONT style="BACKGROUND-COLOR: #ffff00"><B>el</B></FONT>.MathTools&lt;/<FONT style="BACKGROUND-COLOR: #00ff00"><B>function</B></FONT>-class&gt;<BR>        &lt;<FONT style="BACKGROUND-COLOR: #00ff00"><B>function</B></FONT>-signature&gt;int gcd(int,int)&lt;/<FONT style="BACKGROUND-COLOR: #00ff00"><B>function</B></FONT>-signature&gt;    <BR>    &lt;/<FONT style="BACKGROUND-COLOR: #00ff00"><B>function</B></FONT>&gt;<BR>    &lt;<FONT style="BACKGROUND-COLOR: #00ff00"><B>function</B></FONT>&gt;<BR>        &lt;description&gt;PI Tool&lt;/description&gt;<BR>        &lt;name&gt;pi&lt;/name&gt;<BR>        &lt;<FONT style="BACKGROUND-COLOR: #00ff00"><B>function</B></FONT>-class&gt;demo.<FONT style="BACKGROUND-COLOR: #ffff00"><B>el</B></FONT>.MathTools&lt;/<FONT style="BACKGROUND-COLOR: #00ff00"><B>function</B></FONT>-class&gt;<BR>        &lt;<FONT style="BACKGROUND-COLOR: #00ff00"><B>function</B></FONT>-signature&gt;double pi()&lt;/<FONT style="BACKGROUND-COLOR: #00ff00"><B>function</B></FONT>-signature&gt;    <BR>    &lt;/<FONT style="BACKGROUND-COLOR: #00ff00"><B>function</B></FONT>&gt;<BR><BR>&lt;/taglib&gt;</DIV></PRE><BR><BR>大部分的標籤光看標籤名就可知道它的作用了（這是XML文件的自描述特性），我們注意一下&lt;<FONT style="BACKGROUND-COLOR: #00ff00"><B>function</B></FONT>-signature&gt;，它與&lt;name&gt;對應，&lt;name&gt;是<FONT style="BACKGROUND-COLOR: #ffff00"><B>EL</B></FONT>呼叫函式時所用的名稱，而&lt;<FONT style="BACKGROUND-COLOR: #00ff00"><B>function</B></FONT>-signature&gt;定義了函式的傳入參數與傳回值。<BR><BR>接下來我們在web.xml中添加對.tld與類別檔的相關描述:<BR><PRE><DIV class=codeStyle>    &lt;jsp-config&gt;<BR>        &lt;taglib&gt;<BR>            &lt;taglib-uri&gt;http://www.caterpillar.onlyfun.net/phpBB2&lt;/taglib-uri&gt;<BR>            &lt;taglib-location&gt;/WEB-INF/tlds/mathtools.tld&lt;/taglib-location&gt;<BR>        &lt;/taglib&gt;<BR>    &lt;/jsp-config&gt;</DIV></PRE><BR><BR>&lt;taglib-uri&gt;用來設定使用.tld時的名稱空間識別，這個資訊在JSP網頁中是用來指定將使用哪一個位置的tld檔，將下來我們直接看JSP網頁中如何使用定義好的<FONT style="BACKGROUND-COLOR: #ffff00"><B>EL</B></FONT>函式:<BR><PRE><DIV class=codeStyle>&lt;%@taglib prefix="math" uri="http://www.caterpillar.onlyfun.net/phpBB2"%&gt;<BR>&lt;html&gt;<BR>&lt;body&gt;<BR>    Math Tools GCD Test: ${ math:gcd(100, 14) }&lt;br&gt;<BR>    Math Tools PI Test: ${ math:pi() }<BR>&lt;/body&gt;<BR>&lt;/html&gt;</DIV></PRE><BR><BR>我們使用指令元素taglib來指定tld檔的URI位置，並設定使用時的前置文字，前置文字的作用是當有許多同名函式時（例如用了兩個位置的函式庫，而當中有相同的函式時），可以根據前置文字來識別使用的是哪一個函式。<BR><BR>接下來就是啟動Tomcat並執行了，傳回的結果是:<BR><PRE><DIV class=codeStyle>&lt;html&gt;<BR>&lt;body&gt;<BR>    Math Tools GCD Test: 2&lt;br&gt;<BR>    Math Tools PI Test: 3.141592653589793<BR>&lt;/body&gt;<BR>&lt;/html&gt;</DIV></PRE><BR><BR>附帶一提的是，我們並不一定要在web.xml中添加對.tld與類別檔的相關描述，如果沒有這個步驟的話，在JSP網頁中直接指定.tld的實體位置也是可以的:<BR><PRE><DIV class=codeStyle>&lt;%@taglib prefix="math" uri="/WEB-INF/tlds/mathtools.tld"%&gt;</DIV></PRE><BR><BR>在web.xml中定義.tld的資訊是為了管理的方便，如果不定義，則每次更動.tld檔案的位置或名稱，則必須修改每一個JSP網頁，如果有在web.xml檔中定義，則更動.tld檔案的位置或名稱後，只要修改web.xml中的定義即可，當中維護在方便性的差別上可見一般。 </SPAN><img src ="http://www.blogjava.net/kapok/aggbug/5537.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-06-03 19:48 <a href="http://www.blogjava.net/kapok/archive/2005/06/03/5537.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Webwork 中的iterator标签</title><link>http://www.blogjava.net/kapok/archive/2005/06/03/5505.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Fri, 03 Jun 2005 02:23:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/06/03/5505.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/5505.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/06/03/5505.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/5505.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/5505.html</trackback:ping><description><![CDATA[<A href="http://forum.javaeye.com/viewtopic.php?t=8770&amp;postdays=0&amp;postorder=asc&amp;highlight=xml&amp;start=0">http://forum.javaeye.com/viewtopic.php?t=8770&amp;postdays=0&amp;postorder=asc&amp;highlight=xml&amp;start=0</A><BR><BR><A href="http://wiki.opensymphony.com/display/WW/Iteration+Tags">http://wiki.opensymphony.com/display/WW/Iteration+Tags</A><BR><img src ="http://www.blogjava.net/kapok/aggbug/5505.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-06-03 10:23 <a href="http://www.blogjava.net/kapok/archive/2005/06/03/5505.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>正确理解OpenSessionInView</title><link>http://www.blogjava.net/kapok/archive/2005/05/31/5350.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Tue, 31 May 2005 01:16:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/05/31/5350.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/5350.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/05/31/5350.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/5350.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/5350.html</trackback:ping><description><![CDATA[<P>准备这几天如果有空整理一下，先放在这里。<BR>要正确理解OpenSessionInView必须具备以下几个知识点：<BR>1.<BR>servlet的多线程模型。<BR>2.<BR>Filter的工作原理。<BR>3.<BR>ThreadLocal的使用。<BR>4.<BR>Hibernate的Session以及Connection的管理。<BR>5.<BR>Lazy Load<BR>6.<BR>Hibernate的FlushMode<BR>7.<BR>Spring的处理分两种：<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;7.1 One session per request&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;7.2 One new session per operation(这里的operation是指一次业务操作，例如如果我们的一次请求调用了某个业务方法，而这个方法里面有两个与Session相关的操作，则每一个操作都会新开一个Session,但是使用结束以后并不关闭，而是注册到ThreadLocal的deffered close 的变量里面，等到open session in view filter执行完毕的最后进行一起close, 这样有个问题就是一个request可能会启动很多Session,而且这些session之间的一级缓存不能共享，另外Hibernate限制不能在一个session里面load一个po却在这个session没有关闭的情况下面在另外一个session里面save或者update,也就是说同一个po实例同一时间只能跟一个session发生关联，这样导致的结果是如果在上面假设的两个业务方法中的第一个进行load,而在第二个中对load出来的进行修改，铁定会报错。)<BR><BR>&nbsp;&nbsp;&nbsp;另外如果是One session per request&nbsp;&nbsp;（官方说法是叫SingleSession Mode）, 会看到在open session in view filter中进行了FlushMode.setMode(Never),也就是从来不进行flush, 想当然地就是只能进行read-only的操作。但是没有关系，回到HibernateTemplate的模板方法以及SessionFactoryUtil的getSession方法，可以看到我们的业务方法发出的每个请求都会试图先取到一个session(取session的策略与上面描述的有关)，在取Session的过程中会判断当前的操作是不是配制成read-only的（同时也会注册transaction sync manager,有spring和Jta两种），如果不是read-only并且当前的flush mode&nbsp; 是 never的话，会把flush mode改成auto,因此就可以进行读写操作了。<BR><BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;spring的callback使得所有的操作最后基本归由HibernateTemplate当中的excute方法进行处理，对session的管控也是在这里集中进行，如果我们自己控制session,有两种方法，一种是我们也写callback,让spring帮我们擦屁股，另外一种是直接调用HibernateTemplate的getSession方法，但是需要我们对session的生命周期以及关闭作仔细的控制，具体的控制策略可以参考spring地实现。<BR><BR><BR><BR><BR></P><img src ="http://www.blogjava.net/kapok/aggbug/5350.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-05-31 09:16 <a href="http://www.blogjava.net/kapok/archive/2005/05/31/5350.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>DefaultValueListHandlerImpl 的分页</title><link>http://www.blogjava.net/kapok/archive/2005/05/30/5346.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Mon, 30 May 2005 13:34:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/05/30/5346.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/5346.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/05/30/5346.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/5346.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/5346.html</trackback:ping><description><![CDATA[<P>暂时没有太多时间求证,暂时记录在这里,根据这几句话好像是说valueList的分页也是假分页,仅仅是取ValueList的subList?<BR><BR>DefaultValueListHandlerImpl implements ValueListHandler<BR><BR><BR>if ((adapterType &amp; ValueListAdapter.DO_PAGE) == ValueListAdapter.DO_PAGE)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (valueList.getValueListInfo() != null &amp;&amp; valueList.getList() != null<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;&amp; valueList.getValueListInfo().getPagingNumberPer() &lt; valueList.getValueListInfo().getTotalNumberOfEntries())<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int start = (valueList.getValueListInfo().getPagingPage() - 1) * valueList.getValueListInfo().getPagingNumberPer();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int end = Math.min(start + valueList.getValueListInfo().getPagingNumberPer(), valueList.getList().size());<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; valueList = new DefaultListBackedValueList(valueList.getList().subList(start, end), valueList.getValueListInfo());<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (LOGGER.isDebugEnabled())<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LOGGER.debug("The ValueList was paged by post process.");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</P><img src ="http://www.blogjava.net/kapok/aggbug/5346.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-05-30 21:34 <a href="http://www.blogjava.net/kapok/archive/2005/05/30/5346.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>对filter的理解还是不够深</title><link>http://www.blogjava.net/kapok/archive/2005/05/30/5341.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Mon, 30 May 2005 08:58:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/05/30/5341.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/5341.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/05/30/5341.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/5341.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/5341.html</trackback:ping><description><![CDATA[<TABLE cellSpacing=0 cellPadding=0 width="95%" align=center bgColor=#f7f7f7 border=0>
<TBODY>
<TR>
<TD height=30>
<DIV align=center><B class=ab>软件体系架构模式在J2EE中的应用 (2)</B></DIV></TD></TR>
<TR>
<TD height=30>
<DIV align=center>
<DIV align=center>作者：刘兵　时间： 2005年1月18日 16:47:39　来源：sanwin</DIV></DIV></TD></TR>
<TR>
<TD bgColor=#f7f7f7>
<TABLE cellSpacing=10 width="100%" align=center>
<TBODY>
<TR>
<TD>&nbsp;&nbsp;&nbsp;&nbsp;<B>Servlet2.3 Filter</B><BR><BR>　　1、Servlet Filter概述<BR><BR>　　凡是开发过J2EE的web application的人员都知道,经常需要处理以下几种情况:<BR><BR>　　　访问特定资源（Web 页、JSP 页、servlet）时的身份认证 <BR>　　　应用程序级的访问资源的审核和记录 <BR>　　　应用程序范围内对资源的加密访问，它建立在定制的加密方案基础上 <BR>　　　对被访问资源的及时转换, 包括从 servlet 和 JSP 的动态输出 <BR>　　<BR>　　在servlet2.3之前这些功能处理是很难实现的,但是Java Servlet 2.3 规范新增了不少激动人心的功能，其中之一便是过滤器(Filter),其实这就是我们所说的管道和过滤器体系架构在J2EE中的应用实践. 通过使用该模式使得Web Application开发者能够在请求到达Web资源之前截取请求，在处理请求之后修改应答。其结构图如下:<BR><BR>
<TABLE width="90%" align=center border=0>
<TBODY>
<TR>
<TD>
<DIV align=center>
<TABLE cellSpacing=2 cellPadding=0 width=10 align=center border=0>
<TBODY>
<TR>
<TD><IMG src="http://computer.sz.net.cn/2005-01-18/bb2005011800051.bmp"></TD></TR>
<TR class=a>
<TD><FONT class=f style="FONT-SIZE: 10.4pt"></FONT></TD></TR></TBODY></TABLE><BR></DIV></TD></TR></TBODY></TABLE><BR>　　一个执行过滤器的Java 类必须实现javax.servlet.Filter 接口。这一接口含有三个方法：<BR><BR>　　<B>init(FilterConfig)</B>：这是容器所调用的初始化方法。它保证了在第一次 doFilter() 调用前由容器调用。它能获取在 web.xml 文件中指定的filter初始化参数。 <BR><BR>　　<B>doFilter(ServletRequest, ServletResponse, FilterChain)</B>：这是一个完成过滤行为的方法。它同样是上一个过滤器调用的方法。引入的 FilterChain 对象提供了后续过滤器所要调用的信息。 <BR><BR>　　<B>destroy()</B>：容器在销毁过滤器实例前，doFilter()中的所有活动都被该实例终止后，调用该方法。 <BR><BR>
<TABLE width="90%" align=center border=0>
<TBODY>
<TR>
<TD>
<DIV align=center>
<TABLE cellSpacing=2 cellPadding=0 width=10 align=center border=0>
<TBODY>
<TR>
<TD><IMG src="http://computer.sz.net.cn/2005-01-18/bb2005011800052.bmp"></TD></TR>
<TR class=a>
<TD><FONT class=f style="FONT-SIZE: 10.4pt"></FONT></TD></TR></TBODY></TABLE><BR></DIV></TD></TR></TBODY></TABLE><BR>　　2、Filter链介绍<BR><BR>　　所有过滤器都服从调用的过滤器链，并通过定义明确的接口得到执行。WebApplication可以指定许多过滤器来完成相关的工作.那么它们就组成一个过滤器链来完成相应的工作.其结构如下图:<BR><BR>
<TABLE width="90%" align=center border=0>
<TBODY>
<TR>
<TD>
<DIV align=center>
<TABLE cellSpacing=2 cellPadding=0 width=10 align=center border=0>
<TBODY>
<TR>
<TD><IMG src="http://computer.sz.net.cn/2005-01-18/bb2005011800053.bmp"></TD></TR>
<TR class=a>
<TD><FONT class=f style="FONT-SIZE: 10.4pt"></FONT></TD></TR></TBODY></TABLE><BR></DIV></TD></TR></TBODY></TABLE><BR>　　3、例子<BR><BR>　　3.1 简单filter<BR><BR>　　在PetStore1.3.1中的就存在两个Filter过滤器.其中一个过滤器,完成字符集的编码的转化,如大家经常遇到的汉字编码问题,你只需配置为GBK即可.它从Web.xml之中读取这些参数的配置信息,然后进行编码的转化.另一个是安全校验Fliter,它负责进行安全检查哪些页面可以进行,哪些不可.它们组成一个Filter链,结构图和实现代码如下:<BR><BR>
<TABLE width="90%" align=center border=0>
<TBODY>
<TR>
<TD>
<DIV align=center>
<TABLE cellSpacing=2 cellPadding=0 width=10 align=center border=0>
<TBODY>
<TR>
<TD><IMG src="http://computer.sz.net.cn/2005-01-18/bb2005011800054.bmp"></TD></TR>
<TR class=a>
<TD><FONT class=f style="FONT-SIZE: 10.4pt"></FONT></TD></TR></TBODY></TABLE><BR></DIV></TD></TR></TBODY></TABLE><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#dadacf border=1>
<TBODY>
<TR>
<TD>public class EncodingFilter implements Filter {<BR>　private FilterConfig config = null;<BR>　// default to ASCII<BR>　private String targetEncoding = "ASCII";<BR><BR>　public void init(FilterConfig config) throws ServletException {<BR>　　this.targetEncoding = config.getInitParameter("encoding");<BR>　}<BR>　//在过滤器中实现字符集编码转化<BR>　public void doFilter(ServletRequest srequest, ServletResponse sresponse, FilterChain chain)<BR>　throws IOException, ServletException {<BR>　<BR>　　HttpServletRequest request = (HttpServletRequest)srequest;<BR>　　request.setCharacterEncoding(targetEncoding);<BR>　　// move on to the next<BR>　　chain.doFilter(srequest,sresponse);<BR>　}<BR>　public void destroy() {<BR>　　……………..<BR>　} <BR>}<BR><BR>public class SignOnFilter implements Filter {<BR>　public void init(FilterConfig config) throws ServletException {<BR>　　this.config = config;<BR>　　URL protectedResourcesURL = null;<BR>　　try {<BR>　　　protectedResourcesURL = config.getServletContext().getResource("/WEB-INF/signon-config.xml");<BR>　　　...............<BR>　　} catch (java.net.MalformedURLException ex) {<BR>　　　System.out.println("SignonFilter: malformed URL exception: " + ex);<BR>　　}<BR>　}<BR><BR>　public void destroy() {<BR>　　config = null;<BR>　}<BR>　public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)<BR>　throws IOException, ServletException {<BR>　　........<BR>　}<BR>}</TD></TR></TBODY></TABLE><BR>　　容器通过 Web 应用程序中的配置描述符 web.xml 文件解析过滤器配置信息。有两个新的标记与过滤器相关：＜filter＞ 和 ＜filter-mapping＞。＜filter＞ 标记是一个过滤器定义，它必定有一个 ＜filter- name＞ 和 ＜filter-class＞ 子元素。＜filter-name＞ 子元素给出了一个与过滤器实例相关的名字。＜filter-class＞ 指定了由容器载入的实现类。您能随意地包含一个 ＜init-param＞ 子元素为过滤器实例提供初始化参数。＜filter-mapping＞ 标记代表了一个过滤器的映射，指定了过滤器会对其产生作用的 URL 的子集。<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#dadacf border=1>
<TBODY>
<TR>
<TD>＜!-- Encoding Filter Declaration Start --＞<BR>＜filter＞<BR>　＜filter-name＞EncodingFilter＜/filter-name＞<BR>　＜display-name＞Encoding Filter＜/display-name＞<BR>　＜description＞no description＜/description＞<BR>　＜filter-class＞com.sun.j2ee.blueprints.encodingfilter.web.EncodingFilter＜/filter-class＞<BR>　＜init-param＞<BR>　　＜param-name＞encoding＜/param-name＞<BR>　　＜param-value＞UTF-8＜/param-value＞<BR>　＜/init-param＞<BR>＜/filter＞<BR>＜!-- Encoding Filter Declaration End --＞<BR>＜!-- Signon Filter Declaration Start --＞<BR>＜filter＞<BR>　＜filter-name＞SignOnFilter＜/filter-name＞<BR>　＜display-name＞SignOn Filter＜/display-name＞<BR>　＜description＞no description＜/description＞<BR>　＜filter-class＞com.sun.j2ee.blueprints.signon.web.SignOnFilter＜/filter-class＞<BR>＜/filter＞<BR>＜!-- Signon Filter Declaration End --＞<BR><BR>＜!-- Encoding Filter Mapping Start--＞<BR>＜filter-mapping＞<BR>　＜filter-name＞EncodingFilter＜/filter-name＞<BR>　＜url-pattern＞/*＜/url-pattern＞<BR>＜/filter-mapping＞<BR>＜!-- Encoding Filter Mapping End --＞<BR>＜!-- Signon Filter Mapping Start--＞<BR>＜filter-mapping＞<BR>　＜filter-name＞SignOnFilter＜/filter-name＞<BR>　＜url-pattern＞/*＜/url-pattern＞<BR>＜/filter-mapping＞<BR>＜!-- Signon Filter Mapping End --＞</TD></TR></TBODY></TABLE><BR>　　3.2 复杂的filter<BR><BR>　　上面是petstore的例子,演示了通过Fliter修改字符编码和安全认证的功能.下面提供一个示例演示通过修改返回数据(通过过滤器把response的字符串变成大写).<BR><BR>
<TABLE borderColor=#ffcc66 width="90%" align=center bgColor=#dadacf border=1>
<TBODY>
<TR>
<TD>public class UCaseResponse extends HttpServletResponseWrapper {<BR>　public UCaseResponse(HttpServletResponse response) {<BR>　　super(response);<BR>　}<BR><BR>　public PrintWriter getWriter() throws IOException {<BR>　　return new UCaseWriter(super.getWriter());<BR>　}<BR>}<BR><BR>public class UCaseWriter extends PrintWriter {<BR>　public UCaseWriter(Writer out) {<BR>　　super(out);<BR>　}<BR>　public void write(int c) {<BR>　　super.write(Character.toUpperCase( (char) c));<BR>　}<BR>　public void write(char buf[], int off, int len) {<BR>　　for (int i = 0;i ＜ len;i++) {<BR>　　　write(buf[off + i]);<BR>　　}<BR>　}<BR>　public void write(String s, int off, int len) {<BR>　　for (int i = 0;i ＜ len;i++) {<BR>　　　write(s.charAt(off + i));<BR>　　}<BR>　}<BR>} <BR><BR>public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) {<BR>　try {<BR>　　filterChain.doFilter(request, new UCaseResponse((HttpServletResponse)(response)));<BR>　}catch(Exception sx) {<BR>　　filterConfig.getServletContext().log(sx.getMessage());<BR>}</TD></TR></TBODY></TABLE><BR>　　该示例使用HttpServletResponseWrapper技术,它是对HttpServletResponse的包装,其实就是装饰(decorate)设计模式的应用.这个例子能够工作的关键是UCaseResponse和UCaseWriter类，它实现了对每个要输出的字符都转成了大写后再写入实际的输出流的功能。<BR><BR>　　4、体系架构的实现<BR><BR>　　实现一个管道和过滤器一般要注意以下几个方面:<BR><BR>　　把系统任务分成一系列处理阶段。<BR><BR>　　根据管道和过滤器的设计方案,必须把系统处理的任务分割成相应独立的任务,如日志,数据转化,安全认证等.这样每个阶段仅依赖其前一阶段的输出。通过数据流将所有阶段相连起来。并且你可以进行替换每个步骤,或者可以调整它们之间的顺序,以产生新的结果.如petstore中的编码转化Filter和安全Filter,分成两个独立的处理阶段.<BR><BR>　　定义沿每个管道传输的数据格式。<BR><BR>　　我们知道每个过滤器,定义一个统一格式以获得最大的灵活性，因为它使过滤器的重组变得容易。如:每个过滤器的方法是doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)它们的参数是必须相同的.<BR><BR>　　决定如何实现每个管道连接<BR><BR>　　Filter过滤器的连接是推得方式来实现的.前一个过滤器主动的调用filterChain.doFilter(request, response);来实现转向下一个过滤器.<BR><BR>　　设计和实现过滤器<BR><BR>　　设计每个Filter具有独立的功能,如编码转化,安全校验,等功能.并且每个Fliter都应该在实现javax.servlet.Filter接口.<BR><BR>　　建立处理流水线<BR><BR>　　过滤器的部署是在Web.xml中进行配置,描述过滤器的实现类以及它们的map关系,来确定它们的顺序.<BR></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><img src ="http://www.blogjava.net/kapok/aggbug/5341.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-05-30 16:58 <a href="http://www.blogjava.net/kapok/archive/2005/05/30/5341.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Hibernate FlushMode</title><link>http://www.blogjava.net/kapok/archive/2005/05/30/5338.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Mon, 30 May 2005 08:09:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/05/30/5338.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/5338.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/05/30/5338.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/5338.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/5338.html</trackback:ping><description><![CDATA[The Hibernate Session implements transparent write behind. Changes to the domain<BR>model made in the scope of a Session aren’t immediately propagated to the database.<BR>This allows Hibernate to coalesce many changes into a minimal number of<BR>database requests, helping minimize the impact of network latency.<BR>For example, if a single property of an object is changed twice in the same<BR>Transaction, Hibernate only needs to execute one SQL UPDATE. Another example<BR>of the usefulness of transparent write behind is that Hibernate can take<BR>advantage of the JDBC batch API when executing multiple UPDATE, INSERT, or<BR>DELETE statements.<BR><FONT color=#ff1493>Hibernate flushes occur only at the following times:<BR>■ When a Transaction is committed<BR>■ Sometimes before a query is executed<BR>■ When the application calls Session.flush() explicitly</FONT><BR>Flushing the Session state to the database at the end of a database transaction is<BR>required in order to make the changes durable and is the common case. Hibernate<BR>doesn’t flush before every query. However, if there are changes held in memory that<BR>would affect the results of the query, Hibernate will, by default, synchronize first.<BR>You can control this behavior by explicitly setting the Hibernate FlushMode via a<BR>call to session.setFlushMode(). The flush modes are as follows:<BR>■ FlushMode.<FONT color=#800080>AUTO</FONT>—The default. Enables the behavior just described.<BR>■ FlushMode.<FONT color=#800080>COMMIT</FONT>—Specifies that the session won’t be flushed before query<BR>execution (it will be flushed only at the end of the database transaction). Be<BR>aware that this setting may expose you to stale data: modifications you made<BR>to objects only in <FONT color=#800080>memory</FONT> may conflict with the results of the query.<BR>■ FlushMode.NEVER—Lets you specify that only explicit calls to flush() result<BR>in synchronization of session state with the database.<BR><STRONG><FONT color=#800080>We don’t recommend that you change this setting from the default</FONT></STRONG>.(OpenSessionInview需要仔细考虑) It’s provided<BR>to allow performance optimization in rare cases. Likewise, most applications rarely<BR>need to call flush() explicitly. This functionality is useful when you’re working<BR>with triggers, mixing Hibernate with direct JDBC, or working with buggy JDBC drivers.<BR>You should be aware of the option but not necessarily look out for use cases.<BR>Now that you understand the basic usage of database transactions with the<BR>Hibernate Transaction interface, let’s turn our attention more closely to the subject<BR>of concurrent data access.<BR>It seems as though you shouldn’t have to care about transaction isolation—the<BR>term implies that something either is or is not isolated. This is misleading. Complete<BR>isolation of concurrent transactions is extremely expensive in terms of application<BR>scalability, so databases provide several degrees of isolation. For most applications,<BR>incomplete transaction isolation is acceptable. It’s important to understand the<BR>degree of isolation you should choose for an application that uses Hibernate and<BR>how Hibernate integrates with the transaction capabilities of the database.<img src ="http://www.blogjava.net/kapok/aggbug/5338.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-05-30 16:09 <a href="http://www.blogjava.net/kapok/archive/2005/05/30/5338.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>About cache regions</title><link>http://www.blogjava.net/kapok/archive/2005/05/30/5335.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Mon, 30 May 2005 07:22:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/05/30/5335.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/5335.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/05/30/5335.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/5335.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/5335.html</trackback:ping><description><![CDATA[<TABLE cellPadding=4 width="100%" border=0>
<TBODY>
<TR>
<TD width=10></TD>
<TD><SPAN id=ArticleContent1_ArticleContent1_lblContent>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-outline-level: 3" align=left><B><SPAN style="FONT-SIZE: 14pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-ascii-font-family: Verdana; mso-hansi-font-family: Verdana; mso-bidi-font-family: 宋体"><A href="http://dev.csdn.net/Develop/article/27/27107.shtm">http://dev.csdn.net/Develop/article/27/27107.shtm</A><BR><BR>框架的主要原理</SPAN></B><B><SPAN lang=EN-US style="FONT-SIZE: 14pt; COLOR: black; FONT-FAMILY: Verdana; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体"><?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /><o:p></o:p></SPAN></B></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-outline-level: 4" align=left><B><SPAN style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-ascii-font-family: Verdana; mso-hansi-font-family: Verdana; mso-bidi-font-family: 宋体">缓存属性</SPAN></B><B><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: Verdana; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体"><o:p></o:p></SPAN></B></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto" align=left><SPAN style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">我们将所有的缓存参数配置在名为<I><SPAN lang=EN-US>cache.ccf</SPAN></I><SPAN style="mso-bidi-font-style: italic">的属性文件中</SPAN>。这些参数包括缓存信息如：内存中存储的对象的最大数量，缓存时间（过了时间之后缓存的数据九自动从内存中释放），中断时间（<SPAN lang=EN-US>elapsed time since last access time</SPAN>）， 内存缓存名称（例如：缓存算法如<SPAN lang=EN-US>LRU</SPAN>或<SPAN lang=EN-US>MRU</SPAN>）等。在当前版本的<SPAN lang=EN-US>JCS</SPAN>中，缓存属性文件是纯文本格式的。<SPAN lang=EN-US>SpiritCache framework</SPAN>，一种来自<SPAN lang=EN-US>SpiritSoft</SPAN>的<SPAN lang=EN-US>Jcache API</SPAN>商业实现，支持<SPAN lang=EN-US>XML</SPAN>格式的缓存配置。<SPAN lang=EN-US><o:p></o:p></SPAN></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto" align=left><SPAN style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">确认该属性文件存放在类路径中。注意：如果你需要使用其它不同的文件来存放缓存属性的话，<SPAN lang=EN-US>JCS </SPAN>也提供了方法来指定一个配置文件的名称。请参考<SPAN lang=EN-US>JCS</SPAN>的<SPAN lang=EN-US> Javadocs </SPAN>学习如<?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" /><st1:PersonName w:st="on">何</st1:PersonName>从其它非缺省的属性文件中读取缓存配置信息。<SPAN lang=EN-US><o:p></o:p></SPAN></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto" align=left><SPAN style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">下面列出来的是<SPAN lang=EN-US>web</SPAN>应用使用缓存功能需要了解的一些<SPAN lang=EN-US>Java</SPAN>类。这些类存放在本文的示例代码的</SPAN><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: #003366; FONT-FAMILY: 'Courier New'; mso-font-kerning: 0pt">common.caching</SPAN><SPAN style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">包中。这些类的<SPAN lang=EN-US>Javadocs</SPAN>也包括在源代码压缩包中。 （图<SPAN lang=EN-US>2 </SPAN>中的类图显示了这些<SPAN lang=EN-US>Java</SPAN>类的关系）<SPAN lang=EN-US><o:p></o:p></SPAN></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-outline-level: 4" align=left><B><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: Verdana; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">ICacheManager<o:p></o:p></SPAN></B></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto" align=left><SPAN style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">这是客户应用实现所有缓存有关操作（如：存储、访问以及释放缓存中的数据）的主接口（契约）。客户程序可以是<SPAN lang=EN-US>JSP</SPAN>、<SPAN lang=EN-US>Struts Action</SPAN>类，或者就是一个<SPAN lang=EN-US>POJO</SPAN>对象。创建该接口用于对客户端隐藏所有缓存的实现细节，这样当我们将来需要切换另一种的第三方缓存<SPAN lang=EN-US>API</SPAN>的时候无需对客户端代码做任<st1:PersonName w:st="on">何</st1:PersonName>调整。<SPAN lang=EN-US><o:p></o:p></SPAN></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-outline-level: 4" align=left><B><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: #003366; FONT-FAMILY: 'Courier New'; mso-font-kerning: 0pt">BaseCacheManager</SPAN></B><B><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: Verdana; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体"><o:p></o:p></SPAN></B></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto" align=left><SPAN style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">这是<SPAN lang=EN-US>web</SPAN>门户缓存框架的主类。是对</SPAN><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: #003366; FONT-FAMILY: 'Courier New'; mso-font-kerning: 0pt">ICacheManager</SPAN><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体"> </SPAN><SPAN style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">接口的最基本实现。创建</SPAN><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: #003366; FONT-FAMILY: 'Courier New'; mso-font-kerning: 0pt">BaseCacheManager</SPAN><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体"> </SPAN><SPAN style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">用于在一个类中集中所有缓存相关的方法。它被设计为单例模式保证在<SPAN lang=EN-US>servlet</SPAN>容器的<SPAN lang=EN-US>JVM</SPAN>中有且仅有一个</SPAN><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: #003366; FONT-FAMILY: 'Courier New'; mso-font-kerning: 0pt">ICacheManager</SPAN><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体"> </SPAN><SPAN style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">的实例被创建。在多<SPAN lang=EN-US>web</SPAN>服务器<SPAN lang=EN-US>/servlet</SPAN>容器实例共同处理<SPAN lang=EN-US>web</SPAN>请求的集群环境中，每个<SPAN lang=EN-US>JVM</SPAN>将会创建独立的</SPAN><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: #003366; FONT-FAMILY: 'Courier New'; mso-font-kerning: 0pt">ICacheManager</SPAN><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体"> </SPAN><SPAN style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">实例。如果将来你要转换到不同的缓存<SPAN lang=EN-US>API </SPAN>，这是唯一需要为新的缓存<SPAN lang=EN-US>API</SPAN>修改的类。如果你切换到<SPAN lang=EN-US>JCache-</SPAN>兼容的缓存实现，对缓存管理器的修改将会是很小的。<SPAN lang=EN-US><o:p></o:p></SPAN></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-outline-level: 4" align=left><B><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: #003366; FONT-FAMILY: 'Courier New'; mso-font-kerning: 0pt">ICacheLoader</SPAN></B><B><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: Verdana; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体"><o:p></o:p></SPAN></B></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto" align=left><SPAN style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">该接口用于在<SPAN lang=EN-US>web</SPAN>客户端实现真正的数据访问逻辑。所有需要使用缓存机制的客户端应用必须实现该接口。它包括仅有的一个方法叫做</SPAN><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: #003366; FONT-FAMILY: 'Courier New'; mso-font-kerning: 0pt">loadCacheObject()</SPAN><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体"> </SPAN><SPAN style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">，有两个输入参数：一个<SPAN lang=EN-US>String</SPAN>参数制定缓存区域名称，一个对象参数制定缓存键值。这样，缓存管理器将知道在缓存的对象超过指定的“生存时间”的时候，使用哪个客户端程序（运行</SPAN><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: #003366; FONT-FAMILY: 'Courier New'; mso-font-kerning: 0pt">loadCacheObject</SPAN><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体"> </SPAN><SPAN style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">方法）来重载缓存中的对象 。<SPAN lang=EN-US><o:p></o:p></SPAN></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-outline-level: 4" align=left><B><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: #003366; FONT-FAMILY: 'Courier New'; mso-font-kerning: 0pt">ICacheKey</SPAN></B><B><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: Verdana; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体"><o:p></o:p></SPAN></B></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto" align=left><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: #003366; FONT-FAMILY: 'Courier New'; mso-font-kerning: 0pt">ICacheKey</SPAN><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体"> </SPAN><SPAN style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">接口创建的目的是为了隐藏特定的创建缓存键值的细节。有时候缓存的键值不是一个简单的字符串。它可能像多个对象组合起来一样复杂，从数据源获取这些值需要多个查找方法而不是单一的方法。在这种情况下， </SPAN><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: #003366; FONT-FAMILY: 'Courier New'; mso-font-kerning: 0pt">ICacheKey</SPAN><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体"> </SPAN><SPAN style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">接口可以被用来定义创建缓存键值的所有复杂的逻辑。这样，缓存键值创建逻辑将会被定义为独立的类。我编写了一个简单的类</SPAN><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: #003366; FONT-FAMILY: 'Courier New'; mso-font-kerning: 0pt">TestCacheKey</SPAN><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体"> </SPAN><SPAN style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">实现了该接口并实现了</SPAN><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: #003366; FONT-FAMILY: 'Courier New'; mso-font-kerning: 0pt">getCacheKey()</SPAN><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体"> </SPAN><SPAN style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">方法来演示使用该接口的方法。<BR><SPAN lang=EN-US><o:p></o:p></SPAN></SPAN></P>
<H1 class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-outline-level: 4" align=left><FONT size=5><FONT><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: #003366; FONT-FAMILY: 'Courier New'; mso-font-kerning: 0pt"></SPAN></FONT></FONT>&nbsp;</H1>
<H1 class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-outline-level: 4" align=left><SPAN style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体"><FONT color=#800080 size=6>CacheRegions:</FONT></SPAN></H1>
<H1 class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-outline-level: 4" align=left><SPAN style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体"></SPAN>&nbsp;</H1>
<H1 class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-outline-level: 4" align=left><SPAN style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">一个 <I>缓存区域</I> 被定义为一个组织起来的命名空间用于容纳<FONT style="BACKGROUND-COLOR: #ffffff" color=#800080>一组缓存对象集合</FONT>。你需要在配置文件中定义缓存区域来实现在一块单独的内存空间中存储数据，管理缓存数据的有效期限。如果需要的话，对<FONT color=#800080>有相似特征的对象（例如：生存时间和业务用途）应该被缓存在相同的缓存区域中，让它们可以在相同的时间失效</FONT>。我定义了分离的缓存区域来存储静态数据和小变动数据。为了避免同步操作带来的效率影响，我对每个缓存区域使用了单独的</SPAN><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: #003366; FONT-FAMILY: 'Courier New'; mso-font-kerning: 0pt">Cache</SPAN><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体"> (</SPAN><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: #003366; FONT-FAMILY: 'Courier New'; mso-font-kerning: 0pt">JCS</SPAN><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">) </SPAN><SPAN style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">实例。<BR></H1><SPAN lang=EN-US><o:p></o:p></SPAN></SPAN>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-outline-level: 4" align=left><B><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: #003366; FONT-FAMILY: 'Courier New'; mso-font-kerning: 0pt"><BR>CacheElementInfo</SPAN></B><B><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: Verdana; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体"><o:p></o:p></SPAN></B></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto" align=left><SPAN style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">该类用于封装所有的缓存统计信息（例如：命中数、不中数、命中比例等），用来监测在<SPAN lang=EN-US>web</SPAN>应用中所有缓存对象的效率。<SPAN lang=EN-US><o:p></o:p></SPAN></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-outline-level: 3" align=left><B><SPAN style="FONT-SIZE: 14pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-ascii-font-family: Verdana; mso-hansi-font-family: Verdana; mso-bidi-font-family: 宋体">编译</SPAN></B><B><SPAN lang=EN-US style="FONT-SIZE: 14pt; COLOR: black; FONT-FAMILY: Verdana; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">, </SPAN></B><B><SPAN style="FONT-SIZE: 14pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-ascii-font-family: Verdana; mso-hansi-font-family: Verdana; mso-bidi-font-family: 宋体">构建</SPAN></B><B><SPAN lang=EN-US style="FONT-SIZE: 14pt; COLOR: black; FONT-FAMILY: Verdana; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">, </SPAN></B><B><SPAN style="FONT-SIZE: 14pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-ascii-font-family: Verdana; mso-hansi-font-family: Verdana; mso-bidi-font-family: 宋体">和单元测试</SPAN></B><B><SPAN style="FONT-SIZE: 14pt; COLOR: black; FONT-FAMILY: Verdana; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体"> <SPAN lang=EN-US><o:p></o:p></SPAN></SPAN></B></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto" align=left><SPAN style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">我用<SPAN lang=EN-US>Ant</SPAN>创建了一个构建脚本来编译我的对象缓存框架的所有代码。<SPAN lang=EN-US>Ant</SPAN>的构建脚本<I><SPAN lang=EN-US>build.xml</SPAN></I><SPAN lang=EN-US>, </SPAN>放在<I><SPAN lang=EN-US>WEB-INF\classes</SPAN></I><SPAN lang=EN-US> </SPAN>目录下。我还编写了<SPAN lang=EN-US>Junit</SPAN>测试客户端来测试使用<SPAN lang=EN-US>web</SPAN>门户缓存框架的不同缓存场景。测试脚本</SPAN><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: #003366; FONT-FAMILY: 'Courier New'; mso-font-kerning: 0pt">CachingTestCase</SPAN><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">, </SPAN><SPAN style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">放在<I><SPAN lang=EN-US>WEB-INF\classes\common\caching\test</SPAN></I><SPAN lang=EN-US> </SPAN>目录下。解压缩示例代码到一个新的<SPAN lang=EN-US>web</SPAN>应用目录，如果要验证<SPAN lang=EN-US>Junit</SPAN>测试脚本，从命令行运行以下命令：<SPAN lang=EN-US><o:p></o:p></SPAN></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto" align=left><SPAN style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">切换当前目录到<I><SPAN lang=EN-US>%TOMCAT_HOME%/webapps/web-app-name/WEB-INF/classes</SPAN></I><SPAN lang=EN-US> </SPAN>（在<SPAN lang=EN-US>Unix</SPAN>测试环境中，目录应该是<I><SPAN lang=EN-US>$TOMCAT_HOME/webapps/web-app-name/WEB-INF/classes</SPAN></I>）。<SPAN lang=EN-US><o:p></o:p></SPAN></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto" align=left><SPAN style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">运行以下命令：<SPAN lang=EN-US><o:p></o:p></SPAN></SPAN></P>
<UL type=disc>
<LI class=MsoNormal style="MARGIN: 0cm 0cm 0pt; COLOR: black; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l2 level1 lfo1; tab-stops: list 36.0pt"><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: #003366; FONT-FAMILY: 'Courier New'; mso-font-kerning: 0pt">ant common.compile</SPAN><SPAN lang=EN-US style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体"><BR></SPAN><SPAN style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">编译缓存框架中所有的<SPAN lang=EN-US>Java</SPAN>类。<SPAN lang=EN-US><o:p></o:p></SPAN></SPAN> 
<LI class=MsoNormal style="MARGIN: 0cm 0cm 0pt; COLOR: black; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l2 level1 lfo1; tab-stops: list 36.0pt"><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: #003366; FONT-FAMILY: 'Courier New'; mso-font-kerning: 0pt">ant common.runjunit</SPAN><SPAN lang=EN-US style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体"><BR></SPAN><SPAN style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">用于运行<SPAN lang=EN-US>Junit</SPAN>测试脚本。测试脚本使用<SPAN lang=EN-US>Log4J API</SPAN>来显示所有的输出信息。<SPAN lang=EN-US><o:p></o:p></SPAN></SPAN></LI></UL>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-outline-level: 3" align=left><B><SPAN style="FONT-SIZE: 14pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-ascii-font-family: Verdana; mso-hansi-font-family: Verdana; mso-bidi-font-family: 宋体">考虑<st1:PersonName w:st="on">何</st1:PersonName>时使用对象缓存的指导方针</SPAN></B><B><SPAN lang=EN-US style="FONT-SIZE: 14pt; COLOR: black; FONT-FAMILY: Verdana; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体"><o:p></o:p></SPAN></B></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto" align=left><SPAN style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">当你决定要在你的<SPAN lang=EN-US>web</SPAN>应用中缓存一些特定类别的数据的时候，请参照这些指导方针。缓存的应用应该经过谨慎地考虑，只有当其它方法，如：数据访问等，已经无法再进一步改进的时候才需要使用缓存。缓存将会带来复杂性，让维护工作变得更加复杂。因此，必须统筹考虑性能和缓存带来的复杂性的平衡关系。<SPAN lang=EN-US><o:p></o:p></SPAN></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto" align=left><SPAN style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">当考虑使用缓存的时候，需要考虑对象的预定执行时间和刷新率或者叫做对象的生存时间。缓存不能容纳所有我们想要存储的数据，因此缓存使用的内存及时得到释放，即可以通过定义合理的生存时间实现，也可以在数据不再需要的时候显式地释放被缓存的对象。可以指定缓存算法如最近被访问算法（<SPAN lang=EN-US>LRU</SPAN>）或者最少被使用算法（<SPAN lang=EN-US>LFU</SPAN>）以便缓存基于访问频率来释放对象。<SPAN lang=EN-US>Jack Shirazi</SPAN>的著作 <SPAN lang=EN-US><A href="http://www.oreilly.com/catalog/javapt2/index.html?CMP=IL7015">Java <SPAN lang=EN-US><SPAN lang=EN-US>性能调整</SPAN></SPAN></A> </SPAN>提供了一个关于缓存主题的非常有趣的讨论，讨论了什么类型的数据应该被缓存，以及<st1:PersonName w:st="on">何</st1:PersonName>时使用缓存的建议。<SPAN lang=EN-US><o:p></o:p></SPAN></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto" align=left><SPAN style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">注意缓存框架并没有处理在<SPAN lang=EN-US>web</SPAN>应用中需要被缓存的对象的创建（例如：从数据源检索数据的数据访问逻辑并没有在缓存类中编写）。这要依赖于客户程序来定义真正的数据访问逻辑。像<SPAN lang=EN-US>Java</SPAN>数据对象等技术通常用于在企业级<SPAN lang=EN-US>web</SPAN>应用中封装数据访问逻辑。参考<SPAN lang=EN-US>O'Reilly</SPAN>的 <SPAN lang=EN-US><A href="http://www.oreilly.com/catalog/jvadtaobj/index.html?CMP=IL7015">Java <SPAN lang=EN-US><SPAN lang=EN-US>数据对象</SPAN></SPAN></A> </SPAN>来学习更多的关于如<st1:PersonName w:st="on">何</st1:PersonName>将数据访问层与业务逻辑层分离的知识。<SPAN lang=EN-US><o:p></o:p></SPAN></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-outline-level: 3" align=left><B><SPAN style="FONT-SIZE: 14pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-ascii-font-family: Verdana; mso-hansi-font-family: Verdana; mso-bidi-font-family: 宋体">结论</SPAN></B><B><SPAN lang=EN-US style="FONT-SIZE: 14pt; COLOR: black; FONT-FAMILY: Verdana; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体"><o:p></o:p></SPAN></B></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto" align=left><SPAN style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">本文提供了对使用<SPAN lang=EN-US>Jakarta</SPAN>的<SPAN lang=EN-US>Java</SPAN>缓存系统（<SPAN lang=EN-US>JCS</SPAN>）来为<SPAN lang=EN-US>web</SPAN>门户应用开发对象缓存框架的概要介绍。该框架非常稳定并可以被重用于其它任<st1:PersonName w:st="on">何</st1:PersonName><SPAN lang=EN-US>web</SPAN>应用，甚至可以用于客户<SPAN lang=EN-US>/</SPAN>服务器模式的<SPAN lang=EN-US>Java</SPAN>应用程序。本文详细介绍了<SPAN lang=EN-US>web</SPAN>门户缓存框架的工作原理，并提供了<SPAN lang=EN-US>Junit</SPAN>测试脚本来对不同场景的缓存框架进行测试。<SPAN lang=EN-US><o:p></o:p></SPAN></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto" align=left><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">JCS was built as a system close to JCACHE Java Temporary Caching API (<A href="http://www.jcp.org/en/jsr/detail?id=107">JSR-107</A>), a description of the caching system used in Oracle 9i and other popular caching frameworks. </SPAN><SPAN style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">该规范可能会在将来的<SPAN lang=EN-US>JDK</SPAN>发行版本中作为一种<SPAN lang=EN-US>Java</SPAN>扩展框架。我的其中一个目的就是让<SPAN lang=EN-US>web</SPAN>门户缓存框架与<SPAN lang=EN-US>JCS</SPAN>保持松散耦合。这样的话，如果我将来需要转换到另一种框架（例如<SPAN lang=EN-US>Jcache</SPAN>），我可以在对<SPAN lang=EN-US>web</SPAN>门户应用客户程序代码不做大的调整的情况下完成切换。<SPAN lang=EN-US><o:p></o:p></SPAN></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto" align=left><SPAN style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">我现在通过记录缓存监测信息的日志（使用<SPAN lang=EN-US>Log4J API</SPAN>）如：命中数、不中数、命中率来衡量缓存的效率。可能将来有其它参数需要被监测来衡量缓存的效率。同样的，用来测量使用或不使用缓存对数据访问的反馈时间，应该使用一些负载测试工具如：<SPAN lang=EN-US>Grinder</SPAN>或者<SPAN lang=EN-US>Jmeter</SPAN>来测试其伸缩性和性能效果。<SPAN lang=EN-US><o:p></o:p></SPAN></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto" align=left><SPAN style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">在机群环境下保持缓存同步将是一个挑战，因为每个<SPAN lang=EN-US>servlet</SPAN>容器将会在自己的<SPAN lang=EN-US>JVM</SPAN>中拥有一个缓存管理器实例。解决该问题的方法就是创建消息驱动<SPAN lang=EN-US>Bean</SPAN>（<SPAN lang=EN-US>MDB</SPAN>）在需要刷新数据的时候通知所有的缓存管理器。<SPAN lang=EN-US><o:p></o:p></SPAN></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto" align=left><SPAN style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">通常的对象查找方法，如：简单的<SPAN lang=EN-US>Hashtable</SPAN>、<SPAN lang=EN-US>JNDI</SPAN>甚至是<SPAN lang=EN-US>EJB</SPAN>，提供了在内存中存放对象并通过键值查找对象的方法。但是任<st1:PersonName w:st="on">何</st1:PersonName>一种方法都没有提供当对象不需要的时候从内存中移出的机制，或者当访问对象迟于对象存放期限的时候自动创建对象的机制。</SPAN><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: #003366; FONT-FAMILY: 'Courier New'; mso-font-kerning: 0pt">HttpSession</SPAN><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体"> </SPAN><SPAN style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">对象<SPAN lang=EN-US> (in the servlet package) </SPAN>也允许对象被缓存，但是它没有共享、失效、单一对象存放期满、自动装载或者<SPAN lang=EN-US>spooling </SPAN>这些缓存框架需要具备的基础机制。<SPAN lang=EN-US><o:p></o:p></SPAN></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto" align=left><SPAN style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">虽然将缓存功能集成到<SPAN lang=EN-US>web</SPAN>应用中需要额外的设计和开发工作，但我认为缓存带来的利益大于额外付出的工作。我已经看到在我实现了缓存框架之后 ，我的<SPAN lang=EN-US>web</SPAN>应用的性能有很大的提高，特别是在访问静态数据和查找结果方面。该<SPAN lang=EN-US>web</SPAN>应用模块目前处于测试阶段。在不久的将来，我会提供一些性能方面的测试数据（包括使用与不使用缓存的情况）来比较缓存如<st1:PersonName w:st="on">何</st1:PersonName>帮助设计更快、更具伸缩性的<SPAN lang=EN-US>web</SPAN>应用。<SPAN lang=EN-US><o:p></o:p></SPAN></SPAN></P>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-outline-level: 3" align=left><B><SPAN style="FONT-SIZE: 14pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-ascii-font-family: Verdana; mso-hansi-font-family: Verdana; mso-bidi-font-family: 宋体">示例代码</SPAN></B><B><SPAN lang=EN-US style="FONT-SIZE: 14pt; COLOR: black; FONT-FAMILY: Verdana; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体"><o:p></o:p></SPAN></B></P>
<UL type=disc>
<LI class=MsoNormal style="MARGIN: 0cm 0cm 0pt; COLOR: black; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l0 level1 lfo2; tab-stops: list 36.0pt"><SPAN lang=EN-US style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体"><A href="http://www.onjava.com/onjava/2003/12/23/examples/cachingcode.zip">cachingcode.zip</A> <o:p></o:p></SPAN></LI></UL>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-outline-level: 3" align=left><B><SPAN style="FONT-SIZE: 14pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-ascii-font-family: Verdana; mso-hansi-font-family: Verdana; mso-bidi-font-family: 宋体">参考资源</SPAN></B><B><SPAN lang=EN-US style="FONT-SIZE: 14pt; COLOR: black; FONT-FAMILY: Verdana; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体"><o:p></o:p></SPAN></B></P>
<UL type=disc>
<LI class=MsoNormal style="MARGIN: 0cm 0cm 0pt; COLOR: black; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l1 level1 lfo3; tab-stops: list 36.0pt"><SPAN lang=EN-US style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体"><A href="http://www.oreilly.com/catalog/javapt/">Java <SPAN lang=EN-US><SPAN lang=EN-US>性能调整</SPAN></SPAN></A>, Jack Shirazi, O'Reilly <o:p></o:p></SPAN>
<LI class=MsoNormal style="MARGIN: 0cm 0cm 0pt; COLOR: black; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l1 level1 lfo3; tab-stops: list 36.0pt"><SPAN lang=EN-US style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体"><A href="http://jakarta.apache.org/turbine/jcs/index.html">JCS <SPAN lang=EN-US><SPAN lang=EN-US>主页</SPAN></SPAN><SPAN lang=EN-US><SPAN lang=EN-US> </SPAN></SPAN></A><o:p></o:p></SPAN>
<LI class=MsoNormal style="MARGIN: 0cm 0cm 0pt; COLOR: black; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l1 level1 lfo3; tab-stops: list 36.0pt"><SPAN lang=EN-US style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体"><A href="http://sourceforge.net/projects/jcache">JCache API</A> <o:p></o:p></SPAN>
<LI class=MsoNormal style="MARGIN: 0cm 0cm 0pt; COLOR: black; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l1 level1 lfo3; tab-stops: list 36.0pt"><SPAN lang=EN-US style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体"><A href="http://www.opensymphony.com/oscache/">OSCache</A> <o:p></o:p></SPAN>
<LI class=MsoNormal style="MARGIN: 0cm 0cm 0pt; COLOR: black; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l1 level1 lfo3; tab-stops: list 36.0pt"><SPAN lang=EN-US style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体"><A href="http://www.spiritsoft.com/products/cache/introducing.shtml">SpiritCache</A> <o:p></o:p></SPAN>
<LI class=MsoNormal style="MARGIN: 0cm 0cm 0pt; COLOR: black; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l1 level1 lfo3; tab-stops: list 36.0pt"><SPAN lang=EN-US style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体"><A href="http://jakarta.apache.org/commons/collections.html">Commons Collections</A> <o:p></o:p></SPAN>
<LI class=MsoNormal style="MARGIN: 0cm 0cm 0pt; COLOR: black; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l1 level1 lfo3; tab-stops: list 36.0pt"><SPAN lang=EN-US style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体"><A href="http://www.tangosol.com/coherence.jsp">Coherence</A> <o:p></o:p></SPAN>
<LI class=MsoNormal style="MARGIN: 0cm 0cm 0pt; COLOR: black; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto; mso-list: l1 level1 lfo3; tab-stops: list 36.0pt"><SPAN lang=EN-US style="FONT-SIZE: 12pt; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体"><A href="http://www.jcp.org/aboutJava/communityprocess/jsr/cacheFS.pdf">Object Caching Service for Java</A> <o:p></o:p></SPAN></LI></UL>
<P class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left; mso-pagination: widow-orphan; mso-margin-top-alt: auto; mso-margin-bottom-alt: auto" align=left><I><SPAN lang=EN-US style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体"><A href="http://www.onjava.com/pub/au/1418">Srini Penchikala</A> </SPAN></I><I><SPAN style="FONT-SIZE: 12pt; COLOR: black; FONT-FAMILY: 宋体; mso-font-kerning: 0pt; mso-bidi-font-family: 宋体">是一名软件咨询顾问，当前为<SPAN lang=EN-US>Computer Consultants of America, Inc. </SPAN>工作。</SPAN></I></P></SPAN></TD></TR></TBODY></TABLE><img src ="http://www.blogjava.net/kapok/aggbug/5335.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-05-30 15:22 <a href="http://www.blogjava.net/kapok/archive/2005/05/30/5335.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Some Parent-Child Principles</title><link>http://www.blogjava.net/kapok/archive/2005/05/24/5134.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Tue, 24 May 2005 12:56:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/05/24/5134.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/5134.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/05/24/5134.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/5134.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/5134.html</trackback:ping><description><![CDATA[<DIV><A name=A1></A>
<H1><A href="http://www.hibernate.org/209.html">http://www.hibernate.org/209.html</A></H1>
<H1>Some Parent-Child Principles</H1><A name=A2></A>
<H2>Getting Parents and Children to Play Nice</H2>
<P>If you browse the Hibernate Discussion forum <A href="http://forum.hibernate.org/"><FONT color=#256b87>http://forum.hibernate.org</FONT></A> you'll notice that about 50% of the questions are answered by RTFM, referencing the Parent/Child example in chapter 16 of the Hibernate Reference <A href="http://www.hibernate.org/hib_docs/reference/en/html."><FONT color=#256b87>http://www.hibernate.org/hib_docs/reference/en/html.</FONT></A></P>
<P>In addition, here are some of the basic principles and pitfalls I've discovered, along with an example. Corrections are most welcome ... this is a repost from our internal blog</P>
<P>First ... the situation. Let's use two classes called <STRONG>ParentClass</STRONG> and <STRONG>jobs</STRONG> as an example</P>
<P>We have (pseudocode):</P><PRE class=code>ParentClass {
     Set jobs;
}
</PRE>
<P>as our parent, and:</P><PRE class=code>ChildJobClass implements GenericJob {
     ParentClass parent;
}
</PRE>
<P>as our child.</P>
<P>One context can have zero to many jobs, hence the set of jobs. One notification has but one parent.</P>
<P><STRONG>PRINCIPLE 1: Your job is to set up native object references</STRONG></P>
<P>1. All the references in these classes are <STRONG>object</STRONG> references. The most common place to go wrong is trying to manage object references as pointers or integers in your code. Don't do that! Translating the object references into serialized pointers is Hibernate's job. Your job is to make sure the native Java structures are correct on an object level, e.g. that <STRONG>ParentClass</STRONG> contains a real live set of <STRONG>ChildJobClass</STRONG> objects, not their ID's, and that <STRONG>ChildJobClass</STRONG> objects have a real live parent object which is an instance of the <STRONG>ParentClass</STRONG> class.</P>
<P>2. Next, keep in your mind the idea that, while you do not manage the relational mapping of the object references, you <STRONG>do</STRONG> need to manage the object reference itself. You must go beyond declaring <STRONG>ParentClass parent</STRONG> and actually make a call to *ChildJobClass.setParent(myParentClass) * in your code. This is mostly done at construction time, so you'll probably have a constructor like this:</P><PRE class=code>    public ChildJobClass (ParentClass thisParent)
    {
        setParent(thisParent);
    }
</PRE>
<P>Note that the class that is constructing the notification is responsible for passing in a valid parent <STRONG>ParentClass</STRONG> to the constructor. Note also that the constructor itself is responsible for assigning that parent <STRONG>ParentClass</STRONG> to the parent property of the <STRONG>ChildJobClass</STRONG>. Fail either step and you'll get "cannot insert" or not null integrity constraint problems.</P>
<P><STRONG>Principle 2: Hibernate's job is to read *.hbm.xml maps and translate your object references into crossreferences in the database</STRONG></P>
<P>This means that your code does <STRONG>not</STRONG> (at the risk of being redundant) mess around with integer values representing object identifiers. Each of your mapped objects will <STRONG>have</STRONG> an identifier, complete with getter and setter, but unless you move into special features of the id mapping (and you should not!), you do not actually call those setters and getters. Hibernate does.</P>
<P>All your help to Hibernate comes in the area of mapping, e.g. *.hbm.xml files. That's where you inform hibernate of the structure of your objects and how that structure maps to database tables and columns</P>
<P><STRONG>Principle 3: Most parent-child relationships should be mapped as bidirectional</STRONG></P>
<P>Generally speaking you'll want parent objects to have their kids in place when constructed and child objects to have their parents when constructed. If you're concerned about overhead you can defer the construction of the references to the moment they are needed using lazy initialization, outside the scope of this note. The most common form of bidirectional parent child mapping is the Basic Collection pattern, which maps one parent to many children, many children to one parent. You can also do many-to-many on the child side -- see the excellent Index of Relationships <A href="http://www.xylax.net/hibernate/"><FONT color=#256b87>http://www.xylax.net/hibernate/</FONT></A> page for examples. We'll deal with basic here.</P>
<P>In the Basic Collection pattern the mapping works like this:</P>
<P>1. The parent maps the set containing the children to the appropriate child table as a one-to-many relationship, e.g. one parent to many children.</P>
<P>2. The parent marks the relationship as "inverse". This attribute says that the parent doesn't actually update the relationship; the child updates the relationship. We do this so that we can deal with "NOT NULL" constraints on the child side.</P>
<P>So far our parent-side mapping looks like this:</P><PRE class=code> &lt;set name="jobs" table="GENERIC_JOB" lazy="false" cascade="all" inverse="true" &gt;
   &lt;key column="PARENT_ID"/&gt;
   &lt;one-to-many class="org.mitretek.MyApplication.workflow.job.ChildJobClass"/&gt;
 &lt;/set&gt;
</PRE>
<P>Which says:</P>
<UL>
<LI>The property of the parent object is called jobs, and is a set class 
<LI>The objects in the jobs set are stored in the GENERIC_JOB table 
<LI>Hibernate uses the PARENT_ID column of the GENERIC_JOB table to store the identifier of the parent object 
<LI>The class to be stored in the GENERIC_JOB table is the ChildJobClass class</LI></UL>
<P>The class property might give you pause ... you might expect a GenericJob class. Ordinarily you'd be right; in our specific example GenericJob is really an interface that ChildJobClass and a few other classes implement, and you're looking at a polymorphic persistence situation. We'll address that later. Pretend you entered the ChildJobClass table if you want.</P>
<P>3. On the child end, map the child back to the parent using a many-to-one relationship. This relationship actually manages the link to and from parent.</P><PRE class=code>    &lt;many-to-one name="parent" class="org.mitretek.MyApplication.workflow.ParentClass" column="parent_id" not-null="true" /&gt;
</PRE>
<P>There are a few things to note in this simple snip of mapping:</P>
<UL>
<LI>The many-to-one mapping is <STRONG>instead of</STRONG> a <STRONG>&lt;property .../&gt;</STRONG> mapping for the parent property, not along with! 
<LI>The name attribute lists the name of the property of the child where the parent is inserted. As we said earlier, this is done by getters and setters on the child, and a genuine Java property of the appropriate parent class in the child object. Be sure to put a parent object in the property at construction or another appropriate time before persistance</LI></UL>
<UL>
<LI>The parent_id column must exist in the child table. The child table isn't referenced because the class that is being mapped (it happens to be in GenericJob.hbm.xml) has already indicated tables.</LI></UL>
<UL>
<LI>Not null is enforced here.</LI></UL>
<P>That's pretty much it. At persistance time, assuming you're persisting a <STRONG>ParentClass</STRONG> with a few <STRONG>ChildJobClasss</STRONG> nested in a set property the following activities happen (not necessarily in order):</P>
<UL>
<LI>Hibernate notices from the parent mapping that the jobs need to be mapped to the GENERIC_JOB table using the PARENT_ID column of GENERIC_JOB, but that the actual maintenance of the insert is done from the child end.</LI></UL>
<UL>
<LI>Hibernate notices from the child mapping that the object being mapped to the GENERIC_JOB table is called this.parent, and that its ID indeed goes into the PARENT_ID column, and furthermore it must not be null</LI></UL>
<UL>
<LI>Hibernate transacts enough SQL to get identifiers for any not-yet-persisted objects. This is the big reason you don't mess directly with identifiers.</LI></UL>
<UL>
<LI>Hibernate persists the parent and automatically persists its kids in the same transaction, being sure to set the parent's identifier in the parent_id field of the child so that it can be reconstituted in a future query.</LI></UL>
<P><STRONG>Principle 4: Deal with Polymorphic Persistence from the superclass and the subclass will take care of itself</STRONG></P>
<P>We mentioned earlier that GenericJob was really an interface that ChildJobClass and a bunch of other classes implement for different job types. We used the table-per-subclass mapping strategy for this, which is outside the scope of this note, except to point out that the strategy implements as a join from a superclass GENERIC_JOB table which contains the properties of the interface or top level class, to each subclass table, such as NOTIF_JOB for notification jobs.</P>
<P>The point here is that you <STRONG>do not</STRONG> deal directly with the subclass tables -- that too is Hibernate's job. Deal with the top-level table but indicate which underlying class is being persisted. Hibernate will extend the record across the join automatically.</P>
<P>For that reason we mapped our notification to the GENERIC_JOB table but indicated it was implemented as a ChildJobClass.</P>
<P>Hibernate stored the interface attributes in GENERIC_JOB and the child-specific nonInterface attributes in the NOTIF_JOB table.</P></DIV>
<DIV><BR>
<TABLE style="BACKGROUND: #a0a0a0; WIDTH: 100%" cellSpacing=0 cellPadding=0 border=0>
<TBODY>
<TR>
<TD width="100%">&nbsp;</TD>
<TD onmouseup="fireClickEventOnChild(this, 'A')" class=topNav2 onmouseover="mover(this, '#cccccc')" onmouseout="mout(this, '#a0a0a0')" vAlign=center><A onfocus="if (this.blur) this.blur()" href="http://www.hibernate.org/209.html?comid=0&amp;cmd=newcom"><SPAN class=topNav2Link><FONT face=Verdana color=#ffffff size=1>NEW&nbsp;COMMENT</FONT></SPAN></A> </TD></TR></TBODY></TABLE><BR>
<TABLE style="BACKGROUND: #cccccc" cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD style="FLOAT: left; MARGIN-LEFT: 4px; WIDTH: 100%" noWrap><A href="http://www.hibernate.org/209.378.html"><FONT color=#256b87>Fills in the gaps</FONT></A> 
<DIV></DIV>
<TD class=label style="WIDTH: 150px; COLOR: black" noWrap>01 Dec 2004, 11:56 
<DIV></DIV>
<TD class=label style="COLOR: black" noWrap>mungo@knotwise 
<DIV></DIV></TD>
<TR>
<TD style="PADDING-RIGHT: 4px; PADDING-LEFT: 4px; BACKGROUND: #eeeeee; PADDING-BOTTOM: 4px; PADDING-TOP: 4px" colSpan=3><PRE>Maybe you'd eliminate 50% of the RTFMs if you'd put this content into
the reference manual. This fills in lots of gaps.</PRE></TD></TR>
<TR>
<TD bgColor=#ffffff colSpan=3>&nbsp;</TD></TR>
<TR>
<TD style="FLOAT: left; MARGIN-LEFT: 4px; WIDTH: 100%" noWrap><A href="http://www.hibernate.org/209.434.html"><FONT color=#256b87>Physical RI on tables</FONT></A> 
<DIV></DIV>
<TD class=label style="WIDTH: 150px; COLOR: black" noWrap>16 Feb 2005, 16:10 
<DIV></DIV>
<TD class=label style="COLOR: black" noWrap>rpruthee 
<DIV></DIV></TD>
<TR>
<TD style="PADDING-RIGHT: 4px; PADDING-LEFT: 4px; BACKGROUND: #eeeeee; PADDING-BOTTOM: 4px; PADDING-TOP: 4px" colSpan=3><PRE>I am trying to do an insert in the parent and child with no 
Referrential integrity. Hibernated inserts a row in the parent table 
and in the child but it puts 0 in the FK column in the child table. I 
have checked eveything and I could not find any problems with the code 
and the mapping.

Also when I try to insert multiple child rows in the db, Hibernate 
throws NonUniqueObjectException.

Any ideas? Is it because no physical FK constraints have been defined.
Thanks.</PRE></TD></TR>
<TR>
<TD bgColor=#ffffff colSpan=3>&nbsp;</TD></TR></TBODY></TABLE></DIV><img src ="http://www.blogjava.net/kapok/aggbug/5134.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-05-24 20:56 <a href="http://www.blogjava.net/kapok/archive/2005/05/24/5134.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>HyperJAXB - relational persistence for XML objects with JAXB and Hibernate</title><link>http://www.blogjava.net/kapok/archive/2005/05/24/5133.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Tue, 24 May 2005 12:30:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/05/24/5133.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/5133.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/05/24/5133.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/5133.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/5133.html</trackback:ping><description><![CDATA[<H1><A href="http://www.hibernate.org/218.html">http://www.hibernate.org/218.html</A></H1>
<H1>HyperJAXB - relational persistence for XML objects with JAXB and Hibernate</H1><A name=A2></A>
<H2>What is HyperJAXB?</H2>
<P>HyperJAXB is an add-on for Sun's reference implementation of JAXB (Java Architecture for XML Binding). It provides JAXB objects with relational persistence layer using Hibernate.</P><A name=A3></A>
<H2>What can I do with HyperJAXB?</H2>
<P>HyperJAXB eases usage of XML with relational databases. With HyperJAXB you can combine JAXB and Hibernate to implement one of the following target usage scenarios:</P>
<UL>
<LI>load (unmarshal) object structure from XML document and save (persist) these objects in a relational database; 
<LI>load objects from a relational database and present (marshal) loaded objects in XML form; 
<LI>query relational database using HQL and present query results in XML.</LI></UL><A name=A4></A>
<H2>How it works?</H2>
<P>JAXB is basically a code generation engine. It accepts a schematic definition of you XML documents (usually in the form of an XML Schema) as input and generates source code of XML-enabled classes. HyperJAXB augments generated code by adding Hibernate XDoclet annotations. Added annotations effectively define Hibernate O/R mapping for the generated classes.</P>
<P>Combination of JAXB RI, HyperJAXB and Hibernate toolset allows you automatically generate the following artifacts out of you XML Schema:</P>
<UL>
<LI>source code of XML-enabled objects with Hibernate XDoclet annotations; 
<LI>object/relational mapping for Hibernate; 
<LI>database schema for the target database.</LI></UL>
<P>Please see <A href="https://hyperjaxb.dev.java.net/doc/reference/en/html/"><FONT color=#256b87>the reference</FONT></A> for more information.</P><A name=A5></A>
<H2>A code example?</H2>
<P>Unmarshalling and saving:</P><PRE class=code>// Unmarshall the document
final MyObject myObject = (MyObject) unmarshaller.unmarshal(document);

// Open the session, save object into the database
final Session saveSession = sessionFactory.openSession();
// Save id for the later use
final String id = saveSession.save(myObject);
saveSession.flush();
// Close the session
saveSession.close()
</PRE>
<P>Loading and marshalling:</P><PRE class=code>// Open the session, load the object
final Session loadSession = sessionFactory.openSession();
final MyObject myLoadedObject = (MyObject) loadSession.load(MyObject.class, id);
loadSession.close();

// Marshall loaded object into the document
final Document loadedDocument = documentBuilder.newDocument();
marshaller.marshal(myLoadedObject, loadedDocument);
</PRE>
<P>Mapping generated by HyperJAXB ensures that <TT><FONT size=2>document</FONT></TT> and <TT><FONT size=2>loadedDocument</FONT></TT> are identical.</P><A name=A6></A>
<H2>Where can I find it?</H2>
<P>Check out this project on <A href="https://hyperjaxb.dev.java.net/"><FONT color=#256b87>https://hyperjaxb.dev.java.net</FONT></A>.</P><img src ="http://www.blogjava.net/kapok/aggbug/5133.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-05-24 20:30 <a href="http://www.blogjava.net/kapok/archive/2005/05/24/5133.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Adding a Hibern8IDE browser to your Hibernate Project</title><link>http://www.blogjava.net/kapok/archive/2005/05/24/5114.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Tue, 24 May 2005 07:49:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/05/24/5114.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/5114.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/05/24/5114.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/5114.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/5114.html</trackback:ping><description><![CDATA[<DIV class=entry>
<H3 class=entry><A href="http://blog.ideoplex.com/software/2003/12/11.html">http://blog.ideoplex.com/software/2003/12/11.html</A></H3>
<H3 class=entry><A class=weblogItemTitle href="http://blog.ideoplex.com/software/2003/12/11.html#a736">Adding a Hibern8IDE browser to your Hibernate Project</A></H3>
<P>The <A href="http://www.xam.dk/hibern8ide/">Hibern8 IDE</A> is a quick'n'dirty interactive Hibernate Query Language (HQL) tool. And while it's not really an IDE, it's a pretty slick way of viewing the contents of your persistent Hibernate data store. There are basically two ways of opening up your data store with Hibern8IDE: you can interactively add hibernate properties and configuration files or you can programmatically start Hibern8IDE with a hibenate configuration object. Since I hate to constantly repeat myself, I opted for the programmatic option. </P>
<P>If you've been following my series on Hibernate, then you know that I've been keeping all my configuration files in the cfg directory tree. So I decided to simply traverse the cfg directory tree, adding all .hbm.xml files to a configuration object before passing the configuration object on to Hibern8IDE. And since traversing a directory is a pretty common operation, I decided to implement a general solution consisting of a simple TreeWalker class and a TreeAction interface. </P>
<P><I>14 May 04: Max Andersen from the Hibernate team comments that the load order of .hbm.xml files is important when a subclass or joined-subclass is defined in a separate file from the parent. If the parent is defined in parent.hbm.xml and the child is defined in child.hbm.xml, then parent.hbm.xml must be loaded before child.hbm.xml. </I></P><PRE class=code>package util; <BR> <BR>public interface TreeAction {
    public void handle( java.io.File f );
}
</PRE><PRE class=code>package util;<BR> <BR>import java.io.File;<BR> <BR>public class TreeWalker {<BR> <BR>    public void walk ( TreeAction actor, File root )
    {
        if ( root.exists() ) {
            if ( root.isDirectory() ) {
                File[] files = root.listFiles();<BR> <BR>                for ( int i=0 ; i&lt;files.length ; ++i ) {
                    if ( files[i].isDirectory() ) {
                        this.walk( actor, files[i] );
                    }
                    else if ( files[i].isFile() ) {
                        actor.handle( files[i] );
                    }
                }
            }
            else if ( root.isFile() ) {
                actor.handle( root );
            }
        }
    }
}
</PRE>
<P>The top level Ide class treats each command line argument as a configuration root directory It contains a static inner class Action implementing TreeAction that adds each .hbm.xml file to the hibernate configuration. And we hand the configuration object to Hibern8IDE once all the configuration directories have been traversed. </P>
<P>Note that static inner class Action has to catch the hibernate MappingException to match the TreeAction interface. After catching the MappingException, we use it to create a RuntimeException. This trick of <A href="http://www.tbray.org/ongoing/When/200x/2003/05/08/FutureLanguage">wrapping checked exceptions in a RuntimeException</A> comes courtesy of Tim Bray. This is an appropriate response because our only other choice is to ignore it entirely. </P><PRE class=code>import java.io.File;<BR> <BR>import net.sf.hibernate.cfg.Configuration;
import net.sf.hibernate.MappingException;
import net.sf.hibernate.HibernateException;<BR> <BR>import net.sf.hibern8ide.Hibern8IDE;<BR> <BR>import util.*;<BR> <BR>public class Ide {<BR> <BR>    static class Action implements TreeAction {
        private Configuration config;<BR> <BR>        public Action ( Configuration aConfig )
        {
            config = aConfig;
        }<BR> <BR>        public void handle ( File f )
        {
            String name = f.toString();
            if ( name.endsWith( ".hbm.xml" ) ) {
                try {
                    config.addFile( f );
                } catch ( MappingException e ) {
                    throw new RuntimeException( e );
                }
            }
        }
    }<BR> <BR>    public static void main ( String[] args )
        throws MappingException,
               HibernateException
    {
        Configuration cfg = new Configuration();
        Action      actor = new Action( cfg );
        TreeWalker walker = new TreeWalker();<BR> <BR>        for ( int i=0 ; i&lt;args.length ; ++i ) {
            File f = new File( args[i] );
            walker.walk( actor,f );
        }<BR> <BR>        Hibern8IDE.startWith( cfg );
    }
}
</PRE>
<P>Finally, we add a new ide action to our build file (other minor buildfile changes not shown here). The single command line argument references our configuration directory. </P><PRE class=code>  ...
  &lt;target name="ide" depends="compile,setup-run"&gt;
    &lt;java classname="Ide"
          fork="true"&gt;
      &lt;classpath refid="classpath.ide" /&gt;
      &lt;arg value="${cfg-dir}" /&gt;
      &lt;sysproperty key="log4j.configuration" value="${LOG4J_OUT}" /&gt;
    &lt;/java&gt;
  &lt;/target&gt;
  ...
</PRE>
<P>Now we can launch the Hibern8IDE query tool by simply typing "ant ide". Here's the <A href="http://blog.ideoplex.com/software/java/hibernate/hibern8ide.zip">source for adding a Hibern8IDE query tool to your Hibernate project.</A>. </P><BR>
<P><STRONG>Disclaimer:</STRONG> I don't claim to be an expert on hibernate. Please <A href="http://radio.xmlstoragesystem.com/rcsPublic/mailto?usernum=0118153">send</A> comments and corrections. </P></DIV><img src ="http://www.blogjava.net/kapok/aggbug/5114.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-05-24 15:49 <a href="http://www.blogjava.net/kapok/archive/2005/05/24/5114.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>DAO继续讨论</title><link>http://www.blogjava.net/kapok/archive/2005/05/24/5096.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Tue, 24 May 2005 01:56:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/05/24/5096.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/5096.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/05/24/5096.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/5096.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/5096.html</trackback:ping><description><![CDATA[<A href="http://forum.javaeye.com/viewtopic.php?p=62574#62560">http://forum.javaeye.com/viewtopic.php?p=62574#62560</A><img src ="http://www.blogjava.net/kapok/aggbug/5096.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-05-24 09:56 <a href="http://www.blogjava.net/kapok/archive/2005/05/24/5096.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Squiggle SQL Builder for Java</title><link>http://www.blogjava.net/kapok/archive/2005/05/24/5094.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Tue, 24 May 2005 01:33:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/05/24/5094.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/5094.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/05/24/5094.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/5094.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/5094.html</trackback:ping><description><![CDATA[<H1>Squiggle SQL Builder for Java</H1>
<P><BR><A href="http://joe.truemesh.com/squiggle/">http://joe.truemesh.com/squiggle/</A></P><img src ="http://www.blogjava.net/kapok/aggbug/5094.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-05-24 09:33 <a href="http://www.blogjava.net/kapok/archive/2005/05/24/5094.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Lazy Initialization and the DAO pattern with Hibernate and Spring </title><link>http://www.blogjava.net/kapok/archive/2005/05/24/5089.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Mon, 23 May 2005 16:07:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/05/24/5089.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/5089.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/05/24/5089.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/5089.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/5089.html</trackback:ping><description><![CDATA[<P>转自：Karl Baum's Weblog</P>
<CENTER>
<H1>Karl Baum's Weblog</H1>
<P class=descrip></P>
<DIV class=rWeblogCategoryChooser><SPAN class=rChosenCategory>All</SPAN> | <SPAN class=rUnchosenCategory><A href="http://www.jroller.com/page/kbaum?catname=/General"><FONT color=#4a664d>General</FONT></A></SPAN> | <SPAN class=rUnchosenCategory><A href="http://www.jroller.com/page/kbaum?catname=/Java"><FONT color=#4a664d>Java</FONT></A></SPAN> </DIV><BR></CENTER>
<DIV id=next-previous></DIV>
<DIV class=box>
<DIV class=entry><A href="http://www.jroller.com/page/kbaum/20040708"><IMG class=daypermalink title="Permanent link to this day" alt="&#13;&#10;20040708&#13;&#10;" src="http://www.jroller.com/images/permalink.gif"></A> Thursday July 08, 2004 </DIV>
<P><A id=orm_lazy_initialization_with_dao name=orm_lazy_initialization_with_dao></A><B>Lazy Initialization and the DAO pattern with Hibernate and Spring</B> 
<P><STRONG><FONT size=2>Hibernate and Lazy Initialization</FONT></STRONG> </P>
<P><FONT size=2>Hibernate object relational mapping offers both lazy and non-lazy modes of object initialization. Non-lazy initialization retrieves an object and all of its related objects at load time. This can result in hundreds if not thousands of select statements when retrieving one entity. The problem is compounded when bi-directional relationships are used, often causing entire databases to be loaded during the initial request. Of course one could tediously examine each object relationship and manually remove those most costly, but in the end, we may be losing the ease of use benefit sought in using the ORM tool. </FONT></P>
<P><FONT size=2>The obvious solution is to employ the lazy loading mechanism provided by hibernate. This initialization strategy only loads an object's one-to-many and many-to-many relationships when these fields are accessed. The scenario is practically transparent to the developer and a minimum amount of database requests are made, resulting in major performance gains. One drawback to this technique is that lazy loading requires the Hibernate session to remain open while the data object is in use. This causes a major problem when trying to abstract the persistence layer via the Data Access Object pattern. In order to fully abstract the persistence mechanism, all database logic, including opening and closing sessions, must not be performed in the application layer. Most often, this logic is concealed behind the DAO implementation classes which implement interface stubs. The quick and dirty solution is to forget the DAO pattern and include database connection logic in the application layer. This works for small applications but in large systems this can prove to be a major design flaw, hindering application extensibility.</FONT> </P>
<P><STRONG><FONT size=2>Being Lazy in the Web Layer</FONT></STRONG> </P>
<P><FONT size=2>Fortunately for us, the Spring Framework has developed an out of box web solution for using the DAO pattern in combination with Hibernate lazy loading. For anyone not familiar with using the <A href="http://www.springframework.org/"><FONT color=#4a664d>Spring Framework</FONT></A> in combination with <A href="http://www.hibernate.org/"><FONT color=#4a664d>Hibernate</FONT></A>, I will not go into the details here, but I encourage you to read <A href="http://www.hibernate.org/110.html"><FONT color=#4a664d>Hibernate Data Access with the Spring Framework</FONT></A>. In the case of a web application, Spring comes with both the <A href="http://www.springframework.org/docs/api/org/springframework/orm/hibernate/support/OpenSessionInViewFilter.html"><FONT color=#4a664d>OpenSessionInViewFilter</FONT></A> and the <A href="http://www.springframework.org/docs/api/org/springframework/orm/hibernate/support/OpenSessionInViewInterceptor.html"><FONT color=#4a664d>OpenSessionInViewInterceptor</FONT></A>. One can use either one interchangeably as both serve the same function. The only difference between the two is the interceptor runs within the Spring container and is configured within the web application context while the Filter runs in front of Spring and is configured within the web.xml. Regardless of which one is used, they both open the hibernate session during the request binding this session to the current thread. Once bound to the thread, the open hibernate session can transparently be used within the DAO implementation classes. The session will remain open for the view allowing lazy access the database value objects. Once the view logic is complete, the hibernate session is closed either in the Filter doFilter method or the Interceptor postHandle method. Below is an example of the configuration of each component: </FONT></P>
<P><STRONG><FONT size=1>Interceptor Configuration</FONT></STRONG> </P><PRE><FONT size=2>&lt;beans&gt; 
  &lt;bean id="urlMapping"     
     class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"&gt;    
       &lt;property name="interceptors"&gt;
         &lt;list&gt;
              &lt;ref bean="openSessionInViewInterceptor"/&gt;
         &lt;/list&gt;
       &lt;/property&gt;
       &lt;property name="mappings"&gt;
  ...
  &lt;/bean&gt;
  ...
  &lt;bean name="openSessionInViewInterceptor"  
    class="org.springframework.orm.hibernate.support.OpenSessionInViewInterceptor"&gt;
       &lt;property name="sessionFactory"&gt;&lt;ref bean="sessionFactory"/&gt;&lt;/property&gt;
  &lt;/bean&gt;
&lt;/beans&gt;</FONT></PRE>
<P><STRONG><FONT size=1>Filter Configuration</FONT></STRONG></P><PRE>&lt;web-app&gt;
 ...      
  &lt;filter&gt;
    &lt;filter-name&gt;hibernateFilter&lt;/filter-name&gt;
    &lt;filter-class&gt;
      org.springframework.orm.hibernate.support.OpenSessionInViewFilter
    &lt;/filter-class&gt;
   &lt;/filter&gt;
  ...      
  &lt;filter-mapping&gt;
    &lt;filter-name&gt;hibernateFilter&lt;/filter-name&gt;
     &lt;url-pattern&gt;*.spring&lt;/url-pattern&gt;
  &lt;/filter-mapping&gt;
  ...
&lt;/web-app&gt;</PRE>
<P><FONT size=2>Implementing the Hibernate DAO's to use the open session is simple. In fact, if you are already using the Spring Framework to implement your Hibernate DAO's, most likely you will not have to change a thing. The DAO's must access Hibernate through the convenient HibernateTemplate utility, which makes database access a piece of cake. Below is an example DAO.</FONT></P>
<P><STRONG><FONT size=1>Example DAO</FONT></FONT></STRONG></P><PRE><FONT size=2>public class HibernateProductDAO extends HibernateDaoSupport implements ProductDAO  {      

       public Product getProduct(Integer productId) {
              return (Product)getHibernateTemplate().load(Product.class, productId);
       }

       public Integer saveProduct(Product product) {
              return (Integer) getHibernateTemplate().save(product);
       }       

       public void updateProduct(Product product) {
              getHibernateTemplate().update(product);
       }
 }</FONT></PRE>
<P><FONT size=2><STRONG>Being Lazy in the Business Layer</STRONG> </FONT></P>
<P><FONT size=2>Even outside the view, the Spring Framework makes it easy to use lazy load initialization, through the AOP interceptor HibernateInterceptor. The hibernate interceptor transparently intercepts calls to any business object configured in the Spring application context, opening a hibernate session before the call, and closing the session afterward. Let's run through a quick example. Suppose we have an interface BusinessObject:</FONT> <PRE>public interface BusinessObject { 
     public void doSomethingThatInvolvesDaos(); 
}</PRE>
<P><FONT size=2>The class BusinessObjectImpl implements BusinessObject:</FONT></P>
<P></P><PRE>public class BusinessObjectImpl implements BusinessObject {
    public void doSomethingThatInvolvesDaos() {
        // lots of logic that calls
        // DAO classes Which access 
        // data objects lazily
    }
}</PRE>
<P><FONT size=2>Through some configurations in the Spring application context, we can instruct the HibernateInterceptor to intercept calls to the BusinessObjectImpl allowing it's methods to lazily access data objects. Take a look at the fragment below: </FONT></P><PRE>&lt;beans&gt;
    &lt;bean id="hibernateInterceptor" class="org.springframework.orm.hibernate.HibernateInterceptor"&gt;
         &lt;property name="sessionFactory"&gt;
           &lt;ref bean="sessionFactory"/&gt;
         &lt;/property&gt;
    &lt;/bean&gt;
    &lt;bean id="businessObjectTarget" class="com.acompany.BusinessObjectImpl"&gt;
       &lt;property name="someDAO"&gt;&lt;ref bean="someDAO"/&gt;&lt;/property&gt;
    &lt;/bean&gt;
    &lt;bean id="businessObject" class="org.springframework.aop.framework.ProxyFactoryBean"&gt;
         &lt;property name="target"&gt;&lt;ref bean="businessObjectTarget"/&gt;&lt;/property&gt;
         &lt;property name="proxyInterfaces"&gt;
           &lt;value&gt;com.acompany.BusinessObject&lt;/value&gt;
         &lt;/property&gt;
         &lt;property name="interceptorNames"&gt;
           &lt;list&gt;
              &lt;value&gt;hibernateInterceptor&lt;/value&gt;
           &lt;/list&gt;
         &lt;/property&gt;
     &lt;/bean&gt;            
&lt;/beans&gt;
</PRE>
<P><FONT size=2>When the businessObject bean is referenced, the HibernateInterceptor opens a hibernate session and passes the call onto the BusinessObjectImpl. When the BusinessObjectImpl has finished executing, the HibernateInterceptor transparently closes the session. The application code has no knowledge of any persistence logic, yet it is still able to lazily access data objects.</FONT></P>
<P><FONT size=2><STRONG>Being Lazy in your Unit Tests</P></FONT></STRONG>
<P><FONT size=2>Last but not least, we'll need the ability to test our lazy application from J-Unit. This is easily done by overriding the setUp and tearDown methods of the TestCase class. I prefer to keep this code in a convenient abstract TestCase class for all of my tests to extend.</FONT></P><PRE>public abstract class MyLazyTestCase extends TestCase {

        private SessionFactory sessionFactory;
        private Session session;
	
        public void setUp() throws Exception {
	    super.setUp();
	    SessionFactory sessionFactory = (SessionFactory) getBean("sessionFactory");
	    session = SessionFactoryUtils.getSession(sessionFactory, true);
	    Session s = sessionFactory.openSession();
	    TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(s));

        }

        protected Object getBean(String beanName) {
            //Code to get objects from Spring application context
        }
	
        public void tearDown() throws Exception {
	    super.tearDown();
	    SessionHolder holder = (SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
	    Session s = holder.getSession(); 
	    s.flush();
	    TransactionSynchronizationManager.unbindResource(sessionFactory);
	    SessionFactoryUtils.closeSessionIfNecessary(s, sessionFactory);
        }
}
</PRE><SPAN class=dateStamp>( Jul 08 2004, 09:39:55 AM EDT )</SPAN> <A class=entrypermalink title="Permanent link to this weblog entry" href="http://www.jroller.com/page/kbaum/20040708#orm_lazy_initialization_with_dao"><FONT color=#4a664d>Permalink</FONT></A> <A class=entrycommentslink href="http://www.jroller.com/comments/kbaum/Weblog/orm_lazy_initialization_with_dao#comments"><FONT color=#4a664d>Comments [2]</FONT></A> 
<P></P></DIV>
<DIV class=trackbackUrl>Trackback URL: http://jroller.com/trackback/kbaum/Weblog/orm_lazy_initialization_with_dao </DIV>
<DIV class=comments id=comments>
<DIV class=comments-head>Comments:</DIV><BR>
<DIV class=comment id=comment1>A few things to keep in the back of your mind if you take this approach; 1. If any errors occur while attempting to lazy load relationships in the view (JSP) it would be hard to present a nice error to the user. 2. This would result in at least 2 hibernate sessions (db connections being open for any one request), so you might want to up the number of connections available. Cheers, Dan 
<P class=comment-details>Posted by <A title=192.148.125.11 href="mailto:%64%61%6e%20%61%74%20%72%65%61%63%74%69%76%65%20%64%6f%74%20%6f%72%67"><FONT color=#4a664d>Dan Washusen</FONT></A> on July 08, 2004 at 09:02 PM EDT <A class=entrypermalink title="comment permalink" href="http://www.jroller.com/comments/kbaum/Weblog/orm_lazy_initialization_with_dao#comment1"><FONT color=#4a664d>#</FONT></A> </P></DIV>
<DIV class=comment id=comment2>I am a little confused on why it would be difficult to show a nice error jsp. Couldn't we just use the provided servlet container error page mechanisms? In regards to the 2 hibernate sessions being opened. Are you saying that the OpenSessionInViewInterceptor would be run twice if an exception was thrown? Thanks for your feedback! 
<P class=comment-details>Posted by <B>Karl Baum</B> (63.170.158.133) on July 09, 2004 at 09:48 AM EDT <A class=entrypermalink title="comment permalink" href="http://www.jroller.com/comments/kbaum/Weblog/orm_lazy_initialization_with_dao#comment2"><FONT color=#4a664d>#</FONT></A> </P></DIV></DIV><img src ="http://www.blogjava.net/kapok/aggbug/5089.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-05-24 00:07 <a href="http://www.blogjava.net/kapok/archive/2005/05/24/5089.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>转载potain的一篇关于OpenSessionInViewFilter的文章</title><link>http://www.blogjava.net/kapok/archive/2005/05/23/5087.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Mon, 23 May 2005 15:40:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/05/23/5087.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/5087.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/05/23/5087.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/5087.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/5087.html</trackback:ping><description><![CDATA[<P>转自：Potain 的BLOG</P>
<DIV class=snip-title>
<H1 class=snip-name>OpenSessionInView </H1>
<DIV class=snip-info>Created by <A href="http://www.aspectoriented.org:9080//space/potian"><FONT color=#4a664d>potian</FONT></A>. Last edited by <A href="http://www.aspectoriented.org:9080//space/admin"><FONT color=#4a664d>admin</FONT></A> 61 days ago. Viewed 181 times.</DIV>
<DIV class=snip-buttons><SPAN class=inactive>[edit]</SPAN> <SPAN class=inactive>[attach]</SPAN> </DIV></DIV>
<DIV class=snip-content id=snip-content>
<DIV class=snip-attachments></DIV>Hibernate的Lazy初始化1:n关系时，你必须保证是在同一个Session内部使用这个关系集合，不然Hiernate将抛出例外。 
<P class=paragraph>另外，你不愿意你的DAO测试代码每次都打开关系Session，因此，我们一般会采用OpenSessionInView模式。 
<H3 class=heading-1><A name=0></A><B style="COLOR: black; BACKGROUND-COLOR: #ffff66">OpenSessionInViewFilter</B>解决Web应用程序的问题 </H3>如果程序是在正常的Web程序中运行，那么Spring的<B style="COLOR: black; BACKGROUND-COLOR: #ffff66">OpenSessionInViewFilter</B>能够解决问题，它： 
<P class=paragraph>
<DIV class=code><PRE><SPAN class=java-keyword>protected</SPAN> void doFilterInternal(HttpServletRequest request, 
             HttpServletResponse response,
	     FilterChain filterChain) <SPAN class=java-keyword>throws</SPAN> ServletException, IOException {
	SessionFactory sessionFactory = lookupSessionFactory();
	logger.debug(<SPAN class=java-quote>"Opening Hibernate Session in <B style="COLOR: black; BACKGROUND-COLOR: #ffff66">OpenSessionInViewFilter</B>"</SPAN>);
	Session session = getSession(sessionFactory);
	TransactionSynchronizationManager.bindResource(sessionFactory, 
             <SPAN class=java-keyword>new</SPAN> SessionHolder(session));
	<SPAN class=java-keyword>try</SPAN> {
		filterChain.doFilter(request, response);
	}
	<SPAN class=java-keyword>finally</SPAN> {
		TransactionSynchronizationManager.unbindResource(sessionFactory);
		logger.debug(<SPAN class=java-quote>"Closing Hibernate Session in <B style="COLOR: black; BACKGROUND-COLOR: #ffff66">OpenSessionInViewFilter</B>"</SPAN>);
		closeSession(session, sessionFactory);
	}
}</PRE></DIV>可以看到，这个Filter在request开始之前，把sessionFactory绑定到TransactionSynchronizationManager，和这个SessionHolder相关。这个意味着所有request执行过程中将使用这个session。而在请求结束后，将和这个sessionFactory对应的session解绑，并且关闭Session。 
<P class=paragraph>为什么绑定以后，就可以防止每次不会新开一个Session呢？看看HibernateDaoSupport的情况： 
<DIV class=code><PRE><SPAN class=java-keyword>public</SPAN> <SPAN class=java-keyword>final</SPAN> void setSessionFactory(SessionFactory sessionFactory) {
    <SPAN class=java-keyword>this</SPAN>.hibernateTemplate = <SPAN class=java-keyword>new</SPAN> HibernateTemplate(sessionFactory);
  }
 <SPAN class=java-keyword>protected</SPAN> <SPAN class=java-keyword>final</SPAN> HibernateTemplate getHibernateTemplate() {
  <SPAN class=java-keyword>return</SPAN> hibernateTemplate;
 }</PRE></DIV>
<P class=paragraph>我们的DAO将使用这个template进行操作： 
<DIV class=code><PRE><SPAN class=java-keyword>public</SPAN> <SPAN class=java-keyword>abstract</SPAN> class BaseHibernateObjectDao
	<SPAN class=java-keyword>extends</SPAN> HibernateDaoSupport
	<SPAN class=java-keyword>implements</SPAN> BaseObjectDao {<P class=paragraph>
	<SPAN class=java-keyword>protected</SPAN> BaseEntityObject getByClassId(<SPAN class=java-keyword>final</SPAN> <SPAN class=java-object>long</SPAN> id) {
		BaseEntityObject obj =
			(BaseEntityObject) getHibernateTemplate()
				.execute(<SPAN class=java-keyword>new</SPAN> HibernateCallback() {<P class=paragraph>			<SPAN class=java-keyword>public</SPAN> <SPAN class=java-object>Object</SPAN> doInHibernate(Session session)
				<SPAN class=java-keyword>throws</SPAN> HibernateException {
				<SPAN class=java-keyword>return</SPAN> session.get(getPersistentClass(), 
                                       <SPAN class=java-keyword>new</SPAN> <SPAN class=java-object>Long</SPAN>(id));
			}<P class=paragraph>		});
		<SPAN class=java-keyword>return</SPAN> obj;
	}<P class=paragraph><P class=paragraph>	<SPAN class=java-keyword>public</SPAN> void save(BaseEntityObject entity) {
		getHibernateTemplate().saveOrUpdate(entity);
	}<P class=paragraph>	<SPAN class=java-keyword>public</SPAN> void remove(BaseEntityObject entity) {
		<SPAN class=java-keyword>try</SPAN> {<P class=paragraph>			getHibernateTemplate().delete(entity);
		} <SPAN class=java-keyword>catch</SPAN> (Exception e) {
			<SPAN class=java-keyword>throw</SPAN> <SPAN class=java-keyword>new</SPAN> FlexEnterpriseDataAccessException(e);
		}
	}<P class=paragraph>	<SPAN class=java-keyword>public</SPAN> void refresh(<SPAN class=java-keyword>final</SPAN> BaseEntityObject entity) {
		getHibernateTemplate().execute(<SPAN class=java-keyword>new</SPAN> HibernateCallback() {<P class=paragraph>			<SPAN class=java-keyword>public</SPAN> <SPAN class=java-object>Object</SPAN> doInHibernate(Session session)
				<SPAN class=java-keyword>throws</SPAN> HibernateException {
				session.refresh(entity);
				<SPAN class=java-keyword>return</SPAN> <SPAN class=java-keyword>null</SPAN>;
			}<P class=paragraph>		});
	}<P class=paragraph>	<SPAN class=java-keyword>public</SPAN> void replicate(<SPAN class=java-keyword>final</SPAN> <SPAN class=java-object>Object</SPAN> entity) {
		getHibernateTemplate().execute(<SPAN class=java-keyword>new</SPAN> HibernateCallback() {<P class=paragraph>			<SPAN class=java-keyword>public</SPAN> <SPAN class=java-object>Object</SPAN> doInHibernate(Session session)
				<SPAN class=java-keyword>throws</SPAN> HibernateException {
				session.replicate(entity, 
                                ReplicationMode.OVERWRITE);
				<SPAN class=java-keyword>return</SPAN> <SPAN class=java-keyword>null</SPAN>;
			}<P class=paragraph>		});
	}</P></PRE></DIV>而HibernateTemplate试图每次在execute之前去获得Session，执行完就力争关闭Session 
<DIV class=code><PRE><SPAN class=java-keyword>public</SPAN> <SPAN class=java-object>Object</SPAN> execute(HibernateCallback action) <SPAN class=java-keyword>throws</SPAN> DataAccessException {
	Session session = (!<SPAN class=java-keyword>this</SPAN>.allowCreate ?
		SessionFactoryUtils.getSession(getSessionFactory(), 
                  <SPAN class=java-keyword>false</SPAN>) :
		SessionFactoryUtils.getSession(getSessionFactory(),
                  getEntityInterceptor(),
                  getJdbcExceptionTranslator()));
	<SPAN class=java-object>boolean</SPAN> existingTransaction =  
          TransactionSynchronizationManager.hasResource(getSessionFactory());
	<SPAN class=java-keyword>if</SPAN> (!existingTransaction &amp;&amp; getFlushMode() == FLUSH_NEVER) {
		session.setFlushMode(FlushMode.NEVER);
	}
	<SPAN class=java-keyword>try</SPAN> {
		<SPAN class=java-object>Object</SPAN> result = action.doInHibernate(session);
		flushIfNecessary(session, existingTransaction);
		<SPAN class=java-keyword>return</SPAN> result;
	}
	<SPAN class=java-keyword>catch</SPAN> (HibernateException ex) {
		<SPAN class=java-keyword>throw</SPAN> convertHibernateAccessException(ex);
	}
	<SPAN class=java-keyword>catch</SPAN> (SQLException ex) {
		<SPAN class=java-keyword>throw</SPAN> convertJdbcAccessException(ex);
	}
	<SPAN class=java-keyword>catch</SPAN> (RuntimeException ex) {
		// callback code threw application exception
		<SPAN class=java-keyword>throw</SPAN> ex;
	}
	<SPAN class=java-keyword>finally</SPAN> {
		SessionFactoryUtils.closeSessionIfNecessary(
                    session, getSessionFactory());
	}
}</PRE></DIV>而这个SessionFactoryUtils能否得到当前的session以及closeSessionIfNecessary是否真正关闭session，端取决于这个session是否用sessionHolder和这个sessionFactory在我们最开始提到的TransactionSynchronizationManager绑定。 
<DIV class=code><PRE><SPAN class=java-keyword>public</SPAN> <SPAN class=java-keyword>static</SPAN> void closeSessionIfNecessary(Session session, 
    SessionFactory sessionFactory)   
    <SPAN class=java-keyword>throws</SPAN> CleanupFailureDataAccessException {
	<SPAN class=java-keyword>if</SPAN> (session == <SPAN class=java-keyword>null</SPAN> || 
	   TransactionSynchronizationManager.hasResource(sessionFactory)) {
		<SPAN class=java-keyword>return</SPAN>;
	}
	logger.debug(<SPAN class=java-quote>"Closing Hibernate session"</SPAN>);
	<SPAN class=java-keyword>try</SPAN> {
		session.close();
	}
	<SPAN class=java-keyword>catch</SPAN> (JDBCException ex) {
		// SQLException underneath
		<SPAN class=java-keyword>throw</SPAN> <SPAN class=java-keyword>new</SPAN> CleanupFailureDataAccessException(
		<SPAN class=java-quote>"Cannot close Hibernate session"</SPAN>, ex.getSQLException());
	}
	<SPAN class=java-keyword>catch</SPAN> (HibernateException ex) {
		<SPAN class=java-keyword>throw</SPAN> <SPAN class=java-keyword>new</SPAN> CleanupFailureDataAccessException(
		<SPAN class=java-quote>"Cannot close Hibernate session"</SPAN>, ex);
	}
}</PRE></DIV>
<H3 class=heading-1>HibernateInterceptor和OpenSessionInViewInterceptor的问题 </H3>
<P class=paragraph>使用同样的方法，这两个Interceptor可以用来解决问题。但是关键的不同之处在于，它们的力度只能定义在DAO或业务方法上，而不是在我们的Test方法上，除非我们把它们应用到TestCase的方法上，但你不大可能为TestCase去定义一个接口，然后把Interceptor应用到这个接口的某些方法上。直接使用HibernateTransactionManager也是一样的。因此，如果我们有这样的测试： 
<DIV class=code><PRE>Category parentCategory  = <SPAN class=java-keyword>new</SPAN> Category ();
	parentCategory.setName(<SPAN class=java-quote>"parent"</SPAN>);
	dao.save(parentCategory);<P class=paragraph>	Category childCategory  = <SPAN class=java-keyword>new</SPAN> Category();
        childCategory.setName(<SPAN class=java-quote>"child"</SPAN>);<P class=paragraph>	parentCategory.addChild(childCategory);
	dao.save(childCategory);<P class=paragraph>	Category savedParent = dao.getCategory(<SPAN class=java-quote>"parent"</SPAN>);
	Category savedChild = (Category ) savedParent.getChildren().get(0);
	assertEquals(savedChild, childCategory);</P></PRE></DIV>将意味着两件事情： 
<UL class=minus>
<LI>每次DAO执行都会启动一个session和关闭一个session 
<LI>如果我们定义了一个lazy的关系，那么最后的Category savedChild = (Category ) savedParent.getChildren().get(0);将会让hibernate报错。 </LI></UL>
<H3 class=heading-1>解决方案 </H3>
<P class=paragraph>一种方法是对TestCase应用Interceptor或者TransactionManager，但这个恐怕会造成很多麻烦。除非是使用增强方式的AOP.我前期采用这种方法(Aspectwerkz)，在Eclipse里面也跑得含好。 
<P class=paragraph>另一种方法是在TestCase的setup和teardown里面实现和Filter完全一样的处理，其他的TestCase都从这个TestCase继承，这种方法是我目前所使用的。 </P></DIV><img src ="http://www.blogjava.net/kapok/aggbug/5087.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-05-23 23:40 <a href="http://www.blogjava.net/kapok/archive/2005/05/23/5087.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>一个很不错的基于角色的权限管理系统设计！</title><link>http://www.blogjava.net/kapok/archive/2005/05/23/5086.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Mon, 23 May 2005 15:39:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/05/23/5086.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/5086.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/05/23/5086.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/5086.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/5086.html</trackback:ping><description><![CDATA[<P><A href="http://firebody.blogbus.com/logs/2004/08/320481.html">http://firebody.blogbus.com/logs/2004/08/320481.html</A><BR>一般对于权限管理的基本思路是ACL列表。关系逻辑如下：</P>
<P>user(group)--AccessI(访问级别)---resources(各种资源)</P>
<P>一行就是一个ACL记录。基于这种原理，opensymphony开发了两个框架：OSUser ,OsAccess。都是很优秀的框架，可以去参考一下。</P>
<P>ACL缺点：</P>
<P>用数据的格式硬性限定权限管理，失去灵活定制的功能。</P>
<P>ACL以resource，user为主。</P>
<P>抽象不大合理。ACL本身是一个抽象吗？值得探讨。现实世界中，一个人具备一些权限，这些权限可以操作特定资源。那我们的抽象就应该是user本身关联着权限，关联的箭头由User指向权限，然而ACL中的User与权限的关联却是被动的关联。</P>
<P>ACL本身不适合反映用户组（机构）权限继承关系。</P>
<P>在OA场合中，也不适合使用ACL。</P>
<P>接下来，我们探讨一下基于role的权限的管理设计。</P>
<P>引用一篇java道上的一片帖子：</P>
<P>作者：<A title=songyx href="http://www.jdon.com/jive/profile.jsp?user=40159"><B><FONT color=#4a664d>dunel</FONT></B></A>&nbsp; 出自：<A href="http://www.jdon.com/"><FONT color=#4a664d>http://www.jdon.com</FONT></A> </P>
<P>
<TABLE cellSpacing=0 cellPadding=0 width="100%" align=center border=0>
<TBODY>
<TR>
<TD>
<H3 align=center>最近对就有系统人员权限升级计划——也谈人员权限的设计。</H3>
<P align=center><A title=songyx href="http://www.jdon.com/jive/profile.jsp?user=40159"><B><FONT color=#4a664d>dunel</FONT></B></A> http://www.jdon.com Apr 16, 2004 5:43 PM <A href="http://www.jdon.com/jive/post.jsp?forum=46&amp;thread=13450&amp;message=6002552&amp;reply=true"><IMG height=17 alt=回复此消息 hspace=3 src="http://www.jdon.com/jive/images/reply.gif" width=17 border=0></A> <FONT size=+0><A title=回复此消息 href="http://www.jdon.com/jive/post.jsp?forum=46&amp;thread=13450&amp;message=6002552&amp;reply=true"><FONT color=#4a664d>回复</FONT></A> </FONT></P>
<P>前言：<BR>人员、机构、角色、权限对象间组织关系的重新抽象；<BR><BR>人员、机构、角色、权限；<BR>这四个经过抽象的对象原型经过事实证明还是比较成功的；<BR><BR>这几个对象不会有大的变动；<BR>但是实际中也碰到了问题，就是这些对象之间关系的不确定性；<BR>下面描述以下四个对象新的关系结构；<BR><BR>1 核心思想：<BR><BR>1。人员在系统中总是扮演某种角色的；<BR>例如：小张在属于办公厅这个部门，以前我们都倾向于认为办公厅聚合了小张。<BR>其实考察一下实际情况，办公厅下面有个办公厅人员这个角色，而小张只是扮演了这个角色而已；<BR>考虑一下如果当小长被调动另外一个部门时候那种抽象更容易处理。<BR><BR>2。业务逻辑希望面对的是系统中的角色，而非扮演角色的具体的人。<BR>例如：以工作流示例，一个公文的下一步流转的对象是组织部部长，他不关心谁扮演这个部长，人员权限模块知道就行了。<BR><BR>2 抽象关系描述：<BR><BR><BR>.人员和机构之间的松耦合（通过角色耦合）<BR><BR>目的：改变糟糕地人员和机构之间的强聚合关系。<BR><BR><BR><BR><BR>人员和部门通过角色进行耦合；<BR><BR>人员和部门的关系可以容易的变化，不会产生大的影响。<BR><BR>.人员和角色间变成强聚合关系；<BR><BR>目的：强调人员在系统中必须扮演角色，起码扮演某个机构的人员这个角色；<BR><BR><BR><BR><BR>人员的角色又可以同时扮演的，也有互相排斥的。<BR><BR>排斥的角色就是不能同时扮演的，登录以后可以选择的角色；<BR><BR>由管理员规定人员的那些角色是相互排斥的；<BR><BR><BR>.机构和权限点之间没有直接关系；<BR><BR>目的：让机构的抽象更清晰化，机构和权限的关系通过角色体现；<BR><BR><BR>.机构和角色之间强聚合关系；<BR><BR>目的：明确部门的作用是行使某种特定职能的。<BR><BR><BR><BR>说明：<BR><BR>部门和部门之间还有从属关系；<BR><BR>部门可以专门设置部门管理员用于管理下级的机构和人员，使分级管理在模型上成为可能；<BR><BR>部门拥有的角色有写是系统规定的，如：部门人员，部门管理员；<BR><BR>部门可以自定义自己的角色，并由部门管理员管理；<BR><BR>.角色之间的继承关系；<BR>目的：更好的处理角色和权限点之间的关系；<BR><BR><BR><BR>说明：<BR><BR>角色之间拥有继承关系如上图所示。<BR><BR>可以被继承的角色是由系统管理员制定的，部门管理员不能制定；<BR><BR>3 总结：<BR><BR>经过考虑，我认为 人员、机构、角色、权限 的关系应该做成可以改变的插件，系统初始时候进行选择不同组织方式；<BR><BR>也就是说，上文描述的 人员、机构、角色、权限 的关系还有原有关系都做成的插件，并不抛弃原有的人员权限关系。<BR></P></TD></TR></TBODY></TABLE><BR><A name=6002614></A>
<TABLE cellSpacing=0 cellPadding=0 width="100%" align=center bgColor=#cccccc border=0>
<TBODY>
<TR>
<TD width="1%" bgColor=#ffffff></TD>
<TD width="99%">
<TABLE cellSpacing=1 cellPadding=4 width="100%" bgColor=#cccccc border=0>
<TBODY>
<TR bgColor=#eae9ea>
<TD width="98%"><FONT face=arial,sans-serif color=#000000 size=-1><B>Re: 最近对就有系统人员权限升级计划——也谈人员权限的设计。</B> </FONT></TD>
<TD noWrap width="1%"><FONT class=p2 face=arial,sans-serif color=#000000>发表时间: Apr 16, 2004 5:46 PM </FONT></TD>
<TD noWrap align=middle width="1%">
<TABLE cellSpacing=0 cellPadding=2 border=0>
<TBODY>
<TR>
<TD><A href="http://www.jdon.com/jive/post.jsp?forum=46&amp;thread=13450&amp;message=6002614&amp;reply=true"><IMG height=17 alt=回复此消息 hspace=3 src="http://www.jdon.com/jive/images/reply.gif" width=17 border=0></A></TD>
<TD><FONT class=p2 face=arial,sans-serif><A title=回复此消息 href="http://www.jdon.com/jive/post.jsp?forum=46&amp;thread=13450&amp;message=6002614&amp;reply=true"><FONT color=#4a664d>回复</FONT></A> </FONT></TD></TR></TBODY></TABLE></TD></TR>
<TR bgColor=#eae9ea>
<TD vAlign=top colSpan=3><FONT class=p2 face=arial,sans-serif color=#000000>发表人: <A href="http://www.jdon.com/jive/profile.jsp?user=40159"><B><FONT color=#4a664d>dunel</FONT></B></A> </FONT>&nbsp;&nbsp; <FONT class=p2 face=arial,sans-serif color=#000000>发表文章: 13 / 注册时间: 2004-04 </FONT></TD></TR>
<TR bgColor=#eae9ea>
<TD vAlign=top colSpan=3><FONT face=arial,sans-serif color=#000000 size=-1><IMG src="http://www.jdon.com/jive/upload/dunel50rsV.gif"><BR><IMG src="http://www.jdon.com/jive/upload/dunel00x43.gif"><BR><IMG src="http://www.jdon.com/jive/upload/duneluD2Gh.gif"><BR><IMG src="http://www.jdon.com/jive/upload/dunelrW4U6.gif"><BR><IMG src="http://www.jdon.com/jive/upload/duneljB57I.gif"> </FONT>
<P></P></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><BR><A name=6002662></A>
<TABLE cellSpacing=0 cellPadding=0 width="100%" align=center bgColor=#cccccc border=0>
<TBODY>
<TR>
<TD width="1%" bgColor=#ffffff></TD>
<TD width="99%">
<TABLE cellSpacing=1 cellPadding=4 width="100%" bgColor=#cccccc border=0>
<TBODY>
<TR bgColor=#e3e7f0>
<TD width="98%"><FONT face=arial,sans-serif color=#000000 size=-1><B>Re: 最近对就有系统人员权限升级计划——也谈人员权限的设计。</B> </FONT></TD>
<TD noWrap width="1%"><FONT class=p2 face=arial,sans-serif color=#000000>发表时间: Apr 16, 2004 5:53 PM </FONT></TD>
<TD noWrap align=middle width="1%">
<TABLE cellSpacing=0 cellPadding=2 border=0>
<TBODY>
<TR>
<TD><A href="http://www.jdon.com/jive/post.jsp?forum=46&amp;thread=13450&amp;message=6002662&amp;reply=true"><IMG height=17 alt=回复此消息 hspace=3 src="http://www.jdon.com/jive/images/reply.gif" width=17 border=0></A></TD>
<TD><FONT class=p2 face=arial,sans-serif><A title=回复此消息 href="http://www.jdon.com/jive/post.jsp?forum=46&amp;thread=13450&amp;message=6002662&amp;reply=true"><FONT color=#4a664d>回复</FONT></A> </FONT></TD></TR></TBODY></TABLE></TD></TR>
<TR bgColor=#e3e7f0>
<TD vAlign=top colSpan=3><FONT class=p2 face=arial,sans-serif color=#000000>发表人: <A href="http://www.jdon.com/jive/profile.jsp?user=40159"><B><FONT color=#4a664d>dunel</FONT></B></A> </FONT>&nbsp;&nbsp; <FONT class=p2 face=arial,sans-serif color=#000000>发表文章: 13 / 注册时间: 2004-04 </FONT></TD></TR>
<TR bgColor=#e3e7f0>
<TD vAlign=top colSpan=3><FONT face=arial,sans-serif color=#000000 size=-1>与RBAC向做的设想就是人员和权限的直接耦合关系。<BR>在应用中经常会出现某个人就是要比其他人多出来一个权限，而且这样的情况非常多，如果每次都为这种情况做一个角色的话，基本上会造成角色的泛滥。<BR>不知道大家有什么好的设想，或者解决方案。 </FONT>
<P></P></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><BR><A name=6016200></A>
<TABLE cellSpacing=0 cellPadding=0 width="100%" align=center bgColor=#cccccc border=0>
<TBODY>
<TR>
<TD width="1%" bgColor=#ffffff></TD>
<TD width="99%">
<TABLE cellSpacing=1 cellPadding=4 width="100%" bgColor=#cccccc border=0>
<TBODY>
<TR bgColor=#eae9ea>
<TD width="98%"><FONT face=arial,sans-serif color=#000000 size=-1><B>Re: 最近对就有系统人员权限升级计划——也谈人员权限的设计。</B> </FONT></TD>
<TD noWrap width="1%"><FONT class=p2 face=arial,sans-serif color=#000000>发表时间: Apr 19, 2004 4:32 PM </FONT></TD>
<TD noWrap align=middle width="1%">
<TABLE cellSpacing=0 cellPadding=2 border=0>
<TBODY>
<TR>
<TD><A href="http://www.jdon.com/jive/post.jsp?forum=46&amp;thread=13450&amp;message=6016200&amp;reply=true"><IMG height=17 alt=回复此消息 hspace=3 src="http://www.jdon.com/jive/images/reply.gif" width=17 border=0></A></TD>
<TD><FONT class=p2 face=arial,sans-serif><A title=回复此消息 href="http://www.jdon.com/jive/post.jsp?forum=46&amp;thread=13450&amp;message=6016200&amp;reply=true"><FONT color=#4a664d>回复</FONT></A> </FONT></TD></TR></TBODY></TABLE></TD></TR>
<TR bgColor=#eae9ea>
<TD vAlign=top colSpan=3><FONT class=p2 face=arial,sans-serif color=#000000>发表人: <A href="http://www.jdon.com/jive/profile.jsp?user=40159"><B><FONT color=#4a664d>dunel</FONT></B></A> </FONT>&nbsp;&nbsp; <FONT class=p2 face=arial,sans-serif color=#000000>发表文章: 13 / 注册时间: 2004-04 </FONT></TD></TR>
<TR bgColor=#eae9ea>
<TD vAlign=top colSpan=3><FONT face=arial,sans-serif color=#000000 size=-1>最近研究了RBAC的规范，RBAC没有提到关于人员是如何组织的，更关注与资源的控制。但是我认为人员机构的管理是应该一起关注的，所以做出了这样的设计。<BR><BR>大家给提提意见吧。 </FONT></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE></P>
<TABLE cellSpacing=1 cellPadding=4 width="100%" bgColor=#cccccc border=0>
<TBODY>
<TR bgColor=#e3e7f0>
<TD width="98%"><FONT face=arial,sans-serif color=#000000 size=-1><B>Re: 最近对就有系统人员权限升级计划——也谈人员权限的设计。</B> </FONT></TD>
<TD noWrap width="1%"><FONT class=p2 face=arial,sans-serif color=#000000>发表时间: Apr 20, 2004 1:28 PM </FONT></TD>
<TD noWrap align=middle width="1%">
<TABLE cellSpacing=0 cellPadding=2 border=0>
<TBODY>
<TR>
<TD><A href="http://www.jdon.com/jive/post.jsp?forum=46&amp;thread=13450&amp;message=6024366&amp;reply=true"><IMG height=17 alt=回复此消息 hspace=3 src="http://www.jdon.com/jive/images/reply.gif" width=17 border=0></A></TD>
<TD><FONT class=p2 face=arial,sans-serif><A title=回复此消息 href="http://www.jdon.com/jive/post.jsp?forum=46&amp;thread=13450&amp;message=6024366&amp;reply=true"><FONT color=#4a664d>回复</FONT></A> </FONT></TD></TR></TBODY></TABLE></TD></TR>
<TR bgColor=#e3e7f0>
<TD vAlign=top colSpan=3><FONT class=p2 face=arial,sans-serif color=#000000>发表人: <A href="http://www.jdon.com/jive/profile.jsp?user=40159"><B><FONT color=#4a664d>dunel</FONT></B></A> </FONT>&nbsp;&nbsp; <FONT class=p2 face=arial,sans-serif color=#000000>发表文章: 13 / 注册时间: 2004-04 </FONT></TD></TR>
<TR bgColor=#e3e7f0>
<TD vAlign=top colSpan=3><FONT face=arial,sans-serif color=#000000 size=-1>ROLE BASE ACCESS CONTROL<BR>最近研究了RBAC的标准，这个标准刚刚成为美国国家标准。http://csrc.nist.gov/rbac/<BR><BR>下面是我的一些心得和笔记，希望能有用处。<BR><BR>1. RBAC的中心思想就是通过角色来做到用户和权限点的关联；<BR>2. 扩展RBAC角色是可以继承的。<BR>一般继承，就是多继承，一个角色可以继承多个角色；<BR>限制继承就是单继承，一个角色只能继承一个角色；<BR>3. 静态职责分离Static Separation of Duty --SSD是说，一个人一次只能扮演一个角色；<BR>经常在系统的行政角色上实施SSD；这个限制强加在人员分配的情况下的；<BR>4. 动态职责分离 Dynamic Separation of Duty – DSD就是：timely revocation of truest,就是能给人员分配冲突的角色，但是一个人每次只能扮演其中的一个；原来的设想中涉及到了DSD就是让人选择角色进入；<BR></FONT></TD></TR></TBODY></TABLE>
<P>&nbsp;</P>
<P>我们看看一个XML实现：</P>
<P>user.xml:－－－》对应role权限系统中的参与者。</P>
<P>&lt;?xml version="1.0" encoding="UTF-8"?&gt;<BR>&lt;users&gt;<BR>&nbsp; &lt;user id="superuser"&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;roles&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;role id="profilestore.administrator"/&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;/roles&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;attributes&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;attribute name="firstname" value="superuser"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;attribute name="password" value="secret"/&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;/attributes&gt;<BR>&nbsp; &lt;/user&gt;<BR>&nbsp; &lt;user id="bob"&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;roles&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;role id="portal.user"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;role id="portal.it"/&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;/roles&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;attributes&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;attribute name="firstname" value="Bob"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;attribute name="password" value="bob1"/&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;/attributes&gt;<BR>&nbsp; &lt;/user&gt;<BR>&nbsp; &lt;user id="alice"&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;roles&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;role id="portal.user"/&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;/roles&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;attributes&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;attribute name="firstname" value="Alice"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;attribute name="password" value="alice1"/&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;/attributes&gt;<BR>&nbsp; &lt;/user&gt;<BR>&lt;/users&gt;<BR>----------------------------------------------------------------</P>
<P>rolse.xml:----&gt;对应role权限设计系统中的角色定义</P>
<P>&lt;?xml version="1.0" encoding="UTF-8"?&gt;<BR>&lt;roles&gt;<BR>&nbsp; &lt;role description="This role is for IT users of the portal" id="portal.it"&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;resources&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;resource id="gadget.slashdot"/&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;/resources&gt;<BR>&nbsp; &lt;/role&gt;<BR>&nbsp; &lt;role description="This role is for normal users of the portal" id="portal.user"&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;resources&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;resource id="gadget.info"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;resource id="gadget.calc"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;resource id="gadget.news"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;resource id="gadget.joke"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;resource id="gadget.cal"/&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;/resources&gt;<BR>&nbsp; &lt;/role&gt;<BR>&nbsp; &lt;role description="This role can administer the profilestore" id="profilestore.administrator"&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;resources&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;resource id="profilestore.AddResource"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;resource id="profilestore.AddResourceToRole"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;resource id="profilestore.AddRole"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;resource id="profilestore.AddRoleToUser"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;resource id="profilestore.AddUser"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;resource id="profilestore.ChangePassword"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;resource id="profilestore.DeleteResource"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;resource id="profilestore.DeleteRole"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;resource id="profilestore.DeleteUser"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;resource id="profilestore.GetUser"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;resource id="profilestore.GetUserProperty"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;resource id="profilestore.GetUsers"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;resource id="profilestore.HasResource"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;resource id="profilestore.HasRole"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;resource id="profilestore.ModifyUser"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;resource id="profilestore.RemoveResourceFromRole"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;resource id="profilestore.RemoveRoleFromUser"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;resource id="profilestore.SetUserProperty"/&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;/resources&gt;<BR>&nbsp; &lt;/role&gt;<BR>&lt;/roles&gt;<BR>-----------------------------------------------------------------------</P>
<P>resources.xml:－－－&gt;对应role权限设计中的权限点</P>
<P>&nbsp;&lt;/resource&gt;<BR>&nbsp; &lt;resource description="The ability to add a resource to a role" id="profilestore.AddResourceToRole"&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;roles&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;role id="profilestore.administrator"/&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;/roles&gt;<BR>&nbsp; &lt;/resource&gt;<BR>&nbsp; &lt;resource description="The ability to add a role" id="profilestore.AddRole"&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;roles&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;role id="profilestore.administrator"/&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;/roles&gt;<BR>&nbsp; &lt;/resource&gt;<BR>&nbsp; &lt;resource description="The ability to add a role to a user" id="profilestore.AddRoleToUser"&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;roles&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;role id="profilestore.administrator"/&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;/roles&gt;<BR>&nbsp; &lt;/resource&gt;<BR>&nbsp; &lt;resource description="The ability to add a user" id="profilestore.AddUser"&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;roles&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;role id="profilestore.administrator"/&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;/roles&gt;<BR>&nbsp; &lt;/resource&gt;<BR>&nbsp; &lt;resource description="The ability to change a password" id="profilestore.ChangePassword"&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;roles&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;role id="profilestore.administrator"/&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;/roles&gt;<BR>&nbsp; &lt;/resource&gt;<BR>&nbsp; &lt;resource description="The ability to delete a resource" id="profilestore.DeleteResource"&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;roles&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;role id="profilestore.administrator"/&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;/roles&gt;<BR>&nbsp; &lt;/resource&gt;<BR>&nbsp; &lt;resource description="The ability to delete a role" id="profilestore.DeleteRole"&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;roles&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;role id="profilestore.administrator"/&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;/roles&gt;<BR>&nbsp; &lt;/resource&gt;<BR>&nbsp; &lt;resource description="The ability to delete a user" id="profilestore.DeleteUser"&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;roles&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;role id="profilestore.administrator"/&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;/roles&gt;<BR>&nbsp; &lt;/resource&gt;<BR>&nbsp; &lt;resource description="The ability to get a user's details" id="profilestore.GetUser"&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;roles&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;role id="profilestore.administrator"/&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;/roles&gt;<BR>&nbsp; &lt;/resource&gt;<BR>&nbsp; &lt;resource description="The ability to get a user property" id="profilestore.GetUserProperty"&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;roles&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;role id="profilestore.administrator"/&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;/roles&gt;<BR>&nbsp; &lt;/resource&gt;<BR>&nbsp; &lt;resource description="The ability to get user details" id="profilestore.GetUsers"&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;roles&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;role id="profilestore.administrator"/&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;/roles&gt;<BR>&nbsp; &lt;/resource&gt;<BR>&nbsp; &lt;resource description="The ability to check if a user has&nbsp; a resource" id="profilestore.HasResource"&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;roles&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;role id="profilestore.administrator"/&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;/roles&gt;<BR>&nbsp; &lt;/resource&gt;<BR>&nbsp; &lt;resource description="The ability to check if a user has a role" id="profilestore.HasRole"&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;roles&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;role id="profilestore.administrator"/&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;/roles&gt;<BR>&nbsp; &lt;/resource&gt;<BR>&nbsp; &lt;resource description="The ability to modify a user's details" id="profilestore.ModifyUser"&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;roles&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;role id="profilestore.administrator"/&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;/roles&gt;<BR>&nbsp; &lt;/resource&gt;<BR>&nbsp; &lt;resource description="The ability to remove a resource from a role" id="profilestore.RemoveResourceFromRole"&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;roles&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;role id="profilestore.administrator"/&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;/roles&gt;<BR>&nbsp; &lt;/resource&gt;<BR>&nbsp; &lt;resource description="The ability to remove a role from a user" id="profilestore.RemoveRoleFromUser"&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;roles&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;role id="profilestore.administrator"/&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;/roles&gt;<BR>&nbsp; &lt;/resource&gt;<BR>&nbsp; &lt;resource description="The ability to set a user property" id="profilestore.SetUserProperty"&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;roles&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;role id="profilestore.administrator"/&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;/roles&gt;<BR>&nbsp; &lt;/resource&gt;<BR>&lt;/resources&gt;<BR>-------------------------------------------------</P>
<P>&nbsp;</P><img src ="http://www.blogjava.net/kapok/aggbug/5086.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-05-23 23:39 <a href="http://www.blogjava.net/kapok/archive/2005/05/23/5086.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>java中执行程序的例子 </title><link>http://www.blogjava.net/kapok/archive/2005/05/23/5085.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Mon, 23 May 2005 15:37:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/05/23/5085.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/5085.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/05/23/5085.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/5085.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/5085.html</trackback:ping><description><![CDATA[<P><SPAN class=postbody><A href="http://firebody.blogbus.com/logs/2004/08/355958.html">http://firebody.blogbus.com/logs/2004/08/355958.html</A><BR><BR>本来也想弄这样的咚咚！javaeye论坛刚好有人贴出来，我就转过来，以做个记录！</SPAN></P>
<P><SPAN class=postbody></SPAN><SPAN class=postbody>数据库导出 <BR>String path="mysqldump.exe -uuser -ppwd --opt databasename &gt; d:/databack/xx.sql"; <BR>java.lang.Runtime.getRuntime().exec("cmd /c "+path); <BR>System.out.println("数据表已导出到文件xx.sql中"); <BR><BR>//数据库导入 <BR>String path="mysqladmin -uroot -p create databasename"; <BR>java.lang.Runtime.getRuntime().exec("cmd /c "+path); <BR>path="mysql databasename ＜ d:/databack/xx.sql"; <BR>java.lang.Runtime.getRuntime().exec("cmd /c "+path); <BR>System.out.println("数据表已从文件xx.sql中导入");</SPAN></P><img src ="http://www.blogjava.net/kapok/aggbug/5085.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-05-23 23:37 <a href="http://www.blogjava.net/kapok/archive/2005/05/23/5085.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>先简单记录在这里</title><link>http://www.blogjava.net/kapok/archive/2005/05/23/5082.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Mon, 23 May 2005 15:14:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/05/23/5082.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/5082.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/05/23/5082.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/5082.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/5082.html</trackback:ping><description><![CDATA[<A href="http://magician.mblogger.cn/posts/10215.aspx">http://magician.mblogger.cn/posts/10215.aspx</A><img src ="http://www.blogjava.net/kapok/aggbug/5082.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-05-23 23:14 <a href="http://www.blogjava.net/kapok/archive/2005/05/23/5082.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>这里的几篇文章都不错的</title><link>http://www.blogjava.net/kapok/archive/2005/05/18/4586.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Wed, 18 May 2005 04:54:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/05/18/4586.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/4586.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/05/18/4586.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/4586.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/4586.html</trackback:ping><description><![CDATA[<A href="http://www.jetmaven.net/documents/java.php">http://www.jetmaven.net/documents/java.php</A><img src ="http://www.blogjava.net/kapok/aggbug/4586.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-05-18 12:54 <a href="http://www.blogjava.net/kapok/archive/2005/05/18/4586.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>学习映射类层次结构的三个易于实现的策略</title><link>http://www.blogjava.net/kapok/archive/2005/05/17/4432.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Tue, 17 May 2005 10:27:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/05/17/4432.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/4432.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/05/17/4432.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/4432.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/4432.html</trackback:ping><description><![CDATA[<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR vAlign=top>
<TD width=5><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width=5 border=0></TD>
<TD width="100%"><A href="http://www-128.ibm.com/developerworks/cn/java/j-hibernate/?ca=dwcn-newsletter-java">http://www-128.ibm.com/developerworks/cn/java/j-hibernate/?ca=dwcn-newsletter-java</A><BR><BR>
<TABLE cellSpacing=0 cellPadding=0 width=168 align=right border=0>
<TBODY>
<TR>
<TD width=8><IMG height=21 alt="" src="http://www.ibm.com/i/c.gif" width=5></TD>
<TD width=160>
<TABLE cellSpacing=0 cellPadding=0 width=160 border=0>
<TBODY>
<TR>
<TD width=160 bgColor=#000000 height=1><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD align=middle background=/developerworks/cn/i/bg-gold.gif height=5><B>内容：</B></TD></TR>
<TR>
<TD width=160 bgColor=#666666 height=1><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD>
<TABLE cellSpacing=0 cellPadding=0 width=160 border=0>
<TBODY>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/java/j-hibernate/?ca=dwcn-newsletter-java#0">概述</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/java/j-hibernate/?ca=dwcn-newsletter-java#1">我们的支持示例</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/java/j-hibernate/?ca=dwcn-newsletter-java#2">策略 1: 每个子类一个表（Persons）</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/java/j-hibernate/?ca=dwcn-newsletter-java#3">策略 2：每个类层次结构一个表（Rights）</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/java/j-hibernate/?ca=dwcn-newsletter-java#3.1">数据库模型的完整性</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/java/j-hibernate/?ca=dwcn-newsletter-java#4">策略 3: 每个具体类一个表（Estates）</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/java/j-hibernate/?ca=dwcn-newsletter-java#5">多态 —— 用到极限！</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/java/j-hibernate/?ca=dwcn-newsletter-java#5">结束语</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR><!--Standard links for every dw-article-->
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/java/j-hibernate/?ca=dwcn-newsletter-java#resources">参考资料 </A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/java/j-hibernate/?ca=dwcn-newsletter-java#download">下载</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/java/j-hibernate/?ca=dwcn-newsletter-java#author1">作者简介</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/java/j-hibernate/?ca=dwcn-newsletter-java#rating">对本文的评价</A></TD></TR>
<TR>
<TD><IMG height=10 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE>
<TABLE cellSpacing=0 cellPadding=0 width=160 border=0>
<TBODY>
<TR>
<TD width=160 bgColor=#000000 height=1><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD align=middle background=/developerworks/cn/i/bg-gold.gif height=5><B>相关内容：</B></TD></TR>
<TR>
<TD width=160 bgColor=#666666 height=1><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD>
<TABLE cellSpacing=0 cellPadding=1 width=160 border=0>
<TBODY>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/java/j-hibern/index.html" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">无需容器的对象关系映射 </A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/db2/library/techarticle/0306bhogal/0306bhogal.html" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Using Hibernate to persist your Java objects to IBM DB2 Universal Database</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/websphere/techjournal/0409_patil/0409_patil.html" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Developing Hibernate application for use with WebSphere Application Server</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD><A href="http://www.ibm.com/developerworks/java/jdk/index.html" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">IBM developer kits for the Java platform (downloads)</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE>
<TABLE cellSpacing=0 cellPadding=0 width=160 border=0>
<TBODY>
<TR>
<TD width=160 bgColor=#000000 height=1><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD align=middle background=/developerworks/cn/i/bg-gold.gif height=5><B>订阅:</B></TD></TR>
<TR>
<TD width=160 bgColor=#666666 height=1><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD>
<TABLE cellSpacing=0 cellPadding=1 width=160 border=0>
<TBODY>
<TR>
<TD><A href="http://www-128.ibm.com/developerworks/cn/newsletter/">developerWorks 时事通讯</A></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD height=1><IMG height=5 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE>
<TABLE cellSpacing=0 cellPadding=0 width=160 border=0>
<TBODY>
<TR>
<TD width=150 bgColor=#000000 colSpan=2 height=2><IMG height=2 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR>
<TR>
<TD width=150 bgColor=#ffffff colSpan=2 height=2><IMG height=2 alt="" src="http://www.ibm.com/i/c.gif" width=160></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><SPAN class=atitle2>学习映射类层次结构的三个易于实现的策略</SPAN><BR>
<TABLE cellSpacing=0 cellPadding=0 border=0>
<TBODY>
<TR vAlign=top align=left>
<TD>
<P>级别: 初级</P></TD></TR></TBODY></TABLE>
<P><A href="http://www-128.ibm.com/developerworks/cn/java/j-hibernate/?ca=dwcn-newsletter-java#author1"><NAME>Xavier Coulon</NAME></A>, 电子商务 IT 专家, IBM Business Consulting Services<BR><A href="http://www-128.ibm.com/developerworks/cn/java/j-hibernate/?ca=dwcn-newsletter-java#author2"><NAME>Christian Brousseau</NAME></A>, J2EE 顾问<BR><BR>2005 年 1 月 14 日</P>
<BLOCKQUOTE>Hibernate 是一个对象关系映射和持久性框架，它提供了许多高级特性，从内省到多态和继承映射。但是，把类的层次结构映射到关系数据库模型，可能会比较困难。本文将介绍三个策略，在日常的编程之中您可以用它们把复杂的对象模型容易地映射到关系数据库模型。</BLOCKQUOTE>
<P><A name=0><SPAN class=atitle2>概述</SPAN></A><BR>Hibernate 是一个纯 Java 的对象关系映射和持久性框架，它允许您用 XML 配置文件把普通 Java 对象映射到关系数据库表。使用 Hibernate 能够节约大量项目开发时间，因为整个 JDBC 层都由这个框架管理。这意味着您的应用程序的数据访问层位于 Hibernate 之上，完全是从底层数据模型中抽象出来的。 </P>
<P>比起其他类似的对象关系映射技术（JDO、实体 bean、内部开发等），Hibernate 有许多优势：它是免费的、开源的，已经成熟到良好的程度，并得到广泛应用，而且还有一个非常活跃的社区论坛。 </P>
<P>要把 Hibernate 集成到现有的 Java 项目，则需要执行以下步骤：</P>
<OL xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<LI>从 Hibernate 的 Web 站点下载 Hibernate 框架的最新发行版（请参阅 <A href="http://www-128.ibm.com/developerworks/cn/java/j-hibernate/?ca=dwcn-newsletter-java#resources">参考资料</A>一节中的链接。） <BR><BR>
<LI>把必需的 Hibernate 库（JAR 文件）复制到应用程序的 CLASSPATH。 <BR><BR>
<LI>创建 XML 配置文件，用它把 Java 对象映射到数据库表。（我们将在本文中描述这个过程。） <BR><BR>
<LI>把 XML 配置文件复制到应用程序的 CLASSPATH。 </LI></OL>
<P>您会注意到，不必修改任何 Java 对象，您就可以支持框架。例如，假设您对 Java 应用程序使用的数据库表做了些修改 —— 例如修改了列名。在修改完表之后，您要做的只是更新对应的 XML 配置文件。 <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">您不需要重新编译任何 Java 代码。</I> </P>
<P><A name=0.1><SPAN class=atitle3>Hibernate 查询语言（HQL）</SPAN></A><BR>Hibernate 提供了一个查询语言，叫作 Hibernate 查询语言（HQL），它与 SQL 很相似。如果您喜欢用老式的 SQL 查询，那么 Hibernate 也为您提供了使用它们的机会。但是我们使用的示例只用 HQL。 </P>
<P>HQL 用起来相当简单。您会发现所有的关键字都与您熟悉的 SQL 中的关键字类似，例如 <CODE>SELECT</CODE>、 <CODE>FROM</CODE> 和 <CODE>WHERE</CODE>。HQL 与 SQL 的差异在于，您不用针对数据模型（即针对表和列等）直接编写查询，而是应该针对 Java 对象，使用 Java 对象的属性和关系编写查询。 </P>
<P>清单 1 演示了一个基本的示例。这个 HQL 代码检索 <CODE>firstName</CODE> 为 “John.” 的所有 <CODE>Individual</CODE>。 </P><A name=listing1><B>清单 1. 基本 HQL 查询</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
SELECT * FROM eg.hibernate.mapping.dataobject.Individual WHERE firstName = "John"
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>如果想了解更多有关 HQL 语法的内容，那么您可以参阅 Hibernate 的 Web 站点上有关 HQL 的参考材料（请参阅 <A href="http://www-128.ibm.com/developerworks/cn/java/j-hibernate/?ca=dwcn-newsletter-java#resources" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">参考资料</A>，以获得链接）。 </P>
<P><A name=0.2><SPAN class=atitle3>XML 配置文件</SPAN></A><BR>功能的核心在于 XML 配置文件。这些文件必须存在于应用程序的 CLASSPATH 中。我们把它们放在示例代码包的 config 目录中（您可以从 <A href="http://www-128.ibm.com/developerworks/cn/java/j-hibernate/?ca=dwcn-newsletter-java#resources" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">参考资料</A>下载）。 </P>
<P>我们要研究的第一个文件是 hibernate.cfg.xml。它包含与数据源有关的信息（数据库 URL、模式名称、用户名、口令等），以及对包含映射信息的其他配置文件的引用。 </P>
<P>其余的 XML 文件允许您把 Java 类映射到数据库表。稍后我再深入介绍这些文件，但重要的是要清楚它们的文件名要遵守 ClassName.hbm.xml 这个模式。 </P>
<P><A name=1><SPAN class=atitle2>我们的支持示例</SPAN></A><BR>在本文中，我们要研究一个基本示例，演示 Hibernate 如何工作，如何良好地运用三个不同策略，利用 Hibernate 进行对象关系映射。我们的示例是一家保险公司使用的应用程序，公司必须保持客户投保的所有产权的法律记录。我们随本文提供了完整的源代码（请参阅 <A href="http://www-128.ibm.com/developerworks/cn/java/j-hibernate/?ca=dwcn-newsletter-java#resources" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">参考资料</A>）；这个代码提供了基本功能，您可以根据它构建全功能的应用程序，例如 Web 或 Swing 应用程序。 </P>
<P>我们的示例采用了这类应用程序的经典用例。用户提供搜索参数，查找各种类型的客户（个人、公司、政府机构等），然后显示与指定参数匹配的所有客户列表 —— 即使这些客户的类型不同。用户可以访问同一列表中的某一特定客户更加详细的视图。 </P>
<P>在我们的应用程序中，产权由 <CODE>Right</CODE> 类表示。 <CODE>Right</CODE> 可以是 <CODE>Lease</CODE> 也可以是 <CODE>Property</CODE>。 <CODE>Right</CODE> 由客户所有。为了表示我们的客户，我们要使用通用类 <CODE>Person</CODE>。 <CODE>Person</CODE> 即可以是 <CODE>Individual</CODE> 也可以是 <CODE>Corporation</CODE>。当然，保险公司必须知道这些 <CODE>Right</CODE> 被分配给哪个 <CODE>Estate</CODE>。您应当同意， <CODE>Estate</CODE> 这个术语代表的意义非常泛。所以，我们要用 <CODE>Land</CODE> 和 <CODE>Building</CODE> 类给我们的开发人员提供更具体的操作对象。 </P>
<P>从这个抽象出发，我们可以开发图 1 所示的类模型：</P>
<P><A name=figure1><B>图 1. 完整的类模型</B></A><BR><IMG height=221 alt=完整的类模型 src="http://www-128.ibm.com/developerworks/cn/java/j-hibernate/ClassModel-large.jpg" width=600 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> </P>
<P>我们的数据库模型是为了介绍将在本文中讨论的三个不同策略而设计的。对于 <CODE>Right</CODE> 层次结构来说，我们要使用一个表（ <CODE>TB_RIGHT</CODE>），并用 <CODE>DISCRIMINATOR</CODE> 列映射到正确的类。对于 <CODE>Person</CODE> 结构，我们要使用一个称为 <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">超表</I>（ <CODE>TB_PERSON</CODE>）的表，它与另外两个表（ <CODE>TB_CORPORATION</CODE> 和 <CODE>TB_INDIVIDUAL</CODE>）共享相同的 <CODE>ID</CODE>。第三个层次结构（ <CODE>Estate</CODE>）使用两个不同的表（ <CODE>TB_BUILDING</CODE> 和 <CODE>TB_LAND</CODE>），这两个表通过由两个列（ <CODE>REF_ESTATE_ID</CODE> 和 <CODE>REF_ESTATE_TYPE</CODE>）组合定义的外键连接在一起。 </P>
<P>图 2 显示了这个数据模型:</P>
<P><A name=figure2><B>图 2. 完整的数据模型</B></A><BR><IMG height=221 alt=完整的数据模型 src="http://www-128.ibm.com/developerworks/cn/java/j-hibernate/DataModel-large.jpg" width=600 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> </P>
<P><A name=1.1><SPAN class=atitle3>设置数据库</SPAN></A><BR>Hibernate 支持各种各样的 RDBMS，其中任何一种都可以使用我们的示例。但是，本文的示例代码和文本已经针对 HSQLDB（请参阅 <A href="http://www-128.ibm.com/developerworks/cn/java/j-hibernate/?ca=dwcn-newsletter-java#resources" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">参考资料</A>查找链接）进行了调整，这是一个完全用 Java 语言编写的全功能的关系数据库系统。在示例代码包的 sql 目录中，可以找到叫作 datamodel.sql 的文件。这个 SQL 脚本可以创建我们示例中使用的数据模型。 </P>
<P><A name=1.2><SPAN class=atitle3>设置 Java 项目</SPAN></A><BR>虽然您总能用命令行构建并执行示例代码，但是您可能想在 IDE 中设置项目，以便更好地进行集成。在示例代码包里，您可以找到以下目录：</P>
<UL xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<LI><B>config</B>，包含样本的所有 XML 配置文件（映射、Log4J 等）。 <BR><BR>
<LI><B>data</B>，包含 HSQLDB 使用的配置文件。您还可以找到一个叫作 startHSQLDB.bat 的批处理文件，您可以用它启动数据库。 <BR><BR>
<LI><B>src</B>，包含示例的所有源代码。 </LI></UL>
<P>请确保把必需的 Java 库和 XML 配置文件复制到应用程序的 CLASSPATH。只需要 Hibernate 和 HSQLDB 库，这些代码就可以正确地编译和运行。您可以从 <A href="http://www-128.ibm.com/developerworks/cn/java/j-hibernate/?ca=dwcn-newsletter-java#resources" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">参考资料</A>一节下载这些包。 </P>
<P><A name=2><SPAN class=atitle2>策略 1: 每个子类一个表（Persons）</SPAN></A><BR>在我们第一个策略中，我们要看看如何映射我们的 <CODE>Person</CODE> 层次结构。您会注意到，数据模型与我们的类模型非常接近。所以，我们要为层次结构中的每个类采用一个不同的表，但是所有这些表都必须共享相同的主键（我们很快就会详细说明）。Hibernate 在向数据库中插入新记录时，就会使用这个主键。在访问数据库时，它还会利用同一主键执行 <CODE>JOIN</CODE> 操作。 </P>
<P>现在我们需要把对象层次结构映射到表模型。我们有三个表（ <CODE>TB_PERSON</CODE>、 <CODE>TB_INDIVIDUAL</CODE>、和 <CODE>TB_CORPORATION</CODE>）。前面我们提过，它们都有一个叫作 <CODE>ID</CODE> 的列，并将该列作为主键。表之间不一定非要有这样的共享列名称，但是这是一个很好的实践 —— 这样做的话，生成的 SQL 查询更容易阅读。 </P>
<P>在清单 2 所示的 XML 映射文件中，您会注意到，在 <CODE>Person</CODE> 的映射定义中，声明了两个具体的 <CODE>&lt;joined-subclass&gt;</CODE> 类。XML 元素 <CODE>&lt;id&gt;</CODE> 映射到顶级表 <CODE>TB_PERSON</CODE> 的主键，同时 <CODE>&lt;key&gt;</CODE> 元素（来自每个子类）映射到 <CODE>TB_INDIVIDUAL</CODE> 的 <CODE>TB_CORPORATION</CODE> 表中匹配的主键。 </P><A name=listing2><B>清单 2. Person.hbm.xml</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
	"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"&gt;

&lt;hibernate-mapping&gt;
  &lt;class name="eg.hibernate.mapping.dataobject.Person" table="TB_PERSON" polymorphism="implicit"&gt;
    &lt;id name="id" column="ID"&gt;
      &lt;generator class="assigned"/&gt;
    &lt;/id&gt;
    &lt;set name="rights" lazy="false"&gt;
      &lt;key column="REF_PERSON_ID"/&gt;
      &lt;one-to-many class="eg.hibernate.mapping.dataobject.Right" /&gt;
    &lt;/set&gt;
    
        <SPAN class=boldcode>&lt;joined-subclass name="eg.hibernate.mapping.dataobject.Individual" table="TB_INDIVIDUAL"&gt;</SPAN>
      &lt;key column="id"/&gt;
      &lt;property name="firstName" column="FIRST_NAME" type="java.lang.String" /&gt;
      &lt;property name="lastName" column="LAST_NAME" type="java.lang.String" /&gt;
    &lt;/joined-subclass&gt;
    
        <SPAN class=boldcode>&lt;joined-subclass name="eg.hibernate.mapping.dataobject.Corporation" table="TB_CORPORATION"&gt;</SPAN>
      &lt;key column="id"/&gt;
      &lt;property name="name" column="NAME" type="string" /&gt;
      &lt;property name="registrationNumber" column="REGISTRATION_NUMBER" type="string" /&gt;
    &lt;/joined-subclass&gt;
  &lt;/class&gt;
&lt;/hibernate-mapping&gt;

      </CODE></PRE></TD></TR></TBODY></TABLE>
<P>保存 <CODE>Individual</CODE> 的一个新实例，形成我们使用 Hibernate 的 Java 代码非常容易，如清单 3 所示： </P><A name=listing3><B>清单 3. 保存 Individual 的一个新实例</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
public Object create(Object object) {
  Session session = null;
  try {
    session = sessionFactory.openSession();
    Transaction tx = session.beginTransaction();
    
        <SPAN class=boldcode>session.save(object);</SPAN>
    session.flush();
    tx.commit();
    ...
}

      </CODE></PRE></TD></TR></TBODY></TABLE>
<P>接着，Hibernate 生成两个 SQL <CODE>INSERT</CODE> 请求，如清单 4 所示。这两个请求面向的是一个 <CODE>save()</CODE>。 </P><A name=listing4><B>清单 4. SQL 插入查询</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
insert into TB_PERSON (ID) values (?)
insert into TB_INDIVIDUAL (FIRST_NAME, LAST_NAME, id) values (?, ?, ?)
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>要想访问数据库中的 <CODE>Individual</CODE>，只需在 HQL 查询中指定类名即可，如清单 5 所示。 </P><A name=listing5><B>清单 5. 调用 HQL 查询</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
public Person findIndividual(Integer id) {
  ...
  session.find(
        <SPAN class=boldcode>"select p from " + Individual.class.getName() + " as p where p.id = ?"</SPAN>,
    new Object[] { id },
    new Type[] { Hibernate.INTEGER });	
  ...
}

      </CODE></PRE></TD></TR></TBODY></TABLE>
<P>Hibernate 会自动执行 SQL 的 <CODE>JOIN</CODE>，从两个表中检索所有必要信息，如清单 6 所示： </P><A name=listing6><B>清单 6. 查找 Individual 的 SQL SELECT 查询</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
select individual0_.id as ID, individual0_.FIRST_NAME as FIRST_NAME55_, 
  individual0_.LAST_NAME as LAST_NAME55_ 
  from TB_INDIVIDUAL individual0_ 
  
        <SPAN class=boldcode>inner join</SPAN> TB_PERSON individual0__1_ on individual0_.id=individual0__1_.ID 
  where (individual0_.id=? )

      </CODE></PRE></TD></TR></TBODY></TABLE>
<P>
<TABLE cellSpacing=0 cellPadding=5 width="30%" align=right border=1>
<TBODY>
<TR>
<TD background=/developerworks/cn/i/bg-gold.gif>
<P><A name=sidebar1><B>查询抽象类</B></A><BR>当查询抽象类时，Hibernate 会自动返回一个集合，由匹配的具体异构子类构成。例如，如果我们查询数据库中的每个 <CODE>Person</CODE>，Hibernate 会返回一列 <CODE>Individual</CODE> 和 <CODE>Corporation</CODE> 对象。 </P></TD></TR></TBODY></TABLE></P>
<P>但是，当没有指定具体类时，Hibernate 需要执行 SQL 的 <CODE>JOIN</CODE>，因为它不知道要查询哪个表。在 HQL 查询返回的检索到的所有表的列中，还会返回一个额外的 <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">dynamic</I> 列。Hibernate 使用 <CODE>clazz</CODE> 列来初始化和填充返回的对象。我们把这个类叫作决定因子（determination <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">dynamic</I>），与我们在第二个策略中使用的方法相对。 </P>
<P>清单 7 显示了如何指定抽象类的 <CODE>id</CODE> 属性查询抽象类 <CODE>Person</CODE>，清单 8 显示了 Hibernate 自动生成的 SQL 查询，其中包括表连接： </P><A name=listing7><B>清单 7. find() 方法调用中的 HQL 查询</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
public Person find(Integer id) {
  ...
  session.find(
        <SPAN class=boldcode>"select p from " + Person.class.getName() + " as p where p.id = ?"</SPAN>,
    new Object[] { id },
    new Type[] { Hibernate.INTEGER });	
  ...
}

      </CODE></PRE></TD></TR></TBODY></TABLE><A name=listing8><B>清单 8. 查找任何类型的 Person 的 SQL SELECT 查询</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
select person0_.ID as ID0_,
        <SPAN class=boldcode>
  casewhen(person0__1_.id is not null, 1,  
  casewhen(person0__2_.id is not null, 2,  
  casewhen(person0_.ID is not null, 0, -1))) as clazz_0_</SPAN>, 
  person0__1_.FIRST_NAME as FIRST_NAME61_0_, 
  person0__1_.LAST_NAME as LAST_NAME61_0_, 
  person0__2_.NAME as NAME62_0_, 
  person0__2_.REGISTRATION_NUMBER as REGISTRA3_62_0_ 
  from TB_PERSON person0_ 
  left outer join TB_INDIVIDUAL person0__1_ on person0_.ID=person0__1_.id 
  left outer join TB_CORPORATION person0__2_ on person0_.ID=person0__2_.id 
  where person0_.ID=?

      </CODE></PRE></TD></TR></TBODY></TABLE>
<P><A name=3><SPAN class=atitle2>策略 2：每个类层次结构一个表（Rights）</SPAN></A><BR>对于我们的 <CODE>Right</CODE> 层次结构，我们只使用一个表（ <CODE>TB_RIGHT</CODE>）来保存整体类层次结构。您会注意到， <CODE>TB_RIGHT</CODE> 表拥有保存 <CODE>Right</CODE> 类层次结构的每个属性所需要的所有列。保存的实例值也就会保存在表中，每个没有使用的列则用 NULL 值填充。（因为它到处都是“洞”，所以我们经常把它叫作 <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">瑞士奶酪表</I>） </P>
<P>在图 3 中，您会注意到， <CODE>TB_RIGHT</CODE> 表中包含一个额外的列 <CODE>DISCRIMINATOR</CODE>。Hibernate 用这个列自动初始化对应的类并相应进行填充。这个类用映射文件中的 XML 元素 <CODE>&lt;discriminator&gt;</CODE> 进行映射。 </P>
<P><A name=figure3><B>图 3. TB_RIGHT 表的内容</B></A><BR><IMG height=416 alt="TB_RIGHT 表的内容" src="http://www-128.ibm.com/developerworks/cn/java/j-hibernate/select.jpg" width=600 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> </P>
<P>
<TABLE cellSpacing=0 cellPadding=5 width="30%" align=right border=1>
<TBODY>
<TR>
<TD background=/developerworks/cn/i/bg-gold.gif>
<P><A name=sidebar2><B>简明性技巧</B></A><BR>在每个大型项目中，您都会面临包含多级抽象类的复杂的类层次结构。幸运的是，您不必指定抽象类的 <CODE>discriminator-value</CODE>，只需为 Hibernate 实际要使用的具体类指定这个值即可。 </P></TD></TR></TBODY></TABLE></P>
<P>正如清单 2 所示的 <CODE>Person</CODE> 映射文件，在清单 9 中，我们映射了抽象类（ <CODE>Right</CODE>）及其所有属性。要映射两个具体类（ <CODE>Lease</CODE> 和 <CODE>Property</CODE>），就要使用 <CODE>&lt;subclass&gt;</CODE> XML 标签。这个标签非常简单；它要求 <CODE>name</CODE> 属性，仅仅是因为 <CODE>class</CODE> 标签要求 <CODE>discriminator-value</CODE> 属性。Hibernate 将用后一个属性标识它要处理的类。 </P>
<P>从 <A href="http://www-128.ibm.com/developerworks/cn/java/j-hibernate/?ca=dwcn-newsletter-java#figure1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">图 1</A> 的类图中您会注意到， <CODE>discriminator</CODE> 不是任何 Java 类都有的属性。实际上，它甚至没有映射。它仅仅是 Hibernate 和数据库之间共享的一个技术性的列。 </P><A name=listing9><B>清单 9. Right.hbm.xml</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;!DOCTYPE hibernate-mapping PUBLIC 
  "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
  "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"&gt;
&lt;hibernate-mapping&gt;
  &lt;class name="eg.hibernate.mapping.dataobject.Right" table="TB_RIGHT" polymorphism="implicit"&gt;
    &lt;id name="id" column="ID"&gt;
      &lt;generator class="assigned"/&gt;
    &lt;/id&gt;
    
        <SPAN class=boldcode>&lt;discriminator&gt;
      &lt;column name="DISCRIMINATOR"/&gt;
    &lt;/discriminator&gt;</SPAN>
    &lt;property name="date" column="DATE" type="java.sql.Date" /&gt;
    &lt;many-to-one name="person" class="eg.hibernate.mapping.dataobject.Person" column="REF_PERSON_ID"/&gt;
    &lt;any name="estate"
         meta-type="string"
         id-type="java.lang.Integer"&gt;
      &lt;meta-value value="LND" class="eg.hibernate.mapping.dataobject.Land"/&gt;
      &lt;meta-value value="BLD" class="eg.hibernate.mapping.dataobject.Building"/&gt;
      &lt;column name="REF_ESTATE_TYPE"/&gt;
      &lt;column name="REF_ESTATE_ID"/&gt;          
    &lt;/any&gt;
    
    
        <SPAN class=boldcode>&lt;subclass name="eg.hibernate.mapping.dataobject.Property" discriminator-value="PRO"/&gt;
    
    &lt;subclass name="eg.hibernate.mapping.dataobject.Lease" discriminator-value="LEA"&gt;</SPAN>
      &lt;property name="duration" column="DURATION" type="java.lang.Integer" /&gt;
     
        <SPAN class=boldcode>&lt;/subclass&gt;</SPAN>
  &lt;/class&gt;
&lt;/hibernate-mapping&gt;

      </CODE></PRE></TD></TR></TBODY></TABLE>
<P>在清单 9 的映射文件中，您会注意到 <CODE>Right</CODE> 和 <CODE>Person</CODE> 层次结构之间的“多对一”关系，它（实质上）与 <CODE>Person</CODE> 层次结构（一对多）的关系正好相反。还请注意 <CODE>Right</CODE> 和 <CODE>Estate</CODE> 层次结构之间的关系；稍后我们将在本文中介绍这层关系。 </P>
<P>使用第一个策略时，Hibernate 在访问数据库时生成了非常有效的 SQL 语句。当我们查询具体类时，如清单 10 所示，会在 <CODE>discriminator</CODE> 的 Hibernate 过滤器上自动取值 —— 这是件好事，因为这意味着 Hibernate 只读取与指定类对应的列。 </P><A name=listing10><B>清单 10. 针对具体类的 SQL 查询</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
select property0_.ID as ID, property0_.DATE as DATE, 
	property0_.REF_PERSON_ID as REF_PERS4_, property0_.REF_ESTATE_TYPE as REF_ESTA5_, 
	property0_.REF_ESTATE_ID as REF_ESTA6_ 
	from TB_RIGHT property0_ where property0_.DISCRIMINATOR='PRO'
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>当我们查询抽象类时，事情变得有些复杂。因为 Hibernate 不知道您要查询哪个特定的类，所以必须读取每个列（包括 discriminator 类），然后才能决定要初始化哪个类，最后再填充它。接下来，discriminator 充当的角色与第一个策略中 <CODE>clazz</CODE> 列充当的角色相同。但是这个方法显然更死板，因为类名直接派生自 discriminator 的值。 </P><A name=listing11><B>清单 11. （抽象的） Right 类的 SQL 查询</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
select right0_.ID as ID, 
	right0_.DISCRIMINATOR as DISCRIMI2_, 
	right0_.DATE as DATE, right0_.REF_PERSON_ID as REF_PERS4_, 
	right0_.REF_ESTATE_TYPE as REF_ESTA5_, right0_.REF_ESTATE_ID as REF_ESTA6_, 
	right0_.DURATION as DURATION from TB_RIGHT right0_
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>
<TABLE cellSpacing=0 cellPadding=5 width="30%" align=right border=1>
<TBODY>
<TR>
<TD background=/developerworks/cn/i/bg-gold.gif>
<P><A name=sidebar3><B>策略的不兼容</B></A><BR>按照 Hibernate 映射的 DTD 定义，本文中描述的前两个策略是相互排斥的，这意味着它们无法组合在一起，映射同一个层次结构。</P></TD></TR></TBODY></TABLE></P>
<P><A name=3.1><SPAN class=atitle3>数据库模型的完整性</SPAN></A><BR>关于第二个策略，有一个需要重点考虑的地方：为了让它工作，必须把所有非共享列设置为 <CODE>NULLABLE</CODE>。因为开发人员通常会依赖数据库的约束，所以生成的表可能非常难以处理。（毕竟，把有持续时间的 <CODE>Lease</CODE> 设置为 <CODE>NULL</CODE> 没多大意义！） </P>
<P>解决方案之一是用 <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">数据库级检测约束</I>。您可以根据 <CODE>DISCRIMINATOR</CODE> 的值，定义一套要实施的规则，如清单 12 所示。当然，数据库引擎必须支持这些特性。而且，由于必须同时为全部具体类用一个有效表达式表示这些约束，所以当层次结构发展的时候，维护会很困难。 </P><A name=listing12><B>清单 12. 数据完整性约束</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
alter table TB_RIGHT 
	add constraint CHK_RIGHT check(	
		(discriminant ='DPP' and date is null and duration is null)
	or	(discriminant ='DLM' and date is not null and duration is not null));
</CODE></PRE></TD></TR></TBODY></TABLE>
<P><A name=4><SPAN class=atitle2>策略 3: 每个具体类一个表（Estates）</SPAN></A><BR>我们的第三个，也是最后一个策略可能是三个策略当中最有想象力的：每个具体类一个表，抽象超类 <CODE>Estate</CODE> 没有表。我们依靠 Hibernate 提供对 <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">多态（polymorphism）</I>的支持。在清单 3 所示的 XML 映射文件中，您会注意到，其中只映射了两个具体类（ <CODE>Building</CODE> 和 <CODE>Land</CODE>）： </P><A name=listing13><B>清单 13. Estate.hbm.xml</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;!DOCTYPE hibernate-mapping PUBLIC 
  "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
  "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"&gt;

&lt;hibernate-mapping&gt;
  &lt;class name="eg.hibernate.mapping.dataobject.Land" table="TB_LAND" polymorphism="implicit"&gt;
    &lt;id name="id" column="ID"&gt;
      &lt;generator class="assigned"/&gt;
    &lt;/id&gt;
    &lt;property name="description" column="DESCRIPTION" type="java.lang.String" /&gt;
    &lt;property name="squareFeet" column="SQUARE_FEET" type="java.lang.Double"/&gt;
  &lt;/class&gt;

  &lt;class name="eg.hibernate.mapping.dataobject.Building" table="TB_BUILDING" polymorphism="implicit"&gt;
    &lt;id name="id" column="ID"&gt;
      &lt;generator class="assigned"/&gt;
    &lt;/id&gt;
    &lt;property name="description" column="DESCRIPTION" type="java.lang.String" /&gt;
    &lt;property name="address" column="ADDRESS" type="java.lang.String"/&gt;
  &lt;/class&gt;
&lt;/hibernate-mapping&gt;
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>
<TABLE cellSpacing=0 cellPadding=5 width="30%" align=right border=1>
<TBODY>
<TR>
<TD background=/developerworks/cn/i/bg-gold.gif>
<P><A name=sidebar4><B>在表间共享 ID 值</B></A><BR>要点在于，在同一类层次结构中映射的两个表之间，不必共享相同的 <CODE>ID</CODE> 值。如果您进行映射，那么 Hibernate 会为同一 <CODE>ID</CODE> 返回多个不同的对象。这可能会使 Hibernate 弄混 —— 您也会弄混。 </P></TD></TR></TBODY></TABLE></P>
<P>当您查看清单 13 中的映射文件时，您的第一个反应可能是说：“呵！这个映射与我每天使用的没什么不同啊！这里没什么重要的东西！”而且您这么想应当是对的。实际上，第三个策略只需要一个条件：需要把 <CODE>polymorphism</CODE> 属性设置为 <CODE>implicit</CODE>。 </P>
<P>即使在映射文件中找不到 <CODE>Estate</CODE> 类，它仍然存在于类层次结构之中。而且，因为两个映射的类（ <CODE>Building</CODE> 和 <CODE>Land</CODE>）是从 <CODE>Estate</CODE> 中继承而来，所以我们可以在 HQL 查询中使用这个抽象超类，如清单 14 所示。Hibernate 会用 <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">内省（introspection）</I> 找到扩展这个抽象类的类，以便依次为每个子类执行对应的 SQL 查询。 </P><A name=listing14><B>清单 14. find() 方法调用中的 HQL 查询</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
public Estate find(Integer id) {
  ...
  List objects =
    session.find(
      "select e from " + Estate.class.getName() + " as e where e.id = ?",
        new Object[] { id },
        new Type[] { Hibernate.INTEGER });
  ...
}
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>为了找到与指定 <CODE>ID</CODE> 匹配的 <CODE>Estate</CODE>，Hibernate 必须把清单 15 中的两个查询提交给数据库。 </P><A name=listing15><B>清单 15. SQL 查询</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
select land0_.ID as ID, land0_.DESCRIPTION as DESCRIPT2_, land0_.SQUARE_FEET as SQUARE_F3_ 
from TB_LAND land0_ where (land0_.ID=? )

select building0_.ID as ID, building0_.DESCRIPTION as DESCRIPT2_, building0_.ADDRESS as ADDRESS 
from TB_BUILDING building0_ where (building0_.ID=? )
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>正如我们在第二个策略中看到的，在 <CODE>Right</CODE> 和 <CODE>Estate</CODE> 类之间存在着 <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">多对一</I> 关系。在一般的表述中，这两个表的关系可以这么表述：“一个 <CODE>Estate</CODE> 可以指向 <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">许多 </I><CODE>Right</CODE>。但是每个 <CODE>Right</CODE> 只能指向 <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">一个 </I><CODE>Estate</CODE>。”但是从我们数据模型的角度来看，我们没有一个惟一的表可以用来创建我们的外键约束，就像 <CODE>TB_RIGHT</CODE> 和 <CODE>TB_PERSON</CODE> 之间那样。这使得我们几乎不可能创建外键。幸运的是，Hibernate 为我们提供了一个非常强大的 XML 映射元素 —— <CODE>&lt;any&gt;</CODE> 标签，它的用法如清单 16 所示。 </P><A name=listing16><B>清单 16. any 关系的 XML 映射</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
&lt;any name="estate"
	meta-type="string"
	id-type="java.lang.Integer"&gt;
  &lt;meta-value value="LND" class="eg.hibernate.mapping.dataobject.Land"/&gt;
  &lt;meta-value value="BLD" class="eg.hibernate.mapping.dataobject.Building"/&gt;
  &lt;column name="REF_ESTATE_TYPE"/&gt;
  &lt;column name="REF_ESTATE_ID"/&gt;          
&lt;/any&gt;
</CODE></PRE></TD></TR></TBODY></TABLE>
<P>
<TABLE cellSpacing=0 cellPadding=5 width="30%" align=right border=1>
<TBODY>
<TR>
<TD background=/developerworks/cn/i/bg-gold.gif>
<P><A name=sidebar5><B>禁止多态</B></A><BR>对于多态支持被禁止的类（ <CODE>&lt;class...polymorphism="explicit"...&gt;</CODE>），如果针对它们的超类进行查询，会把它们排除在外。 </P></TD></TR></TBODY></TABLE></P>
<P>我们进一步查看我们的新映射。我们的 <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">虚拟外键</I> 基于 <CODE>TB_RIGHT</CODE> 表的两个列。第一个列（ <CODE>REF_ESTATE_TYPE</CODE>）包含 <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">discriminator 字符串</I>，用这个字符串映射对应的类名。第二个（ <CODE>REF_ESTATE_ID</CODE>）是另外一个表的主键的列名。使用默认设置时，Hibernate 会在第一个列中保存映射的类名，这么做可能会非常消耗空间、没有效率（特别是在代码重构修改类名的时候）。谢天谢地，Hibernate 还提供了一个用 <CODE>&lt;meta-value&gt;</CODE> XML 元素把类名映射到字符串约束的方法。这些约束的作用与在第二个策略中讨论的 discriminator 的作用相同。再次声明，这些特性只包含 Hibernate 和数据库，所以不会改变类的层次结构。 </P>
<P><A name=4.1><SPAN class=atitle3>数据库模型的完整性</SPAN></A><BR>虽然标准 SQL 不允许对多个表同时针对指定列进行参考约束，但是仍有可能添加触发器，根据读取的 discriminator 值，触发器会检测目录中是否有数据。但是，这样的 <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">完整性实施</I> 方法可能非常难以维护，也有可能降低数据的整体性能。 </P>
<P><A name=5><SPAN class=atitle2>多态 —— 用到极限！</SPAN></A><BR>在使用 Hibernate 内置的多态时需要记住一件事：如果您一不小心，把所有的类都用 <CODE>polymorphism</CODE> 属性设置为 <CODE>implicit</CODE>，那么您检索到的信息可能要比您想要的多得多。清单 17 显示了一种使用两个词的 HQL 查询来检索 <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">整个数据库</I> 的方法。 </P><A name=listing17><B>清单 17. HQL 查询</B></A><BR>
<TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1>
<TBODY>
<TR>
<TD><PRE><CODE>
public List all() {
  ...
  List objects = session.find("
        <SPAN class=boldcode>from Object</SPAN>");
  ...
}

      </CODE></PRE></TD></TR></TBODY></TABLE>
<P>该查询的功能非常强大，您觉得呢？当然，我们之中没有多少人需要只用一个 HQL 查询检索整个数据库。这个（没有实际意义）的示例的目的就是为了显示隐式多态的能力。您可以利用这个能力避免把无用的、耗费资源的 SQL 查询发送到数据库。 </P>
<P><A name=5><SPAN class=atitle2>结束语</SPAN></A><BR>在本文中，我们试图向您提供一个相当简单的实现示例，演示 Hibernate 提供的三个映射策略。回头来看，每个策略都有自己的优势与不足：</P>
<UL xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<LI>对于第一个策略（每个子类一个表），Hibernate 每次初始化和填充对象时，会读取多个表。如果您的索引定义得很好，而且层次结构不是太深，那么这个操作可以产生良好的结果。但是，如果不是这种情况，那么您可能会遇到各种性能问题。 <BR><BR>
<LI>对于第二个策略（每个类层次结构一个表），您必须用 <I>检测约束</I> 定义您自己的完整性。但随着列的数量与时俱增，这个策略可能会变得难以维护。另一方面，您可能选择根本不用这样的约束，而依靠应用程序的代码来管理自己的数据完整性。 <BR><BR>
<LI>第三个策略（每个具体类一个表）有一些映射限制，而底层数据模型不能使用参照完整性，这意味着您不能发挥关系数据库引擎的所有潜力。但是，从好的方面说，该策略很常容易与另两个策略组合在一起。 </LI></UL>
<P>不管您选择哪种策略，都要记住，在整个过程当中，无需修改 Java 类，这意味着业务对象与持续性框架之间一点联系都没有。正是这样高水平的灵活性使 Hibernate 在对象关系 Java 项目中如此流行。</P>
<P><A name=resources><SPAN class=atitle2>参考资料 </SPAN></A>
<UL>
<LI>您可以参阅本文在 developerWorks 全球站点上的 <A href="http://www.ibm.com/developerworks/java/library/j-hibernate/" target=_blank xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">英文原文</A>。 <BR><BR>
<LI>请单击本文顶部或底部的 <B xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Code</B> 图标下载本文中使用的源代码示例。 <BR><BR>
<LI><A href="http://www.hibernate.org/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Hibernate site</A> 提供了您需要的与这个强大的对象持久性框架有关的所有信息。可以从这个站点下载运行示例应用程序所需的 Hibernate 文件。 <BR><BR>
<LI><A href="http://hsqldb.sourceforge.net/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">HSQLDB</A> 数据库是一个开源的轻量级数据库，完全用 Java 语言编写。可以从这个站点下载 HSQLDB，并把它用作示例应用程序的数据库。 <BR><BR>
<LI>请参阅 Hibernate 撰写的 <A href="http://www.hibernate.org/hib_docs/reference/en/html/queryhql.html" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">HQL reference</A>，来获得 Hibernate 查询语言的完整文档。 <BR><BR>
<LI>“ <A href="http://www.ibm.com/developerworks/db2/library/techarticle/0306bhogal/0306bhogal.html" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Using Hibernate to persist your Java objects to IBM DB2 Universal Database</A>”，作者是 Javid Jamae 和 Kulvir Singh Bhogal（developerWorks，2003 年 6 月），该书提供了用 Hibernate 将类映射到数据库表的良好指导。 <BR><BR>
<LI>“ <A href="http://www-128.ibm.com/developerworks/cn/java/j-hibern/index.html" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">无需容器的对象关系映射 </A>”，作者是 Richard Hightower，（developerWorks，2004 年 4 月），它对使用 Hibernate 和 Spring 框架开发事务性持久性层进行了介绍。 <BR><BR>
<LI>“ <A href="http://www.ibm.com/developerworks/websphere/techjournal/0409_patil/0409_patil.html" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Developing Hibernate 应用程序s for use with WebSphere Application Server</A>”，作者 Sunil Patil（ <I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">IBM WebSphere Developer Technical Journal，</I> 2004 年 9 月），提供了创建 Hibernate 应用程序时使用 Websphere Application Server 连接和事务管理的详细指导。 <BR><BR>
<LI>“ <A href="http://www.onjava.com/pub/a/onjava/2004/01/14/hibernate.html" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Hibernate your data</A>”，作者是 Davor Cengija（ONJava.com，2004 年 1 月），它提供了 Hibernate API 的所有基础知识，描述了如何利用 Hibernate API 的映射文件。 <BR><BR>
<LI><I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><A href="http://devworks.krcinfo.com/WebForms/ProductDetails.aspx?ProductID=193239415X">Hibernate in Action</A> </I>，作者是 Christian Bauer 和 Gavin King（Independent Pub Group，2004 年），这是一份关于对象关系映射的理论与实践指南。由 Hibernate 小组编写。 <BR><BR>
<LI><I xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><A href="http://devworks.krcinfo.com/WebForms/ProductDetails.aspx?ProductID=0596006969">Hibernate: A Developer's Notebook</A> </I>，作者是 James Elliot（O'Reilly，2004 年），这是另一份 Hibernate 的精彩指南。 <BR><BR>
<LI>在 <A href="http://www-128.ibm.com/developerworks/cn/java/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><I>developerWorks</I> Java 技术专区 </A>中，可以找到数百篇有关 Java 各个方面的技术文章。 <BR><BR>
<LI>请访问 <A href="http://devworks.krcinfo.com/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Developer Bookstore</A>，以获得技术书籍的完整清单，其中包括数百本 <A href="http://devworks.krcinfo.com/WebForms/ProductList.aspx?Search=Category&amp;id=1200&amp;p=Java" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Java 相关主题</A>的书籍。 <BR></LI></UL>
<P></P><A name=download></A><SPAN class=atitle2>下载</SPAN><BR>
<TABLE cellSpacing=0 cellPadding=0 border=0>
<TBODY>
<TR vAlign=top>
<TD width=1 height=8><IMG height=8 alt="" src="http://www.ibm.com/i/c.gif" width=1 border=0></TD></TR></TBODY></TABLE>
<TABLE cellSpacing=0 cellPadding=0 border=0>
<TBODY>
<TR vAlign=top>
<TD class=lgray width=4 height=18><NBSP></NBSP></TD>
<TD class=lgray height=18><B>Name</B></TD>
<TD class=lgray width=8 height=18><NBSP></NBSP></TD>
<TD width=2 height=18><IMG alt="" src="http://www.ibm.com/i/c.gif" width=2 border=0></TD>
<TD class=lgray width=4 height=18><NBSP></NBSP></TD>
<TD class=lgray height=18><B>Size</B></TD>
<TD class=lgray width=8 height=18><NBSP></NBSP></TD>
<TD width=2 height=18><IMG alt="" src="http://www.ibm.com/i/c.gif" width=2 border=0></TD>
<TD class=lgray width=4 height=18><NBSP></NBSP></TD>
<TD class=lgray height=18><B>Download method</B></TD>
<TD class=lgray width=8 height=18><NBSP></NBSP></TD>
<TD width=2 height=18><IMG alt="" src="http://www.ibm.com/i/c.gif" width=2 border=0></TD></TR>
<TR vAlign=top>
<TD width=4 height=18><NBSP></NBSP></TD>
<TD height=18>j-hibernate-source.zip</TD>
<TD width=8 height=18><NBSP></NBSP></TD>
<TD width=2 height=18><IMG alt="" src="http://www.ibm.com/i/c.gif" width=2 border=0></TD>
<TD width=4 height=18><NBSP></NBSP></TD>
<TD height=18></TD>
<TD width=8 height=18><NBSP></NBSP></TD>
<TD width=2 height=18><IMG alt="" src="http://www.ibm.com/i/c.gif" width=2 border=0></TD>
<TD width=4 height=18><NBSP></NBSP></TD>
<TD noWrap height=18><A class=fbox href="ftp://www6.software.ibm.com/software/developer/library/j-hibernate-source.zip"><B>FTP</B></A></TD>
<TD width=8 height=18><NBSP></NBSP></TD>
<TD width=2 height=18><IMG alt="" src="http://www.ibm.com/i/c.gif" width=2 border=0></TD></TR>
<TR vAlign=top>
<TD class=lgray height=1><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width="100%" border=0></TD>
<TD class=lgray height=1><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width="100%" border=0></TD>
<TD class=lgray height=1><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width="100%" border=0></TD>
<TD width=2 height=1><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width=2 border=0></TD>
<TD class=lgray height=1><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width="100%" border=0></TD>
<TD class=lgray height=1><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width="100%" border=0></TD>
<TD class=lgray height=1><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width="100%" border=0></TD>
<TD width=2 height=1><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width=2 border=0></TD>
<TD class=lgray height=1><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width="100%" border=0></TD>
<TD class=lgray height=1><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width="100%" border=0></TD>
<TD class=lgray height=1><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width="100%" border=0></TD>
<TD width=2 height=1><IMG height=1 alt="" src="http://www.ibm.com/i/c.gif" width=2 border=0></TD></TR></TBODY></TABLE>
<TABLE cellSpacing=0 cellPadding=0 border=0>
<TBODY>
<TR vAlign=top>
<TD><IMG height=12 alt="" src="http://www.ibm.com/i/c.gif" width=12 border=0></TD></TR>
<TR>
<TD><IMG height=11 alt=* src="http://www-128.ibm.com/developerworks/cn/i/fprtarrow.gif" width=12 border=0><A href="http://www-128.ibm.com/developerworks/cn/whichmethod.html">关于下载方法的信息</A><BR><IMG height=30 alt="" src="http://www.ibm.com/i/c.gif" width=10 border=0></TD></TR></TBODY></TABLE>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><A name=author1></A><SPAN class=atitle2>作者简介</SPAN><BR><IMG height=80 alt=作者照片 src="http://www-128.ibm.com/developerworks/cn/i/p-xco.jpg" width=64 align=left xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Xavier Coulon 六年前以 IT 专家的身份加入 IBM 的法国分部，并开始从事各类平台上的 ERP 咨询工作。最近两年，他一直在处理一个大型 J2EE 项目，这个项目包含诸如 Struts 和 Hibernate 之类的开源框架。您可以通过 <A href="mailto:xavier.coulon@fr.ibm.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">xavier.coulon@fr.ibm.com</A> 与 Xavier 联系。 </TD></TR>
<TR>
<TD>
<P><A name=author2><BR></A><IMG height=80 alt=作者照片 src="http://www-128.ibm.com/developerworks/cn/i/p-cbr.jpg" width=64 align=left xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Christian Brousseau 是一个了不起的加拿大人，他一直在开发软件，有十多年的经验。最初他做了大量 Windows 开发（C++、Visual Basic、MFC、ActiveX），后来，从 Java 语言的 1.0 版本起，他开始转移到 Java 项目上。做 J2EE 顾问时积累的专业知识为他提供了一个去法国的好机会，在法国，他协助设计、开发、部署了一些重要的企业级 J2EE 项目。可以通过 <A href="mailto:cbrous@fr.ibm.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">cbrous@fr.ibm.com</A> 与 Christian 联系。 </P></TD></TR></TBODY></TABLE><BR clear=all><IMG height=10 alt="" src="http://www.ibm.com/i/c.gif" width=100 border=0><BR></TD></TR></TBODY></TABLE><img src ="http://www.blogjava.net/kapok/aggbug/4432.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-05-17 18:27 <a href="http://www.blogjava.net/kapok/archive/2005/05/17/4432.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>用 Hibernate 和 Spring 开发事务持久层</title><link>http://www.blogjava.net/kapok/archive/2005/05/17/4422.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Tue, 17 May 2005 08:36:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/05/17/4422.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/4422.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/05/17/4422.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/4422.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/4422.html</trackback:ping><description><![CDATA[<TABLE cellSpacing=0 cellPadding=0 width=730 border=0>
<TBODY>
<TR>
<TD style="BORDER-RIGHT: #e8e8e8 1px solid; BORDER-LEFT: #e8e8e8 1px solid" align=middle bgColor=#f9f9f9 colSpan=3>
<TABLE style="WORD-BREAK: break-all" cellSpacing=0 cellPadding=0 width=700 align=center border=0>
<TBODY>
<TR bgColor=#f9f9f9>
<TD id=fontzoom style="LINE-HEIGHT: 200%">
<P><A href="http://www.javafan.net/article/20041223180912482.html">http://www.javafan.net/article/20041223180912482.html</A><BR><BR><BR><BR>当您自以为已经了解了所有开发工具时，肯定又会冒出一个新的工具。在本文中，developerWorks 的固定撰稿人 Rick Hightower 用一个真实世界的例子向您介绍两个最激动人心的企业新技术。<A href="http://www.javafan.net/special/hibernate/index.jsp" target=_blank>Hibernate</A> 是一个对象关系映射工具，而 <A href="http://www.javafan.net/special/spring/index.jsp" target=_blank>Spring</A> 是一个 AOP 框架和 IOC 容器。Rick 介绍了如何结合这两者，为企业应用程序构建一个事务持久层。</P>
<P>　　如果关心开发人员的最新热点，那么您可能听说过 IOC （控制倒置，Inversion of Control）容器和 AOP （面向方面编程）。不过，像许多开发人员一样，您可能不清楚在自己的开发工作中如何使用这些技术。在本文中，通过具体介绍使用 Hibernate 和 Spring 在企业应用程序中构建一个事务持久层，您会认识到这些技术。</P>
<P>　　Hibernate 是 Java 平台上的一种流行的、容易使用的开放源代码对象关系（OR）映射框架。Spring 是一个 AOP 框架和 IOC 容器。这两种技术一起提供了本文中介绍的开发工作的基础。将使用 Hibernate 把一些持久性对象映射到关系数据库中，用 Spring 使 Hibernate 更容易使用并提供声明性事务支持。由于为示例类编写测试代码时使用了 DbUnit，我还附带介绍了一点 TDD （测试驱动的开发）的内容。</P>
<P>　　注意，本文假定读者熟悉 Java 平台上的企业开发，包括 JDBC、OR 映射内容、J2EE 设计模式如 DAO，以及声明性事务支持，如 Enterprise JavaBean （EJB）技术所提供的事务支持。理解这里的讨论不需要成为这些技术的专家，也不需要熟悉 AOP、IOC 或者 TDD，因为在本文中对这三者都做了介绍。</P>
<P>　　我将首先介绍两种开发技术，然后分析例子。</P>
<P>　　<STRONG>Hibernate 简介</STRONG> </P>
<P>　　Hibernate 是 Java 平台上的一种全功能的、开放源代码 OR 映射框架。Hibernate 在许多方面类似于 EJB CMP CMR （容器管理的持久性/容器管理的关系）和 JDO（Java Data Objects）。与 JDO 不同，Hibernate 完全着眼于关系数据库的 OR 映射，并且包括比大多数商业产品更多的功能。大多数 EJB CMP CMR 解决方案使用代码生成实现持久性代码，而 JDO 使用字节码修饰。与之相反，Hibernate 使用反射和运行时字节码生成，使它对于最终用户几乎是透明的（以前 Hibernate 的实现只使用反射，它有助于调试，当前版本保留了这种选项）。</P>
<P>　　移植基于 Hibernate 的应用程序</P>
<P>　　如果应用程序必须在多个 RDBMS 系统上运行 ，那么基于 Hibernate 的应用程序可以毫不费力地移植到 IBM DB2、MySQL、PostgreSQL、Sybase、Oracle、HypersonicSQL 和许多其他数据库。我最近甚至将一个应用程序从 MySQL 移植到 Hibernate 没有很好支持的 Firebird，而这种移植是很容易的。<BR>Hibernate 可以模拟继承（有几种方式）、关联（一对一或者一对多、containment 和 aggregation）和 composition。我将在本文中讨论每种关系类型的几个例子。</P>
<P>　　Hibernate 提供了一种称为 Hibernate Query Language （HQL） 的 查询语言，它类似于 JDO 的 JDOQL 和 EJB 的 EJB QL，尽管它更接近于前者。但是 Hibernate 没有就此止步：它还可以进行直接的 SQL 查询和/或使用 object criteria 很容易地在运行时构成查询条件。在本文的例子中我将只使用 HQL。</P>
<P>　　与 EJB CMP CMR 不同，Hibernate 像 JDO 一样可以在 J2EE 容器内部或者外部工作，这可以让那些进行 TDD 和敏捷开发的人受益。</P>
<P>　　<STRONG>Spring 简介</STRONG></P>
<P>　　AOP 专家 Nicholas Lesiecki 第一次向我解释 AOP 时，他说的我一个词也没理解，我觉得就像第一次考虑使用 IOC 容器的可能性时一样。每一种技术的概念基础本身就需要很好地消化，每一种技术所使用的各种各样的缩写让事情更糟了——特别是其中许多术语与我们已经使用的根本不一样了。 </P>
<P>　　像许多技术一样，理解这两种技术的实际使用比学习理论更容易。经过自己对 AOP 和 IOC 容器实现（即 XWork、PicoContainer 和 Spring）的分析，我发现这些技术可以帮助我获得功能，而不会在多框架中添加基于代码的依赖性。它们都将成为我后面开发项目的一部分。</P>
<P>　　简单地说，AOP 让开发人员可以创建非行为性的关注点，称为横切关注点，并将它们插入到应用程序代码中。使用 AOP 后，公共服务（比如日志、持久性、事务等）就可以分解成方面并应用到域对象上，同时不会增加域对象的对象模型的复杂性。</P>
<P>　　IOC 允许创建一个可以构造对象的应用环境，然后向这些对象传递它们的协作对象。正如单词 倒置 所表明的，IOC 就像反过来的 JNDI。没有使用一堆抽象工厂、服务定位器、单元素（singleton）和直接构造（straight construction），每一个对象都是用其协作对象构造的。因此是由容器管理协作对象（collaborator）。</P>
<P>　　Spring 既是一个 AOP 框架、也是一个 IOC 容器。我记得 Grady Booch 说过，对象最好的地方是可以替换它们，而 Spring 最好的地方是它有助于您替换它们。有了 Spring，只要用 JavaBean 属性和配置文件加入依赖性（协作对象）。然后可以很容易地在需要时替换具有类似接口的协作对象。</P>
<P>　　Spring 为 IOC 容器和 AOP 提供了很好的入口（on-ramp）。因此，不需要熟悉 AOP 就可以理解本文中的例子。所需要知道的就是将要用 AOP 为示例应用程序声明式地添加事务支持，与使用 EJB 技术时的方式基本相同。</P>
<P>　　<STRONG>具体到业务</STRONG></P>
<P>　　在本文的其余部分，所有的讨论都将基于一个实际的例子。起点是一个企业应用程序，要为它实现一个事务持久层。持久层是一个对象关系数据库，它包括像 User、User Group、Roles 和 ContactInfo 这些熟悉的抽象。</P>
<P>　　在深入到数据库的要素——查询和事务管理——之前，需要建立它的基础：对象关系映射。我将用 Hibernate 设置它，并只使用一点 Spring。</P>
<P>　　<STRONG>用 Hibernate 进行 OR 映射</STRONG> </P>
<P>　　Hibernate 使用 XML (*.hbm.xml) 文件将 Java 类映射到表，将 JavaBean 属性映射到数据库表。幸运的是，有一组 XDoclet 标签支持 Hibernate 开发，这使得创建所需要的 *.hbm.xml 文件更容易了。清单 1 中的代码将一个 Java 类映射到数据库表。</P>
<P>　　清单 1. 将 Java 类映射到 DB 表<BR>　　[User.java]</P>
<P style="BACKGROUND: #eeeeee">/**<BR>&nbsp;* @hibernate.class table="TBL_USER"<BR>&nbsp;* ..<BR>&nbsp;* ..<BR>&nbsp;* ...<BR>&nbsp;*/<BR>public class User {<BR><BR>&nbsp;private Long id = new Long(-1);<BR>&nbsp;private String email;<BR>&nbsp;private String password;<BR>&nbsp;<BR>&nbsp;.<BR>&nbsp;.<BR>&nbsp;.<BR><BR>&nbsp;/**<BR>&nbsp; * @return<BR>&nbsp; * @hibernate.id column="PK_USER_ID" <BR>&nbsp; * unsaved-value="-1" <BR>&nbsp; * generator-class="native"&nbsp; <BR>&nbsp; */<BR>&nbsp;public Long getId() {<BR>&nbsp;&nbsp;return id;<BR>&nbsp;}<BR><BR>&nbsp;...<BR><BR>&nbsp;/**<BR>&nbsp; * @hibernate.property column="VC_EMAIL" <BR>&nbsp; * type="string" <BR>&nbsp; * update="false"<BR>&nbsp; * insert="true"<BR>&nbsp; * unique="true"<BR>&nbsp; * not-null="true"<BR>&nbsp; * length="82" <BR>&nbsp; * @return<BR>&nbsp; */<BR>&nbsp;public String getEmail() {<BR>&nbsp;&nbsp;return email;<BR>&nbsp;}<BR><BR>&nbsp;/**<BR>&nbsp; * @hibernate.property column="VC_PASSWORD" <BR>&nbsp; * type="string" <BR>&nbsp; * update="false"<BR>&nbsp; * insert="true"<BR>&nbsp; * unique="true"<BR>&nbsp; * not-null="true"<BR>&nbsp; * length="20" <BR>&nbsp; * @return<BR>&nbsp; */<BR>&nbsp;public String getPassword() {<BR>&nbsp;&nbsp;return password;<BR>&nbsp;}<BR><BR>&nbsp;...<BR>&nbsp;...<BR>&nbsp;...<BR>}</P>
<P>　　可以看到，@hibernate.class table="TBL_USER" 标签将 User 映射到 TBL_USER 表。@hibernate.property column="VC_PASSWORD" 将 JavaBean 属性 password 映射到 VC_PASSWORD 列。@hibernate.id column="PK_USER_ID" 标签声明id 属性是主键，它将使用本机（generator-class="native"）数据库机制生成键（例如，Oracle sequences 和 SQL Server Identity 键）。Hibernate 可以指定 generator-class="native" 以外的、其他可以想象的得到主键获得策略，不过我更愿意使用 native。type 和 length 属性用于从 Hibernate *.hbm.xml OR 映射文件生成表。这些 final 属性是可选的，因为使用的可能不是 green-field 数据库。在这个例子中，已经有数据库了，所以不需要额外的属性。（green-field 应用程序 是一个新的应用程序， green-field 数据 是新应用程序的一个新数据库。不会经常开发一个全新的应用程序，不过偶尔有一两次也不错）。</P>
<P>　　看过了表如何映射到类以及列如何映射到 JavaBean 属性，该使用 Hibernate 在 OR 数据库中设置一些关系了。</P>
<P>　　<STRONG>设置对象关系</STRONG></P>
<P>　　在本节中，我将只触及 Hibernate 提供的设置对象间关系的选项的一小部分。首先设置像 User、User Group、Roles 和 ContactInfo 这些类之间的关系。其中一些关系如图 1 所示，这是数据库的验证对象模型。</P>
<P align=center><IMG height=286 src="http://www.javafan.net/uploadfiles/20041223180912100.jpg" width=600><BR>图 1. 关系的图示</P>
<P>　　如您所见，在上述抽象中存在各种各样的关系。User 与 ContactInfo 有一对一关系。ContactInfo 的生命周期与 User 相同（用数据库的术语，UML 中的组成 aka 级联删除）。如果删除 User，则相应的 ContactInfo 也会删除。在 Users 与 Roles 之间存在多对多关系（即与独立生命周期相关联）。在 Groups 与 Users 之间存在一对多关系，因为组有许多用户。用户可以存在于组外，即是 aggregation 而不是 composition （用数据库的说法，在 Groups 和 Users 之间没有级联删除关系）。此外，User 和 Employee 有子类关系，就是说，Employee 的类型为 User。表 1 显示了如何用 XDoclet 标签创建一些不同类型的对象关系。</P>
<DIV align=center>表 1. 用 XDoclet 创建对象关系</DIV>
<TABLE cellSpacing=1 cellPadding=1 width=690 align=center bgColor=#999999 border=0>
<TBODY>
<TR bgColor=#ffffff>
<TD align=middle height=25><STRONG>关系</STRONG></TD>
<TD align=middle height=25><STRONG>Java/XDoclet</STRONG></TD>
<TD align=middle height=25><STRONG>SQL DDL（由 Hibernate Schema Export 生成的 MySQL）</STRONG></TD></TR>
<TR bgColor=#ffffff>
<TD height=25><STRONG>组包含用户</STRONG><BR>一对多<BR>Aggregation<BR>双向<BR>(Group&lt;--&gt;Users)</TD>
<TD height=25>[Group.java]<BR>/**<BR>* <BR>* @return<BR>* <BR>* @hibernate.bag name="users"<BR>* cascade="save-update"<BR>* lazy="true"<BR>* inverse="true"<BR>* <BR>* @hibernate.collection-key <BR>* column="FK_GROUP_ID"<BR>* <BR>* @hibernate.collection-one-to-many <BR>* class="net.sf.hibernateExamples.User"<BR>*/<BR>public List getUsers() {<BR>return users;<BR>} 
<P>[User.java]<BR>/**<BR>* @hibernate.many-to-one <BR>* column="FK_GROUP_ID" <BR>* class="net.sf.hibernateExamples.Group"<BR>*/<BR>public Group getGroup() {<BR>return group;<BR>}</P></TD>
<TD height=25>create table TBL_USER (<BR>PK_USER_ID BIGINT NOT NULL AUTO_INCREMENT,<BR>USER_TYPE VARCHAR(255) not null,<BR>FK_GROUP_ID BIGINT,<BR>VC_EMAIL VARCHAR(82) not null unique,<BR>primary key (PK_USER_ID)<BR>) 
<P><BR>create table TBL_GROUP (<BR>PK_GROUP_ID BIGINT NOT NULL AUTO_INCREMENT,<BR>VC_DESCRIPTION VARCHAR(255),<BR>VC_NAME VARCHAR(40) unique,<BR>primary key (PK_GROUP_ID)<BR>)</P>
<P>alter table TBL_USER add index (FK_GROUP_ID), <BR>add constraint FK_111 foreign key (FK_GROUP_ID) <BR>references TBL_GROUP (PK_GROUP_ID)</P></TD></TR>
<TR bgColor=#ffffff>
<TD height=25><STRONG>用户有联系信息</STRONG><BR>一对一<BR>Composition <BR>单向<BR>(User--&gt;ContactInfo)</TD>
<TD height=25>[User.java]<BR>/**<BR>* @return<BR>* <BR>* @hibernate.one-to-one cascade="all" <BR>* <BR>*/<BR>public ContactInfo getContactInfo() {<BR>return contactInfo;<BR>} 
<P>[ContactInfo.java]<BR>(Nothing to see here. Unidirectional!)</P></TD>
<TD height=25>create table TBL_USER (<BR>PK_USER_ID BIGINT NOT NULL AUTO_INCREMENT,<BR>USER_TYPE VARCHAR(255) not null,<BR>FK_GROUP_ID BIGINT,<BR>VC_EMAIL VARCHAR(82) not null unique,<BR>primary key (PK_USER_ID)<BR>) 
<P>create table TBL_CONTACT_INFO (<BR>PK_CONTACT_INFO_ID BIGINT not null,<BR>...<BR>...<BR>...<BR>primary key (PK_CONTACT_INFO_ID)<BR>)</P></TD></TR>
<TR bgColor=#ffffff>
<TD height=25><STRONG>用户与角色关联</STRONG><BR>多对多<BR>Association<BR>单向<BR>(Users--&gt;Roles)</TD>
<TD height=25>[User.java]<BR>/**<BR>* @return<BR>* @hibernate.bag <BR>* table="TBL_JOIN_USER_ROLE"<BR>* cascade="all"<BR>* inverse="true"<BR>* <BR>* @hibernate.collection-key <BR>* column="FK_USER_ID"<BR>* <BR>* @hibernate.collection-many-to-many <BR>* class="net.sf.hibernateExamples.Role" <BR>* column="FK_ROLE_ID"<BR>* <BR>*/<BR>public List getRoles() {<BR>return roles;<BR>} 
<P>[Role.java]<BR>Nothing to see here. Unidirectional!</P></TD>
<TD height=25>create table TBL_ROLE (<BR>PK_ROLE_ID BIGINT NOT NULL AUTO_INCREMENT,<BR>VC_DESCRIPTION VARCHAR(200),<BR>VC_NAME VARCHAR(20),<BR>primary key (PK_ROLE_ID)<BR>) 
<P>create table TBL_USER (<BR>PK_USER_ID BIGINT NOT NULL AUTO_INCREMENT,<BR>USER_TYPE VARCHAR(255) not null,<BR>FK_GROUP_ID BIGINT,<BR>VC_EMAIL VARCHAR(82) not null unique,<BR>primary key (PK_USER_ID)<BR>)</P>
<P>create table TBL_JOIN_USER_ROLE (<BR>FK_USER_ID BIGINT not null,<BR>FK_ROLE_ID BIGINT not null<BR>)</P></TD></TR>
<TR bgColor=#ffffff>
<TD height=25><STRONG>雇员是用户</STRONG><BR>Inheritance<BR>用户<BR>雇员</TD>
<TD height=25>[User.java]<BR>/**<BR>* @hibernate.class table="TBL_USER" <BR>* discriminator-value="2" <BR>* @hibernate.discriminator column="USER_TYPE"<BR>* <BR>...<BR>...<BR>...<BR>*/<BR>public class User { 
<P>[Employee.java]<BR>/**<BR>* @hibernate.subclass discriminator-value = "1"<BR>*/<BR>public class Employee extends User{</P></TD>
<TD height=25>create table TBL_USER (<BR>PK_USER_ID BIGINT NOT NULL AUTO_INCREMENT,<BR>USER_TYPE VARCHAR(255) not null,<BR>FK_GROUP_ID BIGINT,<BR>VC_EMAIL VARCHAR(82) not null unique,<BR>primary key (PK_USER_ID)<BR>)</TD></TR></TBODY></TABLE>
<P>　　<STRONG>Hibernate 中的查询</STRONG></P>
<P>　　Hibernate 有三种类型的查询：</P>
<P>　　☆ Criteria, object composition <BR>　　☆ SQL <BR>　　☆ HQL</P>
<P>　　在下面的例子中将只使用 HQL。本节还要使用 Spring，用它的 AOP-driven HibernateTemplate 简化 Hibernate 会话的处理。在本节将开发一个 DAO（Data Access Object）。要了解更多关于 DAO 的内容，请参阅 参考资料。</P>
<P>　　清单 2 展示了两个方法：一个使用 HQL 查询的组查询，另一个是后面接一个操作的组查询。注意在第二个方法中，Spring HibernateTemplate 是如何简化会话管理的。</P>
<P>　　清单 2. 使用查询</P>
<P style="BACKGROUND: #eeeeee">import net.sf.hibernate.HibernateException;<BR>import net.sf.hibernate.Session;<BR>import net.sf.hibernate.Query;<BR>import org.springframework.orm.hibernate.HibernateCallback;<BR>import org.springframework.orm.hibernate.support.HibernateDaoSupport;<BR><BR>/**<BR>&nbsp;* @author Richard Hightower<BR>&nbsp;* ArcMind Inc. http://www.arc-mind.com<BR>&nbsp;*/<BR>public class UserDAO extends HibernateDaoSupport{<BR><BR>&nbsp; .<BR>&nbsp; .<BR>&nbsp; .<BR><BR>&nbsp; /**<BR>&nbsp;* Demonstrates looking up a group with a HQL query<BR>&nbsp;* @param email<BR>&nbsp;* @return<BR>&nbsp;*/&nbsp;<BR>&nbsp;public Group findGroupByName(String name) {<BR>&nbsp;&nbsp;&nbsp; return (Group) getHibernateTemplate().find("from Group g where g.name=?",name).get(0);<BR>&nbsp;}<BR>&nbsp;<BR>&nbsp;/**<BR>&nbsp; * Demonstrates looking up a group and forcing it to populate users (relationship was lazy)<BR>&nbsp; * @param email<BR>&nbsp; * @return<BR>&nbsp; */&nbsp;<BR>&nbsp;public Group findPopulatedGroupByName(final String name) {<BR>&nbsp;&nbsp;&nbsp; HibernateCallback callback = new HibernateCallback(){<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp; public Object doInHibernate(Session session) throws HibernateException, SQLException {<BR>&nbsp;&nbsp;&nbsp;Group group =null;<BR>&nbsp;&nbsp;&nbsp;String query = "from Group g where g.name=?";<BR>&nbsp;&nbsp;&nbsp;Query queryObject = getHibernateTemplate().createQuery(session, query);<BR>&nbsp;&nbsp;&nbsp;queryObject.setParameter(0, name);<BR>&nbsp;&nbsp;&nbsp;group = (Group) queryObject.list().get(0);<BR>&nbsp;&nbsp;&nbsp;group.getUsers().size();//force load<BR>&nbsp;&nbsp;&nbsp;return group;<BR>&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;};<BR>&nbsp;&nbsp;<BR>&nbsp;&nbsp;return (Group) getHibernateTemplate().execute(callback);<BR>&nbsp;}<BR>&nbsp; .<BR>&nbsp; .<BR>&nbsp; .<BR>}&nbsp;&nbsp;&nbsp;</P>
<P>　　您可能会注意到第二个方法比第一个方法复杂得多，因为它强迫加载 users 集合。因为 Group-&gt;Users 之间的关系设置为 lazy initialize（即表 2 中 lazy="true"），组对象需要一个活跃的会话以查询用户。在定义 Group 和 Users 之间关系时设置这个属性为 lazy="false"，则不需要第二个方法。在这种情况下，可能使用第一种方法 (findGroupByName) 列出组，用第二种方法（findPopulatedGroupByName）查看组细节。</P>
<P>　　<STRONG>Spring IOC 和 Hibernate</STRONG></P>
<P>　　使用 Spring 时，在 J2EE 容器内和容器外工作一样容易。比如在最近的项目中，我在 Eclipse 中，使用 HSQL 和本地数据库对使用 Hibernate 事务管理器的 Hypersonic SQL 数据库进行持久性单元测试。然后，在部署到 J2EE 服务器时，将持久层转换为使用 J2EE 数据源（通过 JNDI）、JTA 事务和使用 FireBird （一个开放源代码版本的 Interbase）。这是用 Spring 作为 IOC 容器完成的。</P>
<P>　　从清单 3 中可以看出，Spring 允许加入依赖性。注意清单中应用程序上下文文件是如何配置 dataSource 的。dataSource 传递给 sessionFactory，sessionFactory 传递给 UserDAO。</P>
<P>　　清单 3. Spring IOC 和 Hibernate</P>
<P style="BACKGROUND: #eeeeee">&lt;beans&gt;<BR><BR>&nbsp;&lt;!-- Datasource that works in any application server<BR>&nbsp;&nbsp;You could easily use J2EE data source instead if this were<BR>&nbsp;&nbsp;running inside of a J2EE container.<BR>&nbsp; --&gt;<BR>&nbsp;&lt;bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"&gt;<BR>&nbsp;&nbsp;&lt;property name="driverClassName"&gt;&lt;value&gt;com.mysql.jdbc.Driver&lt;/value&gt;&lt;/property&gt;<BR>&nbsp;&nbsp;&lt;property name="url"&gt;&lt;value&gt;jdbc:mysql://localhost:3306/mysql&lt;/value&gt;&lt;/property&gt;<BR>&nbsp;&nbsp;&lt;property name="username"&gt;&lt;value&gt;root&lt;/value&gt;&lt;/property&gt;<BR>&nbsp;&nbsp;&lt;property name="password"&gt;&lt;value&gt;&lt;/value&gt;&lt;/property&gt;<BR>&nbsp;&lt;/bean&gt;<BR><BR>&nbsp;&lt;!-- Hibernate SessionFactory --&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;<BR>&nbsp;&nbsp;&lt;!-- Must references all OR mapping files. --&gt;<BR>&nbsp;&nbsp;&lt;property name="mappingResources"&gt;<BR>&nbsp;&nbsp;&nbsp;&lt;list&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;value&gt;net/sf/hibernateExamples/User.hbm.xml&lt;/value&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;value&gt;net/sf/hibernateExamples/Group.hbm.xml&lt;/value&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;value&gt;net/sf/hibernateExamples/Role.hbm.xml&lt;/value&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;value&gt;net/sf/hibernateExamples/ContactInfo.hbm.xml&lt;/value&gt;<BR>&nbsp;&nbsp;&nbsp;&lt;/list&gt;<BR>&nbsp;&nbsp;&lt;/property&gt;<BR>&nbsp;&nbsp;<BR>&nbsp;&nbsp;&lt;!-- Set the type of database; changing this one property will port this to Oracle, <BR>&nbsp;&nbsp;&nbsp; MS SQL etc. --&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.MySQLDialect&lt;/prop&gt;<BR>&nbsp;&nbsp;&nbsp;&lt;/props&gt;<BR>&nbsp;&nbsp;&lt;/property&gt;<BR>&nbsp;&lt;/bean&gt;<BR>&nbsp;<BR>&nbsp;&lt;!-- Pass the session factory to our UserDAO --&gt;<BR>&nbsp;&lt;bean id="userDAO" class="net.sf.hibernateExamples.UserDAO"&gt;<BR>&nbsp;&nbsp;&lt;property name="sessionFactory"&gt;&lt;ref local="sessionFactory"/&gt;&lt;/property&gt;<BR>&nbsp;&lt;/bean&gt;<BR>&nbsp;<BR>&lt;/beans&gt;&nbsp;&nbsp;&nbsp;</P>
<P>　　设置了 UserDAO 后，下一步就是定义并使用更多的查询以展示可以完成的操作。Hibernate 可以用预定义查询将查询存储到源代码之外，如清单 4 所示。</P>
<P>　　清单 4. 预定义查询<BR>　　[User.java]</P>
<P style="BACKGROUND: #eeeeee">/**<BR>&nbsp;* @author Richard Hightower<BR>&nbsp;* ArcMind Inc. http://www.arc-mind.com<BR>&nbsp;* @hibernate.class table="TBL_USER" discriminator-value="2" <BR>&nbsp;* @hibernate.discriminator column="USER_TYPE"<BR>&nbsp;* <BR>&nbsp;* @hibernate.query name="AllUsers" query="from User user order by user.email asc"<BR>&nbsp;* <BR>&nbsp;* @hibernate.query name="OverheadStaff" <BR>&nbsp;* query="from Employee employee join employee.group g where g.name not in ('ENGINEERING','IT')"<BR>&nbsp;* <BR>&nbsp;* @hibernate.query name="CriticalStaff" <BR>&nbsp;* query="from Employee employee join employee.group g where g.name in ('ENGINEERING','IT')"<BR>&nbsp;* <BR>&nbsp;* @hibernate.query name="GetUsersInAGroup" <BR>&nbsp;* query="select user from Group g join g.users user"<BR>&nbsp;* <BR>&nbsp;* @hibernate.query name="GetUsersNotInAGroup" <BR>&nbsp;* query="select user from User user where user.group is null"<BR>&nbsp;* <BR>&nbsp;* @hibernate.query name="UsersBySalaryGreaterThan" <BR>&nbsp;* query="from User user inner join user.contactInfo info where info.salary &gt; ?1"<BR>&nbsp;* <BR>&nbsp;* @hibernate.query name="UsersBySalaryBetween" <BR>&nbsp;* query="from User user join user.contactInfo info where info.salary between ?1 AND ?2"<BR>&nbsp;* <BR>&nbsp;* @hibernate.query name="UsersByLastNameLike" <BR>&nbsp;* query="from User user join user.contactInfo info where info.lastName like ?1"<BR>&nbsp;* <BR>&nbsp;* @hibernate.query name="GetEmailsOfUsers" <BR>&nbsp;* query="select user.email from Group g join g.users as user where g.name = ?1"<BR>&nbsp;* <BR>&nbsp;*/<BR>public class User {<BR>&nbsp;&nbsp;&nbsp;.<BR>&nbsp;&nbsp;&nbsp;.<BR>&nbsp;&nbsp;&nbsp;.&nbsp;&nbsp;&nbsp;</P>
<P>　　上述代码定义了几个预定义查询。预定义查询 是存储在 *.hbm.xml 文件中的查询。在清单 5 中，可以看到如何执行预定义查询。</P>
<P>　　清单 5. 使用预定义查询<BR>　　[UserDAO.java]</P>
<P style="BACKGROUND: #eeeeee">&nbsp;/**<BR>&nbsp; * Demonstrates a query that returns a String.<BR>&nbsp; */&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;public String[] getUserEmailsInGroup(String groupName){<BR>&nbsp;&nbsp;List emailList =<BR>&nbsp;&nbsp;getHibernateTemplate().findByNamedQuery("GetEmailsOfUsers"); <BR>&nbsp;&nbsp;return (String [])<BR>&nbsp;&nbsp;&nbsp;emailList.toArray(new String[emailList.size()]);<BR>&nbsp;}<BR><BR>&nbsp;/**<BR>&nbsp; * Demonstrates a query that returns a list of Users<BR>&nbsp; *<BR>&nbsp; * @return A list of emails of all of the users in the authentication system.<BR>&nbsp; * <BR>&nbsp; */&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;public List getUsers(){<BR>&nbsp;&nbsp;return getHibernateTemplate().findByNamedQuery("AllUsers");<BR>&nbsp;}<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /**<BR>&nbsp;* Demonstrates passing a single argument to a query. <BR>&nbsp;*<BR>&nbsp;* @return A list of UserValue objects.<BR>&nbsp;* <BR>&nbsp;*/&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;public List getUsersBySalary(float salary){<BR>&nbsp;&nbsp;&nbsp; return getHibernateTemplate()<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; .findByNamedQuery("UsersBySalaryGreaterThan",<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; new Float(salary));<BR>&nbsp;}<BR><BR>&nbsp;/**<BR>&nbsp; * Demonstrates passing multiple arguments to a query<BR>&nbsp; * <BR>&nbsp; * @return A list of UserValue objects.<BR>&nbsp; * <BR>&nbsp; */&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;public List getUsersBySalaryRange(float start, float stop){<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return getHibernateTemplate()<BR>&nbsp;&nbsp; .findByNamedQuery("UsersBySalaryBetween",<BR>&nbsp;&nbsp;&nbsp;new Object[] {new Float(start), new Float(stop)});<BR>&nbsp;}</P>
<P>　　查询进行时，可以在持久层中加上最后一层：使用 Spring 的事务管理。</P>
<P>　　<STRONG>用 Spring 管理事务</STRONG></P>
<P>　　Spring 可以声明式地管理事务。例如，UserDAO.addUser 方法当前不是在单个事务中执行的。因此，组中的每一个用户都插入到自己的事务中，如清单 6 所示。</P>
<P>　　清单 6. 添加一组用户<BR>　　[UserDAO.java]</P>
<P style="BACKGROUND: #eeeeee">/**<BR>&nbsp;* @param group<BR>&nbsp;*/<BR>public void addGroup(Group group) {<BR>&nbsp;getHibernateTemplate().save(group);<BR>&nbsp;<BR>}</P>
<P>[UserDAOTest.java]</P>
<P style="BACKGROUND: #eeeeee">public void testAddGroupOfUsers(){<BR>&nbsp;Group group = new Group();<BR>&nbsp;<BR>&nbsp;for (int index=0; index &lt; 10; index++){<BR>&nbsp;&nbsp;User user = new User();<BR>&nbsp;&nbsp;user.setEmail("rick"+index+"@foobar.com" );<BR>&nbsp;&nbsp;user.setPassword("foobar");<BR>&nbsp;&nbsp;group.addUser(user);&nbsp;<BR>&nbsp;}<BR>&nbsp;<BR>&nbsp;group.setName("testGroup");<BR>&nbsp;<BR>&nbsp;userDAO.addGroup(group);<BR>&nbsp;assertNotNull(group.getId());<BR>&nbsp;<BR>&nbsp;Group group2 = userDAO.findPopulatedGroupByName("testGroup");<BR>&nbsp;<BR>&nbsp;assertEquals("testGroup",group2.getName());<BR>&nbsp;assertEquals(10, group2.getUsers().size());<BR>&nbsp;String email = ((User)group2.getUsers().get(0)).getEmail();<BR>&nbsp;assertEquals("rick0@foobar.com", email);<BR><BR>}</P>
<P>　　不建议使用上述解决方案，因为每一个 User 都要在自己的事务中插入到数据库中。如果出现问题，那么只能添加部分用户。如果希望保留 ACID 属性（即保证所有都发生或者所有都不发生），可以通过程序进行事务管理，但是它很快就会变得一团糟了。相反，应使用 Spring 的 AOP 来支持声明式的事务，如清单 7 所示。</P>
<P>　　清单 7. 声明式管理事务<BR>　　[applicationContext.xml]</P>
<P style="BACKGROUND: #eeeeee">&lt;!-- Pass the session factory to our UserDAO --&gt;<BR>&nbsp;&nbsp; &lt;bean id="userDAOTarget" class="net.sf.hibernateExamples.UserDAOImpl"&gt;<BR>&nbsp;&lt;property name="sessionFactory"&gt;&lt;ref local="sessionFactory"/&gt;&lt;/property&gt;<BR>&nbsp;&nbsp; &lt;/bean&gt;<BR>&nbsp;<BR>&nbsp;&nbsp; &lt;bean id="transactionManager" <BR>&nbsp;&nbsp; &nbsp;class="org.springframework.orm.hibernate.HibernateTransactionManager"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;property name="sessionFactory"&gt;&lt;ref bean="sessionFactory"/&gt;&lt;/property&gt;<BR>&nbsp;&nbsp; &lt;/bean&gt;<BR><BR>&lt;bean id="userDAO"<BR>&nbsp;&nbsp;&nbsp;&nbsp; class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"&gt;<BR>&nbsp;&lt;property name="transactionManager"&gt;&lt;ref local="transactionManager"/&gt;&lt;/property&gt;<BR>&nbsp;&lt;property name="target"&gt;&lt;ref local="userDAOTarget"/&gt;&lt;/property&gt;<BR>&nbsp;&lt;property name="transactionAttributes"&gt;<BR>&nbsp;&nbsp;&lt;props&gt;<BR>&nbsp;&nbsp;&nbsp;&lt;prop key="addGroup"&gt;PROPAGATION_REQUIRED&lt;/prop&gt;<BR>&nbsp;&nbsp;&lt;/props&gt;<BR>&nbsp;&lt;/property&gt;<BR>&lt;/bean&gt;</P>
<P>　　注意在准备清单 7 的代码时，我重新改写了 UserDAO 并提取了其接口。这个接口现在是 UserDAO，它的实现类是 UserDAOImpl。这样清单 7 中的事务代码就使用了带有事务属性 (PROPAGATION_REQUIRED) 的 UserDAO.addGroup() 方法。现在只要底层数据库支持，就可以在一个事务中添加所有用户。</P>
<P>　　<STRONG>结束语</STRONG></P>
<P>　　在本文中，介绍了如何使用 Hibernate 和 Spring 实现一个事务持久层。Hibernate 是一种先进的 OR 映射工具，而 Spring 是一个 AOP 框架和 IOC 容器。这两种技术的综合使用，使得开发人员可以编写媲美数据库厂商的代码，它可以在 J2EE 容器中运行，也可以单独运行。使用了 DbUnit （JUnit 的扩展）构建和测试本文中例子的所有代码，虽然这不是讨论的重点。</P></TD></TR></TBODY></TABLE></TD></TR>
<TR>
<TD width=10 height=11><IMG height=11 src="http://www.javafan.net/article/images/u_16.gif" width=10></TD>
<TD style="BORDER-BOTTOM: #e8e8e8 1px solid" width=695 bgColor=#f9f9f9 height=11><IMG height=1 src="" width=1></TD></TR></TBODY></TABLE><img src ="http://www.blogjava.net/kapok/aggbug/4422.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-05-17 16:36 <a href="http://www.blogjava.net/kapok/archive/2005/05/17/4422.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>考虑在你的程序中集成 picocontainer或spring...框架 </title><link>http://www.blogjava.net/kapok/archive/2005/05/08/4074.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Sat, 07 May 2005 16:09:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/05/08/4074.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/4074.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/05/08/4074.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/4074.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/4074.html</trackback:ping><description><![CDATA[<P><A href="http://www.huihoo.com/column/~chen56/webwork-pico/">http://www.huihoo.com/column/~chen56/webwork-pico/</A></P>
<P>&nbsp;</P>
<UL>
<LI><A href="http://www.huihoo.com/column/~chen56/webwork-pico/index.html">第1部分 构建易于单元测试的对象结构。</A>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <FONT color=#ff0000>&lt;- now</FONT> 
<LI><A href="http://www.huihoo.com/column/~chen56/webwork-pico/2.html">第2部分 集成PicoContainer来更完美的消除对象依赖。</A> </LI></UL>　 
<P>实现和接口分离，使用和组装分离是一个基本的对象设计原则，简单的工厂方法(gof)、服务定位器模式(j2ee核心模式)已经被广泛使用,近来由于测试驱动方法的深入人心，有洁癖的程序员们又重新理解了ioc(Inversion of Control)，并把它们变成实现，代表性的实现有<A href="http://www.picocontainer.org/">PicoContainer</A>,<A href="http://www.springframework.org/">Spring</A>，而Martin Fowler也趁机总结出了一个新的模式：<A href="http://martinfowler.com/articles/injection.html">Dependency Injection</A> ，让我们别停留在理论与争论了，看看怎样用它来实际的简化我们的程序才是正解，用过之后再吵个翻天也不迟。本文将通过一个数据库访问层和web层的集成来应用picoContainer.让我们这就来看它的威力吧。</P>
<P><I>本文假设你具有junit单元测试/web框架,使用经验,最好了解webwork 1机理,不过他简单的你甚至可以现在才了解。</I></P>
<P>先看看文中所用的东东：</P>
<UL>
<LI><B><A href="http://wiki.opensymphony.com/space/WebWork">webwork 1</A> :&nbsp; </B>一个非常简单的web框架，核心接口是 Action.execute(),我们将实现之来处理每一次web层的action(也就是一次post) 。 
<LI><B>dao模式(j2ee核心模式) :</B>他将封装数据库访问的所有细节。 </LI></UL>
<P>让我们来看看通常我们实现一个简单的用户登陆过程所要做的所有事情：</P>
<P><IMG height=370 src="http://www.huihoo.com/column/~chen56/webwork-pico/webwork.gif" width=474 border=0></P>
<P>从上图可知LoginAction是我们程序的顶层类,它依赖UserDao来完成登陆业务逻辑，ok,这就是一个典型的web应用，一次请求发送到web server，然后由webwork框架接管，他按照一个配置文件把相应的登陆请求对应到LoginAction类上，然后用其缺省的构造器实例化LoginAction,然后把post上来的表单值或url参数值按名称填充到LoginAction（即：name,password）,然后调用命令模式的接口Action.execute()完成调用，让我们来看一下实际代码：<BR></P>
<TABLE style="BORDER-COLLAPSE: collapse" borderColor=#000000 height=1 cellSpacing=0 cellPadding=0 width="80%" bgColor=#efefef border=1>
<TBODY>
<TR>
<TD width="100%"><PRE>public interface UserDao {
    public User load(String username);
}                          </PRE></TD></TR></TBODY></TABLE><BR>
<TABLE style="BORDER-COLLAPSE: collapse" borderColor=#000000 height=1 cellSpacing=0 cellPadding=0 width="80%" bgColor=#efefef border=1>
<TBODY>
<TR>
<TD width="100%"><PRE>import webwork.action.ActionSupport;
import ftsmen.dao.UserDao;
import ftsmen.entity.User;

public class LoginAction extends ActionSupport {
    private String username;
    private String password;
    private UserDao userDao ;

    public LoginAction() {
	<FONT color=#ff0000>//依赖对象在这里初始化</FONT>
        userDao = DaoFactory.createUserDao();
    }

    //为了程序的简单，假设用户总是已经注册过的
    public String execute() {
	User u = userDao.load(getUsername());
	if (!u.verifyPassword(getPassword())) {
	    //密码错误;
	    return ERROR;
	}
	//验证通过......可以把用户信息放在session中
	return SUCCESS;
    }

    public String getUsername() {
	return username;
    }
    public void setUsername(String string) {
	username = string;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String string) {
	password = string;
    }
}                </PRE></TD></TR></TBODY></TABLE>
<P>对象知识告诉我们要让接口和实现分离，于是我们就用工厂方法隐藏了UserDao的实现和初始化细节。我们将如何测试LoginAction而不依赖UserDao的数据库实现呢？我们知道单元测试的一个常用方法是：用mock object替换待测试类的依赖对象,具体使用可以参考<A href="http://www.mockobjects.com/">MockObjects</A>、<A href="http://jmock.codehaus.org/">JMock</A>.</P>
<P>我们这里用一个子类来充当mock，但究竟怎样把这个mock object替换LoginAction中的那个userDao呢？？？</P>
<P><IMG src="http://www.huihoo.com/column/~chen56/webwork-pico/test.gif" border=1></P>
<P>目前<B>常用</B>的3种方法都可以做到,<I>参考<A href="http://martinfowler.com/articles/injection.html">Dependency Injection</A></I>：</P>
<P>1.服务定位器(Service Locator):</P>
<P>我们可以把DaoFactory简单的改造为服务定位器：</P>
<TABLE style="BORDER-COLLAPSE: collapse" borderColor=#000000 height=1 cellSpacing=0 cellPadding=0 width="80%" bgColor=#efefef border=1>
<TBODY>
<TR>
<TD width="100%"><PRE>public class DaoFactory{	
    private static ThreadLocal instance = new  ThreadLocal();
    private UserDao userDao;
    public DaoFactory(UserDao userDao) {
	this.userDao =userDao ;
    }
    private DaoFactory() {}
    public static void load(DaoFactory locator) {
       instance.set(locator);
    }
    public static UserDao createUserDao() {
	return ((DaoFactory)instance.get()).userDao;
    }
}
                </PRE></TD></TR></TBODY></TABLE>
<P>这样就可以不改变原来的LoginAction代码，并可以把mock UserDao插入到待测类LoginAction中：</P>
<TABLE style="BORDER-COLLAPSE: collapse" borderColor=#000000 height=1 cellSpacing=0 cellPadding=0 width="80%" bgColor=#efefef border=1>
<TBODY>
<TR>
<TD width="100%"><PRE>public class LoginActionServiceLocatorTest extends TestCase {
    public void testLogin() throws Exception {
         DaoFactory.load(new DaoFactory(new UserDao() {
    	    public User load(String username) {
	        User u = new User(username);
		u.setPassword("chen");
		return u;
	    }
	}));
        
	action.setUsername("chen56");
	action.setPassword("chen");
	assertEquals("正确登陆", Action.SUCCESS, action.execute());
    }
}
                </PRE></TD></TR></TBODY></TABLE>
<P>当然最后还应该把DaoFactory重构rename为更贴切的名称.</P>
<P>2.setter 注射(Setter Injection):<BR>&nbsp;&nbsp;&nbsp; 我们在LoginAction中加入一个新的方法：</P>
<TABLE style="BORDER-COLLAPSE: collapse" borderColor=#000000 height=1 cellSpacing=0 cellPadding=0 width="80%" bgColor=#efefef border=1>
<TBODY>
<TR>
<TD width="100%"><PRE>public void setUserDao(UserDao userDao){
    this.userDao=userDao;
}               
                </PRE></TD></TR></TBODY></TABLE><BR>这样就可以把mock UserDao插入到待测类LoginAction中：<BR>
<TABLE style="BORDER-COLLAPSE: collapse" borderColor=#000000 height=1 cellSpacing=0 cellPadding=0 width="80%" bgColor=#efefef border=1>
<TBODY>
<TR>
<TD width="100%"><PRE>public class LoginActionSetterInjectionTest extends TestCase {
    public void testLogin() throws Exception {
        LoginAction  action = new LoginAction();
        action.setUserDao(new UserDao() {
	   public User load(String username) {
	       User u = new User(username);
	       u.setPassword("chen");
	       return u;
	   }
        });

        action.setUsername("chen56");
        action.setPassword("chen");
        assertEquals("正确登陆", Action.SUCCESS, action.execute());
    }
}
                </PRE></TD></TR></TBODY></TABLE><BR>3.构造器注射(Constructor Injection)：在LoginAction加入一个新的构造器: 
<TABLE style="BORDER-COLLAPSE: collapse" borderColor=#000000 height=1 cellSpacing=0 cellPadding=0 width="80%" bgColor=#efefef border=1>
<TBODY>
<TR>
<TD width="100%"><PRE>    public LoginAction(UserDao userDao) {
        this.userDao = userDao;
    }
                          </PRE></TD></TR></TBODY></TABLE><BR>这样也可以把依赖对象传入到被测类LoginAction中：<BR>
<TABLE style="BORDER-COLLAPSE: collapse" borderColor=#000000 height=1 cellSpacing=0 cellPadding=0 width="80%" bgColor=#efefef border=1>
<TBODY>
<TR>
<TD width="100%"><PRE>public class LoginActionConstructorInjectionTest extends TestCase {
    public void testLogin() throws Exception {
        LoginAction  action = new LoginAction(new UserDao() {
	   public User load(String username) {
	       User u = new User(username);
	       u.setPassword("chen");
	       return u;
	   }
        });

        action.setUsername("chen56");
        action.setPassword("chen");
        assertEquals("正确登陆", Action.SUCCESS, action.execute());
    }
}
                </PRE></TD></TR></TBODY></TABLE>
<P>以上实现中:Service Locator方法对源程序基本没有修改，但实际组装UserDao的工作却从原来的DaoFactory中分离了出来，通常情况下，我们会在filter或一个所有Action的基类中用模版方法实现他的组装，比如实际上可能会组装hibernate的一个Session到UserDao中。</P>
<P>后2个实现其实在程序中保留了一处专为测试所用的依赖对象入口，在实际使用中，构造器注射更舒心一些。由于我们知道webwork是用缺省构造器来初始化类的，而我们测试则用带UserDao参数的构造器，所以这是一个单选题，很少会产生误解，并且也更简洁些，类的状态也不会在运行期变化。</P>
<P><B>注：</B>事实上遵守<A href="http://c2.com/cgi/wiki?KentBeck">Kent beck</A>的教诲，LoginActionTest是先于LoginAction开发出来的。&nbsp;&nbsp;<BR><BR></P>
<P>ok,前面的方法可以使单元测试更容易些，下面来看看更酷的：集成pico让程序更简洁。</P>
<P><A href="http://www.picocontainer.org/">picoContainer</A>为何物？大家可以google上找一下，连接很多。</P>
<P>我们只说明一下它在我们的程序中的作用并用下面的代码来展现它的可爱之处：</P>
<P>集成pico之后的webwork与上一部分的图示只有一点点不同，就是在实例化Action时，它会查找注册到picoContainer本身的组件，也就是注册到pico中的UserDao,并根据匹配的构造器初始化Action类，即执行new LoginAction(userDao)然后调用Action.execute().就这一点点的不同，让我们看看对我们的构造器注射方式的代码产生了些啥变化。</P>
<P>LoginAction 类：去掉了缺省的构造器。</P>
<P>
<TABLE style="BORDER-COLLAPSE: collapse" borderColor=#000000 height=1 cellSpacing=0 cellPadding=0 width="80%" bgColor=#efefef border=1>
<TBODY>
<TR>
<TD width="100%"><PRE>import webwork.action.ActionSupport;
import ftsmen.dao.UserDao;
import ftsmen.entity.User;

public class LoginAction extends ActionSupport {
    private String username;
    private String password;
    private UserDao userDao ;

<B>    public LoginAction(UserDao userDao) {
	<FONT color=#ff0000>//依赖对象在外部初始化</FONT>
        this.userDao = userDao;
    }</B><I>
</I>
    //为了程序的简单，假设用户总是已经注册过的
    public String execute() {
	User u = userDao.load(getUsername());
	if (!u.verifyPassword(getPassword())) {
	    //密码错误;
	    return ERROR;
	}
	//验证通过......可以把用户信息放在session中
	    return SUCCESS;
    }

    public String getUsername() {
	return username;
    }
    public void setUsername(String string) {
	username = string;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String string) {
	password = string;
    }
}                </PRE></TD></TR></TBODY></TABLE></P>
<P>测试类：没有变化</P>
<P>
<TABLE style="BORDER-COLLAPSE: collapse" borderColor=#000000 height=1 cellSpacing=0 cellPadding=0 width="80%" bgColor=#efefef border=1>
<TBODY>
<TR>
<TD width="100%"><PRE>public class LoginActionConstructorInjectionTest extends TestCase {
    public void testLogin() throws Exception {
        LoginAction  action = new LoginAction(new UserDao() {
	   public User load(String username) {
	       User u = new User(username);
	       u.setPassword("chen");
	       return u;
	   }
        });

        action.setUsername("chen56");
        action.setPassword("chen");
        assertEquals("正确登陆", Action.SUCCESS, action.execute());
    }
}
                </PRE></TD></TR></TBODY></TABLE></P>
<P>UserDao总有被初始化的时候，ok,现在我们把初始化工作集中在一个WebContainerComposer中，并且在web.xml中用<FONT size=2>context-param描述它，这样，pico就可以根据pico容器中相应的UserDao组件初始化UserAction类了.</FONT></P>
<P>
<TABLE style="BORDER-COLLAPSE: collapse" borderColor=#000000 height=1 cellSpacing=0 cellPadding=0 width="80%" bgColor=#efefef border=1>
<TBODY>
<TR>
<TD width="100%"><PRE>package ftsmen.web.action;

import org.picocontainer.MutablePicoContainer;
import org.picoextras.integrationkit.ContainerComposer;

import ftsmen.dao.UserDao;
import ftsmen.dao.HibernateUserDao;
import ftsmen.db.Database;

public class WebContainerComposer implements ContainerComposer {
    String _nameStr;
    public void composeContainer(MutablePicoContainer container, Object name) {
	_nameStr = name.toString().toLowerCase();
	if (isScope("application")) {
	    //可以把scope为application的组件注册到这里
	} else if (isScope("session")) {
	    //可以把scope为session的组件注册到这里
	} else if (isScope("request")) {
            //可以把scope为request的组件注册到这里
            //真实程序中Hibernate的Dao实现用Session来初始化
	    container.registerComponentInstance(
                new HibernateUserDao(Database.currentSession()));
            
            /*jdbc实现可能象这样
            container.registerComponentInstance(
                new JdbcUserDao(Database.connection()));
            */	
        }
    }
	
    private boolean isScope(String scope) {
	return _nameStr.indexOf(scope) != -1;
    }
}            
</PRE></TD></TR></TBODY></TABLE></P>
<P>可以看到，集成pico后的源代码更洁净了，并且Factory的依赖也消除了。</P>
<P>还有什么比清洁溜溜的代码更说明问题呢？</P>
<P>具体集成pico和webwork的方法可以参照<A href="http://www.opensymphony.com/webwork/cookbook/PicoContainer_Integration.html"><BR>http://www.opensymphony.com/webwork/cookbook/PicoContainer_Integration.html</A></P>
<P>但其中的WebContainerAssembler类的实现现在已经有所变化，在2004年2月2日左右的实现已经变为我给出的写法。</P>
<P>以上实现并不表明pico只支持构造器注射，实际上目前2个主要的框架spring和pico都支持构造器和setter注射。</P>
<P>　</P>
<P><B>总结：</B>用pico或spring这样的东东可以迅速的提高程序依赖方面的质量，你不想试一下吗？</P>
<P><B>资源：</B></P>
<UL>
<LI>这里给出一个已经<A href="http://www.huihoo.com/column/~chen56/webwork-pico/webwork1.4-pico_2004-02-04_3.zip">集成过pico-1.0beta5的webwork1.4的例子下载<BR></A>
<LI><A href="http://martinfowler.com/articles/injection.html">Inversion of Control Containers and the Dependency Injection pattern<BR></A>Martin Fowler文章 ，听说熊节的译文将登在程序员第3期上<BR>
<LI><A href="http://blogs.codehaus.org/people/rinkrank/archives/000551_oh_no_were_testing_the_mock.html">Oh no, we're testing the Mock!</A>&nbsp;<BR>一篇很好的关于用Mock对象测试的文章，还好，我不是其中的反例 </LI></UL>
<P><B>关于作者：</B></P>
<P><FONT size=2>陈鹏，狂热的程序员 email <A href="mailto:chen56@msn.com">chen56@msn.com</A> &nbsp;</FONT></P><img src ="http://www.blogjava.net/kapok/aggbug/4074.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-05-08 00:09 <a href="http://www.blogjava.net/kapok/archive/2005/05/08/4074.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>AOP的概念及在Unit Test上的應用</title><link>http://www.blogjava.net/kapok/archive/2005/05/08/4073.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Sat, 07 May 2005 16:05:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/05/08/4073.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/4073.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/05/08/4073.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/4073.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/4073.html</trackback:ping><description><![CDATA[<P class=tt><SPAN class=txttop><FONT color=#cc0000><A href="http://www.dsc.com.tw/newspaper/43/43-2.htm">http://www.dsc.com.tw/newspaper/43/43-2.htm</A><BR><BR>前言</FONT></SPAN><BR>　　物件導向程式設計（以下簡稱：OOP）經過一段時間的演進與發展，目前已漸漸成為設計系統的主流，由於物件（object）的概念與實際世界的個體非常相似，因此用這種方式來進行系統的設計，可以很真實的模擬實際個體的動作，縮短系統與真實世界的距離。但是，以OOP設計系統，有時候還是會遇到一些困擾，以下面的類別（class）為例：</P>
<BLOCKQUOTE>
<P class=txt>public class SomeBusinessClass {<BR>//Core data members //此class的核心資料<BR>//Other data members: Log stream, data-consistency //其他Class的資料<BR>public void performSomeOperation(OperationInformation) {<BR>//Ensure authentication//進行身分驗證<BR>//Lock the object to ensure data-consistency in case other threads access it<BR>//Ensure the cache is up to date<BR>//Log the start of operation //記錄日誌<BR>// ==== Perform the core operation ==== //處理核心作業<BR>//Log the completion of operation //記錄日誌<BR>//Unlock the object<BR>}<BR>//More operations similar to above<BR>public void save(PersitanceStorage ps) {<BR>}<BR>public void load(PersitanceStorage ps) {<BR>}<BR>}</P></BLOCKQUOTE>
<P class=tt>即使是這樣簡單的類別，還是有一些地方值得我們注意：<BR>1. “Other data members”部分所宣告的物件，基本上並不屬於這個類別主要的關心事項（concern），雖然還是必須要用到它。<BR>2. “performSomeOperation()”這個method除了處理自己的核心作業外，還必須負責呼叫身分驗證、鎖定欲處理的資料、記錄日誌等額外的工作，這些工作似乎與他的責任不太相關，但又找不出必須由誰來做這些事情。<BR>3. 如果save()及load()兩個method也是這個類別的核心程式的話，我們很容易就忽略了這層關係，使注意力集中到別的地方了。</P>
<P class=tt>　　 基於這一類的問題，我們可以看出，OOP雖然可以很適當的表現出系統的模組化，但當遇到需要跨越模組（或類別）的應用時（這些模組（或類別）可能與主要作業邏輯是沒有什麼關係的，如身分驗證、日誌記錄等），OOP就無法以較自然的方式來表現或處理。這種被稱為橫切關係（crosscutting）的行為，常會造成軟體設計或實作時不夠簡潔，不易了解，甚至會造成日後維護上的困難。雖然我們可以利用extend的方式來加以萃取，或引入design pattern來減少這種情形，但由於使用的地方不同，就必須引入新的設計，不但造成設計的困難度增加，也造成日後他人學習、維護上的障礙；圖一 是另一個例子，紅色字是所謂的橫切關係的部分，程式的錯綜複雜可想而知。為了解決上述橫切關係所造成的困擾，AOP的概念便被提出。</P>
<P class=tt align=center><IMG height=295 src="http://www.dsc.com.tw/newspaper/43/images/43-3.gif" width=461><BR>圖 一</P>
<P class=tt align=left><SPAN class=txttop><FONT color=#cc0000>AOP（Aspect Oriented Programming）</FONT></SPAN><BR>　　AOP主要的概念是萃取互相獨立的橫切關係事項而加以模組化，使之可以有效的集中、管理，而不會分散在程式碼的各個地方。AOP能有這樣的能力，主要是其特殊的實作架構，實現AOP的運作原理，首先必須先告訴aspect的實作者（例如稍後會提到的AspectJ），程式的哪些特殊點為橫切關係（我們可以稱其為連接點（join point），一般如method被呼叫的點），將被攔截並加入aspect的規則，這些aspect規則可能是額外的動作或取代原來程式功能等，接著透過aspect實作者特有的compiler，將相對應的aspect程式碼加入之前設定的連接點中（一般稱為aspect weaver，參考 圖二），然後再加以執行，便可以使原來的程式不用在程式碼中呼叫這些特殊橫切關係，而仍能得到所要的結果（以log的例子來說，我們不用再呼叫log，但程式執行的結果卻會幫我們產生log）。</P>
<P align=center><IMG height=150 src="http://www.dsc.com.tw/newspaper/43/images/43-4.gif" width=447><SPAN class=tt><BR><FONT size=2>圖 二</FONT></SPAN></P>
<P class=tt>所以加入了AOP的概念後，我們可以看到 圖 1 的程式可以有這樣的改變（如：圖 三）</P>
<P align=center><IMG height=256 src="http://www.dsc.com.tw/newspaper/43/images/43-5.gif" width=461><BR>圖三 </P>
<P class=tt align=left>　　由於AOP在使用上有這種特殊性，當我們在使用它的時候，一般會搭配其他主要的程式設計的方法來共同建構一個系統，例如，以OOP來當成它的主要底層結構，建構系統主要的核心作業，然後再用AOP填補OOP不足的特殊橫切關係。</P>
<P class=tt align=left>　　 有了aspect的理論架構，接著我們來看看怎麼將它實際的應用在我們的另一個主題“單元測試（Unit Test）”上；我們將使用Xerox的PARC實驗室發展出來的AspectJ來作為接下來實作的語言。AspectJ（Java base AOP implementation language）是實作AOP的語言之一，它擴充自Java語言，並與Java語言互相搭配，一同實現aspect的機制。</P>
<P class=tt><SPAN class=txttop><FONT color=#cc0000>傳統的單元測試（Unit Test）方式</FONT></SPAN><BR>　　當系統開發時，常常需要對某一個個別的物件進行單元測試，以求得物件的正確性；圖 4 是一個簡單的單元測試模型。</P>
<TABLE cellSpacing=0 cellPadding=5 width="100%" align=center border=0>
<TBODY>
<TR>
<TD class=tt vAlign=top height=236>　　當我們針對某個物件進行單元測試時，如果這個被測的物件又呼叫到別的物件（如 圖 四 中LoginView物件用到AccessController物件時），此時為了使這個被呼叫的物件不會影響到我們的被測物件，我們常常就會用一個假的物件來取代這個額外物件，這個假物件就是單元測試中常常被提到的Mock Object（如 圖 5 的例子）。使用Mock Object的好處是我們可以保留唯一一個真實的被測物，使其他額外的物件都是模擬的，如此便可以很容易的餵入測試資料或模擬其他如資料庫或網路連線等狀態，來達到測試的需求。</TD>
<TD vAlign=top>
<DIV align=center><IMG height=208 src="http://www.dsc.com.tw/newspaper/43/images/43-6.gif" width=302><BR>圖四 </DIV></TD></TR></TBODY></TABLE>
<P class=tt align=left>雖然Mock Object可以解決這些問題，但隨著系統的發展及變更，Mock Object會跟著越來越多，反而造成Mock Object維護不易，單元測試的成本愈來愈高；於是，我們必須找出一種方法，既可以不用維護Mock Object的程式碼，又可以達到Mock Object的功能與好處。</P>
<P class=tt align=left>　　由於AOP的概念之一是攔截特殊method的呼叫，並取代其內容，這樣的行為與Mock Object的功能非常類似，再加上部分單元測試的研究文獻，也提到這樣的做法，於是我們認為使用AspectJ，應該可以解決Mock Object所產生的問題。</P>
<P align=center><IMG height=210 src="http://www.dsc.com.tw/newspaper/43/images/43-7.gif" width=333> <BR>圖五 </P>
<P align=center><FONT size=2><SPAN class=tt>使用Aspect的概念來進行單元測試利用AOP的概念，整個測試架構可以修改如 圖 六。</SPAN><BR><IMG height=273 src="http://www.dsc.com.tw/newspaper/43/images/43-8.gif" width=352><BR></FONT>圖六</P>
<P class=tt align=left><SPAN class=txttop><FONT color=#990000>詳細的動作過程，說明如下：</FONT></SPAN><BR><STRONG>1. </STRONG>首先設定攔截連接點的條件（利用AspectJ的程式來實作，如 圖 6 MethodInterceptor部分），我們假設所有被測物件及其相關的所有物件的method呼叫都是可攔截的連接點，而Unit Test class及AspectBasedTest class則不在攔截的範圍，因為test case內的程式碼，並沒有被攔截取代的必要。<BR><STRONG>2.</STRONG> 接著提供一個可以設定哪些method將被取代的設定機制（如 圖六 的AspectBasedTest class）；當程式執行時，很多相關的method呼叫都會被攔截，我們必須可以設定哪一些method要用我們的設定值來取代其執行結果，哪一些method被攔截後不會被取代，而是進行正常的執行動作。這樣，當測試程式執行時，我們才能掌握所有的測試環境。<BR><STRONG>3. </STRONG>完成了上述的機制後，我們看一下整個動作的過程；當測試程式開始執行時，會先設定要被取代的method call（即連接點），取代後又會傳回哪一些回傳值等，接著便開始執行測試的程式碼，當它呼叫設定的method call時，AspectJ的程式便會去判斷這些method call是否被指定要被取代，如果不需要被取代，則程式便會執行原來的method，如果這些method call已經被設定必須被取代，則AspectJ便會傳回被指定的回傳值，使被測物件能在執行時取到事先設定的測試資料，最後，測試完成並顯示測試的結果。<BR><STRONG>4. 以下是AspectJ攔截點與設定模擬method機制的程式碼及說明：</STRONG><BR>/** 此class讓使用者可以設定哪一些method call要在攔截時被取代以及取代後要傳回什麼要的值，這就好像模擬一個Mock Object的method call，只是我們不需真的去寫Mock Object<BR><SPAN class=txt><FONT size=2>*/<BR>public class ComponentTestCase extends TestCase<BR>{<BR>public ComponentTestCase(String name)<BR>{<BR>super(name);<BR>}<BR>//設定哪些method call要被當成mock的method call以及當它被呼叫時，要傳回什麼值。這裡我們主要是使用一個Hashtable來儲存使用者設定的回傳值，當程式執行時呼叫到這個method，取代的機制就會啟動，於是就會到Hashtable內去取得相對應的回傳值；如果找不到相對應的值，就傳回null，代表該method並未被設定要被取代<BR>public static void setMock(String className, String methodName, Object returnValue)<BR>{<BR>testData.put(makeKey(className, methodName), returnValue);<BR>}<BR>public static void setMock(String className, String methodName)<BR>{<BR>setMock(className, methodName, new Object());<BR>}<BR>//取回之前設定的回傳值<BR>public static Object getMockReturnValue(String className, String methodName)<BR>{<BR>return testData.get(makeKey(className, methodName));<BR>}<BR>//驗證設定的mock method call是否如預期的被測試程式使用到<BR>public void assertCalled(String className, String methodName)<BR>{<BR>if ( ! isCalled(className, methodName) )<BR>fail("The method '" + methodName + "' in class '" +<BR>className + "' was expected to be called but it wasn't");<BR>}<BR>//驗證使用mock method call的參數是否如預期正確輸入的相關程式<BR>public Object getArgument(String className, String methodName, String argumentName)<BR>{<BR>Object argument = null;<BR>Hashtable arguments = (Hashtable)callsMade.get(makeKey(className, methodName));<BR>if (arguments != null)<BR>argument = arguments.get(argumentName);<BR>return argument;<BR>}<BR>//驗證設定的mock method call是否如預期的被測試程式使用到的相關程式<BR>public static void indicateCalled(String className, String methodName, Hashtable arguments)<BR>{<BR>callsMade.put(makeKey(className, methodName), arguments);<BR>}<BR>//驗證設定的mock method call是否如預期的被測試程式使用到的相關程式<BR>public static boolean isCalled(String className, String methodName)<BR>{<BR>return callsMade.get(makeKey(className, methodName)) != null;<BR>}<BR>//驗證使用mock method call的參數是否如預期正確輸入<BR>public void assertArgumentPassed(String className, String methodName,<BR>String argumentName, Object argumentValue)<BR>{<BR>Object argument = getArgument(className, methodName, argumentName);<BR>if (argument == null || !argument.equals(argumentValue))<BR>fail("The argument '" + argumentName + "' of method '" +<BR>methodName + "' in class '" +<BR>className + " ' should have the value '" +<BR>argumentValue + "' but it was '" +<BR>argument + "'!");<BR>}<BR>//組合Hashtable所使用的Key的程式<BR>private static String makeKey(String className, String methodName)<BR>{<BR>return className + "." + methodName;<BR>}<BR>private static Hashtable testData = new Hashtable();<BR>private static Hashtable callsMade = new Hashtable(); }</FONT></SPAN></P>
<P>/**此程式是AspectJ攔截點設定程式，主要是設定哪些method call會被此程式加以取代，也就是說，被設定了攔截的method call，在別人呼叫時，會先跑到這個程式來執行，再依程式的邏輯決定要執行真正的method或用其他值取代<BR>*/<BR>aspect AspectBasedMethodInterceptor<BR>{<BR>pointcut allCalls():execution(* *.*(..)) &amp;&amp; !within(ajmock.*); //設定哪些method call是攔截點<BR>Object around() : allCalls() //攔截點攔截後要做的事（around()在AspectJ中是取代攔截的call）<BR>{<BR>String className = thisJoinPoint.getSignature().getDeclaringType().getName();<BR>Object receiver = thisJoinPoint.getThis();<BR>if (receiver != null)<BR>className = receiver.getClass().getName();<BR>String methodName = thisJoinPoint.getSignature().getName();<BR>//嘗試去取得mock method call的回傳設定值<BR>Object returnValue = ajmock.ComponentTestCase.getMockReturnValue(className, methodName);<BR>//回傳值如果不是空的，表示此method call是使用者設定的mock method call必須用使用者設定的回傳值來取代<BR>if (returnValue != null)<BR>{<BR>Hashtable arguments = (Hashtable)getArguments(thisJoinPoint);<BR>//呼叫此method主要是為之後的method call驗證之用<BR>ComponentTestCase.indicateCalled(className, methodName, arguments);<BR>return returnValue;<BR>} else {<BR>//如果回傳值是空的，表示此method call不是使用者設定的mock method call，必須執行原來的method，而不被取代<BR>return proceed();<BR>}<BR>}<BR>//取得method call的參數值以便做事後的驗證（驗證是否與使用者之前設定的參數值相同）<BR>private Hashtable getArguments(JoinPoint jp)<BR>{<BR>Hashtable arguments = new Hashtable();<BR>Object[] argumentValues = jp.getArgs();<BR>String[] argumentNames =<BR>((CodeSignature)jp.getSignature()).getParameterNames();<BR>for (int i = 0; i &lt; argumentValues.length; i++)<BR>{<BR>if (argumentValues[i] != null)<BR>arguments.put(argumentNames[i], argumentValues[i]);<BR>}<BR>return arguments;<BR>}<BR>}<BR><FONT size=2><STRONG><SPAN class=tt>5. 測試程式便可以這樣寫</SPAN></STRONG><BR></FONT>public class TestLoginView extends ComponentTestCase { //繼承設定模擬method機制的class</P>
<P>public TestLoginView(String s) {<BR>super(s);<BR>}<BR>protected void setUp() {<BR>}<BR>public void testValidateValidUser() {<BR>LoginView view = new LoginView();<BR>Integer mockResult = new Integer(AccessController.USER_INVALID);<BR>//設定ajusage.AccessController的login method要被取代，取代值是mockResult<BR>setMock("ajusage.AccessController","login", mockResult);<BR>/呼叫被測試method<BR>view.validate();<BR>//驗證測試結果<BR>assertEquals("login successful", view.getStatus());<BR>}</P>
<P class=tt align=left>如此便達到我們的需求，不但可以使用Mock Object的好處，又可以不須維護額外的程式碼。</P>
<P class=tt><SPAN class=txttop><FONT color=#cc0000>總結</FONT></SPAN><BR>　　麻省理工學院在2001一月份出刊的Technology Review雜誌中，特別選出可改變未來世界的10大創新科技（http://www.technologyreview.com/articles /tr10_toc0101.asp），在其中一項”解開糾結的程式碼”（Untangling Code）中提到，AOP的出現能有效幫助軟體發展者開發出容易解讀的程式碼，降低軟體開發的複雜度，所以將其列為可以改變未來世紀的科技之一。對AOP所擁有的功能來說，Unit Test祇是其中一小部份的應用，它對程式的模組化或重整等，也都可發揮不小的益處，端看我們對它的活用程度而定。而這篇文章，也不過是一個開端而已。</P>
<P><SPAN class=tt><FONT size=2><STRONG>1. 參考資料</STRONG><BR>如果你想更詳細的了解AOP的內容，也可以參考以下資料：<BR>1. AspectJ網站：</FONT><A href="http://www.eclipse.org/aspectj/" target=_blank><FONT color=#1c4548>http://www.eclipse.org/aspectj/</FONT></A><FONT size=2>。<BR>2. I want my AOP, Part1, Part2, part3 （By Ramnivas Laddad）：<BR></FONT><A href="http://www.javaworld.com/javaworld/jw-01-2002/jw-0118-aspect.html" target=_blank><FONT color=#1c4548>http://www.javaworld.com/javaworld/jw-01-2002/jw-0118-aspect.html</FONT></A><FONT size=2>，<BR></FONT><A href="http://www.javaworld.com/javaworld/jw-03-2002/jw-0301-aspect2.html" target=_blank><FONT color=#1c4548>http://www.javaworld.com/javaworld/jw-03-2002/jw-0301-aspect2.html</FONT></A><FONT size=2>，<BR></FONT><A href="http://www.javaworld.com/javaworld/jw-04-2002/jw-0412-aspect3.html" target=_blank><FONT color=#1c4548>http://www.javaworld.com/javaworld/jw-04-2002/jw-0412-aspect3.html</FONT></A><FONT size=2>。<BR>3. Junit網站：</FONT><A href="http://www.junit.org/" target=_blank><FONT color=#1c4548>http://www.junit.org</FONT></A><FONT size=2>。<BR>4. Virtual Mock Objects using AspectJ with JUNIT，<BR></FONT><A href="http://www.xprogramming.com/xpmag/virtualMockObjects.htm" target=_blank><FONT color=#1c4548>http://www.xprogramming.com/xpmag/virtualMockObjects.htm</FONT></A><FONT size=2>。<BR>5. AspectJ for JBuilder，</FONT><A href="http://aspectj4jbuildr.sourceforge.net/" target=_blank><FONT color=#1c4548>http://aspectj4jbuildr.sourceforge.net/</FONT></A><FONT size=2>。<BR>6. Mock Object，</FONT><A href="http://www.mockobjects.com/" target=_blank><FONT color=#255f63>http://www.mockobjects.com</FONT></A><FONT size=2>。</FONT></SPAN></P><img src ="http://www.blogjava.net/kapok/aggbug/4073.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-05-08 00:05 <a href="http://www.blogjava.net/kapok/archive/2005/05/08/4073.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>使用spring-mock进行dao集成测试</title><link>http://www.blogjava.net/kapok/archive/2005/05/07/4072.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Sat, 07 May 2005 15:56:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/05/07/4072.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/4072.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/05/07/4072.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/4072.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/4072.html</trackback:ping><description><![CDATA[<P><STRONG><A href="http://rongsantang.yculblog.com/post.621226.html">http://rongsantang.yculblog.com/post.621226.html</A><A class=post_title href="http://rongsantang.yculblog.com/post.621226.html"><FONT color=#0079a2><BR><BR>使用spring-mock进行dao集成测试</FONT></A></STRONG><BR><FONT color=#003399><SPAN class=post_user>一地鸡毛 @ 2005-03-25 16:19</SPAN><BR><BR></FONT>在进行dao的集成测试时候，数据清理，察看数据都是比较麻烦的事情，使用Spring-mock.jar可以帮助我们简化着一个过程。我举一个简单的例子，说明一下如何使用spring-mock。 <BR><BR>首先是po, hbm.xml, dao, daoimpl没什么好说的： <BR>
<TABLE style="BORDER-RIGHT: #000000 1px solid; BORDER-TOP: #000000 1px solid; MARGIN: 10px; BORDER-LEFT: #000000 1px solid; BORDER-BOTTOM: #000000 1px solid" cellSpacing=0 cellPadding=10 width="90%">
<TBODY>
<TR>
<TD bgColor=#eeeeee><FONT face="Courier New"><BR>Customer.java : <BR><BR>package rst.spring.mock; <BR><BR>import java.io.Serializable; <BR><BR>/** @author Hibernate CodeGenerator */ <BR>public class Customer implements Serializable { <BR><BR>&nbsp;&nbsp;&nbsp;/** identifier field */ <BR>&nbsp;&nbsp;&nbsp;private Long id; <BR><BR>&nbsp;&nbsp;&nbsp;/** nullable persistent field */ <BR>&nbsp;&nbsp;&nbsp;private String name; <BR><BR>&nbsp;&nbsp;&nbsp;/** full constructor */ <BR>&nbsp;&nbsp;&nbsp;public Customer(String name) { <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.name = name; <BR>&nbsp;&nbsp;&nbsp;} <BR><BR>&nbsp;&nbsp;&nbsp;/** default constructor */ <BR>&nbsp;&nbsp;&nbsp;public Customer() { <BR>&nbsp;&nbsp;&nbsp;} <BR><BR>&nbsp;&nbsp;&nbsp;public Long getId() { <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return this.id; <BR>&nbsp;&nbsp;&nbsp;} <BR><BR>&nbsp;&nbsp;&nbsp;public void setId(Long id) { <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.id = id; <BR>&nbsp;&nbsp;&nbsp;} <BR><BR>&nbsp;&nbsp;&nbsp;public String getName() { <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return this.name; <BR>&nbsp;&nbsp;&nbsp;} <BR><BR>&nbsp;&nbsp;&nbsp;public void setName(String name) { <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.name = name; <BR>&nbsp;&nbsp;&nbsp;} <BR><BR>} <BR><BR>Customer.hbm.xml : <BR><BR>&lt;?xml version="1.0" encoding="UTF-8"?&gt; <BR>&lt;!DOCTYPE hibernate-mapping PUBLIC <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"-//Hibernate/Hibernate Mapping DTD//EN" <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"&gt; <BR>&lt;hibernate-mapping package="rst.spring.mock"&gt; <BR>&lt;class name="Customer" table="customer"&gt; <BR>&lt;id name="id" column="id" type="long" unsaved-value="null"&gt; <BR>&lt;generator class="identity"/&gt; <BR>&lt;/id&gt; <BR>&lt;property name="name" column="name" type="string"/&gt; <BR>&lt;/class&gt; <BR><BR>&lt;/hibernate-mapping&gt; <BR><BR>CustomerDAO : <BR>/* <BR>* Created on 2005-3-25 <BR>*/ <BR>package rst.spring.mock; <BR><BR>import org.springframework.dao.DataAccessException; <BR><BR>/** <BR>* @author rst <BR>* <BR>*/ <BR>public interface CustomerDAO { <BR>&nbsp;&nbsp;&nbsp;public void add(Customer customer) throws DataAccessException; <BR>} <BR><BR>CustomerDAOImpl : <BR><BR>package rst.spring.mock; <BR><BR>import org.springframework.dao.DataAccessException; <BR>import org.springframework.orm.hibernate.support.HibernateDaoSupport; <BR><BR>/** <BR>* Class description. <BR>* <BR>* @author rst <BR>*/ <BR>public class CustomerDAOHibernateImpl extends HibernateDaoSupport implements CustomerDAO{ <BR>&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;public void add(Customer customer) throws DataAccessException{ <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.getHibernateTemplate().save(customer); <BR>&nbsp;&nbsp;&nbsp;} <BR>} <BR><BR></FONT></TD></TR></TBODY></TABLE><BR><BR>然后测试的基类SpringDAOTestCase继承自AbstractTransactionalDataSourceSpringContextTests，目前只有一个指定测试用xml文件位置的逻辑。 <BR>
<TABLE style="BORDER-RIGHT: #000000 1px solid; BORDER-TOP: #000000 1px solid; MARGIN: 10px; BORDER-LEFT: #000000 1px solid; BORDER-BOTTOM: #000000 1px solid" cellSpacing=0 cellPadding=10 width="90%">
<TBODY>
<TR>
<TD bgColor=#eeeeee><FONT face="Courier New"><BR>package rst.spring.mock; <BR><BR>import org.springframework.test.AbstractTransactionalDataSourceSpringContextTests; <BR><BR>/** <BR>* Class description. <BR>* <BR>* @author rst <BR>*/ <BR>public abstract class SpringDAOTestCase extends AbstractTransactionalDataSourceSpringContextTests { <BR><BR>&nbsp;protected String[] getConfigLocations() { <BR>&nbsp;&nbsp;&nbsp;return new String[] { "test.xml" }; <BR>&nbsp;} <BR><BR>} <BR></FONT></TD></TR></TBODY></TABLE><BR><BR>接着是我们真正测试的类CustomerDAOTest.java： <BR>
<TABLE style="BORDER-RIGHT: #000000 1px solid; BORDER-TOP: #000000 1px solid; MARGIN: 10px; BORDER-LEFT: #000000 1px solid; BORDER-BOTTOM: #000000 1px solid" cellSpacing=0 cellPadding=10 width="90%">
<TBODY>
<TR>
<TD bgColor=#eeeeee><FONT face="Courier New"><BR>package rst.spring.mock; <BR><BR>/** <BR>* Class description. <BR>* <BR>* @author rst <BR>*/ <BR>public class CustomerDaoTest extends SpringDAOTestCase { <BR><BR>&nbsp;&nbsp;&nbsp;private CustomerDAOHibernateImpl customerDAO; <BR><BR>&nbsp;&nbsp;&nbsp;protected void onSetUpInTransaction() throws Exception { <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;super.onSetUpInTransaction(); <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//this.setPopulateProtectedVariables(true); <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;customerDAO = (CustomerDAOHibernateImpl) this.applicationContext.getBean("customerDAO"); <BR>&nbsp;&nbsp;&nbsp;} <BR><BR>&nbsp;&nbsp;&nbsp;protected void onTearDownInTransaction() { <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;customerDAO = null; <BR>&nbsp;&nbsp;&nbsp;} <BR><BR>&nbsp;&nbsp;&nbsp;public void testInsert() { <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Customer customer = new Customer(); <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;customer.setName("javaeye"); <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;customerDAO.add(customer); <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String name = (String) jdbcTemplate.queryForObject("select name from customer where id=?", new Object[]{customer.getId()}, String.class); <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;assertEquals(customer.getName(), name); <BR>&nbsp;&nbsp;&nbsp;} <BR><BR>} <BR></FONT></TD></TR></TBODY></TABLE><BR><BR>最后看看配置文件test.xml： <BR>
<TABLE style="BORDER-RIGHT: #000000 1px solid; BORDER-TOP: #000000 1px solid; MARGIN: 10px; BORDER-LEFT: #000000 1px solid; BORDER-BOTTOM: #000000 1px solid" cellSpacing=0 cellPadding=10 width="90%">
<TBODY>
<TR>
<TD bgColor=#eeeeee><FONT face="Courier New"><BR>&lt;?xml version="1.0" encoding="UTF-8"?&gt; <BR>&lt;!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"&gt; <BR><BR>&lt;!-- <BR>&nbsp;- Application context definition for Petclinic on Hibernate. <BR>--&gt; <BR>&lt;beans&gt; <BR><BR>&lt;!-- ========================= RESOURCE DEFINITIONS ========================= --&gt; <BR>&nbsp; <BR>&lt;!-- Configurer that replaces ${...} placeholders with values from a properties file --&gt; <BR>&lt;!-- (in this case, JDBC-related settings for the dataSource definition below) --&gt; <BR>&lt;bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"&gt; <BR>&lt;property name="location"&gt;&lt;value&gt;classpath:jdbc.properties&lt;/value&gt;&lt;/property&gt; <BR>&lt;/bean&gt; <BR><BR>&lt;!-- Local DataSource that works in any environment --&gt; <BR>&lt;bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"&gt; <BR>&lt;property name="driverClassName"&gt;&lt;value&gt;${jdbc.driverClassName}&lt;/value&gt;&lt;/property&gt; <BR>&lt;property name="url"&gt;&lt;value&gt;${jdbc.url}&lt;/value&gt;&lt;/property&gt; <BR>&lt;property name="username"&gt;&lt;value&gt;${jdbc.username}&lt;/value&gt;&lt;/property&gt; <BR>&lt;property name="password"&gt;&lt;value&gt;${jdbc.password}&lt;/value&gt;&lt;/property&gt; <BR>&lt;/bean&gt; <BR><BR>&lt;!-- Hibernate SessionFactory --&gt; <BR>&lt;bean id="sessionFactory" class="org.springframework.orm.hibernate.LocalSessionFactoryBean"&gt; <BR>&lt;property name="dataSource"&gt;&lt;ref local="dataSource"/&gt;&lt;/property&gt; <BR>&lt;property name="mappingResources"&gt; <BR>&lt;value&gt;rst/spring/mock/Customer.hbm.xml&lt;/value&gt; <BR>&lt;/property&gt; <BR>&lt;property name="hibernateProperties"&gt; <BR>&lt;props&gt; <BR>&lt;prop key="hibernate.dialect"&gt;${hibernate.dialect}&lt;/prop&gt; <BR>&lt;prop key="hibernate.show_sql"&gt;true&lt;/prop&gt; <BR>&lt;/props&gt; <BR>&lt;/property&gt; <BR>&lt;/bean&gt; <BR><BR>&lt;!-- Transaction manager for a single Hibernate SessionFactory (alternative to JTA) --&gt; <BR>&lt;bean id="transactionManager" class="org.springframework.orm.hibernate.HibernateTransactionManager"&gt; <BR>&lt;property name="sessionFactory"&gt;&lt;ref local="sessionFactory"/&gt;&lt;/property&gt; <BR>&lt;/bean&gt; <BR><BR>&lt;bean id="hibernateTemplate" class="org.springframework.orm.hibernate.HibernateTemplate"&gt; <BR>&lt;property name="sessionFactory"&gt;&lt;ref local="sessionFactory"/&gt;&lt;/property&gt; <BR>&lt;/bean&gt; <BR><BR>&lt;bean id="customerDAO" class="rst.spring.mock.CustomerDAOHibernateImpl"&gt; <BR>&lt;property name="hibernateTemplate"&gt;&lt;ref local="hibernateTemplate"/&gt;&lt;/property&gt; <BR>&lt;/bean&gt; <BR>&lt;/beans&gt; <BR></FONT></TD></TR></TBODY></TABLE><BR>这个文件很简单，不要忘记transactionManager的配置，Test类会自动装配的。 <BR><BR>运行之后，就可以看到应有的结果，并且数据库中不会有数据污染。这个过程主要是开始一个transaction，然后开始你的test方法，执行dao操作，执行sql查询验证结果，最后无论成功失败rollback transaction。</P>
<P><SPAN class=post_user><FONT color=#003399>Trackback地址: </FONT></SPAN><A class=post_user onclick="return false;" href="http://www.yculblog.com/trackback/0/621226"><FONT color=#003399>http://www.yculblog.com/trackback/0/621226</FONT></A></P><img src ="http://www.blogjava.net/kapok/aggbug/4072.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-05-07 23:56 <a href="http://www.blogjava.net/kapok/archive/2005/05/07/4072.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Sitemesh</title><link>http://www.blogjava.net/kapok/archive/2005/05/07/4062.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Sat, 07 May 2005 06:58:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/05/07/4062.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/4062.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/05/07/4062.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/4062.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/4062.html</trackback:ping><description><![CDATA[<A href="http://www.huihoo.com/java/sitemesh/">http://www.huihoo.com/java/sitemesh/</A><BR><BR>
<P align=center><FONT size=2><B>使用sitemesh建立复合视图 - 1.hello</B></FONT></P>(作者:chen-neu ,提供给<FONT color=green><EM> </EM>huihoo.com</FONT> 发布) 
<HR color=#ff0000 SIZE=1>

<P><FONT size=2><B><A href="http://www.huihoo.com/java/sitemesh/index.html">使用sitemesh建立复合视图 - 1.hello</A>&nbsp; <FONT color=#ff0000>&lt;- now</FONT></B></FONT></P>
<P><FONT size=2><B><A href="http://www.huihoo.com/java/sitemesh/2.html">使用sitemesh建立复合视图 - 2.装饰器</A>&nbsp;&nbsp;</B></FONT></P>
<P><FONT size=2><B><A href="http://www.huihoo.com/java/sitemesh/3.html">使用sitemesh建立复合视图 - 3.其它讨论</A>&nbsp;</B></FONT></P>
<P><FONT size=2><B>sitemesh是opensymphony团队开发的j2ee应用框架之一，旨在提高页面的可维护性和复用性。opensymphony的另一个广为人知的框架为webwork是用作web层的表示框架。他们都是开源的，可以在www.sf.net下找到。</B></FONT></P>
<P><FONT size=2><B>应用于以下大项目的例子：http://opensource.thoughtworks.com/projects/sitemesh.html</B></FONT></P>
<UL>
<LI><A href="http://www.jboss.org/">www.jboss.org</A> 
<LI><A href="http://www.theserverside.com/">www.theserverside.com</A> 
<LI><A href="http://www.opensymphony.com/">www.opensymphony.com</A> 
<LI><A href="http://www.atlassian.com/">www.atlassian.com</A> </LI></UL>
<TABLE style="BORDER-COLLAPSE: collapse" borderColor=#000000 height=1 cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD width="100%" bgColor=#000080 height=19><FONT color=#ffffff size=2>简介：</FONT> </TD></TR>
<TR>
<TD width="100%" height=63><FONT size=2>sitemesh应用Decorator模式，用filter截取request和response,把页面组件head,content,banner结合为一个完整的视图。通常我们都是用include标签在每个jsp页面中来不断的包含各种header, stylesheet, scripts and footer，现在，在sitemesh的帮助下，我们可以开心的删掉他们了。如下图，你想轻松的达到复合视图模式，那末看完本文吧。</FONT> 
<P><IMG height=495 src="http://www.huihoo.com/java/sitemesh/index_example-diagram.gif" width=704></P>
<P>　</P></TD></TR>
<TR>
<TD width="100%" bgColor=#000080 height=19><FONT color=#ffffff size=2>hello sitemesh：</FONT> </TD></TR>
<TR>
<TD width="100%" height=15>
<OL>
<LI><FONT size=2>在WEB-INF/web.xml中copy以下filter的定义:</FONT> 
<TABLE style="BORDER-COLLAPSE: collapse" borderColor=#000000 height=1 cellSpacing=0 cellPadding=0 width="80%" border=1>
<TBODY>
<TR>
<TD width="100%" bgColor=#efefef><PRE class=codeblock>&lt;filter&gt;
  &lt;filter-name&gt;sitemesh&lt;/filter-name&gt;
  &lt;filter-class&gt;com.opensymphony.module.sitemesh.filter.PageFilter&lt;/filter-class&gt;
&lt;/filter&gt;

&lt;filter-mapping&gt;
  &lt;filter-name&gt;sitemesh&lt;/filter-name&gt;
  &lt;url-pattern&gt;/*&lt;/url-pattern&gt;
&lt;/filter-mapping&gt;

&lt;taglib&gt;
  &lt;taglib-uri&gt;sitemesh-decorator&lt;/taglib-uri&gt;
  &lt;taglib-location&gt;/WEB-INF/sitemesh-decorator.tld&lt;/taglib-location&gt;
&lt;/taglib&gt;

&lt;taglib&gt;
  &lt;taglib-uri&gt;sitemesh-page&lt;/taglib-uri&gt;
  &lt;taglib-location&gt;/WEB-INF/sitemesh-page.tld&lt;/taglib-location&gt;
&lt;/taglib&gt;
</PRE></TD></TR></TBODY></TABLE><FONT size=2><BR></FONT>
<LI><FONT size=2>copy所需jar和dtd文件至相应目录，访问<A href="http://opensymphony.sourceforge.net/">opensymphony.sourceforge.net</A>的cvs以获取sitemesh最新版本。</FONT> 
<TABLE width="80%" border=0>
<TBODY>
<TR>
<TD width="35%"><FONT size=2>sitemesh.jar</FONT></TD>
<TD width="65%"><FONT size=2>WEB-INF/lib</FONT> </TD></TR>
<TR>
<TD width="35%"><FONT size=2>sitemesh-decorator.tld</FONT></TD>
<TD width="65%"><FONT size=2>WEB-INF</FONT> </TD></TR>
<TR>
<TD width="35%"><FONT size=2>sitemesh-page.tld</FONT></TD>
<TD width="65%"><FONT size=2>WEB-INF</FONT> </TD></TR></TBODY></TABLE><FONT size=2><BR></FONT>
<LI><FONT size=2>建立</FONT><FONT size=2>WEB-INF/</FONT><FONT size=2>decorators.xml描述各装饰器页面(可仿照sitemesh例子)。</FONT> 
<TABLE style="BORDER-COLLAPSE: collapse" borderColor=#000000 height=1 cellSpacing=0 cellPadding=0 width="80%" bgColor=#efefef border=1>
<TBODY>
<TR>
<TD width="100%"><PRE><FONT size=2>&lt;decorators defaultdir="/_decorators"&gt;
    &lt;decorator name="main" page="main.jsp"&gt;
        &lt;pattern&gt;*&lt;/pattern&gt;
    &lt;/decorator&gt;
&lt;/decorators&gt;</FONT></PRE></TD></TR></TBODY></TABLE>
<P><FONT size=2><BR><BR></FONT></P>
<LI><FONT size=2>建立装饰器页面 /_decorators/main.jsp<BR></FONT>
<TABLE style="BORDER-COLLAPSE: collapse" borderColor=#000000 height=1 cellSpacing=0 cellPadding=0 width="80%" bgColor=#efefef border=1>
<TBODY>
<TR>
<TD width="100%"><PRE><FONT size=2>&lt;%@ page contentType="text/html; charset=GBK"%&gt;
&lt;%@ taglib uri="sitemesh-decorator" prefix="decorator" %&gt;

&lt;html&gt;
  &lt;head&gt;
    &lt;title&gt;&lt;decorator:title default="装饰器页面..." /&gt;&lt;/title&gt;
    &lt;decorator:head /&gt;
  &lt;/head&gt;
  &lt;body&gt;
    sitemesh的例子&lt;hr&gt;
    &lt;decorator:body /&gt;
    &lt;hr&gt;chen56@msn.com
  &lt;/body&gt;
&lt;/html&gt;
</FONT></PRE></TD></TR></TBODY></TABLE>
<P><FONT size=2><BR></FONT></P>
<LI><FONT size=2>建立一个的被装饰页面 /index.jsp(内容页面)</FONT> 
<TABLE style="BORDER-COLLAPSE: collapse" borderColor=#000000 height=1 cellSpacing=0 cellPadding=0 width="80%" bgColor=#efefef border=1>
<TBODY>
<TR>
<TD width="100%"><PRE><FONT size=2>&lt;%@ page contentType="text/html; charset=GBK"%&gt;
&lt;html&gt;
  &lt;head&gt;
    &lt;title&gt;Agent Test&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;p&gt;本页只有一句，就是本句.&lt;/p&gt;
  &lt;/body&gt;
&lt;/html&gt;</FONT></PRE></TD></TR></TBODY></TABLE></LI></OL>
<P><FONT size=2>最后访问index.jsp，将生成如下页面：</FONT> 
<P><FONT size=2><IMG height=363 src="http://www.huihoo.com/java/sitemesh/index_sitemesh.jpg" width=411 border=0></FONT> 
<P><FONT size=2>而且，所有的页面也会如同index.jsp一样，被sitemesh的filter使用装饰模式修改成如上图般模样，却不用再使用include标签。</FONT> 
<P>　 
<TABLE style="BORDER-COLLAPSE: collapse" borderColor=#000000 height=1 cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD width="100%" bgColor=#000080 height=19><B><FONT color=#ffffff size=2>装饰器&nbsp; decorator概念</FONT></B> </TD></TR>
<TR>
<TD width="100%" height=63><FONT size=2>建立可复用的web应用程序,一个通用的方法是建立一个分层系统，如同下面一个普通的web应用：</FONT> 
<UL>
<LI><FONT size=2>前端，front-end:JSP和Servlets，或jakarta的velocity</FONT> 
<LI><FONT size=2>控制层框架 Controller ： (Struts/Webwork)</FONT> 
<LI><FONT size=2>业务逻辑 Business ：主要业务逻辑</FONT> 
<LI>
<P><FONT size=2>持久化框架 ：hibernate/jdo</FONT></P></LI></UL>
<P><FONT size=2>可糟糕的是前端的页面逻辑很难被复用，当你在每一个页面中用数之不尽的include来复用公共的header, stylesheet, scripts，footer时，一个问题出现了-重复的代码，每个页面必须用copy来复用页面结构，而当你需要创意性的改变页面结构时，灾难就爱上了你。</FONT></P>
<P><FONT size=2>sitemesh通过filter截取request和response，并给原始的页面加入一定的装饰(可能为header,footer...)，然后把结果返回给客户端，并且被装饰的原始页面并不知道sitemesh的装饰，这也就达到了脱耦的目的。</FONT></P>
<P><FONT size=2>据说即将新出台的Portlet规范会帮助我们标准的实现比这些更多更cool的想法，但可怜的我还不懂它到底是一个什末东东，有兴趣的人可以研究<BR><A href="http://jakarta.apache.org/jetspeed">jetspeed</A>，或<A href="http://www.jcp.org/en/jsr/detail?id=168">JSR (Java Specification Request) 168</A>,但我想sitemesh如此简单，我们不妨先用着。</FONT></P>
<P>　</P></TD></TR>
<TR>
<TD width="100%" bgColor=#000080 height=19><B><FONT color=#ffffff size=2>让我们看看怎样配置环境</FONT> </B></TD></TR>
<TR>
<TD width="100%" height=15><FONT size=2>除了要copy到WEB-INF/lib中的sitemesh.jar, </FONT><FONT size=2>copy到WEB-INF中的sitemesh-decorator.tld,sitemesh-page.tld文件外，还有2个文件要建立到WEB-INF/：</FONT> 
<UL>
<LI><FONT size=2>sitemesh.xml (可选)&nbsp;&nbsp;</FONT> 
<LI><FONT size=2>decorators.xml&nbsp;</FONT> </LI></UL>
<H2><FONT size=2>sitemesh.xml&nbsp;可以设置2种信息:</FONT></H2>
<P><FONT size=2><B>Page Parsers</B> ：负责读取stream的数据到一个Page对象中以被SiteMesh解析和操作。(不太常用，默认即可)</FONT></P>
<P><FONT size=2><B>Decorator Mappers</B>&nbsp;: 不同的装饰器种类，我发现2种比较有用都列在下面。一种通用的mapper,可以指定装饰器的配置文件名，另一种可打印的装饰器，可以允许你当用http://localhost/aaa/a.html?printable=true方式访问时给出原始页面以供打印(免得把header,footer等的花哨的图片也搭上)<BR><BR><I>(但一般不用建立它，默认设置足够了：com/opensymphony/module/sitemesh/factory/sitemesh-default.xml）：</I></FONT> </P>
<P><FONT size=2><B>范例：</B></FONT> </P>
<TABLE style="BORDER-COLLAPSE: collapse" height=1 cellSpacing=0 cellPadding=0 width="100%" bgColor=#e3e3e3 border=1>
<TBODY>
<TR>
<TD width="100%"><CODE><FONT size=2>&lt;sitemesh&gt;<BR>&nbsp;&nbsp;&lt;page-parsers&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;parser default="true" class="com.opensymphony.module.sitemesh.parser.DefaultPageParser" /&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;parser content-type="text/html" class="com.opensymphony.module.sitemesh.parser.FastPageParser" /&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;parser content-type="text/html;charset=ISO-8859-1" class="com.opensymphony.module.sitemesh.parser.FastPageParser" /&gt;<BR>&nbsp;&nbsp;&lt;/page-parsers&gt;<BR><BR>&nbsp;&nbsp;&lt;decorator-mappers&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;mapper class="com.opensymphony.module.sitemesh.mapper.ConfigDecoratorMapper"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;param name="config" value="/WEB-INF/decorators.xml" /&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;/mapper&gt;<BR></FONT></CODE><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;mapper class="com.opensymphony.module.sitemesh.mapper.PrintableDecoratorMapper"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;param name="decorator" value="printable" /&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;param name="parameter.name" value="printable" /&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;param name="parameter.value" value="true" /&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/mapper&gt;<CODE><BR>&nbsp;&nbsp;</CODE></FONT><CODE><FONT size=2>&lt;/decorator-mappers&gt;<BR>&lt;/sitemesh&gt; </FONT></CODE></TD></TR></TBODY></TABLE>
<P><FONT size=2><B>decorators.xml</B>&nbsp;：定义构成复合视图的所有页面构件的描述(主要结构页面，header,footer...)，如下例：</FONT></P>
<TABLE style="BORDER-COLLAPSE: collapse" height=1 cellSpacing=0 cellPadding=0 width="100%" bgColor=#e3e3e3 border=1>
<TBODY>
<TR>
<TD width="100%"><FONT size=2>&lt;decorators defaultdir="/_decorators"&gt;<BR>&nbsp; &lt;decorator name="main" page="main.jsp"&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;pattern&gt;*&lt;/pattern&gt;<BR>&nbsp; &lt;/decorator&gt;<BR>&nbsp; &lt;decorator name="printable" page="printable.jsp" role="customer" webapp="aaa" /&gt;<BR>&lt;/decorators&gt;</FONT></TD></TR></TBODY></TABLE>
<P><FONT size=2></FONT>&nbsp;</P>
<UL>
<LI><FONT size=2>defaultdir: 包含装饰器页面的目录</FONT> 
<LI><FONT size=2>page : 页面文件名</FONT> 
<LI><FONT size=2>name : 别名</FONT> 
<LI><FONT size=2>role : 角色，用于安全</FONT> 
<LI><FONT size=2>webapp : 可以另外指定此文件存放目录</FONT> 
<LI><FONT size=2>Patterns : 匹配的路径，可以用*,那些被访问的页面需要被装饰。</FONT> </LI></UL>
<P><FONT size=2>　</FONT> </P></TD></TR>
<TR>
<TD width="100%" bgColor=#000080 height=13><B><FONT color=#ffffff size=2>最重要的是写出装饰器本身(也就是那些要复用页面，和结构页面)。</FONT></B> </TD></TR>
<TR>
<TD width="100%" height=15><FONT size=2>其实，重要的工作就是制作装饰器页面本身(也就是包含结构和规则的页面)，然后把他们描述到decorators.xml中。</FONT> 
<P><FONT size=2>让我们来先看一看最简单的用法：其实最常用也最简单的用法就是我们的hello例子，面对如此众多的技术，我想只要达到功能点到为止即可，没必要去研究太深(除非您有更深的需求)。</FONT></P>
<TABLE style="BORDER-COLLAPSE: collapse" borderColor=#000000 height=1 cellSpacing=0 cellPadding=0 width="80%" bgColor=#efefef border=1>
<TBODY>
<TR>
<TD width="100%"><PRE><FONT size=2>&lt;%@ page contentType="text/html; charset=GBK"%&gt;
&lt;%@ taglib uri="sitemesh-decorator" prefix="decorator" %&gt;

&lt;html&gt;
  &lt;head&gt;
    &lt;title&gt;&lt;decorator:title default="装饰器页面..." /&gt;&lt;/title&gt;
    &lt;decorator:head /&gt;
  &lt;/head&gt;
  &lt;body&gt;
    sitemesh的例子&lt;hr&gt;
    &lt;decorator:body /&gt;
    &lt;hr&gt;chen56@msn.com
  &lt;/body&gt;
&lt;/html&gt;
</FONT></PRE></TD></TR></TBODY></TABLE>
<P><FONT size=2>我们在装饰器页面只用了2个标签：</FONT></P>
<P><FONT size=2>&lt;decorator:title default="装饰器页面..." /&gt;&nbsp;&nbsp;&nbsp; ： 把请求的原始页面的title内容插入到&lt;title&gt;&lt;/title&gt;中间。</FONT></P>
<P><FONT size=2>&lt;decorator:body /&gt; ： 把请求的原始页面的body内的全部内容插入到相应位置。</FONT></P>
<P><FONT size=2>然后我们在decorator.xml中加入以下描述即可： </FONT></P>
<P><FONT size=2>&lt;decorator name="main" page="main.jsp"&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;pattern&gt;*&lt;/pattern&gt;<BR>&lt;/decorator&gt;<BR></FONT></P>
<P><FONT size=2>这样，请求的所有页面都会被重新处理，并按照main.jsp的格式重新展现在你面前。</FONT></P>
<P>　 </P></TD></TR>
<TR>
<TD width="100%" bgColor=#000080 height=19><B><FONT color=#ffffff size=2>让我们看看更多的用法。(抄袭sitemesh文档)</FONT></B> </TD></TR>
<TR>
<TD width="100%" height=15><FONT size=2>以下列着全部标签：</FONT> 
<TABLE width="80%" align=center>
<TBODY>
<TR>
<TD vAlign=top><STRONG><FONT size=2>Decorator Tags</FONT></STRONG></TD>
<TD vAlign=top><STRONG><FONT size=2>Page Tags</FONT></STRONG></TD></TR>
<TR>
<TD vAlign=top><FONT size=2>被用于建立装饰器页面.</FONT></TD>
<TD vAlign=top><FONT size=2>被用于从原始内容页面访问装饰器.</FONT></TD></TR>
<TR>
<TD vAlign=top><FONT size=3><A href="http://www.huihoo.com/java/sitemesh/2.html#decorator:head"><CODE>&lt;decorator:head /&gt;</CODE></A><BR><A href="http://www.huihoo.com/java/sitemesh/2.html#decorator:body"><CODE>&lt;decorator:body /&gt;</CODE></A><BR><A href="http://www.huihoo.com/java/sitemesh/2.html#decorator:title"><CODE>&lt;decorator:title /&gt;</CODE></A><BR><A href="http://www.huihoo.com/java/sitemesh/2.html#decorator:getProperty"><CODE>&lt;decorator:getProperty /&gt;</CODE></A><BR><A href="http://www.huihoo.com/java/sitemesh/2.html#decorator:usePage"><CODE>&lt;decorator:usePage /&gt;</CODE></A><BR></FONT></TD>
<TD vAlign=top><FONT size=3><A href="http://www.huihoo.com/java/sitemesh/2.html#page:applyDecorator"><CODE>&lt;page:applyDecorator /&gt;</CODE></A><BR><A href="http://www.huihoo.com/java/sitemesh/2.html#page:param"><CODE>&lt;page:param</CODE></A></FONT></TD></TR></TBODY></TABLE>　 
<P><STRONG><A name=decorator:head><FONT size=2>&lt;decorator:head /&gt;</FONT></A></STRONG></P>
<P><FONT size=2>插入原始页面(被包装页面)的head标签中的内容(不包括head标签本身)。</FONT></P><STRONG><A name=decorator:body><FONT size=2>&lt;decorator:body /&gt;</FONT></A></STRONG> 
<P><FONT size=2>插入原始页面(被包装页面)的body标签中的内容。</FONT></P>
<P><STRONG><A name=decorator:title><FONT size=2>&lt;decorator:title [ default="..." ] /&gt;</FONT></A></STRONG></P>
<P><FONT size=2>插入原始页面(被包装页面)的<A name=decorator:title>title</A>标签中的内容，还可以添加一个缺省值。</FONT></P>
<P><FONT size=2>例：</FONT></P>
<P><FONT size=2>/_decorator/main.jsp中 （装饰器页面）: &lt;title&gt;</FONT><A name=decorator:title><FONT size=2>&lt;decorator:title default="却省title-hello"&nbsp; /&gt;</FONT></A><FONT size=2><A name=decorator:title> - 附加标题</A>&lt;/title&gt;</FONT></P>
<P><FONT size=2>/aaa.jsp中 (原始页面)：&lt;title&gt;<A name=decorator:title>aaa页面</A>&lt;/title&gt;</FONT></P>
<P><FONT size=2>访问/aaa.jsp的结果：&lt;title&gt;<A name=decorator:title>aaa页面</A> <A name=decorator:title>- 附加标题</A>&lt;/title&gt;</FONT></P>
<P><STRONG><A name=decorator:getProperty><FONT size=2>&lt;decorator:getProperty property="..." [ default="..." ] [ writeEntireProperty="..." ]/&gt;</FONT></A></STRONG></P>
<P><FONT size=2>在标签处插入原始页面(被包装页面)的原有的<A name=decorator:title>标签的属性</A>中的内容，还可以添加一个缺省值。</FONT></P>
<P><FONT size=2>sitemesh文档中的例子很好理解：</FONT><BR><FONT size=2>The decorator: </FONT><FONT size=3><CODE>&lt;body bgcolor="white"&lt;decorator:getProperty property="body.onload" writeEntireProperty="true" /&gt;&gt;</CODE><BR></FONT><FONT size=2>The undecorated page: </FONT><FONT size=3><CODE>&lt;body onload="document.someform.somefield.focus();"&gt;</CODE><BR></FONT><FONT size=2>The decorated page: </FONT><FONT size=3><CODE>&lt;body bgcolor="white" onload="document.someform.somefield.focus();"&gt;</CODE></FONT></P>
<P><FONT size=2><STRONG>注意，</STRONG></FONT><CODE><FONT size=2>writeEntireProperty="true"会在插入内容前加入一个空格。</FONT></CODE></P>
<P><FONT size=2><STRONG><A name=decorator:usePage>&lt;decorator:usePage id="..." /&gt;</A></STRONG><BR>象jsp页面中的&lt;jsp:useBean&gt;标签一样，可以使用被包装为一个Page对象的页面。 (懒的用)</FONT></P>
<P><FONT size=2>例：可用<A name=decorator:usePage>&lt;decorator:usePage id="page" /&gt;<STRONG> ：</STRONG></A></FONT><A name=decorator:usePage><FONT size=3>&lt;%=</FONT></A><FONT size=3><CODE><NOBR>page.getTitle()%&gt;达到</NOBR>&lt;decorator:title/&gt;的访问结果。</CODE></FONT></P>
<P>　</P>
<P><FONT size=2><A name=page:applyDecorator><STRONG>&lt;page:applyDecorator name="..." [ page="..." title="..." ] &gt;<BR></STRONG></A><STRONG><A name=page:param>&lt;page:param name="..."&gt; ... &lt;/page:param&gt;</A></STRONG><BR><STRONG><A name=page:param>&lt;page:param name="..."&gt; ... &lt;/page:param&gt;</A><BR>&lt;/page:applyDecorator&gt;</STRONG></FONT></P>
<P><FONT size=2>应用包装器到指定的页面上，一般用于被包装页面中主动应用包装器。这个标签有点不好理解，我们来看一个例子：</FONT></P>
<P><FONT size=2>包装器页面 /_decorators/panel.jsp：&lt;p&gt;&lt;decorator:title /&gt;&lt;/p&gt;&nbsp; ... &lt;p&gt;&lt;decorator:body /&gt;&lt;/p&gt;<BR>&nbsp; 并且在decorators.xml中有&lt;decorator name="panel" page="panel.jsp"/&gt;<BR><BR>一个公共页面，即将被panel包装：/_public/date.jsp:&nbsp;&nbsp;<BR>&nbsp; ... &lt;%=new java.util.Date()%&gt;&nbsp; ...&lt;decorator:getProperty property="myEmail" /&gt;<BR><BR>被包装页面 /page.jsp ：&nbsp;<BR>&nbsp; &lt;title&gt;page的应用&lt;/title&gt;&nbsp;<BR>&nbsp; .....&nbsp;&nbsp;</FONT><A name=page:applyDecorator><FONT size=2><BR>&nbsp; &lt;page:applyDecorator name="panel" page="/_public/date.jsp" &gt;<BR>&nbsp;&nbsp;&nbsp; &lt;page:param name="myEmail"&gt; chen_p@neusoft.com &lt;/page:param&gt;<BR>&nbsp; &lt;/page:applyDecorator&gt;<STRONG><BR></STRONG></FONT></A></P>
<P><FONT size=2>最后会是什末结果呢？除了/page.jsp会被默认的包装页面包装上header,footer外，page.jsp页面中还内嵌了date.jsp页面，并且此date.jsp页面还会被panel.jsp包装为一个title加body的有2段的页面，第1段是date.jsp的title，第2段是date.jsp的body内容。</FONT></P>
<P><FONT size=2>另外，<A name=page:applyDecorator>page:applyDecorator</A>中包含的page:param标签所声明的属性值还可以在包装页面中用</FONT><A name=decorator:getProperty><FONT size=2>decorator:getProperty</FONT></A><FONT size=2>标签访问到。 </FONT></P></TD></TR></TBODY></TABLE></P></TD></TR></TBODY></TABLE><BR>
<TABLE style="BORDER-COLLAPSE: collapse" borderColor=#000000 height=1 cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD width="100%" bgColor=#000080 height=19><FONT color=#ffffff size=2>可打印的界面装饰</FONT> </TD></TR>
<TR>
<TD width="100%" height=63><FONT size=2>前面说过有1种可打印的装饰器，可以允许你当用http://localhost/aaa/a.html?printable=true方式访问时，应用其他的装饰器(自己指定)，给出原始页面以供打印(免得把header,footer等的花哨的图片也搭上)。</FONT> 
<P><FONT size=2>让我们来看一看怎样实现他：</FONT></P>
<P><FONT size=2>1.首先在WEB-INFO/sitemesh.xml中设置：<BR>&nbsp; &lt;mapper class="com.opensymphony.module.sitemesh.mapper.PrintableDecoratorMapper"&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;param name="decorator" value="printable" /&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;param name="parameter.name" value="printable" /&gt;<BR>&nbsp;&nbsp;&nbsp; &lt;param name="parameter.value" value="true" /&gt;<BR>&nbsp; &lt;/mapper&gt;<BR>这样就可以通过?printable=true来使用名为printable的装饰器，而不是用原来的装饰器。</FONT></P>
<P><FONT size=2>2.在WEB-INFO/decorators.xml中定义相应的printable装饰器<BR>&nbsp; &lt;decorator name="printable" page="printable.jsp"/&gt;</FONT></P>
<P><FONT size=2>3.最后编写printable装饰器/decorators/printable.jsp </FONT></P>
<TABLE style="BORDER-COLLAPSE: collapse" borderColor=#000000 height=1 cellSpacing=0 cellPadding=0 width="80%" bgColor=#efefef border=1>
<TBODY>
<TR>
<TD width="100%" bgColor=#efefef><FONT size=2>&lt;%@ taglib uri="sitemesh-decorator" prefix="decorator" %&gt;<BR>&lt;html&gt;<BR>&lt;head&gt;<BR>&nbsp; &lt;title&gt;&lt;decorator:title /&gt;&lt;/title&gt;<BR>&nbsp; &lt;decorator:head /&gt;<BR>&lt;/head&gt;<BR>&lt;body&gt;<BR><BR>&nbsp; &lt;h1&gt;&lt;decorator:title /&gt;&lt;/h1&gt;<BR>&nbsp; &lt;p align="right"&gt;&lt;i&gt;(printable version)&lt;/i&gt;&lt;/p&gt;<BR><BR>&nbsp; &lt;decorator:body /&gt;<BR><BR>&lt;/body&gt;<BR>&lt;/html&gt;</FONT></TD></TR></TBODY></TABLE>
<P><FONT size=2>这样就可以让一个原始页面通过?printable=true开关来切换不同的装饰器页面。</FONT></P>
<P>　 </P></TD></TR></TBODY></TABLE>
<TABLE style="BORDER-COLLAPSE: collapse" borderColor=#000000 height=1 cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD width="100%" bgColor=#000080 height=19><FONT color=#ffffff size=2>中文问题</FONT></TD></TR>
<TR>
<TD width="100%" height=63><FONT size=2>由于sitemesh内部所使用的缺省字符集为iso-8859-1，直接使用会产生乱码，我们可以通过以下方法纠正之：</FONT> 
<UL>
<LI><FONT size=2>方法1：可以在您所用的application server的配置文件中找一找，有没有设置encoding或<CODE></CODE></FONT><CODE><FONT size=2>charset的项目，然后设成gbk或gb2312即可</FONT></CODE> 
<LI><CODE><FONT size=2>方法2：这也是我们一直使用的方法。<BR>1.在每一个jsp页里设置: &lt;%@ page contentType="text/html; charset=gbk"%&gt; 来告诉server你所要求的字符集。<BR>2.在每个jsp页的head中定义：&lt;META HTTP-EQUIV="content-type" CONTENT="text/html; charset=gbk"&gt; 来告诉浏览器你所用的字符集。</FONT></CODE> </LI></UL></TD></TR></TBODY></TABLE>
<TABLE style="BORDER-COLLAPSE: collapse" borderColor=#000000 height=1 cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD width="100%" bgColor=#000080 height=19><FONT color=#ffffff size=2>总结：使用sitemesh最通常的途径：</FONT></TD></TR>
<TR>
<TD width="100%" height=15>
<P><FONT size=2>1.配置好环境，</FONT> 
<P><FONT size=2>2.在WEB-INFO/decroators.xml中描述你将建立的包装器。</FONT> 
<P><FONT size=2>3.开发在decroators.xml中描述的包装器，最好存放在/_decorators目录下</FONT></P>
<P><FONT size=2>4.ok ，可以看看辛勤的成果了 :)</FONT></P></TD></TR></TBODY></TABLE>
<HR color=#ff0000 SIZE=1>

<P><FONT color=#ff0000 size=2><B>资源：</B></FONT></P>
<UL>
<LI><FONT size=2><A href="http://www.huihoo.com/java/sitemesh/@test/sitemesh-cvs-2003-08-13_test-2003-08-21.zip">下载我提供的ant build的例子&nbsp;</A>&nbsp;<BR>我在j2sdk-1_4_0,tomcat4.0.3和tomcat5下测试通过，请先运行/build.bat,以生成/dist/web.war文件，然后实施到你的服务器即可。<BR></FONT>
<LI><FONT size=2>opensymphony团队的项目集合：<BR><A href="http://sourceforge.net/projects/opensymphony">http://sourceforge.net/projects/opensymphony</A> <BR></FONT>
<LI><SPAN class=nav><A href="http://wiki.opensymphony.com/"><FONT size=2>Wiki ：http://wiki.opensymphony.com/<BR></FONT></A></SPAN>
<LI><SPAN class=nav><A href="http://sourceforge.net/cvs/?group_id=9890"><FONT size=2>在sf的cvs上下载最新版本CVS</FONT></A></SPAN> </LI></UL>
<P class=MsoNormal><B><FONT color=#ff0000 size=2>关于作者：</FONT></B></P>
<P class=MsoNormal><FONT size=2>陈鹏，西安东软公司。作为一名狂热的程序员希望每一天都能成长进步，并希望与大家分享快乐和知识。<BR>请用以下方式和他联系：email <A href="mailto:chen56@msn.com">chen56@msn.com</A> &nbsp;</FONT></P><img src ="http://www.blogjava.net/kapok/aggbug/4062.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-05-07 14:58 <a href="http://www.blogjava.net/kapok/archive/2005/05/07/4062.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>标题：谁动了我的EJB？---J2EE 开发应用框架反思 </title><link>http://www.blogjava.net/kapok/archive/2005/05/06/4045.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Thu, 05 May 2005 16:46:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/05/06/4045.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/4045.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/05/06/4045.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/4045.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/4045.html</trackback:ping><description><![CDATA[<P><A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15">http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15</A><BR>作者：zhoubikui(dev2dev ID) <BR><STRONG>摘要：<BR></STRONG><BR>　　希望本文能引起各位J2EE开发同行的激烈争论。EJB是J2EE的全部吗？被偷走的EJB该如何回来？本文结合自己最近在WEBLOGIC的开发的J2EE项目的得与失和最新的J2EE发展技术尝试一种新的J2EE解决方案。最后用DEMO小试牛刀，希望能得到答案和启迪。<A id=0 name=0></A><BR><BR>目录： <BR><A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#1">一、背景：</A><BR><A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#2">二、开发环境</A><BR><A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#3">三、系统开发框架</A><BR>　<A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#3_1">3.1该开发模型的特点</A><BR>　<A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#3_2">3.2 层与层之间数据交互XML Data Bus</A><BR>　<A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#3_3">3.3 数据持久层设计</A><BR><A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#4">四、反思</A><BR>　<A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#4_1">4.1该模式的优点</A><BR>　<A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#4_2">4.2借鉴</A><BR>　<A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#4_3">4.3反思</A><BR><A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#5">五、较理想的J2EE解决方案</A><BR>　<A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#5_1">5.1 J2EE分层</A><BR>　<A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#5_2">5.2设计目标</A><BR>　<A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#5_3">5.3 J2EE数据持久层开发</A><BR>　<A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#5_4">5.4业务逻辑层的设计</A><BR>　<A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#5_5">5.5 业务层和数据持久层数据总线</A><BR><A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#6">六、实践</A><BR>　<A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#6_1">6.1开发思路</A><BR>　<A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#6_2">6.2系统设计</A><BR>　　<A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#6_2_1">6.2.1总体设计</A><BR>　　<A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#6_2_2">6.2.2 数据持久层设计</A><BR>　　<A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#6_2_3">6.2.3 业务逻辑层设计</A><BR>　　<A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#6_2_4">6.2.4 WEB层设计</A><BR>　　<A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#6_2_5">6.2.5 总 结</A><BR>　<A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#6_3">6.3源码下载</A><BR><A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#7">七、BEA能为我们做更多</A><BR><A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#8">八、总结</A><BR><A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#9">相关资源下载</A><BR><A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#10">参考资料</A><BR><A id=1 name=1></A></P>
<P><B>一、背景：</B><A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#0">(目录)</A><BR>　　J2EE是开发企业很好的平台。它内涵非常丰富，使用人群也非常庞大，有很多最佳实践经验和优秀工具。事实证明,J2EE确实可以为复杂的企业应用提供强大的技术保障。但由于它过于复杂，开发人员缺乏足够技巧和开发经验，往往也是导致J2EE项目落马的原因。 <BR>　　这个题目可能会让您觉得有些哗众起宠的味道，但这个在J2EE潜水的小虾米如果能让您对目前的开发哪怕有一点小的触动就很满足了。我写这篇文章时已经穿好了防弹衣，期待着您的西红柿和鲜花! <BR>　　看了一些论坛里的帖子，让我很疑惑的是为什么EJB看来是如此的成功?但我却发现在 本公司和其它竞争对手以及我兄弟们中的牛公司中使用EJB的是少之又少。<BR>疑惑!谁动了我的EJB？ <BR>　　Bea的 WebLogic是如此的出色，以至于WebLogic 8出来后 我们这些日日夜夜的不停推磨的驴子简直都可以洗洗睡了。 <BR>　　但具有讽刺意味的是倘若你问我们为什么要买WebLogic，因为sun的 iplanet不停的挂掉，我们终于受不了，也不敢再相信sun one server。选择WebLogic因为我们需要一个可靠的web server支持jsp罢了。 <BR>　　首先说明我不是EJB的反对者，我依然希望EJB能在数据持久层开发中发挥重要作用。我将在正文中讲我的想法，我想BEA的WebLogic可以带给我们更多好的东西。<A id=2 name=2></A><BR><BR><B>二、开发环境 </B><B></B><A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#0">(目录)</A><BR>　　&gt; 操作系统：WINDOWS 2000<BR>　　&gt; J2EE应用程序服务器：BEA WEBLOGIC SERVER 6.1<BR>　　&gt; 开发工具：EOS studio33 （其实就是对WEBLOGIC studio的改造加入了一些财政开发常用的库让业务流程能象工作流的开发），DEMO例子都是在eclipse下开发的与这开发工具无关。<BR>　　&gt; 数据库：ORACLE 9I<A id=3 name=3></A><BR><BR><B>三、系统开发框架</B><B></B><A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#0">(目录)</A><BR>　　下面是我们公司和XXX一块合作开发的一套国库县乡系统，为了提高开发效率我们对WebLogic 6 的STUDIO开发工具作了一些改造，加入了我们的构件库。使业务能图形化开发的尝试，有点类似现在WebLogic 8的工作流的开发。这是一次不太成功的试验，但确实能带给我们一些新的东西。由于大多数用户没有相同的开发环境，我不打算对技术细节做太多描述，只写对现在J2EE开发框架有冲击的东东。 <BR>　　我们将企业系统可以划分为页面层、业务流程层、服务层、对象层和数据层五个层次。从实现各层的技术标准看，页面层技术有JSP及portal等，业务流程技术有WfMC及BPML等，服务层技术有EJB及Web Services、对象技术有JDBC、XML，数据技术有各类数据库产品，如：DB2、Oracle、SQL Server，以及SQL92等相关标准。</P>
<P align=center><IMG height=263 src="http://dev2dev.bea.com.cn/images/image2004111679.gif" width=572><BR>图一</P>
<P><BR>　　该系统就是在这些技术标准基础上，对各技术层次上的业务内容实现了面向构件的封装。构件的最终实现技术仍为各层次的标准技术。将页面层业务内容封装为页面构件及页面模版构件，将流程业务内容封装为工作流程图和页面流程构件，将服务业务内容封装为业务逻辑构件；将对象封装为运算逻辑等基础构件，将数据封装为数据构件。<A id=3_1 name=3_1></A><BR><BR><B>3.1该开发模型的特点</B><B></B><A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#0">(目录)</A><BR>　　（1） 从系统框架层次看，它是一个标准的MVC系统框架，是当前主流的、公认的系统框架。 <BR>　　（2） 在应用系统中，业务构件在运行时的表现为标准的EJB。在运行系统中，构件接口形式、互操作形式完全使用EJB标准。 <BR>　　（3） 基础构件都是完全符合J2EE技术标准的，包括JDBC、JMS、JSP、JNDI技术等。这些技术标准的采用使开发的应用系统成为完全符合J2EE标准的应用系统。 <BR>　　（4） 在系统配置、数据接口、内存数据管理各方面采用了XML技术标准。由于XML具有良好的自描述性和顺序无关性，利用XML标准进行数据交换、内存数据管理使系统具有更加良好的可扩展性和伸缩性。基于该开发的应用系统是一个符合XML标准的应用系统。 <BR>　　在面向构件和可视化的构件组装过程中，同样采用了XML的定义格式，使构件的组装基于XML而非代码层定义，并在应用装载时由运行支撑环境解释为标准的J2EE。这即保证了此应用系统自身的简洁性，又保持了应用系统的标准性。 <A id=3_2 name=3_2></A><BR><BR><B>3.2 层与层之间数据交互XML Data Bus</B><A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#0">(目录)</A><BR>　　层间上下可互访，但不能越过相邻层通讯。层间数据交互用XML作为交互载体。见下图。</P>
<P align=center><IMG height=179 src="http://dev2dev.bea.com.cn/images/image2004111680.gif" width=637> <BR>图二<A id=3_3 name=3_3></A></P>
<P><B>3.3 数据持久层设计</B><A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#0">(目录)</A><BR>　　普通的J2EE平台都不提供相关的建模与数据管理工具，而采用专用工具（如：PowerDesigner 或 Rational Rose等），这增加了多个工具间的切换和使用复杂度；另外，由于数据模型工具对是多个、异构数据库系统支持的简单性，使程序员常常需要在这些工具之外进行许多的代码编写工作。 <BR>　　它提供了专用数据模型工具（Database Designer Tools）对应用模型设计、模型管理、数据结构生成进行一体化的处理。同时，由于其工具内部内置了数据映射功能，使数据结构面对多样、异构的数据库（如:Oracle、DB2、MSSQL等）时，可以以统一的调用和对前台无影响的系统数据移植方式为前台应用提供服务。</P>
<P align=center><IMG height=292 src="http://dev2dev.bea.com.cn/images/image2004111681.gif" width=496><BR>图三<A id=4 name=4></A></P>
<P><B>四、反思</B><A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#0">(目录)</A><BR>　　看了这么多花花绿绿的图片和理论，可能把您的头都搞大了。下面汇总一下前面的内容。<A id=4_1 name=4_1></A><BR><BR><B>4.1该模式的优点</B><A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#0">(目录)</A><BR>&gt; 严格分层，为了实现层的修改不会影响相关层的。系统采用了显示层，展现层、业务流程层、服务层、对象层和数据层六层结构。 <BR>&gt; 层与层之间数据交互采用XML Data Bus，这样基本上实现层中修改不会影响上下两层，最多是利用映射，服务器重起一下。 <BR>&gt; 这种模块化有利于分层开发相对的控件和图形化开发工具。 <BR>&gt; 最令人心动的是专用数据模型工具（Database Designer Tools），只需配一下数据库连接就可以实现得到表的增删改查。 <BR>&gt; 最让人感动的是处处用到EJB技术，可使用者从来没感觉到使用了某种特定技术，只是简单调用数据持久层的增删改查方法就OK了！<A></A><A id=4_2 name=4_2></A><BR><BR><B>4.2借鉴</B><A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#0">(目录)</A><BR>　　多么美的事情，我当初看到真是一夜未眠，最终也为后来项目不太成功打下伏笔。现在分析起来主要是： <BR>　　XML Data Bus设计有问题，由于XML在HTTP中传数据有自身的弱点，如XML会把XML空格结点去掉，结果页面有个TEXT显示数据库某个字段aa的值test,我想把它赋空，在页面到展现层时XML自动忽略这个结点，最后数据库更新可想而知，我依然是我。 <BR>　　开发工具不稳定，某些技术不太成熟，也没有较好的编辑工具，修改一个小东西，简直有点大海捞针的味道非常考验人的耐性，开发前期很顺，到了后期，服务器无名火到处发，现象重现率也不高，系统正常都不知怎么正常的，维护简直是噩梦。 <BR>　　简单是我们程序开发中一个很重要的原则，简单会减少我们出错的概率，而且即使出现错误也能很快纠正。<A id=4_3 name=4_3></A><BR><BR><B>4.3反思</B><A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#0">(目录)</A><BR>　　虽然有这样或那样的失败原因，但凭心而论，这套J2EE解决方案确实还是有其独特的魅力。 <BR>　　我的体会主要有这样的几点：<BR>&gt; 　J2EE开发肯定需要分层，但不宜分层太多。MVC的方式比较好。从开发和维护角度考虑，分四层结构比较合适。</P>
<P align=center><IMG height=287 src="http://dev2dev.bea.com.cn/images/image2004111682.gif" width=560><BR>图四</P>
<P>&gt;　数据持久层好写但不容易写好，这是系统最基础也是最核心的部件，一个程序的好坏很大关系是它决定的。EJB想说爱你真的不容易，O/R面向对象的数据库编程对一些简单的业务操作能提升很大的开发效率，可惜我们的数据库很复杂，简单说说我们的权限判断，严肃到数据库表的哪个字段用户当前职责是可读还是可写，而且这些关系大量SQL执行找到最接近的数据访问权限，我想目前除了IBATIS勉强满足外，Hibernate敢都不敢想。非常希望BEA的WEBLOGIC能开发类似EOS的专用数据模型工具（Database Designer Tools），不要让我知道我在用什么技术调用数据库，我只用告诉它需要对数据库做什么操作就行，最好能像MS的工具箱一样，给个控件告诉它对应什么表，该做什么操作，以及该操作需要什么前提条件。条件只是按它映射命名的对象就可以自动组合合适的SQL或采用相应的EJB操作。我们真得不想关心，你是用JDBC还是EJB实现的，我们只需要最佳的效果，这个WEBLGIC完全可能根据当前SERVER容器的支持环境做出。 <BR>&gt;　层与层间数据传递用VO对象，也可用串行化实现。 <BR>&gt;　MVC中M的开发业务层和数据层组织形式采用DAO模式比较好。事务放在业务层，采用J2EE容器的事务声明方式。<BR>　　可能这些想法并没太多新鲜的成分，但我需要强调的是数据持久层的设计应该有构件化的产品，开发人员不需要了解如何得到是通过EJB或是JDBC从数据库中得到数据，WEB SERVER容器中提供什么服务，它可以自动选择方式，当然也可手工设置。 <BR>　　这个问题是可以得到很好解决的，注意我DAO的设计思想，数据持久层不需要考虑事务等一些复杂的问题，它只需简单对表做增删改查的工作。<BR><BR><B>开发人员需要做的是二件事</B><BR>&gt;　开发人员配置数据库连接，工具能自动建立表与数据持久层对象的映射<BR>&gt;　开发人员利用构件化的工具包（属性包含数据持久层对象名和调用条件（VO对象），方法有增删改查）简单配置，然后装配到业务层就能实现业务逻辑的演练。 <BR>　　这个开发需要注意它是个独立的层，不会依赖某一数据库或WEB SERVER容器，是个通用的数据持久层的解决方案。我相信BEA公司会把这种设想做得更好！ <BR>　　开发人员需要数据持久层开发效率和性能，我们再使用EJB，但不需要过多陷于EJB中。<A id=5 name=5></A><BR><BR><B>五、较理想的J2EE解决方案</B><B></B><A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#0">(目录)</A><A id=5_1 name=5_1></A><BR><B>5.1 J2EE分层</B><A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#0">(目录)<BR></A>　　涉及到的java类可以分为这么几种： <BR>&gt;　VO/command类。<BR>　　用于各层之间传输数据用。只有set,get方法。值对象尽量要通用性强。属性方法多。 <BR>&gt;　controller类<BR>　　用于处理用户提交请求。建议将多个有紧密关系的功能点放在一个类里。如对计划的浏览，提交，修改，就可以放在一个类里。Controller类负责前台的，不重要的数据校验。并调用façade类完成业务。 <BR>&gt;　façade类<BR>　　管理类或者门面类。一个模块只能有一个这样的类，所有的controller类都调用该类，而不直接调用DAO类。该类里面起事务处理，本身没有功能，调用DAO类完成功能。 <BR>&gt;　DAO类<BR>　　负责具体的数据库存取操作。原则上一个表对应一个DAO类，也可以按对象来建立类。<BR>　　这个MVC开发方式本身可能没有太多新鲜的地方，其相同点在这里就不说了，本文具体就怎样构件M？也就是业务逻辑层和数据持久层的架构。<A id=5_2 name=5_2></A><BR><BR><B>5.2设计目标</B><A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#0">(目录)</A><BR><B>　　充分利用OOP设计的优势<BR>　　避免了不必要的复杂<BR>　　具有可维护性、可扩展性<BR>　　避免任何特定平台或非标准化</B> <BR>　　<B>不重新开发已有的东西</B>目前Spring的Ioc模式实现的BeanFactory和AOP，编制程序时，只要写被调用者的接口代码，具体子类实例可通过配置实现，它不依赖特定平台，你可一全部用它的框架，也可以只用其中一部分。 <BR>　　这种新的架构出现，使我觉得以下情况实现成为可能！ <BR>　　<B>数据持久层设计图形化构件化开发能成为可能，它只管和数据库数据交互更新，不要担当太多的责任。</B>而业务逻辑层自我定制构件将会变得非常简单，而且借鉴J2EE的EJB事务容器申明也得到解决。业务逻辑层实现有点象小时侯搭积木一样轻松的把<B>数据持久层构件</B>组合就能实现。下面将就<B>数据持久层构件化和业务逻辑层</B>分别论述。<A id=5_3 name=5_3></A><BR><BR><B>5.3 J2EE数据持久层开发</B><A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#0">(目录)</A><BR>　　<B>数据持久层设计构件化开发可能？</B> <BR>　　数据持久层设计目标： <BR>　　操作简单，我们希望只需将数据连接配置好，然后我们将构件与数据库具体表对应上，接下来告示它操作类型，传入条件参数（VO类）后，余下的任务全有他完成。<B>数据持久层只管和数据库数据交互更新，本身只对表的增删改查的动作，非常简洁，硬编码也不会太复杂。</B> <BR>　　调用只需要利用Spring的BeanFactory简单配置，就可在业务处理层直接调用。这个过程只是简单改写一下配置文件，其图形化，非常容易实现。 <BR>　　测试，我们在数据持久层用的是接口编程模式，利用JUIT或Spring本身资源读取API很容易写测试方法。 <BR>　　维护和修改很方便。由于是具体子类实例可通过配置实现，非常方便修改和扩展。 <BR>　　目前有一些类似产品，可惜都是依赖某一特定平台或技术，你要么全部按他的规范做，要么全部放弃。它使你在某一方面方便的却在另外方面限制你，让你没法施展拳脚。 <BR>　　当前比较恰当的组合Spring的BeanFactory+IBATIS，能较好满足轻量级系统的开发。我希望BEA公司的WEBLOCIC能在这方面为我们提供精彩的解决方案。 <BR>　　借鉴Spring的BeanFactory和AOP技术和BEA在EJB的造诣，BEA完全可能把它做得更好！而且基于以上分析，技术难度上不会太大。<B>我们有理由期待数据持久层设计构件化图形化的到来</B>。我觉得它的出现应该除了前面的特点，还应有下面的特点： <BR>　　开发者，不需要知道数据持久层是用EJB或是JDBC实现的具体细节。该构件能自动根据服务容器的支持，选择是EJB或是JDBC实现。 <BR>　　EJB本身没有错，错在它能力太多，让使用者无法驾御。我们开发人员关注的是<B>数据持久层的本身功用和性能</B>，我们不管你是用EJB或是JDBC实现的。我们需要简单持久<B>性能优良的解决方案</B>。请不要让EJB做太多的实质是数据持久层不要做太多。<A id=5_4 name=5_4></A><BR><BR><B>5.4业务逻辑层的设计</B><A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#0">(目录)</A><BR>　　面向接口的开发，它具有很强的灵活度和扩展性。 <BR>　　业务逻辑层调用也用Spring的BeanFactory组合，这样在<B>数据持久层设计构件化</B>的同时，我们业务逻辑层自我定制构件将会变得非常简单。 <BR>　　我们将<B>数据持久层中的事务处理提到</B>业务逻辑层的理由是不用多说的。我们只需简单对SPRING的bean管理的配置文件增加拦截代理对象，为了给业务逻辑对象增加事务处理。其简单声明如下：<BR><BR><FONT color=#009933>&lt;!-- Transaction manager for a single JDBC DataSource --&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp; &lt;bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;property name="dataSource"&gt;&lt;ref local="dataSource"/&gt;&lt;/property&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp; &lt;/bean&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp; &lt;bean id="systemManager" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;property name="transactionManager"&gt;&lt;ref local="transactionManager"/&gt;&lt;/property&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;property name="target"&gt;&lt;ref bean="业务逻辑"/&gt;&lt;/property&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;property name="transactionAttributes"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;props&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;prop key="query*"&gt;PROPAGATION_SUPPORTS,readOnly&lt;/prop&gt;</SPAN><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;prop key="*"&gt;PROPAGATION_REQUIRED&lt;/prop&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/props&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/property&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp; &lt;/bean&gt;</FONT></P>
<P>　　transactionAttributes属性可以设置事务处理的方式，事务隔离级别，是否只读三个属性，用逗号隔开。事务隔离级别各数据库系统不完全支持，一般不设置，用默认的即可。 <BR>　　事务处理选项有如下几个：（前面2个常用） <BR>　　PROPAGATION_REQUIRED － 需要事务处理。如果当前不存在事务环境，则创建一个； <BR>　　PROPAGATION_SUPPORTS － 如果当前存在事务环境，则作为其中的一部分。如果不存在，则按非事务方式执行； <BR>　　PROPAGATION_REQUIRES_NEW － 需要事务处理。并总是开启一个新事务。如果已经存在事务环境，则挂起之； <BR>　　PROPAGATION_MANDATORY － 执行到指定方法时，必须已经存在事务环境，否则出错； <BR>　　PROPAGATION_NEVER － 不支持事务操作，如果存在事务环境会出错； <BR>　　PROPAGATION_NOT_SUPPORTED － 不支持事务操作。如果存在事务，则挂起。　　如果要追求好的性能，我们可以在此定义事务精确到到某一方法的某一异常起事务回滚。 <BR>　　WEBLOGIC SERVER可以协调涉及多个资源的事务时，JTA的许多高级事务协调特性才能发挥。对一个跨数据库资源的事务，将采用两个阶段提交（Two-Phase Common,2PC）技术处理。实际上对用户而言2PC技术是透明的。在应用JTA驱动程序时，需要连接多个资源，指定针对这些资源的操作等。只要XA兼容，WEBLOGIC就会把这些资源集成到一个事务中，使所有的资源相互协调一致工作，或者都成功或者都失败。TXDATASOURCE建立与多个XA兼容资源之间的桥实现。JAT负责处理JTA事务与底层资源的相互协调。<BR><BR>　　要实现这种功能只需将&lt;bean id= transactionManager &gt;换成如下写法就可：<BR><BR><FONT color=#009933>&lt;bean id=” transactionManager”class=”org.springframework.transaction.jta.JtaTransactionManager”/&gt;</FONT><A id=5_5 name=5_5></A><BR><BR><B>5.5 业务层和数据持久层数据总线</B><A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#0">(目录)</A><BR>　　业务层和数据持久层交互为何采用VO对象而不是XML或SQL字符串作为传递参数？ <BR>　　①前者分层明晰，后者把SQL生成放在业务层，在业务类中硬编码SQL，导致代码难于维护和扩展SQL代码到处出现在你的类代码中。这样的好处是写代码效率很高，对于小型应用程序或者原型，这样是可行的。缺点是直接耦合了你的业务类与关系数据库结构。业务层不应该做数据层的工作。而且业务层或数据库字段变动，会引起两层修改。维护不方便，也不利于数据持久层的接口抽象。 <BR>　　②前者更加符合面向对象的开发思想。面向对象应用程序可以使用关系数据库作为持久机制而不需要在程序中使用嵌入SQL。后者没有仔细考虑对持久机制维护和管理的应用程序带来的继承脆弱性。 <BR>　　③前者增加VO对象时，业务和数据持久层代码几乎不需任何修改。可理解性和兼容性都比后者好。<A id=6 name=6></A><BR><BR><B>六、实践</B><A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#0">(目录)</A><BR>　　前面说了不少理论的东东，大家可能都有点厌倦了，下面通过一个实例解释一下。限于篇幅，我将项目中常用的分页实现作为例子给大家说说。由于目前数据持久层还没有比较好的构件化开发工具，现用IBATIS代替。大部分内容都不会讲太细的细节，因为我假定读者有SPRING和IBATIS的基础知识。限于篇幅，我们重点放在业务逻辑和数据持久层的实现上，详细内容见源码，有比较详细的解释。<A id=6_1 name=6_1></A><BR><BR><B>6.1开发思路</B><A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#0">(目录)</A><BR>　　分页逻辑：页面需要保存以下参数： <BR>　　总行数：根据sql语句得到总行数　　　每页显示行数：设定值pageSize　　　当前页数：请求参数page <BR>　　页面根据当前页数和每页行数计算出当前页第一行行数，定位结果集到此行，对结果集取出每页显示行数的行即可。 <BR>　　在SESSION存放数据列表是这么考虑的：对于大量数据，比如10000条，肯定不能一次全取出来。通常是每次取一页的数据，如20条，其实效率也不高。 最好是每次取出来(比如)10页的数据，放在session里。这样，翻10页才会查一次数据库。</P>
<P align=center><IMG height=493 src="http://dev2dev.bea.com.cn/images/image2004111684.gif" width=712><BR>图五<A id=6_2 name=6_2></A></P>
<P><B>6.2系统设计</B><A id=6_2_1 name=6_2_1></A><A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#0">(目录)</A><BR><B>6.2.1总体设计</B><A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#0">(目录)</A><BR>　　后台数据持久层，采用DAO模式；中间逻辑层，采用Facade模式；前端Web层，采用MVC结构，使用JSP作为视图。其组装详见下图：</P>
<P align=center><IMG height=546 src="http://dev2dev.bea.com.cn/images/image2004111685.gif" width=556><BR>图六</P>
<P>　　利用Spring的Ioc模式实现的BeanFactory非常方便将以上组装实现。<A id=6_2_2 name=6_2_2></A><BR><BR><B>6.2.2 数据持久层设计</B><A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#0">(目录)</A><BR>　　数据持久层与数据库联系非常简单，在SPRING的配置文件中配置如下两步就可。 <BR>　　第一步：设置iBATIS <BR><BR><FONT color=#009933>&lt;bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;property name="configLocation"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;value&gt;WEB-INF/sqlMap-config.xml&lt;!-- iBATIS配置文件--&gt;&lt;/value&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/property&gt;<BR>&lt;/bean&gt;&nbsp;</FONT><BR><BR>　　第二步：数据访问层(DAO)对象与数据库配置和iBATIS配置联系<BR><BR><FONT color=#009933>&lt;bean id="userDao" class="cn.com.mofit.demo.system.dao.SqlMapUserDAO"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;property name="dataSource"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;ref local="dataSource"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/property&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;property name="sqlMapClient"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;ref local="sqlMapClient"/&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/property&gt;<BR>&lt;/bean&gt;</FONT><BR><BR>　　接下来业务逻辑就可使用这些数据持久层的方法了。 <BR>　　你看不是很方便，开发者在代码编写中从来没感觉到用到Spring什么东东，而且简化IBATIS操作数据库的步骤。 <BR>　　数据持久层的开发任务是将业务层传过来VO值对象转化为数据库中对满足条件的特定操作。 <BR>　　IBATIS引入了动态映射机制，根据配置文件中设定的SQL动态生成规则，创建相应的SQL语句。通过<DYNAMIC prepend=""><ISPROPERTYAVAILABLE prepend="" property=""><BR><ISNOTNULL prepend=" " property="">等一些IBATIS的SQL动态生成规则组合很方便的实现VO值对象到数据库操作映射。<A id=6_2_3 name=6_2_3></A><BR><BR><B>6.2.3 业务逻辑层设计</B><A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#0">(目录)</A><BR>　　该类是系统管理模块的facede类。所有对系统管理模块的业务操作都应当通过该类完成。 <BR>　　该类会通过配置文件为所有方法增加事务处理。 <BR>　　建议类里面的方法都加上如下前缀，以方便用指定的命名规则设置事务处理级别： <BR>　　queryXXX() － 不需要，或者只读事务<BR>　　addXXX() － 需要事务处理<BR>　　removeXXX() － 需要事务处理<BR>　　updateXXX() － 需要事务处理<BR>　　XXXX() － 其他方法，需要事务处理 <BR>　　这样，我在TransactionProxyFactoryBean的配置里就可以这样设置：<BR><property name="transactionAttributes"><BR><PROPS></PROPS></property><FONT color=#009933>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;property name="transactionAttributes"&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;props&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;prop key="query"&gt;PROPAGATION_SUPPORTS&lt;/prop&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;prop key=""&gt;PROPAGATION_REQUIRED&lt;/prop&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/props&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/property&gt;</FONT></P>
<P>　　由于该类的特殊性和事务处理，建议类里面不要有无谓的辅助方法。 <BR>　　我们在CONTROL层引用的业务层实质是业务逻辑层加上事务后的业务逻辑接口。<BR><BR><B>6.2.4 WEB层设计</B><A id=6_2_4 name=6_2_4></A><A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#0">(目录)</A><BR>　　Controller类负责前台的，不重要的数据校验。并调用façade类完成业务。非常干净利落。</P>
<P align=center><IMG height=117 src="http://dev2dev.bea.com.cn/images/image2004111686.gif" width=398><BR>图七</P>
<P>　　对应的SPRING配置文件<BR><BR><FONT color=#009933>&lt;!-- 用户登录处理类 --&gt;<BR>&lt;bean id="do_login" class="cn.com.mofit.demo.system.web.UserLogAction"&gt;<BR>&lt;property name="formView"&gt;&lt;value&gt;login&lt;/value&gt;&lt;/property&gt;<BR>&lt;property name="successView"&gt;&lt;value&gt;main&lt;/value&gt;&lt;/property&gt;<BR>&lt;property name="systemManager"&gt;&lt;ref bean="systemManager"/&gt;&lt;/property&gt;<BR>&lt;/bean&gt;</FONT></P>
<P><FONT color=#009933>&nbsp;&nbsp;&nbsp; &lt;!-- 用户列表浏览类 --&gt;<BR>&lt;bean id="do_browse" class="cn.com.mofit.demo.system.web.browseUserAction"&gt;<BR>&lt;property name="systemManager"&gt;&lt;ref bean="systemManager"/&gt;&lt;/property&gt;<BR>&lt;/bean&gt;</SPAN></FONT><BR><BR>　　非常轻松的将加上事务后的业务逻辑接口引到控制层，页面流配置也类似STRUTS。<BR>UserLogAction继承SimpleFormController，该子类非常适合处理表单提交事件。 它的处理流程是这样的： <BR>　　get请求来到时，这样处理： <BR>　　1) 请求传递给一个controller对象 <BR>　　2) 调用formBackingObject()方法，创建一个command对象的实例。 <BR>　　3) 调用initBinder()，注册需要的类型转换器 <BR>　　4) 调用showForm()方法，返回准备呈现给用户的视图 <BR>　　5) 调用referenceData()方法，准备给用户显示相关的数据。如用户登录需要选择的年度信息 <BR>　　6) 返回formView指定的视图 <BR>　　post请求来到时，这样处理： <BR>　　1) 调用formBackingObject()方法，创建一个command对象的实例。 <BR>　　2) 将请求传来的参数写入command对象 <BR>　　3) 如果设置为要求验证，则调用validator类进行数据验证 <BR>　　4) 调用onBindAndValidate()方法，该方法允许自定义数据绑定和校验处理 <BR>　　5) 调用onSubmit()方法，进行业务逻辑处理 <BR>　　结合我们的设计我们可以用command对象，直接将页面数据通过控制对象传给业务逻辑处理，当中都不需要类型转换，非常方便。是不是很合开发者的胃口。<BR><BR><B>6.2.5 总 结</B> <A id=6_2_5 name=6_2_5></A><A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#0">(目录)</A><BR>　　这个事例非常简单，但还是能说明Spring的Ioc模式实现的BeanFactory和AOP确实能给我们开发带来极大方便。我想如果我们有工具将BeanFactory的配置文件生成图形化的话，将会极大提高我们的开发效率。 <BR>　　以前构建一个持久层是惊人的困难，现在我们想一下结合iBATIS数据持久层的开发，我们将数据持久层构件化是否是个非常简单的事，现在需要做的仅仅是IBATIS的sqlMap文件生成的图形化工具，然后是几个摸版化增删改查的方法。业务逻辑层对它们的装配利用Spring的BeanFactory也能轻松实现。 <BR>　　这是个轻量级的J2EE开发的图形化构件化的解决方案，那么我们EJB老大哥呢？ <BR>　　我想如果单一抛开EJB那么多功能不说，我想就EJB专注做好数据持久层设计构件化这个工作，它有许多优势的。现在已经有人在这方面做了比较大的突破。 <BR>　　请不要让我们一起重复数据持久层开发的辛劳，我们不管你用什么技术，我们只是需要较佳从数据库中交互数据的构件，让我们在这方面学学MS，而且我们能比它做得更好！ <BR>　　我们需要数据持久层构件化，结合Spring的Ioc模式实现的BeanFactory原理，简单的让业务逻辑组装，要是这两者都有方便的可视化工具。我想下次开发我们仅仅需要动一下鼠标，拖拉几个控件，一切搞定。而这离我们到底有多远，我觉得就差最后的一步！<A id=6_3 name=6_3></A><BR><BR><B>6.3源码下载</B><A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#0">(目录)</A><BR>　　本程序在JDK4.1,TOMCAT 5.0或WebLogic 8.1上测试通过。代码有详细注释，大家看过就能上手。<BR><BR>　　<A href="http://dev2dev.bea.com.cn/images/demo.rar">点击这里</A>下载demo!<A id=7 name=7></A><BR><BR><B>七、BEA能为我们做更多</B><A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#0">(目录)</A><BR>　　Bea的 WebLogic出色表现有目共睹，尤其是现在WebLogic 8。现在应用服务越来越复杂，我这个用WebLogic6的人突然看到WebLogic 8会欣喜又不知所措，我们还希望Bea能给我们较简单的解决方案。 <BR>　　突破口在哪呢？就是数据持久层的开发，数据持久层的不依赖某一特定服务器的构件化图形化开发做好，一切将变得EASY！前面有Spring的Ioc模式实现的BeanFactory和AOP和轻量级数据库解决方案IBATIS(Hibernate)做急先锋，我们觉得数据持久层构件化时代已经到来！这样WebLogic运行见下图：</P>
<P align=center><IMG height=290 src="http://dev2dev.bea.com.cn/images/image2004111687.gif" width=636><BR>图八</P>
<P>　　BEA能为我们做得更多？！ <BR>　　BEA下个春季，值得我们期待！<A id=8 name=8></A><BR><BR><B>八、总结</B><A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#0">(目录)</A><BR>　　这篇文章对我来说是个挑战，但一直以来J2EE开发的快乐与痛苦刺激着我前行。我觉得我们应该为这作些什么。 <BR>　　题目选得有点大，我有几次想放弃，但我老婆APPLE一直鼓励着我，让我在痛苦中成长。 <BR>　　我希望将来的J2EE开发不再会有EJB该如何使用的疑问。因为开发者本身都不会感觉EJB的存在。限于本人水平和理解层次，可能会有许多误解，期待着与您交流和争论！<A id=9 name=9></A></P>
<P><B>相关资源下载</B><A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#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 5.0、Ant 1.6可以从<A href="http://www.apache.org/" target=_blank>http://www.apache.org</A>下载。<BR>　　WebLogic Server / Workshop 8.1可以从<A href="http://commerce.bea.com/" target=_blank>http://commerce.bea.com</A>下载。<BR>　　oracle9I的JDBC驱动，commons-dbcp下载<A id=10 name=10></A><BR><BR><B>参考资料</B><A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#0">(目录)</A><BR>　　Wrox - Professional J2EE with BEA WebLogic Server<BR>　　J2EE Applications and BEAWebLogic Server <BR>　　Expert One-on-One J2EE Development without EJB <BR>　　Expert One-on-One J2EE Design and Development by Rod Johnson<BR>　　部分图片来自公司培训幻灯片<A id=11 name=11></A></P>
<P><B>关于作者</B><A href="http://dev2dev.bea.com.cn/bbs/jishudata/ArticleShow.jsp?Id=15#0">(目录)</A><BR>　　关于作者：<BR>　　周必奎（dev2dev ID:zhoubikui），软件工程师，从事J2EE开发<BR>　　电子邮件：<A href="mailto:zhoubikui@eyou.com">zhoubikui@eyou.com</A> <BR>　　地址：财政部信息网络中心北京兴财信息有限公司</P><img src ="http://www.blogjava.net/kapok/aggbug/4045.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-05-06 00:46 <a href="http://www.blogjava.net/kapok/archive/2005/05/06/4045.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>BeanFactory </title><link>http://www.blogjava.net/kapok/archive/2005/05/06/4044.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Thu, 05 May 2005 16:43:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/05/06/4044.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/4044.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/05/06/4044.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/4044.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/4044.html</trackback:ping><description><![CDATA[<P>/*<BR>&nbsp;* Copyright 2002-2005 the original author or authors.<BR>&nbsp;* <BR>&nbsp;* Licensed under the Apache License, Version 2.0 (the "License");<BR>&nbsp;* you may not use this file except in compliance with the License.<BR>&nbsp;* You may obtain a copy of the License at<BR>&nbsp;* <BR>&nbsp;*&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <A href="http://www.apache.org/licenses/LICENSE-2.0">http://www.apache.org/licenses/LICENSE-2.0</A><BR>&nbsp;* <BR>&nbsp;* Unless required by applicable law or agreed to in writing, software<BR>&nbsp;* distributed under the License is distributed on an "AS IS" BASIS,<BR>&nbsp;* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.<BR>&nbsp;* See the License for the specific language governing permissions and<BR>&nbsp;* limitations under the License.<BR>&nbsp;*/</P>
<P>package org.springframework.beans.factory;</P>
<P>import org.springframework.beans.BeansException;</P>
<P>/**<BR>&nbsp;* The root interface for accessing a Spring IoC container.<BR>&nbsp;*<BR>&nbsp;* &lt;p&gt;This interface is implemented by objects that hold a number of bean definitions,<BR>&nbsp;* each uniquely identified by a String name. Depending on the bean definition,<BR>&nbsp;* the factory will return either an independent instance of a<BR>&nbsp;* contained object (the Prototype design pattern), or a single<BR>&nbsp;* shared instance (a superior alternative to the Singleton<BR>&nbsp;* design pattern, in which the instance is a singleton in the scope of<BR>&nbsp;* the factory). Which type of instance will be returned depends on the bean<BR>&nbsp;* factory configuration - the API is the same. The Singleton approach is<BR>&nbsp;* more useful and more common in practice.<BR>&nbsp;*<BR>&nbsp;* &lt;p&gt;The point of this approach is that the BeanFactory is a central registry<BR>&nbsp;* of application components, and centralizes configuration of application<BR>&nbsp;* components (no more do individual objects need to read properties files,<BR>&nbsp;* for example). See chapters 4 and 11 of "Expert One-on-One J2EE Design and<BR>&nbsp;* Development" for a discussion of the benefits of this approach.<BR>&nbsp;*<BR>&nbsp;* &lt;p&gt;Note that it is generally better to rely on Dependency Injection<BR>&nbsp;* ("push" configuration) to configure application objects through setters<BR>&nbsp;* or constructors, rather than use<BR>&nbsp;* any form of "pull" configuration like a BeanFactory lookup. Spring's<BR>&nbsp;* Dependency Injection functionality is implemented using BeanFactory<BR>&nbsp;* and its subinterfaces.<BR>&nbsp;*<BR>&nbsp;* &lt;p&gt;Normally a BeanFactory will load bean definitions stored in a configuration<BR>&nbsp;* source (such as an XML document), and use the org.springframework.beans package<BR>&nbsp;* to configure the beans. However, an implementation could simply return Java<BR>&nbsp;* objects it creates as necessary directly in Java code. There are no constraints<BR>&nbsp;* on how the definitions could be stored: LDAP, RDBMS, XML, properties file etc.<BR>&nbsp;* Implementations are encouraged to support references amongst beans, to either<BR>&nbsp;* Singletons or Prototypes.<BR>&nbsp;*<BR>&nbsp;* &lt;p&gt;In contrast to the methods in ListableBeanFactory, all of the methods in this<BR>&nbsp;* interface will also check parent factories if this is a HierarchicalBeanFactory.<BR>&nbsp;* If a bean is not found in this factory instance, the immediate parent is asked.<BR>&nbsp;* Beans in this factory instance are supposed to override beans of the same name<BR>&nbsp;* in any parent factory.<BR>&nbsp;*<BR>&nbsp;* &lt;p&gt;Bean factory implementations should support the standard bean lifecycle interfaces<BR>&nbsp;* as far as possible. The maximum set of initialization methods and their standard<BR>&nbsp;* order is:&lt;br&gt;<BR>&nbsp;* 1. BeanNameAware's setBeanName&lt;br&gt;<BR>&nbsp;* 2. BeanFactoryAware's setBeanFactory&lt;br&gt;<BR>&nbsp;* 3. ApplicationContextAware's setApplicationContext (only applicable if running<BR>&nbsp;* in an application context)&lt;br&gt;<BR>&nbsp;* 4. postProcessBeforeInitialization methods of BeanPostProcessors&lt;br&gt;<BR>&nbsp;* 5. InitializingBean's afterPropertiesSet&lt;br&gt;<BR>&nbsp;* 6. a custom init-method definition&lt;br&gt;<BR>&nbsp;* 7. postProcessAfterInitialization methods of BeanPostProcessors<BR>&nbsp;*<BR>&nbsp;* &lt;p&gt;On shutdown of a bean factory, the following lifecycle methods apply:&lt;br&gt;<BR>&nbsp;* 1. DisposableBean's destroy&lt;br&gt;<BR>&nbsp;* 2. a custom destroy-method definition<BR>&nbsp;*<BR>&nbsp;* @author Rod Johnson<BR>&nbsp;* @author Juergen Hoeller<BR>&nbsp;* @since 13 April 2001<BR>&nbsp;* @see BeanNameAware#setBeanName<BR>&nbsp;* @see BeanFactoryAware#setBeanFactory<BR>&nbsp;* @see InitializingBean#afterPropertiesSet<BR>&nbsp;* @see DisposableBean#destroy<BR>&nbsp;* @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization<BR>&nbsp;* @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization<BR>&nbsp;* @see org.springframework.beans.factory.support.RootBeanDefinition#getInitMethodName<BR>&nbsp;* @see org.springframework.beans.factory.support.RootBeanDefinition#getDestroyMethodName<BR>&nbsp;* @see org.springframework.context.ApplicationContextAware#setApplicationContext<BR>&nbsp;*/<BR>public interface BeanFactory {</P>
<P>&nbsp;/**<BR>&nbsp; * Used to dereference a FactoryBean and distinguish it from beans<BR>&nbsp; * &lt;i&gt;created&lt;/i&gt; by the FactoryBean. For example, if the bean named<BR>&nbsp; * &lt;code&gt;myEjb&lt;/code&gt; is a FactoryBean, getting &lt;code&gt;&amp;myEjb&lt;/code&gt; will<BR>&nbsp; * return the factory, not the instance returned by the factory.<BR>&nbsp; */<BR>&nbsp;String FACTORY_BEAN_PREFIX = "&amp;";</P>
<P><BR>&nbsp;/**<BR>&nbsp; * Return an instance, which may be shared or independent, of the given bean name.<BR>&nbsp; * This method allows a Spring bean factory to be used as a replacement for<BR>&nbsp; * the Singleton or Prototype design pattern.<BR>&nbsp; * &lt;p&gt;Callers may retain references to returned objects<BR>&nbsp; * in the case of Singleton beans. <BR>&nbsp; * &lt;p&gt;This method delegates to the parent factory if the <BR>&nbsp; * bean cannot be found in this factory instance.<BR>&nbsp; * @param name the name of the bean to return<BR>&nbsp; * @return the instance of the bean<BR>&nbsp; * @throws NoSuchBeanDefinitionException if there is no bean definition<BR>&nbsp; * with the specified name<BR>&nbsp; * @throws BeansException if the bean could not be obtained<BR>&nbsp; */<BR>&nbsp;Object getBean(String name) throws BeansException;</P>
<P>&nbsp;/**<BR>&nbsp; * Return an instance (possibly shared or independent) of the given bean name.<BR>&nbsp; * &lt;p&gt;Behaves the same as getBean(String), but provides a measure of type safety by<BR>&nbsp; * throwing a Spring BeansException if the bean is not of the required type.<BR>&nbsp; * This means that ClassCastException can't be thrown on casting the result correctly,<BR>&nbsp; * as can happen with getBean(String). <BR>&nbsp; * @param name the name of the bean to return<BR>&nbsp; * @param requiredType type the bean must match. Can be an interface or superclass<BR>&nbsp; * of the actual class, or null for any match. For example, if the value is<BR>&nbsp; * Object.class, this method will succeed whatever the class of the returned instance.<BR>&nbsp; * @return an instance of the bean (never null)<BR>&nbsp; * @throws BeanNotOfRequiredTypeException if the bean is not of the required type<BR>&nbsp; * @throws NoSuchBeanDefinitionException if there's no such bean definition<BR>&nbsp; * @throws BeansException if the bean could not be created<BR>&nbsp; */<BR>&nbsp;Object getBean(String name, Class requiredType) throws BeansException;</P>
<P>&nbsp;/**<BR>&nbsp; * Does this bean factory contain a bean definition with the given name?<BR>&nbsp; * &lt;p&gt;Will ask the parent factory if the bean cannot be found in this factory instance.<BR>&nbsp; * @param name the name of the bean to query<BR>&nbsp; * @return whether a bean with the given name is defined<BR>&nbsp; */<BR>&nbsp;boolean containsBean(String name);</P>
<P>&nbsp;/**<BR>&nbsp; * Is this bean a singleton? That is, will getBean() always return the same object?<BR>&nbsp; * &lt;p&gt;Will ask the parent factory if the bean cannot be found in this factory instance.<BR>&nbsp; * @param name the name of the bean to query<BR>&nbsp; * @return is this bean a singleton<BR>&nbsp; * @throws NoSuchBeanDefinitionException if there is no bean with the given name<BR>&nbsp; */<BR>&nbsp;boolean isSingleton(String name) throws NoSuchBeanDefinitionException;</P>
<P>&nbsp;/**<BR>&nbsp; * Determine the type of the bean with the given name.<BR>&nbsp; * More specifically, checks the type of object that getBean would return.<BR>&nbsp; * For a FactoryBean, returns the type of object that the FactoryBean creates.<BR>&nbsp; * @param name the name of the bean to query<BR>&nbsp; * @return the type of the bean, or null if not determinable<BR>&nbsp; * @throws NoSuchBeanDefinitionException if there is no bean with the given name<BR>&nbsp; * @since 1.1.2<BR>&nbsp; * @see #getBean<BR>&nbsp; * @see FactoryBean#getObjectType<BR>&nbsp; */<BR>&nbsp;Class getType(String name) throws NoSuchBeanDefinitionException;</P>
<P>&nbsp;/**<BR>&nbsp; * Return the aliases for the given bean name, if defined.<BR>&nbsp; * &lt;p&gt;Will ask the parent factory if the bean cannot be found in this factory instance.<BR>&nbsp; * @param name the bean name to check for aliases<BR>&nbsp; * @return the aliases, or an empty array if none<BR>&nbsp; * @throws NoSuchBeanDefinitionException if there's no such bean definition<BR>&nbsp; */<BR>&nbsp;String[] getAliases(String name) throws NoSuchBeanDefinitionException;</P>
<P>}<BR></P><img src ="http://www.blogjava.net/kapok/aggbug/4044.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-05-06 00:43 <a href="http://www.blogjava.net/kapok/archive/2005/05/06/4044.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>webwork 阅读备忘录</title><link>http://www.blogjava.net/kapok/archive/2005/04/19/3442.html</link><dc:creator>笨笨</dc:creator><author>笨笨</author><pubDate>Tue, 19 Apr 2005 02:19:00 GMT</pubDate><guid>http://www.blogjava.net/kapok/archive/2005/04/19/3442.html</guid><wfw:comment>http://www.blogjava.net/kapok/comments/3442.html</wfw:comment><comments>http://www.blogjava.net/kapok/archive/2005/04/19/3442.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kapok/comments/commentRss/3442.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kapok/services/trackbacks/3442.html</trackback:ping><description><![CDATA[<A href="http://wiki.opensymphony.com/display/WW/Templates">http://wiki.opensymphony.com/display/WW/Templates</A><img src ="http://www.blogjava.net/kapok/aggbug/3442.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kapok/" target="_blank">笨笨</a> 2005-04-19 10:19 <a href="http://www.blogjava.net/kapok/archive/2005/04/19/3442.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>