<?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/216779" style="color:red;">http://www.javaeye.com/topic/216779</a>&nbsp;
          发表时间: 2008年07月21日
          <br/>
          声明：本文系JavaEye网站发布的原创文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <div class="Section0" style="layout-grid: 15.6pt none;">
<p class="0" style="text-indent: 18pt;"><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">我所在的公司和大多数国内IT公司一样，十几到几十人的规模，每次在做完项目过程中我们都会感觉</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">很</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">累，老板其实也很累，在小公司老板更像是一个项目经理的角色，很多东西都没有流程化的东西可走，所以很多事情都要等老板拍板后才可以继续下去，员工在很多时候就会感到迷茫，随着公司规模的扩大，公司也意识到没有一套规范的项目管理方案是万万不行的</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">，自己在这方面也摸索的一段时间。</span></span></p>
<p class="0" style="text-indent: 18pt;"><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">我首先接触的是敏捷开发的方法，但很快我就感觉这个方法行不通，至少对于我们是这样，因为我们无法保证和客户以及业务人员及时沟通，一个月见几次面就很不错了，而且我们的开发人员也并不具有敏捷能力。后来接触了下CMMI，CMMI对于小公司就更不靠谱了，它庞大的身躯足以把一个小公司压垮，如果仅为一个证书的话，我建议完全可以向o6z订购，但不可否认的是CMMI也有很多优秀的地方可以借鉴。那么我对小公司项目管理的看法是一定要精简，做到不是傻瓜都能够理解并且能够执行，况且很多项目经理（老板）也并不是领域专家。在此我想简单谈谈我对适合小公司的项目管理方案的一些想法</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">，</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">所谓基本适合就是80%适合，我要是说100%适合那我是在扯淡，另外20%怎么办？那就像06z所说的那样，靠经验这个王道。</span></span></p>
<p class="0" style="text-indent: 18pt;"><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">首先</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">要谈的是</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">需求这个东西，那么什么是需求？需求就是掏钱买你产品的人一些需要，只要是客户的需要，不管是合理不合理那都是需求。其实很多开发人员都意识需求的重要性，那么真正去做需求的人有多少呢？需求应该是包括需求开发和需求管理这两个过程，这里有个特别的情况是对于自主研发的项目，</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">我接触的项目也是这种情况居多，于是</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">我们认为自己就是客户，所以需求开发做</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">的</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">很简单甚至跳过去，结果后期的需求管理非常混乱，我</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">觉得</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">既然自己是客户，那就要当好客户这个角色，在做客户时应完全忘却自己是个开发人员</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">，同样要把需求做全面</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">。很多教科书上都说应该做需求，但关于怎么做的问题上却和实际情况差别比较大，以下是我关于需求该做什么以及怎么做到一些看法。</span></span></p>
<p class="0" style="text-indent: 18pt;"><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';">1&nbsp;<span style="font-family: 宋体;">需求调研</span></span></p>
<p class="0" style="text-indent: 18pt;"><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">我觉得需求调研非常的重要，</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';">1</span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">年前我还打算做一个在线教育服务平台，理念就是淘宝在网上卖商品，我在网上卖教育资源，我提供网上交易场所，签约的老师、学校以及培训机构提供</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">可交易的</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">服务，这种服务可以通过视频、音频、在线PPT、文本的形式展现。忙活了好一阵，发现这个市场早就有很多人做了，而且这个市场并不是很好做，首先在网上学习的人有几个？并且先不说前期推广需要海量资金就是所需要的那么些高性能服务器丫也买不起！这件事就此搁浅，</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">结果</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">信了马云</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">的邪</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">，2年后你</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">还</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">想创业你在创业！我觉得这就是典型的需求调研没做好，没有对用户需求做调查，没有考虑同行竞争，没有考虑可行性！另外还要考虑括行业标准和法律规定，比如前些时候国家就出台了关于办视频网站的政策，我觉得你丫没有足够的背景就不要往火坑里跳楼。总之根据所做行业情况尽可能的把需求调研做全面，这样才可以保证项目首先是可以赚钱的。那么文档要写吗？我觉得可以不要正式的文档，小公司的人手本来就不够用，要把主要文档</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">化工作</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">集中在重要的环节上，对于需求调研，本来就很杂乱，完全可以记在工作笔记上，放到需求分析的时候整理。</span></span></p>
<p class="0" style="text-indent: 18pt;"><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';">2&nbsp;<span style="font-family: 宋体;">需求分析</span></span></p>
<p class="0" style="text-indent: 18pt;"><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">为了得到用户的金钱，我们总是在说，用户是上帝，用户永远是对的，尽管背地里在说客户端坏话：&ldquo;你丫钱给的倒不多，要求还真少，这需求根本不合理，是正常人的逻辑吗？&rdquo;，如果你想活下去，最终我们还是要想方设法满足用户的要求。</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">用户</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">是个外界因素，我们</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">是</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">无法控制</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">的</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">，那么我们只有尽可能改进需求分析的方法来尽量减少不必要的麻烦。那么我觉得日本人做法倒是可以借鉴，在有条件的情况下派专人去现场，随时记录关键性的需求，即使不能去现场也尽可能的获取尽可能多大信息，不要指望开发后去获取什么有价值的东西。那么是否应该做个原型给客户看看？我是觉得这不大合适，因为如果项目周期短的话，等你做好原型，黄花菜都凉了。但我觉得等到需求做到差不多的时候可以做用户界面，所谓用户界面就是用户接口，是和用户打交道的地方，所谓一图解千言，有了界面用户会清楚自己所买的东西在未来会是个什么样的东西，再者开发几个有说明性都界面倒是不会暂用很多时间。等到需求确定下来后</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">就</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">要整理成文档了，这个是很重要的一步，是做设计时候的重要凭证和依据，这个文档就是用户规格说明书，所谓规格就是有规范的格式和内容。</span></span></p>
<p class="0" style="text-indent: 18pt;"><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';">3 <span style="font-family: 宋体;">需求评审</span></span></p>
<p class="0" style="text-indent: 18pt;"><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">我们已经有了较正规的文档了，那么下一步就是召集所有开发人员开会，最好有客户代表参加，尽管我是很厌烦开会，但该开的会还是要开到，因为之前我遇到这种情况，开发人员根据设计文档写代码，可是他并不知道自己在开发什么，站在自己的角度想一下，如果自己都不确定自己做的东西，即使有再完备的设计，也会对开发毫无兴趣，只会让自己觉得自己是个代码机器。所以所有人员参加需求评审是让大家知道自己在做一件有意义的事情，自己正在满足社会的需要，自己在为和谐社会做贡献，即使你从没那么想过，那你敢保证的你的潜意识没那么想过吗？人是要有社会满足感的吧。另外开会前一定要准备关键有价值的议题，据我观察需求评审会最容易扯到不着边的话题，所以主持人要控制话题，会议控制在2-3个钟头，</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">最好做成幸运52的形式，所有人员一定要互动起来，</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">否则</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">就</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">变成了</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">个人演讲</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">。需求也做了，会也开了，那么要求客户签字吧。</span></span></p>
<p class="0" style="text-indent: 18pt;"><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';">4&nbsp;<span style="font-family: 宋体;">需求管理</span></span></p>
<p class="0" style="text-indent: 18pt;"><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">需求管理是在开发开始之后进行的，这也是另所有人头疼的一件事，之前做完一个项目后，客户经常打电话找我们，改过来改过去，后</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">来</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">我听到电话，血压都要</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">升</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">高50个百分点，后来索性就不接电话，客户就在网上找我，搞的我连QQ都不敢登，但躲是躲不掉滴，客户直接打我手机，丫的真烦人，见过难缠的，没见过这么难缠的。后来转念一想，难道这种情况真的不能避免吗？至少是可以大幅度的缓解</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">的</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">吧。这就是我们需求管理中的变更管理没做好，</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">改了哪些地方自己都忘记了，最后是跟着感觉走，拆东墙补西墙。</span></span><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">在这里我建议要建立需求跟踪矩阵表，有了这个表我们至少可以对要修改的地方有了依据，迫使我们去调查到底是改什么地方，怎么改，最后改成了什么样。可能你会说客户需要大幅度修改原有计划，很难跟踪到具体某一项需求，那么我觉得这是由于前期的需求开发没有做好，在后期客户进行实质性的修改的几率是很小的，比如客户要求我们做个OA系统，最后总不会要我们改成个门户网站吧，在举个例子，在比如你开发一个ERP系统，客户自己的业务流程不会轻易的改变吧，总不至于把盘点这个业务改成一个报表系统吧。如果真是这样，我们完全有理由告诉客户，你丫乖乖掏银子，我们再给你们开发2期工程，要改，没门！</span></span></p>
<p class="0" style="text-indent: 18pt;"><span style="font-size: 9pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">大家开始拍砖吧，有时间继续&hellip;&hellip;</span></span></p>
</div>
<!--EndFragment-->
          <br/>
          <span style="color:red;">
            <a href="http://superleo.javaeye.com/topic/216779#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>Mon, 21 Jul 2008 08:09:12 +0800</pubDate>
        <link>http://www.javaeye.com/topic/216779</link>
        <guid>http://www.javaeye.com/topic/216779</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/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://superleo.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/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, 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>看看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://superleo.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/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>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>小公司如何做项目管理(下)</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://superleo.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/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, 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>分享一款word风格的rails在线编辑器</title>
        <author>JavaEye网站</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://liuqiang.javaeye.com">liuqiang</a>&nbsp;
          链接：<a href="http://www.javaeye.com/topic/213151" style="color:red;">http://www.javaeye.com/topic/213151</a>&nbsp;
          发表时间: 2008年07月09日
          <br/>
          声明：本文系JavaEye网站发布的原创文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <div class="Section0" style="LAYOUT-GRID:  15.6pt none">
