Java设计模式—代理模式之远程代理

远程代理

远程代理是一种常用的代理模式,它使得客户端程序可以访问在远程主机上的对象。为一个位于不同地址空间的对象提供一个本地的代理对象,因此,在客户端完全可以认为被代理的远程业务对象是在本地而不是远程。下图为远程代理示意图 在这里插入图片描述

Java语言中可以通过一种名为RMI(Remote Method Invocation,远程方法调用)的方法来实现远程代理,它能够实现一个Java虚拟机的对象调用另一个Java虚拟机中的对象。
RMI将客户辅助对象称为Stub(桩)即本地代理,服务辅助对象称为skeleton(骨架)

下面简单介绍下RMI工作流程:
①客户端发起请求,将请求转交至RMI客户端的Stub类。
②Stub将这些请求命令进行编码(序列化,方便在网络上进行传输),并将他们通过Socket发送到服务器
③服务器接收到流后将他们转发到相应的Skeleton类,Skeleton类将这些请求信息进行反序列化,然后找出真正被调用的方法,以及该方法所在的对象。
④服务器处理完结果之后,将结果发送给Skeleton类,Skeleton类将结果进行编码(序列化),再次通过Socket发送给客户端。
⑤客户端Stub接收到流后,再进行反序列化,然后将反序列化后的结果返还给客户端调用者。

至此,一次完整的调用过程得以完成。下面将通过具体的实例,更加具体的描述实现过程。


