2009年7月29日星期三

[转]对控制反转(IOC)和依赖注入(DI)的理解

×××××××××××××首先说一下什么是IOC和DI,IOC是Inversion of Control(控制反 转)的简写,DI是Dependency Injection(依赖注入)的简写,martinfowler对IOC的解释为:“Inversion of control is a common characteristic of frameworks, so saying that these lightweight containers are special because they use inversion of control is like saying my car is special because it has wheels.”

  我想对这一概念进行一个个人的阐述,以方便我的理解。控制反转,从字面意思来看,就是控制权由被动变主动又变为被动,或被动变主动又变为被动。从这个角度来说,IOC就变得非常容易理解了。

  举个例子:你的主管要求你做一件事情,这个时候就存在这么几个过程,

  主管命令你做事情(这个时候主动权在主管,你是被动的)

  你接到命令做事情(这个时候主题是你,你是主动的,控制权在你手里)

  你完成事情(这个时候主题依然是你,控制权在你手里)

  报告主管做完事情(主动权又叫交到主管手里了)

  上面的整个过程就完成了一次IOC,从上面可以看出,IOC的基本思想是控制权的转换过程。

  举个代码的例子:

 假如有Class A,Class B,在A内部会初始化一个B,调用B的一个方法DoMethod

public Class B

  {

  public void DoMethod()

  {

  /// do somthing;

  }

  }

  public Class A

  {

  public void Excute()

  {

  B b = new B();

  b.DoMethod();

  }

  }

  假如在Main函数中如下执行:

  A a = new A();

  a.Excute();

  从这两行代码来看,事实上也存在一个IOC的过程,a——>b——>a,理解的关键点就在在A的内部调用Excute的时候,方法b.DoMethod的执行。

 理解了IOC,我们再看一下DI,从上面A调用B我们可以看出,在初始化一个A的实例时,也必须实例化一个B,也就是说如果没有B或者B出了问题,A就无法实例化,这就产生了一种依赖,就是A依赖B,这种依赖从设计的角度来说就是耦合,显然它是无法高内聚低耦合的要求的。这个时候就需要解耦,当然解耦有很多种方法,而DI就是其中一种。不管任何一种解耦方法,都不是说使A和B完全没有关系,而是把这种 关系的实现变得隐晦,不那么直接,但是又很容易实现,而且易于扩展,不像上面的代码那样,直接new一个B出来。那为什么我们总是把IOC和DI联系到一 起呢?是因为DI的基本思想就是IOC,而体现IOC 思想的方法还有另外一个,那就是Service Locator,这个方法好像涉及到的很少。

××××××××××××××××××××IoC的几种实现类型

(1)Type1接口注入

通常做法是利用接口将调用者与实现者分离。
public class Sport {
private InterfaceBall ball; //InterfaceBall是定义的接口
public void init() {
//Basketball实现了InterfaceBall接口
ball = (InterfaceBall) Class.forName("Basketball").newInstance();
}
}
Sport类在编译期依赖于InterfaceBall的实现,为了将调用者与实现者分离,我们动态生成Basketball类并通了强制类型转换为InterfaceBall。Apache Avalon是一个典型的Type1型IoC容器。

(2)setter方法注入

在类中暴露setter方法来实现依赖关系。
public class Sport {
private InterfaceBall ball;
public void setBall(InterfaceBall arg) {
ball = arg;
}
}
这种方式对已经习惯了JavaBean的程序员而言,更显直观。Spring就是实现了该类型的轻量级容器。

(3)Type3构造子注入

即通过构造方法完成依赖关系。
public class Sport {
private InterfaceBall ball;
public Sport(InterfaceBall arg) {
ball = arg;
}
}
可以看到,通过类的构造方法建立依赖关系。由于Type3在构造期就形成了对象的依赖关系,即存对象的重用变的困难。有些框架需要组件提供一个默认的构造 方法,此时就体现了Type3的局限性。通常所有的参数都是通过构造方法注入的,当对象间的依赖关系较多时,构造方法就显的比较复杂,不利于单元测试。 PicoContainer就是实现了Type3依赖注入模式的轻量级容器。

  DI,依赖注入,从字面意思就可以看出,依赖是通过外接注入的方式来实现的。这就实现了解耦,而DI的方式通常有三种,

  构造器注入

  属性设置器注入

 接口注入(我感觉接口注入是同时存在于上两种注入方式的,而不应该独立出来)

 以 上的阐述只是为了先让我们能对IOC和DI有一个感性的理解,那么IOC他真正解决的问题是什么呢?我们讲了那么多主动被动的问题,那我们是从什么视角来 看待这个问题的呢?所谓为什么你是主动,而我不是主动呢?这就需要一个参照物,那这个参照物是什么呢?就是容器,在容器中来体现主动和被动。“用白话来 讲,就是由容器控制程序之 间的关系,而非传统实现中,由程序代码直接操控。这也就是所谓”控制反转“的概念所在:控制权由应用代码中转到了外部容器,控制权的转移,是所谓反转", 这是通常对IOC的一个解释。从容器的角度来看主动和被动,和由容器来控制程序之间的关系,应该是相通的,是一个意思。到这里我们就应该基本明白 了,IOC要解决的就是程序之间调用的一个问题,它应该是一个思想层面的东西,是一个中心,就像一支乐队的指挥,而程序就是乐器,通过指挥来协调各种乐 器,来演奏出美好的音乐来。

2009年7月28日星期二

[转]PO/VO/DAO/BO/POJO是什么(JAVA几种对象的解释)

PO:persistant object持久对象,可以看成是与数据库中的表相映射的java对象。最简单的PO就是对应数据库中某个表中的一条记录,多个记录可以用PO的集合。PO中应该不包含任何对数据库的操作.
VO:value object值对象。通常用于业务层之间的数据传递,和PO一样也是仅仅包含数据而已。但应是抽象出的业务对象,可以和表对应,也可以不,这根据业务的需要.个人觉得同DTO(数据传输对象),在web上传递.
DAO:data access object数据访问对象,此对象用于访问数据库。通常和PO结合使用,DAO中包含了各种数据库的操作方法。通过它的方法,结合PO对数据库进行相关的操作.
BO:business object业务对象,封装业务逻辑的java对象,通过调用DAO方法,结合PO,VO进行业务操作;
POJO:plain ordinary java object 简单无规则java对象,我个人觉得它和其他不是一个层面上的东西,VO和PO应该都属于它.
PO:
persistant object持久对象
最形象的理解就是一个PO就是数据库中的一条记录。
好处是可以把一条记录作为一个对象处理,可以方便的转为其它对象。
BO:
business object业务对象
主要作用是把业务逻辑封装为一个对象。这个对象可以包括一个或多个其它的对象。
比如一个简历,有教育经历、工作经历、社会 关系等等。
我们可以把教育经历对应一个PO,工作经历对应一个PO,社会 关系对应一个PO。
建立一个对应简历的BO对象处理简历,每个BO包含这些PO。
这样处理业务逻辑时,我们就可以针对BO去处理。
VO :
value object值对象
ViewObject表现层对象
主要对应界面显示的数据对象。对于一个WEB页面,或者SWT、SWING的一个界面,用一个VO对象对应整个界面的值。
DTO :
Data Transfer Object数据传输对象
主要用于远程调用等需要大量传输对象的地方。
比如我们一张表有100个字段,那么对应的PO就有100个属性。
但是我们界面上只要显示10个字段,
客户端用WEB service来获取数据,没有必要把整个PO对象传递到客户端,
这时我们就可以用只有这10个属性的DTO来传递结果到客户端,这样也不会暴露服务端表结构.到达客户端以后,如果用这个对象来对应界面显示,那此时它的身份就转为VO
POJO :
plain ordinary java object 简单java对象
个人感觉POJO是最常见最多变的对象,是一个中间对象,也是我们最常打交道的对象。
一个POJO持久化以后就是PO
直接用它传递、传递过程中就是DTO
直接用来对应表示层就是VO
DAO:
data access object数据访问对象
这个大家最熟悉,和上面几个O区别最大,基本没有互相转化的可能性和必要.
主要用来封装对数据库的访问。通过它可以把POJO持久化为PO,用PO组装出来VO、DTO

2009年7月14日星期二

我看到中华民族的希望了

今天与表弟谈了一些关于民主、自由以及中国与美国的差距的一些话题。谈到了中国政府体制的漏洞等话题。

表弟今年才刚初三,准备考中考。成绩不错能进个上海的不错的重点高中,比我强。以前从没和他谈论过政治的问题。今日能的聊了一下,让我惊叹不已。他对中国政治体制缺点的认识和我当年相比真是天差地别。他谈到的中国政府中存在的既得利益集团,以及政府将新一代的70后、80后"团结"到既得利益团体来维护统治地位等等问题。他还提到了网络将来在中国民主化进程中会起到较大的作用。并谈到谷歌被焦点访谈归类为传播淫秽思想网站的事。他认为这是政府为了控制网络这一新的新闻传播媒体的决心的显示。

虽然说的不仅全对,我也和他交流了一下我的想法。谈及了绿坝、GFW等等一些实事,以及让他看了下汶川地震危房小学的一些视频。

从他身上看到了中国民主发展的可能性。深感欣慰!

2009年7月10日星期五

[转]JavaScript运行机制浅探

从一个简单的问题谈起:

<script type="text/javascript">
alert(i); // ?
var i = 1;
</script>

输出结果是undefined,
这种现象被称成"预解析":JavaScript引擎会优先解析var变量和function定义。在预解析完成后,才会执行代码。如果一个文档流中包含多个script代码段(用script标签分隔的js代码或引入的js文件),运行顺序是:

step1. 读入第一个代码段
step2. 做语法分析,有错则报语法错误(比如括号不匹配等),并跳转到step5
step3. 对var变量和function定义做"预解析"(永远不会报错的,因为只解析正确的声明)
step4. 执行代码段,有错则报错(比如变量未定义)
step5. 如果还有下一个代码段,则读入下一个代码段,重复step2
step6. 结束

上面的分析,已经能解释很多问题了,但老觉得欠缺点什么。比如step3里,"预解析"究竟是怎么回事?还有step4里,看下面的例子:

<script type="text/javascript">
alert(i); // error: i is not defined.
i = 1;
</script>

为什么第一句会导致错误?JavaScript中,变量不是可以不定义吗?
编译过程

时间如白马过隙,书柜旁翻开恍如隔世般的《编译原理》,熟悉而又陌生的空白处有着这样的笔记:

对于传统编译型语言来说,编译步骤分为:词法分析、语法分析、语义检查、代码优化和字节生成。
但对于解释型语言来说,通过词法分析和语法分析得到语法树后,就可以开始解释执行了。

简单地说,词法分析是将字符流(char stream)转换为记号流(token stream), 比如将c = a - b;转换为:

NAME "c"
EQUALS
NAME "a"
MINUS
NAME "b"
SEMICOLON

上面只是示例,更进一步的了解请查看 Lexical Analysis.

《JavaScript权威指南》的第2章,讲的就是词法结构(Lexical Structure),ECMA-262
中也有描述。词法结构是一门语言的基础,很容易掌握。至于词法分析的实现那是另一个研究领域,在此不探究。

可以拿自然语言来类比,词法分析是一对一的硬性翻译,比如一段英文,逐词翻译成中文,得到的是一堆记号流,还很难理解。进一步的翻译,就需要语法分析了,下图是一个条件语句的语法树:
synttree