<p class="0"><span style="font-size: 10.5pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">&nbsp;&nbsp;&nbsp;&nbsp;在线编辑器是web应用中最常见的东西了，关于它的作用和意义我不想多说什么了。对于编辑器的使用，之前就是随便找个简单的HTML编辑器甚至是textarea来应付，也没花多少时间来整这个东西，但事情开始逐渐变得麻烦起来，因为我们的客户（可能就是你的老板）的要求越来越高：能不能加上点丰富的表情？能不能使编辑器再多支持几种格式排版？或许有一天客户看到了搜狐的编辑器，回来说能不能让我们的编辑器也增加本地图片上传？最好再要加个附件功能，并且也要从本地上传&nbsp;&hellip;&hellip;&hellip;&hellip;，这一切让我以前做的小编辑器显得无地自容，也忒简陋了吧。于是我很自然地想到了rails里面最常用的FCK编辑器，的确，它基本可以满足我的所有需要，可是仍有3点让我感到不大舒服的地方：</span></span><span style="font-size: 10.5pt; font-family: '宋体'; mso-spacerun: 'yes';"></span></p>
<p class="0"><span style="font-size: 10.5pt; font-family: '宋体'; mso-spacerun: 'yes';">&nbsp; 1&nbsp;<span style="font-family: 宋体;">尽管FCk的配置使用已经很方便了，但仍然有不少需要汉化和定制的地方。</span></span><span style="font-size: 10.5pt; font-family: '宋体'; mso-spacerun: 'yes';"></span></p>
<p class="0"><span style="font-size: 10.5pt; font-family: '宋体'; mso-spacerun: 'yes';">&nbsp; 2&nbsp;<span style="font-family: 宋体;">这个东西显的有稍稍有点大，为了性能，我还期望让它再小一点。</span></span><span style="font-size: 10.5pt; font-family: '宋体'; mso-spacerun: 'yes';"></span></p>
<p class="0"><span style="font-size: 10.5pt; font-family: '宋体'; mso-spacerun: 'yes';">&nbsp;&nbsp;3&nbsp;<span style="font-family: 宋体;">这是个不是理由的理由：用户感到单调了，为什么我们的系统里面的编辑器都是一个模样，我们需要点新口味。</span></span><span style="font-size: 10.5pt; font-family: '宋体'; mso-spacerun: 'yes';"></span></p>
<p class="0"><span style="font-size: 10.5pt; font-family: '宋体'; mso-spacerun: 'yes';"></span>&nbsp;</p>
<p class="0"><span style="font-size: 10.5pt; font-family: '宋体'; mso-spacerun: 'yes';"><span style="font-family: 宋体;">&nbsp;&nbsp;&nbsp; 我首先的想法就是改改FCk，旧米做新饭，可是我发现修改FCK不是件容易的事，并且把FCK修改成另外一种风格和结构更难，后来转念一想，反正里外都是改，我何不找另外一款编辑器做做文章，之前在51js混的时候听说新浪的编辑器做的不错，于是load下来看看，豁豁豁&hellip;&hellip;，这不就是word吗？太酷了，我有了马上就使用它的冲动，但是打开源码一看是个asp版本的，并且转为utf8后，注释也成了乱码，好在它的js代码可读性还算比较好，于是我开始着手做两件事，1是裁掉一些功能&nbsp;2是把它转为rails版本的。经过一段时间的修改和维护这个东东算是稳定下来了，使用和效果如下：</span></span></p>
<p class="0"><span style="font-size: 10.5pt; font-family: '宋体'; mso-spacerun: 'yes';">&nbsp;&nbsp;&nbsp; 将editor文件夹放在public下，在需要用编辑器的的form中写：</span></p>
<p class="0"><span style="font-size: 10.5pt; font-family: '宋体'; mso-spacerun: 'yes';"></span>&nbsp;</p>
<pre name="code" class="ruby"><span style="color: #ff0000;"> 注意这里的id不要写成content，貌似是由于命名冲突的原因</span> 
&lt;input type="hidden" id="content1" name="content" value=""&gt;&lt;/input&gt; 
<span style="color: #ff0000;">注意这里要把上面的隐藏域的id穿给编辑器的iframe</span>
 &lt;iframe id="myEditor" src="/editor/editor.htm?id=content1" frameborder="0" scrolling="no" width="550" height="220"&gt;&lt;/iframe&gt;

再附上一段js校验编辑器内容不为空的代码
var content = myEditor.getHTML().replace(/&lt;.*?&gt;/g,"").split(" ").join("").split(" ").join("");
if(content.length==0){
        alert('公告内容不能为空！');
        return false;
    }</pre>
