哪位大大有空帮忙解决一下resin下载启动错误

Resin的喜欢 | LOFTER(乐乎) - 每个人的理想国
LOFTER for ipad —— 记录生活,发现同好
Resin 的喜欢
&nbsp&nbsp被喜欢
&nbsp&nbsp被喜欢
{list posts as post}
{if post.type==1 || post.type == 5}
{if !!post.title}${post.title|escape}{/if}
{if !!post.digest}${post.digest}{/if}
{if post.type==2}
{if post.type == 3}
{if !!post.image}
{if post.type == 4}
{if !!post.image}
{list photos as photo}
{if photo_index==0}{break}{/if}
品牌${make||'-'}
型号${model||'-'}
焦距${focalLength||'-'}
光圈${apertureValue||'-'}
快门速度${exposureTime||'-'}
ISO${isoSpeedRatings||'-'}
曝光补偿${exposureBiasValue||'-'}
镜头${lens||'-'}
{if data.msgRank == 1}{/if}
{if data.askSetting == 1}{/if}
{if defined('posts')&&posts.length>0}
{list posts as post}
{if post_index < 3}
{if post.type == 1 || post.type == 5}
{if !!post.title}${post.title|escape}{/if}
{if !!post.digest}${post.digest}{/if}
{if post.type == 2}
{if post.type == 3}
{if post.type == 4}
{if drlist.length>0}
更多相似达人:
{list drlist as dr}{if drlist.length === 3 && dr_index === 0}、{/if}{if drlist.length === 3 && dr_index === 1}、{/if}{if drlist.length === 2 && dr_index === 0}、{/if}{/list}
暂无相似达人,
{if defined('posts')&&posts.length>0}
{list posts as post}
{if post.type == 2}
{if post.type == 3}
{if post.type == 4}
this.p={ dwrMethod:'queryLikePosts',fpost:'260e96_3d66cd3',userId:2287548,blogListLength:30};Resin的喜欢 | LOFTER(乐乎) - 每个人的理想国
LOFTER for ipad —— 记录生活,发现同好
Resin 的喜欢
&nbsp&nbsp被喜欢
&nbsp&nbsp被喜欢
{list posts as post}
{if post.type==1 || post.type == 5}
{if !!post.title}${post.title|escape}{/if}
{if !!post.digest}${post.digest}{/if}
{if post.type==2}
{if post.type == 3}
{if !!post.image}
{if post.type == 4}
{if !!post.image}
{list photos as photo}
{if photo_index==0}{break}{/if}
品牌${make||'-'}
型号${model||'-'}
焦距${focalLength||'-'}
光圈${apertureValue||'-'}
快门速度${exposureTime||'-'}
ISO${isoSpeedRatings||'-'}
曝光补偿${exposureBiasValue||'-'}
镜头${lens||'-'}
{if data.msgRank == 1}{/if}
{if data.askSetting == 1}{/if}
{if defined('posts')&&posts.length>0}
{list posts as post}
{if post_index < 3}
{if post.type == 1 || post.type == 5}
{if !!post.title}${post.title|escape}{/if}
{if !!post.digest}${post.digest}{/if}
{if post.type == 2}
{if post.type == 3}
{if post.type == 4}
{if drlist.length>0}
更多相似达人:
{list drlist as dr}{if drlist.length === 3 && dr_index === 0}、{/if}{if drlist.length === 3 && dr_index === 1}、{/if}{if drlist.length === 2 && dr_index === 0}、{/if}{/list}
暂无相似达人,
{if defined('posts')&&posts.length>0}
{list posts as post}
{if post.type == 2}
{if post.type == 3}
{if post.type == 4}
this.p={ dwrMethod:'queryLikePosts',fpost:'e3914_fce1c4',userId:2287548,blogListLength:30};Resin的喜欢 | LOFTER(乐乎) - 每个人的理想国
LOFTER for ipad —— 记录生活,发现同好
Resin 的喜欢
&nbsp&nbsp被喜欢
&nbsp&nbsp被喜欢
{list posts as post}
{if post.type==1 || post.type == 5}
{if !!post.title}${post.title|escape}{/if}
{if !!post.digest}${post.digest}{/if}
{if post.type==2}
{if post.type == 3}
{if !!post.image}
{if post.type == 4}
{if !!post.image}
{list photos as photo}
{if photo_index==0}{break}{/if}
品牌${make||'-'}
型号${model||'-'}
焦距${focalLength||'-'}
光圈${apertureValue||'-'}
快门速度${exposureTime||'-'}
ISO${isoSpeedRatings||'-'}
曝光补偿${exposureBiasValue||'-'}
镜头${lens||'-'}
{if data.msgRank == 1}{/if}
{if data.askSetting == 1}{/if}
{if defined('posts')&&posts.length>0}
{list posts as post}
{if post_index < 3}
{if post.type == 1 || post.type == 5}
{if !!post.title}${post.title|escape}{/if}
{if !!post.digest}${post.digest}{/if}
{if post.type == 2}
{if post.type == 3}
{if post.type == 4}
{if drlist.length>0}
更多相似达人:
{list drlist as dr}{if drlist.length === 3 && dr_index === 0}、{/if}{if drlist.length === 3 && dr_index === 1}、{/if}{if drlist.length === 2 && dr_index === 0}、{/if}{/list}
暂无相似达人,
{if defined('posts')&&posts.length>0}
{list posts as post}
{if post.type == 2}
{if post.type == 3}
{if post.type == 4}
this.p={ dwrMethod:'queryLikePosts',fpost:'272c73_1a213d2',userId:2287548,blogListLength:30};请有空的大大帮我翻译这个东西!/f?ct=&tn=baiduPostBrowser&sc=&z=&pn=0&rn=30&lm=0&word=%C0%B5%B5%C2%C6%E5#RT 就是这个 要求①纯人工翻译
②无低级语法错误 ③星_百度作业帮
请有空的大大帮我翻译这个东西!/f?ct=&tn=baiduPostBrowser&sc=&z=&pn=0&rn=30&lm=0&word=%C0%B5%B5%C2%C6%E5#RT 就是这个 要求①纯人工翻译
②无低级语法错误 ③星
请有空的大大帮我翻译这个东西!/f?ct=&tn=baiduPostBrowser&sc=&z=&pn=0&rn=30&lm=0&word=%C0%B5%B5%C2%C6%E5#RT 就是这个 要求①纯人工翻译
②无低级语法错误 ③星期六之前完成④通过机器反翻译成中文时要通顺⑤直接把机器翻译的拿给我的请回火星 谢谢⑥不要笑 虽然很弱智 不过这是我唯一的纪念品了PS:这个帖子的2楼是一个典型的机器翻译作品 很低级的错误不希望有人拿这个忽悠朕
The day is deserted and solitary unbearable,Do not accompany me to look at the sea because of you,Stand alone in the windowsill,Look into the distance from a high place at shell adorning a color,Think of you besides in long distance,If the happy heart is broken,I did not come back but before,Melt my sighing with emotion slowly,Return only no longer because of you,Leave over the lower sack,Chockful pretending is love not only all,The possessions still having me coming and going has no choice,Maybe know your expectation,It truns out that you wait to have to suffer injury very much but unavoidably only because of us,Not think that this is only one accident please,This is that the end belongs to our love relay race,You still need to persist in leaving,Forget my calling for to you in ocean,Return ,return,Although you throw still me come untied,Still ,finclause one's own looks at the sea,Every single one day is deserted and solitary unbearable,
Because the day bears, you to have lonelily accompanies me to look at the sea, from the station, in the window, looks into the distance from a high place the color, to think that you before great dist...OOP-关于面向对象的哲学体系及科学体系的研讨_resin常见有关问题_且行且珍惜:又现模板短信,在改变中求生存__脚本百事通
稍等,加载中……
^_^请注意,有可能下面的2篇文章才是您想要的内容:
OOP-关于面向对象的哲学体系及科学体系的研讨
resin常见有关问题
且行且珍惜:又现模板短信,在改变中求生存
OOP-关于面向对象的哲学体系及科学体系的研讨
OOP-关于面向对象的哲学体系及科学体系的探讨
目录 Catelog
目录 Catelog
序言 Perface
真经第一章:世界 Waltanschauung
真经第二章:抽象 Abstraction
真经第三章:层次 Arrangement
真经第四章:继承 Inheritance
真经第五章:耦合 Couple
真经第六章:运作 Moving
真经第七章:建造 Build
真经第八章:刻画 Delineate
真经第九章:模式 Pattern
真经第十章:悟道 Doctrine
后记 Afterword
参考文献 Reference
序言 Perface
“佛曰:苦海无涯,回头是岸。——佛教用语”
面向对象(Object-Oriented),这是一条令无数开发人员魂牵梦绕的短语。几乎每个软件分析师、设计师和程序员都时刻将它铭记于心,对它顶礼膜拜。然而,对大多数人来说,它又像是天边的霞光,可望而不可及,无数次伸出双手,总是抓不住这虚无缥缈的圣物。于是,我们依然每天将面向对象高高供其,却始终无法悟得其道,更不要谈娴熟运用其道法了。
面向对象像一滩苦海,无数人游弋其中,却久久不得其要领;类、对象、继承、多态、接口、UML、设计模式……无数概念看得我们眼花缭乱,却也悟不透其真谛。佛教有云:苦海无涯,回头是岸。如果置身苦海中无法脱离,那么,我们是否应该提高一个层面去看这片苦海:从哲学及科学的角度,去审视面向对象。
曾有人说:艺术的极致是科学,科学的极致是哲学。此话不无道理,牛顿、爱因斯坦等科学界泰斗,在其后期都不约而同地转向哲学研究。当然,这里本人无意更不敢将自己与上面两位大师相提并论,而且本人也不奢求此文能成为一篇颇有思想的佳作。只不过,本人在平时的实践和思考中,略有小得,于是,在这里拿出,和大家一起分享讨论。虽然肤浅,但希望本文能成为一丝波纹,为各位脱离苦海提供一点点的推动作用。
真经第一章——世界 Weltanschauung
“世界观(德文:Weltanschauung)意为‘着眼世界之上’,是人们对世界的总的根本的看法。任何哲学问题的探讨,归其出发点和本源,都是世界观的问题。什么样的世界观决定了什么样的哲学观点。“
我们知道,哲学领域中,最根本的对立是唯物主义和唯心主义的对立,而附属其下,又有许多对立,如形而上学和辩证法的对立、可知论和不可知论的对立等等。这些对立形成了哲学的基本体系、派别和出发点。实际上,这些对立,都是世界观的对立。世界观,简而言之即如何看待这个世界。世界观是一切哲学问题的本源和出发点。
同样,在程序世界里,也有着不同的世界观。而这其中最根本的对立便是过程论和对象论的对立,这个对立,衍生出了面向过程和面向对象两种方法论。于是,要真正理解面向过程和面相对象,我们就不得不先深究一下程序世界中这两种世界观。
首先要提到的是,不论是过程论还是对象论,都承认一点,那就是程序世界本质上只有两种东西——数据和逻辑。数据天性喜静,构成了程序世界的本体和状态;逻辑天性好动,作用于数据,推动程序世界的演进和发展。尽管上述观点是统一的,但是在数据和逻辑的存在形式和演进形式上,过程论和对象论的观点截然不同。
过程论认为:数据和逻辑是分离的、独立的,各自形成程序世界的一个方面(Aspect)。所谓世界的演变,是在逻辑作用下,数据做改变的一个过程。这种过程有明确的开始、结束、输入、输出,每个步骤有着严格的因果关系。过程是相对稳定的、明确的和预定义的,小过程组合成大过程,大过程还可以组合成更大的过程。所以,程序世界本质是过程,数据作为过程处理对象,逻辑作为过程的形式定义,世界就是各个过程不断进行的总体。
对象论认为:数据和逻辑不是分离的,而是相互依存的。相关的数据和逻辑形成个体,这些个体叫做对象(Object),世界就是由一个个对象组成的。对象具有相对独立性,对外提供一定的服务。所谓世界的演进,是在某个“初始作用力”作用下,对象间通过相互调用而完成的交互;在没有初始作用力下,对象保持静止。这些交互并不是完全预定义的,不一定有严格的因果关系,对象间交互是“偶然的”,对象间联系是“暂时的”。世界就是由各色对象组成,然后在初始作用力下,对象间的交互完成了世界的演进。
上面的描述也许有些不够直观,那么,下面我们通过一个实际的例子,直观感受一下在两种世界观下,对同一件事物是怎么看的。
大家都听过这么个智力题吧:
说有甲、乙、丙三人住店,一间房30。于是每人10元,共计给店老板30元住进一间房。后来店老板发现弄错了,房价应该是25元,于是给小二5元让小二退给房客。小二黑心,贪污了2元,退给甲乙丙每人1元。这样房客每人付了10-1=9元,三九27,加上小二贪污的2元,共29元,问那1元哪里去了?
不知各位聪明的看官是否已经参透其中玄机。不过参不透也没有关系,这不是重点,重点是,我们现在来分别用过程论和对象论分析一下这件事。
首先,我们来看看过程论是怎么看这件事情的。
图1.1、过程论看世界
如图1.1所示,这就是过程论下看这件事的样子。左边是过程的各个步骤,而右边红字表示在每个过程步骤的数据情况,这种数据情况反映了世界当前的状态。为简单起见,我们只考虑在这个过程中参与分配的数据。
初始时甲乙丙各10元,老板和小二没有钱,这可以认为是这个过程的初始状态,这些数据是输入。随着各个步骤的进行,数据不断更新,而在每个步骤,数据如何更新、更新多少,都是由步骤严格确定的。经历五个步骤后,数据变为甲乙丙各1元,老板25元,小二2元,这就是终止状态,也是这个过程的输出。
下面,再来看看对象论下如何看这件事。
图1.2、对象论看世界
对象论眼中,世界是由各种对象组成的,每个对象有自己的数据和逻辑,如图1.2所示。在这件事里,有五个基本对象:甲、乙、丙、小二和老板(注意,这里我们还没有提到类和抽象等概念,所以不要让固有思维跳出来,在这里要只认识对象,不认识类等概念。现在我们只讨论世界观的基本问题:程序世界的本质,至于更具体的问题,留待后面讨论)。每个对象有自己的一系列数据和逻辑,这里只列出了我们关心的部分。
然后呢?没有然后了。没错,在对象论眼里,这就是这件事的本质模样,这件事所涉及的东西就是这么几个对象,本来它们各自独立,老死不相往来。只不过在“住店”这个外部驱动力下,几个对象“偶然”、“暂时”互相联系,利用其他对象提供的公开服务,完成了一些交互。在交互中,各自的数据可能会发生一些变化,但对象的本质没有变。这里也要注意,这种交互虽然在一定程度上由既定逻辑预定义,但不像过程论认为“万事万物都已注定”,在对象论下,对象间的交互是“偶然的”、“暂时的”,这次五个人因为住店这个外部驱动力交互了一次。但下次如果魏国和蜀国交战变为驱动力,他们间的交互就不是拿钱给钱了,而是刀兵相见。所以,对象论不认为“一切都已注定”。
通过上面一个例子,不知各位是否已经明白程序世界中两种世界观看事物的不同。下面,有一些问题还要明确一下。
I. 过程论和对象论是两种看世界的观点,没有孰对孰错、孰好孰坏之分。
II. 过程论和对象论不是一种你死我活的绝对对立,而是一种辩证统一的对立,两者相互渗透、在一定情况下可以相互转化,是一种“你中有我、我中有你”的对立。如果将对象论中的所有交互提取出来而撇开对象,就变成了过程论,而如果对过程论中的数据和逻辑分类封装并建立交互关系,就变成了对象论。
III. 过程论相对确定,有利于明晰演进的方向,但当事物过于庞大繁杂,将很难理清思路。因为过程繁多、过程中又有子过程,容易将整个世界看成一个纷繁交错的过程网,让人无法看清。
IV. 对象论相对不确定,但是因为以对象为基本元素,即使很庞大的事物,也可以很好地分离关注,在研究一个对象的交互时,只需要关系与其相关的少数几个对象,不用总是关注整个流程和世界。但是,对象论也有困难。例如,如何划分对象才合理?对于同一个驱动力,为什么不同情况下参与对象和交互流程不一样?如何确定?其实,这些困难也正是面向对象技术中的困难。
综上,我们知道在程序世界中,存在着过程论和对象论两种对立的世界观,并且其各有千秋,无法定夺孰好孰坏。但是,对象论似乎更有助于分析规模较大的事物。本文是探讨面向对象的,所以,在下文中,都会选择对象论作为世界观。这种以对象为本的世界观,也是本文后续一切的基础和出发点。
真经第二章——抽象 Abstraction
“金、木、水、火、土元素,构成宇宙万物,并作为各种自然现象变化之基础——五行说”
上文探讨了世界观问题。我们知道,要想真正理解面向对象,首先要用对象论去审视世界。而在对象论中,万事万物的本源是对象,对象是组成世界的基本元素。但是,要真正看透一个世界,只有基本元素是不行的。
中国古代的朴素唯物主义哲学中,比较有代表性的是五行说。五行说认为,世界的基本元素是“金、木、水、火、土”,但若说世界只有“金、木、水、火、土”,也是不成的,所以后续有云:五行相生相克,相互交织结合,组成了大千世界。虽然从现代科学角度看,五行说并不完全准确,但其有一点事非常正确的,那就是世界首先有基本元素,然后基本元素还要衍生出各种其它东西。
在第一章中,我们说了在对象论中,对象是组成世界的基本元素,但这还不能构成真正的世界。下面,我们来看看对象是如何构成和衍生出其它事物的。
和真实世界中构成和衍生方式不同,程序世界中,最重要的衍生方式是抽象。例如,众所周知的类(Class),就是从对象上首先抽象出来的概念。下面我们看一看类是怎么来的。
从哲学角度说,先有对象,然后才有类,类和对象是“一般和特殊”这一哲学原理在程序世界中的具体体现。这可能和很多人的直觉不同,因为在具体写程序时,是先定义类,然后才能实例化对象。在这里,我们是从哲学层面进行探讨,所以,对象是本源,类的概念是衍生。为什么?因为从认识论来说,首先有具体认知能力,才能有抽象认知能力,抽象认知能力是一种高层的,人类特有的认知能力,它使我们可以从大量具体认知中,舍弃个别的、非本质的属性,提取出共同的、本质的属性,是形成概念的必要手段。
还是以住店的故事为例吧。在我们的世界观中,那个故事涉及了五个对象,刚开始我们没有抽象的概念,而只是从具体认知角度对这五个对象进行认知:首先是甲,他有头、有身子、有胳膊有腿,头上有眼睛鼻子耳朵,他还有个名字叫刘备,有个身份是顾客……除了这些数据,这个对象还可以做一些事情,可以吃饭、呼吸、喝水,还能给钱和拿钱……好的,一通认知后,我们对甲这个对象有具体认知了;然后,我们对乙进行认知:他有头、有身子、有胳膊有腿,头上有眼睛鼻子耳朵,他还有个名字叫关羽,有个身份是顾客……除了这些数据,这个对象还可以做一些事情,可以吃饭、呼吸、喝水,还能给钱和拿钱……认知完了,接着是丙、小二和老板……当具体认知足够多后,我们发现一件事情:这几个对象很相似啊,有相似的数据(但具体值可能不同),有相同的逻辑,于是,我们的抽象认知能力告诉我们,这五个对象很相似,可以看做一类东西,于是,我们给出一个类,叫“人”,并且认为这五个对象都是“人”这个类的具体例子,我们叫其为实例。以后遇到类似的对象,我们都可以知道,这个对象属于“人”类。
图2.1、“人”类的由来
所以,类其实是抽象认知能力作用于程序世界的基本元素——对象后所衍生出来的抽象概念,是抽象思维在程序世界中物化后的产物。当然,现实世界中每个对象都有无数的数据和逻辑,但在具体到程序世界时,我们往往只关心具体场景中相关的数据和逻辑。例如,在住店场景中我们关心现金这则数据,至于这个人力气大不大无所谓;而如果上战场打仗,我们就关心攻击力和力量,现金就不重要了。
知道了类是怎么来的,那么类的作用是什么,我们为什么需要类呢?
类可以帮助我们方便地认识和定义世界中的对象。这个作用是显而易见的。例如当今世界有60几亿人,如果不会抽象思维,我们每遇到一个人,都要认知一遍:啊!这个对象有眼睛,有耳朵,有鼻子有嘴,有胳膊有腿……要是真这样,世界也太疯狂了。有了类的概念,我们就可以只记类的数据和逻辑,而对于具体对象,只要知道它属于什么“类”,一切就都知道了,所需要区分的只是不同对象的数据具有不同值而已。
其实,这不仅仅是类的作用,我们进行抽象思维,就是为了这个目的。
这一章叙述了类的哲学本质、衍生过程和作用。要记住,抽象是形成和衍生概念的基本方法,不只是类,后面的很多概念,都是通过抽象形成的。所以,我们可以说:上天只给了这个世界各种对象,但我们用抽象去更好地认识世界。
真经第三章——层次 Arrangement
“道生一,一生二,二生三,三生万物——老子”
上文提到,在对象论中,抽象是衍生概念的基本方法。但是你有没有一个疑问?所谓抽象,是对许多对象撇开个性,抽出共性,这样,抽象过程就不是确定的、唯一的。例如,我们在看过很多对象后,发现有一类对象有四个轮子、有发动机、可以驾驶、是可以被意识反映的客观实在。我们抽象出一个叫“汽车”的类。这次抽象中,我们将有四个轮子看做了共性,但是,如果撇开这条性质,仅看后三条,摩托车、轮船、飞机都符合,于是,我们又可以抽象出“机动交通工具”类。再把有发动机撇掉,自行车、脚踏三轮车,甚至马都符合,所以,又得出个“代步工具”类,最后,把可以驾驶也撇掉,只剩下“是可以被意识反映的客观实在”,如果这样,所有物质都符合,这样,就得出一个“物质”类。
这下子困难就来了,你说我家的奔驰应该归到哪一类呢?我家的奔驰和一只是不是一类东西呢?如果从前三类看,当然不是,但是从最后一个“物质”类看,又确实是一类东西。那到底哪一个对?事情究竟是怎样的?其实答案很简单:归到哪一类都正确。至于后一个问题,无法回答,因为这个问题单独问根本没有意义。为什么?
关键在于:抽象是有层次的。
上文说到,对象是基本,我们从对象上抽象出类。但是,世界可并不是一层对象一层类那么简单,对象抽象出类,在类的基础上可以再进行抽象,抽象出更高层次的类。所以经过抽象的对象论世界,形成了一个树状结构。
图3.1、抽象层次树示例
图3.1展示了一棵抽象层次树的示例。不要怀疑,在对象论中,经过初步抽象思维加工后的世界就是这样样子。本来,世界只有各个具体对象(最底下紫色文字表示的层次),这是第0层,是一切抽象的本源和起始,然后,抽象思维作用其上,抽象出初步的类,然后在既有类和对象的基础上可以再进行抽象……如此归纳下去,最终整个世界归结于树的根节点:本体。所谓本体,即万物之源、万物之本,是哲学层面上最高层次的抽象。在这里,我们将其看成是一个特殊的类,作为抽象层次树的根。
千万不要小看了这棵抽象层次树,如果能参透其中的奥秘,就能明白很多面向对象中的玄机,而且很多问题就都迎刃而解了。这种抽象层次树理论也是后续诸多内容的理论基础。例如,OO中重要的概念——继承(Inheritance)和多态(Polymiorphism),如若探究其哲学本源,就是从这里来的。
下面,对这棵树做一些必要的说明。
I. 这是一棵单根树,最顶层“本体”为唯一的根,最下层叶子节点为基本对象。一切中间节点都为类。
II. 越往上的类抽象层次越高,具体度越低,其内涵越小,外延越大;越往下的类抽象层次越低,具体度越高,其内涵越大,外延越小。说明一下,所谓类的内涵,是指类对属于自己的对象的说明力度,而外延是指类能包含的具体对象的总和。例如,家用电器这个类,其内涵是使用电作为能源并完成特定功能的家用器具,各个电冰箱、洗衣机、电磁炉、游戏机、DVD机等都在其外延之内;而娱乐家用电器这个类,作为比家用电器更低层次的类,其内涵除了“使用电作为能源并完成特定功能的家用器具”外,还要是具有娱乐功能,其内涵明显大了,但外延却缩小了,只包括了各个游戏机、DVD机等对象。
III. 抽象层次树不是从根部向下长的,而是从叶子节点向上归纳生成的。
IV. 某一个叶子节点所代表的对象可以归入所有其祖先结点所代表的类
V. 直接问两个叶子节点属不属于一个类没有意义,而要指定抽象层次才有意义。例如在较低层,一辆宝马属于汽车,而一只苍蝇属于昆虫,不是一类。但如果指定在较高层比较,两个都属于具体物质,属于一个类。
VI. 我们定义,如果一个节点CNode非叶子节点也非根节点,那么在哲学意义上,这个节点继承于其父节点PNode,并且说PNode是CNode的泛化。
VII. 我们定义,如果一个节点CNode非叶子节点也非根节点,如果强行将它看成其任何一个祖先节点ANode,并当做ANode使用,那么在哲学意义上,叫做多态性。
先说明这么多了,随着后续内容的深入,还会有更多丰富的内容进来。例如,后面会看到,所谓的“里氏代换原则(LSP)”,在哲学本质上不过是在这棵树上所加的一条限制规则,而“面向接口编程”、“低耦合、高内聚”、“依赖倒置”等一系列耳熟能详的短语,归结到哲学上也只是这棵树的一些精化。
另外,看了上面的理论,我想本章开头留下的疑问也已经烟消云散了吧。
再提示一遍,这棵树非常重要,得其精髓,就能理解诸多OO中概念、原则和方法的本质。后续讨论中,抽象层次树理论将作为重要的理论基础。
真经第四章——继承 Inheritance
“子类型必须能够替代掉其父类型——Barbara Liskov”
这一章我们讨论继承(Inheritance)。
我们先看一看继承在哲学意义上时怎么来的。对象论的世界观认为,世界的基本元素是对象,我们将抽象思维作用于对象,形成了类的概念,而抽象的层次性形成了抽象层次树的概念。接着,我们就可以定义:在抽象层次树上,除根节点和叶子节点外,任一节点CNode非严格继承其所有祖先节点所组成的集合中的任一元素,而CNode严格继承其父节点PNode。
继承概念,看似简单,若深入思考,却隐藏众多玄机。首先,继承描述的实际是抽象层次树上祖先节点与子孙节点的关系,但我个人一直不赞成使用继承(Inheritance)一词来描述这种关系,而推荐使用泛化(Generalization)一词。为什么呢?因为我们已经知道,从哲学和认识论角度来说,是先有对象,然后有类;先有子类,然后有父类,是一种自底向上形成的体系。而继承一词,明显带有自顶向下的暗示,因为往往是先有爷爷、有父亲继承爷爷、然后才能有儿子继承父亲。这样,就容易让人误解成是先有父类才有子类。所以,为了更好的体现继承的哲学本质,我更倾向于使用“泛化”代替“继承”。当然,由于继承一词已经被普遍使用和接受,接下来我还是会沿用继承一词,只不过希望各位时刻牢记,其实是先有了子类,才从子类泛化出父类。
当然,当父类被抽象出来后,可能还会有新的子类加进来。但是,当初父类一定是从某些子类中泛化出来的,而不会是凭空突然出现的。
探讨了继承的本质,然后我们来探讨继承存在的意义。一切存在的东西都是有意义的,否则就不可能存在。注意,这里的“意义”是中性词,指事物存在的原由,不要理解成褒义。
我们需要继承这个概念,本质上是因为对象论中世界的运作往往是在某一抽象层次上进行的,而不是在最低的基本对象层次上。举个例子,某人发烧了,对其他人说:我生病了,要去医院看医生。这句简短的话中有一个代词“我”和三个名词“病”、“医院”、“医生”。这四个具有名词性的词语中,除了“我”是运作在世界的最底层——基本对象层外,其他三个都运作在抽象层次,在这个语境中,“病”、“医院”、“医生”都是抽象的,他并没有在医院里拉着某个医生对别人说:我生了这个,需要去这里看这个。但是,本质上他确实是生了一个具体的病,要去一个具体的医院看一个具体的医生,那么在哲学上要如何映射这种抽象和具体呢?就是靠继承, 拿医生来说吧,所有继承自“医生”类的类所指的所有具体对象都可以替换掉这里具体的医生,这都不影响这句话语义的正确性。
所以,继承的哲学作用就是:规定了抽象与具体之间的可映射性。形式化一点说:设G(c1,c2)意为c1非严格泛化自c2,I(c,o)意为对象o属于c的外延,其中c1,c2,c均为类,o为对象。那么,c可在哲学语义上映射成o,当且仅当o∈{o|I(c,o)}∪{o|I(c’,o) 且 G(c,c’)}
如果你讨厌看形式化的东西,那么上面蓝色文字不看也罢,但是,有一条原则你一定很感兴趣,那就是著名的开放-关闭原则(OCP)。
开放-关闭原则(OCP):软件实体应该可以扩展,但不可以修改。
为什么忽然扯到OCP呢?因为,OCP正是上文讨论的哲学原理在程序世界的具体表述。我们来对比看一下,到底OCP是个什么意思。
还是上面看病那个例子,什么叫可以扩展?就是说,因为在某个抽象层次是进行表述,就不能把话说死了,不能全是这个、那个的把每个对象都指派明白。如,那句话改成“我的右脚扭到了,要去北京航空航天大学医院去看胡青牛医生”,这句话就没有扩展性可言了,所有话都说死了,你如果去的是北医三院或临沂市人民医院,那么语义就不对了,而如果找的不是胡青牛而是华佗或扁鹊,语义也不对了。为什么无法扩展?因为所有点都指定了具体的对象。
而原话“我生病了,要去医院看医生”则扩展性很大,因为只要不违反可映射性定义,映射到任何符合条件的对象都正确。扩展性和灵活性大大提高了。所以,“可以扩展”四字从哲学上其实是要我们在设计和开发软件时提高抽象层次,不要总在具体对象层面上进行处理。这下,你明白为什么说OCP可以提高软件的可扩展性和灵活性了吧。
再来说说“不可以修改”,因为如果随便乱改,那就天下大乱了。还是医院那个例子,“医院”这个类所映射到的对象,一定是治病的地方。如果这东西随便改,例如明天“医院”和“食堂”的概念对换了,那麻烦了,我们所有人都要改,要把两个概念从脑子中对换过来,全世界的书、报纸、Internet……凡是依赖这两者进行表述的地方都要改,那不是天下大乱么?软件世界中也会发生这种牵一发而动全身的问题。所以我们提倡设计好的类一定要“对修改关闭”。
以上,就是OCP的哲学意义。
不过,要想世界正常运作,只有OCP似乎还有点问题。到目前为止,我们都是在抽象层次树已经存在,并且假定它完全正确的前提下讨论的,可是,我们并没有任何规则限制抽象层次树的正确性,例如,如果我把食堂挂到医院下,让食堂成为医院的子类,在理论上时没有错的,但如果这样随便乱规定继承关系,那么一切依赖继承正确性的原则、概念都没有意义了。所以,只有OCP是不够的,需要对继承进行一个限制。
Barbara Liskov在1987年的OOPSLA大会上发表了一篇文章——《Data Abstraction and Hierarchy》,其中提出了一个非常重要的原则,叫里氏代换原则(LSP)。
里氏代换原则(LSP):子类型应该能代替掉其父类型,且代替后程序运行情况不会错乱。
我们还是用例子去理解LSP。
现代办公几乎都要用到个人计算机,个人计算机本身是一个抽象概念,台式PC是其中一个子类。后来,发明了笔记本电脑,我们想把笔记本电脑归为个人计算机的子类,是否合理呢?根据LSP,我们将台式PC都替换成笔记本电脑,世界应该是照常运行的(当然,实际情况可能复杂些,有些地方不能用笔记本电脑替换,但这里我们忽略这种差别)。我们办公时依赖的类是“个人计算机”,而笔记本电脑完全可以替代这个类型而使得世界运行正常,所以,我们说将笔记本电脑归于个人计算机的子类是符合LSP的。
后来,又发明了转基因黄瓜,我们也想将它归到个人计算机的子类中去,行不行呢?好的,现在我们再运用LSP,将世界上每个依赖个人计算机的地方都替换成一根转基因黄瓜。好的,世界人民都疯了!明显这种替换会令世界运行错乱。所以,我们不能让转基因黄瓜继承个人计算机。
上面的例子是显而易见的,但有些却不那么明显。例如,现在问,兽医是医生的子类吗?这个问题,一下子还真不是很好回答,但我们可以LSP一下,现在,我们把医院里的医生都替换为兽医,你还敢去医院看病吗?嗯,这下子不用我多说了吧。
最后一定要说明的是,LSP应用于程序世界和现实世界时有很大差别的,现实世界繁杂、不确定性因素多,而程序世界简单、确定。总之,LSP就是让你记住一条,凡是系统中有继承关系的地方,子类型一定能代替父类型,而且替换后程序运行要正常。换言之,继承是一种严格的“IS-A”关系,也是“一般和特殊”的哲学原理在程序世界中的体现。
继承的话题就讨论到这里了。很多朋友在运用继承时有疑惑,或不能很好的确定继承关系,归其根本是没有真正理解继承的意义。只要能理解继承的本质意义,加上OCP和LSP的运用,是可以写出正确的继承体系。
真经第五章——耦合 Couple
“一只蝴蝶在巴西轻拍翅膀,可以导致一个月后德克萨斯州的一场龙卷风——蝴蝶效应”
做程序的人,往往感觉“耦合(Couple)”不是什么好东西。经常有人、有书、有文章对我们谆谆教导:要降低耦合,要降低耦合……久而久之,好像耦合在程序界成了贬义词,弄得我们恨不得把耦合从程序里全部拿掉。
这误解可委屈耦合了。要是哪天没了耦合,这世界还真玩不转。其实耦合还有另一个名字,叫“联系”,试问要是世界上所有对象间的联系都没了,世界还能运作么?耦合的存在是世界演进的途径,如果没有耦合,世界就变成了“死世界”,无法演进和发展。所以,耦合可是好东西,我们要感谢它!但是任何东西都有两面性,过度的耦合确实会令世界的运作产生困难,所以我们提倡降低耦合,这些是后话。
下面,我们探讨各种耦合式怎么出现的。
上一章讲述了继承,其实,继承的概念出现后,有父子、祖孙关系的类就有了一种联系,这种联系叫做“泛化耦合”。这就是我们认识的第一种耦合。
泛化耦合(Generalization Couple):由于泛化(继承)关系的存在,在两个有祖孙、父子关系的类间形成的一种逻辑关联。
然后,我们讨论另一种耦合。
在文章开始,我们说对象论将对象看做基本元素,而对象中有数据和方法。在现实世界中,数据并不总是简单数据。客观存在一些对象,它们的数据是另一个或另一些对象。例如,一个具体的羊群,有一项数据是很多具体的羊。其中羊也是对象。当抽象成抽象的“羊群”和“羊”类的时候,这种包含关系也随之被抽象到了类中,由此在两个类之间就形成了耦合。
这种耦合出现的哲学基础是,对象本身固有的包含关系,在进行事物抽象时被同时抽象到了类中。所以,我个人将其称为包含耦合。
包含耦合又分为两种情况,一种是被包含对象单纯聚合在包含对象中,但没有形成哲学意义上“整体与部分”的关系,这是一种相对较弱的联系,叫做聚合。例如,上例中羊群和羊就是聚合关系,如果拿掉一两只羊,羊群还是羊群。
聚合(Aggregation):一种弱的拥有关系,体现A对象可以包含B对象,但B对象不是A对象的一部分。
另一种情况是,被包含对象和包含对象形成了哲学意义上“整体与部分”的关系,如汽车和轮子,把轮子拿掉,汽车就不再是完整意义上的汽车了。这种关系叫做组合。
组合(Composition):一种强的拥有关系,体现了严格的部分和整体的关系,部分和整体具有一样的生命周期。
通过上面的探讨,我们认识了泛化耦合、聚合和组合三种耦合形式,最后,还有一种耦合叫依赖。什么是依赖呢?我们知道,在对象论中,将世界的演进看成是在初始作用力下,对象之间相互调用、相互协作完成的。如果两个类在需求范围内,既定逻辑上存在协作的可能,那么这两个类就存在依赖关系(或叫关联关系)。其实,我们常说的“低耦合,高内聚”、“降低耦合”等建议,主要是针对依赖说的。
依赖(Dependency):由于逻辑上相互协作可能,而形成的一种关系。
好的,到目前为止,我们已经认识了四种基本耦合。下面用一副图,直观感受一下世界的各种耦合。
图5.1、耦合示例
图5.1展示了几种耦合的示例。其中汽车和交通工具属于泛化耦合,轮子和方向盘组合于汽车,汽车聚合成车队,而汽车和司机具有依赖关系。这幅图只是耦合的一个小片段,实际上,世界上各种对象形成了一张复杂的耦合网,正因为有耦合的存在,世界才能演进。联系是普遍的、客观的。所以,耦合的存在,有其深刻的哲学意义。
不知你是否会有这样的疑问:文章开始,不是说对象论将对象看做相互独立的吗?怎么又耦合起来了。这是矛盾的吗?实则不矛盾。因为我们所处的境界已经不同。刚开始,我们抛开一切,忘记一切,从本质的角度用对象论去看世界,我们看到的对象是相对孤立的。而后来,我们的抽象思维作用于这个世界,所衍生出来的一系列概念,是我们的抽象能力给这个世界抹上的色彩。就如我们用唯物主义看世界时,刚开始要抛开一切,认为世界只有“可被意识所反映的客观实在”,而后,这个物质为本的世界在我们的抽象思维中衍生出各种概念。为了让我们更好的、系统的认识对象论,刚开始,我们抛开一切直取本质,而后来,我们要层层衍生,将抛却的东西再找回来,在这个“找”的过程中,我们才能领会OO中的各种概念、事物其在哲学意义上是怎么来的。
要想真正描述一个世界,仅有结构式不行的。开始我们说过,世界观主要关注两个方面:一是世界是什么样子的(结构),另一个就是世界时如何演进的(运作)。现在,我们来讨论对象论中关于世界运作的理论。
[1] 世界本没有类[2] 程序世界[3] 依赖是如何被倒置的
真经第六章——运作 Moving
“运动是绝对的——牛顿”
在前五章中,我们从世界观的这话题开始,逐步引出了抽象、层次、继承和耦合。这些内容,形成了对象论中关于世界的结构体系。
然而,要想真正描述一个世界,仅有结构式不行的。开始我们说过,世界观主要关注两个方面:一是世界是什么样子的(结构),另一个就是世界时如何演进的(运作)。现在,我们来讨论对象论中关于世界运作的理论。
这里首先要指出一点,“对象论”是关于程序世界(即将一个软件系统看成一个世界)的世界观,而非关于现实世界的,所以,将对象论应用于现实世界时,往往会有所偏颇。其实前面的某些地方已经体现出这一点,而在运作理论这里,会体现的尤其明显。但是为了直观起见,我依然会将对象论应用于现实世界去举例子,当然我会非常谨慎和小心,并且会明确指出对象论应用于现实世界的偏颇在哪里。
6.2、世界本没有类
对象论认为:世界的演进,是而且只是各种对象通过互相调用其他对象的公开服务而完成交互。
注意,是对象交互,而不是类交互!没错,类之间是永远不可能交互的。因为不论是现实世界还是程序世界,从来不存在具体的类。类只是抽象思维作用于对象的产物,它帮助我们理解、记忆、分析和设计。类是抽象的概念,它“客观”存在,但不是“具体”的存在。
例如,现实世界中,我们可以找出很多个“具体的苹果”对象,但是你能找出一个东西,说它是“苹果”这个类吗?你这一辈子吃的每一个苹果,都是一个具体的苹果对象,从来没有具体的“苹果类”和你交互过。再上升一点,你一生交互过的所有东西,都是对象,而没有一个具体的类。“类”不过是你的抽象思维作用于对象形成的帮助你理解认识世界的抽象概念罢了。“类们”从不曾和你真正交互。
程序世界中也是一样,程序运行起来,从来都是具体对象之间的交互,类只是帮助你分析设计的概念工具罢了。
认识到上面几点对于理解对象论的世界运行理论非常重要,时刻铭记,参与真正世界运行的,只有对象,没有类!对象在世界中,类在我们心中!
这一小节的标题是“世界本没有类”,代表两个意思:一是世界“本来”没有类,二是世界“本质”没有类。
你可能会问,在第五章“耦合”中,不是说依赖关系是“两个类因为可能交互而产生的关系”吗?其实,确切点说,应该是“两个类所能映射到的对象因为可能交互而产生的关系”,本质上,依赖本来是对象间的依赖,只不过在抽象时被同时抽象到类里面了。
6.3、程序世界——大同的和谐世界
虽然在对象论里,现实世界和抽象世界的基本运作机理是一样的,但程序世界和现实世界在具体运作上有很大差别。首先,我要告诉你,程序世界时多么的大同和和谐!
程序世界与现实世界第一点区别:现实世界的依赖以对象为单位,程序世界的依赖以类为单位。
没明白这意味着什么?
举个例子,在现实世界中,是不是关系很重要啊。为什么?因为你认识的人多,可依赖的人就多。例如你生病了,如果你有个医生朋友,看病就方便很多;如果你要打官司,而你又恰巧认识律师朋友,是不是很爽呢;如果你想上清华大学,刚好清华大学校长是你亲戚,那一切就好办多了是吧。
为什么会这样?究其本质,是因为现实世界中对象间的依赖是以对象为单位的,这种依赖关系不会随着泛化过程而被泛化到类里面去。例如,有一个人现在在北京航空航天大学上学,从这“一个人可”以泛化出“人”这个类,而北航可以泛化出“大学”这个类,但这个具体的人和北航的这种关系可没有被泛化到两个类中,也就是说,并不是每一个“人对象”都可以去任何一个“大学对象”去上学的。
不过,如果是程序世界里,上面的推理是可行的,因为程序世界中对象间的依赖是以类为单位的,这种依赖关系会随着泛化过程而被泛化到类里面去。并且,只要两个类建立了依赖,那么两个类之间的所有对象都两两依赖了。换句话说,在程序世界里,只要有一个“人”和一个“大学”发生了联系,那么这种联系就被泛化到类中了,随后,所有的“人”都可以上“任何”的大学。
图6.1、两个世界中依赖的区别
看图6.1,假设世界上只有三个人和三所大学。在现实世界中,小龙女考上了清华,不过这和其他人其他大学一点关系也没有,这种关系并没有体现在类上,看,两个类没有任何联系。但在程序世界中,小龙女考上了清华,一下子人和大学两个类就关联起来了,接着,张无忌和郭靖这两个不好好学习的学生也沾了光,和三所大学都联系起来了。(提示:其实这里和第四章讲到的OCP和LSP联系非常紧密,读者可以联系OCP和LSP两个原则自己思考一下为什么程序世界会这样。)
你知道了吧,在程序世界里,全世界的医生随你看,律师随你用,大学随你上,美食随你吃!多么和谐大同的美好世界!
看了上面对程序世界的描述,你是不是已经垂涎三尺了?恨不得自己变成一段代码,跑到程序世界里。不过别着急,事情也许没有你想象的那么美好。下面我们来看另一个程序世界与现实世界的区别。
6.4、程序世界——封建的专制世界
上文描述了程序世界是多么多么美好,
resin常见有关问题
resin常见问题1.1. Resin停止响应
●可能是一个线程死锁的问题,应该进行线程转储。
●启用完全调试日志模式,检查日志最后的纪录看看发生了什么。
1.2. Resin不停重启动
●启用完全调试日志模式,检查记录看看Resin为什么不停的重启它。
1.3. java.lang.OutOfMemoryError错误,应用程序内存溢出
●使用JVM启动参数增加堆(heap)内存。
●转储堆,看看那个对象无法被垃圾回收器无法回收。
●转储线程,检查占用着对象的不能释放的线程
一个OutOfMemoryError错误通常意味着堆(heap)内存被用尽。一般是应用程序代码保持了对不在使用的对象的引用,垃圾回收器无法对其进行回收。转储堆,能够查到什么代码和什么种类的对象被占用了。如果对转储或者其它监视工具显示服务器和你的程序实际没有超出堆内存,那么 OutOfMemoryError意味着JVM超出了虚拟内存,也就是底层的malloc()调用失败。通常这种情况,通过使用操作系统工具显示内存使用,JVM自己能够显示其自己的堆内存,但是操作系统工具确显示进程占用了大量的内存。在Windows下使用任务管理器,Unix下使用top或者ps 命令。
JVM无法进行堆内存分配可能有如下原因:
●线程,特别是线程堆占用虚拟内存。
●JNI库可能调用malloc或者nmap占用虚拟内存。这包括很多数据库驱动,也包含一些Resin使用的JNI代码。
●对于.jar/.zip文件,JDK要分配虚拟内存。如果你打开了大量的jar文件,你可能会遇到问题。可以想到用于打开jar的getResourceAsStream没有关闭将会耗尽.jar内存。
1.4. 运行一会儿,服务器开始变得非常慢
● 这可能是一个垃圾回收问题。如果你的内存缺乏,然后又创建了大量的对象,这导致垃圾回收器耗尽CPU。如果你内存溢出,JVM将会慢慢停止(连续地进行垃圾收集)直到它死亡。
○ 监视垃圾收集。
○ 转储堆,看看是否是有对象无法被回收。
○ 参看JVM垃圾回收参数调整的文档获得更多垃圾回收的信息。
● 可能有一个死循环的线程或者一个请求耗尽资源。回应一个请求的线程如果不能返回,Resin就没法再次利用它,那么可用来服务的线程就会越来越少。
○ 进行线程转储,检查可能占用对象的无法释放的线程。
1.5. CPU尖峰,高的CPU使用率
● 转储线程,检查那些线程在无限循环。
● 检查垃圾收集的部分。
1.6. 会话(sessions)变成null,会话丢失
1.6.1. 调试日志
首先启用调试日志。特别是浏览器请求提交的头信息能够显示一个客户端的JSESSIONID状态,日志也能说明Resin什么时候识别、创建和失效一个会话。
1.6.2. Resin会话配置
另一个可能是session-max设置过低,导致当前用户建立会话的数量大于你设置的这个值。另一个可能是会话超时,你可以通过session-timeout标签来配置它。
&web-app id='/'&
&session-config&
&!-- timeout after 120 minutes --&
&session-timeout&120&/session-timeout&
&!-- up to 4096 sessions at once --&
&session-max&4096&/session-max&
&/session-config&
&/web-app&
1.6.3. 应用程序重载
无论何时,一个java源文件、web.xml或者resin.xml改变,Resin都会重启应用程序。如果这个情况发生,你当前的会话就会丢失,除非你配置了一个持久性会话存储。
1.6.4. 浏览器cookie的局限
一些用户报告,如果他们的应用程序使用大量的cookie,浏览器将会丢弃旧的cookie为新的腾出空间。这就会出现浏览器丢失了Resin 用来跟踪会话的cookie。IE浏览器用户特别容易遇到这个问题。如果你的应用程序使用大量的cookie,最好的解决方案就是减少cookie数量和 cookie数据的大小。Resin使用一个单一的cookie其存储相对很少的数据用拉跟踪用户的会话ID。应用程序存储在cookie中的信息可以使用HttpSession对象来存储。作为最后的手段,你可以配置Resin总是使用URL重写,这需要把enable-cookies设置成 false。由于安全的原因URL重写式不推荐的,因为重写URL增加了重写某些页面丢失调用的高可能性。
&web-app id='/'&
&session-config&
&enable-cookies&false&/enable-cookies&
&enable-url-rewriting&true&/enable-url-rewriting&
&/session-config&
&/web-app&
1.6.5. cookie域名的问题
如果你的cookie域名不兼容也可能丢失会话。例如,如果你有一个服务器使用cookie域名"",另一个使用 "",在浏览器中""的cookie会干扰在""上的会话。方法是改变cookie域名""为""。 你可以在session-config标签中设置 cookie域名。
1.6.6. cookie名称冲突
如果你使用Resin和另一个应用服务器(例如Tomcat),你可能遇到这个冲突,因为它们使用相同的cookie名称(他通常是 JSESSIONID) 来跟踪会话。Resin提供session-cookie 和 ssl-session-cookie让你可以改变Resin使用的cookie名称。
改变用来跟踪会话的cookie名称的片断:
&session-cookie&RJESSESSIONID&/session-cookie&
1.6.7. URL重写
如果你忘记了重写一个URL,一个需要重写的用户当访问到这个URL时将丢失他们的会话。Resin在一个用户浏览器和一个会话 (session)之间建立一个关联,是通过为每一个新请求返回一个惟一的id。这可通过两种方式之一来完成:使用cookie或者URL重写。 Resin首先尝试向用户浏览器发送一个包含惟一会话ID的cookie来跟踪一个用户的会话。有时Resin不能建立cookie,不是因为用户在其浏览器禁用了cookies就是因为某些浏览器不支持它们(例如一些HDML和WML浏览器)。如果cookie不能建立那么就会使用URL重写。在这种情况下,Resin重写每一个它提交给用户的URL,让其包含一个名称为_jsessionid的参数。然后为每一个新来的请求做的第一件事就是查找这个参数,如果这个参数存在那么就知道一个会话已经建立,它移出参数并使用它来查找用户会话对象。URL重写需要开发者的协同合作。开发者必须编码每一个URL 引用让Resin有一个合适的机会放置_jsessionid参数。
使用JSTL实现URL重写
&%@ taglib prefix='c' uri='/jstl/core' %&
Time to go &a href="&c:url _fcksavedurl="&c:url _fcksavedurl="&c:url value='home.jsp' /&"&Home&/a&!
使用Java scriptlet实现URL重写
String homeUrl = response.encodeURL("home.jsp");
&%-- the presentation --%&
Time to go &a href="&%= homeUrl %&"&Home&/a&!
1.7. J2EE规范,javax.servlet包规范1.3和Resin不兼容
参看清除classpath环境变量。
1.8. Unsupported major.minor version 48.0
这个错误经常在发现一个冲突的jar时发生,参看清除classpath环境变量。
如果环境变量classpath被完全清除,然而一个JDK或者旧Resin的一个jar或者一些其它组件出现在的什么地方,如果你已经在那些地方添加了,在你的JAVA_HOME树里的一些jar可能有一个问题,那里可有一个和你的web程序WEB-INF/lib/目录下冲突的jar。另一种可能是你还没设置JAVA_HOME,或者你使用了一个冲突的JDK的一些组件。
如果在Windows上,检查JAVA_HOME之外的java.exe的拷贝,例如C:/WINDOWS/java.exe或者在你PATH路径里其它地方的java.exe。
1.9. 读取POST数据的问题
首先启用调试日志。调试日志会显示发送到Resin的请求,提供一些Resin如何处理这些数据的信息。最重要的是确保在读取POST参数之前编码设置正确。浏览器总是发回和输出页面编码相同的参数。因为请求不包含编码,应用程序代码需要确保编码匹配。因此第一件事就是确定发送到浏览器的表单的编码。你的应用程序总应该指定它。一旦你指定了它,你就知道浏览器POST使用编码。(这里UTF-8是个自然的编码选择,我不能确信你为什么使用其它的编码)。在读取POST参数之前确保设置了正确的编码,你可以调用request.setCharacterEncoding(encoding)来设置编码。
2. 技巧方法
2.1. 启用调试日志
Resin使用JDK日志工具提供了大量的诊断信息。通过使用一个空名称(匹配所有名字)可以启用完全调试日志,调试级别为“全部”。因为将会产生大量信息,把这些信息放在一个单独的文件中比较好。
2.1.1. 服务器和所有应用程序的完全调试日志
下面的配置每天创建一个日志,当一个问题出现时用来查找问题出现在什么地方。因为日志配置在resin.xml中,日志的捕捉是服务器和其上所有应用程序的。日志输出的信息在 $RESIN_HOME/log/debug.log。
&!-- resin.xml --&
&resin xmlns="/ns/resin"&
&log-handler name="" level="all" path="log/debug.log"
timestamp="[%H:%M:%S.%s] {%{thread}} " /&
&logger name="" level="finer" /&
有其它一些的日志配置选项,请参看Resin日志文档。
2.1.2. 一个web应用程序的完全调试日志
通常你一般仅需要一个程序输出的调试日志。日志配置记录放在&web-app-root&/WEB-INF/web.xml中,那么仅这个web应用程序的日志信息被输出到日志文件中。下面的配置每天创建一个调试日志,位置&web-app-root&/WEB-INF /work/debug.log。
&!-- &web-app-root&/WEB-INF/web.xml --&
&log name="" path="WEB-INF/work/debug.log" timestamp="[%H:%M:%S.%s] {%{thread}} " /&
&logger name="" level="finer" /&
&/web-app&
2.2. 线程转储
如果应用程序好像有问题或者超出资源泄露,线程转储能够显示服务器的状态。对于服务器调试Java的县城转储是一个重要的工具。因为 Servlet是多线程的,没有处理好的话很可能出现死锁,或者出现死循环和导致内存溢出错误。特别是你使用了第三方软件例如数据库、EJB和Corba ORBs。
2.2.1. 使用JDK5工具转储线程
在JDK5里可以使用jps和jstack,一个快捷的命令行方法获得当前所有线程的堆栈跟踪信息。
20087 Resin
# jstack 20087
Attaching to process ID 20087, please wait...
Debugger attached successfully.
Client compiler detected.
JVM version is 1.5.0-beta2-b51
Thread 12691: (state = BLOCKED)
- java.lang.Object.wait(long) (C information may be imprecise)
- com.caucho.util.ThreadPool.runTasks() @bci=111, line=474 (Compiled frame)
- com.caucho.util.ThreadPool.run() @bci=85, line=423 (Interpreted frame)
- java.lang.Thread.run() @bci=11, line=595 (Interpreted frame)
Thread 12689: (state = BLOCKED)
- java.lang.Object.wait(long) (C information may be imprecise)
- com.caucho.util.ThreadPool.runTasks() @bci=111, line=474 (Compiled frame)
- com.caucho.util.ThreadPool.run() @bci=85, line=423 (Interpreted frame)
- java.lang.Thread.run() @bci=11, line=595 (Interpreted frame)
2.2.2. 通过发送一个信号转储线程
在 Windows, ctrl-break会产生线程转储。
在Unix, "kill -QUIT" 会产生线程转储。
2.2.3. 如果发送信号无效时的线程转储
你可以在启动JVM时指定附加的参数允许附加一个调试器而不是发送信号来转储线程。你然后在任何时候附加调试器来得到线程转储。 这种方法在所有的操作系统上得到支持。
下面是是逐步的指导:
1. 使用附加的参数启动Resin来允许一个调试器附加:
resin.xml for debugging
&resin xmlns="/ns/resin"&
&cluster id=""&
&server-default&
&jvm-arg&-Xdebug&/jvm-arg&
&jvm-arg&-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5432&/jvm-arg&
&/server-default&
&server id="" address="127.0.0.1" port="6800" /&
&/cluster&
2. 等待,直到你认为应用程序出现了死锁或者失去控制。
3. 打开另一个终端 (window), 使用jdb连接正在运行的Resin实例:
$JAVA_HOME/bin/jdb -connect com.sun.jdi.SocketAttach:hostname=localhost,port=5432
jdb会显示类似如下信息:
Set uncaught java.lang.Throwable
Set deferred uncaught java.lang.Throwable
Initializing jdb ...
4. 使用 "suspend" 命令, 然后 "where all"命令获得一个线程转储:
例子: jdbc suspend
All threads suspended.
& where all
tcpConnection-6862-3:
[1] java.lang.Object.wait (native method)
[2] com.caucho.server.TcpServer.accept (TcpServer.java:650)
[3] com.caucho.server.TcpConnection.accept
(TcpConnection.java:208)
[4] com.caucho.server.TcpConnection.run (TcpConnection.java:131)
[5] java.lang.Thread.run (Thread.java:536)
tcpConnection-543-2:
[1] java.lang.Object.wait (native method)
[2] com.caucho.server.TcpServer.accept (TcpServer.java:650)
[3] com.caucho.server.TcpConnection.accept
(TcpConnection.java:208)
[4] com.caucho.server.TcpConnection.run (TcpConnection.java:131)
[5] java.lang.Thread.run (Thread.java:536)
5. 使用 "resume" 命令来恢复进程
Unix 用户(和Windows上的Cygwin用户)可以使用一个脚本:
resin-thread-dump.sh
echo -e "suspend\nwhere all\nresume\nquit" | $JAVA_HOME/bin/jdb -connect \
com.sun.jdi.SocketAttach:hostname=localhost,port=5432
虽然没有进行过严格基准测试,好像使用线程转储参数启动的JVM在性能上影响不大。
2.2.4. 理解线程转储
在任何情况下,你会最终得到类似如下的跟踪调试信息(不同的JDK有稍微的差别):
Full thread dump:
"tcpConnection-8080-2" daemon waiting on monitor [0xbddff000..0xbddff8c4]
at java.lang.Object.wait(Native Method)
at com.caucho.server.TcpServer.accept(TcpServer.java:525)
at com.caucho.server.TcpConnection.accept(TcpConnection.java:190)
at com.caucho.server.TcpConnection.run(TcpConnection.java:136)
at java.lang.Thread.run(Thread.java:484)
"tcpConnection-8080-1" daemon waiting on monitor [0xbdfff000..0xbdfff8c4]
at java.lang.Object.wait(Native Method)
at com.caucho.server.TcpServer.accept(TcpServer.java:525)
at com.caucho.server.TcpConnection.accept(TcpConnection.java:190)
at com.caucho.server.TcpConnection.run(TcpConnection.java:136)
at java.lang.Thread.run(Thread.java:484)
"tcpConnection-8080-0" daemon waiting on monitor [0xbe1ff000..0xbe1ff8c4]
at java.lang.Object.wait(Native Method)
at com.caucho.server.TcpServer.accept(TcpServer.java:525)
at com.caucho.server.TcpConnection.accept(TcpConnection.java:190)
at com.caucho.server.TcpConnection.run(TcpConnection.java:136)
at java.lang.Thread.run(Thread.java:484)
"tcp-accept-8080" runnable [0xbe7ff000..0xbe7ff8c4]
at java.net.PlainSocketImpl.socketAccept(Native Method)
at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:413)
at java.net.ServerSocket.implAccept(ServerSocket.java:243)
at java.net.ServerSocket.accept(ServerSocket.java:222)
at com.caucho.server.TcpServer.run(TcpServer.java:415)
at java.lang.Thread.run(Thread.java:484)
"resin-cron" daemon waiting on monitor [0xbe9ff000..0xbe9ff8c4]
at java.lang.Thread.sleep(Native Method)
at com.caucho.util.Cron$CronThread.run(Cron.java:195)
"resin-alarm" daemon waiting on monitor [0xbebff000..0xbebff8c4]
at java.lang.Thread.sleep(Native Method)
at com.caucho.util.Alarm$AlarmThread.run(Alarm.java:268)
"Signal Dispatcher" runnable [0..0]
"Finalizer" daemon waiting on monitor [0xbf3ff000..0xbf3ff8c4]
at java.lang.Object.wait(Native Method)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:108)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:123)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:162)
"Reference Handler" daemon waiting on monitor [0xbf5ff000..0xbf5ff8c4]
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:420)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:110)
"main" waiting on monitor [0xbfffd000..0xbfffd210]
at java.lang.Thread.sleep(Native Method)
at com.caucho.server.http.ResinServer.waitForExit(ResinServer.java:674)
at com.caucho.server.http.ResinServer.main(ResinServer.java:821)
at com.caucho.server.http.HttpServer.main(HttpServer.java:95)
每个线程都被命名了。这里有一些通用的名称:
tcp-accept-8080
在8080端口监听新连接的线程
tcpConnection-8080-3
处理从8080端口连接的servlet线程
Resin的run-at线程
Resin的警告线程
Resin为每一个&http&和&srun&开启一个 tcp-accept-xxx
线程,tcp-accept-xxx 线程总是处于socketAccept状态。应该有一些tcpConnection-xxx-n线程,每一个都是一个servlet线程。在一个忙碌的服务器上,这些能在你代码里任何地方出现。如果几个出现在一个位置,你可能有某种死锁或者至少一个慢锁。空闲线程不是tcpAccept就是 httpRequest 或者runnerRequest。对于死锁,你应查看"waiting on monitor"线程和很多线程阻塞在同一位置的任一实例。
2.3. 内存溢出和垃圾收集
大部分内存问题时应用程序设计上的内存漏洞。例如,一个缓存或者vector填充了过期的数据,或者一个singleton或者静态变量不能适当地侦测到web-app重启。大部分怪异的内存问题是堆内存或者虚拟内存溢出,当使用了大量的线程(〉256)。
追踪捕获内存问题的步骤是:
1. 使用 resin.sh start or resin.exe -install启用 -J-verbosegc。 -verbosegc标志记录堆的垃圾收集,让你知道你是否堆内存溢出了(大部分情况是这样的)。
2. 获得一个heap profiler或者在JVM中使用堆转储。JProfiler是一个价格便宜的商业的heap profiler.虽然JVM的堆转储不是很用户友好,但是它也是可用的。你应该使用一个heap profiler作为你开发过程的一部分,在任一产品投入使用前应该使用一个。
使用heap profiler, 找到2-3个过量消耗内存的用户并修正这些内存漏洞。
4. 一般应用程序错误包括:
○ 在每一个请求(request)结束ThreadLocal变量没有正常清除。
○ 单一模式(Singleton) 或者静态散列影射和缓存,web-app重启要清除。
○ web-app重启后衍生出来的线程不能被停止。
○ web-app 变量 (像 "application" 变量), 被存储在一个静态变量中。
5.如果堆没问题,例如 -verbosegc显示了一个稳定的堆,你应该看看非堆内存:
○ 线程栈的使用(-Xss2m). 每一个线程的消耗一些非堆内存。一些系统默认是8M。在一些32位系统上虚拟内存的限制大约是2G,256个线程,每个消耗8M,就能耗尽虚拟内存。你可以减小栈内存通过使用 -Xss指令。
○ Java 本地接口内存(JNI memory)。如果你使用JNI库或者使用了利用JNI的驱动,JNI分配了比可用内存更多的内存是可能的。
○ fork/exec 和 OS 限制. 如果操作系统没有足够的交换空间可用,例如操作系统可能拒绝一个"jikes"编译。
○ NIO, 内存影射, 和 .jar 文件. JDK在内存中影射jar文件。在某些情况下,大量的jar文件能够耗尽虚拟内存。这种情况也出现在NIO内存映射中。
6. 如果所有这些情况都被排除,它可能是一个Resin的BUG。然而你应该在报告Resin BUG之前找到了这个内存漏洞,例如在报告BUG之前你已经进行了上面所有的检查。关于内存溢出的BUG报告,如果没有得到一个JDK内存转储,它更有可能是一个程序上的错误。你必须在报告任一潜在的Resin内存问题时提供一个堆转储。
2.3.1. -verbosegc
-verbosegc是一个JVM的调试输出。对于检查基本的内存使用和垃圾收集时间它是一个非常方便的工具。对于任一产品系统使用 -verbosegc 是一个好主意。当启动Resin时,你可以使用-J-verbosegc。
特定的输出依赖于JVM,一些内容看起来如下:
-verbosegc output
[GC 9176K-&K), 0.0014790 secs]
[GC 9287K-&K), 0.0011120 secs]
[GC 9308K-&K), 0.0007810 secs]
"(9768K)"是非常重要的数据,表示最大可分配的堆大约是10M。其它数值显示了实际的堆使用在垃圾收集前后。
2.3.2. 使用堆转储检查内存使用
如果一个应用程序过分地消耗内存直到抛出内存溢出错误,或者好像在垃圾收集上消耗了大量的时间,一个堆转储能够帮助你找到问题的根源。真正需要你去做的是有一个CPU和堆调试程序(profile)。JDK自带一个简单的(界面不是很用户友好),因此不必一定需要买一个profile。 jvmstat就是一个简单的堆监视器。它是一个标准的java参数,因此"java -Xrunhprof:help"会告诉你如何启动它。例如你可以如下启动Resin
& resin.sh -J-Xrunhprof:heap=sites,cpu=samples
(在Unix上, Resin启动脚本有个 -cpuprof-ascii 参数被自动设置.)
运行一个负载一定的时间(你可以运行类似Apache "ab"工具10分钟时间),然后正常停止服务器,你不应使用crtl-C杀死它,你需要一个正常的退出。它会转储一个 java.hprof.txt 文件。在这个文件的尾部查看跟踪信息。
2.3.3. 理解 java.hprof.txt 文件中的栈信息
假设你采用廉价方案,使用JDK的堆调试器而不是购买一个,你就需要帮助来解释它。下面是一个运行中的Resin堆转储的一个例子。在这个例子中你要跳到"SITES BEGIN" 开始的段落。对于这大部分信息,你仅需要注意上面的20行。别的其它的都是杂乱信息,忽略它。
SITES BEGIN (ordered by live bytes) Tue Jan
9 17:44:33 2001
stack class
bytes objs
bytes objs trace name
1 11.87% 11.87%
9.89% 21.76%
9.09% 30.85%
4970 [L&Unknown&;
5.83% 36.68%
5.74% 42.42%
4.35% 46.77%
2.97% 49.74%
2.37% 52.11%
1.88% 53.99%
1.78% 55.77%
1.53% 57.30%
1.34% 58.64%
1213 sun/io/CharToByteISO8859_1
1.25% 59.88%
5942 java/lang/Class
1.21% 61.10%
5003 [L&Unknown&;
1.21% 62.31%
5005 [L&Unknown&;
1.07% 63.38%
0.79% 64.18%
0.79% 64.97%
4 27630 [C
0.70% 65.67%
0.68% 66.35%
有两个需要查找的。首先,如果任何一个类在"live objs"列数值大,你需要分析它。 那可能有内存漏洞。第二,如果一些类在"alloc'ed objs"列数值大,这可能浪费了大量的垃圾收集时间,你可以使用缓存来解决它。
在类名称中的 [C 意味着一个字符数组。要知道它到底表示什么,你需要查看栈跟踪 (3271):
TRACE 3271:
java/lang/String.&init&(String.java:244)
com/caucho/util/CharBuffer.close(CharBuffer.java:714)
com/caucho/vfs/FilesystemPath.normalizePath(FilesystemPath.java:162)
com/caucho/vfs/FilesystemPath.schemeWalk(FilesystemPath.java:127)
那是 Resin的VFS代码部分。也许在将来会尽力减少它。你使用 "-prof-depth 10"参数能得到更长的信息。(或者在-Xrunhprof指定相应的深度)。那会通常会给出更多的信息。
2.3.4. 理解 java.hprof.txt 文件中的CPU信息
CPU信息比较容易理解。在一些JDK,你需要禁用JIT来运行它。
CPU SAMPLES BEGIN (total = 424614) Tue Jan
9 17:44:33 2001
count trace method
1 21.36% 21.36%
9 com/caucho/server/http/VirtualHost.logAccess
2 10.84% 32.20%
4 java/net/SocketInputStream.socketRead
5.99% 38.19%
2 java/lang/Class.newInstance0
5.11% 43.31%
2 com/caucho/util/CharBuffer.toString
4.82% 48.13%
2 sun/io/CharToByteISO8859_1.convert
3.54% 51.66%
1 sun/io/CharToByteConverter.&init&
2.68% 54.35%
1 java/io/PrintWriter.&init&
2.47% 56.82%
1 com/caucho/server/http/Request.fillCookies
2.27% 59.09%
sun/io/ByteToCharConverter.&init&
1.85% 60.94%
java/lang/String.&init&
1.59% 62.53%
java/lang/String.substring
1.57% 64.10%
java/lang/String.getBytes
0.92% 65.02%
java/lang/String.&init&
0.76% 65.78%
com/caucho/vfs/FilePath.fsWalk
0.75% 66.53%
com/caucho/server/http/Request.fillCookie
0.71% 67.25%
java/lang/String.getBytes
0.71% 67.95%
com/caucho/util/CharBuffer.close
0.68% 68.63%
java/lang/String.&init&
0.66% 69.29%
com/caucho/vfs/FilePath.openWriteImpl
0.61% 69.90%
java/io/FileOutputStream.&init&
你仅需要注意顶部的20行。你可能需要忽略顶部10行的一些信息,因为它们仅仅是等待一个用户的回应。SocketInputStream.socketRead是一个例子。你可使用跟踪号莱调用跟踪信息:
TRACE 7266:
com/caucho/server/http/VirtualHost.logAccess(VirtualHost.java:487)
com/caucho/server/http/Application.logAccess(Application.java:1846)
com/caucho/server/http/Response.finish(Response.java:1345)
com/caucho/server/http/Request.finish(Request.java:416)
2.3.5. 监视垃圾回收
使用附加参数-Xloggc:gc.log 运行Resin, "gc.log" 是日志文件的名称,其将会在Resin根目录创建,例如 /resin/gc.log。一旦服务器在一定负载下运行一定时间,或者开始出现了问题,查看 gc.log文件,并搜索"Full"。开始它出现的不是很频繁,往底部查看,将会变得越来越频繁知道连续出现。注意在第一列的"timestamp"是进程已运行的秒数。垃圾收集日志会对性能有轻微的影响,但是它对诊断与垃圾收集的相关问题是很重要的。过多的垃圾收集的可能原因是内存泄露和不充足的堆内存。
2.3.6. 增加堆内存
参看JVM微调中有关内存部分的内容。
2.4. 清空classpath
旧的或者不兼容的类版本经常引起冲突。摒除这些类的第一个步骤是使用一个空的CLASSPATH环境变量来启动Resin。
win& set CLASSPATH=
win& bin/resin.exe
unix.sh& export CLASSPATH=""
unix.sh& bin/resin.sh
如果你已经在$RESIN_HOME/lib目录或者你的JDK目录放置了一些jar文件,也同样可能引起冲突。
如果在一个旧版本的Resin上安装一个新的Resin(例如安装在相同目录),一些旧的jar可能残留。最好给每一版本独立的目录。
如果RESIN_HOME环境变量没有设置,Resin可能采用一个旧版本的。
你可以使用 -verbose 选项运行resin.sh/resin.exe来查看当Resin启动时使用的CLASSPATH。
2.5. 监视HTTP传输
要监视HTTP头信息,在$RESIN_HOME/resin.xml文件中启用如下调试日志:&resin xmlns="/ns/resin"&
&log-handler name='com.caucho.server.http' level='finer'
path='log/http.log' /&
&log-handler name='com.caucho.server.connection' level='finer'
path='log/http.log' /&
...&/resin&侦听和监视一个web浏览器和Resin之间传递的原始数据能够提供很有价值的信息。这个原始数据包括浏览器提交的信息头和内容,及Resin返回给浏览器的信息头和内容。 Apache Axis jar包含了一个工具"tcpmon",它可以用来侦听和监视浏览器和Resin之间的传输。使用tcpmon, 你要指定一个"listen port" 、一个 "target host" 、一个"target port"。例如,如果你通常运行Resin在8080端口上,你可以启动tcpmon使用"listen port"9090端口,一个localhost的目标主机和一个目标端口8080。现在你可以在浏览器中使用一个url de&http://localhost:9090。这时浏览器就会使用tcpmon。tcpmon会纪录发送的请求,同时转发内容到8080端口上的Resin,也会纪录Resin返回的数据并把它也发送回浏览器。de&
2.6. 使用一个外部编译器
Resin默认使用内部(internal)编译器,因为它是很容易使用的。有时内部编译器会导致错误,抛出错误或者简单挂起和占用一个线程。解决方法是在resin.xml中改变编译器为"javac"。
&javac compiler="javac" args="" /&
当然也可以使用Jikes等编译器。
2.7. 调整栈内存避免线程限制
每一个线程分配了一个栈,如果栈尺寸太大,当线程数量增大时可能内存溢出。请参看JVM参数调整的文章。
2.8. 使用操作系统的 netstat 命令获得当前 TCP/IP 端口的使用
netstat命令可用来获取当前系统的网络状态。
unix$ netstat -anp
win& netstat -an
-a 指示侦听的和非侦听的套接字都显示。-n 指示显示端口号而不是端口名(例 http)。-p 显示正在使用套接字的进程。因为Windows下的netstat命令和UNIX下的有些不同,-p选项在Winodws系统上无效。
2.8.1. 连接状态
连接状态可能是最重要的信息。可查看netstat命令帮助获得相关状态的详细描述。
"LISTEN" or "LISTENING" 表示,进程在套接字上等待连接。
"TIME_WAIT"表示包处理结束之后套接字仍在等待的状态。连接关闭之后,套接字会被操作系统保持在打开状态一个短期的时间。即使连接完全关闭了,在网络上也可能有些偏离的包需要连接。TIME_WAIT就是保持套接字足够长的打开时间来捕捉这些偏离的包,以至于这些偏离的包不会传输到在同一个套接字上的新连接上。
2.8.2. 端口使用
如果Resin显示不能绑定到一个端口,这意味着可能令一个其它进程在使用这个端口,netstat可以查出那个程序在使用这个端口。因为netstat产生了很多信息,应该滤掉那些没用的信息。下面的例子是查找使用80端口的程序:
unix$ netstat -anp | grep ":80" | less
且行且珍惜:又现模板短信,在改变中求生存
且行且珍惜:又现模板短信,在改变中求生存!
移动终端一直以来被广泛的认为是最有效、直接的营销载体,而“短信”作为其传播形式的一大利器,虽被大众所反感,但仍有商家敢冒天下而大不韪,纷纷踏足这一领域。
“造成垃圾短信“久治不愈”的一个重要原因是,电信运营商与垃圾短信推送商利益“勾结”。工信部向《经济参考报》记者提供的数据显示,“今年1至 9月我国移动短信息业务量约6970.4亿条,据有关部门估算,垃圾短信约占全部短信量20%左右”。据此计算,仅今年前三季度,垃圾短信就为三大电信运营商带来几十亿元的收入”
可见,短信这一“顽疾”仍需加强监管与排查。
在信息泛滥的今天,如何给广大的普通民众一片广阔的蓝天,是难点……,同时也是一个长期工程。
前几天,时不时的在网络上看到“模板短信”的宣传报道,搜索后才了解到“模板短信,即将自定义的不同短信内容封装成可适配不同营销场景的内容模块,客户可以根据目标群体的特征灵活地选择相应的模板进行信息PUSH”。可见,“模板短信”是短信的又一变相形态,同时也是运营商封杀垃圾短信的又一杀招,拨乱反正,扶持短信市场进入正轨。
初步调查了一下,三大运营商仅联通、电信提供了短信类开放接口,其中:
(1) 电信短信接口
据悉,中国电信模板短信能力构建于运营商级推送服务通道之上,不同于行业下游的SP,其核心特点及服务竞争力主要表现在:首先,开发者可以根据自身业务需要,自定义短信内容,并灵活调配;其次,覆盖国内电信、移动、联通全网,三网用户皆可收到;再次,多条运营商自营优质通道确保内容可24小时不间断高速发送;最后,近100%短信push到达率,稳定性强,开发者可以结合自身业务需要选择合适的套餐付费使用。
(2) 联通短信接口
联通能力平台的短信接口较为全面,不仅提供应用端对手机端的点对点短信接口,还提供模板短信接口,不过模板短信接口仅提供短信验证码和动态密码服务。同时,据了解这些短信接口目前仅对企业用户开放
从应用领域来看,模板短信的应用主要集中在如下三个方面:
应用场景示例如下:
有专家称“短信服务这块领域可谓璞玉一块,精细雕琢或将成为商务企业包装、营销以及整合其他服务的有力渠道,在未来极有可能为企业开发出无限的商机”。同样,它也是一把双刃剑,获得利益的同时在一定程度上伤害了无辜的民众。
希望,“模板短信”的出现能有效纠正“商家”牟利行为。短信在改变中求生存,望且行且珍惜。
原文载自:alan昱博文,/iKlKG
如果您想提高自己的技术水平,欢迎加入本站官方1号QQ群:&&,&&2号QQ群:,在群里结识技术精英和交流技术^_^
本站联系邮箱:

我要回帖

更多关于 epoxy resin 的文章

 

随机推荐