发表于: 2019-10-16 18:22:15

1 738


一、今天完成的事
简单实现Java-RMI
RMI的代码实现:
实现RMI所需的API几乎都在:
java.rmi:提供客户端需要的类、接口和异常; 
java.rmi.server:提供服务端需要的类、接口和异常;
java.rmi.registry:提供注册表的创建以及查找和命名远程对象的类、接口和异常;
基本步骤
(1)创建远程接口,继承java.rmi.Remote接口;
public interface Hello extends Remote {
    /**
     * 写一个接口类继承Remote接口,标识所包含的方法可以从非本地虚拟机上调用的接口,
     * Remote接口本身不包含任何方法
     * @param name
     * @return
     * @throws RemoteException
     */
    String sayHello(String name) throws RemoteException;
    // 由于远程方法调用的本质依然是网络通信,只不过隐藏了底层实现,网络通信是经常会出现异常的,
    // 所以接口的所有方法都必须抛出RemoteException以说明该方法是有风险的
}


(2)创建远程类,实现远程接口;
public class HelloImpl extends UnicastRemoteObject implements Hello {


    /**
     * Java 的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的
     * 在进行反序列化时,JVM 会把传来的字节流中的serialVersionUID与本地相应实体(类)的serialVersionUID进行比较,
     * 如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。
     * 1L是默认值,详细生成serialVersionUID查看https://www.jb51.net/article/129458.htm
     */
    private static final long serialVersionUID =1L;


    /**
     * 这里需要显式的调用父类的构造方法,因为要抛出RemoteException
     * @throws RemoteException
     */
    public HelloImpl() throws RemoteException {
        super();
    }


    /**
     * 写一个接口类继承Remote接口,标识所包含的方法可以从非本地虚拟机上调用的接口,
     * Remote接口本身不包含任何方法
     * @param name
     * @return
     * @throws RemoteException
     */
    @Override
    public String sayHello(String name) throws RemoteException {
        return "Hello"+name;
    }
}


(3)创建服务器程序,在rmiregistry注册表中注册远程对象;
public class Server {
    public static void main(String[] args)
            throws RemoteException, AlreadyBoundException, MalformedURLException {
        // 创建一个远程对象,同时也会创建stub对象、skeleton对象
        Hello hello = new HelloImpl();
        // 启动注册服务. 创建并导出接受指定 port 请求的本地主机上的 Registry 实例。
        LocateRegistry.createRegistry(2004);
        // 注册对象,把对象与服务名绑定
        Naming.bind("//127.0.0.1:2004/ksy", hello);
        System.out.println("服务已启动");
    }
}


(4)创建客户端程序,负责定位远程对象,并且调用远程方法。
public class Client {
    public static void main(String[] args)
            throws RemoteException, NotBoundException, MalformedURLException {
// 查找对象,返回与指定名称相同的对象。
        Hello hello = (Hello) Naming.lookup("//127.0.0.1:2004/ksy");
        System.out.println(hello.sayHello("world"));
    }
}


简单实现Spring-RMI
Spring RMI中,主要有两个类:
org.springframework.remoting.rmi.RmiServiceExporter
org.springframework.remoting.rmi.RmiProxyFactoryBean
步骤:
     ①在服务器端定义服务的接口,定义特定的类实现这些接口;
public interface HelloWorld {
    String helloWorld();
    String sayHelloToSomeBody(String someBodyName);
}
implament
public class HelloWorldImpl implements HelloWorld {
    @Override
    public String helloWorld() {
        return"HelloWorld";
    }
    @Override
    public String sayHelloToSomeBody(String someBodyName) {
        return "Hello World!" + someBodyName;
    }
}


     ②在服务器端使用org.springframework.remoting.rmi.RmiServiceExporter类来注册服务;