<p class="0">&nbsp;<span style="font-size: 10.5pt; font-family: '宋体'; mso-spacerun: 'yes';">
<p class="0">&nbsp;</p>
</span></p>
<p class="0"><img src="../../upload/picture/pic/17662/0cff6c23-c499-31a1-9f16-be27f2dbb9e2.jpg " height="464" alt="" width="631" /></p>
<p class="0"><span style="font-size: 10.5pt; font-family: '宋体'; mso-spacerun: 'yes';">&nbsp;&nbsp;&nbsp; 效果还不错吧，下面还有2件重要的事情要做：上传图片和上传附件，之所以说这个是rails版的，也就是这两个功能和rails有点干系。先简分析下上传本地图片吧，原理是这样的，点击上传图片后弹出一个新窗口，叫你输入你的本地图片，这个窗口对应的页面是editor\editor目录下的img.htm文件，打开看看可以看到有个向服务器提交图片的form，并且action指向服务器保存编辑器图片的那个action，用户提交本地图片后，服务器将图片保存，并把该图片的保存地址返还回去，编辑器接收到该图片地址后，插进编辑器的主窗口，这样图片基本就在编辑中显现了，当然你可以在img.htm写上校验逻辑，我已经加上了图片类型判别的js校验。整个流程就是这样，上传本地附件和图片的流程基本一样，上传附件窗口对应的文件是attach.htm文件，只不过服务器保存用户提交的附件后，返还的是下载该附件的地址，我也已经在attach.htm加上zip和rar的校验。注意我已经将上述的东西做好了，无需修改任何东西！当然你可以再去修改成你自己喜好的风格。</span></p>
<p class="0"><span style="font-size: 10.5pt; font-family: '宋体'; mso-spacerun: 'yes';">&nbsp;&nbsp;&nbsp; 接下来就开始写服务器处理上传图片的附件的逻辑了，注意由于我在上传图片和上传附件的form中把action的地址分别指向的是:/front/editors/upload_editor_image和/front/editors/upload_editor_attach，所以如果你不想修改任何东西的话，那么请保证后台的控制器一定要是front/editors,action分别是upload_editor_image和upload_editor_attach。代码附上：</span></p>
<span style="font-size: 10.5pt; font-family: '宋体'; mso-spacerun: 'yes';">
<pre name="code" class="ruby">class Front::EditorsController &lt; ApplicationController
  skip_before_filter :verify_authenticity_token
  # 上传图片
  def upload_editor_image
    file = params[:imgfile]
    filename = params[:imgfile].original_filename.split('.').reverse  
    filename = Time.now.strftime("%Y%m%d%H%M%S")  + rand(10000).to_s + "." + filename[0]  
    File.open("#{File.expand_path(RAILS_ROOT)}/public/upload/editor_image/#{filename}", "wb") do |f|   
      f.write(file.read)        
    end      
    render :text =&gt; "&lt;script&gt;window.parent.LoadIMG('/upload/editor_image/#{filename}')&lt;/script&gt;"
  rescue
    render :text =&gt; "&lt;script&gt;window.parent.alert('您上传的图片无效或者损坏！');window.parent.divProcessing.style.display='none'; &lt;/script&gt;"
  end
  
  # 上传附件
  def upload_editor_attach
    file = params[:attach]
    filename = params[:attach].original_filename.split('.').reverse  
    filename = Time.now.strftime("%Y%m%d%H%M%S") + rand(10000).to_s + "." + filename[0]  
    File.open("#{File.expand_path(RAILS_ROOT)}/public/upload/editor_attach/#{filename}", "wb") do |f|   
      f.write(file.read)        
    end      
    render :text =&gt; "&lt;script&gt;window.parent.LoadAttach('/upload/editor_attach/#{filename}')&lt;/script&gt;"
  rescue
    render :text =&gt; "&lt;script&gt;window.parent.alert('您上传的附件无效或者损坏！');window.parent.divProcessing.style.display='none'; &lt;/script&gt;"
  end
  
  def download_attach
    send_file params[:path]
  rescue
    render :text =&gt; "对不起，改附件已经损坏，无法下载！"
  end
end
</pre>
<p class="0">&nbsp;</p>
</span>
<p class="0">&nbsp;</p>
<p class="0"><span style="font-size: 10.5pt; font-family: '宋体'; mso-spacerun: 'yes';"></span>&nbsp;效果如下：</p>
<p class="0"><img src="../../upload/picture/pic/17664/73cd0cd5-1038-3a79-a77e-ff62417b1a70.jpg " height="208" alt="" width="412" /></p>
<p class="0">&nbsp;</p>
<p class="0"><img src="../../upload/picture/pic/17660/ae845d1e-c7ce-3dcd-b737-7448aadc4e43.jpg " height="337" alt="" width="649" /></p>
<p class="0">下面是改编辑器的压缩包，直接解压即可。</p>
<p class="0"><span style="font-size: 10.5pt; font-family: '宋体'; mso-spacerun: 'yes';"></span>&nbsp;</p>
</div>
<!--EndFragment-->
          <br/>
          <span style="color:red;">
            <a href="http://superleo.javaeye.com/topic/213151#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, 09 Jul 2008 16:51:10 +0800</pubDate>
        <link>http://www.javaeye.com/topic/213151</link>
        <guid>http://www.javaeye.com/topic/213151</guid>
      </item>
      <item>
        <title>一次Java垃圾收集调优实战</title>
        <author>JavaEye网站</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://calvin.javaeye.com">江南白衣</a>&nbsp;
          链接：<a href="http://www.javaeye.com/topic/212967" style="color:red;">http://www.javaeye.com/topic/212967</a>&nbsp;
          发表时间: 2008年07月09日
          <br/>
          声明：本文系JavaEye网站发布的原创文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <h3>1 资料</h3>
