Distributed Object 2(三)|学习笔记

简介: 快速学习 Distributed Object 2(三)

开发者学堂课程【高校精品课-上海交通大学-企业级应用体系架构:Distributed Object 2】学习笔记,与课程紧密联系,让用户快速学习知识。

课程地址:https://developer.aliyun.com/learning/course/75/detail/15822


Distributed Object 2(三)

 

内容介绍:

一、Establishing Connection

二、Java Naming and Directory Interface

三、Get InitialContext

四、DataSource in Tomcat

五、The RMI Programming Model

六、Transferring Nonremote Objects

五、The RMI Programming Model

WarehouseImpl centralWarehouse = new WarehouseImpl();

Context namingContext = new InitialContext();

namingContext.bind( "rmi:central_ warehouse",

centralWarehouse);

在 jndi 树上,实际上它根本的工作机制是它把一个对象和一个名字绑定了,就像这里看到的,它把 warehouse 的对象和一个叫这样的名字绑定了,所以工作方式跟刚才去找 datasource 一样,要创建一个 initial context 对象,这时候没有给任何参数,所以需要给的参数,建议写 jndi.properties 文件,这是在服务器端它做的事情。它把对象绑到了 jndi 树上,这个例子用的是 rmi 注册表,就是 jndi 的一种实现,用它来绑上去。

image.png

Jndi 只是一种实现,rmi 就是 jndi 的一种实现,如果把应用服务器里面控制台上有 jndi 的启动以后有一个 jndi 的服务,可以看到上面绑的都是什么东西。

Context ctx = new InitialContext();

ctx.bind("jdbc/AcmeDB",vds) ;

ctx.rebind("jdbc/ZenithDB",vds);

在服务器端要做的事情是绑定,如果绑错了可以重绑一下,就是 rebind。

Context ctx = new InitialContext();

DataSource ds = (DataSource )ctx.lookup( "jdbc/ZenithDB");

在客户端要做的是 lookup,去查找。但是无论是绑定还是查找,要先到 rmi 的注册表上,把注册表描述成一棵树,你就要到达这个根,都需要通过 initial context。为什么强调它是一棵树呢?它的命名空间,比如是 rmi,下面要绑很多对象,所以可以在 rmi 底下设两个子空间,一个叫做 warehouse,这是仓库的,还有一个比说叫 classroom,在 warehouse 底下有 warehouse A、warehouse B,在 classroom 底下,有 classroom A、classroom B。对于某一个,比如classroom B,它的完整的名字叫做 RMI://classroom/B,这样就是它的完整路径,看起来它就像一个在操作系统里面看到的文件夹一样,就是按照文件夹,也就是按照路径来组织这个名字,从 RMI 这个根一直到要找的这个对象所有的部件合起来,这才是完整的。

public class WarehouseServer

{

public static void main(String[] args) throws

RemoteException, NamingException

{

System.out.print1n("Constructing server implementation...")s

WarehouseImpl centralWarehouse = new WarehouseImpl();

System.out.println(" Binding server implementation to registry...");

Context namingContext = new InitialContext();

namingContext.bind("rmi:central_ warehouse",centralWarehouse);

System.out.print1n( "Waiting for invocations from clients...");

}

}

再看 warehouseServer,写一个这样的类,注意,前面写的类有一个接口,有接口的实现类,实现了业务逻辑。现在再写一个 server 的类,这个 server 类要干什么呢?它新创建一个刚才实现类的对象,然后到达 rmi 的根,然后把这个对象给它绑定到这个名字上,一旦绑上去之后,服务器端的对象就在一直运行,一直等待有人来调用它上面的方法 getPrice,因为它只有一个方法暴露出去了,所以别人只能通过这一个方法来调用它,它就一直在那里等待客户端的调用,为了防止别人觉得它死掉了,输出一条信息,等着客户端的调用。

image.png

