先上代码,最后是一些整理的概念
rmi服务发布和使用都是比较简单的,不论是使用原生还是结合spring
第一部分:原生rmi
POJO:必须要序列化,否则会报"java.io.WriteAbortedException: writing aborted; java.io.NotSerializableException: com.shine.rmi.Message",看来只有webservice不需要做这一步,因为SOAP协议将某种编程语言的数据编码为了可以识别的xsd,然后将对象编码为xml格式,最后自动序列化和反序列化,这样就统一了数据类型
1.序列化,就是将内存中的对象保存到磁盘上 或者 在IO流中传输。
2.implements Serializable就是用来标识这个类的对象可以被序列化,如果不标识,则不可序列化。
内存中的对象(就是你new出来的那些东西)就好比自然界中的空气,它是真实存在的,但是你又无法真实的看到摸到,有一天你想搞个空气运输或者保存个空气样本,那你怎么办?通常情况下是把空气压缩一下,压缩成液体或者固体,这样就可以进进行运输或保存了。
对象序列化就好比把空气“压缩”的过程(注意压缩只是一种操作过程,并不是说吧对象给压缩了,而是给序列化),你把对象从内存中“序列化”出来,序列化成你可以“看的见,摸的着”的东西,然后就可以把对象保存到磁盘上或者进行IO运输了。
public class Message implements java.io.Serializable{
/**
*
*/
private static final long serialVersionUID = 1735228251595190659L;
private String name;
private Integer age;
private Boolean married;
/**
* @return the name
*/
public String getName() {
return name;
}
/**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
/**
* @return the age
*/
public Integer getAge() {
return age;
}
/**
* @param age the age to set
*/
public void setAge(Integer age) {
this.age = age;
}
/**
* @return the married
*/
public Boolean getMarried() {
return married;
}
/**
* @param married the married to set
*/
public void setMarried(Boolean married) {
this.married = married;
}
}
接口:这边的传输的数据类型还是算复杂的
package com.zgy.naming;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.Map;
public interface IHello extends Remote{
//必须继承Remote和抛出RemoteException
public String sayHelloToSomeBody(String someBodyName) throws RemoteException;
public Message getMessage(String name,Integer age,Boolean married) throws RemoteException;
Map<String, Object> getMap() throws RemoteException;
}
实现类:
public class HelloImpl extends UnicastRemoteObject implements IHello {
public HelloImpl() throws RemoteException {
}
public String sayHelloToSomeBody(String someBodyName) throws RemoteException {
return "Hello" + someBodyName + "!";
}
/* (non-Javadoc)
* @see com.zgy.naming.IHello#getMessage(java.lang.String, java.lang.Integer, java.lang.Boolean)
*/
@Override
public Message getMessage(String name, Integer age, Boolean married) throws RemoteException {
Message message = new Message();
message.setAge(age*3);
message.setMarried(married);
message.setName(name);
return message;
}
@Override
public Map<String, Object> getMap() {
Map<String, Object> map = new HashMap<String, Object>();
map.put("1", 1);
map.put("ZGY", "LHM");
Message message = new Message();
message.setAge(1);
message.setMarried(true);
message.setName("LDH");
map.put("message", message);
return map;
}
}
服务发布和使用:
发布:
try {
HelloImpl rhello = new HelloImpl();
LocateRegistry.createRegistry(8886);
Naming.bind("rmi://localhost:8886/RHello",rhello); //两种发布方式都可以
// Naming.bind("//127.0.0.1:8886/RHello",rhello);
} catch (Exception e) {
System.out.println("");
e.printStackTrace();
}
使用
try {
//在RMI服务注册表中查找名称为RHello的对象,并调用其上的方法
IHello rhello =(IHello) Naming.lookup("rmi://10.168.2.56:8886/RHello");
System.out.println(rhello.sayHelloToSomeBody("zgy"));
Message message = rhello.getMessage("ZGY", 20, false);
System.out.println(message.getName()+message.getAge()+message.getMarried());
Map<String, Object> map = rhello.getMap();
System.out.println(map.get("1"));
System.out.println(map.get("ZGY"));
Message ldh = (Message)map.get("message");
System.out.println(ldh.getName()+ldh.getAge()+ldh.getMarried());
} catch (RemoteException e) {
e.printStackTrace();
}
第二部分:结合spring,其实spring只是轻轻包装了一层,使使用者运用起来,看起来更优雅,例如:接口不用基础Remote,方法不用抛出RemoteException,同时减少了代码的侵入性,而且配置也很简单:
服务端:
applicationContext-rmi.xml放在与web.xml同一路径下
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<bean id="serviceReleaseImpl" class="com.shine.rmi.ServiceReleaseImpl">
</bean>
<bean class="org.springframework.remoting.rmi.RmiServiceExporter">
<!-- 不一定要与要输出的bean同名-->
<property name="serviceName" value="ServiceRelease"/>
<property name="service" ref="serviceReleaseImpl"/>
<property name="serviceInterface" value="com.shine.rmi.ServiceRelease"/>
<!-- 默认为1199-->
<property name="registryPort" value="1199"/>
</bean>
</beans>
web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<!-- ContextLoaderListener -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext-rmi.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
客户端:
applicationContext-rmiclient.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<!-- RMI实例 -->
<bean id="serviceRelease" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
<property name="serviceUrl" value="rmi://localhost:1199/ServiceRelease"/>
<property name="serviceInterface" value="com.shine.rmi.ServiceRelease"/>
</bean>
</beans>
测试类:
ApplicationContext context = new ClassPathXmlApplicationContext(
new String[] {"applicationContext-rmiclient.xml"});
ServiceRelease simpleObject = (ServiceRelease)context.getBean("serviceRelease");
Message message = simpleObject.getMessage("ZGY", 20, false);
System.out.println(message.getName()+message.getAge()+message.getMarried());
Map<String, Object> map = simpleObject.getMap();
System.out.println(map.get("1"));
System.out.println(map.get("ZGY"));
Message ldh = (Message)map.get("message");
System.out.println(ldh.getName()+ldh.getAge()+ldh.getMarried());
while(true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
有图有真相:
基本概念,PS:有一部分是百度百科的,还有一部分是参考网络上的一篇文章,这位老兄只在08年写了几篇博客,后来估计是转行了吧,还有位老兄更甚,在写博客时说这是自己的第一篇博客,结果也是最后一篇,不知道自己能写到某一年某一月某一日
RMI是Java的一组拥护开发分布式应用程序的API。RMI使用Java语言接口定义了远程对象,它集合了Java序列化和Java远程方法协议(Java Remote Method Protocol)。简单地说,这样使原先的程序在同一操作系统的方法调用,变成了不同操作系统之间程序的方法调用,由于J2EE是分布式程序平台,它以RMI机制实现程序组件在不同操作系统之间的通信。比如,一个EJB可以通过RMI调用Web上另一台机器上的EJB远程方法。
RMI(Remote Method Invocation,远程方法调用)是用Java在JDK1.1中实现的,它大大增强了Java开发分布式应用的能力。Java作为一种风靡一时的网络开发语言,其巨大的威力就体现在它强大的开发分布式网络应用的能力上,而RMI就是开发百分之百纯Java的网络分布式应用系统的核心解决方案之一。其实它可以被看作是RPC的Java版本。但是传统RPC并不能很好地应用于分布式对象系统。而Java RMI 则支持存储于不同地址空间的程序级对象之间彼此进行通信,实现远程对象之间的无缝远程调用。
RMI目前使用Java远程消息交换协议JRMP(Java Remote Messaging Protocol)进行通信。JRMP是专为Java的远程对象制定的协议。因此,Java RMI具有Java的“Write Once,Run Anywhere”的优点,是分布式应用系统的百分之百纯Java解决方案。用Java RMI开发的应用系统可以部署在任何支持JRE(Java Run Environment Java,运行环境)的平台上。但由于JRMP是专为Java对象制定的,因此,RMI对于用非Java语言开发的应用系统的支持不足。不能与用非Java语言书写的对象进行通信。
RMI系统运行机理RMI系统运行机理:
Stub和Skeleton在什么位置产生
网上有太多的stub和skeleton的疑问了,对stub和skeleton迷惑影响了对RMI的学习。从JDK5.0以后,这两个类就不需要rmic来产生了,而是有JVM自动处理,实际上他们还是存在的。Stub存在于客户端,作为客户端的代理,让我们总是认为客户端产生了stub,接口没有作用。实际上stub类是通过Java动态类下载机制下载的(具体内容请参考:Java RMI实现代码动态下载),它是由服务端产生,然后根据需要动态的加载到客户端,如果下次再运行这个客户端该存根类存在于classpath中,它就不需要再下载了,而是直接加载。(具体的内部细节,需要参考Sun的Rmi - Java Remote Method Invocation – Specification)。总的来说,stub是在服务端产生的,如果服务端的stub内容改变,那么客户端的也是需要同步更新。
Stub和公共接口interface(远程对象的功能)的关系
我的一篇译文上,一个网友说“如果客户端有接口的话,我还要那个存根来干什么??直接用接口不就完事了?经过测试,客户端放一个接口的话,根本不需要存根,这个是我一直想不明白的..........”。在开始学习时,我也是有这个疑问,通过接口直接调用不就OK了吗?实际上,存根是我们必需的。一个服务如果没有合适的存根类,客户就没有办法去调用远程的接口,RMI使用存根来返回引用远程对象接口的参数。在类的关系中,接口是不能实例化的,但是它可以指向一个实现该接口的实例。存根和接口就是这种关系,而存根类的实例就是在:lookup ()方法调用时加载、实例的。接口只是告诉JVM,内存中这片的字节码中,这几个方法我可以调用。IHello rhello =(IHello) Naming.lookup("rmi://10.168.2.56:8886/RHello");,这里接口指向的就是stub
rmiregistry介绍:
Rmiregistry需要在提供远程对象服务端启动,它提供了一个环境,说白了就是在内存中,开辟了一片空间,用来接受服务端服务程序的注册,产生一个类似于数据库,提供存储检索远程对象功能的注册表。这个RMI注册表,就是我们常听到的RMI名字服务。远程客户端,就是依靠它获得存根,调用远程方法。
RMI(远程方法调用)的优点
从最基本的角度看,RMI是Java的远程过程调用(RPC)机制。与传统的RPC系统相比,RMI具有若干优点,因为它是Java面向对象方法的一部分。传统的RPC系统采用中性语言,所以是最普通的系统--它们不能提供所有可能的目标平台所具有的功能。
RMI以Java为核心,可与采用本机方法与现有系统相连接。这就是说,RMI可采用自然、直接和功能全面的方式为您提供分布式计算技术,而这种技术可帮助您以不断递增和无缝的方式为整个系统添加Java功能。
RMI的主要优点如下:
面向对象:RMI可将完整的对象作为参数和返回值进行传递,而不仅仅是预定义的数据类型。也就是说,您可以将类似Java哈希表这样的复杂类型作为一个参数进行传递。而在目前的RPC系统中,您只能依靠客户机将此类对象分解成基本数据类型,然后传递这些数据类型,最后在服务器端重新创建哈希表。RMI则不需额外的客户程序代码(将对象分解成基本数据类型),直接跨网传递对象。
可移动属性:RMI可将属性(类实现程序)从客户机移动到服务器,或者从服务器移到客户机。例如,您可以定义一个检查雇员开支报告的接口,以便察看雇员是否遵守了公司目前实行的政策。在开支报告创建后,客户机就会从服务器端获得实现该接口的对象。如果政策发生变化,服务器端就会开始返回使用了新政策的该接口的另一个实现程序。您不必在用户系统上安装任何新的软件就能在客户端检查限制条件--从而向用户提供烁快的反馈,并降低服务器的工作量。这样就能具备最大的灵活性,因为政策改变时只需要您编写一个新的Java类,并将其在服务器主机上安装一次即可。
设计方式:对象传递功能使您可以在分布式计算中充分利用面向对象技术的强大功能,如二层和三层结构系统。如果您能够传递属性,那么您就可以在您的解决方案中使用面向对象的设计方式。所有面向对象的设计方式无不依靠不同的属性来发挥功能,如果不能传递完整的对象--包括实现和类型--就会失去设计方式上所提供的优点。
安 全:RMI使用Java内置的安全机制保证下载执行程序时用户系统的安全。RMI使用专门为保护系统免遭恶意小应用程序侵害而设计的安全管理程序,可保护您的系统和网络免遭潜在的恶意下载程序的破坏。在情况严重时,服务器可拒绝下载任何执行程序。
便于编写和使用:RMI使得Java远程服务程序和访问这些服务程序的Java客户程序的编写工作变得轻松、简单。远程接口实际上就是Java接口。服务程序大约用三行指令宣布本身是服务程序,其它方面则与任何其它Java对象类似。这种简单方法便于快速编写完整的分布式对象系统的服务程序,并快速地制做软件的原型和早期版本,以便于进行测试和评估。因为RMI程序编写简单,所以维护也简单。
可连接现有/原有的系统:RMI可通过Java的本机方法接口JNI与现有系统进行进行交互。利用RMI和JNI,您就能用Java语言编写客户端程序,还能使用现有的服务器端程序。在使用RMI/JNI与现有服务器连接时,您可以有选择地用Java重新编写服务程序的任何部分,并使新的程序充分发挥Java的功能。类似地,RMI可利用JDBC、在不修改使用数据库的现有非Java源代码的前提下与现有关系数据库进行交互。
编写一次,到处运行:RMI是Java“编写一次,到处运行 ”方法的一部分。任何基于RMI的系统均可100%地移植到任何Java虚拟机上,RMI/JDBC系统也不例外。如果使用RMI/JNI与现有系统进行交互工作,则采用JNI编写的代码可与任何Java虚拟机进行编译、运行。
分布式垃圾收集:RMI采用其分布式垃圾收集功能收集不再被网络中任何客户程序所引用的远程服务对象。与Java虚拟机内部的垃圾收集类似,分布式垃圾收集功能允许用户根据自己的需要定义服务器对象,并且明确这些对象在不再被客户机引用时会被删除。
并行计算:RMI采用多线程处理方法,可使您的服务器利用这些Java线程更好地并行处理客户端的请求。Java分布式计算解决方案:RMI从JDK 1.1开始就是Java平台的核心部分,因此,它存在于任何一台1.1 Java虚拟机中。所有RMI系统均采用相同的公开协议,所以,所有Java 系统均可直接相互对话,而不必事先对协议进行转换。