<ul>
<li><span class="nobr"><a href="http://calvin.javaeye.com/blog/91905" title="Visit page outside Confluence" rel="nofollow">JDK5.0垃圾收集优化之--Don't Pause(花钱的年华)<sup><img class="rendericon" src="../../../../images/icons/linkext7.gif" border="0" height="7" align="absmiddle" alt="" width="7" />
</sup>
</a>
</span>
&thinsp;</li>
<li><span class="nobr"><a href="http://calvin.javaeye.com/blog/91903" title="Visit page outside Confluence" rel="nofollow">编写对GC友好，又不泄漏的代码(花钱的年华)<sup><img class="rendericon" src="../../../../images/icons/linkext7.gif" border="0" height="7" align="absmiddle" alt="" width="7" />
</sup>
</a>
</span>
&thinsp;</li>
<li><span class="nobr"><a href="http://pengjiaheng.spaces.live.com/blog/cns!2DAA368B386E6AEA!770.entry" title="Visit page outside Confluence" rel="nofollow">JVM调优总结<sup><img class="rendericon" src="../../../../images/icons/linkext7.gif" border="0" height="7" align="absmiddle" alt="" width="7" />
</sup>
</a>
</span>
&thinsp;</li>
<li><span class="nobr"><a href="http://www.md.pp.ru/~eu/jdk6options.html" title="Visit page outside Confluence" rel="nofollow">JDK 6所有选项及默认值<sup><img class="rendericon" src="../../../../images/icons/linkext7.gif" border="0" height="7" align="absmiddle" alt="" width="7" />
</sup>
</a>
</span>
&thinsp;</li>
</ul>
<h3>2 GC日志打印</h3>
<p>&nbsp; GC调优是个很实验很伽利略的活儿，GC日志是先决的数据参考和最终验证：</p>
<div class="macro">
<div class="code">
<div class="codeContent">
<pre class="code-java">-XX:+PrintGCDetails -XX:+PrintGCTimeStamps(GC发生的时间) -XX:+PrintGCApplicationStoppedTime(GC消耗了多少时间) -XX:+PrintGCApplicationConcurrentTime(GC之间运行了多少时间)</pre>
</div>
</div>
</div>
<p>&nbsp;</p>
<h3>3 收集器选择</h3>
<h4><a name="JavaPerformance-CMS收集器：暂停时间优先" style="width: 20px; height: 20px; text-indent: 20px; background-repeat: no-repeat; background-image: url(../../../CuteSoft_Client/CuteEditor/Load.ashx?type=image&amp;amp;file=anchor.gif);"></a>
CMS收集器：暂停时间优先</h4>
<p>&nbsp;&nbsp; 配置参数：-XX:+UseConcMarkSweepGC<br />
&nbsp;&nbsp; 已默认无需配置的参数：-XX:+UseParNewGC(Parallel收集新生代)
-XX:+CMSPermGenSweepingEnabled(CMS收集持久代)
-XX:UseCMSCompactAtFullCollection(full gc时压缩年老代)</p>
<p>&nbsp;&nbsp; 初始效果：1g堆内存的新生代约60m，minor gc约5-20毫秒，full gc约130毫秒。</p>
<h4><a name="JavaPerformance-Parallel收集器：吞吐量优先" style="width: 20px; height: 20px; text-indent: 20px; background-repeat: no-repeat; background-image: url(../../../CuteSoft_Client/CuteEditor/Load.ashx?type=image&amp;amp;file=anchor.gif);"></a>
Parallel收集器：吞吐量优先</h4>
<p>&nbsp;&nbsp;&nbsp; 配置参数： -XX:+UseParallelGC -XX:+UseParallelOldGC(Parallel收集年老代，从JDK6.0开始支持)</p>
<p>&nbsp;&nbsp;&nbsp; 已默认无需配置的参数： -XX:+UseAdaptiveSizePolicy(动态调整新生代大小)</p>
<p>&nbsp;&nbsp;&nbsp; 初始效果：1g堆内存的新生代约90-110m(动态调整)，minor gc约5-20毫秒，full gc有无UseParallelOldGC 参数分别为1.3/1.1秒，差别不大。</p>
<p>&nbsp;&nbsp;&nbsp; 另外-XX:MaxGCPauseMillis=100 设置minor gc的期望最大时间，JVM会以此来调整新生代的大小，但在此测试环境中对象死的太快，此参数作用不大。</p>
<h3><a name="JavaPerformance-1.4调优实战" style="width: 20px; height: 20px; text-indent: 20px; background-repeat: no-repeat; background-image: url(../../../CuteSoft_Client/CuteEditor/Load.ashx?type=image&amp;amp;file=anchor.gif);"></a>
4 调优实战</h3>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Parallel收集高达1秒的暂停时间基本不可忍受，所以选择CMS收集器。
</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在被压测的Mule 2.0应用里，每秒都有大约400M的海量短命对象产生：</p>
<ol>
<li>因为默认60M的新生代太小了，频繁发生minor gc，大约0.2秒就进行一次。</li>
<li>因为CMS收集器中MaxTenuringThreshold(生代对象撑过过多少次minor gc才进入年老代的设置)默认0，存活的临时对象不经过Survivor区直接进入年老代，不久就占满年老代发生full gc。</li>
</ol>
<p>&nbsp; &nbsp;&nbsp; 对这两个参数的调优，既要改善上面两种情况，又要避免新生代过大，复制次数过多造成minor gc的暂停时间过长。</p>
<ol>
<li>使用-Xmn调到1/3 总内存。观察后设置-Xmn500M，新生代实际约460m。(用-XX:NewRatio设置无效，只能用 -Xmn)。</li>
<li>添加-XX:+PrintTenuringDistribution 参数观察各个Age的对象总大小，观察后设置-XX:MaxTenuringThreshold=5。</li>
</ol>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 优化后，大约1.1秒才发生一次minor gc，且速度依然保持在15-20ms之间。同时年老代的增长速度大大减缓，很久才发生一次full gc，</p>
<p>&nbsp; &nbsp; &nbsp; 参数定稿：</p>
<div class="macro">
<div class="code">
<div class="codeContent">
<pre class="code-java"> -server -Xms1024m -Xmx1024m -Xmn500m -XX:+UseConcMarkSweepGC   -XX:MaxTenuringThreshold=5  -XX:+ExplicitGCInvokesConcurrent</pre>
</div>
</div>
</div>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 最后服务处理速度从1180 tps 上升到1380 tps，调整两个参数提升17%的性能还是笔很划算的买卖。</p>
<p>&nbsp;</p>
<p>&nbsp; &nbsp;&nbsp; 另外，JDK6 Update 7自带了一个VisualVM工具，内里就是之前也有用过的Netbean Profiler，类似JConsole一样使用，可以看到线程状态，内存中对象以及方法的CPU时间等调优重要参考依据。免费捆绑啊，Sun 这样搞法，其他做Profiler的公司要关门了。</p>
          <br/>
          <span style="color:red;">
            <a href="http://superleo.javaeye.com/topic/212967#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, 09 Jul 2008 10:39:01 +0800</pubDate>
        <link>http://www.javaeye.com/topic/212967</link>
        <guid>http://www.javaeye.com/topic/212967</guid>
      </item>
      <item>
        <title>适用于各类Swing应用的通用数据验证模块</title>
        <author>JavaEye网站</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://polygoncell.javaeye.com">polygoncell</a>&nbsp;
          链接：<a href="http://www.javaeye.com/topic/216556" style="color:red;">http://www.javaeye.com/topic/216556</a>&nbsp;
          发表时间: 2008年07月19日
          <br/>
          声明：本文系JavaEye网站发布的原创文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p><span style="color: red;"><strong>请注意：本文的版权归作者所有，如转载请注明出处。</strong>
