<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
  <channel>
    <title>JavaEye论坛精彩帖子</title>
    <description>JavaEye论坛精彩帖子 - Java编程，Ruby编程，微软.net，AJAX，敏捷软件开发，综合软件技术</description>
    <link>http://www.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>我们不是在做技术决策，我们在玩</title>
        <author>JavaEye网站</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://liuqiang.javaeye.com">liuqiang</a>&nbsp;
          链接：<a href="http://www.javaeye.com/topic/222159" style="color:red;">http://www.javaeye.com/topic/222159</a>&nbsp;
          发表时间: 2008年07月31日
          <br/>
          声明：本文系JavaEye网站发布的原创文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p class="0" style="margin-top: 5pt; text-align: justify;"><span style="font-size: 11pt; font-family: 'Times New Roman'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">&nbsp;&nbsp;&nbsp;&nbsp;在这里我不想一味地去抱怨对公司管理的不满，只想和大家一起分享下我们在做技术决策时遇到的问题。</span></span></p>
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;"><span style="font-size: 11pt; font-family: 'Times New Roman'; mso-spacerun: 'yes';">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="font-family: 宋体;">遇到的很多情况是，公司领导是以前在大公司做过什么CTO之类的人，有了自己的一些资本和人脉积累后，自己跳出来开个小公司，这类人在技术上有着自己独特的见解和十分强的自信心。然后开始招兵买马，那么是什么兵什么马呢？据我了解招的大多数是应届生或不足一年工作经验的。之后项目来了，于是开始带领大家做项目，那么这里存在一个采用什么技术进行开发的问题，如果我是老板我一定会选最NiuBility的技术，你想啊，既然我的人拼不过别人，那么我在技术的先进性一定要比别人NiuBility，再加上自己伟大的创意，这样才有和别人竞争的余地。</span></span></p>
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;"><span style="font-size: 11pt; font-family: 'Times New Roman'; mso-spacerun: 'yes';">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="font-family: 宋体;">事实上我们目前也是这么干的，昨天j2ee，今天SOA，明天restful，后天&hellip;&hellip;，为了超越别人。于是一帮本来就没有多少的经验的人在各种技术之间疲于奔命。就举个实例吧，最近就有这么一个项目，为了做的比别的同类产品NiuBility，打算用flex做前端，丫的，多酷啊，加上javaeyer一起看好，难道有错？在项目启动会上老板首先把这个东西鼓吹一番，什么跨平台、未来趋势、AIR&hellip;&hellip;，之后大家开始讨论这个东西，当时只有我保留意见，其他人一致通过。现在不想去讨论这个东西有多么NiuBility，关键是目前我们有几个人会这个东西，nobody！于是边学边干，据说pureMVC好，于是就基于pureMVC做，做着做着，我越发的感觉这个这个世界太疯狂了，连AS都还没弄清楚怎么回事的人就开始玩转pureMVC了，尽管我没有参与这个项目。结果是一片混乱，工期开始肆意的延长，老板急了，那加工加点吧，赶制出来的东西粗糙的不得了，一堆问题也不知道怎么办，界面不好看为什么不去修改flex的皮肤？由于不熟练怕耽误时间。性能跟不上为什么不去找瓶颈去优化？因为不会。出错了为什么不能很快解决？因为没有调试经验。和后台交互为什么那么费劲？还是因为不熟。&nbsp;&nbsp;&nbsp;</span></span></p>
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;"><span style="font-size: 11pt; font-family: 'Times New Roman'; mso-spacerun: 'yes';">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style="font-family: 宋体;">那么你可能奇怪，为什么不早点发现这个技术瓶颈问题从而解决呢？那就要话说在项目决策的时候，经过老板这么一鼓吹，大家都被吹晕了，因为这个东西大家都没接触过，也不好反对，再说确实也拿不出一手的经验去证明这个东西不适宜我们，谁能反驳他？他说好那就好喽。而且还有一点，你要是反对使用这个东西你就会被贴上保守、不自信的标签，老板嘴上不说，心里多少会这么想的，我就由于就没投赞成票，老板几次和我说(做不满意状)，叫偶多了解点这方面的东西。</span></span></p>
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt; text-indent: 22pt;"><span style="font-size: 11pt; font-family: 'Times New Roman'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">那么技术决策该怎么做呢？我觉得很简单，根据员工的特长去选，扬长避短，其实这个道理是蛮简单的，但是这里有个矛盾的地方，就是有些公司为了节省人力成本还是愿意接收大量的应届生或者刚入门者，但又想提高公司的技术水准，所以在做项目的时候大家基本是边学边做，这无疑把企业培训的风险和成本加到项目开发过程中了，不可否认的是技术的提高是个积累的过程&nbsp;，拔苗助长只会让项目死的更快，所以也就让老板感觉，他在工作，我们却在玩。折腾了那么多的NiuBility的技术，结果我们</span></span><span style="font-size: 11pt; font-family: '宋体'; mso-spacerun: 'yes';">j2ee<span style="font-family: 宋体;">了吗？我们soa了吗？我们restful了吗？我只感觉我们在玩！</span></span></p>
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt; text-indent: 22pt;"><span style="font-size: 11pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">那么为什么不去聘请O6Z这样的大佬来带领大家一边拍脑袋，一边干活，多帅啊，很遗憾，我们供不起这样的大佛，你还别不信，在用人上，有些老板有时就会把一块钱看的和月亮一样大，谁要价低，包装看得过得去，就买谁。这里人力资源可能就是T1说的服务期货，没有服务器、路由器等这样的现货来的实在，来的让人心里感到踏实。</span></span></p>
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt; text-indent: 22pt;"><span style="font-size: 11pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">总之我认为，老板有什么样的枪，就去打什么样的鸟，否则结局很可能就是，老板来收我们的烂摊子，而我们只得另投明主了。</span></span><span style="font-size: 11pt; font-family: 'Times New Roman'; mso-spacerun: 'yes';">&nbsp;&nbsp;&nbsp;&nbsp;</span></p>
<p class="0"><span style="font-size: 11pt; font-family: 'Times New Roman'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">&nbsp;&nbsp;&nbsp; 欢迎拍砖，如有雷同，纯属巧合</span></span></p>
<!--EndFragment-->
          <br/>
          <span style="color:red;">
            <a href="http://complystill.javaeye.com/topic/222159#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></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Thu, 31 Jul 2008 12:58:32 +0800</pubDate>
        <link>http://www.javaeye.com/topic/222159</link>
        <guid>http://www.javaeye.com/topic/222159</guid>
      </item>
      <item>
        <title>小公司如何做项目管理(下)</title>
        <author>JavaEye网站</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://liuqiang.javaeye.com">liuqiang</a>&nbsp;
          链接：<a href="http://www.javaeye.com/topic/217488" style="color:red;">http://www.javaeye.com/topic/217488</a>&nbsp;
          发表时间: 2008年07月22日
          <br/>
          声明：本文系JavaEye网站发布的原创文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <div class="Section0" style="layout-grid: 15.6pt none;">
<p class="0" style="margin-top: 5pt; text-align: justify;"><span style="font-size: 10pt; font-family: '宋体'; mso-spacerun: 'yes';">&nbsp;&nbsp;&nbsp;&nbsp;在上篇文章里，我简要谈了项目管理中的需求开发和管理，那么在这篇文章里就和各位以闲话家常的方式讨论下项目规划和项目监控。项目规划、项目监控其实也是项目管理中比较核心的工作，也是很多开发人员最敏感的话题，因为这两个东西与公司的领导和员工关系都非常的密切。</span></p>
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;"><span style="font-size: 10pt; font-family: '宋体'; mso-spacerun: 'yes';">&nbsp;&nbsp;&nbsp; 先从我以前的学校说起，以前我们学校有片荒地，当时的领导觉得学校应该搞绿化，于是组织在荒地上植树，不到一年换了一个校长，这位校长觉得学校应该抓体育运动，决定再造一个足球场，于是把树移走，造了一个足球场，再后来北京奥运会来了，学习为了迎合绿色奥运的理念又开始植树，这就是没有规划和监控的典型例子，结果是劳民又伤财。当然对于学校来说，有国家财政的支持，有资本这么折腾，可是对于小公司做项目来说，这么折腾几下估计很快就要牺牲了。</span></p>
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;"><span style="font-size: 10pt; font-family: '宋体'; mso-spacerun: 'yes';">&nbsp;&nbsp;&nbsp; 事实求是的说大多数小公司在这两个方面做得很少有令人的满意的，小公司的老板其实也会意识到公司在项目规划和监控方面做得不咋地，但很少能做到有效的改进，其实这个也是有很多方面的原因的，以我自作多情的猜测主要有以下两个原因，对于小公司，尽管盈利不是很多，但基本还是可以撑下去的，老板会觉得管他乱不乱，公司总之每个月还是有盈利的，少就少点吧，多干几年自己的下半辈子基本有别墅有车了，所以比较保守，要是改革吧，万一鸡飞蛋打怎么办？还是本分点好，小心使得万年船。其实是对项目规划和监控其实需要大量的成本，老板觉得钱应该花在刀刃上，搞这些东西就是在务虚。再者更恶劣的老板有病就乱烧香，就有想想借助CMMI这个洋玩意治病的，其实很多老板都蛮喜欢CMMI的，CMMI就是假定人就是一个机器的部件，可以替换可以不停的运转，总之管机器总比管人省心吧，结果是万分的矛盾，银子撒了一大把，收效却甚微，甚至比以前更乱，大家做的都不开心。与其听咨询师们拿什么高深的方法论来瞎掰，不如我们谈点实际的，想就以下议题来和各位消遣。</span></p>
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;"><span style="font-size: 10pt; font-family: '宋体'; mso-spacerun: 'yes';">1&nbsp;工作量估算</span></p>
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;"><span style="font-size: 10pt; font-family: '宋体'; mso-spacerun: 'yes';">&nbsp;&nbsp;&nbsp; 对于工作量估算很多项目经理（老板）喜欢用数学公式来计算，可能数学公式更加的客观和科学，ok,，看看市面流行的计算方法吧，最常见的是基于代码行的数学模型，那么这里存在不少问题，工作量估算主要是为了在项目进行中进行有效的项目监控，那么软件开发尚未结束，谁知道最后的代码行有多大？代码经常会被修改，那么修改的代码算不算？如果算，那么代码修改越多难道能说明工作量越大？代码效率的区别也是很大的，假如一个10行代码可以实现的东西被写成50行，难道能客观的反映工作量？还有2种比较高级点的方法是基于功能点和COCOMO的方法，那么我想问的是它们的公式中的系数该怎么定？那么不少咨询师忽悠我说，根据自己的实际情况来定呗，那么我想问的是，算命是迷信，电脑意味着科学，那么用电脑算命算不算迷信?所以我是主张这里还是要靠人的经验来估算，大家可以在项目周会上对工作量进行充分的估算，在估算时要同时考虑到项目执行的难度，根据经验给出合理的评估。</span></p>
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;"><span style="font-size: 10pt; font-family: '宋体'; mso-spacerun: 'yes';">2&nbsp;任务分配</span></p>
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;"><span style="font-size: 10pt; font-family: '宋体'; mso-spacerun: 'yes';">&nbsp;&nbsp;&nbsp; 大多数的做法是将整个项目划分成一个个可以独立执行的原子任务，这些任务要划分优先级和难度，至少心理有个数，并且每项任务要制定负责人。那么问题就出在这个任务分配上了，软件开发是一项智力创造的活动，如果按CMMI假设的那样，在分配任务时忽略人的因素是万万不可取的，我就有血的教训，不久之前做一个ruby的项目，然后开始在公司内部随便抓了几个有点ruby基础的人，也没太顾忌别人的想法。做着做着，觉得他们有点心在曹营心在汉，平时还是抱着本thinking&nbsp;in&nbsp;java看，做ruby也是在敷衍了事，结果是代码质量不行，需要大规模的修改。当然按理说员工应该服从公司的安排，做一样就要做好一样，但员工也有员工的规划，你去叫他做他压根就不喜欢的事只能说明管理有问题。</span></p>
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;"><span style="font-size: 10pt; font-family: '宋体'; mso-spacerun: 'yes';">&nbsp;&nbsp;&nbsp; 另外还有一个普遍性的问题是能者多劳，有个朋友刚进公司动手能力很强，也非常的积极，每次做项目都分给他最难最累的任务，做着做着也就厌倦了，这时老板会忽悠你说，你能力强，要挑起公司的大梁，以后公司壮大了给你个什么职位，我觉得这就是在扯淡，这就是我经常见到的忽悠式的管理，很多管理手段完全靠人情，很多人都是在这种环境中被忽悠长大，到最后怎么样？被忽悠了几年还不是另谋高就了，所以指望人情化管理的公司很难长大。我觉得该讲原则的地方就要讲原则，在任务分配上给能力强的分配少而精的任务，而且要考虑到员工自己的想法，有些人想做java架构师，你叫他做oracle&nbsp;dba就不合适，有些对ui设计感兴趣，你叫他做系统分析员也不合适，有些人就喜欢搞技术，你硬要叫他做管理也是不合适。</span><span style="font-size: 10pt; font-family: 'Arial'; mso-spacerun: 'yes';">&nbsp;</span></p>
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;"><span style="font-size: 10pt; font-family: '宋体'; mso-spacerun: 'yes';">3&nbsp;进度管理</span></p>
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;"><span style="font-size: 10pt; font-family: '宋体'; mso-spacerun: 'yes';">&nbsp;&nbsp;&nbsp; 在做进度之前，一项最重要的任务是识别关键任务，很多进度表进行任务安排时将各项任务平均分的特点为觉得极不合适，有些任务比较难处理，而且许多后续任务依赖于该项任务，那么这项任务就应该配备更精良的人手和充裕的时间，依我的经验80%的时间都是在处理这些20%的关键任务上。这里还有个比较重要的问题是时间安排，我听很多项目经理说时间安排要尽可能的紧，也就是比预计要靠前，这样员工才有紧迫感。我觉得这是不可取的，首先即使你按原计划进行，八成也是要要延期的，那么这就会导致项目严重延期，长此以往，项目延期成了家常便饭，不延期反而不正常，于是大家都成了老油条，那么进度表不就是废纸一张，毫无约束力而言吗！我觉得根据实际情况指定个合理的进度表是比较重要的，或许你会说项目还是在延期，那我觉得是你项目估算没有做好，项目延期在10%左右比较正常，否则就可以调查是什么原因导致进度滞后，如果是客观原因，以后完全可以延长项目时间，总之一个合理的进度表比较重要。</span></p>
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;"><span style="font-size: 10pt; font-family: '宋体'; mso-spacerun: 'yes';">4&nbsp;项目奖金</span></p>
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;"><span style="font-size: 10pt; font-family: '宋体'; mso-spacerun: 'yes';">&nbsp;&nbsp;&nbsp; 这里牵扯到一个钱的问题，据我了解国内大多小公司很少有项目奖金这么一说，年底给点路费就不错了！国内的大多数项目经理更像是一个技术负责人，根本没有用钱的权利，我就曾像公司申请项目奖金，结果计划全盘泡汤，给的理由很荒唐，说项目奖金不好分配，给张三多一点吧，李四不爽，反之亦然。我心理暗自想：&ldquo;你丫不想给就直说呗！&rdquo;，这里会导致一个问题，就是&ldquo;项目经理&rdquo;凭什么约束成员，大锅饭的道理我也不想再解释了，总之结果就是3个月的项目就得做个5个月，其实老板的小算盘看似很精明，其实未见得，虽然项目奖金能省就省了，那么工作效率的下降所带来的成本的提高，孰轻孰重？长远一点说，产品质量的下滑导致的项目维护的成本你计算过吗？依我的经验，3个月的有效工作时间其实也就是1个月，这已经不错了。不过项目奖金的分配确实是个难问题，但有没有项目奖金和分配合理与否是2码子事吧？由于我也没有能耐申请到项目奖金所以也就没有深入研究这个问题，只得望梅止渴，看看人家华为了，员工根据能力分等级，加上年限、加班、表现得出个权值来计算。总之现有鸡才能有蛋，这个问题需要更深入的讨论。</span></p>
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;"><span style="font-size: 10pt; font-family: '宋体'; mso-spacerun: 'yes';">&nbsp;&nbsp;&nbsp; 先写那么多，有时间继续&hellip;&hellip;</span></p>
<p class="0" style="margin-top: 5pt; margin-bottom: 5pt;"><span style="font-size: 10pt; font-family: 'Arial'; mso-spacerun: 'yes';">&nbsp;</span></p>
<p class="0">&nbsp;</p>
</div>
<!--EndFragment-->
          <br/>
          <span style="color:red;">
            <a href="http://complystill.javaeye.com/topic/217488#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></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Tue, 22 Jul 2008 10:09:25 +0800</pubDate>
        <link>http://www.javaeye.com/topic/217488</link>
        <guid>http://www.javaeye.com/topic/217488</guid>
      </item>
      <item>
        <title>看看mina和memcached的联姻（适合不同语言客户端，高并发？）</title>
        <author>JavaEye网站</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://ahuaxuan.javaeye.com">ahuaxuan</a>&nbsp;
          链接：<a href="http://www.javaeye.com/topic/217165" style="color:red;">http://www.javaeye.com/topic/217165</a>&nbsp;
          发表时间: 2008年07月21日
          <br/>
          声明：本文系JavaEye网站发布的原创文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <span style="font-size: medium">/** <br />* 作者：张荣华 <br />* 日期：2008-07-21 <br />**/<br /><br />看看mina和memcached的联姻<br /><br />先来解释一下这两个东东的身世<br /><br />Mina，是什么？<br />	Minan是一个network 应用框架，她能很方便的帮助用户开发出高性能和高可扩展性的网络应用程序。官方地址请看：<a href="http://mina.apache.org/" target="_blank">http://mina.apache.org/</a><br /><br /><br />Memcached是什么？<br />memcached一个remote cache，它只提供数据存储服务，不过它得java客户端比较不错，还有很多其他语言的客户端，也就是说其周边比较丰富。Memcached的文章太多了，随便一搜一大堆。<br /><br />现在相信大家对这两个东西有点了解了，接着转入正题吧，我估计猛一看标题，很多童鞋估计都有那么一点云中子的感觉（因为云中子一般在云里或者雾里，所以云中子＝＝云里雾里），从上面的解释来看mina和memcached好像是八竿子打不到一起去的东西。这两个东西怎么能走到一起呢。先看他们能解决什么样的需求。<br /><br />	需求：我们知道，所有的互联网服务几乎都离不开connection这个东东，比如我们打开一个网页，从浏览器发起请求到tomcat接受请求并返回数据，这个过程就开启了一个短连接，数据返回之后这个连接就关闭了，也就是说每个请求其实都是一个新连接的开启和关闭。然后，tomcat中的application向数据库发送一个查询语句，它需要从连接池中拿到一个connection，这个connection一直在pool中，显然这个connection是一个长连接，由此可见一次请求，从浏览器到db再到浏览器既经过了短连接有经过了长连接，我们的生活是离不开连接的。<br /><br />	上面说的这个例子是一个请求的最简单模型，因为我们的application可不只是依赖数据库，尤其在互联网应用中，通常我们的application还依赖于其他的server，比如说我们的互联网应用可能还连接着memcached server，通常，这里也有一个连接池，维护着一堆长连接，那么结束了吗，不，再通常我们的application还依赖于其他的application。好及了，一个相对有点复杂的应用网络，接着往下说，快到重点了<br /><br />	重点：<br />一般情况下，我们的application依赖于其他application的的时候我们会直接使用http协议，或者再次封装过的http协议(诸如webservice之流)，而且这种情况是大多数情况，但是不是全部情况，因为在互联网上我们会遇到各种各样的需求。因为http连接是短连接，每次发起连接的3次握手不可避免，这是造成其并发量不高的重要原因之一（有的人也许会说，即使http并发高有什么用，你的application还是撑不住，但是我想说的是不是所有的应用都是这样的，只是你没有遇到过而已）。那么假设我有一个数据中心，这个数据中心可以提供common数据的服务，这些common的数据会被网站的各个地方获取，这些common的数据之间可能还有一些计算，我可以通过请求的参数来执行对应的操作，比如查询，统计等等(哇，看来能有效的降低数据库的压力啊)，那么看来memcached是不行了(不能定制计算)，只能自己写这样的应用了，不过以什么样的形势发布接口呢，短连接不行，并发量有限，只能长连接，还要考虑到一点，我的服务的客户端是不定的，有可能是php，也有可能是java，也有可能是python，也有可能是ruby，如何是好啊。<br /><br />	首先长连接是不二选择，高并发，高连接数是我们最中意的，有了这两个特性，我们这个application就可以被其他很多app使用了，就象共享memcached server一样。<br /><br />其次支持多客户端语言最好是能够有效利用现有资源，比如说不需要自己去开发客户端。<br /><br />	这时候memcached就可以抱着mina出场了。理由：<br />1 Memcached有众多的客户端，可谓周边齐全，看来非它莫数。<br />2 mina可以非常方便的开发出server端程序，好姑娘啊。<br /><br />来吧，看看最简单的示例：<br />Server端主类：<br /><pre name="code" class="java">import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;

