开发者学堂课程【高校精品课-上海交通大学-企业级应用体系架构:Distributed Object】学习笔记,与课程紧密联系,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/75/detail/15821
Distributed Object
内容介绍:
一、 分布对象的介绍
二、 示例讲解
三、 RMI 协议的内容与拓展
四、 代码类的分析
五、 服务器端的工作
一、 分布对象的介绍
今天上第二次课仍然是探讨服务器端的构建,讲分布式的对象,实际上分布式对象,就是指我们的程序,互相之间要进行这种远程的通信。
l The roles of client and server
l Remote method calls
l The RMI programming model
l Parameters and return values in remote methods
l Remote object activation
所以要讲的内容就是远程通信里面服务器端和客户端各是一个什么样的角色,然后远程方法调用如何来实现?在Java里因为前面的课一直都是 Java 来讲,Java 里面关于能调用使用的 RMI 编程模型,它的编程模型应该是什么样?谈谈在远程方法调用里面,如果想返回参数或者其它类型返回,就是想传递参数和其它类型的返回值该怎么做?要讲远程方法调用,先要问一个问题。觉得下面我写的这两个场景,哪一个或者哪两个或者哪两个都不是远程通信的场景。第一个是有两个程序,它们运行在两个不同的机器上,第二个场景是这两个程序存在同一台机器上。
二、 示例讲解
大家可以思考一下:
答案是 A,B 都是。
解释:
所谓一个程序。就是在操作系统上跑,是一个单独的进程,在进程里,如果是32位机器,当然64位机器无非就是大一点,在32位的机器里一个内一个程序是一个改进,城市可以有4GB 的空间的内存空间,所以在它的内存空间里,知道内存空间是陷阱。就是线性排列,线性的里面,每一个可以从空间,从这0GB 到4GB,实际上都有一个唯一编码。也就是当你要去用一个地址去访问一个变量的时候。用这32位的一个地址。是否去访问,最多访问到它自己的进程里的这4GB 的空间,当 A 和 B,这两个程序要进行通信的时候。还有4GB 的空间,所谓通信就是想程序进程想访问的进程里的某一个地址上的一个对象,比如A要去调用B里面的某一个对象,来表示在上面用 M 方法,但先要知道 A 在哪里,就得到它的地址,但是得到地址,会发现直接用这32位地址,始终只能访问到的是自己 A 的这程序的刺激的空间,不会映射到 B 这程序的4GB 的空间。所以无论怎么访问,在32位地址里面填任何一个地址进去,没有办法访问到B程序设计的空间里。
这就是两个进程之间,实际上内存是互相独立的,是隔离的,要想通信,不可能直接通过地址去访问,所以这是这两个程序之间要通信,就必须是远程通信。
第二个问题,之前也碰到过这样的问题,比如这是我们用的,里面部署了我们的用 Tomlat,是使用的数据库。开始在访问数据库的时候,都配了 Datesourse。在配套之后回忆一下,当时都在写 HTTP。然后就是 IP,数据库服务器的IP当然大,当时大多数同学的机器还都是在1台上跑车,这里基本上写都是 l ogo。
然后是3306端口,这样去访问的数据数据源的名字,比如 test 的数据。所以可以看到,可以有其它的方法来进行统计。最常用的可能是 HTTP 协议,现在的问题就是 HTTP 协议既然可以实现两个进程之间的通信,在一台机器上。或者在一个比较小的局域网里面有两台机器,在一个局域网里,A 和 B 两个进程,也用一个 HTTP 协议来访问,不是挺好的吗?为什么要去考虑向这里提到的 RMI 这种方式?来审视一下 HTTP 协议在 A 和 B 之间能传递什么?
可以传递之前看到的 HTML 的页面,其实里面还带了 CS 的脚本,还带了 JS 的脚本,总的来讲,它们之间传递的,实际上是以内容为主的,可以传输这一次。就是只传数据。至于前端的页面的展示,完全交给像 react 或者 vall 框架来解决。但是本质上来看一看这两类内容是纯文本。也就是在 HTTP 的协议里面,在客户端和服务器端之间通信的时候,实际上传递这种文本数据。可以传图片,不要忘了图片需要转成 BASE64。
要转成 BASE64,本质上是一种字节数组,所以传的是一种文本。它的限制既然在这里,就带来一个什么问题,如果真的想去通过远程的知道一个对象在上面调 M,也就是在B 返回给 A,或者要想调用在通过,要去跟 B 通信的时候,是要通过一个对象去得到的,要得到 B 里面运行的对象 A 的地址,然后在上面调M,这时如果通过 HTTP 的协议,怎么样才能得到一个对象的引用呢?实际上是没有办法处理的,所以 CP 协议有它的限制。在全部转成文本之后,即便也可以传对象,传的是 Jason,Jason 可是以键值对的方式在表示所有的数据。实际上只是一个文本化的一个对象的一个描述,它的一个最大的问题,没有所谓的方法概念,不可能把一个对象的方法包装起来,传递出来。所以实际上只也只是一个数据的封装,要真想要得到一个对象,在对象上调用方法,它的逻辑 HTTP 协议一下,这不合适。所以需要有其它的协议来处理。今天讲的 RMI,也就是远程方法调用,就是要解决问题。
三、 RMI 协议的内容与延申
远程方法调用就是现在看到的客户端和服务器之间要进行沟通,互相之间要发送数据,并且要返回数据。但是现在的问题是这里的请求数据不是像 HTTP 协议一样一个比较简单的一个 个 HTTP 请求,它的参数是以 URL 后面,不是比较繁琐的传递,这样的传递已经限制了在客户端和服务器端进行通信的时候,其实传递的参数只能是基本类型,比如是个整数,是一个字符串,这样写没问题,如果传递的是一个对象,比如 book 对象,这种没有办法去表示,或者至少没有一种很简单的方法表示,需要转化另一种形式去表示。
本质上来讲,已经不是一个对象,道理在返回的数据里面,可以是 HTML 的页面,但不管怎么说,它只是一个纯文本的方式描述的一个对象,数据仅仅只是个数据,它不再是一个带行为的带方法的这样一个对象。所以实际上我们是受限于数据协议里面受限,现在就要想,没有办法能够在传递的对数据里面能表示一个对象,而且在返回的数据里面能返回一个对象的引用,就是 C 这一端有一个对象A,能把它的引用传递回来,发送回客户端,让客户端能够得到A一旦客户端得到了 A 之后,它就可以像一个本地方法一样去调用它,直接在上面调用 RMI 方法,很显然,它和 HTTP有存在的一个比较大的区别,它是能够返回对象类型的东西来调用,所以 RMI 这种方式,面对的所谓的客户端,实际上更多的是一个程序。只有写的程序才会想要得到一个远程的对象在身边好方法,前面讲的 HTTP 的客户端更多的是真正的用户,终端用户是在哪?也就是终端用户通过浏览器来访问,是要去看结果,是要拿眼睛去看,所以 HTTP 和RMI 它们对应的对象用户是不一样的。
AMI 它面向的是程序,HTTP 面向的是用户,所以用户是需要看出去,而程序是要得到程序里面。选择这些对象,有本质的不同,所以讲起来很简单,就是客户端的程序要发请求,请求通过网络发送到服务器端,服务器端在处理完之后要产生一个响应,发回客户端,但是本质上它们之间有很大的差异。刚才讲到了一个问题,服务器端,实际上对象,没有办法在客户端直接通过内存的地址去访问。所以要把调用要包装一下。包装一下是什么意思呢?就是要把调用,调用里面实际上是包含了 A 远程对象的 M。
方法里面可能还带一个参数 B,要把 A 远程方法本身打包,就要把 B 本身可能是 book 类型的一个对象,打包是什么意思呢?对应的一个动作就是要把序列化一下变成个字节流,然后传递到服务器端去,这件事情本身可以看到,和客户端的业务逻辑是没有任何关系。只是拿到一个对象,然后拿到它的方法,拿到它的参数之后,就把它拆解,解析出来,然后再包装成箱,直接流传过。其实无论是哪一种调用,基本的逻辑都一样,所以需要一个所谓的代理,代理就专门做这件事情。要把客户端发送的这种请求要翻译成在网络上可以去执行的,可以去传递的这样的一个字节流发送到服务器端,同样服务器端也要有一个代理,要把字节流转,换回一个 Java 调用传递给服务器端,服务器端在处理完之后走同样的路径,把结果给代理,结果到了代理之后,同样要把它做一个处理,把它包装成适合在网络上传输的形式。
然后传递给客户端,客户端的代理再次解析。翻译成 Java 方法,调用的结果给客户端。客户端于是就拿到了A调用,用 B 的对象来调用 A 对象的 M 方法的结果。所以在过程当中,可以看到客户端和服务器都只关注于自己的业务逻辑,而proxy 实现了远程客户端要去调用的服务器的接口,但是实现是在做业务逻辑的处理,是在怎么把它包装成适合在网络上传输的格式,或者是做内容的解析,从网络上的字节流转换成大的调用,需要这样的一个代理,走任何一种远程方法调用的框架,都是需要的,实际上 RMI 是 Java 的远程方法调用,实际上是一个更泛化的一个东西,PC 也就是远程过程调用的一个夹板的实现。只要是 RPC 的调用,没有办法直接访问搜索进程,一定需要一个 proxy 在来做转换,在这里做转换的时候,会暴露出来一个接口。
接口里面有一个 N 方法就是对于 A 类来讲,对于 M 方法。看到的是 server 端肯定有一个实现类要实现 A 给 A的一个具体的执行逻辑,但是看到 proxy。proxy 实际上也实现了 i nterface。interface 但是它的实现逻辑不是在做业务逻辑的实现,它的实现逻辑是先分析一下,写个伪代码就先分析一下。调用可以审核。然后把它组装发走。
过程叫 invitation,然后把它发走。大概就是这样的逻辑,所以形式上看, proxy 也在实现要暴露出来的接口,但它的实现内容就这里看到的不是在执行业务逻辑,而是在怎么把调用变成一个在适合在网络上传输的格式。从这里可以看到,Proxy 是必须的,要来实现客户端和服务器端之间的交互。但是再仔细去看,在客户端的 proxy 和在服务器端的 proxy 还有点差异,客户端的 practice 把调用转换成对,在网络上可以传输的这种字节流,而服务器端的 proxy,要把服务客户端发送过来的字节流翻译成对 server 的调用。所以大家可以看到,Proxy 是在把客户端调用转换成网络字节流,然后把网络直接流转换成方法调用返回,而服务器端的 proxy 是在做字节流到方法调用的转换,然后是方法调用的结果转换成字节流。它们有点相反,所以它们不是同一个方法,在客户端的 PC 和服务器端的 proxy 是不同的方法。然后再讲解为什么这种方法叫 proxy。
proxy 就是所谓的代理模式,代理模式是什么呢?所谓代理,就是当一个对象被另外一个对象给代理时,代理就代表对象去进行通信,去进行处理,别人可能就不会再直接跟着被代理的对象进行直接交互,所以代理就就可以看到,在这张图里也可以明确的看到,没有直接和所有交互,甚至都没有直接和服务器端的 proxy 交互,是 proxy 对 proxy,两个互相不直接交互,实际上代理就是在做这样的工作。
后面讲到的时候,大家会看到,实际上在 client 眼里,与代理交互就相当于交互眼里看到的就是思维一样。这是什么意思呢?讲到后面之后再去看。在这里主要在这一页上要强调的,就是要明白的是,现在要通过代理,client 和 server之间是没有办法直接要通过代理来进行统计。在进行调用的时候,可以看到就是刚才讲的是 RPC 的一般规律,就是RPC 一般规律,所有的 RPC 都需要加入,在 Java 里,Java 的 RPC。就叫做远程方法调用,Java 里面没有 proper procedure 概念,所以 M 实际上就是夹板的一个 RPC。除 RMI 之外,Java 里还有其它的方式进行。这种远程的对象,互相之间的一个两个远程进程之间进行通信,比如柯达,这是一个比较老的协议,当然也可以用来作为体系,就是仍然是走HTTP协议。但是刚才讲了 HTTP 协议。面向的是用户,要向用户去表明。它传递的内容是什么,就展示出来,所以不是让用户去调用对象的方法,所以它和 RMI 存在着很大的差别,RMI一端就是一个程序,所以后面可以看到。给的例子里在客户端的这一端,际上也是一个程序。
刚才讲在客户端的 proxy 和 server 一端,proxy 实际上是有差异的。刚才一端的 Starbuck,叫 start,端实际上它还有一个名字叫 Stefan。它们不一样,就是反过来看到客户端。在 step 上面去做一个调用,会把参数,刚才的动作叫Marshall,把它变成一个字节流,经过 Marshal 以后,所有的调用加参数加其它的都变成了一个字节流,将节流给发走,把字节流恢复成一个方法调用。
然后 skeleton 拿到方法用的结果之后再收一下,就转换成字节流,是直接流里面解析它的返回值,或者是包装的一个异常。不管如何,它们的作用相当于翻译和反翻译,作用虽然都是 proxy,都是在进行通信,但它们实际的功能正好是反过来。刚才说过对于客户端来说,实际上得到了 stub,就相当于得到了一个服务器端的对象一样,因为被它代理,所有东西都是通过 Skype 来实现,可以看到这里的箭头没有直接跟交互,所以在编程的时候,可以看到所谓的要跟远程划定,就是要得到 start 对象。一旦得到了对象。就在客户端这边,现在看这两行代码都是在客户端的。
一旦得到了对象,它就是服务器对象,所以在它下面就是调用 get price 为什么可以调用 get price? 刚才说过有一个服务器端对象,有一个接口 M,实际上 proxy 它都实现了,a 对象的 M,就是它的服务器接口 a,都实现了一个 M 方法,只不过它的实现是在不执行业务逻辑,是在做网络上的数据的传递和解析的工作,所以拿到了 starve 对象,拿到对象之后,在它上面是这样 get stub 传递参数,然后就会把get price加上参数,翻译过来以后发到 silent,silent 发给 server,就得到了方法的反馈结果。
在 club 的这一端来看。只要能找到 stub,对我来说就相当于是一个服务器端的对象,在下面直接带动它的方法,至于这后面发生了这一长串的事情,被 client 全部都屏蔽了,最后就能得到方法的返回值,就是 price,这就是 RMI协议里面它的一个基本的工作原理,这里大可以看到在 HTTP 协议里没有办法传对象,但是在 RMI 的协议里,就可以把有办法传递一下。
可以看到无非是怎么得到对象的方法不一样,原来在全在本地,可能有一个方法,就出对象了,现在不是这样,New出来是通过某种方式得到 stub,但一旦得到远程对象,首先跟HTTP存在一个最大的差异,就是现在得到远程对象的引用,实际上对象是在服务器端运行的,但是可以调用它,所以不是在得到一个纯文本描述的一个数据。而且一旦得到了远程对象在使用方法上和使用一个本地对象是没有任何差异,可以看到没有做其它任何特殊的操作,直接就在上面去调用 get price 也就把它的方法发送到了服务器,服务器的反馈结果也就拿到了,所以一旦得到 stub,实际上就像一个本地对象一样去使用,无论是从获取对象的方式,还是使用对象方式,大家都可以看到 HTTP 协议是搞不定的,它的工作原理是不同的,而刚才反复强调是给客户端的使用,这时的客户端程序从这里可以看到,肯定是有这样的代码,如果不是这样的代码进行处理就没有必要用 MFC。
所以从这里面看到它背后发生的事情,是有个叫做Marshall的东西。程序里把对象的方法给了 stub 之后,Starbucks就会去把对象给 Marshall,所谓的 Marshall 就是构建出来一个块,一个信息块就是一个字节流,它包含什么呢?首先要调用一个远程方法,远程方法的 ID 是什么?然后其次方法要调用对象的哪个方法。再其次第二个方法有没有参数,刚才看到的所谓 a 传进去的。这三个信息就组装出来了一个信息快,而是所谓的信息块,就是一个字节流。
适合在网络上传输的字节流,然后字节流就会被发送走到服务集团,注意这里表示的是这一切东西。是 Starbucks 也在实现 get price 方法服务器端的接口的方法,只不过 stark 里面的 get price 方法体里面执行的就是这样一些逻辑,把这些东西构建出一个自己的把它发走,刚才它不会执行业务逻辑,业务逻辑并不在 start 里面举行在负责接收的,再看在服务器端,文件发过来了,服务器端 receiver 要做的动作已经刚才解开一下,用了哪一个远程对象,接收到之后,就会去定位远程对象在哪里,用的就是远程对象的 ID。然后告诉它要调哪个方法,所以就要去定位好对象之后,就去掉方法还要把传递的参数。剩下的事情就跟 receiver 对象没关系了,就是服务器端的真正的叫做 a 的对象。
执行的时候也有可能是抛错了,抛了个异常,有可能是反有返回值,不管是哪一种,把它也打了个包。打包的过程跟这里是类似的,就是要构建一个字节流,然后就是把返回值,或者将异常代码收一下,就给传回来传回到style,剩下的事情就是得把它再解析出来,反馈客户端与客户端拿到结果,在过程当中,可以看到,反复在说一个事,就是Marshall 拿手事怎么做。在 Java 里要想让一个对象可以被 Marshall,它的前提是必须实现 serializeeble 这样的一个接口,实现接口的对象才能够被 Marshall。是一个标记接口,也就是它里面实际上没有什么要求去实现的方法,只是标记一下,就是可序列化对象。
问题来了,在课里没有专门去列一个内容去讲,但缺的话要注意的是这样一个问题。从客户端的代码来说想要编译的通过这两行代码。必须要有 warehouse 的一个定义,否则编译不通过,所以道理是类似的,如果里面 price 不是一个double 类型,是一 个 book 类型。要返回本书,很显然,Client 这一端需要有 class 类的定义,很显然搜的这段也有。
因为要组装一个 book 返回给客户端去,所以也有一个 class,序列化有一个比较强的一个约束是如果客户端的book class,假设写个名字叫做0.1版本。搜索这段是昨天写的,跟今天写的有点不一样,比如0.01版本。今天写的跟昨天写的增加了一个字段。或者增加了一个方法,它们的类不太一样。这时候就告诉服务器端的顾客对象,是没有办法序列化回来以后在客户端去把它恢复出来的。
尽管昨天写的 book 类和今天写的 book 类都实现了 serializeeble 接口。它们也没办法恢复出来,所以讲的目的就是serializeeble 的前提是如果两端之间要传递book对象,必须要 serializeeble,即便是 serializeeble 接口。
实现了要保证两边的 book 类的定义,要是精确一致,就是所谓的 serializeeble。就完全一致,哪怕差一点也不行。
四、 代码类的分析
import java.rmi.*;
public interface Warehouse extends Remote
{
double getPrice(String description)throws RemoteException;
}
看一下,写服务器端和客户端的代码的时候,就是所谓的 R的编程模型是什么样子。服务器端首先要有一个接口,假设刚才看到是有一个叫做 warehouse 类型的接口,服务器端还有 get price 这样的方法,所以在编程的时候,肯定服务器端和客户端都要有的,把它定义成一个接口。接口就是给起个名字叫 warehouse,就是两边都要有,它要实现remote 接口。服务器端的对象都要实现 remote 接口,表明它是一个远程的可通信的一个对象。接口里面要注意的是什么呢?它其实是一个标记接口,就是一个 mark 的一个接口。没有什么其它类型的方法,需要特别去实现,所以warehouse 己定义的 get price 方法没有其它的方法必须要去列到里面,只是个标记,只是让用服务器,是编译器的编译的时候,就要去帮你去生成,刚才在图里看到的 stub 和 receive,稍微解释一下,写好 interface,正常情况下用编译器。肯定会生成这 warehouse.class 文件,但问题 stub 和 receiver 一个 scarlet,这两个对象是怎么生成呢?
在编译的时候,一旦发现有远程接口。在此基础之上调用,编译好叫做 warehouse.class。所以它是一个标记接口,告诉Java的编辑和虚拟机如何去动作,唯一的要求就是如果这方法在远程接口里面去定义,这方法必须要告诉要抛出远程异常。就是完全有可能因为通信的问题,导致方法非常正常,所以会抛异常,各种各样的都有可能会抛出异常,强制要求所有在 remote 接口里定义的方法都必须要声明被抛出 remote 异常,这是一个我们看到了它强制要求要做的,除此之外:
public class WarehouseImpl extends UnicastRemoteobject implements Warehouse
{
private Map<String,Double>prices;
public WarehouseImpl()throws RemoteException
{
prices new HashMap<>();
prices.put("Blackwell Toaster",24.95);
prices.put("ZapXpress Microwave Oven",49.95);
}
Public double getprice(String description)throws RemoteException
{
Double price prices.get(description);
return price =null ? 0:price;
}
}
另外一个远程接口和一个本地接口没有任何差别。所以看到,如果没有这语句,就是个普通加二接口,没有任何差异。要去实现它真正远程的对象,在服务器端运行的对象,要实现就比较简单,要把 get price 实现掉,get past可能会抛出远程异常,所以它是直接往外抛的,所以在代码里不用去捕获它。所以直接写业务逻辑。业务逻辑是什么呢?在一个类里面有一个 Mac 叫 prices,是一个键值对,它的键值字符串值是 double 类型的值。从用户传递过来的,就想获取某个商品的价格,去拿商品作为建,到 APP 里面去获取它的价格,然后把价格反馈一下,如果用户查询商品就不在 map 里,会返回一个 not surprising not now,所以就会返回一个零,会发现其实在实现业务逻辑。业务逻辑本身它和远程方法,所谓刚才讲的远程,任何通讯的逻辑都没有任何关系,只关注业务关系。只是要回升,抛出远程异常,所以类本身业务方法没有什么特殊的,还是按照正常的业务逻辑去写或者看它有一个构造器,因为对象是远程的,所以构造器也要说明会超出。而它的构造器哪在做什么呢?是业务逻辑,实际上看到的远程通信没有任何关系,它就对 prices,map 创建了一个实例,然后往里面放进去的一些信息。
再看整个类的结构,去实现刚才的接口本身也没有任何的特殊的地方,唯一的地方就是有个叫做拓展的一个词句,是写一个普通的家类是不会看到的。它的差异是什么?一般的一个 java 类,它的差异是扩展类,类的意思是将来对象应该在某一个,比如加上虚拟机里面一直在那。等待着客户端的请求发过来调用它。后台运行的一个程序。现在要跑起来的话,要跑程序,刚才看到在图里面,要有一个 Starbucks,要有一个 student,它们之间才能通信。问题是客户端怎么得到 Starbucks 呢?怎样才能得到一个用来通信的对象呢?这时候就到了一个 JNDI Java 命名与目录服务。把source 数据源适配到了 JNDI 上。实际上它的意思是什么呢?有一个专门放远程对象绑定的一个服务器,这就是JNDI的服务器,它里面就是一些课外的东西,客户端拿着T来查找就能得到 value 是什么呢?Value 就是一个地址。所以根据想法,在跑远程对象的时候也是一样,如果能按照一个名字来查找,返给所谓 stub 的对象,拿着对象就能去跟服务器端访问了。
五、 服务器端的工作
所以了解 RMI 的服务器在哪里。RMI 的服务器就和 G 的服务器是起一个同样的作用。
rmi://regserver.mycompany.com:1099/central_warehouse
它的服务器叫注册表,从名字上就能看出来注册表和 RMI 的名字很像,而且它们的作用就是完全一样。就是绑了很多的键值对。很显然,对象实现了 warehouse 的业务逻辑,对象要绑在慢慢注册表的某一个位置上,T 就是它的名字全称。前面是讲要用 RMI 协议来通信,这里是在运行 R 的程序的。主机在哪里?在这台机器上,RMI 的服务器运行。端口就找到了注册表,之后只要拿后面的,是 T 去找对象,对象就返给我了,所以在客户端和服务器端分别要做的事情,与数据源的作用是一样的,但服务器端要做的事情是先创建一个,刚才实现了 warehouse 的接口的对象的一个实现类,然后把它绑到 RMI 树上去, 给它一个 key。就是刚才看到的名字,然后把对象绑定上去。
-
registering a WarehouseImpl object with the RMI registry on the same
server:
WarehouseImpl centralWarehouse = new WarehouseImpl();
Context namingContext = new InitialContext();
namingContext.bind("rmi:central_werehouse",
centralWarehouse);
要想做执行这动作,需要有一个能到达,首先需要先到达注册表。在注册表上再去进行绑定动作,或者就持有一个注册表引用,注册表引用是怎么得到的呢?
回想一个复杂的问题, contacts 的实际上是要有构造器参数。就有三种方法来传递参数,让它去获取注册表的引用。那么参数是指的什么呢?要说清楚,host 在哪里,POS 在哪里,然后才能去访问到,才能去绑定,实际上在创建,很快就要去解释清楚,或者它们合起来 URL 在哪里。还有一点在复杂的应用里面,没有在一台机器上,在这台机器的端口里面,注册表只有一个,可能有好几个。
它们并存的一个前提是实现不一样。所以指定到这台机器的端口,还得指定是哪一个注册表上去处理。所以区分注册表的东西就是获取的时候 context factory 是 contacts 工 厂类的哪一个?所以在参数里面要指定两个东西,第一个是URL,来指定 MV 的注册表在哪,第二个是在 new initial context 的工厂位置。所谓的工厂类指的就是想要去说明在注册在端口,MF 服务器上有号。注册表项发哪一个.两个参数是怎么去获取的呢?比如有三种方法,第一个在运行Java 虚拟机传递给它。第二个是在创建它的时候。用构造器传出直接去写进去,通常会弄一个 map,第三种方法比较推荐的方法,一个叫做 JNDI. proper prop 的文件。文件里面写清楚两个参数,然后把文件放到 class path 里面去。于是当执行这段代码不带参数,就会去在 c lass 来找文件,拿找到的文件的内容去作为创建因为 Context 的依据创建了,在创建时 CONTEXT 时,就得到了一张注册表的引用剩下的都在上面,绑上去一个对象,这是在服务器端做的事情。