</span>
<br />
<br />
这段时间真是忙得要死，一方面要开发公司项目的系统框架，要将项目分成不同的子项目，编写核心代码；另一方面要将极限编程（XP）引入团队开发，部署各类XP需要的服务例如subversion啦，ant+ivy啦，Hudson啦等等。顺便说句题外话，ubuntu还真是不是一般的好用，建议有能力的全部转到ubuntu上去开发。<br />
<br />
我目前开发的这个框架的客户端是具肥的客户端，也就是Swing客户端了。Swing应用相对于Web应用有很多优势，因为它更肥。数据验证就是其中一个。当然现在的Web应用通过使用Ajax也要比以前强很多了，但是还是避免不了在验证数据时向服务段发出请求，至少你无法避免验证结果从Web服务器传输到用户浏览器上这段过程。而Swing这类肥客户端可以实现完全在本地对数据进行验证，甚至可以断网继续工作（这也是Web应用目前在研发的一个重要课题）。<br />
<br />
前段时间开发出了一个可以应用于所有Swing应用的通用数据验证模块，发现它在项目中使用后，对于普通的数据验证,程序员几乎不需要编码，效率提高了不少，就写了一篇博文拿出来和大家分享。原文是用英文写的，在这里：<a href="http://polygoncell.blogspot.com/2008/07/validation-module-for-swing-application.html" target="_blank">http://polygoncell.blogspot.com/2008/07/validation-module-for-swing-application.html</a>
。英文好的朋友可以直接去那里看。<br />
<br />
编写这个模块使用了很多不同的开源框架和类库，其中很重要的一个就是JXLayer。文章写完后，我就跑去邀请JXLayer的作者Alexp来指点一下，然后就在我的文章后面开始了一段讨论，挺有意思的，他不愧为是Swing team里面的牛人啊！厉害啊！呵呵。<br />
<br />
ok，回到今天这篇文章的正题。今天的主要目的是将我的英文博文翻译成中文（自己的文章，我就不逐字逐句翻译了，意思到了就行了，可能还会随兴展开一番讨论）在这里展示给大家，与大家分享开发经验，希望大家能够从中获益，也希望能够以文会友，广交朋友。废话少说，切入正题。<br />
<br />
数据验证（Validation）一直是软件开发中非常重要的一环，有了它，你的系统会让客户感到更加友善，同时你的系统也得到了一定程度的保护。一般来说，数据验证既可以在客户端也可以在服务端。默认的JSF数据验证就是在服务端，数据只能在被提交以后才能够被验证，然后把错误信息传递回用户的浏览器。后来大规模使用Ajax后，基本可以实现对修改的数据&ldquo;即时&rdquo;验证，注意这里是个打了引号的即时，数据事实上还是要在浏览器和服务端之间进行传递的，只不过Ajax将这种传递改为隐式了而已，理论上并没有真正实现（断网）即时验证。而在Swing应用上就能够达成这种愿望。<br />
<br />
事实上，开发Swing应用时，数据验证一直比较棘手，需要手工编码的地方太多，效率不高。后来出了JGoodies Validation 结合JGoodies binding后，好了一些。这个JGoodies Validation既可以实现model层面的验证，也可以实现Bean层面的验证，但是多年使用下来，发现其实它比较适用于中小项目，而且要编写的代码其实一点不比自己手动编写的少。<br />
<br />
JGoodies流行了一段时间后，sun开始推出自己的bean绑定方案：beansbinding（JSR 295），我个人感觉要比JGoodies binding好用（JGoodies的作者Karsten也在专家组里，这个人我以前和他一起共事过，我的msn space里面还有跟他的合影，绝对是Swing界的牛人）。这个beansbinding也提供数据验证，但是它的这个数据验证只是在target被改动后，数据被同步回source之前才会起作用，使用起来局限性比较大，而且编码量也不小。<br />
<br />
由于目前绝大部分项目是基于POJO的，Hibernate validator已经提供了一个很好的数据验证框架，我们完全没必要再重复发明轮子，我们应该努力站在巨人的肩膀上，这样我们才能站得更高，看得更远。于是我考虑结合beansbinding和Hibernate Validator开发数据验证。还有一个重要的问题，那就是数据错误的时候，需要在用户界面上展示相应的信息，例如Error icon和错误提示，这部分我考虑使用JXLayer。<br />
<br />
你可以在如下链接中找到相关框架的具体信息： <br />
<br />
1. Hibernate Validator： <a href="http://www.hibernate.org/hib_docs/validator/reference/en/html_single/" target="_blank">http://www.hibernate.org/hib_docs/validator/reference/en/html_single/</a>
<br />
2. Beansbinding: <a href="https://beansbinding.dev.java.net/" target="_blank">https://beansbinding.dev.java.net/</a>
<br />
3. JXlayer: <a href="http://weblogs.java.net/blog/alexfromsun/" target="_blank">http://weblogs.java.net/blog/alexfromsun/</a>
<br />
<br />
阅读这篇文章，不需要你熟悉这些类库，不过了解这些类库能够帮助你更好地理解这篇文章。<br />
<br />
我的这个通用模块是参考JXLayer里面的一个demo类TextValidationDemo的，这个JXlayer是由Alexander Potochkin开发的，我很喜欢，使用起来很顺手，强烈推荐使用。<br />
<br />
下面开始介绍代码。首先是建立一个java项目，对于这个小项目，我使用netbeans。这里说句题外话，中型和大型的Swing应用，建议最好还是不要使用netbeans的GUI Builder，一方面它生成的代码超级烂，另一方面很难测试。目前市面上有很多好用的layout的框架，例如 JGoodies form和MigLayout，开发效率绝对不比netbeans的GUI builder差，你还不需要面对令人头疼的机器成的代码。<br />
<br />
项目创建好后，加入类库：<br />
<br />
<img src="http://bp2.blogger.com/_kE1wl-BJf90/SIBqgMo4shI/AAAAAAAAAAM/SMNf6l7bfw8/s320/image002.jpg" alt="" />
<br />
<br />
然后写一个persistence bean:<br /></p>
<pre name="code" class="java">package de.jingge.domain;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import org.hibernate.validator.Length;
import org.hibernate.validator.NotEmpty;

@Entity
public class Country extends AbstractBean {

private static final long serialVersionUID = 5341382564159667599L;
public static final String PROPERTYNAME_NAME = &quot;name&quot;;
public static final String PROPERTYNAME_CODE = &quot;code&quot;;
private String name;
private String code;
private Long id;

public Country() {
}

public Country(String code, String name) {
    super();
    setCode(code);
    setName(name);
}

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
public Long getId() {
    return id;
}

public void setId(Long id) {
    this.id = id;
}

@NotEmpty
public String getName() {
    return name;
}

public void setName(String name) {
    firePropertyChange(PROPERTYNAME_NAME, this.name, this.name = name);
}

@Length(min=2, max= 2, message=&quot;Code length must be 2&quot;)
@NotEmpty
public String getCode() {
    return code;
}

public void setCode(String code) {
    firePropertyChange(PROPERTYNAME_CODE, this.code, this.code = code);
}
}
</pre>
<p>
<br />
<br />
这里我为了强调可以在Swing客户端直接使用和验证persistence bean，故意写了一个persistence bean，实际应用中，这个类只需要是一个pojo就行了。<br />
<br />
这个Country类代表一个国家，它有两个属性，code和name，我给他们分别加上个各自的验证限制。code不能为空，且必须正好是两个字符，例如CN，DE，US。name不能为空。这些annotaion均出自Hibernate Validator。那个父类AbstractBean出自SwingX类库，我们的Country类继承了它之后就可以支持property change event了。<br />
<br />
ok, 下面可以开始编写这个模块的核心代码了。前面说过，我会使用JXlayer。使用它的好处是：所有JXlayer的painting event都会被转到UI类来，我们只需要编写一个集成Hibernate Validator的UI类就可以了，我称这个类为HibernateValidationUI，代码如下：<br /></p>
<pre name="code" class="java">package de.jingge.view;

import java.awt.Graphics2D;
import java.awt.image.BufferedImage;

import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.text.JTextComponent;

import org.hibernate.validator.ClassValidator;
import org.hibernate.validator.InvalidValue;
import org.jdesktop.beansbinding.ELProperty;
import org.jdesktop.beansbinding.PropertyStateEvent;
import org.jdesktop.beansbinding.PropertyStateListener;
import org.jdesktop.jxlayer.JXLayer;
import org.jdesktop.jxlayer.plaf.AbstractLayerUI;