import org.apache.mina.common.ByteBuffer;
import org.apache.mina.common.IoAcceptor;
import org.apache.mina.common.SimpleByteBufferAllocator;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
import org.apache.mina.transport.socket.nio.SocketAcceptor;
import org.apache.mina.transport.socket.nio.SocketAcceptorConfig;

/**
 * @author ahuaxuan(aaron zhang)
 * @since 2008-7-21
 * @version $Id$
 */
public class MinaServer {

	private static final int PORT = 11211;
	
	public static void main(String[] args) throws IOException {
		// code will go here next
		ByteBuffer.setUseDirectBuffers(false);
		ByteBuffer.setAllocator(new SimpleByteBufferAllocator());

		IoAcceptor acceptor = new SocketAcceptor();

		SocketAcceptorConfig cfg = new SocketAcceptorConfig();
//		cfg.getFilterChain().addLast("logger", new LoggingFilter());
		cfg.getFilterChain().addLast(
				"codec",
				new ProtocolCodecFilter(new TextLineCodecFactory(Charset
						.forName("UTF-8"))));

		 acceptor.bind(new InetSocketAddress(PORT), new ServerHandler(), cfg);

		 System.out.println("------------ Mina Server start up -----------");
	}

}</pre><br />这样，启动这个main方法，就可以建立一个socket server的实例了，欢迎大家来连！！！<br />相当简单啊，再看看一个重要的ServerHandler类<br />其中有一个重要的方法：<br /><pre name="code" class="java">public void messageReceived(IoSession session, Object msg) throws Exception {
		String str = msg.toString();
		String[] parts = str.split(" ");
		if (parts != null && "get".equals(parts[0])) {
			
			StringBuilder sb = new StringBuilder();
			sb.append("VALUE").append(" key ");
			sb.append("1").append(" ");
			sb.append(str.length()).append(" \r\n ");
			sb.append(str + "\r\n").append("");

			//看看这里吧
			session.write(sb.toString());
			session.write("END\r\n");

			System.out.println("Message written..." + sb.toString());
		} else {
			throw new IOException("unsupportoperation");
		}

	}</pre><br />哟，这么多\r\n，还有”END\r\n”这种东西？不好意思，这个是memcached协议定的我也没有办法（说到这里大家终于知道了本文其实只不过是用mina来实现memcached协议而已，前面被我那么多废话解释弄晕了吧，嘿嘿）。<br /><br /><br />	看到这里，我想要提醒一下，msg通常是这样的get aaabbbccc,其中aaabbbccc是key，但是用在我们自己的server上，它就可以不是key了，比如说它可以是/getDistrict.do?name=xx&cc=yy`````````，server拿到这样的字符串之后，一解析就知道客户端要什么了，那么就可以返回数据了，是我的话我会用json序列化我的对象，然后返回。只是一定要告诉客户端我的数据包括哪些内容，又快并发又高（再次提醒，我的application可以高并行计算，比如说大多数数据都在内存中哦），hoho，而且任何一个客户端语言都可以享受这种服务哦。<br /><br /><br />     好了，数据成功返回之后，为了让memcached的客户端能够成功解析，我们必须使用memcacached协议，看一段get协议的解释吧（目前主要是使用这个）：<br /><br />一行取回命令如下：<br /><span style="color: green">get &lt;key>*\r\n<br />&lt;key>* 表示一个或多个键值，由空格隔开的字串<br />这行命令以后，客户端的等待0个或多个项目，每项都会收到一行文本，然后跟着数据区块。所有项目传送完毕后，服务器发送以下字串：<br />"END\r\n"来指示回应完毕。<br /><br />服务器用以下形式发送每项内容：VALUE &lt;key> &lt;flags> &lt;bytes>\r\n<br />&lt;data block>\r\n<br />&lt;key> 是所发送的键名<br />- &lt;flags> 是存储命令所设置的记号<br />&lt;bytes> 是随后数据块的长度，*不包括* 它的界定符“\r\n”<br />- &lt;data block> 是发送的数据如果在取回请求中发送了一些键名，而服务器没有送回项目列表，这意味着服务器没这些键名（可能因为它们从未被存储，或者为给其他内容腾出空间而被删除，或者到期，或者被已客户端删除）。</span><br /><br /><br /><br />这样就可以了，注意，这里只不过是很简单的测试代码（正式代码不便给出），也只不过是提供一个思路而已，大家如果也需要用到这样的场景可以沿着这个思路走下去。<br /><br /><br />声明：由于ahuaxuan水平有限，文中难免有不妥之处，希望大家不吝赐教。<br /><br />ps:如果你没有memcached的客户端，最快的见效的是通过telnet，在windows console里输入telnet localhost 11211，就可以了<br /></span>
          <br/>
          <span style="color:red;">
            <a href="http://complystill.javaeye.com/topic/217165#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></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Mon, 21 Jul 2008 17:06:45 +0800</pubDate>
        <link>http://www.javaeye.com/topic/217165</link>
        <guid>http://www.javaeye.com/topic/217165</guid>
      </item>
      <item>
        <title>OGNL &amp; ValueStack 入门</title>
        <author>JavaEye网站</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://jxb8901.javaeye.com">jxb8901</a>&nbsp;
          链接：<a href="http://www.javaeye.com/topic/223612" style="color:red;">http://www.javaeye.com/topic/223612</a>&nbsp;
          发表时间: 2008年08月03日
          <br/>
          声明：本文系JavaEye网站发布的原创文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          h1. 一个例子<br />请看下面的需求，假设有如下用户对象模型：<br /><pre name="code" class="java">
public interface User {
    public String getName();
    public Date getRegisterDate();
    public Customer getCustomer();
}
public interface Customer {
    public String getId();
    public String getName();
    public boolean isVip();
}
public interface EntCustomer extends Customer {
    public String getTrustId(); // 组织机构代码证号
}
</pre><br />对于给定的用户jack，且该用户所属客户是企业客户，那么我们如何获取该用户的姓名？如何获取用户所属客户的名称？如何判断该用户所属客户是否是VIP客户？如何取jack所属企业的组织机构代码证号？<br /><br />* 采用java代码的方式，我们可以用如下的API调用得到所需信息：<br /><pre name="code" class="java">
jack.getName();
jack.getCustomer().getName();
jack.getCustomer().isVip();
((EntCustomer)jack.getCustomer()).getTrustId();
</pre><br /><br />* 但我们现在在讲述OGNL，因此通过采用OGNL，我们可以用如下方式取得我们所需要的信息：<br /><pre name="code" class="java">
jack.name
jack.customer.name
jack.vip
jack.customer.trustId
</pre><br />由此我们可以看到OGNL的表达方式与java表达方式有以下几点不同：<br />** 不需关注对象类型，不需进行类型转换<br />** 表达方式更简短和直观<br /><br />OGNL表达式最大的优点就是：*简单* 和 *直观*，你不这样认为吗？ 如果你觉得上面的表达式还不够简单和直观，那我们再来看：<br /><pre name="code" class="java">name</pre><br />这也是一个OGNL表达式，也就是取姓名！简单吗？至少足够直观了吧:)<br /><br />h1. 基本概念<br />我们前面看到了OGNL的一个最简单的例子，事实上OGNL确实很简单，如果能理解上面那个例子的用法，那么我们就掌握了OGNL的80%的用法了。<br />上面的例子虽然简单，但其中却含有OGNL的两个最基本的概念：*表达式(expression)* 和 *上下文(context)*，我们先看*表达式*。<br /><br />h3. 表达式<br />OGNL就是表达式！它能让我们用简洁直观的语法表达我们的想法，如同上面的例子一般。简洁直观就是表达式的最大优点！我们知道表达式总是有一个结果，也就是说表达式总是会求值出一个结果，这个结果可能是一个字符串（如名称、组织机构代码证号等），或者是一个布尔值（如是否是VIP客户等），至于这个结果要怎么使用，那就是我们自己来决定的了。<br /><br />h3. 上下文（context）<br />表达式的概念，我相信很好理解，但什么是上下文（context）？简单来说上下文就是环境，表达式求值的环境！还是不理解吗？我们来看一个例子：<br />还是上面最后那个例子：<pre name="code" class="java">name</pre> 细心的你是否会问，这个表达式要取谁的姓名呢？OK，很好！这就是环境，"谁"就存在于环境之中，也就是存在上下文之中。对于不同的环境/上下文，相同的表达式会有不同的结果！而环境/上下文的实质是什么呢？就是一组带名称的对象集合。<br /><div class="quote_title">引用</div><div class="quote_div">思考：表达环境或上下文这个概念的最好的数据结构是什么？</div><br /><br />h3. OGNL上下文概念详解<br />我们前面说上下文就是一组名称-对象对的集合，如下图所示就是一个简单的上下文：<br /><pre name="code" class="java">
user ---> User(name:"jack", ...)
request ---> HttpServletRequest(header: ...)
</pre><br />那么在上面的环境中，我们可以有如下的OGNL表达式：<br /><pre name="code" class="java">
#user.name // 取用户的姓名
#user.age // 取用户年龄
#user.birthday // 取用户生日
#user.customer.name // 取用户所属客户的名称
#request.parameters // 取请求参数
</pre><br />请注意上面表达式中的"#user"和"#request"的用法，"#"表示访问环境/上下文中的对象。<br /><br />现在可以很方便地访问环境中的对象了，那么如果你比较懒惰的话（记住：在程序员群体，懒惰是褒义词！），你是否觉得访问用户的姓名，年龄，生日，等等其它属性如果全部要使用"#user"来访问会不会太麻烦了呢？OK，ONGL的设计者早就考虑了这个问题，我们可以指定user为环境中的特权对象，访问该对象可以不需要使用#user的方式，如下所示代码与上面的完全等价，当然，前提是要预先指定user为特权对象：<br /><pre name="code" class="java">
name // 取用户的姓名
age // 取用户年龄
birthday // 取用户生日
customer.name // 取用户所属客户的名称
#request.parameters // 取请求参数
</pre><br /><br />我们上面所说的"特权对象"在OGNL中称为"根对象"(root)<br /><br />h3. 小结<br />综上所述，理解OGNL表达式的关键是理解其上下文的概念，因为OGNL的上下文概念中引入了"根对象"的概念，所以初学者往往会在这里迷失方向。<br /><br /><div class="quote_title">引用</div><div class="quote_div"><br />OGNL的中文全称是对象图导航语言，也就是说OGNL是一门语言，如同java是一门语言一样。你是否会认为OGNL的作者太夸张了，竟敢把表达式谎称为语言？不，OGNL的语法确实非常简洁，OGNL的代码（我没有说表达式，因为代码是和语法相匹配的词语）通常不会换行，这意味着我们不可能把OGNL的代码写得很长，但是，这并不意味着OGNL的表达能力很弱。事实上，OGNL的语法设计非常简洁，但其功能却相当强大，如果你有兴趣，可以深入阅读OGNL参考手册的集合与lambda章节。<br /></div><br /><br />慢着，事情还未至此结束！struts2对OGNL中的上下文的概念又定义了新的含义，且听我慢慢道来！<br /><br />h3. struts2中的OGNL上下文<br />struts2对OGNL上下文的概念又做了进一步扩充，在struts2中，OGNL上下文通常如下所示：<br /><pre name="code" class="java">
|
                     |--request
                     |
                     |--application
                     |
       context map---|--OgnlValueStack(root) [ user, action, OgnlUtil, ... ]
                     |
                     |--session
                     |
                     |--attr
                     |
                     |--parameters
</pre><br />我们可以使用"#requet"访问HttpServletRequest对象, "#session"访问HttpSession对象，但请注意"根对象"是什么？是ValueStack!<br />那么ValueStack是什么？值栈。也就是一组对象的堆栈。也就是说，在struts2中，根对象不是我们通常的一个对象，而是一组对象。我们可以push新的对象到值栈中，也可以弹出值栈的栈顶对象。如上图所示，假设我们将user对象push到值栈中，那么如下的表达式将与之前我们见过的表达式一样，具有相同的结果：<br /><pre name="code" class="java">
name // 取用户的姓名
age // 取用户年龄
birthday // 取用户生日
customer.name // 取用户所属客户的名称
#request.parameters // 取请求参数
</pre><br />也就是说，我们使用name这个表达式的时候，ONGL会取"根对象"的name属性，但现在根对象是ValueStack！那么访问ValueStack的name属性意味着什么呢？这意味着: ValueStack会先查看栈顶元素是否有name属性，如果有就返回该属性值，否则取出栈顶下的元素，继续查看，直到栈底为止。<br /><br />以上就是OGNL表达式的核心概念，你理解了吗？下一步，你需要了进一步了解OGNL的语法，以发掘其更强大的功能！
          <br/>
          <span style="color:red;">
            <a href="http://complystill.javaeye.com/topic/223612#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></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Sun, 03 Aug 2008 12:53:45 +0800</pubDate>
        <link>http://www.javaeye.com/topic/223612</link>
        <guid>http://www.javaeye.com/topic/223612</guid>
      </item>
      <item>
        <title>让google来为rails画图表</title>
        <author>JavaEye网站</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://liuqiang.javaeye.com">liuqiang</a>&nbsp;
          链接：<a href="http://www.javaeye.com/topic/219853" style="color:red;">http://www.javaeye.com/topic/219853</a>&nbsp;
          发表时间: 2008年07月26日
          <br/>
          声明：本文系JavaEye网站发布的原创文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 去年年底的时候，所做的一个rails项目涉及到图表功能，主要有显示投票结果（柱状图）、网上办事统计结果（饼状图）、已办事件按月统计结果（线状图）&hellip;&hellip;，当时可真是一件很麻烦的事情，开始准备搬flex来做，结果考虑到开发成本等等原因没采用，后来是自己写js，做了好一段时间，结果在跨平台上效果却不是很理想。当时真是苦煞我也，心想要是请几个专职的google专家来帮我做成和google一样的效果多好啊，巧合的是Google于去年晚些时候悄然推出了新图表API。Google图表最初是作为视频和财经服务的一项中间项目，后来Google决定将其公诸于世。Google始终如一地向大家提供如此优雅和高效的解决方案来处理通用问题，当然Google图表也不例外。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 那么是Google图表是如何为我们服务的呢？主要通过简单地发送一条URL来生成图表，调用者的主要工作是构建这些URL，该URL最主要有以下三个参数：图表的类型、图表的大小和图表的数据。图表的类型由&ldquo;cht&rdquo;参数指定。图表大小用chs指定，包括图表的长和宽，用整数来表示。图表数据用chd表示，Google提供了四种不同的数据编码方式，最简单的就是文本编码。通过给数据添加&ldquo;t:&rdquo;前缀。比如 <a href="http://chart.apis.google.com/chart?cht=lc&amp;chs=100x50&amp;chd=t:25,75,50"><span style="font-family: Courier New;">http://chart.apis.google.com/chart?cht=lc&amp;chs=100x50&amp;chd=t:25,75,50</span></a> 就是一条完整的图表服务路径，更完整的图表API可以参考 <a href="http://code.google.com/apis/chart/">http://code.google.com/apis/chart/</a>。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 那么接下来我们就是去构建这些URL，这里仍存在2个问题：</p>
<p>1 构建这样的URL需要大量的字符串拼接操作，较为繁琐，对于比较数据量比较大的图表，构建这样的URL就很麻烦。</p>
<p>2&nbsp;构建这样的URL其实很多地方是重复的，只是少数的参数不一样，对于有大量图表显示的系统来说要做很多重复性的工作。</p>
<p>幸运的是，我们不必重复发明轮子了，DEEpak Jois已经封装了该API，他的gem叫做gchartrb，提供一个整洁简明的方式来生成图表URL。使用该gem的第一步是安装它：gem install gchartrb。&nbsp;使用起来超乎想象的简单，效果也非常的炫，不信？看看我做的一些demo吧：）</p>
<p>&nbsp;</p>
<p>场景一 venn图<strong> </strong>例如：A有500个元素，B有400个元素，C有300个元素，AB交集为200，AC交集为100， BC交集为50通过以上的数据得到代表变量A,B,C的三个圆圈，圆圈的面积代表变量所含元素个数，圆圈的交集代表变量之间的交集。</p>
<p>&nbsp;</p>
<pre name="code" class="java">require 'rubygems'
require 'google_chart'
def venn_diagram
    GoogleChart::VennDiagram.new("400x400", 'Venn Diagram') do |vd|
      vd.data "Blue", 500,'0000ff'
      vd.data "Green", 400, '00ff00'
      vd.data "Red", 300, 'ff0000'
      vd.intersections 200, 100, 50
      @chart = vd.to_url
    end
end</pre>
<pre name="code" class="java">&lt;%= image_tag @chart %&gt; </pre>
<p>&nbsp;</p>
<p><img src=" http://liuqiang.javaeye.com/upload/picture/pic/18582/47d44714-be77-36d8-9899-b0f50dc410b8.bmp " height="417" alt="" width="427" /></p>
<p>&nbsp;</p>
<p>场景二 柱状图 例如JE会员分布图，beijing 20000人， shanghai 18000人，tianjin 10000人，nanjing 8000 ，guangzhou 14000，shenzhen 16000</p>
<p>&nbsp;</p>
<pre name="code" class="ruby"> def bar_chart
    GoogleChart::BarChart.new('800x200', "Bar Chart", :vertical, false) do |bc|
      bc.data "beijing", [20000], '0000ff'
      bc.data "shanghai", [18000], 'ff0000'
      bc.data "tianjin", [10000], '00ff00'
      bc.data "nanjing", [8000], '00aaff'
      bc.data "guangzhou", [16000], '0effee'
      bc.data "shenzhen", [14000], 'eeff00'
      @chart = bc.to_url
    end
  end</pre>
<pre name="code" class="ruby">&lt;%= image_tag @chart %&gt; </pre>
<p>&nbsp;</p>
<p><img src=" http://liuqiang.javaeye.com/upload/picture/pic/18584/91e6df58-d84e-3bea-91b5-a41c62eb8490.bmp " height="259" alt="" width="339" /></p>
<p>场景三 饼状图 例如JE文章投票人数统计，very good 200票， good 150票，just so so100票，bad 180票</p>
<p>&nbsp;</p>
<pre name="code" class="ruby">def pie_chart
    GoogleChart::PieChart.new('320x200', "Pie Chart",false) do |pc|
      pc.data "very good", 300
      pc.data "good", 200
      pc.data "just so so", 100
      pc.data "bad", 180
      pc.show_labels = true
      @chart = pc.to_url
    end
end</pre>
<pre name="code" class="ruby">&lt;%= image_tag @chart %&gt; </pre>
<p>&nbsp;&nbsp;</p>
<p><img src="http://liuqiang.javaeye.com/upload/picture/pic/18586/ec091b1d-7705-3a77-a3fa-dd7457bdf61d.bmp " height="248" alt="" width="354" /></p>
<p>&nbsp;场景四 折线图 例如统计每周JE会员增加数量，一周的数量分别是 56&nbsp;48 68 59 66 67 59</p>
<p>&nbsp;</p>
<pre name="code" class="ruby">GoogleChart::LineChart.new('320x200', "Line XY Chart", true) do |lcxy|
      lcxy.data "amount", [[1,56], [2,48], [3,68], [4,59], [5,66], [6,67], [7,59]], '0000ff'
      @chart = lcxy.to_url
end</pre>
<pre name="code" class="ruby">&lt;%= image_tag @chart %&gt; </pre>
<p>&nbsp;<img src="http://liuqiang.javaeye.com/upload/picture/pic/18588/1648c1da-21d3-32f6-bff2-27dbaa131e7e.bmp " height="252" alt="" width="346" /></p>
<p>&nbsp;</p>
<p>当然图表中的数据源在实际应用是应该来自于数据库的！</p>
          <br/>
          <span style="color:red;">
            <a href="http://complystill.javaeye.com/topic/219853#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></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Sat, 26 Jul 2008 17:05:30 +0800</pubDate>
        <link>http://www.javaeye.com/topic/219853</link>
        <guid>http://www.javaeye.com/topic/219853</guid>
      </item>
      <item>
        <title>Ruby/Rails: 不一样的'Web'应用</title>
        <author>JavaEye网站</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://rubynroll.javaeye.com">rubynroll</a>&nbsp;
          链接：<a href="http://www.javaeye.com/topic/219826" style="color:red;">http://www.javaeye.com/topic/219826</a>&nbsp;
          发表时间: 2008年07月26日
          <br/>
          声明：本文系JavaEye网站发布的原创文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          我不是Web程序员，也从来未开发过用户超过10个人的'web'项目:-)，但这并不意味着web技术对我无用，正相反，我开发过许多和web技术有关的程序和库,web架构和思想对我的设计和开发有着很大的帮助。<br /><br />例如我经常碰到要设计通讯协议以适应不可靠的传输，web的无状态特性正是克服不可靠传输的法宝，屡试屡爽:-)<br /><br />最近一段时间用ruby作了不少东西，也有用rails，但是从来没有对rails内部进行深究.直到最近碰到一个项目，促使我不得不到rails里面去挖掘，以借鉴rails一些优秀的东西。<br /><br />这个项目非常有趣，是一套用于农场的自动化系统（所以使用者大多是奶牛，哈哈～），包含用于工人们佩戴的移动W设备，到处安装的RFID数据收集设备，闸门控制设备，电子称，显示屏....还有许多名目繁多的设备，这些设备的数据有的通过电缆，有的通过ZigBee无线网络汇集到一台服务器上，这台服务器跑Linux，运行Rails。服务器负责收集所有设备的数据，和发送指令给相关设备，例如控制闸门。<br /><br />给我不断带来麻烦的是这个W设备。<br /><br />W设备是这个系统里面除服务器外唯一和人打交道的设备，配有RFID数据收集器，一个小液晶显示屏，数个按钮，通过ZigBee无线网络和服务器通讯---典型的嵌入式移动设备。<br /><br />客户起初对W设备的操作功能要求不高而对成本敏感，因此W设备配置了很低的硬件资源，基本上就是靠一个集成了无线功能的MCU操作，仅数十k的内存，但对于简单的数据收集和传输，绰绰有余。<br /><br />然而好景不长，随着项目进行，客户对W设备赋予了更多更重要的角色，功能需求暴涨，且快速变化，更重要的是，要求日后能够定制功能（二次开发）。<br /><br />Mission Impossible ? 不，这正是一个典型B/S架构的系统最适合做的事情了。当然普通web浏览器无法在W的数十k内存上跑，HTTP/HTML也无法有效的在ZigBee网络上传输，因此我们就新设计了一套通讯协议和标记语言,姑且称之为MML(Mini Markup Language)。<br /><br />那么服务器端呢？已经有rails在跑用于收集/展示数据，输出报表之类的任务，可以利用rails来作为W的服务端么?<br /><br />通讯问题，改改Webric，从串口(连接到ZigBee网络控制器)上获取数据并伪装成web请求,或许还可以...路由问题，由于ZigBee带宽极其有限，需要高度精简传输内容，因此传回来的请求串中包含的是非常简短的内容，需要转换到对应的Controller/Action，这个通过添加router映射似乎也可行。MML Render问题就比较头痛了，需要修改的地方不少。<br /><br />考虑到修改rails可能工作量和分险比较高，于是采用另外一个方案：设计新的W Server，在W Server里面直接利用rails的资源。实际上，最终这个W Server直接放在rails应用程序的scripte目录下（也许vendor目录更合适），在W server里面只要加入：<br /><pre name="code" class="ruby">require File.dirname(__FILE__) + '/../config/boot'</pre><br />然后就可以使用rails提供的任何资源了，当然对于W Server来说，最有用的是Models。这样W Server就和rails应用程序浑然一体了。<br /><br />W Server有了rails这样强大的后盾之后，还需要有：Parser, Router,Server,Controller,和MML render.<br /><br />Parser: 负责解析自定义的通讯协议，获取从W设备传来的请求，并解析参数等。<br />Router: 扫描并装载Controllers，缓存Controller对象，根据Parser的结果取得相应Controller的对象。Router还负责监控Controller是否已被修改和重新装载Controller.<br />Server: 总控制，从串口中读取数据，调用Parser解析,把结果传给Router，从Router中获取Controler对象并根据Parser结果调用Controller.Action，以及缓存Controller的输出等等。<br />Controller: 业务逻辑都在这里实现，调用rails的Models访问数据库。<br />MML Render: 使用erb作为模板文件，再加上一些辅助的功能，例如render link，menu之类的，和自定义的MML特性密切相关。MML Render作为module最终mixin到Controller里面。<br /><br />可以看出，W Server模型几乎和rails的一样，在设计W Server碰到问题时经常探究在rails中是怎么解决的，因此倒是对rails的了解增进了不少。当然由于W Server的工作方式，目标和rails不同，因此复杂度就不可同日而语.<br /><br />这个项目做完之后，我有几个感受：<br />＊ Ruby的开发效率真的很高<br />  除去注释，full stack的W Server的全部代码少于千行,what a surprise！当然，这里沾了rails的光，省却了Models，还有erb拿来就用.然而相比之下，在W上光MML解析和Render部分C代码就超过2000行.<br /><br />＊ 复用Rails真的很容易<br />  在这个案例中，就是增加一行代码而已.<br /><br />＊ Web技术，可以无处不在<br />Ruby/Rails = Make it real!<br /><br /><br />～文以共勉～
          <br/>
          <span style="color:red;">
            <a href="http://complystill.javaeye.com/topic/219826#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></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Sat, 26 Jul 2008 15:45:24 +0800</pubDate>
        <link>http://www.javaeye.com/topic/219826</link>
        <guid>http://www.javaeye.com/topic/219826</guid>
      </item>
      <item>
        <title>Ruby/Rails: 不一样的'Web'应用（续）</title>
        <author>JavaEye网站</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://rubynroll.javaeye.com">rubynroll</a>&nbsp;
          链接：<a href="http://www.javaeye.com/topic/220771" style="color:red;">http://www.javaeye.com/topic/220771</a>&nbsp;
          发表时间: 2008年07月28日
          <br/>
          声明：本文系JavaEye网站发布的原创文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          上一篇文章(<a href="http://www.javaeye.com/topic/219826" target="_blank">http://www.javaeye.com/topic/219826</a>)发出之后，很多人表示对这个案例很感兴趣，要求我再深入地谈谈。应大家之邀, 我对上一篇内容进行一些补充,谈谈如何在一个传统的嵌入式领域项目中为了拥抱变化而引入web技术,以及用定制的rails框架解决非web应用问题，最后简要地谈谈一般性应用的思考。<br /><br />在上一篇中,我轻描淡写地描述了由于客户对W设备赋予更多角色而导致W设备功能需求暴涨,最终选择web技术来解决问题,其实这里并非一蹴而就。<br /><br /><br />首先我们来分析一下要迎接的挑战:<br />(1) 增加很多复杂的操作界面(超出了W设备的现有资源能力)<br />(2) 功能变化快<br />(3) 需要日后可定制功能(二次开发)<br />(4) 维持低成本(意味着维持现有硬件架构不变)<br /><br />和大多数人一样,我们首先想到的是客户的要求是不是不太合理呢?又要马儿跑又要马儿不吃草?但是很快发现这里有一个契机,那就是W设备是保持在线的(通过ZigBee网络),那么我们就有机会透过网络转移计算,于是一个方案马上跃上来: unix终端。<br /><br />是的,古老的终端。<br /><br />在许多年前,我刚迈出大学校门时参加第一项开发工作就是字符终端设备的开发,所以对终端还算熟悉。不幸的是,我也开发过服务端的程序,知道在ncurses库下开发应用并不轻松,拥抱变化?难！客户的第(2)和第(3)项需求也无法很好地得到满足。<br /><br />我突然想起DHH在RailsConf 2007上的那个keynote(就是他大谈Cargo Cult的那次), 他把浏览器和IBM 3270做了有趣的对比。是的,那是个绝妙的对比,它给我留下的印象远大于Cargo Cult调侃。<strong>浏览器和终端本质上要解决的是同一个问题</strong>。由于web技术的发展取得长足的进步,服务器端进行应用开发资源也异常丰富。rails正是其中一颗冉冉升起的新星,更重要的是,服务器已经在跑rails了,就理所当然继续用rails。<br />在rails下开发应用那是太轻松了, 那么对付"功能快速变化"和"二次开发"就好办了。<br /><br />那么,焦点又回到了W设备: 浏览器?<br /><br />显然,在现有的硬件平台上上web浏览器是不可能的,如果升级硬件平台上WinCE或Embedded Linux,那么W设备的成本势必上升。况且,还有另外一些问题: 耗电问题，传输问题（ZigBee网络带宽极其有限,肥大的HTTP/HTML并不适合）; 而且W设备显示屏很小,所有功能操作都是只需要字符即可,无需fancy界面。因此,定义一套适合在ZigBee网络传输的协议和适合W设备的简易标记语言(MML)显然更为合理。<br /><br />客户端问题解决了，现在焦点回到服务器端，rails可以作为非web应用么？<br /><br />我对rails的内部细节不太了解，但是从外部来看，rails提供了以下主要服务：<br />1) MVC编程框架<br />2) 透过ActiveRecord与数据库打交道<br />3) 为HTML渲染提供服务<br />4) 其他如测试，数据迁移，插件，与web server的接口等等<br /><br />其中份量最重的ActiveRecord部分，与web完全无关。很多便利工具例如测试，数据迁移，插件机制等等，其实与web也无多大关系。<br /><br />既然rails的MVC中，M与web无关，C部分主要留给业务逻辑，而V部分对于非web领域价值不大，倘若要基于rails再建一个领域特定的MVC，工作量也就是集中在V部分了。而这个V部分，既然是领域相关，则无论采用什么方案都是一个不可避免的工作。当我们把rails的思想与习惯用法再应用到这个新的MVC上时，我们就得到了一个基于rails并且与rails神似的框架。我也来创一个buzzworld: DSF(Domain Specific Framework)，或者谦虚一点：RBDSF(Rails Based Domain Specific Framework)。<br /><br />就我的案例来看，这个DSF采用的通讯不是HTTP/TCP/IP而是基于ZigBee无线网络的自定义协议，展现数据的不是HTML而是自定义的MML，然而编程模式却和rails的web应用类似。举个简单的"Hello world"程序作为例子:<br /><br />contollers/main.rb:<br /><br /><pre name="code" class="ruby">class MainController &lt; WSController
  controller_map_to :m
  action_maps :index => "i"
  
  def index
    @text = "Hello world"
  end
end
</pre><br /><br />views/main/main_index.erb:<br /><pre name="code" class="ruby">&lt;%= "&lt;P3.20CrS2E3.5>#{@text}" %></pre><br /><br />在MainController中的controller_map_to起的作用是把自己（main）映射到一个较短的名字"m"，而action_maps则对action取短的别名，这样做主要是为了减少请求串的长度。剩下的，就和rails差不多了。<br />再看看view，其中&lt;P3.20CrS2E3.5>表示在第3行20列处(P3.20)，以红色(Cr)2号字(S2)显示@text，经过3.5妙后清楚屏幕(E3.5)。当然，如果想以更好的方式描述这个MML属性，则可以定义一系列helper函数,或者来点重的，弄个DSL。<br />这个例子没有演示Models，因为它是直接使用rails的Models，因此使用起来没有丝毫差别。<br /><br /><strong>[</strong>补充：在我的上一篇文章中，有一个功能我没有介绍，那就是模拟器。由于W设备需要在ZigBee网络中工作，对应用开发人员来说，为了开发应用而去安装整套设备比较麻烦;另外，客户想把整个系统作为产品推广，而不仅仅是自家用。这样一来，就需要有一个可以模拟W设备的环境,怎么实现？<br /><br />由于W Server是用ruby写的并基于rails，因此产生了一个绝妙的解决方案：在rails应用程序的一个控制器中直接调用W Server，把W Server的输出(MML)转换成HTML;同样的，把浏览器传来的@params内容转换成W Server需要的格式，然后我们就可以用浏览器模拟W设备了。当我们把用浏览器模拟W设备这个解决方案告诉客户时，客户诺以重金，而其实我们才用了数百行Ruby代码而已:-)<strong>]</strong><br /><br />总结：<br />遇到需求变化时，运用恰当的技术手段有时候可以柳暗花明,特别是跨领域交叉应用，能收到意想不到的效果。<strong>web技术的长足发展，也能给其他领域带来福音</strong>。当我们把MVC的概念推广到web之外，那么这个V就可以是任意的领域特定的数据展示格式。它既可以是基于文本的，也可以是基于二进制的；既可以自定义，也可以去兼容已有的格式；如果我们仔细去分析，其实很多基于主机计算模型的应用都可以用定制的MVC框架来实现，好处是MVC能够使应用程序结构更加清晰。而基于rails来实现DSF的优势是:rails已经提供了很好的基础，加上Ruby语言的强大语法，可以<strong>以很小的代价来实现适合你的应用的DSF</strong>。
          <br/>
          <span style="color:red;">
            <a href="http://complystill.javaeye.com/topic/220771#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></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Mon, 28 Jul 2008 21:23:30 +0800</pubDate>
        <link>http://www.javaeye.com/topic/220771</link>
        <guid>http://www.javaeye.com/topic/220771</guid>
      </item>
      <item>
        <title>潜入memcached server</title>
        <author>JavaEye网站</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://ahuaxuan.javaeye.com">ahuaxuan</a>&nbsp;
          链接：<a href="http://www.javaeye.com/topic/225692" style="color:red;">http://www.javaeye.com/topic/225692</a>&nbsp;
          发表时间: 2008年08月08日
          <br/>
          声明：本文系JavaEye网站发布的原创文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          /**<br /><br />*作者：张荣华<br /><br />*日期：2008-08-08<br /><br />**/ <br /><br />Memcached，人所皆知的remote distribute cache（不知道的可以javaeye一下下，或者google一下下，或者baidu一下下，但是鉴于baidu的排名商业味道太浓（从最近得某某事件可以看出），所以还是建议javaeye一下下），使用起来也非常的简单，它被用在了很多网站上面，几乎很少有大型的网站不会使用memcached。<br /><br />	曾经我也看过很多剖析memcached内部机制的文章，有一点收获，但是看过之后又忘记了，而且没有什么深刻的概念，但是最近我遇到一个问题，这个问题迫使我重新来认识memcache，下面我阐述一下我遇到的问题<br /><br />	问题：我有几千万的数据，这些数据会经常被用到，目前来看，它必须要放到memcached中，以保证访问速度，但是我的memcached中数据经常会有丢失，而业务需求是memcached中的数据是不能丢失的。我的数据丢失的时候，memcached server的内存才使用到60%，也就是还有40%内存被严重的浪费掉了。但不是所有的应用都是这样，其他应用内存浪费的就比较少。为什么内存才使用到60%的时候LRU就执行了呢（之所以确定是LRU执行是因为我发现我的数据丢失的总是前面放进去的，而且这个过程中，这些数据都没有被访问，比如第一次访问的时候，只能访问第1000w条，而第300w条或者之前的数据都已经丢失了，从日志里看，第300w条肯定是放进去了）。<br /><br />	带着这些疑问，我开始重新审视memcached这个产品，首先从它的内存模型开始：我们知道c++里分配内存有两种方式，预先分配和动态分配，显然，预先分配内存会使程序比较快，但是它的缺点是不能有效利用内存，而动态分配可以有效利用内存，但是会使程序运行效率下降，memcached的内存分配就是基于以上原理，显然为了获得更快的速度，有时候我们不得不以空间换时间。<br /><br />	也就是说memcached会预先分配内存，对了，memcached分配内存方式称之为allocator，首先，这里有3个概念：<br />1 slab<br />2 page<br />3 chunk<br />解释一下，一般来说一个memcahced进程会预先将自己划分为若干个slab，每个slab下又有若干个page，每个page下又有多个chunk，如果我们把这3个咚咚看作是object得话，这是两个一对多得关系。再一般来说，slab得数量是有限得，几个，十几个，或者几十个，这个跟进程配置得内存有关。而每个slab下得page默认情况是1m，也就是说如果一个slab占用100m得内存得话，那么默认情况下这个slab所拥有得page得个数就是100，而chunk就是我们得数据存放得最终地方。<br /><br />举一个例子，我启动一个memcached进程，占用内存100m，再打开telnet，telnet localhost 11211，连接上memcache之后，输入stats  slabs，回车，出现如下数据：<br /><pre name="code" class="java">STAT 1:chunk_size 80
STAT 1:chunks_per_page 13107
STAT 1:total_pages 1
STAT 1:total_chunks 13107
STAT 1:used_chunks 13107
STAT 1:free_chunks 0
STAT 1:free_chunks_end 13107
STAT 2:chunk_size 100
STAT 2:chunks_per_page 10485
STAT 2:total_pages 1
STAT 2:total_chunks 10485
STAT 2:used_chunks 10485
STAT 2:free_chunks 0
STAT 2:free_chunks_end 10485
STAT 3:chunk_size 128
STAT 3:chunks_per_page 8192
STAT 3:total_pages 1
STAT 3:total_chunks 8192
STAT 3:used_chunks 8192
STAT 3:free_chunks 0
STAT 3:free_chunks_end 8192</pre><br /><br />以上就是前3个slab得详细信息<br />chunk_size表示数据存放块得大小，chunks_per_page表示一个内存页page中拥有得chunk得数量，total_pages表示每个slab下page得个数。total_chunks表示这个slab下chunk得总数（＝total_pages * chunks_per_page），used_chunks表示该slab下已经使用得chunk得数量，free_chunks表示该slab下还可以使用得chunks数量。<br /><br />从上面得示例slab 1一共有1m得内存空间，而且现在已经被用完了，slab2也有1m得内存空间，也被用完了，slab3得情况依然如此。 而且从这3个slab中chunk得size可以看出来，第一个chunk为80b，第二个是100b，第3个是128b，基本上后一个是前一个得1.25倍，但是这个增长情况我们是可以控制得，我们可以通过在启动时得进程参数 –f来修改这个值，比如说 –f 1.1表示这个增长因子为1.1，那么第一个slab中得chunk为80b得话，第二个slab中得chunk应该是80*1.1左右。<br /><br />解释了这么多也该可以看出来我遇到得问题得原因了，如果还看不出来，那我再补充关键的一句：memcached中新的value过来存放的地址是该value的大小决定的，value总是会被选择存放到chunk与其最接近的一个slab中，比如上面的例子，如果我的value是80b，那么我这所有的value总是会被存放到1号slab中，而1号slab中的free_chunks已经是0了，怎么办呢，如果你在启动memcached的时候没有追加-M（禁止LRU，这种情况下内存不够时会out of memory），那么memcached会把这个slab中最近最少被使用的chunk中的数据清掉，然后放上最新的数据。这就解释了为什么我的内存还有40%的时候LRU就执行了，因为我的其他slab中的chunk_size都远大于我的value，所以我的value根本不会放到那几个slab中，而只会放到和我的value最接近的chunk所在的slab中(而这些slab早就满了，郁闷了)。这就导致了我的数据被不停的覆盖，后者覆盖前者。<br /><br />问题找到了，解决方案还是没有找到，因为我的数据必须要求命中率时100%，我只能通过调整slab的增长因子和page的大小来尽量来使命中率接近100%，但是并不能100%保证命中率是100%（这话怎么读起来这么别扭呢，自我检讨一下自己的语文水平），如果您说，这种方案不行啊，因为我的memcached server不能停啊，不要紧还有另外一个方法，就是memcached-tool，执行move命令，如：move 3 1，代表把3号slab中的一个内存页移动到1号slab中，有人问了，这有什么用呢，比如说我的20号slab的利用率非常低，但是page却又很多，比如200，那么就是200m，而2好slab经常发生LRU，明显page不够，我就可以move 20 2，把20号slab的一个内存页移动到2号slab上，这样就能更加有效的利用内存了（有人说了，一次只移动一个page，多麻烦啊？ahuaxuan说，还是写个脚本，循环一下吧）。<br /><br />	有人说不行啊，我的memcache中的数据不能丢失啊，ok，试试新浪的memcachedb吧，虽然我没有用过，但是建议大家可以试试，它也使利用memcache协议和berkeleyDB做的（写到这里，我不得不佩服danga了，我觉得它最大的贡献不是memcache server本身，而是memcache协议），据说它被用在新浪的不少应用上，包括新浪的博客。<br /><br />	补充，stats slab命令可以查看memcached中slab的情况，而stats命令可以查看你的memcached的一些健康情况，比如说命中率之类的，示例如下：<br /><pre name="code" class="java">STAT pid 2232
STAT uptime 1348
STAT time 1218120955
STAT version 1.2.1
STAT pointer_size 32
STAT curr_items 0
STAT total_items 0
STAT bytes 0
STAT curr_connections 1
STAT total_connections 3
STAT connection_structures 2
STAT cmd_get 0
STAT cmd_set 0
STAT get_hits 0
STAT get_misses 0
STAT bytes_read 26
STAT bytes_written 16655
STAT limit_maxbytes 104857600</pre><br />从上面的数据可以看到这个memcached进程的命中率很好，get_misses低达0个，怎么回事啊，因为这个进程使我刚启动的，我只用telnet连了一下，所以curr_connections为1，而total_items为0，因为我没有放数据进去，get_hits为0，因为我没有调用get方法，最后的结果就是misses当然为0，哇哦，换句话说命中率就是100%，又yy了。<br /><br />该到总结的时候了，从这篇文章里我们可以得到以下几个结论：<br />结论一，memcached得LRU不是全局的，而是针对slab的，可以说是区域性的。<br />结论二，要提高memcached的命中率，预估我们的value大小并且适当的调整内存页大小和增长因子是必须的。<br />结论三，带着问题找答案理解的要比随便看看的效果好得多。<br /><br />Ok,晚了，睡了。<br /><br /><br />注明：由于ahuaxuan水平有限，文中不妥之处还望不吝指正，谢谢。
          <br/>
          <span style="color:red;">
            <a href="http://complystill.javaeye.com/topic/225692#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></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Fri, 08 Aug 2008 09:57:40 +0800</pubDate>
        <link>http://www.javaeye.com/topic/225692</link>
        <guid>http://www.javaeye.com/topic/225692</guid>
      </item>
      <item>
        <title>利用&quot;事件上提&quot; 来简化事件注册(防止潜在的内存泄露).</title>
        <author>JavaEye网站</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://fins.javaeye.com">fins</a>&nbsp;
          链接：<a href="http://www.javaeye.com/topic/217512" style="color:red;">http://www.javaeye.com/topic/217512</a>&nbsp;
          发表时间: 2008年07月22日
          <br/>
          声明：本文系JavaEye网站发布的原创文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          众所周知, 浏览器中内存泄露以及内存无法回收(两者不是一回事,很多人都把他们弄混淆了),常常是由于对dom元素注册事件不当引起的.<br /><br />通常的解决方案是, 自行实现一套 添加事件, 移除事件 以及删除dom元素的机制.<br />为dom元素添加事件时, 同时记录 这个事件 以及对应的函数,<br />在删除dom元素时, 先移除dom元素上已经添加的事件 再删除dom元素本身.<br /><br />而当页面中添加了事件监听的dom元素很多时, 移除元素变得很麻烦.<br />例如 一个div 里面有个form form里有多个元素都添加了事件.<br />那么移除这个div时, 就要先去移除他下面每一个元素上的事件 然后再移除这个div.<br /><br />这种做法很多时候是必须的, 而且自己写一个"深度遍历子节点,并移除其事件"的函数也并不是很困难.<br /><br />但是 在很多时候 这种做法是可以避免的, 避免的方法就是, 把事件监听注册到更上层的dom元素中.<br />并且在事件函数中 通过 event.target/event.srcElement 来 事件发生在哪个元素上,然后来执行相关的方法.<br />这样 在移除元素时 只要移除这个元素以及它上面的事件 就可以了, 而不必执行(或者少量的执行)"移除所有子节点事件"的动作了.<br /><br /><br />见下面的例子 :<br /><br /><pre name="code" class="html">

  &lt;table width="300" border="1"  onclick="showDetail(event)"> 
  &lt;tr>
	&lt;td>1&lt;/td>
	&lt;td>Tom&lt;/td>
	&lt;td>&lt;input type="button" value="详细信息" userid="1" />&lt;/td>
  &lt;/tr>
  &lt;tr>
	&lt;td>2&lt;/td>
	&lt;td>Kate&lt;/td>
	&lt;td>&lt;input type="button" value="详细信息" userid="2"  />&lt;/td>
  &lt;/tr>
  &lt;tr>
	&lt;td>3&lt;/td>
	&lt;td>John&lt;/td>
	&lt;td>&lt;input type="button" value="详细信息"  userid="3" />&lt;/td>
  &lt;/tr>
  &lt;/table>

</pre><br /><br />在这个例子中, 实际上事件只是注册在table上, 而没有在"input type="button"上.<br /><br />"showDetail" 可以这样写<br /><br /><pre name="code" class="javascript">
	function showDetail_b(event) {
		event=event||window.event;
		var target=event.target||event.srcElement;
		if ( String(target.tagName).toLowerCase()=='input' &&  target.value=="详细信息") {
			showUserDetail(target.getAttribute('userid') );
		}
	}
</pre><br /><br /><br />当然 这种做法不是绝对的, 有时候这么做很可能让代码变得臃肿冗长.<br />到底是否使用"事件上提"的做法 要根据实际情况来选择.<br />不过 根据我的以往经验, 在列表(table)中, 使用这种技术非常合适.<br />因为 列表有着"行与行之间模型一致"(只是数据不一致,结构一致)的特点.<br /><br />例如,下面的效果, 都可以通过在 table上注册事件来实现:<br /><br />1 点击行, 行变色  (不必在 tr 上注册点击事件)<br />2 点击行中的某个按钮 (不必在 tr 里的 button 上注册点击事件)<br />3 鼠标经过行时 行变色 (不必在 tr上注册 mouseover/mouseout 事件, 而是可以在table上注册mousemove事件)<br />4 还有关于单元格的 很多效果.....<br /><br />当然,在非列表里 这种做法也有很多的用武之地.<br />总之 合理的利用"事件上提"的方法, 可以增强dom元素和事件的可控性, 有效的防止内存泄露和内存无法回收的情况.
          <br/>
          <span style="color:red;">
            <a href="http://complystill.javaeye.com/topic/217512#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></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Tue, 22 Jul 2008 10:26:30 +0800</pubDate>
        <link>http://www.javaeye.com/topic/217512</link>
        <guid>http://www.javaeye.com/topic/217512</guid>
      </item>
      <item>
        <title>HDFS用户指南(翻译）</title>
        <author>JavaEye网站</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://dennis-zane.javaeye.com">dennis_zane</a>&nbsp;
          链接：<a href="http://www.javaeye.com/topic/228132" style="color:red;">http://www.javaeye.com/topic/228132</a>&nbsp;
          发表时间: 2008年08月14日
          <br/>
          声明：本文系JavaEye网站发布的原创文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>
    
    
HDFS用户指南<br id="o3qg" />
原文地址：http://hadoop.apache.org/core/docs/current/hdfs_user_guide.html<br id="jh7z" />
译者：dennis zhuang(killme2008@gmail.com),有错误请指正，多谢。<br id="o3qg0" />
</p>
<h2 class="h3" id="o3qg1">目的<br id="o3qg2" />
</h2>
<div class="section" id="o3qg3">
<p id="o3qg4">本文档可以作为使用Hadoop分布式文件系统用户的起点，无论是将HDFS应用在一个Hadoop集群中还是作为一个单独的分布式文件系统使用。HDFS被设计成可以马上在许多环境中工作起来，那么一些HDFS的运行知识肯定能大大地帮助你对一个集群做配置改进和诊断。<br id="bz6u" />
 </p>
</div>
<p>
    
<a name="N1001B" id="o3qg6"></a>
<a name="Overview" id="o3qg7"></a>
</p>
<h2 class="h3" id="o3qg8">概览<br id="l_0z" />
  </h2>
<div class="section" id="o3qg9">
<p id="o3qg10">HDFS是Hadoop应用的主要分布式存储。一个HDFS集群由一个管理文件系统元数据的NameNode，和存储实际
数据的一些Datanode组成。HDFS的架构在这里有详细描述。这个用户指南主要提供给需要跟HDFS集群打交道的用户或者管理员。HDFS架构文章
中的图描绘了Namenode、Datanode和客户端们之间的基本交互。本质上，客户端与Namenode通讯获取或者修改文件的元数据，与
Datanode进行实际的IO操作。<br id="l_0z0" />
 </p>
<p id="l_0z1">下面的列表应该是大多数用户关心的HDFS突出特点。斜体字的术语将在后面详细描述。</p>
<p id="fvr_">1）Hadoop，包括HDFS，非常适合廉价机器上的分布式存储和分布式处理。它是容错的、可伸缩的，并且非常易于扩展。并且，以简单性和适用性著称的Map-Reduce是Hadoop不可或缺的组成部分。</p>
<p id="ag1q">2）HDFS的默认配置适合于大多数安装的应用。通常情况下，只有在一个非常大规模的集群上才需要修改默认配置。</p>
<p id="d26i">3）HDFS是用java编写的，支持大多数平台。</p>
<p id="queu">4）支持shell命令行风格的HDFS目录交互。</p>
<p id="ufzm">5）Namenode和Datanode都内建了web服务器，可以方便地查看集群的状态</p>
<p id="jafv">6）HDFS经常性地实现新的特性和改进，下面是HDFS中的一些有用特性的子集：</p>
<p id="c.lr">&nbsp;&nbsp; <em id="v3zz">文件许可和授权</em>
</p>
<p id="c.lr0">&nbsp;&nbsp; <em id="v3zz0">Rack awareness</em>
:当调度任务和分配存储的时候将节点的物理位置考虑进去。</p>
<p id="cbpn">&nbsp;&nbsp; <em id="v3zz1">Safemode(安全模式）</em>
：用于维护的一个管理状态</p>
<p id="tej-">&nbsp;&nbsp; <em id="v3zz2">fsck</em>
： 诊断文件系统的一个工具，用来查找丢失的文件或者block</p>
<p id="z_lu">&nbsp;&nbsp; <em id="v3zz3">Rebalancer</em>
:当数据在Datanode间没有均匀分布的时候，用于重新平衡集群的工具</p>
<p id="p1gp">&nbsp;&nbsp; <em id="v3zz4">升级和回滚</em>
：当Hadoop软件升级，在升级遇到不可预期的问题的时候，可以回滚到HDFS升级前的状态</p>
<p id="fzza">&nbsp;&nbsp; <em id="v3zz5">二级Namenode</em>
：帮助Namenode维持包含了HDFS修改的日志的文件（edits日志文件,下文谈到）大小在限制范围内。<br id="ubzs" />
</p>
</div>
<p> 
<a name="N10083" id="o3qg41"></a>
<a name="Pre-requisites" id="o3qg42"></a>
</p>
<h2 class="h3" id="o3qg43">前提条件<br id="px_d" />
  </h2>
<div class="section" id="o3qg44">
<p id="o3qg45">下面的文档描述了一个Hadoop集群的安装和设置：<br id="px_d0" />
</p>
<ul id="o3qg46">
<li id="o3qg47">
 		
<a href="http://hadoop.apache.org/core/docs/current/quickstart.html" id="o3qg48">Hadoop Quickstart</a>
，给初次使用用户 <br id="bb4w" />
</li>
<li id="o3qg49">
 		
<a href="http://hadoop.apache.org/core/docs/current/cluster_setup.html" id="o3qg50">Hadoop Cluster Setup</a>
 大规模、分布式集群<br id="p8d9" />
</li>
</ul>
<p id="o3qg51"><br id="tegj" />
</p>
<p id="tegj0">本文档的剩余部分假设你已经搭设并运行了一个至少拥有一个Datanode的HDFS。基于本文档的目的，Namenode和Datanode可以运行在同一台机器上。<br id="tegj1" />
</p>
</div>
<p> 
<a name="N100A1" id="o3qg52"></a>
<a name="Web+Interface" id="o3qg53"></a>
</p>
<h2 class="h3" id="o3qg54"> Web接口 </h2>
<div class="section" id="o3qg55">
<p id="o3qg56">Namenode和Datanode分别跑了一个内置的web服务器，来展现集群当前状态的一些基本信息。在默认配置
下，Namenode的首页地址是http://namenode:50070（namenode就是Namenode节点所在机器IP或者名称）。这个
页面列出了集群中的所有datanode以及集群的基本统计。web接口同样可以用于浏览文件系统（点击Namenode首页上的&ldquo;Browse
the file system&quot;链接）。<br id="lb5-" />
 	</p>
</div>
<p> 
<a name="N100AE" id="o3qg58"></a>
<a name="Shell+Commands" id="o3qg59"></a>
</p>
<h2 class="h3" id="o3qg60">Shell命令<br id="wep4" />
</h2>
<div class="section" id="o3qg61">
<p id="o3qg62">Hadoop包括了多种shell风格的命令，用于跟HDFS或者Hadoop支持的其他文件系统交互。命令
bin/hadoop fs -help 可以列出Hadoop shell支持的命令。更进一步，bin/hadoop fs -help
command
可以展现特定命令command的帮助细节。这些命令支持一般文件系统的操作，例如拷贝文件、修改文件权限等。同时也支持了部分HDFS特有的命令，例如
修改文件的replication因子。<br id="wep40" />
      </p>
<a name="N100BD" id="o3qg65"></a>
<a name="DFSAdmin+Command" id="o3qg66"></a>
<h3 class="h4" id="o3qg67"> DFSAdmin命令 </h3>
<p id="o3qg68">
   	
<span class="codefrag" id="o3qg69">'bin/hadoop dfsadmin'</span>
命令支持一些HDFS管理功能的操作。'bin/hadoop dfsadmin -help'可以列出所有当前支持的命令。例如：</p>
<ul id="o3qg71">
<li id="o3qg72">
   	    
<span class="codefrag" id="o3qg73">-report</span>
   	    : 报告HDFS的基本统计信息。部分信息同时展现在Namenode的web首页上。&nbsp;
   	</li>
<li id="o3qg74">
   		
<span class="codefrag" id="o3qg75">-safemode</span>
   		: 尽管通常并不需要，管理员还是可以通过手工操作进入或者离开safemode状态
   	</li>
<li id="o3qg77">
   		
<span class="codefrag" id="o3qg78">-finalizeUpgrade</span>
   		: 移除上一次升级时集群所做的备份。
   	</li>
</ul>
</div>
<p> 
<a name="N100E6" id="o3qg79"></a>
<a name="Secondary+Namenode" id="o3qg80"></a>
</p>
<h2 class="h3" id="o3qg81"> 二级Namenode </h2>
<div class="section" id="o3qg82">
<p id="o3qg83">Namenode将对文件系统的修改存储在一个原生文件系统文件中（名为edits的文件）。当Namenode启动的时
候，它从映像文件（fsimage)读取HDFS的状态，然后将edits日志文件中的修改作用在此内存状态上，接着将得到的新的HDFS状态写回
fsimage，后续的正常操作开始于一个空的edits日志文件。由于Namenode仅仅在启动的时候将fsimage和edits合并，因此在一个
大的集群上经过一定时间操作后，edits文件将会非常大。由此带来的一个副作用就是下次Namenode的重新启动将花费很长时间。二级
Namenode就是为了解决这个问题，它会周期性地合并fsimage和edits日志文件，并且将edits日志文件的大小保持在限制范围内。通常它
会跑在另一个机器上，因为它的内存要求跟主namenode一样。二级Namenode可以通过'bin/start-dfs.sh'启动在conf
/masters配置文件里配置的节点上。<br id="s4h2" />
     </p>
</div>
<p> 
<a name="N1010B" id="o3qg93"></a>
<a name="Rebalancer" id="o3qg94"></a>
</p>
<h2 class="h3" id="o3qg95"> Rebalancer </h2>
<div class="section" id="o3qg96">
<p id="o3qg97">HDFS的数据可能不会总是在Datanode之间分布得很一致。一个常见的原因是往现有的集群中加入了新的Datanode。当分配block的时候，Namenode依据几个参数来决定哪个datanode来接受这些block。一些需要考虑的因素如下：</p>
<p id="i-5q">1）一个block的副本存放在正在写该block的节点上</p>
<p id="fj63">2）需要将一个block的副本扩展到其他机架上，防止因为整个机架故障导致的数据丢失。</p>
<p id="ik23">3）副本之一通常放在同一个机架的另一个节点上，减少跨机架的网络IO</p>
<p id="dswx">4）将HDFS数据均匀一致地分布在集群中的datanode上。</p>
<p id="dswx0">&nbsp;&nbsp;&nbsp; 基于这些相互竞争的因素，数据可能不会在Datanode之间扩展得一致。HDFS给管理员提供了一个工具，用来分析block的分配情况和在datanode之间重新平衡数据。这个功能暂未实现，它的描述可以在这个&nbsp;<a href="http://issues.apache.org/jira/secure/attachment/12368261/RebalanceDesign6.pdf" id="o3qg105">PDF</a>
文档中看到，记录编号<a href="http://issues.apache.org/jira/browse/HADOOP-1652" id="o3qg106">HADOOP-1652</a>
.</p>
</div>
<p> 
<a name="N10132" id="o3qg107"></a>
<a name="Rack+Awareness" id="o3qg108"></a>
</p>
<h2 class="h3" id="o3qg109"> Rack Awareness </h2>
<div class="section" id="o3qg110">
<p id="o3qg111">典型的大规模Hadoop集群是部署在数个机架上的，那么显然同一个机架内的节点间的网络通讯比之不同机架间节点间的网
络通讯更可取。另外，Namenode会尝试将block的副本分布在数个机架中以提高容错性。Hadoop让集群管理员来决定某个节点从属于哪个机架，
通过配置变量dfs.network.script来实现。当这个脚本有配置的时候，每个节点都运行该脚本来决定它的rackid。默认安装假设所有的节
点从属于同一个机架。这个特性和配置进一步的阐述在这个<a href="http://issues.apache.org/jira/secure/attachment/12345251/Rack_aware_HDFS_proposal.pdf" id="o3qg116">PDF</a>
文档，编号为 
      <a href="http://issues.apache.org/jira/browse/HADOOP-692" id="o3qg117">HADOOP-692</a>
。
      </p>
</div>
<p> 
<a name="N10150" id="o3qg118"></a>
<a name="Safemode" id="o3qg119"></a>
</p>
<h2 class="h3" id="o3qg120"> Safemod(安全模式） </h2>
<div class="section" id="o3qg121">
<p id="o3qg122">当Namenode启动的时候，它从fsimage和edits日志两个文件中加载文件系统的状态。然后等待
datanode报告他们的block信息，以便防止Namenode在确认block副本是否足够前过早地开始复制block。这段时间的
Namenode就是处于所谓safemode状态。处于safemode的Namenode也是HDFS集群的只读模型，此时不允许任何对文件系统或者
block的修改。正常情况下，Namenode会在开始后自动退出safemode。如果有需要，HDFS可以通过'bin/hadoop
dfsadmin
-safemode'命令显式地进入safemode状态。Namenode的web首页显示当前的safemode是否打开。更详细的描述和配置可以参
考<a href="http://hadoop.apache.org/core/docs/current/api/org/apache/hadoop/dfs/NameNode.html#setSafeMode%28org.apache.hadoop.dfs.FSConstants.SafeModeAction%29" id="o3qg128"><span class="codefrag" id="o3qg129">setSafeMode()</span>
</a>
方法的JavaDoc。</p>
<p id="y.-h">译
注：详细介绍下safemode的配置参数，在safemode状态，Namenode会等待所有的datanode报告他们自己的block信息，看看
所有的block的副本是否达到最低要求的数目，这个数目可以通过dfs.replication.min参数配置，默认是1,也就是至少要求有一个副
本。当报告合格的Datanode的数目达到一定百分比，Namenode才会离开safemode状态。这个百分比也是可配置的，通过
dfs.safemode.<tt id="v23v">threshold.pct参数，默认是0.999f(也就是要求99.9%的Datanode
合格）。Namenode在合格的datanode数目达到要求的时候，并不是马上离开safemode状态，会有一个扩展时间，让剩余的
datanode来报告block信息，这个扩展时间默认是30秒，可以通过</tt>
<tt id="zwjf">dfs.safemode.extension参数配置，单位是毫秒。</tt>
      </p>
</div>
<p> 
<a name="N1016E" id="o3qg130"></a>
<a name="Fsck" id="o3qg131"></a>
</p>
<h2 class="h3" id="o3qg132"> Fsck </h2>
<div class="section" id="o3qg133">
<p id="o3qg134">HDFS提供了fsck命令用来检测各种各样的不一致性。fsck被设计用来报告各种文件的问题，例如某个文件丢失的
block，block的副本数目是否低于设置等。不同于传统的一般原生文件系统的fsck命令，hdfs的fsck命令并不修正所检测到的错误。通常情
况下，Namenode会自动修正大多数可以被修复的错误，HDFS的fsck不是Hadoop shel的命令，可以通过'bin/hadoop
fsck'执行，可以运行在整个文件系统上或者一个文件子集上。<br id="as3m" />
    
      </p>
</div>
<p> 
<a name="N1017E" id="o3qg137"></a>
<a name="Upgrade+and+Rollback" id="o3qg138"></a>
</p>
<h2 class="h3" id="o3qg139"> 升级和回滚 </h2>
<div class="section" id="o3qg140">
<p id="o3qg141">当升级某个集群的Hadoop的时候，正如任何软件的升级一样，可能会引入新的bug或者不兼容的修改导致现有的应用出
现过去没有发现的问题。在所有重要的HDFS安装应用中，是不允许出现因丢失任何数据需要从零开始重启HDFS的情况。HDFS允许管理员恢复到
Hadoop的早期版本，并且将集群的状态回滚到升级前。HDFS的升级细节请参考 <a href="http://wiki.apache.org/hadoop/Hadoop%20Upgrade" id="o3qg143">upgrade wiki</a>
。HDFS在任何时间只能有一个备份，因此在升级前，管理员需要通过'bin/hadoop dfsadmin -finalizeUpgrade'命令移除现有的备份。下面简要描述了典型的升级过程：</p>
<p id="ctbn">1）在升级Hadoop前，如果已经存在备份，需要先结束（finalize)它。可以通过'dfsadmin -upgradeProgress status'命令查询集群是否需要执行finalize</p>
<p id="kpd3">2)停止集群，分发部署新版本的Hadoop</p>
<p id="ur8l">3）执行新版本的hadoop，通过添加 -upgrade 选项，例如/bin/start-dfs.sh -upgrade</p>
<p id="gmxl">4)大多数情况下，集群在升级后可以正常运行。一旦新的HDFS在运行若干天的操作后没有出现问题，那么就可以结束(finalize)这次升级。请注意，在升级前删除的文件并不释放在datanode上的实际磁盘空间,直到集群被结束（finalize)升级前。</p>
<p id="xsll">5）如果有需要回到老版本的Hadoop，那么可以：</p>
<p id="xsll0">&nbsp;&nbsp; a)停止集群，分发部署老版本的Hadoop</p>
<p id="xsll1">&nbsp;&nbsp; b)通过rollback选项启动集群，例如bin/start-dfs.sh -rollback<br id="ctbn0" />
      </p>
<a name="N101BF" id="o3qg160"></a>
<a name="File+Permissions+and+Security" id="o3qg161"></a>
</div>
<h2 class="h3" id="o3qg162"> 文件许可和安全</h2>
<div class="section" id="o3qg163">
<p id="o3qg164">文件许可的设计与其他平台(如linux)
的文件系统类似。在当前实现，安全被限制在简单的文件许可上。启动Namenode的用户被作为HDFS的超级用户。HDFS的未来版本将支持网络验证，
例如Kerberos方案（译注：MIT开发的一个验证系统）的用户验证以及数据传输的加密。更详细的讨论参考<a href="http://hadoop.apache.org/core/docs/current/hdfs_permissions_guide.html" id="o3qg166"><em id="o3qg167">Permissions User and Administrator Guide</em>
</a>
。           
      </p>
</div>
<p> 
<a name="N101D1" id="o3qg168"></a>
<a name="Scalability" id="o3qg169"></a>
</p>
<h2 class="h3" id="o3qg170">伸缩性<br id="m-bp" />
  </h2>
<div class="section" id="o3qg171">
<p id="o3qg172">Hadoop正运行在成千上万个节点的集群上。      <a href="http://wiki.apache.org/hadoop/PoweredBy" id="o3qg173">PoweredBy Hadoop</a>
列
出了一些部署Hadoop在大规模集群上的组织和机构。HDFS在每个集群上只有一个Namenode节点，Namenode节点上可用内存是当前伸缩性
的主要限制。在非常大规模的集群上，增加HDFS中存储的文件的平均大小，将可以帮助提高集群的大小而不用增加Namenode的内存需求。默认的配置可
能不适合非常大规模的集群应用。<a href="http://wiki.apache.org/hadoop/FAQ" id="o3qg174">Hadoop FAQ</a>
页列出了对于大规模Hadoop集群的配置改进建议。
      </p>
</div>
<p> 
<a name="N101E3" id="o3qg175"></a>
<a name="Related+Documentation" id="o3qg176"></a>
</p>
<h2 class="h3" id="o3qg177">关联文档<br id="n8q_" />
 </h2>
<div class="section" id="o3qg178">
<p id="o3qg179">&nbsp;本用户指南可作为使用HDFS很好的一个起点，在本文档持续改进的同时，有一些非常有价值的关于Hadoop和HDFS的文档资料可供参考。下列资料可作为进一步探索的起点：<br id="ugay" />
</p>
<ul id="o3qg180">
<li id="o3qg181">
        
<a href="http://hadoop.apache.org/" id="o3qg182">Hadoop Home Page</a>
        : Hadoop一切的起始页。
      </li>
<li id="o3qg183">
        
<a href="http://wiki.apache.org/hadoop/FrontPage" id="o3qg184">Hadoop Wiki</a>
        :由社区维护的wiki文档。</li>
<li id="o3qg185"> 
<a href="http://wiki.apache.org/hadoop/FAQ" id="o3qg186">FAQ</a>
 from Hadoop Wiki.
      </li>
<li id="o3qg187">
        Hadoop <a href="http://hadoop.apache.org/core/docs/current/api/" id="o3qg188">
          JavaDoc API</a>
.
      </li>
<li id="o3qg189">
        Hadoop User Mailing List : 
        <a href="mailto:core-user@hadoop.apache.org" id="o3qg190">core-user[at]hadoop.apache.org</a>
.
      </li>
<li id="o3qg191">
         浏览<span class="codefrag" id="o3qg192">conf/hadoop-default.xml</span>
文件，它包括了当前可用配置变量的概括介绍。
      </li>
</ul>
</div>
<p><br id="o3qg193" />
</p>
          <br/>
          <span style="color:red;">
            <a href="http://complystill.javaeye.com/topic/228132#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></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Thu, 14 Aug 2008 20:27:56 +0800</pubDate>
        <link>http://www.javaeye.com/topic/228132</link>
        <guid>http://www.javaeye.com/topic/228132</guid>
      </item>
      <item>
        <title>ActionView 的魔术：ERB &amp; Binding</title>
        <author>JavaEye网站</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://hozaka.javaeye.com">hozaka</a>&nbsp;
          链接：<a href="http://www.javaeye.com/topic/227618" style="color:red;">http://www.javaeye.com/topic/227618</a>&nbsp;
          发表时间: 2008年08月13日
          <br/>
          声明：本文系JavaEye网站发布的原创文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>Rails 作为一个 MVC 框架，其核心包括三个模块：ActiveRecord，ActionController 和 ActionView。今天这篇博文的主角是 ActionView，解开模板系统的魔术。<br />

<br />

通常情況下，通过 scaffold 已经能够建立简单的、包含CRUD基本功能的页面，完全不需要手动修改 view 的代码。即使不使用 scaffold ，Rails 也提供了众多的辅助方法，创造一个功能丰富的动态页面简直是易如反掌。但是，会用不代表深入理解，最近有朋友问我这些问题：</p>
<ol>
<li>为什么编辑一个对象需要在 Controller 创造一个实例变量</li>
<li>View 通过什么方式访问这些实例变量的</li>
<li>那么多表单辅助方法，都是需要提供 object_name, method 两个参数，怎么就变成实例变量的值了</li>
</ol>
<p>相信大部分的 Rails 程序员手边的书都是《Agile Web Development with Rails》，书中提到这一点的时候一笔带过，只是说 Rails 在这里用了一个小魔术。这里，我们就来揭开这个魔术吧！</p>
<p>&nbsp;</p>
<h4>Part 1. Template Files - 模板文件<br />
</h4>
<p>当一个 action 需要返回一段 html 片段的时候，我们需要建立一个模板文件。根据不同的版本、请求类型，模板文件的文件名也各不相同，从早期版本的 action_name.rhtml ，到现在的 action_name.text.html.erb ，以及扩展的 rjs，在扩展名中都包含了一个关键字：r / erb。它就是 Rails 模板系统的关键：ERb。</p>
<p>&nbsp;</p>
<h4>Part 2. ERb - Ruby Templating<br />

</h4>
<p>ERb - 嵌入式 Ruby (<a href="http://ruby-doc.org/stdlib/libdoc/erb/rdoc/index.html">http://ruby-doc.org/stdlib/libdoc/erb/rdoc/index.html</a>

)，是 Ruby 语言提供的一个基本扩展。它支持在字符串中嵌入 ruby 代码片段。看上去似乎很神秘，其实我们每天都用到，下面这种形式一定不陌生吧？</p>
<pre name="code" class="ruby">&lt;p&gt;
  &lt;%= Time.now %&gt;
&lt;/p&gt;</pre>
<p>没错，正是因为 ERb 的存在，使得模板中可以动态地引用对象的属性。</p>
<p>&nbsp;</p>
<h4>Part 3. Instance Variables of Ruby - Ruby 的实例变量</h4>
<p>让我们回顾一下 Ruby 语言的基本要素之一：实例变量。通常我们通过</p>
<pre name="code" class="ruby">@time = Time.now</pre>
<p>的形式创造一个实例变量。这里我们不重新解释对于&ldquo;实例变量&rdquo;的定义，但是必须牢记一点，正如字面所见，实例变量的作用域是当前实例内，也就是说，只有在实例的内部，才可以直接对实例变量进行读写操作（扩展的访问子方法等等不在讨论范围之内）。那么，为什么 Rails Controller 里创建的实例变量能够在 View 里面访问呢，不是自相矛盾吗？</p>
<p>&nbsp;</p>
<p>Ruby 作为一种动态语言，因为其&ldquo;开放&rdquo;的特点，使很多原本不可能的编程模式变为可能。比如，通过 Open Class 的特性，你可以动态的为对象注入新的方法定义，或者改写方法的逻辑，或者，通过不同的方法可以在对象的外部访问对象的内部的实例变量。经常使用 console 的朋友可能会了解其中的一种方式：Object#instance_variable_get / Object#instance_variable_set 方法。举个简单的例子：</p>
<pre name="code" class="ruby">class User
  def initialize( name )
    @name = name
  end
end

user = User.new( &quot;Jack&quot; )
user.name #=&gt; raise NoMethodError
user.instance_variable_get('@name') #=&gt; &quot;Jack&quot;
user.instance_variable_set('@name', 'Tom')
user.instance_variable_get('@name') #=&gt; &quot;Tom&quot;</pre>
<p>可以看到，在没有任何访问子的情况下，我们用这种方式对一个实例变量进行读写操作。</p>
<p>&nbsp;</p>
<p>除了这种简单的方式，还有另外一种进阶的方式，也是 ERb 常用的一种方式：Binding</p>
<p>&nbsp;</p>
<h4>Part 4. Binding</h4>
<p>Binding (<a href="http://ruby-doc.org/core/classes/Binding.html">http://ruby-doc.org/core/classes/Binding.html</a>
) 是 Ruby 语言的自身的一个特性，在任何对象内，self.binding 方法都会返回一个当前对象关联的 binding 实例。不精确的说，binding 对象可以理解成为当前对象的完整的上下文环境。文档中已经包含了一些示例代码，帮助大家理解 Binding 对象的作用。最重要的一点是：既然是当前对象的完整上下文环境，自然就包括了对象的实例变量。</p>
<p>&nbsp;</p>
<p>那么 Binding 在 ERb 中扮演一个什么样的角色？是一个运行环境的提供者。</p>
<p>&nbsp;</p>
<p>回到我们最初的问题，Rails 在 ActionView 中使用了什么样的魔法？答案就是 ERb 和 Binding。首先，获得当前实例的 binding，自然，binding 内也包括了实例变量；紧接着，ERb 允许将模板的内容动态地，绑定到另一个环境中运行。我们依然用刚才的 User 的例子来说明这一点：</p>
<pre name="code" class="ruby"># A simple template string
template = &quot;Hello, &lt;%= @user.name %&gt;&quot;

@user = User.new( &quot;Jack&quot; )

# Get binding
binder = self.send( :binding ) # calling a private method

# Rendering template
puts ERB.new( template ).result( binder )
#=&gt; &quot;Helo, Jack&quot;</pre>
<p>虽然例子不是非常恰当，但是足以展示 Binding 和 ERB 的用法。我们可以看到，ERB#result 方法将模板字符串绑定在另外一个环境中运行，而这个环境包含了我们创建的 @user 实例变量，因此，模板中的 @user.name 得到了正确的值。</p>
<p>&nbsp;</p>
<p>这就是 ERB 的真面目，也是为什么在 ActionView 中能够访问到 Controller 里实例变量的原因。感叹一下动态语言的强大吧！以上只是非常粗糙的讲述ERB的使用，Rails 所做的魔法远不止如此，如果有兴趣，可以查看 Rails 源代码，对于深入学习 Rails 框架也有很大的好处。</p>
<p>&nbsp;</p>
<p>最后，补充一下 ERB 的应用场景。虽然在普通的需求中，ActionView 所做的已经足够，但是某些情况还是需要创造独立的模板系统。比如某个场景，客户要求提供一个完全自定义的模板系统，这个时候 ERB 就大显身手了。只需要将数据源载入实例变量中，并且在使用手册里列出可以访问的方法，即使完全不懂 Ruby 语言也可以写出使用这套简单的模板系统了。</p>
          <br/>
          <span style="color:red;">
            <a href="http://complystill.javaeye.com/topic/227618#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></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Wed, 13 Aug 2008 18:53:49 +0800</pubDate>
        <link>http://www.javaeye.com/topic/227618</link>
        <guid>http://www.javaeye.com/topic/227618</guid>
      </item>
      <item>
        <title>结合Maven2进行J2EE项目构建</title>
        <author>JavaEye网站</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://orpheus.javaeye.com">orpheus</a>&nbsp;
          链接：<a href="http://www.javaeye.com/topic/230265" style="color:red;">http://www.javaeye.com/topic/230265</a>&nbsp;
          发表时间: 2008年08月19日
          <br/>
          声明：本文系JavaEye网站发布的原创文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <strong>一.背景</strong><br />Maven2 的基本原理很简单，采用远程仓库和本地仓库以及  pom（project object model）.xml  ，将  pom.xml  中定义的  jar  文件从远程仓库下载到本地仓库，各个应用使用同一个本地仓库的  jar  ，同一个版本的  jar  只需下载一次，而且避免每个应用都去拷贝  jar  。如图  1  。同时它采用了现在流行的插件体系架构，只保留最小的核心，其余功能都通过插件的形式提供，所以在执行  maven  任务时，才会自动下载需要的插件。这个特性也为客户系统的升级带来的很大的方便，客户每次升级的时候可以使用maven的远程部署功能自动下载最新的系统组件（jar），并重新打包部署，很大程度的减少的系统升级的工作量。<br />理解Maven的原理，可以参考 Pear ――ＰＨＰ扩展与应用库（ the PHP Extension and Application Repository ），其原理非常类似，都有一个官方库，都是微内核，通过网络将需要的文件下载到本地，通过官方仓库将相应的类库进行统一管理。<br />     Maven2的基本安装方法网上很多，就到<a href="http://maven.apache.org" target="_blank">http://maven.apache.org</a>下载一个最新版，解压后即可，如果需要在命令行运行，还需要设置一些环境变量，网上的资料很多，这里就不多说了。总之，安装成功后当你在命令行下执行maven -version后正确显示当前maven的版本即可。<br />     我们在项目中结合maven的进行开发的主要思路：<br />   1.建立支持Maven2的开发框架，框架中结合了一些项目功能和工具类，并且此框架本身是一个eclipse工程，支持使用eclipse IDE的开发，并通过CVS可进行团队协作。<br />   2.在Maven2的pom.xml中制定开发框架的依赖包，并建立依赖包的团队管理本地服务器，使团队中的包依赖得到统一管理。<br />   3.每日下班后，在构建服务器上每日从cvs上下载各个团队开发人员的代码，统一进行集成构建和测试。由于是每日构建，所以发现的bug可及时反馈给开发人员进行修正，避免了一般开发过程中的bug长时间遗留的情况。<br /><br /><strong>二.实施过程</strong><br /><br />为了实现上述思路，我们分几步实施：<br /><br /><strong>1.首先需要构建一个系统的开发框架</strong>，<br />    我们有两种方式构建，<br />    其一是从零开始构建全新的框架，进入commond line，cd 到一个目录 ，执行<br /> <br /> <br /><pre name="code" class="java">mvn archetype:create -DgroupId=com.mycompany.app -DartifactId=my-webapp -DarchetypeArtifactId=maven-archetype-webapp</pre><br /><br />执行完毕后接下来cd 到项目目录my-webapp下，执行<br /><pre name="code" class="java">mvn package
mvn eclipse:eclipse
</pre><br /><br />之后，打开eclipse，到其目录下导入项目，并手动编辑pom.xml文件，设定指定的jar包，比如加入一个jwebunit的jar包，我们需要在pom中添加一段：<br /><pre name="code" class="java">&lt;dependency>
            &lt;groupId>jwebunit&lt;/groupId>
            &lt;artifactId>jwebunit&lt;/artifactId>
            &lt;version>1.2&lt;/version>
            &lt;scope>test&lt;/scope>
            &lt;exclusions>
                &lt;exclusion>
                    &lt;groupId>rhino&lt;/groupId>
                    &lt;artifactId>js&lt;/artifactId>
                &lt;/exclusion>
            &lt;/exclusions>
        &lt;/dependency></pre><br /><br /><br />其中指定了包的名称，版本，使用的范围域等，pom.xml设置方式网上也是一堆一堆的，具体的可以自己搜搜。同时我们也可以使用maven2在 eclipse中的插件进行编辑，很方便，就不用记住那些该死的标签了。插件下载地址 <a href="http://m2eclipse.codehaus.org" target="_blank">http://m2eclipse.codehaus.org</a> /update，将这个url填入到eclipse的Help-》Software Updates->find&install中新建一个插件下载地址的对话框中即可下载。<br />这种方式是完全自定义一个全新的工程后再进行框架搭建，比较累，尤其是添加依赖包的时候，需要根据自己的项目需要一个一个添加，很烦人，所以我们使用的第二个方法就直接找了一个现成的，到 Appfus 的网站<a href="http://appfuse.org/" target="_blank">http://appfuse.org/</a> 根据项目需要下载了一个项目框架作为原型，我们使用的是appfuse-light-webwork-ibatis- 1.8.2（webwork2.26,spring2.0,ibatis2.0），如果你使用的是其他的的技术，如 struts2，hibernate....直接到网站上下载一个相应的框架即可。appfuse框架使用maven2作为基本构建工具，其中自带的 pom.xml也替开发人员写好了，中所定义的依赖包可满足一般的开发需要，如需要自己指定的包，那么直接在其pom.xml中添加即可。要将这个框架作为eclipse工程使用，需要在解压后的框架目录下执行：<br /><br /><pre name="code" class="java">mvn eclipse:eclipse -DdownloadSources=true</pre><br /><br /><br />这个命令会将工程将框架转换为eclipse工程，并从远程下载jar包到本地仓库（window下是(C:\Documents and Settings\${username}\.m2\repository），之后执行：<br /><br /><pre name="code" class="java">mvn -Declipse.workspace=&lt;path-to-eclipse-workspace> eclipse:add-maven-repo</pre><br /><br /><br />其中path-to-eclipse-workspace是本机的eclipse的worksapce的路径。执行后maven会在eclipse中建立一个M2_REPO环境变量，并将其中所有的jar包引入到工程中，完全自动化，十分方便。<br />     打开eclipse修改开发中的环境变量（我们项目中使用了Myeclipse插件），找到相应的工程，发现框架中已有一些代码，这是appfuse提供给开发人员的示例代码，我们可以按照自己以前项目的积累进行对框架进行完善，形成一套自己的开发框架，之后设置工程环境变量，在该项目中右键 ->Myeclipse->add web capabilities->指定该工作空间下的Src/main/webapps作为WEB工程的根路径，并指定修改JAVA Build Path中<br /><pre name="code" class="java">src/main/java
src/main/resource
src/test/java
</pre><br />的三个soucrefolder的outputpath 为scr/main/webapp/WEB-Inf/class，这样设置的目的是便于开发人员在本地进行部署测试，否则按照appfuse原有的工程设置是不能进行顺利部署的。<br />至此，我们已经将Maven2结合到项目中，一开始可能对目录结构有些不适应，毕竟这是maven提供的项目框架格式，可以修改为自己习惯的，但是不建议这样做。设置完成后，cd到项目路径下，运行<br /><pre name="code" class="java">mvn test
mvn package
mvn install</pre><br /><br /><br />三个命令，均成功后，可上传到cvs/svn上面去，共享给项目组人员，各开发人员可直接使用，但有可能M2_REPO环境设置路径不一样（C:\Documents and Settings\${username}\.m2\repository，毕竟不是所有人都把系统装在C盘），需要手动修改一下。<br /><br /><strong>2.建立开发团队内部仓库</strong><br />          为了便于团队的依赖包管理，我们不能全部使用官网的仓库，毕竟上面不具备我们项目开发所需要的所有的依赖包，所以我们需要为自己的团队建立一个内部仓库，可以自己管理所需的依赖包，建立一个内部仓库也十分简单（附录中我们会使用artifactory进行开发内部库建立）：<br /><br />首先需要一个 http server ，找台服务器装上 apache 就行。放一个空的 maven 目录到 htdocs 下，假设服务器 ip 为 192.168.0.1 ，确认能用 <a href="http://192.168.0.1/maven" target="_blank">http://192.168.0.1/maven</a> 访问到。<br /><br />copy 本地仓库的jar包到服务器：对于 windows xp 来说一般在 C:\Documents and Settings\ ％ username%\.m2 下，其中％ username ％为操作系统登录用户名。这时你可以看到 ${user.home}/.m2/ 下有个 repository 目录，里面有很多的项目相关 jar ，目录按 groupId/ artifactId/version 排好。把 repository 目录整个拷贝到 apache 服务器的 maven 目录下，如果需要官方缺少的 jar 或公司内部 jar ，仿照这个目录结构，做好 jar 放到 maven 目录下。或者把包copy到本地，运行：<br /><pre name="code" class="java">mvn install:install-file -Dfile=X:/path/mail-1.3.jar -DartifactId=javamail -Dversion=1.3.1 -Dpackaging=jar -DgroupId=javamail</pre><br /><br /><br /><br />开发人员要使用内部仓库，只需修改本地工程pom.xml ，在 repository 配置后加上：<br /><br /><pre name="code" class="java">&lt;repository>
      &lt;id>companyName&lt;/id>
      &lt;url>http:// ${ip}/maven&lt;/url>
&lt;/repository></pre><br /><strong><br />3.每日构建</strong><br />    为了保证项目质量，尽早的发现项目中的bug，我们需要每日对系统进行构建，这也是我们使用maven的初衷之一，maven的几个命令就可帮助我们完成这项任务，当然我们可以使用持续构建工具与maven结合实现定时自动构建。构建方式：<br /><pre name="code" class="java">mvn test
mvn package
mvn install</pre><br /><br />maven 会自动编译，测试，运行所有的testcase，这也要求我们的开发人员一定要按照规则编写单元测试代码，否则每日构建的意义就不大了。appfuse框架中提供了很好的单元测试代码，包括针对数据库层，业务逻辑层，web展示层等等，如果我们能很好的编写这些单元测试，那么对于系统后续的缺陷管理和控制是大有裨益的。<br /><br />构建完成后或构建时需要对最新版本的项目进行部署，便于次日安排测试人员进行测试，maven提供多多种部署方式，在pom.xml进行项目的部署配置，不同的部署方式根据协议的不同，配置方式也有所差异：<br />以文件方式部署<br />  <br /><pre name="code" class="java">&lt;project>
        [...]
        &lt;distributionManagement>
            &lt;repository>
                &lt;id>proficio-repository&lt;/id>
                &lt;name>Proficio Repository&lt;/name>
                &lt;url>file://${basedir}/target/deploy&lt;/url>
            &lt;/repository>
        &lt;/distributionManagement>
        [...]
    &lt;/project></pre><br /><br />以SSH2方式部署<br />  <br /><pre name="code" class="java">&lt;project>
        [...]
        &lt;distributionManagement>
            &lt;repository>
                &lt;id>proficio-repository&lt;/id>
                &lt;name>Proficio Repository&lt;/name>
                &lt;url>scp://sshserver.yourcompany.com/deploy&lt;/url>
            &lt;/repository>
            &lt;/distributionManagement>
        [...]
    &lt;/project></pre><br /><br /> 以SFTP方式部署<br /> <br /><pre name="code" class="java">   &lt;project>
    [...]
    &lt;distributionManagement>
        &lt;repository>
            &lt;id>proficio-repository&lt;/id>
            &lt;name>Proficio Repository&lt;/name>
            &lt;url>sftp://ftpserver.yourcompany.com/deploy&lt;/url>
        &lt;/repository>
    &lt;/distributionManagement>
    [...]
    &lt;/project></pre><br />以扩展SSH方式部署<br />   <br />目前为止上述3中方式已经被Maven包含，所以只要distributionManagement就可以了，但是使用扩展SSH命令部署的话你不仅需要配置distributionManagement还需要一个build extension，如下<br />   <pre name="code" class="java"> &lt;project>
        [...]
        &lt;distributionManagement>
            &lt;repository>
                &lt;id>proficio-repository&lt;/id>
                &lt;name>Proficio Repository&lt;/name>
                &lt;url>scpexe://sshserver.yourcompany.com/deploy&lt;/url>
            &lt;/repository>
        &lt;/distributionManagement>
        &lt;build>
            &lt;extensions>
                &lt;extension>
                    &lt;groupId>org.apache.maven.wagon&lt;/groupId>
                    &lt;artifactId>wagon-ssh-external&lt;/artifactId>
                    &lt;version>1.0-alpha-6&lt;/version>
                &lt;/extension>
            &lt;/extensions>
        &lt;/build>
        [...]
    &lt;/project></pre><br />    The build extension specifies the use of the Wagon external SSH provider, which does the work of moving your files to the remote server. Wagon is the general purpose transport mechanism used throughout Maven.<br /><br />以FTP方式部署<br />  <br /><pre name="code" class="java">&lt;project>
        [...]
        &lt;distributionManagement>
        &lt;repository>
            &lt;id>proficio-repository&lt;/id>
            &lt;name>Proficio Repository&lt;/name>
            &lt;url><a href="ftp://ftpserver.yourcompany.com/deploy&lt;/url>" target="_blank">ftp://ftpserver.yourcompany.com/deploy&lt;/url></a>
        &lt;/repository>
        &lt;/distributionManagement>
        &lt;build>
            &lt;extensions>
                &lt;extension>
                &lt;groupId>org.apache.maven.wagon&lt;/groupId>
                &lt;artifactId>wagon-ftp&lt;/artifactId>
                &lt;version>1.0-alpha-6&lt;/version>
                &lt;/extension>
            &lt;/extensions>
        &lt;/build>
        [...]
    &lt;/project></pre><br /><br />一旦你配置好了相应的POM你可以执行下列命令来开始部署：<br />mvn deploy<br /><br />同时也可通过执行一下命令生成此项目的站点报告，供项目参与人员使用。<br />mvn site<br /><br /><br /><strong>三. 结论<br /></strong><br />    maven的强大显而易见，有很多其他的特性本文没有提及，如对各类插件的支持，以及对项目模块划分和继承关系的管理，这些都是maven的特性，也是 maven对项目生命周期的详尽诠释，有兴趣深入的TX可以下载我在附件中提供的教程《Better Builds With Maven2》.同时我也提供我根据appfuse建立的一套项目框架，可在myeclipse环境下使用，大家可以共同探讨完善。<br /><br /><strong>附1：使用artifactory为Maven2团队开发建立内部开发仓库详解</strong><br />在真正使用Maven后是为团队进行定制，所以我们不应使用官网的开发库，应在本地建立一个内部开发库对团队的jar包进行管理，所以我们首先搭建一个内部库环境，除文章上面所述的搭建Apache服务器方法外，我们还可以使用artifactory(下载地址：<a href="http://www.jfrog.org/sites/artifactory /latest/" target="_blank">http://www.jfrog.org/sites/artifactory /latest/</a>)，一个很好的maven内部库的应用系统，下载后执行bin目录下的artifactory.bat命令即可。启动后可访问控制台http://内部库ip:8081/artifactory/验证服务是否成功启动。默认的用户名为admin，密码为password。artifactory最重要的是可配置第三方jar包，在deploy artifacts中加入并制定其groupId和artifactId即可<br />（不要忘记更改本地的pom.xml文件引入新加的jar包）。<br />在开发端我们需要更改全局配置文件setting.xml文件，将工程中setting.xml放入本地maven2->conf目录下，配置内部仓库的地址，只需要在setting.xml的mirrors元素中加入以下配置：<br /><pre name="code" class="java">&lt;mirror>
      &lt;id>emay local&lt;/id>
      &lt;mirrorOf>central&lt;/mirrorOf>
      &lt;name>emay local artifactory&lt;/name>
      &lt;url>http://内部库ip:8081/artifactory/repo&lt;/url>
    &lt;/mirror>
</pre><br />这里要注意的是，在加入这段代码后我使用的appfuse框架中自带的应用服务器tomcat6进行构建，不能正常运行，报tomcat出错，把这段去掉或者在pom.xml中将应用服务器改为tomcat5.5后运行正常。看来maven还是有不少bug需要改进。<br /><br />配置完成后再运行mvn install即可正常进行构建，maven会从本地内部库中寻找项目所依赖的jar包。运行mvn clean清除maven生成文件。<br /><br /><br /><br /><strong>附2：maven2命令大全</strong><br /><br />    validate，验证工程是否正确，所有需要的资源是否可用。<br />    compile，编译项目的源代码。<br />    test-compile，编译项目测试代码。<br />    test，使用已编译的测试代码，测试已编译的源代码。<br />    package，已发布的格式，如jar，将已编译的源代码打包。<br />    integration-test，在集成测试可以运行的环境中处理和发布包。<br />    verify，运行任何检查，验证包是否有效且达到质量标准。<br />    install，把包安装在本地的repository中，可以被其他工程作为依赖来使用<br />    deploy，在整合或者发布环境下执行，将最终版本的包拷贝到远程的repository，使得其他的开发者或者工程可以共享。 <br />    generate-sources，产生应用需要的任何额外的源代码，如xdoclet。
          <br/>
          <span style="color:red;">
            <a href="http://complystill.javaeye.com/topic/230265#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></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Tue, 19 Aug 2008 22:14:44 +0800</pubDate>
        <link>http://www.javaeye.com/topic/230265</link>
        <guid>http://www.javaeye.com/topic/230265</guid>
      </item>
      <item>
        <title>项目管理工具-streber中文资料-实践使用笔记 </title>
        <author>JavaEye网站</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://orpheus.javaeye.com">orpheus</a>&nbsp;
          链接：<a href="http://www.javaeye.com/topic/230399" style="color:red;">http://www.javaeye.com/topic/230399</a>&nbsp;
          发表时间: 2008年08月20日
          <br/>
          声明：本文系JavaEye网站发布的原创文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p><span style="font-size: small;"><strong>1.Streber背景介绍：</strong>
</span>
<br />
    Streber是一个基于WEB的在线项目协调工具，它融合了wiki的思想和项目协作管理机制，成为了一个适用于小型团队的可以贯穿真个项目生命周期的项目协作和管理工具。<br />
    Streber的出现的历史并不长，作者为德国人，网名pixtur，其产品原型为05年一个作者的在线office系统，在进行这个在线office系统开发工作当中，作者发现其思路可以很好的成为一个在项目开发的协调组织的工作平台。于是作者对原有产品的不断的修改和完善其在线协作理念，乃至到最后从原有产品中完全剥离出来成为独立的开源项目。<br />
    &ldquo;Streber&rdquo;在德语中意为是一个具有高度热情和经理旺盛的人，按我们白话说就是一得瑟的人....它是基于PHP开发的项目，目前的最高版本是 0.803，基于php5。同时Sreber是基于GPL开源协议，这点一定要注意，这意味着你如果使用其进行修改和发布也要遵循GPL协议，把你的修改的代码进行开源发布。</p>
<p><img src="http://zhangmeng.blog.51cto.com/attachment/200807/200807191216457710137.jpg" height="571" alt="Strber" width="993" />
<br />
                                                          图1-1<br />
Streber截图<br />
-----------------------------------------------------------------------------------------------------------------------<br />
<span style="font-size: small;"><strong>2.Streber特点概述：</strong>
</span>
<br />
1.基于wiki的方式<br />
    Streber中采用基于wiki的管理方式和语法，采用多人协作的方式进行项目的管理和文档的编写，项目中的任务和文档资源无论创建者是谁，其他人都可以方便的修改和完善，在这种方式下项目人员自由度很高，极大的提高了项目协作的效率，但需要求人员在遵守一定的项目规则下进行项目协作。基于wiki的思想更可以使知识管理与项目紧密的联合起来，不用再为项目搭建一套知识管理系统。<br />
2.简单而灵活项目管理协作系统<br />
    Streber类似于jira，也是面向issue的项目协作系统，使用方便，操作简单，系统中常用操作基本元素就是task（也称为issue）和 comment，并且可灵活进行运用，可以作为贯穿项目生命周期中的支持系统，也可以单独作为缺陷跟踪系统，甚至可以单独作为项目知识管理系统使用。<br />
3.label标签分类功能：<br />
    Streber中的任务类型可以是一般Task类型，DOC类型，bug类型，idel类型，<br />
feature类型，research类型，refactor类型等等，我们可以通过这些类型对任务进行表示和搜索。<br />
4.全面的角色及其权限分类<br />
    Steber中的默认角色按照一般项目类的角色分为项目成员，系统管理员，项目经理，开发人员，方案人员，测试人员，客户，受信客户，以及Guest集中角色，各个角色的默认权限不同，如有特殊情况，管理员可以为每个人定制权限。这些角色是系统自带的，如果要添加自己项目的权限，可以通过直接修改数据库数据实现。<br />
5.邮件通知和RSS支持<br />
    在项目中的每个task的辨变更和更新记录都会被详细的记录下来，从更改者的角度，这些在进行变更和任务更新的时候可以选择是否将更新邮件通知此此任务的相关干系人，保证信息的及时同步。 并且任务的相关联系人可以使用RSS的方式对项目的变更和更新记录进行订阅，实现主动获取变更信息的功能。<br />
6.替代sharepoint等项目门户网站功能<br />
    我们可以使用Streber代替项目门户的功能，可以将在各个项目中中Contact Info、Baselined Schedule、News元素放在项目主页上，使项目信息沟通全面通畅。Streber支持，英，德，法，西班牙，意大利等12种语言的系统界面，可惜的是目前还不支持中文的系统界面。<br />
-----------------------------------------------------------------------------------------------------------------------<br />
<br />
<strong><span style="font-size: small;">3.Streber应用介绍</span>
</strong>
<br />
3.1安装：<br />
   S