构造语法树的时候,如果发现无法构造,比如if(a { i = 2; }, 就会报语法错误,并结束整个代码块的解析,这就是本文开头部分的step2.

通过语法分析,构造出语法树后,翻译出来的句子可能还会有模糊不清的地方,接下来还需要进一步的语义检查。对于传统强类型语言来说,语义检查的主要部分是类型检查,比如函数的实参和形参类型是否匹配。对于弱类型语言来说,这一步可能没有(精力有限,没时间去看JS的引擎实现,不敢确定JS引擎中是否有语义检查这一步)。

通过上面的分析可以看出,对于JavaScript引擎来说,肯定有词法分析和语法分析,之后可能还有语义检查、代码优化等步骤,等这些编译步骤完成之后(任何语言都有编译过程,只是解释型语言没有编译成二进制代码),才会开始执行代码。

上面的编译过程,还是无法更深入的解释文章开头部分的"预解析",我们还得仔细探究下JavaScript代码的执行过程。
执行过程

周爱民在《JavaScript语言精髓与编程实践》的第二部分,对此有非常仔细的分析。下面是我的一些领悟:

通过编译,JavaScript代码已经翻译成了语法树,然后会立刻按照语法树执行。

进一步的执行过程,需要理解JavaScript的作用域机制,JavaScript采用的是词法作用域(lexcical
scope)。通俗地讲,就是JavaScript变量的作用域是在定义时决定而不是执行时决定,也就是说词法作用域取决于源码,编译器通过静态分析就能确定,因此词法作用域也叫做静态作用域(static
scope)。但需要注意,with和eval的语义无法仅通过静态技术实现,实际上,只能说JS的作用域机制非常接近lexical scope.

JS引擎在执行每个函数实例时,都会创建一个执行环境(execution context)。execution
context中包含一个调用对象(call object),
调用对象是一个scriptObject结构,用来保存内部变量表varDecls、内嵌函数表funDecls、父级引用列表upvalue等语法分析结构(注意:varDecls和funDecls等信息是在语法分析阶段就已经得到,并保存在语法树中。函数实例执行时,会将这些信息从语法树复制到
scriptObject上)。scriptObject是与函数相关的一套静态系统,与函数实例的生命周期保持一致。

lexical scope是JS的作用域机制,还需要理解它的实现方法,这就是作用域链(scope chain)。scope
chain是一个name lookup机制,首先在当前执行环境的scriptObject中寻找,没找到,则顺着upvalue到父级scriptObject中寻找,一直
lookup到全局调用对象(global object)。

当一个函数实例执行时,会创建或关联到一个闭包(closure)。
scriptObject用来静态保存与函数相关的变量表,closure则在执行期动态保存这些变量表及其运行值。closure的生命周期有可能比函数实例长。函数实例在活动引用为空后会自动销毁,closure则要等要数据引用为空后,由JS引擎回收(有些情况下不会自动回收,就导致了内存泄漏)。

别被上面的一堆名词吓住,一旦理解了执行环境、调用对象、闭包、词法作用域、作用域链这些概念,JS语言的很多现象都能迎刃而解。
小结

至此,对于文章开头部分的疑问,可以解释得很清楚了:

step3中所谓的"预解析",其实是在step2的语法分析阶段完成,并存储在语法树中。当执行到函数实例时,会将varDelcs和funcDecls从语法树中复制到执行环境的scriptObject上。

step4中,未定义变量意味着在scriptObject的变量表中找不到,JS引擎会沿着scriptObject的upvalue往上寻找,如果都没找到,对于写操作i
= 1; 最后就会等价为 window.i = 1;
给window对象新增了一个属性。对于读操作,如果一直追溯到全局执行环境的scriptObject上都找不到,就会产生运行期错误。

理解后,雾散花开,天空一片晴朗。

[转]javascript---类的构建

javascript里构建类主要有4种方式
1.构造方式定义类
2.原型方式定义类
3.构造和原型结合方式创建类
4.动态的原型方式

各有优缺点,具体如下
1.构造方式定义类,优点:多个实例对象不共享类的属性值,缺点:每个实例对象都会产生出一个函数say

Java代码

1. //构造方式定义类,优点:多个实例对象不共享类的属性值,缺点:每个实例对象都会产生出一个函数say
2. function User(){
3. this.username = "zhangsan";//this.不能丢
4.
5. this.say = function(){//this.不能丢
6. alert("username:" + this.username );//this.不能丢
7. }
8. //下面注释的这种写法不对
9. // function say(){
10. // alert("username:" + this.username );
11. // }
12. }
13.
14. var user = new User();
15. user.username = "lisi";
16. user.say();//username:lisi
17.
18. var user1 = new User();
19. user1.say();//username:zhangsan,不受user对象的影响

//构造方式定义类,优点:多个实例对象不共享类的属性值,缺点:每个实例对象都会产生出一个函数say
function User(){
this.username = "zhangsan";//this.不能丢

this.say = function(){//this.不能丢
alert("username:" + this.username );//this.不能丢
}
//下面注释的这种写法不对
// function say(){
// alert("username:" + this.username );
// }
}

var user = new User();
user.username = "lisi";
user.say();//username:lisi

var user1 = new User();
user1.say();//username:zhangsan,不受user对象的影响

//多个实例对象不共享类的属性值:
Java代码

1. //多个实例对象不共享类的属性值,如下:
2. function User(){
3. this.username = new Array();//this.不能丢
4.
5. this.say = function(){//this.不能丢
6. alert("username:" + this.username );//this.不能丢
7. }
8. }
9.
10. var user = new User();
11. user.username.push("zhangsan");
12. user.say();//username:zhangsan
13.
14. var user1 = new User();
15. user1.say();//user1的username为空,不为zhangsan,因为user1的属性值不受user影响

//多个实例对象不共享类的属性值,如下:
function User(){
this.username = new Array();//this.不能丢

this.say = function(){//this.不能丢
alert("username:" + this.username );//this.不能丢
}
}

var user = new User();
user.username.push("zhangsan");
user.say();//username:zhangsan

var user1 = new User();
user1.say();//user1的username为空,不为zhangsan,因为user1的属性值不受user影响

2.原型方式定义类,缺点:类的属性值如果是引用类型的(非Number和String类型),则多个实例对象共享
Java代码

1. //原型方式定义类,缺点:类的属性值如果是引用类型的(非Number和String类型),则多个实例对象共享
2. function User(){
3. }
4. User.prototype.username = "zhangsan";
5. User.prototype.say = function(){
6. alert("username: " + this.username );
7. }
8.
9. var user = new User();
10. user.username = "lisi";
11. user.say();//username:lisi
12.
13. var user1 = new User();
14. user1.say();//username:zhangsan

//原型方式定义类,缺点:类的属性值如果是引用类型的(非Number和String类型),则多个实例对象共享
function User(){
}
User.prototype.username = "zhangsan";
User.prototype.say = function(){
alert("username: " + this.username );
}

var user = new User();
user.username = "lisi";
user.say();//username:lisi

var user1 = new User();
user1.say();//username:zhangsan

类的属性值如果是引用类型的(非Number和String类型),则多个实例对象共享:
Java代码

1. //类的属性值如果是引用类型的(非Number和String类型),则多个实例对象共享,如下
2. function User(){
3. }
4. User.prototype.username = new Array();
5. User.prototype.say = function(){
6. alert("username: " + this.username );
7. }
8.
9. var user = new User();
10. user.username.push("zhangsan") ;
11. user.say();//username:zhangsan
12.
13. var user1 = new User();
14. user1.say();//username:zhangsan,因为user1属性也会受到user的影响,user1和user指向同一引用,即共享同一属性

//类的属性值如果是引用类型的(非Number和String类型),则多个实例对象共享,如下
function User(){
}
User.prototype.username = new Array();
User.prototype.say = function(){
alert("username: " + this.username );
}

var user = new User();
user.username.push("zhangsan") ;
user.say();//username:zhangsan

var user1 = new User();
user1.say();//username:zhangsan,因为user1属性也会受到user的影响,user1和user指向同一引用,即共享同一属性

3.构造和原型结合方式创建类:优点:类的方法只会产生一个,不会产生大量方法,同时属性还不共享;缺点:属性和方法分开定义不是太好。

Java代码

1. //构造和原型结合方式创建类:优点:类的方法只会产生一个,不会产生大量方法,同时属性还不共享;缺点:属性和方法分开定义不是太好。
2. function User(){
3. this.username = "zhangsan";
4. }
5. User.prototype.say = function(){
6. alert("username: " + this.username );
7. }
8. var user = new User();
9. alert(user.username);

//构造和原型结合方式创建类:优点:类的方法只会产生一个,不会产生大量方法,同时属性还不共享;缺点:属性和方法分开定义不是太好。
function User(){
this.username = "zhangsan";
}
User.prototype.say = function(){
alert("username: " + this.username );
}
var user = new User();
alert(user.username);

4.动态的原型方式:优点:类的方法只会产生一个,不会产生大量方法,同时属性还不共享,同时属性和方法不是分开定义的

Java代码

1. ////动态的原型方式:优点:类的方法只会产生一个,不会产生大量方法,同时属性还不共享,同时属性和方法不是分开定义的
2.
3. function User(){
4. this.username = "zhangsan";
5.
6. if(typeof User.flag == "undefined"){
7. alert("execute...");
8. User.prototype.say = function(){
9. alert("username: " + this.username );
10. }
11.
12. User.flag = true;
13. }
14. }
15.
16. var user1 = new User();//execute...
17. var user2 = new User();//不会打印出execute...,则说明方法只创建了一次,即方法只会产生一个
18. user1.say();//username

////动态的原型方式:优点:类的方法只会产生一个,不会产生大量方法,同时属性还不共享,同时属性和方法不是分开定义的

function User(){
this.username = "zhangsan";

if(typeof User.flag == "undefined"){
alert("execute...");
User.prototype.say = function(){
alert("username: " + this.username );
}

User.flag = true;
}
}

var user1 = new User();//execute...
var user2 = new User();//不会打印出execute...,则说明方法只创建了一次,即方法只会产生一个
user1.say();//username

总结:
构造方式定义类:缺点:类里的方法,每个实例对象都会产生一个,导致产生大量方法;优点:所有实例对象都单独拥有一份类里的属性,即属性不共享
原型方法定义类:缺点:所有实例对象都共同拥有一份类里的属性,即属性共享。优点:类的方法只会产生一个,不会产生大量方法
构造和原型结合方式创建类:优点:类的方法只会产生一个,不会产生大量方法,同时属性还不共享;缺点:属性和方法分开定义不是太好。
动态的原型方式:优点:类的方法只会产生一个,不会产生大量方法,同时属性还不共享,同时属性和方法不是分开定义的

[转]了解JavaScript类

*********类是什么?

  许多刚接触编程的朋友都可能理解不了类,其实类是对我们这个现实世界的模拟,把它说成"类别"或者"类型"可能会更容易理解一些。比如"人"这种动物就是一个类,而具体某一个人就是"人"这个类的一个实例,"人"可以有许多实例(地球人超过六十亿了),但"人"这个类只有一个。你或许会说那男人和女人不也是人么?怎么只能有一个?其实这里要谈到一个继承的东西,后边才讲,请继续看下去。

*********如何建立一个类?
  在C++中是以class来声明一个类的,JavaScript与
C++不同,它使用了与函数一样的function来声明,这就让许多学Jscript的朋友把类与函数混在一起了,在Jscript中函数与类确实有些混,但使用久了自然而然会理解,这篇文章是针对想进攻面向对象编程的朋友而写,就不打算一下子讨论得太深了。
  请看下边这个类的定义:

  function WuYouUser()
  {
    this.Name; //名字
  }

  上边的代码定义了一个WuYouUser(无忧用户)类,它有个属性:Name(名字)。Name就是WuYouUser类的一个属性。
  一个类有固定的属性,但类的实例却有不同的属性值,就像我是属于"人"这个类的,性别是男,而我有一个女同学,她也属于"人"类,但她的性别属性值却为女。
  那么如何声明某个类的一个实例呢?非常简单:

  var Wo = new WuYouUser(); //实例一:"我"
  var Biyuan = new WuYouUser(); //实例二:"碧原"(Biyuan哥,不好意思。。。嘿嘿)


*********类的属性

  这个Wo(我)就是WuYouUser类的一个实例,它拥有WuYouUser给它的一切:Name属性、Sex属性以及Age属性,我们可以这样子来设置它的属性:

  Wo.Name = "泣红亭";

  很简单是不是?试着运行

  window.document.write(Wo.Name);

  看看,是不是输出了我的名字:泣红亭?

  同样设置一下碧原兄的属性

  Biyuan.Name = "碧原";

  运行

    window.document.write(Biyuan.Name);

  可以看到输出了"碧原",也就说明了Biyuan与Wo同样是WuYouUser类的实例,但却是不同的实体,具有不同的属性值。

  属性是可以设置默认值的,无忧里都有记录大家各自发了多少贴子,我们也同样给WuYouUser类添加一个发贴数量的属性ArticleCount

  function WuYouUser()
  {
    this.Name;
    this.ArticleCount = 0;
  }

  一个无忧新用户刚注册完之后他的发贴数量为0,在上边的代码中可以看到直接给属性ArticleCount设置值为0。

  可以运行一下这样的代码:

  var Wo = new WuYouUser();
  window.document.write(Wo.ArticleCount);

  可以看到输出了0,说明ArticleCount属性被我们成功设置默认值为0


*********类的方法

  方法这个词不大好理解,我觉得说成行为会更容易理解。一个人具有许多共同的行为,比如睡觉、吃饭、走路等等,现在我们给WuYouUser类添加一个发贴的方法。

  function WuYouUser()
  {
    this.Name;
    this.ArticleCount = 0;

    this.NewArticle = function()
    {
      /*
      *
      *  具体如何发贴我们大家都知道,不就是打打字,加加图片再按一下保存之类的按钮么?
      *  关于具体如何发贴的代码没有必要在这里写出来,我们要了解的仅仅是方法的定义与使用
      *  我们在这里实现一个最简单的功能,也是很重要的功能:给我们的发贴数量加上1!
      *  注意:恐龙等级就是这样加出来的,因此呀……大家狂发贴吧。。。
      */

      this.ArticleCount++;
    }
  }

  既然定义好了这个方法,我们来试试效果如何:

  var Wo = new WuYouUser();
  Wo.NewArticle();
  document.write(Wo.ArticleCount);

  可以看到输出了1,说明我们发贴成功了!真是有历史纪念意义的一刻,离恐龙等级又近一步了。


*********静态属性

  静态属性又称公共属性,它不属于某个类的实例,而是直接属于某个类。

  比如说无忧用户有一个属性:注册用户的数量,它是属于整个无忧用户的,而不是属于泣红亭或者谁的
  静态属性的声明方法是:

  类名.prototype.属性名 = 属性值;

  比如给WuYouUser类定义一个注册用户的数量Count:

  WuYouUser.prototype.Count = 0;

  那么如何读取它呢?有两种方法:

  1. 直接用 WuYouUser.prototype.Count
  2. 使用Wo.Count

  这两者没有区别,都是得到0

  虽然读取方法可以有两种,但在改变它的时候却得特别小心了,请看下边代码

  var Biyuan = new WuYouUser();
  WuYouUser.prototype.Count++;
  document.write(Wo.Count);
  document.write(Biyuan.Count);

  你会发现两者的Count属性都是1,也就是说WuYouUser.prototype.Count改变了会影响到各个实例的相应属性,其实原理就是Wo、Biyuan的Count属性与WuYouUser.prototype.Count根本就是同一个!

  现在来看另外一段代码:

  var Biyuan = new WuYouUser();

  Biyuan.Count++; //特别注意一下这里,这是直接改变Biyuan的Count属性
  document.write(Biyuan.Count); // 输出 1
  document.write(WuYouUser.prototype.Count); //输出 0
  document.write(Wo.Count); //同样输出0,为什么?

  可以看到如果直接修改实例的静态属性值,那么会出现其它实例甚至类的静态属性与它不同步了?这是因为直接修改的时候,该实例会生成一个属于该实例的属性
Count,这个时候Biyuan.Count不再与WuYouUser.prototype.Count是同一个了,也不与Wo.Count是同一个,这个Count属性是属于Biyuan自己所有的,以后改变了它也只是影响它自己而已。

  因此如果不是特别的需要,建议不管在读取还是赋值的时候,都统一使用WuYouUser.prototype.Count这样的方式,以做到万无一失!


*********静态方法

  与静态属性相似,它也有个另称:公共方法,同样属于类本身的。

  静态方法的定义方式是:

  类名.方法名 = function(参数1,参数2...参数n)
  {
    //方法代码
  }

  我们现在就来定义一个无忧用户类的注册新用户静态方法:

  WuYouUser.prototype.AddOne = function()
  {
    //*** 同样具体代码不写出来,给静态属性Count增加1,表示注册用户数量又多一个
    WuYouUser.prototype.Count++;
  }

  现在我们来看看如何用它,同样有两种方法:

  1.直接使用WuYouUser.prototype.AddOne()
  2.使用某实例的AddOne()

  这两种方法没有什么不同:

  var Wo = new WuYouUser();
  var Biyuan = new WuYouUser();
  document.write(WuYouUser.prototype.Count); // 0

  Wo.AddOne();
  document.write(WuYouUser.prototype.Count); // 1
  document.write(Wo.Count); // 1
  document.write(Biyuan.Count); // 1

  WuYouUser.prototype.AddOne();
  document.write(WuYouUser.prototype.Count); // 2
  document.write(Wo.Count); // 2
  document.write(Biyuan.Count); // 2

  可以看出不管是使用Wo.AddOne()还是WuYouUser.prototype.AddOne()效果都是一样的,都是给WuYouUser.prototype.Count加上1

  现在再看一段代码:
  function NewClass() //由于上边的WuYouUser类不合适当这个例子的代码,我声明了一个新类NewClass
  {
    this.Name = "泣红亭"; //这里默认值为我的名字
  }

  NewClass.prototype.ChangeName = function(NewName)
  {
    this.Name = NewName;
  }

  var Wo = new NewClass();
  Wo.ChangeName("郑运涛"); //我的真名

  可以看到Wo.Name确实已经变成了"郑运涛",这个方法似乎是可以用的,但里边是不是内有天机呢?
  再看下边的代码,类的定义以及ChangeName的定义我们照样,但改变一下下边的代码:

  NewClass.prototype.ChangeName("郑运涛");
  document.write(NewClass.Name); //undefined,即未定义
  document.write(NewClass.prototype.Name); //郑运涛
  var Wo = new NewClass();
  document.write(Wo.Name); //泣红亭

  可以看到我们并没有定义NewClass.prototype.Name这个静态属性,但编译器给我们自己加了一个。
  可是再看下边输出Wo.Name,它并不是为"郑运涛",而是原来的默认值"泣红亭",说明了什么?
  其实很简单,看一下NewClass的定义里已经有Name这个属性,因此Wo也有自己的Name属性,它跟NewClass.prototype.Name并不是同一个的,因此就还是那样子。

  那为什么前一个例子运行了Wo.ChangeName("郑运涛")却能够实现改变Wo.Name属性呢?其实在这里跟改变Wo.Count的值是同一个道理,编译器自动给Wo增加了一个方法ChangeName,这个方法代码与NewClass.prototype.ChangeName一样,但
Wo.ChangeName是Wo这个实例所特有的,而非NewClass.prototype.ChangeName!

  分析可知道在静态方法里尽量不要使用this这样的关键字来引用实例本身的属性,除非你有特别的目的,而且能够清楚地明白这里边的运行机制!

  如果真的需要在静态方法里使用this,可以直接把this当作参数传进去:

  NewClass.ChangeName = function(This,NewName) //注意这里是This,不是this
  {
    This.Name = NewName;
  }


*********构造函数

  一个类在初始化的时候其实也是一个函数的执行过程,这个函数就是构造函数,我们看一下下边的代码:

  function WuYouUser()
  {
    this.Name = "泣红亭"; //默认定义为泣红亭
    alert(this.Name);
  }
  var Wo = new WuYouUser();//可以看到出现一个窗口显示泣红亭三个字

  可以看出类的定义不仅仅是定义了它的属性与方法,还同时可以加入一些代码,而这些代码就是该类的构造函数的代码,在实例声明过程中被执行!
  其实说起来,类的属性与类的方法都是在构造函数里执行定义的,看下边的代码:

  function WuYouUser()
  {
    this.Name = "泣红亭";
    return;
    this.Sex = "男";
  }
  var Wo = new WuYouUser();
  document.write(Wo.Name); //泣红亭
  document.write(Wo.Sex); //undefined,即未定义

  看得出什么?Sex属性是在return;之后的,而WuYouUser类的构造函数遇到return即停止运行,换句话说this.Sex =
"男";这一行是没有被执行,即Sex属性根本没有被定义!

  构造函数可以有参数,参数值在声明实例的时候被传入:
  function WuYouUser(Name)
  {
    this.Name = Name;
  }
  var Wo = new WuYouUser("泣红亭");
  document.write(Wo.Name); //泣红亭

  构造函数不需要返回值,但如果你设置了返回值,可以把它当成一个函数来使用。
  function Sum(a, b)
  {
    this.a = a;
    this.b = b;
    return this.a + this.b;
  }
  document.write(Sum(12, 23)); //输出的是12与23的和35
  var Obj = new Sum(12,23);
  document.write(Obj.a) // 12
  document.write(Obj.b) // 23

  感觉挺奇妙,对吧?我写这文章写着写着也觉得挺奇妙的,呵呵!

  但强烈建议不要把一个类当成一个函数来使用!如果你需要的是一个函数,请直接写成函数而不要写成类,以免搞混了。


*********继承

  继承这个词在面向对象的编程里是非常重要的,虽然JavaScript并不是真正面向对象的语言,而是跟VB一样是基于对象的语言,它同样提供了继承机制。

  文章开头时谈到了男人与女人,这也同样是两个不同的类,但却具有相同的一些属性以及方法,而这些相同的特性是来自"人"这个类的,换句话说男人与女人继承了"人"的所有特性!但是男人与女人却有其不同的地方,编程语言里的继承也一样,一个类A继承了另一个类B,那么类B就是类A的父类,类A就是类B的派生类,也称为子类。比如男人就是人的派生类,而人就是男人的父类。最高一级的类称为基类,想象一下就可以明白,男人继承自人,男孩继承自男人,人就是男孩的基类,男人就是男孩的父类。

*********题外:多重继承

  这里再涉及一个多重继承的话题,但如果你仅仅是学JavaScript的话就没有必要看下去,因为JavaScript不提供多重继承,准确一点说没有一种简单而标准的方法来实现多重继承(其实是有办法实现的,只不过麻烦了一点,而且确实没有必要)。

  在C++中是有多重继承的概念的,这里是讨论JavaScript,因此不打算讲,只是说说它的一点点思想以供参考。

  在上边男孩的继承问题中,男孩其实不仅仅是继承自男人,还继承自孩子(有男孩子,也有女孩子)这个类,因此,它同时继承了两个类:男人与男孩,这就是所谓的多重继承。

  好,这个问题打住,我们还是回归主题。

  先看第一个类的定义:

  function A()
  {
    this.Name = "泣红亭";
    alert(this.Name);
  }

  这个类定义了一个属性Name,默认值为"泣红亭"

  现在看第二个类的定义:

  function B()
  {
    this.Sex = "男";
    alert(this.Sex);
  }

  定义了一个属性Sex,默认值为"男"

  继承的方式就是 子类.prototype = new 父类();
  现在我们来让B类继承A类:

  B.prototype = new A();

  运行这一段代码:

  var Obj = new B(); //首先打开警告窗口显示"泣红亭",再显示"男"

  可以从上边的结果看出B类继承了A类,拥有了A类的属性Name,并且执行了A类的构造函数,而且A类的构造函数在B类的构造函数执行之前执行。因此我们利用这个可以实现重写父类的方法以及重设置父类某属性的默认值:

  function A()
  {
    this.Name = "泣红亭";
    this.Show = function()
    {
      alert("这是A类的Show方法");
    }
    alert(this.Name);
  }

  function B()
  {
    this.Name = "郑运涛";
    this.Show = function()
    {
      alert("这是B类的Show方法");
    }
    alert(this.Name);
  }

  var Obj = new B();
  Obj.Show();

  结果出现了三次警告窗口,第一个内容为泣红亭,是执行A类的构造函数里的alert(this.Name),那时候Name属性值还为"泣红亭",因为
B类的构造函数还没执行,第二次内容为"郑运涛",这是B类里的alert(this.Name),因为B类的构造函数里给Name重赋值为"郑运涛"。最后是调用了Obj.Show(),执行了不是A类的Show方法里的Show(显示"这是A类的Show方法"),而是执行了B类的Show(显示"这是B类的Show方法"),很明显Show方法被重写了。


*********类作为一个对象时的属性与方法(不知道如何简洁地表达,因此用了这么长的题目)

  不知道在这里谈这个话题是否有点混人耳目,但又觉得不谈这篇文章就不算完整,因为文章目的就是要让人搞清楚类的方方面面。

  看了这一小节的题目,或许你会觉得奇怪,类就是类,怎么会"作为一个对象"呢?在JavaScript里,一切都是对象,包括类!对象可以有属性,可以有方法,类也同样可以有,但这个非常容易跟前边说到的静态属性与静态方法搞混了,因此要仔细看清楚两者的分别!

  定义一个类:
  function WuYouUser()
  {
    this.Name = "泣红亭";
  }

  定义类作为一个对象时的属性:

  WuYouUser.Url = "http://www.51js.com";
//静态属性的定义是:WuYouUser.prototype.Url = "http://www.51js.com";
  var Wo = new WuYouUser();
  document.write(WuYouUser.Url); //http://www.51js.com
  document.write(Wo.Url); //undefined,即未定义!注意这里的未定义

  从这里可以看出Url这个属性是WuYouUser自个所有,改变了它与其它类以及它的子类完全无关!

  引用类的属性只有一个办法,就是类名.属性名,改变它也一样。

  定义类作为一个对象时的方法:

  WuYouUser.ChangeUrl = function()
  {
    this.Url = "http://51js.com";
  }

  你或许会觉得奇怪,这里的this是什么?因为ChangeUrl这个方法是属于对象WuYouUser的,因此this指的就是WuYouUser本身!

  可以运行下边的代码试试:

  document.write(WuYouUser.Url); // http://www.51js.com
  WuYouUser.ChangeUrl();
  document.write(WuYouUser.Url); // http://51js.com

  明显ChangeUrl直接修改了WuYouUser.Url的值,因此后边才能输出http://51js.com

  如果你这一节看不明白,也不要着急,编程嘛,许多东东都只能意会不能言传,而且我又没口才,说不清楚,只要以后多写写代码,多用用类自然而然会体会到这一些,还有可以去看看JSVM的代码,里边几乎每个类都有用到类作为一个对象时的属性与方法。

2009年7月7日星期二

[转]js包含js文件 Import javascript in javascript

if (typeof(jsfile) == 'undefined') var jsfile = new Object();

function includejs(jsFile) {
if (jsfile[jsFile] != null) return;
var scriptElt = document.createElement('script');
scriptElt.type = 'text/javascript';
scriptElt.src = jsFile;
document.getElementsByTagName('head')[0].appendChild(scriptElt);
jsfile[jsFile] = jsFile; // or whatever value your prefer
}

includejs('../myext/ext2/ext-base.js');
includejs('../myext/ext2/ext-all.js');
includejs('../myext/ext2/ext-lang-zh_CN.js');
includejs('../myext/ext2/PagingMemoryProxy.js');

[转]javascript 中数组使用方法汇总

<script language="javascript">
//Author :东阁
//Date:2008-1-11
//目的: 练习数组的基本操作

/*
由于javascript是一种无类型语言,所以一个数组的元素可以具有任意的数据类型,同一个数组的不同元素
可以具有不同的类型,数组的元素设置可以包含其他数组,这样就可以创建一个复杂的数组了.
并且在这点上说javascript作为一种脚本语言不同于那种严格的面向对象的c++.c#,java了.具有更高的灵活性.
*/

/*
*在javascript1.1和其后的版本中,数组是用构造函数Array()和运算符new来创建,
可用以下的三种方式来创建javascript 中的数组.
*/
var a=new Array();
var b=new Array(5,4,3,"first","test,string");
var c=new Array(20);

a[1.23]="test";
document.write("a[1.23]="+a[1.23]);
//相信每位从强类型的编程语言学习javascript时,绝对会以为上面这种操作感到惊讶,
//float数据也作数组的下标了,事实上 并非如您所想
//javascript在您是用负数,浮点数,(或布尔型,对象,其他值时),javascript会将它转换为一个字符串
//用生成的字符串作为对象的属性名字,而不是定义了一个新的数组元素
//上面的实例事实就是为a 创建了一个名为:"1.23"的属性.
document.write("a.length="+a.length);
document.write("b.length="+b.length);
document.write("c.length="+c.length);

a[3]="Test";
document.write("<br />a[3]="+a[3]);
document.write("<br/>a.length="+a.length);
//以上测试也很明确我们用整数作为数组的下标是才会真正为数组添加一个元素,
//这里用数组的长度来体现了javascript的数组中的奥妙。


//通过设置数组的length属性能过截断数组的长度。
a.length=3;
if (a[3]==undefined)
{
document.write("<br />在a.length="+a.length+"后,a[3]="+a[3]);
}
else
{
document.write("<br />在a.length="+a.length+"后,a[3]="+a[3]);
}

//这里测试我们的多维数组元素
/*
*javascript中实际上是不支持多维数组
*但是我们将一个一维数组的元素再赋给其一个一维数组,这样就看起来就实现了多维数组了,但
实际上他还是个一维数组,这和我们理解c语言的数组时的那种想法一样,但他们的实现机制是不一样的。
*/
var g=new Array(3);
g[3]=a;
g[3][2]="Test"
document.write("<br />g[3][2]="+g[3][2]);

//数组join()方法
for (var i=0;i<20 ;i++ )
{
c[i]=i;
document.write("<br />c[i]="+c[i]);
}
document.write("<br/>c的元素join()方法后是:"+c.join());
//数组的reverse()方法
c.reverse();
document.write("<br />c的元素在reverse()方法再join()后的结果是:"+c.join("|"));

//concat()方法的测试
var h=new Array(1,2,3);
h= h.concat([4,5]);
//但是concat函数不会递归地展开一个元素为数组的数组。
h=h.concat(6,7,[9,[10,20]]);
document.write("<br />h.length="+h.length+"<br />"+h);
document.write("h[8]="+h[8]);


//slice()方法
document.write("<br>h.slice(4,5)="+h.slice(4,5));
document.write("h.slice(5,9)="+h.slice(5,9))
//slice()方法:返回的数组包含有第一个参数指定的元素和那个元素开始到第二个参数指定的
//元素为止的元素但不包含第二个参数所指定的元素。


//splice()方法
//splice()方法是插入或删除数组元素通用的方法。
/*
splice函数第一个参数指定了要插入或删除的元素在数组中的位置。
第二个参数指定了要从数组中删除的元个数
在第二参数之后可以有任意多个参数,它们指定的是从第一个参数指定的位置处插入的元素。
第一个元素及后续元素,做出相应的移动。
*/

document.write("<br />h.splice(8,1)后的h为::"+h.splice(8,1));
//document.write("<br
/>h.splice(8,0,'a','b','Test')后的h为::"+h.splice(8,0,'a','b','Test'));
h.splice(7,0,'a','b','Test');
document.write("<br />h.splice(7,0,'a','b','Test')后的h为:"+h);


//javascript中的数组作为堆栈时和php类似
//这点有趣更有用。
//以下是作为堆栈是使用的小实例
/*
push方法是将一个或多个新元素附加到数组的尾部,然后返回数组的新长度。
pop将删除数组的最后一个元素,坚守数组的长度,返回他删除的值。
*/
var stack=new Array();
stack.push(1,2);
document.write("<br>stack的元素是:"+stack);
document.write("<br />stack.length="+stack.length);
document.write("<br>stack.pop()返回的结果是:"+stack.pop());
document.write("<br />stack.length="+stack.length);

//以下是作为队列使用的小实例
/*
unshift方法将一个或多个元素添加到数组元素的头部,然后把已有的元素移动到下标最大的位置已腾出空间
,它返回的是主族的新长度。
方法shift是删除并返回数组的第一个元素,然后将后面的所有元素都向前移动以填补第一个元素留下的空白。
*/
var list=[];
list.unshift(6,2);
document.write("<br >list的内容为:"+list);
document.write("<br>list的shift方法是:"+list.shift());

//此外就剩下,我们在java中熟悉的toString()方法 了
//It's a piece of cake!
document.write(c.toString());
//说白了,其实数组的toString()方法和无参数的join()的效果是完全相同
//OK,this's chapter for Array,that's all!

</script>

2009年7月6日星期一

[转]js中null和undefined的区别

在JavaScript中存在这样两种原始类型:Null与Undefined。这两种类型常常会使JavaScript的开发人员产生疑惑,在什么时候是Null,什么时候又是Undefined?

Undefined类型只有一个值,即undefined。当声明的变量还未被初始化时,变量的默认值为undefined。
Null类型也只有一个值,即null。null用来表示尚未存在的对象,常用来表示函数企图返回一个不存在的对象。
js 代码


1. var oValue;
2. alert(oValue == undefined); //output "true"


这段代码显示为true,代表oVlaue的值即为undefined,因为我们没有初始化它。
js 代码


1. alert(null == document.getElementById('notExistElement'));


当页面上不存在id为"notExistElement"的DOM节点时,这段代码显示为"true",因为我们尝试获取一个不存在的对象。
js 代码


1. alert(typeof undefined); //output "undefined"
2. alert(typeof null); //output "object"


第一行代码很容易理解,undefined的类型为Undefined;第二行代码却让人疑惑,为什么null的类型又是Object了呢?其实这是JavaScript最初实现的一个错误,后来被ECMAScript沿用下来。在今天我们可以解释为,null即是一个不存在的对象的占位符,但是在实际编码时还是要注意这一特性。
js 代码


1. alert(null == undefined); //output "true"


ECMAScript认为undefined是从null派生出来的,所以把它们定义为相等的。但是,如果在一些情况下,我们一定要区分这两个值,那应该怎么办呢?可以使用下面的两种方法。
js 代码


1. alert(null === undefined); //output "false"
2. alert(typeof null == typeof undefined); //output "false"


使用typeof方法在前面已经讲过,null与undefined的类型是不一样的,所以输出"false"。而===代表绝对等于,在这里null
=== undefined输出false。

2009年7月5日星期日

[转]GWT 中文文档, 模块

作为GWT应用的最小单元,也就是模块,是用一个XML文件来配置的。一个模块配置文件具有如下的内容:
1.继承的模块,<inherits name="com.google.gwt.user.User" />
2.一个可选的入口, <entry-point class="com.youorg.client.ClassName" />
3.源码路径, <source path="client" />
4.公用资源路径,
5.延迟绑定规则,提供属性配置的文件、类产生器
虽然你可以任意的指定模块在你的代码文件树上位置, 但是, 还是严重的推荐使用google的默认的标准方式, 这样会省掉很多功夫。

入口类

被制定为入口类的类必须具有一个无参数的构造器,入口类是通过EntryPoint.onModuleLoad()
方法初始化的。理解上你可以将之看做一个main()方法。

源码路径

这个路径里面存放的就是所有需要被GWT
编译器转译(我觉得应该叫转译)成JS代码。当然也只有在源路径及其子目录下的文件才有可能被转译为JS代码。这个办法使得服务器和客户端的代码可以放在一个大的命名空间下面,而不冲突。默认的客户端代码的命名空间的末尾是:
client, 对应的服务器端是: server

公用资源路径

一个模块,可以通过将对应的子包的名字配置到公用源下面来指定那些子包是公用的。一般公用资源是HTML,图片啊,其他的什么。
当转译发生是,所有的公共源下面的东西都被复制到模块的输出目录。具体到URL地址的时候,
公共资源的访问是不需要加入包名路径的。如果模块有发生继承,则所有的公共资源将被合并到一起。

继承
GWT1.5提供的功能都是按照模块组成的, 下表提供了一个概览,
Standard Modules GWT 1.5
Module Logical Name Module Definition Contents
User com.google.gwt.user.User User.gwt.xml GWT 核心功能
HTTP com.google.gwt.http.HTTP HTTP.gwt.xml 底层的HTTP链接库
JSON com.google.gwt.json.JSON JSON.gwt.xml JSON 创建和解析
JUnit com.google.gwt.junit.JUnit JUnit.gwt.xml 整合的单元测试框架
XML com.google.gwt.xml.XML XML.gwt.xml XML文件的创建和解析

模块的主题
有些象dot net 和 java, GWT的组建也有界面主题的概念, 下面的这些我是照抄过来的。
Module Logical Name
Module Definition Contents
Chrome com.google.gwt.user.theme.chrome.Chrome Chrome.gwt.xml
chrome 浏览器风格的主题
Dark com.google.gwt.user.theme.dark.Dark Dark.gwt.xml 黑暗风格的主题
Standard com.google.gwt.user.theme.standard.Standard Standard.gwt.xml 这个是默认的

2009年7月4日星期六

[RPC]Java远程通讯可选技术及原理

在分布式服务框架中,一个最基础的问题就是远程服务是怎么通讯的,在Java领域中有很多可实现远程通讯的技术,例如:RMI、MINA、ESB、
Burlap、Hessian、SOAP、EJB和JMS等,这些名词之间到底是些什么关系呢,它们背后到底是基于什么原理实现的呢,了解这些是实现分布式服务框架的基础知识,而如果在性能上有高的要求的话,那深入了解这些技术背后的机制就是必须的了,在这篇blog中我们将来一探究竟,抛砖引玉,欢迎大家提供更多的实现远程通讯的技术和原理的介绍。

基本原理

要实现网络机器间的通讯,首先得来看看计算机系统网络通信的基本原理,在底层层面去看,网络通信需要做的就是将流从一台计算机传输到另外一台计算机,基于传输协议和网络IO来实现,其中传输协议比较出名的有http、tcp、udp等等,http、tcp、udp都是在基于Socket概念上为某类应用场景而扩展出的传输协议,网络IO,主要有bio、nio、aio三种方式,所有的分布式应用通讯都基于这个原理而实现,只是为了应用的易用,各种语言通常都会提供一些更为贴近应用易用的应用层协议。

应用级协议

远程服务通讯,需要达到的目标是在一台计算机发起请求,另外一台机器在接收到请求后进行相应的处理并将结果返回给请求端,这其中又会有诸如one
way request、同步请求、异步请求等等请求方式,按照网络通信原理,需要实现这个需要做的就是将请求转换成流,通过传输协议传输至远端,远端计算机在接收到请求的流后进行处理,处理完毕后将结果转化为流,并通过传输协议返回给调用端。
原理是这样的,但为了应用的方便,业界推出了很多基于此原理之上的应用级的协议,使得大家可以不用去直接操作这么底层的东西,通常应用级的远程通信协议会提供:

1. 为了避免直接做流操作这么麻烦,提供一种更加易用或贴合语言的标准传输格式;
2. 网络通信机制的实现,就是替你完成了将传输格式转化为流,通过某种传输协议传输至远端计算机,远端计算机在接收到流后转化为传输格式,并进行存储或以某种方式通知远端计算机。

所以在学习应用级的远程通信协议时,我们可以带着这几个问题进行学习:

1. 传输的标准格式是什么?
2. 怎么样将请求转化为传输的流?
3. 怎么接收和处理流?
4. 传输协议是?

不过应用级的远程通信协议并不会在传输协议上做什么多大的改进,主要是在流操作方面,让应用层生成流和处理流的这个过程更加的贴合所使用的语言或标准,至于传输协议则通常都是可选的,在java领域中知名的有:RMI、XML-RPC、Binary-RPC、SOAP、CORBA、JMS,来具体的看看这些远程通信的应用级协议:

RMI

RMI是个典型的为java定制的远程通信协议,我们都知道,在single vm中,我们可以通过直接调用java object
instance来实现通信,那么在远程通信时,如果也能按照这种方式当然是最好了,这种远程通信的机制成为RPC(Remote
Procedure Call),RMI正是朝着这个目标而诞生的。

来看下基于RMI的一次完整的远程通信过程的原理:

1. 客户端发起请求,请求转交至RMI客户端的stub类;
2. stub类将请求的接口、方法、参数等信息进行序列化;
3. 基于socket将序列化后的流传输至服务器端;
4. 服务器端接收到流后转发至相应的skelton类;
5. skelton类将请求的信息反序列化后调用实际的处理类;
6. 处理类处理完毕后将结果返回给skelton类;
7. Skelton类将结果序列化,通过socket将流传送给客户端的stub;
8. stub在接收到流后反序列化,将反序列化后的Java Object返回给调用者。

根据原理来回答下之前学习应用级协议带着的几个问题:

1. 传输的标准格式是什么?
是Java ObjectStream。
2. 怎么样将请求转化为传输的流?
基于Java串行化机制将请求的java object信息转化为流。
3. 怎么接收和处理流?
根据采用的协议启动相应的监听端口,当有流进入后基于Java串行化机制将流进行反序列化,并根据RMI协议获取到相应的处理对象信息,进行调用并处理,处理完毕后的结果同样基于java串行化机制进行返回。
4. 传输协议是?
Socket。

XML-RPC

XML-RPC也是一种和RMI类似的远程调用的协议,它和RMI的不同之处在于它以标准的xml格式来定义请求的信息(请求的对象、方法、参数等),这样的好处是什么呢,就是在跨语言通讯的时候也可以使用。

来看下XML-RPC协议的一次远程通信过程:

1. 客户端发起请求,按照XML-RPC协议将请求信息进行填充;
2. 填充完毕后将xml转化为流,通过传输协议进行传输;
3. 接收到在接收到流后转换为xml,按照XML-RPC协议获取请求的信息并进行处理;
4. 处理完毕后将结果按照XML-RPC协议写入xml中并返回。

同样来回答问题:

1. 传输的标准格式是?
标准格式的XML。
2. 怎么样将请求转化为传输的流?
将XML转化为流。
3. 怎么接收和处理流?
通过监听的端口获取到请求的流,转化为XML,并根据协议获取请求的信息,进行处理并将结果写入XML中返回。
4. 传输协议是?
Http。

Binary-RPC

Binary-RPC看名字就知道和XML-RPC是差不多的了,不同之处仅在于传输的标准格式由XML转为了二进制的格式。

同样来回答问题:

1. 传输的标准格式是?
标准格式的二进制文件。
2. 怎么样将请求转化为传输的流?
将二进制格式文件转化为流。
3. 怎么接收和处理流?
通过监听的端口获取到请求的流,转化为二进制文件,根据协议获取请求的信息,进行处理并将结果写入XML中返回。
4. 传输协议是?
Http。

SOAP

SOAP原意为Simple Object Access
Protocol,是一个用于分布式环境的、轻量级的、基于XML进行信息交换的通信协议,可以认为SOAP是XML
RPC的高级版,两者的原理完全相同,都是http+XML,不同的仅在于两者定义的XML规范不同,SOAP也是Webservice采用的服务调用协议标准,因此在此就不多加阐述了。

CORBA

Common Object Request Broker
Architecture(公用对象请求代理[调度]程序体系结构),是一组用来定义"分布式对象系统"的标准,由OMG(Object
Menagement Group)作为发起和标准制定单位。CORBA的目的是定义一套协议,符合这个协议的对象可以互相交互,不论它们是用什么样的语言写的,不论它们运行于什么样的机器和操作系统。
CORBA在我看来是个类似于SOA的体系架构,涵盖可选的远程通信协议,但其本身不能列入通信协议这里来讲,而且CORBA基本淘汰,再加上对CORBA也不怎么懂,在此就不进行阐述了。

JMS

JMS 呢,是实现java领域远程通信的一种手段和方法,基于JMS实现远程通信时和RPC是不同的,虽然可以做到RPC的效果,但因为不是从协议级别定义的,因此我们不认为JMS是个RPC协议,但它确实是个远程通信协议,在其他的语言体系中也存在着类似JMS的东西,可以统一的将这类机制称为消息机制,而消息机制呢,通常是高并发、分布式领域推荐的一种通信机制,这里的主要一个问题是容错(详细见ErLang论文)。

来看JMS中的一次远程通信的过程:

1. 客户端将请求转化为符合JMS规定的Message;
2. 通过JMS API将Message放入JMS Queue或Topic中;
3. 如为JMS Queue,则发送中相应的目标Queue中,如为Topic,则发送给订阅了此Topic的JMS Queue。
4. 处理端则通过轮训JMS Queue,来获取消息,接收到消息后根据JMS协议来解析Message并处理。

回答问题:

1. 传输的标准格式是?
JMS规定的Message。
2. 怎么样将请求转化为传输的流?
将参数信息放入Message中即可。
3. 怎么接收和处理流?
轮训JMS Queue来接收Message,接收到后进行处理,处理完毕后仍然是以Message的方式放入Queue中发送或Multicast。
4. 传输协议是?
不限。

基于JMS也是常用的实现远程异步调用的方法之一。

可选实现技术

当然,在上面的原理中并没有介绍到所有的java领域可选的远程通信协议了,例如还有EJB采用的ORMI、Spring自己定义的一个简单的Http
Invoker等等。
看完原理后我们再来看看目前java领域可用于实现远程通讯的框架或library,知名的有:JBoss-Remoting、Spring-
Remoting、Hessian、Burlap、XFire(Axis)、ActiveMQ、Mina、Mule、EJB3等等,来对每种做个简单的介绍和评价,其实呢,要做分布式服务框架,这些东西都是要有非常深刻的了解的,因为分布式服务框架其实是包含了解决分布式领域以及应用层面领域两方面问题的。
当然,你也可以自己根据远程网络通信原理(transport protocol+Net IO)去实现自己的通讯框架或library。

那么在了解这些远程通讯的框架或library时,会带着什么问题去学习呢?

1. 是基于什么协议实现的?
2. 怎么发起请求?
3. 怎么将请求转化为符合协议的格式的?
4. 使用什么传输协议传输?
5. 响应端基于什么机制来接收请求?
6. 怎么将流还原为传输格式的?
7. 处理完毕后怎么回应?

JBoss-Remoting

Jboss-remoting是由jboss编写的一个java领域的远程通讯框架,基于此框架,可以很简单的实现基于多种传输协议的java对象的RPC。

直接来回答问题:

1. 是基于什么协议实现的?
JBoss-Remoting是个通讯框架,因此它支持多种协议方式的通信,例如纯粹的socket+io方式、rmi方式、http+io方式等。
2. 怎么发起请求?
在JBoss-Remoting中,只需将需要发起的请求参数对象传入jboss-remoting的InvocationRequest对象即可,也可根据协议基于InvocationRequest封装符合需求的InvocationRequest对象。
3. 怎么将请求转化为符合协议的格式的?
JBoss-Remoting基于Java串行化机制或JBoss自己的串行化实现来将请求转化为对象字节流。
4. 使用什么传输协议传输?
支持多种传输协议,例如socket、http等。
5. 响应端基于什么机制来接收请求?
响应端只需将自己的处理对象注册到JBoss-Remoting提供的server端的Connector对象中即可。
6. 怎么将流还原为传输格式的?
JBoss-Remoting基于java串行化机制或jboss自己的串行化实现来将请求信息还原为java对象。
7. 处理完毕后怎么回应?
处理完毕后将结果对象直接返回即可,jboss-remoting会将此对象按照协议进行序列化,返回至调用端。

另外,jboss-remoting支持多种通信方式,例如同步/异步/单向通信等。

Spring-Remoting

Spring-remoting是Spring提供java领域的远程通讯框架,基于此框架,同样也可以很简单的将普通的spring
bean以某种远程协议的方式来发布,同样也可以配置spring bean为远程调用的bean。

1. 是基于什么协议实现的?
和JBoss-Remoting一样,作为一个远程通讯的框架,Spring通过集成多种远程通讯的library,从而实现了对多种协议的支持,例如rmi、http+io、xml-rpc、binary-rpc等。
2. 怎么发起请求?
在Spring中,由于其对于远程调用的bean采用的是proxy实现,发起请求完全是通过服务接口调用的方式。
3. 怎么将请求转化为符合协议的格式的?
Spring按照协议方式将请求的对象信息转化为流,例如Spring Http
Invoker是基于Spring自己定义的一个协议来实现的,传输协议上采用的为http,请求信息是基于java串行化机制转化为流进行传输。
4. 使用什么传输协议传输?
支持多种传输协议,例如rmi、http等等。
5. 响应端基于什么机制来接收请求?
响应端遵循协议方式来接收请求,对于使用者而言,则只需通过spring的配置方式将普通的spring bean配置为响应端或者说提供服务端。
6. 怎么将流还原为传输格式的?
按照协议方式来进行还原。
7. 处理完毕后怎么回应?
处理完毕后直接返回即可,spring-remoting将根据协议方式来做相应的序列化。

Hessian

Hessian是由caucho提供的一个基于binary-RPC实现的远程通讯library。

1. 是基于什么协议实现的?
基于Binary-RPC协议实现。
2. 怎么发起请求?
需通过Hessian本身提供的API来发起请求。
3. 怎么将请求转化为符合协议的格式的?
Hessian通过其自定义的串行化机制将请求信息进行序列化,产生二进制流。
4. 使用什么传输协议传输?
Hessian基于Http协议进行传输。
5. 响应端基于什么机制来接收请求?
响应端根据Hessian提供的API来接收请求。
6. 怎么将流还原为传输格式的?
Hessian根据其私有的串行化机制来将请求信息进行反序列化,传递给使用者时已是相应的请求信息对象了。
7. 处理完毕后怎么回应?
处理完毕后直接返回,hessian将结果对象进行序列化,传输至调用端。

Burlap

Burlap也是有caucho提供,它和hessian的不同在于,它是基于XML-RPC协议的。

1. 是基于什么协议实现的?
基于XML-RPC协议实现。
2. 怎么发起请求?
根据Burlap提供的API。
3. 怎么将请求转化为符合协议的格式的?
将请求信息转化为符合协议的XML格式,转化为流进行传输。
4. 使用什么传输协议传输?
Http协议。
5. 响应端基于什么机制来接收请求?
监听Http请求。
6. 怎么将流还原为传输格式的?
根据XML-RPC协议进行还原。
7. 处理完毕后怎么回应?
返回结果写入XML中,由Burlap返回至调用端。

XFire、Axis

XFire、Axis是Webservice的实现框架,WebService可算是一个完整的SOA架构实现标准了,因此采用XFire、Axis这些也就意味着是采用webservice方式了。

1. 是基于什么协议实现的?
基于SOAP协议。
2. 怎么发起请求?
获取到远端service的proxy后直接调用。
3. 怎么将请求转化为符合协议的格式的?
将请求信息转化为遵循SOAP协议的XML格式,由框架转化为流进行传输。
4. 使用什么传输协议传输?
Http协议。
5. 响应端基于什么机制来接收请求?
监听Http请求。
6. 怎么将流还原为传输格式的?
根据SOAP协议进行还原。
7. 处理完毕后怎么回应?
返回结果写入XML中,由框架返回至调用端。

ActiveMQ

ActiveMQ是JMS的实现,基于JMS这类消息机制实现远程通讯是一种不错的选择,毕竟消息机制本身的功能使得基于它可以很容易的去实现同步/异步/单向调用等,而且消息机制从容错角度上来说也是个不错的选择,这是Erlang能够做到容错的重要基础。

1. 是基于什么协议实现的?
基于JMS协议。
2. 怎么发起请求?
遵循JMS API发起请求。
3. 怎么将请求转化为符合协议的格式的?
不太清楚,猜想应该是二进制流。
4. 使用什么传输协议传输?
支持多种传输协议,例如socket、http等等。
5. 响应端基于什么机制来接收请求?
监听符合协议的端口。
6. 怎么将流还原为传输格式的?
同问题3。
7. 处理完毕后怎么回应?
遵循JMS API生成消息,并写入JMS Queue中。

基于JMS此类机制实现远程通讯的例子有Spring-Intergration、Mule、Lingo等等。

Mina

Mina是Apache提供的通讯框架,在之前一直没有提到网络IO这块,之前提及的框架或library基本都是基于BIO的,而Mina是采用NIO
的,NIO在并发量增长时对比BIO而言会有明显的性能提升,而java性能的提升,与其NIO这块与OS的紧密结合是有不小的关系的。

1. 是基于什么协议实现的?
基于纯粹的Socket+NIO。
2. 怎么发起请求?
通过Mina提供的Client API。
3. 怎么将请求转化为符合协议的格式的?
Mina遵循java串行化机制对请求对象进行序列化。
4. 使用什么传输协议传输?
支持多种传输协议,例如socket、http等等。
5. 响应端基于什么机制来接收请求?
以NIO的方式监听协议端口。
6. 怎么将流还原为传输格式的?
遵循java串行化机制对请求对象进行反序列化。
7. 处理完毕后怎么回应?
遵循Mina API进行返回。

MINA是NIO方式的,因此支持异步调用是毫无悬念的。

EJB

EJB最突出的在于其分布式,EJB采用的是ORMI协议,和RMI协议是差不多的,但EJB在分布式通讯的安全控制、transport
pool、smart proxy等方面的突出使得其在分布式领域是不可忽视的力量。

1. 是基于什么协议实现的?
基于ORMI协议。
2. 怎么发起请求?
EJB调用。
3. 怎么将请求转化为符合协议的格式的?
遵循java串行化机制对请求对象进行序列化。
4. 使用什么传输协议传输?
Socket。
5. 响应端基于什么机制来接收请求?
监听协议端口。
6. 怎么将流还原为传输格式的?
遵循java串行化机制对请求对象进行反序列化。
7. 处理完毕后怎么回应?
直接返回处理对象即可。

在之前的分布式服务框架系列的文章中对于jndi有误导的嫌疑,在这篇blog中也顺带的提下jndi的机制,由于JNDI取决于具体的实现,在这里只能是讲解下jboss的jndi的实现了。

在将对象实例绑定到jboss jnp
server后,当远程端采用context.lookup()方式获取远程对象实例并开始调用时,jboss jndi的实现方法是从jnp
server上获取对象实例,将其序列化回本地,然后在本地进行反序列化,之后在本地进行类调用。
通过这个机制,就可以知道了,本地其实是必须有绑定到jboss上的对象实例的class的,否则反序列化的时候肯定就失败了,而远程通讯需要做到的是在远程执行某动作,并获取到相应的结果,可见纯粹基于JNDI是无法实现远程通讯的。
但JNDI也是实现分布式服务框架一个很关键的技术点,因为可以通过它来实现透明化的远端和本地调用,就像ejb,另外它也是个很好的隐藏实际部署机制(就像datasource)等的方案。

总结

由上一系列的分析可知,在远程通讯领域中,涉及的知识点还是相当的多的,例如有:通信协议(Socket/tcp/http/udp/rmi/xml-
rpc etc.)、消息机制、网络IO(BIO/NIO/AIO)、MultiThread、本地调用与远程调用的透明化方案(涉及java
classloader、Dynamic Proxy、Unit Test
etc.)、异步与同步调用、网络通信处理机制(自动重连、广播、异常、池处理等等)、Java Serialization
(各种协议的私有序列化机制等)、各种框架的实现原理(传输格式、如何将传输格式转化为流的、如何将请求信息转化为传输格式的、如何接收流的、如何将流还原为传输格式的等等),要精通其中的哪些东西,得根据实际需求来决定了,只有在了解了原理的情况下才能很容易的做出选择,甚至可以根据需求做私有的远程通讯协议,对于从事分布式服务平台或开发较大型的分布式应用的人而言,我觉得至少上面提及的知识点是需要比较了解的。

参考文档(感谢这些文章)
RMI原理及实现:http://www.yesky.com/274/1625274.shtml
Java NIO原理和使用:
http://www.jdon.com/concurrent/nio%D4%AD%C0%ED%D3%A6%D3%C3.htm
XML RPC协议:http://hedong.3322.org/archives/000470.html
http://www.mengyan.org/blog/archives/2005/07/12/30.html
Spring技术应用中的远程服务详解:http://www.builder.com.cn/2007/1027/583384.shtml
JAVA RPC通信机制之SOAP:http://www.java114.com/content16/content3826.html
Java Remoting:Protocol BenchMarks:http://q.sohu.com/forum/5/topic/1148909
Evalution of RMI
Alternative:https://www.jfire.org/modules/phpwiki/index.php/Evaluation%20of%20RMI%20Alternative
Metaprotocol Taxonomy:http://hessian.caucho.com/doc/metaprotocol-taxonomy.xtp
什么是Webservice:http://www.vchome.net/dotnet/webservice/webservice15.htm

[转]同步和异步的区别

***答案一:
1.异步传输

通常,异步传输是以字符为传输单位,每个字符都要附加 1 位起始位和 1
位停止位,以标记一个字符的开始和结束,并以此实现数据传输同步。所谓异步传输是指字符与字符(一个字符结束到下一个字符开始)之间的时间间隔是可变的,并不需要严格地限制它们的时间关系。起始位对应于二进制值
0,以低电平表示,占用 1 位宽度。停止位对应于二进制值 1,以高电平表示,占用 1~2 位宽度。一个字符占用
5~8位,具体取决于数据所采用的字符集。例如,电报码字符为 5 位、ASCII码字符为 7 位、汉字码则为8 位。此外,还要附加 1
位奇偶校验位,可以选择奇校验或偶校验方式对该字符实施简单的差错控制。发送端与接收端除了采用相同的数据格式(字符的位数、停止位的位数、有无校验位及校验方式等)外,还应当采用相同的传输速率。典型的速率有:9
600 b/s、19.2kb/s、56kb/s等。

异步传输又称为起止式异步通信方式,其优点是简单、可靠,适用于面向字符的、低速的异步通信场合。例如,计算机与Modem之间的通信就是采用这种方式。它的缺点是通信开销大,每传输一个字符都要额外附加2~3位,通信效率比较低。例如,在使用Modem上网时,普遍感觉速度很慢,除了传输速率低之外,与通信开销大、通信效率低也密切相关。


--------------------------------------------------------------------------------

2. 同步传输

通常,同步传输是以数据块为传输单位。每个数据块的头部和尾部都要附加一个特殊的字符或比特序列,标记一个数据块的开始和结束,一般还要附加一个校验序列
(如16位或32位CRC校验码),以便对数据块进行差错控制。所谓同步传输是指数据块与数据块之间的时间间隔是固定的,必须严格地规定它们的时间关系。

***答案二:
请讲详细一些,本人比较弱智,谢谢各位
---------------------------------------------------------------

同步是阻塞模式,异步是非阻塞模式。
---------------------------------------------------------------

我的理解:同步是指两个线程的运行是相关的,其中一个线程要阻塞等待另外一个线程的运行。异步的意思是两个线程毫无相关,自己运行自己的。

不知对错?楼下说
---------------------------------------------------------------

同步是指:发送方发出数据后,等接收方发回响应以后才发下一个数据包的通讯方式。
异步是指:发送方发出数据后,不等接收方发回响应,接着发送下个数据包的通讯方式。

CSDN上有讨论过:
http://expert.csdn.net/Expert/topic/2646/2646592.xml?temp=.3842584
http://expert.csdn.net/Expert/topic/2659/2659726.xml?temp=.1480219
---------------------------------------------------------------

举个不太恰当的例子,就像:
SendMessage(...)
TRACE0("just like send");

PostMessage(...)
TRACE0("just like WSASend using overlapped");

SendMessage是调用的时候不返回,等消息响应后才执行TRACE0,这就是同步.
PostMessage是调用后马上返回,不用消息响应就执行TRACE0,这就是异步.

***答案三:
同步和异步的区别

举个例子:普通B/S模式(同步)AJAX技术(异步)
同步:提交请求->等待服务器处理->处理完毕返回 这个期间客户端浏览器不能干任何事
异步: 请求通过事件触发->服务器处理(这是浏览器仍然可以作其他事情)->处理完毕
--------------------------------------------------------------------------------------------------------------------
同步就是你叫我去吃饭,我听到了就和你去吃饭;如果没有听到,你就不停的叫,直到我告诉你听到了,才一起去吃饭。
异步就是你叫我,然后自己去吃饭,我得到消息后可能立即走,也可能等到下班才去吃饭。

所以,要我请你吃饭就用同步的方法,要请我吃饭就用异步的方法,这样你可以省钱。
--------------------------------------------------------------------------------------------------------------------
举个例子 打电话时同步 发消息是异步

2009年7月2日星期四

[解决方法]CHM打开提示:Internet Explorer 不能链接到您请求的网页 此页可能暂时不可用

打开一个.chm文件的时候报错:“Internet Explorer 不能链接到您请求的网页。此页可能暂时不可用”,如下图


因 为chm的访问存在严重的安全漏洞。虽然微软为此发布了很多的security patch,但是,显然还不够。所以它特地加上了这个功能,当你打开这种来路不明(对系统来说)的chm文档时,系统会提醒你可能存在风险。而如果你选择 强制打开,那么IE会忽略msits协议,结果就是你看到的“无法显示网页”的错误页面。

解决方法有二:


1、在要打开的CHM文件上点鼠标右键属性,会在底下属性中多了一个“解除锁定”按钮,点击后就可以打开该CHM正常显示了。

2、上面第1个方法只能解决单独一个CHM文件的打开问题,要想一劳永逸地能打开所有CHM文件,解决办法是:

新建一个文本文件,将下面这段复制过去,然后将文本文件扩展名改为.reg后,双击导入,就ok了。

REGEDIT4  
[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\HTMLHelp]  

[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\HTMLHelp\\1.x\\HHRestrictions]  
"MaxAllowedZone"=dword:00000003  
"UrlAllowList"=""  

[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\HTMLHelp\\1.x\\ItssRestrictions]  
"MaxAllowedZone"=dword:00000003  
"UrlAllowList"=""