/**
* Header:
* Description: A layerUI which will validate the referenced property value of
* the object each time when the paint(...) method is called.

* The value of the given object property will be observed.
* Note: This UI works only with {@link JXLayer}. Any change of the property
* will force repainting the UI. The work process looks like: property changed -&gt;
* jxlayer will be repainted -&gt; the paint(...) method of this UI will be called.
* The logic of validation will be handled by the Hibernate validator
* framework.
*
*/
public class HibernateValidationUI extends AbstractLayerUI&lt;jTextComponent&gt; {

private Object object;
private String propertyName;
private ClassValidator validator;
private ELProperty elProperty;
private PropertyStateListener propertyChangeHandler;

public HibernateValidationUI(Object obj, String propertyName) {
    this.object = obj;
    this.propertyName = propertyName;
    propertyChangeHandler = new PropertyChangeHandler();
    validator = new ClassValidator(obj.getClass());

    elProperty = ELProperty.create(&quot;${&quot; + propertyName + &quot;}&quot;);
}

public void installUI(JComponent c) {
    super.installUI(c);
    elProperty.addPropertyStateListener(object, propertyChangeHandler);
}

public void uninstallUI(JComponent c) {
    super.uninstallUI(c);
    elProperty.removePropertyStateListener(object, propertyChangeHandler);
}

protected void paintLayer(Graphics2D g2, JXLayer&lt;jTextComponent&gt; l) {
    super.paintLayer(g2, l);
    InvalidValue[] validationMessages = validator.getInvalidValues(object,
            propertyName);
    if (validationMessages.length &gt; 0) {
        BufferedImage image = Java2DIconFactory.createErrorIcon();
        g2.drawImage(image, l.getWidth() - image.getWidth() - 1,
                l.getHeight() - 8, null);
        l.getView().setToolTipText(validationMessages[0].getMessage());

        return;
    }
    l.getView().setToolTipText(null);
}

boolean isValid() {
    return validator.getInvalidValues(object, propertyName).length == 0;
}

class PropertyChangeHandler implements PropertyStateListener {

    @Override
    public void propertyStateChanged(PropertyStateEvent pse) {
        setDirty(true);
    }
}
}
</pre>
<p>
<br />
<br />
这个HibernateValidationUI类只有一个构建器，它接收两个参数，一个是source object，也就是我们要修改的那个Bean类的实例，另外一个是这个bean的一个属性，这个HibernateValidationUI就负责验证这个属性。<br />
<br />
在installUI()方法中，我们启动对属性变化的观察类，而在uninstallUI()方法里面，我们需要卸载这个观察类。<br />
<br />
当给定对象的属性值发生变化时，PropertyChangeHandler的propertyStateChanged（）方法就会被调用，这个功能是通过elProperty和PropertzChangeHandler相结合来实现的。在propertyStateChangeed()方法里UI类的方法setDirty()会被调用，该方法的调用会导致UI类的状态变化，进而引发(re)painting，之后经过一系列的方法调用传递，paintLayer(Graphics2D g2, JXLayer&lt;jTextComponent&gt; l)这个方法将会被调用，这个方法要做的就是我们这个数据验证模块的核心功能：<br />
<br />
1. 调用Hibernate Validator验证该属性。<br />
2. 如果数据不正确，则在GUI上显示一个error icon，并且将错误信息作为tooltip展示给用户。<br />
<br />
在第二点里面产生了一个问题，谢谢Alexp对我的指点。Swing team里面有一些规定，其中之一就是，在paint()方法里面最好不要改变Component的状态，而setTooltip()方法将会改变component的状态，因此需要在paint()方法之外调用。我目前使用下来，还没有发现什么严重的错误，决定暂时不改了，回头有时间在将这个代码翻新一下。<br />
<br />
类中用到的Java2DIconFactory代码如下：<br /></p>
<pre name="code" class="java">package de.jingge.view;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;

public class Java2DIconFactory {

public static BufferedImage createErrorIcon() {
    return createErrorIcon(7, 8);
}



public static BufferedImage createErrorIcon(int width, int height) {
    BufferedImage icon = new BufferedImage(width, height,
            BufferedImage.TYPE_INT_ARGB);
    Graphics2D g2 = (Graphics2D) icon.getGraphics();
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);
    g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
            RenderingHints.VALUE_STROKE_PURE);
    g2.setColor(Color.RED);
    g2.fillRect(0, 0, width, height);
    g2.setColor(Color.WHITE);
    g2.drawLine(0, 0, width, height);
    g2.drawLine(0, height, width, 0);
    g2.dispose();
    return icon;
}
}
</pre>
<p>
<br />
<br />
没什么太多好解释的，就是使用Java 2D画一个Error icon。<br />
<br />
接着，我们需要编写一个Factory类，构建一个JTextField，尽量把复杂技术封装起来，这样程序员开发起来可以提高效率，代码如下：<br /></p>
<pre name="code" class="java">package de.jingge.view;

import javax.swing.JTextField;
import javax.swing.text.JTextComponent;
import org.jdesktop.beansbinding.AutoBinding;
import org.jdesktop.beansbinding.BeanProperty;
import org.jdesktop.beansbinding.BindingGroup;
import org.jdesktop.beansbinding.Bindings;
import org.jdesktop.beansbinding.ELProperty;
import org.jdesktop.jxlayer.JXLayer;
import static org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.*;

public class GuiComponentFactory {

public static JXLayer&lt;jTextComponent&gt; createTextField(
        BindingGroup bindingGroup, Object sourceObject,
        String sourceProperty) {
    JTextField field = new JTextField();
    AutoBinding binding = Bindings.createAutoBinding(READ_WRITE,
            sourceObject, ELProperty.create(&quot;${&quot; + sourceProperty + &quot;}&quot;),
            field, BeanProperty.create(&quot;text&quot;));
    bindingGroup.addBinding(binding);
    bindingGroup.bind();
    return new JXLayer&lt;jTextComponent&gt;(field, new HibernateValidationUI(
            sourceObject, sourceProperty));
}
}
</pre>
<p>
<br />
<br />
createTextField()方法主要将给定对象属性的值与JTextField的text绑定，然后将JTextField纳入到JXLayer的管理之下。这样一来，一旦用户在JTextField里面修改数据，这个改变就会同步到该对象属性上，然后就引发了前面描述的一系列逻辑，最终改变的数据就会被Hiberante Validator加以验证。<br />
<br />
最后，我们可以编写一个Demo application来看看效果如何，代码如下：<br /></p>
<pre name="code" class="java">package de.jingge.main;

import de.jingge.domain.Country;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Toolkit;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.text.JTextComponent;
import net.miginfocom.swing.MigLayout;
import org.jdesktop.beansbinding.BindingGroup;
import org.jdesktop.jxlayer.JXLayer;
import static de.jingge.view.GuiComponentFactory.*;

