精彩的人生

好好工作,好好生活

BlogJava 首页 新随笔 联系 聚合 管理
  147 Posts :: 0 Stories :: 250 Comments :: 0 Trackbacks

2006年5月7日 #

今天在搜寻tree的实现,找到了这段代码,觉得很有用但是还没想到怎么用,先保存在这里


一般用匿名内部类实现callback


public interface YourInterface {

  public void interfaceMethod();

}


public class YourClass{

  public void classMethod(YourInterface yInterface)
  {
        ......
        yInterface.interfaceMethod();
        ......
    }
 
  public static void main(String[] args)
  {
      YourClass yClass = new YourClass();

      yClass.classMethod(new YourInterface()
      {
          public void interfaceMethod()
          {
              //do sth
              System.out.print("hello world.");
              .......   
          }
      });
}

posted @ 2008-09-16 14:14 hopeshared 阅读(1523) | 评论 (3)编辑 收藏

很奇怪,以从来没觉得这么困惑过。

从今年开始,对,就是今年,困惑了,很困惑。按照“奋斗”的说法,我很焦虑,非常焦虑。

为什么会困惑?可能是理想与现实出现了反差吧。其实这个反差以前就有,只不过以前拥有信念,这个信念就是付出就会有回报,不管是什么经历都是一种成长。

参加工作了,生活变成了柴米油盐。信念依然坚持着,但是当发现付出并没有得到相应的回报,甚至得不到重视,甚至被漠视和忽略,这种困惑,从内心深处迸发了出来,充满了身体里每个细胞。信念,被怀疑了。人性的丑恶面毫不遮掩的出现在你面前。利益,永远的利益,才应该是每个人的最终目标。但是,应该这样么?我不知道。

自我感觉良好,有缺点我承认,不管是工作上还是生活上。但是这并没有让自己觉得差人一等,我有我的特色。但是,就是我这样的人,兢兢业业的工作,不计得失不喊苦累,还找的到么?恐怕很少了吧,不然怎么找个能踏实干活的实习生都找不到?

伯乐与千里马,其他同事的故事有时能让自己宽慰一下,或许继续等等,终会有出头之日。机遇,多好的词阿,它告诉你机会是可遇不可求的。但是,埋头苦干的人们,能看到头顶上飘过的机会么?是不是该抬起头,看看窗外的世界了?

美国的一个同事离职了,十分吃惊。最开始以为他是被累坏了于是提出离职,后来才搞清楚是移民文件的问题。仔细想了想他的境遇,是不是比我更加悲惨?但是他没有一句怨言。呵呵,我想,更多的原因应该是环境和观念的区别。不过如果让我遇到这事,估计能比现在更加困惑。

困惑,也许似曾相识。在升高中的时候,在选大学的时候,在复试研究生的时候。但是那时候的困惑很快就一扫而过了。为什么现在挥之不去呢?

我想,那是因为我知道现在想要什么,应该得到什么,而却没有得到。为什么会这样?自我反省。是自己能力不够,其实不是。是因为桀骜不驯所以不能趋炎附势?或许吧。又或者是性格太软弱,不敢去争去抢去豪夺?或许吧。突然想起了老黄牛,开垦者荒芜的土地,吃着最廉价的草,偶尔的嗷嗷声是唯一发泄的方式,却永远不敢挣脱身上的缰绳。这是我么?

也许困惑了才是机遇,是生命中的沟沟坎坎.也许我该重新审视一下自己,也许该环顾一下四周,也许该看看外面的世界,也许吧.

posted @ 2008-08-27 19:15 hopeshared 阅读(1329) | 评论 (1)编辑 收藏

在做的项目里,需要一个这样的显示效果.
以前从来没有做过,也不知道该用什么关键字来搜索.
于是,只好用最原始的办法,看Eclipse的源代码.最后一些跟decorator相关的类引起了我的注意,还发现有一个相关的LabelProvider.于是我用这个词作为关键字搜索到了下面这篇文章,觉得十分有用,所以暂存到这里.


原文标题: org.eclipse.ui.decorators得用法

<extension point="org.eclipse.ui.decorators">
    
<decorator icon="icons/warning_small.gif" id="cnooc.rcp.decorator.node.warning" label="Data Warning Decorator" lightweight="true" location="BOTTOM_LEFT" state="true">
        
<enablement>
            
<and>
                
<objectClass "com.eplat.cnooc.rcp.node.INode"/>
                
<objectState name="hasWarning" value="true"/>
            
</and>
        
</enablement>
    
</decorator>
    
<decorator icon="icons/error_small.gif" id="cnooc.rcp.decorator.node.error" label="Data Error Decorator" lightweight="true" location="BOTTOM_LEFT" state="true">
        
<enablement>
            
<and>
                
<objectClass "com.eplat.cnooc.rcp.node.INode"/>
                
<objectState name="hasError" value="true"/>
            
</and>
        
</enablement>
    
</decorator>
</extension>

INode是TreeViewer里面节点得对象.
Viewer设置LabelProvider时需要如下:

viewer.setLabelProvider(new DecoratingLabelProvider(new ViewLabelProvider(),
        Activator.getDefault().getWorkbench().getDecoratorManager().getLabelDecorator()));

写了这个以后还没完, 需要让INode实现IActionFilter接口. eclipse得API中说要么实现IActionFilter接口, 要么实现IAdapter接口, 如果实现后者得话, 系统会调用getAdapter()方法. 不过我还是选择前者.

    /**
     * (non-Javadoc)
     * 
     * 
@see org.eclipse.ui.IActionFilter#testAttribute(java.lang.Object, java.lang.String,java.lang.String)
     
*/

    
public boolean testAttribute(Object target, String name, String value){
        
if (name.equals("hasWarning")){
            
return !getProblems().hasError() && getProblems().hasWarning();
        }
 else if (name.equals("hasError")){
            
return getProblems().hasError();
        }

        
return false;
    }

意思应该比较明显得, 如果有warning并且没有error得时候warning得decorator生效. 如果有error则error得decorator生效. name参数对应得就是objectState得name参数. value参数对应得objectState得value参数. target参数就是viewer中得节点对象, 不过由于INode实现了IActionFilter接口, 因此这里得target就是this了.
一般来说需要判断得就是根据name获取得值是否等于value, 等于返回true, 否则返回false. 不过这里我不需要判断这个了, 直接根据当前状态返回就好了.
按照eclipse得原理, 理论上应该INode变化以后viewer就会跟着变化, 但是我实验下来有时候好有时候不好.
因此在Editor得verify方法里增加了下面得逻辑:

    /**
     * 校验数据
     * 
     * 
@return
     
*/

    
private boolean verify(){
        
//校验数据
        
//不管是否有error或者warning都需要通知向导树刷新一下节点.
        ExplorerView view = (ExplorerView) getSite().getPage().findView(ExplorerView.class.getName());
        view.refresh((INode) getEditorInput());

        
return true;
    }

现在好了, 只要执行verify方法, viewer就会刷新对应得节点, 以达到显示左下方小图标得目的.


原文地址: http://blog.csdn.net/bradwoo8621/archive/2007/05/11/1604738.aspx








posted @ 2008-03-25 15:00 hopeshared 阅读(4554) | 评论 (2)编辑 收藏

1、一个和谐社会需要一个和谐股市,一个和谐股市不应该让绝大多数投资者被套牢!
——沙黾农(2007年股市的红火,带动了股票博客的红火,可以说2007年的中文博客是财经股票年。)


2、我会不会加入作协?如果我去了就能当主席,我就去,我下一秒就把作协给解散了。
——韩寒


3、梁(朝伟)的屁股同样的苍白、塌陷,像难民的胸膛。一个没有翘屁股的男星是不够敬业的。
——孟静说《色戒》


4、我是个平凡的农民,没有超常的骗术。

——周正龙博客称做堂堂正正的中国农民


5、人渣中的战斗机,败类中的VIP。

——带头大哥骂某记者


6、X主席一直在“蠢蠢欲动”,这个成语真好,只有蠢之又蠢,才会动。
——李承鹏说中国足球联赛南北分区


7、故宫不是首都机场。

——芮成钢博客的呐喊,最终让星巴克从故宫搬了出去。


8、我敢买他的股票,敢贷款买他的股票。

——刘韧说史玉柱巨人网络上市


9、我的能量和天分不做总统夫人算得上是一种浪费。

——杨二车娜姆博客向离婚的法国总统表达爱意


10、中国男人的通病——对待女人,只知解裤带,不解风情。

——二月丫头


11、深圳一直盛产小心眼的大公司。

——keso


12、互联网是重体力活,重到比农民种田都耗体力。

——方兴东


13、黄垂玲敞开的身体是越南改革开放的一个标志。

——木木


14、爱好文学的女人都不是好女人。

——张怀旧


15、假如,清蒸是对一只鸡的最高礼遇。那么,散步是我们对厦门的最高致敬。
——这是《城市画报》的一句精彩语录,本来并不是博客上说的,但会让人想起一个博客


16、我虽然不建议散户今天不计成本地抢购中石油,但我强烈地不建议散户在今天的任何价位抛掉中签的中石油!留住你割肉、你踏空200点换来的中石油吧!留住吧!哪怕你只中了1000股,请把这1000股留给你的子孙吧!留住这1000股,你就留住了价值投资的理念;留住这1000股,你就克服了追涨杀跌的恶习;留住这1000股,你就留住了与股市相爱后戴上的一枚结婚戒指!
——沙黾农在中石油上市的时候说(这句话会让人想起黄健翔的那句话。)


17、董方卓像曼联的老板——所有队友都在拼命给他喂球,否则就要被扣工资似的。
——董路曼联来华的比赛


18、爱情和赌博一样,红了眼的都拿器官下注。
——北京女病人


19、史玉柱很清楚,在屎壳螂的国度里卖大粪才能成功。
——这是水木社区网友lowy@newsmth对史玉柱搞网游的评价,此后,这句话经很多博客推荐广为流传。


20、我的技术如果还不算好,那这个市场就没人敢说自己技术好。
——带头大哥


21、中国足协可能是预备役征兵办,可能是联合国人文形象大使,可能是专业堂会承办商,可能是抗日诗歌爱好协会,可能是扫黄打非办公室,但就不是足球管理机构。
——李承鹏


22、有多少女记者男朋友有外遇都没哭过,但是为什么她们的眼里总是含着泪水?都是被主编骂的。
——王小峰说《三联生活周刊》


23、中国人的劳动力不应该这么便宜,中国人制造的产品不应该这么便宜,中国人的钱不应该这么便宜,中国人的股票也不应该这么便宜,中国人要瞧得起中国人,中国人要瞧得起中国股民,中国人要瞧得起中国的上市公司,中国人要瞧得起中国的股票!
——沙黾农


24、我认识16号(阿森纳的弗拉米尼),化成灰我都认识他,只是我面前的屏幕太小了,只有巴掌大,就是我亲爹在场上踢球我也不一定能看得清楚啊……
——董路在博客上说这句话不是自己的最好语录


25、脸大不能怨社会。
——孟静说自己脸大


26、女人的可怕之处,不在于拳头上,而在于枕头上。
——艳女Coco


27、以前流行热血青年,如今流行的是狗血青年。
——北京女病人


28、第一个错误判断是,中国的成年人比较像青少年,容易学坏,一旦让他们看到性,他们就要乱来,就要堕落,所以只能给中国的成人以青少年的待遇。第二个错误判断是,中国的成年人没有国外成年人那样的需求,他们不喜欢看成年人的性影象。第三个错误判断是,中国的成年人欣赏水平比较低,对于比较微妙的性(比如虐恋)理解不了,欣赏不了,只能理解最一般最常见的性方式。
——李银河我觉得有关部门有一些错误的判断,这些错误的判断导致了中国人不得不忍受被搞得残缺不全的《色·戒》。


29、在操盘手的世界里我就是个神。
——带头大哥


30、怀才就像怀孕,时间长了才能看出来。
——这句话被是2007年度的经典MSN签名,实际上,很多人并不知道,这句话是韩寒说的。


31、“白领”就是工资领了也白领。
——李承鹏


32、存银行不如买银行——还不如买工行这只“乌龟”。买黄金不如买黄金股,加油越来越贵不妨加仓石油股。
——沙黾农


33、人类的那点事和人类的那点恶大体都在网络上。网络是这样一个玩意:看着是在阳光下,实则是阳光下的黑影。黑是天然的掩体,像熄灯后的电影院,摸黑中的黑摸。
——董路


34、您认识的人越多,就越喜欢动物。
——北京女病人


35、保护一直在弱势的文艺工作者其实才是协会该做的,妇联就是文联的改革方向。
——韩寒


36、现在的一些新闻真好看,怎么看都像是小说,而且还是郭敬明的小说。
——《犯贱报》李坏


37、以前,隔着桌子,能看见对面台布下有放肆进取的手和羞涩闪避的大腿。现在,看见的是奋不顾身的大腿和躲闪退避的手。
——和菜头说在小饭店里吃饭


38、感觉自己这半年来,就像一块肉,被人扔在油锅里炒来炒去,什么人都可以拿一把铲子来炒我一把。电视台炒我,收视率上去了。我的油榨干了,再把我扔回去给媒体炒。接着,想上博客点击率的,想发片的,想出点小名的,都拿我炒一把!我被炒干了,就会被扔掉了。然后,一块新鲜的肉又会被扔了进去。
——杨二车娜姆


39、北京还有一个高手,还有我师傅,所以我排行全国第三。
——带头大哥


40、什么是最好的分组和最好的赛程?是不是把马尔代夫文莱东帝汶分过来,我们在高原屯积重兵练着,直等哪天觉得自己练出状态了,就打个电话下山说“你们来吧”,那就不是打比赛,那是叫外卖。
——李承鹏


41、麦克拉伦是一根至尊无敌的搅屎棍,先将自己的脑子搅成一陀屎,然后再将英格兰队搅成更大的一陀屎,最终屎得其所。
——董路


42、很多人的撒谎体验都是从作文开始的。
——韩寒


43、每个男人心中都有一个狐狸精。
——王小峰


44、我是一个LADY(女士),住在某个CITY(城市),样子像个BABY(婴儿),经常缺少MONEY(金钱),只因自己LAZY(懒惰),不太爱去STUDY(学习),偶尔感到LONELY(孤独),生活让我CRAZY(疯狂),就去上网HAPPY(快乐),最近有点HEAVY(发福),减肥不太EASY(容易),看戏喜欢FUN-NY(有趣),性格有点HURRY(急性子),我是不是有点LOVELY(可爱)?
——艳女Coco


45、双人舞是一种性爱的仪式。
——王朔


46、中国的股票是个好东西,值得收藏!“你家有股票吗?”将比“你家有宝马吗?”、“你家有别墅吗?”更值得炫耀!
——沙黾农


47、我和其他女人最大的不同在于,其他女人苦恼的是谁会来跟她约会,而我是我该跟谁去约会。
——千金小姐

48、一个天天在斗室里对着一滴水搞科研的人是危险的。朱指导总是把简单的事情搞复杂,恨不得把飞机原理运用到做风筝上。
——李承鹏评朱指导


49、主宰美国好莱坞浮沉的,有两种人:一是犹太人,一是同性恋;主宰中国演艺圈沉浮的,也有两种人:一是有关的领导,一是与领导有关的人。
——黄安


50、饭桌是万能武器,放倒围绕在它身边的那群人。每一顿饭都是鸿门宴,定位精准的解决吃饭的人。
——北京女病人


51、女人会害怕撞衫,却从不担心撞LV。
——费乐沃“YY先生语录”


52、靠身体吃饭的人,我向来都是鄙视的,他们就跟妓女一样;靠力气吃饭的人,我向来都是同情的,他们就跟老牛一样。刘翔,是个没有才华的人。他靠的就是身体,靠的就是力气,奔跑起来没有任何的技术含量,根本就不用脑子,唯一的办法就是以犀牛为榜样,使劲地往前冲,快到终点的时候一头砸下去,不管前面是空气还是大树,反正先砸下去再说。看看时间,12秒88!靠,第一,世界第一!百米飞人!于是露出了笑容,就跟傻子一样。
——张怀旧说刘翔没有文化


53、有的人想事不走脑子,他走肾。
——李承鹏说中国足球


54、部分警察抓平民很拿手,抓小偷就不行了。警察抓平民,平民抓小偷才是正确的生物链。
——韩寒


55、我是个不穿衣主义者,能不穿尽量不穿。
——海容天天


56、每个网站,甭管跟媒体沾不沾边,都好像发誓要把自己弄成大众媒体,即使弄不成《纽约时报》,至少也要弄成《太阳报》,而且只有三版,没别的版。
——keso


57、这个夜晚,所有中国职业球员、半职业球员乃至业余球员,每个人都会坚信一点:自己有能力成为中国国家队的一员……
——董路说中国队7:0战胜缅甸队


58、总有人想把方便面拉直了吃,其实他们都是证券公司的打工仔!
——沙黾农


59、春天我把一个男朋友埋到了地下,秋天的时候,我被警察叔叔埋到了地下。
——北京女病人


60、有钱绝对不是网站成功的充分条件,否则中国人民银行早就是google了;山西煤老板也可以facebook了。陈一舟几年来,吃亏就吃亏在不懂互联网。
——麦田


61、你成功了,你说什么做什么都是对的;你失败了,你的一切都是错的。
——方兴东说创业就是这样的游戏


62、我人生的信条就是:像孙子一样活几十年,熬成爷爷再死。
——赖宝杨小星


63、求求你了,一天写1万字的记者,少用点文字吧,就当我们现在还写在羊皮上,谁能忍受那么多羊皮用来写字,而让我们身体在风中受冻呢?
——刘韧说写文章要简练


64、终结者。
——刘涛结婚,博客里这样称呼自己的先生。


65、马拉多纳带球时可以做到一步一趟,高峰可以两步一趟,郝海东可以三步一趟……李毅可以六步一趟,然后就带出底线了。
——李承鹏


66、骑车去吃拉面,发现连拉面也涨价了。老板解释说,牛肉拉面涨价是因为猪肉涨价了。
——韩寒


67、中国是个熟人社会,一般人出了门,满眼陌生人,就敢疯起来,回到自己村子,就立马乖起来。
——十年砍柴


68、拉莫斯带球,他看见禁区线就像看到了床,他倒了下去……但,没有点球。
——董路


69、警察连好人都不放过,更何况坏人!这样的城市,我们生活的能不踏实吗。
——王小峰


70、傻逼,别这样,对自己好一点儿。
——罗永浩说苹果粉丝用没有右键的苹果鼠标很累


71、贱骨头硬心肠,做人的最高境界。
——北京女病人


72、宁可丢掉工作饭碗不能潜规则、宁可没有钱交房租也不能答应五十万的性交易。
——郑沛芳


73、一块纯棉布料,本来可以用来做最有创意的T恤,可你非把它做成高档西装,傻B才会埋单呢。
——keso


74、如果一块煤炭在你眼中呈现出汉白玉的景象,那,你就应该去检查一下是否有白内障。
——李承鹏说中国足球


75、谢亚龙的问题已不在于搞足球专不专业,而是做人专不专业。
——李承鹏说这是目前中国足球面临的最大专业问题


76、今天,我封杀央视。
——韩寒在博客中质疑央视的体育转播水平后,自称受到央视的“奇怪”对待:转播赛车比赛时极力避免提到自己的名字,实在避免不了便以“王睿的队友”来形容。为此,韩寒说将封杀央视。


77、昨天我的车挂不上两档,今天维修后,问题还是存在,所以基本上一直用1档和3,4档在跑。但还好车速还是比较快,加上退出比赛的朋友帮我把车调的操控比昨天好了很多,所以名次还排在第3,领先了第4名一分钟。
——这是韩寒博客上的一句话,进入了湖北一些名校联考的语文试卷,要求考生从中挑出4个语法错误。


78、以前的传说是为了吓唬小孩别去游泳,现在的传闻则是为了吸引游客去观光。
——方舟子说水怪


79、一个人的个人魅力大小取决于另一批无知者数量的多少。
——王小峰


80、有人瞎扯,有人瞎踢。
——董路说中国足球。


81、牛市不是美国名将加特林百米短跑,一眨眼就从起跑线冲刺到终点;牛市不是刘翔110米跨栏,还没来得及为他加油鼓劲他就跨过了10个栏;牛市也不是王军霞万米长跑,老是在一块平地上“兜圈子”。
——沙黾农说牛市是一场马拉松


82、2007年不买小盘买大盘,不看股评看博客。
——沙黾农


83、我是裸睡女人俞晴,我的歌曲叫裸睡。
——俞晴


84、我女人得很,刘翔英雄盖世足以驾驭我!
——二月丫头


85、它的名字更应该叫《钻戒》,而不是《色戒》。
——吴虹飞


86、在牛市中你选什么股都是对的。在熊市中最好的操作就是不操作。
——凯恩斯


87、傻X穿他的衣服都会显得聪明,因为他的衣服太有思考能力。
——洪晃夸王一杨的时装设计


88、没办法,死马当克拉玛依吧。
——董路说中国队“死亡之组”


89、如果祖德的长相打55分的话,金城武的长相最多也就57分。金城武演诸葛亮,就象是芙蓉姐姐演宋庆龄。
——宋祖德说金城武生活中长相十分平庸


90、你上网么孙子?还跃然纸上我早跃然网上!
——王朔向因为侯耀文猝死而询问是否与自己有关的记者说


91、年底三部大片的启示:《色戒》:女人靠不住《投名状》:兄弟也靠不住!《集结号》:组织更靠不住!!
——王小山博客说收到的短信,此后,“靠不住”成了年底博客们流行的盘点游戏


92、像我这样一个在食堂吃面都思考巴基斯坦局势的人,对高油价怎能坐视不管呢?
——王佩


93、实在不想露点,我遭到无数火眼金睛的观众无情的蹂躏,感觉被变相强奸。
——芙蓉姐姐参加某活动后说


94、河南人有钱了,他们一定会学上海人,上海人没钱了他们一定会像河南人。
——张怀旧


95、你什么时候见过宝马和夏利跑一样速度的?
——罗永浩评论说很多人把苹果机当成电脑中的宝马


96、这部电视剧的成功不是因为太现实,而是因为太不现实,给年轻人们营造了一个童话世界。
——十年砍柴说《奋斗》是一现代版武侠


97、每个人都用一把筛子筛选日子,像淘金人一样想看到剩下的金块。
——和菜头


98、红得都已经发焦了,冒烟了。
——杨二车娜姆博客说网友让自己成了“2007年度红人”


99、上帝是很坏的,他永远不会把运气平均分配在你的每一年里,一定要集中的发迹、集中的倒霉。
——孟静


100、日子就像手里的筹码,每个人一辈子捏着的也不过是那么几十枚,过完一年就丢出去一只。
——北京女病人
posted @ 2008-02-01 18:35 hopeshared 阅读(1671) | 评论 (0)编辑 收藏

公司的产品有点复杂,有些功能又只能通过命令行完成。

但是有的时候产品容易被默认安装到c:\Program Files\这样包含空格的路径下,那么在命令行中输入一个带空格的file path作为参数往往会出错。所以大家都流传着这样一种说法:咱们公司的产品安装的时候选择的目录千万不要包含空格。

但是,一个那么大的产品装好了,使用命令行配置的时候发现path带空格命令过不去,而卸载重装太恐怖了,怎么办?

现在有两个办法来解决这个问题

1)用缩写。比如c:\Program Files缩写为c:\Progra~1
       再来刨根问底查查这个命名是否有规则,于是找到:
                 文件夹(sub-directry)名称,以前是不允许带空白的,后来允许带空白,但由于有了空白,许多命令出现二义性,于是采用双引号括起来的办法。例如:
                    cd Documents and Settings
                按老定义 等于 CD Documents, CD 命令找不到名叫Documents 的 directry
                于是采用双引号:
                    cd “Documents and Settings“
                但用到 set PATH 时很麻烦,名字太长,双引号时常括错。于是采用8个字符缩写,即写头六个字母(略去空白),另加波浪号和1。例如:
                    "Documents and Settings“ -- DOCUME~1
                    "Local Settings" -- LOCALS~1 (注意略去空白,用了第二个词的字母,凑成六个,再加波浪号和1)。
                于是,这种方法成了规定。

       再来个十万个为什么的下一个,如果多个文件前6字符一样怎么办?为什么最后是1而不是0或者其他数字呢?看看这个例子
                假设下面是你的C盘根目录中的文件夹:
                    Program Files
                    Progra file
                    Progra zhang
                则三个目录分别表示为:C:\Progra~1; C:\Progra~2; C:\Progra~3;

2)绕过去,创建一个镜像。例如在cmd中输入  subst w: "C:\Documents and Settings\hopeshared"。然后就可以直接用w:\替代C:\Documents and Settings\hopeshared了

posted @ 2008-01-31 15:41 hopeshared 阅读(30520) | 评论 (5)编辑 收藏

僖坊:在双安那边的店,是4-5折,基本是买一送一,质量不错! 一套四件套,大号的,148,还有抱枕什么的,颜色也挺多的,68一个,买一送一,合着68两个.还有餐垫什么的,东西挺多的,颜色也挺多的.中关村津乐汇。

A02:顺义国泰,通州上科华联。中关村优价百货。

卡西欧casio手表折扣专卖店:北京西单明珠五层

NEWBALANCE(新百伦):上品折扣。灶君庙法雅。中关村津乐汇专卖店也经常有7折的货品。

NIKE KIDS:上品折扣五棵松店,5-7折。

episode:小白楼4折2件再八折.首体上品折扣3折

jessica:小白楼4折2件再八折.首体上品折扣3折

colour18:小白楼4折2件再八折.首体上品折扣3折

oxygen:小白楼4折2件再八折.首体上品折扣3折.望京华联店2层

wenkend workshop:小白楼4折2件再八折.首体上品折扣3折.望京华联店2层

三文治 sandwich :中关村一直是折扣店,有时候部分5折,有时候全场5折。
望京嘉茂购物中心,当时也是全场5折。

艾格 etam http://www.etam.com/    http://www.etam.com.cn/
复兴商业城地下,电梯下去就是;
大兴黄村的星城商厦二楼的右边,常年3折起;
石景山华联商厦艾格打折专柜,一般3-5折。
昌平国泰和阳光,常年3折起。
西三旗蓝岛金隅百货有艾格的折扣店,都是三折。

江南布衣 JNBY http://www.jnby.com/
望京华堂边上有家,总有部分五折。王府井工美大楼5层,上品折扣。

http://www.ribo-cn.com
南礼士路复兴商业城艾格对面

WEEDEND
王府井的WEEKEND打折店,新东安对面,三层。

班尼路http://www.baleno.com.hk/
王府井外文书店旁边,班尼路正价店二层。
班尼路attitude:新东安三层

NIKE和adidas:五道口易出莲花的打折店,学院路百盛,法雅,跨世。

G2000男装:甘家口商场

翠贝卡:中粮广场打折店,上品折扣。

Max Studio:在中粮的店也是常年打折的,有的才100米,也就是1折左右

耐克360:迪斯康特折扣广场,在丽泽桥和丰北路交叉那往西一点。朝阳门丰联广场地下二层365折扣广场。

滕氏:折扣店在东单协和医院往北路西,大部分都5折
大兴工业开发区滕士的厂子有它的折扣中心店,2—6折,新款六折。好像新款可以预订,上市半个月后就可以55折购买,大概是这样子。当季商品上市50天后 可通过电话订购,以55折的价格购买。地址是京开高速高米店出口出来,金星桥下往东走,第一个路口右拐,红绿灯处可以看到广告牌。电话是: 60214867

探路者:西三旗花卉市场靠西,一般8折,好好讲讲7折-7.5也可能拿到。最便宜的店是昌平的85折,其他的店一般都9折。法雅地下一层。

LEE:西单赛特,常年有199的KK!都很陈旧的款式!

the north face:在望京有家打折店,就在五环边上。鞋基本上没有41码以下的,好像是可以打到5、6折的样子,平时是10点到5点营业好像.
望京嘉茂中心一层及二层运动100店, 地址:望京广顺北大街33号C座(望京新城对面),电话:84729302

百图 butu:西单新一代6楼;通州华联  和ol一层;城乡华懋好像总是有打折的。

爱乐芬:新街口"爱乐芬"专卖常年打折;东单也有爱乐芬的常年打折店,睡衣非常好也便宜。就在灯市口的审美斜对过。

达夫妮:在北京有很多打折专卖店,东单大华影院旁边有一个。翠微大厦往西几十米的地方,路北也有一个。

美特斯邦威:清华园东门附近那有个美特斯邦威,3楼常年特价。灶君庙法雅地下一层,常年5折。上品折扣。王府井大街上的那个MB大楼的三楼也是特卖,裤子100块以下,t30元以下,衬衫40元,毛衣60-80。

PLAYBOY花花公子:在灯市口有一家常年打折店,在106站牌那边

西村名物包包:在灯市东口北侧,有个折扣店。东四。

爱慕内衣:北5环附近(望京地区)有个爱慕大厦,隔周有特卖会,剪标内衣\睡衣,很抢手,去的人得有心里准备;电话64392626,一般是周三有特卖会(隔周一次),但要当天早上打电话才知道是否有特卖,切记要早去,不然好DD都被抢光了。

Tom tailor:华宇 二层 Tom tailor店,去年款,4~5折。丰联365,最低是3折,大多都是5折的。

木真了:朝外月秀 ,大门旁边 ,“木真了”  常年打折。安贞商场西边深房大厦好像是3层,款式都比较旧,而且一般号也不全,大部分只有一两个号或者一两件。

雅戈尔:新街口。东四三友2层最里面,衬衫多,也有西服,裤子什么的。

安贞眼镜折扣店:安定门桥往北过第一个红绿灯在往北一点,在路西。

史努比 snoopy:家乐福双井店一层有史努比折扣店,但也有mm说他家折扣很少的  还没有君太或者新世界打折划算呢。中关村家乐福。

成熟女装玛斯菲尔:华宇时尚购物1_5折

李宁折扣店:东四,地坛南边三利百货。

one polar: 法雅地下一层,6折起。

奥索卡:法雅二楼,3折起。

佛罗伦萨:通州华联,原来TOm Tailor的柜台

百丽:地坛南边三利百货,上品折扣

esprit:顺义国泰2f,看着挺多5折的。燕莎奥特莱燕斯。

edc:通县华联和奥莱基本上都是五折

莱尔斯丹的常年折扣店: 牡丹园翠微。新华百货的莱尔斯丹是折扣店,基本上鞋子都在200多,包包也都是打折的,有一款黑色特大PU包,原价698,现在是199。

生活几何S&K 折扣店:王府井大街上,就在BALENO的三层

佐丹奴:新一代六层

顺美西服的折扣店:奥莱。石景山华联,顺美店全场都打折,西服基本都是6折。最贵的一身折后2200元左右。KK,三折后才144米 三折的号不全。

罗宾汉的折扣店:sparkle,kama连一起,就这三家,貌似一直打折!在劲松西街!

FA:GE和克罗地亚:在双安边上的kfc的楼上3层楼。都是半价的衣服,旧款基本上,好像各季的衣服都有。

欧迪芬:工美的上品折扣店,有打折的,是2-5折。在亚运村家和超市的上品折扣店,内衣一般几十元,底裤大部分低于30元。

法罗:中关村购物广场,30-40

阿桑娜折扣店:大兴帝园新城商厦,每周末特价。好多都是3-4折 虽然都是去年的 但是羽绒服阿还有大衣都是超级划算的呢。经典的小锚 双面羽绒服都是3折。中关村优价百货。

ebase常年折扣店:昌平国泰,不过款式不太好。朝阳区青年路那里有个店卖Ebase男装的,5折。

贝拉维拉:甘家口商厦

NAUTICA:奥莱。亚运村的上品折扣。

TOMMY HILFIGER:恒基二层有,还是折扣店,不过号不全。

派克兰蒂的折扣店:北太平庄物美,2~7折

萨侬:翠微牡丹园店

米莲诺:东四,3折起。

浩沙:地安门百货商场二层浩沙常年打折,一般5折,也有还有近于3折的衣服。上品折扣也有。工大桥的燕沙奥特莱斯里也有。丰联地下二层365折扣 价格都在5折吧,样子有的还可以。上品折扣。


化妆品折扣店:

red earth: 燕莎奥特来丝的ESPRIT专卖有折扣店


家居折扣店

科宝折扣店,5折,6.5折.7折都有,有集采号可以再打九折。广安门。听说鼓楼也有一家。
posted @ 2007-12-11 11:26 hopeshared 阅读(1045) | 评论 (1)编辑 收藏

BIRT的chart功能似乎并不是很强大,反正在使用过程中,出现过编辑器不好使,被迫直接修改xml文件的情况.

现在,有一个这样的需求,就是让Y轴的标题显示用户输入的值.



如图所示,这个标题是在编辑/定义图表的时候输入的固定值,这个值不管是改成params["test"]还是reportContext.getParameter("test")都会直接返回文本,而不是表达式值.

但是,这个文本唯一支持的动态,就是本地化,那么本文将介绍如何利用resources.properties来实现这个标题的动态显示

首先,设置这个chart的resource使用<YOUR_LOCATION>/reources.properties文件

然后,在这个properties文件中输入test_field=Hopeshared's Test

接着,修改这个title


这个时候,preview的结果,title就会显示properties文件里的内容

向report添加一个名为test的参数,这个参数是string类型,必填项,用户的输入将保存到properties文件并显示在chart中

接着,写个脚本,让properties文件内容发生变化
在整个report的initialize方法中,写入
 1importPackage(Packages.java.lang);
 2importPackage(Packages.java.util);
 3importPackage(Packages.java.io);
 4importPackage(Packages.java.net);
 5
 6var t_value = params["test"];
 7var prop=new Properties();
 8
 9var in_stream=new FileInputStream("<YOUR_PROPERTIES_FOLDER>/resources.properties");
10prop.load(in_stream);
11in_stream.close();
12        
13prop.put("test_field", t_value);
14
15var   fout=new  FileOutputStream("<YOUR_PROPERTIES_FOLDER>/resources.properties");
16prop.store(fout,"Test Properties");
17fout.close();
18
19var current = new File("<YOUR_PROPERTIES_FOLDER>");
20
21var cl = new URLClassLoader(new Array(current.toURL()));
22res = ResourceBundle.getBundle("resource", Locale.getDefault(), cl);

最后,preview一下,看看结果.

欢迎大家告诉我其他更好的办法来达到这个目标,谢谢!
posted @ 2007-11-12 13:20 hopeshared 阅读(3919) | 评论 (4)编辑 收藏

转眼2X岁了,已经不再年轻,发现从开始实习的时候,老的特别快,皮肤明显比以前衰老,皱纹和黑眼圈已经到达了无法自己修复的地步.

年纪大了,越来越喜欢安逸,害怕变化,不愿意承担任何痛苦.觉得现在的小日子过着也还马马乎乎.

生日收到了几个祝福,bf很嫉妒的说为什么他的生日没有人记得,但是我的有人惦记,还都是男的,呵呵

不过,老了,确实老了

转眼变成大龄,不过目前的状况还不是剩女.

恩,前几天装修,太累,生日也没好好过,以后不能这样了.
posted @ 2007-11-12 11:35 hopeshared 阅读(440) | 评论 (0)编辑 收藏

英文巨烂无比,从现在开始慢慢积累吧
跟鬼子们talk经常会碰到标点符号,比如报个url,这个时候/念不出来会很尴尬
so,收录一段标点符号对照表在此


! exclamation mark
@ at
# number sign

& ampersand
* asterisk
() parentheses or round brackets
– dash, figure dash(?), en dash (–), em dash (—), and quotation dash (―)
_ underscore
+ plus sign
= equal mark
[] square brackets
{} curly brackets
: colon
; semicolon
' " quotation marks
, comma
. full stop or period
< less than sign
>; greater than sign
? question mark
/ slash
~ tilde or swung dash
` prime
| vertical bar
\ backslash

不过,即使知道了每个单词是什么,一旦这个词是从印度人嘴里说出来,就完全听不懂了,看来需要加强跟他们的合作阿,以后机会多多。
奇怪的是,居然能听懂日本鬼子的大部分英文
一个是巨快的卷舌音,一个是巨慢的平舌音,呵呵

posted @ 2007-10-22 19:19 hopeshared 阅读(481) | 评论 (0)编辑 收藏

似乎从来到现在的这个组开始起,就很少写blog。

一方面应该是自己的原因,越来越懒了,工作之外大把的时间都用来打游戏看片片。即使有N多额外的事情要做。另一方面可能跟工作有关,现在的这个组需要研究的东西不多,经常会忙个一天碰不到什么棘手的难题,不需要上网四处搜索资料。

脑袋里还是经常在闲暇的时候冒出火花,很多有意思的想法,但是不愿意去实现,不再愿意去寻找相关资料,即使找了也不会去看。想想真的还挺怀念在CRL的时光。在那里受到的培养,恐怕将对我今后的很多年起到举足轻重的作用。

其实IBM还是个不错的公司。并不是因为我是这里的员工,所以在这里做宣传。而是真正的在这里工作,所以能体会到。不是说薪水很高,而是在培养和激发人的潜力,鼓励大家朝自己喜欢的方向自行研究,激励所有的员工不断学习和自强。其实从另一个角度来看,这里很辛苦,你会发现你的业余时间要用来写文章,发专利等等。毕竟,任何事情都有两个方面。毕竟我们还年轻,很辛苦的去积累总比很悠闲的荒废生命要好得多。

言归正传。很久没有看自己的blog了,人气骤减阿!
最近有很多写东西的idea,不过都在朝着写书和发表这个方向作。嘿嘿,毕竟这样是有money的嘛。我不是拜金哦,没钱怎么生活呢,对吧。没有足够的钱我怎么会有心情写blog呢,对吧。

好了,回来冒个泡。该工作了。

P.S. 最近的计划是写BIRT的文章。自己写着写着觉得发不出去。要是真发不出去,还有这里能收留呢,呵呵
posted @ 2007-07-24 14:10 hopeshared 阅读(444) | 评论 (0)编辑 收藏

先看个例子:
<xsl:call-template name="footer">
<xsl:with-param name="date" select="@lastupdate"/>
</xsl:call-template>
<xsl:template name="footer">
<xsl:param name="date">today</xsl:param>
<hr/>
<xsl:text>Last update: </xsl:text>
<xsl:value-of select="$date"/>
</xsl:template>
对xml模板 来说,name属性是很关键的 call-template /apply-template 的name必须要和模板的name
相对应。模板相当于一个函数,可以暂时这么看。而name相当于函数名称把。
在call-template中 使用xsl:with-param 相当于函数参数输入
而参数声明相当就是在xsl:template的 xsl:param
说到xsl:variable。
可以用<xsl:variable name="ShowDepth"><计算的值></xsl:variable>来声明
相当于c中的  const 因为变量一旦声明就无法再被改变。
对于xsl:param和xsl:variable 都可以用 $+name 来直接选择比如
<xsl:value-of select="$date"/>  就是选择date变量或者参数
变量和参数,都是有声明范围的 这点和语言中的道理一样。
最后最最重要一点 :xsl的variable是常量不能再改变
				
						
								
										不要被它的名称迷惑、
								
						
				
		
posted @ 2007-03-14 16:28 hopeshared 阅读(5376) | 评论 (1)编辑 收藏

     摘要: 作者:李红霞时间:2006-10-19声明:本文可以算作Axis2用户手册的翻译,但是翻译后的文本是经过作者理解写出来的,可能有些偏差,欢迎讨论。本文属作者原创,允许转载,但请注明出处。 英文原文http://ws.apache.org/axis2/1_0/userguide.html   ...  阅读全文
posted @ 2006-10-23 16:46 hopeshared 阅读(7829) | 评论 (9)编辑 收藏

java中的类是动态加载的,我们先看一下我们常用的类加载方式,先有一个感性的认识,才能进一步
深入讨论,类加载无非就是下面三种方式。
class A{}
class B{}
class C{}
public class Loader{
    public static void main(String[] args) throws Exception{
       Class aa=A.class;
       Class bb=Class.forName("B");
       Class cc=ClassLoader.getSystemClassLoader().loadClass("C");
    }
}
我们先看.class字面量方式,很多人可能不知道这种方式,因为这种用法不是一般java语法。
通过javap我们可以发现,这种方式的大致等价于定义了一个静态成员变量
    static Class class$0;(后面的编号是增长的)
你可以试图再定义一个  static Class class$0,应该会收到一个编译错误(重复定义)。
Class aa=A.class;
就相当于
    if(class$0==null){
 try{
           Class.forName("A");
 }
 cacth(ClassNotFoundException e){
    throw new NoClassDefFoundError(e);
 }
    }
    Class aa=class$0;
可以很清楚的看到,这种类的字面量定义其实不是加载类的方式,而是被编译器处理了,实质
上是使用了Class.forName方法,但是使用这种方式有一个很大的好处就是不用处理异常,因为
编译器处理的时候如果找不到类会抛出一个NoClassDefFoundError。也许你觉得需要处理
ClassNotFoundException这种异常,事实上99%的情况下我们可以把这种异常认为是一个错误。
所以大部分情况我们使用这种方式会更简洁。
最常用的方式就是Class.forName方式了,这也是一个通用的上层调用。这个方法有两个重载,
可能很多人都忽略了第二个方法。
public static Class forName(String name) throws ClassNotFoundException
public static Class forName(String name, boolean initialize,ClassLoader loader) throws ClassNotFoundException
第二个方法后面多了两个参数,第二个参数表示是否初始化,第三个参数为指定的类加载器。
在上面的例子中:
Class bb=Class.forName("B");等价于
Class bb=Class.forName("B",true,Loader.class.getClassLoader());
这里要详细说一下这个类的初始化这个参数,如果这个参数为false的话,
类中的static成员不会被初始化,static语句块也不会被执行。
也就是类虽然被加载了,但是没有被初始化,不过在第一次使用时仍然会初始化。
所以我们有时候会看到Class.forName("XXX").newInstance()这样的语句,为什么这里要创建一个
不用的实例呢?不过是为了保证类被初始化(兼容以前的系统)。
其实第二个方法是比较难用的,需要指定类加载器,如果不指定而且又没有安装安全管理器的化,
是无法加载类的,只要看一下具体的实现就明白了。
最本质的方式当然是直接使用ClassLoader加载了,所有的类最终都是通过ClassLoader加载的,
Class cc=ClassLoader.getSystemClassLoader().loadClass("C");
这里通过使用系统类加载器来加载某个类,很直接的方式,但是很遗憾的是通过这种方式加载类,
类是没有被初始化的(也就是初始化被延迟到真正使用的时候).不过我们也可以借鉴上面的经验,加载
后实例化一个对象Class cc=ClassLoader.getSystemClassLoader().loadClass("C").newInstance()。
这里使用了系统类加载器,也是最常用的类加载器,从classpath中寻找要加载的类。
java中默认有三种类加载器:引导类加载器,扩展类加载器,系统类加载器。
java中的类加载有着规范的层次结构,如果我们要了解类加载的过程,需要明确知道哪个类被谁
加载,某个类加载器加载了哪些类等等,就需要深入理解ClassLoader的本质。
以上只是类加载的表面的东西,我们还将讨论深层次的东西。

原文:http://dev.csdn.net/author/treeroot/a481eb323af84caab1149221432e46b9.html

posted @ 2006-10-23 16:20 hopeshared 阅读(896) | 评论 (0)编辑 收藏

     摘要: [文章信息] 作者: cqfz 时间: ...  阅读全文
posted @ 2006-10-23 16:19 hopeshared 阅读(1868) | 评论 (0)编辑 收藏

  类加载是java语言提供的最强大的机制之一。尽管类加载并不是讨论的热点话题,但所有的编程人员都应该了解其工作机制,明白如何做才能让其满足我们的需要。这能有效节省我们的编码时间,从不断调试ClassNotFoundException, ClassCastException的工作中解脱出来。

  这篇文章从基础讲起,比如代码与数据的不同之处是什么,他们是如何构成一个实例或对象的。然后深入探讨java虚拟机(

JVM)是如何利用类加载器读取代码,以及java中类加载器的主要类型。接着用一个类加载的基本算法看一下类加载器如何加载一个内部类。本文的下一节演示一段代码来说明扩展和开发属于自己的类加载器的必要性。紧接着解释如何使用定制的类加载器来完成一个一般意义上的任务,使其可以加载任意远端客户的代码,在JVM中定义,实例化并执行它。本文包括了J2EE关于类加载的规范——事实上这已经成为了J2EE的标准之一。

  类与数据

  一个类代表要执行的代码,而数据则表示其相关状态。状态时常改变,而代码则不会。当我们将一个特定的状态与一个类相对应起来,也就意味着将一个类事例化。尽管相同的类对应的实例其状态千差万别,但其本质都对应着同一段代码。在JAVA中,一个类通常有着一个.class文件,但也有例外。在JAVA的运行时环境中(Java runtime),每一个类都有一个以第一类(first-class)的Java对象所表现出现的代码,其是java.lang.Class的实例。我们编译一个JAVA文件,编译器都会嵌入一个public, static, final修饰的类型为java.lang.Class,名称为class的域变量在其字节码文件中。因为使用了public修饰,我们可以采用如下的形式对其访问:

  java.lang.Class klass = Myclass.class;

  一旦一个类被载入JVM中,同一个类就不会被再次载入了(切记,同一个类)。这里存在一个问题就是什么是“同一个类”?正如一个对象有一个具体的状态,即标识,一个对象始终和其代码(类)相关联。同理,载入JVM的类也有一个具体的标识,我们接下来看。

  在JAVA中,一个类用其完全匹配类名(fully qualified class name)作为标识,这里指的完全匹配类名包括包名和类名。但在JVM中一个类用其全名和一个加载类ClassLoader的实例作为唯一标识。因此,如果一个名为Pg的包中,有一个名为Cl的类,被类加载器KlassLoader的一个实例kl1加载,Cl的实例,即C1.class在JVM中表示为(Cl, Pg, kl1)。这意味着两个类加载器的实例(Cl, Pg, kl1) 和 (Cl, Pg, kl2)是不同的,被它们所加载的类也因此完全不同,互不兼容的。那么在JVM中到底有多少种类加载器的实例?下一节我们揭示答案。

  类加载器

  在JVM中,每一个类都被java.lang.ClassLoader的一些实例来加载.类ClassLoader是在包中java.lang里,开发者可以自由地继承它并添加自己的功能来加载类。

  无论何时我们键入java MyMainClass来开始运行一个新的JVM,“引导类加载器(bootstrap class loader)”负责将一些关键的Java类,如java.lang.Object和其他一些运行时代码先加载进内存中。运行时的类在JRE\lib\rt.jar包文件中。因为这属于系统底层执行动作,我们无法在JAVA文档中找到引导类加载器的工作细节。基于同样的原因,引导类加载器的行为在各JVM之间也是大相径庭。

  同理,如果我们按照如下方式:

  log(java.lang.String.class.getClassLoader());

  来获取java的核心运行时类的加载器,就会得到null。

  接下来介绍java的扩展类加载器。扩展库提供比java运行代码更多的特性,我们可以把扩展库保存在由java.ext.dirs属性提供的路径中。

  (编辑注:java.ext.dirs属性指的是系统属性下的一个key,所有的系统属性可以通过System.getProperties()方法获得。在编者的系统中,java.ext.dirs的value是” C:\Program Files\Java\jdk1.5.0_04\jre\lib\ext”。下面将要谈到的如java.class.path也同属系统属性的一个key。)

  类ExtClassLoader专门用来加载所有java.ext.dirs下的.jar文件。开发者可以通过把自己的.jar文件或库文件加入到扩展目录的classpath,使其可以被扩展类加载器读取。

  从开发者的角度,第三种同样也是最重要的一种类加载器是AppClassLoader。这种类加载器用来读取所有的对应在java.class.path系统属性的路径下的类。

  Sun的java指南中,文章“理解扩展类加载”(Understanding Extension Class Loading)对以上三个类加载器路径有更详尽的解释,这是其他几个JDK中的类加载器

  ●java.net.URLClassLoader
  ●java.security.SecureClassLoader
  ●java.rmi.server.RMIClassLoader
  ●sun.applet.AppletClassLoader

  java.lang.Thread,包含了public ClassLoader getContextClassLoader()方法,这一方法返回针对一具体线程的上下文环境类加载器。此类加载器由线程的创建者提供,以供此线程中运行的代码在需要加载类或资源时使用。如果此加载器未被建立,缺省是其父线程的上下文类加载器。原始的类加载器一般由读取应用程序的类加载器建立。

  类加载器如何工作?

  除了引导类加载器,所有的类加载器都有一个父类加载器,不仅如此,所有的类加载器也都是java.lang.ClassLoader类型。以上两种类加载器是不同的,而且对于开发者自订制的类加载器的正常运行也至关重要。最重要的方面是正确设置父类加载器。任何类加载器,其父类加载器是加载该类加载器的类加载器实例。(记住,类加载器本身也是一个类!)

  使用loadClass()方法可以从类加载器中获得该类。我们可以通过java.lang.ClassLoader的源代码来了解该方法工作的细节,如下:

protected synchronized Class<?> loadClass
    (String name, boolean resolve)
    throws ClassNotFoundException{

    // First check if the class is already loaded
    Class c = findLoadedClass(name);
    if (c == null) {
        try {
            if (parent != null) {
                c = parent.loadClass(name, false);
            } else {
                c = findBootstrapClass0(name);
            }
        } catch (ClassNotFoundException e) {
            // If still not found, then invoke
            // findClass to find the class.
            c = findClass(name);
        }
    }
    if (resolve) {
            resolveClass(c);
    }
    return c;
}

  我们可以使用ClassLoader的两种构造方法来设置父类加载器:

public class MyClassLoader extends ClassLoader{

    public MyClassLoader(){
        super(MyClassLoader.class.getClassLoader());
    }
}

  或

public class MyClassLoader extends ClassLoader{

    public MyClassLoader(){
        super(getClass().getClassLoader());
    }
}

  第一种方式较为常用,因为通常不建议在构造方法里调用getClass()方法,因为对象的初始化只是在构造方法的出口处才完全完成。因此,如果父类加载器被正确建立,当要示从一个类加载器的实例获得一个类时,如果它不能找到这个类,它应该首先去访问其父类。如果父类不能找到它(即其父类也不能找不这个类,等等),而且如果findBootstrapClass0()方法也失败了,则调用findClass()方法。findClass()方法的缺省实现会抛出ClassNotFoundException,当它们继承java.lang.ClassLoader来订制类加载器时开发者需要实现这个方法。findClass()的缺省实现方式如下:

    protected Class<?> findClass(String name)
        throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }

  在findClass()方法内部,类加载器需要获取任意来源的字节码。来源可以是文件系统,URL,数据库,可以产生字节码的另一个应用程序,及其他类似的可以产生java规范的字节码的来源。你甚至可以使用BCEL (Byte Code Engineering Library:字节码工程库),它提供了运行时创建类的捷径。BCEL已经被成功地使用在以下方面:编译器,优化器,混淆器,代码产生器及其他分析工具。一旦字节码被检索,此方法就会调用defineClass()方法,此行为对不同的类加载实例是有差异的。因此,如果两个类加载实例从同一个来源定义一个类,所定义的结果是不同的。

  JAVA语言规范(Java language specification)详细解释了JAVA执行引擎中的类或接口的加载(loading),链接(linking)或初始化(initialization)过程。

  图一显示了一个主类称为MyMainClass的应用程序。依照之前的阐述,MyMainClass.class会被AppClassLoader加载。 MyMainClass创建了两个类加载器的实例:CustomClassLoader1 和 CustomClassLoader2,他们可以从某数据源(比如网络)获取名为Target的字节码。这表示类Target的类定义不在应用程序类路径或扩展类路径。在这种情况下,如果MyMainClass想要用自定义的类加载器加载Target类,CustomClassLoader1和CustomClassLoader2会分别独立地加载并定义Target.class类。这在java中有重要的意义。如果Target类有一些静态的初始化代码,并且假设我们只希望这些代码在JVM中只执行一次,而这些代码在我们目前的步骤中会执行两次——分别被不同的CustomClassLoaders加载并执行。如果类Target被两个CustomClassLoaders加载并创建两个实例Target1和Target2,如图一显示,它们不是类型兼容的。换句话说,在JVM中无法执行以下代码:

  Target target3 = (Target) target2;

  以上代码会抛出一个ClassCastException。这是因为JVM把他们视为分别不同的类,因为他们被不同的类加载器所定义。这种情况当我们不是使用两个不同的类加载器CustomClassLoader1 和 CustomClassLoader2,而是使用同一个类加载器CustomClassLoader的不同实例时,也会出现同样的错误。这些会在本文后边用具体代码说明。

  图1. 在同一个JVM中多个类加载器加载同一个目标类

  关于类加载、定义和链接的更多解释,请参考Andreas Schaefer的"Inside Class Loaders."

  为什么我们需要我们自己的类加载器

  原因之一为开发者写自己的类加载器来控制JVM中的类加载行为,java中的类靠其包名和类名来标识,对于实现了java.io.Serializable接口的类,serialVersionUID扮演了一个标识类版本的重要角色。这个唯一标识是一个类名、接口名、成员方法及属性等组成的一个64位的哈希字段,而且也没有其他快捷的方式来标识一个类的版本。严格说来,如果以上的都匹配,那么则属于同一个类。

  但是让我们思考如下情况:我们需要开发一个通用的执行引擎。可以执行实现某一特定接口的任何任务。当任务被提交到这个引擎,首先需要加载这个任务的代码。假设不同的客户对此引擎提交了不同的任务,凑巧,这些所有的任务都有一个相同的类名和包名。现在面临的问题就是这个引擎是否可以针对不同的用户所提交的信息而做出不同的反应。这一情况在下文的参考一节有可供下载的代码样例,samepath 和 differentversions,这两个目录分别演示了这一概念。

  图2 显示了文件目录结构,有三个子目录samepath, differentversions, 和 differentversionspush,里边是例子:

  图2. 文件夹结构组织示例

  在samepath 中,类version.Version保存在v1和v2两个子目录里,两个类具有同样的类名和包名,唯一不同的是下边这行:

    public void fx(){
        log("this = " + this + "; Version.fx(1).");
    }

  V1中,日志记录中有Version.fx(1),而在v2中则是Version.fx(2)。把这个两个存在细微不同的类放在一个classpath下,然后运行Test类:

  set CLASSPATH=.;%CURRENT_ROOT%\v1;%CURRENT_ROOT%\v2

  %JAVA_HOME%\bin\java Test

  图3显示了控制台输出。我们可以看到对应着Version.fx(1)的代码被执行了,因为类加载器在classpath首先看到此版本的代码。

  图3. 在类路径中samepath测试排在最前面的version 1

  再次运行,类路径做如下微小改动。

  set CLASSPATH=.;%CURRENT_ROOT%\v2;%CURRENT_ROOT%\v1

  %JAVA_HOME%\bin\java Test

  控制台的输出变为图4。对应着Version.fx(2)的代码被加载,因为类加载器在classpath中首先找到它的路径。

  图4. 在类路径中samepath测试排在最前面的version 2

  根据以上例子可以很明显地看出,类加载器加载在类路径中被首先找到的元素。如果我们在v1和v2中删除了version.Version,做一个非version.Version形式的.jar文件,如myextension.jar,把它放到对应java.ext.dirs的路径下,再次执行后看到version.Version不再被AppClassLoader加载,而是被扩展类加载器加载。如图5所示。

  图5. AppClassLoader及ExtClassLoader

  继续这个例子,文件夹differentversions包含了一个RMI执行引擎,客户端可以提供给执行引擎任何实现了common.TaskIntf接口的任务。子文件夹client1 和 client2包含了类client.TaskImpl有个细微不同的两个版本。两个类的区别在以下几行:

    static{
        log("client.TaskImpl.class.getClassLoader
        (v1) : " + TaskImpl.class.getClassLoader());
    }

    public void execute(){
        log("this = " + this + "; execute(1)");
    }

  在client1和client2里分别有getClassLoader(v1) 与 execute(1)和getClassLoader(v2) 与 execute(2)的的log语句。并且,在开始执行引擎RMI服务器的代码中,我们随意地将client2的任务实现放在类路径的前面。

  CLASSPATH=%CURRENT_ROOT%\common;%CURRENT_ROOT%\server;

  %CURRENT_ROOT%\client2;%CURRENT_ROOT%\client1

  %JAVA_HOME%\bin\java server.Server

  如图6,7,8的屏幕截图,在客户端VM,各自的client.TaskImpl类被加载、实例化,并发送到服务端的VM来执行。从服务端的控制台,可以明显看到client.TaskImpl代码只被服务端的VM执行一次,这个单一的代码版本在服务端多次生成了许多实例,并执行任务。

  图6. 执行引擎服务器控制台

  图6显示了服务端的控制台,加载并执行两个不同的客户端的请求,如图7,8所示。需要注意的是,代码只被加载了一次(从静态初始化块的日志中也可以明显看出),但对于客户端的调用这个方法被执行了两次。

  图7. 执行引擎客户端 1控制台

  图7中,客户端VM加载了含有client.TaskImpl.class.getClassLoader(v1)的日志内容的类TaskImpl的代码,并提供给服务端的执行引擎。图8的客户端VM加载了另一个TaskImpl的代码,并发送给服务端。

  图8. 执行引擎客户端 2控制台

  在客户端的VM中,类client.TaskImpl被分别加载,初始化,并发送到服务端执行。图6还揭示了client.TaskImpl的代码只在服务端的VM中加载了一次,但这“唯一的一次”却在服务端创造了许多实例并执行。或许客户端1该不高兴了因为并不是它的client.TaskImpl(v1)的方法调用被服务端执行了,而是其他的一些代码。如何解决这一问题?答案就是实现定制的类加载器。

  定制类加载器

  要较好地控制类的加载,就要实现定制的类加载器。所有自定义的类加载器都应继承自java.lang.ClassLoader。而且在构造方法中,我们也应该设置父类加载器。然后重写findClass()方法。differentversionspush文件夹包含了一个叫做FileSystemClassLoader的自订制的类加载器。其结构如图9所示。

  图9. 定制类加载器关系

  以下是在common.FileSystemClassLoader实现的主方法:

public byte[] findClassBytes(String className){

        try{
            String pathName = currentRoot +
                File.separatorChar + className.
                replace('.', File.separatorChar)
                + ".class";
            FileInputStream inFile = new
                FileInputStream(pathName);
            byte[] classBytes = new
                byte[inFile.available()];
            inFile.read(classBytes);
            return classBytes;
        }
        catch (java.io.IOException ioEx){
            return null;
        }
    }

    public Class findClass(String name)throws
        ClassNotFoundException{

        byte[] classBytes = findClassBytes(name);
        if (classBytes==null){
            throw new ClassNotFoundException();
        }
        else{
            return defineClass(name, classBytes,
                0, classBytes.length);
        }
    }

    public Class findClass(String name, byte[]
        classBytes)throws ClassNotFoundException{

        if (classBytes==null){
            throw new ClassNotFoundException(
                "(classBytes==null)");
        }
        else{
            return defineClass(name, classBytes,
                0, classBytes.length);
        }
    }

    public void execute(String codeName,
        byte[] code){

        Class klass = null;
        try{
            klass = findClass(codeName, code);
            TaskIntf task = (TaskIntf)
                klass.newInstance();
            task.execute();
        }
        catch(Exception exception){
            exception.printStackTrace();
        }
    }

  这个类供客户端把client.TaskImpl(v1)转换成字节数组,之后此字节数组被发送到RMI服务端。在服务端,一个同样的类用来把字节数组的内容转换回代码。客户端代码如下:

public class Client{

    public static void main (String[] args){

        try{
            byte[] code = getClassDefinition
                ("client.TaskImpl");
            serverIntf.execute("client.TaskImpl",
                code);
            }
            catch(RemoteException remoteException){
                remoteException.printStackTrace();
            }
        }

    private static byte[] getClassDefinition
        (String codeName){
        String userDir = System.getProperties().
            getProperty("BytePath");
        FileSystemClassLoader fscl1 = null;

        try{
            fscl1 = new FileSystemClassLoader
                (userDir);
        }
        catch(FileNotFoundException
            fileNotFoundException){
            fileNotFoundException.printStackTrace();
        }
        return fscl1.findClassBytes(codeName);
    }
}

  在执行引擎中,从客户端收到的代码被送到定制的类加载器中。定制的类加载器把其从字节数组定义成类,实例化并执行。需要指出的是,对每一个客户请求,我们用类FileSystemClassLoader的不同实例来定义客户端提交的client.TaskImpl。而且,client.TaskImpl并不在服务端的类路径中。这也就意味着当我们在FileSystemClassLoader调用findClass()方法时,findClass()调用内在的defineClass()方法。类client.TaskImpl被特定的类加载器实例所定义。因此,当FileSystemClassLoader的一个新的实例被使用,类又被重新定义为字节数组。因此,对每个客户端请求类client.TaskImpl被多次定义,我们就可以在相同执行引擎JVM中执行不同的client.TaskImpl的代码。

public void execute(String codeName, byte[] code)throws RemoteException{

        FileSystemClassLoader fileSystemClassLoader = null;

        try{
            fileSystemClassLoader = new FileSystemClassLoader();
            fileSystemClassLoader.execute(codeName, code);
        }
        catch(Exception exception){
            throw new RemoteException(exception.getMessage());
        }
    }

  示例在differentversionspush文件夹下。服务端和客户端的控制台界面分别如图10,11,12所示:

  图10. 定制类加载器执行引擎

  图10显示的是定制的类加载器控制台。我们可以看到client.TaskImpl的代码被多次加载。实际上针对每一个客户端,类都被加载并初始化。

  图11. 定制类加载器,客户端1

  图11中,含有client.TaskImpl.class.getClassLoader(v1)的日志记录的类TaskImpl的代码被客户端的VM加载,然后送到服务端。图12 另一个客户端把包含有client.TaskImpl.class.getClassLoader(v1)的类代码加载并送往服务端。

  图12. 定制类加载器,客户端1

  这段代码演示了我们如何利用不同的类加载器实例来在同一个VM上执行不同版本的代码。

  J2EE的类加载器

  J2EE的服务器倾向于以一定间隔频率,丢弃原有的类并重新载入新的类。在某些情况下会这样执行,而有些情况则不。同样,对于一个web服务器如果要丢弃一个servlet实例,可能是服务器管理员的手动操作,也可能是此实例长时间未相应。当一个JSP页面被首次请求,容器会把此JSP页面翻译成一个具有特定形式的servlet代码。一旦servlet代码被创建,容器就会把这个servlet翻译成class文件等待被使用。对于提交给容器的每次请求,容器都会首先检查这个JSP文件是否刚被修改过。是的话就重新翻译此文件,这可以确保每次的请求都是及时更新的。企业级的部署方案以.ear, .war, .rar等形式的文件,同样需要重复加载,可能是随意的也可能是依照某种配置方案定期执行。对所有的这些情况——类的加载、卸载、重新加载……全部都是建立在我们控制应用服务器的类加载机制的基础上的。实现这些需要扩展的类加载器,它可以执行由其自身所定义的类。Brett Peterson已经在他的文章 Understanding J2EE Application Server Class Loading Architectures给出了J2EE应用服务器的类加载方案的详细说明,详见网站TheServerSide.com。

  结要

  本文探讨了类载入到虚拟机是如何进行唯一标识的,以及类如果存在同样的类名和包名时所产生的问题。因为没有一个直接可用的类版本管理机制,所以如果我们要按自己的意愿来加载类时,需要自己订制类加载器来扩展其行为。我们可以利用许多J2EE服务器所提供的“热部署”功能来重新加载一个新版本的类,而不改动服务器的VM。即使不涉及应用服务器,我们也可以利用定制类加载器来控制java应用程序载入类时的具体行为。Ted Neward的书Server-Based Java Programming中详细阐述java的类加载,J2EE的API以及使用他们的最佳途径。


原文:http://searchwebservices.techtarget.com.cn/tips/362/2158862.shtml

posted @ 2006-10-23 16:16 hopeshared 阅读(1513) | 评论 (0)编辑 收藏

内容:

对象池化技术
Jakarta Commons Pool组件
下载和安装
PoolableObjectFactory、ObjectPool和ObjectPoolFactory
创立PoolableObjectFactory
使用ObjectPool
利用ObjectPoolFactory
借助BasePoolableObjectFactory
各式各样的ObjectPool
StackObjectPool
SoftReferenceObjectPool
GenericObjectPool
GenericObjectPool.Config
带键值的对象池
当出借少于归还
线程安全问题
什么时候不要池化
结束语
参考资料
关于作者
对于本文的评价

孙海涛 (alexhsun@hotmail.com)

2003 年 12 月

恰当地使用对象池化技术,可以有效地减少对象生成和初始化时的消耗,提高系统的运行效率。Jakarta Commons Pool组件提供了一整套用于实现对象池化的框架,以及若干种各具特色的对象池实现,可以有效地减少处理对象池化时的工作量,为其它重要的工作留下更多的精力和时间。
创建新的对象并初始化的操作,可能会消耗很多的时间。在这种对象的初始化工作包含了一些费时的操作(例如,从一台位于20,000千米以外的主机上读出一些数据)的时候,尤其是这样。在需要大量生成这样的对象的时候,就可能会对性能造成一些不可忽略的影响。要缓解这个问题,除了选用更好的硬件和更棒的虚拟机以外,适当地采用一些能够减少对象创建次数的编码技巧,也是一种有效的对策。对象池化技术(Object Pooling)就是这方面的著名技巧,而Jakarta Commons Pool组件则是处理对象池化的得力外援。

对象池化技术
对象池化的基本思路是:将用过的对象保存起来,等下一次需要这种对象的时候,再拿出来重复使用,从而在一定程度上减少频繁创建对象所造成的开销。用于充当保存对象的“容器”的对象,被称为“对象池”(Object Pool,或简称Pool)。

对于没有状态的对象(例如String),在重复使用之前,无需进行任何处理;对于有状态的对象(例如StringBuffer),在重复使用之前,就需要把它们恢复到等同于刚刚生成时的状态。由于条件的限制,恢复某个对象的状态的操作不可能实现了的话,就得把这个对象抛弃,改用新创建的实例了。

并非所有对象都适合拿来池化??因为维护对象池也要造成一定开销。对生成时开销不大的对象进行池化,反而可能会出现“维护对象池的开销”大于“生成新对象的开销”,从而使性能降低的情况。但是对于生成时开销可观的对象,池化技术就是提高性能的有效策略了。

说明:英语中的Pool除了“池”之外,还有“供多方共享的资源”意思。作者十分怀疑第二种才是“Object Pool”中的Pool的实际含义,但是“对象池”的说法已经广为流传,而一时又没有足以替代的贴切译法,因此这里仍然沿用这种译名。
Jakarta Commons Pool组件


Jakarta Commons Pool是一个用于在Java程序中实现对象池化的组件。它的基本情况是:

主要作者:Morgan Delagrange、Geir Magnusson、Craig McClanahan、Rodney Waldhoff、David Weinrich和Dirk Verbeeck


最新版本:1.1


所含包数:2个(org.apache.commons.pool和org.apache.commons.pool.impl)


所含类数:21个(其中有4个抽象类和6个接口)


适用平台:Java 2, Standard Edition.


单纯地使用Pool组件不需要太多的Java 2的知识和经验,对语法和基本概念(对象、异常、类、接口、实例、继承和实现等)有一般了解即可。


下载和安装
为了顺利的按照本文中提到的方法使用Pool组件,除去Java 2 SDK外,还需要先准备下列一些东西:

Jakarta Commons Pool

所需版本:1.0.1+
下载地址: http://jakarta.apache.org/commons/pool
作用:处理对象池化
Jakarta Commons Collections

所需版本:2.1+
下载地址: http://jakarta.apache.org/commons/collections
作用:支持Jakarta Commons Pool的运行
以上两种软件均有已编译包和源代码包两种形式可供选择。一般情况下,使用已编译包即可。不过建议同时也下载源代码包,作为参考资料使用。

如果打算使用源代码包自行编译,那么还需要准备以下一些东西:

Ant

所需版本:1.5.3+
下载地址: http://ant.apache.org
作用:运行编译用脚本
JUnit

所需版本:3.8.1+
下载地址: http://www.junit.org
作用:编译和运行单元测试
具体的编译方法,可以参看有关的Ant文档。

将解压或编译后得到的commons-pool.jar和commons-collections.jar放入CLASSPATH,就可以开始使用Pool组件了。

PoolableObjectFactory、ObjectPool和ObjectPoolFactory
在Pool组件中,对象池化的工作被划分给了三类对象:

PoolableObjectFactory用于管理被池化的对象的产生、激活、挂起、校验和销毁;


ObjectPool用于管理要被池化的对象的借出和归还,并通知PoolableObjectFactory完成相应的工作;


ObjectPoolFactory则用于大量生成相同类型和设置的ObjectPool。


相应地,使用Pool组件的过程,也大体可以划分成“创立PoolableObjectFactory”、“使用ObjectPool”和可选的“利用ObjectPoolFactory”三种动作。

创立PoolableObjectFactory
Pool组件利用PoolableObjectFactory来照看被池化的对象。ObjectPool的实例在需要处理被池化的对象的产生、激活、挂起、校验和销毁工作时,就会调用跟它关联在一起的PoolableObjectFactory实例的相应方法来操作。

PoolableObjectFactory是在org.apache.commons.pool包中定义的一个接口。实际使用的时候需要利用这个接口的一个具体实现。Pool组件本身没有包含任何一种PoolableObjectFactory实现,需要根据情况自行创立。

创立PoolableObjectFactory的大体步骤是:

创建一个实现了PoolableObjectFactory接口的类。


import org.apache.commons.pool.PoolableObjectFactory;

public class PoolableObjectFactorySample
implements PoolableObjectFactory {
private static int counter = 0;
}




为这个类添加一个Object makeObject()方法。这个方法用于在必要时产生新的对象。


public Object makeObject() throws Exception {
Object obj = String.valueOf(counter++);
System.err.println("Making Object " + obj);
return obj;
}





为这个类添加一个void activateObject(Object obj)方法。这个方法用于将对象“激活”??设置为适合开始使用的状态。

public void activateObject(Object obj) throws Exception {
System.err.println("Activating Object " + obj);
}





为这个类添加一个void passivateObject(Object obj)方法。这个方法用于将对象“挂起”??设置为适合开始休眠的状态。

public void passivateObject(Object obj) throws Exception {
System.err.println("Passivating Object " + obj);
}





为这个类添加一个boolean validateObject(Object obj)方法。这个方法用于校验一个具体的对象是否仍然有效,已失效的对象会被自动交给destroyObject方法销毁

public boolean validateObject(Object obj) {
boolean result = (Math.random() > 0.5);
System.err.println("Validating Object "
+ obj + " : " + result);
return result;
}




为这个类添加一个void destroyObject(Object obj)方法。这个方法用于销毁被validateObject判定为已失效的对象。

public void destroyObject(Object obj) throws Exception {
System.err.println("Destroying Object " + obj);
}




最后完成的PoolableObjectFactory类似这个样子:


PoolableObjectFactorySample.java



import org.apache.commons.pool.PoolableObjectFactory;

public class PoolableObjectFactorySample
implements PoolableObjectFactory {
private static int counter = 0;

public Object makeObject() throws Exception {
Object obj = String.valueOf(counter++);
System.err.println("Making Object " + obj);
return obj;
}

public void activateObject(Object obj) throws Exception {
System.err.println("Activating Object " + obj);
}

public void passivateObject(Object obj) throws Exception {
System.err.println("Passivating Object " + obj);
}

public boolean validateObject(Object obj) {
/* 以1/2的概率将对象判定为失效 */
boolean result = (Math.random() > 0.5);
System.err.println("Validating Object "
+ obj + " : " + result);
return result;
}

public void destroyObject(Object obj) throws Exception {
System.err.println("Destroying Object " + obj);
}
}




使用ObjectPool
有了合适的PoolableObjectFactory之后,便可以开始请出ObjectPool来与之同台演出了。

ObjectPool是在org.apache.commons.pool包中定义的一个接口,实际使用的时候也需要利用这个接口的一个具体实现。Pool组件本身包含了若干种现成的ObjectPool实现,可以直接利用。如果都不合用,也可以根据情况自行创建。具体的创建方法,可以参看Pool组件的文档和源码。

ObjectPool的使用方法类似这样:

生成一个要用的PoolableObjectFactory类的实例。

PoolableObjectFactory factory = new PoolableObjectFactorySample();




利用这个PoolableObjectFactory实例为参数,生成一个实现了ObjectPool接口的类(例如StackObjectPool)的实例,作为对象池。

ObjectPool pool = new StackObjectPool(factory);




需要从对象池中取出对象时,调用该对象池的Object borrowObject()方法。

Object obj = null;
obj = pool.borrowObject();




需要将对象放回对象池中时,调用该对象池的void returnObject(Object obj)方法。

pool.returnObject(obj);




当不再需要使用一个对象池时,调用该对象池的void close()方法,释放它所占据的资源。

pool.close();




这些操作都可能会抛出异常,需要另外处理。

比较完整的使用ObjectPool的全过程,可以参考这段代码:


ObjectPoolSample.java



import org.apache.commons.pool.ObjectPool;
import org.apache.commons.pool.PoolableObjectFactory;
import org.apache.commons.pool.impl.StackObjectPool;

public class ObjectPoolSample {

public static void main(String[] args) {
Object obj = null;
PoolableObjectFactory factory
= new PoolableObjectFactorySample();
ObjectPool pool = new StackObjectPool(factory);
try {
for(long i = 0; i < 100 ; i++) {
System.out.println("== " + i + " ==");
obj = pool.borrowObject();
System.out.println(obj);
pool.returnObject(obj);
}
obj = null;//明确地设为null,作为对象已归还的标志
}
catch (Exception e) {
e.printStackTrace();
}
finally {
try{
if (obj != null) {//避免将一个对象归还两次
pool.returnObject(obj);
}
pool.close();
}
catch (Exception e){
e.printStackTrace();
}
}
}
}




另外,ObjectPool接口还定义了几个可以由具体的实现决定要不要支持的操作,包括:

void clear()

清除所有当前在此对象池中休眠的对象。

int getNumActive()

返回已经从此对象池中借出的对象的总数。

int getNumIdle()

返回当前在此对象池中休眠的对象的数目。

void setFactory(PoolableObjectFactory factory)

将当前对象池与参数中给定的PoolableObjectFactory相关联。如果在当前状态下,无法完成这一操作,会有一个IllegalStateException异常抛出。

如果所用的ObjectPool实现不支持这些操作,那么调用这些方法的时候,会抛出一个UnsupportedOperationException异常。
利用ObjectPoolFactory
有时候,要在多处生成类型和设置都相同的ObjectPool。如果在每个地方都重写一次调用相应构造方法的代码,不但比较麻烦,而且日后修改起来,也有所不便。这种时候,正是使用ObjectPoolFactory的时机。

ObjectPoolFactory是一个在org.apache.commons.pool中定义的接口,它定义了一个称为ObjectPool createPool()方法,可以用于大量生产类型和设置都相同的ObjectPool。

Pool组件中,对每一个ObjectPool实现,都有一个对应的ObjectPoolFactory实现。它们相互之间,有一一对应的参数相同的构造方法。使用的时候,只要先用想要的参数和想用的ObjectPoolFactory实例,构造出一个ObjectPoolFactory对象,然后在需要生成ObjectPool的地方,调用这个对象的createPool()方法就可以了。日后无论想要调整所用ObjectPool的参数还是类型,只需要修改这一处,就可以大功告成了。

将《使用ObjectPool》一节中的例子,改为使用ObjectPoolFactory来生成所用的ObjectPool对象之后,基本就是这种形式:


ObjectPoolFactorySample.java


import org.apache.commons.pool.ObjectPool;
import org.apache.commons.pool.ObjectPoolFactory;
import org.apache.commons.pool.PoolableObjectFactory;
import org.apache.commons.pool.impl.StackObjectPoolFactory;

public class ObjectPoolFactorySample {

public static void main(String[] args) {
Object obj = null;
PoolableObjectFactory factory
= new PoolableObjectFactorySample();
ObjectPoolFactory poolFactory
= new StackObjectPoolFactory(factory);
ObjectPool pool = poolFactory.createPool();
try {
for(long i = 0; i < 100 ; i++) {
System.out.println("== " + i + " ==");
obj = pool.borrowObject();
System.out.println(obj);
pool.returnObject(obj);
}
obj = null;
}
catch (Exception e) {
e.printStackTrace();
}
finally {
try{
if (obj != null) {
pool.returnObject(obj);
}
pool.close();
}
catch (Exception e){
e.printStackTrace();
}
}
}
}




借助BasePoolableObjectFactory
PoolableObjectFactory定义了许多方法,可以适应多种不同的情况。但是,在并没有什么特殊需要的时候,直接实现PoolableObjectFactory接口,就要编写若干的不进行任何操作,或是始终返回true的方法来让编译通过,比较繁琐。这种时候就可以借助BasePoolableObjectFactory的威力,来简化编码的工作。

BasePoolableObjectFactory是org.apache.commons.pool包中的一个抽象类。它实现了PoolableObjectFactory接口,并且为除了makeObject之外的方法提供了一个基本的实现??activateObject、passivateObject和destroyObject不进行任何操作,而validateObject始终返回true。通过继承这个类,而不是直接实现PoolableObjectFactory接口,就可以免去编写一些只起到让编译通过的作用的代码的麻烦了。

这个例子展示了一个从BasePoolableObjectFactory扩展而来的PoolableObjectFactory:


BasePoolableObjectFactorySample.java



import org.apache.commons.pool.BasePoolableObjectFactory;

public class BasePoolableObjectFactorySample
extends BasePoolableObjectFactory {

private int counter = 0;

public Object makeObject() throws Exception {
return String.valueOf(counter++);
}
}




各式各样的ObjectPool
可口可乐公司的软饮料有可口可乐、雪碧和芬达等品种,百事可乐公司的软饮料有百事可乐、七喜和美年达等类型,而Pool组件提供的ObjectPool实现则有StackObjectPool、SoftReferenceObjectPool和GenericObjectPool等种类。

不同类型的软饮料各有各自的特点,分别适应不同消费者的口味;而不同类型的ObjectPool也各有各自的特色,分别适应不同的情况。

StackObjectPool
StackObjectPool利用一个java.util.Stack对象来保存对象池里的对象。这种对象池的特色是:

可以为对象池指定一个初始的参考大小(当空间不够时会自动增长)。
在对象池已空的时候,调用它的borrowObject方法,会自动返回新创建的实例。
可以为对象池指定一个可保存的对象数目的上限。达到这个上限之后,再向池里送回的对象会被自动送去回收。
StackObjectPool的构造方法共有六个,其中:

最简单的一个是StackObjectPool(),一切采用默认的设置,也不指明要用的PoolableObjectFactory实例。
最复杂的一个则是StackObjectPool(PoolableObjectFactory factory, int max, int init)。其中:

参数factory指明要与之配合使用的PoolableObjectFactory实例;
参数max设定可保存对象数目的上限;
参数init则指明初始的参考大小。
剩余的四个构造方法则是最复杂的构造方法在某方面的简化版本,可以根据需要选用。它们是:

StackObjectPool(int max)
StackObjectPool(int max, int init)
StackObjectPool(PoolableObjectFactory factory)
StackObjectPool(PoolableObjectFactory factory, int max)
用不带factory参数的构造方法构造的StackObjectPool实例,必须要在用它的setFactory(PoolableObjectFactory factory)方法与某一PoolableObjectFactory实例关联起来后才能正常使用。

这种对象池可以在没有Jakarta Commmons Collections组件支持的情况下正常运行。

SoftReferenceObjectPool
SoftReferenceObjectPool利用一个java.util.ArrayList对象来保存对象池里的对象。不过它并不在对象池里直接保存对象本身,而是保存它们的“软引用”(Soft Reference)。这种对象池的特色是:

可以保存任意多个对象,不会有容量已满的情况发生。
在对象池已空的时候,调用它的borrowObject方法,会自动返回新创建的实例。
可以在初始化同时,在池内预先创建一定量的对象。
当内存不足的时候,池中的对象可以被Java虚拟机回收。
SoftReferenceObjectPool的构造方法共有三个,其中:

最简单的是SoftReferenceObjectPool(),不预先在池内创建对象,也不指明要用的PoolableObjectFactory实例。
最复杂的一个则是SoftReferenceObjectPool(PoolableObjectFactory factory, int initSize)。其中:

参数factory指明要与之配合使用的PoolableObjectFactory实例
参数initSize则指明初始化时在池中创建多少个对象。
剩下的一个构造方法,则是最复杂的构造方法在某方面的简化版本,适合在大多数情况下使用。它是:

SoftReferenceObjectPool(PoolableObjectFactory factory)
用不带factory参数的构造方法构造的SoftReferenceObjectPool实例,也要在用它的setFactory(PoolableObjectFactory factory)方法与某一PoolableObjectFactory实例关联起来后才能正常使用。

这种对象池也可以在没有Jakarta Commmons Collections组件支持的情况下正常运行。

GenericObjectPool
GenericObjectPool利用一个org.apache.commons.collections.CursorableLinkedList对象来保存对象池里的对象。这种对象池的特色是:

可以设定最多能从池中借出多少个对象。
可以设定池中最多能保存多少个对象。
可以设定在池中已无对象可借的情况下,调用它的borrowObject方法时的行为,是等待、创建新的实例还是抛出异常。
可以分别设定对象借出和还回时,是否进行有效性检查。
可以设定是否使用一个单独的线程,对池内对象进行后台清理。
GenericObjectPool的构造方法共有七个,其中:

最简单的一个是GenericObjectPool(PoolableObjectFactory factory)。仅仅指明要用的PoolableObjectFactory实例,其它参数则采用默认值。
最复杂的一个是GenericObjectPool(PoolableObjectFactory factory, int maxActive, byte whenExhaustedAction, long maxWait, int maxIdle, boolean testOnBorrow, boolean testOnReturn, long timeBetweenEvictionRunsMillis, int numTestsPerEvictionRun, long minEvictableIdleTimeMillis, boolean testWhileIdle)。其中:

参数factory指明要与之配合使用的PoolableObjectFactory实例。
参数maxActive指明能从池中借出的对象的最大数目。如果这个值不是正数,表示没有限制。
参数whenExhaustedAction指定在池中借出对象的数目已达极限的情况下,调用它的borrowObject方法时的行为。可以选用的值有:
GenericObjectPool.WHEN_EXHAUSTED_BLOCK,表示等待;
GenericObjectPool.WHEN_EXHAUSTED_GROW,表示创建新的实例(不过这就使maxActive参数失去了意义);
GenericObjectPool.WHEN_EXHAUSTED_FAIL,表示抛出一个java.util.NoSuchElementException异常。
参数maxWait指明若在对象池空时调用borrowObject方法的行为被设定成等待,最多等待多少毫秒。如果等待时间超过了这个数值,则会抛出一个java.util.NoSuchElementException异常。如果这个值不是正数,表示无限期等待。
参数testOnBorrow设定在借出对象时是否进行有效性检查。
参数testOnBorrow设定在还回对象时是否进行有效性检查。
参数timeBetweenEvictionRunsMillis,设定间隔每过多少毫秒进行一次后台对象清理的行动。如果这个值不是正数,则实际上不会进行后台对象清理。
参数numTestsPerEvictionRun,设定在进行后台对象清理时,每次检查几个对象。如果这个值不是正数,则每次检查的对象数是检查时池内对象的总数乘以这个值的负倒数再向上取整的结果??也就是说,如果这个值是-2(-3、-4、-5……)的话,那么每次大约检查当时池内对象总数的1/2(1/3、1/4、1/5……)左右。
参数minEvictableIdleTimeMillis,设定在进行后台对象清理时,视休眠时间超过了多少毫秒的对象为过期。过期的对象将被回收。如果这个值不是正数,那么对休眠时间没有特别的约束。
参数testWhileIdle,则设定在进行后台对象清理时,是否还对没有过期的池内对象进行有效性检查。不能通过有效性检查的对象也将被回收。
另一个比较特别的构造方法是GenericObjectPool(PoolableObjectFactory factory, GenericObjectPool.Config config) 。其中:

参数factory指明要与之配合使用的PoolableObjectFactory实例;
参数config则指明一个包括了各个参数的预设值的对象(详见《GenericObjectPool.Config》一节)。
剩下的五个构造函数则是最复杂的构造方法在某方面的简化版本,可以根据情况选用。它们是:

GenericObjectPool(PoolableObjectFactory factory, int maxActive)
GenericObjectPool(PoolableObjectFactory factory, int maxActive, byte whenExhaustedAction, long maxWait)
GenericObjectPool(PoolableObjectFactory factory, int maxActive, byte whenExhaustedAction, long maxWait, boolean testOnBorrow, boolean testOnReturn)
GenericObjectPool(PoolableObjectFactory factory, int maxActive, byte whenExhaustedAction, long maxWait, int maxIdle)
GenericObjectPool(PoolableObjectFactory factory, int maxActive, byte whenExhaustedAction, long maxWait, int maxIdle, boolean testOnBorrow, boolean testOnReturn)
这种对象池不可以在没有Jakarta Commmons Collections组件支持的情况下运行。

GenericObjectPool.Config
调用一个有很多的参数的方法的时候,很可能将参数的位置和个数搞错,导致编译或运行时的错误;阅读包含了有很多参数的方法调用的代码的时候,也很可能因为没有搞对参数的位置和个数,产生错误的理解。因此,人们往往避免给一个方法安排太多的参数的做法(所谓的“Long Parameter List”)。不过,有些方法又确实需要许多参数才能完成工作。于是,就有人想到了一种将大批的参数封装到一个对象(称为参数对象,Parameter Object)里,然后将这个对象作为单一的参数传递的两全其美的对策。

因为生成GenericKeyedObjectPool时可供设置的特性非常之多,所以它的构造方法里也就难免会需要不少的参数。GenericKeyedObjectPool除了提供了几个超长的构造方法之外,同时也定义了一个使用参数对象的构造方法。所用参数对象的类型是GenericKeyedObjectPool.Config。

GenericKeyedObjectPool.Config定义了许多的public字段,每个对应一种可以为GenericKeyedObjectPool设置的特性,包括:

int maxActive
int maxIdle
long maxWait
long minEvictableIdleTimeMillis
int numTestsPerEvictionRun
boolean testOnBorrow
boolean testOnReturn
boolean testWhileIdle
long timeBetweenEvictionRunsMillis
byte whenExhaustedAction
这些字段的作用,与在GenericKeyedObjectPool最复杂的构造方法中与它们同名的参数完全相同。

使用的时候,先生成一个GenericKeyedObjectPool.Config对象,然后将个字段设置为想要的值,最后用这个对象作为唯一的参数调用GenericKeyedObjectPool的构造方法即可。

注意:使用有许多public字段、却没有任何方法的类,也是一个人们往往加以避免的行为(所谓的“Data Class”)。不过这次GenericKeyedObjectPool特立独行了一回。

带键值的对象池
有时候,单用对池内所有对象一视同仁的对象池,并不能解决的问题。例如,对于一组某些参数设置不同的同类对象??比如一堆指向不同地址的java.net.URL对象或者一批代表不同语句的java.sql.PreparedStatement对象,用这样的方法池化,就有可能取出不合用的对象的麻烦。

可以通过为每一组参数相同的同类对象建立一个单独的对象池来解决这个问题。但是,如果使用普通的ObjectPool来实施这个计策的话,因为普通的PoolableObjectFactory只能生产出大批设置完全一致的对象,就需要为每一组参数相同的对象编写一个单独的PoolableObjectFactory,工作量相当可观。这种时候就适合调遣Pool组件中提供的一种“带键值的对象池”来展开工作了。

Pool组件采用实现了KeyedObjectPool接口的类,来充当带键值的对象池。相应的,这种对象池需要配合实现了KeyedPoolableObjectFactory接口的类和实现了KeyedObjectPoolFactory接口的类来使用(这三个接口都在org.apache.commons.pool包中定义):

KeyedPoolableObjectFactory和PoolableObjectFactory形式如出一辙,只是每个方法都增加了一个Object key参数而已:

makeObject的参数变为(Object key)
activateObject的参数变为(Object key, Object obj)
passivateObject的参数变为(Object key, Object obj)
validateObject的参数变为Object key, Object obj)
destroyObject的参数变为(Object key, Object obj)
另外Pool组件也提供了BaseKeyedPoolableObjectFactory,用于充当和BasePoolableObjectFactory差不多的角色。

KeyedObjectPool和ObjectPool的形式大同小异,只是某些方法的参数类型发生了变化,某些方法分成了两种略有不同的版本:

用Object borrowObject(Object key)和void returnObject(Object key, Object obj)来负责对象出借和归还的动作。
用void close()来关闭不再需要的对象池。
用void clear(Object key)和void clear()来清空池中的对象,前者针对与特定键值相关联的实例,后者针对整个对象池。
用int getNumActive(Object key)和int getNumActive()来查询已借出的对象数,前者针对与特定键值相关联的实例,后者针对整个对象池。
用int getNumIdle(Object key)和int getNumIdle()来查询正在休眠的对象数,前者针对与特定键值相关联的实例,后者针对整个对象池。
用void setFactory(KeyedPoolableObjectFactory factory)来设置要用的KeyedPoolableObjectFactory实例。
void clear、int getNumActive、int getNumIdle和void setFactory的各种版本都仍然是可以由具体实现自行决定是否要支持的方法。如果所用的KeyedObjectPool实现不支持这些操作,那么调用这些方法的时候,会抛出一个UnsupportedOperationException异常。

KeyedObjectPoolFactory和ObjectPoolFactory的形式完全相同,只是所代表的对象不同而已。
这一类对象池的基本使用方法接近于这样:


KeyedObjectPoolSample.java



import org.apache.commons.pool.BaseKeyedPoolableObjectFactory;
import org.apache.commons.pool.KeyedObjectPool;
import org.apache.commons.pool.KeyedObjectPoolFactory;
import org.apache.commons.pool.KeyedPoolableObjectFactory;
import org.apache.commons.pool.impl.StackKeyedObjectPoolFactory;

class KeyedPoolableObjectFactorySample
extends BaseKeyedPoolableObjectFactory {

public Object makeObject(Object key) throws Exception {
return new String("[" + key.hashCode() + "]");
}

}

public class KeyedObjectPoolSample {
public static void main(String[] args) {
Object obj = null;
KeyedPoolableObjectFactory factory
= new KeyedPoolableObjectFactorySample();
KeyedObjectPoolFactory poolFactory
= new StackKeyedObjectPoolFactory(factory);
KeyedObjectPool pool = poolFactory.createPool();
String key = null;
try {
for (long i = 0; i < 100 ; i++) {
key = "" + (int) (Math.random() * 10);
System.out.println("== " + i + " ==");
System.out.println("Key:" + key);
obj = pool.borrowObject(key);
System.out.println("Object:" + obj);
pool.returnObject(key, obj);
obj = null;
}
}
catch (Exception e) {
e.printStackTrace();
}
finally {
try{
if (obj != null) {
pool.returnObject(key, obj);
}
pool.close();
}
catch (Exception e){
e.printStackTrace();
}
}
}
}





Pool组件自带的KeyedObjectPool的实现有StackKeyedObjectPool和GenericKeyedObjectPool两种。它们的使用方法分别与它们各自的近亲KeyedObjectPool和KeyedObjectPool基本一致,只是原来使用GenericObjectPool.Config的地方要使用GenericKeyedObjectPool.Config代替。

当出借少于归还
Java并未提供一种机制来保证两个方法被调用的次数之间呈现一种特定的关系(相等,相差一个常数,或是其它任何关系)。因此,完全可以做到建立一个ObjectPool对象,然后调用一次borrowObject方法,借出一个对象,之后重复两次returnObject方法调用,进行两次归还。而调用一个从不曾借出对象的ObjectPool的returnObject方法也并不是一个不可完成的任务。

尽管这些使用方法并不合乎returnObject的字面意思,但是Pool组件中的各个ObjectPool/KeyedObjectPool实现都不在乎这一点。它们的returnObject方法都只是单纯地召唤与当前对象池关联的PoolableObjectFactory实例,看这对象能否经受得起validateObject的考验而已。考验的结果决定了这个对象是应该拿去作passivateObject处理,而后留待重用;还是应该拿去作destroyObject处理,以免占用资源。也就是说,当出借少于归还的时候,并不会额外发生什么特别的事情(当然,有可能因为该对象池处于不接受归还对象的请求的状态而抛出异常,不过这是常规现象)。

在实际使用中,可以利用这一特性来向对象池内加入通过其它方法生成的对象。

线程安全问题
有时候可能要在多线程环境下使用Pool组件,这时候就会遇到和Pool组件的线程安全程度有关的问题。

因为ObjectPool和KeyedObjectPool都是在org.apache.commons.pool中定义的接口,而在接口中无法使用“synchronized”来修饰方法,所以,一个ObjectPool/KeyedObjectPool下的各个方法是否是同步方法,完全要看具体的实现。而且,单纯地使用了同步方法,也并不能使对象就此在多线程环境里高枕无忧。

就Pool组件中自带的几个ObjectPool/KeyedObjectPool的实现而言,它们都在一定程度上考虑了在多线程环境中使用的情况。不过还不能说它们是完全“线程安全”的。

例如,这段代码有些时候就会有一些奇怪的表现,最后输出的结果比预期的要大:


UnsafeMultiThreadPoolingSample.java



import org.apache.commons.pool.ObjectPool;
import org.apache.commons.pool.impl.StackObjectPool;

class UnsafePicker extends Thread {
private ObjectPool pool;
public UnsafePicker(ObjectPool op) {
pool = op;
}
public void run() {
Object obj = null;
try {
/* 似乎…… */
if ( pool.getNumActive() < 5 ) {
sleep((long) (Math.random() * 10));
obj = pool.borrowObject();
}
}
catch (Exception e) {
e.printStackTrace();
}
}
}

public class UnsafeMultiThreadPoolingSample {

public static void main(String[] args) {
ObjectPool pool = new StackObjectPool
(new BasePoolableObjectFactorySample());
Thread ts[] = new Thread[20];
for (int j = 0; j < ts.length; j++) {
ts[j] = new UnsafePicker(pool);
ts[j].start();
}
try {
Thread.sleep(1000);
/* 然而…… */
System.out.println("NumActive:" + pool.getNumActive());
}
catch (Exception e) {
e.printStackTrace();
}
}
}






要避免这种情况,就要进一步采取一些措施才行:


SafeMultiThreadPoolingSample.java



import org.apache.commons.pool.ObjectPool;
import org.apache.commons.pool.impl.StackObjectPool;

class SafePicker extends Thread {
private ObjectPool pool;
public SafePicker(ObjectPool op) {
pool = op;
}
public void run() {
Object obj = null;
try {
/* 略加处理 */
synchronized (pool) {
if ( pool.getNumActive() < 5 ) {
sleep((long) (Math.random() * 10));
obj = pool.borrowObject();
}
}
}
catch (Exception e) {
e.printStackTrace();
}
}
}

public class SafeMultiThreadPoolingSample {

public static void main(String[] args) {
ObjectPool pool = new StackObjectPool
(new BasePoolableObjectFactorySample());
Thread ts[] = new Thread[20];
for (int j = 0; j < ts.length; j++) {
ts[j] = new SafePicker(pool);
ts[j].start();
}
try {
Thread.sleep(1000);
System.out.println("NumActive:" + pool.getNumActive());
}
catch (Exception e) {
e.printStackTrace();
}
}
}




基本上,可以说Pool组件是线程相容的。但是要在多线程环境中使用,还需要作一些特别的处理。

什么时候不要池化
采用对象池化的本意,是要通过减少对象生成的次数,减少花在对象初始化上面的开销,从而提高整体的性能。然而池化处理本身也要付出代价,因此,并非任何情况下都适合采用对象池化。

Dr. Cliff Click在JavaOne 2003上发表的《Performance Myths Exposed》中,给出了一组其它条件都相同时,使用与不使用对象池化技术的实际性能的比较结果。他的实测结果表明:

对于类似Point这样的轻量级对象,进行池化处理后,性能反而下降,因此不宜池化;

对于类似Hashtable这样的中量级对象,进行池化处理后,性能基本不变,一般不必池化(池化会使代码变复杂,增大维护的难度);
对于类似JPanel这样的重量级对象,进行池化处理后,性能有所上升,可以考虑池化。
根据使用方法的不同,实际的情况可能与这一测量结果略有出入。在配置较高的机器和技术较强的虚拟机上,不宜池化的对象的范围可能会更大。不过,对于像网络和数据库连接这类重量级的对象来说,目前还是有池化的必要。

基本上,只在重复生成某种对象的操作成为影响性能的关键因素的时候,才适合进行对象池化。如果进行池化所能带来的性能提高并不重要的话,还是不采用对象池化技术,以保持代码的简明,而使用更好的硬件和更棒的虚拟机来提高性能为佳。

结束语
恰当地使用对象池化,可以有效地降低频繁生成某些对象所造成的开销,从而提高整体的性能。而借助Jakarta Commons Pool组件,可以有效地减少花在处理对象池化上的工作量,进而,向其它重要的工作里,投入更多的时间和精力。

参考资料

很多疑难问题的答案都可以通过查阅Pool组件的Javadoc文档和源代码的方法解决。


从 Pool组件的官方站点 上,还可以进一步得到许多有用的信息。


DBCP 是一个基于Pool组件的Java数据库连接池管理组件,同时也可以作为Pool组件的用法示例使用。


蔡学镛在 《Java夜未眠(Sleepless Java)》 中的 《测不准原理》一文里,介绍了Java中的包括“软引用”(Soft Reference)在内的各种不同的引用的特点和用处。


Martin Fowler在 《Refactoring -- Improving the Design of Existing Code》(中译本名为《重构??改善既有代码的设计》,由侯捷、熊节合译)一书的第三章《代码的坏味道(Bad Smells in Code)》中讨论了被称为“Long Parameter List”和“Data Class”的“坏味道”。并指明了一些可以用于对付这些问题的重构手法。


Brian Goetz在IBM developerWorks上发表的《Java 理论与实践:描绘线程安全性》一文中,说明了为什么单纯地使用同步方法还不能让对象就此在多线程环境里高枕无忧的原因。


Dr. Cliff Click发表在JavaOne 2003上的《Performance Myths Exposed》(Session #1522),给出了一组包括“对象池化”在内的、对“能提高Java程序性能”的做法的实际效果的测试数据,和一些恰当使用这些做法的建议。



关于作者
孙海涛从1994年6月的一个风雨交加的下午开始了他的编程生涯。目前,他的兴趣集中于Java、Web、开源软件和人机交互。但是,这并不表示他不会对其它的事物给予足够的关心和重视。可以通过 alexhsun@hotmail.com 与他取得联系。
 

原文:http://www.jspcn.net/htmlnews/11049384467341731.html
posted @ 2006-10-10 12:57 hopeshared 阅读(1858) | 评论 (0)编辑 收藏

import java.util.*;

/**
* <p>Title: 对象缓冲池---采用最近最久未使用策略管理对象,同时带有事件监听功能</p>
*<p> 工作原理
* <LI>采用集合框架(java.connection包)来实现最近最久未使用对象池</li>
* <LI>首先构造对象池、设置池的大小</li>
* <li>放置对象到池中,保存时候,池的指针指向该对象,以表明该对象最近最短被使用过</li>
* <li>当把新的对象放入到池中时候,池已经满,那把删除最久没有被使用的对象,然后放入对象</li>
* <li>当从池中读取对象时候,根据条件从池中获得对象;然后把池的指针指向该取出的对象,以表明该对象最近最短被使用过</li>
* <li>当池中有对象被清除时候(当成垃圾清除),会触发相关事件
* <li>当池被清空时候,会出发相关事件
* </p>
* <p>其他说明
* 这个类参考了org.apache.tomcat.util.collections.LRUCache的实现细节。
* 当然原代码采用Hashtable来存储池的对象列表,这里采用另外的存储方式---HashMap来存储
* </p>
* <p>Copyright: Copyright (c) 2004</p>
* <p>Company: </p>
*@see org.apache.tomcat.util.collections.LRUCache
* <li> 文件位置jakarta-tomcat-5.5.6\jakarta-tomcat-connectors\\util
* @author wdz123@hotmail.com
* @version 1.0
*/

public class LRUCacheWithListener {

/**
* 池对象的包裹类,这样便于相关处理
* **/
class CacheNode {
CacheNode prev;
CacheNode next;
Abandon value;
Object key;
public CacheNode() {
}
}

/**
* 对象池大小
**/
private int cacheSize;
/**
* 对象列表、当然可以采用泛型编程,这样就实现自动装箱、解箱(boxing/unboxing)
* **/
private HashMap nodes;

/**
* 对象池当前对象数
* **/
private int currentSize;
/**
* 第一个节点
* **/
private CacheNode first;
/***
* 最后一个节点
* **/
private CacheNode last;
/**
* 对象锁
* 实际上下面也就几个地方使用了该锁,可以用同步方法来取代调使用这个对象锁
* **/
private static int DEFAULT_SIZE = 10;
public LRUCacheWithListener() {
this(DEFAULT_SIZE);
}

public LRUCacheWithListener(int poolSize) {
cacheSize = poolSize;
currentSize = 0;
first = null; //
last = null; //
nodes = new HashMap(poolSize);
}

/**
* 读取一个对象
* ***/
public synchronized Object get(Object key) {
CacheNode node = (CacheNode) nodes.get(key);
if (node != null) {
moveToHead(node);
return node.value;
}
else {
return null;
}

}

/**
* 把指定对象移动到链表的头部
* */
private void moveToHead(CacheNode node) {
if (node == first) {
return;
}
if (node.prev != null) {
node.prev.next = node.next;
}
if (node.next != null) {
node.next.prev = node.prev;
}
if (last == node) {
last = node.prev;
}
if (first != null) {
node.next = first;
first.prev = node;
}
first = node;
node.prev = null;
if (last == null) {
last = first;
}
}

/**
* 删除池中指定对象
* **/
public synchronized Object remove(Object key) {
CacheNode node = (CacheNode) nodes.get(key);
if (node != null) {
if (node.prev != null) {
node.prev.next = node.next;
}
if (node.next != null) {
node.next.prev = node.prev;
}
if (last == node) {
last = node.prev;
}
if (first == node) {
first = node.next;
}
}
return node;

}




/****
* 放置一个对象到池中
* */
public synchronized void put(Object key, Abandon value) {

CacheNode node = (CacheNode) nodes.get(key);
if (node == null) {
//池满,删除最久没有使用的对象
if (currentSize >= cacheSize) {
if (last != null) {
nodes.remove(last.key);
}
removeLast();
}
//池没有满,直接把对象放入池中
else {
currentSize++;
}
node = getANewCacheNode();
}
node.value = value;
node.key = key;
// 把放入池的这个对象移动到链表的头部,表示最近最短被使用过
moveToHead(node);
nodes.put(key, node);

}

/**晴空池中对象
* **/
public synchronized void clear() {
if (first!=null){
Iterator i= nodes.values().iterator();
//触发事件,该池已经被清空
CacheNode n;
while(i.hasNext()){
n = (CacheNode)(i.next());
n.value.poolClear();
}
}

first = null;
last = null;

}

/***
* 获得一个新的包裹对象
* **/
private CacheNode getANewCacheNode(){
CacheNode node = new CacheNode();
return node;
}

/**
* 删除池中最久没有使用的对象
* **/
private void removeLast() {
if (last != null) {

//对象从池中被抛弃,触发事件
last.value.onAbandon();

if (last.prev != null) {
last.prev.next = null;
}
else {
first = null;
}
last = last.prev;
}

}
}

******代码2
package memorymanager.lrucache;

/**
* <p>Title: </p>
*
* <p>Description: 定义对象被抛弃和池被清空的事件接口</p>
*
* <p>Copyright: Copyright (c) 2003</p>
*
*@see org.apache.tomcat.util.collections.LRUCache
* <li> 文件位置jakarta-tomcat-5.5.6\jakarta-tomcat-connectors\\util
* @author wdz123@hotmail.com
* @version 1.0
*/
public interface Abandon {
//
public void onAbandon();
public void poolClear();
}



代码3,测试部分
package memorymanager.lrucache;

/**
* <p>Title: </p>
*
* <p>Description: 测试使用带有事件回调机制的对象缓冲池---采用最近最久未使用策略管理对象</p>
*
* <p>Copyright: Copyright (c) 2003</p>
*
*@see org.apache.tomcat.util.collections.LRUCache
* <li> 文件位置jakarta-tomcat-5.5.6\jakarta-tomcat-connectors\\util
* @author wdz123@hotmail.com
* @version 1.0
*/
public class CacheNodeWithListener implements Abandon {
int id;
public CacheNodeWithListener() {

}
public CacheNodeWithListener(int i) {
id=i;
}

/**
* 当对象被池所抛弃时候,进行相关处理
* ***/
public void onAbandon(){
System.out.println(this+"---onAbandon()");
}
/**
* 当对象池被清空时候,进行相关处理
* ***/
public void poolClear(){
System.out.println(this+"---poolClear()");
}
public String toString(){
return "id="+id ;
}

static public void main(String [] s){
LRUCacheWithListener pool =new LRUCacheWithListener(3);
int i;
for (i=1;i<=5;i++) {
pool.put("obj"+i,new CacheNodeWithListener(i));
}
pool.clear();
}
}


原文:http://www.aptime.cn/forum/ShowPost.asp?menu=Previous&ForumID=26&ThreadID=3107
posted @ 2006-10-10 12:55 hopeshared 阅读(1429) | 评论 (0)编辑 收藏

鲜肉蟹500克一只,花椒碎末10克,辣椒油15克,芫茜5克,小红辣椒三四只,盐10克,姜5克,白糖5克,料酒10克,熟芝麻5克,茨粉5克

以上配料可以根据个人喜好和实际情况进行增减


做法:
先用刷子把肉蟹洗净,去掉肉蟹的脚和脚钳,掰开蟹壳,注意,是从蟹没有钳子的一端朝向长有大钳的一端掰开。掰开后,将腔内肮脏的地方洗净,再用刀将蟹身切成两刀四块,并将蟹钳拍破,上述全置入盘里。

把小辣椒切成小片,芫茜切段,姜洗干净切成末,然后与料酒、盐一起撒在肉蟹盘里,放入蒸笼里先蒸8分钟,关火。

另起镬,置入辣椒油,同时将花椒碎末,另份小辣椒片倒入,以慢火炒出香味,然后将蒸过的蟹及蒸出的汤水全倒入镬中,煸炒4分钟,勾一个芡,撒上芝麻,即可出镬。上碟后,可用筷子把螃蟹摆回原状,并撒点芫茜,红红绿绿,色香形味俱全,呵呵。

选蟹要活泼且重,既是肉蟹,钳大壳厚也不一定划算,倒不如身肥丰满。掰蟹壳的时候小心弄着手,拍蟹钳也别太碎,稍破就可以。
宰蟹之前,最好是能先让螃蟹喝点烈酒。这螃蟹醉一阵后,肉质带点酒香,宰时也较容易处理。
posted @ 2006-10-08 21:47 hopeshared 阅读(1388) | 评论 (2)编辑 收藏

美食菜谱正宗泡椒凤爪做法。下面我再介绍一款经典的菜:泡椒凤爪(我家另一半的最爱,经常当零食吃的,所以我得经常做 这次我买了三种材料-凤爪,翅尖,鸡肫,如果只喜欢凤爪单泡一种也是可以的:)
先用水把三种材料洗干净,备用



然后去掉凤爪的指甲,并切开成一只一只的,如果不是凤爪的话听起来有点恐怖哈:)
把鸡肫也一分为二,或者切厚一点的片也行
只有鸡翅不用改刀




锅里放水,烧开,倒入凤爪,鸡翅,鸡肫,放盐(多一点,因为水多,不容易进味道),最重要的一步不要忘了,倒入黄酒,这是去腥味的.
水开后再煮七-九分钟起锅



起锅后放在冷开水里洗一洗,然后沥干水待用
用冷开水洗或冲的作用是去掉表面的油的胶质,以免泡的时候水混浊




调料的制作
主要调料野山椒,市场上有好多种,我主要是用这两种。
1把野山椒切成粒,因为我喜欢食辣,切细了辣味比较重,不喜欢太辣的用整个的也行
2老姜切成薄片
3整的干花椒少量
先在盆里倒少量开水,放花椒和野山椒,这样更容易出味,不太喜欢麻辣的可以直接用凉开水.




泡凤爪汤料的制作:
1先在盆里倒少量开水,放花椒和野山椒,这样更容易出味,不太喜欢麻辣的可以直接用凉开水.野山椒瓶里本来有水,可以放在一起泡,味更浓.
2放入姜片,盐,鸡精(少量),白醋(一斤五分之一瓶的量),前面用水的量和白醋一样,也可以根据自己的口味调整




然后把沥过水的凤爪,鸡翅,鸡肫倒入制作好的汤料里浸泡三-五个小时,就OK了
泡到一小时左右翻动一次,以便入味均匀.
上菜啰!




终于做好拉!文章来源:http://club.adsenser.org/forumdisplay.php?fid=15

posted @ 2006-10-08 21:38 hopeshared 阅读(146661) | 评论 (97)编辑 收藏

。。。。。。
posted @ 2006-10-06 11:48 hopeshared 阅读(532) | 评论 (0)编辑 收藏

     摘要: 发布日期: 12/15/2005 | 更新日期: 12/15/2005 John Evdemon Microsoft Corporation, Architecture Strategy 团队 适用于: • 企业体系结构 ...  阅读全文
posted @ 2006-09-27 11:04 hopeshared 阅读(785) | 评论 (0)编辑 收藏

ACTIVATOR/行 动

“ 我 们 什 么 时 候 可 以 开 始 ? ” 你 最 爱 问 这 个 问 题 。 你 急 不 可 耐 地 要 行 动 。 你 也 许 会 同 意 , 分 析 自 有 它 的 作 用 , 争 辩 和 讨 论 有 时 亦 不 无 真 知 灼 见 。 但 你 深 知 ,没 有 行 动, 一 切 皆 为 空 谈 。 唯 有 行 动 才 能 做 事 情 。唯 有 行 动 才 能 出 业 绩 。 一 旦 作 出 决 定 , 你 就 必 须 行 动 。 别 人 或 许 会 担 忧 , “ 还 有 些 事 我 们 不 知 道 , ” 但 是 你 不 会 因 此 而 放 慢 步 伐 。 如 果 决 定 通 过 城 区 , 你 便 认 定 到 达 目 的 地 的 最 快 途 径 就 是 从 一 个 红 绿 灯 走 到 下 一 个 红 绿 灯 ,而 不 会 坐 等 所 有 的 灯 都 变 绿 。 此 外 , 在 你 看 来 , 行 动 和 思 考 并 不 互 相 排 斥 。 事 实 上 , 在 你 的 行 动 才 干 指 引 下 , 你 确 信 最 好 的 办 法 是 干 中 学 。 你 作 出 决 定 , 采 取 行 动 , 检 验 结 果 , 继 而 学 习 进 步。 在 学 习 的 基 础 上,你 决 定 你 未 来 一 步 接 一 步 的 行 动 。 如 果 你 没 有 作 出 反 应 的 对 象 , 你 怎 么 能 够 进 步 ? 你 的 答 案 很 简 单 , 不 能 。 你 必 须 不 断 冲 锋 陷 阵 。 你 必 须 不 断 走 出 下 一 步 。 否 则 , 你 如 何 保 持 思 维 敏 锐 和 耳 聪 目 明 ? 这 就 是 你 的 底 线 : 你 深 知 评 判 你 的 标 准 不 是 你 的 言 辞 , 也 不 是 你 的 思 想 , 而 是 你 的 行 动 。 你 丝 毫 不 为 此 感 到 畏 惧 。 恰 恰 相 反 , 你 感 到 快 慰 。

ACHIEVER/成 就

你 的 成 就 主 题 说 明 你 的 内 在 动 力 。 它 表 明 你 始 终 渴 望 有 所 建 树 。 你 感 到 每 一 天 似 乎 都 从 零 开 始 。 一 天 结 束 时 , 你 必 须 获 得 某 种 有 形 的 成 果 , 如 此 才 能 感 觉 良 好 。你 所 谓 的 “ 每 一 天 ” 指 的 是 所 有 的 日 子 - 工 作 日 、 周 末 、 休 假 日 。 无 论 你 多 么 需 要 歇 一 天 , 如 果 这 一 天 你 无 所 事 事 , 你 就 会 感 到 不 满 意 。 你 体 内 有 团 烈 火 在 熊 熊 燃 烧 。 它 促 使 你 多 做 事 情 , 多 出 成 果 。 完 成 一 项 任 务 后 , 这 火 会 暂 时 减 弱 , 但 很 快 重 新 燃 起 ,迫 使 你 朝 一 个 又 一 个 新 的 目 标 不 断 攀 登 。 你 对 成 就 的 不 懈 追 求 或 许 缺 乏 逻 辑 ; 甚 至 漫 无 边 际 。 然 而 , 它 却 是 永 无 满 足 的 , 并 将 伴 随 你 一 生 。 作 为 一 名 追 求 成 就 的 人 , 你 必 须 学 会 与 这 种 隐 隐 的 不 满 足 感 相 处 。 它 毕 竟 不 无 益 处 。 它 给 你 动 力 , 使 你 能 长 久 工 作 而 不 知 疲 倦 。 它 不 失 时 机 地 使 你 奋 起 , 去 迎 接 新 的 任 务 和 新 的 挑 战 。 它 为 你 输 入 充 沛 的 精 力 , 帮 助 你 为 你 所 领 导 的 班 组 设 定 工 作 进 度 和 业 绩 指 标 。 它 是 推 动 你 不 断 前 进 的 主 题 。

RESPONSIBILITY/责 任

你 的 责 任 主 题 促 使 你 在 心 理 上 对 你 的 诺 言 负 全 责 。 你 一 旦 作 出 承 诺 , 无 论 大 小 , 你 从 感 情 上 就 觉 得 有 义 务 将 其 完 全 落 实 。 你 的 名 声 有 赖 于 此 。 如 果 由 于 某 种 原 因 你 不 能 兑 现 , 你 会 自 动 寻 找 其 他 途 径 给 对 方 以 补 偿 。 仅 仅 道 歉 是 不 够 的 。 托 辞 和 辩 解 是 完 全 不 可 接 受 的 。 你 不 作 出 补 偿 , 就 会 寝 食 不 安 。 你 的 这 种 自 觉 性 , 这 种 几 乎 是 走 火 入 魔 的 行 为 准 则 , 这 种 无 可 挑 剔 的 道 德 标 准 , 使 你 作 为 完 全 可 以 信 赖 的 人 而 美 名 四 扬 。 当 分 配 新 任 务 时 , 人 们 会 首 先 想 到 你 , 因 为 他 们 知 道 你 说 到 做 到 。 他 们 很 快 就 会 向 你 求 助 - 但 你 必 须 有 所 选 择 , 从 而 避 免 因 为 乐 善 好 施 而 力 不 从 心 。

ADAPTABILITY/适 应

你 生 活 在 当 前 。 你 不 把 未 来 视 为 固 定 的 目 的 地 。 相 反 , 你 认 为 未 来 是 你 在 现 有 选 择 的 基 础 上 创 造 出 来 的 。 因 此 , 你 通 过 选 择 逐 一 发 现 你 的 未 来 。 这 并 不 意 味 着 你 没 有 计 划 。 你 很 可 能 有 。 但 你 的 适 应 主 题 使 你 能 够 自 愿 地 对 眼 前 需 求 作 出 反 应 , 即 使 此 举 使 你 偏 离 原 有 计 划 。 你 与 有 的 人 不 同 , 并 不 讨 厌 突 如 其 来 的 要 求 或 出 乎 意 料 的 曲 折 。 你 对 此 有 料 在 先 。 它 们 是 不 可 避 免 的 。 事 实 上 , 在 某 种 意 义 上 , 你 期 待 它 们 发 生 。 你 本 质 上 是 一 个 非 常 灵 活 的 人 , 即 使 工 作 的 不 同 需 求 把 你 东 拉 西 扯 , 你 仍 能 保 持 高 效 率 。

DELIBERATIVE/审 慎

你 为 人 谨 慎 ,处 世 警 觉 。 你 是 一 个 十 分 关 注 隐 私 的 人 。 你 深 知 世 事 难 测 。 表 面 上 一 切 井 井 有 条 , 但 深 处 危 机 四 伏 。 你 并 不 否 认 这 些 危 险 , 相 反 , 你 把 它 们 全 部 暴 露 在 光 天 化 日 之 下 , 逐 一 识 别 、 评 判 , 并 最 终 消 除 。 就 此 而 言 , 你 是 一 个 十 分 认 真 的 人 , 你 对 生 活 的 态 度 是 有 所 保 留 的 。例 如 , 你 喜 欢 提 前 计 划 , 以 防 不 测 。 你 谨 慎 地 选 择 朋 友 , 并 避 而 不 谈 私 事 。 你 避 免 过 度 赞 扬 别 人 , 以 免 被 人 误 解 。 如 果 有 人 因 为 你 不 如 别 人 热 情 洋 溢 而 对 你 不 满 , 那 就 随 他 的 便 。 在 你 看 来 , 生 活 不 是 一 场 取 悦 的 竞 赛 , 而 更 象 一 片 雷 场 。 其 他 人 如 果 愿 意 尽 可 以 不 顾 一 切 地 跑 过 去 , 你 则 不 然 。 你 识 别 各 种 危 险 , 判 断 其 各 自 影 响 , 然 后 小 心 地 落 脚 ,谨 慎 前 行 。




Activator/行 动 : 行动主题较强的人能够将想法付诸行动。他们往往缺乏耐心。

Achiever/成 就 : 成就主题较强的人大都精力充沛,锲而不舍。他们乐于忙忙碌碌并有所作为。

Responsibility/责 任 : 责任心强的人言必有信。他们信奉的价值观是诚实、忠诚。

Adaptability/适 应 : 适应性强的人倾向于“随大流”。他们活在“当前”,接受现实,随遇而安。

Deliberative/审 慎 : 他们每做一个决定均慎之又慎,并设想所有的困难。

posted @ 2006-09-27 01:53 hopeshared 阅读(814) | 评论 (0)编辑 收藏

原文出处:Google Home Base
作者:只说  sayonly.com 

概要:
  本文试图通过一系列线索揭示Google Base与Semantic Web(语义网,以下简称SW)的关系,以此窥探Google在互联网服务的战略布局。当然本文属于创业生存手册系列,在系列的开篇中只说提到这个系列会提到web2.0,所以本文也会比较SW在web2.0的关系。本文引用的SW的资料大多数为英文资料,有识之士可以翻译并推介这部分材料,将是对于国内互联网整体水平的大的提升。
Dedicated to another SW - Simon Willison。

1,Google Base
  Google Base(应该是base.google.com,暂时无法访问)还没有发布,谣言已经满天飞了,从webleon的给出的链接看到,google的产品拓展经理Tom Oliveri列出了一份清单,给出了正式的解释(只说译):
你也许已经看到了今天很多关于我们正在测试的一款新产品的报道,猜测了我们的计划。在这里我告诉你们我们真正在作的是什么。我们在测试一种内容拥有者提交他们的内容到google的新方式,通过这种方式,有希望补充我们已经使用的方式如google机器人以及SiteMaps(站点地图)。我们认为这是一款让人激动的产品,有新消息我们会立即通知你们。
  这则简单的声明已经没有办法满足很多人的好奇心,试用过的人给出截图,更多的人在猜测google究竟在干什么。
  webleon文中说,应该是一个由用户自行创建网络数据库的服务。这些数据可以是任何的内容,从可以看到的数据内容看到,有
-聚会服务的描述;
-网站上关于时事的文章;
-二手车出售列表;
-蛋白质结构的数据库。
  这些内容,真的只是网络数据库、用户隐私?或者是google头脑发热的一次作恶(evil)?
  从google的对这个项目的声明和更多的猜测看,恐怕不这么简单。种种迹象表明,这是google在作一次SW的试水,是google开始向SW服务靠拢的一个试探。为什么只说能这么肯定,SW究竟是个什么东西,它怎么有那么大的魔力,让google这么讳莫如深?这话得从头说起。
  当然,也只有google,在产品的测试期间就能掀起这么强烈的关注。


2,Google与Semantic Web的亲密接触
  几年前,Simon Willison发了一个简短的blog文章,对于google在作一些关于SW的研究而赞叹,他看到了一份以未来笔调描述google如何战胜Amazon和Ebay这些竞争对手的恢宏论文,作者是 Paul Ford。Simon Willison是一位很geek的程序员,我一直有看他的blog,虽然未必能完全看懂,他现在去了yahoo,有趣的是,它的名字的简写也是SW,把本篇文章献给他(其实应该是本章,但那样说也太失礼了)。
  Paul Ford那篇被多次提到(还有 Stuart)的文章讲的是,2009年,Google统治了互联网这个媒介,回顾如何击败Amazon和Ebay的历程,其实是一篇比较通俗的整体讲述什么是SW的文章,读起来颇为有趣。同样有趣的还有那个EPIC,当然就与SW无关了。
  其实美国东岸的几所学校对于SW的应用研究都很长时间了,最有成果的应该是piggy bank

  2003年,google买了一家小公司,叫做Applied Semantic,应该用来做Google adsense的。因此有人写了一篇题为google在SW投资的文章,可以参看。

  google的搜索质量总监Peter Norvig今年初有一篇文章,题目叫做SW可以做什么,不能做什么是只说读到关于SW应用最透彻的文章之一,这系列文章很长,从各个方面探讨了SW应用和概念。Peter Norvig是个非常有眼光的人,我以前也是一直看他的网站,虽然至今他还没有blog,但是终于有RSS输出了。他有一篇传世文章,叫做十年学编程后来被很多人翻译过,其实这是他在NASA研究中心时候写的,呵呵,时间过去得真快。

  如今的Google Base的出现,必然有Norvig的眼光和推动力来成就这个网站。其实欧洲人比美国人更急于想实现SW,甚至已经有了semantic weblog,例如qlogger.com,但是没有人象norvig一样技术渗透,而且身后是google这样的公司。

  背靠着索引着最大互联网网页数量的google,在将网络爬虫使用到了极致之后,极有可能是第一个可能局部实现SW的商业机构,无论从技术还是从市场上看。当然SW是一种理想,至少google base让我们初尝到这种口味。

  让我们看一看,什么是SW,为什么Google要实现SW?

3,什么是Semantic Web?
  什么是SW,就得先谈谈它的发明人Tim Berners-Lee,同时也是WWW的发明者。

  Tim Berners-Lee在近几年的报道提到互联网发展时(一般放到Future一页里面)无一例外的提到了SW,大约是发明WWW之后再发明不了其他玩意儿了,或者是其他玩意儿都没劲了。当然也还有其他的,5月的报告指出,目前网络在手机上面临的困境跟96年互联网在pc上面临的困境一样。当然,SW是对于整个互联网说的,跟接入的设备没有什么关系。专门关于SW的报告是题为SW在这里,列出了Nokia、HP、IBM等厂商的SW的进展,也可以在这里看到那次会议中谈论的细节,不过那里看不到那个SW在这里报道中的那个SW的形象图,画的是各种材料,包括砖头和木材,组合成的一头大象。形象地说明了在SW下,是各种可以识别的材料,组成了整个世界。many things to many people。只说喜欢他们另外一个宣传口号:Web Evolution causing a quiet revolution

  SW的核心意义在于网络内容是由多种可以识别的数据组成的,在早期的互联网,93年左右,互联网停留在文件形态,组成的是一个个文件,传送都是使用ftp 等工具;94年左右互联网处于文本的形式,出现了html和URI(唯一地址),可以通过这个地址进行访问;而不断演化,今后将在以XML等可以标记的数据结构中,而网页只是展示这些数据的一种工具,你可以通过任何其他的形式进行展示,甚至机器也可以识别。互联网不再是由一篇篇的文档和页面组成,而是由一部分一部分细碎的数据构成。

  这样说比较玄妙了,其实还可以解释得更简单一点。SW就是把原来的互联网内容,切成碎片,文章标题归文章标题,发布时间放到发布时间,文章概要归文章概要,分别存放,每一个部分都是机器可以识别的(当然实际可能更复杂一点)。在Paul Ford的2002年如何战胜Amazon和Ebay中提到,它其实就是描述这些内容的另一种方式,这种方式下机器可以识别,具体方式虽然不是十分清晰,但是逻辑上,其实跟在你在学校里面学习的方式没有什么两样:
-如果A是B的朋友,那么B就是A的朋友;
-张三有一个朋友叫李四
-因此,李四将有一个朋友叫张三
-李四有一个朋友叫张三
-那么,张三会有一个朋友叫李四
  就是这么简单。
  在互联网上,我们把内容放在一些定义好的XML标签指定的文件里面。然后会有程序自动收集这些内容,通过这些简单的规则,进行分析。所有区别于现在操作的就是,在搜索的时候,服务器的程序会综合更多的因素,进行更复杂的判断,理解你的请求的真实意义,然后给你最准确的内容。
例如,你输入只说,他们准确的判断出,你要找的是我这个人,而不是错认为,你又说了一句什么话,或者给你一个许如芸的“只说给你听”的歌曲应付一下你。

4,Google怎么实现Semantic Web?
  Google究竟怎么实现SW,在Peter Norvig的文章SW可以做什么,不能做什么已经可以看出些端倪,Norvig在今年一月份(或者更早)都已经想好了应该怎么启动了,或者说,应该怎么逐步打造SW。他谈到了四个问题:
  1)先有鸡还是先有蛋的问题,
  这个问题涉及到如何建立所需的信息,因为要必须有有组织的信息才能打造相应的工具,而如果没有相应的工具,怎么把信息放到组织里面去呢?
这个问题只说要展开说一下,其实google并不是要建立一个Tim Berners-Lee等人理想中的SW,因为其实google其实只需要索引SW中的信息即可,因为如果SW建立起来,索引是一件简单的事情,甚至产品实现上面比google现在的搜索引擎更简单,技术要求更低。然而,问题就出来了,是先建立一个SW,然后来索引呢,还是先索引整个互联网,然后再生成把它放到有组织的SW里面去呢,这就是为什么google打造SW时遇到了先有鸡还是先有蛋的问题。
  那么只说的猜测是,目前Google base的作法是,目前互联网上的信息是很难组织,那么让用户提交有组织的信息到google,就能形成局部的SW。而这个局部的SW,就可以实现聚会服务的描述、网站上关于时事的文章、二手车出售列表等等信息的精确定位,机器也就能够理解这个范围内的信息。

  在Norvig后面的描述中可以证实只说的说法:
在正常情况下,定义语义的标准格式(schemas)似乎更好,但是,问题出在把什么内容放进这些标准格式,还有很多工作要做。
因为还有以下提到的几个问题,这些问题在把内容放进这些标准格式中的时候,这些问题同样会出现,而且,google不能把握住这些环节,或者从整个互联网角度来讲,把握这些环节的公司服务或者工具太分散,无法形成标准,也无法保证安全和质量。Norvig举了一个google news例子,在前一个晚上google news一共索引了658个不同来源的新闻,google可以根据这些新闻页进行一个cluster运算,算出其中重要度最高的是Blair的新闻,然而,如果google依据这些写入新闻的新闻源来做这件事情,则几乎是不可能的。
  不过通过他们的页面上的新闻来索引计算出来的质量毕竟不高,所以google现在想到另外一个办法,也就是,让用户通过google base的接口提交到google,提交的数据是定义好的一些数据标准,google来控制这个提交过程并更准确的判断提交的质量、spam等等情况,并且可以将各种数据综合起来进行分析。

  2)竞争问题,你有不同的和相似方法和工具可以选择。
  这样子就无法跟踪用户行为的全貌。

  3)Cyc问题,
  Cyc是一个专业术语,讲的是通过广泛的本题作常识推理。这样说也许不太明白,举个例子就很容易了,例如“周杰伦”,这是一个人名,如果以错输为“周杰论”,这时机器就识别不出来了,但是如果拥有了一个很大的词库,那么这个通过识别出“周杰论”可能就是“周杰伦”,那么这就是一个Cyc问题。如何在SW 中判断这些Cyc以识别出常识的判断,这是建立真正意义的SW必须解决的问题。

  4)Spam,
  垃圾,这个不用多说了。但是注意到,由于SW是精确匹配,并且要求根据意图来适配,所以对于spam要求更高。
顺便提及,Splog不就是Semantic Spam嘛。


5,Semantic Web与Web2.0
  web2.0是tim o'reilly的概念,开始这个概念定义很模糊。应该是互联网应用的发展模式,催生了新一代的应用以及人们对于这些应用的理解方式和使用方法(这里谈到过这几个概念的分别)。国外也有人撰文web2.0会杀掉SW吗?,也有称Semantic Web 2.0。有很有趣的讨论。前一篇文章说得有点道理,web2.0是给少数人用的,SW会提供Accessiblity。Stefan Decker在这里补充了一下,Web2.0重“应用”,SW则是标准。这跟只说那边谈到web2.0是应用发展模式不谋而合。其实web2.0用来说明一种公司特性也未尝不可,不过你大声的说google是web2.0的公司,而M$是1.0的公司,确实有点怪。

  当然SW也作了很多应用,例如美国东岸的几所学校,例如欧洲连Semantic weblog也搞出来了,deri也做了很多应用了。

  另外,gnowsis也是另外一个狂想,只是我还没看懂它的结构图,为什么会有一个semantic web server在里面。


6,结语
  还有几点:
  本文并没有分析google为什么要做SW,只说想这已经用不着只说在这里分析。SW对于各种应用的好处是显然的。
  Google对于SW的探索看似给予搜索引擎的,Norvig那篇文章下面也有人回复说,似乎google只是在搜索的角度看待SW,其实不然,因为互联网是一个请求应答系统,是我们人为将互联网标准定义成一个url指向一个网页的,这是一个陈旧的标准,或者对于更高层次的信息获取来讲,并非是必要的。关于信息适配的探索,其实google比任何其他人(谄媚呀)都高。
  有人说,Google还是从信息组织的角度来看待整个互联网(google的信条就是组织信息),或者,它只是互联网的一个信息组织者,以后也将成为SW 的信息组织者。其实,从根本来说,互联网整个媒介都是信息,除了信息没有其他任何东西,当然你可以持有另外一个观点互联网应用才是主导,这到了最深处都是殊途同归。
  刚写完,发现keso的已经出来了:
互联网提供了很多破坏规则的机会。门户新闻和搜索引擎新闻已经破坏了传统媒体的规则,分类网站正在破坏一些电子商务网站和招聘网站的规则。即将露面的 http://base.google.com/服务,很可能是一个更大的破坏者,它有可能笼络更多的个人内容提供者,进而改变互联网长期以来内容的组织方式。
  其实规则很简单,就是在得到最小的spam的情况下,获得最有组织并且方便组织的信息,google实现的局部SW当然有控制,然而,SW的目标,不是web2.0那样的应用,而是Accessibility呀。 这场革命如此quiet,甚至谈不上“规则破坏”。(指Web Evolution causing a quiet revolution的quiet)
  本文引用的大量连接都是英文链接,由于时间关系,不能将其中摘录翻译,深感抱歉。SW的很多文章并不完全是很技术化的话题,这些材料对于国内互联网水平的增长是十分有益的。

  再次强调一下本文的观点:很显然,google base是google在SW的试验和测试。而SW就是google的本垒(home base)。
posted @ 2006-09-08 15:34 hopeshared 阅读(816) | 评论 (1)编辑 收藏

啤酒游戏——究竟是谁的错?

  为了进一步体认行动中的学习智障,首先让我们来做一个有趣的实验,它也是系统思考训练中,非常重要的一项活动。我们的方法是建立一个模拟组织,因为在那里可以看到你作决定的后果,且可能比在真实组织里看到的更清楚。这个我们常邀人参加的实验,是一个叫作“啤酒游戏”(beer game)的模拟,这项游戏第一次开发完成,是在六十年代麻省理工学院的史隆管理学院(Sloan School of Management),它是类似“大富翁”的桌上游戏,但更简单,参加者只须做一项决策。在实验中,我们能够比在真实组织中,更加鲜明地暴露这些智障以及它们的成因。上万次的实验结果都显示,问题大部分源自人类思考与互动的基本习性,它超过了组织与政策特性的影响。希望每个人都有机会能亲身尝试一下这个对全人类的困境而言;都非常重要的游戏。
  在啤酒游戏中,我们暂时置身在一种很少受到注意、但普遍存在的组织。它是一个在所有工业国家都有的、负责商品生产与运销的产销系统。这个实验是一个生产和配销单一品牌啤酒的系统。参加游戏的人,各自扮演不同的角色,且完全可以自由作出任何决定。他们惟一的目标,是尽量扮演好自己的角色,使利润最大。这三个主要角色是零售商、批发商和制造商的行销主管。现在就让我们分别由三个不同角色的角度,来看故事的发展。

                 零售商

  假想你是个零售商。你可能是郊区某家二十四小时营业的连锁店管理者;或者你经营的是古老小镇上一家家庭式食品杂货店;也可以是在偏远公路边的饮料批发商。 无论你的商店像什么样子,或你销售任何其他货品,啤酒是你主要的营业项目。它不仅有利润,而且吸引顾客进来买其他商品,例如爆米花与炸薯片等。你至少保持一打各种品牌啤酒的库存,并概略地登录店里的存货量。 每周一次,会有卡车司机来到你的商店后门。你递给他一份该周订货单,上面写明每个品牌你要多少箱。卡车司机在跑完其他地方之后,会把你的订单交给你的啤酒批发商处理,适当地安排出货,并把订货运到你的商店。渐渐的,你已习惯这些处理流程,你的订单平均需时四周;换句话说,啤酒通常在你订购之后大约四周才送达你的商店。
  你与你的啤酒批发商彼此从未直接见面和聊过天。你们只透过一张纸上的核对记号来沟通讯息。这是因为你的商店经销几百项货品,来自几十家批发商,而你的啤酒批发商要处理十多个不同的城市中几百家商店的交货。在你的顾客和订单持续增加时,谁有时间聊天?数目是你们惟一需要彼此沟通的事项。
  你销售最稳定的一种啤酒叫作“情人啤酒”。你隐约晓得情人啤酒是由一家离你店面大约300英里、规模小但是有效率的制造商所生产的。它不是超级流行的品牌;事实上,该制造商全然不作广告。但是,就像每天规律收到早报一般,每周你都卖掉4箱情人啤酒。可以确定的是,这些顾客大部分是20岁左右的年轻人,没有固定的偏好;但是不知何故,每当一位顾客转而购买其他品牌啤酒,都会有其他人接替他来买情人啤酒。为了确定你总是有足够的情人啤酒,你尝试随时保持12箱的库存量。因此每周一啤酒卡车来到时,你都订购4箱,久而久之,你心里已经将每周4箱的周转率看作理所当然,甚至你在发订单的时候会不自觉地说:“喔,是的,情人啤酒4箱。”
  【第二周】:十月份的某一周(让我们称之为第二周),情人啤酒的销售量突然增加一倍,从4箱增至8箱。你想那很好,你店里还剩8箱。你不知道何以情人啤酒的销售突然增加4箱。或许有人举行宴会吧!但是为了补充额外卖出的4箱,你把订单上的数量提高为8箱。这将使你的库存恢复正常的12箱。
  【第三周】:但奇怪的是,下一周你又卖出8箱情人啤酒。偶尔你会在得空时短暂地思索原因何在。但春假还没到,啤酒公司也没通知你有举办特别的促销活动,或许有其他的原因……,但是有一位顾客进来了,打断了你的思绪。
  就在此时,送货员来到,关于情人啤酒的事,你还是没有机会仔细思考,你低头看送货单,看到他这次只送来四周前你所订的4箱。你目前只有4箱的库存,意思是说除非接下来销售下降,否则这周你将卖光所有的情人啤酒。因此至少订购8箱才能赶得上销售的速度。但为了安全起见,你订购了12箱,这样你可以重建原有的标准安全库存量。  
  【第四周】:星期二那天,你找时间向一、二位年轻的顾客询问。结果发现大约一个月以前,有一个新的流行音乐录影带在电视频道播出,这个合唱团以“我喝下最后一口情人啤酒,投向太阳”,作为他们歌曲的结尾。你不知道他们为什么使用这句话,但是如果有任何新的广告促销,你的批发商应当会事先告诉你。你想打电话给批发商问问,但是炸薯片的送货员正好来交货,情人啤酒这个问题又滑出了你的脑子。
  你订的啤酒下一次到货时,只送来5箱。(尽管因为只剩1箱库存,你有些懊恼,但是觉得也不错。多亏这个录影带免费促销。)你虽然知道自己前几次已多订了几箱,但是想想需求可能进一步上升,最好再订购16箱。
  【第五周】:周一早上你仅存的1箱卖光了。幸运的是你又收到7箱情人啤酒(显然你的批发商已经开始回应你较高量的订单)。但是所有的啤酒又在本周结束之前销售一空,你的库存完全没货了。你有些担心地望着空空的货架,最好再另外订16箱。你不想落得流行的啤酒没货的名声。
  【第六周】:果然,顾客在本周开始的时候就夹看看有没有情人啤酒。有两位实在忠心,愿意等着购买。他们说:“货来了请通知我们,我们立刻上门购买。”你抄下这两位顾客的名字与电话号码,他们承诺每人买1箱。
  下次到货只有6箱。你打电话给那两位预先订货的顾客,他们依约前来各自买了他们的1箱,剩下的4箱啤酒在周末之前又卖光了。又有两位顾客把他们的名字留给你,下批货一到。请你立刻打电话给他们。你不禁想:“要是货架不是空的,不知道已经多卖掉多少啤酒。”似乎这个牌子的啤酒有供不应求的情形。在这个地区没有一家商店有货可卖。似乎这种啤酒受欢迎的程度不断增加。
  
  在瞪眼看着空的货架两天之后,觉得如果不另外订购16箱是不对的。你内心挣扎着是否要订购更多的数量,但是你克制了自己,因为你知道自己所下的几次大订单将很快开始交货。可是他们什么时候才能交货呢?
  【第七周】:这周交货卡车只送来5箱,也就是说你又要面对空空的货架一个星期。你把货给了预订的顾客,不到两天,剩下的情人啤酒又卖光了。这一周更吓人,有5位顾客留下他们的名字。你另外订购了16箱,并暗自祷告你的大订单将会开始到货。
  【第八周】:此时你对情人啤酒的注意,比任何你销售的其他货品更为密切。每当有顾客购买这种看起来不显眼、半打装的啤酒,你都会特别注意。人们似乎在谈论情人啤酒。你殷切地等待卡车司机送来16箱啤酒。
  但是卡车司机只送来5箱。你说:“5箱,你是什么意思?”他告诉你:“嘿!这件事情我毫无所悉,我猜他们接到的订单多于存货的数量。不过我想你几周以后会收到货的。”几周?那时你光是应付预先订货的顾客就卖完了,接下来的整个星期,你的货架上会连1瓶情人啤酒也没有。这将对你的声誉造成怎样的影响?你深感挫折与生气。
  这次你订购了24箱,比你原先计划要订购的多一倍。真想不通为什么批发商这样对待你?难道他不知道我们这里的市场胃口有多大?

                 批发商

  作为一家批发配销公司的主管,啤酒就是你的生活。你一天中花极长的时间在小仓库里面,仓库里高高地堆着每一种能够想得到品牌的啤酒,当然也包括情人啤酒。你所服务的地区包括本市、本市的四郊和偏远的乡间。你不是本地惟一的啤酒批发商,但是你基础稳固。而且有一些小品牌,包括情人啤酒在内,是由你在此地区独家代理。 你跟制造厂商联络的方法,大多与零售商用来跟你联络的方法一样。你每周在一张表单上,潦草地填上数目,交给你的驾驶员。平均在4周以后,所订的啤酒会送到。只是你订购的不是以精计,而是整批的。每批的总数大约够装满1辆小卡车,所以你所想的是卡车量。就像你的零售商每周都向你订购4箱的情人啤酒,你每周都向制造商订购4卡车的量,足够让你随时积有12卡车的标准库存量。
  到了第八周你几乎已经像你的零售商一样的感到挫折与生气。情人啤酒一向都是个可靠稳定的品牌,但是几周之前(约在第四周)订单突然开始急遽上升。再下来那一周,从零售商来的订单仍然继续增加。到了第八周,大部分的零售商所订购的啤酒数量已经是平常的三或四倍。
  起初,仓库中的库存,轻而易举地满足了额外订单的需要。而当你注意到情人啤酒有销售增加的趋势,你马上向制造商提高订购这种啤酒的数量。第六周你在啤酒配销新闻上看到一篇关于流行音乐录影带的文章之后,进一步提高订购量,大幅增加到每周20卡车量。这是你平常所订购啤酒数量的5倍。但是你需要这么多,因为啤酒受欢迎的程度,从零售商订货的需求判断,呈二倍、三倍、甚至四倍增加。
  到了第六周,你所有库存的啤酒都送出去了,然而欠货的数量还是惊人。有几家较大型的连锁店还急得直接打电话给你,但是你的情人啤酒库存空空如也。不过至少你知道,再几周你多订的啤酒将会送到。
  在第八周,当你打电话给制造商,问是否有什么办法加速他们的交货(并且通知他们你已把订单增加为30卡车量),你骇然发现他们在两周前才增加生产量。他们刚刚才得悉需求增加。他们怎么会这么慢?
  【第九周】:现在是第九周了。你每周自各零售商接到约20卡车量的情人啤酒订单,而你仍然没有货。上周结束之前,你对零售商的欠货又多了29卡车量。你的员工对于外勤人员打来的电话已经司空见惯,甚而他们要求你安装一具答录机,专门用来说明关于情人啤酒的事情。但你有自信在本周内,从制造商那儿收到你在一个月以前订购的ZO卡车量。
  然而,他们只送来6卡车量。显然制造商仍然缺货,而较大量的生产运转现在才开始出货。你打电话给几家较大的连锁店客户,向他们保证他们订购的啤酒不久将送到。
  
  【第十周】:第十周令人怒气冲天。你预期会送到的额外(至少20卡车量)啤酒不见踪影。你猜或许制造商确实无法这么快提高产量。他们只交给你8卡车量。打电话去也没人接电话,显然他们所有的人都在工厂里全力生产。
  在此同时,各家商店显然疯狂地在销售啤酒,因为你接到空前大幅的订单——本周为26卡车量。或许他们正因为无法从你这里拿到啤酒,所以订购这么多。不管是怎样,你必须跟得上订单。如果你拿不到啤酒,他们可能转向你的竞争者购买。 你向制造商订购了40卡车量。
  【第十一周】:在第十一周,你发现自己在仓库附近的酒吧进午餐的时间特别长(因为你实在很怕接到连锁店打来的电话)。情人啤酒只送来12卡车。你仍然无法联络上制造商的任何一位人员。而你有超过100卡车量的订单等着补货:已订未交的77卡车量,加上本周从商店接到的28卡车量的订单
  你必须得到啤酒,于是你向制造商再订购了40卡车量。
  【第十二周】:到了第十二周,情势更明白了。对情人啤酒需求变化的程度远高于你所预期的。只要你想到假如有足够的存货,可以赚到多少钱,便只好叹口气认了。制造商怎能对你这样?为什么长期缺货的需求会上升这么快?你怎能预料而跟得上?你所知道的一切就是千万不要再陷入这种长期缺货的状况。你再订购了60卡车量。接下来的四周,需求量继续超过你的供应量。事实上,到了第十三周,你的欠货量
全然没有降低。

  【第十四周】:在第十四周与第十五周,你终于开始从制造商收到较大量的出货。同时,从商店来的订购量下降了一点点。你想也许是因为上周他们多订了一点。在这个节骨眼上,任何有助于降低欠货数量的事情都是受欢迎的。现在到了第十六周,你终于几乎拿到前几周要求的所有啤酒:55卡车量。这些啤酒必须用板架叠放起来,而这些进来的货将很快卖出去。
  一整个星期下来,你盼望商店的订单再进来。但是你看到一张接一张的订单填的都是相同的数目:零、零、零、零、零。这些人出了什么毛病?四周以前,他们大声吼着向你要啤酒,现在他们甚至1箱也不要了。  突然之间,你觉得一股寒意自心底冒上来。你把刚要送给制造商的订单上写着的24
卡车量全数删除。
  【第十七周】:接下来这周,又送到60卡车量的情人啤酒。商店的订购数量仍然是零。109卡车量的货品在你的仓库里纹风不动,你天天在这些货品堆里走来走去,还是没有凹掉一块。
  这周商店要的货应该会增多一些,毕竟那个录影带仍在播出。你心中暗自思量,若再不来订货,你要把每一个该死的零售商打入第十八层地狱。
  可恶的是,零售商向你订购情人啤酒的数量又再一次挂零。你向制造商订购的数量自然也是零。然而,可恨的制造商还是继续把啤酒送来,这周又运来60卡车量,实在太过分了,他难道不知道我的处境?为什么制造商还要把货运来?这要到什么时候才会结束?

                 制造商

  假想你是四个月以前被雇来负责这家啤酒制造商的配销与行销主管,情人啤酒只是它几项主要产品当中的一项。这是一家小型制造厂,以品质闻名,行销则不太出色,所以公司才雇用你来加强行销。
  现在,显然你做对了一些事情。因为你就任不到两个月(这个游戏的第六周),新的订单开始急遽上升。到了你担任这个工作的第三个月结束,啤酒的订单达到每周40批,你刚开始时只有4批,大幅成长让你觉得满意。而你的出货量是30批。 制造厂内也同样有欠货的问题。在这个工厂,从你决定酿制一瓶啤酒,到啤酒完成出货的准备,至少需要两周的时间,所以你在仓库保持几批啤酒的库存,这是理所当然
的。但是这些库存在第七周就出光了,而这只是进来的订单开始上升两周之后的事。下一周你有9批已订未交的订单,还有24批新的订单,你只能送出22批。这时你在公司内已经成为英雄。厂长给每一位员工奖励,把工作时间延长一倍,并兴奋地进行面谈为工厂招募新的帮手。
  你运气好是由于流行音乐的录影带提到了情人啤酒。你在第三周从青少年写来的信件中得悉录影带的事,但是直到第六周才看见录影带造成订单上升的影响。
  甚至到了第十四周,工厂仍然赶不上已订未交的订购量。你不断要求酿制更多的量。你想像自己这一年的奖金不知将会有多少,或许可以要求利润的某一个百分比。你无聊得在行销周刊的封面上为自己画像。
  在第十六周,你终于赶上已订未交的数量。但是到了第十七周,你的经销商只订了19批的货。而至第十八周,他们完全不再订购啤酒,有些订单上甚至可以看出是被全数删减掉的。
  现在是第十九周。你在仓库里有100批啤酒存货。而啤酒销售业绩仍然挂零。同时你过去要求大量酿制的啤酒,还在不断的酿造中。你战战兢兢地打了一通电话给老板。你说:“最好把生产延缓一、二周,我们碰到了……”你使用一个你在商业学校学到的字眼“断续(discontinuity)”。电话的另一端默不作声,你说:“但是我确定这只是暂时的。”
  
  【第二十一周】:相同的模式又延续了四周之久:第二十周、第二十一周、第二十二周、第二十三周。你对订货再现高峰的希望日渐渺茫,而你的借口听起来愈来愈薄弱。你说经销商和零售商胡搞,上个月疯狂地要货,这个月突然什么都不订,都是新闻与流行音乐录影带制造了情人啤酒高涨的需求。
  【第二十四周】:你在第二十四周初,借用公司汽车去拜访的第一站,是批发商的办公室。这不仅是你与批发商第一次会面,也只是你们第二次交谈,因为直至这个危机发生之前,并没有什么事情好说的。你们彼此面色凝重地寒暄了一下,然后批发商把你带到后面的仓库。批发商说:“我们已有两个月没收到零售商任何一张情人啤酒的订单了,我感到完全茫然不解。你看,这里还有220卡车量在仓库里啊!”  你们一起断定必然发生了需求暴起暴跌的现象,“消费大众的需求是反复无常的。”你们共同如此结论。如果零售商留意并警告你们,决不会发生这样的情形。
   
  在回程的路上,你在脑子里构思行销策略报告的措辞,一时性起,你决定在一家沿途经过的零售商店停一下。运气不错,店主在店里。你自我介绍,零售商的脸上勉强挤出笑容。他。要一名助手照看店铺,你们两人一同走到隔壁一家速食餐厅,各要了一杯咖啡。
  零售商随手带来店里库存记录簿,打开横放在桌子上。“你不知道几个月之前我多么想要勒死你。”
  “为什么?”
  “你看,我们后面的房间还有93箱没卖掉的情人啤酒。依照现在售货的速度,我们再订购是六周以后的事了。”
  六周,你自己盘算着。接着你掏出一具口袋型计算机。如果这个地区每一家零售商都等待六周再订购啤酒,然后每周只订购几箱,将费时一年或一年以上,才能使批发商220卡车量的库存大幅下降。你说:“这是一个悲剧。是谁让它发生的……我的意思是说,我们要如何防止这样的悲剧再次发生?”
  在噘了几口咖啡后,零售商说:“那不是我们的错,在音乐录影带开始播出的时候,我们一直都是卖4箱啤酒。接下来的第二周我们卖掉8箱。”你接着说:“然后销售量迅速增加。但是为什么现在销售量会减到连1箱都没有呢?”
  零售商说:“不,你不了解,需求从来就没有迅速增加过,顾客也从未完全停止购买。我们仍然每周卖出8箱啤酒。”
  “什么?你说什么?需求从来就没迅速增加过?顾客也从来没有完全停止购买?这怎么可能?到底是怎么回事?”
  “但是我们需要的啤酒数量你们不送货给我们。我们只好不断地订货,以确保有足够的数量来跟上顾客的需要。”
  “但是我们都按照必要的速度把啤酒送出去。”
  零售商说:“那么也许是批发商搞砸了吧,我在想是否该换供应商了。无论如何,我希望你举办一次赠券促销或其他办法,让我能够赚回一些本钱。我只想把那93箱啤酒卖掉一些。”
  你拿起咖啡的帐单。回程中,你一路计划辞呈的措辞要怎么写。显然,你将因这项危机所造成的工人解雇或关闭生产线而受到责备,就像批发商责怪零售商、零售商责怪批发商,而两者都想要责怪你一样。至少现在还够早,能让你带着些许尊严离开。只要你能想出一些解释,说明这不是你的错,显示你也是被害人,而不是元凶。

               啤酒游戏的省思

  一、结构影响行为
  不同的人处于相同的结构之中,倾向于产生性质类似的结果。当问题发生或绩效无法如愿达成的时候,通常我们会怪罪于某些人或某些事情。然而我们的问题或危机,却常常是由我们所处系统中的结构所造成,而不是由于外部的力量或个人的错误。
  二、人类系统中的结构是微妙而错综复杂的
  我们倾向于只把结构想作外在的限制,但在人类系统中,结构还包括大家作决定时所根据的许多运作原则,我们依据这些原则诠释认知、目标、规范,并将之化为行动。
  三、有效的创意解常出自新的思考方式
  在人类系统中,常隐藏着更有效的创意解,但是我们却不曾发觉,因为只专注于自己的决定,而忽略了自己的决定对他人有怎样的影响。在啤酒游戏之中,三个角色在他们的能力范围内,都有消除大幅振荡的巧妙做法。但是他们无法做到,因为他们根本不知道自己是如何开始制造出振荡的。

               危机一再重演

  商业界的人喜爱英雄。我们大肆赞美和升迁那些达成有形成果的人。但是如果事情出了问题,我们直觉上认为一定有人搞砸了。  在啤酒游戏中没有这样的元凶,没有人该受到责备。在我们故事中的三个角色,任何一个人的意图都是善良的:好好服务顾客,保持产品顺利地在系统中流通,并避免损失。每一个角色都以自己的理性猜测可能发生什么,并作了善意、果决的判断。没有一个人的用意是坏的,虽然如此,危机还是存在于系统的结构中。
  近二十年以来,啤酒游戏在教室与管理训练讲习会中被玩过好几干次。在五大洲都有人玩过这个游戏,参加的人有各种年龄、国籍、文化和行业背景;有些参加者以前没听说过生产/配销系统,有些人已花了相当长的时间在这样的业务上。然而每次玩这个游戏,相同的危机还是发生。首先是大量缺货,整个系统的订单都不断增加,库存逐渐枯竭,欠货也不断增加。随后好不容易达到订货量,大批交货,但新收到的订购数量却开始骤降。到实验结束之前,几乎全部参加游戏的人,都坐着他们无法降低的庞大库存;制造商库存已有好几百箱,望着批发商每周只8箱、10箱的订单而一筹莫展。
  如果成千上万、来自不同背景的人参加游戏,却都产生类似的结果,其中原因必定超乎个人因素以上。这些原因必定藏在游戏本身的结构里面。
  更值得令人注意的是,啤酒游戏的产销模式结构,竟也会导致真实企业生产配销系统常见的危机。例如1985年个人电脑记忆晶片的价格低廉并且货源充足,销售却下滑18%,美国的业者遭受25~60%的亏损。但是在1986年后期,突然发生的短缺,却因恐慌与超量订购,而使短缺加剧,结果同样的晶片价格上涨100到300%。类似的需求暴起暴落发生在1973年到1975年的半导体产业。在订单大量增加,造成整个产业缺货与交货时间迟延之后,需求量却随后暴跌,你需要任何产品,马上就可以拿到。几年之内,西门子(Siemens)、赛格尼(Signetics)北方电讯(Northern Telecom)、汉伟(Honeywell)、以及史林伯格(Schlumberger)等全都经由购买走下坡路的半导体制造公司而进入这个产业。
  在1989年中期的通用汽车、福特和克莱斯勒,就如同华尔街日报所说的“因生产的汽车远高于销售量,经销商库存不断累积。这些公司闲置工厂,并以多年来所未见的高比率解雇工人。”整个国家的经济也常经历这种经济学者所称“存货加速器理论”(Inventory accelerator theory)的商业景气循环——需求小幅上扬,导致库存过度增加,然后引起滞销和不景气。
  各种服务业也一而再、再而三地发生这种类似波动。例如房地产业抢购的盛况以及随后严重的滞销,太多太多这类的例子,大家却又一再重蹈覆辙,是因为人类太健忘呢,还是因为更深一层的“群体”智障?
  其实,在生产配销系统的真实状况,往往比啤酒游戏的情形还要糟糕。真实世界的零售商会同时向三、四家批发商订货,等到有一家交货了,就取消还没交货的其他订货。真实世界的制造商常常会碰到在游戏里役有出现的产能限制问题,使整个配销系统的恐慌更加恶化。或者制造商可能提高产能,因为他们相信目前的需求水准将继续下去,然而如此一来,旦需求滑落,又会发现自己陷入产能过剩的困境。
  像啤酒游戏这样的生产配销系统的波动现象,揭示了系统思考的第一项原理:结构影响行为。

               结构影响行为

  即使是非常不同的人,当他们置身于相同系统之中,也倾向于产生类似的结果。  系统的观点告诉我们,要了解重要的问题,我们的眼界必须高于只看个别的事件、个别的疏失或是个别的个性。我们必须深入了解影响我们个别的行动,以及使得这些个别行动相类似背后的结构。就如七十年代震撼全球的巨著《成长的极限>(The Limitsto Growth)一书的作者米锋丝(Donella Meadows)所说的:“真正深入、独特的洞察力,来自于认清楚系统本身正是导致整个变化形态的因素。”

             历史的法则与系统思考

  一百多年前,一位杰出的“系统思想家”托尔斯泰(Leo Tolstoy)已经表达了相同的感慨。他在《战争与和平》(War and Peace)这本书中,除了描述拿破仑与沙皇时代的俄国历史的故事之外,约有三分之二的篇幅在沉思为何大多数的历史学家无法真正解释史实:

  “十九世纪的前十五年,出现一个很不寻常的运动,有数百万人加入。人们抛开惯常从事的工作;整个运动的波澜,从欧洲的这一头冲击到那一头。掠夺与残杀、胜利与绝望充斥人心。人类整个生活步调发生巨变,起先以递增的速度移动,然后又慢慢地递减。有人问:这项活动的原因是什么,或者,这到底是依什么法则发生?  “为了回答这个问题,历史学家把当时巴黎的重要人物一一剖析,然后以两个字为这些人的言行做总结——革命。这些历史学家并为拿破仑,及与他亲近或交恶的人写了详细的传记,谈论其中重要人物之间的相互关系,然后下结论说,就是因为这些事情促使革命发生,而这些就是历史的法则。
  “但是,有智慧的人拒绝相信这样的解释。事实上‘革命’和拿破仑的产生,是许多个人意志汇集的结果,这种意志的汇集可造就他们,也可使其灭亡。
  “历史学家认为:‘不论什么时候,有战争就有伟大的军事领袖;不论什么时候,发生革命的国家都会有伟人。’有智慧的人却认为:‘不论什么时候,有伟大的军事领袖时,的确是有战争;但那并不表示,将领就是造成战争的原因,或者可以从某一个人的活动中找到导致战争的因素。’”

  托尔斯泰认为,只有尝试了解历史背后的法则,才有更深入理解的希望。他所谓的“历史的法则”就是我们在称此为“系统整体结构”的同义词:

  “为了查证历史的法则,我们必须完全改变观察的主题,必须撇开国王、大臣、将军,而着手研究引导大众的那些类似而又极细微的要素。没有人能够说到底人类在这方面了解历史法则的进展有多深;但是显然只有朝这方面努力才可能发现历史的法则。而直到现在,人类用在研究这个方法上的聪明才智,还不到历史学家用在叙述许多国王、大臣、将军……等作为上的百万分之一。”

               看不见的运作

  在这里,结构指的不是论证上的逻辑结构,也不是指那些组织平面图所显示的结构。在此,系统结构所指的是随着时间的推移,影响行为的一些关键性的相互关系。这些关系不是存在于人与人之间的相互关系,而是存在于关键性的变数之间;像是人口、天然资源、开发中国家的粮食生产,或高科技公司工程师的产品构想,以及技术和管理要素。  在啤酒游戏中,引起订单与库存剧烈波动的结构,包括环环相扣的多层产销链、其中供需之间的时间滞延(delay)、资讯取得的有限性,和影响每个人下决策的目标、成本、认知、恐惧感等。但是当我们使用系统结构这个名词的时候,必须了解的是,它不只是个人之外的结构。相反的,在微妙的人类社会系统中,结构的本质是微妙的,每一分子都是整体结构的一部分。这也就是说,我们有力量改变置身其中的结构,参与运作。
  然而,我们多半未能认知这样的力量。事实上我们通常全然看不见这些结构怎样运作,只发现自己不得不这么做。
  1973年心理学家辛巴铎(Philip Zimbardo)做了一个实验。在实验中,史丹福大学的学生被安排在心理系大楼地下室,模仿监狱内囚犯与警卫的角色。在狱中,囚犯开始时只是温和地抗拒,但当警卫加强压力时,囚犯的叛逆日益高涨。这个实验直到警卫开始在肉体上虐待囚犯,实验者感觉状况已经严重地失控时才停止。实验在进行六天以后提早结束,因为学生开始受到沮丧的折磨,无法自制的哭泣,并且身心都已经疲惫不堪。
  另外一个令我觉得不寒而栗的是国际政治结构的力量。在苏联出兵阿富汗几个月以后,苏联官员还自诩曾经在阿富汗成立的时候率先承认这个国家;在阿富汗内部斗争、不安定的时候,也曾多次向其政府军伸出援手。七十年代后期,阿富汗游击队的威胁开始升高,执政当局请求苏联增加援助,导致内战的扩大,更加深了阿富汗政府对苏联援助的依赖。而苏联对其侵略阿富汗的说词是:“我们实在不得不进行军事干预。”
  当我聆听这个故事的时候,不禁想到啤酒游戏的零售商或批发商,在游戏结束以后如何解释,他们或许也会说实在不得不持续增加订购量。这个故事也让我想起美国官员在十或十五年前尝试解释美国如何卷入越战的故事。

                蝴蝶效应

  当我们说结构产生特别的行为变化形态,确切的意思是什么?如何辨认这样具有控制力量的结构j这样的知识将如何帮助我们在一个复杂的系统之中更为成功?
  啤酒游戏提供一个探究结构如何影响行为的实验室。参加游戏的人——零售商、批发商、制造商,每周只作一个决定,那便是订购多少啤酒。零售商是第一个突然增加盯购量的角色,并在第十二周左右达到订购巅峰。此时啤酒之所以无法如他所预期的准时送达,是因为批发与制造商那儿已经开始欠货,但是零售商并不曾想过上游的欠货情形,仍不计代价地大量增加订购量以取得啤酒。那样一个小幅的扰动,透过整个系统的加乘作用,竟使得大家的订购量都大幅增加(就如混沌理论所说的“蝴蝶效应”一般——佛
罗里达的暴风,是由于北京的一只蝴蝶翅膀挥动了一下而引起的)。零售商订购单的巅峰大约在40单位,制造商的生产巅峰大约在80单位。  结果起先每个角色的订购量都不断增加,然后再陡然下降。这种变化形态,从零售商到制造商,愈往上游愈放大。换句话说,离开最终消费者愈远,订购量愈高,也跌得
愈厉害。在每场游戏中扮演制造商的人,都遭遇到重大的危机,在每周生产40、60、100或更多的量后没几周,就一直以接近零的生产量直至游戏结束。
  游戏中另一项值得注意的行为变化形态,可以由库存与欠货数量中观察到。零售商的库存大约从第五周开始降到零以下。零售商的欠货数量在继续增加了几周之后,它的库存在第十二周到第十五周还是未能回到正数。同样的,批发商的欠货情形大约从第七周开始,持续到第十七周左右;制造商的欠货情形大约从第九周开始,持续到第十九周左右。然而一旦库存开始又有存货,它的数量便开始激增(在第三十周,零售商大约为40,批发商大约为80到120,制造商大约为60到80),远高于所期望的量。所以每一个
角色都经历“欠货——存货”的循环:先是库存不足,然后库存过多。
  尽管消费者的需求是稳定的,然而以上所言“欠货——存货”的循环变化形态仍然发生。消费者实际订购数量只变动一次,即在第二周从每周4箱啤酒增加为8箱,之后一直到游戏结束,仍然是每周8箱。
  换句话说,消费者的需求提高了一次以后,在随后的模拟过程中一直是平稳的。当然,参加游戏的三个角色,除了零售商以外,没有人知道消费者的需求,而零售商所得到的消费者需求讯息,也是每周一次的片段讯息,没有什么线索得知接下来会发生什么事。

              究竟发生了什么事?

  啤酒游戏结束以后,我们要求扮演批发商与制造商的人,画出他们心里所认为的、最下游消费者的需求情形。大多数的人是画一条有起有落的曲线,就像他们所收到的订单有升有降那样。换句话说,这些参加游戏的人认定,如果在游戏中所收到的订购量又升又降,必定是由于消费者的需求大起大落。比种认为有一个“外部原因”的假设,正是非系统思考的特性。
  参加游戏的人关于消费者需求的猜想,说明了在发生问题的时候,我们常一味地归咎并责备某些人或某些事情的倾向。游戏结束时,许多人深信元凶是游戏中担任其他角色的人;但在看到同样的游戏,不论是什么人来扮演这些角色,每次都出现相同的问题,他们原来所深信的假设才被粉碎。然而仍有许多人将归罪的箭头指向消费者,他们推论必定是消费者的需求暴起暴落。但是,当他们将自己的推测与消费者稳定的需求量作过比较之后,这个推论不攻自破。
  啤酒游戏有时会对参加者产生极大的冲击。我永远忘不了一家大货运公司的总裁颓然跌坐,睁大眼睛凝视啤酒游戏的图表。到了再次暂停休息时跑去打电话。他回来的时候,我问:“发生什么事了?”他说:“就在我们到这里之前,我的最高管理团体刚刚完成三天的营运检讨。其中一个部门的车队运用,有非常不稳定的波动。似乎相当明显的是该部门的总裁没有做好工作。我们当然就责怪这个人,正如在我们这个实验中的每个人都不假思索地责怪制造商一样。但我刚刚猛然醒悟,这些问题或许是结构性的,而不是个人的。所以我方才冲出去打电话回公司总部,取消解雇他的手续。”

             如何改善啤酒游戏的绩效

  当大家了解不能再责怪他人或顾客,参加游戏的人还有最后一个责怪的对象——系统。有些人说:“这是一个无法管理的系统,问题在于我们未能互相沟通。”这种论点已被证实是站不住脚的。其实,即使是像这样一个出货时间迟延、资讯供应不足的存货模拟系统,仍有许多改善的可能性。
  为了能够先让各位了解改善的可能性,首先让我们假想,如果每一位参加游戏的人,都不采取任何改正库存过多或过少的行动时,会产生怎样的结果。如果遵循“没有策略”的策略,每一位参加游戏的人只是发出与他收到的订单相等量的新订单;这可能是最简单的订购政策。换句话说,如果你收到新进来4箱啤酒的订单,就发出4箱的订单;收到8箱啤酒的订单,就发出8箱的订单。

              “没有策略”的策略

  如果参加游戏的三个人完全遵循这种“无为而治”的策略,大约到了第十一周,三个角色便都趋向“稳定”。也就是零售商与批发商一直处于欠货状态。在这个简单的游戏中,持续的欠货之所以会发生,乃是由于所订购的数量迟延交货。而这些参加游戏的人并没有花力气去改正它们,因为没有策略的策略先就排除以大量的订单调整欠货。
  没有策略的策略成功吗?或许大部分参加游戏的人会说不,因为这样的策略造成欠货数量居高不下,使系统中的每一个人必须花很长的时间去等候自己的订单交足。在真实的情况中,无疑的这种情形将引诱新的竞争者进入市场,他们可能以提供更佳的交货服务来取胜。只有对市场有独占能力的产销公司才可能坚守这样一个策略。  但是这个策略却能消除如前所述订购量急遽上升、下跌,以及相伴而生的库存波动。
此外,在没有策略的策略之下,由三个角色所产生的总成本,低于大部分(75%)参加过游戏的人所造成的成本。换句话说,大多数参加游戏者(其中许多是经验丰富的管理者)表现的成绩,比使用没有策略的策略的人差。也就是说,在尝试改正的成本不均衡状态时,大多数参加游戏的人矫枉过正,愈弄愈糟。
  另一方面值得注意的是,这些参加游戏的人,有大约25%的得分,比没有策略的策略为佳,其中有大约10%的人分数好很多。换句话说,成功是可能的。但是这需要大多数参加游戏的人转变观点:深入体认“我们习以为常的思考方式所了解的”与“系统实际运作情形”两者之间根本的差距,也就是我们后面所称的“改变心智模式”。大多数参加游戏的人只专注于自己这一部分的工作,但真正需要做的是,看清自己这一部分与其所处的更大的系统如何互动。

              被切割的局限思考

  假设你也参加这个游戏,不论扮演任何一种典型的角色,想想看你的感觉如何。你密切注意自己的库存、成本、欠货数量、订单和出货情形。像大多数的批发商与制造商一般,到了游戏后半,你会百思不解,原本预期应有大量订单源源不绝而来,却忽然一周接着一周出现零订单的情况。另一方面,假想你是制造商,你以出货回应新的订单,但是你一点也没有意识到出货对于批发商下一回合订单的影响。同样的,如果你是批发商,对于所下的订单会发生什么事情,你也不很清楚;你只是期望在合理的迟延之后订
货送到。那么你对系统的认知范围就只有图3—8的白色部分所示,右边的环被切断了。
  
  按照图上白色部分所认知的情况,如果需要啤酒,你只要向上游发出订单便好了。如果你的啤酒没有按预期时间送到,你就发出更多的订单。你算是把自己的这部分的工作处理妥善,亦即对进来的订单、送来的啤酒,以及你的供应商没按预期时间交货等外部的变化都作出了反应。
  局限思考的典型疏失,在于误认为自己订单与他人订单的互动方式,所影响的变数是“外部的”。绝大多数人对于自己是较大系统内一部分的这个事实,认知非常模糊。譬如,他们并未想到自己发出的大订单,会把供应商的库存吸得精光,因而造成供应商交货更加迟延。他们更没想到,如果接下来以发出更大的订单以应付交货迟延,将导致一个恶性循环,加重整个系统的问题。

nbsp; 这个恶性循环会因任何一位参加游戏的人发生恐慌而开始加剧;无论他是系统的任何一个角色,即使是制造商,都会因未能生产足够的啤酒,而产生相同的恐慌效应。最
后,当一个恶性循环牵动另一个恶性循环时,恐慌便会上下扩散到整个产销系统。系统
一旦被恐慌所主导,各人就会发出超过实际需要20到50倍的订单,这是常见到的现象。

               扩大思考的范围

  要改善啤酒游戏的绩效,参加的人必须扩大思考的范围,如图3—9的白色部分所示。在任何一个位置上的人的影响,都会超出自己位置的范围以外。例如当你发出订单时,供应商送啤酒来,所以你的订单影响供应商的行为,接着他的行为还会影响另外一个供应商的行为。其次,你的成功不仅受到你所下订单的影响,也受到系统里面其他每个人的行动的影响。譬如,如果制造商的啤酒没货了,很快的其他每一个人也都将没有货。大的系统顺利运作,每个部分才能顺利运作。在啤酒游戏以及许多其他的系统中,自己若想成功,必须其他人能成功。此外,每位参加游戏的人必须有此共识。因为,如果任何一位参加游戏的人产生恐慌,而发出一张大订单,恐慌便会透过系统而愈演愈烈。  这里有两项关键要领提供给参加游戏者参考:
  第一,要把你已经订购,但是由于时间滞延而尚未到货的啤酒数量牢记在心。我的一帖秘方是:“吞两颗阿司匹林,然后耐心地等。”如果你头痛需要服用阿司匹林,你不会每五分钟吃一颗阿司匹林,直到头痛消失为止,你会耐心地等候阿司匹林产生药效,因为你知道阿司匹林要迟延一段时间以后才产生作用。许多参加游戏的人每一周都订购啤酒,直到他们的欠货额消除为止,其后果可想而知。
  第二,不要恐慌。当你的供应商无法像正常那样,迅速地给你想要的啤酒时,你所能做的最糟糕的决策就是订购更多的啤酒;然而这正好是许多参加游戏的人所做的。当欠货的数量持续累增,而顾客大声抗议的时候,便更需要修炼来抑制自己订购更多啤酒的冲动。缺乏这种修炼,你和其他人都将遭殃。
  大多数参加游戏的人抓不住这些要领,因为只有当你扩大自己的思考边界,而了解不同角色之间的互动情形之后,这些要领才显而易见。“吞两颗阿司匹林,然后耐心地等”这项要领,来自于了解供应商在处理订单出货上,会有一段时间的迟延。“不要恐慌”这项要领,来自于了解你再发出的订单,将恶化供应商交货迟延的现象,而导致恶性循环。
  如果参加游戏的人依照这些要领去做,他们能改善到什么程度呢?要完全消除所有过度订购与“存货/欠货”的波动循环是不可能的,但要把这些不稳定控制到一个较小的程度是可能的,我们大约可将总成本降为平均参加游戏者的十分之一。也就是说,大幅改善是可能的。
    更新思考、去除智障
  第二章中所描述的七项学习智障全都可在啤酒游戏中发现:
  ●“局限思考”使人们无法看到自己的行动如何影响其他的角色。
  ●随后当问题发生时,他们旋即“归罪于外”,“敌人”是参加游戏的其他角色,甚至顾客。
  ●他们“主动积极”解决问题,发出更多的订单,反而把事情弄糟。
  ●超量订购是逐渐累增的,因此他们像“被煮的青蛙”般,并没有意识到情况的严重性,直到情况已无法扭转。
  ●他们“未能从经验学习”。自己的行动在系统内其他地方所引起的后果,最后回过头来造成自己的问题,却责怪他人。
  ●通常每个角色是由二至三人所组成的团体来扮演,当决策出问题时,只会互相归咎责备,无法共同学习。
  啤酒游戏使我们深入体认,在复杂情况下,这些学习智障与我们所习惯的思考方式之间的关系。大多数人在玩完这个游戏的时候都感到单调而不满,因为只是做些被动的反应而已,然而大多数人后来也体认到,这种被动的反应源于自己只专注在一个又一个星期的事件上。参加游戏的人,大多数被库存及新送到啤酒数量的不足、以及订单的突然增加所震慑。当被问及为什么会这么作决定的时候,他们大多会针对事件做解释:“我在第十一周订购了40箱,因为我的零售商订购了36箱,清光了我的库存。”只要他
们持续“专注在事件上”,他们就注定只能采取被动的反应。

             掌握结构层次的洞寨力

  如图3—10所示,以系统观点解释复杂的状况,有多重的层次。在某些意义上,所有的层次都同样的真实;但其效益则十分不同。如果以“谁对谁做了什么”的事件层次来解释事情,注定会采取反应式的立场。如前面所谈到的,事件的解释在当代的文化中最为常见,而这正是为什么反应式管理盛行的原因。
  根据行为变化形态层次而提出的解释,则专注于察看较长期的趋势,并评量他们的涵义。譬如在啤酒游戏中,其中一种行为变化形态的解释可能是:“产销系统本来就是循环而不稳定的,你离开零售商愈远,情形将变得愈严重。所以迟早制造厂商会有发生严重危机的可能。”行为变化模式的解释开始打破短期反应的局限,至少它建议如何在经过一段较长时间之后,能顺应变动中的趋势。

         图3—10 以系统观点解释复杂状况的层次

              系统结构层次的观点
            (能改造行为的变化形态)
                  ↓
            行为变化形态层次的观卢、
             (能顺应变动中的趋势)
                  ↓
               事件层次的观点
             (采取反应式的行为)

  第三个层次“结构性”的解释最罕见,但却是最强有力而具有创造性的。它专注于回答:“是什么造成行为变化的形态?”在啤酒游戏里,结构性的解释必须显示发出的订单、出货、库存如何互动,而产生所观察到的不稳定与扩大的效应,以及考量迟延交货对新订单交货的影响,和可能引发的恶性循环。结构性的解释虽然不易找到,但是一旦当我们能清楚而全盘地从结构层面来解释时,其效力则非常之大。
  美国总统罗斯福便是一个具有这种洞察力的领导者。1933年3月12日,罗斯福透过无线电广播,解释为什么银行要休假四天。当时全国正陷入恐慌,罗斯福平心静气地说明银行系统结构性运作的情形。他说:“让我简单地说,当你把钱存入银行时,银行并不是把钱放入保险库内,而是把你的钱投资在许多不同形态的信用事业上,如债券与抵押等。换句话说,银行运用你的钱使经济保持转动。”他解释何以银行需要保有储备,
如果提款的人大多,则会造成储备不足,进而提出为什么银行歇业四天对于重整金融秩序是必要的。他最后说服公会支持这项激进但是必要的措施,也从此获得大众沟通高手的美誉。
  结构性的解释之所以如此重要,是因为只有它才能触及行为背后的原因,并进而改进行为变化形态。结构影响行为,因此改变背后的结构,能够产生不同的行为变化形态。
在这个意义上,结构的解释就深具创造性。由于人类系统中的结构,还包括系统中许多影响我们如何做决定的因素,因此,重新设计我们做决策的方式,等于重新设计系统结构。
  对大多数参加游戏的人来说,最大的收获是深入体认到,门己的问题以及改善的可能,全都无可避免地受到自己思考方式的影响。真正具有创造性的学习,在一个以事件思考为主的组织里,无法持续。它需要一个结构性或系统性的思考架构,也就是找出行为背后所有结构性原因的能力。光有创造未来的热忱是不够的。
  当参加啤酒游戏的人了解行为背后的结构后,他们更清楚地看见自己改变这些行为的力量,也因此能采取能在大系统中更有效运作的订购决策。他们也印证了几年前柯利(Walt Keily)在他的漫画《扑高》(Pogo)中的一句名言:“我们碰到敌人了,敌人就是我们自己。
posted @ 2006-09-08 11:31 hopeshared 阅读(826) | 评论 (0)编辑 收藏

BBS: 电子公告牌系统,常被翻译为讨论板、论坛等,一些网络虚拟社区的核心也是基于BBS的核心。天涯,猫扑等是典型的web BBS服务。

SNS: Social Network Service, 社会性网络服务,其核心在于强调用户之间的联系,是最近开始流行的一种新型网络服务。 UUZone是典型的SNS.

经常有朋友问我: SNS和BBS到底有什么区别啊? UUZone的小圈子里不也就有个BBS吗?

其实从泛社会性的角度来看,BBS具有很强的社会性, 应该也属于早期的社会性服务,其实email, IM等任何能把人和人连接起来的软件和服务都具备社会性。 但是目前所流行的严格意义的SNS和BBS, EMail等前一代的网络服务相比,却有着一些区别,也许这些区别从形式上看并不那么大,但是正是这些小小的区别,在用户的线上行为模式和使用体验,以及社会性效果方面却可能发生巨大的变化。 可以认为SNS类服务,其实是BBS等前一代社会性网络服务的一种进化,是一种进步。

最近著名的BBS天涯上出现了一个所谓“杀人事件”,说的是一个女大学生网上“卖身救母”,很多朋友倾囊相助,结果却有人质疑其不诚信,然而最终其母亲却身死于手术台。王小山的“天涯网友集体杀人事件”一文谈了这件事情,并且直接表达了其看法。 我本人支持王小山的看法,我讨厌那些道貌岸然的卫道士,虚伪地维护所谓“真相”,欺负一个年青弱女子。 最喜欢他文章结尾讲的一个故事:

好像(记不清楚了)是在阿根廷,一个高尔夫球冠军得了一大笔奖金,领完奖走向停车场时,遇到了一个女人。女人向冠军诉说自己的不幸:她的小孩子得了重病,眼看就快死掉了,需要一大笔钱去医院。冠军把自己的奖金全部给了女人。

冠军的朋友说:你上当了,那个女人是附近著名的骗子,她根本没有孩子,她骗了你的钱。

冠军说:你的意思是说根本没有一个小孩子快病死了?这是我今天听到最高兴的事了

的确这个社会上有很多人利用了别人的同情心。利用别人的同情心而谋私利,是一种无耻的行为; 但如果因为会有人利用而变得没有同情心,那是将是更大的错误; 而以担心被利用而作为自己没有同情心的借口, 其实是另外一种利用别人的同情心,所谓五十步和百步的区别。

其实在BBS类的系统上,这样的争议和故事在不断上演,而随着这类故事的不断上演,BBS系统在社会性方面的弊端正不断显露出来。SNS其实正是在经历了这么多大大小小的问题后产生的一种演进。

BBS更强调帖子, SNS更强调人

毫无疑问,这是一个现象上的明显区别。 BBS中的人物主要通过其ID, 以及这个id发表的文章来呈现。 虽然不少bbs已经扩展了很多个人资料:比如照片,联系信息,个人简介等,但毕竟bbs本身的特点决定了文章是bbs的主体。

SNS中的主体是人,其他一切,无论blog, 文章,照片等一切都是人的附属物。 一个真正意义上的sns, 功能和一个改进的bbs完全相同, 其侧重点一定是人。

当SNS更侧重于人的时候, 很多线索围绕人来展开了,而不是围绕一个帖子、主题来展开。 这时候,这个人的资料、文章、照片、blog、过去的行为等 成为了这个人网络化生存的佐证。

信息在BBS上的传播范围是系统范围扩散的, SNS上的传播范围是关系链型扩散的

BBS是全开放的, SNS是半开放的,其实并不能绝对地说“BBS是全开放的, SNS是半开放的”,只是一种总体上的概括。 为什么说bbs比较开放,而sns是半开放,我主要从信息传播的方式来看的:从传播角度来看,信息在BBS上的传播范围是系统范围扩散的, SNS上的传播范围是关系链型扩散的。相对来说,SNS更加接近人类社会现实的信息传播 -- 所谓“一传十,十传百”。

由于sns需要人来作为传播媒介,所以其目标人群和扩散速度也许没有bbs那么迅速,甚至由于中间人的原因,而发生停顿或者变化。

坏行为在BBS上无法得到有效惩罚, 坏行为在SNS上相对付出成本更大

BBS上是一个真正鱼龙混杂的地方,所谓林子大了什么鸟都有。而BBS最大的问题是,当用户有“坏行为”的时候,几乎没有实质性的处理措施。 一般只能通过删贴、封ID甚至封IP的办法。而这些动作其实对进行坏行为的用户惩罚性不强,换个马甲,很快就可以卷土重来。

而SNS上,很多SNS要求用户之间必须建立某种联系才能执行某些进一步的动作; 并且SNS基本都提供黑名单等工具,用户的坏行为一旦产生,可能导致很难继续打入某个圈子。 重新换一个马甲再来,在sns里往往需要较长的经营时间, 所以sns里,用户往往倾向于更加友好和对言行负责。 个别新来的“捣蛋鬼”很快会被ignore所惩罚,要么选择离开,要么选择“重新做人”。

SNS比BBS更像人际社会,而人际社会中很多情况是靠自律、社会舆论、关系等形成的无形的掌控力维护了人际社会的秩序。

BBS上容易出现信息泛滥,SNS通过人际关系来过滤信息

原理同上,BBS很容易成为一个信息泛滥,信息过载的地方,也会成为一个spam, 扬言惑众的场所。

SNS从技术上来说并不比bbs高明,但sns中信息的传播没有那么容易,需要通过人。所以通过人际关系,信息传播中获得了过滤。

BBS的规则是版主制定的, SNS的规则是由用户来形成的

这是非常重要的一点,BBS是版主为中心的, 游戏规则是版主定的。SNS是每个人自我中心的, 规则是一群人来共同形成的,SNS更加接近于人类社会的社群。

最后来分析一下这个“天涯社区网络杀人事件”。 王小山的“天涯网友集体杀人事件”一文和相关的链接可以看到一些,在他的blog上有个朋友的总结性留言把整个事情的过程回放得较清楚,原文太长摘录部分如下:

天涯之善行这几天演进得叫人哭笑不得,他们中那么大的一个群体,打着善行的旗帜,偏偏干着解构主义的行为艺术。让我们细看这次网络善行的全过程:

第一幕:卖身救母。陈易说她的母亲有严重肝病,需要进行第二次肝移置手术才可能有机会活下来,陈和母亲相依为命,她非常爱她,宁愿卖身筹措第二次手术的巨额费用。

第二幕:捐款。天涯很多人被打动了,解囊相助,据说捐款有十万之巨。

第三幕:怀疑。天涯网友开始怀疑陈易求助的动机,要求其公开帐目。

第四幕:调查。有一个叫八分斋的网友集资去重庆调查,并出具一份文学色彩非常浓的调查书,而且调查的过程中不知怎么搞的西西TV也跟着凑热闹;更有一些技术高超的IT小偷们撬开了陈易QQ记录,“本着公义之举”把陈易隐私大白于天下。

第五幕:陈母之死。八分斋带着集体的名义要求陈母在一个月内动手术,否则要求陈易把捐款转移给红十字会,这样陈母在压力下不得不限期手术,最终在手术台上大出血死亡。

第六幕:道德审判台。至爱母亲的死去并没有成为这次事情的终结,反而陈易被钉在道德的审判台,被疯狂的大众贴上“骗子”,“骗子集团”的大字报,她的灵魂被天涯竞相践踏,其情状仿佛让故去的陈母被鞭尸三日,活着的陈易被脱光衣服遭千人凌辱也不足以解恨。

王小山用案发现场来给出了天涯论坛的地址,但跑过去看感觉头绪很多,各种观点,各种马甲,林林总总, 根本看不出一个主线。 当然这就是典型的热闹BBS呈现出来的感觉。

这里不去说这件事情本身如何。 至于我的观点,我和王小山的观点基本是相同的,在本文上篇就已经清楚表达了。

和天涯的事情类似,前一段时间,一群blogger们也采用blog, sns等方式为另外一个生病的朋友王俊传播过捐款信息,捐款超过15万,王俊也顺利接受了手术,详细的信息可以参见爱心援助王俊的Bloghttp://befresh.bjug.org/),那里不但有事情的全部而且有捐款的具体数量和详细信息。王俊的网络捐赠活动可以说是很圆满的,但其涉及的传播面,造成的社会影响,引发的争议,远远比不上天涯的这件事。 blog已经具备非常多的SNS的特性, 甚至不少人认为一群blog链接成为网络就是一个SNS. blog和bbs的现实性不言而喻,但就是那么一点点区别,造成了用户行为和结果的截然不同。

比较这两件事情,可以看到这些:

  • BBS传播能力很强,传播速度很快,范围很广; blog/SNS传播扩散较慢,但一旦形成扩散后范围也足够广
  • BBS传播的结果是受众太广,结果什么人都来了,有捐款的善人,有同情者, 但也有无聊的好事者,打算乘机炒作的,企图做道德卫道士的... Blog/sns传播的对象范围较集中,由于人 在其中起了很大作用, 所以一般不相关的人接触机会相对少
  • BBS如同一个混乱的大公共集市,由于什么人都有,发生摩擦、引发群殴、拉帮结派的情况容易发生; blog/sns更加个人化,秩序是各自维护的, 不太容易发生bbs上那种混乱的局面;

天涯目前也正在开始提供blog服务,mop这个和天涯齐名的bbs也开始了朋友圈的sns业务...这些都可以看到BBS自身也已经意识到问题的存在,并且开始在引进sns的特性来完善自身。

现在的SNS并不完美,但可以看到比起古老的BBS, SNS属于慢热的那种,但是一种进步。 而在这样一个不完美的SNS上,用户仍然可以通过巧妙欺骗性经营sns, blog上的形象来达到不良目的,其实欺骗、混乱等问题并不能本质上消除,只能比bbs有一定程度的改善。

不过回过头看看我们的社会, 虽然总体上是好的,也在不断进步,但各种丑陋现象还是难以根除的。SNS只是比BBS更加接近一个社会,因此SNS将比BBS有更强的生命力,会成为未来网络服务的一个核心业务。

原文:http://www2.uuzone.com/blog/mao/47969.htm

posted @ 2006-09-07 18:21 hopeshared 阅读(655) | 评论 (0)编辑 收藏

      “个人中心”是最近比较热门的词汇。按照解决问题的不同,我想至少可能存在两种类型的“中心”。一种是个性化的信息获取、处理、存储、共享工具,帮助我们实现信息到知识的转化过程。 OKRSSPOTU读岛等可先归入此类。另一种是建立网络环境中的个人身份。根据自己的意愿,人们将不同侧面的个人信息与在线行为集中于一组页面中展示。往往通过浏览这组页面,就能够获取足够的信息对人物的基本特征作出判断,大脑还会根据个人经验在勾勒人物的形象、性格、品德、气质、趣味等方面加入必要的想象成分。这种“个人中心”与“SNS中的节点”,“社会化blog”都是在解决相同的问题,只是观察角度不同,称谓亦不同。
      现实生活中我们会经常遇到类似的判断过程,譬如,人事经理通过简历判断是否给予应聘者面试机会。通过浏览彼此的“个人中心”,人们会产生交往意愿,在整个互联网中扩展,形成网状关系结构,任意“个人中心”都可视为这个巨大的网状结构的中心,就个人而言我们通过网络拓展了自己的关系圈,形成多对一的关系结构。突破以往封闭的SNS系统,将个体置于开放的网络环境中,仿佛从孤岛重归社会。
     “个人中心”通常会存储如下个人信息:称谓,性别,年龄,照片,学历,职业,兴趣,简介,意愿等。这些静态信息描绘出人物的“平面”特征,但这还不够,只有结合人物的在线行为记录,方能刻画出成立体,饱满,鲜活的个性人物形象。譬如blog,评论,书签,opml等都从不同角度记录着人们的网络行为。通过阅读,分析这些行为记录,可以判断出谁更符合我们的交往意愿。需要注意的是这些信息往往分散在不同的在线服务中。为了能够利用这些信息,需要“个人中心”具备信息聚合的能力或是干脆提供相关的服务用已记录人们的网络行为。
     “个人中心”需要为建立,维系人际关系提供适当的工具。通常采用好友列表,好友共享,短信等手段。朋友说IM就是最好的SNS,确实无法否认在交流方面IM具有得天独厚的优势。就象我们的“嘴”对于表达思想所起的重要作用。如果说将IM比作人们的交流中心。那么“个人中心”则是人们的活动中心。我想IM与个人中心还是不能完全等同的,毕竟他们所解决的问题并不相同。
     现实生活中很少会有人刻意表白自己认识某某人,拥有多少好友,哪怕是对最好的朋友也不会如此,因为没有这样做的必要。朋友作为一种个人资源,只有在合适的场合,才会被用来为其他人提供帮助,这取决许多十分复杂的因素相互作用的结果。而网络中情况恰好相反,大多数SNS都会将个人的好友列表在一定范围内公开共享,目的是促进人们通过朋友建立起更广泛的交往。在我看来这是一种很游戏化,娱乐化的做法,时常会看到某人的圈子里拥有数十,数百好友。但我想这种好友的作用可能也就只会局限在网络中吧!相信大多数人都会有自己习惯的关系维系方法,譬如:通讯录(不会随便给别人看)。突出的表现好友列表只会将复杂的人际交往简单化,表面化而使SNS流于肤浅。这不会比一个EMAIL列表更实用,也不会比blogroll的覆盖更广泛。
       针对“个人中心”一定还会出现更多不同的理解,但无论如何这都说明用户的地位正在比以往任何时候都受到服务商的重视。未来的用户体验将会随着各类“个人中心”的出现与发展发生巨大的飞跃。

http://www.360doc.com/showWeb/0/0/58610.aspx
posted @ 2006-09-07 18:11 hopeshared 阅读(373) | 评论 (0)编辑 收藏

1、 人的影响力是不同的、影响力是有价值的;
影响力的价值往往体现在它所促动的事件、物品上,比如合作、交易、馈赠、继承等!
2、不同人对不同人的影响力是不同的。人之间的关系是分级别的,从亲人到陌生;
显然,小布什和胡锦涛对我的影响力是不一样的!
另一方面,同样是潘石屹,对我妈妈和我的影响力是不同的,我妈妈根本不知道他是谁,连他的名字都不知道怎么念;而我却非常希望能够见到他!
人之间的关系从近到远可以分为,至亲、近亲、亲戚、好友、朋友、相识、耳闻、陌生。
3、一个人对你的影响力最初来源于你周围的人对他的评价;
首先,这要抛开媒体不讲,媒体只作用于个别公众人物,他们是特例。
你刚开始了解一个人的时候,所获得的信息大都是你周围的人对他的评价,周围的人重视他,你就重视他!
你小的时候,第一次见到你的舅舅,你的爸爸妈妈对他非常好,你就知道这个人比较重要。之后,他再给你买糖果、买玩具,你们之间的关系就更密切了!
你上中学的时候,你听周围的同学议论隔壁班的一个男生,总爱在外面打架,你就知道这小子不好惹!
4、影响力具有传递性,有影响力的人周围的人也会具有影响力。但影响力传递的级数是有限的,一般不会超过两级,信息的传播也是一样;
江泽民有影响力,江泽民的儿子同样有影响力,江泽民的儿子的同学也会有一定的影响力,但是江泽民的儿子的同学的朋友的影响力就比较小了!
你有事情请求朋友帮助,朋友会再找他的朋友,但大多数情况下,朋友的朋友不会再找他的朋友,这种请求帮助的信息不会传递到更远的关系。
5、人之间的关系有时间性,是由不断发生的事件来维持的。每个人同时可维持的关系是有限的;
你大学时期的同学可能是当时你最好的朋友,但是会因为常年失去联系而疏远。而你到公司的新同事,却因为不断接触而成为你最亲密的朋友。关系是由不断发生的事件而维持的,正向事件加强关系,负向事件破坏关系。
而一个人同时可维持的比较密切的关系是有限的——仅仅记录在通讯簿里的是不算的,按照六维理论和全球60亿人口计算,这个数字大约是50。
6、人之间的关系有方向性,甲信任乙,乙不一定信任甲;信任取决于掌握对方信息的数量和真实度;
人之间的关系有方向性,你把张三当作你最好的朋友,但是张三并不一定把你当作他最好的朋友。
信任取决于掌握对方信息的数量和真实度。
如果你知道对方的身份证号码、家庭住址,那么你借给对方钱的可能性就更大!
如果你知道对方企业的地址、规模,那么你跟对方做生意的把握就更足!
一般情况下,掌握对方的信息越多,就会对对方越信任!但真实度是一个比较难把握的事情!骗子往往就利用这一点来骗人,他们往往主动向被骗者披露一些比较难验证的虚假信息,让被骗者以为掌握了比较多的信息,增加信任度!

前面我提到,有一些SNS方面非常不错的网站,比如联络家www.linkist.com和天际网www.tianji.com!但是,似乎都没有寻找到很好的盈利方式,如果我们能够按照反观现实社会的思路,我相信是不难找到好的盈利方式的!

欢迎多交流!
Email:LidaChina*126.com(请将*换为@)
MSN:Lida.China*hotmail.com(请将*换为@)
Blog:LidaChina.bokee.com

原文:http://www.360doc.com/showWeb/0/0/83865.aspx
posted @ 2006-09-07 18:06 hopeshared 阅读(878) | 评论 (0)编辑 收藏

前几天,突然想到算算父母为了培养我究竟花了多少钱。

从小学到高中,在印象中学费都不是太贵,尽管那个时候钱比较值钱。这部分的钱不好算,毕竟吃住都在家里,再说还有9年义务教育。

从大学开始,开销明显增加。

我的大学学费4860/年,生活费300/月,住宿费1200/年。除此以外,经常回家“蹭饭”,买衣服的钱也是家里出。还有,电脑,还有考研的开销,还有大四最后一个学期的开销。大学四年的总开销>5w。

研究生的开销更大了,学费4w加上2个学期的生活费1w。后来的生活费就都是自己挣的了。

我们家并不是富裕的家庭,很难想象父母居然在5年内为了我花费了10w块钱。家里并不是只有我一个大学生,还有老人,还有一个准研究生。

本以为就只有我这么能花钱。男朋友拿出存折,惊讶的发现他在大学四年的总支出也是5w多,而研究生的支出也多于5w。他很难想象本科花销那么大,因为在他看来,他本科的四年过的非常节省了。

我不得不想,现在的高等教育究竟想怎么样?高等教育成了国家从人民手中掠夺财富的手段了么?

大学生越来越多,工作越来越难找,工资越来越低,学费却越来越高。说着可以贷款,可是,毕业后谁能还得上这笔钱?花那么多钱读正在严重贬值的大学,有意义么?

现在的社会问题越来越多,不知道是因为我长大了,还是国家出问题了。反正,这么发展下去,倒霉的始终是那些最普通的老百姓。
posted @ 2006-09-06 20:55 hopeshared 阅读(471) | 评论 (0)编辑 收藏

最近,打算做一个项目,但是又不知道这个项目的价值如何。毕竟,公司以利益为目标嘛。
具体的调查在这里

调查的内容如下:
这样的QQ你会用么?

尝试下再决定 
试用,但是不会信任它
不会   
其他

这是一个简单的调查。

我打算做这样一个项目,项目的细节就不说了。从用户体验的角度来说,你可以把它看成是一个新型的MSN或者QQ

作为用户,您的“好友列表”里存在的可能不再是您的好友,而是您订阅的服务。比如天气预报,订机票,订餐等各种服务。试想,如果您想去某个地方,只需要在这样的QQ中跟某个"好友"询问是否可以订票,是否能定到票,是不是可以极大的节省您的时间和精力呢?当然,这里的服务不会只有订票这样简单的,还有很复杂的,比如帮助您设计旅行计划,帮助您理财等等。这些服务可以通过查找的方式找到他们。如果您需要某种服务,而这种服务又不存在,您还可以发布一个这样的需求。当有人现实了您的需求,这个QQ会立刻通知您。

作为服务提供商,您可以发布各种服务。我们还可以将用户发布的需求提供给您,同时统计出某个需求的需求量,让您清楚地了解此类需求的商业价值。

我们的系统将会监控各个服务的质量,并将最新的服务质量立刻发布给用户和服务提供商,用这种方式维护整个系统中服务的有效性。

这样的系统是否吸引你呢?请投上您的一票,谢谢!
如果对这个项目有什么意见或建议,也请提出来,再次感谢!
 

目前,参与这个调查的人并不多。但是目前看来,大家都觉得这个点子不错,有发展前景。当然,可能也会碰到很多问题。比如,有人说它应该做的简洁、专业,并且需要进行很好的市场推广;有人说不知道它提供的信息是否能满足使用者的需求;有人质疑这样的平台如何收费;有人强调这个平台上的信息可靠性。。。

当然,我也听到了反面的声音。其实这个时候反面的声音更有价值。

质疑:
1.QQ本来是个沟通交流平台,不是信息平台.想要信息东西网上的资源很多
2.这样的信息平台可能开始会有点作用,但是运行一段时间之后很可能会变相成为广告平台
3.这跟直接在网上搜索什么区别
4.天天开机就要挂的那种,占用资源,浪费带宽
5.对于服务,不是时时刻刻都用,可以说,很多是很久才会查查。如果你天天电脑上N多东西,肯定不爽

回答:
1.这不是im,不是qq,不是msn,而是一个服务平台。你可以把服务想象成商品,在这个平台上使用和买卖。只不过,它的外型跟qq相似罢了
2.作为用户,你有权利来对一个服务作出评分。其他人在使用这个服务之前,会查看别人对这个服务的评分。你有权利加服务为好友,发起者只能是用户而不能是服务,两个对象不是平级的
3.网上搜信息要时间并且找到的也不一定可靠。再说,你需要某种服务,如果找不出一个合适的,怎么发布出需求?怎么匹配出别人实现了你想要的东西?
4.qq刚出来的时候,有多少人能够预见到今天这么多人一上网就开qq?一旦上网成为习惯,必然有些软件随着这些习惯变成生活必需品。
5.等到有一天你发现发邮件,打印,定会议室,打电话都成了一种服务,你就会改变这种看法了。我们做的这个就是想把什么都做上来,任何东西都可以作为服务,你刚刚说的全都是。每个人都在这个服务的海洋之中寻找自己需要的那一小部分服务。那么,你的机器上就不再需要装很多东西了,一个就够



一旦上网成为习惯,必然有些软件随着这些习惯变成生活必需品。ie是这样,msn是这样,qq是这样,下一个是什么呢?

posted @ 2006-09-05 13:08 hopeshared 阅读(765) | 评论 (2)编辑 收藏

一个完整的社会化网络,应该由三个部分组成:

  1. 网络结构(network)。社会化网络的骨架,也就是通过什么样的技术和手段来把独立节点联系在一起。
  2. 内容(content)。社会化网络的血肉,也就是每个节点能够为这个网络提供什么样的内容。
  3. 通过网络处理内容的方式(method)。以什么样的方式重新组合和处理网络中各个独立节点的内容,好的方式将会产生量变到质变的结果。

来看看这三个要素在一些典型的社会化网络产品中是如何作用的。
Orkut,用户是独立节点,通过用户之间的关系可以建立很好的网络,除了论坛的帖子,和节点相关的内容主要是个人资料和关系,除了继续发展你的关系,这样的网络很难有更多的用处。网络结构:强。内容:弱。内容处理方式:弱。
Wallop,有一个类似Orkut的网络,以用户的Blog、照片、媒体文件等作为内容,通过网络可以方便得知属于你的网络中的其他用户的信息。网络结构:强。内容:中。内容处理方式:中。
delicious,网络结构比较松散,但网络中的节点(用户)提供大量的内容,对内容有极好的重组方式。网络结构:弱。内容:强。内容处理方式:强。
flickr,通过关系或者兴趣组成一定的网络结构,节点(用户)提供大量的内容,通过tag和group等对内容进行重组。网络结构:中。内容:强。内容处理方式:中。
没有对这些社会化网络产品进行评比的意思,只想通过这些例子说明一个问题,对于社会化网络产品,建立起一个网络固然重要,内容和内容的处理方式同样起决定性作用。
blog是一个天然的社会化网络,可以以blogroll建立网络结构,内容也是现成的,唯独缺乏的是通过网络处理内容的方式。technorati是一种处理和重组blog的内容的方式,但它基于的网络结构太松散。要让这个网络变得更有趣,我们还差一步。

原文:http://www.robinlu.com/blog/archives/59

posted @ 2006-09-04 00:03 hopeshared 阅读(408) | 评论 (0)编辑 收藏

这些是如何发生的?
大部份我们所了解和相信的事物是来自于我们所交往的其它人;我们的价值观;理念及信仰受到四周人的影响甚钜着。

用人际关系观察的重点可以追溯到20世纪30年代。从那时开始,学术界已经发展出一系列称为社群网络分析 (SNA) 的技术。SNA 资料通常是用调查的方式收集,然后用图表的统计方法进行分析。统计图表可以更理解人与人之间的相互关系、评估一个组织的健康状况和确认出网络中发挥关键作用的人。在社会中,这样的分析已经被用来理解和帮助缓和社会问题。在业务上,这样的分析已经被用来管理变革,识别关键的人员和专家,提高智力财富的重复使用,理解决策过程。

Milgram's 的小世界实验可能是对 SNA 最著名的使用。Milgram 认为,任何二个人之间的距离不超过 6 层,根据这样的实验,得出了“六度分离 (Six Degrees of Separation)”的概念。由此;人们对于人际关系的科学研究,有一定的认识。

不过,也有证据显示, SNA 正快速地在商业市场上应用,原因有四:

知识管理的兴起。
可视化应用的有效性。
深度运算的技术发展。
资料收集的大幅增加。
知识管理 -- 对企业而言,知识管理 (KM) 是一个相当新的概念。举例说明,在亚马逊 (Amazon.com) 网站上目前列出的数百条企业相关的KM出版物中,最早的日期仅到1994年。现在 KM 已经得到了业界的关注,我们可以进一步讨论 SNA 的工具和潜在的好处。

可视化应用 -- 绘图和其它视觉效果的技术对 SNA 使用化得到原始资料来说是非常关键的,并可使原始资料易于被理解。一部分感谢计算机游戏技术的发展,它让可视化的硬件和软件更新速度越来越快,也越廉价;容易操作,并可处理数量更大的资料。

深度运算的技术 -- 协同过滤 (Collaborative filtering)、专家搜寻器 (expert finder technology)、内容管理 (content management) 和资料采矿软件 (data mining software) 都可以用来辅助 SNA 的工具。另外,使深度运算高级算法和网格系统大量分析相互关系,也使大量资料的运算成为可能。

资料可用性 -- 这是最显著的因素。传统的SNA主要依靠昂贵的调查和存取来获得资料,近年来大量关系相关性的资料也变得易于取得且成本不高。信用卡交易 (credit card transactions)、无线行动通讯 (cell phone calls)、全球定位系统 (GPS) 资料、网络存取 (web site access),、电子邮件 (e-mail)、联网网络通讯 (internet messaging)、ATM 交易、边境管理 (border control)、电子卡付费和超市贵宾卡 (loyalty),都是一些可以获取资料的源头。对这些原始资料的分析就可以协助相互关系分析研究。

SNA的应用越来越广泛,例如,在反恐行动中,可以使用SNA来追踪恐怖分子的已知网络并揭露其它未知的网络。分析其旅行资料、银行交易、电话呼叫和联际网络的使用可以发现恐怖分子制造破坏的时机。 SNA 可以帮助识别:

孤立无援的人 -- 愈缺乏组织关心的人,愈有可能成为讯息提供者。


远离权力中心的人 -- 这样的人可能会散播未经验证的假情报


筹措资金的公共来源 -- 可被破坏或切断。从相似的行为模式进行推断也可以显示出被监视的恐怖分子的支持者和未知的恐怖分子组织。
SNA 也可以被用来帮助创新的思维,在非正规和正规人际网络中,帮助团队衡量出何者更有效率。Gerry Falkowski 率先在 IBM 内部倡导使用 SNA,他最近分析了一个虚拟团队,发现了隐藏在背后的可利用力量,还有未完全参与的部分。因此他做出多个目标建议以改善团队的运 作。且其建议得到团队相互的支持,这样的相互作用很具说服力。

IBM 的知识组织研究所-IBM's Institute for Knowledge-based Organizations (IKO) 已经使用社群网络分析来研究在知识创造和传播中非正规网络的重要性。该项目是弗吉尼亚大学的助理教授、IBM's Institute for Knowledge-based Organizations (IKO) 的研究员;安德鲁×帕克是 IBM's Institute for Knowledge-based Organizations (IKO) 的咨询顾问。IBM's Institute for Knowledge-based Organizations (IKO) 在过去三年中已经研究了 30 个组织中 40 个以上的网络。他们研究的议题包括在知识共享网络中的意识、存取、约定和安全的作用,影响网络发展的个人、组织和组织干涉的用途,个人网络的发展和重要性。且其工作论文已经发表在主流商业出版物上,例如哈佛商业周刊 (Harvard Business Review)、斯隆管理周刊 (Sloan Management Review)、加州管理周刊 (California Management Review) 和组织动态学 (Organizational Dynamics)。

这些对你意味着什么?
社群网络分析是知识管理领域非常重要的方法。目前,SNA 正在被用于企业管理、法律实施和公共卫生。诸如 IBM 全球服务部这样的大型组织和诸如 Humax 和 Verity 这样的小公司都在对所有的事情使用 SNA,从对建立社会资本的支持到研究开发技术专家的使用。但使用的技术和基本的 SNA 概念在诸如社会学和人类学这样的领域时,SNA 应用在实际问题上仍然是一门艺术而不是科学。所得到的分析和判断需要透过其它方式加以证实,且这些分析并没有为组织功能失调订定标准或现成的解决方案。所以 UCINET 软件 (用于社会网络资料的分析) 是被学术界认可的工具,但在一般业界并没有被认可。

SNA 经过成功的实验证实有吸引力。同时,SNA 可以结合到其它工作中。最重要的是,可用于分析大量的资料和相对低的成本可能会促进创新应用的发展,这样就会鼓励对 SNA 的投资和接受度。

社群网络分析已经成为一套理论并且是和受过训练的专业人员的制式训练 (formal discipline)。透过专业人员的使用能够帮助一个公司理解内部沟和策略执行的情况。SNA 也在企业改组和并购中产生关键作用。

两个行业:零售和运输
零售业已经在收集和共享消费者资料方面投入了大量的资金。SNA 可以用来帮助在目标市场中识别群体,并可确定在社群中的意见领袖。在市场交易之外,SNA可以透过发展和支持社群,提供诚信的信息。

运输业很久以前就意识到有个人的价值。行销人员通常是以相关的社群和家庭为目标的。社群网络分析能够对社会组织内部的相互关系和角色提供研究,这样的研究被用于扩大市场,并增强了解组织内部的社会关系。

当技术在组织内部已经把人与人结合在一起的时候,企业结构,无论是正式的等级、网络或基于市场的结构,都已经变得模糊不清和易改变。公司可以使用 SNA 来理解其结构,更深层地理解客户并组织公司的商品或服务对客户提供支持。


原文:http://mmbear.blogdriver.com/mmbear/574999.html

posted @ 2006-09-03 23:58 hopeshared 阅读(2420) | 评论 (0)编辑 收藏

zheng 在今天的Blog谈到了对150法则的一些观点,

“这并不是指一个人所能达到的交往人数的极限。实际上,如果我们回忆起都认识过的人,会发现人数远不止这些。这样看来,“150”这个数字所代表的,是某个方面的同时交往人数。比如我订阅的Blog的RSS数目(每个Blog对应着一个人),超过130多个的时候就感觉在阅读和跟进上有点吃力,当到了180个的时候,发现在加上去就的玩完,什么也做不了。尽管订阅了这么多人的Blog,这并不妨碍我在IM上和其他人的交往。更不会妨碍到现实中的交往了。”----[“150”如何在社会性软件中使用]

我赞同其中“同时”的说法,也就是说,无论你曾经认识多少人,或者通过一种社会性网络服务与多少人建立了弱链接,那些强连接仍然在此次此刻符合150法则(拿人的大脑容量来设计模型是有意义的)。这可以用多种角度来理解,例如你的现实价值被这150(当然不是确定数字)个强连接所反映,所以好的Social Software会有设计得非常完善的评价系统(例如LinkedIn)。拿拇指原则来说,就是80%的社会活动可能被这150个强连接所占有。如果你是一个SalesMan,可能最大的生意都来自与这150个强连接。

从社会学的角度来说,现代人的交往范围当然远远不止150个,甚至很多人曾经有的性伙伴都超过数百人(还记得几个月前的一个流行人物吗?)。但是并非所有认识的人都有机会成为你的强连接,甚至可能经过一段时间,连弱连接都不是了(你还能够联系到所有和你有过email往来的人吗?)。社会性软件的意义在于让你有机会更加多地“导通”可能的弱连接(六度间隔),并“选择和替换”你的强连接组合。所以并不是说,一个人在LinkedIn或者Friendster中有了数百个联系人就凌越了150法则,相反,正是加强了这个法则的含义。

供大家讨论,希望对设计社会性软件有所帮助。在我理解,社会性软件不是技术,而是一种设计方法。

-------------------------------
zheng也在中文Blog心得集上介绍了新出现的亿友社会网络服务,很高兴看到有越来越多的人用不同的方式来探索社会性软件,而且大部分的国内社会网络服务创业者也都在Social Software Mailing List上登记参加开放探讨。唯一的担忧是,如果把交友当作黏着点,把短信当作盈利点,可能会有点南辕北辙。


原文:http://www.isaacmao.com/2004/1/13/

posted @ 2006-09-03 23:57 hopeshared 阅读(587) | 评论 (0)编辑 收藏

SNS(Social Network Service)从其两年前的红火被追捧,到逐渐怀疑论四起, 到前些时候在国内风头被blog服务盖过,其实也是在经历任何事物发展的必然规律:螺旋曲折地发展上升。

作为中文SNS最早也是坚持最久的实践者,我们也在不断地用变化发展的眼光审视和思考这个市场,这里和大家分享一些心得。 这些心得,这两天和建硕、sayonly、Michael等碰头的时候曾经分享过一些,但还没有深入讨论。希望这里能抛砖引玉吧。

我的一个最深刻感受是, SNS正从单一的Social Network Service逐渐在混合各种元素,越来越成为一个Social Network Platform,这个platform可能是封闭的,也可能是开放的,而我相信只有开放的平台才有生命力。

SNS发展经历了这样的一个大致的过程:

早期Social network形式

EMail, BBS, 新闻组,个人主页,虚拟社群等都具有一定的社会性,因此早期的网络social network就是由这些看起来很原始的服务下形成的。

这些早期的social network的关系是潜在的,或者表现为一些“原始”的存在形式,比如链接,blogroll等。

blog具有不错的社会性,但其社会性的体现是比较原始的。

第一代SNS

以friendster.com为代表, 强调和引入了"6度假说"。 早期众多的中文SNS大多是friendster clone.

friendster.com 这类服务除了能帮助连成以自我为中心的“朋友圈”外,能提供的服务非常有限。

第二代SNS

以myspace.com, facebook.com为代表,其特点是引入了social object,比如myspace是音乐为一个重要的社交对象,而facebook虽然功能很简单,但校园生活是一个重要联系纽带。

此外flickr等更加侧重某个功能,但把社会性关系引入的服务也表现出比单纯的social network更加有一些生命力。  

第三代SNS

SNS的发展的最新趋势就是social network作为一个平台而存在,在这样一个社会性平台上,服务是广泛而多方面的。

社会本身就具有复杂性和多态性,社会网络内用户的需求必然是广泛多样的, 因此一个单纯提供社会关系的服务,比如linkedin这类的,开始看上去很美,但实际作用非常令人怀疑。 flickr, del.ico.us等服务很有价值,但他们的服务单一,虽然具有社会性,但社会网络是各自为政, 难以统一的。

---

uuzone本身的发展正是也在经历这样一个过程,我们不久将会提供开放的API能试图和其他blog, 其他服务建立起联系,形成一个开放的综合的社会性网络平台。


原文:http://www.uuzone.com/blog/mao/80319.htm

posted @ 2006-09-03 23:55 hopeshared 阅读(632) | 评论 (0)编辑 收藏

原作:王兆献

前些时间我写了一个SN网站,WEB2.0时代的门户网站,就自己对于SNS人际网站的一些看法,从宏观的角度进行了分析和说明,并未就实际的内容进行说明,从宏观看出实际的操作,这样的人还是有的,有些话是没有必要说的太明白的。不过有些内容还是很含糊,这次就细致一点的内容做些说明。我所认为SNS网站主要有以下两个作用:

1.维持大量的不常接触的人群的人际关系。
2.通过不同的圈子组织兴趣爱好连接认识自己需要认识的人。

第一个作用是维持大量不常接触人群的人际关系。比如我有一度好友,也就是我很好的现实的朋友,我想联系他,是否需要打开电脑,连上互联网,打开IE,进入SNS网站,然后给他发一条信息,然后等他收到网站提示,然后再来答复我?那我不就是傻掉了,直接给他打个手机不就完了嘛。对于一度好友,手机电话永远是最快的,对于人际关系理论而言,6个人决定你的生活方式和生存方式,这6个人就是一度。每个人正常的管理幅度是12个人,潜在的影响力是25个人,能够平时联系的最多人数是150人,能够记住的人是2000人,一生中经过见过的人大概是四万多人吧。通过6个人是完全可以找到地球上任何一个人的,这是不需要做什么怀疑的。一度其实是不需要SN来维持的一个东西。
那些在生活中,学校过程中,工作过程中,所认识的,一面之交的人,这些才是需要通过SN网站来进行维护的人,因为没有办法经常联系,需要联系的时候又找不到,那就需要SN网站来维护一下,找一下,一下子就可以了。SN站点,只要更新自己的信息,那么所有的联系人都可以自动查找到信息,同样,其它人也是如此,这样不就省事多了。最起码换手机的时候,不用一个一个去通知了,任何人也丢不下了。
这里就延伸出现实一度和网络一度的概念。现实一度就好像是女朋友,天天需要见面的,包括直系亲属工作同事和死党好友,这部分人基本上也不需要网络来维持关系。网络一度就好像是大学好友,毕业后就天各一方,很难见到一面,却真是的一度好友,这些偶尔见面,偶尔想联系的人,这才是需要SNS们做的网络一度。
第二个作用就是通过不同的圈子组织兴趣爱好连接认识自己需要认识的人。每个人在现实生活中都会遇到人际关系资源不足的问题,总想找到和自己同类的人,也都有这样的需要。通过兴趣,学校,同地,同城,同行,战友,之类的可以这样联系起来的人,这都可以在互联网上找到,如果贸然的去打扰成为朋友,感觉都会很奇怪,在SN网站里面,是一种平台,可以先有个缓冲的印象,这样再谈交友,就好办多了。而来自于认识的人的推荐,自然的屏蔽一些不安全因素,而出自于自我形象的需要,每个人也会特别注意自我的言行,更注意信用和形象。

SNS网站的三级结构

第一级结构就是个人展示平台,包括基本信息,博客,相册,音乐例表,兴趣,爱好,格言,留言,简历,工作档案,诸如此类的,解决一个问题,我是谁。别人怎么认识你,取决于你所表现出来的言行和资料。当看到一个人的时候,我们需要了解对方是什么样的,喜欢什么事,大概对一些问题有什么样的观点,作为个人来说,这也是一种展示,即你是什么样的人,你希望我以什么样的态度和你相处,你的喜欢列表里是否有我感兴趣的内容,是否是同类,可以认同,做朋友。如果不知道一个人是什么样的,可能对方很可能想认识你,但是不知道,那也是只能放在那里了。只是一个ID而已,总要了解一下,相交的是一个什么样的人吧。中国虽然有潜龙勿用的说法,但是在谁也不知道谁的互联网,偶尔的展示一下,无疑会得到更多的认同和机会,这同样会是一种影响力。让我们知道你是谁。

第二级是交流相识平台,在这一级上包括朋友圈,兴趣组,爱好团,校友录,同乡会,论坛,主题,俱乐部,聚会,沙龙,讨论组等诸如此类的东西。物以类聚,人以群分,通过不同的圈子去认识不同的人,有什么样的兴趣就去做什么样的事,找自己的伙伴,寻找自己想要的资源,去玩自己喜欢的娱乐,在活动中去认识自己需要认识的人物,得到资源,整合组织,实现人际交往,创建和维护你的社会资本。

第三级是协作合作平台,这一平台也是需要付费的平台,在这一平台上将会产生虚拟组织,也是SN网的真正用处。通过相互相知的团队组织,在SN网里面可以实现组日程和安排,组计划和组行动,特别适合不容易见面的组织使用,真正的把互联网力量发挥到极致,不计较地域,不计较时间,跨区协作,跨区开发,网络的力量显现,未知的创新在悄无声息的开始,如果有足够的信任度,一直工作可能从未见过面的组织都将出现。

内网与外网的统一

内网的意思就是必须登录后才能够进入的网站,SNS网站最注重一点的就是其会员的私密性,毕竟活动的内容也是属于个人隐私的,谁也不想自己的什么事都让别人知道的一清二楚,让别人来评论,在首面就一个登录框,做什么事必须先登入后才能够查看,没有加入网络的人什么也不能做。
外网就是那些可以在外部就访问网站。不登入网站,也可以看到部分会员的讨论结果,公共的娱乐话题,大部分的娱乐性的SNS网站使用了外网模式,商业类的SNS网站基本上都使用了内网模式。运营最大的关键就是让用户自动学习和宣传,MSN空间就是用户自己建立使用指南,用户们自己学习怎么使用。使用外网模式的好处就是可以让用户宣传的你的网站,MSN空间,都有唯一的访问名字,很多人的QQ网页后缀上都缀有MSN空间的网站名。
使用内网,就不能自动的让会员自动的宣传网站,也无法公开的展示,使用外网,则无法保证会员的隐秘性,而通过SNS的度数设计就可以完全解开这个难题。SNS分为六度,通过不同的度数就可以控制不同的访问人群,再加上两个,一个为私有,就是完全为自己所有,任何人都无法查阅,一个为公有,那么就是所有人都可以查阅。一度的意思就是只有在自己一度好友内的朋友才可查看,二度就是朋友的朋友也可查看,五度的意思基本上在这个SNS网站里面的人都可以查看了。

单人模式和多人模式的区别

每个人最大的关注度也就在12个人,再多生理也会受不了,所以也有一种理论是20*20*20,通过管理一度的20个人,然后每个人再去管理20人,三度下来也有8000人了,这个理论看起来好看,用不起来难用,因为需要的二度不见得正好在那20个人当中。这就是单人的管理模式,最多只能常联系的有个十来人,再多已经不可能了,只能偶尔联系。
假如我要同时管理500个人脉关系呢,特别是需要和大量人群经常保持联系的人呢?这就是多人管理模式下的SN了,这也是可以收费的地方,需要一个专门的人际管理器,包括能够批量的发送邮件,批量的自动更新的地址本,自动的日期提醒等等,当然,还需要批量的使用手机短信此类方式。

企业用户与个人用户

当个人作为节点的时候,获益再大其实也没有多少,顶多是生存线,只有当企业成为节点的时候,才真正的是盈利线。现实中的企业成为节点,可以发布企业的基本信息,如果有招聘信息的话,可以直接发布,而SN网里面的人看到也可以直接以节点应聘,提供诸如此类的企业服务。对于个人来讲,如果自动的加到一个企业的节点,就表示成为其企业用户,愿意接收其提供的资讯,比如一个SPA馆,女生加入后,如果有什么新的打折活动,有什么新的护肤资讯,都可以随时得到,预约也极为方便,在网上预约时间后,在企业节点自动以日期的形式显示其什么时候过来,做什么事的日程安排,一目了然。而如果不再想接到这家的资讯,直接把节点删除即可。
如上所述,当公司成为节点的时候,也就是SNS们真正价值产生的地方。同个人节点一样,公司节点也是划分为三级结构的。第一级结构同样是展示平台,向公众和朋友展示公司的价值观,主营业务,所活动区域,即告诉公众,我能做什么,你能以什么样的方式联系到我,我将以什么样的方式提供服务。SNS网站提供的服务包括基本信息框架,新闻信息发布系统,网站招聘和网站联系系统。第二级结构是交流平台,包括同行业的公司,公共信息频道,相互的产业讨论,诸如此类的服务。第三级结构就是协作合作平台,这部分包括两部分,一部分是内网,比如这个公司或者企业里面的办公自动化系统,包括日程安排系统,项目进程安排,业务文件的共享,组织结构图的内容。另外一部分就是外网,包括我的供应商节点,我的联盟商节点,我的销售商节点,当我有一个时间安排计划在自己的公司日程里做完,需要合作伙伴节点合作的时候,能够自动出现在他的日程里,他确认后就可以直接就用。例如,我在下周需要从北京到达上海,去洽谈合作,那么在自己的日程安排完后,在上海的合作伙伴查看自己日程的时候,自动就会发现自己的日程里多了一项,他确认后,北京这边可以收到,就按计划行事了。

SP部分

网络不是随时随地的,连上一个网站需要首先得找到电脑,然后要连上网络,打开网站,才能使用其服务,而手机是随时随地的。任何一个网站都最后都要连入手机网,这样才能提供随时随地的服务,作为SNS网站来说,手机也是必备的接入终端,而且此类服务也可以小收一点。
通过手机SNS,可以自动和已经注册手机号的一度好友直接通话,其换手机号也不用再每个人去通知,直接打名字就可以了。手机自动排出联系最多的人,纵然是SNS网上有几百个一度好友也没有关系,通过手机SNS,真正实现了无所不在的连接。

无所不在的接连

SNS网站最大的特点就在于其无所不在的连接。网站的连接有三部分,第一是邮件,第二是站内短信,第三就是手机。
SNS网站的邮件大概是世界上最没有垃圾邮件的邮件系统,设置为只有自己的一度好友才可以接收,只有自己好友的邮件地址才被接触,当然其它的信件也就进不来了。如果登录网站,那么还要邮件做什么,大概就一个作用,就是远程本地接收,不用上网站也可以收邮件。还有就是很容易群发邮件。如果一个会员发送让你觉得不舒服的邮件,把他的节点删除就可以了。
第二就是站内短信,最好设计的发过什么样的短信都能够看得到,在一个页面里,这样联贯,不至于看到后都不知道以前说了什么,这也是最方便的方式了。
第三个就是手机,手机短信和手机电话,允许一度好友可以直接手机联系,走在那里都很方便,如果换手机号了,就直接更新自己的资料,其它的不用管就行,其它人的手机全部的自动更新。

信用平衡系统

网络信用一直是个问题,但其实最没有办法讲信用的地方,也可能是信用最好的地方,因为在这个地方,如果信用被破坏了一次,全部的信用都没有了,所以必须加倍小心自己的形象。SNS网号称是基于现实的连接,当然也需要信用体系来支撑,第一就是实名系统,有昵称是可以,但必须要用实名来交往,第二就是信用自动评价体系,由其它节点人物给好评和恶评,这都是在人物名字旁边用星级显示的,直接就可以看到,如果受到的恶评过多,根据网站条例,自动的降低其交往的权限,其它人看以,基本也不会理会这个人,以这样的约束来控制信用的水平。

人性化的设计理念

SNS网站本来就是关于人的网站,因此更需要注意作为人的心理特征和使用习惯。美国软件设计的特点,第一就是简洁,整个网站设计都很简单,但看起来很舒服,第二就是完善,什么样的功能都有,开始的时候可能没有内容,但改进不断,功能越来越完善,我们这边一个软件或网站设计的不错,过几年他还是那个样子,一点变化都没有。
对于个人而言,第一关注的基本都是自己,从心理学的角度上讲,现在出来的个人门户也是如此,首先看到的是自己,可以花无限的时间修改自己的网页,兴趣和爱好,在首页里面基本喜欢看到的都自己的内容,然后就是交往,去看别人的主页,随机查看,有个博客上面说,人们有偷窥他人生活的欲望,我不这么看,以铜为镜,可以正衣冠,以人为镜,可以明得失,人们看其它人的内容,是期望从他人的生活中看自己的生活。在网站设计上面,尽量多看看心理学,会有意想不到的收获。

最后,创新永无止境,营运最是关键。如自然的花朵,玫瑰有玫瑰的高傲,百合有百合的清香,都以各自的方式在生活。这仅仅是SNS网站的一个基础设计文案,仅为练习之作,如果是实际的商业运营,那还需要根据实际现实量身打造。每天都会有海量的创新想法出现,世上有些人是会先知先觉的,到最后总要以营运来解决问题,想法再好,无法实现等于没想,每年都有花开花落,只是年年岁岁花相似,岁岁年年人不同。

原文:http://forum.techweb.com.cn/viewthread.php?tid=11053
posted @ 2006-09-03 23:54 hopeshared 阅读(483) | 评论 (0)编辑 收藏

当Web2.0在中国互联网成为一种时尚的时候,SNS逐渐被中国网友提起和熟知,从SNS的网站认识志趣相投的好友,通过SNS的线上空间构建虚拟小屋,甚至在SNS线下结识男友恋人成就异地婚姻……但究竟什么是SNS?目前哪些网站可以叫SNS网站?互联网领域的SNS区别在哪里?很多人说不清楚。

  从概念上说,SNS和很多Web2.0应用类似——因为理解不同,网站建设也各不相同。SNS起源于美国著名社会心理学家米尔格伦(Stanley Milgram)在20世纪60年代最先提出的六度理论,他说:“你和任何一个陌生人之间所间隔的人不会超过六个,也就是说,最多通过六个人你就能够认识任何一个陌生人。”

  所以SNS的一种主流理解就是Social Network Sofwaret,即社会性网络软件,依据六度理论,以认识朋友的朋友为基础,扩展自己的人脉的一种网络软件。基于此理论的SNS网站2003年3月在美国出现,经过极短的时间便风靡北美洲,被众多互联网企业和投资家所看作未来两年内增长最快的业务,2005年美国的TheFaceBook就获得了1300万元美金的风险投资。

  仔细分析社会性网络软件SNS,可以说它属于比较显性的社会性软件,比较关注直接的社会朋友关系的建立,具有社群性质,朋友之间进行人力资源分享,有直接的应用目的指向性,在建立社会关系的过程中完成或解决具体的应用问题,更加侧重商务。

  人们相信,通过使用SNS可以实现个人数据处理、个人社会关系管理、可信的商业信息共享,可以安全地对信任的人群分享自己的信息和知识,利用信任关系拓展自己的社会性网络,达成更加有价值的沟通和协作。最终,人们的社会性资本完成累积,这样的体系未来如果服务于各种社会活动,将带来巨大的商业和社会价值。

  另外做一种假设,放在Web 2.0 的背景下,每个用户都拥有自己的Blog、自己维护的Wiki、社会化书签或者Podcast用户通过Tag 、RSS 或者IM、邮件等方式连接到一起,“按照六度分隔理论,每个个体的社交圈都不断放大,最后成为一个大型网络,即社会化网络”。

  SNS进入“个性空间”时代

  但是几乎所有的舶来品在进入中国之后,变化或者说“创新”也随之注定,SNS引入中国互联网之后也是如此。

  “最早构建网络社区交友的时候,我们首先参考的就是六度理论。从人与人交往的过程来说,首先需要确认的是这个‘人’的存在,而且他有需要认识别人的需求。但在中国、在互联网上,‘理论’很难成为现实。”记者认识的一位互联网“精英”从2000年开始经营一家社区交友型网站,一度人气飙升,可是“虚拟化的网络世界,网民追求的是一种脱离现实的存在,娱乐性和隐密性更占主导”。

  以六度理论指导建设的社区交友网站曾经层出不穷,但技术难度和商业模式都限制了网站的发展,终于在互联网冬天“洗礼”后所剩无几。

  “幸运的是,六度理论的迷信正在逐渐淡去,现在的网站更加强调人,而不是空洞的六度关系。要壮大SNS网站的长期用户,必须以人为主,特别是青年人的社交平台,这是中国互联网的客观环境。”FZone总裁尹海龙认为国外的成功模式可以参考,但更要自己的网站有创意才能符合Web2.0的大潮。

  那么,目前网民的选择是什么?简单分析一下SNS交友的平台情况,QQ交友目前仍依靠QQ庞大的用户群,match和mop(2005年猫扑网收购了UUMe)定位于男女婚介和恋爱,亿友(YeeYoo)和碰碰(PengPeng)侧重于整合互联网和电信增值业务。

  由于交友模式单一和网络实名在互联网领域地缓慢进展,SNS网站的访问量和社区交友量都不能达到预期的状况,终于催生了“个性空间”的出现。韩国赛我网的“迷你小窝”和国内FZone的mini部落在这方面走在了前面。

  作为近两年在中日韩等亚洲国家兴起的最受年轻人喜爱的热门网络服务,第二代交友网站“个性空间”形态上和Blog相似,但其强调的是真实化的网络社交,强调追求娱乐化和人与人之间的互动、沟通。

  个性空间似乎是SNS和Blog、Podcast等个性化软件的结合,通过生动逼真的mini房间和朋友圈拓展功能的个人部落为基点,展现丰富个性风采的网络家园和轻松自然的朋友圈。个性空间整合了资讯、日记、音乐、相册、留言、朋友圈的功能。依托个性空间提供的平台,全面的文字、影音、图像等等相关服务最大限度、最全面的视角展示网友自己,并进行基于网络虚拟环境下的真实社区交友活动。

  至此,SNS衍生出了新的理解——Social Network Service,即社会性网络服务,一种以人个性化需求为服务的网络交友平台在Web2.0时代开始重放光芒。

原文:http://www.blog.edu.cn/user2/26032/archives/2006/1171902.shtml

posted @ 2006-09-03 23:52 hopeshared 阅读(571) | 评论 (0)编辑 收藏

六度分割是这样的理论:所谓六度分割理论是指six degrees of separation,是在20世纪60年代由哈佛大学心理学家 stanley milgram提出的,six degrees of separation,六度分割。简单来说,六度分割就是在这个社会里,任何两个人之间建立一种联系,最多需要六个人(包括这两个人在内),无论这两个人是否认识,生活在地球的任何一个地方,他们之间只有六度分割。

第一次听说六度理论的时候大约在一年以前,听说后没有多久,就参加了朋友火炬的“六度买车票实验”,实验的方法是我们把MSN名字改为原名+“请朋友们都把名字包含着句话:"求购19,20日北京到南宁t5车次卧铺2张,请联系139110xxxxx")”,开始的时候速度很慢,只有我们几个人改了名字,慢慢的越来越多的人改了名字,最后一天过后,虽然没有完全成功,但是结果非常接近。这个实验让我们体会到了六度的力量。

后来,我们发现了越来越多的SNS网站,他们的旗号都是遵循六度的原则,开始都让我们很兴奋,一个阶段里面我们几乎实验了能看到的所有的SNS网站,然而最后我们都离开了这些网站。他们没有给我们带来任何的便捷和好处。这让我一再反思六度理论。

再后来,在365kit上线之后,我很久没见的朋友LLF非常兴奋的认为365kit是实现六度思想的一个很好的载体,我们在msn上面进行了简单的讨论,发现大家有很多不太相同的看法,于是相约在七夕之夜,边吃边聊。长谈之后,LLF接受了我的很多观点,并说收益匪浅。然而在我看来,他对我启发也良多,尤其是那天我们说过的东西,是我思索了很久的,但是一直没有机会串连在一起的东西,我们的长谈让我思维中很多零碎的东西变得更加条理化,所以这篇文章很大一部份要归功于他。

特别需要说明的是,本文不是在说明六度是错误的理论,而是在讨论,在我们的SNS实践中,六度是不是完整的可以作为指导的思想。我的看法是六度作为SNS的指导原则,并不足够,还有很多残缺,这些残缺会给我们的SNS实践带来失败的结果。

1、残缺的六度

六度虽然是个社会学的理论,但是实际上它更像一个数学理论,很多人说六度和四色问题有异曲同工之妙。在我看来,六度理论很好的阐述了在一个网状结构(我们的人类社会)下,不同节点之间的联系和连接关系,然而它并不完整,并不足以指导我们的实践。

(1)关系的强弱——权值问题
首先六度肯定了人与人之间的普遍联系,但是没有对这种联系作定量分析。我们一生可能会认识千百人,他们有的对我极其重要,有的对我无足轻重,我们联系的建立的原因和方法也是千差万别,有父母亲属这类生而固有的联系,也有因为地理位置接近发展出来的,如邻里关系,还有因为共同学习生活而发展出来的同学、同事关系。六度理论中只把他们统统归结于联系,没有强弱之分。在网状结构里面,人与人的关系,需要加权处理,在这里,六度是残缺的。

(2)到达和建立联系的区别——目的和结果问题
20世纪60年代,耶鲁大学的社会心理学家米尔格兰姆(Stanley Milgram)就设计了一个连锁信件实验。他将一套连锁信件随机发送给居住在内布拉斯加州奥马哈的160个人,信中放了一个波士顿股票经纪人的名字,信中要求每个收信人将这套信寄给自己认为是比较接近那个股票经纪人的朋友。朋友收信后照此办理。最终,大部分信在经过五、六个步骤后都抵达了该股票经纪人。六度分割(也叫“六度空间”)的概念由此而来。这个故事很多六度的爱好者都知道,并奉为圣经。但是我请大家注意这个故事和我们现在流行的SNS网站的理念的重要查别。在这个故事里面,信到达了波士顿股票经纪人手里面没错,但是请注意整个过程中,每个人的朋友关系都没有发生改变。对,这点很重要,这个故事里面传递的信息,而我们现在看到的SNS网站希望在用户之间传递的是什么呢?是联系方式是朋友关系。
说到这里想提一下前面提到的火炬的买车票的实验,在那个实验里面,传递的实际上也是信息,而不是朋友关系。

(3)传递的成本和激励——阻尼问题
在Stanley Milgram的实验和火炬的实验里面,都没有任何的花费,或者说看起来成本为0。但是是不是真的成本为0呢?每个人传递一下信件花费极低,改下msn名字更是没有成本,然而那些人肯这么做,其实是看着朋友的面子上,所以这里花费的成本实际是什么呢?是中国人说的人情债,所谓的关系成本。没有人喜欢一个整天都要人帮忙这帮忙那的人,人情债和金钱债一样,背了就一定要还,这就是传递中的成本问题。火炬的火车实验后,我们一直在想这个问题,今天我们急需车票,可以请朋友们改他们的名字,但是我们能不能天天都用这种方法来找人帮忙呢?今天买车票,明天买球票,也许一次两次可以,次数多了,朋友们肯定会觉得厌烦,甚至放弃你这个朋友。

Gmail的邀请方式直至今日仍被很多人称颂,刚刚出现的时候,一个邀请甚至可以卖到60美金。很多人惊呼这是最伟大的营销。然而,到了今天,很多人的邀请已经变得无法送出去。为什么呢?因为一开始的时候Gmail是稀缺物品,所以价值高昂,加上Gmail带有Google的强势品牌和高度用户认同感,所以就更加被追捧,拥有Gmail成了荣誉的象征。这是这种荣誉成为了Gmail邀请在六度网络中疯狂传播的激励。然而随着Gmail的高度普及,这种荣誉感逐步下降,最终降低了激励,从来使传播陷入了停滞状态。

阻尼是好还是坏?没有阻尼我们可以给任何人发送信息,每个SNS网站都在宣扬你只需要六度就可以认识克林顿可以认识盖茨,但是有几个人真的去认识他们了?是因为他们不值得认识么?不是,是因为联系虽然看起来只有六度,然而每度的阻尼都有可能都是无法跨越的。但是你不要悲观,如果没有阻尼也许你会更加不爽!LLF算过“举例来说吧。假设每个人有30个朋友,信息经过六度是30的6次方 =729000000,数量足够到达一个能够覆盖所有可能的人的级别。”,如果六度的连接没有任何的阻尼,估计我们每天收到的来自六度好友的各种各样的信息就会让我们的脑袋爆炸。

在我们的生活里面,一个身份越高的人,越有名的人他就会有越多的好友,于是他也就越不想随便拓展自己的关系圈子,因为他们往往不胜其扰。前些日子的600演艺名人联系方式泄露事件就是一个例子,本来我们作为社会一分子都和这600名人有着六度的联系,然而某天因为他们的联系方式被公开,他们和我们的联系立刻被扁平化变成了一度。一瞬间,阻尼消失了,你可以随便打电话给那英、田震了,你不是想跟冯小刚聊电影么?你现在可以打电话了。但是,我们只能说结果这成了一场灾难,很多名人诉苦,说很多人打电话到他们的家里,说了句“你是XXX么?我很喜欢你!”然后就挂了电话。很多人不堪其扰停了机,甚至换了号。

这场灾难对我们这些局外人来说是一个很有意思的故事,很有趣的一点在于此,一旦这些名人和大众的关系扁平化后(六度变成一度),他们对大众的价值也开始流失,大众们只能打电话过去,问一声,然后炫耀自己给明星打过电话,仅此而已。这个巨大的扁平化工程并没有扩展追星族们的朋友圈子,他们仍旧离那些明星很远……

(4)朋友的朋友是朋友的假设——关系的方向和传递问题
 SNS网站最爱说的一句话也许就是“朋友的朋友是朋友”,然而那天我跟LLF在Msn聊天的时候就说过这个问题,我认识的某A的朋友某B是我非常反感的一个家伙,而且我的朋友里面还有个人某C对那个家伙某B更加痛恨。所以在现在的SNS服务里面我是不敢把某A和某C同时引入的,因为他们同时引入后,很可能的结果是某B和某C建立联系后,开始吵架。

2年前,我创办了Mop天津联盟,开始的时候是蜜月,那时候认识的朋友很多至今还是我很好的朋友,虽然我已经离开天津良久了。然而随着联盟的扩大,每次聚会的人数越来越多,关系越来越复杂,我们发现小圈子一旦扩大,人数一多,里面就会出现矛盾。不管我们怎么去努力调和,总是有些人会闹得不可开交。最后大圈子分化成多个小圈子,然后各自相安无事,然后小圈子扩大,又开始混乱,然后再分裂。这个过程我亲身经历,感受颇深。

和六度经常提起的还有一个150人原则,这个原则说从人的精力来看,很难管理超过150人的关系。其实我相信这里也是因为好友扁平化的保存在一度空间内过多,容易造成矛盾。150人原则从另一个层面说明我们的社会结构为什么会呈现树状体系或着说金子塔结构(树状和网状是观察点的区别,树可以看作网的特殊形式,网可以看作包含了树。),这是我一直想聊的一个问题,但是一直觉得想得还不够深入,所以这里就不细说了。

我们把友善关系当作正向,那么敌对关系就是负向。朋友的朋友是朋友的假设是一种幻想,同时和某人有正向关系的两个人的关系,很有可能是负向的,朋友关系是不能简单传递的。

文章已经很长了,但是我在这里没有想提出一种解决方案,只是跟大家聊聊我对六度的看法,希望对大家有启发,也希望大家的意见能给我启示,后面有时间也许会聊聊我对现有SNS服务的看法,最近总是很忙,呵呵。

原文: http://blog.donews.com/tinyfool/archive/2005/08/16/511891.aspx

posted @ 2006-09-03 23:47 hopeshared 阅读(294) | 评论 (0)编辑 收藏

六度理论传递的是什么?

目前国内SNS网站分为两种,一是交友,二是商务。

交友走的是大众路线,例如yeeyoo等;商务走的是专业路线,例如www.linkist.com等。

看到有很多的关于sns的评论文章,有赞扬,有批评。

批评者往往抓住六度理论中节点之间的系数问题做文章,应该说这些人对于sns有很好的了解,也有很好的认识。但是他们忽视了一点,就是六度理论节点与节点之间传递的是什么的问题。

在我的理解当中,SNS的节点与节点之间传递的是信任,是利益,而不是友谊。所谓的朋友的朋友是朋友,这个是错误的。在现实生活中,朋友的朋友通过介绍,相对于陌生人更容易建立起联系,这就是关系的哲学。但是,这个并不是友谊的直接传递,而是信任的传递,由信任而结成友谊。

通过网络,a认识b,b认识c,a与c之间建立起链接,这个要比a直接找c成功率大的多。人与人之间,信任、利益都可能成为朋友的充分必要条件,而这也是SNS网站存在的基础。

举个例子,还是abc,a认识b,b认识c,a想认识c,这中间有两个传递的力量:

a和b有很好的信任关系,b和c有很好的信任关系,那么a通过b认识c,这个信任是可以传递的;

a和b有利益关系,b和c有利益关系,a为了利益希望认识c,那么b是可以看到预期利益的,也就会帮助a认识c。


这两个力量的传递是存在组合关系的,因此,也就存在多种可能,并将其传递下去。网络的力量就在于包容一切的力量,在SNS的世界里,并不存在友谊的传递,但是存在信任和利益的传递。SNS的六度理论也是建立在这个的基础上,而不是友谊的基础。a是b的朋友,b是c的朋友,c是d的朋友,但d不一定和a能够成为朋友。

SNS的意义在于,通过这一个系统,你可以链接到你想认识的人。还是打个比方:

a想认识h,通过SNS他可以通过b认识h,也可以通过c-d认识h,也可以通过e-f-g认识h,有三个途径的选择,而途径的选择取决于两个因素,一是路径的距离,二是节点之间的链接系数。

无法否认的是,SNS的六度理论是建立在弱链接的基础上的,但由于存在信任和利益等影响因素,这个弱链接是可以传递的,并且有多种传递的可能性。

以上是看了笨狸《“关系”和SNS》http://blog.donews.com/banly/archive/2005/09/01/535403.aspx 之后的一些想法,与笨狸先生讨论。

原文:http://blog.donews.com/writerrr/archive/2005/09/01/536231.aspx

posted @ 2006-09-03 23:44 hopeshared 阅读(346) | 评论 (0)编辑 收藏

小时候听说洋鬼子来中国做生意会水土不服,因为他们不懂什么叫做“关系”。现在看到不少国人,居然学习着SNS,看来关于“关系”,鬼子不但已经懂,还开始让我们反过来去学。

一开始我以为“关系”是1.0,而SNS是2.0,很是敬佩,学了半天一度六度,发现SNS其实很肤浅,完全不如“关系”这种1.0的东西。

“关系”很简单,完全东方式,只可意会无法言传。一定要“定义”它的话,勉强可以说:

所谓关系,就是围绕某种利益目的而搭建的价值传递载体。

最多通过六个人,我可以认识拉登,也许,不过绝对是空话。相反,如果我能够提供百万个活体炸弹,拉登应该马上会来找我。这就是“关系”比SNS理论强的地方。

依稀记得,当年城里只要有了一张二十吨鸡爪的批文,这个信息传递到我这个不做批文的圈外人手里时,让我以为城里有了千万吨的鸡爪进口许可。每个人都说他有这个批文,都在找买家。在这个传递过程中,信息是不完整和不对称的,可能中间有人要了其中的10吨,想找另外一个买家来合作。所以,有人说他有5吨,有人说他有15吨。如果我把总量加起来,去兜售千万吨鸡爪的批文,到头来,会发现只剩下5吨的额度。传递链条中的每个人,为了保护自己的增量价值,都不可能直接把源头供出。我不知道SNS理论,如何定义这中间的变量。

当我曾经在云南看到很多某主席,某参谋长的纸条时,刚出校门的我很兴奋,觉得自己离中央真近。不过很快就发现,那些拿着纸条的人,其实很多也都没有和那个签名有过任何接触以及任何信息传递,他们也知道不可能,而且一般做完事情就结束掉,以后提都不提。

在国内,对于那些营造商业圈的SNS模式,我没有办法看好,因为缺乏明确的“某种利益目的”。至于做男女交友的,可以看好,因为利益目的很明晰,是性交网。按照这个推理,做商业应用的SNS,只能垂直,比如批文网,证监会网,煤矿网,运营商网等等。这种推论,只有在“关系”这个1.0理论的指导下才能完成。

SNS错误的地方,是犯了西方文明的老毛病,关注人本体,总是说节点。节点根本不重要,重要的是利益目的。比如笨狸这个本体一点用处都没有,只有当笨狸被任命为局长的时候,参与到利益目的中去了,才能进入“关系”,当笨狸退休或者被双规,那么一般来说,就在这个“关系”中消失,特例就不讨论了。

SNS是一个个节点和一条条线,所以编织成所谓的社会关系网络,这种僵硬呆板的理论实在不值得研究。而“关系”是动态的一个个圈,有了某个利益目的中心,就泛一个美丽而秘密的小涟漪,涟漪消失之后,一切都没有痕迹。

a通过b找到了c办成了一个事情,不证明c就是a的二度关系,没有关系。只有a-b,b-c这两个小涟漪浮动了一下。

“关系”是模糊数学,混沌理论范畴的,也是实用的。巩固原有关系也好,拓展交际圈也好,主要在于“围绕某种利益目的”,然后忽略掉分享增值价值的所有环节吧。找到利益维系的中心,比找到认识的人更为重要。所以结论是:每个人都是共同利益之下的一度好友。



cplwj 发表于2005-09-01 10:23 AM  IP: 219.82.107.*
从”六度交友”理论想到的

从今年春天开始的对Web2.0的叫嚣, 从Web2.0引发的对网络SNS交友网络的狂热, 我们想到了很多. 现在到了该坐下来冷静思考的时候了.

一, 老生常谈的”六度理论”更多的是个数学理论

关于”六度理论”, 最近在互联网上多有描述, 这里不再赘述. 本人认为, “六度理论”与其说是一个社会学理论或心理学理论, 还不如把它归类为一个以数学理论为主而略具社会学或心理学特征的理论. 说它更多的具有的是数学理论的特性, 因为根据“六度分隔法”(Six degrees of separation)事实上可以将世界上的所有人联系起来。事实上,如果网络中的每个人都能新结识100个朋友,那么他们每人将拥有100万个“三度朋友”;如果再增加“两度”的话,整个个人关系网络的人数将超过全球人口。难怪乎, 有人说, 我们可以通过”六度理论””结识”克林顿和比尔盖茨.

二. “弱联系”在SNS交友网络和现实生活中的区别.

“六度理论”中极力推崇的”弱联系”或称”弱连接”在人类人际交往中的作用. 我初次接触这一说法的时候曾经兴奋过一阵子, 认为SNS网络似乎真的在为我们提供一个快速便捷的社交平台. 后来一细想, 网络社交平台上的”弱连接”在互联网低成本高效率的背后无法真正达到”弱连接”本来应该具有的作用. 让我们回到现实社会,想一想这个弱连接是如何发挥作用. 比如我找到一个一度的朋友帮忙要解决某个问题, 一度朋友不能解决这个问题, 于是乎他便打电话给他的二度朋友, 二度朋友可能再向三度朋友传递, 结果问题在三度朋友手上给解决了. 其实我跟这个”三度”完全是有一种微弱的关系维系着的. 但是由于这种”微弱关系”有了一层接一层的”打电话”的”加固”, 使得现实生活中的我们频频受惠于这”微弱关系”. 但请记住, 现实生活中的加固”微弱关系”的”打电话”的过程, 其实其成本是很高的. 这就是中国人通常所说的”人情债”, 这个成本是无法以金钱计算的.

让我们再回到SNS网络里提到的”微弱关系”, 这个微弱关系似乎可以通过互联网的低成本优势得以”加固”, 但是实际上这种通过”搜索”朋友圈里的联系人的看似低成本的方法, 它根本无法实现现实生活中的加固微弱关系的作用, 它反而成了对朋友的骚扰. 所以任何想”低成本”的加固”微弱关系”的尝试最终将是徒劳的.

三. 六度关系中传递的什么?

六度理论中宣称我们可以通过”朋友的朋友”认识可靠的朋友. 这个理论听起来似乎没错. 20 世纪60年代,耶鲁大学的社会心理学家米尔格兰姆(Stanley Milgram)就设计了一个连锁信件实验。他将一套连锁信件随机发送给居住在内布拉斯加州奥马哈的160个人,信中放了一个波士顿股票经纪人的名字,信中要求每个收信人将这套信寄给自己认为是比较接近那个股票经纪人的朋友。朋友收信后照此办理。最终,大部分信在经过五、六个步骤后都抵达了该股票经纪人。六度理论(也叫“六度空间””六度分割”)的概念由此而来。这个故事很多六度的爱好者都知道,并奉为圣经。但是我请大家注意这个故事和我们现在流行的SNS网站的理念的重要区别。在这个故事里面,信到达了波士顿股票经纪人手里面没错,但是请注意整个过程中,每个人的朋友关系都没有发生改变。这个故事里面传递的信息,而我们现在看到的SNS网站希望在用户之间传递的是什么呢?是联系方式是朋友关系。但这个试验中传递的实际上是信息而已,而不是朋友关系。这也就是我们通过六度理论可以把”信息”传递给克林顿, 而无法打电话给老克一起共进早餐的原因.通过”六度”是无法传递”友情”的. 友谊和友情是有限的人群在亲密和共识的基础上建立起来的。一个真正的社交网络狭小而封闭:它是一个由栏杆围起的社区,而不是一个向地平线延伸的大都市。

四. 150人原则

上面提到的有限的人群的基础上的朋友感情, 实际上是个很狭小的空间. 这就是和六度理论一起经常提起的”150人原则”,这个原则说从人的精力来看,很难管理超过150人的关系. 而我们从小学开始成为一个社会人开始, 大都经历小学, 初中,高中和大学, 然后进入社会从事各类工作. 这么一个过程, 一个人能结识的比较”一度”的朋友也就150个人.当然这个数字随个人的性格而有差异.

五. 对”150人”关系管理的缺失

我们都承认我们有这”150人”的一度关系的存在.但是现实的现状是, 我们没有一个人把这个150人的一度关系管理好. 以一个刚刚走入工作岗位的人为例, 我们经历了各个学习阶段, 试问自己, 我们还有几个人还知道几个小学同学的联系方式? 几个初中同学的联系方式? 几个高中同学的联系方式? 再过五六年, 还有几个人还能随时联系得上大学的同学? 这里有必要提醒大家, 同学友谊是世间最纯洁的友谊之一, 它不带有任何功利的因素. 其实我们的同学, 以及以后认识的同事朋友分布在各行各业, 密切的维护这些关系对我们的事业和生活都已足够. 而在SNS叫嚣下的中国, 似乎只有二度三度甚至五度六度的朋友才是事业成功的基石. 我们在此是大错了. 新近的 www.LL51.com ”联络无忧”通讯录倒是为我们解决了这个问题. 这是一个简单的通讯录工具, 是个互动的工具, 我们不妨可以冠之以”AddressBook2.0”, 这是一个维护”150人”一度朋友的工具. 每个人的联系方式有很多, 你在充分消除你对”隐私”的忧虑之后, 你只要留给你的一度朋友一个或多个能够随时找到你的联系方式, 如现在经常使用邮址, 如手机号码, 如QQ, 如MSN等等, 这样我们就不会间断和朋友的联系了!

罗里罗嗦写多了. 不妨让我们从叫嚣一阵子了的”六度理论”中走出来, 回到现实中去. 社交网络让我们一下子拥有几千个几万个几十万个“朋友”短时间内还感觉挺有趣的,但这种快乐最终将逐渐消失。更重要的是, 让我们管好眼前的朋友, 不要让培养多年的同学友情, 同事友情在岁月无情的涤荡中消失在我们的视野.

lucy@linkist 发表于2005-09-01 10:40 AM  IP: 218.1.194.*
呵呵,果然是刚出校门不久的社会人 :) 如果你将来走的是从政之路,那么这篇文章非常有道理,SNS对政客确实也没什么用。如果不小心走了国际化的商业之路,我是不太相信你过10年还象今天这么想。
“a通过b找到了c办成了一个事情,不证明c就是a的二度关系,没有关系。只有a-b,b-c这两个小涟漪浮动了一下。”
是不是二度关系并不重要,SNS的精髓也不在于确定a和c之间到底应该是几度。没有SNS,a为了找到c,只能一个个电话去询问所有的朋友,最后也许碰巧发现c能办事;而有了SNS,a一下就可以找到c了。至于c帮不帮,那是互联网外的事情了。
前两天有个朋友拜托我找人帮他递个简历给Cisco(听说Cisco对无介绍人的简历几乎不看,也从来不委托猎头招聘),没有SNS的话,也许花一年都找不到引荐人…… 你认为还可以用什么方法很快地寻找到介绍人呢?


原文:http://blog.donews.com/banly/archive/2005/09/01/535403.aspx


posted @ 2006-09-03 23:42 hopeshared 阅读(991) | 评论 (0)编辑 收藏

by Hao He
August 11, 2004

Despite the lack of vendor support, Representational State Transfer (REST) web services have won the hearts of many working developers. For example, Amazon's web services have both SOAP and REST interfaces, and 85% of the usage is on the REST interface. Compared with other styles of web services, REST is easy to implement and has many highly desirable architectural properties: scalability, performance, security, reliability, and extensibility. Those characteristics fit nicely with the modern business environment, which commands technical solutions just as adoptive and agile as the business itself.

A few short years ago, REST had a much lower profile than XML-RPC, which was much in fashion. Now XML-RPC seems to have less mindshare. People have made significant efforts to RESTize SOAP and WSDL. The question is no longer whether to REST, but instead it's become how to be the best REST?

The purpose of this article is to summarize some best practices and guidelines for implementing RESTful web services. I also propose a number of informal standards in the hope that REST implementations can become more consistent and interoperable.

The following notations are used in this article:

  1. BP: best practice
  2. G: general guideline
  3. PS: proposed informal standard
  4. TIP: implementation tip
  5. AR: arguably RESTful -- may not be RESTful in the strict sense

Reprising REST

Let's briefly reiterate the REST web services architecture. REST web services architecture conforms to the W3C's Web Architecture, and leverages the architectural principles of the Web, building its strength on the proven infrastructure of the Web. It utilizes the semantics of HTTP whenever possible and most of the principles, constraints, and best practices published by the TAG also apply.

The REST web services architecture is related to the Service Oriented Architecture. This limits the interface to HTTP with the four well-defined verbs: GET, POST, PUT, and DELETE. REST web services also tend to use XML as the main messaging format.

[G] Implementing REST correctly requires a resource-oriented view of the world instead of the object-oriented views many developers are familiar with.

Resource

One of the most important concepts of web architecture is a "resource." A resource is an abstract thing identified by a URI. A REST service is a resource. A service provider is an implementation of a service.

URI Opacity [BP]

The creator of a URI decides the encoding of the URI, and users should not derive metadata from the URI itself. URI opacity only applies to the path of a URI. The query string and fragment have special meaning that can be understood by users. There must be a shared vocabulary between a service and its consumers.

Query String Extensibility [BP, AR]

A service provider should ignore any query parameters it does not understand during processing. If it needs to consume other services, it should pass all ignored parameters along. This practice allows new functionality to be added without breaking existing services.

[TIP] XML Schema provides a good framework for defining simple types, which can be used for validating query parameters.

Deliver Correct Resource Representation [G]

A resource may have more than one representation. There are four frequently used ways of delivering the correct resource representation to consumers:

  1. Server-driven negotiation. The service provider determines the right representation from prior knowledge of its clients or uses the information provided in HTTP headers like Accept, Accept-Charset, Accept-Encoding, Accept-Language, and User-Agent. The drawback of this approach is that the server may not have the best knowledge about what a client really wants.
  2. Client-driven negotiation. A client initiates a request to a server. The server returns a list of available of representations. The client then selects the representation it wants and sends a second request to the server. The drawback is that a client needs to send two requests.
  3. Proxy-driven negotiation. A client initiates a request to a server through a proxy. The proxy passes the request to the server and obtains a list of representations. The proxy selects one representation according to preferences set by the client and returns the representation back to the client.
  4. URI-specified representation. A client specifies the representation it wants in the URI query string.

Server-Driven Negotiation [BP]

  1. When delivering a representation to its client, a server MUST check the following HTTP headers: Accept, Accept-Charset, Accept-Encoding, Accept-Language, and User-Agent to ensure the representation it sends satisfies the user agent's capability.
  2. When consuming a service, a client should set the value of the following HTTP headers: Accept, Accept-Charset, Accept-Encoding, Accept-Language, and User-Agent. It should be specific about the type of representation it wants and avoid "*/*", unless the intention is to retrieve a list of all possible representations.
  3. A server may determine the type of representation to send from the profile information of the client.

URI-Specified Representation [PS, AR]

A client can specify the representation using the following query string:

mimeType={mime-type}

A REST server should support this query.

Different Views of a Resource [PS, AR]

A resource may have different views, even if there is only one representation available. For example, a resource has an XML representation but different clients may only see different portion of the same XML. Another common example is that a client might want to obtain metadata of the current representation.

To obtain a different view, a client can set a "view" parameter in the URI query string. For example:


GET http://www.example.com/abc?view=meta

where the value of the "view" parameter determines the actual view. Although the value of "view" is application specific in most cases, this guideline reserves the following words:

  1. "meta," for obtaining the metadata view of the resource or representation.
  2. "status," for obtaining the status of a request/transaction resource.

Service

A service represents a specialized business function. A service is safe if it does not incur any obligations from its invoking client, even if this service may cause a change of state on the server side. A service is obligated if the client is held responsible for the change of states on server side.

Safe Service

A safe service should be invoked by the GET method of HTTP. Parameters needed to invoke the service can be embedded in the query string of a URI. The main purpose of a safe service is to obtain a representation of a resource.

Service Provider Responsibility [BP]

If there is more than one representation available for a resource, the service should negotiate with the client as discussed above. When returning a representation, a service provider should set the HTTP headers that relate to caching policies for better performance.

A safe service is by its nature idempotent. A service provider should not break this constraint. Clients should expect to receive consistent representations.

Obligated Services [BP]

Obligated services should be implemented using POST. A request to an obligated service should be described by some kind of XML instance, which should be constrained by a schema. The schema should be written in W3C XML Schema or Relax NG. An obligated service should be made idempotent so that if a client is unsure about the state of its request, it can send it again. This allows low-cost error recovery. An obligated service usually has the simple semantic of "process this" and has two potential impacts: either the creation of new resources or the creation of a new representation of a resource.

Asynchronous Services

One often hears the criticism that HTTP is synchronous, while many services need to be asynchronous. It is actually quite easy to implement an asynchronous REST service. An asynchronous service needs to perform the following:

  1. Return a receipt immediately upon receiving a request.
  2. Validate the request.
  3. If the request if valid, the service must act on the request as soon as possible. It must report an error if the service cannot process the request after a period of time defined in the service contract.

Request Receipt

An example receipt is shown below:

<receipt xmlns="http://www.xml.org/2004/rest/receipt" requestUri = "http://www.example.com/xya343343" received = "2004-10-03T12:34:33+10:00">
  <transaction uri="http://www.example.com/xyz2343" status = "http://www.example.com/xyz2343?view=status"/>
</receipt>

A receipt is a confirmation that the server has received a request from a client and promises to act on the request as soon as possible. The receipt element should include a received attribute, the value of which is the time the server received the request in WXS dateTime type format. The requestUri attribute is optional. A service may optionally create a request resource identified by the requestUri. The request resource has a representation, which is equivalent to the request content the server receives. A client may use this URI to inspect the actual request content as received by the server. Both client and server may use this URI for future reference.

However, this is application-specific. A request may initiate more than one transaction. Each transaction element must have a URI attribute which identifies this transaction. A server should also create a transaction resource identified by the URI value. The transaction element must have a status attribute whose value is a URI pointing to a status resource. The status resource must have an XML representation, which indicates the status of the transaction.

Transaction

A transaction represents an atomic unit of work done by a server. The goal of a transaction is to complete the work successfully or return to the original state if an error occurs. For example, a transaction in a purchase order service should either place the order successfully or not place the order at all, in which case the client incurs no obligation.

Status URI [BP, AR]

The status resource can be seen as a different view of its associated transaction resource. The status URI should only differ in the query string with an additional status parameter. For example:

Transaction URI: http://www.example.com/xyz2343 Transaction Status URI: http://www.example.com/xyz2343?view=status

Transaction Lifecycle [G]

A transaction request submitted to a service will experience the following lifecycle as defined in Web Service Management: Service Life Cycle:

  1. Start -- the transaction is created. This is triggered by the arrival of a request.
  2. Received -- the transaction has been received. This status is reached when a request is persisted and the server is committed to fulfill the request.
  3. Processing -- the transaction is being processed, that is, the server has committed resources to process the request.
  4. Processed -- processing is successfully finished. This status is reached when all processing has completed without any errors.
  5. Failed -- processing is terminated due to errors. The error is usually caused by invalid submission. A client may rectify its submission and resubmit. If the error is caused by system faults, logging messages should be included. An error can also be caused by internal server malfunction.
  6. Final -- the request and its associated resources may be removed from the server. An implementation may choose not to remove those resources. This state is triggered when all results are persisted correctly.

Note that it is implementation-dependent as to what operations must be performed on the request itself in order to transition it from one status to another. The state diagram of a request (taken from Web Service Management: Service Life Cycle) is shown below:

The state diagram of a request (taken from Web Service Management: Service Life Cycle)

As an example of the status XML, when a request is just received:

<status state="received" timestamp="2004-10-03T12:34:33+10:00" />

The XML contains a state attribute, which indicates the current state of the request. Other possible values of the state attribute are processing, processed, and failed.

When a request is processed, the status XML is (non-normative):

<status state="processed" timestamp="2004-10-03T12:34:33+10:00" >
  <result uri="http://example.com/rest/1123/xyz" />
</status>

This time, a result element is included and it points to a URL where the client can GET request results.

In case a request fails, the status XML is (non-normative):


<status   state="failed" timestamp="2002-10-03T12:34:33+10:00" >
  <error code="3" >
    <message>A bad request. </message>
    <exception>line 3234</exception>
  </error>
</status>

A client application can display the message enclosed within the message tag. It should ignore all other information. If a client believes that the error was not caused by its fault, this XML may serve as a proof. All other information is for internal debugging purposes.

Request Result [BP]

A request result view should be regarded as a special view of a transaction. One may create a request resource and transaction resources whenever a request is received. The result should use XML markup that is as closely related to the original request markup as possible.

Receiving and Sending XML [BP]

When receiving and sending XML, one should follow the principle of "strict out and loose in." When sending XML, one must ensure it is validated against the relevant schema. When receiving an XML document, one should only validate the XML against the smallest set of schema that is really needed. Any software agent must not change XML it does not understand.

An Implementation Architecture

An Implementation Architecture

The architecture represented above has a pipe-and-filter style, a classical and robust architectural style used as early as in 1944 by the famous physicist, Richard Feynman, to build the first atomic bomb in his computing team. A request is processed by a chain of filters and each filter is responsible for a well-defined unit of work. Those filters are further classified as two distinct groups: front-end and back-end. Front-end filters are responsible to handle common Web service tasks and they must be light weight. Before or at the end of front-end filters, a response is returned to the invoking client.

All front-end filters must be lightweight and must not cause serious resource drain on the host. A common filter is a bouncer filter, which checks the eligibility of the request using some simple techniques:

  1. IP filtering. Only requests from eligible IPs are allowed.
  2. URL mapping. Only certain URL patterns are allowed.
  3. Time-based filtering. A client can only send a certain number of requests per second.
  4. Cookie-based filtering. A client must have a cookie to be able to access this service.
  5. Duplication-detection filter. This filter checks the content of a request and determines whether it has received it before. A simple technique is based on the hash value of the received message. However, a more sophisticated technique involves normalizing the contents using an application-specific algorithm.

A connector, whose purpose is to decouple the time dependency between front-end filters and back-end filters, connects front-end filters and back-end filters. If back-end processing is lightweight, the connector serves mainly as a delegator, which delegates requests to its corresponding back-end processors. If back-end processing is heavy, the connector is normally implemented as a queue.

Back-end filters are usually more application specific or heavy. They should not respond directly to requests but create or update resources.

This architecture is known to have many good properties, as observed by Feynman, whose team improved its productivity many times over. Most notably, the filters can be considered as a standard form of computing and new filters can be added or extended from existing ones easily. This architecture has good user-perceived performance because responses are returned as soon as possible once a request becomes fully processed by lightweight filters. This architecture also has good security and stability because security breakage and errors can only propagate a limited number of filters. However, it is important to note that one must not put a heavyweight filter in the front-end or the system may become vulnerable to denial-of-service attacks.

posted @ 2006-08-31 17:26 hopeshared 阅读(793) | 评论 (0)编辑 收藏

I am seeing a lot of new web services are implemented using a REST style architecture these days rather than a SOAP one. Lets step back a second and explain what REST is.

What is a REST Web Service

The acronym REST stands for Representational State Transfer, this basically means that each unique URL is a representation of some object. You can get the contents of that object using an HTTP GET, to delete it, you then might use a POST, PUT, or DELETE to modify the object (in practice most of the services use a POST for this).

Who's using REST?

All of Yahoo's web services use REST, including Flickr, del.icio.us API uses it, pubsub, bloglines, technorati, and both eBay, and Amazon have web services for both REST and SOAP.

Who's using SOAP?

Google seams to be consistent in implementing their web services to use SOAP, with the exception of Blogger, which uses XML-RPC. You will find SOAP web services in lots of enterprise software as well.

REST vs SOAP

As you may have noticed the companies I mentioned that are using REST api's haven't been around for very long, and their apis came out this year mostly. So REST is definitely the trendy way to create a web service, if creating web services could ever be trendy (lets face it you use soap to wash, and you rest when your tired). The main advantages of REST web services are:

  • Lightweight - not a lot of extra xml markup
  • Human Readable Results
  • Easy to build - no toolkits required

SOAP also has some advantages:

  • Easy to consume - sometimes
  • Rigid - type checking, adheres to a contract
  • Development tools

For consuming web services, its sometimes a toss up between which is easier. For instance Google's AdWords web service is really hard to consume (in CF anyways), it uses SOAP headers, and a number of other things that make it kind of difficult. On the converse, Amazon's REST web service can sometimes be tricky to parse because it can be highly nested, and the result schema can vary quite a bit based on what you search for.

Which ever architecture you choose make sure its easy for developers to access it, and well documented.




原文:http://www.petefreitag.com/item/431.cfm

posted @ 2006-08-31 17:21 hopeshared 阅读(1300) | 评论 (0)编辑 收藏

Building Web Services the REST Way

Roger L. Costello

I will first provide a brief introduction to REST and then describe how to build Web services in the REST style.

What is REST?

REST is a term coined by Roy Fielding in his Ph.D. dissertation [1] to describe an architecture style of networked systems. REST is an acronym standing for Representational State Transfer.

Why is it called Representational State Transfer?

The Web is comprised of resources. A resource is any item of interest. For example, the Boeing Aircraft Corp may define a 747 resource. Clients may access that resource with this URL:

http://www.boeing.com/aircraft/747
A representation of the resource is returned (e.g., Boeing747.html). The representation places the client application in a state. The result of the client traversing a hyperlink in Boeing747.html is another resource is accessed. The new representation places the client application into yet another state. Thus, the client application changes (transfers) state with each resource representation --> Representational State Transfer!

Here is Roy Fielding's explanation of the meaning of Representational State Transfer:

"Representational State Transfer is intended to evoke an image of how a well-designed Web application behaves: a network of web pages (a virtual state-machine), where the user progresses through an application by selecting links (state transitions), resulting in the next page (representing the next state of the application) being transferred to the user and rendered for their use."

Motivation for REST

The motivation for REST was to capture the characteristics of the Web which made the Web successful. Subsequently these characteristics are being used to guide the evolution of the Web.

REST - An Architectural Style, Not a Standard

REST is not a standard. You will not see the W3C putting out a REST specification. You will not see IBM or Microsoft or Sun selling a REST developer's toolkit. Why? Because REST is just an architectural style. You can't bottle up that style. You can only understand it, and design your Web services in that style. (Analogous to the client-server architectural style. There is no client-server standard.)

While REST is not a standard, it does use standards:
  • HTTP
  • URL
  • XML/HTML/GIF/JPEG/etc (Resource Representations)
  • text/xml, text/html, image/gif, image/jpeg, etc (MIME Types)

The Classic REST System

The Web is a REST system! Many of those Web services that you have been using these many years - book-ordering services, search services, online dictionary services, etc - are REST-based Web services. Alas, you have been using REST, building REST services and you didn't even know it.

REST is concerned with the "big picture" of the Web. It does not deal with implementation details (e.g., using Java servlets or CGI to implement a Web service). So let's look at an example of creating a Web service from the REST "big picture" perspective.

Parts Depot Web Services

Parts Depot, Inc (fictitious company) has deployed some web services to enable its customers to:
  • get a list of parts
  • get detailed information about a particular part
  • submit a Purchase Order (PO)
Let's consider how each of these services are implemented in a RESTful fashion.

Get Parts List

The web service makes available a URL to a parts list resource. For example, a client would use this URL to get the parts list:

http://www.parts-depot.com/parts
Note that "how" the web service generates the parts list is completely transparent to the client. All the client knows is that if he/she submits the above URL then a document containing the list of parts is returned. Since the implementation is transparent to clients, Parts Depot is free to modify the underlying implementation of this resource without impacting clients. This is loose coupling.

Here's the document that the client receives:

<?xml version="1.0"?>
<p:Parts xmlns:p="http://www.parts-depot.com" 
         xmlns:xlink="http://www.w3.org/1999/xlink">
      <Part id="00345" xlink:href="http://www.parts-depot.com/parts/00345"/>
      <Part id="00346" xlink:href="http://www.parts-depot.com/parts/00346"/>
      <Part id="00347" xlink:href="http://www.parts-depot.com/parts/00347"/>
      <Part id="00348" xlink:href="http://www.parts-depot.com/parts/00348"/>
</p:Parts>
[Assume that through content negotiation the service determined that the client wants the representation as XML (for machine-to-machine processing).] Note that the parts list has links to get detailed info about each part. This is a key feature of REST. The client transfers from one state to the next by examining and choosing from among the alternative URLs in the response document.

Get Detailed Part Data

The web service makes available a URL to each part resource. Example, here's how a client requests part 00345:

http://www.parts-depot.com/parts/00345
Here's the document that the client receives:

<?xml version="1.0"?>
<p:Part xmlns:p="http://www.parts-depot.com"   
        xmlns:xlink="http://www.w3.org/1999/xlink">
      <Part-ID>00345</Part-ID>
      <Name>Widget-A</Name>
      <Description>This part is used within the frap assembly</Description>
      <Specification xlink:href="http://www.parts-depot.com/parts/00345/specification"/>
      <UnitCost currency="USD">0.10</UnitCost>
      <Quantity>10</Quantity>
</p:Part>
Again observe how this data is linked to still more data - the specification for this part may be found by traversing the hyperlink. Each response document allows the client to drill down to get more detailed information.

Submit PO

The web service makes available a URL to submit a PO. The client creates a PO instance document which conforms to the PO schema that Parts Depot has designed (and publicized in a WSDL document). The client submits PO.xml as the payload of an HTTP POST.

The PO service responds to the HTTP POST with a URL to the submitted PO. Thus, the client can retrieve the PO any time thereafter (to update/edit it). The PO has become a piece of information which is shared between the client and the server. The shared information (PO) is given an address (URL) by the server and is exposed as a Web service.

Logical URLs versus Physical URLs

A resource is a conceptual entity. A representation is a concrete manifestation of the resource. This URL:
http://www.parts-depot.com/parts/00345
is a logical URL, not a physical URL. Thus, there doesn't need to be, for example, a static HTML page for each part. In fact, if there were a million parts then a million static HTML pages would not be a very attractive design.

[Implementation detail: Parts Depot could implement the service that gets detailed data about a particular part by employing a Java Servlet which parses the string after the host name, uses the part number to query the parts database, formulate the query results as XML, and then return the XML as the payload of the HTTP response.]

As a matter of style URLs should not reveal the implementation technique used. You need to be free to change your implementation without impacting clients or having misleading URLs.

REST Web Services Characteristics

Here are the characteristics of REST:
  • Client-Server: a pull-based interaction style: consuming components pull representations.
  • Stateless: each request from client to server must contain all the information necessary to understand the request, and cannot take advantage of any stored context on the server.
  • Cache: to improve network efficiency responses must be capable of being labeled as cacheable or non-cacheable.
  • Uniform interface: all resources are accessed with a generic interface (e.g., HTTP GET, POST, PUT, DELETE).
  • Named resources - the system is comprised of resources which are named using a URL.
  • Interconnected resource representations - the representations of the resources are interconnected using URLs, thereby enabling a client to progress from one state to another.
  • Layered components - intermediaries, such as proxy servers, cache servers, gateways, etc, can be inserted between clients and resources to support performance, security, etc.

Principles of REST Web Service Design

1. The key to creating Web Services in a REST network (i.e., the Web) is to identify all of the conceptual entities that you wish to expose as services. Above we saw some examples of resources: parts list, detailed part data, purchase order.

2. Create a URL to each resource. The resources should be nouns, not verbs. For example, do not use this:

http://www.parts-depot.com/parts/getPart?id=00345
Note the verb, getPart. Instead, use a noun:

http://www.parts-depot.com/parts/00345
3. Categorize your resources according to whether clients can just receive a representation of the resource, or whether clients can modify (add to) the resource. For the former, make those resources accessible using an HTTP GET. For the later, make those resources accessible using HTTP POST, PUT, and/or DELETE.

4. All resources accessible via HTTP GET should be side-effect free. That is, the resource should just return a representation of the resource. Invoking the resource should not result in modifying the resource.

5. No man/woman is an island. Likewise, no representation should be an island. In other words, put hyperlinks within resource representations to enable clients to drill down for more information, and/or to obtain related information.

6. Design to reveal data gradually. Don't reveal everything in a single response document. Provide hyperlinks to obtain more details.

7. Specify the format of response data using a schema (DTD, W3C Schema, RelaxNG, or Schematron). For those services that require a POST or PUT to it, also provide a schema to specify the format of the response.

8. Describe how your services are to be invoked using either a WSDL document, or simply an HTML document.

Summary

This article described REST as an architectural style. In fact, it's the architectural style of the Web. REST describes what makes the Web work well. Adhering to the REST principles will make your services work well in the context of the Web.

In a future article I will write about the evolution of the Web using the REST principles.

Acknowledgement

Thanks to Robert Leftwich and Philip Eskelin for their very helpful comments in creating this document.

References

[1] http://www.ebuilt.com/fielding/pubs/dissertation/top.htm


原文:http://www.xfront.com/REST-Web-Services.html

posted @ 2006-08-31 17:17 hopeshared 阅读(496) | 评论 (0)编辑 收藏

市长热线:12345   65128088      
反扒热线:64011327            
火警:119                
交通伤急救热线:68455655   68455665  
中毒援助热线:83163388   83160233  
抗癌互助热线:68276491            
查号台:114                
移动电话话费查询台:1861                
铁路查询售票信息:2586                
市燃气集团公司24小时热线电话:65940469   66510382
北京市煤气公司急修:65022414            
北京市供电局报修中心:63129999            
公证咨询专线:98148              
节水服务热线:64200960   64200972  
特快专递服务台:185                
报时:12117              
气象综合信息服务台:211                
首都公众信息网:263                
秘书台:269                
青春热线:64015039            
人工信息服务台:160                
道路交通事故咨询热线:26688122            
法律服务专线:1600148            
老年痴呆症咨询热线:8008208220          
市政热线:63088467            
匪警:110                
医疗急救台:120                
道路交通报警台:122                
紧急避孕热线:62173454            
移动电话服务台:222                
铁路车票定票热线:63217188            
北京市热力公司急修:65005943            
天然气急修:64269603            
施工扰民投诉电话:68413817   68017755  
中国政法大学环境法研究和服务中心热线电话:62228836            
水上搜救专用电话:12395              
天气预报台:12121              
语音信箱:166                
妇女热线:64033383   64073800  
北京公共交通李素丽服务热线:96166
小公共汽车营运服务监督电话:68351150   68351570  
火车票新订票热线:51016666  51016688  
复婚热线:62383388            
违规用水举报电话:64247906            
中华骨髓库信息热线:16895999            
市地税局举报电话:12366
免费宠物热线:160101011
公共卫生投诉电话:65066969
拆迁举报电话:64409883
市公安局监督养犬举报电话:69738604
道路质量监督电话:87501879
劳保咨询热线:12333
土地矿产法律免费热线:16829999
单亲孩子援助热线:62356688
热水器抢修服务热线:1600110
市重大动物疫病指挥部办公室:62015024   62016809
危改热线:weigai@bjjs.gov.cn
北京新闻广播“新闻热线”:8006101006
民工维权热线电话:12333
西站24小时举报电话:63984059
燃气报修热线:96777
自来水集团报修中心:66189955
电梯安全问题投诉电话:12365
加油卡更换咨询电话:64678605
食品安全监察处:82691421  12315
黑车、黑导游举报电话:96310
北京通信公司北京客户服务中心:10060
北京通信公司话费传真号码:65263399
北京市电信公司客户服务热线:96388
金银建出租汽车调度中心:96103
汽车援救电话:63489198
驾驶员违章积分短信查询热线:9626899122
城八区闯红灯咨询电话:9516888114
北京西站问讯处:51826273
北京南站问讯处:51837262
北京北站问讯处:51866223
铁路信息查询台:962585
火车票订票热线:962586
客票中心订票热线:51827188
北京火车站订票热线:51016666
posted @ 2006-08-25 12:31 hopeshared 阅读(401) | 评论 (0)编辑 收藏

使用TreeViewer贡献视图(根据《Eclipse入门到精通》中的例子进行的改编)


作者:李红霞 2005-8-13

本文章允许转载,但请要求注明文章作者及出处


一 创建插件项目
创建一个插件项目example.my.treeview,这个例子将向eclipse贡献一个视图,这个视图采用树(Treeviewer)来实现。

下图是本例的文件清单
<抱歉,图片传不上来>
+ example.my.treeview
+ src
  + example.my.treeview
    - TreeviewPlugin.java
  + exampe.my.treeview.data
    - CityEntity.java
    - CountryEntity.java
    - DataFactory.java
    - PeopleEnrity.java
  + example.my.treeview.internal
    - ITreeEntry.java
    - TreeViewerContentProvider.java
    - TreeViewerLabelProvider.java
    - TreeViewPart.java
  + JRE System Library
  + Plug-in dependencies
  + META-INF
    - MENIFEST.MF
  - build.properties
  - plugin.xml

二 准备数据模型
首先我们准备数据模型,这些数据模型都保存在example.my.treeview.data这个包中

我们定义一个接口ItreeEntry,这个接口将定义树中每个节点共同特征(名称和子节点),代码如下

package example.my.treeview.internal;
import java.util.List;
public interface ITreeEntry {
     public String getName();
     public void setName(String name);
     //设置得到子节点的集合
     public void setChildren(List children);
     public List getChildren();
}

这里涉及的实体一共有3个,以下是他们的代码

package example.my.treeview.data;
import java.util.List;
import example.my.treeview.internal.ITreeEntry;
public class CityEntity implements ITreeEntry{
     private Long id;//唯一识别码
     private String name;//城市名
     private List peoples;//城市中的人
     public CityEntity(){}
     public CityEntity(String name){this.name=name;}
     public Long getId() {return id;}
     public void setId(Long id) {this.id = id;}
     public String getName() {return name;}
     public void setName(String name) {this.name = name;}
     public List getChildren() {return peoples;}
     public void setChildren(List peoples) {
           this.peoples = peoples;
     }
}




package example.my.treeview.data;
import java.util.List;
import example.my.treeview.internal.ITreeEntry;
public class CountryEntity implements ITreeEntry{
  //唯一识别码,在数据库里常为自动递增的ID列
     private Long id;      
private String name;//国家名
//此国家所包含的城市的集合,集合元素为City对象
     private List cities;      
     //两个构造函数
     public CountryEntity(){}
     public CountryEntity(String name){this.name = name;}
  //相应的get和set方法
     public List getChildren() {return cities;}
     public void setChildren(List cities) {this.cities = cities;}
     public Long getId() {return id;}
     public void setId(Long id) {this.id = id;}
     public String getName() {return name;}
     public void setName(String name) {this.name = name;}
}



package example.my.treeview.data;
import java.util.List;
import example.my.treeview.internal.ITreeEntry;
public class PeopleEntity implements ITreeEntry{
     private String name;
     public PeopleEntity(){}
     public PeopleEntity(String name){this.name=name;}
     public List getChildren(){return null;}
     public void setChildren(List children){}
     public String getName() {return name;}
     public void setName(String name) {this.name = name;}
}


三 创建树中的数据结构

代码如下

package example.my.treeview.data;
import java.util.ArrayList;
public class DataFactory {
     public static Object createTreeData(){
           //生成人的数据对象
           PeopleEntity p1 = new PeopleEntity("李红霞");
           PeopleEntity p2 = new PeopleEntity("金利军");
           PeopleEntity p3 = new PeopleEntity("何涛");
           //生成城市的数据对象
           CityEntity city1=new CityEntity("湖北");
           CityEntity city2=new CityEntity("北京");
           CityEntity city3=new CityEntity("湖南");
           //生成国家的数据对象
           CountryEntity c1 = new CountryEntity("美国");
           CountryEntity c2 = new CountryEntity("中国");
           //将数据对象连接起来
           //人和城市的关系
           {
                 ArrayList list = new ArrayList();
                 list.add(p1);
                 city1.setChildren(list);
           }
           {
                 ArrayList list = new ArrayList();
                 list.add(p2);
                 city2.setChildren(list);
           }
           {
                 ArrayList list = new ArrayList();
                 list.add(p3);
                 city3.setChildren(list);
           }
           //城市和国家的关系
           {
                 ArrayList list = new ArrayList();
                 list.add(city1);
                 c1.setChildren(list);
           }
           {
                 ArrayList list = new ArrayList();
                 list.add(city2);
                 list.add(city3);
                 c2.setChildren(list);
           }
           //将国家置于一个对象之下,
//这个对象可以是List也可以是数组
           {
                 ArrayList list = new ArrayList();
                 list.add(c1);
                 list.add(c2);
                 return list;
           }
     }
}

四 标签器和内容器
TreeViewer和TableViewer一样,是用内容器和标签器来控制记录对象的显示,并且使用内容器和标签器的语句也是一样的。

下面是标签器的代码

package example.my.treeview.internal;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.swt.graphics.Image;
/**
* @author hopeshared
* 标签提供器,控制纪录在树中显示的文字和图像等
*/
public class TreeViewerLabelProvider
implements ILabelProvider{
     //纪录显示 的文字,不能返回null
     public String getText(Object element){
           ITreeEntry entry = (ITreeEntry)element;
           return entry.getName();
     }
     //纪录显示的图像
     public Image getImage(Object element){
           return null;
     }
     //以下方法暂不用,空实现
     public void addListener(ILabelProviderListener listener){}
     public void dispose(){}
     public boolean isLabelProperty(Object e, String p){return false;}
     public void removeListener(ILabelProviderListener listen){}
}


下面是内容器的代码

package example.my.treeview.internal;
import java.util.List;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.Viewer;
/**
* @author hopeshared
* 内容器,由它决定哪些对象应该输出在TreeViewer里显示
*/
public class TreeViewerContentProvider
implements ITreeContentProvider{
     //由这种方法决定树的哪一级显示哪些对象
     public Object[] getElements(Object inputElement)
     {
           if(inputElement instanceof List){
                 List list = (List)inputElement;
                 return list.toArray();
           }else{
                 return new Object[0];//生成一个空的数组
           }
     }
     //判断某节点是否有子节点,如果有子节点,
//这时节点前都有一个“+”号图标
     public boolean hasChildren(Object element){
           ITreeEntry entry = (ITreeEntry)element;
           List list = entry.getChildren();
           if(list==null||list.isEmpty()){return false;
           }else{return true;}
     }
     //由这个方法来决定父节点应该显示哪些子节点
     public Object[] getChildren(Object parentElement){
           ITreeEntry entry = (ITreeEntry)parentElement;
           List list = entry.getChildren();
           if(list==null || list.isEmpty()){return new Object[0];
           }else{return list.toArray();}            
     }
     //以下方法空实现
     public Object getParent(Object element){return null;}
     public void dispose(){}
     public void inputChanged(Viewer v, Object oldInput, Object newInput){}
}

五 修改清单文件
下面给出的是plugin.xml文件代码

<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.0"?>
<plugin>
  <extension point="org.eclipse.ui.views">
    <view
    class="example.my.treeview.internal.TreeViewPart"
id="example.my.treeview.treeview"
name="my first tree view plugin"/>
  </extension>
</plugin>

六 插件的实现
在清单文件中已经指出了这个视图的实现类是example.my.treeview.internal.TreeViewPart,下面给出这个文件的代码

package example.my.treeview.internal;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.part.ViewPart;
import example.my.treeview.data.DataFactory;
public class TreeViewPart extends ViewPart{
     public void createPartControl(Composite parent){
           Composite topComp = new Composite(parent, SWT.NONE);
           topComp.setLayout(new FillLayout());
           TreeViewer tv = new TreeViewer(topComp, SWT.BORDER);
           tv.setContentProvider(new TreeViewerContentProvider());
           tv.setLabelProvider(new TreeViewerLabelProvider());
           Object inputObj = DataFactory.createTreeData();
           tv.setInput(inputObj);
     }
     public void setFocus(){}
}

七 运行结果
<抱歉,图片上传失败>


八 给节点增加动作
增加一个ActionGroup类

package example.my.treeview.internal;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.ui.actions.ActionGroup;
/**
* @author hopeshared
* 生成菜单Menu,并将两个Action传入
*/
public class MyActionGroup extends ActionGroup{
     private TreeViewer tv;
     public MyActionGroup(TreeViewer treeViewer){
           this.tv = treeViewer;
     }
     //生成菜单Menu,并将两个Action传入
     public void fillContextMenu(IMenuManager mgr){
           //加入两个Action对象到菜单管理器
           MenuManager menuManager = (MenuManager)mgr;
           menuManager.add(new OpenAction());
           menuManager.add(new RefreshAction());
           //生成Menu并挂在树Tree上
           Tree tree = tv.getTree();
           Menu menu = menuManager.createContextMenu(tree);
           tree.setMenu(menu);
     }
     //打开Action类
     private class OpenAction extends Action{
           public OpenAction(){
                 setText("打开");
           }
           //继承自Action的方法,动作代码写在此方法中
           public void run(){
                 IStructuredSelection selection = (IStructuredSelection)tv.getSelection();
                 ITreeEntry obj = (ITreeEntry)(selection.getFirstElement());
                 if(obj != null){
                       MessageDialog.openInformation(null, null, obj.getName());
                 }
           }
     }
     //刷新的Action类
     private class RefreshAction extends Action{
           public RefreshAction(){
                 setText("刷新");
           }
           //继承自Action的方法,动作代码写在此方法中
           public void run(){
                 tv.refresh();
           }
     }
}

接着,修改TreeViewPart.java,代码如下

……
Object inputObj = DataFactory.createTreeData();
           //-------------加入动作开始
           MyActionGroup actionGroup = new MyActionGroup(tv);
           actionGroup.fillContextMenu(new MenuManager());
           //-------------加入动作结束
           tv.setInput(inputObj);
……


结果如下图所示
<抱歉,图片上传不成功>


九 自定义扩展点
我们想将这个视图的显示内容与视图框架分开,这样,我们需要修改视图显示内容的时候只要重新贡献一次显示内容就可以了。
9.1 添加shema文件
这个sheme文件是采用可视化编辑器进行编辑,然后pde自动生成的,代码如下

<?xml version='1.0' encoding='UTF-8'?>
<!-- Schema file written by PDE -->
<schema targetNamespace="example.my.treeview">
<annotation>
    <appInfo>
      <meta.schema plugin="example.my.treeview" id="datafactory" name="Data Factory"/>
    </appInfo>
    <documentation>
      [Enter description of this extension point.]
    </documentation>
  </annotation>
<element name="extension">
    <complexType>
      <sequence><element ref="factory"/></sequence>
      <attribute name="point" type="string" use="required">
        <annotation><documentation></documentation></annotation></attribute>
      <attribute name="id" type="string">
        <annotation><documentation></documentation></annotation></attribute>
      <attribute name="name" type="string">
        <annotation><documentation></documentation>
          <appInfo><meta.attribute translatable="true"/></appInfo></annotation>
      </attribute>
    </complexType>
  </element>
  <element name="factory">
    <complexType>
      <attribute name="id" type="string">
        <annotation><documentation></documentation></annotation></attribute>
      <attribute name="name" type="string">
        <annotation><documentation></documentation></annotation></attribute>
      <attribute name="class" type="string" use="required">
        <annotation><documentation></documentation></annotation></attribute>
    </complexType>
  </element>
  <annotation>
    <appInfo><meta.section type="since"/></appInfo>
<documentation>[Enter the first release in which this extension point appears.]
</documentation>
  </annotation>
  <annotation>
    <appInfo><meta.section type="examples"/></appInfo>
    <documentation>[Enter extension point usage example here.]</documentation>
  </annotation>
  <annotation>
    <appInfo><meta.section type="apiInfo"/></appInfo>
    <documentation>[Enter API information here.]</documentation></annotation>
  <annotation>
    <appInfo><meta.section type="implementation"/></appInfo>
<documentation>
[Enter information about supplied implementation of this extension point.]
    </documentation></annotation>
  <annotation>
    <appInfo><meta.section type="copyright"/></appInfo>
    <documentation></documentation></annotation>
</schema>

9.2 创建接口文件
ItreeEntry.java之前就已经创建好了,不需要修改。现在添加另一个接口文件,代码如下:

package example.my.treeview.internal;
public interface IDataFactory {
     public Object createTreeData();
}


于是我们修改DataFactory.java,使它实现这个接口。
9.3 修改清单文件
我们来修改清单文件,加入扩展点声明,并扩展它,代码如下

……
<extension-point id="datafactory" name="Data Factory"
schema="schema/datafactory.exsd"/>
<extension point="example.my.treeview.datafactory">
<factoryclass="example.my.treeview.data.DataFactory"/>
</extension>
……


9.4 修改TreeviewPlugin.java
增加一个方法Object loadDataFactory(),代码如下

……
public static Object loadDataFactory(){
       IPluginRegistry r=Platform. getPluginRegistry();
       String pluginID="example.my.treeview";
       String extensionPointID="datafactory";
       IExtensionPoint p=r.getExtensionPoint( pluginID, extensionPointID);
       IConfigurationElement[] c=p.getConfigurationElements();
       if( c != null) {
             for( int i= 0; i <c.length; i++) {
                   IDataFactory data = null;
                   try { data=( IDataFactory)c
.createExecutableExtension("class");
                         if( data != null){ return data.createTreeData(); }
                   } catch( CoreException x) { }}}
       return new Object();
  }
……

9.5 修改TreeViewPart.java

Object inputObj = DataFactory.createTreeData();
替换为
Object inputObj = TreeviewPlugin.loadDataFactory();

9.6 其他辅助文件
其实TreeViewerLabelProvider.java和TreeViewerContentProvider.java可以看成是对DataFactory这个扩展点的辅助文件

9.7运行
跟之前的实现没有区别,但是我们向eclipse贡献了一个扩展点


十 参考资料
《Eclipse入门到精通》
www.sohozu.com 《自己动手编写Eclipse扩展点》
EclipseCon2005_Tutorial1.pdf 《Contributing to Eclipse: Understanding and WritingPlug- ins》


图片:
 

图片:
 


经过一个多小时的努力。。。(嘿嘿,以前没有仔细研究Property什么的,今天弄了一下)

终于出现了property并且可以修改属性页中的值




代码

example.my.treeview.rar


哦,补充一下,由于模型(就是treeviewer中的初始数据)是写死的,改的property其实是修改了内存中的对象的值。假如用emf做模型持久化,就会保存修改。但是目前是不能保存修改的。


关于本文的讨论还是很多的,也有很多有用的信息。见http://www.eclipseworld.org/bbs/read.php?tid=168
本文第一次发表是在社区之中,本来也没觉得有转到blog的必要,但是后来发觉自己的记忆力越来越差,曾经作过的都忘记的差不多了,为了避免丢失,还是存在这里备份比较好。
posted @ 2006-07-25 13:54 hopeshared 阅读(3509) | 评论 (1)编辑 收藏

总是写不好ppt,为什么呢?
先把ppt的写法总结放在这里,这样就不容易忘记了。

1. 选择合适的模板,通常选择根内容相关,简单清新的模板就可以了。当然,也要看ppt是用来做什么的。
2. 标题简单明了,突出核心内容。
3. 第一页一般为Outline或者Agenda。我经常犯的错误是第一页命名为Outline,但是Outline其实跟Introduction是一个抽象级别的。
4. 每页的Items都要在一个抽象级别。
5. 每个子Item都要从同一个角度来划分
6. 通常在Introduction中介绍Challeges。如果Challeges不太多Solutions也不复杂,那么可以将两者合并作为一个Topic。如果Challeges太多在Introduction中讲不清楚,那么可以在Introduction之后列一个Challeges的Topic。
7. Introduction中应列出问题,现状,目前的挑战以及High Level的解决办法。
8. ppt的一般结构是:本项目的意义,难度,如何解决,演示。
9. Introduction之后应该紧接着介绍项目中“创造”的概念。
10. Solution应该是紧接在Challeges后面的一个Topic,它仍然是抽象的描述解决方法。更具体的涉及到技术细节的应归结为Approach或者Technics紧跟在Solution之后作为补充。
11. 实现本系统用到了其他的成熟技术。那么应该首先说明遇到了什么问题(why),如何来解决(how),有多少种解决方法(what),为什么选择你使用的这种(why)。我想可能要补充上如何来用它吧。
12. 具体的技术细节应该是在Scenarios之前。
13. Scenarios应该用少量的文字配合大量的图片来表达,不应该出现技术
14. 项目的关键特性要结合Demo体现出来
15. ppt中的结构图应该采用立体感强的图片来表示。ppt中出现的图片都应该有很强的表现力(千言万语尽在图中)。
16. 作为ppt还有一点跟paper不一样,那就是,不再需要Reference。而Conclusion&Future work则应该提炼成精简的Sumerize。

最后,ppt的灵魂就是逻辑,逻辑,还是逻辑。一定要有逻辑。

经常作总结,是增强逻辑性,条理性的最有效途径。

sigh,谁让我一向不用逻辑思考问题呢,555

posted @ 2006-07-21 14:06 hopeshared 阅读(8813) | 评论 (10)编辑 收藏

作者:Brian McBride
发表时间:2000年9月2日
原文链接: http://www.hpl.hp.co.uk/people/bwm/rdf/jena/rssinjena.htm
译者:dlee
翻译时间:2001年5月26日

    RSS 1.0 是最近宣布的一个格式,顺从于 W3C 的 RDF (资源定义框架),用来分发(distributing) 站点摘要 (site summary) 和企业联合 (syndication) 元数据。一个站点摘要文档的例子可以在规范中找到。Jena 是一套实验性的用来处理 RDF 的 Java API。这篇笔记描述了一个应用程序使用 Jena 来将一个站点摘要文档翻译成 HTML。整个程序的源代码作为 RenderToHTML 可以在 Jena 发布的例子包里得到。

    这篇文章和例子代码基于 RSS 规范候选发布版本1 (Release Candidate 1 version)。
    这个应用程序以创建一个 RDF 模型开始,实际上在内存中是一个 RDF statement 的集合。然后解析站点摘要文档,使用一个 RDF 解析器,并加载 statement 到新创建的模型中。

      Model model = new ModelMem();
      model.read(" http://www.xml.com/xml/news.rss");

    在写出一个样板 HTML 头后,程序列出和处理在输入流中的每个 channel。在 RDF 术语中,channel 是具有一个 rdf:type 属性的 rss:channel 的资源。我们使用 Jena API 来列出具有一个有这个值的 rdf:type 属性的所有的资源。在下面的代码中,假设输出是一个接收 HTML 输出的 PrintWriter。

      ResIterator channels =  model.listSubjectsWithProperty(RDF.type, RSS.channel);
      while (channels.hasNext()) {
          renderChannel(channels.next(), out);
      }

    为了呈现 (render) 一个 channel,程序首先写出它的 title,description 和相关的 image 和 textinput 字段 (如果有的话)。getProperty 方法用来得到 channel 的 title,link 和 description 属性,随后这些可以被呈现为 HTML。

      void renderChannel(Resource channel, PrintStream out)
             throws RDFException {
          String url = null;
          String title = null;
          String desc = null;

          url = channel.getProperty(RSS.link).getString();
          title = channel.getProperty(RSS.title).getString();
          desc = channel.getProperty(RSS.description).getString();

    一个 channel 可以有一个相关联的 image 和 textinput,测试是否存在这些属性和是否需要调用合适的方法来呈现它们是很简单的。

      if (channel.hasProperty(RSS.image)) {
          renderImage(channel.getProperty(RSS.image) .getResource(), out);
      }
      if (channel.hasProperty(RSS.textinput)) {
          renderImage(channel.getProperty(RSS.textinput) .getResource(), out);
      }

    为了处理一个 image,同样的调用被用来确定 image 的属性。

      String url = null;
      String title = null;

      // does the image have a link?
      if (image.hasProperty(RSS.link)) {
          url = image.getProperty(RSS.link).getString();
      }

      // does the image have a title?
      if (image.hasProperty(RSS.title)) {
          title = image.getProperty(RSS.title).getString();
      } image.getProperty(SSF.title).getString();

    然后这个 image 可以被呈现成为 output stream。textinput 以 相似的方法处理。

    channel 有一个 items 属性,它的值为一个 RDF sequence。因此使用 getProperty 方法得到这个 sequence 就很简单的了。然后迭代每一个元素以呈现它。

      if (channel.hasProperty(RSS.items)) {
          Seq items = channel.getProperty(RSS.items).getSeq();
          for (int i=1; i<= items.size(); i++) {
              renderItem(items.getResource(i), out);
          }
      }

    以相同的模式呈现每一个 items。首先得到需要呈现的 items 的属性,然后写出 HTML。

      void renderItem(Resource item, PrintWriter out) {
          String url = item.getProperty(RSS.link).getString();
          String title = item.getProperty(RSS.title).getString();
          String desc = item.getProperty(RSS.description) .getString();
          ...
      }

    使用 Jena 来处理 RSS 1.0 流是简单和直接的。

    Brian McBride
    HP实验室,2000年9月2日
    修改于2000年12月2日
posted @ 2006-07-20 10:36 hopeshared 阅读(608) | 评论 (0)编辑 收藏

jUDDI,发音(Judy),是服务于WebServices 的UDDI的java实现开源包。

1 安装

1.1 下载

下载地址:http://ws.apache.org/juddi/releases.html

目前的jUDDI的最新版本是0.9rc3(Release Candidate #3 for Version 0.9),不过在这个版本中有一些的bug。

juddi0.9版本发布应该是不会久,可以参考下面这段话,是Viens Stephen(juddi主要开发者之一)在mail list中说的:we've closed 40+ issues since January 1, 2005. We'll be releasing a 0.9rc4 as soon as Axis 1.2 final is released and then releasing a 0.9 final a few weeks after that. (March 22, 2005)

1.2 数据库安装

UDDI需要有一个地方来存储注册的数据,因此首先要选择一个关系数据库安装。JUDDI可以使用任何支持ANSI standard SQL关系数据库( 例如MySQL, DB2, Sybase, JdataStore等)。本实例使用MySQL。

数据库安装完成后,在MySQL数据库中运行juddi-0.9rc3\\sql\\mysql\\create_database.sql, juddi-0.9rc3\\sql\\mysql\\insert_publishers.sql。数据库准备完成。

1.3 安装juddi及配置

首先将juddi-0.9rc3\\webapp下的juddi文件夹复制到Tomcat下的webapps中,并将 mysql-connector-java-3.1.7\\mysql-connector-java-3.1.7-bin.jar复制到Tomcat 5.0\\webapps\\juddi\\WEB-INF\\lib下。

下面就是连接数据库的配置,在Tomcat/conf/server.xml的Host element中加入:
<Context path="/juddi" docBase="juddi" debug="5" reloadable="true" crossContext="true">
<Logger className="org.apache.catalina.logger.FileLogger" prefix="localhost_juddiDB_log"
suffix=".txt" timestamp="true"/>
<Resource name="jdbc/juddiDB" auth="Container" type="javax.sql.DataSource"/>
<ResourceParams name="jdbc/juddiDB">
<parameter>
<name>factory</name>
<value>org.apache.commons.dbcp.BasicDataSourceFactory</value>
</parameter>
<!-- Maximum number of dB connections in pool. Make sure you
configure your mysqld max_connections large enough to handle
all of your db connections. Set to 0 for no limit. -->
<parameter><name>maxActive</name><value>100</value></parameter>
<!-- Maximum number of idle dB connections to retain in pool.
Set to 0 for no limit. -->
<parameter><name>maxIdle</name><value>30</value></parameter>
<parameter><name>maxWait</name><value>10000</value></parameter>
<!-- MySQL dB username and password for dB connections 帐号密码根据数据库安装配置修改 -->
<parameter><name>username</name><value>root</value></parameter>
<parameter><name>password</name><value>****</value></parameter>
<!-- Class name for mysql JDBC driver -->
<parameter>
<name>driverClassName</name>
<value>com.mysql.jdbc.Driver</value>
</parameter>
<!-- The JDBC connection url for connecting to your MySQL dB.
The autoReconnect=true argument to the url makes sure that the
mm.mysql JDBC Driver will automatically reconnect if mysqld closed the
connection. mysqld by default closes idle connections after 8 hours.
数据库url连接配置
-->
<parameter>
<name>url</name>
<value>jdbc:mysql://host.domain.com:3306/juddi?autoReconnect=true</value>
</parameter>
<parameter>
<name>validationQuery</name>
<value>select count(*) from PUBLISHER</value>
</parameter>
</ResourceParams>
</Context>

1.4 本地安装检查

访问http://127.0.0.1:8080/juddi/happyjuddi.jsp页面,此页面检查了jUDDI所必须的包和配置的正确性,并测试数据库连接是否成功。 如果没有红色文字,即本地安装成功,即可进行webservices的发布发现等服务。

2 测试实例



以上安装成功的是UDDI的服务器端,而进行发布、查找服务的客户端的应用则要用jUDDI、UDDI4J等包来进行开发。我们可以直接使用jUDDI自 带的测试代码来进行客户端使用的学习。

2.1 使用uddi4j测试

使用uddi4j作为客户端进行测试。

代码位置:juddi-0.9rc3\\src\\uddi4j\\org\\apache\\juddi\\uddi4j

新建立好一个工程并引入此代码,然后对代码进行必要的修改,主要是包名和配置。引入必要的包,比如:junit.jar、 uddi4j.jar、juddi.jar、soap.jar等(因为欧的代码库中有很多种代码,对应很多包,不知道其他哪些是必须的了:)。

接着是数据库的初始化,需要插入一个可以添加其他Publisher的Publisher,sql 语句: INSERT INTO PUBLISHER (PUBLISHER_ID,PUBLISHER_NAME,ENABLIED,ADMIN) VALUES ('juddi','juddi user','true','true');

调试代码后,运行TestAll测试,您可能会发现测试FAILURE很多,这些当中有些是测试代码的错误,也有可能是juddi-0.9rc3的缺陷( juddi-0.9rc3不是正式发布版)。

以下列举一些本测试案例测试失败的可能出现的修改方法:

2.1.1 加载配置文件时访问不到samples.prop

我的解决办法是将建立一个新配置文件位置,在工程目录下的:conf\\samples.prop。

在Configurator.load()方法中代码可以这样修改:
    Properties config = new Properties();
try {
config.load(new java.io.FileInputStream("./conf/samples.prop"));
}
catch (Exception e) {
System.out.println("Error loading samples property file\\n" + e);
}
解决方法很多,您可以自己思索。

2.1.2 TransportClassName配置错误

如果错误提示中有这样的报告,即可能是此错误:
org.xml.sax.SAXParseException: Element or attribute do not match QName production: QName::=(NCName':')?NCName.

在当前测试实例代码中的默认配置(samples.prop)中,TransportClassName定义成org.uddi4j.transport.ApacheSOAPTransport, 而我们使用的包是axis.jar,因此需要修改成相应的类,代码修改如下:
# -----------------------------------------------------------------------

# Transport classname. Typically defined on commandline as

# -Dorg.uddi4j.TransportClassName=xxx.

# -----------------------------------------------------------------------

#TransportClassName=org.uddi4j.transport.ApacheSOAPTransport

TransportClassName=org.uddi4j.transport.ApacheAxisTransport

# TransportClassName=org.uddi4j.transport.HPSOAPTransport

2.1.3 TestFindBusiness案例不通过

TestFindBusiness中有大小写匹配测试,但是在juddi-0.9rc3中的大小写匹配(caseSensitiveMatch)有bug,因此可以将大小写匹配的测 试案例注释掉。

2.1.4 PublisherManager的代码错误

在测试Test_save_tModel的时候_testAuthTokenExpired()中,我们测试过期验证时,在错误匹配的时候,会出现测试失败,如果捕捉这个 匹配的结果,你会发现,出错的类型是E_authTokenRequired而不是期待的E_authTokenExpired。

这是因为在我们所获得的AuthToken是空的,在根源就是在PublisherManager. getExpiredAuthToken(String, String)方法中,代码:
RegistryProxy proxy = new RegistryProxy();
proxy的实例的配置是空的。因此,我们修改这个方法变成:
  /**

* changed by xio

* @param publisher String

* @param password String

* @param testprops Properties:增加的参数,传入基本配置

* @return String

*/

public static String getExpiredAuthToken(String publisher, String password,

Properties testprops) {

Properties props = new Properties();

props.setProperty(RegistryProxy.ADMIN_ENDPOINT_PROPERTY_NAME,

testprops.getProperty("adminURL"));

props.setProperty(RegistryProxy.INQUIRY_ENDPOINT_PROPERTY_NAME,

testprops.getProperty("inquiryURL"));

props.setProperty(RegistryProxy.PUBLISH_ENDPOINT_PROPERTY_NAME,

testprops.getProperty("publishURL"));



RegistryProxy proxy = new RegistryProxy(props);

AuthToken token = null;

AuthInfo authInfo = null;

String ret = null;

try {

token = proxy.getAuthToken(publisher, password);

authInfo = token.getAuthInfo();

ret = authInfo.getValue();

System.out.println("getExpiredAuthToken:" + authInfo);

proxy.discardAuthToken(authInfo);

}

catch (Exception ex) {

ex.printStackTrace();

}

return ret;

}

2.2 使用jUDDI测试

在juddi-0.9rc3版本中自带的代码中没有客户端的使用实例,虽然附带了整个项目代码的测试代码,但是估计没什么人喜欢从这里抽取学 习客户端使用的学习。

当然,学习的实例还是有的,在cvs当前的工程代码中,有个samples的文件夹,这部分代码便是一个十分齐全的实例(有几个类没完成, 但不影响:)。

Cvs服务器数据:http://ws.apache.org/juddi/cvs.html

Wincvs的使用请网上下载阅读。

其他:在进行代码学习的同时,建议阅读webservices相关资料文档。强烈建议阅读:理解 UDDI 注册中心的 WSDL 系列 (http://www-900.ibm.com/developerWorks/cn/webservices/ws-uwsdl/part1/)

参考资料:
http://wiki.apache.org/ws/jUDDI_HOW-TOs
http://ws.apache.org/juddi/lists.html

原作者:xio@qq.com
来 源:http://xio.mblogger.cn



原文地址:http://it.13520.org/ArticleView/2005-9-7/Article_View_121697.Htm

posted @ 2006-07-18 15:04 hopeshared 阅读(2096) | 评论 (0)编辑 收藏

在过去的数年中,许多开发人员都使用了各种版本的J2EE,使服务器端软件编程的情形得到了很大的改观,现在,他们将再次挑战SOAP,在服务器端软件编程方面取得更大的进展。
SOAP服务的支持者认为:
·企业级应用服务器是服务(或事务)的集合。
·可以使用的服务应当很方便地列出来供用户浏览、搜索和访问。
·象现在的基于组件的开发模式那样,将应用服务器设计为服务的集合将鼓励开发人员采用更好的设计模式。
·这些事务能够被重新定位、负载平衡、替代等。
而对SOAP持怀疑态度的人认为,SOAP是推广CORBA和COM的又一次尝试。他们指出,要简单地访问一个对象,需要完成太多的准备性工作,而且,UDDI带来的好处也被夸大了。
那么,到底哪一种观点更合理呢?对于一些思想开放的人士而言,在决定是否采用SOAP服务前,他们一定希望了解其中的一些核心技术。
解密UDDI
我们首先来看看UDDI代表什么?UDDI是Universal Description, Discovery and Integration(统一描述、发现和集成)的缩写。UDDI的意图是作为一个注册簿,就象黄页是一个地区企业的注册簿一样。象在黄页中那样,在UDDI注册簿中,企业将在不同的目录下注册它们自己或其服务。通过浏览一个UDDI注册簿,开发人员能够查找一种服务或一个公司,并发现如何调用该服务。
除了黄页外,UDDI还使用了白页和绿页。白页是企业实体列表,绿页是调用一项服务所必需的文档。
UDDI的定义非常全面,足以适应不同种类的服务。一个UDDI服务定义可能代表一个传真服务或电话服务。作为一种注册簿,UDDI一般使用数据库一类的软件来实现,在该数据库中,存在一个允许发布或查询服务的有关信息。
UDDI数据模型
UDDI数据模型包括下面的主要元素:
·businessEntity:表示一个实际的企业。
·businessService:表示一个企业提供的服务。
·bindingTemplate:如何调用服务的说明。
·tModel>: Good luck understanding this! (Just kidding, I will explain this later.)
为了加深对UDDI数据模型的理解,我们来看看这些数据元素的UML表示法。图1是这四种主要元素之间的关系图:

从上面的图中我们可以知道,一个businessEntity(一家公司)有一个能够告诉我们更多有关公司信息的描述性URL和联系人清单,此外,businessEntity还有一个商业服务清单。每种服务可能有多种调用方法,每种调用都由一个绑定模板描述。绑定模板详细地描述了如何访问一个服务,它受益于一系列描述用户如何访问这一服务的文档。绑定模板和其必要的文档之间的联系是通过所谓的tModel完成的。在上面的图中,这种联系被简单地描述为一个绑定模板有许多tModels。在进一步地解释tModels与绑定模板的关系前,我们必须先弄清楚tModels是什么。
TModel是什么?
我们可以把tModel想象成数据库库中的一个独立的表,其中包含下面的字段:名字、描述、URL、唯一的关健字。实际上,tModel就是包括有名字和描述,那么使用数据库表表示它是否是一种浪费呢?我们下面就会讨论这一问题:
下面是一个假想的tModel数据库表中的二个实体:
键 名字 描述 URL
1 Java-class 表示一个具备完全资格的java类的名字 http://www.javasoft.com/
2 Jndi-home 表示一个JNDI名字 http://www.javasoft.com/

在将tModel比作数据库表方面,有几点值得注意。首先,tModel是一个独立的表,意味着它可以不依赖其他软件而存在;其次,tModel是查找表,提供了键与键的表示之间的转换关系。从这一点来看,tModel象词典那样,是一个引用表。在一些数据库中,这样的表也被称作是码集。
因此,如果在上面的tModel中存在下面的记录:
com.mycompany.HelloWorld, 1
com.mycompany.HelloWorldHome, 2
就意味着字符串com.mycompany.HelloWorld是一个有完整资格的Java类;而字符串com.mycompany.HelloWorldHome是一个JNDI名。

因此在一定程度上,tModels中唯一的键与“名字空间”这个概念差不多。为了进一步地说明这个问题,我们来看一下下面的数字:
904-555-1212
904-555-1213
1-56592-391-x
你能够分清这些数字的意义吗?我们需要在一个环境或名字空间中来确认,904-555-1212是电话号码,904-555-1213是传真号,1-56592-391-x是一个ISBN号。
因此在tModel数据库表中,我们将需要定义三个实体:一个是电话号码;一个是传真号码,一个是ISBN号码。
下面我们以mycompany公司公布了一条号码为1-800-my-helpline的电话支持热线,并在UDDI中注册。那么,我们的数据模型为:
company name: mycompany
Service name: helpline
tModel: key=11 (representing telephoneline), name=telephone,
description=telephone stuff, url:
some at&t url
binding:
accesspoint: 1-800-my-helpline
tModelInstanceInfo: 11

有了对tModel的基本理解后,我们就可以利用UML图表来研究绑定模板与tModels之间的关系了。我在上面曾经说过,这将使我们对绑定模板如何完成UDDI的“如何调用一项服务”的要求有一个直观的理解。

在图2中,我们讨论了一个绑定模板与tModels之间的关系。从图表中我们可以看出,一个绑定模板可以指向一个由一个tModel确定的技术规格,技术规格有二部分组成:
·规格的类型。(例如电子邮件、传真、WSDL、SOAP等。)
·确定输入和输出的文档(在SOAP服务中,这些文档可以是XML输入/输出消息格式。)
既然我们已经对tModels有了一定程度的详细了解,就该再讨论UDDI中更复杂的东西了,也就是身份包和类别包。
理解标识符包和类别包
如果说从概念上理解tModels是理解UDDI需要跨越的第一道障碍,那么理解标识符包和类别包则是需要跨越的第二道障碍。下面的例子可以帮助我们理解这二个概念。
例如,您的公司在美国开展业务需要有一个税号,如果还在另外的国家(例如墨西哥)开展业务,就需要有一个墨西哥的税号。为了能够在UDDI注册簿中获取您的公司的这些信息,在UDDI中应当包括下面的内容:
公司名字:mycompany
标识符:
美国税号:111111
墨西哥税号:2223344
其他国家税号: 333333

...其他的xml内容
<identifierBag>
<keyedReference tModelKey="US-tax-code"
keyName="taxnumber" keyValue="1111111">
<keyedReference tModelKey="Mexico-tax-code"
keyName="taxnumber" keyValue="2223344">
<keyedReference tModelKey="other-stuff"
keyName="taxnumber" keyValue="333333">
</identifiedBag>
... 其他的xml内容
现在明白tModels如何被用作名字空间了吧。为了进一步地深化对标识符包的理解,我们在下面的图中再次解释了标识符和类别包的概念:

从上面的图中我们能够看出,标识符包是一个在特定环境中的键/值对集合,这个环境从本质上说就是能够唯一地解析名字/值对儿的名字空间,它是由tModel确定的。类别包也是如此,二者之间唯一的区别就是类别包中由tModel确定的名字空间是一个预先确定好的类别。
类别包
我想将公司归类于饭店,其地理位置位于杰克逊维尔。
公司名字:mycompany
适用类:
企业类型:饭店
所在城市:杰克逊维尔
<categoryBag>
<keyedReference tModelKey="BusinessTypeClassification"
keyName="restaurant" keyValue="..">
<keyedReference tModelKey="CityClassification"
keyName="JAX" keyValue="..">
</categoryBag>
现在,我们已经搞清楚了tModels是如何用在标识符和类别包中的。从本质上说,tModels就是名字空间。

tModels也能被分类吗?
我们已经明白了企业实体是如何利用使用了类别包的。另外,UDDI也允许tModels本身被分类。
我们用分层次的文件系统进行说明。目录是用来对文件进行分类的,但目录还可以在父目录下再被分类。象硬盘上的目录那样,tModels也可以被分层次地进行组织。
下面我们来讨论名字为getUniversalTime()的服务,该服务将返回当前全球任一地方的时间。二家存在竞争关系的公司可能会提供这一服务的不同实现。商业服务只限于在公司内部使用,公司之外的用户是不可使用的:
company1:getTime()
company1:getCurrentTime()
这二者的作用相同,为了表明它们实现的是同一个被称作getUniversalTime()的服务,我们可以定义如下所示的tModel:
tModel
name:: Get Universal Time
category: uddi-org:types, wsdl
[意味着这是一个由WSDL文档定义的服务]
上面的定义表明getUniversalTime()是一个WSDL服务,可以由任何公司实现。
既然已经阐明了tModels和包之间的关系,我们下面可以看看一个tModel的UML表示:

从上面的图表中,我们可以看出tModel基本上就是一个名字和描述,另外,它也可以包含一个URL,以提供更进一步的详细资料。它可以由一个标识符包确定和由一个类别包进行分类。
我们已经知道,一个tModel所属于的类别是由UDDI━━WSDL、SOAP等预先定义好的,下面是一个uddi-org:types名字空间中预先定义类别的清单:
tModel
identifier (唯一标识符)
namespace
categorization
specification
xmlSpec
soapSpec
wsdlSpec
protocol
transport
signatureComponent
如何开始在UDDI中创建一个服务?
在开始定义服务的tModel时,要求我们为服务指定一个名字和上面所列出的服务类型中的一个。当然了,如果不喜欢上面的分类可以自己创建类别。
我们可以针对上面定义的tModel开发一个商业服务。在商业服务定义中,我们需要指定一个指向定义了服务的输入/输出的WSDL文档的URL。

在UDDI中为什么需要tModels?
在UDDI中使用tModels的目的如下:
·对商业实体进行标识和分类
·对商业服务进行标识和分类
·对tModels进行标识和分类
·将商业服务绑定到它们抽象的tModel定义上
UDDI名词的快速参考
在看有关UDDI的资料时,如果看到很深奥的术语是一件很烦人的事儿,下面我们就把一些经常用到的与UDDI有关的概念搜集起来,通过比较帮助我们对UDDI概念的理解:
接口和实现
服务绑定是一个容器,接口依赖于其实现;绑定元素详细说明了tModelInstaceInfo和instanceDetail, 我们将再通过一个例子加深对这一问题的例子。对于“获得统一时间”服务而言,细节将提供定义了输入、输出的实际的WSDL文档,绑定的访问点将提供实现服务的物理机器和端口。了解了这些,我们可以得出如下的结论:
·为一种服务定义的tModel是允许多家公司提供该服务的多个实现的界面。
·服务绑定是具体的实现。
名字空间
Java、C++和XML中都有名字空间的概念,尽管其叫法可能不同,但它们都提供了使名字有意义的环境。在UDDI中,tModel象征着“名字”。当把这个名字作为目录使用时,你的本意是,“我属于这个名字”。从这个意义上说,tModel表示的是名字空间。
技术指纹
当一种服务被注册为tModel时,我们可以注册用来与该服务通讯的协议(例如WSDL、SOAP等)。因此根据所使用的协议不同,tModel有WSDL指纹或SOAP指纹。因此tModel被认为是技术性指纹,每种指纹与一种特定的协议有关。如果说一种tModel可以代表一个“技术性指纹”,我们也可以说tModel能够表示一种“协议”。
分类
分类与类别、名字空间类似,它提供了一种环境,只有在这种环境下,一些数字和名字才有意义。例如,
白页
UDDI注册簿中的企业实体列表和根据名字搜索企业的能力。
黄页
黄页将企业实体按企业类别或其他适用的类别分类。
绿页(技术规格)
绿页有助于我们理解服务的定义和访问服务的要求,serviceBindings和tModels也有助于这一目标的实现。
JAXR
JAXR是在服务注册中使用的一种Java XML API。我们以前曾经讨论过,UDDI中的所有元素都用XML文档进行描述。从某种意义上说,嘦我们能够发送包含有服务描述的XML消息,UDDI注册簿就能够对它进行注册或更新。下面我们讨论在没有工具的情况下如何完成这一任务:
1、编写必要的服务描述XML文档。
2、理解SOAP头部,以便能够将XML文件作为一个SOAP附件发送给UDDI注册簿。
3、使用SOAP客户端Java API完成第二步中的任务。
4、通过编程的方式处理SOAP消息。
5、根据UDDI服务的描述,构造消息完成该UDDI服务的任务。
6、对每个消息,完成第2、3步中的操作。
如果使用JAXR,我们可以更好地完成这一任务。因为JAXR允许我们在发送XML消息时无需考虑SOAP头部,而且是一种严格的面向消息的协议。使用JAXR,上面的任务将简化为:
1、编写必要的服务描述XML文档。
2、使用JAXM API发送和接收XML消息,JAXR隐藏了SOAP信息。
3、根据UDDI服务构建消息完成该UDDI服务。(例如,向UDDI注册簿注册UDDI服务包含有4次信息交换过程。)这意味着我们仍然需要与XML内容打交道。
使用这种方式,我们只需处理高层次的Java API即可,这些Java API能够产生必需的消息并通过JAXR传输它们。需要指出的一点是,JAXR也用来完成该任务。
下面列出了JAXR的一些目标:
·支持业界标准的XML注册功能
·支持成员机构和企业的注册
·支持任意注册内容的存储和提交
·支持XML、非XML注册内容的管理
·支持注册内容间用户定义的结合
·支持用户定义的注册内容的多级分类
·支持基于定义的分类计划的注册内容查询
·支持基于复杂查询的注册内容查询
·支持基于关健词的注册内容查询
·支持Web服务的共享
·支持合作伙伴间商业过程的共享
·支持合作伙伴间计划的共享
·支持合作伙伴间商业文档的共享
·支持合作伙伴间贸易伙伴协议的谈判
·支持计划汇编
·支持异类分布注册
·支持参与各方发布/订阅XML消息
JAXR将不仅仅支持UDDI,还会支持其他的类似注册服务标准(例如ebXML)。


原文地址:http://www.wyt2008.com/Article/java/web/200603/Article_921.html
posted @ 2006-07-18 10:16 hopeshared 阅读(798) | 评论 (0)编辑 收藏

利用w3c的dom:

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 
  DocumentBuilder builder;
  
try   {
   builder 
=  factory.newDocumentBuilder();
   Document doc 
=  builder.parse( new  ByteArrayInputStream(str.getBytes())); 
  }
  catch  (ParserConfigurationException e)  {
   
//  TODO Auto-generated catch block
   e.printStackTrace();
  }
  catch  (SAXException e)  {
   
//  TODO Auto-generated catch block
   e.printStackTrace();
  }
  catch  (IOException e)  {
   
//  TODO Auto-generated catch block
   e.printStackTrace();
  }
 

利用dom4j
SAXReader saxReader = new SAXReader();
        Document document;
        
try {
            document 
= saxReader.read(new ByteArrayInputStream(str.getBytes()));
            Element incomingForm 
= document.getRootElement();
        }
 catch (DocumentException e) {
            
// TODO Auto-generated catch block
            e.printStackTrace();
        }
posted @ 2006-07-06 11:17 hopeshared 阅读(13954) | 评论 (6)编辑 收藏

日益提高的机动性和通讯的多种模式已经使个人通讯管理成为了一项非常耗费时间的任务。企业语音应用(通常指统一通讯应用)有助于提高人员的工作效率和管理复杂的多频道通信。融合的语音和数据网络使创建企业语音应用成为可能。企业语音应用采用一个单一的接口来满足所有人员的通讯需求从而简化了通讯。

  企业语音应用的例子包括自动总机、统一消息、follow-me(跟随)、一号通、语音拨号、鼠标拨号、语音门户、会议和协作应用等。部署一个企业语音应用的最初投资一般包括运行这些应用的平台和应用程序本身。这些应用包括一台服务器、电话或者VoIP接口硬件或者软件和一个语音应用平台)。

  最近向开放的、基于标准的解决方案过渡的趋势对降低平台的成本做出了很大的贡献。语音应用程序能够安装在运行标准操作系统(Windows或者Linux)的标准英特尔服务器上和基于标准的使用VoiceXML、CCXML和MRCP的语音/媒体平台上。企业有时候为了减少老式系统每年的维护成本将用新的、开放的、标准的系统替换那些老式专有的系统。

  根据应用的复杂性和所需要的个性化处理的数量,语音应用的成本有很大的差别。对于许多应用来说(如自动总机或者一个会议应用),这种语音应用多数都是捆绑式的服务,而且使用的价格比较合理,至少要比前几年的成本低很多。

  这些较低的成本、因为语音和数据网络的融合而大量节省的成本、以及在整个企业范围内提供这些应用等因素结合在一起将对企业更有吸引力并且能节省更多的成本。无线网络(包括局域网和广域网)能够更方便地让人们在移动中使用同样的应用程序。

  如果你已经拥有一个融合的网络并且正在设法证明一个企业语音应用的实例是合理的,这个实例在几乎所有的应用实例中都能很容易地做到。例如,一个每周7天每天24小时提供话务员服务并且成本不到4万美元的自动总机可以通过节省一个话务员的工资和福利待遇来证明是合理的。

  在一个雇员采取计时工资制度的企业中,例如律师事务所或者咨询服务公司,更高效率的通讯可以显著提高办公效率,因此,部署企业语音应用毫无疑问是取胜的重要的条件。例如,Sage Research研究公司总裁Kathryn Korostoff引述的一项调查结果称,一半以上的接受调查的企业表示,由于增加使用了协作会议、统一消息和找人式的消息(find-me-style messaging)等电话功能,每个雇员每个星期的工作效率能够提高4个小时。在一个有100名雇员如此提高了办公效率的公司中,每年就可以获得2000个小时额外的生产率。一家律师事务所每年就可以重新获得2000个小时的工时,从而增加40万美元的收入(按照每小时200美元计算)。

  即使这种节省没有导致计时工资的小时数量的增加,它也会显著提高一个机构的效率,使这个机构更好地对用户做出反应和做出决策,并且能够更快地完成项目。在这些情况下,成本的合理性是以比较软的生产率节省为基础的,但是,这个好处仍然是很明显的。精明的机构认识到这方面是有可证明的好处的,这个好处就是可以消除这些无效率和实施企业语音解决方案以改善拖累这个机构的具体的商务流程。

  总之,在今后的几年里,你将会看到大多数机构部署语音应用。基于标准的硬件和软件的入门级解决方案的成本比较低是合理的,融合的网络的节省和生产率的提高很容易证明它的合理性。企业雇员很快就将把语音应用当成一种他们固有的权利,就像他们现在使用电话、PC和互联网一样。

  翻译:东缘


中文:http://searchnetworking.techtarget.com.cn/NetInformation/137/2463637.shtml
英文:http://searchvoip.techtarget.com/tip/1,289483,sid66_gci1195600,00.html

posted @ 2006-06-30 17:29 hopeshared 阅读(550) | 评论 (0)编辑 收藏

刚刚被一个比较麻烦的问题所困扰。这个问题就是如何判断数据中某张表是否存在,如果不存在则创建它。

恩,我先用了最笨的方法,就是写个select从表中读数据,捕获异常的同时就知道了改表没有创建。

此法不通,因为这个时候的异常似乎被认定为了系统错误,于是后面创建表的代码被忽略了。

大部分人的做法类似于select system.table where tabblename='***',反正我曾经用类似的句子查询过DB2,是成功的。

但是,我现在面对的不是DB2,而是7个不同的数据库,基本上常用的都包括了。是不是每类数据库都有上面的查询语句呢?是否查询语句相似呢?于是我挑了hsqldb,也是当前的默认数据库,来寻找解决办法。

很遗憾,我没有找到类似前面的句子。正当我打算放弃的时候发现了下面的代码,这段代码是我从一个国外的论坛中找到的,尽管我不知道它是不是万能钥匙,但是他这次对我而言确成了万能的:

java.sql.Connection con = getYourConnection();
   
ResultSet rs 
= con.getMetaData().getTables(nullnull"yourTable"null);
if (rs.next()) {
//yourTable exist
}
else {
//yourTable not exist
}

 

posted @ 2006-06-28 17:12 hopeshared 阅读(3021) | 评论 (3)编辑 收藏

原文地址:http://javaresearch.org/article/showarticle.jsp?column=5&thread=50134


在进行所有的开发之前,自然是到http://xfire.codehaus.org下载最新的xfire的发布版本,在写这篇文档的时候,xfire最新的版本是xfire-distribution-1.1-beta-1版,从官方网站下载到本地机器后,解压,目录结构如下:

xfire-distribution-1.1-beta-1

|____api (javadoc文档)

|____sample (几个xfire例子)

|____lib (xfire所需的jars)

|____modules (xfire 模块)

|____xfire-all-1.1-beta-1.jar

|____几个授权和说明TXT文档

 

它所提供的例子需要Maven2编译执行,如果你还没有安装Maven,可以到apache网站下载安装。在你阅读的这篇教程的例子中,我将采用ant进行编译,毕竟Ant才是大家所常用的项目管理编译工具。

 

在你的tomcat的webapps建立一个web应用程序,例如xfire,目录结构如下:

       xfire

         |____WEB_INF

                |____web.xml

             |____classes

             |____lib

 

将下载解压的xfire-distribution-1.1-beta-1\lib文件夹下的所有的jar放入的这个lib文件夹下(tomcat/webapps/xfire/WEB-INF/lib)、将xfire-distribution-1.1-beta-1\xfire-all-1.1-beta-1.jar放入到tomcat/webapps/xfire/WEB-INF/lib文件夹下。

将xfire-distribution-1.1-beta-1\examples\book\src\webapp\WEB-INF下的web.xml文件复制到tomcat/webapps/xfire/WEB-INF文件夹下。

Web.xml的内容如下:
  1. <?xml version="1.0" encoding="ISO-8859-1"?>
  2. <!-- START SNIPPET: webxml -->
  3. <!DOCTYPE web-app
  4.     PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
  5.     "http://java.sun.com/dtd/web-app_2_3.dtd">
  6.    
  7. <web-app>
  8.   <servlet>
  9.     <servlet-name>XFireServlet</servlet-name>
  10.     <display-name>XFire Servlet</display-name>
  11.     <servlet-class>
  12.         org.codehaus.xfire.transport.http.XFireConfigurableServlet
  13.     </servlet-class>
  14.   </servlet>
  15.   <servlet-mapping>
  16.     <servlet-name>XFireServlet</servlet-name>
  17.     <url-pattern>/servlet/XFireServlet/*</url-pattern>
  18.   </servlet-mapping>
  19.   <servlet-mapping>
  20.     <servlet-name>XFireServlet</servlet-name>
  21.     <url-pattern>/services/*</url-pattern>
  22.   </servlet-mapping>
  23. </web-app>

启动tomcat,然后打开浏览器,在浏览器地址栏中输入http://localhost:8080/xfire/services/,如何能够正常显示页面,说明xfire就配置成功了。

 
这样,我们的XFire就配置完成了。
posted @ 2006-06-04 19:53 hopeshared 阅读(958) | 评论 (0)编辑 收藏

     摘要: Applying the Web services invocation framework Calling services independent of protocols 独立于协议的服务调用 ...  阅读全文
posted @ 2006-05-15 17:31 hopeshared 阅读(926) | 评论 (0)编辑 收藏

即使 SOAP 只是众多访问 Web 服务的可能的绑定之一,它已几乎成为 Web 服务的同义词。这意味着使用 Web 服务的应用程序通常通过绑到 SOAP 的特定实现的 API 来完成工作。本系列文章将描述一个更通用的、独立于 SOAP 的调用 Web 服务的方法,称之为“Web 服务调用框架”(Web Service Invocation Framework(WSIF))。它专门设计来直接调用用“Web 服务描述语言”(Web Services Description Language(WSDL))描述的 Web 服务,隐藏了底层访问协议(比如 SOAP)的复杂性。

Web 服务承诺为因特网分布式计算提供基于标准的平台,集中在简易性和灵活性。一种关键的 Web 服务技术是 WSDL,即“Web 服务描述语言”。使用 WSDL,开发者可以以抽象的形式描述 Web 服务,与用在其它分布式计算框架(比如 CORBA)的现有“接口描述语言”(Interface Description Language(IDL))类似。WSDL 也使 Web 服务开发者能够给服务指定具体的绑定。并且这些绑定描述怎样将抽象服务描述映射到特定访问协议。WSDL 的这部分是可扩展的,这就是说任何人都可以提出他们自己的绑定,使之可能通过某个定制的协议访问服务。

因此,从某种程度来说,使用 Web 服务是一种挑战。对于同样的服务可以有多个绑定。这些绑定中的一些可能适合于一些情形,其它可能适合于另外的情形。绑定本身可以代表抽象服务描述到用来访问服务的任意一种协议的映射。虽然有多种选择使用服务和允许绑定扩展是很有用的,但这给客户机以统一方式查看服务造成了困难。

我以当前客户机端 API 及其性能的讨论开始这篇文章。我将通过讨论来激发人们对 WSIF,即“Web 服务调用框架”的需要,然后继续进行 WSIF 的概述。

当前的调用风格及其缺点
用于 Web 服务的 SOAP 绑定是 WSDL 规范的一部分。在大多数编程语言中,该协议有可用的实现和工具,在许多情况下是免费的。这样,它使得开发者能以微乎其微的成本进行用于 Web 服务的独立于平台的开发。

因此,下述情况是不足为奇的:大多数开发者当想到使用 Web 服务时,在他们头脑中出现的是使用某个 SOAP 客户机 API 来装配一个 SOAP 消息并将它经由网络发送到服务端点。例如,使用 Apache SOAP,客户机将创建和植入一个 Call 对象。它封装了服务端点、要调用的 SOAP 操作的标识、必须发送的参数等等。而这是对 SOAP 而言,它仅限于将其用作调用 Web 服务的一般模型,这是因为下面的原因:

  • Web 服务不仅仅是 SOAP 服务
    将 Web 服务视为 SOAP 上提供的服务的同义词。这是对 Web 服务狭隘的见解。带有功能方面和访问协议 WSDL 描述的任何一段代码均可以被认为是 Web 服务。WSDL 规范为 Web 服务定义了 SOAP 绑定,但是原则上可能要添加绑定扩展,这样,例如,使用 RMI/IIOP 作为访问协议,EJB 就可以作为 Web 服务来提供。或者您甚至可以想象任意一个 Java 类可以被当作 Web 服务,以本机 Java 调用作为访问协议。就这个更广阔的 Web 服务定义来说,您需要用于服务调用的独立于绑定的机制。
  • 将客户机代码绑到一个特殊的协议实现要受到限制
    将客户机代码紧密地绑定到特殊的协议实现的客户机库造成了难以维护的代码。让我们假设您在客户机端有一个用 Apache SOAP v2.1 调用 Web 服务的应用程序。如果您想利用 v2.2 中出现的新的功能和错误修正,您将不得不更新所有的客户机代码,这是一项耗时的任务,将涉及到常见的令人头痛的迁移问题。类似的,如果您想从 Apache SOAP 移到一个不同的 SOAP 实现,这个过程并非无关紧要。所需要的是用于服务调用的独立于协议实现的机制。
  • 将新的绑定融入到客户机代码是很困难的。
    WSDL 允许有定义新的绑定的可扩展性元素。这使开发者能够定义绑定,这种绑定允许使用某种定制协议的代码作为 Web 服务。但是,事实上实现起来是很困难的。将不得不设计使用该协议的客户机 API 。应用程序本身可能正是使用 Web 服务的抽象接口,因此将必须编写一些工具来生成启用抽象层的存根。这些又一次是并非无关紧要的而且耗时的任务。所需要的是使绑定能够被更新或新的绑定能够容易地插入的服务调用机制。
  • 可以以灵活的方式使用多绑定
    例如,设想您已经成功地部署了一个应用程序,该应用程序使用提供多绑定的 Web 服务。为了使这个示例更具体,假设您有用于服务的 SOAP 绑定和允许您将本地服务实现(一个 Java 类)作为 Web 服务的本地 Java 绑定。显而易见,如果客户机部署在与服务本身相同的环境中,只能使用面向服务的本地 Java 绑定,并且如果情况确实如此,通过直接进行 Java 调用而不是使用 SOAP 绑定与服务进行通信将高效得多。Java 绑定作为一种快捷访问机制。接下来,想要利用多绑定可用性的客户机将必须具有一种能力 — 根据运行时信息对要用的实际绑定进行切换的能力。因此为了利用提供多绑定的 Web 服务,您需要一种服务调用机制,允许您在运行时在可用的服务绑定之间进行切换,而不需要生成或重编译存根。

介绍 WSIF
“Web 服务调用框架”(WSIF)是为调用 Web 服务提供简单 API 的工具箱,而不管服务怎样提供或由哪里提供。它具有上面讨论中我确定的所有功能:

  • 有给任何 Web 服务提供独立于绑定访问的 API。
  • 提供端口类型编译器来生成允许使用抽象服务接口的调用的存根。
  • 允许无存根(完全动态)的 Web 服务调用。
  • 可以在运行时将更新的绑定实现插入到 WSIF。
  • 可以在运行时插入的新的绑定。
  • 允许将绑定选择延后到运行时。

分析 WSIF 的客户机 API
为了进行讨论,我将使用很常见的股票报价程序 — Web 服务的“Hello World”示例。请考虑下面清单 1 所示的使用 WSDL 的抽象服务描述。

清单 1:股票报价 Web 服务的 WSDL 描述

<?xml version="1.0" ?> 
<definitions targetNamespace="http://www.ibm.com/namespace/wsif/samples/stockquote-interface" 
             xmlns:tns
="http://www.ibm.com/namespace/wsif/samples/stockquote-interface" 
             xmlns:xsd
="http://www.w3.org/1999/XMLSchema" 
             xmlns
="http://schemas.xmlsoap.org/wsdl/"> 
  
<message name="GetQuoteInput"> 
    
<part name="symbol" type="xsd:string"/> 
  
</message> 
  
<message name="GetQuoteOutput"> 
    
<part name="quote" type="xsd:float"/> 
  
</message> 
  
<portType name="StockquotePT"> 
    
<operation name="getQuote"> 
      
<input message="tns:GetQuoteInput"/> 
      
<output message="tns:GetQuoteOutput"/> 
    
</operation> 
  
</portType> 
</definitions>


足够简单:它描述了只带有由一个端口类型提供一个操作的服务。该操作等待一个字符串,被解释为股票的报价机符号,然后返回当前该股票报价,一个浮点值。为了实际使用该服务,您需要某个定义访问机制的绑定以及该绑定的服务端点。清单 2 展示了该服务的 SOAP 绑定:

清单 2:股票报价 Web 服务的 SOAP 绑定

<?xml version="1.0" ?> 
<definitions targetNamespace="http://www.ibm.com/namespace/wsif/samples/stockquote" 
             xmlns:tns
="http://www.ibm.com/namespace/wsif/samples/stockquote" 
             xmlns:tns-int
="http://www.ibm.com/namespace/wsif/samples/stockquote-interface" 
             xmlns:xsd
="http://www.w3.org/1999/XMLSchema" 
             xmlns:soap
="http://schemas.xmlsoap.org/wsdl/soap/" 
             xmlns:java
="http://schemas.xmlsoap.org/wsdl/java/" 
             xmlns
="http://schemas.xmlsoap.org/wsdl/"> 
  
<import namespace="http://www.ibm.com/namespace/wsif/samples/stockquote-interface" 
          location
="stockquote-interface.wsdl"/> 
  
<binding name="SOAPBinding" type="tns-int:StockquotePT"> 
    
<soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/> 
    
<operation name="getQuote"> 
      
<soap:operation soapAction="http://example.com/GetTradePrice"/> 
      
<input> 
        
<soap:body use="encoded" 
                   namespace
="urn:xmltoday-delayed-quotes" 
                   encodingStyle
="http://schemas.xmlsoap.org/soap/encoding/"/> 
      
</input> 
      
<output> 
        
<soap:body use="encoded" 
                   namespace
="urn:xmltoday-delayed-quotes" 
                   encodingStyle
="http://schemas.xmlsoap.org/soap/encoding/"/> 
      
</output> 
    
</operation> 
  
</binding> 
  
<service name="StockquoteService"> 
    
<documentation>Stock quote service</documentation> 
    
<port name="SOAPPort" binding="tns:SOAPBinding"> 
      
<soap:address location="http://localhost:8080/soap/servlet/rpcrouter"/> 
    
</port> 
  
</service> 
</definitions>

这是一个标准的 SOAP 绑定,使用 RPC 风格和作为传输协议的 HTTP 与服务进行通信。在文档的 port 部分中,我定义了使用 SOAP 绑定可以访问服务的 URI。在清单 3 中,我将就通过 Apache SOAP 2.2 客户机端 API 和 WSIF 的客户机 API 使用该服务进行对比。

清单 3:用于服务访问的 Apache SOAP API 与 WSIF API 对比

Apache SOAP API

// Step 1: identify the service 
 Call call = new Call(); 
 call.setTargetObjectURI(
"urn:xmltoday-delayed-quotes"); 
 
// Step 2: identify the operation 
 call.setMethodName("getQuote"); 
 call.setEncodingStyleURI(encodingStyleURI); 
 
// Step 3: identify the parameters 
 Vector params = new Vector(); 
 params.addElement(
new Parameter("symbol"
                          String.
class, symbol, null));
 call.setParams(params); 
 
// Step 4: execute the operation 
 Response resp = call.invoke(url, 
                   
"http://example.com/GetTradePrice");
 
// Step 5: extract the result or fault 
 if(resp.generatedFault()) 
 

   Fault fault 
= resp.getFault(); 
   System.out.println(
"Ouch, the call failed: "); 
   System.out.println(
"  Fault Code   = " +  
                      fault.getFaultCode()); 
   System.out.println(
"  Fault String = " +  
                      fault.getFaultString()); 
   
throw new SOAPException("Execution failed " + fault); 
 }
 
 
else 
 

   Parameter result 
= resp.getReturnValue(); 
   
return ((Float) result.getValue()).floatValue(); 
 }


WSIF API

// Step 1: identify the service 
 Definition def = WSIFUtils.readWSDL(null,wsdlLocation);
 
// Step 2: identify the operation (choose an 
 
// appropriate service port) 
 WSIFDynamicPortFactory portFactory = 
        
new  WSIFDynamicPortFactory(def, nullnull); 
 
// Get default port 
 WSIFPort port = portFactory.getPort(); 
 
// The user can also explicitly select a port  
 
// to use. 
 
// WSIFPort port =  
 
//             portFactory.getPort("SOAPPort"); 
 
// Step 3: identify the parameters 
 
// Prepare the input message 
 WSIFMessage input = port.createInputMessage(); 
 input.setPart(
"symbol",  
               
new WSIFJavaPart(String.class, symbol));
 
// Prepare a placeholder for the output value 
 WSIFMessage output = port.createOutputMessage(); 
 
// Step 4: execute the operation 
 port.executeRequestResponseOperation("getQuote"
                             input, output, 
null);
 
// Step 5: extract the result or fault 
 WSIFPart part = output.getPart("quote"); 
 
return ((Float)part.getJavaValue()).floatValue();


正如您可以看到的,WSIF 的 API 由以 WSDL 编写的抽象服务描述驱动;它完全从实际使用的绑定中分离出来。该调用 API 是面向 WSDL 的,并且使用它更自然,因为它使用 WSDL 术语引用消息部件(message part)、操作等等。当您阅读一个 WSDL 描述,出于直觉会想到选用支持所需端口类型的端口,然后通过提供必需抽象输入消息(由必要部件组成)调用操作(不用担心怎样将消息映射到特定的绑定协议);WSIF API 就是这样设计的。

常用的 API,比如上面所示的 Apache SOAP API 采用了以特殊协议为中心的概念,比如在使用 SOAP 的情况下,目标 URI 和编码风格。这是不可避免的,因为 API 不是普遍适用于 WSDL,而是为特殊的协议设计。

两种使用 WSIF 的调用模型
WSIF 允许 Web 服务以两种方式调用。一种是无存根的动态调用,它要求直接使用 WSIF API;另一种是通过生成允许应用程序使用 Java 接口(直接对应于 WSDL 端口类型)和隐藏了 WSIF API 的存根的调用。

无存根(动态的)调用
访问 Web 服务所需的所有信息 — 抽象接口、绑定和服务端点可以通过 WSDL 得到。如果您仔细查看上面的客户机 API 示例,您将会注意到唯一由用户提供的信息是用于服务的 WSDL 文件位置和所需要的股票报价符号。很明显接下来是用 WSFL API 在运行时装入这个服务和进行该调用。

WSIF 分发包包含了演示怎样执行 WSDL 的任意一个操作的 DynamicInvoker。它以 WSDL 文件的 URI 和所需的操作参数作为命令行参数(在最初的实现中只接受简单的类型,如字符串),并且直接使用 WSIF API 完成工作。其用来调用股票报价服务的示例如清单 4 所示;您可以在包含 WSIF 分发包的文档中查找详细信息。

因为这类调用不会导致生成存根类,并且不需要单独的编译周期,所以它很方便。

清单 4:使用 DynamicInvoker 访问股票报价服务

java clients.DynamicInvoker http://services.xmethods.net/soap/urn:
    xmethods-delayed-quotes.wsdl getQuote IBM
Reading WSDL document from 'http://services.xmethods.net/soap/urn:
    xmethods-delayed-quotes.wsdl'
Preparing WSIF dynamic invocation
Executing operation getQuote
Result:
Result=108.8
Done!

使用存根的调用
在应用程序需要更抽象的服务视图和不希望处理 WSIF 客户机 API 的情况下,无存根调用不适用。WSIF 提供一个端口编译器,如果给定一个 WSDL,与所用到的复杂类型的 Java 等价物(以 JavaBean 的形式)一起,端口编译器将为每一个端口类型生成一个客户机存根。使用生成存根的应用程序可以利用抽象服务接口,并且这样与协议和 WSIF 客户机 API 分离。对于实际服务调用,这些存根使用缺省端口。通过由 WSIF 的端口类型编译器生成的存根使用股票报价服务的示例如清单 5 所示。

清单 5:使用生成存根的 WSIF 访问股票报价服务。

StockquotePT service = new StockquotePTStub(WSIFUtils.readWSDL(null, wsdlLocation), 
    
nullnull);
float quote = service.getQuote("IBM");
System.err.println (
">> Received quote: "+quote);

值得注意的是存根驻留在某些受管环境(如应用程序服务器)的情况,它们能够被定制;例如,可以改变负责返回恰当端口的工厂来定制端口选择算法,或者可以改变用于调用自身的实际端口。

在这篇文章中,我略述了对 WSIF 的需要,分析其通过高级 API 进行独立于绑定的服务访问的主要特征以及生成可定制的存根(通过其抽象接口使用服务)的端口类型编译器的可用性。直接通过 SOAP 客户机 API 和使用 WSIF API 进行服务访问之间的对比展示了 WSIF(由服务的 WSDL 描述驱动)怎样将 Web 服务调用的全部问题从以绑定为中心的观点转移到更抽象的级别。这是 WSIF 的设计原则之一,使服务绑定能被看作是根据特殊协议进行调用所需的代码片断,可以在任何时候插入它们。

在接下来的文章,我将分析 WSIF 的体系结构,看一看它怎样允许新的或更新的绑定实现被插入,它怎样使定制的类型系统用于 Web 服务以及怎样使运行时环境使用定制的探索性方法在多端口之间进行选择。

参考资料

  • 请参加关于这篇文章的讨论论坛
  • 请下载 alphaworks 上的 WSIF 分发包,并且试验比较简单的样本。它将给您一个由 WSIF 支持的不同调用风格的第一手示例以及它优于特定协议客户机 API 的优势。
  • 重温 WSDL 规范,看一看允许哪一种扩展;如果用来为访问 Web 服务定义 SOAP 绑定,您也可以学习 WSDL 的扩展机制。
  • 重温 SOAP 规范本身。
  • 如果以前您没有对 Web 服务编过程,Web Services Toolkit 是很好的起点。
  • 请查看 WSDL4J,一个可扩展的 WSDL 分析框架,WSIF 在此基础上进行构建。

关于作者
Nirmal K. Mukhi 是 IBM 的 T J Watson Research Lab 的副研究员,自 2000 年 11 月,他一直在从事各种 Web 服务技术的研究。他还对 AI、创造性写作以及过时的计算机游戏感兴趣。您可以通过
nmukhi@us.ibm.com 与 Nirmal 联系。




转载自:http://www.itepub.net/page/article/htmldata/2004_10/17/76/article_3575_1.html
posted @ 2006-05-15 17:20 hopeshared 阅读(551) | 评论 (0)编辑 收藏

我们都知道,RDF只表示二元关系,但在实际应用中,多元关系非常常见,如:小红借给小明语文书,是个三元关系: borrow(小红,语文书,小明); 再如,小明的身高是170cm.也是个三元关系 length(小明,170, cm). 推广来说,n元关系如何在RDF和OWL中表示呢?

我们假设三元组为(a,b,c). a,b.c 都是资源或Literal

1. 方法一
如果三元组中a是老大,即有个资源的地位是支配性的,如:小明的身高是170cm.
表示方法为 把老大提出来,再把三元关系分解为3个二元关系:
  R1(a, a’) , R2(a’,b),  R3(a’,c)  // R1(a, a’) 用RDF三元组表示为 (a , R1 , a’)
例如:小明的例子可以表示为
length(小明,length_obj_1);  //小明是老大, length_obj_1 是一个身高对象 
value(length_obj_1,170);     //值
unit(length_obj_1,cm);      //单位
2. 方法二
如果三元组中没有明显的老大,如: 小红借给小明语文书.
表示方法为提出一个对象,每个元素都和这个对象有关系:
R1(g, a) , R2(g,b),  R3(g, c)
例如:小红借书的例子可以表示为
rdf:type (borrow_obj_1,  BorrowRelation); // BorrowRelation 是一个表示借书关系的类
borrow_owner((borrow_obj_1,小红); 
borrow_agent((borrow_obj_1,小明); //借书的人
borrow_book((borrow_obj_1, 语文书);


3. 结论
1) n-元关系有2exp(n-2) 种表示方法: 二元关系一种表示法,三元关系有如上二种表示法,由数学归纳法得证。
2) 如果用RDF对复杂系统建模,有必要引入一个中间的抽象层,用以表示N元关系,还有named graph, context 等。如引入rdfe:relation(a,b,c,d,….)表示n元关系 
3) n-关系的表示对RDF数据的查询和存储优化很有价值,因为n-关系往往对应了数据库中的表。


注:大部分摘译自: 
http://www.w3.org/2001/sw/BestPractices/OEP/n-aryRelations-20040623/

更为详细的信息也参考它。

原文地址:http://bbs.w3china.org/dispbbs.asp?boardID=2&ID=8416

posted @ 2006-05-10 10:31 hopeshared 阅读(813) | 评论 (0)编辑 收藏

 

在网络中为了保证数据的实时性,需要对数据进行异步操作。Java Web Service和J2EE中的异步操作通过java消息机制来完成,消息机制是非常完善的技术了。而Microsoft的Web Service的异步功能是怎样完成的呢?怎样才能达到java的境地呢?当然,Microsoft有自己的一套。

众所周知,Web Service是靠SOAP协议进行数据传输的。而SOAP是基于XML技术之上的。SOAP协议是连接客户和服务器的桥梁。而SOAP协议本身没有异步功能,需要在客户端实现异步调用。我们以一个简单的Web Service的例子来说明这一点。

一、MathService.asmx

<%@ WebService Language="C#" Class="MathService" %>

using System;

using System.Web.Services;

[WebService]

public class MathService : WebService {

[WebMethod]

public float Add(float a, float b)

{

return a + b;

}

[WebMethod]

public double Subtract(double a, double b)

{

return a - b;

}

[WebMethod]

public float Multiply(float a, float b)

{

return a * b;

}

[WebMethod]

public float Divide(float a, float b)

{

if (b==0) return -1;

return a / b;

}

}

这是个实现了加,减,乘,除的Web Service,任何客户端程序都可以调用它。下面我们用wsdl(微软公司提供)工具产生一个MathService.asmx 的客户代理程序:wsdl /n:MyMath http://localhost/mathservice.asmx (假设MathService.asmx放在IIS服务器的根目录) ,产生一个MathService.cs代理程序,默认是SOAP协议。

二、MathService.cs:

namespace MyMath{

using System.Diagnostics;

using System.Xml.Serialization;

using System;

using System.Web.Services.Protocols;

using System.ComponentModel;

using System.Web.Services;

[System.Diagnostics.DebuggerStepThroughAttribute()]

[System.ComponentModel.DesignerCategoryAttribute("code")]

[System.Web.Services.WebServiceBindingAttribute(Name="MathServiceSoap", Namespace="http://tempuri.org/")]

public class MathService : System.Web.Services.Protocols.SoapHttpClientProtocol {

public MathService() {

this.Url = "http://localhost/mathservice.asmx";

}

[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://tempuri.org/Add", RequestNamespace="http://tempuri.org/", ResponseNamespace="http://tempuri.org/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]

public System.Single Add(System.Single a, System.Single b) {

object[] results = this.Invoke("Add", new object[] {

a,

b});

return ((System.Single)(results[0]));

}

public System.IAsyncResult BeginAdd(System.Single a, System.Single b, System.AsyncCallback callback, object asyncState) {

return this.BeginInvoke("Add", new object[] {

a,

b}, callback, asyncState);

}

/// <remarks/>

public System.Single EndAdd(System.IAsyncResult asyncResult) {

object[] results = this.EndInvoke(asyncResult);

return ((System.Single)(results[0]));

}

/// <remarks/>

[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://tempuri.org/Subtract", RequestNamespace="http://tempuri.org/", ResponseNamespace="http://tempuri.org/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]

public System.Double Subtract(System.Double a, System.Double b) {

object[] results = this.Invoke("Subtract", new object[] {

a,

b});

return ((System.Double)(results[0]));

}

/// <remarks/>

public System.IAsyncResult BeginSubtract(System.Double a, System.Double b, System.AsyncCallback callback, object asyncState) {

return this.BeginInvoke("Subtract", new object[] {

a,

b}, callback, asyncState);

}

/// <remarks/>

public System.Double EndSubtract(System.IAsyncResult asyncResult) {

object[] results = this.EndInvoke(asyncResult);

return ((System.Double)(results[0]));

}

/// <remarks/>

[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://tempuri.org/Multiply", RequestNamespace="http://tempuri.org/", ResponseNamespace="http://tempuri.org/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]

public System.Single Multiply(System.Single a, System.Single b) {

object[] results = this.Invoke("Multiply", new object[] {

a,

b});

return ((System.Single)(results[0]));

}

/// <remarks/>

public System.IAsyncResult BeginMultiply(System.Single a, System.Single b, System.AsyncCallback callback, object asyncState) {

return this.BeginInvoke("Multiply", new object[] {

a,

b}, callback, asyncState);

}

/// <remarks/>

public System.Single EndMultiply(System.IAsyncResult asyncResult) {

object[] results = this.EndInvoke(asyncResult);

return ((System.Single)(results[0]));

}

/// <remarks/>

[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://tempuri.org/Divide", RequestNamespace="http://tempuri.org/", ResponseNamespace="http://tempuri.org/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]

public System.Single Divide(System.Single a, System.Single b) {

object[] results = this.Invoke("Divide", new object[] {

a,

b});

return ((System.Single)(results[0]));

}

/// <remarks/>

public System.IAsyncResult BeginDivide(System.Single a, System.Single b, System.AsyncCallback callback, object asyncState) {

return this.BeginInvoke("Divide", new object[] {

a,

b}, callback, asyncState);

}

/// <remarks/>

public System.Single EndDivide(System.IAsyncResult asyncResult) {

object[] results = this.EndInvoke(asyncResult);

return ((System.Single)(results[0]));

}

}

}

之后我们用csc /t:library MathService.cs编译并产生一个MathService.dll.

现在我们可以写任何的客户程序去调用服务器上的MathService.asmx。

如:WinForm, C#,ASPX等。

下面我们写一个test.cs去测试异步调用:

三、test.cs:

using System;

public class test{

public static void Main(){

MyMath.MathService math = new MyMath.MathService();

IAsyncResult result1 = math.BeginAdd(10,20,null,null);

Object result=math.EndAdd(result1);

Console.WriteLine("result =========="+result);

}

}

我们看到它是先调用代理MathService.cs中的BeginAdd方法,然后状态信息保存在IasyncResult中,直到调用了EndAdd方法才返回调用的确切值。本例是远端调用MathService.asmx中的Add方法。

那Microsoft到底怎样实现客户端的异步呢?设计模式又是怎样的呢?

异步模式所提供的革新之一就是调用方确定特定调用是否应是异步的。  对于被调用的对象,没有必要执行附加的编程来用于支持其客户端的异步行为;在该模式中异步委托提供此功能。公共语言运行库处理调用方和被调用的对象视图之间的差异。被调用的对象可以选择显式支持异步行为,这或者是因为它可以比一般结构更为有效地实现异步行为,或者是因为它想只支持其调用方的异步行为。但是,建议这种被调用的对象遵循公开异步操作的异步设计模式。

类型安全是异步模式的另一项革新。尤其对于异步委托,针对 .NET 框架和公共语言运行库的语言编译器可令映射到规则 Invoke 方法的开始和结束操作(例如,BeginInvoke 和 EndInvoke)的方法签名是类型安全的。这是十分重要的,因为编译器为异步委托将同步调用拆分成开始和结束操作,使其能够只传递有效参数。

在此模式中所蕴含的基本想法如下所示:

1.调用方确定特定调用是否应是异步的。

2. 对于被调用的对象,没有必要由其客户端执行附加的编程来用于支持异步行为。公共语言运行库结构应该能够处理调用方和被调用的对象视图之间的差异。

3. 被调用的对象可以选择显式支持异步行为,这或者是因为它可以比一般结构更为有效地实现异步行为,或者是因为它想只支持其调用方的异步行为。但是,建议这种被调用的对象遵循公开异步操作的异步设计模式。

4. 编译器为 BeginInvoke 和 EndInvoke 以及异步委托生成类型安全方法签名。

5. .NET 框架提供支持异步编程模型所需的服务。此类服务的部分列表示例是:

(1)同步基元,例如监视器和阅读器编写器锁定。

(2)线程和线程池。

(3)同步构造,例如支持等候对象的容器。

(4)向基础结构片(例如 IMessage 对象和线程池)公开。

该模式将一个同步调用拆分成各构成部分:开始操作、结束操作和结果对象。考虑以下示例,在其中可能要用大量时间来完成 Factorize 方法。

public class PrimeFactorizer

{

public bool Factorize(int factorizableNum, ref int primefactor1, ref int primefactor2)

{

// Determine whether factorizableNum is prime.

// If is prime, return true. Otherwise, return false.

// If is prime, place factors in primefactor1 and primefactor2.

}

}

如果遵循异步模式,则类库编写器添加 BeginFactorize 和 EndFactorize方法,这两个方法将同步操作拆分成两个异步操作:

public class PrimeFactorizer

{

public bool Factorize(

    int factorizableNum,

    ref int primefactor1,

    ref int primefactor2)

{

// Determine whether factorizableNum is prime.

// if is prime, return true; otherwise return false.

// if is prime palce factors in primefactor1 and primefactor2

}

public IAsyncResult BeginFactorize(

   int factorizableNum,

   ref int primefactor1,

   ref int primefactor2,

   AsyncCallback callback,

   Object state)

{

 // Begin the factorizing asynchronously, and return a result object,

}

public bool EndFactorize(

   ref int primefactor1,

   ref int primefactor2,

   IAsyncResult asyncResult

 )

{

// End (or complete) the factorizing, and

// return the results,

// and obtain the prime factors.

}

}

服务器将异步操作拆分成两个逻辑部分:采用来自客户端的输入并调用异步操作的部分,向客户端提供异步操作结果的部分。

除了异步操作所需的输入外,第一部分还采用在完成异步操作时后要被调用的 AsyncCallback 委托。第一部分返回一个可等待的对象,该对象实现客户端使用的 IAsyncResult 接口来确定异步操作的状态。

服务器还利用它返回到客户端的可等待的对象来维护与异步操作关联的任何状态。通过提供可等待的对象,客户端使用第二部分获取异步操作的结果。

可用于客户端来启动异步操作的选项有:

在开始异步调用时提供回调委托。

 public class Driver1

   {

     public PrimeFactorizer primeFactorizer;

     public void Results(IAsyncResult asyncResult)

    {

     int primefactor1=0;

      int primefactor2=0;

      bool prime = primeFactorizer.EndFactorize(

         ref primefactor1,

         ref primefactor2,

         asyncResult);

    }

     public void Work()

     {

      int factorizableNum=1000589023,

      int primefactor1=0;

      int primefactor2=0;

      Object state = new Object();

      primeFactorizer = new PrimeFactorizer();

      AsyncCallback callback = new Callback(this.Results);

      IAsyncResult asyncResult =                      primeFactorizer.BeginFactorize(

       factorizableNum,

       ref primefactor1,

       ref primefactor2,

       callback,

       state);

    } 

在开始异步调用时不提供回调委托。

public class Driver2

{

public static void Work()

{

int factorizableNum=1000589023,

int primefactor1=0;

int primefactor2=0;

Object state = new Object();

PrimeFactorizer primeFactorizer = new PrimeFactorizer();

AsyncCallback callback = new Callback(this.Results);

IAsyncResult asyncResult = primeFactorizer.BeginFactorize(

factorizableNum,

ref primefactor1,

ref primefactor2,

callback,

state);

bool prime = primeFactorizer.EndFactorize(

ref primefactor1,

ref primefactor2,

asyncResult);

}

}

我们以.Net的一个例子来说明这一点:

AsyncDelegate2.cs

using System;

using System.Threading;

using System.Runtime.Remoting;

using System.Runtime.Remoting.Messaging;

public class Wak

{

public int Pat(int i)

{

Console.WriteLine("Hash: {0} Wak Pat", Thread.CurrentThread.GetHashCode());

return i*2;

}

};

public delegate int WakPatDelegate(int i);// 异步调用的委派.

public class Simple

{

public static void SomeMethod(IAsyncResult ar)

{

// Obtain value from AsyncState object

int value = Convert.ToInt32(ar.AsyncState);

// Obtain results via EndInvoke

int result = ((WakPatDelegate)((AsyncResult)ar).AsyncDelegate ).EndInvoke(ar);

Console.WriteLine("Simple.SomeMethod (AsyncCallback): Result of {0} in Wak.Pak is {1} ",value, result);

}

public static void Main(String[] args)

{

Console.WriteLine("Thread Simple Context Sample");

Console.WriteLine("");

Console.WriteLine("Make an instance of a context-bound type Wak");

Wak oWak = new Wak();

int value=0;

int result=0;

Console.WriteLine("Make a sync call on the object");

value = 10;

result = oWak.Pat(value);

Console.WriteLine("Result of {0} in Wak.Pak is {1} ",value, result);

Console.WriteLine("Make single Async call on Context-bound object");

WakPatDelegate wpD1 = new WakPatDelegate(oWak.Pat);

value = 20;

IAsyncResult ar1 = wpD1.BeginInvoke(value,null,null);

ar1.AsyncWaitHandle.WaitOne();

result = wpD1.EndInvoke(ar1);

Console.WriteLine("Result of {0} in Wak.Pak is {1} ",value, result);

Console.WriteLine("Make single Async call on Context-bound object - use AsyncCallback and StateObject");

WakPatDelegate wpD2 = new WakPatDelegate(oWak.Pat);

value = 30;

IAsyncResult ar2 = wpD2.BeginInvoke(

value,

new AsyncCallback(Simple.SomeMethod),

value

);

Console.WriteLine("Make multiple Async calls on Context-bound object");

int asyncCalls = 5;

IAsyncResult[] ars = new IAsyncResult[asyncCalls];

WaitHandle[] whs = new WaitHandle[asyncCalls];

int[] values = new int[asyncCalls];

WakPatDelegate wpD3 = new WakPatDelegate(oWak.Pat);

for (int i=0; i < asyncCalls; i++)

{

values[i] = i;

ars[i] = wpD3.BeginInvoke(values[i],null,null);

whs[i] = ars[i].AsyncWaitHandle;

}

WaitHandle.WaitAll(whs,1000, false);

for (int i=0; i < asyncCalls; i++)

{

result = wpD3.EndInvoke(ars[i]);

Console.WriteLine("Result of {0} in Wak.Pak is {1} ",values[i], result);

}

Console.WriteLine("");

Console.WriteLine("Done");

}

}

    

如果异步调用成功,将显示:

Thread Simple Context Sample

Make an instance of a context-bound type Wak

Make a sync call on the object

Hash: 3 Wak Pat

Result of 10 in Wak.Pak is 20

Make single Async call on Context-bound object

Hash: 16 Wak Pat

Result of 20 in Wak.Pak is 40

Make single Async call on Context-bound object - use AsyncCallback and StateObje

ct

Hash: 16 Wak Pat

Make multiple Async calls on Context-bound object

Simple.SomeMethod (AsyncCallback): Result of 30 in Wak.Pak is 60

Hash: 16 Wak Pat

Hash: 16 Wak Pat

Hash: 16 Wak Pat

Hash: 16 Wak Pat

Hash: 16 Wak Pat

Result of 0 in Wak.Pak is 0 

Result of 1 in Wak.Pak is 2

Result of 2 in Wak.Pak is 4

Result of 3 in Wak.Pak is 6

Result of 4 in Wak.Pak is 8

Done

原文地址:http://www.ccw.com.cn/htm/center/prog/02_8_23_6.asp

posted @ 2006-05-07 16:28 hopeshared 阅读(2665) | 评论 (1)编辑 收藏

  Web services 是一种很有前途的技术,在面向服务的架构( Service Oriented Architectures , SOA )中起着重要的作用。这种正在兴起的技术的一个关键方面就是提供了异步服务的能力。尽管现在的 web service 标准规范中包括了提供异步服务的内容,但客户端应用程序前景的细节还有一些混乱和模糊。 Web services 回调是实现这些异步服务的一个重要因素。这篇文章为创建带有回调操作的 Web services 的客户应用程序提供了实践指导。这篇文章中所有的代码段都来自于您可以下载的例子。这些例子包含了这些代码段的完整实现和使用指导。

术语

  在开始讨论支持回调的客户端之前,阐明相关术语是很重要的。下图就显示了客户端使用带有回调操作的 Web service 时所涉及到的主要实体。

Figure 1
图 1. 调用 Web service 的客户端

  上图描述了客户端对 Web service 的调用。 Web service 能够在一段时间后通过对客户端的回调做出响应 。因此,包含回调操作的 Web service 客户端的特别之处在于,客户端本身必须提供一个端点。我们调用这一回调端点 ,并将这个端点定义为由 URI 确定的唯一地址, SOAP 请求消息将发送到这个 URI 。

  将 SOAP 消息发送到Web service 端点 之后,客户端本身开始与 Web service 进行交互。由客户端发送给 Web service 的相关请求消息及其相关响应消息构成了客户端初始化操作 。如前所述,客户能够处理 Web service 发送到回调端点的请求消息。相关的请求和响应消息一起被称为一个回调 操作。

  理解这些术语之后,让我们走近一步考察 Web service 回调的概念,以及它与会话式 Web services 和异步 Web service 调用之间的关系。

异步、回调和会话

  异步 Web service 调用的概念有时容易与 Web services 回调和会话式 Web services 相混淆。虽然这三个概念很相关,但却不同。

  异步 Web services 调用 是指在不阻塞接收服务器发来的相应响应消息的情况下,客户端能够发送 SOAP 消息请求 。 BEA WebLogic Platform 8.1 web services 上进行异步 Web service 调用的过程已经详细地 记录在软件文档中了 ,因此在本文中不作进一步的讨论。

  Web services 回调 是指 Web services 提供者向客户端发回 SOAP 消息的情况。 Web Services Description Language (WSDL) specifications 将这种操作定义为一种“请求 / 响应”。支持回调操作的 Web services 客户端本身必须有一个 Web services 端点,这样 Web service 就可以利用这个 Web services 端点在任意时间发送回调请求,也就是说,可以是异步的。注意,这跟我们上面讨论的异步调用 没有关联。

  如果一系列可在两个端点之间来回传送的消息可以被唯一会话 ID 追踪,而这个 ID 又由特定的操作来标识消息流的始末,这种 Web services 就是 会话式 的。提供回调的 Web services 也定义为会话式的。这是因为正常情况下如果 Web services 能够对客户端进行回调访问,它就必须有它自己的回调端点信息。这种情况只有客户端做出了初始调用以后才会发生。因此,至少由客户启动的初始化操作和由 Web services 做出的回调操作是相互关联的,并且由唯一的会话 ID 跟踪。如果不是这样,客户端就无法分辨与不同初始调用相关联的回调操作。

  我们现在将考虑这些问题并创建支持回调的客户端,就像我们刚才所看到的,这些客户端构成了请求 - 响应和会话式 Web services 的基础。

创建支持回调的客户端

  正如前面讨论的,支持回调的 Web services 客户端需要提供一个能够异步接收和处理回调操作消息的回调端点。为避免必须提供回调端点这类复杂的事情,一种叫做 polling (轮询)的技术可以作为替代技术。然而这种技术要求客户端周期性地调用服务端以校验回调事件。如果这种事件很少发生,那么调用的开销就太大了。如果客户端提供一个回调端点并直接处理回调操作,这种开销就可以避免。

  我们还应该注意到,尽管可以通过用 JMS 上的 Web services (如果提供了这种连接)创建支持回调的客户端,但这种方法有一些限制,其中一个重要的限制就是客户端要被迫采用与 Web services 提供者相同的 JMS 实现。因此我们将集中于经过 HTTP 传输来完成我们的任务。有两个领域需要创建这样的客户端:创建适当的客户端启动 SOAP 消息,以及处理回调操作。

  当客户端启动有回调的 Web service 操作时,它需要以某种方式包含回调端点的 URI ,使其在请求消息中监听。 Web Services Addressing SOAP Conversation Protocol 规范都定义了 SOAP 头部元素,允许您实现这一目标。从理论上说,用于规定回调端点的规范并不重要。但是大多数 Web services 容器(包括 BEA WebLogic Server 8.1 )都还没有包含 Web services Addressing 规范的实现形式。当前, BEA WebLogic Workshop 8.1 的 Web services 支持 SOAP Conversation Protocol 规范,我们将在例子客户端中使用。

  根据 SOAP Conversation Protocol , SOAP 头部在会话的不同阶段是不同的。对于会话中的第一个客户端启动(开始)操作,我们需要通过 callbackLocation 头部元素 提供有回调端点的 Web service 。所有操作(包括第一个操作)也将需要唯一的 ID ,这个 ID 在整个会话过程中都用在我们的 SOAP 消息里。这可通过 conversationID 元素 来完成。下面是一个启动支持回调会话的 SOAP 请求消息的例子:

<soapenv:Envelope soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" 
    xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
    xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:enc="http://schemas.xmlsoap.org/soap/encoding/" 
    xmlns:env="http://schemas.xmlsoap.org/soap/envelop/" 
    xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
  <soapenv:Header>
    <con:StartHeader soapenv:actor="http://schemas.xmlsoap.org/soap/actor/next" 
     soapenv:mustUnderstand="0" 
     xmlns:con="http://www.openuri.org/2002/04/soap/conversation/">
      <con:conversationID>[123456]:192.168.1.100:8181</con:conversationID>
         <con:callbackLocation>
          http://192.168.1.100:8181/StockNotificationCallback
         </con:callbackLocation>
      </con:StartHeader>
 </soapenv:Header>
  <soapenv:Body>
    <n1:registerForThresholdNotif xmlns:n1="http://www.openuri.org/">
      <n1:stockTicker>CCC</n1:stockTicker>
      <n1:threshold>10</n1:threshold>
    </n1:registerForThresholdNotif>
  </soapenv:Body>
</soapenv:Envelope>

  现有的大多数 Java Web service 工具包(例如 BEA WebLogic 的 clientgen 、 Apache Axis 和 JWSDP )都允许您创建一个代理库,客户端程序可以容易地用它来调用 Web services 操作。但是,这些框架中没有一种支持回调操作,主要问题是它们的代理不生成所需的头部。在能提供这种支持以前,通过扩展它们对回调操作的支持来利用这些框架(例如复杂类 XML 映射),这种益处还是很需要的。一种用来达到这种效果的技术就是应用 SOAP 消息处理程序

  上面提到的所有 Web services 工具包都能支持 SOAP 消息处理程序。消息处理程序是一种 Java 类,它实现了 javax.xml.rpc.handler.GenericHandler 界面,并且还包含一种称为先送出(或后接收) SOAP 消息的方法。这里介绍我们客户端中的消息处理程序,它能够找出一个特定会话的当前阶段,并据此扩展带有所需头部的请求消息。

  注意到这一点是很重要的,客户端 SOAP 消息处理程序必须能确定消息属于会话的哪个阶段,以便创建合适的头部元素。生成会话 ID 也是客户端处理程序要完成的一个任务。

  一旦 Web services 端点收到扩展的请求消息,它就会将请求消息发送到由开始消息的 callbackLocation 元素规定的回调端点。在大多数情况下,客户端过程自身就需要提供这个端点,并且恰当地处理回调消息。如果客户端在 Web services 的容器中运行,这项工作就可以通过把有回调操作的 Web services 部署在同一个容器内来完成。然而,如果客户端不是正在容器中运行,这项工作就要靠在一个嵌入在客户端过程本身的轻量级容器中执行回调端点来完成。这使我们能够调用客户端生成的操作,并且处理同一过程上下文中的传入回调操作。注意在回调端点背后(和在客户端中)的过程实体要求不仅能够分配对适当域的代码操作,而且还能处理 XML 映射。

  当然,客户端也可以选择简单地设置恰当的 callbackLocation 头部元素来规定一个在容器中的回调端点,而这个容器与访问过程相分离。

  正如我们已经看到的,为带回调操作的 Web services 创建客户端并不是毫无意义的,它包含了复杂元素,比如处理 SOAP 消息、管理会话(包括阶段和 ID )、参数映射以及操作分配。当 Web service 通过 BEA WebLogic Workshop Service Control 访问时,这些复杂性就都不存在了,它会自动为用户执行所有操作。

使用服务控件创建支持回调的客户端

  通过 WebLogic Workshop Service Control 访问 Web services 在 软件文档 中已经做了详细描述。如果客户端实体能够使用控件(也就是 Java Process Definition 业务流程或其他控件; Page Flows 不能使用控件回调),这个解决方案就是最简单的使用支持回调的 Web services 的方案了。采用这种方法,所有涉及访问支持回调的 Web service 的复杂性就都迎刃而解了。

股票通知服务例子

  本文的例子包括一个股票通知( Stock Notification )的会话式 Web service 和一个能阐明概念的示例客户端。 Web service 提供的操作允许客户端注册股票接收机并设置一个相关的阈值价格。然后服务端就监视股票,只要股票价格超过了阈值价格就通过一个回调( onThresholdPassed() )通知客户端。

Figure 2
图 2. 本图说明了这个例子的配置

  很显然,轮询技术不是创建客户端的最佳解决方案:股票价格有可能经常超过阈值价格也可能极少超过阈值价格。轮询间隔短就会造成很多不必要的访问,而另一方面,轮询间隔长又可能导致股票价格超过阈值价格很长时间后客户端才被告知。

  客户端应用程序中回调端点的实现应该是一种更好的解决方案。让我们首先来看一下例子客户端是如何将操作传递给 StockNotification Web service 的。我们已经采用两种不同的技术实现了客户端。第一种技术使用 WebLogic Workshop 生成的代理库,并由 StockNotificationClient 类的 makeCallUsingBEAProxy() 方法来实现:

public void makeCallUsingBEAProxy() {
    try {
      // Get the proxy instance so that we can start calling the web service operations
      StockNotificationSoap sns = null;
      StockNotification_Impl snsi = new StockNotification_Impl();
      sns = snsi.getStockNotificationSoap();

      // Register our conversational handler so that it can properly set our
      // our conversation headers with a callback location
      QName portName = new QName("http://www.openuri.org/",
                                 "StockNotificationSoap");
      HandlerRegistry registry = snsi.getHandlerRegistry();
      List handlerList = new ArrayList();
      handlerList.add(new HandlerInfo(ClientConversationHandler.class, null, null));
      registry.setHandlerChain(portName, handlerList);

      // Register, call some methods, then sleep a bit so that our callback
      // location can receive some notifications and finally finish the conversation.
      sns.registerForThresholdNotif("AAAAA", "100");
      sns.addToRegistration("BBBBB", "5");
      StockList stocks = sns.getAllRegisteredStocks();

      ...

      sns.endAllNotifications();
    }
    catch (Exception e) {
      throw new RuntimeException(e);
    }
}

  正如从上述代码段中看到的,我们可以使用 clientgen 生成的类。与没有回调的 Web service 调用相比,这段代码唯一强调的就是,在方法一开始就实例化和注册了客户端 SOAP 消息处理程序。这个处理程序截取了发出的 SOAP 消息,并加上必要的会话头部。每当客户端过程发出 SOAP 请求消息时,消息处理程序的 handleRequest() 方法就被调用,它利用了一个包含了输出 SOAP 请求信息的 MessageContext 对象。下面是例子客户端消息处理程序的代码段:

package weblogic.webservice.core.handler;

...

public class ClientConversationHandler
    extends GenericHandler {

public boolean handleRequest(MessageContext ctx) {  
  ...
  if (phase.equals("START")) {
    headerElement
              = (SOAPHeaderElement) header
              .addChildElement("StartHeader",
                               "con",
                               "http://www.openuri.org/2002/04/soap/conversation/");

    headerElement.addChildElement("con:callbackLocation")
              .addTextNode(CALLBACK_URI);
  }
  else if (phase.equals("CONTINUE") || phase.equals("FINISH")) {
    headerElement
              = (SOAPHeaderElement) header
              .addChildElement("ContinueHeader",
                               "con",
                               "http://www.openuri.org/2002/04/soap/conversation/");
  }

  headerElement.addChildElement("con:conversationID").addTextNode(convID);
  ...
}
}

  BEA clientgen 工具生成的代理库已经用了一个缺省的客户端 SOAP 消息处理程序,可创建会话式 StartHeader 头部元素。不幸的是,目前的 clientgen 输出不支持回调操作,因此缺省的处理程序不会创建所需的 callbackLocation 次级元素。所以我们必须保证自己的消息处理程序重写缺省的会话式处理程序,以便管理创建 callbackLocation 头部的额外任务。我们通过确保处理程序在一个与缺省处理程序相同的程序包中创建来做到这一点,这个程序包就是 weblogic.webservice.core.handler ,并且它还有具体的相同类名称 ClientConversationHandler 。注意,这种技术涉及到重写现有会话消息处理程序,但它并不能保证是向前兼容的。这个例子展示了消息处理程序如何用于创建回调头部,以及这种概念如何应用于任何其他支持 SOAP 消息处理程序的 Web services 框架中。

  有一种使用 SOAP 消息处理程序和代理类的可选技术,它使用 JAXM API 直接创建 SOAP 请求消息,并把它们送往 Web service 。这是我们例子客户端采用的第二种技术,它在 StockNotificationClient 类的 makeCallsUsingJAXM() 和 callMethodFromFile() 方法中实现:

public void makeCallsUsingJAXM() {
      callMethodFromFile("C:\\registerForNotifSOAP.xml");
      ...
      callMethodFromFile("C:\\endAllNotifSOAP.xml");
}

private void callMethodFromFile(String fileName) {

    try {
      // Create a SOAP connection object
      SOAPConnectionFactory scf = SOAPConnectionFactory.newInstance();
      SOAPConnection conn = scf.createConnection();

      // Create the SOAP request message from the specified file
      MessageFactory mf = MessageFactory.newInstance();
      SOAPMessage msg = mf.createMessage();
      SOAPPart sp = msg.getSOAPPart();

      // Read the file content into a string and replace the conversation ID with
      // the current one.
      String fileContent = readFileIntoString(fileName);
      fileContent = fileContent.replaceFirst(CONV_ID_HOLDER,
                                             ConvIDGenerator.getInstance().
                                             getConversationID());

      StreamSource prepMsg = new StreamSource(new StringBufferInputStream(
          fileContent));
      sp.setContent(prepMsg);
      msg.saveChanges();

      // Make the actual call
      conn.call(msg, TARGET_SERVICE_URI);
      conn.close();
    }
    catch (Exception ex) {
      throw new RuntimeException(ex);
    }
}   

  callMethodFromFile() 方法从指定的文件读取 SOAP 消息,用客户端的当前会话 ID 取代消息会话 ID ,最后生成实际调用。当使用 JAXM 方法时, Java 到 XML 的映射操作参数和返回值必须由客户端来完成。通过使用从文件中预先生成的 SOAP 消息(它已经包含了所需的参数值),我们已经避免了这一任务。尽管从概念上来说这种方法很好,但对于比较复杂的客户端端来说,这不是一种可行的解决方案。这些客户端必须从头开始通过编程方式使用 JAXM 来创建消息。

  既然我们已经回顾了客户端在生成 Web services 操作中的作用,下面让我们转向客户端的回调端点的实现方式和对来自 Web services 回调消息的处理。因为回调端点需要能够处理输入的 HTTP 请求, servlet 就是一种合适的选择。进一步说,我们非常需要一个 servlet ,它能够从输入的 HTTP 请求中提取 SOAP 消息,并以一种容易使用的方式提供给我们。 javax.xml.messaging 程序包里的 ReqRespListener 接口和 JAXMServlet 类提供给我们这个功能。只要安排合理,应用了这种接口和类的 servlet 将把所有的目标 HTTP post 请求自动转换成 SOAPMessage 实例,并通过 onMessage() 方法传递给我们。下面的代码显示了 onMessage() 方法如何帮助例子客户端实现这些功能。

public class CallBackHandlerServlet
    extends JAXMServlet
    implements ReqRespListener {


  public SOAPMessage onMessage(SOAPMessage message) {
    try {
      // We have just received a SOAP message at the callback
      // endpoint. In this example we will just assume that
      // the message received is in fact the onThresholdPassed
      // callback request. However, a servlet handling
      // multiple callbacks cannot make this assumption and
      // should process the SOAP message to see what callback
      // it relates to and do as appropriate for each.
      String stockTicker = extractOnThresholdPassedArgument(message);

	System.out.println("[DEV2DEV CALLBACK EXAMPLE]: Received treshold notification 
	                                for: " + stockTicker);

      // Now we have to create a proper response to the callback
      // request. Returning this response as part of the onMessage()
      // method will ensure that the SOAP response gets back to the server.
      SOAPMessage response = createThresholdPassedResponse();

      return response;
    }
    catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
  ...
}

  一旦回调请求消息被 onMessage() 方法所接收,客户端就从股票接收机中提取参数,将它输出到标准设备上,并生成一条相应的响应消息,它还会通过 servlet 返回到 Web service 。使用带有多个回调操作的 Web services 客户端也需要把传入的请求分发给客户端代码的合适部分,这个过程是基于相应的回调操作的。

  尽管 servlet 包含了合适代码来处理来自 Web services 的传入 onThresholdPassed() 回调消息,但它需要在监听回调 URI 的 servlet 容器中部署完成,这样才能完成回调端点的实现。 Jetty 开源项目有一个轻量级 servlet 容器,它可以嵌入在我们的客户端中。将 CallBackHandlerServlet servlet 部署在一个嵌入式 Jetty 容器中,允许我们将回调端点驻留在客户端 Java 进程中。 StockNotificationCallbackProcessor 类是一种客户端使用的公共类,它用于启动 Jetty 服务器和部署CallBackHandlerServlet servlet :

public class StockNotificationCallbackProcessor {

  public static final String CALLBACK_SERVER_PORT = ":8181";
  public static final String CALLBACK_SERVLET_CLASS = 
       "asynchwsconsumer.CallBackHandlerServlet";
  public static final String CALLBACK_SERVLET_NAME = "CallBackHandlerServlet";

  private HttpServer server;

  public StockNotificationCallbackProcessor() {}

  public void startListening() {

    try {
      // Configure the server so that we listen on the callback URI
      server = new Server();
      server.addListener(CALLBACK_SERVER_PORT);
      ServletHttpContext context = (ServletHttpContext) server.getContext("/");
      context.addServlet(CALLBACK_SERVLET_NAME, "/*",
                         CALLBACK_SERVLET_CLASS);

      // Start the http server
      server.start();
    }
    catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  public void stopListening() {
    try {
      server.stop();
    }
    catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
}

  startListening() 方法把 CallBackHandlerServlet 部署在端口 8181 上并配置嵌入式 servlet 容器,这样所有 HTTP 请求都将指向这个端口。这种方法还启动了嵌入式 servlet 容器。

  现在我们已经考察了 Web service 操作的创建和回调操作的处理,下面我们来看看例子客户端的 main() 方法是如何使用这些元素的:

public static void main(String[] args) {

    StockNotificationClient snClient = new StockNotificationClient();

    snClient.snCallbackProcessor.startListening();

    snClient.makeCallUsingBEAProxy();

    ConvIDGenerator.getInstance().resetConversationID();

    snClient.makeCallsUsingJAXM();

    snClient.snCallbackProcessor.stopListening();
}

  正如您所看到的,这种方法开始于建立一个回调端点(正如前面所看到的,它包括启动嵌入式 servlet 容器和部署回调过程)。该方法接着使用 BEA clientgen 代理来调用股票通知的 Web service 。这样,这种与 Web services 的交互就通过调用 endAllNotifications() 端操作来完成会话,客户端需要改变会话 ID 以启动新会话。客户端使用 ConvIDGenerator 单一类来管理会话 ID ,而 ConvIDGenerator 类又使用 java.rmi.dgc.VMID 类来创建合适的格式化唯一会话 ID 。

  一旦会话 ID 发生了变化,客户端就会用 JAXM 再一次调用股票通知 Web services 。最后,客户端终止嵌入式 servlet 容器,这个过程就结束了。

  我们现在已经完成了对例子客户端的分析,该例子客户端可作为本文的一部分下载。当您运行客户端和股票通知 Web services 的时候,客户端和 Web service 都将把消息显示在标准输出设备上,用以描述会话的不同阶段,包括对回调操作的处理。

结束语

  本文展示了为包含回调操作的 Web services 创建独立客户端过程的步骤。这种客户端需要管理 Web services 调用的会话方面,并提供一个端点用于 Web service 进行回调操作。

下载

CallbackExample.zip (1.7Mb) ——这个文件包括了本文讲述的完整实例应用程序。

参考资料

原文出处

http://dev2dev.bea.com/pub/a/ 2005/03/c allback_clients.html

posted @ 2006-05-07 16:19 hopeshared 阅读(1308) | 评论 (0)编辑 收藏

摘要:

到目前为止,web service交互作用是独立同步的,同时本质上是应答式的。不过,很显然同步应答类型在基于消息的应用中只是一个很小的子集。消息在耦合松散系统中是非常重要的,因此这种限制很关键。Web service规范,例如WS-addressing和WSDL,已经融入了消息的概念并且为包含一个相当大范围的消息应用奠定了基础。Apache Axis2 架构既不基于任一个消息交换模式,也不基于同步/异步行为。这篇文章解释了消息概念和Axis2在几种众所周知的消息场合中怎样被扩展使用。

关于Apache Axis2的Web service消息

作者:Srinath Perera, Ajith Ranabahu

07/27/2005

翻译:doomsday

版权声明:可以任意转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明
英文原文地址:
http://www.onjava.com/pub/a/onjava/2005/07/27/axis2.html
中文地址:
http://www.matrix.org.cn/resource/article/43/43723_Apache_Axis2.html
关键词: Apache Axis2 Web service

  
     到目前为止,web service交互作用是独立同步的,同时本质上是应答式的。不过,很显然同步应答类型在基于消息的应用中只是一个很小的子集。消息在耦合松散系统中是非常重要的,因此这种限制很关键。Web service规范,例如WS-addressing和WSDL,已经融入了消息的概念并且为包含一个相当大范围的消息应用奠定了基础。Apache Axis2 架构既不基于任一个消息交换模式,也不基于同步/异步行为。这篇文章解释了消息概念和Axis2在几种众所周知的消息场合中怎样被扩展使用。

消息的简单介绍

    贯穿计算历史,分布式运算是其中的一个很大的挑战:当资源是分布式时,进程间的通信变得相当困难,研究人员仍然在寻找更好的解决方案。有趣的是,几乎所有关于分布式计算机计算能力问题的解决方案来源于两种概念基础: 远程过程调用(RPC) 和消息传递。

    毫无疑问,使用RPC在开发人员中是非常流行的技术,部分原因是是本地过程调用与它的类似之处。本地过程调用在程序员中很有人气,所以在分布式系统中使用RPC是很自然的选择,而另一方面,消息传递不是非常流行,当它被提起时很少有开发人员关注它。不过,在某些场合使用消息相比RPC系统有更好的优势。

RPC和消息框架的基本差异如下所示:

●消息完全不懂客户端和服务器,因为一个消息框架(通常所说的面向消息的中间件,或message-oriented middleware,即MOM)集中于传递消息,所有接收和散发消息的节点身份平等,术语称之为对等体。RPC始终有服务请求者 (AKA client) 和服务提供者 (AKA server)的概念。
●消息对于一个特定范畴是时间独立的。没有任何对等体希望实时接收消息--当对等体可用时MOM关注于传递一个消息到相应的对等体,然而,RPC在失去一方时立即失效。
●消息可被复制并且轻易的传递到众多对待体。RPC本质上是一种一对一的交流方式,而消息更灵活,并且毫不费力地传递同一消息的拷贝到多种对等体。

Web service消息

    Web service是在XML消息的基础上定义的,下面3个因素描述了一个给定的Web service的消息交互。

●消息交换模式
●同步和异步客户端API
●单向和双向传送行为

    从最抽象的角度来讲,,web service消息传递建立在发送和接收消息基础上,一个给定的消息被一方发出,并且被另一方接收。消息可能相互关联,识别这些相互关联的消息群中的最常见的应用场合是非常重要的,这些消息群被定义为消息交换模式(message exchange patterns),简称MEPs.

    过渡时期下在两种相关消息间的一个服务请求者的行为在客户端API定义了消息同步/异步行为。同步场合下,客户端请求将会阻塞,在相关消息到达目的地后前一直等待,在非阻塞场合下,客户端请求不会阻塞,当相关消息到达时,它与之前的消息相互联系。

    传送分类为单向或双向,基于单方或双方行为。类似SMTP和JMS传送即是单向传送的,不会阻塞,另一方面,类似HTTP和TCP即是双向传送,相关消息可能在回路中返回,实际上,在Web service消息中,双向传送可能被用作单向传送,但是在这些场合下,它们可被有效的处理为单向方式。

消息交换模式

    根据W3C建议,消息交互模式就是一个为交流双方构建消息交换的一个模板,一个MEP将相关消息确定为一组。MEPs根据服务请求者和服务提供者来定义,需要注意,为了清晰,MEPs以服务提供者的消息特性来命名。为方便理解,所有的命名都可以用request代替in, 用response代替out。

例如,我们看看两个有名的MEPS

1.In-only/"发后不理:" 服务请求者发送消息给服务提供者但是不关心任何后继相关消息
2.In-out/"应答式:" 服务请求者发送消息给服务提供者并希望返回结果

    MEPS概念仍在扩展中,模式的数目是没有限制的,所以Web service中间件应用强制使用几种选定的MEPs,"发后不理"和 "应答式" 是被明确使用的,其它大多数的模式可由这两种组合使用。

客户端API同步/异步行为

    同步/异步(或阻塞/非阻塞)行为是基于在web service请求的线程,同步服务将会阻塞,等待相关消息到达。另一方面,异步请求仅仅返回,等待相关消息被后台另一个不同线程执行。

    这两种途径有典型的用例。考虑一下银行事务,其需要一定数量的消息来来回传递。银行事务本质是连续的,当结果到达时后执行下一步骤,因此同步等待结果很有意义。另一方面,设想一个航班预约程序,其需要搜集多种数据来源,根据这些结果再匹配。这个案例中,异步行为发挥作用,因为程序可以提交所有结果并且当数据到达时工作。考虑到网络响应,异步方式获得较好的结果。

    同步请求很简单:请求在相关消息到达前等待,并且可以像本地过程调用一样被编码。但是异步消息的相互关系就比较复杂,客户端必须处理这种复杂性。尽管如此,通过一些额外工作来处理这种复杂情况仍是必要的。

传输层的行为

    传输层的行为是个关键因素,决定了Web service消息的发生,传输根据依据其行为分类为单向和双向。
    单向传送减少web service消息的复杂性,因为相关消息必须来源于各自的通道。另一方面,如果传送是双向的,消息有机会选择使用单向还是双向,例如,传输是HTTP时,相关消息来自HTTP连接的返回路径,或者这么讲web service提供者可以写HTTP 200来指出没有来自同一连接的响应,在这种案例下回应经由独立的HTTP连接发送。

Web service寻址角色

    webs ervice寻址框架(也被称为WS-addressing)是为了在同一web service交互活动中交换不同信息部分,下面的5个因素定义了交互活动:

    1.消息交换模式
    2.可被访问的服务传送
    3.相关消息传送
    4.传送的行为
    5.客户端API的同步/异步行为

    服务提供者申明了头两个,客户端定义了其余因素,客户端级别的同步/异步行为对服务提供者是完全透明的,客户端使用WS-addressing来解释web service消息。

    在其它大多数结构中,WS-addressing定义了四种标题:To, ReplyTo, RelatesTo, FaultTo以及一个被称为匿名地址的特定地址,当一个服务提供者接收到SOAP消息时,它会查找在to地址上的目标服务并且调用服务。如果有结果,即被发送到ReplyTo地址,错误被发送到FaultTo地址,如果以上任一个的标题没有指定或具有匿名值,结果通过双向传送返回路径返回(因为匿名决定双向传送返回路径)。

    传送和WS-addressing一起定义了查找相关消息的机制,消息是相关缘于它们共享了相同的传送通道,也可能因为它们都共享把它们互相链接的公共信息。web service RelateTo标题正好提供了这种关系。

下面的表显示的明确定义的消息交互活动的寻址标题的不同值



Axis2客户端API概念

    Axis2客户端API处理了In-Only和In-OutMEPs,所有的消息结合在下面的章节讨论。MEPs的空间是无限的,因此,Axis2强制提供了支持任意消息交换模式的核心,并且提供了两种常被使用的模式In-Only和In-Out的API,有两种方法实现更多的复杂模式:组合In-Only和In-Out来完成希望的模式,或者对希望的模式写新的扩展。因为Axis2为任意MEP提供核心级别的支持,实现是显而易见的。In-Only和In-OutMEPS被InOnlyMEPClient和InOutMEPClient类支持,下两节即做具体描述。

In-Only MEP 支持: InOnlyMEPClient

    InOnlyMEPClient类对发送不理消息提供了支持,所有的传送类型作为单向传送对待,InOnlyMEPClient和InOutMEPClient真正的差别是寻址参数起先没有锁定,并且寻址参数随后被Axis2控制。作为可被控制的寻址参数,InOnlyMEPClient可被用作消息API,并且在此基础上构建更复杂的消息交互。

In-Out MEP 支持: InOutMEPClient

    InOutMEPClient和继承了InOutMEPClient的调用类为应答式消息提供了支持,Axis2关注完整的操作,除了To地址外的所有的寻址属性都在Axis2的控制下

    用户可以配置InOutMEPClient 来表现不同,利用以下的四个参数。

1.发送者传输
2.监听者传输
3.是用单独监听
4.使用阻塞



    客户端API当前提供了针对HTTP和SMTP传输的支持,下面的表格显示了这些参数可能的组合以及它们怎样结合来提供不同特效。

举例

    下面的代码实例显示了怎样使用Apache Axis2做几个定义明确的交互作用,用户可以在客户端API简单的转换属性从而转换不同的交互作用,客户端Axis2 API仅仅支持XML级别的消息和代表大块XML的OME元素。

调用单向消息

    单向MEP简单之处在于在仅有一个消息来回传送时它能表现正确的单向,这些消息被异步对待并且传送是单向的。

应答式消息

    可以表现四种方式的应答式消息

    1.双向In-Out 同步
    2.双向In-Out 异步
    3.单向In-Out 同步
    4.单向In-Out 异步

    下面的代码实例说明这些案例怎样被Axis2寻址,注意客户端API的四种属性怎样被使用。

    1.In-Out同步,HTTP作为双向传输方式

OMElement payload = .... 
Call call = new Call();
call.setTo(
        new EndpointReference(AddressingConstants.WSA_TO,
                "HTTP://...));
call.setTransportInfo(Constants.TRANSPORT_HTTP,
       Constants.TRANSPORT_HTTP, false);
OMElement result =
        (OMElement) call.invokeBlocking(
        operationName.getLocalPart(), payload);


    这里,SOAP消息经由同一个HTTP连接传播,地址属性没有指定,所以它们在服务器方缺省为匿名,客户端API将被锁定直到回复消息到达。

    2.In-Out异步,HTTP使用HTTP作为双向传送

//this is the payload goes on the body of SOAP message 
OMElement payload = ....
Call call = new Call();
call.setTo(
        new EndpointReference(AddressingConstants.WSA_TO,
                "HTTP://...));
call.setTransportInfo(Constants.TRANSPORT_HTTP,
              Constants.TRANSPORT_HTTP, false);

Callback callback = new Callback() {
    public void onComplete(AsyncResult result) {
        //what user can do to result
    }
    public void reportError(Exception e) {
       //on error
    }
};
call.invokeNonBlocking(operationName.getLocalPart(),
       payload, callback);


    和前面相同,SOAP消息经由同一个HTTP连接传输并且不需要寻址,一旦回复消息到达客户端API不会阻塞并且回调将被执行。

    3.In-Out, 异步HTTP 作为单向传输

OMElement payload = .... 
Call call = new Call();
call.setTo(
        new EndpointReference(AddressingConstants.WSA_TO,
                "HTTP://...));
call.setTransportInfo(Constants.TRANSPORT_HTTP,
    Constants.TRANSPORT_HTTP, true);
Callback callback = new Callback() {
        public void onComplete(AsyncResult result) {
        ....
        }

        public void reportError(Exception e) {
        ...
        }
};
call.engageModule(new Qname("addressing"));
call.invokeNonBlocking(operationName.getLocalPart(), method, callback);


    在这个案例中,SOAP消息通过两个HTTP连接传输,寻址是强制的,ReplyTo标题出现指示服务器端经由单独的通道发送回应。客户端没有阻塞,当回应消息到达时,唤起回调。

4.In-Out, 同步 HTTP 作为单向传送

OMElement payload = .... 
Call call = new Call();
call.setTo(
new EndpointReference(AddressingConstants.WSA_TO,
        "HTTP://...));
call.setTransportInfo(Constants.TRANSPORT_HTTP,
    Constants.TRANSPORT_HTTP, true);
OMElement result =
        (OMElement) call.invokeBlocking(
           operationName.getLocalPart(), payload);


    在这种场合下使用"In-Out,异步HTTP作为单向传送"类型,在结果到达第二种连接时唤起阻塞,执行并返回结果。

总结

    总而言之,web wervice消息行为建立在三种因素上:消息交互模式,客户端同步异步模式和传送行为。Asis2建立核心在不一定要任何MEP类型,不过为MEPs的广泛支持:单向和应答提供了客户端API支持,这篇文章解释Axis2消息支持概念和客户端API的使用。

资源
  
Apache Axis2的官方站点
●W3C讨论稿, "WSDL Version 2.0 Part 2: Message Exchange Patterns"
"Why Messaging? The Mountain of Worthless Information," Ted Neward的博客, 星期三,2003年5月9日
"Introduction to WS-Addressing,"  作者Beth Linker, dev2dev, 2005年1月31日
"Async," Dave Orchard的博客, 2005年5月5日
"Programming Patterns to Build Asynchronous Web Services,"  Holt Adams, IBM开发者文集, 2002年6月1日

Srinath Perera是Apache Axis2的主要架构师
Ajith Ranabahu是Apache Axis2项目的成员并且在基于Web service的项目里工作了3年


posted @ 2006-05-07 16:05 hopeshared 阅读(696) | 评论 (0)编辑 收藏

     摘要: 深入学习Web Service系列之 异步开发模式 ——《深入学习 Web Service 系列》之一 Terrylee , 2005 年 12 月 4 日 ...  阅读全文
posted @ 2006-05-07 15:28 hopeshared 阅读(760) | 评论 (0)编辑 收藏

在确定是否适合在您的应用程序中采用异步 Web 方法时,有几个问题需要考虑。首先,调用的 BeginXXX 函数必须返回一个 IAsyncResult 接口。IAsyncResult 是从多个异步 I/O 操作返回的,这些操作包括访问数据流、进行 Microsoft&reg; Windows&reg; 套接字调用、执行文件 I/O、与其他硬件设备交互、调用异步方法,当然也包括调用其他 Web 服务。您可以从这些异步操作中得到 IAsyncResult,以便从 BeginXXX 函数返回它。您也可以创建自己的类以实现 IAsyncResult 接口,但随后可能需要以某种方式包装前面提到的某个 I/O 操作。

对于前面提到的大多数异步操作,使用异步 Web 方法包装后端异步调用很有意义,可以使 Web 服务代码更有效。但使用委托进行异步方法调用时除外。委托会导致异步方法调用占用进程线程池中的某个线程。不幸的是,ASMX 处理程序为进入的请求提供服务时同样要使用这些线程。所以与对硬件或网络资源执行真正 I/O 操作的调用不同,使用委托的异步方法调用在执行时仍将占用其中一个进程线程。您也可以占用原来的线程,同步运行您的 Web 方法。

下面的示例显示了一个调用后端 Web 服务的异步 Web 方法。它已经使用 WebMethod 属性标识了 BeginGetAge 和 EndGetAge 方法,以便异步运行。此异步 Web 方法的代码调用名为 UserInfoQuery 的后端 Web 方法,以获得它需要返回的信息。对 UserInfoQuery 的调用被异步执行,并被传递到 AsyncCallback 函数,后者被传递到 BeginGetAge 方法。这将导致当后端请求完成时,调用内部回调函数。然后,回调函数将调用 EndGetAge 方法以完成请求。此示例中的代码比前面示例中的代码简单得多,并且还具有另外一个优点,即没有在与为中间层 Web 方法请求提供服务的相同线程池中启动后端处理。

[WebService]

public class GetMyInfo : System.Web.Services.WebService
{

[WebMethod]
public IAsyncResult BeginGetAge(AsyncCallback cb, Object state)
{

// 调用异步 Web 服务调用。
localhost.UserInfoQuery proxy
= new localhost.UserInfoQuery();
return proxy.BeginGetUserInfo("用户名",
cb,
proxy);
}

[WebMethod]
public int EndGetAge(IAsyncResult res)
{
localhost.UserInfoQuery proxy
= (localhost.UserInfoQuery)res.AsyncState;
int age = proxy.EndGetUserInfo(res).age;
// 在此对 Web 服务的结果进行其他
// 处理。
return age;
}
}

发生在 Web 方法中的最常见的 I/O 操作类型之一是对 SQL 数据库的调用。不幸的是,目前 Microsoft&reg; ADO.NET 尚未定义一个完好的异步调用机制;而只是将 SQL 调用包装到异步委托调用中对提高效率没有什么帮助。虽然有时可以选择缓存结果,但是也应当考虑使用 Microsoft SQL Server 2000 Web Services Toolkit(英文)将您的数据库发布为 Web 服务。这样您就可以利用 .NET Framework 中的支持,异步调用 Web 服务以查询或更新数据库。

通过 Web 服务调用访问 SQL 时,需要注意众多的后端资源。如果您使用了 TCP 套接字与 Unix 计算机通信,或者通过专用的数据库驱动程序访问其他一些可用的 SQL 平台,甚至具有使用 DCOM 访问的资源,您都可以考虑使用众多的 Web 服务工具包将这些资源发布为 Web 服务。

使用这种方法的优点之一是您可以利用客户端 Web 服务结构的优势,例如使用 .NET Framework 的异步 Web 服务调用。这样您将免费获得异步调用能力,而您的客户端访问机制会与异步 Web 方法高效率地配合工作。

使用异步 Web 方法聚合数据

现在,许多 Web 服务都访问后端的多个资源并为前端的 Web 服务聚合信息。尽管调用多个后端资源会增加异步 Web 方法模型的复杂性,但最终还是能够显著提高效率。

假设您的 Web 方法调用两个后端 Web 服务:服务 A 和服务 B。从您的 BeginXXX 函数,您可以异步调用服务 A 和服务 B。您应该向每个异步调用传递自己的回调函数。在从服务 A 和服务 B 接收到结果后,为触发 Web 方法的完成,您提供的回调函数将验证所有的请求都已完成,在返回的数据上进行所有的处理,然后调用传递到 BeginXXX 函数的回调函数。这将触发对 EndXXX 函数的调用,此函数的返回将导致异步 Web 方法的完成。

小结

异步 Web 方法在 ASP.NET Web 服务中提供了一个有效的机制,可以调用后端服务,而不会导致占用却不利用进程线程池中的宝贵线程。通过结合对后端资源的异步请求,服务器可以使用自己的 Web 方法使同时处理的请求数目达到最大。您应该考虑使用此方法开发高性能的 Web 服务应用程序。


原文地址:http://ewebapp.cnblogs.com/articles/237372.html
posted @ 2006-05-07 15:22 hopeshared 阅读(512) | 评论 (0)编辑 收藏

为举例说明异步 Web 方法,我从一个名为 LengthyProcedure 的简单同步 Web 方法开始,其代码如下所示。然后我们再看一看如何异步完成相同的任务。LengthyProcedure 只占用给定的毫秒数。

[WebService]

public class SyncWebService : System.Web.Services.WebService
{

[WebMethod]
public string LengthyProcedure(int milliseconds)
{
System.Threading.Thread.Sleep(milliseconds);
return "成功";
}
}

现在我们将 LengthyProcedure 转换为异步 Web 方法。我们必须创建如前所述的 BeginLengthyProcedure 函数和 EndLengthyProcedure 函数。请记住,我们的 BeginLengthyProcedure 调用需要返回一个 IAsyncResult 接口。这里,我打算使用一个委托以及该委托上的 BeginInvoke 方法,让我们的 BeginLengthyProcedure 调用进行异步方法调用。传递到 BeginLengthyProcedure 的回调函数将被传递到委托上的 BeginInvoke 方法,从 BeginInvoke 返回的 IAsyncResult 将被 BeginLengthyProcedure 方法返回。

当委托完成时,将调用 EndLengthyProcedure 方法。我们将调用委托上的 EndInvoke 方法,以传入 IAsyncResult,并将其作为 EndLengthyProcedure 调用的输入。返回的字符串将是从该 Web 方法返回的字符串。下面是其代码:

[WebService]

public class AsyncWebService : System.Web.Services.WebService
{
public delegate string LengthyProcedureAsyncStub(
int milliseconds);

public string LengthyProcedure(int milliseconds)
{
System.Threading.Thread.Sleep(milliseconds);
return "成功";
}

public class MyState
{
public object previousState;
public LengthyProcedureAsyncStub asyncStub;
}

[ System.Web.Services.WebMethod ]

public IAsyncResult BeginLengthyProcedure(int milliseconds,
AsyncCallback cb, object s)
{
LengthyProcedureAsyncStub stub
= new LengthyProcedureAsyncStub(LengthyProcedure);
MyState ms = new MyState();
ms.previousState = s;
ms.asyncStub = stub;
return stub.BeginInvoke(milliseconds, cb, ms);
}

[ System.Web.Services.WebMethod ]
public string EndLengthyProcedure(IAsyncResult call)
{
MyState ms = (MyState)call.AsyncState;
return ms.asyncStub.EndInvoke(call);
}
}


原文地址:http://ewebapp.cnblogs.com/articles/237375.html
posted @ 2006-05-07 15:18 hopeshared 阅读(287) | 评论 (0)编辑 收藏

1. 在JavaScript中调用WebService

<script language="javascript">
function PostRequestData(URL,data){
var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
xmlhttp.Open("POST",URL, false);
xmlhttp.SetRequestHeader ("Content-Type","text/xml; charset=utf-8");
xmlhttp.SetRequestHeader ("SOAPAction","http://tempuri.org/myService/test/isNumner");

try {
xmlhttp.Send(data);
var result = xmlhttp.status;
}
catch(ex) {
return("0" + ex.description + "|" + ex.number);
}
if(result==200) {
return("1" + xmlhttp.responseText);
}
xmlhttp = null;
}

function loadit(value){
var url = ''http://localhost/myService/test.asmx'';
var data ;
var r;

data = ''<?xml version="1.0" encoding="utf-8"?>'';
data = data + ''<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">'';
data = data + ''<soap:Body>'';
data = data + ''<isNumner xmlns="http://tempuri.org/myService/test">'';
data = data + ''<str>''+value+''</str>'';
data = data + ''</isNumner>'';
data = data + ''</soap:Body>'';
data = data + ''</soap:Envelope>'';

r=PostRequestData(url,data);
document.write(r);
}
loadit(''5'');
</script>


还可以使用微软的htc组件来实现,可以到这里下载:

http://msdn.microsoft.com/workshop/author/webservice/webservice.htc

<script language="javascript">
function timer(){
service.useService("http://localhost/myService/test.asmx?WSDL","test");
service.test.callService(callback,"isNumner",''gdh'');
}

function callback(res){
if (!res.error)
time.innerText=res.value;
}
</script>

<div id="service" style="behavior:url(webservice.htc)"></div>
<span id="time"></span>

2. 在Asp中

<%@LANGUAGE="VBSCRIPT" CODEPAGE="936"%>
<%
Dim strxml
Dim str

''定义soap消息

strxml = "<?xml version=''1.0'' encoding=''tf-8''?>"
strxml = strxml & "<soap:Envelope xmlns:xsi=''http://www.w3.org/2001/XMLSchema-instance'' xmlns:xsd=''http://www.w3.org/2001/XMLSchema'' xmlns:soap=''http://schemas.xmlsoap.org/soap/envelope/''>"
strxml = strxml & "<soap:Body> "
strxml = strxml & "<isNumner xmlns=''http://tempuri.org/myService/test''>"
strxml = strxml & "<str>4</str>"
strxml = strxml & "</isNumner>"
strxml = strxml & "</soap:Body>"
strxml = strxml & "</soap:Envelope>"

''定义一个XML的文档对象,将手写的或者接受的XML内容转换成XML对象

''set x = createobject("Microsoft.DOMDocument")

''初始化XML对象

''将手写的SOAP字符串转换为XML对象

'' x.loadXML strxml

''初始化http对象

Set h = createobject( "Microsoft.XMLHTTP")

''向指定的URL发送Post消息

h.open "POST", "http://localhost/myService/test.asmx", False
h.setRequestHeader "Content-Type", "text/xml"
h.setRequestHeader "SOAPAction", "http://tempuri.org/myService/test/isNumner"
h.send (strxml)
While h.readyState <> 4
Wend

''显示返回的XML信息

str = h.responseText

''将返回的XML信息解析并且显示返回值

''Set x = createobject("MSXML2.DOMDocument")
'' x.loadXML str
''str = x.childNodes(1).Text
response.write(str)
%>

3.在.net中

在.net中调用WebService就方便多了,没有必要自己写soap消息了,以上都是用XMLHTTP来发送WebService请求的,在.net只要添加了web引用,会自动为你创建一个代理类。然后使用代理类就像用自己定义的类一样方便。


原文地址:http://ewebapp.cnblogs.com/articles/237386.html
posted @ 2006-05-07 15:12 hopeshared 阅读(603) | 评论 (0)编辑 收藏