﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-日出而作兮勤于外，日落而归兮忙于内-随笔分类-名家好文</title><link>http://www.blogjava.net/cherishchen/category/23928.html</link><description>The art of living is to know when to hold fast and when to let go</description><language>zh-cn</language><lastBuildDate>Sun, 19 Jul 2009 23:55:44 GMT</lastBuildDate><pubDate>Sun, 19 Jul 2009 23:55:44 GMT</pubDate><ttl>60</ttl><item><title>How to stop Java thread safely</title><link>http://www.blogjava.net/cherishchen/archive/2009/07/15/286861.html</link><dc:creator>凭栏观海</dc:creator><author>凭栏观海</author><pubDate>Wed, 15 Jul 2009 05:36:00 GMT</pubDate><guid>http://www.blogjava.net/cherishchen/archive/2009/07/15/286861.html</guid><wfw:comment>http://www.blogjava.net/cherishchen/comments/286861.html</wfw:comment><comments>http://www.blogjava.net/cherishchen/archive/2009/07/15/286861.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cherishchen/comments/commentRss/286861.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cherishchen/services/trackbacks/286861.html</trackback:ping><description><![CDATA[How to stop Java thread safely<br />
这篇文章不仅仅介绍为什么interrupt()是最好的停止线程的方式，还介绍了JDk1.4引入的新版本的 java concurrent 包，值得深入一读。<br />
http://forward.com.au/javaProgramming/HowToStopAThread.html
<img src ="http://www.blogjava.net/cherishchen/aggbug/286861.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cherishchen/" target="_blank">凭栏观海</a> 2009-07-15 13:36 <a href="http://www.blogjava.net/cherishchen/archive/2009/07/15/286861.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>scsi,fc,iscsi（转)</title><link>http://www.blogjava.net/cherishchen/archive/2008/05/30/204083.html</link><dc:creator>凭栏观海</dc:creator><author>凭栏观海</author><pubDate>Fri, 30 May 2008 06:40:00 GMT</pubDate><guid>http://www.blogjava.net/cherishchen/archive/2008/05/30/204083.html</guid><wfw:comment>http://www.blogjava.net/cherishchen/comments/204083.html</wfw:comment><comments>http://www.blogjava.net/cherishchen/archive/2008/05/30/204083.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cherishchen/comments/commentRss/204083.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cherishchen/services/trackbacks/204083.html</trackback:ping><description><![CDATA[http://space.doit.com.cn/17236/viewspace-2729.html<br />
<br />
其历史顺序应该SCSI-&gt;FC(用于SCSI)-&gt;ISCSI；<br />
最初是从SCSI开始的，它也是存储领域最为广泛的协议；SCSI的命令和数据，可以直接在SCSI接口中传输，也可以通过封装进行传输，比如用USB，1394，FC，以及iSCSI等方式。 <br />
由于在传统的SCSI接口中，其传输的距离有限；因此用FC来扩大传输距离就应运而生，从而封装SCSI的FC接口流行起来，物理上它只是加上的FC的电路，其核心的SCSI部分基本不做修改，因此软件上移植SCSI HBA到FC的HBA实现难度并不大。&nbsp;&nbsp;<br />
同样，由于FC的成本和传输距离问题，iSCSI横空出世，它用TCP/IP协议来封装和传输，物理上加上TOE电路(或者用软件来实现这部分)，同样其
核心仍然是SCSI的处理，方式和FC查不错。至于iSCSI的流行，这和软硬件厂商的推广(需要在存储的各个应用环境中都加入iSCSI的支持)，以及
市场的接受程度相关。<br />
从物理上来讲，对于FC和iSCSI需要特殊的IC来完成处理；而软件上，改变会比较小，在windows下面，PCI
RAID卡、FC卡、iSCSI卡的驱动，都是采用Port/MiniPort驱动架构，其中Port
driver(是硬件无关的)由微软提供(在2003以前叫SCSIPORT,现在叫StorPort，在windows的系统目录下可以看见这该驱
动)，而Miniport包含了上面所说的三种卡，其架构都一样，只是要针对各种卡做对应的处理而已。对于其他的操作系统，这3种卡的软件处理方面也是类
似的。因为最主要的差别都在物理传输上，所以基本在硬件上完成；而软件上，都是以处理SCSI命令和数据为核心，然后围绕传输接口做相关的处理。<br />
个人对iSCSI，比较看好！
<img src ="http://www.blogjava.net/cherishchen/aggbug/204083.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cherishchen/" target="_blank">凭栏观海</a> 2008-05-30 14:40 <a href="http://www.blogjava.net/cherishchen/archive/2008/05/30/204083.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>设计及设计模式：关于Java权限控制算法(转）</title><link>http://www.blogjava.net/cherishchen/archive/2008/02/25/181882.html</link><dc:creator>凭栏观海</dc:creator><author>凭栏观海</author><pubDate>Mon, 25 Feb 2008 01:20:00 GMT</pubDate><guid>http://www.blogjava.net/cherishchen/archive/2008/02/25/181882.html</guid><wfw:comment>http://www.blogjava.net/cherishchen/comments/181882.html</wfw:comment><comments>http://www.blogjava.net/cherishchen/archive/2008/02/25/181882.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cherishchen/comments/commentRss/181882.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cherishchen/services/trackbacks/181882.html</trackback:ping><description><![CDATA[<p>http://publish.itpub.net/j/2008-01-24/200801240919412.shtml</p>
<p>
作者：<strong class="red">来自ITPUB论坛</strong>&nbsp;&nbsp;2008-01-24</p>
<p><br />
</p>
<p>向大家介绍一种很不错，也是Linux中的权限管理算法。
</p>
<p>　　定义a^b为：a的b次方</p>
<p>　　假如，我们为每一个操作设定一个唯一的整数值，比如：</p>
<p>　　删除A---0</p>
<p>　　修改A---1</p>
<p>　　添加A---2</p>
<p>　　删除B---3</p>
<p>　　修改B---4</p>
<p>　　添加B---5</p>
<p>　　。。。</p>
<p>　　理论上可以有N个操作，这取决于你用于储存用户权限值的数据类型了。</p>
<p>　　这样，如果用户有权限：添加A---2;删除B---3;修改B---4</p>
<p>　　那用户的权限值 purview =2^2+2^3+2^4=28，也就是2的权的和了</p>
<p>　　化成二进制可以表示为11100</p>
<p>　　如果要验证用户是否有删除B的权限，就可以通过位与运算来实现。</p>
<p>　　在Java里，位与运算运算符号为&amp;</p>
<p>　　即是：int value = purview &amp;((int)Math.pow(2,3));</p>
<p>　　你会发现，当用户有操作权限时，运算出来的结果都会等于这个操作需要的权限值!</p>
<p>　　原理：</p>
<p>　　位与运算，顾名思义就是对位进行与运算：</p>
<p>　　以上面的式子为例：purview &amp; 2^3 也就是　28&amp;8</p>
<p>　　将它们化成二进制有</p>
<p>　　11100</p>
<p>　　&amp; 01000</p>
<p>　　-------------------</p>
<p>　　01000 == 8(十进制)　==　2^3</p>
<p>　　同理，如果要验证是否有删除A---0的权限</p>
<p>　　可以用：purview &amp;((int)Math.pow(2,0));</p>
<p>　　即：</p>
<p>　　11100</p>
<p>　　&amp; 00001</p>
<p>　　------------------------</p>
<p>　　00000 == 0(十进制)　　!=　2^0</p>
<p>　　这种算法的一个优点是速度快。可以同时处理N个权限，设置N种角色.</p>
<p>　　如果想验证是否同时有删除A---0和删除B---3的权限</p>
<p>　　可以用purview&amp;(2^0+2^3)==(2^0+2^3)?true:false;</p>
<p>　　设置多角色用户。根据权限值判断用户的角色。。。</p>
<p>　　下面提供一个java的单操作权限判断的代码：</p>
<div style="overflow: auto; width: 500px;">
<pre style="border: 1px solid black; padding: 4px; background-color: #ededed;">
<div><!-- Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
--><span style="color: #000000;">　　</span><span style="color: #008000;">//</span><span style="color: #008000;">userPurview是用户具有的总权限<br />
</span><span style="color: #008000;">//</span><span style="color: #008000;">optPurview是一个操作要求的权限为一个整数(没有经过权的!)</span><span style="color: #008000;"><br />
</span><span style="color: #000000;"><br />
</span><span style="color: #0000ff;">public</span><span style="color: #000000;"> </span><span style="color: #0000ff;">static</span><span style="color: #000000;"> boolean checkPower(</span><span style="color: #0000ff;">int</span><span style="color: #000000;"> userPurview, </span><span style="color: #0000ff;">int</span><span style="color: #000000;"> optPurview){<br />
</span><span style="color: #0000ff;">int</span><span style="color: #000000;"> purviewValue </span><span style="color: #000000;">=</span><span style="color: #000000;"> (</span><span style="color: #0000ff;">int</span><span style="color: #000000;">)Math.pow(</span><span style="color: #000000;">2</span><span style="color: #000000;">, optPurview);<br />
</span><span style="color: #0000ff;">return</span><span style="color: #000000;"> (userPurview </span><span style="color: #000000;">&amp;</span><span style="color: #000000;"> purviewValue) </span><span style="color: #000000;">==</span><span style="color: #000000;"> purviewValue;<br />
}</span></div>
<br />
</pre>
</div>
<p>　　当然，多权限的验证只要扩展一下就可以了。</p>
<p>　　几点注意事项：首先，一个系统可能有很多的操作，因此，请建立数据字典，以便查阅，修改时使用。其次，如果用数据库储存用户权限，请注意数值的
有效范围。操作权限值请用唯一的整数!Java的int类型最多可以储存11个权限和.如果超过，可以选择其它数据类型，而且建议不同模块，使用多个权限
变量.</p>
<p>&nbsp;</p>
<img src ="http://www.blogjava.net/cherishchen/aggbug/181882.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cherishchen/" target="_blank">凭栏观海</a> 2008-02-25 09:20 <a href="http://www.blogjava.net/cherishchen/archive/2008/02/25/181882.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>OOD Principle study:开放－封闭原则（OCP：The Open-Closed Principle）</title><link>http://www.blogjava.net/cherishchen/archive/2007/08/29/140805.html</link><dc:creator>凭栏观海</dc:creator><author>凭栏观海</author><pubDate>Wed, 29 Aug 2007 02:38:00 GMT</pubDate><guid>http://www.blogjava.net/cherishchen/archive/2007/08/29/140805.html</guid><wfw:comment>http://www.blogjava.net/cherishchen/comments/140805.html</wfw:comment><comments>http://www.blogjava.net/cherishchen/archive/2007/08/29/140805.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cherishchen/comments/commentRss/140805.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cherishchen/services/trackbacks/140805.html</trackback:ping><description><![CDATA[<p><strong>开放－封闭原则：</strong>Software Entities Should Be Open For Extension, Yet Closed For Modification</p> <p><a title="http://www.textcn.com/jishuwz/prosj/netnet/200612/55186.html" href="http://www.textcn.com/jishuwz/prosj/netnet/200612/55186.html">http://www.textcn.com/jishuwz/prosj/netnet/200612/55186.html</a> <p>大家可能都有这样的体会，要满足各种各样的客户，并且客户的需求经常变化，程序员就是这样的辛苦命，整天改过来改过去，特别用一些非面向对象的语言写的代码，函数的参数变得越来越长，里面的Case情况慢慢增加，函数变得很大。软件几年之后就变得难于理解和维护，软件的生命周期好像就要终止。如果能够扩展模块的功能，同时又不修改原来已经测试通过的代码，那多好啊！ <p>这完全是可以实现的，关键是抽象。把可能的变化用抽象来隔离它。面向接口编程，而不是面向对象编程，能增强程序的灵活性；如Client类调用Server 类，如果我们希望Client对象使用另外一个不通的Server对象，就必须修改Client中使用Server类的地方；如果Client调用 Server的接口就可以避免这种修改，只要生成新的接口实现类，修改Main等初次使用新子类的地方而不需要修改Client类；使用Strategy 模式和Template Method模式是满足OCP的最常用方法。 <p>如果需要适应某种变化就需要对这种变化进行抽象，会增加程序的复杂度。所以设计人员应该熟悉业务和了解客户需求，预测到需要进行抽象的变化。 <p>敏捷建模不建议提前进行各种假想的变化抽象，而是当变化发生第一次的时候抽象这种变化，以后同样的变化就变得很容易。对代码进行重构以保持良好的结构是很重要的，每次抽象都不应该使软件变得越来越僵化。这是非面向对象的语言不具备的优势。 <p>&nbsp; <p>参考：<a href="http://blog.csdn.net/kxy/">Health King的专栏</a> <h4><a title="http://blog.csdn.net/kxy/archive/2005/06/27/405013.aspx" href="http://blog.csdn.net/kxy/archive/2005/06/27/405013.aspx">http://blog.csdn.net/kxy/archive/2005/06/27/405013.aspx</a></h4> <p>满足OCP的设计给系统带来两个无可比拟的优越性. <ul> <li>通过扩展已有的软件系统,可以提供新的行为,以满足对软件的新需求,使变化中的软件系统有一定的适应性和灵活性.  <li><strong>已有的软件模块,特别是最重要的抽象层模块不能再修改,这就使变化中的软件系统有一定的稳定性和延续性.</strong></li></ul> <p>具有这两个优点的软件系统是一个高层次上实现了复用的系统,也是一个易于维护的系统.那么,我们如何才能做到这个原则呢?不能修改而可以扩展,这个看起来是自相矛盾的.其实这个是可以做到的,按面向对象的说法,<strong>这个就是不允许更改系统的抽象层,而允许扩展的是系统的实现层.</strong></p> <p>解决问题的关键在:抽象化.我们让模块依赖于一个固定的抽象体,这样它就是不可以修改的;同时,通过这个抽象体派生,我们就可以扩展此模块的行为功能.如此,这样设计的程序只通过增加代码来变化而不是通过更改现有代码来变化,前面提到的修改的副作用就没有了. <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "开-闭"原则如果从另外一个角度讲述,就是所谓的"对可变性封装原则"(Principle of Encapsulation of Variation, EVP).讲的是找到一个系统的可变因素,将之封装起来.在我们考虑一个系统的时候,我们不要把关注的焦点放在什么会导致设计发生变化上,而是考虑允许什么发生变化而不让这一变化导致重新设计.<strong>也就是说,我们要积极的面对变化,积极的包容变化,而不是逃避.</strong> <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [SHALL01]将这一思想用一句话总结为:"找到一个系统的可变因素,将它封装起来",并将它命名为"对可变性的封装原则". <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "对可变性的封装原则"意味者两点: <ol> <li>一种可变性应当被封装到一个对象里面,而不应当散落到代码的很多角落里面.同一种可变性的不同表象意味着同一个继承等级结构中的具体子类.继承应当被看做是封装变化的方法,而不应当是被认为从一般的对象生成特殊的对象的方法(继承经常被滥用).  <li>一种可变性不应当与另外一种可变性混合在一起.从具体的类图来看,如果继承结构超过了两层,那么就意味着将两种不同的可变性混合在了一起.</li></ol> <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "对可变性的封装原则"从工程的角度说明了如何实现OCP.如果按照这个原则来设计,那么系统就应当是遵守OCP的. <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 但是现实往往是残酷的,我们不可能100%的遵守OCP,但是我们要向这个目标来靠近.设计者要对设计的模块对何种变化封闭做出选择.</p><img src ="http://www.blogjava.net/cherishchen/aggbug/140805.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cherishchen/" target="_blank">凭栏观海</a> 2007-08-29 10:38 <a href="http://www.blogjava.net/cherishchen/archive/2007/08/29/140805.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>软件需求调研中的5W+1H定律(转)</title><link>http://www.blogjava.net/cherishchen/archive/2007/08/13/136279.html</link><dc:creator>凭栏观海</dc:creator><author>凭栏观海</author><pubDate>Mon, 13 Aug 2007 01:39:00 GMT</pubDate><guid>http://www.blogjava.net/cherishchen/archive/2007/08/13/136279.html</guid><wfw:comment>http://www.blogjava.net/cherishchen/comments/136279.html</wfw:comment><comments>http://www.blogjava.net/cherishchen/archive/2007/08/13/136279.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cherishchen/comments/commentRss/136279.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cherishchen/services/trackbacks/136279.html</trackback:ping><description><![CDATA[<p>程序员在关注技术之外，应该同样关注需求</p> <p>2005.06.28&nbsp;&nbsp;来自：<a title="http://mypm.net/" href="http://mypm.net/">http://mypm.net/</a>&nbsp;&nbsp; 作者：<a href="http://www.csai.cn/incSearch/search_author.asp?in=%CE%E2%B3%AC">吴超</a></p> <p>&nbsp;</p> <p>&nbsp;&nbsp; 对于软件的需求调研活动，虽然曾经写过三篇相关的需求管理文章，出发角度是从整体的需求管理过程考虑；在引入CMM（二）需求管理KPA活动的基础上，列举了如何进行需求调研前的需求管理计划活动；在失败的项目中，找出规范和管理软件需求过程的关健点及需求关联的模型架构（这些可以参考以前写过的《CMM 需求管理实践经验记录谈》、《从CMM角度考虑需求管理计划》、《如何用CRC模型来确定需求》）。一直以来，感觉自己在经过几个项目试验的基础上对于软件的需求管理应该是有一定的基础和经验了，然而在最近参与的一个大型项目过程中，在新加坡项目经理的引导与帮助下，对于软件需求调研又有了更深一层的体会和认识；总结出需求调研中的5W+1H定律，在此把自己的一些过程和经验描述出来，希望能与各同仁一起分享与讨论。 <p><strong>项目背景：<br></strong>一个中型的企业信息化项目，其中乙方的项目经理是一个拥有PMP证书的资深项目管理人员。甲方的项目经理是一个有着丰富项目实施和管理经验的新加坡项目管理人员。（在这里需要补充的时，在调研产生冲突过程中，外籍人员如何用自己的经验和技巧，让乙方完全可以接收） <p><strong>项目成员：</strong><br>甲方：外包项目经理、外包项目管理人员<br>乙方：项目经理、系统分析员、界面制作人员 <p><strong>工作内容：</strong><br>项目需求阶段的活动，对于系统的需求，甲乙双方与最终用户能达成一致，甲方作为外包管理者，主要是对乙方项目组的项目进度、项目阶段成果进行跟踪与验收，以保证项目在预期的时间内完成预期的工作任务。 <p><strong>过程描述：<br></strong>项目启动后，乙方的项目经理列了一份详细的需求调研时间表、调研阶段成果目录清单、界面成果等的计划内容，可以用一个 “赞” 字来形容；从计划上看，乙方的项目经理计划真的是完美无缺；在与用户进行业务需求调研的活动中，乙方不仅记录下目前用户现有的业务流程，包括目前流程的局限性，流程的执行性等方面，还为用户进行了将来系统流程的规划，的确是一个不错的开始。可是在乙方提交其阶段的需求分析文档和界面时，却发现二者存在了种种的冲突和矛盾，我们无法将需求分析文档与界面结合在一起。此时，乙方的项目经理解释是因为文档比界面细，所以二者存在一些理解上的差异。而我们甲方却总觉得有些不太对劲，但因为同样存在着对用户流程细节的不熟悉，所以我们也提不出具体的问题，直到有一天，跟着乙方一起做用户的需求活动后，从乙方项目经理的提问方面，我们终于明白为什么他们会做出这样的文档和界面。 <p>首先，乙方项目经理对用户的提问是没有序列的，我们所谓的序列就是项目经理的逻辑是否清晰，除了问及目前的流程外，最重要的引入项目（即新的软件系统）的目的，所需达到的效果，可以对用户辅助的东东，而这些乙方的项目经理一字未提与问，只记录用户所说的过程、局限和要求。这样，乙方项目经理在分析与规划系统的需求时，就没有一个明确的目的性和方向性，这里就要引入第一个W定律---WHY定律。<strong>WHY就是为什么用户要引入系统，引入新的信息系统对用户有什么帮助，在总体工作效能上如何实现一个最终的结果？WHY定律是要求在需求开始时，项目经理就应该明确的，这个项目是为了改进用户工作效率；提高部门间的协作机制；加快对客户反应的体系服务；提升企业的竞争力等等。</strong>有了这么一个WHY引入思想，<strong>项目经理就可以理清用户最终要的是可以提供给他们什么样的系统，在系统的定位和建立上，就有一个明确地最终目标。</strong> <p>其次，有了一个总体的目标性，从各业务流程的要求入手，引入第二个W定律---WHAT定律，WHAT则是这个系统要做什么？实现什么？就是乙方项目经理提出的各业务流程问题、流程局限性问题、系统要解决的问题等，在这个WHAT的基础上，把系统划分成各功能模块，逐步弄清模块流程需求、功能需求、结构需求。引入WHAT定律可以让我们了解到系统的初步需求。 <p>再次，引入第三、四、五个定律---WHO、WHEN、WHERE定律，这个阶段其实就是需求细化阶段，在WHAT定律的基础上，细分系统的用户需求：分析什么人，在什么时间，什么阶段可以或必须操作这个功能，结合前面的WHAT定律，理清系统的流程阶段划分，记录并分析系统功能实现的细节，在这个阶段就可以产生系统需求的用例图（Use Case），作为下阶段设计的依据。 <p>最后，就是所谓的1H定律---HOW定律，就是怎样实现系统了，在前面的WHY、WHAT、WHO、WHEN、WHERE基础上，我们已经搭建了一个非常好的系统需求基础框架，如何在这些用户需求的基础上，分析系统的需求，如何进行需求规格的分析与下阶段的设计、实现工作，就是HOW TO ACCOMPLISH THE SYSTEM了。 <p>在需求阶段引入这5W+1H的定律，在一定程度上保证了系统需求的准确性，也使得项目经理或需求分析人员可以非常有序的有条理的开展需求挖掘和调研活动，这样的安排用户在配合上也非常清晰，知道如何与项目人员配合。其后，在我们的建议下，乙方改进了工作方式，理清了一些工作序列，不过在最终文档的提交上，乙方的项目经理为了迎合我们的需求，一直对需求文档的格式与内容进行修改，没有保持需求分析中应该有的从粗到细的阶层分析，也导致其需求分析中的不确定性因素较多，后期的设计工作展开不顺，这些算后话，希望能在以后的外包管理方面，就存在的这些问题进行其它的分析和讨论。</p><img src ="http://www.blogjava.net/cherishchen/aggbug/136279.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cherishchen/" target="_blank">凭栏观海</a> 2007-08-13 09:39 <a href="http://www.blogjava.net/cherishchen/archive/2007/08/13/136279.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>ORACLE的隔离级别（收藏）</title><link>http://www.blogjava.net/cherishchen/archive/2007/07/26/132502.html</link><dc:creator>凭栏观海</dc:creator><author>凭栏观海</author><pubDate>Thu, 26 Jul 2007 04:55:00 GMT</pubDate><guid>http://www.blogjava.net/cherishchen/archive/2007/07/26/132502.html</guid><wfw:comment>http://www.blogjava.net/cherishchen/comments/132502.html</wfw:comment><comments>http://www.blogjava.net/cherishchen/archive/2007/07/26/132502.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cherishchen/comments/commentRss/132502.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cherishchen/services/trackbacks/132502.html</trackback:ping><description><![CDATA[<p>作者: Parrotao  <p><a href="http://epub.itpub.net/3/4.htm" target="_bank">orace的隔离级别</a>  <p><b></b><b>隔离级别（isoation eve）</b>  <p><b></b>隔离级别定义了事务与事务之间的隔离程度。  <p>隔离级别与并发性是互为矛盾的：隔离程度越高，数据库的并发性越差；隔离程度越低，数据库的并发性越好。  <p>ANSI/ISO SQ92标准定义了一些数据库操作的隔离级别：  <ul> <li>未提交读（read uncommitted）  <li>提交读（read committed）&nbsp;&nbsp;  <li>重复读（repeatabe read）&nbsp;&nbsp;  <li>序列化（seriaizabe） </li></ul> <p>通过一些现象，可以反映出隔离级别的效果。这些现象有：  <ul> <li>更新丢失（ost update）：当系统允许两个事务同时更新同一数据是，发生更新丢失。&nbsp;&nbsp;  <li>脏读（dirty read）：当一个事务读取另一个事务尚未提交的修改时，产生脏读。  <li>非重复读（nonrepeatabe read）：同一查询在同一事务中多次进行，由于其他提交事务所做的修改或删除，每次返回不同的结果集，此时发生非重复读。(A transaction rereads data it has previousy read and finds that another committed transaction has modified or deeted the data.&nbsp; )  <li>幻像（phantom read）：同一查询在同一事务中多次进行，由于其他提交事务所做的插入操作，每次返回不同的结果集，此时发生幻像读。(A transaction reexecutes a query returning a set of rows that satisfies a search condition and finds that another committed transaction has inserted additiona rows that satisfy the condition.&nbsp; ) </li></ul> <p>下面是隔离级别及其对应的可能出现或不可能出现的现象 </p> <table cellspacing="0" cellpadding="2" width="579" border="0" unselectable="on"> <tbody> <tr> <td valign="top" width="150">&nbsp;</td> <td valign="top" width="104">Dirty Read</td> <td valign="top" width="138">NonRepeatabe Read&nbsp;</td> <td valign="top" width="182">Phantom Read&nbsp;</td></tr> <tr> <td valign="top" width="150">Read uncommitted </td> <td valign="top" width="104">Possible</td> <td valign="top" width="138">Possible</td> <td valign="top" width="179">Possible</td></tr> <tr> <td valign="top" width="150">Read committed </td> <td valign="top" width="104">not possible</td> <td valign="top" width="138">Possible</td> <td valign="top" width="178">Possible</td></tr> <tr> <td valign="top" width="150">Repeatabe read </td> <td valign="top" width="104">not possible</td> <td valign="top" width="138">not possible</td> <td valign="top" width="176">Possible</td></tr> <tr> <td valign="top" width="149">Seriaizabe </td> <td valign="top" width="104">not possible</td> <td valign="top" width="138">not possible</td> <td valign="top" width="176">not possible</td></tr></tbody></table> <p>&nbsp;  <p><b>ORACE</b><b>的隔离级别</b>  <p>ORACE提供了SQ92标准中的read committed和seriaizabe，同时提供了非SQ92标准的read-ony。  <p><strong>read committed：</strong>  <ul> <li><font color="#ff0000">这是ORACE缺省的事务隔离级别。</font>  <li>事务中的每一条语句都遵从语句级的读一致性。  <li>保证不会脏读；但可能出现非重复读和幻像。 </li></ul> <p><strong>seriaizabe：（</strong>串行执行事务，并发性最小）  <ul> <li><font color="#ff0000">简单地说，seriaizabe就是使事务看起来象是一个接着一个地顺序地执行。</font>  <li>仅仅能看见在本事务开始前由其它事务提交的更改和在本事务中所做的更改。  <li>保证不会出现非重复读和幻像。  <li>Seriaizabe隔离级别提供了read-ony事务所提供的读一致性（事务级的读一致性），同时又允许DM操作。 </li></ul> <p>如果有在seriaizabe事务开始时未提交的事务在seriaizabe事务结束之前修改了seriaizabe事务将要修改的行并进行了提交，则seriaizabe事务不会读到这些变更，因此发生无法序列化访问的错误。（换一种解释方法：<strong>只要在seriaizabe事务开始到结束之间有其他事务对seriaizabe事务要修改的东西进行了修改并提交了修改，则发生无法序列化访问的错误</strong>。）  <p>If a serializable transaction contains data manipulation language (DML) that attempts to update any resource that may have been updated in a transaction uncommitted at the start of the serializable transaction, （并且修改在后来被提交而没有回滚），then the DML statement fails. 返回的错误是ORA-08177: Cannot serialize access for this transaction。  <p>ORACE在数据块中记录最近对数据行执行修改操作的N个事务的信息，目的是确定本事务开始时，是否存在未提交的事务修改了本事务将要修改的行。具体见英文：  <p>Oracle permits a serializable transaction to modify a data row only if it can determine that prior changes to the row were made by transactions that had committed when the serializable transaction began.  <p>To make this determination efficiently, Oracle uses control information stored in the data block that indicates which rows in the block contain committed and uncommitted changes. In a sense, the block contains a recent history of transactions that affected each row in the block. The amount of history that is retained is controlled by the INITRANS parameter of CREATE TABLE and ALTER TABLE. Under some circumstances, Oracle may have insufficient history information to determine whether a row has been updated by a "too recent" transaction. This can occur when many transactions concurrently modify the same data block, or do so in a very short period. You can avoid this situation by setting higher values of INITRANS for tables that will experience many transactions updating the same blocks. Doing so will enable Oracle to allocate sufficient storage in each block to record the history of recent transactions that accessed the block.  <p>The INITRANS Parameter：Oracle stores control information in each data block to manage access by concurrent transactions. Therefore, if you set the transaction isolation level to serializable, you must use the ALTER TABLE command to set INITRANS to at least 3. This parameter will cause Oracle to allocate sufficient storage in each block to record the history of recent transactions that accessed the block. Higher values should be used for tables that will undergo many transactions updating the same blocks.  <p><strong>read-ony：</strong>  <ul> <li>遵从事务级的读一致性，仅仅能看见在本事务开始前由其它事务提交的更改。  <li>不允许在本事务中进行DM操作。  <li>read ony是seriaizabe的子集。它们都避免了非重复读和幻像。区别是在read ony中是只读；而在seriaizabe中可以进行DM操作。  <li>Export with CONSISTENT = Y sets the transaction to read-ony. </li></ul> <p>&nbsp;</p> <p><strong>read committed和seriaizabe的区别和联系：</strong>  <p>事务1先于事务2开始，并保持未提交状态。事务2想要修改正被事务1修改的行。事务2等待。如果事务1回滚，则事务2（不论是read committed还是seriaizabe方式）进行它想要做的修改。如果事务1提交，则当事务2是read committed方式时，进行它想要做的修改；当事务2是seriaizabe方式时，失败并报错“Cannot seriaize access”，因为事务2看不见事务1提交的修改，且事务2想在事务1修改的基础上再做修改。  <p>即seriaizabe不允许存在事务嵌套  <p>具体见英文：  <p>Both read committed and serializable transactions use row-level locking, and both will wait if they try to change a row updated by an uncommitted concurrent transaction. The second transaction that tries to update a given row waits for the other transaction to commit or roll back and release its lock. If that other transaction rolls back, the waiting transaction (regardless of its isolation mode) can proceed to change the previously locked row, as if the other transaction had not existed. However, if the other (blocking) transaction commits and releases its locks, a read committed transaction proceeds with its intended update. A serializable transaction, however, fails with the error "Cannot serialize access", <strong>because the other transaction has committed a change that was made since the serializable transaction began</strong>.  <p>read committed和seriaizabe可以在ORACE并行服务器中使用。  <p>关于SET TRANSACTION READ WRITE：read write和read committed 应该是一样的。在读方面，它们都避免了脏读，但都无法实现重复读。虽然没有文档说明read write在写方面与read committed一致，但显然它在写的时候会加排他锁以避免更新丢失。在加锁的过程中，如果遇到待锁定资源无法锁定，应该是等待而不是放弃。这与read committed一致。  <p>语句级的读一致性  <ul> <li>ORACE保证语句级的读一致性，即一个语句所处理的数据集是在单一时间点上的数据集，这个时间点是这个语句开始的时间。  <li>一个语句看不见在它开始执行后提交的修改。  <li>对于DM语句，它看不见由自己所做的修改，即DM语句看见的是它本身开始执行以前存在的数据。 </li></ul> <p>事务级的读一致性  <ul> <li>事务级的读一致性保证了可重复读，并保证不会出现幻像。 </li></ul> <p><strong>设置隔离级别</strong>  <p>设置一个事务的隔离级别  <ul> <li>SET TRANSACTION ISOLATION LEVEL READ COMMITTED;  <li>SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;  <li>SET TRANSACTION READ ONLY;</li></ul> <p>设置增个会话的隔离级别  <ul> <li>ATER SESSION SET ISOLATION_LEVE SERIALIZABLE;  <li>ATER SESSION SET ISOLATION_LEVE READ COMMITTED; </li></ul> <p>&nbsp;</p> <h2>Choice of Isolation Level</h2> <p>Application designers and developers should choose an isolation level based on application performance and consistency needs as well as application coding requirements.  <p><a></a> <p>For environments with many concurrent users rapidly submitting transactions, designers must assess transaction performance requirements in terms of the expected transaction arrival rate and response time demands. Frequently, for high-performance environments, the choice of isolation levels involves a trade-off between consistency and concurrency.  <h3>Read Committed Isolation</h3> <p><strong>For many applications, read committed is the most appropriate isolation level.</strong> Read committed isolation can provide considerably more concurrency with a somewhat increased risk of inconsistent results due to phantoms and non-repeatable reads for some transactions.  <p><a></a> <p><strong>Many high-performance environments with high transaction arrival rates require more throughput and faster response times than can be achieved with serializable isolation.</strong> Other environments that supports users with a very low transaction arrival rate also face very low risk of incorrect results due to phantoms and nonrepeatable reads. Read committed isolation is suitable for both of these environments.  <p>两种情况：（1）在事务量大、高性能的计算环境，需要更高的吞吐量和响应时间；（2）事务数少，并且发生幻影和不可重复读的几率的比较低  <p><a></a> <p>Oracle read committed isolation provides transaction set consistency for every query. That is, every query sees data in a consistent state. Therefore, read committed isolation will suffice for many applications that might require a higher degree of isolation if run on other database management systems that do not use multiversion concurrency control.  <p><a></a> <p>Read committed isolation mode does not require application logic to trap the "Cannot serialize access" error and loop back to restart a transaction. In most applications, few transactions have a functional need to issue the same query twice, so for many applications protection against phantoms and non-repeatable reads is not important. Therefore many developers choose read committed to avoid the need to write such error checking and retry code in each transaction.  <h3>Serializable Isolation</h3><a></a> <p>Oracle's serializable isolation is suitable for environments where there is a relatively low chance that two concurrent transactions will modify the same rows and the long-running transactions are primarily read-only. It is most suitable for environments with large databases and short transactions that update only a few rows.  <p>（1）适合于很少存在两个事务同时修改同一条记录的情况  <p>（2）长事务以只读为主  <p>（3）大型数据库并且每个短事务只修改很少的记录  <p><a></a> <p>Serializable isolation mode provides somewhat more consistency by protecting against phantoms and nonrepeatable reads and can be important where a read/write transaction executes a query more than once.  <p><a></a> <p>Unlike other implementations of serializable isolation, which lock blocks for read as&nbsp;well as write, Oracle provides nonblocking queries and the fine granularity of row-level locking, both of which reduce write/write contention. For applications that experience mostly read/write contention, Oracle serializable isolation can provide significantly more throughput than other systems. Therefore, some applications might be suitable for serializable isolation on Oracle but not on other&nbsp;systems.  <p><a></a> <p>All queries in an Oracle serializable transaction see the database as of a single point in time, so this isolation level is suitable where multiple consistent queries must be issued in a read/write transaction. A report-writing application that generates summary data and stores it in the database might use serializable mode because it provides the consistency that a <code>READ</code> <code>ONLY</code> transaction provides, but also allows <code>INSERT</code>, <code>UPDATE</code>, and <code>DELETE</code>.  <p>&nbsp;  <p>作者联系方式:  <p>E&nbsp;&nbsp;&nbsp;&nbsp; TaoPuyin<br>G&nbsp;&nbsp; System Engineer Oracle DBA<br>G&nbsp; Fuji Xerox China Limited (Shanghai)</p><img src ="http://www.blogjava.net/cherishchen/aggbug/132502.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cherishchen/" target="_blank">凭栏观海</a> 2007-07-26 12:55 <a href="http://www.blogjava.net/cherishchen/archive/2007/07/26/132502.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Hibernate的缓存机制介绍(转)</title><link>http://www.blogjava.net/cherishchen/archive/2007/07/22/131736.html</link><dc:creator>凭栏观海</dc:creator><author>凭栏观海</author><pubDate>Sun, 22 Jul 2007 08:54:00 GMT</pubDate><guid>http://www.blogjava.net/cherishchen/archive/2007/07/22/131736.html</guid><wfw:comment>http://www.blogjava.net/cherishchen/comments/131736.html</wfw:comment><comments>http://www.blogjava.net/cherishchen/archive/2007/07/22/131736.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cherishchen/comments/commentRss/131736.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cherishchen/services/trackbacks/131736.html</trackback:ping><description><![CDATA[<p><strong>作者 benja</strong>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>原文位于 </strong><a href="http://gocom.primeton.com/blog/index.php?op=ViewArticle&amp;articleId=467&amp;blogId=37&amp;src=jdon&amp;srcforum=62#" target="_blank"><strong>Hibernate的缓存机制介绍</strong></a><strong>&nbsp; </strong></p> <p>　缓存是介于应用程序和物理数据源之间，其作用是为了降低应用程序对物理数据源访问的频次，从而提高了应用的运行性能。缓存内的数据是对物理数据源中的数据的复制，应用程序在运行时从缓存读写数据，在特定的时刻或事件会同步缓存和物理数据源的数据。 <p>缓存的介质一般是内存，所以读写速度很快。但如果缓存中存放的数据量非常大时，也会用硬盘作为缓存介质。缓存的实现不仅仅要考虑存储的介质，还要考虑到管理缓存的并发访问和缓存数据的生命周期。 <p>Hibernate的缓存包括Session的缓存和SessionFactory的缓存，其中SessionFactory的缓存又可以分为两类：内置缓存和外置缓存。 <p><strong>Session的缓存是内置的，不能被卸载，也被称为Hibernate的第一级缓存。</strong> <p>SessionFactory的内置缓存和Session的缓存在实现方式上比较相似，前者是SessionFactory对象的一些集合属性包含的数据，后者是指Session的一些集合属性包含的数据。SessionFactory的内置缓存中存放了映射元数据和预定义SQL语句，映射元数据是映射文件中数据的拷贝，而预定义SQL语句是在Hibernate初始化阶段根据映射元数据推导出来，SessionFactory的内置缓存是只读的，应用程序不能修改缓存中的映射元数据和预定义SQL语句，因此SessionFactory不需要进行内置缓存与映射文件的同步。 <p>SessionFactory的外置缓存是一个可配置的插件。在默认情况下，SessionFactory不会启用这个插件。外置缓存的数据是数据库数据的拷贝，外置缓存的介质可以是内存或者硬盘。<strong>SessionFactory的外置缓存也被称为Hibernate的第二级缓存。</strong> <p>Hibernate的这两级缓存都位于持久化层，存放的都是数据库数据的拷贝，那么它们之间的区别是什么呢？为了理解二者的区别，需要深入理解持久化层的缓存的两个特性：<strong>缓存的范围和缓存的并发访问策略。</strong> <h3><b>持久化层的缓存的范围</b></h3> <p>缓存的范围决定了缓存的生命周期以及可以被谁访问。缓存的范围分为三类。 <p>1 事务范围：缓存只能被当前事务访问。缓存的生命周期依赖于事务的生命周期，当事务结束时，缓存也就结束生命周期。在此范围下，缓存的介质是内存。事务可以是数据库事务或者应用事务，每个事务都有独自的缓存，缓存内的数据通常采用相互关联的的对象形式。 <p>2 进程范围：缓存被进程内的所有事务共享。这些事务有可能是并发访问缓存，因此必须对缓存采取必要的事务隔离机制。缓存的生命周期依赖于进程的生命周期，进程结束时，缓存也就结束了生命周期。进程范围的缓存可能会存放大量的数据，所以存放的介质可以是内存或硬盘。缓存内的数据既可以是相互关联的对象形式也可以是对象的松散数据形式。松散的对象数据形式有点类似于对象的序列化数据，但是对象分解为松散的算法比对象序列化的算法要求更快。 <p>3 集群范围：在集群环境中，缓存被一个机器或者多个机器的进程共享。缓存中的数据被复制到集群环境中的每个进程节点，进程间通过远程通信来保证缓存中的数据的一致性，缓存中的数据通常采用对象的松散数据形式。 <p>对大多数应用来说，应该慎重地考虑是否需要使用集群范围的缓存，因为访问的速度不一定会比直接访问数据库数据的速度快多少。 <p>持久化层可以提供多种范围的缓存。如果在事务范围的缓存中没有查到相应的数据，还可以到进程范围或集群范围的缓存内查询，如果还是没有查到，那么只有到数据库中查询。事务范围的缓存是持久化层的第一级缓存，通常它是必需的；进程范围或集群范围的缓存是持久化层的第二级缓存，通常是可选的。 <h3><b>持久化层的缓存的并发访问策略</b></h3> <p>当多个并发的事务同时访问持久化层的缓存的相同数据时，会引起并发问题，必须采用必要的事务隔离措施。 <p>在进程范围或集群范围的缓存，即第二级缓存，会出现并发问题。因此可以设定以下四种类型的并发访问策略，每一种策略对应一种事务隔离级别。 <p>事务型：仅仅在受管理环境中适用。它提供了Repeatable Read事务隔离级别。对于经常被读但很少修改的数据，可以采用这种隔离类型，因为它可以防止脏读和不可重复读这类的并发问题。 <p>读写型：提供了Read Committed事务隔离级别。仅仅在非集群的环境中适用。对于经常被读但很少修改的数据，可以采用这种隔离类型，因为它可以防止脏读这类的并发问题。 <p>非严格读写型：不保证缓存与数据库中数据的一致性。如果存在两个事务同时访问缓存中相同数据的可能，必须为该数据配置一个很短的数据过期时间，从而尽量避免脏读。对于极少被修改，并且允许偶尔脏读的数据，可以采用这种并发访问策略。 <p>只读型：对于从来不会修改的数据，如参考数据，可以使用这种并发访问策略。 <p>事务型并发访问策略是事务隔离级别最高，只读型的隔离级别最低。事务隔离级别越高，并发性能就越低。 <p><b>什么样的数据适合存放到第二级缓存中？</b> <p><b>1 </b>很少被修改的数据 <p><strong>2 </strong>不是很重要的数据，允许出现偶尔并发的数据 <p><strong>3</strong> 不会被并发访问的数据 <p><strong>4</strong> 参考数据 <p><b>不适合存放到第二级缓存的数据？</b> <p><b>1 </b>经常被修改的数据 <p><strong>2 </strong>财务数据，绝对不允许出现并发 <p><strong>3</strong> 与其他应用共享的数据。 <h3><b>Hibernate</b><b>的二级缓存</b></h3> <p>如前所述，Hibernate提供了两级缓存，第一级是Session的缓存。由于Session对象的生命周期通常对应一个数据库事务或者一个应用事务，因此它的缓存是事务范围的缓存。第一级缓存是必需的，不允许而且事实上也无法卸除。在第一级缓存中，持久化类的每个实例都具有唯一的OID。 <p>第二级缓存是一个可插拔的的缓存插件，它是由SessionFactory负责管理。由于SessionFactory对象的生命周期和应用程序的整个过程对应，因此第二级缓存是进程范围或者集群范围的缓存。这个缓存中存放的对象的松散数据。第二级对象有可能出现并发问题，因此需要采用适当的并发访问策略，该策略为被缓存的数据提供了事务隔离级别。缓存适配器用于把具体的缓存实现软件与Hibernate集成。第二级缓存是可选的，可以在每个类或每个集合的粒度上配置第二级缓存。 <p><b>Hibernate</b><b>的二级缓存策略的一般过程如下：</b> <p><b></b>1) 条件查询的时候，总是发出一条select * from table_name where …. （选择所有字段）这样的SQL语句查询数据库，一次获得所有的数据对象。 <p>2) 把获得的所有数据对象根据ID放入到第二级缓存中。 <p>3) 当Hibernate根据ID访问数据对象的时候，首先从Session一级缓存中查；查不到，如果配置了二级缓存，那么从二级缓存中查；查不到，再查询数据库，把结果按照ID放入到缓存。 <p>4) 删除、更新、增加数据的时候，同时更新缓存。 <p>Hibernate的二级缓存策略，是针对于ID查询的缓存策略，对于条件查询则毫无作用。为此，Hibernate提供了针对条件查询的Query缓存。 <p><strong>Hibernate</strong><strong>的Query</strong><strong>缓存策略的过程如下：</strong> <p><strong></strong>1) Hibernate首先根据这些信息组成一个Query Key，Query Key包括条件查询的请求一般信息：SQL, SQL需要的参数，记录范围（起始位置rowStart，最大记录个数maxRows)，等。 <p>2) Hibernate根据这个Query Key到Query缓存中查找对应的结果列表。如果存在，那么返回这个结果列表；如果不存在，查询数据库，获取结果列表，把整个结果列表根据Query Key放入到Query缓存中。 <p><strong>3) </strong>Query Key中的SQL涉及到一些表名，如果这些表的任何数据发生修改、删除、增加等操作，这些相关的Query Key都要从缓存中清空。 <img src ="http://www.blogjava.net/cherishchen/aggbug/131736.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cherishchen/" target="_blank">凭栏观海</a> 2007-07-22 16:54 <a href="http://www.blogjava.net/cherishchen/archive/2007/07/22/131736.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>基于J2EE的Blog平台(转)</title><link>http://www.blogjava.net/cherishchen/archive/2007/07/16/130475.html</link><dc:creator>凭栏观海</dc:creator><author>凭栏观海</author><pubDate>Mon, 16 Jul 2007 01:22:00 GMT</pubDate><guid>http://www.blogjava.net/cherishchen/archive/2007/07/16/130475.html</guid><wfw:comment>http://www.blogjava.net/cherishchen/comments/130475.html</wfw:comment><comments>http://www.blogjava.net/cherishchen/archive/2007/07/16/130475.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cherishchen/comments/commentRss/130475.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cherishchen/services/trackbacks/130475.html</trackback:ping><description><![CDATA[<p><a title="http://www.javaeedev.com/blog/article.jspx?articleId=ff80808112e766ee01130e1db01c0018" href="http://www.javaeedev.com/blog/article.jspx?articleId=ff80808112e766ee01130e1db01c0018">http://www.javaeedev.com/blog/article.jspx?articleId=ff80808112e766ee01130e1db01c0018</a></p> <p>很清楚，还有源码下载</p><img src ="http://www.blogjava.net/cherishchen/aggbug/130475.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cherishchen/" target="_blank">凭栏观海</a> 2007-07-16 09:22 <a href="http://www.blogjava.net/cherishchen/archive/2007/07/16/130475.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>一篇介绍css的文章</title><link>http://www.blogjava.net/cherishchen/archive/2007/07/11/129626.html</link><dc:creator>凭栏观海</dc:creator><author>凭栏观海</author><pubDate>Wed, 11 Jul 2007 07:07:00 GMT</pubDate><guid>http://www.blogjava.net/cherishchen/archive/2007/07/11/129626.html</guid><wfw:comment>http://www.blogjava.net/cherishchen/comments/129626.html</wfw:comment><comments>http://www.blogjava.net/cherishchen/archive/2007/07/11/129626.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cherishchen/comments/commentRss/129626.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cherishchen/services/trackbacks/129626.html</trackback:ping><description><![CDATA[http://www.cnblogs.com/cathsfz/archive/2007/04/09/706336.html<br><br><img src ="http://www.blogjava.net/cherishchen/aggbug/129626.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cherishchen/" target="_blank">凭栏观海</a> 2007-07-11 15:07 <a href="http://www.blogjava.net/cherishchen/archive/2007/07/11/129626.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>用HttpClient来模拟浏览器GET POST(收藏)</title><link>http://www.blogjava.net/cherishchen/archive/2007/07/10/129369.html</link><dc:creator>凭栏观海</dc:creator><author>凭栏观海</author><pubDate>Tue, 10 Jul 2007 08:05:00 GMT</pubDate><guid>http://www.blogjava.net/cherishchen/archive/2007/07/10/129369.html</guid><wfw:comment>http://www.blogjava.net/cherishchen/comments/129369.html</wfw:comment><comments>http://www.blogjava.net/cherishchen/archive/2007/07/10/129369.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.blogjava.net/cherishchen/comments/commentRss/129369.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cherishchen/services/trackbacks/129369.html</trackback:ping><description><![CDATA[<h3><strong>用HttpClient来模拟浏览器GET&nbsp;POST</strong></h3> <p><a title="http://www.xd-tech.com.cn/blog/article.asp?id=34" href="http://www.xd-tech.com.cn/blog/article.asp?id=34">http://www.xd-tech.com.cn/blog/article.asp?id=34</a></p> <h4>作者:jaddy0302 日期:2006-12-21</h4> <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 一般的情况下我们都是使用IE或者Navigator浏览器来访问一个WEB服务器，用来浏览页面查看信息或者提交一些数据等等。所访问的这些页面有的仅仅是一些普通的页面，有的需要用户登录后方可使用，或者需要认证以及是一些通过加密方式传输，例如HTTPS。目前我们使用的浏览器处理这些情况都不会构成问题。不过你可能在某些时候需要通过程序来访问这样的一些页面，比如从别人的网页中“偷”一些数据；利用某些站点提供的页面来完成某种功能，例如说我们想知道某个手机号码的归属地而我们自己又没有这样的数据，因此只好借助其他公司已有的网站来完成这个功能，这个时候我们需要向网页提交手机号码并从返回的页面中解析出我们想要的数据来。如果对方仅仅是一个很简单的页面，那我们的程序会很简单，本文也就没有必要大张旗鼓的在这里浪费口舌。但是考虑到一些服务授权的问题，很多公司提供的页面往往并不是可以通过一个简单的URL就可以访问的，而必须经过注册然后登录后方可使用提供服务的页面，这个时候就涉及到 COOKIE问题的处理。我们知道目前流行的***页技术例如ASP、JSP无不是通过COOKIE来处理会话信息的。为了使我们的程序能使用别人所提供的服务页面，就要求程序首先登录后再访问服务页面，这过程就需要自行处理cookie，想想当你用java.net.HttpURLConnection 来完成这些功能时是多么恐怖的事情啊！况且这仅仅是我们所说的顽固的WEB服务器中的一个很常见的“顽固”！再有如通过HTTP来上传文件呢？不需要头疼，这些问题有了“它”就很容易解决了！  <p>我们不可能列举所有可能的顽固，我们会针对几种最常见的问题进行处理。当然了，正如前面说到的，如果我们自己使用 java.net.HttpURLConnection来搞定这些问题是很恐怖的事情，因此在开始之前我们先要介绍一下一个开放源码的项目，这个项目就是 Apache开源组织中的httpclient，它隶属于Jakarta的commons项目，目前的版本是2.0RC2。commons下本来已经有一个net的子项目，但是又把httpclient单独提出来，可见http服务器的访问绝非易事。 <p>Commons-httpclient项目就是专门设计来简化HTTP客户端与服务器进行各种通讯编程。通过它可以让原来很头疼的事情现在轻松的解决，例如你不再管是HTTP或者HTTPS的通讯方式，告诉它你想使用HTTPS方式，剩下的事情交给 httpclient替你完成。本文会针对我们在编写HTTP客户端程序时经常碰到的几个问题进行分别介绍如何使用httpclient来解决它们，为了让读者更快的熟悉这个项目我们最开始先给出一个简单的例子来读取一个网页的内容，然后循序渐进解决掉前进中的所形侍狻?/font&gt;  <p>1． 读取网页(HTTP/HTTPS)内容 <p>下面是我们给出的一个简单的例子用来访问某个页面 <p>/* <p>&nbsp;* Created on 2003-12-14 by Liudong <p>&nbsp;*/ <p>package http.demo; <p>import java.io.IOException; <p>import org.apache.commons.httpclient.*; <p>import org.apache.commons.httpclient.methods.*; <p>/** <p>&nbsp;* 最简单的HTTP客户端,用来演示通过GET或者POST方式访问某个页面 <p>&nbsp;* @author Liudong <p>&nbsp;*/ <p>public class SimpleClient { <p>&nbsp;&nbsp;&nbsp; public static void main(String[] args) throws IOException <p>&nbsp;&nbsp;&nbsp; { <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HttpClient client = new HttpClient();&nbsp;&nbsp;&nbsp;  <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //设置代理服务器地址和端口&nbsp;&nbsp;&nbsp;&nbsp;  <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //client.getHostConfiguration().setProxy("proxy_host_addr",proxy_port); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //使用GET方法，如果服务器需要通过HTTPS连接，那只需要将下面URL中的http换成https <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HttpMethod method = new GetMethod("<a href="http://java.sun.com/"><a href="http://java.sun.com">http://java.sun.com</a></a>");  <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //使用POST方法 <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //HttpMethod method = new PostMethod("<a href="http://java.sun.com/"><a href="http://java.sun.com">http://java.sun.com</a></a>");  <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; client.executeMethod(method); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //打印服务器返回的状态 <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(method.getStatusLine()); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //打印返回的信息 <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(method.getResponseBodyAsString()); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //释放连接 <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; method.releaseConnection(); <p>&nbsp;&nbsp;&nbsp; }<br>} <p>在这个例子中首先创建一个HTTP客户端(HttpClient)的实例，然后选择提交的方法是GET或者 POST，最后在HttpClient实例上执行提交的方法，最后从所选择的提交方法中读取服务器反馈回来的结果。这就是使用HttpClient的基本流程。其实用一行代码也就可以搞定整个请求的过程，非常的简单！ <p>2． 以GET或者POST方式向网页提交参数 <p>其实前面一个最简单的示例中我们已经介绍了如何使用GET或者POST方式来请求一个页面，本小节与之不同的是多了提交时设定页面所需的参数，我们知道如果是GET的请求方式，那么所有参数都直接放到页面的URL后面用问号与页面地址隔开，每个参数用&amp;隔开，例如：<a href="http://java.sun.com/?name=liudong&amp;mobile=123456">http://java.sun.com?name=liudong&amp;mobile=123456</a>，但是当使用POST方法时就会稍微有一点点麻烦。本小节的例子演示向如何查询手机号码所在的城市，代码如下： <p>/* <p>&nbsp;* Created on 2003-12-7 by Liudong <p>&nbsp;*/ <p>package http.demo; <p>import java.io.IOException; <p>import org.apache.commons.httpclient.*; <p>import org.apache.commons.httpclient.methods.*; <p>/** <p>&nbsp;* 提交参数演示 <p>&nbsp;* 该程序连接到一个用于查询手机号码所属地的页面 <p>&nbsp;* 以便查询号码段1330227所在的省份以及城市 <p>&nbsp;* @author Liudong <p>&nbsp;*/ <p>public class SimpleHttpClient { <p>&nbsp;&nbsp;&nbsp; public static void main(String[] args) throws IOException <p>&nbsp;&nbsp;&nbsp; { <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HttpClient client = new HttpClient(); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; client.getHostConfiguration().setHost("<a href="http://www.imobile.com.cn/">www.imobile.com.cn</a>", 80, "http"); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HttpMethod method = getPostMethod();//使用POST方式提交数据 <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; client.executeMethod(method); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //打印服务器返回的状态 <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(method.getStatusLine()); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //打印结果页面 <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; String response =&nbsp; new String(method.getResponseBodyAsString().getBytes("8859_1")); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //打印返回的信息 <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(response); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; method.releaseConnection(); <p>&nbsp;&nbsp;&nbsp; } <p>&nbsp;&nbsp;&nbsp; /** <p>&nbsp;&nbsp;&nbsp;&nbsp; * 使用GET方式提交数据 <p>&nbsp;&nbsp;&nbsp;&nbsp; * @return <p>&nbsp;&nbsp;&nbsp;&nbsp; */ <p>&nbsp;&nbsp;&nbsp; private static HttpMethod getGetMethod(){ <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return new GetMethod("/simcard.php?simcard=1330227"); <p>&nbsp;&nbsp;&nbsp; } <p>&nbsp;&nbsp;&nbsp; /** <p>&nbsp;&nbsp;&nbsp;&nbsp; * 使用POST方式提交数据 <p>&nbsp;&nbsp;&nbsp;&nbsp; * @return <p>&nbsp;&nbsp;&nbsp;&nbsp; */ <p>&nbsp;&nbsp;&nbsp; private static HttpMethod getPostMethod(){ <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PostMethod post = new PostMethod("/simcard.php"); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NameValuePair simcard = new NameValuePair("simcard","1330227"); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; post.setRequestBody(new NameValuePair[] { simcard}); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return post; <p>&nbsp;&nbsp;&nbsp; } <p>} <p>在上面的例子中页面<a href="http://www.imobile.com.cn/simcard.php">http://www.imobile.com.cn/simcard.php</a>需要一个参数是simcard，这个参数值为手机号码段，即手机号码的前七位，服务器会返回提交的手机号码对应的省份、城市以及其他详细信息。GET的提交方法只需要在URL后加入参数信息，而POST则需要通过NameValuePair类来设置参数名称和它所对应的值 <p>3． 处理页面重定向 <p>在JSP/Servlet编程中response.sendRedirect方法就是使用HTTP协议中的重定向机制。它与JSP中的&lt;jsp:forward …&gt;的区别在于后者是在服务器中实现页面的跳转，也就是说应用容器加载了所要跳转的页面的内容并返回给客户端；而前者是返回一个状态码，这些状态码的可能值见下表，然后客户端读取需要跳转到的页面的URL并重新加载新的页面。就是这样一个过程，所以我们编程的时候就要通过 HttpMethod.getStatusCode()方法判断返回值是否为下表中的某个值来判断是否需要跳转。如果已经确认需要进行页面跳转了，那么可以通过读取HTTP头中的location属性来获取新的地址。 <p>状态码<br>&nbsp;对应HttpServletResponse的常量<br>&nbsp;详细描述<br>301　&nbsp;SC_MOVED_PERMANENTLY<br>&nbsp;页面已经永久移到另外一个新地址<br>302　&nbsp;SC_MOVED_TEMPORARILY<br>&nbsp;页面暂时移动到另外一个新的地址<br>303　&nbsp;SC_SEE_OTHER<br>&nbsp;客户端请求的地址必须通过另外的URL来访问<br>307　&nbsp;SC_TEMPORARY_REDIRECT　&nbsp;同SC_MOVED_TEMPORARILY <p>下面的代码片段演示如何处理页面的重定向 <p>client.executeMethod(post); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(post.getStatusLine().toString());  <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; post.releaseConnection(); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //检查是否重定向 <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int statuscode = post.getStatusCode(); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if ((statuscode == HttpStatus.SC_MOVED_TEMPORARILY) || <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (statuscode == HttpStatus.SC_MOVED_PERMANENTLY) || <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (statuscode == HttpStatus.SC_SEE_OTHER) || <p>(statuscode == HttpStatus.SC_TEMPORARY_REDIRECT)) { <p>//读取新的URL地址 <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Header header = post.getResponseHeader("location"); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (header != null) { <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; String newuri = header.getValue(); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if ((newuri == null) || (newuri.equals(""))) <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; newuri = "/";  <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GetMethod redirect = new GetMethod(newuri); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; client.executeMethod(redirect); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("Redirect:"+ redirect.getStatusLine().toString());  <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; redirect.releaseConnection(); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } else  <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("Invalid redirect"); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <p>我们可以自行编写两个JSP页面，其中一个页面用response.sendRedirect方法重定向到另外一个页面用来测试上面的例子。 <p>4． 模拟输入用户名和口令进行登录 <p>本小节应该说是HTTP客户端编程中最常碰见的问题，很多网站的内容都只是对注册用户可见的，这种情况下就必须要求使用正确的用户名和口令登录成功后，方可浏览到想要的页面。因为HTTP协议是无状态的，也就是连接的有效期只限于当前请求，请求内容结束后连接就关闭了。在这种情况下为了保存用户的登录信息必须使用到Cookie机制。以JSP/Servlet为例，当浏览器请求一个JSP或者是Servlet的页面时，应用服务器会返回一个参数，名为jsessionid（因不同应用服务器而异），值是一个较长的唯一字符串的Cookie，这个字符串值也就是当前访问该站点的会话标识。浏览器在每访问该站点的其他页面时候都要带上jsessionid这样的Cookie信息，应用服务器根据读取这个会话标识来获取对应的会话信息。 <p>对于需要用户登录的网站，一般在用户登录成功后会将用户资料保存在服务器的会话中，这样当访问到其他的页面时候，应用服务器根据浏览器送上的Cookie中读取当前请求对应的会话标识以获得对应的会话信息，然后就可以判断用户资料是否存在于会话信息中，如果存在则允许访问页面，否则跳转到登录页面中要求用户输入帐号和口令进行登录。这就是一般使用JSP开发网站在处理用户登录的比较通用的方法。 <p>这样一来，对于HTTP的客户端来讲，如果要访问一个受保护的页面时就必须模拟浏览器所做的工作，首先就是请求登录页面，然后读取Cookie值；再次请求登录页面并加入登录页所需的每个参数；最后就是请求最终所需的页面。当然在除第一次请求外其他的请求都需要附带上 Cookie信息以便服务器能判断当前请求是否已经通过验证。说了这么多，可是如果你使用httpclient的话，你甚至连一行代码都无需增加，你只需要先传递登录信息执行登录过程，然后直接访问想要的页面，跟访问一个普通的页面没有任何区别，因为类HttpClient已经帮你做了所有该做的事情了，太棒了！下面的例子实现了这样一个访问的过程。 <p>/* <p>&nbsp;* Created on 2003-12-7 by Liudong <p>&nbsp;*/ <p>package http.demo; <p>import org.apache.commons.httpclient.*; <p>import org.apache.commons.httpclient.cookie.*; <p>import org.apache.commons.httpclient.methods.*; <p>/** <p>&nbsp;* 用来演示登录表单的示例 <p>&nbsp;* @author Liudong <p>&nbsp;*/ <p>public class FormLoginDemo { <p>&nbsp;&nbsp;&nbsp; static final String LOGON_SITE = "localhost"; <p>&nbsp;&nbsp;&nbsp; static final int&nbsp;&nbsp;&nbsp; LOGON_PORT = 8080; <p>&nbsp;&nbsp;&nbsp; public static void main(String[] args) throws Exception{ <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HttpClient client = new HttpClient(); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; client.getHostConfiguration().setHost(LOGON_SITE, LOGON_PORT); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //模拟登录页面login.jsp-&gt;main.jsp <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PostMethod post = new PostMethod("/main.jsp"); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NameValuePair name = new NameValuePair("name", "ld");&nbsp;&nbsp;&nbsp;&nbsp;  <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NameValuePair pass = new NameValuePair("password", "ld");&nbsp;&nbsp;&nbsp;&nbsp;  <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; post.setRequestBody(new NameValuePair[]{name,pass}); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int status = client.executeMethod(post); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(post.getResponseBodyAsString()); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; post.releaseConnection();&nbsp;  <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //查看cookie信息 <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CookieSpec cookiespec = CookiePolicy.getDefaultSpec(); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Cookie[] cookies = cookiespec.match(LOGON_SITE, LOGON_PORT, "/", false, client.getState().getCookies()); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (cookies.length == 0) { <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("None");&nbsp;&nbsp;&nbsp;  <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } else { <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for (int i = 0; i &lt; cookies.length; i++) { <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(cookies[i].toString());&nbsp;&nbsp;&nbsp;  <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //访问所需的页面main2.jsp <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GetMethod get = new GetMethod("/main2.jsp"); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; client.executeMethod(get); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(get.getResponseBodyAsString()); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; get.releaseConnection(); <p>&nbsp;&nbsp;&nbsp; } <p>} <p>5． 提交XML格式参数 <p>提交XML格式的参数很简单，仅仅是一个提交时候的ContentType问题，下面的例子演示从文件文件中读取XML信息并提交给服务器的过程，该过程可以用来测试Web服务。 <p>import java.io.File; <p>import java.io.FileInputStream; <p>import org.apache.commons.httpclient.HttpClient; <p>import org.apache.commons.httpclient.methods.EntityEnclosingMethod; <p>import org.apache.commons.httpclient.methods.PostMethod; <p>/** <p>&nbsp;* 用来演示提交XML格式数据的例子 <p>&nbsp;*/ <p>public class PostXMLClient { <p>&nbsp;&nbsp;&nbsp; public static void main(String[] args) throws Exception { <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; File input = new File(“test.xml”); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PostMethod post = new PostMethod(“http://localhost:8080/httpclient/xml.jsp”); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 设置请求的内容直接从文件中读取 <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; post.setRequestBody(new FileInputStream(input)); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (input.length() &lt; Integer.MAX_VALUE)  <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; post.setRequestContentLength(input.length()); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;  <blockquote> <p>post.setRequestContentLength(EntityEnclosingMethod.CONTENT_LENGTH_CHUNKED);</p></blockquote> <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 指定请求内容的类型 <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; post.setRequestHeader("Content-type", "text/xml; charset=GBK"); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HttpClient httpclient = new HttpClient();  <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int result = httpclient.executeMethod(post);  <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("Response status code: " + result); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("Response body: "); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(post.getResponseBodyAsString()); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; post.releaseConnection(); <p>&nbsp;&nbsp;&nbsp; } <p>} <p>6． 通过HTTP上传文件 <p>httpclient使用了单独的一个HttpMethod子类来处理文件的上传，这个类就是MultipartPostMethod，该类已经封装了文件上传的细节，我们要做的仅仅是告诉它我们要上传文件的全路径即可，下面的代码片段演示如何使用这个类。 <p>MultipartPostMethod filePost = new MultipartPostMethod(targetURL); <p>filePost.addParameter("fileName", targetFilePath); <p>HttpClient client = new HttpClient(); <p>//由于要上传的文件可能比较大,因此在此设置最大的连接超时时间 <p>client.getHttpConnectionManager().getParams().setConnectionTimeout(5000); <p>int status = client.executeMethod(filePost); <p>上面代码中，targetFilePath即为要上传的文件所在的路径。 <p>7． 访问启用认证的页面 <p>我们经常会碰到这样的页面，当访问它的时候会弹出一个浏览器的对话框要求输入用户名和密码后方可，这种用户认证的方式不同于我们在前面介绍的基于表单的用户身份验证。这是HTTP的认证策略，httpclient支持三种认证方式包括：基本、摘要以及NTLM认证。其中基本认证最简单、通用但也最不安全；摘要认证是在HTTP 1.1中加入的认证方式，而NTLM则是微软公司定义的而不是通用的规范，最新版本的NTLM是比摘要认证还要安全的一种方式。 <p>下面例子是从httpclient的CVS服务器中下载的，它简单演示如何访问一个认证保护的页面： <p>import org.apache.commons.httpclient.HttpClient; <p>import org.apache.commons.httpclient.UsernamePasswordCredentials; <p>import org.apache.commons.httpclient.methods.GetMethod; <p>public class BasicAuthenticationExample { <p>&nbsp;&nbsp;&nbsp; public BasicAuthenticationExample() { <p>&nbsp;&nbsp;&nbsp; } <p>&nbsp;&nbsp;&nbsp; public static void main(String[] args) throws Exception { <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HttpClient client = new HttpClient(); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; client.getState().setCredentials( <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "<a href="http://www.verisign.com/"><a href="http://www.verisign.com">www.verisign.com</a></a>", <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "realm", <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; new UsernamePasswordCredentials("username", "password") <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GetMethod get = new GetMethod("<a href="https://www.verisign.com/products/index.html">https://www.verisign.com/products/index.html</a>"); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; get.setDoAuthentication( true ); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int status = client.executeMethod( get ); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println(status+""+ get.getResponseBodyAsString()); <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; get.releaseConnection(); <p>&nbsp;&nbsp;&nbsp; } <p>} <p>8． 多线程模式下使用httpclient <p>多线程同时访问httpclient，例如同时从一个站点上下载多个文件。对于同一个HttpConnection 同一个时间只能有一个线程访问，为了保证多线程工作环境下不产生冲突，httpclient使用了一个多线程连接管理器的类： MultiThreadedHttpConnectionManager，要使用这个类很简单，只需要在构造HttpClient实例的时候传入即可，代码如下：<pre>MultiThreadedHttpConnectionManager connectionManager = <span style="color: #0000ff">new</span> MultiThreadedHttpConnectionManager();
HttpClient client = <span style="color: #0000ff">new</span> HttpClient(connectionManager);
</pre>
<p>&nbsp;
<p>&nbsp;
<p>以后尽管访问client实例即可。
<p>参考资料：
<p>httpclient首页：&nbsp;&nbsp;&nbsp; <a href="http://jakarta.apache.org/commons/httpclient/"><a href="http://jakarta.apache.org/commons/httpclient/">http://jakarta.apache.org/commons/httpclient/</a></a><br>关于NTLM是如何工作：&nbsp; <a href="http://davenport.sourceforge.net/ntlm.html"><a href="http://davenport.sourceforge.net/ntlm.html">http://davenport.sourceforge.net/ntlm.html</a></a></p><img src ="http://www.blogjava.net/cherishchen/aggbug/129369.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cherishchen/" target="_blank">凭栏观海</a> 2007-07-10 16:05 <a href="http://www.blogjava.net/cherishchen/archive/2007/07/10/129369.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title> 畅谈工作流应用(转自胡长城（银狐999）BLOG)</title><link>http://www.blogjava.net/cherishchen/archive/2007/06/27/126586.html</link><dc:creator>凭栏观海</dc:creator><author>凭栏观海</author><pubDate>Wed, 27 Jun 2007 06:41:00 GMT</pubDate><guid>http://www.blogjava.net/cherishchen/archive/2007/06/27/126586.html</guid><wfw:comment>http://www.blogjava.net/cherishchen/comments/126586.html</wfw:comment><comments>http://www.blogjava.net/cherishchen/archive/2007/06/27/126586.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cherishchen/comments/commentRss/126586.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cherishchen/services/trackbacks/126586.html</trackback:ping><description><![CDATA[<a href="http://blog.csdn.net/james999/archive/2007/03/17/1532144.aspx">http://blog.csdn.net/james999/archive/2007/03/17/1532144.aspx</a><br><br><br>
<p><strong>1，&nbsp;您好，请先向我们的网友简单做一下自我介绍好吗？</strong><br>&nbsp;我的网名是&#8220;银狐999&#8221;，目前就职于TIBCO中国研发中心，负责tibco administrator server底层组件的维护。<br>&nbsp;我的工作经历即简单又复杂。简单是因为近五年来，我几乎都在围绕着Workflow相关的技术在发展；而复杂则是更换了好几家公司，从事过电子政务领域，数据集成领域、业务基础软件平台、协同办公平台等多方面的工作。<br>&nbsp;我是个很Open的人，这得益于早期javaunion论坛上那帮伙伴，他们的无私奉献让我明白了，开放的技术交流才能让我们学得更多，懂得更多。所以这几年来，我一直坚持写技术心得，写工作流研究心得，并将他们无私的公开在我的主页和Blog上。</p>
<p><strong>2，现在谈到企业流程管理，经常会出现EAI、WF、BPM这些关键词。您做为这方面的专家，请解释一下它们之间的关系以及不同好吗？</strong><br>&nbsp;EAI
我想是比较容易理解和区分的，全称是企业应用集成（Enterprise Application
Integration）。目前EAI更多的是基于消息中间件服务（企业消息总线Information
Bus），利用多种适配装置完成分散的、分布式的应用的整合。目前比较流行的ESB概念，和EAI是有一定融合的，但ESB更多的是站在业务流程服务的角
度，而不单单是消息或应用。</p>
<p>&nbsp;Workflow和BPM之间的区别是比较含糊的。所以在国外，一般不用Workflow去表示工作流应用。Workflow只是用于表示&#8220;工作流技术（Workflow Technology）&#8221;及这个领域范畴，而用&#8220;Process&#8221;来表示流程。<br>&nbsp;我们所熟知的工作流管理联盟组织（WfMC），目前几乎也只是提BPM，其流程描述语言XPDL也是一直采用Process这个元素的。</p>
<p>&nbsp;上面的说法似乎是过于专业化了一点。那让我们回顾到上个世纪九十年代，诞生了&#8220;Process
Reengineering&#8221;这个概念，单那个时候只是一阵风，因为技术跟不上，所以大多都只停留在管理层概念。而在九十年代，workflow技术和应
用却蓬勃发展，可谓是百家争鸣，蒸蒸日上。 <br>&nbsp;2000左右，工作流技术应用已经非常成熟，数据集成，应用集成也发展迅速。随之也推动了业务过程管理、整合、统计、优化等方面的应用需求。在这样一种市场需求下，必然需要一个含义更广泛、更偏管理和应用性的概念来服务于客户，这就诞生了&#8220;BPM&#8221;这个概念。<br>&nbsp;如果Workflow是早期人们为了解决&#8220;办公自动化&#8221;&#8220;流程自动化&#8221;而诞生的应用技术和解决方案的话；那么BPM则是为了&#8220;对全局性的业务分析、整合&#8221;，以及&#8220;能够基于这些分析提供对上层管理决策的支持&#8221;的一种应用技术和解决方案。</p>
<p>&nbsp;事实上，如何去描述业务过程&#8220;Business Process&#8221;，一直还是个争论不休的话题，也因此存在几种标准。主要是以WfMC为代表的XPDL，OASIS为代表的BPEL，OMG为代表的BPMN和BPDM。<br>&nbsp;&nbsp;&nbsp;&nbsp; 虽然描述过程&#8220;Process&#8221;的标准并不一样，但是围绕过程定义、过程仿真、过程执行、过程监控、过程分析、过程优化这几个方面为核心的BPM Solution，这一方面各家几乎都是相同的，只是实现技术不同。</p>
<p><strong>3，工作流管理主要可以帮助企业解决什么样的问题呢？</strong><br>&nbsp;可以帮助企业更清晰、更灵活的管理业务流程。<br>&nbsp;<br><strong>4，目前，国内企业工作流管理应用环境的特点是什么？</strong><br>&nbsp;目前国内的工作流应用还依然围绕电子政务和协同办公的审批类流程应用为主。当然，也存在一定的商业化业务性流程，比如这两年比较火热的物流领域的仓储管理流程等。</p>
<p><strong>5，从个人角度讲，国内的工作流管理的产品他们各自的特点是什么？</strong><br>&nbsp;在国内，工作流应用与组织管理是密切不可分，甚至在很多时候，我们可以说，是为组织管理服务的。受到国内的管理更多的偏向于以&#8220;人&#8221;为本的思想，所以国内的流程存在很严重的&#8220;人为影响&#8221;，比如&#8220;回退&#8221;&#8220;会签&#8221;&#8220;回退&#8221;&#8220;取回&#8221;&#8220;自由流&#8221;&#8220;主办辅办&#8221;等等特色。<br>&nbsp;也正因为这样，国内客户应用需求的差别非常大，单纯和固定的一款工作流产品，很难满足不同领域，不同客户的需求，所以国内的工作流产品更多的不得不依托于项目来生存。也很难短时间发展起来一款或几款非常庞大的产品。</p>
&nbsp;你让我谈谈我对起步软件的看法，是有些不太好绝对公平的评论的。因为我曾经在思维加速（起步软件的前身）待过。<br>&nbsp;起步软件是典型的以技术
和产品征服客户的一家软件产品和应用服务提供商。前两年，从早期的纯粹业务技术软件平台产品提供商，转变为多业务产品和服务提供商（当然，目前主要以协同
领域为主），是非常成功的转型。而他们早在多年以前就定位于&#8220;模型驱动&#8221;的产品架构和思路更是具有超越性的，这首先要得益于他们总经理马科先生的超前眼
光，也更得益于他们的总工程师宋兴烈的执著和坚韧。<br>&nbsp;当然，目前国内偏于应用开发平台的产品已经很多了，处理起步的X3，还有浪潮的loushang，炎黄盈动的AWS，普元的EOS等等。<br><strong>6，许多国际软件巨头比如BEA，IBM，Tibco它们都有涉足这个领域，对于它们的产品，我们国内的产品有哪些优势与不足呢？<br></strong>&nbsp;对于这些洋巨头们，我想用一句成语来形容——&#8220;高山仰止&#8221;。意思是，在BPM、EAI、ESB这几个领域内，我们国内厂商与他们的实力差距太大。当然国内还是有这样的产品，比如东方通的消息产品，西安协同的ESB产品等。<br>&nbsp;
因为我在TIBCO，所以我就拿TIBCO来说吧。TIBCO最初是以消息服务产品发展起来的，直到目前的以提供EAI、BPM及相关的Solution
产品的服务提供商。纳斯达克证券交易所（Nasdaq）的底层信息交易系统就是TIBCO消息产品支撑的。仅在消息服务这一个领域，就是国内产品短时间无
法逾越的。<br>&nbsp;我们再看看BPM这个领域吧，TIBCO的bpm产品是收购自Staffware，而Staffware早在85年的时候就一直在工作流领域发展。<br>&nbsp;<br>&nbsp;
我不是过于夸大国外厂商的在这个领域内的实力。我的一个PSG部门的同事，每次在客户现场POC之后，总是感慨：&#8220;每次在我用TIBCO产品竞标成功之
后，我不是喜悦，而是深深的感到国内产品的落后，以及这个领域的近乎空白&#8221;。但我只能说，我们在基础技术和核心技术方面，落后的太多太多。<br>&nbsp;<br>&nbsp;相比较这些国际巨头们，国内厂商的劣质主要表现在：<br>（1）&nbsp;研发资金不足。<br>（2）&nbsp;理论积累、技术积累和沉淀不足。研发团队的稳定性也是一个问题之一<br>（3）&nbsp;国内客户需求和工作流应用还属于基本应用层次，所以很难推动国内厂商投入人力物力来提供产品的定位和应用层次，比如流程监控、分析等等。
<p>&nbsp;国内厂商的优势在于&#8220;业务&#8221;和&#8220;客户化项目实施&#8221;。如果从单纯流程这个角度，我想国内产品的优势则主要在于两方面：<br>&nbsp;第一：&#8220;客户应用的本地化流程特色&#8221;，比如上面提到的&#8220;回退&#8221;&#8220;会签&#8221;&#8220;回退&#8221;&#8220;取回&#8221;&#8220;自由流&#8221;&#8220;主办辅办&#8221;等等特色；<br>&nbsp;第二：&#8220;本地化组织模型和权限&#8221;，国内的组织模型是国外产品很难理解和超越的，这也是为什么国内工作流应用市场，几乎都还是主要被国内自己的厂商占据的一个主要原因之一。<br>&nbsp;<br><strong>7，企业的需要是个性化的，那么通常国内的工作流管理厂商如何满足企业的个性需求？</strong><br>&nbsp;国内工作流产品则主要依赖于提供灵活的建模工具，表单工具，对外程序接口（API），可外挂的扩展应用和接口实现等等来满足企业的个性化需求。</p>
<p><strong>8，企业部署一套工作流管理系统遇到的最大的困难是来自技术方面吗？你认为最大的困难在哪里？如何克服？</strong><br>&nbsp;我一直有个观点：技术不是最主要的问题。国内应用开发商都是很厉害的，基本上在项目实施过程中，只要客户能够提出基本的需求，技术开发人员总是可以找到解决办法的。</p>
<p>&nbsp;国内企业实施工作流最大的困难来自于两个方面：<br>（1）&nbsp;需求。客户对流程应用的需求很难提的比较清晰和准确。一方面是客户对工作流技术基本理念不清，所以很难准确地阐述应用需求；另一方面，是应用开发商对流程应用的整体性认识不全，系统性不够，所以很难正确的引导客户来分析流程应用和需求。<br>（2）&nbsp;合适的流程产品。国内的产品外部相似性很多，但内部细力度的区别却又很多。但是这种细力度的区别是很难通过简单的演示就能够让应用开发商明白的。</p>
<br>      <img src ="http://www.blogjava.net/cherishchen/aggbug/126586.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cherishchen/" target="_blank">凭栏观海</a> 2007-06-27 14:41 <a href="http://www.blogjava.net/cherishchen/archive/2007/06/27/126586.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>软件开发成功12法则——Joel Spolsky</title><link>http://www.blogjava.net/cherishchen/archive/2007/06/25/126152.html</link><dc:creator>凭栏观海</dc:creator><author>凭栏观海</author><pubDate>Mon, 25 Jun 2007 08:50:00 GMT</pubDate><guid>http://www.blogjava.net/cherishchen/archive/2007/06/25/126152.html</guid><wfw:comment>http://www.blogjava.net/cherishchen/comments/126152.html</wfw:comment><comments>http://www.blogjava.net/cherishchen/archive/2007/06/25/126152.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/cherishchen/comments/commentRss/126152.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/cherishchen/services/trackbacks/126152.html</trackback:ping><description><![CDATA[<p class="MsoNormal" style="text-align: center;" align="center"><strong><span style="font-size: 14pt;" lang="EN-US"></span></strong></p>
<h3 style="text-align: center;">软件开发成功12法则——Joel Spolsky</h3>
<h3 style="text-align: left;">作者简介：作者：Joel Spolsky 是纽约市一家小软件公司Fog Creek Software 的创始人。他毕业于耶鲁大学，曾在美国微软公司、Viacom、Juno 任软件设计师及经理。<br></h3>
<p>
</p>
<p><br>&#8220;有
没有听说过SEMA？这可是衡量一个软件开发组好坏的很深奥的系统。别动，等一下！别按那个链接！给你六年你也搞不清这玩意儿。所以我自己随便攒了一套衡
量系统，信不信由你，这系统，三分钟就可掌握。你可以把省下的时间去读医学院了（译注：美国的医学院可是要读死人的！）。</p>
<p>（注：SEMA：Software Engineering Measurement and Analysis）<br><br>Joel 衡量法则：<br>1. 你们用不用源文件管理系统？ <br>2. 你们可以把整个系统从源码到CD映像文件一步建成吗？ <br>3. 你们每天白天都把从系统源码到CD映像做一遍吗？ <br>4. 你们有软件Bug管理系统吗？ <br>5. 你们在写新程序之前总是把现有程序里已知的软件Bug解决吗？ <br>6. 你们的产品开发日程安排是否反映最新的开发进展情况？ <br>7. 你们有没有软件开发的详细说明书？ <br>8. 你们的程序员是否工作在安静的环境里？ <br>9. 你们是否使用现有市场上能买到的最好的工具？ <br>10. 你们有没有专职的软件测试人员？ <br>11. 你们招人面试时是否让写一段程序？ <br>12. 你们是否随便抓一些人来试用你们的软件？ </p>
<p>&#8220;Joel 衡量法则&#8221;好就好在你只需照着逐条回答以上问题，然后把所答为&#8221;是&#8221;的问题算成一分，再加起来就可以了，而不需要去算什么每天写的程序行数或程序虫的平均数等等。但咱丑话说在前面，可别用&#8221;Joel 衡量法则&#8221;去推算你的核电站管理程序是否可靠。 </p>
<p>如果你们得了12分，那是最好，得了11分还过得去，但如果只得了10分或低于10分，你们可能就有很严重的问题了。严酷的现实是：大多数的软件开发公司只能得到2到3分。这些公司如果得不到急救可就玄了，因为像微软这样的公司从来就没有低过12分。 </p>
<p>当
然，一个公司成功与否不仅仅只取决于以上标准。比如，让一个管理绝佳的软件公司去开发一个没有人要的软件，那开发出来的软件也只能是没有人要。或反过来，
一帮软件痞子以上标准一条也达不到，没准照样也能搞出一个改变世界的伟大软件。但我告诉你，如果不考虑别的因素，你只要能达到以上12条准则，你的团队就
是一个可以准时交活的纪律严明的好团队。&#8221;<br><br>--------------------------------------------------------------------------------</p>
<p>1. 你们用不用源文件管理系统？<br>我用过商业化的源文件管理系统，我也用过免费的系统，比如CVS，告诉你吧，CVS挺好用。但如果你根本就没有用源文件管理系统，那你就是累死了也没法让你的程序员出活：他们没法知道别人在改动什么源文件，写错了的源文件也没法恢复。 </p>
<p>使用源文件管理系统还有一大好处是，由于每一位程序员都把源文件从源文件管理系统里提出来放到自己的硬盘里，几乎不会发生丢失源文件的事，最起码我还没听说过。 </p>
<p>2. 你们可以把整个系统从源码到CD映像文件一步建成吗？<br>这
句话问的问题是：从你们最新的源码开始到建立起能够交出去的最后文件，你们有多少步骤要做？一个好的团队应该有一个批处理程序一步便可将所有的工作做完，
像把源文件提取出来，跟据不同的语言版本要求（英文版，中文版），和各种编译开关（#ifdef）进行编译，联接成可执行文件，标上版本号，打包成CD映
像文件或直接送到网站上去，等等等等。 </p>
<p>如果这些步骤不是一步做完，就有可能出人为差错。而且当你很接近产品开发尾声的时侯，你可能很急于把最后几个虫解决，然后尽快地交活。如果这时候你需要做20步才能把最终文件制出来，你肯定会急得要命，然后犯一些很不该犯的错误。<br> <br>正
因为这个原因，我工作的前一个公司从用WISE改用InstallShield：我们必需要让我们的批处理程序完全自动化地，在夜里，被NT
scheduler起动把最终文件制成，WISE不能被NT
scheduler启动而InstallShield可以，我们只能把WISE扔掉。（WISE的那帮家伙向我保证他们的下一代产品一定支持在夜里自动运
行) </p>
<p>3. 你们每天白天都把从系统源码到CD映像做一遍吗？<br>你们有没有遇到过这样的事情：一个程序员不小心把有毛病的源码放进
源文件管理系统，结果造成最终文件没法制成。比如，他建立了一个新源文件但忘了把它放进源文件管理系统，然后他高高兴兴锁机回家了，因为在他的机器上整个
编译得很好。可是别人却因为这没法工作下去了，也只好闷闷地回家了。 </p>
<p>这种造成最终文件没法制成的情况很糟糕，但却很常见。如果每天在白
天就把最终文件制一遍的话，就可以让这种事不造成太大危害。在一个大的团队里，要想保证有毛病的源码及时得到纠正，最好每天下午（比如午餐时）制一下最终
文件。午餐前，每个人都尽可能地把改动的源文件放到源文件管理系统里，午餐后，大家回来，如果最终文件已经制成了，好！这时大家再从源文件管理系统里取出
最新的源文件接着干活。如果最终文件制作出错，出错者马上修正，而别人还可接着用原有的没问题的源程序干活。 </p>
<p>在我以前曾干过的微软
Excel开发组里，我们有一条规定：谁造成最终文件制作出错，谁就得被罚去负责监视以后的最终文件制作过程，直到下一位造成最终文件制作出错的人来接任
他。这样做不仅可以督促大家少造成最终文件制作出错，而且可以让每个人都有机会去了解最终文件制作过程。 </p>
<p>如果想更多了解这个话题，可以读我的另一篇文章 Daily Builds are Your Friend. </p>
<p>4. 你们有软件虫管理系统吗？<br>不
论你有任何借口，只要你写程序，哪怕只是一个人的小组，如果你没有一个系统化的管理软件虫的工具，你写的程序的质量一定高不了。许多程序员觉得自己可以记
得自己的软件虫。没门！我从来记不住超过２到３个软件虫。而且第二天早上起床后忙着去买这买那，好不容易记住的软件虫早忘掉了。你绝对需要一个系统来管住
你的那些虫。 </p>
<p>软件虫管理系统功能有多有少。但最少要管理以下几种信息：<br> <br>如何重复软件虫的详细步骤 <br>正常情况（无虫）应是怎样 <br>现在情况（有虫）又是怎样 <br>谁来负责杀虫 <br>问题有没有解决 </p>
<p>如果你觉得用软件虫管理系统太麻烦，可以简化一下，建立一个有以上5列的表来用就行了。 </p>
<p>如果想更多了解这个话题，可以读我的另一篇文章Painless Bug Tracking. </p>
<p>5. 你们在写新程序之前总是把现有程序里已知的虫解决吗？<br>微
软Windows
Word的第一版的开发项目曾被认为是&#8220;死亡之旅&#8221;项目。好象永远也做不完，永远超时。所有人疯狂地工作，可怎么也完成不了任务。整个项目一拖再拖，大家
都觉得压力大得受不了。最后终于做完了这个鬼项目，微软把全组送到墨西哥的Cancun去度假，让大家坐下来好好想想。 </p>
<p>大家意识到由于
项目经理过于强求程序员们按时交活，结果大家只能匆匆地赶活，写出的程序毛病百出。由于项目经理的开发计划并没有考虑杀虫的时间，大家只能把杀虫的任务往
后推，结果虫越积越多。有一个程序员负责写计算字体高度的程序，为了图快，居然写一行&#8221;return
12;&#8221;了事。他指望以后的质检人员发现这段程序有毛病后报告他再改正。项目经理的开发计划事实上已变成一个列写程序功能的清单，而上面列的所谓程序功能
迟早都会成为软件虫。在项目总结会上，我们称这种工作方法为&#8220;绝对劣质之路&#8221;。<br> <br>为了避免再犯这个错误，微软制定了&#8220;零缺陷策略&#8221;。许多程序员嘲笑这个策略，觉得经理们似乎在指望靠行政命令来提高产品质量。而事实上&#8220;零缺陷策略&#8221;的真正含义是：在任何时候，都要把解决现有程序里的问题作为首要问题来抓，然后再去写新程序。<br> <br>为什么要这样做呢？ </p>
<p>一
般说来，你越不及时地杀虫，杀虫的代价（时间和金钱）就会越高。比如，你写程序时打错了一个字，编译器马上告诉你，你很容易就把它改正。你刚写好的程序在
第一次运行时发现了一个问题，你也很快就能解决它，因为你对你刚写的程序还记忆犹新。如果你运行你的程序时发现了一个问题，可这个程序是几天以前写的，你
可能就需要折腾一会儿，还好，你还大致记得，所以不会花太长时间。但如果你在你几个月以前写的程序里发现了问题，就比较难解决了，因为你已经忘了许多细
节。这时候，你还没准儿正忙着杀别人程序里的虫呐，因为这家伙到加勒比海阿鲁巴岛度假去了。这时候，解决这一堆问题的难度不亚于从事尖端科学研究。你一定
得小心翼翼地，非常系统化地从事，而且你很难知道多长时间你才能把问题解决。还有更糟糕的，你的程序已交到用户手里了，才发现问题，那你就等着掏腰包吧。
</p>
<p>总结起来，就一条：越早解决问题，越容易解决。 </p>
<p>另外还有一个原因，刚写的程序里发现问题，你能够比较容易地估算解决它的时
间。举个例子，如果我问你写一段程序去把一个列表排序需要花多长时间，你可以给我一个比较确切的估计。如果你的程序，在Internet
Explorer
5.5安装以后，工作不正常。我问你要多长时间把这个问题解决，你恐怕都估计不出来，因为你根本就不知道是什么原因造成了这个问题。你可能要花三天时间才
能解决，也有可能只花两分钟。 </p>
<p>这个例子告诉我们，如果你的开发过程中有许多虫没有及时解决，那你的开发计划肯定不可靠。反过来，如果你们已经把已知的虫全部解决了，要做的事只是写新的程序，那你的开发计划就会比较准确。 </p>
<p>把
已知的虫全部解决，这样做还有一个好处：你可以对竞争对手快速反击。有些人把这叫着&#8220;让开发中的产品随时处在可以交给用户的状态&#8221;。如果你的竞争对手推出
一个新的功能想把你的客户抢走，你可以马上在你的产品里加上这个功能，立刻将新产品交付用户，因为你没有一大堆积累下来的问题要解决。 <br><br>6. 你们的产品开发日程安排是否反映最新的开发进展情况？<br>为什么我们需要开发日程安排？如果你的程序对公司的业务很重要，那公司就必须知道你的程序何时能写完。满世界的程序员都有一个通病，那就是他们都搞不清自己何时才能写完要写的程序。他们都只会对管理人员嚷嚷：&#8220;等我做好了就做好了！&#8221; </p>
<p>不幸的是，程序写完了，事远远没完。作为一个公司，在发行产品之前，还有许许多多的事情要做：何时做产品演示？何时参加展览会？何时发广告？等等。所有的这一且都依赖于产品的开发日程安排。 </p>
<p>定下产品开发日程安排，还有一个很关键的好处：它逼着你只做叫你做的功能，甩掉那些可要可不要的功能，否则这些可要可不要的东西有可能把你缠住。请看featuritis 。 </p>
<p>定下产品开发日程安排，按照它开发，这并不难做，请看我的另一篇文章 Painless Software Schedules ，这篇文章告诉你一种制订产品开发日程的好方法。 </p>
<p>7. 你们有没有软件开发的详细说明书？<br>写软件开发的详细说明书就像是绣花：人人皆知是好东西，可没谁愿意去做。 </p>
<p>我不知道这是为什么，也许是因为多数程序员天生就不喜欢写文章。其结果是，一个开发组里的程序员们，宁可用程序来沟通，也不愿写文章来表达自己。他们喜欢上来就写程序，而不是写什么详细说明书。 </p>
<p>在产品的前期设计过程中，如果你发现了一些问题，你可以轻易地在说明书里该几行字就行了。一旦进入了写程序的阶段，解决问题的代价就要高得多了，不仅仅是时间上的代价，而且也有感情上的代价，因为没人愿意将自己做成的东西扔掉。所以这时候解决问题总有一些阻力。 </p>
<p>没
有产品开发详细说明书就开始写程序，往往会导致程序写的乱七八糟，而且左拖右拖不能交付使用。我觉得这就是Netscape遇到的问题。前四个版本的程序
越写越乱，以至管理人员作出一个愚蠢的决定：把以前的程序统统扔掉，重新写。后来他们在开发Mozilla时又犯了同样的错误。产品越做越乱，完全失控，
花了几年的时间才进入内部测试阶段。 </p>
<p>我最得意的理论是：如果让程序员们接受一些写文章的训练，如an intensive course in writing，他们就可能会改变一下不写说明书的坏习惯，而以上所说的糟糕的例子就有可能少发生。 </p>
<p>另一个解决问题的办法是：雇一些能干的项目主任，专职写产品开发详细说明书。 </p>
<p>不论采用以上哪种方法，道理只有一个：在没有产品开发详细说明书之前，决不可写程序。<br> <br>如果想更多了解这个话题，可以读我的四篇文章。 </p>
<p>8. 你们的程序员是否工作在安静的环境里？<br>当你让你的智囊们工作在安静，宽敞，不受人打扰的环境里，他们往往能更快地出活，这已是不争的事实。有一本经典的讲软件开发管理的书Peopleware（人件） 把这个问题阐述得很清楚。 </p>
<p>问题在于，我们都知道最好不要打断这些智囊们的思路，让他们一直处于他们的最佳状态中，这样他们就能全神贯注，废寝忘食地工作，充分发挥他们的作用。作家，程序员，科学家，甚至篮球运动员都有他们的最佳状态。 </p>
<p>问题还在于，进入这个最佳状态不容易。我觉得平均起来，需要15分钟才能进入最佳状态，达到最高工作效率。有时侯，当你疲劳了或已经高效率地干了许多工作了，你就很难再进入这个状态，只好干点杂事打发时间，或上网，玩游戏等。 </p>
<p>问
题更在于，你很容易就被各种各样的事打扰，被拽出你的最佳状态：噪音啦，电话啦，吃午饭啦，喝杯咖啡啦，被同事打扰啦，等等。如果一个同事问你一个问题，
只花你一分钟，可你却被拽出你的最佳工作状态，重新回到这个状态需要花半小时。你的工作效率因此而受到很大影响。如果让你在一个嘈杂的大房间里工作（那帮
搞网站的家伙还就喜欢这样），边上的推销员在电话里大叫大嚷，你就很难出活，因为你进入不了你的最佳工作状态。 </p>
<p>作为程序员，进入最佳工
作状态更难。你先要把方方面面的细节装在脑子里，任何一种干扰都可能让你忘掉其中某些东西。你重新回来工作时，发现好些东西记不起来了（如你刚用的局部变
量名，或你刚才的搜索程序写到哪里了等），你只好看看刚写的程序，回忆一下，慢慢地回到你刚才的最佳工作状态。 </p>
<p>我们来做一个简单的算
数。假设一个程序员被打扰一下，哪怕只有一分钟，他却需要花15分钟才能回到最佳工作状态（统计资料显示如此）。我们有两个程序员：杰夫和愚夫，
坐在一个大办公区里工作。愚夫想不起来用什么函数去进行Unicode
字符串复制。他可以花30秒查一下，或者花15秒问杰夫。由于他坐在杰夫的旁边，他就选择去问杰夫。杰夫被打扰了一下，耽误了他15分钟，节省了愚夫15
秒钟。 </p>
<p>现在，我们把他们俩用墙和门隔开，让他们俩分坐在不同的办公室里，愚夫又想不起来什么函数名，自己查一下要花30秒；问杰夫，要
花45秒，因为他要站起来走过去问（对这帮程序员来说，这可不是件简单的事，看看他们的体质就知道为什么了）。所以他选择自己去查。愚夫损失了30秒钟，
可是杰夫少损失了 15分钟。哈哈！ </p>
<p>9. 你们是否使用现有市场上能买到的最好的工具？<br>用可编译语言写程序恐怕是这世界上为数
不多的还不能随便抓一个破计算机就可以做的事。如果你用于编译的时间超过几秒钟，你就应该换一台最新最快的计算机了。因为如果编译时间超过15秒，程序员
们就会不耐烦，转而去上网看一些无关的东西比如 The Onion，弄不好一看就是好几个小时。 </p>
<p>调试图形界面软件时，用只有一个显示器的计算机不仅不方便，有时甚至是不可能。用有两个显示器的计算机，要方便许多。 </p>
<p>程序员们经常不可避免地要去画一些图标或工具栏图。多数程序员没有一个好的图形编辑器可用。用微软的&#8220;画笔&#8221;软件去画图标简直是笑话，可事实上大家还就在这样做。 </p>
<p>在我的前一个工作，系统管理员成天给我发来自动警告，说我在服务器上使用了超过220兆的空间。我告诉他，按现在硬盘的价钱，超出这点空间的价钱远低于我用的厕纸的价钱。让我花10分钟去清理我的文件绝对是我工作效率的莫大浪费。 </p>
<p>一流的开发组绝不折腾它的程序员。工具落后会让人用起来觉得难受，一点点积累起来，会让程序员们成天叫苦，而一个成天叫苦的程序员绝对不会是一个高消率的程序员。 </p>
<p>再添一句，要想使你的程序员高兴，最好的办法就是给他们买一些最新最棒的工具软件。用这种方法可以让他们乖乖地为你工作，这可比用高薪吸引他们来得便宜得多。 </p>
<p>10. 你们有没有专职的软件测试人员？<br>如果你的开发组里没有专职的测试人员，或没有足够的测试人员（两到三个程序员就应该配一个测试员），那你的产品就一定是毛病百出。想在测试员身上省钱，绝对是打错了算盘。我真不明白为什么这么多人算不过来这笔帐。 </p>
<p>我有另一篇文章专门讲这个，请看Top Five (Wrong) Reasons You Don't Have Testers。 </p>
<p>11. 你们招人面试时是否让写一段程序？<br>我问你，让你去招一个魔术师，你是否连看都不看一眼他的魔术玩得怎样就要他？当然不会！<br> <br>你举办婚宴，要请一个厨师，你是不是连嚐也不嚐他做的菜好吃不好吃就要他？我想也不会。 </p>
<p>奇
怪的是，几乎每天都有这样的事发生：在面试一个程序员时，简历写得漂亮，谈得热火朝天，问几个简单的问题（如CreateDialog()和
DialogBox()有什么区别？这种问题，查一下帮助文件就知道了），人就招进来了。你真正应该关心的不是这人记不记得这些写程序的边边角角的东西，
而是他能否出产品！更糟糕的是，许多问题是知道就知道，不知道，想死也不知道的问题。 </p>
<p>不能这样下去了！在面试时，请一定要让写一段程序。在我的这篇文章里Guerrilla Guide to Interviewing，我有许多好建议。 </p>
<p>12. 你们是否随便抓一些人来试用你们的软件？<br>这句话的意思是，让你从走道里走过的人中，随便抓几个人来，让他们试用你的软件。如果你抓五个人来用你的软件，那你就可能把你的程序中95%的不方便使用的地方找出来。 </p>
<p>要想让用户去买你的软件，你必须要设计好你的用户界面。这其实并不难。你可以读我的free online book on UI design打打基础。<br> <br>用户界面设计的关键是，如果你让几个人去用你的软件（五六人可能就够了），你可能很快就找出最大的问题。想知道为什么吗，请读Jakob Nielsen's article。只要你坚持随便抓一些人来试用你的软件，你就能将你的用户界面设计得越来越好。<br> </p>
<p>--------------------------------------------------------------------------------</p>
<p><br>The Joel Test 软件开发成功12法则的四个实用领域： </p>
<p>1. 用该法则来衡量你的软件开发组，告诉我你得的分数，让我来品头论足。 </p>
<p>2. 如果你是开发组的经理，用该法则来使你的组提高效率。如果你们一上来就能得12分，你就别再打扰你的程序员了，专心致志别让公司的管理人员来烦你的程序员吧。 </p>
<p>3. 如果你在找一份程序员工作，问问你未来的老板他能得几分，如果分数很低，你一定要确信你进去后有足够的权力来改变这一切，否则，最好躲远点，不然，你在那儿会很难受的。 </p>
<p>4. 如果你是投资者，正在决定是否向一个软件公司投资，或者你的软件公司正在决定是否兼并另一个软件公司，该法则可以帮你做决定。</p>
<p class="MsoNormal" style="text-align: center;" align="center"><br><strong><span style="font-size: 14pt;" lang="EN-US"><o:p></o:p></span></strong></p>
<p class="MsoNormal" style="margin-left: 21pt; text-indent: -21pt;"><strong><span lang="EN-US"><span></span></span></strong></p><img src ="http://www.blogjava.net/cherishchen/aggbug/126152.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cherishchen/" target="_blank">凭栏观海</a> 2007-06-25 16:50 <a href="http://www.blogjava.net/cherishchen/archive/2007/06/25/126152.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>