完整的系统怎么样呢?有一个服务器端的 Warehouse 的实现类,这个实现类它实现了 getPrice 方法,实际上它是有一个接口的,这个接口要给 client 去用,client 就要拿到这个接口类,然后它去写它的代码,因为编译后通不过,所以它要先拿到这个类。Server 类会创建一个 warehouse 的对象,把它绑到 RMI Registry 上,这样 warehouse 的 client 就可以去 lookup 它,去找,于是就找到了这个对象的引用,实际上它不是直接拿到这个对象,拿到的是 stub,stub和 WarehouseImel 分别是代理,代理就意味着你现在不能直接帮到它,实际上是拿到了它的一个代理,换句话说,其实刚才在这里绑定的时候,真正绑的不是 WarehouseImel 对象,绑的是 stub 对象,stub 对象被绑到 RMI Registry 上,在 return stub 找的时候找到了,但为什么绑的是 WarehouseImel 对象呢?在用户眼里,看到 stub 相当于看到了 lookup,用户是没办法直接操作到的,所以他就得到了 stub,这就是 stub 得到的途径。得到 stub 之后,它在上面调用 getPrice,stub 也实现了远程接口里的 getPrice 方法,但它不是业务时间,它是把调用收拾一下传递过来,这一边把它字节流翻译成远程对象的 getPrice 方法调用,剩下的沿着一条路反向来,它把结果或者异常给它,然后 receiver的 getprice 实现是把结果或者异常转换成字节流,stub 再把字节流转换成转回方法,但是中间通信的复杂性都被 stub 和 receiver 屏蔽掉了,所以看到的客户端代码实际上是这样的,

public class WarehouseClient