public class Server {
    public static void main(String[] args) {
        ApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("classpath:/spring/applicationServer.xml");
        System.out.println("服务伴随着spring启动而启动");
    }
}
appliacationServer.xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
    <bean id="helloWorld" class="com.ksy.rmi.HelloWorldImpl"></bean>
    <!-- 将一个类发布为一个RMI服务 -->
    <bean id="serviceExporter" class="org.springframework.remoting.rmi.RmiServiceExporter">
        <!--此处的name是决定helloWorld类中的那个参数,ref是指bean配置文件中的bean名称-->
        <property name="service" ref="helloWorld"/>
        <property name="serviceName" value="hello"/>
        <property name="serviceInterface" value="com.ksy.rmi.HelloWorld"/>
        <property name="registryPort" value="8083"/>
    </bean>
</beans>

     ③在客户端使用org.springframework.remoting.rmi.RmiProxyFactoryBean来实现远程服务的代理功能;
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
    <bean id="helloWorld" class="com.ksy.rmi.HelloWorldImpl"></bean>
    <!-- 将一个类发布为一个RMI服务 -->
    <bean id="serviceExporter" class="org.springframework.remoting.rmi.RmiServiceExporter">
        <!--此处的name是决定helloWorld类中的那个参数,ref是指bean配置文件中的bean名称-->
        <property name="service" ref="helloWorld"/>
        <property name="serviceName" value="hello"/>
        <property name="serviceInterface" value="com.ksy.rmi.HelloWorld"/>
        <property name="registryPort" value="8083"/>
    </bean>
</beans>


     ④在客户端定义访问与服务器端服务接口相同的类
public class Client {
    public static void main(String[] args) {
        ApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("classpath:/spring/applicationClient.xml");
        HelloWorld helloWord = (HelloWorld) applicationContext.getBean("HelloWorld");
        System.out.println(helloWord.helloWorld());
        System.out.println(helloWord.sayHelloToSomeBody("ksy"));
    }
}


RmiServiceExporter把任何Spring管理的Bean输出成一个RMI服务。通过把Bean包装在一个适配器类中工作。适配器类被绑定到RMI注册表中,并且将请求代理给服务类。



二、遇到的问题
三、收获
RMI是很重要的底层机制,RMI(Remote Method Invocation)远程方法调用是一种计算机之间利用远程对象互相调用实现双方通讯的一种通讯机制。使用这种机制,某一台计算机(即JVM虚拟机)上的对象可以调用另外一台计算机上的对象来获取远程数据。
RMI的实现对建立分布式Java应用程序至关重要,是Java体系非常重要的底层技术。

>>RMI的概念和原理

RMI思路是在客户端安装一个代理(proxy),代理是位于客户端虚拟机中的一个对象,对于客户端对象,看起来就像访问的远程对象一样,客户端代理会使用网络协议与服务器进行通信。
同样的服务端也会有一个代理对象来进行通信的繁琐工作。
在RMI中,客户端的代理对象被称为存根(Stub),存根位于客户端机器上,它知道如何通过网络与服务器联系。存根会将远程方法所需的参数打包成一组字节。对参数编码的过程被称为参数编组(parameter marshalling),参数编组的目的是将参数转换成适合在虚拟机之间进行传递的形式。在RMI协议中,对象时使用序列化机制进行编码的。
总的来说,客户端的存根方法构造了一个信息块,它由以下几部分组成
被使用的远程对象的标识符;
被调用的方法的描述;
编组后的参数;

然后,存根将此信息发送给服务器。在服务器的一端,接收器对象执行以下动作:
定位要调用的远程对象;
调用所需的方法,并传递客户端提供的参数;
捕获返回值或调用产生的异常;
将返回值编组,打包送回给客户端存根;
客户端存根对来自服务器端的返回值或异常进行反编组,其结果就成为了调用存根返回值。

>>RMI体系结构

桩/框架(Stub/Skeleton)层:客户端的桩和服务器端的框架;
远程引用(remote reference)层:处理远程引用行为
传送层(transport):连接的建立和管理,以及远程对象的跟踪
 

>>RMI与代理模式

RMI的实现是典型的代理模式思想。
(1)代理模式的UML图

