<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
  <channel>
    <title>大道至简</title>
    <description>大道至简，平淡为归</description>
    <link>http://tailsherry.javaeye.com</link>
    <language>UTF-8</language>
    <copyright>Copyright 2003-2008, JavaEye.com</copyright>
    <docs>http://blogs.law.harvard.edu/tech/rss</docs>
    <generator>JavaEye - 做最棒的软件开发交流社区</generator>
      <item>
        <title>Web2.0时代的新秀 - Nexaweb</title>
        <author>tailsherry</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://tailsherry.javaeye.com">tailsherry</a>&nbsp;
          链接：<a href="http://tailsherry.javaeye.com/blog/221000" style="color:red;">http://tailsherry.javaeye.com/blog/221000</a>&nbsp;
          发表时间: 2008年07月29日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>Web2.0时代，用户对UI交互体验对Web设计上提出了更高的要求，各种各样的Rich Client技术层出不穷，有基于Javascript的，有基于Flex的，也有基于Java Applet的... <br /><br />最近接触到一种新的基于Web2.0应用开发的技术，叫做Nexaweb(<a href="http://www.nexaweb.com" target="_blank">http://www.nexaweb.com</a>)，我们可以把他理解为一个简易的开发平台或框架。Nexaweb的特点就是开发者在一个基于Eclipse的插件集提供的界面上，使用拖拽的方式来构造页面，而最终生成的代码将是一个基于XML的文件，取名为XAL。事实上，接触过JasperReport+iReport的人，应该不会对这种技术感到陌生。其示例内容如下：</p>
<pre name="code" class="xml">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;xal xmlns="http://openxal.org/ui/html"&gt;
	&lt;rootPane&gt;
		&lt;freePane height="768px" width="1024px"&gt;
			&lt;table height="360px" width="500px" x="30px" y="30px"&gt;
				&lt;column&gt;
					&lt;header text="Column #1" width="100px"/&gt;
				&lt;/column&gt;
				&lt;column&gt;
					&lt;header text="Column #2" width="100px"/&gt;
				&lt;/column&gt;
				&lt;row&gt;
					&lt;cell text="Row 1 Cell 1"/&gt;
					&lt;cell text="Row 1 Cell 2"/&gt;
				&lt;/row&gt;
				&lt;row&gt;
					&lt;cell text="Row 2 Cell 1"/&gt;
					&lt;cell text="Row 2 Cell 2"/&gt;
				&lt;/row&gt;
				&lt;row&gt;
					&lt;cell text="Row 3 Cell 1"/&gt;
					&lt;cell text="Row 3 Cell 2"/&gt;
				&lt;/row&gt;
			&lt;/table&gt;
			&lt;button height="25px" text="Button" width="100px" x="30px" y="410px"/&gt;
		&lt;/freePane&gt;
	&lt;/rootPane&gt;
&lt;/xal&gt;</pre>
<p><br />以上的例子只是一个非常简单的示例，XAL所有的内容远非如此。比如，我们还可以在其中定义各种类型的Datasource，Webservice连接，事件管理的MCO，页面组件相关的XModifier和Macro等。XAL的目的就是让你<strong>所见即所得</strong>，可能你自己需要写的Java代码或JSP页面寥寥无几，所有的东西都由Nexaweb Platform全权处理了。无疑，这让我们的Web应用开发更加节省时间。 <br /><br /><br />下面我就介绍一下Nexaweb的主要特点： <br /><br /><strong>1. 灵活的Rich Client展示</strong> <br />由于XAL是基于XML来构建的，所以客户端展示模式可以多种多样。Nexaweb目前主要采用的主要是基于Javascript的Dojo，还有就是Java Applet。这里不讨论各种Rich Client技术的孰优孰劣，我们需要知道的是这两种展示可以被所有的浏览器所支持。 <br /><br />这里值得一提的是，关于XAL文件解析的位置。一般的思路，也是许多类似平台的思路，我们会倾向于将XAL文件放在服务器端去解释，不管是从安全性和效率性方面考虑都是应该的。但是恰恰相反，Nexaweb是将XAL拿到客户端去解释成相应的Dojo对象或者Applet组件。为什么这样做？这主要是因为Nexaweb是要适用于所有的开发平台，不仅仅是Java，还有.Net的等等。 <br /><br /><strong>2. 丰富的界面组件支持</strong> <br />对于Applet在Web界面上的展示效果，这是毋庸置疑的，大家在各种各样相关的例子中都有过类似的体验。对于Dojo，我想很多人都有过接触，知道Dojo本身也提供了丰富的UI组件库，此外，Nexaweb也在基于Dojo的基础上，发展出了XAP(目前是Apache下面的一个开源项目，有兴趣的可以去了解)，还有就是Dojo.E，定制了很多强大的UI组件，大大提高了Dojo在web上的展示能力。 <br /><br /><strong>3. 巧妙的事件处理机制</strong> <br />既然是一个静态的XAL文件，Nexaweb如何处理事件呢？这里主要用到Macro和XModifier。Macro主要用来处理客户端事件，你可以把他看作是Excel中的宏，Macro的主要作用就是进行客户端组件的控制，如某个组件的某个属性的修改，导致何种的界面效果。而XModifier是用来处理Server端的响应，如果是基于Java的应用，一般系统会自动引导你去建一个扩展一个BaseMCO的类，实现里面相应的方法就OK了。 <br /><br /><strong>4. 强大的框架支持</strong> <br />Nexaweb本身集成了Webservice的功能，这样会让你很方便的去访问远程的服务。当然，Nexaweb也加入了对Struts, Spring, Hibernate/iBatis等流行框架的支持。 <br /><br /><strong>5. 对MDA UML2良好的支持</strong> <br />Nexaweb同样集成了MagicDraw 和 OpenArchitectureWare 等插件的支持。 <br /><br /><strong>6. 支持基于VB, C++ Builder应用的移植</strong> <br />由于Nexaweb的XAL的特殊性，Nexaweb可以支持把传统的VB, C++ Builder的应用移植到Nexaweb中来，部署成为一个Web应用。 <br /><br /><br />我现在也是正在学习这个框架，但是自从体验过一次实际开发后，才知道原来开发一个Web2.0的应用只需要短短的几分钟，以前可能需要几个小时的工作，在Nexaweb却一会儿即可完成。 <br /><br />现在Nexaweb主要还是在欧美和日韩有庞大的客户群，而且都是大型的企业。对于小企业呢，可能不会舍得投入这种成本，而选择用一些免费的开源框架去做一些企业应用。</p>
<p>&nbsp;</p>
          <br/>
          <span style="color:red;">
            <a href="http://tailsherry.javaeye.com/blog/221000#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/115' target='_blank'><span style="color:red;font-weight:bold;">JavaEye图灵杯第2届问答大赛开始了！8月4日至8月17日，奖品丰厚！</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Tue, 29 Jul 2008 11:28:48 +0800</pubDate>
        <link>http://tailsherry.javaeye.com/blog/221000</link>
        <guid>http://tailsherry.javaeye.com/blog/221000</guid>
      </item>
      <item>
        <title>Java GUI 历史之争</title>
        <author>tailsherry</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://tailsherry.javaeye.com">tailsherry</a>&nbsp;
          链接：<a href="http://tailsherry.javaeye.com/blog/218898" style="color:red;">http://tailsherry.javaeye.com/blog/218898</a>&nbsp;
          发表时间: 2008年07月24日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>最近在收集一些关于AWT, Swing, SWT之间比较的文章，中间接触到一些关于这三大GUI开发工具包的历史，觉得挺有意思，就用自己的文字记录下来。我想，这对于那些从事Java GUI开发的同志们来说，应该是不可缺的一堂历史课。</p>
<p>&nbsp;</p>
<p>话说20世纪中期，IT界也出现&ldquo;三国&rdquo;，他们都用Smalltalk作为武器在IT界打自己的天下，其中有大家今日熟悉的蓝色巨人IBM，还有就是Digital Talk和Parc-Place. IBM和Digital Talk坚信采用本地化组件的战略来打天下，而Parc-Place却坚持采用仿效机制来自绘组件天下。</p>
<p>&nbsp;</p>
<p>虽然在建国理念上存在冲突，三国之间却一直风平浪静，你走你的独木桥，我走我的阳关道。直到有一天，枭雄IBM揭竿而起，迅速发展国力，军事上逐渐超越另外的两个潜在对手。感觉无法招架IBM的强大威慑力，Digital Talk和Parc-Place决定联盟，组成一个更强大的国家来对抗IBM的强势入侵，起国号为Objectshare。虽然D和P郎情妾意，欢欢喜喜结成连理，哪知道两者性格理念上的差异，为之后的联姻破碎埋下了祸根！</p>
<p>&nbsp;</p>
<p>婚姻有七年之痒，D和P却从一开始就进入了家庭纷争，但最终还是以Amy Fowler为代表的Parc-Place方，赢得了组件仿效战略的胜利。可此时，IBM已经通过他的本地化组件策略赢得了大片的国土和子民。这能怪谁呢？P和D本来就是不合的一对，为了所谓的理念追求，两者啥事不做竟窝里斗，白白折腾了整整一年！而这，恰恰为IBM的壮大提供了契机。直到有一天，Objectshare因为经济不济，被赶出了历史舞台！似乎此时，IBM已经统一了组件开发的河山，本地化组件的策略似乎也大行其道。</p>
<p>&nbsp;</p>
<p>历史上没有真正的王者，IT界也是如此。Sun公司此时凭借他的Java AWT开始打江山。殊不知，AWT也采用了IBM本地化组件的策略，但是做出来的效果却不敢恭维，破绽百出。正在此时，处于流亡状态的Amy Fowler得到了SUN的青睐，被委以重任去修复这个破破烂烂的AWT。当然在前面大家都了解到Amy Fowler这个人，他是一直拥护防效机制建立组件的。得到SUN高层的信赖后，Amy开始笼络并召回以前Parc-Place的一些旧部下，开始实施他们的仿效组件策略，创造了另外一个GUI开发标准，也就是我们熟悉的Swing.</p>
<p>&nbsp;</p>
<p>当然，IBM也没有落后，他的Java开发工具Visual Age也是用Smalltalk写的，当然，也是基于本地化组件策略。由于Java的兴起，他们开始从Smalltalk移植到Java开发页面组件。IBM的那些将士们基本上都是用Smalltalk的，他们都很讨厌基于仿效机制的Swing，讨厌它的丑陋，笨重，破绽重重！于是乎，他们用Java开发了一套本地化组件，也就是我们后来熟知的SWT。后来SWT被移植到Visual Age形成了现在的航空母舰Eclipse。</p>
<p>&nbsp;</p>
<p>历史就是如此，AWT, Swing和SWT的历史之争就这样一直持续到了现在。天下大势，分久必合，合久必分，希望有一天，有一套通用的Java GUI规范，造福我们这些一直以来用Java武装的将士们。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
          <br/>
          <span style="color:red;">
            <a href="http://tailsherry.javaeye.com/blog/218898#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/115' target='_blank'><span style="color:red;font-weight:bold;">JavaEye图灵杯第2届问答大赛开始了！8月4日至8月17日，奖品丰厚！</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Thu, 24 Jul 2008 11:17:26 +0800</pubDate>
        <link>http://tailsherry.javaeye.com/blog/218898</link>
        <guid>http://tailsherry.javaeye.com/blog/218898</guid>
      </item>
      <item>
        <title>在JSF/JSP中集成FCKEditor 2.6</title>
        <author>tailsherry</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://tailsherry.javaeye.com">tailsherry</a>&nbsp;
          链接：<a href="http://tailsherry.javaeye.com/blog/197824" style="color:red;">http://tailsherry.javaeye.com/blog/197824</a>&nbsp;
          发表时间: 2008年05月28日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>目前，FCKEditor (<a href="http://www.fckeditor.net/">http://www.fckeditor.net/</a>) 是开源社区一款强大的HTML编辑器，目前最新版本是2.6，支持Java的插件版本是2.4Beta1。</p>
<p>&nbsp;</p>
<p>对于一般的Java Web应用，我们可以通过直接插入JavaScript代码来构造页面，这种方式操作起来比较简便，也是通用在所有web页面的一种方式。但是，在实际的Java Web应用中，我们除了用到普通的页面编辑功能之外，难免会考虑到页面上传图片、附件等功能。所以，单纯使用JavaScript方式的话，这一大堆的文件上传代码将由你自己来写了，工程量还是比较浩大的。自然而然，我们会想到Jsp Tag，有没有现成的快餐式的页面标签呢？答案是肯定的，就如我上文提到的Java插件就已经实现了这些功能，并集成了Apache Commons-FileUpload，以此来实现文件服务器上传。</p>
<p>&nbsp;</p>
<p>2.4版的Java插件已经封装得更加简洁，不同于之前的其他版本。标签的使用方式是，</p>
<pre name="code" class="xml">&lt;%@taglib uri="http://java.fckeditor.net" prefix="FCK"%&gt;
&lt;FCK:editor instanceName="EditorDefault" width="755" height="460" basePath="/fckeditor" value="Hello, world"&gt;
  &lt;jsp:body&gt;
    &lt;FCK:config SkinPath="skins/office2003/" /&gt;
  &lt;/jsp:body&gt;
&lt;/FCK:editor&gt;</pre>
<p>&nbsp;</p>
<p>在JSF应用中，由于页面都是JSF自定义标签，对于其他标签的内容，JSF不会自动将后台Bean中的值绑定到&lt;FCK:editor/&gt;，那么我们还要做做文章，通过一段JavaScript绑定到body_onload()事件中，以此将JSF标签的内容传递给&lt;FCK:editor/&gt;。</p>
<pre name="code" class="html">&lt;script type="text/javascript"&gt;    
    function renderMessage() {
        YAHOO.util.Dom.get("EditorDefault").value = YAHOO.util.Dom.get("form:content").value;
    }
&lt;/script&gt;

&lt;f:view&gt;
  &lt;h:form id="form"&gt;
    &lt;h:inputTextarea id="content" value="#{mainMessageEditBean.message.messageContent}" style="display:none"&gt;&lt;/h:inputTextarea&gt;
  &lt;/h:form&gt;
&lt;/f:view&gt;
</pre>
<p>&nbsp;</p>
<p>保存HTML编辑内容的时候，你只需要通过获得到的HttpServletRequest对象获取页面提交中的Parameter对象，示例代码如下：</p>
<pre name="code" class="java">    public Map getParamMap() {
        return getFacesContext().getExternalContext().getRequestParameterMap();
    }
    
    public String getParamAsString(String paramName) {
        Object obj = getParamMap().get(paramName);
        if (obj != null) {
            return obj.toString();
        } else {
            return "";
        }
    }

    public String saveMessage() {
        if (message == null) {
            setErrMsg("Message does not exist.");
            return null;            
        }
        String content = getParamAsString("EditorDefault");
        if (content == null || content.length() == 0) {
            setErrMsg("Message must not be empty.");
            return null;                        
        }
    }</pre>
<p>&nbsp;</p>
<p>其他方面，我们需要在自己的web.xml中定义FCKEditor相关的Servlet：</p>
<pre name="code" class="xml">&lt;servlet&gt;
  &lt;servlet-name&gt;Connector&lt;/servlet-name&gt;
    &lt;servlet-class&gt;
         net.fckeditor.connector.ConnectorServlet
    &lt;/servlet-class&gt;
    &lt;load-on-startup&gt;1&lt;/load-on-startup&gt;
&lt;/servlet&gt;
&lt;servlet-mapping&gt;
    &lt;servlet-name&gt;Connector&lt;/servlet-name&gt;
    &lt;url-pattern&gt;
         /fckeditor/editor/filemanager/connectors/*
    &lt;/url-pattern&gt;
&lt;/servlet-mapping&gt;</pre>
<p>&nbsp;</p>
<p>这里需要注明的是，/fckeditor这个web根目录下的目录来源于FCKEditor的核心包(Ver 2.6)，可以在官方网站上面下载得到。</p>
<p>&nbsp;</p>
<p>在目录/fckeditor下面有一个重要的文件fckconfig.js，里面可以配置许多FCKEditor在页面上的展示风格，大家可以仔细慢慢研究 :)</p>
<p>&nbsp;</p>
<p><em>PS: 考虑到安全问题，建议大家把FCKEditor界面上的Source Code按钮屏蔽掉，防止黑客的恶意脚本的破坏。</em></p>
<p><em></em></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>在我的实际应用中，因为要限制上传文件的权限，所以我要用到权限控制。幸好FCKEditor提供了相应的UserAction接口，可以让我自己实现一个类来控制权限。</p>
<pre name="code" class="java">package com.tail.utils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import net.fckeditor.requestcycle.UserAction;

import com.tail.beans.Principal;
import com.tail.objects.User;

public class UserActionImpl implements UserAction {

    public boolean isEnabledForFileBrowsing(HttpServletRequest req) {        
        return true;
    }

    public boolean isEnabledForFileUpload(HttpServletRequest req) {
        HttpSession session = req.getSession();
        Principal principal = (Principal) session.getAttribute(ConstantUtil.SESSION_PRINCIPAL);
        if (principal != null) {  
            User user = principal.getUser();
            if (user.isUploadable()) {
                return true;
            }
        }
        return false;
    }

}</pre>
<p>&nbsp;</p>
<p>如何加载自定义的UserAction类呢？在classes的根目录下，你需要定义一个fckeditor.properties文件：</p>
<pre name="code" class="js">connector.userActionImpl=com.tail.utils.UserActionImpl</pre>
<p>&nbsp;</p>
<p>这样你就可以控制文件上传的权限了。</p>
<p>&nbsp;</p>
<p>我的2.4Beta1版的JAR包在文件上传上还有一些问题，我已经下载了相应Java Source Code做了修改，一切都运行的比较正常！</p>
<p>&nbsp;</p>
<p><em>PS: 下载的Java Project是基于Maven的，如果不懂Maven的同学抓紧温习温习吧！</em></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
          <br/>
          <span style="color:red;">
            <a href="http://tailsherry.javaeye.com/blog/197824#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/115' target='_blank'><span style="color:red;font-weight:bold;">JavaEye图灵杯第2届问答大赛开始了！8月4日至8月17日，奖品丰厚！</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Wed, 28 May 2008 13:03:31 +0800</pubDate>
        <link>http://tailsherry.javaeye.com/blog/197824</link>
        <guid>http://tailsherry.javaeye.com/blog/197824</guid>
      </item>
      <item>
        <title>使用Selenium/Ant做Web应用远程自动化测试</title>
        <author>tailsherry</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://tailsherry.javaeye.com">tailsherry</a>&nbsp;
          链接：<a href="http://tailsherry.javaeye.com/blog/191539" style="color:red;">http://tailsherry.javaeye.com/blog/191539</a>&nbsp;
          发表时间: 2008年05月09日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          接触到Selenium这个Web应用自动化测试工具，实属意外。由于目前项目的停滞，客户那边弄过来一个新的自动化测试的任务。寒~~~ 啥时候也成了QA了！没办法，硬着头皮做下去。<br /><br />一直比较欣赏的就是外国客户的技术定位，当然他们推荐的这个开源的Selenium也比较有特色，而且和Java, C#地结合也都做得非常好。对于熟悉Java代码的我们来说，看着熟悉的亲切的字符，心中的郁闷慢慢消去。在探索的过程中，对开源社区的这些贡献者们的智慧也是由衷的佩服。<br /><br />Selenium是一款Web应用的自动化测试工具，详细的内容可参考官方网站(<a href="http://selenium.openqa.org/" target="_blank">http://selenium.openqa.org/</a>)，你可以获取Firefox下的相关IDE插件，录制一些个脚本，然后导出各种语言形式的脚本。操作过程很简单，在这里也不再赘述。<br /><br />此外，为了支持远程分布式自动化测试，Selenium也提供了client和server端的jar包，通过jar包来部署server端和建造client端，部署出来的server端事实上只是一个可运行的jar程序，时刻监测client端发过来的命令请求。<br /><br />Selenium的远程控制机制，确切地描述应该是client端远程控制server端，server端通过识别client发送过来的script指令，打开指定的浏览器进行自动化测试。这种做法是侵入性的，一般要保证这些测试服务器在一个局部网络范围内。<br /><br />由此可见，server端实际上一个空壳，部署极其简单，不用描述。<br /><br />Client端生成的Java脚本，是基于JUnit的，所以除了引入selenium-java-client-driver.jar包之外，还要引入junit.jar的支持。Client端的应用结构图大致如下：<br /><br /><img src="../../../upload/picture/pic/13987/e6a97510-e8be-3ebc-93e1-786f65aa75dd.jpg" alt="" /><br /><br />Client端主要是通过一个ant build文件来启动JUnit的TestCase的，进而启动TestCase中的test方法，连接并激活server端进行自动化测试。Client端核心测试单元的代码如下：<br /><pre name="code" class="java">package com.tail.p2test;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import junit.textui.TestRunner;

import com.thoughtworks.selenium.DefaultSelenium;
import com.thoughtworks.selenium.Selenium;

public class DemoTest extends TestCase {
    private Selenium selenium;
    
    public void setUp() throws Exception {
        String url = "http://localhost:8080/";
        selenium = new DefaultSelenium("localhost", 4444, "*chrome", url);
        selenium.start();
    }

    protected void tearDown() throws Exception {
        selenium.stop();        
    }
    
    public void testNew() throws Exception {
        selenium.setTimeout("100000");
        selenium.open("/login.action");
        selenium.type("username", "admin");
        selenium.type("password", "123");
        selenium.click("//input[@value='Log In']");
        selenium.waitForPageToLoad("100000");
        Thread.sleep(10000);
        for (int second = 0;; second++) {
            if (second >= 60) fail("timeout");
            try { if (selenium.isElementPresent("signLabel")) break; } catch (Exception e) {}
            Thread.sleep(1000);
        }
        // omit lines
        ...
        selenium.open("/main.action");
    }
}</pre><br /><br />当然，应用可以直接在Eclipse中运行，但是为了能更加灵活，我们考虑用ant脚本来控制client的运行，这里使用ant脚本的一个好处就是可以很方便快捷的输出测试报告，在本例中输出报告的目的就是那个report目录咯。<br /><br />ant的Build.xml的脚本详细如下：<br /><pre name="code" class="xml">&lt;?xml version="1.0"?>

&lt;project name="portal" default="junit" basedir=".">
 &lt;property name="source.dir" value="src" />
 &lt;property name="build.dir" value="build" /> 
 &lt;property name="lib.dir" value="lib" />
 &lt;property name="classes.dir" value="${build.dir}/classes" />
 &lt;property name="report.dir" value="report" />

 &lt;!-- ================================================================== -->
 &lt;!-- C L E A N                                                          -->
 &lt;!-- ================================================================== -->
 &lt;target name="clean">
  &lt;delete dir="${classes.dir}" />
  &lt;mkdir dir="${classes.dir}" />
  &lt;delete dir="${report.dir}" />
  &lt;mkdir dir="${report.dir}" />  
 &lt;/target>

 &lt;!-- ================================================================== -->
 &lt;!-- C O M P I L E                                                      -->
 &lt;!-- ================================================================== -->
 &lt;target name="compile" depends="clean">
  &lt;!-- local project jars -->
  &lt;patternset id="lib.includes.compile">
   &lt;include name="*.jar" />
  &lt;/patternset>
  &lt;fileset dir="${lib.dir}" id="lib.compile">
   &lt;patternset refid="lib.includes.compile" />
  &lt;/fileset>
  &lt;pathconvert targetos="windows" property="libs.compile" refid="lib.compile" />
  &lt;!-- compile -->
  &lt;javac srcdir="${source.dir}" destdir="${classes.dir}" classpath="${libs.compile}" includes="**/*.java" debug="true">
  &lt;/javac>
 &lt;/target>

 &lt;!-- ================================================================== -->
 &lt;!-- J U N I T                                                          -->
 &lt;!-- ================================================================== -->
 &lt;target name="junit" depends="compile">
  &lt;junit printsummary="on" fork="true" haltonfailure="false" failureproperty="tests.failed" showoutput="true">
   &lt;classpath>
    &lt;pathelement path="${classes.dir}" />
    &lt;fileset dir="${lib.dir}">
     &lt;include name="**/*.jar" />
    &lt;/fileset>
   &lt;/classpath>
   &lt;formatter type="xml" />
   &lt;batchtest todir="${report.dir}">
    &lt;fileset dir="${classes.dir}">
     &lt;include name="**/*Test.*" />
    &lt;/fileset>
   &lt;/batchtest>
  &lt;/junit>
  &lt;junitreport todir="${report.dir}">
   &lt;fileset dir="${report.dir}">
    &lt;include name="TEST-*.xml" />
   &lt;/fileset>
   &lt;report format="frames" todir="${report.dir}" />
  &lt;/junitreport>
  &lt;fail if="tests.failed">
  &lt;/fail>
 &lt;/target>
&lt;/project></pre><br /><br />以后，你只需要在work目录下执行一个简单的 <strong>ant</strong> 命令就能轻松运行整个测试了。
          <br/>
          <span style="color:red;">
            <a href="http://tailsherry.javaeye.com/blog/191539#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/115' target='_blank'><span style="color:red;font-weight:bold;">JavaEye图灵杯第2届问答大赛开始了！8月4日至8月17日，奖品丰厚！</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Fri, 09 May 2008 18:41:00 +0800</pubDate>
        <link>http://tailsherry.javaeye.com/blog/191539</link>
        <guid>http://tailsherry.javaeye.com/blog/191539</guid>
      </item>
      <item>
        <title>做一个合格的项目经理</title>
        <author>tailsherry</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://tailsherry.javaeye.com">tailsherry</a>&nbsp;
          链接：<a href="http://tailsherry.javaeye.com/blog/187999" style="color:red;">http://tailsherry.javaeye.com/blog/187999</a>&nbsp;
          发表时间: 2008年04月29日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>在中国的软件业，项目经理的名词往往都会和技术经理混淆，或者说项目经理&asymp;技术经理，这种想法的我想应该不在少数。在一个项目中，大家过多的看重的是技术，素不知项目经理应该是管理+技术+业务的结合体，他们的比重用8:1:1，或者干脆是8:0:2，项目管理在一个项目经理的角色中占有的比重应该是很重的。</p>
<p>&nbsp;</p>
<p>传统的软件开发流程，假设一个项目从需求开始，一般会经历需求定义-&gt;设计-&gt;编码-&gt;测试-&gt;发布这几个主要的阶段，而每一个阶段都涵盖了启动-&gt;计划-&gt;执行-&gt;监控-&gt;收尾的5个过程，其实这也是贯穿整个项目的5个过程。</p>
<p>&nbsp;</p>
<h2>一、计划</h2>
<p>做任何事如果事前都有一个详细周密的计划，往往都能达到事半功倍的效果，软件项目管理也应如此。一个好的项目计划直接影响到整个项目的进度和质量。一般，我们可以把计划划分为范围计划、时间计划、资源计划、成本计划、风险计划、质量计划和沟通计划。</p>
<p>&nbsp;</p>
<p><span style="color: #0000ff;"><strong>范围计划</strong></span>是所有计划的基础，也是整个项目管理的核心，一个好的项目计划就意味着一个项目好的开始。要将一个项目的范围定义全面，一般包括以下几点：<br />1. 总体描述<br />对项目的整体概括地描述，要求字眼尽量详细清晰，能让阅读者马上领会项目的主要功能要求等。<br />2. 交付物<br />最终提交到客户那里的产品及服务，如成型产品，设计说明书，安装操作指南，硬件设备等。<br />3. 验收标准<br />一般就功能，性能，安全性，设计风格等方面的标准。<br />4. 用户方责任<br />指明用户方需要做到的内容，一般这一点可以忽略，合并到假定条件中去说明。<br />5. 假定条件<br />一些项目开始或运行的条件，如硬件设备到位，需求明确等。<br />6. 约束<br />对项目的限制约束，如工期限制，人力资源调配等。</p>
<p>范围计划，往往可以形成一个标准的范围说明书。当然，仅仅上面这些应该还是不够的，我们要学会编制WBS(Work Breandown Structure，工作分解结构)，一般用到的软件就是M$ Project了，这是一个比较复杂的过程，也是整个项目管理的核心，如何将工作、时间、资源等分解得合理、细致，是一件费时费力而且反复的工作。制作一份好的WBS直接影响到后面的范围计划、时间计划、资源计划以及成本计划。</p>
<p>&nbsp;</p>
<p><strong><span style="color: #0000ff;">时间(进度)计划</span></strong>如何写？一般为了全面地去描述一个项目的时间计划，我们可以基于已有的WBS从两方面来描述：项目里程碑和时间进度表。什么叫做项目里程碑？如：<br />1. Kick-off meeting&nbsp; 1月8日<br />2. 批准要求<br />...<br />6. 通过集成测试&nbsp; 3月10日<br />7. 完成系统部署&nbsp; 3月15日<br />8. 通过验收&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 3月18日</p>
<p>这些个里程碑应该说也是WBS中按时间划分后，几个比较重要的活动结点。至于时间进度表，只要从WBS中摘抄下来就OK了。</p>
<p>&nbsp;</p>
<p><strong><span style="color: #0000ff;">资源计划</span></strong>为何物？资源计划也就是我们常提到的人力资源分配，一般我们也分为两点来阐述：组织结构及角色/小组责任、人员投入时间安排。注意这里的人员投入时间安排和时间计划中的时间进度表是有区别的，前者是基于活动，而后者是基于资源角色展开的。</p>
<p>&nbsp;</p>
<p>对于软件项目，<strong><span style="color: #0000ff;">成本计划</span></strong>主要是人力资源成本，这是主要的一块，当然，也有开发电脑、开发工具、培训等费用项目，这些都应该用表格列出来。人力资源成本可以用M$ Project工具来生成。</p>
<p>&nbsp;</p>
<p>至于<span style="color: #0000ff;"><strong>风险计划</strong></span>，应该属于风险管理的范畴，但是拟在计划草案中便可以提前减少风险的发生。一般风险级别由风险发生的概率和风险影响的程度两个因素来确定。比如说，小王要离职跳槽，其概率估算为80%，而且由于他是项目的骨干人员，离职产生的风险影响程度就相应的很高为90%，如此一来，小王离职的这个事件就是一个高风险的事情了，作为项目经理的你，可以尝试提高小王的工资(项目成本增加)或者换人来处理这个风险。当然还有很多种风险，如政策、法律、市场、上级领导、技术能力等，这些都是应该考虑到的风险，风险必须明确一个责任人。</p>
<p>&nbsp;</p>
<p>同样，<strong><span style="color: #0000ff;">质量计划</span></strong>也是非常重要的，直接会影响到项目的质量管理成败。质量计划首先要定义质量环节以及对应的质量标准，如系统需求报告、软件需求规格说明书、软件测试、软件编码等环节，都要有一个标准。既然有了这些标准，然后我们就要列明一些重要的质量问题，保证质量，针对这些问题提出保证措施和责任人。</p>
<p>&nbsp;</p>
<p>相对于其他计划，<strong><span style="color: #0000ff;">沟通计划</span></strong>就是一些软性的指标了。根据项目的特点约定项目全体干系人(包括组员，客户，公司领导，公司其他部门等所有与项目相关的人员或组织)的沟通模式，可以是例行的各种会议、审查报告、情况通报，也可以是某一预定的工作结点或某一情况一旦发生应即进行的某些沟通活动。常见的沟通计划，就如我们项目中时常接触到的周例会、月报告，季度报告，甚至日报告，这些都可以列为沟通计划的一部分。</p>
<p><br />由此看来，一个详细周密的项目计划非常的重要，作为一个项目经理，不要怕做计划，认为那是一件耗时吃力的事情，但是只要你按着你定的计划来，不断调整和跟进，严格按照计划来行事，你必定能成功，任何事情都是熟能生巧的过程，只要敢于迈出第一步。</p>
<h2><br />二、执行</h2>
<p>项目执行的保障，主要包括质量管理、团队建设和沟通管理。对于质量管理和沟通管理，都隐隐约约在计划部分描述到了，我也不详细的再说什么了。至于团队建设，这应该是所有管理者应该兼顾到的，一个具有凝聚力的开发团队能大大的提高整个项目的绩效水平，比如说远离团队政治、以身作则、绩效评估、轻松的工作氛围、团体腐败等，都是团队建设的常用措施。</p>
<h2><br />三、监控</h2>
<p>常见的监控措施如日常工作检查、阶段评审、变更控制、风险管理等。其中变更控制和风险管理对大家来说是相对忽略的地方。</p>
<p>&nbsp;</p>
<p>变更控制是基于一个完备、详细、准确的项目计划的，针对计划变更采用有效的措施来控制变更，避免失控，总结经验教训，不能一犯再犯。项目中一个环节的变更，可能直接影响到范围、时间、费用，甚至质量，项目变更管理的目的就是协调它们之间的关系，找出一个平衡点。</p>
<p>&nbsp;</p>
<p>对于风险管理，其实已经在风险计划中已经提到了，不管怎样，提前预知、识别和分析风险是风险管理的最好的方式。在风险实际发生时，要懂得如何规避风险或减轻风险。</p>
<p>&nbsp;</p>
<p><br />以上是我最近PMP培训的总结，说句实在话，做了许久的PM工作，从来都没有这么清晰的看清楚项目管理的骨架。欢迎大家与我共享交流软件项目管理的经验。</p>
          <br/>
          <span style="color:red;">
            <a href="http://tailsherry.javaeye.com/blog/187999#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/115' target='_blank'><span style="color:red;font-weight:bold;">JavaEye图灵杯第2届问答大赛开始了！8月4日至8月17日，奖品丰厚！</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Tue, 29 Apr 2008 12:11:55 +0800</pubDate>
        <link>http://tailsherry.javaeye.com/blog/187999</link>
        <guid>http://tailsherry.javaeye.com/blog/187999</guid>
      </item>
      <item>
        <title>如何让Struts2.0下载文件流</title>
        <author>tailsherry</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://tailsherry.javaeye.com">tailsherry</a>&nbsp;
          链接：<a href="http://tailsherry.javaeye.com/blog/178351" style="color:red;">http://tailsherry.javaeye.com/blog/178351</a>&nbsp;
          发表时间: 2008年04月01日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>在实际web应用中，大部分文件下载都是通过url文件链接直接下载的，同样在Struts中也可以这样实现。但是考虑到盗链，跨服务器访问等因素，直接文件流下载也是必要的。那么，在Struts2.0中如何实现数据流下载呢？</p>
<p><br />Struts2.0默认支持多种格式的result type，stream即是其中的一种。如果我这里要实现一个Generate Report的功能，将Report存放在一个InputStream里面，Action的示例代码内容如下：</p>
<pre name="code" class="java">package com.test;

import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

public class ReportsAction extends ActionSupport {

    // 定义HTML类型的Report
    private static final int HTML_TYPE = 0;
    
    // 定义EXCEL类型的Report
    private static final int EXCEL_TYPE = 1;
    
    // Report类型
    private int reportType;
    
    // Report输出流
    public InputStream reportStream;

    // 输出流Content Type
    public String contentType;
    
    // 输出流的生成的文件名
    public String fileName;
    
    public ReportsAction() {        
    }

    public String getContentType() {
        return contentType;
    }

    public String getFileName() {
        return fileName;
    }

    public InputStream getReportStream() {
        return reportStream;
    }

    public int getReportType() {
        return reportType;
    }

    public void setReportType(int reportType) {
        this.reportType = reportType;
    }

    public String generateReport() {
        switch (reportType) {
        case HTML_TYPE:
            // 获取HTML流
            reportStream = service.getHtmlStream();
            // contentType为MIME定义的，详细的内容可参考下面的这个网站：http://www.w3schools.com/media/media_mimeref.asp
            contentType = "text/html";
            // inline表示文件直接输出到网页上，不出现保存打开对话框
            fileName = "inline; filename=\"Report.htm\"";
            break;
        case EXCEL_TYPE:
            // 获取EXCEL流
            reportStream = service.getExcelStream();
            // contentType设定
            contentType = "application/vnd.ms-excel";
            // attachment表示网页会出现保存、打开对话框
            fileName = "attachment; filename=\"Report.xls\"";
            break;
        default:
            ;
        }
        return SUCCESS;
    }
        
}</pre>
<p>&nbsp;</p>
<p>当然，Struts的配置也是非常重要的，如下：</p>
<pre name="code" class="xml">&lt;!DOCTYPE struts PUBLIC
          "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
          "http://struts.apache.org/dtds/struts-2.0.dtd"&gt;
&lt;struts&gt;
    &lt;package name="root" namespace="/"&gt;
        &lt;action name="generateReport" method="generateReport"
	  class="com.test.ReportsAction"&gt;
            	    &lt;result name="success" type="stream"&gt;
                      &lt;!-- 对应ReportsAction中的属性contentType --&gt;
	        &lt;param name="contentType"&gt;${contentType}&lt;/param&gt;
                      &lt;!-- ReportsAction中对应的InputStream的属性名 --&gt;
	        &lt;param name="inputName"&gt;reportStream&lt;/param&gt;
                      &lt;!-- 对应ReportsAction中的属性fileName，定义流输出格式 --&gt;
	        &lt;param name="contentDisposition"&gt;${fileName}&lt;/param&gt;
                      &lt;!-- 定义bufferSize，可选 --&gt;
	        &lt;param name="bufferSize"&gt;1024&lt;/param&gt;
	    &lt;/result&gt;
                  ...
        &lt;/action&gt;
    &lt;/package&gt;
&lt;/struts&gt;</pre>
<p>&nbsp;</p>
<p>页面部分我就不详细写了，比如，可以在一个form的提交中绑定这个action，普通的网页调用代码如下：</p>
<pre name="code" class="html">&lt;form id="generateReportForm" action="generateReport.action" method="POST"&gt;
&lt;/form&gt;</pre>
<p>&nbsp;</p>
<p>当然，你也可以用一个Struts中的标签来实现，示例代码如下：</p>
<pre name="code" class="xml">&lt;s:form theme="simple" validate="true"&gt;
    &lt;s:submit cssStyle="width:160px" action="generateReport" value="Generate HTML Report" /&gt;
    &lt;s:url id="generateUrl" action="generateReport"&gt;&lt;/s:url&gt;
    &lt;s:a href="%{generateUrl}"&gt;&lt;s:textfield name="tail.button.generatexls" /&gt;&lt;/s:a&gt;
&lt;/s:form&gt;</pre>
<p>&nbsp;</p>
          <br/>
          <span style="color:red;">
            <a href="http://tailsherry.javaeye.com/blog/178351#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/115' target='_blank'><span style="color:red;font-weight:bold;">JavaEye图灵杯第2届问答大赛开始了！8月4日至8月17日，奖品丰厚！</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Tue, 01 Apr 2008 12:20:04 +0800</pubDate>
        <link>http://tailsherry.javaeye.com/blog/178351</link>
        <guid>http://tailsherry.javaeye.com/blog/178351</guid>
      </item>
      <item>
        <title>组合模式(Composite)</title>
        <author>tailsherry</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://tailsherry.javaeye.com">tailsherry</a>&nbsp;
          链接：<a href="http://tailsherry.javaeye.com/blog/178047" style="color:red;">http://tailsherry.javaeye.com/blog/178047</a>&nbsp;
          发表时间: 2008年03月31日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p><strong><span style="font-family: Arial;"><strong><span style="font-size: medium;"><span style="font-size: small;">一、介绍</span></span></strong></span><br /></strong>组合模式主要用于具有父子关系，或局部-整体关系的情况下。此模式使局部对象和组合对象的使用具有一致性，或者说，我们可以用一个类来统一表示一个对象或组合对象。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><span style="font-size: medium;"><strong><span style="font-size: small;">二、实例</span></strong></span>&nbsp;</p>
<p>首先，定义一个抽象的基类，其中定义了局部对象和组合对象的一些基本元素。</p>
<pre name="code" class="java">import java.util.LinkedList;
import java.util.ListIterator;

public abstract class TeaBags {  
   LinkedList teaBagList; 
   TeaBags parent;
   String name;
    
   public abstract int countTeaBags();
   
   public abstract boolean add(TeaBags teaBagsToAdd);
   public abstract boolean remove(TeaBags teaBagsToRemove);
   public abstract ListIterator createListIterator();
   
   public void setParent(TeaBags parentIn) {
       parent = parentIn;
   }
   public TeaBags getParent() {
      return parent;
   }
   
   public void setName(String nameIn) {
       name = nameIn;
   }
   public String getName() {
       return name;
   }
}</pre>
<p>&nbsp;</p>
<p>此段代码中，包含了组合信息的List对象（表示整体）和上下关联的parent对象（指示局部-整体的关系）。</p>
<p>&nbsp;</p>
<p>现在，我们基于以上类，定义一个局部对象，其基本定义如下：</p>
<pre name="code" class="java">import java.util.ListIterator;

public class OneTeaBag extends TeaBags { 
    public OneTeaBag(String nameIn) {
        this.setName(nameIn);
    }
    
    public int countTeaBags() {
        return 1;
    }
   
    public boolean add(TeaBags teaBagsToAdd) {
        return false;
    }
    public boolean remove(TeaBags teaBagsToRemove) {
        return false;
    }
    public ListIterator createListIterator() {
        return null;
    }
}</pre>
<p>&nbsp;</p>
<p>然后，定义个一组合对象，定义如下：</p>
<pre name="code" class="java">import java.util.LinkedList;
import java.util.ListIterator;

public class TinOfTeaBags extends TeaBags {  
   public TinOfTeaBags(String nameIn) {
       teaBagList = new LinkedList();
       this.setName(nameIn);
   }
   
   public int countTeaBags() {
       int totalTeaBags = 0;
       ListIterator listIterator = this.createListIterator();
       TeaBags tempTeaBags;
       while (listIterator.hasNext()) {
           tempTeaBags = (TeaBags)listIterator.next();
           totalTeaBags += tempTeaBags.countTeaBags();
       }
       return totalTeaBags;
   }
   
   public boolean add(TeaBags teaBagsToAdd) {
       teaBagsToAdd.setParent(this);
       return teaBagList.add(teaBagsToAdd);
   }
   
   public boolean remove(TeaBags teaBagsToRemove) {
       ListIterator listIterator = 
           this.createListIterator();
       TeaBags tempTeaBags;
       while (listIterator.hasNext()) {
           tempTeaBags = (TeaBags)listIterator.next();
           if (tempTeaBags == teaBagsToRemove) {
               listIterator.remove();
               return true;
           }
       }
       return false;
   }
   
   public ListIterator createListIterator() {
       ListIterator listIterator = teaBagList.listIterator();
       return listIterator;
   }
}</pre>
<p>&nbsp;</p>
<p>注意，组合对象TinOfTeaBags实现了add, remove等对局部对象操作的方法，这些个动作也是联系局部和整体的关键。</p>
<p>&nbsp;</p>
<p>最后写一个测试类，来使用我们预先布置好的局部类和整体类：</p>
<pre name="code" class="java">class TestTeaBagsComposite {

   public static void main(String[] args) {
       System.out.println("Creating tinOfTeaBags");
       TeaBags tinOfTeaBags = 
           new TinOfTeaBags("tin of tea bags");
       System.out.println("The tinOfTeaBags has " + 
                           tinOfTeaBags.countTeaBags() + 
                           " tea bags in it.");

       System.out.println(" ");       

       System.out.println("Creating teaBag1");
       TeaBags teaBag1 = new OneTeaBag("tea bag 1");
       System.out.println("The teaBag1 has " + 
                           teaBag1.countTeaBags() + 
                           " tea bags in it.");

       System.out.println(" ");       

       System.out.println("Creating teaBag2");
       TeaBags teaBag2 = new OneTeaBag("tea bag 2");
       System.out.println("The teaBag2 has " + 
                           teaBag2.countTeaBags() + 
                           " tea bags in it."); 

       System.out.println(" ");

       System.out.println(
         "Putting teaBag1 and teaBag2 in tinOfTeaBags");
       if (tinOfTeaBags.add(teaBag1)) {
          System.out.println(
            "teaBag1 added successfully to tinOfTeaBags");
       } else {
          System.out.println(
             "teaBag1 not added successfully tinOfTeaBags");
       } 
       if (tinOfTeaBags.add(teaBag2)) {
          System.out.println(
            "teaBag2 added successfully to tinOfTeaBags");
       } else {
          System.out.println(
            "teaBag2 not added successfully tinOfTeaBags");
       }
       System.out.println("The tinOfTeaBags now has " + 
                           tinOfTeaBags.countTeaBags() + 
                           " tea bags in it.");
       
       System.out.println(" ");
       
       System.out.println("Creating smallTinOfTeaBags");
       TeaBags smallTinOfTeaBags = 
         new TinOfTeaBags("small tin of tea bags");
       System.out.println("The smallTinOfTeaBags has " + 
                           smallTinOfTeaBags.countTeaBags() + 
                           " tea bags in it.");
       System.out.println("Creating teaBag3");
       TeaBags teaBag3 = 
         new OneTeaBag("tea bag 3");
       System.out.println("The teaBag3 has " + 
                           teaBag3.countTeaBags() + 
                           " tea bags in it.");
       System.out.println("Putting teaBag3 in smallTinOfTeaBags");
       if (smallTinOfTeaBags.add(teaBag3)) {
           System.out.println(
             "teaBag3 added successfully to smallTinOfTeaBags");
       } else {
           System.out.println(
             "teaBag3 not added successfully to smallTinOfTeaBags");
       }
       System.out.println("The smallTinOfTeaBags now has " + 
                           smallTinOfTeaBags.countTeaBags() + 
                           " tea bags in it.");
       
       System.out.println(" "); 
       
       System.out.println(
         "Putting smallTinOfTeaBags in tinOfTeaBags");
       if (tinOfTeaBags.add(smallTinOfTeaBags)) {
           System.out.println(
             "smallTinOfTeaBags added successfully to tinOfTeaBags");
       } else {
           System.out.println(
             "smallTinOfTeaBags not added successfully to tinOfTeaBags");
       }
       System.out.println("The tinOfTeaBags now has " + 
                           tinOfTeaBags.countTeaBags() + 
                           " tea bags in it.");
       
       System.out.println(" ");
       
       System.out.println("Removing teaBag2 from tinOfTeaBags");
       if (tinOfTeaBags.remove(teaBag2)) {
           System.out.println(
             "teaBag2 successfully removed from tinOfTeaBags");
       } else {
           System.out.println(
             "teaBag2 not successfully removed from tinOfTeaBags");
       }
       System.out.println("The tinOfTeaBags now has " + 
                           tinOfTeaBags.countTeaBags() + 
                           " tea bags in it.");
   }
} </pre>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><span style="font-size: small;"><span style="font-size: medium; font-family: Arial;"><strong>三、分析</strong></span><br /></span>通过以上实例，我们可以总结出以下几个关键的角色：</p>
<ul>
<li><strong>Compoent</strong> 抽象类或接口，用来定义局部-整体的格局，定义公用基本属性，集合属性以及集合对象，定义抽象的add, remove等对局部对象的集合操作方法。</li>
<li><strong>Leaf</strong> 局部对象，或者说是叶子节点，如例子中的OneTeaBag对象。</li>
<li><strong>Composite</strong> 组合对象，实现add, remove等对局部对象的集合操作。</li>
</ul>
<p>利用组合模式，可以简化客户代码，将组合对象和局部对象统一视为一种对象，忽略它们之间的不同。</p>
<p>&nbsp;</p>
<p>如果从数据结构的角度考虑，我们可以把这种对象看成树状结构中的一个节点，它可以是一个叶子节点，也可以是一个分支节点，层层递归，互相变化，但是他们都是这个树的组成部分，可以共享树结点一些共有的属性。</p>
<p>&nbsp;</p>
          <br/>
          <span style="color:red;">
            <a href="http://tailsherry.javaeye.com/blog/178047#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/115' target='_blank'><span style="color:red;font-weight:bold;">JavaEye图灵杯第2届问答大赛开始了！8月4日至8月17日，奖品丰厚！</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Mon, 31 Mar 2008 14:48:10 +0800</pubDate>
        <link>http://tailsherry.javaeye.com/blog/178047</link>
        <guid>http://tailsherry.javaeye.com/blog/178047</guid>
      </item>
      <item>
        <title>鬼子们的钱不好赚</title>
        <author>tailsherry</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://tailsherry.javaeye.com">tailsherry</a>&nbsp;
          链接：<a href="http://tailsherry.javaeye.com/blog/176181" style="color:red;">http://tailsherry.javaeye.com/blog/176181</a>&nbsp;
          发表时间: 2008年03月26日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          自从去年踏入这家软件外包企业，自己深深地被这种新型的软件工业模式所吸引。陌生的鬼子鬼调，弹性的工作制度，和新兴的软件技术都一齐涌过来。<br /><br />可是工作一年以后，除了发现自己的英语水平有了一定的提高，其他的一切，似乎还是停留在原地，悲乎？抑或说自己有了足够的时间去琢磨自己的技术，技术上有了更大范围的提高，喜乎？<br /><br />鬼子们为了省钱而选择了具有廉价劳动力的中国，当我们每月从老板那里抠来的微弱的报酬的时候，心里不知道是什么滋味。在我们看来，鬼子们的确是很大方了，不管你是junior还是senior都是25美金/小时，爽啊！一人天8小时就是200美金，一人月就是几乎25000人民币！满眼都是$和￥... 但是，自己拿到的却约到其中的1/3多。<br /><br />钱少了，认了！至少老板们也要盈利，而且还要养活公司这个大机器。听HR的老大说，今年的工资不会调整了，心都凉了半截。跳槽吧？时间太短，工作才刚1年就跳槽，下家会怎么看你？用情不专，再怎么个才华横溢，人家也不敢要你啊。<br /><br />郁闷接踵而来，鬼子们经常对我们发脾气，说这个做得不好，那个做得太慢！而事实上，我们的人在一个接一个的起义和辞职，工作量却一天不比一天少。能怪谁？终于有一天，鬼子们发毛了: "Starting tomorrow, I want *daily* reports sent to me at the end of your day detailing progress in the standard form that we agreed."<br /><br />一句感叹: "误入歧途，鬼子们的钱不好赚！"
          <br/>
          <span style="color:red;">
            <a href="http://tailsherry.javaeye.com/blog/176181#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/115' target='_blank'><span style="color:red;font-weight:bold;">JavaEye图灵杯第2届问答大赛开始了！8月4日至8月17日，奖品丰厚！</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Wed, 26 Mar 2008 11:13:59 +0800</pubDate>
        <link>http://tailsherry.javaeye.com/blog/176181</link>
        <guid>http://tailsherry.javaeye.com/blog/176181</guid>
      </item>
      <item>
        <title>基于Java线程实现后台定时监控</title>
        <author>tailsherry</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://tailsherry.javaeye.com">tailsherry</a>&nbsp;
          链接：<a href="http://tailsherry.javaeye.com/blog/176152" style="color:red;">http://tailsherry.javaeye.com/blog/176152</a>&nbsp;
          发表时间: 2008年03月26日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>熟悉编写JavaScript的人，都习惯在页面写入setTimeOut来实现web页面的定时监控或事务处理。但是如何在Java服务端来实现这样一个监控机制呢？一般大家都会想到线程。但是一直以来，都没有亲身动手实践过。趁着工作间隙，自己也搬出一段代码来，与大家一起分享线程编程的神奇魔力。 <br /></p>
<p>首先创建一个基本抽象类SchedThread，代码内容如下：</p>
<pre name="code" class="java">package com.test;

/**
 * 基于Java线程实现后台定时监控 &lt;p/&gt; Created: Mar 26, 2008 10:08:43 &lt;p/&gt;
 * &lt;h4&gt;http://tailsherry.javaeye.com&lt;/h4&gt;
 * &lt;p/&gt;
 * 
 * @author TailSherry
 */
public abstract class SchedThread
{
    protected static final long NEVER = Long.MAX_VALUE;

    // 定义一个线程锁，保证当前只有一个工作在操作中
    private final Object lock = new Object();

    // 定义一个Thread变量
    private Thread thread;

    // 控制线程循环的开关
    private boolean active = true;

    // 定义一个毫秒级的时间变量，指示何时执行下一个操作
    private long nextTime;

    /**
     * 定义个一个抽象的方法用来获取下一个执行操作的时间，可使用NEVER
     */
    protected abstract long getNextTime();

    /**
     * 定义一个抽象的方法，让子类来定义具体的工作过程
     */
    protected abstract void executeWork();

    protected String getName()
    {
        return getClass().getName();
    }

    /**
     * 启动线程
     */
    public void start()
    {
        thread = new Thread(new Runnable()
        {
            public void run()
            {
                runInternal();
            }
        }, getName());
        thread.start();
    }

    /**
     * 强迫停止线程，跳出for循环
     */
    public void stop() throws InterruptedException
    {
        synchronized (lock)
        {
            active = false;
            lock.notify();
        }
        thread.join();
    }

    /**
     * 此方法可以在任何时候激活当前线程，让线程进入工作执行环节
     */
    public void workAdded(long time)
    {
        synchronized (lock)
        {
            if (time &lt; nextTime)
            {
                // 立刻激活线程工作继续运行
                lock.notify();
            }
        }
    }

    /**
     * 线程监测控制逻辑部分
     */
    private void runInternal()
    {
        // 无限循环
        for (;;)
        {
            // 该过程忽略了所有的Exception，以保证线程不会因此而中断
            try
            {
                synchronized (lock)
                {
                    nextTime = getNextTime();
                    // 获得时间区间，即要等待的时间段
                    long interval = nextTime - System.currentTimeMillis();
                    if (interval &gt; 0)
                    {
                        try
                        {
                            lock.wait(interval);
                        }
                        catch (InterruptedException e)
                        {
                            // 忽略此Exception
                        }
                    }
                    // 如果active为false，强制中断
                    if (!active)
                    {
                        break;
                    }
                }
                // 执行具体的工作
                executeWork();
            }
            catch (Throwable t)
            {
                try
                {
                    Thread.sleep(10000);
                }
                catch (InterruptedException ie)
                {
                    // 忽略此Exception
                }
            }
        }
    }
}</pre>
<p>&nbsp;</p>
<p>以上这个类非常关键，基本上已经实现了所有的控制逻辑，如此再扩展出一个实现类出来，比如这里我写了一个模拟实现类MyDataGenerator，大家可以参考一下：</p>
<pre name="code" class="java">package com.test;

public class MyDataGenerator extends SchedThread {
    protected void executeWork() {
        System.out.println("Execute work ...");
    }

    protected long getNextTime() {
        return System.currentTimeMillis() + 2000L;
    }

    public static void main(String argv[]) {
        MyDataGenerator generator = new MyDataGenerator();
        generator.start();
    }
}</pre>
<p>&nbsp;&nbsp;</p>
<p>当然这里没有使用workAdded和stop等功能，可以留给大家扩展。</p>
<p>&nbsp;</p>
          <br/>
          <span style="color:red;">
            <a href="http://tailsherry.javaeye.com/blog/176152#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/115' target='_blank'><span style="color:red;font-weight:bold;">JavaEye图灵杯第2届问答大赛开始了！8月4日至8月17日，奖品丰厚！</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Wed, 26 Mar 2008 10:37:34 +0800</pubDate>
        <link>http://tailsherry.javaeye.com/blog/176152</link>
        <guid>http://tailsherry.javaeye.com/blog/176152</guid>
      </item>
      <item>
        <title>灵活处理select/option对象中的特殊字符</title>
        <author>tailsherry</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://tailsherry.javaeye.com">tailsherry</a>&nbsp;
          链接：<a href="http://tailsherry.javaeye.com/blog/175664" style="color:red;">http://tailsherry.javaeye.com/blog/175664</a>&nbsp;
          发表时间: 2008年03月25日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>现在，已经习惯用js去构造网页上的组件，对于普通的下拉框，我们习惯用以下js来构造成页面组件：</p>
<pre name="code" class="js">var select = document.createElement("select");
var option = document.createElement("option");
option.innerHTML = "Option Text";
option.value = "Value";
select.appendChild(option);</pre>
<p>&nbsp;</p>
<p>这里不能用option.text = "Option Text"来给option定义显示文本，原因很简单：IE在这种情况下不支持这样的属性赋值，必须使用innerHTML来替换，否则，option的text值在IE下无法正常显示在页面组件上。</p>
<p>&nbsp;</p>
<p>如此这般，代码在IE和Firefox下都可以顺利的运行，一切都很正常。但是，潜在的问题也因此产生。</p>
<p>&nbsp;</p>
<p>如果我们要设定的text中如果含有特殊字符，如常见的&amp;,&lt;,&gt;等，直接赋到innerHTML中，在IE下所显示出来的text就是空的了。但是，在Firefox下似乎一切都比较正常，但翻译出来的innerHTML中将特殊字符都转换成了&amp;amp;&amp;gt,&amp;lt等。</p>
<p>&nbsp;</p>
<p>有没有一种折衷的方案，在任意情况下，都能在IE和Firefox下按原文显示或获取一个下拉框的值或文本呢?</p>
<p>&nbsp;</p>
<p>查阅资料后，终于找到一种方案，这里引入了一个比较过时的Syntax可以提供给大家参考：</p>
<pre name="code" class="js">new Option([text[, value[, defaultSelected[, selected]]]]);</pre>
<p>&nbsp;</p>
<p>不错，这就意味着我们必须用这种new对象的方式来创建option，而不是document.createElement。示例代码如下：</p>
<pre name="code" class="js">var select = document.createElement("select");
var option = new Option(“Option Text”, “Value”);
select.options[0] = option;</pre>
<p>&nbsp;</p>
<p>测试在IE和Firefox下均可以用text和value取得原始文本值。这里不提倡用innerHTML来取值，Firefox会将这个innerHTML的内容转译一下，因此也就失去了我们的本意。</p>
<p>&nbsp;</p>
<p>所以，对于一个熟练有经验的js程序员，在创建一个select组件的时候，千万记得动态创建自己的option的时候要使用这种保险的方式。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
          <br/>
          <span style="color:red;">
            <a href="http://tailsherry.javaeye.com/blog/175664#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/115' target='_blank'><span style="color:red;font-weight:bold;">JavaEye图灵杯第2届问答大赛开始了！8月4日至8月17日，奖品丰厚！</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Tue, 25 Mar 2008 09:54:22 +0800</pubDate>
        <link>http://tailsherry.javaeye.com/blog/175664</link>
        <guid>http://tailsherry.javaeye.com/blog/175664</guid>
      </item>
      <item>
        <title>Struts2+JSON+YUI构建Rich Client应用（二）</title>
        <author>tailsherry</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://tailsherry.javaeye.com">tailsherry</a>&nbsp;
          链接：<a href="http://tailsherry.javaeye.com/blog/149892" style="color:red;">http://tailsherry.javaeye.com/blog/149892</a>&nbsp;
          发表时间: 2007年12月21日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p><span style="font-family: Arial;">在UI部分，页面文件很简单，userMessage.jsp的内容如下：</span></p>
<div class="code_title"><span style="font-family: Arial;"><span class="tag">
<pre name="code" class="html">&lt;%@ page contentType="text/html; charset=UTF-8"%&gt;   
&lt;html&gt;   
    &lt;head&gt;   
        &lt;title&gt;Struts + JSONtitle&gt;   
        &lt;link rel="stylesheet" type="text/css" href="css/main.css" /&gt;   
        &lt;script type="text/javascript" src="js/utilities.js"&gt;script&gt;   
        &lt;script type="text/javascript" src="js/main.js"&gt;script&gt;   
        &lt;script type="text/javascript" src="js/message.js"&gt;script&gt;   
        &lt;script&gt;   
            YAHOO.util.Event.addListener(window, "load", function() {    
                YAHOO.TAIL.TestHome.init();    
            });
        &lt;/script&gt;   
    &lt;/head&gt;   
    &lt;body&gt;   
        &lt;div id="container"&gt;   
        &lt;/div&gt;   
    &lt;body&gt;   
&lt;html&gt; </pre>
</span></span></div>
<div class="code_title"><span style="font-family: Arial;">
<p>&nbsp;</p>
<p><span style="font-family: Arial;">从页面head部分可以看出，UI</span>主要工作的有三个js文件：main.js，message.js和utilities.js。</p>
<p>&nbsp;</p>
<p>其中，utilities.js是YUI的核心工具包；main.js主要是我自己对YUI Ajax功能的进一步封装；message.js则是页面使用的核心js文件，用来构造页面元件，和server端进行Ajax交互。其代码如下：</p>
<div class="code_title">
<pre name="code" class="js">YAHOO.namespace("TAIL");    
   
if (!YAHOO.TAIL.TestHome)    
{    
    YAHOO.TAIL.TestHome = function ()     
    {    
        var Dom = YAHOO.util.Dom;    
        var Event = YAHOO.util.Event;    
            
        var messageList;    
            
        function loadData()    
        {    
            // create user editors    
            var container = document.getElementById('container');    
            var userDiv = document.createElement('div');    
            container.appendChild(userDiv);    
            var userLabel = document.createElement('label');    
            userLabel.innerHTML = 'New Test User:';    
            userDiv.appendChild(userLabel);    
            var userEdit = document.createElement('input');    
            userEdit.type = 'text';    
            userEdit.id = 'user';    
            userDiv.appendChild(userEdit);    
            // create msg editors    
            var msgDiv = document.createElement('div');    
            container.appendChild(msgDiv);    
            var msgLabel = document.createElement('label');    
            msgLabel.innerHTML = 'New Message: ';    
            msgDiv.appendChild(msgLabel);    
            var msgEdit = document.createElement('input');    
            msgEdit.type = 'text';    
            msgEdit.id = 'message';    
            msgDiv.appendChild(msgEdit);    
            // create button    
            var button = document.createElement('input');    
            button.type = 'button';    
            button.value = 'Add Now!';    
            msgDiv.appendChild(button);    
            Event.addListener(button, 'click', addNewMessage);    
            // create message div    
            var msgListDiv = document.createElement('div');    
            msgListDiv.id = 'messagelist';    
            msgListDiv.style.color = 'blue';    
            container.appendChild(msgListDiv);    
                
            JSONRequest.sendRequest(    
                'POST',    
                'home/loadMessages.action',    
                null,    
            {    
                success: function(result)    
                {       
                    messageList = result.messageList;    
                    reloadMessageList();    
                },    
                error: function(result)     
                {    
                    alertJSONErrors(result);    
                },    
                failure: function(o)    
                {    
                    var errMsg = "Error loading message list!";    
                    alertFailure(o, null, errMsg);    
                }    
            });    
        }    
            
        function reloadMessageList() {    
            var msgListDiv = document.getElementById('messagelist');    
            if (msgListDiv) {    
                var messages = msgListDiv.childNodes;    
                // remove first    
                for (var i = messages.length - 1; i &gt;= 0; i--)     
                {    
                    msgListDiv.removeChild(messages[i]);    
                }    
                // new add    
                for (var inx in messageList) {    
                    var div = document.createElement('div');    
                    div.innerHTML = '
' + messageList[inx].userName + ': ' + messageList[inx].content;    
                    msgListDiv.appendChild(div);    
                }               
            }    
        }    
            
        function addNewMessage() {    
            var userEdit = document.getElementById('user');    
            var msgEdit = document.getElementById('message');    
            var newUser = userEdit.value;    
            var newMsg = msgEdit.value;    
            if (newUser &amp;&amp; newMsg &amp;&amp; newUser != '') {    
                // format post data    
                var postData = 'newUser=' + newUser + '&amp;newMessage=' + newMsg;    
                for (var i=0; i 
                    postData += '&amp;messageList[' + i  +'].userName=' + messageList[i].userName;    
                    postData += '&amp;messageList[' + i  +'].content=' + messageList[i].content;    
                }    
                // send add message request    
                JSONRequest.sendRequest(    
                    'POST',    
                    'home/addMessageToUser.action',    
                    postData,    
                {    
                    success: function(result)    
                    {       
                        messageList = result.messageList;    
                        reloadMessageList();    
                        userEdit.value = '';    
                        msgEdit.value = '';    
                    },    
                    error: function(result)    
                    {    
                        alertJSONErrors(result);    
                    },    
                    failure: function(o)    
                    {    
                        var errMsg = "Error adding to message list!";    
                        alertFailure(o, null, errMsg);    
                    }    
                });    
            }    
        }    
            
        return {    
            init: function() {    
                loadData();    
            }    
        };    
    }();    
} </pre>
</div>
<p>&nbsp;</p>
<p><strong>ps:</strong> 大家看这种js代码比较费劲，但是这种类oop的js编码风格，大家应该努力去模仿，毕竟这是一种好的习惯。大家可以翻开象YUI这些js原始文件出来看看，学习一下人家的js撰写风格。</p>
<p>&nbsp;</p>
<p><span style="font-family: Arial;">通过userMessage.jsp中的script代码部分，大家可以看出，YAHOO.TAIL.TestHome.init()是UI的入口。进入页面，UI运行顺序为：<span style="color: #0000ff;">与Struts Action交互 -&gt; 调用init方法 -&gt; 通过loadData()构造输入框，按钮，以及事件绑定 -&gt; 通过JSONRequest.sendRequest的Ajax的调用来取得server端的初始messageList -&gt; 调用成功之后，在UI初始构造messageList内容</span>。</span></p>
<p>&nbsp;</p>
<p><span style="font-family: Arial;">页面装载完毕之后，大家可以添加user和message然后点击Add Now按钮，这是触发addNewMessage()方法，这里单独把这部分代码重新罗列一下：</span></p>
<pre name="code" class="js">function addNewMessage() {   
    var userEdit = document.getElementById("user");   
    var msgEdit = document.getElementById("message");   
    var newUser = userEdit.value;   
    var newMsg = msgEdit.value;   
    if (newUser &amp;&amp; newMsg &amp;&amp; newUser != "") {   
        // format post data   
        var postData = "newUser=" + newUser + "&amp;newMessage=" + newMsg;   
        for (var i = 0; i &lt; messageList.length; i++) {   
            postData += "&amp;messageList[" + i + "].userName=" + messageList[i].userName;   
            postData += "&amp;messageList[" + i + "].content=" + messageList[i].content;   
        }   
        // send add message request   
        JSONRequest.sendRequest("POST", "home/addMessageToUser.action", postData, {success:function (result) {   
            messageList = result.messageList;   
            reloadMessageList();   
            userEdit.value = "";   
            msgEdit.value = "";   
        }, error:function (result) {   
            alertJSONErrors(result);   
        }, failure:function (o) {   
            var errMsg = "Error adding to message list!";   
            alertFailure(o, null, errMsg);   
        }});   
    }   
}</pre>
<p>&nbsp;</p>
<p><span style="font-family: Arial;">该function的工作过程是：<span style="color: #0000ff;">获取输入的newUser和newMsg -&gt; 构造需要提交到server端的postData串 -&gt; 提交Ajax请求 -&gt; 调用成功后，重新在UI构造messageList内容</span>。</span></p>
<p>&nbsp;</p>
<span style="font-family: Arial;">
<p><span style="font-family: Arial;">这里提交Ajax的请求不同于loadData()中的的Ajax运用，这里多了一个postData参数，而且postData中除了构造newUser和newMsg，还将messageList的内容重新一起传回到了server端。为什么？因为StrutsTestAction默认是request级别的，不会保留上一次request的状态的。</span></p>
<p>&nbsp;</p>
<p><span style="font-family: Arial;">这里构造postData中messageList部分的时候，是用类似数组的方式将List中Message对象传输到server端的。如果传递的是Map性质的对象，只需要将这里的i换成Map的key string即可，当然针对Map类型的conversion的定义也会不一样，大家可以参</span><span style="font-family: Arial;">考<a href="http://struts.apache.org/2.0.6/docs/type-conversion.html" target="_blank">Struts Type Conversion</a>。</span></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><span style="font-family: Arial;"><strong>&gt;&gt;扩展建议：</strong></span></p>
<p><span style="font-family: Arial;">这里UI部分使用的是简单的jsp文件，大家可以扩展这个应用UI部分的设计，可以结合使用Freemarker，Velocity类似的UI模板来完成构建。</span></p>
<p>&nbsp;</p>
<p><span style="font-family: Arial;">例如，如果要将Struts和Velocity联合使用，同样可以通过type="velocity"的方式来定义使用vm模板，这个是Struts2默认支持的result type。</span></p>
</span></span></div>
          <br/>
          <span style="color:red;">
            <a href="http://tailsherry.javaeye.com/blog/149892#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/115' target='_blank'><span style="color:red;font-weight:bold;">JavaEye图灵杯第2届问答大赛开始了！8月4日至8月17日，奖品丰厚！</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Fri, 21 Dec 2007 11:12:51 +0800</pubDate>
        <link>http://tailsherry.javaeye.com/blog/149892</link>
        <guid>http://tailsherry.javaeye.com/blog/149892</guid>
      </item>
      <item>
        <title>Struts2+JSON+YUI构建Rich Client应用（一）</title>
        <author>tailsherry</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://tailsherry.javaeye.com">tailsherry</a>&nbsp;
          链接：<a href="http://tailsherry.javaeye.com/blog/149891" style="color:red;">http://tailsherry.javaeye.com/blog/149891</a>&nbsp;
          发表时间: 2007年12月21日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p><span style="font-family: Arial;">Struts2的出现在Web2.0纷争的年代，以Ajax为代表的富客户端(Rich Client)应用正唱着Web2.0的主角。虽然Struts2本身对Ajax的应用也提供了自己的Ajax标签，但是这种比较牵强的支持也是赶鸭子上架，不是Struts本身的特长。</span></p>
<p>&nbsp;</p>
<p><span style="font-family: Arial;">这里，就Struts对JSON支持的技术特点，来构建一个Rich Client应用，UI层使用的是YUI工具包，详情参考Yahoo的YUI网站。<a href="http://developer.yahoo.com/yui/">http://developer.yahoo.com/yui/</a>。</span></p>
<p>&nbsp;</p>
<p><span style="font-family: Arial;">本文中所涉及的Web应用的大致结构图如下：</span></p>
<p><a href="../../../upload/picture/pic/6492/025133d4-499d-41c0-bf38-98f058a26cac.jpg"><img src="../../../upload/picture/pic/6492/025133d4-499d-41c0-bf38-98f058a26cac.jpg" height="525" alt="" style="width: 462px; height: 576px" width="466" /></a></p>
<p>&nbsp;</p>
<p><span style="font-family: Arial;">先一睹为快，该应用的实际运行界面如下：</span></p>
<p><img src="../../../upload/picture/pic/6493/4d012c27-ab8b-4f51-a4ca-ff66a877234a.jpg" alt="" /></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><span style="font-family: Arial;"><span style="font-family: Arial;">页面功能大致是，</span>用户加入一个New Test User和New Message，然后点击按钮Add Now，页面无刷新添加新加入的消息。</span></p>
<p>&nbsp;</p>
<p><span style="font-family: Arial;">首先，建立一个Struts的Action类StrutsTestAction，代码如下：</span></p>
<div class="code_title"><span style="font-family: Arial;"><span>
<pre name="code" class="java">package com.tail.test.actions;   
  
import java.util.ArrayList;   
import java.util.List;   
  
//import org.apache.log4j.Logger;   
  
import com.opensymphony.xwork2.ActionSupport;   
import com.tail.test.objects.Message;   
  
public class StrutsTestAction extends BaseAction {   
    // Our logger.   
//    private static Logger logger = Logger.getLogger(StrutsTestAction.class);   
       
    private String newUser;   
       
    private String newMessage;   
       
    private List&lt;Message&gt; messageList;   
  
    public List&lt;Message&gt; getMessageList() {   
        return messageList;   
    }   
  
    public void setMessageList(List&lt;Message&gt; messageList) {   
        this.messageList = messageList;   
    }   
  
    public String getNewMessage() {   
        return newMessage;   
    }   
  
    public void setNewMessage(String newMessage) {   
        this.newMessage = newMessage;   
    }   
  
    public String getNewUser() {   
        return newUser;   
    }   
  
    public void setNewUser(String newUser) {   
        this.newUser = newUser;   
    }   
  
    @Override  
    public String execute() throws Exception {         
        return super.execute();   
    }   
       
    public String loadMessages() {   
        messageList = new ArrayList&lt;Message&gt;();   
        messageList.add(new Message("tail", "This is an piece of initial message."));   
        return result(SUCCESS);   
    }   
       
    public String addMessageToUser() {   
        messageList.add(new Message(newUser, newMessage));   
        //logger.debug("Add user='" + newUser + "', message='" + newMessage + "'");   
        return result(SUCCESS);   
    }   
} </pre>
</span></span></div>
<div class="code_title"><span style="font-family: Arial;">
<p>&nbsp;</p>
<p>其中，newUser和newMessage分别对应界面上的New Test User和New Message这两个输入框中的value。messageList则是下方显示的消息内容，注意messageList本身内含多个Message对象的List，Message类的定义如下：</p>
<div class="code_title">
<pre name="code" class="java">package com.tail.test.objects;   
  
public class Message {   
    private String userName;   
  
    private String content;   
  
    public String getContent() {   
        return content;   
    }   
  
    public void setContent(String content) {   
        this.content = content;   
    }   
  
    public String getUserName() {   
        return userName;   
    }   
  
    public void setUserName(String userName) {   
        this.userName = userName;   
    }   
  
    public Message() {   
  
    }   
  
    public Message(String userName, String content) {   
        this.userName = userName;   
        this.content = content;   
    }   
}  </pre>
</div>
<p>&nbsp;</p>
<p>loadMessages()方法负责初始化装载数据，比如说我们从数据库或第三方资源中取出数据来初始化现有的List，它在页面上的实现，实际上也是结合Ajax来进行的。</p>
<p>&nbsp;</p>
<p><span style="font-family: Arial;">addMessageToUser()方法对应了界面上Add Now按钮的动作内容，他们中间的交互过程也是通过Ajax来完成的，这也是这个应用的核心所在。</span></p>
<p>&nbsp;</p>
<p><span style="font-family: Arial;">注意，这里的Struts Action Bean本身的默认的excute方法也被override过来了，而方法体本身是空的，这里这样做的目的就是，可以让界面操作用户一进入此页面，界面能马上构造出来，而不用等待服务器端的数据装载和返回，在初始装载数据量较大的时候，这一点尤其重要。这一点，大家可以结合后部分讲到的YUI部分的代码来体会。</span></p>
<p>&nbsp;</p>
<p><span style="font-family: Arial;">现在已经有了StrutsTestAction这个工作类，那么这里的Struts核心的配置文件struts.xml也显得更加重要。</span></p>
<div class="code_title"><span style="font-family: Arial;"><span>
<pre name="code" class="java">&lt;?xml version="1.0" encoding="UTF-8"?&gt;  
&lt;!DOCTYPE struts PUBLIC   
    "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"   
    "http://struts.apache.org/dtds/struts-2.0.dtd"&gt;  
&lt;struts&gt;  
    &lt;package name="test" namespace="/" extends="json-default"&gt;  
        &lt;global-results&gt;  
            &lt;!-- any exceptions should redirect to the exception page --&gt;  
            &lt;result name="exception" type="redirect-action"&gt;  
              &lt;param name="actionName"&gt;exception&lt;/param&gt;  
              &lt;param name="namespace"&gt;/&lt;/param&gt;  
            &lt;/result&gt;  
        &lt;/global-results&gt;  
       
        &lt;action name="exception" class="com.tail.test.actions.ExceptionAction"&gt;  
            &lt;result&gt;/exception.jsp&lt;/result&gt;  
        &lt;/action&gt;  
       
        &lt;action name="home" class="com.tail.test.actions.StrutsTestAction"&gt;  
            &lt;result&gt;/userMessage.jsp&lt;/result&gt;  
            &lt;result name="input"&gt;/userMessage.jsp&lt;/result&gt;  
        &lt;/action&gt;  
    &lt;/package&gt;  
       
    &lt;package name="tail-json" namespace="/page" extends="test"&gt;  
        &lt;global-results&gt;  
            &lt;!-- Don't redirect to login for ajax requests. --&gt;  
            &lt;result name="login" type="httpheader"&gt;  
                &lt;param name="status"&gt;403&lt;/param&gt;  
            &lt;/result&gt;  
            &lt;!-- Send a 503 for errors in ajax requests. --&gt;  
            &lt;result name="error" type="httpheader"&gt;  
                &lt;param name="status"&gt;503&lt;/param&gt;  
            &lt;/result&gt;  
        &lt;/global-results&gt;  
    &lt;/package&gt;  
       
    &lt;package name="tail-home" namespace="/home" extends="tail-json"&gt;  
        &lt;global-results&gt;  
            &lt;result name="input" type="json"&gt;  
                &lt;param name="includeProperties"&gt;  
                    result, actionErrors.*, fieldErrors.*   
                &lt;/param&gt;  
            &lt;/result&gt;  
        &lt;/global-results&gt;  
        &lt;action name="loadMessages" method="loadMessages"  
            class="com.tail.test.actions.StrutsTestAction"&gt;  
           &lt;result type="json"&gt;  
                &lt;param name="includeProperties"&gt;  
                    result, messageList.*   
                &lt;/param&gt;  
            &lt;/result&gt;  
        &lt;/action&gt;  
        &lt;action name="addMessageToUser" method="addMessageToUser"  
            class="com.tail.test.actions.StrutsTestAction"&gt;  
           &lt;result type="json"&gt;  
                &lt;param name="includeProperties"&gt;  
                    result, messageList.*   
                &lt;/param&gt;  
            &lt;/result&gt;  
        &lt;/action&gt;  
    &lt;/package&gt;  
&lt;/struts&gt; </pre>
</span></span></div>
<div class="code_title"><span style="font-family: Arial;">
<p>&nbsp;</p>
<p>其中定义了三个package，前两个package没有什么好讲的，如果要详细了解其配置原理，可以参考Struts2的官方文档。</p>
<p>&nbsp;</p>
<p><span style="font-family: Arial;">大家注意到，针对StrutsTestAction事实上已经在第一个package中定义了一次，为什么在第三个tail-home包中也要定义一遍呢，看到其中的json result的定义，大家应该也就看出其中的异样了。</span></p>
<p>&nbsp;</p>
<p><span style="font-family: Arial;">不错，这里的定义主要是为了利用Struts JSON的插件功能将Action中的一个或多个属性/对象转换为json对象，提供到UI层去使用。而第一个package中定义的action，仅仅是页面第一次进入时的入口。</span></p>
<p>&nbsp;</p>
<p><span style="font-family: Arial;">这里的result和messageList.*则是对StrutsTestAction的result和messageList属性的对外公布，includeProperties节点是支持正则表达式的，当然还有其他的json param，详细地大家可以参考struts2-jsonplugin-0.6.jar中的定义。</span></p>
<p>&nbsp;</p>
<p><span style="font-family: Arial;">这里提到一个messageList，主要是给大家谈谈Struts2的conversion的支持，看看StrutsTestAction-conversion.properties文件的内容，大家应该就明白了。</span></p>
<p><span style="font-family: Arial;">Element_messageList = com.tail.test.objects.Message<br />CreateIfNull_messageList = true</span></p>
<p>&nbsp;</p>
<p><span style="font-family: Arial;">不错，这里的定义主要是为了让json对象能被解析和传输。精确一点地说，在Action-&gt;UI的时候，他事实上用处不大，或者说可以省去。但是，如果如果经历UI-&gt;Action的时候，这个conversion定义就是必须的，否则Struts Action无法理解UI传送过来的数据。</span></p>
<p>&nbsp;</p>
</span></div>
</span></div>
          <br/>
          <span style="color:red;">
            <a href="http://tailsherry.javaeye.com/blog/149891#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/115' target='_blank'><span style="color:red;font-weight:bold;">JavaEye图灵杯第2届问答大赛开始了！8月4日至8月17日，奖品丰厚！</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Fri, 21 Dec 2007 11:06:09 +0800</pubDate>
        <link>http://tailsherry.javaeye.com/blog/149891</link>
        <guid>http://tailsherry.javaeye.com/blog/149891</guid>
      </item>
      <item>
        <title>桥接模式(Bridge)</title>
        <author>tailsherry</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://tailsherry.javaeye.com">tailsherry</a>&nbsp;
          链接：<a href="http://tailsherry.javaeye.com/blog/149079" style="color:red;">http://tailsherry.javaeye.com/blog/149079</a>&nbsp;
          发表时间: 2007年12月18日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p><span style="font-family: Arial;"><strong><span style="font-size: medium;">一、介绍</span></strong></span></p>
<p><span style="font-family: Arial;">桥接模式的主要目的是要将类的抽象部分与它的实现分离，使各个不同类之间都能独立的变化和衔接。</span></p>
<p><span style="font-family: Arial;">
<p>&nbsp;</p>
<p><br /><strong><span style="font-size: medium;">二、实例</span></strong></p>
<p>首先，定义一个抽象的基类，可以说这就是桥梁吧！</p>
<pre name="code" class="java">public abstract class Soda {     
   private SodaImp sodaImp;    
      
   public void setSodaImp(SodaImp sodaImp) {   
       this.sodaImp = sodaImp;   
   }   
   public SodaImp getSodaImp() {   
       return this.sodaImp;   
   }   
      
   public abstract void pourSoda();   
}</pre>
<p><br />注意这个类中间封装了一个SodaImp的基本操作类，这个类的主要目的也就是通过它的新方法pourSoda()来对外转发SodaImp中的基本动作。</p>
<p>&nbsp;</p>
<p><span style="font-family: Arial;">下面的一些个类相对来说要简单些了，都是用来基于已有的桥梁来添砖加瓦的。</span></p>
<div class="code_title"><span>
<pre name="code" class="java">//SodaImp抽象类或接口定义   
public abstract class SodaImp {     
   public abstract void pourSodaImp();   
}   
  
//实例化的Soda类(可以定义多个)   
public class MediumSoda extends Soda {     
   public MediumSoda() {   
   }   
      
   public void pourSoda() {   
       SodaImp sodaImp = this.getSodaImp();   
       for (int i = 0; i &lt; 2; i++) {   
           System.out.print("...glug...");   
           sodaImp.pourSodaImp();   
       }   
       System.out.println(" ");   
   }   
}   
  
//实例化的SodaImp类(可以定义多个)   
public class CherrySodaImp extends SodaImp {   
   CherrySodaImp() {}   
       
   public void pourSodaImp() {   
       System.out.println("Yummy Cherry Soda!");   
   }   
}  </pre>
</span></div>
<p>&nbsp;</p>
<p><span style="font-family: Arial;">而实际调用的过程可以示例如下：</span></p>
<span style="font-family: Arial;">
<div class="code_title">
<pre name="code" class="java">class TestBridge {   
   public static void testCherryPlatform() {   
       System.out.println(   
         "testing medium soda on the cherry platform");   
       MediumSoda mediumSoda = new MediumSoda();   
       mediumSoda.setSodaImp(new CherrySodaImp());   
       mediumSoda.pourSoda();    
   }   
       
   public static void main(String[] args) {   
        testCherryPlatform();   
        ...   
   }   
} </pre>
</div>
<div class="code_title"><span style="font-family: Arial;">
<p>&nbsp;</p>
<p>&nbsp;</p>
</span></div>
</span>
<p><span style="font-size: medium; font-family: Arial;"><strong>三、分析</strong></span></p>
<p><span style="font-family: Arial;"><span style="font-family: Arial;">从以上的实例，我们大致可以总结出桥接模式的几个主要角色：</span></span></p>
<ul>
<li><span style="font-family: Arial;"><strong>Abstraction </strong>定义抽象类的接口，维护一个指向Implementor类型对象指针</span></li>
<span style="font-family: Arial;">
<li><strong>RefinedAbstraction</strong> 由Abstraction定义的接口扩充和实例化</li>
<li><strong>Implementor</strong> 定义实现类的接口，一般来讲，该接口仅提供基本的操作，而Abstraction则定义了基于这些基本操作的较高层次的操作。</li>
<li><strong>ConcreteImplementor</strong> 实现Implementor接口并定义它的具体实现。</li>
</span>
</ul>
<p><span style="font-family: Arial;">实现一个桥接模式，我们需要定义Abstraction的桥接抽象类，并注意在其中封装Implementor的实现，其他的应该都显而易懂。</span></p>
<p>&nbsp;</p>
<p><span style="font-family: Arial;">作为一种设计模式，桥接模式的主要<strong>特点</strong>就是：<br /><span style="color: #0000ff;">1、分离接口及其实现部分，这里实现了Abstraction和Implementor的分离，有助于降低对实现部分的依赖性，从而产生更好的结构化系统。<br />2、提高了可扩充性，可以独立的对Abstraction和Implementor层次结构进行扩充。</span></span></p>
</span></p>
          <br/>
          <span style="color:red;">
            <a href="http://tailsherry.javaeye.com/blog/149079#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/115' target='_blank'><span style="color:red;font-weight:bold;">JavaEye图灵杯第2届问答大赛开始了！8月4日至8月17日，奖品丰厚！</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Tue, 18 Dec 2007 11:30:30 +0800</pubDate>
        <link>http://tailsherry.javaeye.com/blog/149079</link>
        <guid>http://tailsherry.javaeye.com/blog/149079</guid>
      </item>
      <item>
        <title>利用Javascript向页面中插入TABLE，IE下无法正常显示</title>
        <author>tailsherry</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://tailsherry.javaeye.com">tailsherry</a>&nbsp;
          链接：<a href="http://tailsherry.javaeye.com/blog/140937" style="color:red;">http://tailsherry.javaeye.com/blog/140937</a>&nbsp;
          发表时间: 2007年11月15日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p><span style="font-family: Arial;">在项目中偶然遇到一个怪异的问题，我要利用javascript动态的向一个DIV中插入一些个TABLE，Firefox可以正常显示，而在IE下，无论版本是6或7，均无法看到这个TABLE。但是，如果我直接把TABLE的HTML代码写在页面上，一切都能正常显示。</span></p>
<p>&nbsp;</p>
<p><span style="font-family: Arial;">最开始出问题的javascript脚本如下：</span></p>
<p><span style="font-family: Arial;">
<div class="code_title">
<pre name="code" class="js">var div = document.getElementById("container");    
var table = document.createElement("table");    
div.appendChild(table);    
var tr = document.createElement("tr");    
table.appendChild(tr);    
var td = document.createElement("td");    
tr.appendChild(td);   </pre>
&nbsp;</div>
<p>乍一看，代码比较干净，为什么在IE下就无法显示呢？其实，问题就出在IE的遍历页面中JS构造的DOM元素的时候，是一个按DOM树结构寻址的。如果漏掉了TABLE的TBODY元素，IE是无法显示这个TABLE的，中间加入一个TBODY元素，问题轻松解决！</p>
<div class="code_title">
<pre name="code" class="js">var div = document.getElementById("container");    
var table = document.createElement("table");    
div.appendChild(table);    
var body = document.createElement("tbody");    
table.appendChild(body);    
var tr = document.createElement("tr");    
body.appendChild(tr);    
var td = document.createElement("td");    
tr.appendChild(td);   </pre>
</div>
</span></p>
          <br/>
          <span style="color:red;">
            <a href="http://tailsherry.javaeye.com/blog/140937#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/115' target='_blank'><span style="color:red;font-weight:bold;">JavaEye图灵杯第2届问答大赛开始了！8月4日至8月17日，奖品丰厚！</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Thu, 15 Nov 2007 15:50:32 +0800</pubDate>
        <link>http://tailsherry.javaeye.com/blog/140937</link>
        <guid>http://tailsherry.javaeye.com/blog/140937</guid>
      </item>
      <item>
        <title>解决JSF + Spring2.0 + Hibernate3.2 + MySQL 乱码问题</title>
        <author>tailsherry</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://tailsherry.javaeye.com">tailsherry</a>&nbsp;
          链接：<a href="http://tailsherry.javaeye.com/blog/140191" style="color:red;">http://tailsherry.javaeye.com/blog/140191</a>&nbsp;
          发表时间: 2007年11月13日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p><span style="font-family: Arial;">在公司做的项目一般都是英文的，也就是默认的拉丁字符集 ISO-8859-1。在今天做的J+S+H应用中，我想插入保存一个中文的用户名，可是不管怎么样保存，都是问号形式的乱码。</span></p>
<p>&nbsp;</p>
<p><span style="font-family: Arial;">乱码问题一直是个比较头疼而又常见的问题，困扰着我们这些实用Unicode的群众。在Java Web应用中，乱码一般会出现在页面JSP显示层或者数据库持久层。对于前者，我们一般是注意在页面的头部代码中加入</span></p>
<p><span style="font-family: Arial;">
<div class="code_title"><span style="font-family: Arial;"><span><span>
<pre name="code" class="html">&lt;%@page contentType="text/html;charset=GB2312"%&gt;  </pre>
</span></span>
<p>或者</p>
<pre name="code" class="html">&lt;%@page contentType="text/html"%&gt;  
&lt;%@page pageEncoding="UTF-8"%&gt;  </pre>
<p>&nbsp;</p>
<p>如果依旧不行，考虑到对request中的数据进行一个ISO-8859到Unicode字符集的转换过程，一般都是通过一个Filter来实现，如下面的样例代码：</p>
<pre name="code" class="java">package com.array.dinner.utils;   
  
import java.io.IOException;   
  
import javax.servlet.Filter;   
import javax.servlet.FilterChain;   
import javax.servlet.FilterConfig;   
import javax.servlet.ServletException;   
import javax.servlet.ServletRequest;   
import javax.servlet.ServletResponse;   
  
public class SetCharacterEncoding implements Filter {   
    protected String encoding = null;   
  
    protected FilterConfig filterConfig = null;   
  
    protected boolean ignore = true;   
  
    public void destroy() {   
        this.encoding = null;   
        this.filterConfig = null;   
    }   
  
    public void doFilter(ServletRequest request, ServletResponse response,   
            FilterChain chain) throws IOException, ServletException {   
        // Conditionally select and set the character encoding to be used   
        if (ignore || (request.getCharacterEncoding() == null)) {   
            String encoding = selectEncoding(request);   
            if (encoding != null)   
                request.setCharacterEncoding(encoding);   
        }   
        // Pass control on to the next filter   
        chain.doFilter(request, response);   
    }   
  
    public void init(FilterConfig filterConfig) throws ServletException {   
        this.filterConfig = filterConfig;   
        this.encoding = filterConfig.getInitParameter("encoding");   
        String value = filterConfig.getInitParameter("ignore");   
        if (value == null)   
            this.ignore = true;   
        else if (value.equalsIgnoreCase("true"))   
            this.ignore = true;   
        else if (value.equalsIgnoreCase("yes"))   
            this.ignore = true;   
        else  
            this.ignore = false;   
    }   
  
    protected String selectEncoding(ServletRequest request) {   
        return (this.encoding);   
    }   
}  </pre>
<p>&nbsp;</p>
<p>对应的web.xml的配置应该添加一下Filter声明：</p>
<div class="code_title"><span>
<pre name="code" class="xml">&lt;filter&gt;  
      &lt;filter-name&gt;setCharacterEncoding&lt;/filter-name&gt;  
      &lt;filter-class&gt;com.array.dinner.utils.SetCharacterEncodingFilter&lt;/filter-class&gt;  
      &lt;init-param&gt;  
            &lt;param-name&gt;encoding&lt;/param-name&gt;  
            &lt;param-value&gt;gb2312&lt;/param-value&gt;  
      &lt;/init-param&gt;  
&lt;/filter&gt;  
&lt;filter-mapping&gt;  
       &lt;filter-name&gt;setCharacterEncoding&lt;/filter-name&gt;  
       &lt;url-pattern&gt;/*&lt;/url-pattern&gt;  
&lt;/filter-mapping&gt;</pre>
</span></div>
<p>&nbsp;</p>
<p>但是，当我跟踪到JSF的后台bean中，又能完整地看到变量的完整地中文字符。很明显，是在数据库的持久层上出问题了。仔细检查一下了自己的MySQL数据库，发现所有的表都是建立在UTF-8之上的，数据库本身应该是没有问题的。</p>
<p>&nbsp;</p>
<p><span style="font-family: Arial;">顺着想一想，结论马上就浮出水面了，我输入的是中文字符GBK的，而数据库接受的却是UTF-8，数据库方面默认用UTF-8来处理和保存一堆传过来的Unicode字符，但是数据库方面是不知道对方来的数据是什么编码的，所以只能用问号表示&ldquo;抗议&rdquo;。所以，这里只需要让数据库了解你的数据包的性质，沟通自然就无问题了。废话少说，你只需要在数据库连接url中这样声明一下，多加两个参数useUnicode和characterEncoding：</span></p>
<div class="code_title"><span><span>
<pre name="code" class="html">jdbc:mysql://localhost:3306/dinner?useUnicode=true&amp;characterEncoding=UTF-8</pre>
</span></span></div>
</span></div>
</span></p>
          <br/>
          <span style="color:red;">
            <a href="http://tailsherry.javaeye.com/blog/140191#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/115' target='_blank'><span style="color:red;font-weight:bold;">JavaEye图灵杯第2届问答大赛开始了！8月4日至8月17日，奖品丰厚！</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Tue, 13 Nov 2007 13:28:14 +0800</pubDate>
        <link>http://tailsherry.javaeye.com/blog/140191</link>
        <guid>http://tailsherry.javaeye.com/blog/140191</guid>
      </item>
      <item>
        <title>在Tomcat中部署JSF应用</title>
        <author>tailsherry</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://tailsherry.javaeye.com">tailsherry</a>&nbsp;
          链接：<a href="http://tailsherry.javaeye.com/blog/136959" style="color:red;">http://tailsherry.javaeye.com/blog/136959</a>&nbsp;
          发表时间: 2007年10月31日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p><span style="font-family: Arial;">众所周知，JSF1.2应用的官方Java应用服务器是Glassfish，网上很少有关于在Tomcat下部署JSF应用的例子。不信这个邪，我硬着头皮尝试在Tomcat中部署一个JSF应用。</span></p>
<p>&nbsp;</p>
<p><span style="font-family: Arial;">我机器上Tomcat的版本是5.5的，将一个在Glassfish下测试通过的JSF应用放在webapp目录下，启动Tomact，错误马上出来：</span></p>
<p><span style="color: #ff0000; font-family: Arial;">SEVERE: Error configuring application listener of class com.sun.faces.config.GlassFishConfigureListener<br />java.lang.NoClassDefFoundError: javax/el/ExpressionFactory<br />&nbsp;at java.lang.Class.getDeclaredConstructors0(Native Method)<br />&nbsp;at java.lang.Class.privateGetDeclaredConstructors(Class.java:2328)<br />&nbsp;at java.lang.Class.getConstructor0(Class.java:2640)<br />&nbsp;at java.lang.Class.newInstance0(Class.java:321)<br />&nbsp;at java.lang.Class.newInstance(Class.java:303)<br />&nbsp;at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:3713)<br />&nbsp;at org.apache.catalina.core.StandardContext.start(StandardContext.java:4216)<br />&nbsp;at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:760)<br />&nbsp;at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:740)<br />&nbsp;at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:544)<br />&nbsp;at org.apache.catalina.startup.HostConfig.deployDirectory(HostConfig.java:920)<br />&nbsp;at org.apache.catalina.startup.HostConfig.deployDirectories(HostConfig.java:883)<br />&nbsp;at org.apache.catalina.startup.HostConfig.deployApps(HostConfig.java:492)<br />&nbsp;at org.apache.catalina.startup.HostConfig.start(HostConfig.java:1138)<br />&nbsp;at org.apache.catalina.startup.HostConfig.lifecycleEvent(HostConfig.java:311)<br />&nbsp;at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupport.java:120)<br />&nbsp;at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1022)<br />&nbsp;at org.apache.catalina.core.StandardHost.start(StandardHost.java:736)<br />&nbsp;at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1014)<br />&nbsp;at org.apache.catalina.core.StandardEngine.start(StandardEngine.java:443)<br />&nbsp;at org.apache.catalina.core.StandardService.start(StandardService.java:448)<br />&nbsp;at org.apache.catalina.core.StandardServer.start(StandardServer.java:700)<br />&nbsp;at org.apache.catalina.startup.Catalina.start(Catalina.java:552)<br />&nbsp;at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)<br />&nbsp;at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)<br />&nbsp;at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)<br />&nbsp;at java.lang.reflect.Method.invoke(Method.java:585)<br />&nbsp;at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:295)<br />&nbsp;at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:433)</span></p>
<p>&nbsp;</p>
<p><span style="font-family: Arial;">很简单的解决，我们的web应用缺少el-ri.jar和el-api.jar两个和el相关的jar包。之后，似乎一切都正常了，原来Tomcat部署JSF不过如此！可是当运行了我的http://localhost:8080/之后，一下子被弄懵了，一个既陌生又熟悉的错误被抛出来：</span></p>
<p><span style="color: #ff0000; font-family: Arial;">java.lang.NoClassDefFoundError: javax/servlet/jsp/tagext/JspIdConsumer<br />&nbsp;java.lang.ClassLoader.defineClass1(Native Method)<br />&nbsp;java.lang.ClassLoader.defineClass(ClassLoader.java:620)<br />&nbsp;java.security.SecureClassLoader.defineClass(SecureClassLoader.java:124)<br />&nbsp;org.apache.catalina.loader.WebappClassLoader.findClassInternal(WebappClassLoader.java:1847)<br />&nbsp;org.apache.catalina.loader.WebappClassLoader.findClass(WebappClassLoader.java:873)<br />&nbsp;org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1326)<br />&nbsp;org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1205)<br />&nbsp;java.lang.ClassLoader.loadClassInternal(ClassLoader.java:319)<br />&nbsp;java.lang.ClassLoader.defineClass1(Native Method)<br />&nbsp;java.lang.ClassLoader.defineClass(ClassLoader.java:620)<br />&nbsp;java.security.SecureClassLoader.defineClass(SecureClassLoader.java:124)<br />&nbsp;org.apache.catalina.loader.WebappClassLoader.findClassInternal(WebappClassLoader.java:1847)<br />&nbsp;org.apache.catalina.loader.WebappClassLoader.findClass(WebappClassLoader.java:873)<br />&nbsp;org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1326)<br />&nbsp;org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1205)<br />&nbsp;java.lang.ClassLoader.loadClassInternal(ClassLoader.java:319)<br />&nbsp;java.lang.ClassLoader.defineClass1(Native Method)<br />&nbsp;java.lang.ClassLoader.defineClass(ClassLoader.java:620)<br />&nbsp;java.security.SecureClassLoader.defineClass(SecureClassLoader.java:124)<br />&nbsp;org.apache.catalina.loader.WebappClassLoader.findClassInternal(WebappClassLoader.java:1847)<br />&nbsp;org.apache.catalina.loader.WebappClassLoader.findClass(WebappClassLoader.java:873)<br />&nbsp;org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1326)<br />&nbsp;org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1205)<br />&nbsp;org.apache.jasper.compiler.Parser.parseCustomTag(Parser.java:1326)<br />&nbsp;org.apache.jasper.compiler.Parser.parseElements(Parser.java:1578)<br />&nbsp;org.apache.jasper.compiler.Parser.parse(Parser.java:127)<br />&nbsp;org.apache.jasper.compiler.ParserController.doParse(ParserController.java:212)<br />&nbsp;org.apache.jasper.compiler.ParserController.parse(ParserController.java:101)<br />&nbsp;org.apache.jasper.compiler.Compiler.generateJava(Compiler.java:156)<br />&nbsp;org.apache.jasper.compiler.Compiler.compile(Compiler.java:296)<br />&nbsp;org.apache.jasper.compiler.Compiler.compile(Compiler.java:277)<br />&nbsp;org.apache.jasper.compiler.Compiler.compile(Compiler.java:265)<br />&nbsp;org.apache.jasper.JspCompilationContext.compile(JspCompilationContext.java:564)<br />&nbsp;org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:299)<br />&nbsp;org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:315)<br />&nbsp;org.apache.jasper.servlet.JspServlet.service(JspServlet.java:265)<br />&nbsp;javax.servlet.http.HttpServlet.service(HttpServlet.java:803)<br />&nbsp;com.sun.faces.context.ExternalContextImpl.dispatch(ExternalContextImpl.java:414)<br />&nbsp;com.sun.faces.application.ViewHandlerImpl.executePageToBuildView(ViewHandlerImpl.java:455)<br />&nbsp;com.sun.faces.application.ViewHandlerImpl.renderView(ViewHandlerImpl.java:139)<br />&nbsp;com.sun.faces.lifecycle.RenderResponsePhase.execute(RenderResponsePhase.java:108)<br />&nbsp;com.sun.faces.lifecycle.LifecycleImpl.phase(LifecycleImpl.java:266)<br />&nbsp;com.sun.faces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:159)<br />&nbsp;javax.faces.webapp.FacesServlet.service(FacesServlet.java:245)</span></p>
<p>&nbsp;</p>
<p><span style="font-family: Arial;">不错，肯定是servlet相关的jar包出了问题，仔细察看Tomcat中的servlet-api.jar和jsp-api.jar包，原来根本就不存在这个所谓的javax/servlet/jsp/tagext/JspIdConsumer接口或类。怎么办？找可用的servlet.jar包！</span></p>
<p>&nbsp;</p>
<p><span style="font-family: Arial;">对于JSF1.2应用来说，它使用的JSP API已经用到了新版本，而5.5自带的JSP API明显已经outdate了。众里寻他千百度，总算找到一个JDK5.0的javaee.jar通用包，怎么放呢，如果直接放在common/lib下，两个servlet包必然冲突，删除Tomcat自带的，启动Tomcat的时候会报一堆所谓servlet violate的错误！</span></p>
<p>&nbsp;</p>
<p><span style="font-family: Arial;">困惑，迷茫！仔细想想，现在已经早有了Tomcat6.0，为什么不拿过来用用看呢？硬着头皮下载了6.0版本的，将我的应用部署在这个新版本的Tomcat中，哇！一切正常。参考一下6.0根目录下的lib目录，其中jsp-api.jar和servlet-api.jar早已经更新了。</span></p>
<p>&nbsp;</p>
<p><span style="font-family: Arial;">如果我事先选择了Tomcat6.0似乎也就不会走这么多弯路，但是，不管怎样，一次经历让自己又多了一份见识，离JSF这位大家闺秀又近了一点，值！<br /></span></p>
          <br/>
          <span style="color:red;">
            <a href="http://tailsherry.javaeye.com/blog/136959#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/115' target='_blank'><span style="color:red;font-weight:bold;">JavaEye图灵杯第2届问答大赛开始了！8月4日至8月17日，奖品丰厚！</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Wed, 31 Oct 2007 11:43:34 +0800</pubDate>
        <link>http://tailsherry.javaeye.com/blog/136959</link>
        <guid>http://tailsherry.javaeye.com/blog/136959</guid>
      </item>
      <item>
        <title>诡异的innerHTML</title>
        <author>tailsherry</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://tailsherry.javaeye.com">tailsherry</a>&nbsp;
          链接：<a href="http://tailsherry.javaeye.com/blog/130803" style="color:red;">http://tailsherry.javaeye.com/blog/130803</a>&nbsp;
          发表时间: 2007年10月10日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p><span style="font-family: Arial;">今天在现在的项目中，涉及到这样一个案例，我要用innerHTML临时保存一个div中的内容，然后在某种条件满足时，再把这个innerHTML返回给这个div。</span></p>
<p>&nbsp;</p>
<p><span style="font-family: Arial;">在这个div中，如果存在有edit的标签，当我重新将保存的内容返回的时候，用Firefox浏览时发现edit里面的内容空空如也！！！</span></p>
<p>&nbsp;</p>
<p><span style="font-family: Arial;">同样的应用方式，另外一个div中的edit的内容却能完完整整的恢复，我诧异啊？？？</span></p>
<p>&nbsp;</p>
<p><span style="font-family: Arial;">我尝试用IE打开，前后两个div都能正常工作，所有内容都能完璧归赵。</span></p>
<p>&nbsp;</p>
<p><span style="font-family: Arial;">赶紧查资料，看代码，最后发现，两个div中的edit的创建方式是不一样的。前一个div是通过createElement创建edit并为之添加各种属性的，代码示意如下： </span></p>
<div class="code_title"><span style="font-family: Arial;"><span>
<pre name="code" class="js">var edit = document.createElement("input");   
edit.id = name;   
edit.type = "text";   
edit.value = val;   
div.appendChild(edit);  </pre>
&nbsp;</span>
<p>而后一个div则是通过直接加入HTML的方式加入的，示意如下：</p>
<div class="code_title"><span style="font-family: Arial;"><span>
<pre name="code" class="js">var html = "&lt;input type='text' id='" + name + "' value='" + val + "' /&gt;";   
div.innerHTML = html;   </pre>
</span></span></div>
<div class="code_title"><span style="font-family: Arial;">
<p>&nbsp;</p>
<p>慢慢的，问题的根源就豁然开朗！不同于M$ IE，在Mozilla Firefox下的innerHTML通过遍历元素的所有节点的tagName还有attrubutes来得到其innerHTML的，因此我们前一个例中的innerHTML得不到改动后的value值。</p>
<p>&nbsp;</p>
<p><span style="font-family: Arial;">将前一段代码修改如下，则innerHTML在IE和FF下都会通行无阻：</span></p>
<div class="code_title"><span>
<pre name="code" class="js">var edit = document.createElement("input");   
edit.id = name;   
edit.setAttribute("type", "text");   
edit.setAttribute("value", name);   
div.appendChild(edit);  </pre>
</span></div>
</span></div>
</span></div>
          <br/>
          <span style="color:red;">
            <a href="http://tailsherry.javaeye.com/blog/130803#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/115' target='_blank'><span style="color:red;font-weight:bold;">JavaEye图灵杯第2届问答大赛开始了！8月4日至8月17日，奖品丰厚！</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Wed, 10 Oct 2007 15:55:18 +0800</pubDate>
        <link>http://tailsherry.javaeye.com/blog/130803</link>
        <guid>http://tailsherry.javaeye.com/blog/130803</guid>
      </item>
      <item>
        <title>适配器模式(Adapter)</title>
        <author>tailsherry</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://tailsherry.javaeye.com">tailsherry</a>&nbsp;
          链接：<a href="http://tailsherry.javaeye.com/blog/127519" style="color:red;">http://tailsherry.javaeye.com/blog/127519</a>&nbsp;
          发表时间: 2007年09月27日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p><span style="font-size: medium; font-family: Arial;"><strong>
<p>一、介绍</p>
</strong></span></p>
<p><span style="font-family: Arial;">适配器模式使得一个接口与其它接口兼容，从而给出多个不同接口的统一抽象。换句话说，该模式是将一个类的接口转换成客户希望的另外一个接口，使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。</span></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><span style="font-size: medium; font-family: Arial;"><strong>二、实例</strong></span></p>
<p><span style="font-family: Arial;">在本模式的实例部分，我们有一个TeaBag类，由TeaCup来支配使用，完成泡茶的这个过程。</span></p>
<pre name="code" class="java">public class TeaBag {     
   boolean teaBagIsSteeped;    
       
   public TeaBag() {   
       teaBagIsSteeped = false;   
   }   
      
   public void steepTeaInCup() {   
       teaBagIsSteeped = true;   
       System.out.println("tea bag is steeping in cup");   
   }   
}   
  
public class TeaCup {     
   public void steepTeaBag(TeaBag teaBag) {   
       teaBag.steepTeaInCup();   
   }   
} </pre>
<p>&nbsp;</p>
<p>现<span style="font-family: Arial;">在有一个LooseLeafTee要被引入，换句话说，我现在想泡一杯新买入的LooseLeafTee。</span></p>
<p><span style="font-family: Arial;">
<pre name="code" class="java">public class LooseLeafTea {  
   boolean teaIsSteeped; 
    
   public LooseLeafTea() {
       teaIsSteeped = false;
   }
   
   public void steepTea() {
       teaIsSteeped = true;
       System.out.println("tea is steeping");
   }
}
</pre>
</span></p>
<p>&nbsp;</p>
<p>为了让我的这个TeaCup可以泡这个LooseLeafTee，我们创建一个TeaBall来牵线搭桥。</p>
<div class="code_title">
<pre name="code" class="java">public class TeaBall extends TeaBag {      
   LooseLeafTea looseLeafTea;    
       
   public TeaBall(LooseLeafTea looseLeafTeaIn) {    
       looseLeafTea = looseLeafTeaIn;    
       teaBagIsSteeped = looseLeafTea.teaIsSteeped;    
   }    
        
   public void steepTeaInCup() {    
       looseLeafTea.steepTea();    
       teaBagIsSteeped = true;    
   }    
}  </pre>
</div>
<p>&nbsp;</p>
<p>在实际使用的过程中，我们这样使用这个适配器模式带来的优越感。</p>
<div class="code_title"><span>
<pre name="code" class="java">class TestTeaBagAdaptation {    
   
   public static void main(String[] args) {    
       TeaCup teaCup = new TeaCup();    
   
       System.out.println("Steeping tea bag");    
       TeaBag teaBag = new TeaBag();           
       teaCup.steepTeaBag(teaBag);    
   
       System.out.println("Steeping loose leaf tea");    
       LooseLeafTea looseLeafTea = new LooseLeafTea();    
       TeaBall teaBall = new TeaBall(looseLeafTea);    
       teaCup.steepTeaBag(teaBall);    
   }    
} </pre>
&nbsp;</span> </div>
<p>&nbsp;</p>
<p><span style="font-size: medium; font-family: Arial;"><strong>三、分析</strong></span></p>
<p><span style="font-family: Arial;">从以上的实例，我们大致可以总结出适配器模式的几个主要角色：</span></p>
<ul>
<li><span style="font-family: Arial;"><strong>Client</strong> 客户对象，即处理业务逻辑的主体，如实例中的TeaCup；</span> </li>
<li><span style="font-family: Arial;"><strong>Target</strong> 目标对象，也是Client使用的与特定领域相关的接口，如实例中的TeaBag；</span> </li>
<li><span style="font-family: Arial;"><strong>Adaptee</strong> 被改编(适应)者，target需要协同工作的接口，如实例中的LooseLeafTea；</span> </li>
<li><span style="font-family: Arial;"><strong>Adapter</strong> 适配器(改编者)，对Adaptee的接口和Target接口进行适配，如实例中的TeaBall；</span> </li>
</ul>
<p><span style="font-family: Arial;">实现一个适配器模式，我们需要首先要定义Target和Adaptee，这里的Target即是Client已经认定的操作对象，而Adaptee可以理解为一个