public class ValidationApplicaton {

private BindingGroup bg;
private Country country;
private JXLayer&lt;jTextComponent&gt; codeField;
private JXLayer&lt;jTextComponent&gt; nameField;

/**
 * @param args the command line arguments
 */
public static void main(String[] args) {
    try {
        UIManager.setLookAndFeel(
                &quot;com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel&quot;);
    } catch (UnsupportedLookAndFeelException ex) {
        System.err.println(
                &quot;Nimbus L&amp;F does not support. Default L&amp;F will be used.&quot;);
    } catch (ClassNotFoundException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (InstantiationException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    ValidationApplicaton app = new ValidationApplicaton();
    JFrame frame = new JFrame(&quot;Demo Validation Application&quot;);
    frame.setPreferredSize(new Dimension(360, 150));
    frame.getContentPane().add(app.buildPanel(), BorderLayout.CENTER);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    setCenter(frame);
    frame.setVisible(true);
    frame.pack();

}

private static void setCenter(JFrame frame) {
    Toolkit toolkit = Toolkit.getDefaultToolkit();
    Dimension screenSize = toolkit.getScreenSize();

    // Calculate the frame location
    int x = (screenSize.width - (int) frame.getPreferredSize().getWidth()) / 2;
    int y = (screenSize.height - (int) frame.getPreferredSize().getHeight()) / 2;

    // Set the new frame location
    frame.setLocation(x, y);
}

public ValidationApplicaton() {
    country = new Country();
    bg = new BindingGroup();
}

private JPanel buildPanel() {

    codeField = createTextField(bg, country, Country.PROPERTYNAME_CODE);
    nameField = createTextField(bg, country, Country.PROPERTYNAME_NAME);
    JPanel panel = new JPanel(new MigLayout(&quot;&quot;,
            &quot;[50px, right]10[200px:250px:300px]&quot;, &quot;[center]&quot;));
    panel.add(new JLabel(&quot;Code:&quot;), &quot;cell 0 0&quot;);
    panel.add(codeField, &quot;cell 1 0, w 200px:250px:300px&quot;);
    panel.add(new JLabel(&quot;Name:&quot;), &quot;cell 0 1&quot;);
    panel.add(nameField, &quot;cell 1 1, w 200px:250px:300px&quot;);
    return panel;
}
}

</pre>
<p>
<br />
<br />
这个类比较简单了，我简单解释一下：<br />
<br />
在main()方法里面，我们创建了一个JFrame，然后放入一个JPanel<br />
<br />
setCenter()方法负责将窗口至于屏幕的正中间。<br />
<br />
在构建器里面，我们创建了Country和BindingGroup的对象实例。<br />
<br />
在buildPanel()方法里面，我们使用MigLayout构建了一个Panel，其中codeField和nameField对应各自的对象属性。更多关于MigLayout的信息看这里：<a href="http://www.miglayout.com/" target="_blank">http://www.miglayout.com/</a>
。这也是一个例子，大家可以看到使用MigLayout开发Swing真的是非常方便。<br />
<br />
从这个Demo里面也可以看出，编写好pojo后，程序员只需要调用createTextField(bg, country, Country.PROPERTYNAME_CODE); 就可以创建一个支持数据验证的JTextField，编码量已经可以说是最大限度的降低了。<br />
<br />
运行程序，你会看到：<br />
<br />
<img src="http://bp3.blogger.com/_kE1wl-BJf90/SIBsALelM7I/AAAAAAAAAAU/LTR5E4GXK5k/s320/image003.png" alt="" />
<br />
<br />
这个code和name的数据都不合法，用户看到了error icon。<br />
<br />
将鼠标移到Text field上，你会看到：<br />
<br />
<img src="http://bp1.blogger.com/_kE1wl-BJf90/SIBsNdzmsxI/AAAAAAAAAAc/Wr1c-XSws8E/s320/image006.jpg" alt="" />
<br />
<br />
填好合法数据后，Error icon就不见了：<br />
<br />
<img src="http://bp3.blogger.com/_kE1wl-BJf90/SIBsYfl8BwI/AAAAAAAAAAk/9GjuWe16qvc/s320/image007.png" alt="" />
<br />
<br />
总结：<br />
<br />
使用这个通用数据验证模块有很多好处：<br />
<br />
1. 如果项目使用ORM，例如Hibernate，这个方案应该是解决数据验证的最好方案之一。<br />
2. 对于普通的数据验证，例如非空，email，长度等等，程序员根本不需要编码，只要在POJO上使用相应的Hibernate Validator annotation就可以了。<br />
3. 对于复杂的数据验证，Hibernate Validator提供了很好的扩展机制，只要写一个annotation外加一个Validator就可以了。Swing应用这边仍然不需要编写任何代码。<br />
<br />
综上所述，可以看出通过使用这个通用数据验证模块，开发效率会提高很多。<br />
<br />
对这个项目感兴趣的朋友可以给我留言，人数够多的话，我会考虑将整个netbeans项目上传到javaeye上来。</p>
<p>&nbsp;</p>
          <br/>
          <span style="color:red;">
            <a href="http://superleo.javaeye.com/topic/216556#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/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>Sat, 19 Jul 2008 18:24:10 +0800</pubDate>
        <link>http://www.javaeye.com/topic/216556</link>
        <guid>http://www.javaeye.com/topic/216556</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://superleo.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/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><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</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://superleo.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/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>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://superleo.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><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, 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>利用&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://superleo.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/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, 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>慎用typo(theme_support)的换肤机制</title>
        <author>JavaEye网站</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://woody-420420.javaeye.com">woody_420420</a>&nbsp;
          链接：<a href="http://www.javaeye.com/topic/215970" style="color:red;">http://www.javaeye.com/topic/215970</a>&nbsp;
          发表时间: 2008年07月17日
          <br/>
          声明：本文系JavaEye网站发布的原创文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <ul>
<li>
<h1>前言</h1>
</li>
</ul>
<p>&nbsp; 本文提到的<a href="https://rubyforge.org/projects/typo/" target="_blank">typo</a>版本是目前最新的5.0.3.98.1，<a href="https://rubyforge.org/projects/theme-generator/" target="_blank">theme_support</a>版本是1.3.0。在typo中，我们看到了很好很花哨的换肤机制，而theme_support则是从typo中抽取出来的一个plugin，以供其他程序进行换肤操作。<br />&nbsp; 先简单介绍下typo换肤的使用。<a href="http://typogarden.org/" target="_blank">typogarden</a>提供了typo十分丰富的皮肤，我们只需要下载喜欢的皮肤，解压，放在typo程序根目录的theme目录下即可，大致的结构图如下所示：<br /><img src="http://woody-420420.javaeye.com/upload/picture/pic/18020/49c22e18-d99f-3853-a474-235e6f62169a.png" height="467" alt="" width="255" /><br />&nbsp; 然后，就可以在admin界面选择自己的皮肤。的确十分方便。但是，使用这种机制，会存在一个严重的性能问题，下面将详细分析问题的原理及其我目前所知的解决方案。</p>
<ul>
<li>
<h1>Typo的换肤原理</h1>
</li>
</ul>
<p>&nbsp; 通常，我们会将程序的图片，css等与皮肤相关的文件放在网站的public目录下，在view中直接引用即可。但是从上图我们可以看到，typo将新皮肤的所有相关文件都存放在theme目录下的各个子目录下。那么typo是如何引用这些文件的呢？下面，我们随便打开某一个皮肤下面的layout文件，例如theme/typographic/layouts下面的default.html.erb文件，可以看到如下代码：</p>
<pre name="code" class="html">&lt;%= stylesheet_link_tag '/stylesheets/theme/style.css', :media =&gt; 'all' %&gt;</pre>
<p>&nbsp; 你仔细搜查一下程序，绝对发现不了/stylesheets/theme目录，乱引用？当然不是，我们可以在routes.rb中发现如下的route信息：</p>
<pre name="code" class="ruby">    get.with_options(:controller =&gt; 'theme', :filename =&gt; /.*/, :conditions =&gt; {:method =&gt; :get}) do |theme|
      theme.connect 'stylesheets/theme/:filename', :action =&gt; 'stylesheets'
      theme.connect 'javascripts/theme/:filename', :action =&gt; 'javascript'
      theme.connect 'images/theme/:filename',      :action =&gt; 'images'
    end</pre>
&nbsp;
<p>&nbsp; 很经典的route配置，原来在typo中，所有对css，javascript，image的引用，不是通过直接引用public目录下的文件，而是通过一个传统的controller：ThemeController来完成的。至于ThemeContorller具体代码，这里不详细谈，因为不是本文的重点，无非就是根据当前选择的皮肤（在admin界面选择的），到相应的皮肤目录（比如：theme/typographic）下，将各个文件找到，然后通过send_file的方式发送到客户端浏览器。那执行完一次action后，controller到底应该执行怎么样的render操作呢？（通过上图，我们看到不同的view文件，在各自的theme子目录下）<br />&nbsp; 在ApplicationController中，有如下代码：</p>
<pre name="code" class="ruby">class ApplicationController &lt; ActionController::Base
......
  def setup_themer
    # Ick!
    self.view_paths = ::ActionController::Base.view_paths.dup.unshift("#{RAILS_ROOT}/themes/#{this_blog.theme}/views")
  end
end</pre>
<p>&nbsp;&nbsp; 大致讲下，setup_themer方法的作用是根据目前皮肤配置（this_blog.theme），将controller的view_paths设置为某一个皮肤的目录（比如：theme/typographic/views），这样，在执行render操作的时候，将使用皮肤目录下的view,layout等等（还记得前面提到的layout中引用css的方法么？重要！）。并且，将setup_themer方法设置为需要换肤的contorller的before_filter。于是，当我们执行程序的时候，就可以达到动态换肤的目的。<br />&nbsp; 大致原理如是。。。</p>
<ul>
<li>
<h1>性能问题的由来</h1>
</li>
</ul>
<p>&nbsp; 前面我讲了，这种机制会存在一个严重的性能问题，是怎么来的呢？在传统方式下，我们将css，images，js都放在public目录下，view进行引用的时候，在web服务器层面，就完成了文件的引用，发送操作。但是在typo这种机制下，每引用一个css，图片，js，web服务器会将请求route到rails，通过rails的ThemeController来处理请求，返回文件。这都还算小事，在一次request，response周期，这样一个额外操作往往占用不了多大的百分比。但是，我们知道ThemeController是rails中一个普通的Controller，它也继承自ApplicationController。通常，我们会将很多程序通用逻辑放在ApplicationController中来做，比如：验证用户合法性，处理本地化等等，而这些操作，大部分都是访问数据库。也就是说，我们通过ThemeController，仅仅想得到一个css或者图片文件，但是ApplicationController仍然会初始化，执行相应的操作（重复，无用的操作）。这就是性能问题的根源。口说无凭，下面的数据揭示了一切：<br /><img src="http://woody-420420.javaeye.com/upload/picture/pic/18016/72a6fba1-dd98-3036-8ace-4e6bb1b22866.png" height="362" alt="" width="281" />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <img src="http://woody-420420.javaeye.com/upload/picture/pic/18014/72522c09-5551-3f59-bd0a-f82663c2be1a.png" height="369" alt="" width="279" /><br />&nbsp; 上面两个性能测试是我随机访问一次typo首页得到的，我们可以看到，对css，image，js的请求，耗费了数十毫秒的操作。为什么请求一个css会耗费如此多时间呢？从下面的图中，我们可以看到原因：<br /><img src="http://woody-420420.javaeye.com/upload/picture/pic/18012/df4d2e29-8e08-3940-9dba-15a1d43054f0.png" height="562" alt="" width="587" /><br />&nbsp; Oh~My god！我出来打个酱油而已，咋搞出个db访问占用了这么长的周期？原因就是我前面提到的，访问ThemeController的时候，ApplicationContorller偷偷摸摸的作祟着（执行了一些业务逻辑的操作，用户验证等）。具体执行的多余db操作如下图所示：<br /><img src="http://woody-420420.javaeye.com/upload/picture/pic/18010/c0ffc657-4b56-359e-8338-460d4541dfa3.png" height="120" alt="" width="800" /><br />&nbsp; 什么show tables，select blogs，select triggers之类操作啊~我请求一个css，何必呢？<br />&nbsp; 性能问题的原因分析完毕！不要忘了，我举例的这些简单的皮肤中，只是简单的十几个css，js，images等。如果你真的使用这种机制在自己的程序中，我想，一个稍微复杂点的皮肤不止十个图片文件。加入我们要在皮肤中引用数十个此类文件，那么性能问题将是十分严重的。</p>
<ul>
<li>
<h1>解决方案</h1>
</li>
</ul>
<p>&nbsp; 目前，我找到三个解决方案处理这个性能问题。</p>
<p>&nbsp; 1. 在ApplicationController中，执行某些业务逻辑的时候，判断一下controller，如果是ThemeController，则跳过。这样做的好处是不用变动typo中换肤方法的使用，仍然将皮肤放在theme目录下即可。但是，这样做似乎&ldquo;侵入性&rdquo;太强，仅仅为了一个皮肤，修改业务逻辑，实在有些得不偿失，因此，这种方案不是一个很好的方案。</p>
<p>&nbsp; 2. 要想不影响已有的业务逻辑解决这个性能问题，我们则需要手动做一些修改。将某一个皮肤放到theme目录下以后，我们收到将css，image，js拷贝到public的相应目录下。比如：将theme/typographic/stylesheets下的所有css文件拷贝到public/stylesheets/typographic目录下，然后，手动将皮肤中所有对css的引用修改为引用public下面相应的目录，比如：我将前面提到的default.html.erb中对css的引用修改为如下所示：</p>
<pre name="code" class="html">&lt;%= stylesheet_link_tag 'typographic/style.css', :media =&gt; 'all' %&gt;</pre>
<p>&nbsp; 然后，将关于theme的roues信息全部删除，让web server为我们完成这个工作。这样，既能解决性能问题，还不影响业务逻辑。只不过，需要不少的手动工作（要将皮肤中所有关于css，js，images的引用修改到public目录下）。</p>
<p>&nbsp; 3. 一劳永逸，按照第二种方法重写theme_support plugin。。。<br />&nbsp; 这里，我按照第二种方法修改了typo的theme机制，主观上来讲，页面访问速度有不小的改进，不像原来，进度条象个蚂蚁一样一耸一耸的~最后，客观起见，还是来一张修改后的性能测试结果：<br /><img src="http://woody-420420.javaeye.com/upload/picture/pic/18018/8a7a650b-a03d-3bfd-b99a-919b2039174c.png" height="119" alt="" width="255" /><br />&nbsp; 整个世界清静了不少~不是吗？：）</p>
<p>&nbsp;</p>
<p>&nbsp; 2008.7.17&nbsp; 23:30&nbsp; 星期四</p>
          <br/>
          <span style="color:red;">
            <a href="http://superleo.javaeye.com/topic/215970#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>Thu, 17 Jul 2008 23:29:15 +0800</pubDate>
        <link>http://www.javaeye.com/topic/215970</link>
        <guid>http://www.javaeye.com/topic/21