<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
  <channel>
    <title>zhijie435</title>
    <description></description>
    <link>http://zhijie435.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>跳槽两次，年薪从11万到35万（连载中)</title>
        <author>zhijie435</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://zhijie435.javaeye.com">zhijie435</a>&nbsp;
          链接：<a href="http://zhijie435.javaeye.com/blog/152576" style="color:red;">http://zhijie435.javaeye.com/blog/152576</a>&nbsp;
          发表时间: 2007年12月26日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          	<p>导读： <br />　　2006年12月，一封来自PMI的信件告诉我，我的PMP考试通过了，我长抒一口气，我和A公司的缘分我知道会要是走到尽头了。A公司是一家巨型的中国公司，正在试图开拓更多的跨国企业客户，而不是传统的政府、银行等客户。我的进入似乎就是在这样的背景之下，在此之前我已经有了四年左右的外企的经验。而我的9000月薪，和同级的同事相比也是高出了一大截。 <br />　　 <br />　　我却在这样的环境却十分的不习惯。我可以忍受加班通宵却不习惯上下班严格的如同防贼的打卡制度，我可以开年会的时候严格小品活跃气氛不习惯唱可笑的公司司歌。当我霹雳吧啦着玩一大段话却看见我的同事迷茫的看着我的时候，我就发现，自己的中英夹杂的毛病又不小心冒出来了。当然在我的同事心中，我怕是也显得有些格格不入。那时的我就如同武林外传的“佟掌柜”一样，无人知时就一直哀叹:“我当初就不应该过来，我要是没有过来就不会这么惨。” <br />　　 <br />　　既然弄拧巴了，跳槽在此时似乎也变成了显而易见的选择了。我有了足够的工作经验，我有了足够的外企背景，我欠缺的不过是一块敲门砖而已。而当这封PMI的信件到来的时候，我知道，时机成熟了。 </p><br /><p>     【评论】<br />　　现在回头来看我这段跳槽经历，其实是有一些盲目的。对于B公司的情况，除了那些公开出来的情况，我其实并不了解。我之所以跳槽的理由，仅仅只是：我做的不爽。这种跳槽其实是危险的。<br />　　<br />　　大多数的情况也许是从一个火坑跳到了另一个火坑。而且因为急着想脱离前面的环境，你往往会作一些牺牲。这些牺牲当时来看也许是为了跳离原先的火坑而值得付出的代价，而当你发现跳进去的又是另一个火坑的时候，你这些做出的牺牲有会变成是在你的伤口上撒了一把焦盐。<br />　　<br />　　所以，看到这里的朋友，你在跳槽的时候，请你千万要慎重。在你跳槽的时候，仔细想想：你为什么要跳槽呢。是因为有更大的发展么？如果不是，请你千万慎重。<br />　　而猪年对于我来说是幸运的一年，我并没有去做我不喜欢的“开发项目经理”职位。相对的来说，我成为B公司整个亚太区实施项目的项目经理，管理各个国家的不同的人员。做的内容也是我非常感兴趣的的ITSM内容。我唯一的牺牲时没有在薪水上做过多的要求。我的薪水最后的数字大约是18万左右一年<br />　　<br />　　而另一方面，在职能部门里我成为了除了我的职能经理外的第二个员工。职能部门的同事也都跟我类似。他们分管着亚太区不同的项目。大家在工作上其实都是非常资深而且自我驱动的人。最重要的一点，大家在工作上并没有冲突，每个人都是负责自己的一块东西。这样同事的关系就不会因为工作而发生冲突，大家都保持了一个非常融洽的同时环境。<br />　　<br />　　面对这样的理想环境，应该说我应该感谢上苍给我这样一个巨大的礼包。我也的确的从心底里喜欢这份工作，我觉得这份工作应该会做的很长。但是命运是一个奇妙的东西，当一个你认为不可能再大的礼包砸在你的头上的时候，可能你并不会想到将会有更大的一个礼包会继续砸在你的头上。<br /><br /><script>show_item("651009","body1");</script><br /><br /><script>show_item("651009","sign");</script><br />        继续，<br />　　<br />　　进入B公司以后，我的薪水上升了差不多50%,在跳槽的市场上，这样一个提升其实还是不错的了。但是理论上，薪水从来就不是一个激励因素。在为这样的薪水激动了一个月后，也就开始习惯这样的薪水<br />　　<br />　　这时候，随着见识和阅历的增加，我开始认真地思考，自己的竞争力在哪里，自己的短板又在哪里。实际上，我觉得这是我在2007年做的最重要，也是最成功的一件事情。<br />　　<br />　　首先，我自己的性格不是能够耐得住寂寞，沉下心来认认真真做研究的性格。所以，我并不希望做技术。我是乐观开朗的性格，喜欢和人打交道。我喜欢和人交流，喜欢做事情，喜欢事情在自己手里成功地感觉。<br />　　<br />　　另一方面，我是一个骨子里非常傲气的人。我可以圆滑的处理事情，但是我不愿意放下身段去求别人去。从沟通交流上来说，我的英文还不错，这是一个自己可以发挥的长处。从技术能力上来说，我的技术能力很弱，自己做过的一些技术其实也开始忘记了。我的逻辑思维还不错，做事情比较有条理。<br />　　<br />　　此外自己从前的一些经验来说，我做过桌面支持，做过ERP的开发，实施，维护。也做过IT咨询，管理及实施。整体上来说，基本上涉及了一个企业IT管理的主要几个方面。<br />　　<br />　　从自己的这些自我分析，我发现我不适合去做销售，我这样宁折不弯的性格在这个客户就是大爷的社会上，注定是要碰上一鼻子灰的。我也不适合去做技术，我不是能够扎实的沉下心来做研究的那种。做中小企业的项目经理也是不适合我的，在中小企业要求项目经理什么都能顶上，是多面手。穿上西装能做sales，脱下来就能直接上去编程。显然我不具备这样的素质。另外中小企业中我的英文的优势也得不到发挥。<br />　　<br />　　从我适合的工作来说，大公司的跨国项目的项目经理是适合我的，大公司把技术和管理分开，这样我可以专心去做管理，而且经常和国外交流的项目，我的语言优势是可以发挥的。这也就是我在B公司做的事情。<br />　　<br />　　而另一方面，其实IT的很多方面我都接触过，ERP这方面的application我有不少经验，但是这个经验对于我在B公司的工作中用处不大，有点可惜了。B公司做的事情有些类似IT的咨询，接触到不少Infrastructure的东西。这些是我感兴趣的工作。如果最终结合我这两方面的经验，我最终决定IT经历是我职业的下一个目标<br />　　<br />　　最终我决定了我的职业道路是这样的。<br />　　<br />　　跨国大企业的：<br />　　项目经理->IT经理->IT总监->CIO<br />　　<br />　　这也是我人生计划的蓝图了。<br /><br /><br />本文转自 <br /><a href="http://club.it.sohu.com/r-it-651005-0-0-0.html">http://club.it.sohu.com/r-it-651005-0-0-0.html</a> </p>	
          <br/>
          <span style="color:red;">
            <a href="http://zhijie435.javaeye.com/blog/152576#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>Wed, 26 Dec 2007 12:45:00 +0800</pubDate>
        <link>http://zhijie435.javaeye.com/blog/152576</link>
        <guid>http://zhijie435.javaeye.com/blog/152576</guid>
      </item>
      <item>
        <title>为何倡导以质量为导向的项目管理？</title>
        <author>zhijie435</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://zhijie435.javaeye.com">zhijie435</a>&nbsp;
          链接：<a href="http://zhijie435.javaeye.com/blog/152577" style="color:red;">http://zhijie435.javaeye.com/blog/152577</a>&nbsp;
          发表时间: 2007年12月20日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          	导读：<br /><br />　　在项目管理的三要素TQC中，这三者在项目管理中是需要平衡的，但是更需要明白他们并不是平等的，为什么这么说呢？这也是因为软件项目的特点引起的，关于软件的项目管理的特点参见我的博客中关于CMM的文章的描述。在软件项目中进度的延迟和成本的增加很大程度上是因为质量引起的，系统的质量问题往往会导致返工，在软件项目中返工已经成为项目经理的恶梦。而返工的直接后果便是进度的拖延和成本的上升，当然还会导致团队人员的沮丧、挫折的心理，这当然都会影响到开发的效率。<br /><br />　　以质量为导向的项目管理在进度控制和成本方面是最经济的。这有业界项目管理专家的统计数据可以说明。对于软件项目来讲，成本主要是来自人工成本，因此人员的效率、沟通成本、过程能力、过程积累等非常重要。另外我们的成本是指Total成本的概念，而不仅仅指开发成本。目前在很大公司都存在这样的问题，开发的预算控制很好，但是由于忽略了质量，导致这个项目在实施、维护阶段返工成本很高，甚至是重新开发，导致了客户的满意度下降，直接影响整体的成本和利润。我个人的体会是Q是这三者之中的因，T、C是结果。因此要想降低和控制成本，就要到源头来控制和找原因。有效控制和提高软件开发的过程质量，往往会减少返工，提高协作和沟通效率，从而节约成本，缩短工期。如果一味以进度、成本的结果为导向，往往会导致进度、成本超出计划，这就象缘木求鱼。<br /><br />　　以质量为导向是项目经理发自内心的要把握项目的质量，而不仅仅是靠与客户的谈判减少项目的范围，这种手段往往最后吃亏的还是项目组。大家都知道软件项目的范围很难划分的很清楚。这也是软件项目的一大特点之一，建筑工程可以用大家都认可的计量单位来描述，客户对软件往往是说你给我做三个功能、四个模块之类的，对话的双方已经就产生了理解的错位。<br /><br />　　下面的一组数据，也许能说明以质量为导向的项目管理的益处。<br /><br />　　平均成本超出 平均进度延迟<br /><br />　　质量驱动型 6.1%          4.5%<br /><br />　　进度驱动型 10.3%          9.0%<br /><br />　　成本驱动型 9.9%          15.9%<br /><br />　　Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1348514<br /><br /><br /><br />本文转自<br /><br /><a href="http://blog.csdn.net/li_hualing/archive/2006/10/24/1348514.aspx">http://blog.csdn.net/li_hualing/archive/2006/10/24/1348514.aspx</a><br />	
          <br/>
          <span style="color:red;">
            <a href="http://zhijie435.javaeye.com/blog/152577#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, 20 Dec 2007 17:41:00 +0800</pubDate>
        <link>http://zhijie435.javaeye.com/blog/152577</link>
        <guid>http://zhijie435.javaeye.com/blog/152577</guid>
      </item>
      <item>
        <title>如何准备测试数据？</title>
        <author>zhijie435</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://zhijie435.javaeye.com">zhijie435</a>&nbsp;
          链接：<a href="http://zhijie435.javaeye.com/blog/152578" style="color:red;">http://zhijie435.javaeye.com/blog/152578</a>&nbsp;
          发表时间: 2007年12月20日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          	导读：<br /><br />　　在软件测试过程中，测试数据的准备是一个工作量很大而且也是一个技术活。因此如何准备大量的测试数据，而且如何准备高质量的测试数据，满足测试的需求，就是一个重要的话题。<br /><br />　　首先看数据的来源，数据的来源一般来讲有三个个，一个是根据被测系统需求的分析，针对正常业务，异常情况，边界情况等来构建完整的数据，又称为“造”数据。这不仅仅包括最基本的基础数据，比如：用户、权限、配置、基础编码、原数据等，还包括上面提到的业务数据。这对于比较小型的系统来说还是可行的，对于大型的系统来说可能就是一个巨大的工程了。<br /><br />　　第二种方式就是利用现有系统，这适合已有类似系统，测试是针对升级或者增加功能的产品化的系统。这种情况把已经在生产环境中运行的数据导出。在此基础上再进行数据的整理、加工为测试数据。<br /><br />　　还有一种方式就是将现有非电子化的业务数据录入到系统中，在验证业务的同时也完成了测试数据的积累。即边测试边积累数据。但是这种情况积累的数据往往有一定局限性，因为已经发生的业务数据基本是正确的、一致的，而且可能缺少某些特定业务的数据（不常发生的业务）。这样就需要根据对测试需求的分析，追加新的测试数据，以便能完整覆盖业务类型。<br /><br />　　确定好数据来源后，还需要对已有数据进行分析、验证、检查，保证数据的质量，数据的质量一般要满足测试需求、覆盖被测业务、覆盖测试边界，以及要满足完整性、一致性等要求。检查完后要整理和完善数据，清除无用和冗余的数据、补录不完整的数据，修改一些错误的数据。<br /><br />　　经过整理好的数据要纳入配置管理，以后根据需求和变更要进行数据的维护和更新，以保证满足系统测试的要求。 <br /><br />　　<img src="http://p.blog.csdn.net/images/p_blog_csdn_net/li_hualing/%B2%E2%CA%D4%CA%FD%BE%DD.GIF" /><br /><br />　　<br /><br />　　<image>http://blog.csdn.net/li_hualing/gallery/image/116195.aspx</image><br /><br /><br /><br />本文转自<br /><br /><a href="http://blog.csdn.net/li_hualing/archive/2006/10/29/1355784.aspx">http://blog.csdn.net/li_hualing/archive/2006/10/29/1355784.aspx</a><br />	
          <br/>
          <span style="color:red;">
            <a href="http://zhijie435.javaeye.com/blog/152578#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, 20 Dec 2007 17:36:00 +0800</pubDate>
        <link>http://zhijie435.javaeye.com/blog/152578</link>
        <guid>http://zhijie435.javaeye.com/blog/152578</guid>
      </item>
      <item>
        <title>测试度量指标</title>
        <author>zhijie435</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://zhijie435.javaeye.com">zhijie435</a>&nbsp;
          链接：<a href="http://zhijie435.javaeye.com/blog/152579" style="color:red;">http://zhijie435.javaeye.com/blog/152579</a>&nbsp;
          发表时间: 2007年12月20日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          	导读：<br /><br />　　序号	类型	度量指标	描述	度量频度	数据源<br /><br />　　1	原始度量元	需求功能点个数	被测系统或被测模块的需求规格说明书的需求个数	测试需求分析阶段	　<br /><br />　　2	系统内部接口数	单系统内部的模块之间的接口数量	测试需求分析阶段	　<br /><br />　　3	跨系统外部接口数	被测系统与外部系统之间的接口数量	测试需求分析阶段	　<br /><br />　　4	测试需求个数	对被测需求分析后的测试需求的个数	测试设计阶段	　<br /><br />　　5	测试案例个数	测试案例的数量，可以根据测试的阶段分不同的类型，比如连接测试案例、系统集成测试案例等	测试设计阶段	　<br /><br />　　6	评审缺陷数量	同行评审或者检查发现的缺陷的数量	测试需求分析和设计阶段	　<br /><br />　　7	测试缺陷总数	测试发现的BUG	测试实施阶段	　<br /><br />　　8	复合度量元	缺陷密度	 ＝缺陷数/需求功能点个数	测试结束后	　<br /><br />　　9	缺陷来源分布	根据缺陷的来源统计缺陷的分布情况	随时	　<br /><br />　　10	缺陷严重程度分布	根据缺陷的严重程度统计缺陷的分布情况	随时	　<br /><br />　　11	缺陷类型分布	根据缺陷的类型统计缺陷的分布情况	随时	　<br /><br />　　12	缺陷位置分布	根据发现缺陷的位置统计缺陷的分布情况	随时	　<br /><br />　　13	缺陷关闭率	 ＝关闭缺陷数/缺陷数总数	随时	　<br /><br />　　14	缺陷修复周期分布	按周统计缺陷修复周期的分布情况	测试结束后、随时	　<br /><br />　　15	评审缺陷密度	 ＝评审发现总缺陷数/功能点	随时	　<br /><br />　　16	评审效率	 ＝评审缺陷密度/工作量	随时	　<br /><br />　　17	测试用例密度	 ＝测试用例数/需求功能点个数	随时	　<br /><br />　　18	测试需求覆盖率	 ＝被测试的需求点数/总需求点数	随时	　<br /><br />　　19	测试案例执行率	 ＝执行的测试用例数/所有测试用例数	随时	　<br /><br />　　20	测试案例通过率	 ＝执行通过的测试案例数量/测试案例总数	随时	　<br /><br />　　<br /><br />　　Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1522330<br /><br /><br /><br />本文转自<br /><br /><a href="http://blog.csdn.net/li_hualing/archive/2007/03/06/1522330.aspx">http://blog.csdn.net/li_hualing/archive/2007/03/06/1522330.aspx</a><br />	
          <br/>
          <span style="color:red;">
            <a href="http://zhijie435.javaeye.com/blog/152579#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, 20 Dec 2007 17:30:00 +0800</pubDate>
        <link>http://zhijie435.javaeye.com/blog/152579</link>
        <guid>http://zhijie435.javaeye.com/blog/152579</guid>
      </item>
      <item>
        <title>软件项目的返工问题</title>
        <author>zhijie435</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://zhijie435.javaeye.com">zhijie435</a>&nbsp;
          链接：<a href="http://zhijie435.javaeye.com/blog/152580" style="color:red;">http://zhijie435.javaeye.com/blog/152580</a>&nbsp;
          发表时间: 2007年12月20日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          	导读：<br /><br />　　<br /><br />　　软件项目的返工问题<br /><br />　　软件行业普遍利润率低，软件项目的成本超支司空见惯，到底成本到哪儿去了？<br /><br />　　软件工程师天天加班加点，说到底还是返工问题。软件项目的返工成本几乎达到<br /><br />　　项目成本的一半以上。到底什么算返工，目前业界好像还没有确切的定义，我总结<br /><br />　　了一下，一下情况应该算是返工：<br /><br />　　返工的定义可以理解为应该并有能力做到返工后的水平的却因为各种主观因素<br /><br />　　却没有一次性达到，只能用返工甚至多次返工的方法来达到目前的要求。<br /><br />　　1. 隐含需求的变更；<br /><br />　　2. 由潜在的需求引起的变更；<br /><br />　　3. 架构选型不当引起的移植、变更；<br /><br />　　4. 需求或设计的理解错误造成的变更；<br /><br />　　5. 在项目范围、技术平台、技术路线决策失误造成的变更；<br /><br />　　6. 设计的抽象不够，造成的开发过程中的浪费、合并、再抽象等工作；<br /><br />　　7. 评审遗漏缺陷造成的变更；<br /><br />　　8. 测试遗漏造成的反复修复工作量。<br /><br />　　其实对比其他行业，软件行业似乎是返工最大的了，很少听说哪个大楼把地基扒<br /><br />　　了三次再盖的，但是很多软件项目确实不止一次的扒掉重来。甚至很少听说哪个项目<br /><br />　　是一直一步一步往前走的，都是来来回回、反反复复完成的。<br /><br />　　<br /><br />　　第一版确认的需求VS最后交付的系统之间的变动分析：<br /><br />　　<strong>变动内容和原因</strong>	<strong>变动的可能性（非互斥关系）</strong>	<strong>对工作量的影响（进度、成本），但是在质量上是改进的</strong><br /><br />　　界面元素、界面风格、界面的易用性、前台的业务逻辑	80%＋	较小<br /><br />　　设计上的变更、后台业务处理逻辑、数据库的变更、易用性设计的变化	50%～60%＋	较大<br /><br />　　返工的需求、局部返工或推倒重来	20%＋	大<br /><br />　　增加的需求、范围的扩大	20%～30%＋	大<br /><br />　　上表是根据以前项目的经验得到的关于软件在开发过程中的变化情况的总结，大部分都是应用类型的项目。<br /><br />　　分析造成这种情况的原因：<br /><br />　　1.                 是客户不成熟，拼命要求进度；导致项目赶进度，前面工作做的不到位，返工是必然的；<br /><br />　　2.                 项目中做需求和设计的人员的能力和经验，经验的欠缺往往导致后期才发现隐含的需求和设计的不到位；<br /><br />　　3.                 项目管理经验如果不成熟，一个重要的表现就是进度狂。要减少返工就要做到胸有成竹、处乱不惊、从容应对。按照计划行事是很重要的，项目过程中突发事情和压力是很多的，做到项目组内部阵脚不乱是非常重要的。<br /><br />　　<br /><br />　　<br /><br />　　Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1577530<br /><br /><br /><br />本文转自<br /><br /><a href="http://blog.csdn.net/li_hualing/archive/2007/04/24/1577530.aspx">http://blog.csdn.net/li_hualing/archive/2007/04/24/1577530.aspx</a><br />	
          <br/>
          <span style="color:red;">
            <a href="http://zhijie435.javaeye.com/blog/152580#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, 20 Dec 2007 17:27:00 +0800</pubDate>
        <link>http://zhijie435.javaeye.com/blog/152580</link>
        <guid>http://zhijie435.javaeye.com/blog/152580</guid>
      </item>
      <item>
        <title>作为项目经理需要重点关注的事情</title>
        <author>zhijie435</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://zhijie435.javaeye.com">zhijie435</a>&nbsp;
          链接：<a href="http://zhijie435.javaeye.com/blog/152581" style="color:red;">http://zhijie435.javaeye.com/blog/152581</a>&nbsp;
          发表时间: 2007年12月20日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          	导读：<br /><br />　　<br /><br />　　在以前的文章中关于项目经理做什么或者如何做好一个项目管理者/项目经理都有很多的叙述。但是最近也有很多的朋友MSN询问作为一个PM应该关注的重要的事情是哪些？ 当然其实所谓的重要的事情，如果从系统化的角度来看的话，有三个系统化教材可以得到全部的答案，这也是作为在软件行业内作为PM应该熟悉的内容，他们是SWEBOK（Software Engineering Body Of Knowledge）、SW-CMMI（Software – Capability Maturity Model Integration）、PMBOK（Project Management Body Of Knowledge）。<br /><br />　　其实这样的答案跟没有回答一样，下面我还是从个人的经验和观点来看：<br /><br />　　需求<br /><br />　　需求是直接与项目的范围相关的，屏蔽和管理需求的风险是作为一个软件项目经理最重要的关注点。同时对需求的了解也是PM有效与外部客户与内部Team有效沟通交流的基础。<br /><br />　　架构（Key Technical Points 关键技术点）<br /><br />　　关于项目的架构设计，PM最需要关心的是关键技术点。因为项目中一般技术问题大都是能够解决的。但是对于关键技术点往往是影响选型、技术方向、性能问题、开发效率等直接相关的。甚至与项目进度也之间相关的，因此不能忽视关键技术对项目引起的风险。<br /><br />　　数据库设计：<br /><br />　　对于Solution项目来说这是非常重要的。数据信息模型是企业应用解决方案项目的重要的模型。数据库设计的好坏一是是否能够满足需求的实现，还有就是能否使得开发实现更容易和简单。不好的数据库数据往往导致业务逻辑的混乱、性能问题、甚至使得开发逻辑更加复杂。<br /><br />　　Build<br /><br />　　Build是开发与测试的桥梁，PM管理的是一个整个的Team，因此确保整个项目Team的工作效率的过程的平滑是PM在进度、成本控制方面要做的事情。因此Build过程尤其对于大项目来说非常重要。<br /><br />　　变更管理<br /><br />　　控制变更是PM确保项目在控制下，不管是范围的变更还是技术的变更都是需要掌握的。<br /><br />　　<br /><br />　　以上的内容是PM对内的管理，作为一个PM，尤其是大的客户项目，PM更需要关注团队外的风险和事务，在其他的文章中都有描述。这里就不总结了。 <br /><br />　　Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1955397<br /><br /><br /><br />本文转自<br /><br /><a href="http://blog.csdn.net/li_hualing/archive/2007/12/20/1955397.aspx">http://blog.csdn.net/li_hualing/archive/2007/12/20/1955397.aspx</a><br />	
          <br/>
          <span style="color:red;">
            <a href="http://zhijie435.javaeye.com/blog/152581#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, 20 Dec 2007 17:15:00 +0800</pubDate>
        <link>http://zhijie435.javaeye.com/blog/152581</link>
        <guid>http://zhijie435.javaeye.com/blog/152581</guid>
      </item>
      <item>
        <title>详说 Subversion备份</title>
        <author>zhijie435</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://zhijie435.javaeye.com">zhijie435</a>&nbsp;
          链接：<a href="http://zhijie435.javaeye.com/blog/152582" style="color:red;">http://zhijie435.javaeye.com/blog/152582</a>&nbsp;
          发表时间: 2007年12月17日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          导读： <br /><br />　　作者：Rock Sun, Subversion中文站。 <br /><br />　　如有转发请注明出处：http://www.subversion.org.cn/index.php?option=com_content&amp;task=view&amp;id=85&amp;Itemid=9 <br /><br />　　版本控制最关键的一件事是保证数据的安全性，不能因为磁盘损坏，程序故障造成版本库无可挽回的错误，为此必须制定较完备的备份策略。在Subversion中，我们有三种备份方式：完全备份，增量备份和同步版本库。 <br /><br />　　1, 完全备份 <br /><br />　　最常见和简单的备份就是直接使用拷贝命令，将版本库目录拷贝到备份目录上，就可以了。但是这样不是很安全的方式，因为如果在拷贝时版本库发生变化，将会造成备份的结果不够准确，失去备份的作用，为此Subversion提供了&ldquo;svnadmin hotcopy&rdquo;命令，可以防止这种问题。 <br /><br />　　还记得我们的版本库目录吗？ <br /><br />　　D:\SVNROOT <br /><br />　　├─project1 <br /><br />　　│ ├─conf <br /><br />　　│ ├─dav <br /><br />　　│ ├─db <br /><br />　　│ │ ├─revprops <br /><br />　　│ │ ├─revs <br /><br />　　│ │ └─transactions <br /><br />　　│ ├─hooks <br /><br />　　│ └─locks <br /><br />　　└─project2 <br /><br />　　├─conf <br /><br />　　├─dav <br /><br />　　├─db <br /><br />　　│ ├─revprops <br /><br />　　│ ├─revs <br /><br />　　│ └─transactions <br /><br />　　├─hooks <br /><br />　　└─locks <br /><br />　　 <br /><br />　　如果要把project1备份到d:\svnrootbak目录下，只需要运行： <br /><br />　　svnadmin hotcopy d:\svnroot\project1 d:\svnrootbak\project1 <br /><br />　　但是我们作为配置管理员，必须想办法优化这个过程，如果我们这个目录下有许多版本库，需要为每个版本库写这样一条语句备份，为此我写了下面的脚本，实现备份一个目录下的所有版本库。我们在D:\SVNROOT下创建了两个文件，simpleBackup.bat： <br /><br />　　@echo 正在备份版本库%1...... <br /><br />　　@%SVN_HOME%\bin\svnadmin hotcopy %1 %BACKUP_DIRECTORY%\%2 <br /><br />　　@echo 版本库%1成功备份到了%2！ <br /><br />　　这个文件仅仅是对&ldquo;svnadmin hotcopy&rdquo;的包装，然后是backup.bat： <br /><br />　　echo off <br /><br />　　rem Subversion的安装目录 <br /><br />　　set SVN_HOME=&quot;D:\Subversion&quot; <br /><br />　　rem 所有版本库的父目录 <br /><br />　　set SVN_ROOT=D:\svnroot <br /><br />　　rem 备份的目录 <br /><br />　　set BACKUP_SVN_ROOT=D:\svnrootbak <br /><br />　　set BACKUP_DIRECTORY=%BACKUP_SVN_ROOT%\%date:~0,10% <br /><br />　　if exist %BACKUP_DIRECTORY% goto checkBack <br /><br />　　echo 建立备份目录%BACKUP_DIRECTORY%&gt;&gt;%SVN_ROOT%/backup.log <br /><br />　　mkdir %BACKUP_DIRECTORY% <br /><br />　　rem 验证目录是否为版本库，如果是则取出名称备份 <br /><br />　　for /r %SVN_ROOT% %%I in (.) do @if exist &quot;%%I\conf\svnserve.conf&quot; %SVN_ROOT%\simpleBackup.bat &quot;%%~fI&quot; %%~nI <br /><br />　　goto end <br /><br />　　:checkBack <br /><br />　　echo 备份目录%BACKUP_DIRECTORY%已经存在，请清空。 <br /><br />　　goto end <br /><br />　　:end <br /><br />　　你在使用的时候，只需要修改backup.bat开头的三个路径，将两个脚本拷贝到&ldquo;SVN_ROOT&rdquo;下就可以了。根据以上的配置，你只需要运行backup.bat，就可以把&ldquo;SVN_ROOT&rdquo;下的版本库都备份到&ldquo;BACKUP_SVN_ROOT&rdquo;里，并且存放在备份所在日的目录里，例如&ldquo;D:\svnrootbak\2006-10-22&rdquo;。 <br /><br />　　虽然这部分工作很简单，可是必须有人定时地去执行这个操作（例如每周一凌晨），为了避免发生遗忘的情况，我们可以将这个操作加入到系统的at任务当中去，例如还是上面的环境，为了安装at任务，我们运行： <br /><br />　　at 1:00/every:M D:\svnroot\backup.bat <br /><br />　　这样在每周一凌晨1:00都会执行这个备份过程。当然备份在本机也是不安全的，你也许需要上传到别的机器，这个就要靠你自己去实现了。 <br /><br />　　<strong>2, 增量备份</strong> <br /><br />　　尽管完全备份非常简单，但是也是有代价的，当版本库非常巨大时，经常进行完全备份是不现实的，也并不必要，但是一旦版本库在备份之间发生问题，该如何呢，这里我们就用到了增量备份。 <br /><br />　　增量备份通常要与完全备份结合使用，就像oracle数据库的归档日志，记录着每次Subversion提交的变化，然后在需要恢复时能够回到最新的可用状态。在我们这个例子中我们使用的是，svnadmin dump命令进行增量的备份，使用方法是： <br /><br />　　svnadmin dump project1 --revision 15 --incremental &gt;dumpfile2 <br /><br />　　上面的命令实现了对修订版本15进行增量的备份，其中的输出文件dumpfile2只保存了修订版本15更改的内容。 <br /><br />　　为了记录每次提交的结果，我们需要使用一项Subversion的特性--钩子（hook），看看我们的project1目录： <br /><br />　　├─project1 <br /><br />　　│ ├─conf <br /><br />　　│ ├─dav <br /><br />　　│ ├─db <br /><br />　　│ │ ├─revprops <br /><br />　　│ │ ├─revs <br /><br />　　│ │ └─transactions <br /><br />　　│ ├─hooks <br /><br />　　│ └─locks <br /><br />　　其中的hooks目录里存放的就是钩子脚本，我们在此处只使用post-commit钩子，这个钩子会在每次提交之后执行，为了实现我们的备份功能，我们在hooks下建立一个文件post-commit.bat，内容如下： <br /><br />　　echo off <br /><br />　　set SVN_HOME=&quot;C:\Program Files\Subversion&quot; <br /><br />　　set SVN_ROOT=D:\svnroot <br /><br />　　set UNIX_SVN_ROOT=D:/svnroot <br /><br />　　set DELTA_BACKUP_SVN_ROOT=D:\svnrootbak\deltaset LOG_FILE=%1\backup.log <br /><br />　　echo backup revision %2 &gt;&gt;%LOG_FILE%for /r %SVN_ROOT% %%I in (.) do if D:/svnroot/%%~nI == %1 %SVN_ROOT%\%%~nI\hooks\deltaBackup.bat %%~nI %2 <br /><br />　　goto end:end <br /><br />　　通过这个脚本，可以实现D:\svnroot下的版本库提交时自动增量备份到D:\svnrootbak\delta（确定这个目录存在），其中使用的deltaBackup.bat其实可以放在任何地方，只是对脚本的svnadmin dump的包装，内容如下： <br /><br />　　@echo 正在备份版本库%2...... <br /><br />　　%SVN_HOME%\bin\svnadmin dump %SVN_ROOT%\%1 --incremental --revision %2 &gt;&gt;%DELTA_BACKUP_SVN_ROOT%\%1.dump <br /><br />　　@echo 版本库%2成功备份到了%3！ <br /><br />　　以上两个脚本可以直接拷贝到project2的hooks目录下，不需要修改就可以实现project2的自动备份。 <br /><br />　　以上的操作已经OK了，现在需要做的是将完全备份和增量备份结合起来，也就是在完全备份后清理增量备份的结果，使之只保存完全备份后的结果。 <br /><br />　　当果真出现版本库的故障，就要求我们实现版本库的恢复操作了，这是用要使用svnadmin load命令，同时也需要上次的完全备份例如要把上次完全备份backuprepo，和之后的增量备份dumpfile： <br /><br />　　svnadmin load backuprepo <dumpfile></dumpfile><br />　　最后的结果，可以下载svnroot.rar，将之解压缩到d:\下，然后修改几个bat文件的SVN_HOME就可以使用了。 <br /><br />　　<strong>3, 版本库同步</strong> <br /><br />　　Subversion 1.4增加了同步机制，可以实现一个版本库同另一个版本库的同步（但好像只是单向的），我们可以用来实现版本库的备份或镜像。 <br /><br />　　<strong>3.1. 对目标库初始化</strong>svnsync init svn://localhost/project2 svn://localhost/project1 <br /><br />　　 <br /><br />　　其中project2是目标的版本库，而project1是源版本库。其中的目标版本库必须为空，而且必须允许修订版本属性的修改，也就是在目标的版本库的hooks目录里添加一个文件pre-revprop-change.bat，内容为空即可。 <br /><br />　　<strong>3.2. 同步project2到project1</strong>svnsync sync svn://localhost/project2 <br /><br />　　 <br /><br />　　这时候你update一下你的project2的一个工作拷贝，就会发现有了project1的所有内容。如果project1又有提交，这时候project2的版本库无法看到最新的变化，还需要再运行一遍sync操作，这样才能将最新的变化同步。需要注意的是，目标版本库只能做成只读的，如果目标版本库发生了变更，则无法继续同步了。 <br /><br />　　<strong>3.3. 同步历史属性的修改</strong> <br /><br />　　因为同步不会更新对历史属性的修改，所以svnsync还有子命令copy-revprops，可以同步某个版本的属性。 <br /><br />　　<strong>3.4. 钩子自动同步</strong> <br /><br />　　希望在每次提交时同步，则需要在源版本库增加post-commit脚本，内容如下： <br /><br />　　echooff <br /><br />　　set SVN_HOME=&quot;D:<strong>\S</strong>ubversion&quot; <br /><br />　　%SVN_HOME%\bin\svnsync sync --non-interactive svn://localhost/project2 <br /><br />　　 <br /><br />　　把以上内容存放为post-commit.bat，然后放到版本库project1下的hooks目录下，这样project1每次提交，都会引起project2的同步。 <br /><br />　　转帖请包含作者等版权信息、并注明来自：我用Subversion- 详说 Subversion备份 <br /><br /><br /><br />本文转自 <br /><br /><a href="http://doc.iusesvn.com/show-37-1.html">http://doc.iusesvn.com/show-37-1.html</a> <br />
          <br/>
          <span style="color:red;">
            <a href="http://zhijie435.javaeye.com/blog/152582#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, 17 Dec 2007 13:56:00 +0800</pubDate>
        <link>http://zhijie435.javaeye.com/blog/152582</link>
        <guid>http://zhijie435.javaeye.com/blog/152582</guid>
      </item>
      <item>
        <title>用Spring 2.0和AspectJ简化企业应用程序</title>
        <author>zhijie435</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://zhijie435.javaeye.com">zhijie435</a>&nbsp;
          链接：<a href="http://zhijie435.javaeye.com/blog/152583" style="color:red;">http://zhijie435.javaeye.com/blog/152583</a>&nbsp;
          发表时间: 2007年12月13日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          	导读： <br />　　Spring的目标是使企业应用程序开发尽可能地简单和高效。这一理论的实例可以从Spring的JDBC、ORM、JMX、依赖注入等方法，以及企业应用程序开发的其他许多重要领域中见到。Spring还区分了使事情简单化和过分单纯化之间的差异。最不可思议的是同时提供了简单化和强大的功能。企业应用程序中复杂性的一个根源来自影响应用程序多个部分的特性和需求的实现。相关于这些特性的代码最终散布在应用程序代码中，使得它更难以添加、维护和理解。Spring 2.0使得以模块化的方式实现这些特性变得更加简单，极大地简化了整体的应用程序代码，并且有时使得在实现没有它的情况下十分痛苦的编码需求变得易如反掌。 <br />　　事务管理是影响应用程序多个部分的一个特性实例：一般来说所有的操作都在服务层。在Spring中解决这种需求的方式是通过使用AOP。Spring 2.0在它对AOP的支持中提供了一个明显的简化，同时还提供了比Spring 1.x所提供的更多富有表现力的功能。这些改善之处主要来自两个主要的领域：通过使用XML schema极大地简化了配置，以及与AspectJ的整合带来了更好的富有表现力的功能和更简单的advice模型。 <br />　　在本文中，我将首先介绍在典型的企业应用程序中，Spring AOP和AspectJ适用于什么地方，之后介绍在2.0中新的Spring AOP支持。大部分篇幅用来讲解企业应用程序中AOP的采用路线图，通过大量可以只用AOP实现的特性实例，但是用任何其他的方法进行实现都将非常困难。 <br />　　<strong>简化企业应用程序</strong> <br />　　典型的企业应用程序——比如一个Web应用程序——由许多层构成。一个包含视图和控制器的Web层，一个表现系统业务接口的服务层，一个负责保存和获取持久化领域对象的数据访问或者<strong>存储</strong>层，与所有这些层共事的，还有一个核心业务逻辑所在的领域模型。 <br />　　Web层、服务层和数据访问层有着许多相同的重要特征：它们应该尽可能地瘦，它们不应该包含业务逻辑，并且它们一般通过Spring组装在一起。在这些层中，Spring负责创建对象和配置。领域模型则有些不同：领域对象由程序员利用新的操作器创建（或者利用从数据库中获取的ORM工具进行扩建）。领域对象有许多唯一的实例，它们（可以）有丰富的行为。 <br />　　服务层可以包含特定于应用程序用例的逻辑，但是所有<strong>领域</strong>相关的逻辑都应该放在领域模型本身里面。 <br />　　服务层一般是使用声明式企业服务（例如事务）的地方。声明式的企业服务，例如事务和安全是影响应用程序中多个点的很好的需求实例。事实上，即使你想让（比如）事务划分只在单个地方，将这项功能与你的应用程序逻辑分开，使得代码更加简单，避免不必要的耦合，这也仍然很好。 <br />　　由于服务对象是Spring管理的bean，Spring AOP天生适合于在这个层中处理需求。事实上，任何人在使用Spring的声明式事务支持时，就已经是在使用Spring AOP了，无论他们是否意识到这一点。Spring AOP很成熟，得到了广泛的应用。它非常适合于Web、服务和数据访问层中受Spring管理的bean，只要你的需求可以通过advice bean方法执行得到处理（且这些层的许多用例都属于这一类）。 <br />　　当提到影响你领域模型中多个点的需求时，你应用程序的最重要部分——Spring AOP——的帮助就小多了。你<strong>可以</strong>编程式地使用Spring AOP，但是这样会很难使用，并且还要你自己负责创建代理和管理同一性。AspectJ天生适合于实现影响领域对象的特性。AspectJ方面不需要任何特殊的代理创建，并且可以很恰当地通知运行时在你的应用程序代码中，或者通过你可能使用的框架所创建的对象。当你想要模块化影响你应用程序的所有不同层的行为，或者模块化性能以任何方式感知的行为时，AspectJ也是一种非常好的解决方案。 <br />　　因此，我们最想要的是一种一致的Spring AOP和AspectJ方法，以便我们可以很容易地一起使用这两种工具，以便如果需求发生变化，你用（比如）Spring AOP开发的能力就可以转移到AspectJ上。无论我们正在使用哪种组合，我们仍然喜欢依赖注入和Spring所提供的配置的所有益处。Spring 2.0中新的AOP支持正好带来了这一点。 <br />　　<strong>底层的技术：AspectJ和Spring AOP简介</strong> <br />　　AOP使得实现在应用程序中影响多个点的特性变得更加简单。这主要因为AOP提供了对名为<strong>通知（advice）</strong>的这个东西的支持。通知不同于必须显式调用的方法，每当发生匹配的触发事件时，它就自动地执行。继续事务主题，触发事件是服务层中一个方法的执行，并且通知逻辑提供所需的事务划分。用AOP的话来说，触发事件被称作<strong>连接点（join point）</strong>，而<strong>切入点表达式（pointcut expression）</strong>则用来选择通知要在那里运行的连接点。这个简单的倒置意味着不用将调用散布到你全部应用程序代码中的事务管理器，而是只要编写一个切入点表达式，定义你需要事务管理器在什么地方完成某事的所有点，并将它与适当的通知关联起来。AspectJ和Spring AOP提供对这个模型的支持，事实上，它们有着完全相同的切入点表达语言。 <br />　　在接下来的讨论中，注意Spring和AspectJ保持为独立的工程，这很重要。Spring只使用反射和由AspectJ 5作为一个库所暴露的工具API。Spring 2.0仍然是一个运行时基于代理的框架，且AspectJ织入器（weaver）不用于Spring方面。 <br />　　我相信你们中大多数人都知道，AspectJ是一种包含完整编译器的语言（构建为Eclipse JDT Java编译器的一个扩展），对离线或者在运行时将（与）二进制的class文件（链接的方面）作为类织入的支持，被加载到了虚拟机中。AspectJ的最新发布版本是AspectJ 5，它为Java 5语言提供完整的支持。 <br />　　AspectJ 5也引入了方面声明的第二种风格，我们称之为“@AspectJ”，它允许你将一个方面编写为一个包含注解的Java类。这种方面可以通过一般的Java 5编译器进行编译。例如，传统的“HelloWorld”方面在AspectJ编程语言中看起来像这样： <br />　　public aspect HelloFromAspectJ { <br />　　pointcut mainMethod() : execution(* main(..)); <br />　　after() returning : mainMethod() { <br />　　System.out.println("Hello from AspectJ!); <br />　　} <br />　　} <br />　　与传统的HelloWorld类共同编译这个方面，当你运行应用程序时，会看到这样的输出： <br />　　Hello World! <br />　　Hello from AspectJ! <br />　　我们可以用@Aspect风格编写相同的方面如下： <br />　　@Aspect <br />　　public class HelloFromAspectJ { <br />　　@Pointcut("execution(* main(..))") <br />　　public void mainMethod() {} <br />　　@AfterReturning("mainMethod()") <br />　　public void sayHello() { <br />　　System.out.println("Hello from AspectJ!"); <br />　　} <br />　　} <br />　　就本文而言，AspectJ 5中另一项重要的新特性是一个完全AspectJ感知的反射API（你可以在运行时为它的通知和切入点成员等等请求一个方面），和让第三方使用AspectJ的切入点解析和匹配引擎的工具API。这些API的第一大用户，就像你很快会见到的，是Spring AOP。 <br />　　与AspectJ相反，Spring AOP是一个基于代理的运行时框架。在使用Spring AOP时，并没有特殊的工具或者构建需求，因而Spring AOP是一种很容易开始的方法。作为一种基于代理的框架，它既有优点也有缺点。除了已经提到过的容易使用的因素之外，基于代理的框架还能够独立地通知相同类型的不同实例。将这一点与AspectJ基于类型的语义相比，在这里，类型的每一个实例都有着相同的行为。对于像Spring这样的框架而言，能够独立地通知独立的对象（Spring beans）是一个重要的必要条件。另一方面，Spring AOP只支持AspectJ功能的一个子集：有可能在Spring beans中通知方法的执行，但是其他没什么。 <br />　　基于代理的框架一般会有同一性的问题：有两个对象（代理和目标）都表示应用程序中的同一个实体。必须始终小心地传递适当的引用，确保给实例化过的任何新的目标对象创建代理。Spring AOP通过管理bean实例化（以便代理可以被透明地创建）和通过依赖注入（以便Spring始终可以注入适当的引用），巧妙地解决了这些问题。 <br />　　<strong>Spring 2.0中新的AOP支持</strong> <br />　　2.0中的Spring AOP可以完全向后与Spring 1.x应用程序和配置兼容。它还提供了比Spring 1.x更简单且更强大的配置。新的AOP支持是基于schema的，因此在你的Spring beans配置文件中将需要相关的命名空间和schema定位属性。它看起来像这样： <br />　　<BEANS <br xmlns="http://www.springframework.org/schema/beans" />xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <br />xmlns:aop="http://www.springframework.org/schema/aop" <br />xsi:schemaLocation= <br />"http://www.springframework.org/schema/beans <br />http://www.springframework.org/schema/beans/spring-beans.xsd <br />http://www.springframework.org/schema/aop <br />http://www.springframework.org/schema/aop/spring-aop.xsd">　　xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <br />　　xmlns:aop="http://www.springframework.org/schema/aop" <br />　　xsi:schemaLocation= <br />　　"http://www.springframework.org/schema/beans <br />　　http://www.springframework.org/schema/beans/spring-beans.xsd <br />　　http://www.springframework.org/schema/aop <br />　　http://www.springframework.org/schema/aop/spring-aop.xsd"> <br />　　... <br />　　 <br />　　与使用DTD时所需要的更简单的xml配置相比，那么目前为止我们还没有超越——但这是标准的xml配置，并且可以在你的IDE中的一个模板里创建，并且只在每当你需要创建一个Spring配置时才被重用。当我们开始将一些内容添加到配置中时，你会领略到这一好处。 <br />　　Spring 2.0默认使用AspectJ 切入点语言（受执行连接点种类的限制）。如果它看到一个AspectJ 切入点表达式，它就调出AspectJ对它进行解析和匹配。这意味着你用Spring AOP编写的任何切入点表达式都将以与AspectJ完全相同的方式进行工作。此外，Spring实际上能理解@AspectJ方面，因此有可能共用Spring和AspectJ之间完整的方面定义。激活这项功能很容易，只要将<aop:aspectj-autoproxy>元素包括在你的配置中。如果AspectJ自动代理以这种方式激活，那么在你的应用程序上下文中定义的、包含@AspectJ方面的任何bean，都将被Spring AOP视为一个方面，并将相应地通知上下文中的bean。 <br />　　下面是当你以这种方式使用Spring AOP时的Hello World程序。首先，应用程序上下文文件中bean元素的内容： <br />　　<BEAN id=helloService <br />class="org.aspectprogrammer.hello.spring.HelloService"/>　　class="org.aspectprogrammer.hello.spring.HelloService"/> <br />　　</aop:aspectj-autoproxy><aop:aspectj-autoproxy> <br />　　<BEAN id=helloFromAspectJ <br />class="org.aspectprogrammer.hello.aspectj.HelloFromAspectJ"/>　　class="org.aspectprogrammer.hello.aspectj.HelloFromAspectJ"/> <br />　　HelloService是一个简单的Java类： <br />　　public class HelloService { <br />　　public void main() { <br />　　System.out.println("Hello World!"); <br />　　} <br />　　} <br />　　HelloFromAspectJ与你在本文前面见过的被注解的Java类（@AspectJ方面）完全相同。以下是启动Spring容器的一个小主类，获得一个对helloService bean的引用，并在它上面调用’main’方法： <br />　　public class SpringBoot { <br />　　public static void main(String[] args) { <br />　　ApplicationContext context = new ClassPathXmlApplicationContext( <br />　　"org/aspectprogrammer/hello/spring/application-context.xml"); <br />　　HelloService service = (HelloService) context.getBean("helloService"); <br />　　service.main(); <br />　　} <br />　　} <br />　　运行这个程序产生下面的输出： <br />　　Hello World! <br />　　Hello from AspectJ! <br />　　记住，这仍然是Spring AOP（我们根本没有在使用AspectJ编译器或者织入器），但它是提供关于@AspectJ方面的反射信息和解析并匹配代表Spring的切入点的AspectJ。 <br />　　Spring 2.0还支持用一个简单的POJO支持的方面声明的一种xml形式（不需要任何注解）。xml形式也使用相同的AspectJ 切入点语言子集，并支持相同的五种AspectJ 通知类型（前置通知（before advice）、后置通知（after returning advice）、异常通知（after throwing advice）、后通知（after [finally] advice）和 环绕通知（around advice））。 <br />　　下面是使用一个基于XML的方面声明的hello world应用程序： <br />　　<BEAN id=helloService <br />class="org.aspectprogrammer.hello.spring.HelloService"/>　　class="org.aspectprogrammer.hello.spring.HelloService"/> <br />　　<aop:config> <br />　　<aop:aspect ref="helloFromSpringAOP"> <br />　　<aop:pointcut id="mainMethod" expression="execution(* main(..))"> <br />　　</aop:pointcut><aop:after-returning method="sayHello" pointcut-ref="mainMethod"> <br />　　</aop:after-returning></aop:aspect> <br />　　</aop:config> <br />　　<BEAN id=helloFromSpringAOP <br />class="org.aspectprogrammer.hello.spring.HelloAspect"/>　　class="org.aspectprogrammer.hello.spring.HelloAspect"/> <br />　　aop命名空间中的元素可以用来声明方面、切入点和通知，有着与它们的AspectJ和@AspectJ等效物完全相同的语义。“aspect”元素引用Spring bean（完全由Spring配置和实例化），并且每个通知元素都在该bean中指定将被调用来执行通知的方法。在这个例子中，HelloAspect类只是： <br />　　public class HelloAspect { <br />　　public void sayHello() { <br />　　System.out.println("Hello from Spring AOP!"); <br />　　} <br />　　} <br />　　运行程序将产生熟悉的输出： <br />　　Hello World! <br />　　Hello from Spring AOP! <br />　　如果你还没有编写过这样的程序，就下载Spring 2.0，亲自尝试一下，这可是个好主意。 <br />　　我不想把本文变成是关于Spring AOP的一个完全的教程，而是想要加紧看一些可以有效地以这种方式实现的特性实例。我将只是指出，传递Spring从使用AspectJ 切入点语言中获得的其中某个东西，是编写静态类型的通知（声明它们真正需要的那些参数的方法）的能力，与始终使用非类型的Object数组相反——这使得通知方法更容易编写。 <br />　　<strong>采用路线图</strong> <br />　　理论说得够多了……让我们看一下你在企业应用程序中实际上如何以及为什么要使用AOP的一些例子。开始AOP，并不一定是一种肯定一切或者否定一切的爆炸性方法。采用可以分阶段进行，每个阶段都为增加的技术暴露回报以更多的益处。 <br />　　建议的采用路线图是只开始使用Spring提供的开箱即用的方面（例如事务管理）。许多Spring用户将已经在这么做了，但多半不太欣赏AOP被“背地里”使用着。根据这一点，你可以实现在使用Spring AOP的Web、服务和数据访问层中可能会有的任何定制横切需求。 <br />　　实现影响领域模型的特性必需使用AspectJ。你听到这句话时可能感到惊讶：有大量的AspectJ方面对于你在开发时都非常有帮助，而且不影响在产品中以任何方式运行的应用程序。这些方面可以增加很多价值，并且采用风险非常小，因此建议用它们开始AspectJ。根据这一点，你可以选择通过AspectJ实现“基础结构的”需求——典型的实例为剖析（profiling）、跟踪（tracing）、错误处理（error-handling）等等。随着你越来越习惯于AspectJ和所配套的工具，最终你可以用方面在领域逻辑自身中开始实现功能。 <br />　　关于AOP采用路线图的其他信息，请见《Eclipse AspectJ》一书中的第11章，或者developerWorks AOP@Work系列中“Next steps with aspects”一文。这两个资源都专门关注AspectJ，而我在这里则正在讨论同时使用Spring和AspectJ。 <br />　　让我们依次看一下这每一种采用阶段。 <br />　　当在一个工程中使用AOP时，首先要做的最有意义的事是定义一组切入点表达式，描述你应用程序中的不同模块或者层。这些切入点表达式在采用的所有不同阶段中都将很有帮助，并且定义一次将减少重复，改善代码的清晰度。如果我们用@AspectJ符号编写这些切入点，它们就可以通过任何常规的Java 5编译器进行编译。利用一般的AspectJ语言关键字也可能编写相同的东西，用ajc编译源文件，并将生成的.class文件添加到classpath中。我将用@AspectJ作为开始Spring AOP的两种方法中更为容易的那一种。许多读者将会熟悉Spring所携带的“jpetstore”范例应用程序。我已经稍微重写了这个应用程序，给它增加了一些方面（本文稍后会讨论到）。以下是在pet store中捕捉主要层和模块的“SystemArchitecture”方面的开头部分： <br />　　@Aspect <br />　　public class SystemArchitecture { <br />　　/** <br />　　* we're in the pet store application if we're within any <br />　　* of the pet store packages <br />　　*/ <br />　　@Pointcut("within(org.springframework.samples.jpetstore..*)") <br />　　public void inPetStore() {} <br />　　// modules <br />　　// =========== <br />　　@Pointcut("within(org.springframework.samples.jpetstore.dao..*)") <br />　　public void inDataAccessLayer() {} <br />　　@Pointcut("within(org.springframework.samples.jpetstore.domain.*)") <br />　　public void inDomainModel() {} <br />　　@Pointcut("within(org.springframework.samples.jpetstore.service..*)") <br />　　public void inServiceLayer() {} <br />　　@Pointcut("within(org.springframework.samples.jpetstore.web..*)") <br />　　public void inWebLayer() {} <br />　　@Pointcut("within(org.springframework.samples.jpetstore.remote..*)") <br />　　public void inRemotingLayer() {} <br />　　@Pointcut("within(org.springframework.samples.jpetstore.validation..*)") <br />　　public void inValidationModule() {} <br />　　// module operations <br />　　// ================== <br />　　@Pointcut("execution(* org.springframework.samples.jpetstore.dao.*.*(..))") <br />　　public void doaOperation() {} <br />　　@Pointcut("execution(* org.springframework.samples.jpetstore.service.*.*(..))") <br />　　public void businessService() {} <br />　　@Pointcut("execution(public * org.springframework.samples.jpetstore.validation.*.*(..))") <br />　　public void validation() {} <br />　　} <br />　　既然我们已经有了谈论应用程序（“inServiceLayer”、“businessOperation”等等）的术语，让我们用它来做一些有意义的事情吧。 <br />　　<strong>使用开箱即用的Spring方面</strong> <br />　　<strong>advisor</strong>是Spring 1.x遗留下来的一个Spring概念，它包含了一个非常小的方面，带有单独的一条通知，和关联的切入点表达式。对于事务划分而言，advisor就是我们所需要的一切。典型的事务需求为：服务层中的所有操作都要利用（几个）底层资源管理器的默认隔离级别在一个事务（REQUIRED语义）中执行。此外，一些操作可以被标识为“只读”事务——这一知识可以给这类事务带来明显的性能改善。jpestore advisor声明如下： <br />　　<!--<br><br >all aspect and advisor declarations are gathered inside an<br><br >aop:config element<br><br >-->　　all aspect and advisor declarations are gathered inside an<br /><br />　　aop:config element<br /><br />　　--> <br />　　<aop:config> <br />　　<?xml:namespace prefix = aop ?><aop:advisor <br />pointcut="org.springframework.samples.jpetstore.SystemArchitecture.businessService()" <br />advice-ref="txAdvice"/>　　pointcut="org.springframework.samples.jpetstore.SystemArchitecture.businessService()" <br />　　advice-ref="txAdvice"/> <br />　　</aop:config> <br />　　这个声明仅仅意味着：当执行一个“businessService”时，我们需要运行被“txAdvice”引用的通知。“BusinessService”切入点在我们前面讨论过的org.springframework.samples.jpetstore.SystemArchitecture方面中定义。它与在服务接口中定义的任何操作的执行相匹配。由于事务通知本身可能需要相当多的配置，因此Spring在tx命名空间中提供了tx:advice元素，使得这项工作变得更加简单和清晰。这就是给jpetstore应用程序的“txAdvice”定义： <br />　　<!--<br><br >Transaction advice definition, based on method name patterns.<br><br >Defaults to PROPAGATION_REQUIRED for all methods whose name starts with<br><br >"insert" or "update", and to PROPAGATION_REQUIRED with read-only hint<br><br >for all other methods.<br><br >-->　　Transaction advice definition, based on method name patterns.<br /><br />　　Defaults to PROPAGATION_REQUIRED for all methods whose name starts with<br /><br />　　"insert" or "update", and to PROPAGATION_REQUIRED with read-only hint<br /><br />　　for all other methods.<br /><br />　　--> <br />　　<tx:advice id="txAdvice"> <br />　　<tx:attributes> <br />　　<tx:method name="insert*"> <br />　　</tx:method><tx:method name="update*"> <br />　　</tx:method><tx:method name="*" read-only="true"> <br />　　</tx:method></tx:attributes> <br />　　</tx:advice> <br />　　还有一种更加简单的方法来配置使用注解的事务。在使用@Transactional注解时，你唯一需要的XML是： <br />　　<!--<br><br >Tell Spring to apply transaction advice based on the presence of<br><br >the @Transactional annotation on Spring bean types.<br><br >-->　　Tell Spring to apply transaction advice based on the presence of<br /><br />　　the @Transactional annotation on Spring bean types.<br /><br />　　--> <br />　　</aop:aspectj-autoproxy><tx:annotation-driven> <br />　　使用注解方法时，PetService实现要做如下注解： <br />　　/* <br />　　* all operations have TX_REQUIRED, default isolation level, <br />　　* read-write transaction semantics by default <br />　　*/ <br />　　@Transactional <br />　　public class PetStoreImpl implements PetStoreFacade, OrderService { <br />　　... <br />　　/** <br />　　* override defaults on a per-method basis <br />　　*/ <br />　　@Transactional(readOnly=true) <br />　　public Account getAccount(String username) { <br />　　return this.accountDao.getAccount(username); <br />　　} <br />　　... <br />　　} <br />　　<strong>简化Web、服务和数据访问层</strong> <br />　　Spring AOP可以用来简化Web、服务和数据访问层。在本节中，我们要看两个实例：一个取自数据访问层，一个取自服务层。 <br />　　假设你已经用Hibernate 3而不是用Spring HibernateTemplate支持类实现了你的数据访问层。你现在准备开始在应用程序中使用Spring，想要在服务层中利用Spring的细粒度DataAccessException层次结构。Spring的HibernateTemplate将自动为你把HibernateExceptions转换成DataAccessExceptions，但是由于现阶段你已经有一个非常满意的数据层实现，因此并不想马上用Spring支持类对它进行重写。这意味着你需要自己实现异常转换。这个需求声明起来很简单： <br />　　从数据访问层中抛出任何HibernateException之后，在将它递给调用者之前将它转换成一个DataAccessException。 <br />　　利用AOP，实现几乎与需求声明一样简单。<strong>没有</strong>AOP时实现这个需求是件非常令人头痛的事。这就是“myapp”的HibernateExceptionTranslator方面： <br />　　@Aspect <br />　　public class HibernateExceptionTranslator { <br />　　private HibernateTemplate hibernateTemplate; <br />　　public void setHibernateTemplate(HibernateTemplate aTemplate) { <br />　　this.hibernateTemplate = aTemplate; <br />　　} <br />　　@AfterThrowing( <br />　　throwing="hibernateEx", <br />　　pointcut="org.aspectprogrammer.myapp.SystemArchitecture.dataAccessOperation()" <br />　　) <br />　　public void rethrowAsDataAccessException(HibernateException hibernateEx) { <br />　　throw this.hibernateTemplate <br />　　.convertHibernateAccessException(hibernateEx); <br />　　} <br />　　} <br />　　方面需要一个HibernateTemplate，以便执行转换——我们要用依赖注入对它进行配置，就像任何其他的Spring bean一样。通知声明应该有望非常容易地理解为需求声明的一个直接转换：“@AfterThrowing从dataAccessOperation()操作中抛出一个HibernateException (hibernateEx) ，并重新抛出 rethrowAsDataAccessException”。简单<strong>而</strong>有力！ <br />　　我们现在可以用ajc（AspectJ编译器）构建应用程序，这样我们就完事了。但是这里不需要使用ajc，因为Spring AOP也能识别@AspectJ方面。 <br />　　在应用程序上下文文件中，我们需要两个配置。首先我们要告诉Spring，包含@AspectJ方面的类型的任何bean都应该用来配置Spring AOP代理。这是通过在应用程序上下文配置文件中的任何位置声明下列元素来实现的一个一次性配置： <br />　　</tx:annotation-driven><aop:aspectj-autoproxy> <br />　　然后我们需要声明异常转换bean，并对它进行配置，就像对待任何一般的Spring bean一样（这里并没有任何特定于AOP的东西）： <br />　　<BEAN id=hibernateExceptionTranslator <br />class="org.aspectprogrammer.myapp.dao.hibernate.HibernateExceptionTranslator">　　class="org.aspectprogrammer.myapp.dao.hibernate.HibernateExceptionTranslator"> <br />　　<property name="hibernateTemplate"> <br />　　<bean class="org.springframework.orm.hibernate3.HibernateTemplate"> <br />　　<constructor-arg index="0" ref="sessionFactory"> <br />　　</constructor-arg></bean> <br />　　</property> <br />　　 <br />　　仅仅因为bean的类（HibernateExceptionTranslator）是一个@AspectJ方面，就足以配置Spring AOP了。 <br />　　为了完整起见，我们也看一下如何用方面声明的xml形式来完成这项工作（例如对于在JDK 1.4下进行工作的）。hibernateExceptionTranslator的bean定义与上面所述的一样。类本身不再被注解，但是它剩下的部分也完全相同： <br />　　public class HibernateExceptionTranslator { <br />　　private HibernateTemplate hibernateTemplate; <br />　　public void setHibernateTemplate(HibernateTemplate aTemplate) { <br />　　this.hibernateTemplate = aTemplate; <br />　　} <br />　　public void rethrowAsDataAccessException(HibernateException hibernateEx) { <br />　　throw this.hibernateTemplate <br />　　.convertHibernateAccessException(hibernateEx); <br />　　} <br />　　} <br />　　由于这不再是一个@AspectJ方面，我们无法使用aspectj-autoproxy元素，而是用XML定义该方面： <br />　　<aop:config> <br />　　<aop:aspect ref="hibernateExceptionTranslator"> <br />　　<aop:after-throwing <br />throwing="hibernateEx" <br />pointcut="org.aspectprogrammer.myapp.SystemArchitecture.dataAccessOperation()" <br />method="rethrowAsDataAccessException"/>　　throwing="hibernateEx" <br />　　pointcut="org.aspectprogrammer.myapp.SystemArchitecture.dataAccessOperation()" <br />　　method="rethrowAsDataAccessException"/> <br />　　</aop:aspect> <br />　　</aop:config> <br />　　这看起来与前一个版本一样：after-throwing 从dataAccessOperation操作中抛出hibernateEx，并且重新抛出rethrowAsDataAccessException。注意aop:aspect元素的“ref”属性，它引用了我们前面定义的hibernateExceptionTranslator bean。这是rethrowAsDataAccessException方法将要在那里被调用的bean实例，而hibernateEx则是在该方法中声明的参数名（这个例子中的唯一参数）。就是这样。我们已经实现了需求（两次！）。利用@AspectJ风格，我们有15个非空的代码行，和一行XML。这足以为我们在整个数据访问层中提供一致、正确的行为，但是它可能很大。 <br />　　这个特殊方面的一大好处在于，如果你以后想要将数据层移植到一个基于利用Hibernate的实现、或者任何其他JPA实现的JPA（EJB 3持久化），你的服务层将不会受到影响，并且可以继续使用DataAccessExceptions（Spring将为JPA提供模板和异常转换，就像对其他的ORM实现所做的一样）。 <br />　　既然我们可以在服务层中使用细粒度的DataAccessExceptions了，就可以利用这一点做些有意义的事情。让我们在将失败传递给客户端之前，实现由于并发失败而失败的任何等幂服务操作都将被透明地重试可设定次数的横切需求。 <br />　　以下是完成这项工作的一个方面： <br />　　@Aspect <br />　　public class ConcurrentOperationExecutor implements Ordered { <br />　　private static final int DEFAULT_MAX_RETRIES = 2; <br />　　private int maxRetries = DEFAULT_MAX_RETRIES; <br />　　private int order = 1; <br />　　private boolean retryOnOptimisticLockFailure = false; <br />　　/** <br />　　* configurable number of retries <br />　　*/ <br />　　public void setMaxRetries(int maxRetries) { <br />　　this.maxRetries = maxRetries; <br />　　} <br />　　/** <br />　　* Whether or not optimistic lock failures should also be retried. <br />　　* Default is not to retry transactions that fail due to optimistic <br />　　* locking in case we overwrite another user's work. <br />　　*/ <br />　　public void setRetryOnOptimisticLockFailure(boolean retry) { <br />　　this.retryOnOptimisticLockFailure = retry; <br />　　} <br />　　/** <br />　　* implementing the Ordered interface enables us to specify when <br />　　* this aspect should run with respect to other aspects such as <br />　　* transaction management. We give it the highest precedence <br />　　* (1) which means that the retry logic will wrap the transaction <br />　　* logic - we need a fresh transaction each time. <br />　　*/ <br />　　public int getOrder() { <br />　　return this.order; <br />　　} <br />　　public void setOrder(int order) { <br />　　this.order = order; <br />　　} <br />　　/** <br />　　* For now, just assume that all business services are idempotent <br />　　*/ <br />　　@Pointcut("org.aspectprogrammer.myapp.SystemArchitecture.businessService()") <br />　　public void idempotentOperation() {} <br />　　@Around("idempotentOperation()") <br />　　public Object doConcurrentOperation(ProceedingJoinPoint pjp) <br />　　throws Throwable { <br />　　int numAttempts = 0; <br />　　ConcurrencyFailureException failureException; <br />　　do { <br />　　try { <br />　　return pjp.proceed(); <br />　　} <br />　　catch(OptimisticLockingFailureException ex) { <br />　　if (!this.retryOnOptimisticLockFailure) { <br />　　throw ex; <br />　　} <br />　　else { <br />　　failureException = ex; <br />　　} <br />　　} <br />　　catch(ConcurrencyFailureException ex) { <br />　　failureException = ex; <br />　　} <br />　　} <br />　　while(numAttempts++ <THIS.MAXRETRIES); <br />　　throw lockFailureException; <br />　　} <br />　　} <br />　　这个方面还是可以被Spring AOP或者AspectJ使用，这一点不变。around advice (doConcurrentOperation)采用了类型ProceedingJoinPoint的一个特殊参数。当proceed在这个对象中被调用时，无论“around”什么样的通知（在这个例子中为服务操作）都将执行。如果你去掉注释和样板getters-and-setters，这个方面的业务端仍然只有32行代码。由于我们在配置文件中已经有aspectj-autoproxy元素，我们需要增加的就只是一个简单的bean定义了： <br />　　<BEAN id=concurrentOperationExecutor <br />class="org.aspectprogrammer.myapp.service.impl.ConcurrentOperationExecutor">　　class="org.aspectprogrammer.myapp.service.impl.ConcurrentOperationExecutor"> <br />　　</aop:aspectj-autoproxy><property name="maxRetries" value="3"> <br />　　</property><property name="order" value="1"> <br />　　 <br />　　如果服务层中并非所有的操作都是等幂的，该怎么办？我们如何判断等幂的操作呢？这就是切入点语言的威力开始显现之处。我们已经有一个表示等幂操作的概念的抽象： <br />　　@Pointcut("org.aspectprogrammer.myapp.SystemArchitecture.businessService()") <br />　　public void idempotentOperation() {} <br />　　如果我们想要改变构成表示等幂操作的东西，我们所要做的就是改变切入点。例如，我们可以给等幂操作定义一个标识注解：@Idempotent。我们可以非常简单地将切入点表达式改为只与包含Idempotent注解的业务服务相匹配： <br />　　@Pointcut( <br />　　"org.aspectprogrammer.myapp.SystemArchitecture.businessService() && <br />　　@annotation(org.aspectprogrammer.myapp.Idempotent)") <br />　　public void idempotentOperation() {} <br />　　现在比使用APT简单一些了！切入点只说：“idempotentOperation是有着Idempotent 注解的businessService”。 <br />　　希望你的大多数服务操作都<strong>是</strong>等幂的。在这种情况下，注解<strong>非</strong>等幂的操作就可能比挑出等幂操作要容易得多。像@IrreversibleSideEffects这样的东西应该会成功。这在技术上和心理上都说得过去（指想要用IrreversibleSideEffects对他们的代码进行注解的人！我宁愿重写代码而避免使用它们；）。由于idempotentOperation的定义只有一处，很容易改变： <br />　　@Pointcut( <br />　　"org.aspectprogrammer.myapp.SystemArchitecture.businessService() && <br />　　!@annotation(org.aspectprogrammer.myapp.IrreversibleSideEffects)") <br />　　public void idempotentOperation() {} <br />　　idempotentOperation是一个没有IrreversibleSideEffects注解的businessService。 <br />　　<strong>用开发时间方面提升生产力</strong> <br />　　一旦你习惯了给Spring AOP编写@AspectJ方面，就会从AspectJ中获得额外的益处，即使你只在开发期间使用它（并且在你正在运行的应用程序中没有AspectJ编译的方面）。方面可以用来针对测试（它们使得某些模拟和错误注入变得更加容易）、调试和诊断问题，以及确保为你的应用程序所设计的设计指导方针得到实施。首先，让我们看一个设计实施方面（enforcement aspects）的实例。继续在数据访问层中进行，我们现在要引入Spring HibernateTemplate，让Spring替我们管理Hibernate会话，而不用我们自己管理。以下这个方面将确保程序员不会忘记开始管理他们自己的会话： <br />　　public aspect SpringHibernateUsageGuidelines { <br />　　pointcut sessionCreation() <br />　　: call(* SessionFactory.openSession(..)); <br />　　pointcut sessionOrFactoryClose() <br />　　: call(* SessionFactory.close(..)) || <br />　　call(* Session.close(..)); <br />　　declare error <br />　　: sessionCreation() || sessionOrFactoryClose() <br />　　: "Spring manages Hibernate sessions for you, "+ <br />　　"do not try to do it programmatically"; <br />　　} <br />　　有了这个方面之后，如果一位程序员在给Eclipse使用AspectJ Development Tools（AJDT）插件，他或者她就将在问题视图中看到一个编译错误的标识，并在源代码中出错的位置（与任何一般的编译错误完全一样）会有错误文本：“Spring替你管理Hibernate会话，请不要试图编程式地进行管理”（Spring manages Hibernate sessions for you, do not try to do it programmatically）。建议引入像这样的实施方面的方法是，将AspectJ编译步骤增加到用实施方面“织入”应用程序的构建过程——如果被方面发现构建错误，这项任务将会失败。 <br />　　现在让我们看一下简单的诊断方面（diagnosis aspect）。回顾一下我们曾将一些事务标识为只读（一项很重要的性能优化）。随着应用程序复杂性的增加，从概念上来说，从事务划分所发生的服务层操作的位置，到作为指定用例的一部分而执行的业务领域逻辑，这之间可能十分遥远。如果在一个只读的事务期间，领域逻辑更新了一个领域对象的状态，我们就会有丢失更新的风险（从来没有提交到数据库）。这可能成为那些莫名其妙bug的根源。 <br />　　LostUpdateDetector方面可以在开发时间用来侦测可能的丢失更新。 <br />　　public aspect LostUpdateDetector { <br />　　private Log log = LogFactory.getLog(LostUpdateDetector.class); <br />　　pointcut readOnlyTransaction(Transactional txAnn) : <br />　　SystemArchitecture.businessService() && <br />　　@annotation(txAnn) &&if(txAnn.readOnly()); <br />　　pointcut domainObjectStateChange() : <br />　　set(!transient * *) && <br />　　SystemArchitecture.inDomainModel(); <br />　　.. <br />　　我已经通过在方面中定义两个有用的切入点开始了。readOnlyTransaction是有着@Transactional注解的businessService()的执行，readOnly()属性设置为true。domainObjectStateChange是任何非瞬时领域inDomainModel()的更新。（注意，这是进行了简化，但是对于组成一个领域对象状态变化的东西仍然很有用——我们可以将该方面扩展为处理集合等等，如果我们希望如此的话）。利用所定义的这两个概念，我们现在就可以通过potentialLostUpdate()表达想说的话了： <br />　　pointcut potentialLostUpdate() : <br />　　domainObjectStateChange() && <br />　　cflow(readOnlyTransaction(Transactional)); <br />　　potentialLostUpdate是在一个readOnlyTransaction（期间）的控制流中所做的一个domainObjectState变化。你从这里可以领略到切入点语言生效的威力。通过组成两个具名的切入点表达式，我们已经能够非常简单地表达一个很强大的概念。与你只有一个粗糙的拦截模型可用时相比，利用切入点语言更容易表达像potentialLostUpdate这样的条件。它也比像EJB 3所提供的那些过于单纯的拦截机制要强大得多。 <br />　　最后，当发生potentialLostUpdate时，我们当然需要真正地做一些事情： <br />　　after() returning : potentialLostUpdate() { <br />　　logLostUpdate(thisJoinPoint); <br />　　} <br />　　private void logLostUpdate(JoinPoint jp) { <br />　　String fieldName = jp.getSignature().getName(); <br />　　String domainType = jp.getSignature().getDeclaringTypeName(); <br />　　String newValue = jp.getArgs()[0].toString(); <br />　　Throwable t = new Throwable("potential lost update"); <br />　　t.fillInStackTrace(); <br />　　log.warn("Field [" + fieldName + "] in type [" + domainType + "] "+ <br />　　"was updated to value [" + newValue + "] in a read-only "+ <br />　　"transaction, update will be lost.",t); <br />　　} <br />　　} <br />　　以下是有了这个方面之后，运行一个测试案例所得到的日志信息： <br />　　WARN - LostUpdateDetector.logLostUpdate(41) | Field [name] in type <br />　　[org.aspectprogrammer.myapp.domain.Pet] was updated to value [Mr.D.] <br />　　in a read-only transaction, update will be lost. <br />　　java.lang.Throwable: potential lost update <br />　　at org.aspectprogrammer.myapp.debug.LostUpdateDetector.logLostUpdate(LostUpdateDetector.aj:40) <br />　　at org.aspectprogrammer.myapp.debug.LostUpdateDetector.afterReturning(LostUpdateDetector.aj:32) <br />　　at org.aspectprogrammer.myapp.domain.Pet.setName(Pet.java:32) <br />　　at org.aspectprogrammer.myapp.service.impl.PetServiceImpl.updateName(PetServiceImpl.java:40) <br />　　at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:100) <br />　　at org.aspectprogrammer.myapp.service.impl.ConcurrentOperationExecutor.doConcurrentOperation(ConcurrentOperationExecutor.java:37) <br />　　at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:478) <br />　　at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:344) <br />　　at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196) <br />　　作为题外话，解释一下干净且易读的堆栈轨迹（和适当的乐观重试逻辑）。易读的堆栈轨迹（stack trace）是由于从异常堆栈轨迹项中去除了干扰的另一个方面。没有适当的堆栈轨迹管理方面，所有的Spring AOP拦截堆栈框也都被显示出来，出现了像下面所示这样的堆栈轨迹。我想，你会认同说简化版是一个很大的改进！ <br />　　WARN - LostUpdateDetector.logLostUpdate(41) | Field [name] in type <br />　　[org.aspectprogrammer.myapp.domain.Pet] was updated to value [Mr.D.] <br />　　in a read-only transaction, update will be lost. <br />　　java.lang.Throwable: potential lost update <br />　　at org.aspectprogrammer.myapp.debug.LostUpdateDetector.logLostUpdate(LostUpdateDetector.aj:40) <br />　　at org.aspectprogrammer.myapp.debug.LostUpdateDetector.ajc$afterReturning$org_aspectprogrammer_myapp_debug_LostUpdateDetector$1$b5d4ce0c(LostUpdateDetector.aj:32) <br />　　at org.aspectprogrammer.myapp.domain.Pet.setName(Pet.java:32) <br />　　at org.aspectprogrammer.myapp.service.impl.PetServiceImpl.updateName(PetServiceImpl.java:40) <br />　　at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) <br />　　at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) <br />　　at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) <br />　　at java.lang.reflect.Method.invoke(Unknown Source) <br />　　at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:287) <br />　　at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:181) <br />　　at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:148) <br />　　at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:100) <br />　　at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:170) <br />　　at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:71) <br />　　at org.aspectprogrammer.myapp.service.impl.ConcurrentOperationExecutor.doConcurrentOperation(ConcurrentOperationExecutor.java:37) <br />　　at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) <br />　　at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) <br />　　at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) <br />　　at java.lang.reflect.Method.invoke(Unknown Source) <br />　　at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:568) <br />　　at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:558) <br />　　at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:57) <br />　　at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:170) <br />　　at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:95) <br />　　at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:170) <br />　　at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:176) <br />　　at $Proxy8.updateName(Unknown Source) <br />　　at org.aspectprogrammer.myapp.debug.LostUpdateDetectorTests.testLostUpdateInReadOnly(LostUpdateDetectorTests.java:23) <br />　　at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) <br />　　at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) <br />　　at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) <br />　　at java.lang.reflect.Method.invoke(Unknown Source) <br />　　at junit.framework.TestCase.runTest(TestCase.java:154) <br />　　at junit.framework.TestCase.runBare(TestCase.java:127) <br />　　at junit.framework.TestResult$1.protect(TestResult.java:106) <br />　　at junit.framework.TestResult.runProtected(TestResult.java:124) <br />　　at junit.framework.TestResult.run(TestResult.java:109) <br />　　at junit.framework.TestCase.run(TestCase.java:118) <br />　　at junit.framework.TestSuite.runTest(TestSuite.java:208) <br />　　at junit.framework.TestSuite.run(TestSuite.java:203) <br />　　at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:478) <br />　　at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:344) <br />　　at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196) <br />　　<strong>简化“基础结构”需求的实现</strong> <br />　　当你越来越习惯于AspectJ和所配套的工具组时，你就可以用AspectJ来实现影响你应用程序所有部分的需求，包括领域模型。作为一个简单的实例，我将向你介绍如何剖析jpetstore范例的应用程序。让我们首先看一下Profiler方面，然后填入一些外围的细节： <br />　　public aspect Profiler { <br />　　private ProfilingStrategy profiler = new NoProfilingStrategy(); <br />　　public void setProfilingStrategy(ProfilingStrategy p) { <br />　　this.profiler = p; <br />　　} <br />　　pointcut profiledOperation() : <br />　　Pointcuts.anyPublicOperation() && <br />　　SystemArchitecture.inPetStore() && <br />　　!within(ProfilingStrategy+); <br />　　Object around() : profiledOperation() { <br />　　Object token = this.profiler.start(thisJoinPointStaticPart); <br />　　Object ret = proceed(); <br />　　this.profiler.stop(token,thisJoinPointStaticPart); <br />　　return ret; <br />　　} <br />　　} <br />　　我们已经将profiledOperation()定义为[the]PetStore()中的anyPublicOperation()了。该方面表现得就像委托给ProfilingStrategy的控制器，我们将利用依赖注入通过Spring对它进行配置。 <br />　　<BEAN id=profiler <br />class="org.springframework.samples.jpetstore.profiling.Profiler" <br />factory-method="aspectOf">　　class="org.springframework.samples.jpetstore.profiling.Profiler" <br />　　factory-method="aspectOf"> <br />　　<property name="profilingStrategy"> <br />　　<ref local="jamonProfilingStrategy"> <br />　　</ref></property> <br />　　 <br />　　<BEAN id=jamonProfilingStrategy <br />class="org.springframework.samples.jpetstore.profiling.JamonProfilingStrategy" <br />init-method="reset" <br />destroy-method="report">　　class="org.springframework.samples.jpetstore.profiling.JamonProfilingStrategy" <br />　　init-method="reset" <br />　　destroy-method="report"> <br />　　 <br />　　注意给方面bean使用了“factory-method”属性，这是配置单例（singleton）AspectJ方面和配置一般的Spring bean之间的唯一区别。我正在用JAMon进行剖析，它提供了一个非常简单的API。 <br />　　public class JamonProfilingStrategy implements ProfilingStrategy { <br />　　public Object start(StaticPart jpStaticPart) { <br />　　return MonitorFactory.start(jpStaticPart.toShortString()); <br />　　} <br />　　public void stop(Object token, StaticPart jpStaticPart) { <br />　　if (token instanceof Monitor) { <br />　　Monitor mon = (Monitor) token; <br />　　mon.stop(); <br />　　} <br />　　} <br />　　} <br />　　这就是我们激活适用于整个pet store的剖析所必须做的全部工作。通过将JAMon提供的jsp增加到pet store应用程序，我们就可以在Web浏览器中观看到剖析的输出。以下是我在应用程序周围点击一会之后的屏幕快照： <br />　　<img src="http://www.infoq.com/resource/articles/Simplifying-Enterprise-Apps/zh/resources/petstore-jamon.jpg;jsessionid=B5AA0A02E7F662B72E12DAE6ED11097C" height="400" width="885" /> <br />　　 <br />　　<strong>简化领域模型</strong> <br />　　具有影响你领域模型的多个部分的业务逻辑需求，这也并不罕见。有些明显的实例为：设计模式实现（请见Nick Leseicki关于这个主题的精彩的developerWorks文章　：part 1、part 2），领域对象的依赖注入（例如使用Spring的@Configurable注解），以及业务规则和策略的实现。在采用的这个阶段，你的核心业务逻辑变成了依赖于方面的存在。 <br />　　你编写的方面将特定于你的领域。AspectJ和AJDT都利用AspectJ构建，我们在它们的构建中使用了大量特定于领域的方面。举个例子，下面是我在1.5.1发布的开发期间增加到AspectJ的一个方面：它实现了一项经常被请求的特性，当一个异常被一个空的捕捉块淹没时，用它来发布一个警告。 <br />　　public aspect WarnOnSwallowedException { <br />　　pointcut resolvingATryStatement(TryStatement tryStatement, BlockScope inScope) <br />　　: execution(* TryStatement.resolve(..)) && <br />　　this(tryStatement) && <br />　　args(inScope,..); <br />　　after(TryStatement tryStatement, BlockScope inScope) returning <br />　　: resolvingATryStatement(tryStatement,inScope) { <br />　　if (tryStatement.catchBlocks != null) { <br />　　for (int i = 0; i <TRYSTATEMENT.CATCHBLOCKS.LENGTH; <br { i++)>　　Block catchBlock = tryStatement.catchBlocks[i]; <br />　　if (catchBlock.isEmptyBlock() || <br />　　catchBlock.statements.length == 0) { <br />　　warnOnEmptyCatchBlock(catchBlock,inScope); <br />　　} <br />　　} <br />　　} <br />　　} <br />　　private void warnOnEmptyCatchBlock(Block catchBlock, BlockScope inScope) { <br />　　inScope.problemReporter() <br />　　.swallowedException(catchBlock.sourceStart(), <br />　　catchBlock.sourceEnd()); <br />　　} <br />　　} <br />　　即使在这个实例中，这个方面只在代码库中建议了一个位置，但它除了JDT编译器的功能之外，还通过将这个AspectJ模块化，使得代码更加清楚了，也使得未来的维护人员非常清楚如何实现这项特性。涉及利用方面给领域建模的进一步详情，则是另一篇文章的主题了。 <br />　　<strong>小结</strong> <br />　　Spring的目标是提供一种简单而强大的企业应用程序开发方法。利用它对AOP的支持，以及与AspectJ的整合，这种方法延伸到了影响应用程序多个部分的特性的实现。传统上而言，这些特性的实现都散布到整个应用程序逻辑中，使得它难以添加、去除和维护特性，并且使得应用程序逻辑复杂化。利用方面，Spring让你能够给这些特性编写整洁、简单且模块化的实现。AOP的采用可以分多个阶段进行：通过利用Spring提供的开箱即用的方面开始，然后可以利用Spring AOP在Web、服务和数据访问层中添加你自己的@AspectJ方面。AspectJ本身可以被用来提供开发生产力，而不用在AspectJ中引入任何依赖。更进一步探讨了横贯你应用程序多个层的基础结构需求，可以利用AspectJ方面被简单地实现。最后，你可以用方面来简化你领域模型本身的实现。 <br />　　<strong>关于作者</strong> <br />　　Adrian Colyer是Interface21的首席科学家，是Eclipse.org的AspectJ项目负责人，以及AspectJ Development Tools（AJDT）项目的创办人。2004年，他被MIT Technology Review投票选为世界前100名年轻的改革者之一，并且经常进行关于Spring、AOP和AspectJ主题的演讲。 <br />　　<strong>关于Interface21</strong> <br />　　Interface21提供Spring、AOP和AspectJ方面的培训和咨询。至于课程安排或者要安排培训的，请见www.interface21.com。 <br />　　Adrian Colyer和Spring社区其他成员出席2006年12月7至10日会议的相关内容请见http://www.thespringexperience.com。 <br />　　<strong>查看英文原文：</strong>Simplifying Enterprise Applications with Spring 2.0 and AspectJ <br />　　<strong>译者简介：</strong>俞黎敏（网名：阿敏总司令），技术顾问，自由撰稿人，开源爱好者，曾经参与Spring中文论坛组织Spring 2.0 Reference中文版的技术审校和满江红开源组织Seam 1.2.1 Reference的中文翻译工作；另外他还翻译了《CSS: The Missing Manual》、《Java Persistence with Hibernate》等书籍，并担任 CSDN、CJSDN、Dev2Dev、Matrix、JavaWorldTW等技术网站Java论坛版主。他的博客是：http://YuLimin.JavaEye.com。 <br /><br />本文转自 <br /><a href="http://www.infoq.com/cn/articles/Simplifying-Enterprise-Apps">http://www.infoq.com/cn/articles/Simplifying-Enterprise-Apps</a> 	</property>
          <br/>
          <span style="color:red;">
            <a href="http://zhijie435.javaeye.com/blog/152583#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, 13 Dec 2007 11:04:00 +0800</pubDate>
        <link>http://zhijie435.javaeye.com/blog/152583</link>
        <guid>http://zhijie435.javaeye.com/blog/152583</guid>
      </item>
      <item>
        <title>Best Practices for Speeding Up Your Web Site</title>
        <author>zhijie435</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://zhijie435.javaeye.com">zhijie435</a>&nbsp;
          链接：<a href="http://zhijie435.javaeye.com/blog/152584" style="color:red;">http://zhijie435.javaeye.com/blog/152584</a>&nbsp;
          发表时间: 2007年12月12日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          导读： <br /><br />　　by Steve Souders, <br /><br />　　Chief Performance Yahoo! <br /><br />　　available from <br /><br />　　O'Reilly Media <br /><br />　　The Importance of Front-End Performance <br /><br />　　Make Fewer HTTP Requests <br /><br />　　Use a Content Delivery Network <br /><br />　　Add an Expires Header <br /><br />　　Gzip Components <br /><br />　　Put Stylesheets at the Top <br /><br />　　Put Scripts at the Bottom <br /><br />　　Avoid CSS Expressions <br /><br />　　Make JavaScript and CSS External <br /><br />　　Reduce DNS Lookups <br /><br />　　Minify JavaScript <br /><br />　　Avoid Redirects <br /><br />　　Remove Duplicate Scripts <br /><br />　　Configure ETags <br /><br />　　Make Ajax Cacheable <br /><br />　　<strong>High Performance Web Sites: The Importance of Front-End Performance</strong> <br /><br />　　In 2004, I started the Exceptional Performance group at Yahoo!. We're a small team chartered to measure and improve the performance of Yahoo!'s products. Having worked as a back-end engineer most of my career, I approached this as I would a code optimization project - I profiled web performance to identify where there was the greatest opportunity for improvement. Since our goal is to improve the end-user experience, I measured response times in a browser over various bandwidth speeds. What I saw is illustrated in the following chart showing HTTP traffic for http://www.yahoo.com. <br /><br />　　<img src="http://l.yimg.com/us.yimg.com/i/rt/stair-step-ydn-blog.gif" alt="" /> <br /><br />　　 <br /><br />　　In the figure above, the first bar, labeled &quot;html&quot;, is the initial request for the HTML document. In this case, only 5% of the end-user response time is spent fetching the HTML document. This result holds true for almost all web sites. In sampling the top ten U.S. websites, all but one spend less than 20% of the total response time getting the HTML document. The other 80+% of the time is spent dealing with what's in the HTML document, namely, the front-end. That's why the key to faster web sites is to focus on improving front-end performance. <br /><br />　　There are three main reasons why front-end performance is the place to start. <br /><br />　　There is more potential for improvement by focusing on the front-end. Cutting it in half reduces response times by 40% or more, whereas cutting back-end performance in half results in less than a 10% reduction. <br /><br />　　Front-end improvements typically require less time and resources than back-end projects (redesigning application architecture and code, finding and optimizing critical code paths, adding or modifying hardware, distributing databases, etc.). <br /><br />　　Front-end performance tuning has been proven to work. Over fifty teams at Yahoo! have reduced their end-user response times by following our performance best practices, often by 25% or more. <br /><br />　　Our performance golden rule is: optimize front-end performance first, that's where 80% or more of the end-user response time is spent. <br /><br />　　<strong>1: Minimize HTTP Requests</strong> <br /><br />　　80% of the end-user response time is spent on the front-end. Most of this time is tied up in downloading all the components in the page: images, stylesheets, scripts, Flash, etc. Reducing the number of components in turn reduces the number of HTTP requests required to render the page. This is the key to faster pages. <br /><br />　　One way to reduce the number of components in the page is to simplify the page's design. But is there a way to build pages with richer content while also achieving fast response times? Here are some techniques for reducing the number of HTTP requests, while still supporting rich page designs. <br /><br />　　Image mapscombine multiple images into a single image. The overall size is about the same, but reducing the number of HTTP requests speeds up the page. Image maps only work if the images are contiguous in the page, such as a navigation bar. Defining the coordinates of image maps can be tedious and error prone. <br /><br />　　CSS Spritesare the preferred method for reducing the number of image requests. Combine all the images in your page into a single image and use the CSS background-imageand background-positionproperties to display the desired image segment. <br /><br />　　<strong>Inline images</strong>use the data:URL schemeto embed the image data in the actual page. This can increase the size of your HTML document. Combining inline images into your (cached) stylesheets is a way to reduce HTTP requests and avoid increasing the size of your pages. <br /><br />　　<strong>Combined files</strong>are a way to reduce the number of HTTP requests by combining all scripts into a single script, and similarly combining all stylesheets into a single stylesheet. It's a simple idea that hasn't seen wide adoption. The ten top U.S. web sites average 7 scripts and 2 stylesheets per page. Combining files is more challenging when the scripts and stylesheets vary from page to page, but making this part of your release process improves response times. <br /><br />　　Reducing the number of HTTP requests in your page is the place to start. This is the most important guideline for improving performance for first time visitors. As described in Tenni Theurer's blog Browser Cache Usage - Exposed!, 40-60% of daily visitors to your site come in with an empty cache. Making your page fast for these first time visitors is key to a better user experience. <br /><br />　　<strong>2: Use a Content Delivery Network</strong> <br /><br />　　The user's proximity to your web server has an impact on response times. Deploying your content across multiple, geographically dispersed servers will make your pages load faster from the user's perspective. But where should you start? <br /><br />　　As a first step to implementing geographically dispersed content, don't attempt to redesign your web application to work in a distributed architecture. Depending on the application, changing the architecture could include daunting tasks such as synchronizing session state and replicating database transactions across server locations. Attempts to reduce the distance between users and your content could be delayed by, or never pass, this application architecture step. <br /><br />　　Remember that 80-90% of the end-user response time is spent downloading all the components in the page: images, stylesheets, scripts, Flash, etc. This is the Performance Golden Rule, as explained in The Importance of Front-End Performance. Rather than starting with the difficult task of redesigning your application architecture, it's better to first disperse your static content. This not only achieves a bigger reduction in response times, but it's easier thanks to content delivery networks. <br /><br />　　A content delivery network (CDN) is a collection of web servers distributed across multiple locations to deliver content more efficiently to users. The server selected for delivering content to a specific user is typically based on a measure of network proximity. For example, the server with the fewest network hops or the server with the quickest response time is chosen. <br /><br />　　Some large Internet companies own their own CDN, but it's cost-effective to use a CDN service provider, such as Akamai Technologies, Mirror Image Internet, or Limelight Networks. For start-up companies and private web sites, the cost of a CDN service can be prohibitive, but as your target audience grows larger and becomes more global, a CDN is necessary to achieve fast response times. At Yahoo!, properties that moved static content off their application web servers to a CDN improved end-user response times by 20% or more. Switching to a CDN is a relatively easy code change that will dramatically improve the speed of your web site. <br /><br />　　<strong>3: Add an Expires Header</strong> <br /><br />　　Web page designs are getting richer and richer, which means more scripts, stylesheets, images, and Flash in the page. A first-time visitor to your page may have to make several HTTP requests, but by using the Expires header you make those components cacheable. This avoids unnecessary HTTP requests on subsequent page views. Expires headers are most often used with images, but they should be used on allcomponents including scripts, stylesheets, and Flash components. <br /><br />　　Browsers (and proxies) use a cache to reduce the number and size of HTTP requests, making web pages load faster. A web server uses the Expires header in the HTTP response to tell the client how long a component can be cached. This is a far future Expires header, telling the browser that this response won't be stale until April 15, 2010. <br /><br />　　Expires: Thu, 15 Apr 2010 20:00:00 GMT <br /><br />　　If your server is Apache, use the ExiresDefault directive to set an expiration date relative to the current date. This example of the ExpiresDefault directive sets the Expires date 10 years out from the time of the request. <br /><br />　　ExpiresDefault &quot;access plus 10 years&quot; <br /><br />　　Keep in mind, if you use a far future Expires header you have to change the component's filename whenever the component changes. At Yahoo! we often make this step part of the build process: a version number is embedded in the component's filename, for example, yahoo_2.0.6.js. <br /><br />　　Using a far future Expires header affects page views only after a user has already visited your site. It has no effect on the number of HTTP requests when a user visits your site for the first time and the browser's cache is empty. The impact of this performance improvement depends, therefore, on how often users hit your pages with a primed cache. (A &quot;primed cache&quot; already contains all of the components in the page.) We measured this at Yahoo!and found the number of page views with a primed cache is 75-85%. By using a far future Expires header, you increase the number of components that are cached by the browser and re-used on subsequent page views without sending a single byte over the user's Internet connection. <br /><br />　　<strong>4: Gzip Components</strong> <br /><br />　　The time it takes to transfer an HTTP request and response across the network can be significantly reduced by decisions made by front-end engineers. It's true that the end-user's bandwidth speed, Internet service provider, proximity to peering exchange points, etc. are beyond the control of the development team. But there are other variables that affect response times. Compression reduces response times by reducing the size of the HTTP response. <br /><br />　　Starting with HTTP/1.1, web clients indicate support for compression with the Accept-Encoding header in the HTTP request. <br /><br />　　Accept-Encoding: gzip, deflate <br /><br />　　If the web server sees this header in the request, it may compress the response using one of the methods listed by the client. The web server notifies the web client of this via the Content-Encoding header in the response. <br /><br />　　Content-Encoding: gzip <br /><br />　　Gzip is the most popular and effective compression method at this time. It was developed by the GNU project and standardized by RFC 1952. The only other compression format you're likely to see is deflate, but it's less effective and less popular. <br /><br />　　Gzipping generally reduces the response size by about 70%. Approximately 90% of today's Internet traffic travels through browsers that claim to support gzip. If you use Apache, the module configuring gzip depends on your version: Apache 1.3 uses mod_gzipwhile Apache 2.x uses mod_deflate. <br /><br />　　There are known issues with browsers and proxies that may cause a mismatch in what the browser expects and what it receives with regard to compressed content. Fortunately, these edge cases are dwindling as the use of older browsers drops off. The Apache modules help out by adding appropriate Vary response headers automatically. <br /><br />　　Servers choose what to gzip based on file type, but are typically too limited in what they decide to compress. Most web sites gzip their HTML documents. It's also worthwhile to gzip your scripts and stylesheets, but many web sites miss this opportunity. In fact, it's worthwhile to compress any text response including XML and JSON. Image and PDF files should not be gzipped because they are already compressed. Trying to gzip them not only wastes CPU but can potentially increase file sizes. <br /><br />　　Gzipping as many file types as possible is an easy way to reduce page weight and accelerate the user experience. <br /><br />　　<strong>5: Put Stylesheets at the Top</strong> <br /><br />　　While researching performance at Yahoo!, we discovered that moving stylesheets to the document HEAD makes pages load faster. This is because putting stylesheets in the HEAD allows the page to render progressively. <br /><br />　　Front-end engineers that care about performance want a page to load progressively; that is, we want the browser to display whatever content it has as soon as possible. This is especially important for pages with a lot of content and for users on slower Internet connections. The importance of giving users visual feedback, such as progress indicators, has been well researched and documented. In our case the HTML page is the progress indicator! When the browser loads the page progressively the header, the navigation bar, the logo at the top, etc. all serve as visual feedback for the user who is waiting for the page. This improves the overall user experience. <br /><br />　　The problem with putting stylesheets near the bottom of the document is that it prohibits progressive rendering in many browsers, including Internet Explorer. Browsers block rendering to avoid having to redraw elements of the page if their styles change. The user is stuck viewing a blank white page. Firefox doesn't block rendering, which means when the stylesheet is done loading it's possible elements in the page will have to be redrawn, resulting in the flash of unstyled contentproblem. <br /><br />　　The HTML specificationclearly states that stylesheets are to be included in the HEAD of the page: &quot;Unlike A, [LINK] may only appear in the HEAD section of a document, although it may appear any number of times.&quot; Neither of the alternatives, the blank white screen or flash of unstyled content, are worth the risk. The optimal solution is to follow the HTML specification and load your stylesheets in the document HEAD. <br /><br />　　<strong>6: Put Scripts at the Bottom</strong> <br /><br />　　Rule 5 described how stylesheets near the bottom of the page prohibit progressive rendering, and how moving them to the document HEAD eliminates the problem. Scripts (external JavaScript files) pose a similar problem, but the solution is just the opposite: it's better to move scripts from the top to as low in the page as possible. One reason is to enable progressive rendering, but another is to achieve greater download parallelization. <br /><br />　　With stylesheets, progressive rendering is blocked until all stylesheets have been downloaded. That's why it's best to move stylesheets to the document HEAD, so they get downloaded first and rendering isn't blocked. With scripts, progressive rendering is blocked for all content belowthe script. Moving scripts as low in the page as possible means there's more content above the script that is rendered sooner. <br /><br />　　The second problem caused by scripts is blocking parallel downloads. The HTTP/1.1 specificationsuggests that browsers download no more than two components in parallel per hostname. If you serve your images from multiple hostnames, you can get more than two downloads to occur in parallel. (I've gotten Internet Explorer to download over 100 images in parallel.) While a script is downloading, however, the browser won't start any other downloads, even on different hostnames. <br /><br />　　In some situations it's not easy to move scripts to the bottom. If, for example, the script uses document.writeto insert part of the page's content, it can't be moved lower in the page. There might also be scoping issues. In many cases, there are ways to workaround these situations. <br /><br />　　An alternative suggestion that often comes up is to use deferred scripts. The DEFERattribute indicates that the script does not contain document.write, and is a clue to browsers that they can continue rendering. Unfortunately, Firefox doesn't support the DEFERattribute. In Internet Explorer, the script may be deferred, but not as much as desired. If a script can be deferred, it can also be moved to the bottom of the page. That will make your web pages load faster. <br /><br />　　<strong>7: Avoid CSS Expressions</strong> <br /><br />　　CSS expressions are a powerful (and dangerous) way to set CSS properties dynamically. They're supported in Internet Explorer, starting with version 5. As an example, the background color could be set to alternate every hour using CSS expressions. <br /><br />　　background-color: expression( (new Date()).getHours()%2 ? &quot;#B8D4FF&quot; : &quot;#F08A00&quot; ); <br /><br />　　As shown here, the expressionmethod accepts a JavaScript expression. The CSS property is set to the result of evaluating the JavaScript expression. The expressionmethod is ignored by other browsers, so it is useful for setting properties in Internet Explorer needed to create a consistent experience across browsers. <br /><br />　　The problem with expressions is that they are evaluated more frequently than most people expect. Not only are they evaluated when the page is rendered and resized, but also when the page is scrolled and even when the user moves the mouse over the page. Adding a counter to the CSS expression allows us to keep track of when and how often a CSS expression is evaluated. Moving the mouse around the page can easily generate more than 10,000 evaluations. <br /><br />　　One way to reduce the number of times your CSS expression is evaluated is to use one-time expressions, where the first time the expression is evaluated it sets the style property to an explicit value, which replaces the CSS expression. If the style property must be set dynamically throughout the life of the page, using event handlers instead of CSS expressions is an alternative approach. If you must use CSS expressions, remember that they may be evaluated thousands of times and could affect the performance of your page. <br /><br />　　<strong>8: Make JavaScript and CSS External</strong> <br /><br />　　Many of these performance rules deal with how external components are managed. However, before these considerations arise you should ask a more basic question: Should JavaScript and CSS be contained in external files, or inlined in the page itself? <br /><br />　　Using external files in the real world generally produces faster pages because the JavaScript and CSS files are cached by the browser. JavaScript and CSS that are inlined in HTML documents get downloaded every time the HTML document is requested. This reduces the number of HTTP requests that are needed, but increases the size of the HTML document. On the other hand, if the JavaScript and CSS are in external files cached by the browser, the size of the HTML document is reduced without increasing the number of HTTP requests. <br /><br />　　The key factor, then, is the frequency with which external JavaScript and CSS components are cached relative to the number of HTML documents requested. This factor, although difficult to quantify, can be gauged using various metrics. If users on your site have multiple page views per session and many of your pages re-use the same scripts and stylesheets, there is a greater potential benefit from cached external files. <br /><br />　　Many web sites fall in the middle of these metrics. For these properties, the best solution generally is to deploy the JavaScript and CSS as external files. The only exception I've seen where inlining is preferable is with home pages, such as Yahoo!'s front page (http://www.yahoo.com) and My Yahoo! (http://my.yahoo.com). Home pages that have few (perhaps only one) page view per session may find that inlining JavaScript and CSS results in faster end-user response times. <br /><br />　　For front pages that are typically the first of many page views, there are techniques that leverage the reduction of HTTP requests that inlining provides, as well as the caching benefits achieved through using external files. One such technique is to inline JavaScript and CSS in the front page, but dynamically download the external files after the page has finished loading. Subsequent pages would reference the external files that should already be in the browser's cache. <br /><br />　　<strong>9: Reduce DNS Lookups</strong> <br /><br />　　The Domain Name System (DNS) maps hostnames to IP addresses, just as phonebooks map people's names to their phone numbers. When you type www.yahoo.com into your browser, a DNS resolver contacted by the browser returns that server's IP address. DNS has a cost. It typically takes 20-120 milliseconds for DNS to lookup the IP address for a given hostname. The browser can't download anything from this hostname until the DNS lookup is completed. <br /><br />　　DNS lookups are cached for better performance. This caching can occur on a special caching server, maintained by the user's ISP or local area network, but there is also caching that occurs on the individual user's computer. The DNS information remains in the operating system's DNS cache (the &quot;DNS Client service&quot; on Microsoft Windows). Most browsers have their own caches, separate from the operating system's cache. As long as the browser keeps a DNS record in its own cache, it doesn't bother the operating system with a request for the record. <br /><br />　　Internet Explorer caches DNS lookups for 30 minutes by default, as specified by the DnsCacheTimeoutregistry setting. Firefox caches DNS lookups for 1 minute, controlled by the network.dnsCacheExpirationconfiguration setting. (Fasterfox changes this to 1 hour.) <br /><br />　　When the client's DNS cache is empty (for both the browser and the operating system), the number of DNS lookups is equal to the number of unique hostnames in the web page. This includes the hostnames used in the page's URL, images, script files, stylesheets, Flash objects, etc. Reducing the number of unique hostnames reduces the number of DNS lookups. <br /><br />　　Reducing the number of unique hostnames has the potential to reduce the amount of parallel downloading that takes place in the page. Avoiding DNS lookups cuts response times, but reducing parallel downloads may increase response times. My guideline is to split these components across at least two but no more than four hostnames. This results in a good compromise between reducing DNS lookups and allowing a high degree of parallel downloads. <br /><br />　　<strong>10: Minify JavaScript</strong> <br /><br />　　Minification is the practice of removing unnecessary characters from code to reduce its size thereby improving load times. When code is minified all comments are removed, as well as unneeded white space characters (space, newline, and tab). In the case of JavaScript, this improves response time performance because the size of the downloaded file is reduced. Two popular tools for minifying JavaScript code are JSMinand YUI Compressor. <br /><br />　　Obfuscation is an alternative optimization that can be applied to source code. Like minification, it removes comments and white space, but it also munges the code. As part of munging, function and variable names are converted into smaller strings making the code more compact as well as harder to read. This is typically done to make it more difficult to reverse engineer the code. But munging can help performance because it reduces the code size beyond what is achieved by minification. The tool-of-choice is less clear in the area of JavaScript obfuscation. Dojo Compressor (ShrinkSafe) is the one I've seen used the most. <br /><br />　　Minification is a safe, fairly straightforward process. Obfuscation, on the other hand, is more complex and thus more likely to generate bugs as a result of the obfuscation step itself. Obfuscation also requires modifying your code to indicate API functions and other symbols that should not be munged. It also makes it harder to debug your code in production. Although I've never seen problems introduced from minification, I have seen bugs caused by obfuscation. In a survey of ten top U.S. web sites, minification achieved a 21% size reduction versus 25% for obfuscation. Although obfuscation has a higher size reduction, I recommend minifying JavaScript code because of the reduced risks and maintenance costs. <br /><br />　　In addition to minifying external scripts, inlined script blocks can and should also be minified. Even if you gzip your scripts, as described in Rule 4, minifying them will still reduce the size by 5% or more. As the use and size of JavaScript increases, so will the savings gained by minifying your JavaScript code. <br /><br />　　<strong>11: Avoid Redirects</strong> <br /><br />　　Redirects are accomplished using the 301 and 302 status codes. Here's an example of the HTTP headers in a 301 response: <br /><br />　　HTTP/1.1 301 Moved Permanently <br /><br />Location: http://example.com/newuri <br /><br />Content-Type: text/html <br /><br />　　The browser automatically takes the user to the URL specified in the Locationfield. All the information necessary for a redirect is in the headers. The body of the response is typically empty. Despite their names, neither a 301 nor a 302 response is cached in practice unless additional headers, such as Expiresor Cache-Control, indicate it should be. The meta refresh tag and JavaScript are other ways to direct users to a different URL, but if you must do a redirect, the preferred technique is to use the standard 3xx HTTP status codes, primarily to ensure the back button works correctly. <br /><br />　　The main thing to remember is that redirects slow down the user experience. Inserting a redirect between the user and the HTML document delays everything in the page since nothing in the page can be rendered and no components can start being downloaded until the HTML document has arrived. <br /><br />　　One of the most wasteful redirects happens frequently and web developers are generally not aware of it. It occurs when a trailing slash (/) is missing from a URL that should otherwise have one. For example, going to http://astrology.yahoo.com/astrologyresults in a 301 response containing a redirect to http://astrology.yahoo.com/astrology/(notice the added trailing slash). This is fixed in Apache by using Aliasor mod_rewrite, or the DirectorySlashdirective if you're using Apache handlers. <br /><br />　　Connecting an old web site to a new one is another common use for redirects. Others include connecting different parts of a website and directing the user based on certain conditions (type of browser, type of user account, etc.). Using a redirect to connect two web sites is simple and requires little additional coding. Although using redirects in these situations reduces the complexity for developers, it degrades the user experience. Alternatives for this use of redirects include using Aliasand mod_rewriteif the two code paths are hosted on the same server. If a domain name change is the cause of using redirects, an alternative is to create a CNAME (a DNS record that creates an alias pointing from one domain name to another) in combination with Aliasor mod_rewrite. <br /><br />　　<strong>12: Remove Duplicate Scripts</strong> <br /><br />　　It hurts performance to include the same JavaScript file twice in one page. This isn't as unusual as you might think. A review of the ten top U.S. web sites shows that two of them contain a duplicated script. Two main factors increase the odds of a script being duplicated in a single web page: team size and number of scripts. When it does happen, duplicate scripts hurt performance by creating unnecessary HTTP requests and wasted JavaScript execution. <br /><br />　　Unnecessary HTTP requests happen in Internet Explorer, but not in Firefox. In Internet Explorer, if an external script is included twice and is not cacheable, it generates two HTTP requests during page loading. Even if the script is cacheable, extra HTTP requests occur when the user reloads the page. <br /><br />　　In addition to generating wasteful HTTP requests, time is wasted evaluating the script multiple times. This redundant JavaScript execution happens in both Firefox and Internet Explorer, regardless of whether the script is cacheable. <br /><br />　　One way to avoid accidentally including the same script twice is to implement a script management module in your templating system. The typical way to include a script is to use the SCRIPT tag in your HTML page. <br /><br />　　script type=&quot;text/javascript&quot; src=&quot;menu_1.0.17.js&quot;&gt; <br /><br />　　An alternative in PHP would be to create a function called insertScript. <br /><br />　　<!--p insertScript("menu.js")--> <br /><br />　　In addition to preventing the same script from being inserted multiple times, this function could handle other issues with scripts, such as dependency checking and adding version numbers to script filenames to support far future Expires headers. <br /><br />　　<strong>13: Configure ETags</strong> <br /><br />　　Entity tags (ETags) are a mechanism that web servers and browsers use to determine whether the component in the browser's cache matches the one on the origin server. (An &quot;entity&quot; is another word for what I've been calling a &quot;component&quot;: images, scripts, stylesheets, etc.) ETags were added to provide a mechanism for validating entities that is more flexible than the last-modified date. An ETag is a string that uniquely identifies a specific version of a component. The only format constraints are that the string be quoted. The origin server specifies the component's ETag using the ETagresponse header. <br /><br />　　HTTP/1.1 200 OK <br /><br />Last-Modified: Tue, 12 Dec 2006 03:03:59 GMT <br /><br />ETag: &quot;10c24bc-4ab-457e1c1f&quot; <br /><br />Content-Length: 12195 <br /><br />　　Later, if the browser has to validate a component, it uses the If-None-Matchheader to pass the ETag back to the origin server. If the ETags match, a 304 status code is returned reducing the response by 12195 bytes for this example. <br /><br />　　GET /i/yahoo.gif HTTP/1.1 <br /><br />Host: us.yimg.com <br /><br />If-Modified-Since: Tue, 12 Dec 2006 03:03:59 GMT <br /><br />If-None-Match: &quot;10c24bc-4ab-457e1c1f&quot; <br /><br />HTTP/1.1 304 Not Modified