RMI应用开发流程: 首先给出流程图: PS:图片插入不进来了(https://blog.csdn.net/QB2049_XG/article/details/3278672#p3)。这个博客里面有流程图。

下面来一步一步的实现开发一个简单的RMI应用:
①定义一个公共接口,我们大家都知道代理模式有一个Subject抽象主题角色,远程代理也一样。需要定义一个公共的接口。

package lib.complex.server;
/**
 * @description:Subject抽象公共接口
 */

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface MyRmiInterface extends Remote {
    public String sayHello() throws RemoteException;
}

②实现远程接口:
客户端调用服务器的远程对象来调用公共接口的方法,所以服务器端必须得实现这个公共接口,并且抛出一个远程对象让客户端来进行调用。

package lib.complex.server;
/**
 * @description:实现远程接口
 */

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;


public class MyRmiImpl extends UnicastRemoteObject implements MyRmiInterface {

    //在实例该类时,就导出了一个远程调用对象,该构造方法是必须的
    public MyRmiImpl()throws RemoteException {
        super();
    }

    public String sayHello(){
        return "Hello,world";
    }
}

这里发现实现远程接口类继承了UnicastRemoteObject类,这点是非常重要的。通常,远程对象都继承UnicastRemoteObject,该类提供了远程对象的创建和导出的一系列方法。这个类我们将在随后的服务端实例化并把其注册到RMI注册表中。

③实现服务端类:

package lib.complex.server;
/**
 * @description:实现服务端类
 */

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class MyRmiServer {
    public static void main(String args[]){
        try{
            //实例化远程对象,同时导出了该对象,使他可以接受来自客户端的信息
            MyRmiImpl impl = new MyRmiImpl();
            //获取本地注册表对象
            Registry registry = LocateRegistry.getRegistry();
            //在注册表中绑定远程对象,桩
            registry.bind("Hello",impl);
            System.out.println("System already!");
        }catch (Exception e){
            System.out.println("在建立远程连接的情况出现了异 常"+e.getMessage());
            e.printStackTrace();
        }
    }
}

服务端类创建了一个远程对象,将他和一个名字绑定了注册表register中。为了能让客户端调用远程对象的方法,调用者必须事先获得远程对象的桩。为了自引导, Java RMI 为应用程序提供了一个注册表API,用于把远程对象的桩绑定到一个名字,这样远程客户端依靠查找该名字就能获得对应的桩。JavaRMI注册表只是一个允许客户端获得远程对象桩的简单-名字服务。一旦远程对象被注册到注册表中,调用者就可以查找名字来获得该对象及引用,然后调用对象的方法。

④实现客户端类:

package lib.complex.client;
/**
 * @description:实现客户端调用类
 */

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class MyRmiClient {
    public static void main(String args[]){
        try{
            //获得运行rmiregistry服务的主机上的注册表
            Registry registry = LocateRegistry.getRegistry();
            //查询并获得远程对象的存根stub
            MyRmiInterface stub = (MyRmiInterface)registry.lookup("Hello");
            //像在使用本地对象方法那样,调用远程方法
            String response = stub.sayHello();
            System.out.println("Response:" + response);
        }catch (Exception e){
            System.out.println("未设置codebase");
        }
    }
}

可以看到客户端的代码可以非常简单的。下面看看怎么运行RMI这个简单实例:
首先需要明确哪些类存在于客户端,哪些类存在于服务端。
存在于客服端的类:公共接口类,客户端调用类。
存在于服务器的类:公共接口类,实现远程接口类,实现服务端类。
PS:我的服务端的类的路径为:D:\RMI\src\lib\complex\server
我的客户端的类的路径为:D:\RMI\src\lib\complex\client
①首先编译服务端的源文件:
在这里插入图片描述

可以看到我首先使用set JAVA_TOOL_OPTIONS=-Dfile.encoding=utf-8设置了编码方式为UTF-8。然后使用javac -d . *.java命令来进行编译,而不是简单的javac *.java。从编译后的文件夹来看
在这里插入图片描述
使用javac -d . *.java进行编译后会出现一个文件夹,lib\complex\server\,然后底下才是class文件,这路径其实是package的包名。我们都知道如果使用javac *.java进行编译会直接在源文件的目录下生成class文件。那为什么要使用javac -d . *java进行编译呢。等后面运行服务端的类再说。

②启动注册表服务:
在这里插入图片描述

③运行服务端类:
在这里插入图片描述
我这里就抛出了异常,java.rmi.UnmarshalException.百度了下,好像是说我codebase路径错了。codebase路径设置为字节码的路径也报错,恳请哪位大佬给我解决一下。
下面说说codebase:
codebase就是远程装载类的路径,当发送者序列化对象时,会在序列化流中附加上codebase的信息,这个信息告诉接收方到什么地方寻找该对象的执行代码。由于本地没有类的对象,RMI提供了一些机制答应接收对象的一方去取回该对象的代码,而到什么地方去取,这就需要发送方配置codebase。客户端可以把classpath看成是“本地代码库址”,而codebase则是“远程代码库址”。Java RMI使用一个叫做存根的特殊类,它能够被下载到客户端和远程对象交流,进行方法调用。codebase属性代表了一个或多个URL地址,从该地址那些存根类能够被下载到客户端。
由此,可以看出Stub是在服务器产生,被下载到客户端进行调用远程对象。

RMI中codebase的使用方式:
①远程对象服务器通过设定java.rmi.server.codebase属性指定远程对象的codebase。RMI server向RMI registry注册一个远程对象,绑定到一个name上,在server JVM上设置的codebase被通知给了RMI registry。
②RMI client请求一个特定名字(注册表中每个远程对象绑定到一个name上)的远程对象的引用,用来调用远程方法。
③RMI registry注册表向请求的类返回一个引用(存根实例)。客户端会优先于codebase在本地的classpath中寻找存根类,如果找到了,那么它就会在本地调用该类。然而,如果在本地的classpath找不到该stub 的存根类,客户端会从远程对象codebase中获取类定义。
④客户端从代码库址(codebase)请求类,客户端根据codebase中的URL获取加载该存根类到客户端。这时,客户端就有了调用远程方法的所有信息。

给出具体实现的过程图:
在这里插入图片描述

上面还遗留一个问题就是为什么要使用javac -d . *.java编译,而不使用javac *.java
下面来看看如果使用javac *.java进行编译会出现什么问题。
在这里插入图片描述
在这里插入图片描述
下面运行看看:
在这里插入图片描述
发现报错了java.lang.NoclassDefFoundError
这个错误原因就是在编译时能够找到该类,而在运行时则无法找到该类。
java.lang.ClassNotFoundException错误是指在编译时就无法找到该类。
发生这个错误的原因就是:
在这里插入图片描述
有包的存在,必须使用javac -d .进行编译,不然在运行时就无法找到字节码文件。
还有一个问题就是前面提到的服务器的Skeleton类一直没有提到,因为jdk1.2以后的RMI可以通过反射API可以直接将请求发送给真实类,所以不需要skeleton类了。

刚学习RMI,有不对的地方恳请给予指出,感谢。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值