{

public static void main(String[] args) throws

NamingException,RemoteException

{

Context namingContext = new InitialContext();

String url = "rmi://localhost/central_ warehouse" ;

Warehouse centralWarehouse = (Warehouse) namingContext.lookup(ur1);

String descr = "Blackwell Toaster";

double price = centralWarehouse.getPrice(descr);

System.out.println(descr + ":”+ price);

}

}

它说我也要创建一个 initial context,然后要去找那个对象,前一半是在说那个名字,就是要用 rmi 协议去找 localhost,后面 central_warehouse 是刚才绑上去的名字,然后用 url 去 lookup 得到一个对象,但是就像刚才说的,在 RMI 这个树里面,classroom、warehouse 都是对象,但它的类型不一样,所以它统一的返回值是一个串,所以要把它的类型强制转换一下变成 warehouse,就得到了 centralWarehouse,得到了它实际上是在图里看到的这个 stub,但是看到 stub 就相当于看到了代理模式。刚才说了一点,WarehouseImel、receiver、stub 这三个类都实现了最开始定义的 warehouse 的接口。所以拿到这个远程对象之后,不能把它类型强制转换成实现类,实现类是看不到的,只能看到接口类。所以把它转换成接口类,实际上拿到的是 centralwarehouse。Stub 里面,因为它也实现了 warehouse 接口,所以在上面可以调用 getPrice,在 Stub 上调用 getPrice,对客户端来说就相当于在最终的对象上调用了一样,所以可以看到拿到 stub 后就相当于是这样的,拿到了一个本地对象,就这样调用就得到了结果。所以再看一下写的东西,写了 warehouse 这样一个接口,这是一个远程接口,然后写了一个实现类 WarehouseImel。针对 warehouse 这个接口会产生三个东西,一个是真正的业务逻辑实现类,这个类叫做 WarehouseImel,它是针对的这个类实现了有业务逻辑,另外,它还实现了一个 stub,还编译出来一个 receiver,这三个东西都实现了这个接口,只不过 stub、receiver 实现的是把方法用字节流做方法转换,然后现在有四个东西了,就是接口、实现类和 stub、receiver,服务器端是创建了一个实现类的对象,说把它绑上去,但实际上,在这棵树里面绑的就是 stub,就是 KeyValue 里面绑的是 stub,尽管这里要求是 new WarehouseImpl 这个对象往上绑,它实际上绑的是 stub,所以在客户端这一端,它在 lookup 出来的时候实际上是 stub 对象,所以它不能直接强制转换成实现类,它只能是接口类,但是接口类里面,即便是 stub,它也有 getPrice 实现,它的实现是把 getprice 传过来,所以才能调用成功。

明白这个工作机制之后,大家可能有这样一个疑问,搞这么复杂干什么?直接把这个对象的引用传回来不行吗?为什么要搞个 stub?把 WarehouseImpl 对象的引用传回来,它也在表示一个远程对象的调用,直接绑可以吗?要注意的是,如果把实现类给人家,这件事情本身就表示跟实现绑定,在做系统开发的时候,接口和实现一定要分离,必须要实现接口和实现的分离,要有 D40这一层,但 D40这一层只是接口,还有一个 D40的实现层,然后 service 要有 service 这一层接口层,它底下还有 service 实现层,每一层都是这样做的接口实现分离,为什么呢?原因就是接口是个稳定的东西,它不太容易变,而实现是变的。在这个例子里面可以看到,现在给出来的实现是这样的一种实现,放一个 map 太 low 了,这个数据要从数据库里面去拿,这个类如果直接给用户意味着客户端要更新这个类,但是如果写了一个接口,把这个接口给客户端,客户端针对接口编程,然后在服务器端更换它的实现的时候,客户端不用做任何改变,所以必须要做接口、实现分离,要保证系统可维护性。所以这是刚才在回答这个问题,为什么要把这个事情变得这么复杂?一定要有一个接口,然后再要有实现,针对的接口有 stub,有 receiver,产生这么多东西,客户端始终是跟 receiver 交互。最复杂的就是客户端怎么拿到 stub 的,就是根据名字在 RMI Registry 上得到的,所以这是一个完整的过程。

看整个应用跑起来是什么样子的,

image.png

这上面的编号就是执行的顺序,第一步是 server 这个类,它创建了一个 central_warehouse 对象,就是具体的实现类的一个对象,它把对象注册到 Registry 上,就是刚才 jndi 的一个服务器一样,就是那个注册表。它在注册的时候会讲清楚这个对象在什么位置上,然后客户端在访问的时候,它先到这里找到这个对象的引用,找到对象引用的时候实际上就知道了那个对象在这台服务器端的什么位置上,那个对象的标识是什么,所以它然后再给它调用。这个过程当中,Client 获取了 stub,它以后是用的 stub 来跟这个进行交互。如果不需要部署在外部服务器里,只有它,它就是这样一个交互的方式。

然后要跑,这里面写了几个问题,

- Add ClassDir to CLASSPATH

- For example:E:\Projects\JavaEE\rmiserver\bin

- Createa jndi.properties file,and copy it into/Server and /Client class dir.

java.naming.factory.initial=com.sun.jndi.rmi.registry. RegistryContex tFactory

java.naming.provider.ur1=rmi://localhost: 1099

- Run the rmiregistry in a windows console

- Run the WarehouseServer in another windows console, you will see:

Constructing server implementation...

Binding server implementation to registry...

Waiting for invocations from clients...

- Run the WarehouseClient in the third windows console, you will see:

RMI registry bindings: central_ warehouse

Blackwell Toaster: 24.95

假设在 Windows 系统跑,现在是个苹果的电脑,反正就是有路径,路径就是说rmi 这个 server 要跑起来,这个东西是在下载 JavaEE 这个企业版的时候,它里面带,现在在 JDK 里面也直接带,下载 JDK 的时候,JDK 有一个 RMI Registry 对象,不要忘了要有 jndi . properties 这个文件,这个文件它会放到某一个 classpath 里面。但要注意,server要去绑对象,client 要去查对象,所以它俩都需要有这个东西。简单来说,把它都放到服务器和 client 的类路径里面,classpath 里面是最方便的,但是如果两个应用都要用到不同的 jndi . properties,为了防止有冲突,就直接把它们放到 server 和 client 自己的 class 目录里,就是编译出来的 class 目录里。在这个里面要指定两个东西,一个是 RegistryContextFactory,一个是 rmi: //localhost:1099在哪里,准备好之后就可以先启动一下 rmiregistry,下载 JDK 后,rmiregistry 在 bin 目录底下,直接在命令行里输入它就可以了。然后运行服务器端的东西,运行 Client 端的东西,它就跑起来。

把这个代码给大家跑一下、看一下,这是原代码,第一个是 warehouse,

image.png

就是一个接口,这个接口是远程接口,它只有一个 getprice 方法,它的每一个方法都要抛出 remote 异常,有了它之后要写它的实现类,

image.png

要去实现 warehouse 的接口,然后要扩展 UnicastRemoteObject,这样它才能变成一个远程对象,在那里一直等待着用户,它的构造器就是把这个项目表给它放了两个公式进去,然后 Getprice 就要返回,这个都比较常规,唯一要注意就是因为要实现这个接口,getprice 在类里面要抛出远程异常,如果漏掉了这一句,它会报错。第一,它会认为没有实现 warehouse 接口,第二,如果再写一个 getprice,它会认为这是两个完全不同的 getprice,使用要加上这个字句。再看服务器端,

image.png

服务器端要做的事情就是创建一个实现类对象,然后创建一个 InitialContext,把它绑到名字里面,然后开始等待。再看 client,

image.png

Client 就是要去找,所以它也要 InitialContext,然后它去找刚才的名字,找到之后就像一个本地对象一样去调用它的 getprice 方法就可以了。它俩在创建 InitialContext 的时候都没有传递任何参数,所以在这里写了个 jndi.properties 文件,

image.png

这个文件实际上是个纯文本的文件,只是它的后缀名是 properties,要用到 RegistryContextFactory,就是 rmi 的东西,它的位置就在本地的1099这个端口.为了运行它,要先编译一下,这边是编译好的东西,到它对应的目录里打开一个 terminal,就是命令行,要去运行 rmi 的 registry,它没有任何输出的,它一直在等待,这个注册表是下一个服务器程序,一直等待。然后先跑一下 server,这个 server 就是刚才看到的它的输出,已经绑定到上面去了,等着客户端的调用,然后跑一下客户端。

image.png

这个客户端跑的时候,它传递的是 Blackwell Toaster,就是这样一个跑面包机,

image.png

在服务器端跑面包机的价格确实是24.95,就是它返回24.95。如果再跑一遍客户端,要求它去返回 ZapXpress Microwave Oven。把客户端改一下,

image.png

把它重新连一下,再跑一次客户端,

image.png

连接拒绝,问题在刚才 rmiregistry 在后端时绑上的服务器的名字已经有了,不让再绑,把它全部停掉,再重新绑一下,就可以看到新的价格会出来。但是有一个坑,在哪里呢?把它先全部停掉,这个后台运行的进程里面,

image.png

这里有一个 Java,这个 Java是谁?这个 Java 既不是客户端,也不是服务器,是一开始启动的 rmiregistry,所以它在这里会一直驻留在后方,所以要把它办掉,把它强制退出,否则即使退出了,可能它也会一直存在,所以现在再跑,

image.png

它说已经在占用,就是不能跑两次,这个问题还不知道出在哪了,可能要退出,这个细节上就不纠缠了,这是它的运行结果,

Running the Program

- Add ClassDir to CLASSPATH

- For example:E:\Projects \JavaEE \rmi server\bin

- Createa jndi. properties file, and copy it into /Server and /Client class dir.

java.naming.factory.initial=com.sun.jndi.rmi.registry. RegistryContextFactory

java.naming.provider.ur1=rmi://localhost:1099

- Run the rmiregistry in a windows console

- Run the WarehouseServer in another windows console, you will see:

Constructing server implementation...

Binding server implementation to registry...

Waiting for invocations from clients...

- Run the WarehouseClient in the third windows console, you will see:

RMI registry bindings: central_ warehouse

Blackwell Toaster: 24.95

 

六、Transferring Nonremote Objects

如果在客户端和服务器端传递的不是一个基本类型,而是一个对象类型,

public interface Warehouse extends Remote

{

double getPrice(String description) throws RemoteException;

Product getProduct(List<String> keywords) throws RemoteException;

}

image.png

因为一开始就说关键的问题就是用 HTAP,它前后端没有办法传对象,如果传递的是 product 这样的对象位置,也就是说,我给你传递一个参数过去,你返给我一个对象,首先就是 product 要满足什么条件?其次,当你把服务器端的对象给我的时候,这种对象是引用给了我,还是复制了一个给我?这是要思考的问题,就是说如果 get 一个 product,它返你一个 product 之后,这个 product,如果你在 client 关键能修改,它反应不反应到这一边去。从图里可以看到,实际上它是复制的一个过来,对这个 product 的任何修改没有直接反应到这里,所以要明白它的传递的过程。

public class Product implements Serializable

{

private String description;

private double price;

private Warehouse location;

public Product(String description, double price)

{

this.description = description;

this.price = price;

}

public String getDescription() { return description; }

public double getPrice() { return price; }

public Warehouse getLocation() { return location; }

public void setLocation(Warehouse location) {

this.location = location;

}

}

首先来看,要想能把它传回来,就要实现 Serializable 接口,这个接口是一个标记接口,含义是这个对象未来可以换成字节流,它是一个标记接口,没有任何方法去实现。但是必须要标记成这样,它才能传出,如果不标记成这样,它就会报错。

public class Book extends Product

{

private String isbn;

public Book(String title, String isbn, double price)

{

super(title, price);

this.isbn = isbn;

}

public String getDescription()

{

return super.getDescription() + " " + isbn;

}

}

Serializable 这种属性是可以自动扩展的,所以 Book 也是可以驱力化的。

public class WarehouseImpl extends UnicastRemote0bject implements Warehouse

{

private Map<String, Product> products;

private Warehouse backup ;

public WarehouseImpl (Warehouse backup) throws RemoteException

{

products = new HashMap<>();

this.backup = backup;

}

public void add(String keyword, Product product)

{

product.setLocation(this);

products.put(keyword, product);

}

public double getPrice(String description) throws RemoteException {

for (Product p : products.values())

if (p.getDescription().equals(description)) return p.getPrice();

if (backup == null) return 0;

else return backup. getPrice(description);

}

public Product getProduct(List<String> keywords) throws RemoteException {

for (String keyword : keywords) {

Product p = products.get(keyword);

if (p != nu1l) return p;

}

if (backup != null)

return backup.getProduct(keywords);

else if (products.values().size() > 0)

return products.values().iterator().next();

else

return null;

}

}

所以在实现的时候,如果产生一个 products,在里面有 add 方法、getProduct 方法,在返回 products 它是允许的,它可以返回一个 product 对象。

public class WarehouseServer

{

public static void main(String[] args) throws RemoteException, NamingException

{

System.out.println( "Constructing server implementation...");

WarehouseImpl backupWarehouse = new WarehouseImpl(nu1l);

WarehouseImpl centralWarehouse = new WarehouseImpl (backupWarehouse);

centralWarehouse.add( "toaster", new Product("Blackwell Toaster", 23.95));

backupWarehouse.add("java", new Book("Core Java vol. 2","0132354799", 44.95));

System.out.println(" Binding server implementation to registry...");

Context namingContext = new InitialContext();

namingContext.bind(" rmi :central_ warehouse", centralWarehouse);

System.out.println("Waiting for invocations from clients...");

}

}

现在 server,我们往里面放一些东西再去跑,客户端拿到它再去访问的时候,

public class WarehouseClient

{

public static void main(String[] args) throws NamingException, RemoteException

{

Context namingContext = new InitialContext() ;

System. out . print("RMI registry bindings: ");

NamingEnumeration<NameClassPair> e =namingContext.list("rmi://localhos t/");

while (e.hasMore())

System.out.println(e.next().getName());

String url = "rmi://localhost :1099/ central_ warehouse";

Warehouse centralWarehouse = (Warehouse) namingContext.lookup(ur1);

Scanner in = new Scanner(System. in);

System.out.print(" Enter keywords: ");

List<String> keywords = Arrays.asList(in.nextLine().split("\\s+"));

Product prod = centralWarehouse.getProduct(keywords);

System.out.println(prod.getDescription() + ":”+ prod. getPrice());

}

}

要求调用 getProduct 就得到了,原因是它是一个可驱力化的对象。如果服务器端想给客户端推送一个对象,应该要注意些。

- Add ClassDir to CLASSPATH

- For example:E:\Projects\JavaEE\WareHouseServer\bin

- Create a jndi.properties file, and copy it into /Server and /Client class dir.java.naming.factory.initial=com.sun.jndi.rmi.registry.RegistryContex tFactory

java.naming.provider.ur1=rmi://localhost:1099

- Run the rmiregistry in a windows console

- Run the WarehouseServer in another windows console, you will see:

Constructing server implementation...

Binding server implementation to registry...

Waiting for invocations from clients...

- Run the WarehouseClient in the third windows console, you will see:

RMI registry bindings:central_ warehouse

Enter Keywords:

同样的东西,再去跑,它能返回一个东西,这个例子没有再写了,因为它意思一样,只是变动一个 Product 而已,跑的方法还是跟刚才一样,先跑一个 registry,然后再跑 Server,最后跑 Client。

class WarehouseImpl extends

Activatable implements Warehouse {...}

刚才讲到的那个对象,它会启动以后一直在那里等。有没有这样一个情况,就是它启动以后,其实它不一定非要在那一直等?一直等就意味着它一直驻留内存,它一直在那里消耗资源,能不能是有客户端请求发过去之后它才开始响应?就要求实现类就不是实现刚才看到的 UnicastRemoteObject 对象,要实现的是 Activatable 对象是动态激活了,后面代码其实就是把这个接口实现改到里头的,其他的没有什么特殊的。

public interface Warehouse extends Remote{

double getPrice(String description) throws RemoteException;

}

public class WarehouseImpl extends Activatable implements Warehouse

{

private Map<String, Double> prices;

public WarehouseImpl(ActivationID id,

Marshalledobject<Map<String,Double>> param)

throws RemoteException, ClassNotFoundException, IOException

{

super(id, 0);

prices = param.get();

System. out. println( "Warehouse implementation constructed.");

}

public double getPrice(String description) throws RemoteException

{

Double price = prices . get(description);

return price == null ? 0 : price;

}

}

它涉及到一个比较复杂的授权问题,

Grant

{

permission java.security.AllPermission;

};

要想得到远程激活,就有个授权问题,这个是讲安全中会谈到的什么叫安全的授权。

public class WarehouseClient

{

public static void main(String[] args) throws NamingException, RemoteException

{

Context namingContext = new InitialContext();

System. out. print("RMI registry bindings: ");

Enumerat ion<NameClassPair> e = namingContext . list("rmi://localhost/");

while (e.hasMoreElements())

System.out.println(e.nextElement().getName());

String url = "rmi://localhost/central_ warehouse";

Warehouse centralWarehouse = (Warehouse) namingContext. lookup(ur1);

String descr = "ZapXpress Microwave Oven" ;//"Blackwell Toaster" ;

double price = centralWarehouse . getPrice(descr); .

System.out.println(descr + ": " + price);

}

}

客户端其实跟服器端没有任何差异,调用时候仍然是这样 lookup,然后在上面直接调用,然后跑起来,客户端代码没差异,但是它也需要有权限,

grant

permission com.sun.rmi.rmid.ExecPermission

"${java. home}${/}bin${/}java";

permission com.sun.rmi.rmid.ExecOptionPermission

"-Djava.security.policy=*";

};

至于这个权限就不细说了,因为要到讲了安全之后就理解了。

编译的方法全部都是跟安全有关,

rmid -J-Djava.security.policy=rmid.policy

java -Djava.rmi.server.codebase=http://localhost:8080/

WarehouseActivator

java -Djava.security.manager

-Djava.security.policy==client.policy

WarehouseClient

讲完安全意识自然就知道这个意思是什么。

Rmi 需要做一些作业,

To build your business logics with:

- At least one stateful server component, such as shopping cart

- At least one stateless server component, such as login

To build an RMI service in E-Book Store for shipping:

- Suppose an Express Company, such as UPS and DHL, has developed a

Java RMI service for placing orders.

- Develop a such RMI service to receive the information of order, and

return a default result to client, such as“Processing"

- Run this service to simulate the Express Company.

- Develop an RMI client in E Book Store to invoke the running RMI service.

用 rmi 干什么呢?要给其他的程序来用,比如有个快递公司像 UPS 或 DHL,给他开发一个 rmi 的服务,让他下订单,那要写一个 RMI 的 service 接受这个订单,还要写一个 RMI 的客户端来模拟快递公司两者之间通信,最终 RMI 得到订单的信息之后,把它写入到数据库里面去,要实现这样一个功能。再强调一下,使用 RMI 的原因,就是因为它可以传对象,它不像 HTTP 基本上传的全是文本,所以 HTTP 是对的最终用户,是对人的,给人承现内容,RMI 对的是程序,想让程序来执行,来调用,在对象上调用方法。

RMI 对象在内存里面发生变化客户端会不会跟着变?

image.png

本质上它是直对象绑定,是直对的不是引用的,从这里可以看出来,product 在服务器端一旦返回,实际上是把它的状态拿回来在客户端用 product 这个类的定义去恢复出来这样的一个东西。所以在左边的修改不能反映到右边,在客户端获取数据之后,当修改服务器端的内容的时候客户端也拿不到修改之后的东西,本质上来说它是把数据拿回来重构的,所以必须是可序列化的,而不是真的得到了一个服器端的引用就拿得到,不断的去修改信息直接反应到 product 上,它做不到这点,原因是它本来就没有拿到可引用,它实际上是通过代理在进行通信,它是 copy 过来的,它应该是个直对象。

image.png RMI 的端口已经被占用了,就是后台刚才刷 RMI 的端口没有刷成功,不可能跑两个 RMI的 Registry,跑这个东西要注意。实际上在电子书城例子里面跑的时候一定要注意,并不是自己在跑一个 RMI 的 Registry,应该借用 tomcat 里面的 JNDI,也就是像 datasource 在绑到 JNDI 上的那种方式,把远程对像绑在 JNDI 树上,这时要注意客户端是一个应用程序在单独放,所以服务器端要稍微改写成建立在 tomcat 里的,把它绑上去,用 JNDI 树往上绑。

服务器端的对象类型要断或者所有内存中故障,本来是对象有两份,要想同步的话,要把客户端的状态刷到服务器端,双向的刷,用 flash 或者用 fresh 这种方式不断的去 set 一下再 get,因为没有框架去做,只有自己不断的去 get、set。

相关文章
|
10月前
java202303java学习笔记第二十五天-object之1
java202303java学习笔记第二十五天-object之1
35 0
|
10月前
java202303java学习笔记第二十五天-object之3
java202303java学习笔记第二十五天-object之3
25 0
java202303java学习笔记第二十五天-object之3
|
11月前
java202303java学习笔记第二十五天-object之3
java202303java学习笔记第二十五天-object之3
31 0
|
前端开发 JavaScript Java
Distributed Object|学习笔记
快速学习 Distributed Object
93 0
Distributed Object|学习笔记
|
对象存储 开发者
对象存储 OSS 文件(Object)管理功能|学习笔记
快速学习对象存储 OSS 文件(Object)管理功能
375 0
|
Java 开发者
利用 Object 类解决重复操作|学习笔记
快速学习 利用 Object 类解决重复操作
|
Java API 开发者
什么时候使用 Object 接收 | 学习笔记
快速学习什么时候使用 Object 接收。
115 0
|
Scala Java
[Scala]Scala学习笔记五 Object
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/SunnyYoona/article/details/76423770 1.
1110 0
|
5月前
|
Java
java判断Object对象是否为空demo
java判断Object对象是否为空demo
|
19天前
|
JavaScript
js 字符串String转对象Object
该代码示例展示了如何将一个以逗号分隔的字符串(`&#39;1.2,2,3,4,5&#39;`)转换为对象数组。通过使用`split(&#39;,&#39;)`分割字符串并`map(parseFloat)`处理每个元素,将字符串转换成浮点数数组,最终得到一个对象数组,其类型为`object`。