RMI全称是Remote Method Invocation-远程方法调用,Java RMI在JDK1.1中实现的,其威力就体现在它强大的开发分布式网络应用的能力上,是纯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可利用标准Java本机方法接口JNI与现有的和原有的系统相连接。RMI还可利用标准JDBC包与现有的关系数据库连接。RMI/JNI和RMI/JDBC相结合,可帮助您利用RMI与目前使用非Java语言的现有服务器进行通信,而且在您需要时可扩展Java在这些服务器上的使用。RMI可帮助您在扩展使用时充分利用Java的强大功能。
一、RMI(远程方法调用)的组成
一个正常工作的RMI系统由下面几个部分组成:
·远程服务的接口定义
·远程服务接口的具体实现
·桩(Stub)和框架(Skeleton)文件
·一个运行远程服务的服务器
·一个RMI命名服务,它允许客户端去发现这个远程服务
·类文件的提供者(一个HTTP或者FTP服务器)
·一个需要这个远程服务的客户端程序
二、RMI(远程方法调用)原理示意图
方法调用从客户对象经占位程序(Stub)、远程引用层(Remote Reference Layer)和传输层(Transport Layer)向下,传递给主机,然后再次经传 输层,向上穿过远程调用层和骨干网(Skeleton),到达服务器对象。 占位程序扮演着远程服务器对象的代理的角色,使该对象可被客户激活。 远程引用层处理语义、管理单一或多重对象的通信,决定调用是应发往一个服务器还是多个。传输层管理实际的连接,并且追踪可以接受方法调用的远程对象。服务器端的骨干网完成对服务器对象实际的方法调用,并获取返回值。返回值向下经远程引用层、服务器端的传输层传递回客户端,再向上经传输层和远程调用层返回。最后,占位程序获得返回值。
要完成以上步骤需要有以下几个步骤:
1、 生成一个远程接口
2、 实现远程对象(服务器端程序)
3、 生成占位程序和骨干网(服务器端程序)
4、 编写服务器程序
5、 编写客户程序
6、 注册远程对象
7、 启动远程对象
三、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 系统均可直接相互对话,而不必事先对协议进行转换。
四、RMI与CORBA的关系
RMI 和 CORBA 常被视为相互竞争的技术,因为两者都提供对远程分布式对象的透明访问。但这两种技术实际上是相互补充的,一者的长处正好可以弥补另一者的短处。RMI 和 CORBA 的结合产生了 RMI-IIOP,RMI-IIOP 是企业服务器端 Java 开发的基础。1997 年,IBM 和 Sun Microsystems启动了一项旨在促进 Java 作为企业开发技术的发展的合作计划。两家公司特别着力于如何将 Java 用作服务器端语言,生成可以结合进现有体系结构的企业级代码。所需要的就是一种远程传输技术,它兼有 Java 的 RMI(Remote Method Invocation,远程方法调用)较少的资源占用量和更成熟的 CORBA(Common Object Request Broker Architecture,公共对象请求代理体系结构)技术的健壮性。出于这一需要,RMI-IIOP问世了,它帮助将 Java 语言推向了目前服务器端企业开发的主流语言的领先地位。
(来源:sun;matrix.org.cn)
Java RMI 简单示例一
以下用一个最简单的Hello的示例来介绍RMI的应用。
1:定义一个远程接口
IHello.java代码如下:
import
java.rmi.Remote;
public
interface
IHello
extends
Remote {
public
String sayHello(String name)
throws
java.rmi.RemoteException;
}
2:实现远程的接口(服务端就在此远程接口的实现类中)
HelloImpl.java代码如下:
import
java.rmi.RemoteException;
import
java.rmi.server.UnicastRemoteObject;
public
class
HelloImpl
extends
UnicastRemoteObject
implements
IHello {
//
这个实现必须有一个显式的构造函数,并且要抛出一个RemoteException异常
protected
HelloImpl()
throws
RemoteException {
super
();
}
/**
* 说明清楚此属性的业务含义
*/
private
static
final
long
serialVersionUID
=
4077329331699640331L
;
public
String sayHello(String name)
throws
RemoteException {
return
"
Hello
"
+
name
+
"
^_^
"
;
}
public
static
void
main(String[] args) {
try
{
IHello hello
=
new
HelloImpl();
java.rmi.Naming.rebind(
"
rmi://localhost:1099/hello
"
, hello);
System.out.print(
"
Ready
"
);
}
catch
(Exception e) {
e.printStackTrace();
}
}
}
3:新建RMI客户端调用程序
Hello_RMI_Client.java代码如下:
import
java.rmi.Naming;
public
class
Hello_RMI_Client {
public
static
void
main(String[] args) {
try
{
IHello hello
=
(IHello) Naming.lookup(
"
rmi://localhost:1099/hello
"
);
System.out.println(hello.sayHello(
"
zhangxianxin
"
));
}
catch
(Exception e) {
e.printStackTrace();
}
}
}
4:编译并运行
4.1 用javac命令编译IHello.java、HelloImpl.java、Hello_RMI_Client.java
>javac *.java
4.2 用rmic命令生成桩和框架文件
>rmic HelloImpl
成功执行完上面的命令可以发现生成一个HelloImpl_stub.class文件,如果JDK是使用Java2SDK,那么还可以发现多出一个HelloImpl_Skel.class文件。如果服务端程序与客户端程序在同一台机器上并在同一目录中,则可以省略掉接口实现类生成的桩和框架文件,但这就失去了使用RMI的意义,而如果要在不同的JVM上运行时,客户端程序就必须得依靠服务端运程方法实现的桩和框架文件以及接口类。
4.3 运行注册程序RMIRegistry,必须在包含刚写的类的目录下运行这个注册程序。
>rmiregistry
注册程序开始运行了,不要管他,现在切换到另外一个控制台运行服务器
4.4 运行服务器HelloImpl
>java HelloImpl
当启动成功出现Ready...... 这个服务器就开始工作了,把接口的实现加载到内存等待客户端的联接。现在切换到第三个控制台,启动我们的客户端。
4.5 启动客户端:为了在其他的机器运行客户端程序你需要一个远程接口(IHello.class) 和一个stub(HelloImpl_Stub.class)。 使用如下命令运行客户端
>java Hello_RMI_Client
当运行成功会在控制台打印:Hello zhangxianxin ^_^
备注:如果不想在控制台上开启RMI注册程序RMIRegistry的话,可在RMI服务类程序中添加LocateRegistry.createRegistry(1099); 如下所示:
修改后的HelloImpl.java代码如下:
import
java.rmi.RemoteException;
import
java.rmi.registry.LocateRegistry;
import
java.rmi.server.UnicastRemoteObject;
public
class
HelloImpl
extends
UnicastRemoteObject
implements
IHello {
//
这个实现必须有一个显式的构造函数,并且要抛出一个RemoteException异常
protected
HelloImpl()
throws
RemoteException {
super
();
}
private
static
final
long
serialVersionUID
=
4077329331699640331L
;
public
String sayHello(String name)
throws
RemoteException {
return
"
Hello
"
+
name
+
"
^_^
"
;
}
public
static
void
main(String[] args) {
try
{
IHello hello
=
new
HelloImpl();
LocateRegistry.createRegistry(
1099
);
//
加上此程序,就可以不要在控制台上开启RMI的注册程序,1099是RMI服务监视的默认端口
java.rmi.Naming.rebind(
"
rmi://localhost:1099/hello
"
, hello);
System.out.print(
"
Ready
"
);
}
catch
(Exception e) {
e.printStackTrace();
}
}
}
Java RMI 简单示例二
以下用一个文件交换程序来介绍RMI的应用。这个应用允许客户端从服务端交换(或下载)所有类型的文件。第一步是定义一个远程的接口,这个接口指定的签名方法将被服务端提供和被客户端调用。
1.定义一个远程接口
IFileUtil.java代码如下:
import
java.rmi.Remote;
import
java.rmi.RemoteException;
public
interface
IFileUtil
extends
Remote {
public
byte
[] downloadFile(String fileName)
throws
RemoteException;
}
接口IFileDownload提供了一个downloadFile方法,然后返回一个相应的文件数据。
2.实现远程的接口
类FileImpl继承于UnicastRemoteObject类。这显示出FileImpl类是用来创建一个单独的、不能复制的、远程的对象,这个对象使用RMI默认的基于TCP的通信方式。
FileUtilImpl.java代码如下:
import
java.io.BufferedInputStream;
import
java.io.File;
import
java.io.FileInputStream;
import
java.rmi.RemoteException;
import
java.rmi.server.UnicastRemoteObject;
public
class
FileUtilImpl
extends
UnicastRemoteObject
implements
IFileUtil {
protected
FileUtilImpl()
throws
RemoteException {
super
();
}
private
static
final
long
serialVersionUID
=
7594622080290821912L
;
public
byte
[] downloadFile(String fileName)
throws
RemoteException{
File file
=
new
File(fileName);
byte
buffer[]
=
new
byte
[(
int
) file.length()];
int
size
=
buffer.length;
System.out.println(
"
download file size =
"
+
size
+
"
b
"
);
if
(size
>
1024
*
1024
*
10
){
//
限制文件大小不能超过10M,文件太大可能导制内存溢出!
throw
new
RemoteException(
"
Error:<The File is too big!>
"
);
}
try
{
BufferedInputStream input
=
new
BufferedInputStream(
new
FileInputStream(fileName));
input.read(buffer,
0
, buffer.length);
input.close();
System.out.println(
"
Info:<downloadFile() hed execute successful!>
"
);
return
buffer;
}
catch
(Exception e) {
System.out.println(
"
FileUtilImpl:
"
+
e.getMessage());
e.printStackTrace();
return
null
;
}
}
}
3.编写服务端
(1)创建并安装一个RMISecurityManager实例。
(2)创建一个远程对象的实例。
(3)使用RMI注册工具来注册这个对象。
FileUtilServer.java 代码如下:
import
java.rmi.Naming;
import
java.rmi.RMISecurityManager;
public
class
FileUtilServer {
public
static
void
main(String argv[]) {
try
{
IFileUtil file
=
new
FileUtilImpl();
//
LocateRegistry.createRegistry(1099);
//
加上此程序,就可以不要在控制台上开启RMI的注册程序,1099是RMI服务监视的默认端口
Naming.rebind(
"
rmi://127.0.0.1/FileUtilServer
"
, file);
System.out.print(
"
Ready
"
);
}
catch
(Exception e) {
System.out.println(
"
FileUtilServer:
"
+
e.getMessage());
e.printStackTrace();
}
}
}
声明Naming.rebind("rmi://127.0.0.1/FileUtilServer", file) 中假定了RMI注册工具(RMI registry )使用并启动了1099端口。如果在其他端口运行了RMI注册工具,则必须在这个声明中定义。例如,如果RMI注册工具在4500端口运行,则声明应为: Naming.rebind("rmi://127.0.0.1:4500/FileUtilServer", file)
另外我们已经同时假定了我们的服务端和RMI注册工具是运行在同一台机器上的。否则需要修改rebind方法中的地址。
4.编写客户端
客户端可以远程调用远程接口(FileInterface)中的任何一个方法。无论如何实现,客户端必须先从RMI注册工具中获取一个远程对象的引用。当引用获得后,方法downloadFile被调用。在执行过程中,客户端从命令行中获得两个参数,第一个是要下载的文件名,第二个是要下载的机器的地址,在对应地址的机器上运行服务端。
FileUtilClient.java 代码如下:
import
java.io.BufferedOutputStream;
import
java.io.File;
import
java.io.FileOutputStream;
import
java.rmi.Naming;
public
class
FileUtilClient {
public
static
void
main(String args[]) {
if
(args.length
!=
3
) {
System.out.println(
"
第一个参数:RMI服务的IP地址
"
);
System.out.println(
"
第二个参数:要下载的文件名
"
);
System.out.println(
"
第三个参数:要文件保存位置
"
);
System.exit(
0
);
}
try
{
String name
=
"
rmi://
"
+
args[
0
]
+
"
/FileUtilServer
"
;
IFileUtil fileUtil
=
(IFileUtil) Naming.lookup(name);
byte
[] filedata
=
fileUtil.downloadFile(args[
1
]);
if
(filedata
==
null
){
System.out.println(
"
Error:<filedata is null!>
"
);
System.exit(
0
);
}
File file
=
new
File(args[
2
]);
System.out.println(
"
file.getAbsolutePath() =
"
+
file.getAbsolutePath());
BufferedOutputStream output
=
new
BufferedOutputStream(
new
FileOutputStream(file.getAbsolutePath()));
output.write(filedata,
0
, filedata.length);
output.flush();
output.close();
System.out.println(
"
~~~~~End~~~~~
"
);
}
catch
(Exception e) {
System.err.println(
"
FileUtilServer exception:
"
+
e.getMessage());
e.printStackTrace();
}
}
}
5.运行程序
为了运行程序,我们必须使用rmic来编译生成stubs和skeletons:
>rmic FileUtilImpl
这将会生成FileUtilImpl_Stub.class和FileUtilImpl_Skel.class两个文件。stub是客户端的代理,而skeleton是服务端的框架。服务端和客户端采用javac来编译(如果服务端和客户端在两个不同的机器,则必须复制一个IFileUtil接口)。
使用rmiregistry或者start rmiregistry 命令来运行RMI注册工具到window系统默认的端口上:
> rmiregistry portNumber
此处的portNumber为端口,RMI注册工具运行之后,需要运行服务FileUtilServer,因为RMI的安全机制将在服务端发生作用,所以必须增加一条安全策略: grant{permission java.security.AllPermission "", "";};
为了运行服务端,需要有除客户类(FileUtilClient.class)之外所有的类文件。确认安全策略在policy.txt文件之后,使用如下命令来运行服务器。
> java -Djava.security.policy=policy.txt FileUtilServer
为了在其他的机器运行客户端程序,需要一个远程接口(IFileUtil.class)和一个stub(FileUtilImpl_Stub.class)。 使用如下命令运行客户端:
> java FileUtilClient fileName machineName savePath
这里fileName是要下载的文件名,machineName 是要下载的文件所在的机器(也是服务端所在的机器),savePath 是要将下载过来的文件保存的路径(带文件名)。如果全部通过的话,当客户端运行后,则这个文件将被下载到本地。
Spring对RMI的支持
1.使用RMI暴露服务
使用Spring的RMI支持,你可以通过RMI基础设施透明的暴露你的服务。设置好Spring的RMI支持后,你会看到一个和远程EJB接口类似的配置,只是没有对安全上下文传递和远程事务传递的标准支持。当使用RMI调用器时,Spring对这些额外的调用上下文提供了钩子,你可以在此插入安全框架或者定制的安全证书。
2. 使用 RmiServiceExporter 暴露服务
使用 RmiServiceExporter,我们可以把AccountService对象的接口暴露成RMI对象。可以使用 RmiProxyFactoryBean 或者在传统RMI服务中使用普通RMI来访问该接口。RmiServiceExporter 显式地支持使用RMI调用器暴露任何非RMI的服务。
当然,我们首先需要在Spring BeanFactory中设置我们的服务:
<
bean
id
="accountService"
class
="example.AccountServiceImpl"
>
<!--
any additional properties, maybe a DAO?
-->
</
bean
>
然后,我们将使用 RmiServiceExporter 来暴露我们的服务:
<
bean
class
="org.springframework.remoting.rmi.RmiServiceExporter"
>
<!--
does not necessarily have to be the same name as the bean to be exported
-->
<
property
name
="serviceName"
value
="AccountService"
/>
<
property
name
="service"
ref
="accountService"
/>
<
property
name
="serviceInterface"
value
="example.AccountService"
/>
<!--
defaults to 1099
-->
<
property
name
="registryPort"
value
="1199"
/>
</
bean
>
正如你所见,我们覆盖了RMI注册的端口号。通常,你的应用服务也会维护RMI注册,最好不要和它冲突。更进一步来说,服务名是用来绑定下面的服务的。所以本例中,服务绑定在 rmi://HOST:1199/AccountService。在客户端我们将使用这个URL来链接到服务。
注意:我们省略了一个属性,就是 servicePort 属性,它的默认值为0。 这表示在服务通信时使用匿名端口。当然如果你愿意的话,也可以指定一个不同的端口。
3. 在客户端链接服务
我们的客户端是一个使用AccountService来管理account的简单对象:
public
class
SimpleObject {
private
AccountService accountService;
public
void
setAccountService(AccountService accountService) {
this
.accountService
=
accountService;
}
}
为了把服务连接到客户端上,我们将创建另一个单独的bean工厂,它包含这个简单对象和服务链接配置位:
<
bean
class
="example.SimpleObject"
>
<
property
name
="accountService"
ref
="accountService"
/>
</
bean
>
<
bean
id
="accountService"
class
="org.springframework.remoting.rmi.RmiProxyFactoryBean"
>
<
property
name
="serviceUrl"
value
="rmi://HOST:1199/AccountService"
/>
<
property
name
="serviceInterface"
value
="example.AccountService"
/>
</
bean
>
这就是我们在客户端为支持远程account服务所需要做的。Spring将透明的创建一个调用器并且通过RmiServiceExporter使得account服务支持远程服务。在客户端,我们用RmiProxyFactoryBean连接它。
Spring对RMI支持的实际应用实例
在OMAS系统中提供给业务系统的RMI客户反馈服务的实现服务暴露是通过Resource/modules/interfaces/spring-conf/serviceContext.xml配置文件实现的,而远程接口的实现类必须序列化(即实现Serializable接口)。
Resource/modules/interfaces/spring-conf/serviceContext.xml的内容如下:
<?
xml version="1.0" encoding="UTF-8"
?>
<!
DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd"
>
<
beans
default-autowire
="byName"
default-lazy-init
="false"
>
<!--
service实现类的配置
-->
<
bean
id
="fbWebService"
class
="com.ce.omas.interfaces.service.impl.FeedbackWebServiceImpl"
/>
<
bean
class
="org.springframework.remoting.rmi.RmiServiceExporter"
>
<!--
does not necessarily have to be the same name as the bean to be exported
-->
<
property
name
="serviceName"
value
="FeedbackRMIService"
/>
<
property
name
="service"
ref
="fbWebService"
/>
<
property
name
="serviceInterface"
value
="com.ce.omas.interfaces.service.IFeedbackWebService"
/>
<!--
<property name="registryHost" value="rmi://192.168.100.7"/>
-->
<!--
defaults to 1099
-->
<
property
name
="registryPort"
value
="1199"
/>
</
bean
>
</
beans
>
对应的暴露的服务接口如下:
public
interface
IFeedbackWebService {
/**
* <b>方法用途和描述:</b> 客户反馈RMI服务端接口方法<br>
* <b>方法的实现逻辑描述:</b> 通过RMI提供服务,Spring支持的RMI远程调用
*
@param
systemID : 业务系统的唯一标识
*
@param
feedbackType :用户反馈的类型(1-系统BUG、2-系统易用性、3-客服人员态度、4-运维人员态度、5-其他)
*
@param
feedbackContent :用户反馈的正文内容
*
@return
反馈是否成功 true | false
*/
public
boolean
setFeedback(String systemID, FeedbackType feedbackType,
String feedbackContent)
throws
OMASServiceException ;
}
暴露的服务接口实现如下:
public
class
FeedbackWebServiceImpl
implements
IFeedbackWebService, Serializable {
private
static
Log _log
=
LogFactory.getLog(FeedbackWebServiceImpl.
class
);
private
static
final
long
serialVersionUID
=
-
5532505108644974594L
;
/**
* 客户反馈服务接口
*/
private
IFeedbackOperationService feedbackService;
/**
* 方法用途和描述: 注入运营模块的添加客户反馈的服务
*
@param
feedbackWebService 运营模块服务
*/
public
void
setFeedbackService(IFeedbackOperationService feedbackWebService) {
_log.info(
"
注入运营模块的添加客户反馈的服务
"
);
this
.feedbackService
=
feedbackWebService;
}
/**
* 方法用途和描述: 外部接口子系统中添加客户反馈的方法
*
@param
systemID :业务系统ID
*
@param
feedbackType :反馈类型
*
@param
feedbackContent :反馈内容
*
@return
操作是否成功 ture or false
*
@throws
ServiceException
*/
public
boolean
setFeedback(String systemID, FeedbackType feedbackType, String feedbackContent)
throws
OMASServiceException {
_log.info(
"
进入到外部接口的添加客户反馈的方法
"
);
if
(BlankUtil.isBlank(systemID)
||
BlankUtil.isBlank(feedbackType)
||
BlankUtil.isBlank(feedbackContent)) {
_log.error(
"
添加客户反馈的接口参数为空!
"
);
throw
new
OMASServiceException(
"
omas.interfaces.001
"
);
//
添加客户反馈的接口参数为空
}
WebServiceFeedbackVO vo
=
new
WebServiceFeedbackVO();
vo.setFeedbackType(String.valueOf(feedbackType.getValue()));
vo.setFeedbackContent(feedbackContent);
vo.setSystemID(systemID);
_log.info(
"
调用运营子系统的添加客户反馈的方法开始!
"
);
try
{
if
(feedbackService
==
null
) {
_log.error(
"
运营模块服务为空
"
);
throw
new
OMASServiceException(
"
omas.interfaces.002
"
);
//
运营模块服务为空
}
feedbackService.addFeedbackOperation(vo);
}
catch
(ServiceException e) {
_log.error(
"
调用运营子系统的添加客户反馈出现异常:
"
+
e.getMessage());
if
(ExceptionConstants.EXCEPTION_OMAS_FEEDBACK_VO.equals(e.getMsgKey())){
//
客户调用接口的对像为空
throw
new
OMASServiceException(
"
omas.interfaces.003
"
);
}
if
(ExceptionConstants.EXCEPTION_OMAS_FEEDBACK_SYSTEMID.equals(e.getMsgKey())){
//
业务系统标识ID为空
throw
new
OMASServiceException(
"
omas.omasservice.010
"
);
}
if
(ExceptionConstants.EXCEPTION_OMAS_FEEDBACK_SIZE.equals(e.getMsgKey())){
//
非法的业务系统唯一标识
throw
new
OMASServiceException(
"
omas.interfaces.004
"
);
}
if
(ExceptionConstants.EXCEPTION_OMAS_FEEDBACK_BASE.equals(e.getMsgKey())){
//
数据库访问出了一点小问题!
throw
new
OMASServiceException(
"
omas.interfaces.005
"
);
}
throw
new
OMASServiceException(
"
omas.omasservice.000
"
);
//
未捕获到的异常信息!
}
return
true
;
}
}
接口方法setFeedback(String, FeedbackType, String)的实现大家不用关心,其与RMI并无关系,只是一些纯业务处理逻辑而已,要注意的是接口实现类必须实现 IfeedbackWebService和Serializable接口。
在客户本地的omasservice.jar包中客户反馈的RMI客户端的配置如下:
Resource/config/omasrmi-client.xml
<?
xml version="1.0" encoding="UTF-8"
?>
<!
DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd"
>
<
beans
default-autowire
="byName"
default-lazy-init
="true"
>
<
bean
id
="fbWebServiceProxy"
class
="org.springframework.remoting.rmi.RmiProxyFactoryBean"
>
<
property
name
="serviceUrl"
>
<
value
>
rmi://127.0.0.1:1199/FeedbackRMIService
</
value
>
</
property
>
<
property
name
="serviceInterface"
>
<
value
>
com.ce.omas.interfaces.service.IFeedbackWebService
</
value
>
</
property
>
</
bean
>
<
bean
class
="com.ce.omas.omasservice.service.impl.FeedbackRMIClientImpl"
>
<
property
name
="feedbackWebService"
ref
="fbWebServiceProxy"
/>
</
bean
>
</
beans
>
客户端调用RMI服务的方法如下所示:
/**
* 方法用途和描述: 客户反馈:通过RMI方法与OMAS通讯
* 方法的实现逻辑描述:
*
@param
feedbackType
*
@param
feedbackContent
*
@return
*
@throws
OMASServiceException
*/
public
static
boolean
setFeedback_RMIClient(String systemID, FeedbackType feedbackType, String feedbackContent)
throws
OMASServiceException {
if
(systemID
==
null
||
""
.equals(systemID)) {
_log.error(
"
业务系统标识<SystemID>为空或不是对象
"
);
throw
new
OMASServiceException(
"
omas.omasservice.010
"
);
}
String rmiClientConfigFilePath
=
PropertyReader .getValue(ConfigConstants.OMASSERVICE_CONFIG_PATH, ConfigConstants.RMI_CLIENT_CONFIG_FILEPATH);
if
(rmiClientConfigFilePath
==
null
||
""
.equals(rmiClientConfigFilePath)) {
_log.error(
"
配置文件错误:Key<rmiClientConfigFile>为空或不存在
"
);
throw
new
OMASServiceException(
"
omas.omasservice.006
"
);
}
_log.info(
"
rmiClientConfigPath =
"
+
rmiClientConfigFilePath);
ApplicationContext context
=
null
;
try
{
context = new ClassPathXmlApplicationContext(rmiClientConfigFilePath);
}
catch
(Exception e) {
_log.error(
"
客户反馈:解析rmi-config.xml文件时出现异常:
"
+
e);
_log.info(
"
rmi-config.xml文件路径:
"
+
rmiClientConfigFilePath);
throw
new
OMASServiceException(
"
omas.omasservice.007
"
);
}
IFeedbackWebService service
=
null
;
try
{
service = (IFeedbackWebService) context .getBean("fbWebServiceProxy");
}
catch
(Exception e) {
_log.error(
"
从Spring的RMI客户端Bean配置文件获取服务对象时出现异常:
"
+
e);
throw
new
OMASServiceException(
"
omas.omasservice.009
"
);
}
boolean
bln
=
service.setFeedback(systemID, feedbackType, feedbackContent);
_log.info(
"
反馈操作是否成功[true|false]:
"
+
bln);
return
bln;
}
在此客户端调用的程序中,你要关注的主要是以上背景色标志为黄色的相关代码。
自己加的
1、关于问题【java RMI客户端引用了服务端的接口类?】解答
答:两边是通过接口来交换信息的,当然都需要接口和它的 Stub。
2、stub、Skeleton什么意思?
答:.为屏蔽客户调用远程主机上的对象,必须提供某种方式来模拟本地对象,这种本地对象称为存根(stub),存根负责接收本地方法调用,并将它们委派给各自的具体实现对象。在远程虚拟机中,每个远程对象都有一个Skeleton对应,Skeleton负责将调用分配给实际的远程对象来实现,他的工作是在服务器端的。
Stub的操作任务:
a、初始化与包含远程对象远程虚拟机的连接
b、与远程虚拟机参数进行编组,也就是调度,包括参数的写入以及传输
c、等待方法的调用结果
d、解编返回值和返回的异常。也就是读取服务器上的返回值,也称为反调度
e、将结果返回给调用程序。
Skeleton对应的任务:
a、读取远程方法的参数,也就是解编
b、调用实际远程对象上的方法
c、将结果或者异常组编(写入并传输)给调用程序。
Stub与Skeleton的关系以及操作是对应的关系。只要我们有编译好的远程对象的类,就可以调用jdk的rmic命令来生成stub和skeleton了,关键是我们需要理解Stub是客户端应用程序的代理,Skeleton是服务器端应用程序的代理,他们之间协作完成客户端与服务器之间的方法调用时的通信。