代理模式为其他对象提供一种代理以控制对这个对象的访问,把调用者与被调用者分离开,由代理负责传递信息来完成调用。
比如你想买美版Iphone6s,朋友出国,帮你海淘带回,整个过程就是代理模式,朋友作为代理,代你完成你想进行的操作。
代理模式能将代理对象与真正被调用的对象分离,在一定程度上降低了系统的耦合度;
在客户端和目标对象之间起到一个中介作用,这样可以起到保护目标对象的作用,代理对象也可以对目标对象调用之前进行其他操作。
代理模式在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢;同时也增加了系统的复杂度。
(2)代理模式在RMI这种的体现
远程代理的内部机制是这样的:

>>RMI应用程序的基本模型


(1)Remote接口:是一个不定义方法的标记接口
Public interface Remote{}

在RMI中,远程接口声明了可以从远程Java虚拟机中调用的方法集。远程接口满足下列要求:
远程接口必须直接或间接扩展Java.rmi.Remote接口,且必须声明为public,除非客户端于远程接口在同一包中;
在远程接口中的方法在声明时,除了要抛出与应用程序有关的一场之外,还必须包括RemoteException(或它的超类,IOExcepion或Exception)异常;
在远程方法声明中,作为参数或返回值声明的远程对象必须声明为远程接口,而非该接口的实现类。

(2)RemoteObject抽象类实现了Remote接口和序列化Serializable接口,它和它的子类提供RMI服务器函数。
(3)LocateRegistry final()类用于获得特定主机的引导远程对象注册服务器程序的引用(即创建stub),或者创建能在特定端口接收调用的远程对象注册服务程序。
服务器端:向其他客户机提供远程对象服务
SomeService servcie=……;//远程对象服务
Registry registry=LocateRegisty.getRegistry();//Registry是个接口,他继承了Remote,此方法返回本地主机在默认注册表端口 1099 上对远程对象 Registry 的引用。
getRegistry(int port) 返回本地主机在指定 port 上对远程对象 Registry 的引用;
getRegistry(String host) 返回指定 host 在默认注册表端口 1099 上对远程对象 Registry 的引用;
getRegistry(String host, int port) 返回指定的 host 和 port 上对远程对象 Registry 的引用
registry.bind(“I serve”,service);// bind(String name,Remote obj) 绑定对此注册表中指定 name 的远程引用。name : 与该远程引用相关的名称 obj : 对远程对象(通常是一个 stub)的引用
unbind(String name)移除注册表中指定name的绑定。
rebind(String name,Remote obj)重新绑定,如果name已存在,但是Remote不一样则替换,如果Remote一样则丢弃现有的绑定
lookup(String name) 返回注册表中绑定到指定 name 的远程引用,返回Remote
String[] list() 返回在此注册表中绑定的名称的数组。该数组将包含一个此注册表中调用此方法时绑定的名称快照。

客户机端:向服务器提供相应的服务请求
Registry registry=LocateRegisty.getRegistry();
SomeService servcie=(SomeService)registry.lookup(“I serve”);
Servcie.requestService();

(4) Naming类和Registry类类似
客户端:
Naming.lookup(String url)
url 格式如下"rmi://localhost/"+远程对象引用

服务器端:
Registry registry=LocateRegistry.createRegistry(int port);
Naming.rebind(“service”,service);

(5) RMISecurityManager类

在RMI引用程序中,如果没有设置安全管理器,则只能从本地类路径加载stub和类,这可以确保应用程序不受由远程方法调用所下载的代码侵害
在从远程主机下载代码之前必须执行以下代码来安装RMISecurityManager:
System.setSecurityManager(new RMISecurityManager());

>>RMI的主要应用

RMI的用途是为分布式Java应用程序之间的远程通信提供服务,提供分布式服务。
目前主要应用时封装在各个J2EE项目框架中,例如Spring封装了RMI技术。
在Spring中实现RMI

在服务器端定义服务的接口,定义特定的类实现这些接口;
在服务器端使用org.springframework.remoting.rmi.RmiServiceExporter类来注册服务;
在客户端使用org.springframework.remoting.rmi.RmiProxyFactoryBean来实现远程服务的代理功能;
在客户端定义访问与服务器端服务接口相同的类
四、明天的计划



返回列表 返回列表
评论

    分享到