RMI基本概念
RMI(Remote Method Invocation,远程方法调用)是从java1.1开始实现的,它大大增强了Java开发分布式应用的能力。RMI对接口有着强烈的依赖,在需要创建一个远程对象的时候,我们通过传递一个接口来隐藏基层的实施细节,所以客户得到远程对象的一个句柄时,它们真正得到的是接口句柄,然后本地代码通过接口操作远程对象。通过RMI编写程序可以非常方便的实现分布式Java应用程序。
创建一个远程接口时,必须遵守下列规则:
- 远程接口必须为public属性(不能是“包访问”),否则一旦Client试图装载一个实现了远程接口的远程对象,就会得到一个错误;
- 远程接口必须扩展(extends)接口java.rmi.Remote;
- 除了应用程序本身可能抛出的Exception外,远程接口中的每个方法还必须在自己的throws从句中声明抛出java.rmi.RemoteException(否则运行Server时会抛出java.rmi.server.ExportException);
- 作为参数或返回值传递的一个远程对象必须声明为远程接口,不可声明为实现类。
RMI开发步骤
- 首先创建远程接口并声明远程方法,需要继承自java.rmi.Remote(Client和Server需要共享这个接口);
- 创建远程接口的实现类,这个类必须继承自java.rmi.server.UnicastRemoteObject(只有Server需要这个实现类,Client可以没有);
- 编写Server,绑定端口,注册对象;
- 编写Client,监听端口并查找对象。
实例
编写基本的,要被传输的bean对象(Person),这个对象可以是基本数据类型或者实现了java.io.Serializable的自定义类型。
-
package com.liu.models;
-
-
public
class Person implements java.io.Serializable {
-
private
static
final
long serialVersionUID =
1L;
-
-
private
int id;
-
private String name;
-
private
int age;
-
-
public int getId() {
-
return id;
-
}
-
-
public void setId(int id) {
-
this.id = id;
-
}
-
-
public String getName() {
-
return name;
-
}
-
-
public void setName(String name) {
-
this.name = name;
-
}
-
-
public int getAge() {
-
return age;
-
}
-
-
public void setAge(int age) {
-
this.age = age;
-
}
-
-
public String toString() {
-
return
“id:” + getId() +
” age:” + getAge() +
” name:” + getName();
-
}
-
}
创建远程接口PersonService,这个接口为Server和Client共有的,必须继承自Remote,同时它的所有方法都必须抛出java.rmi.RemoteException。
-
package com.liu.models;
-
-
import java.rmi.Remote;
-
import java.rmi.RemoteException;
-
import java.util.List;
-
-
public
interface PersonService extends Remote {
-
public List<Person> getPersonList(int n) throws RemoteException;
-
}
创建远程接口的实现类PersionServiceImpl,需要继承自UnicastRemoteObject,这个类只需要Server拥有即可。
-
package com.liu.models;
-
-
import java.rmi.RemoteException;
-
import java.rmi.server.UnicastRemoteObject;
-
import java.util.List;
-
-
public
class PersonServiceImpl extends UnicastRemoteObject implements PersonService{
-
private
static
final
long serialVersionUID =
1L;
-
-
public PersonServiceImpl() throws RemoteException {
-
super();
-
}
-
@Override
-
public List<Person> getPersonList(int n) throws RemoteException {
-
System.out.println(
"Get Persons");
-
return Utils.createPersonList(n);
-
}
-
}
创建Server端代码,绑定特定端口,并注册远程接口的实现类。
-
package com.liu.server;
-
-
import java.rmi.registry.LocateRegistry;
-
-
import javax.naming.Context;
-
import javax.naming.InitialContext;
-
-
import com.liu.models.PersonServiceImpl;
-
import com.liu.models.PersonService;
-
-
public
class Server {
-
public static void main(String[] args) {
-
try {
-
PersonService personService =
new PersonServiceImpl();
-
LocateRegistry.createRegistry(
6600);
-
//这个方法也可以实现绑定
-
//Naming.rebind("rmi://127.0.0.1:6600/PersonService", personService);
-
Context namingContext =
new InitialContext();
-
namingContext.rebind(
"rmi://127.0.0.1:6600/PersonService", personService);
-
System.out.println(
"Service Start!");
-
}
catch (Exception e) {
-
e.printStackTrace();
-
}
-
}
-
}
创建Client端代码,获取远程接口对应的远程实现类,并通过远程接口操作这个远程接口。
-
package com.liu.client;
-
-
import java.rmi.Naming;
-
import java.util.List;
-
-
import com.liu.models.Person;
-
import com.liu.models.PersonService;;
-
-
public
class Client {
-
public static void main(String[] args){
-
try{
-
//远程对象调用的端口和注册类
-
PersonService personService=(PersonService)Naming.lookup(
"rmi://127.0.0.1:6600/PersonService");
-
List<Person> personList = personService.getPersonList(
5);
-
for(Person person : personList){
-
System.out.println(person);
-
}
-
}
catch(Exception ex){
-
ex.printStackTrace();
-
}
-
}
-
}
这里还为实现类添加了一个辅助类用来随机生成Person对象。
-
package com.liu.models;
-
-
import java.util.ArrayList;
-
import java.util.List;
-
import java.util.Random;
-
-
import com.liu.models.Person;
-
-
public
class Utils {
-
public
static
final Random rand =
new Random(
47);
-
public
static
final String names[] = {
"aaa",
"bbb",
"ccc",
"ddd",
"eee",
"fff",
"ggg"};
-
-
public static List<Person> createPersonList(int n){
-
List<Person> list =
new ArrayList<>();
-
for(
int i =
0; i < n;i++){
-
Person p =
new Person();
-
p.setId(i);
-
p.setAge(rand.nextInt(
100));
-
p.setName(names[rand.nextInt(names.length)]);
-
-
list.add(p);
-
}
-
return list;
-
}
-
}
如何运行
命令行中运行的方法
首先创建Server和Client文件夹,将com.liu.models. Person.java、com.liu.models.PersonService.java、com.liu.models. PersionServiceImpl.java、com.liu.models.Utils.java以及com.liu.server.Server.java都放入Server文件夹,将com.liu.models.Person.java、com.liu.models. PersonService.java以及com.liu.client.Client.java都放进Client文件夹。
然后在Server文件夹中编译Server:Server> javac com/liu/server/Server.java;
在Client文件夹中编译Client:Client>javac com/liu/client/Client.java;
最后,先启动Server:Server>java com/liu/server/Server;
再运行Client:Client>java com/liu/client/Client。在eclipse下如何运行
创建三个项目,结构如图:
此外,需要在ModelsServer和ModelsClient项目中添加项目models的引用。
最后可以先运行Server,然后再运行Client就会看到和命令行下类似的结果了。
两种可能的错误
-
/*
-
Person没有实现java.io.Serializable导致的异常
-
java.rmi.UnmarshalException: error unmarshalling return; nested exception is:
-
java.io.WriteAbortedException: writing aborted; java.io.NotSerializableException: com.liu.models.Person
-
at sun.rmi.server.UnicastRef.invoke(Unknown Source)
-
at java.rmi.server.RemoteObjectInvocationHandler.invokeRemoteMethod(Unknown Source)
-
at java.rmi.server.RemoteObjectInvocationHandler.invoke(Unknown Source)
-
at com.sun.proxy.$Proxy0.getPersonList(Unknown Source)
-
at com.liu.client.Client.main(Client.java:14)
-
Caused by: java.io.WriteAbortedException: writing aborted; java.io.NotSerializableException: com.liu.models.Person
-
at java.io.ObjectInputStream.readObject0(Unknown Source)
-
at java.io.ObjectInputStream.readObject(Unknown Source)
-
at java.util.ArrayList.readObject(Unknown Source)
-
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
-
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
-
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
-
at java.lang.reflect.Method.invoke(Unknown Source)
-
at java.io.ObjectStreamClass.invokeReadObject(Unknown Source)
-
at java.io.ObjectInputStream.readSerialData(Unknown Source)
-
at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
-
at java.io.ObjectInputStream.readObject0(Unknown Source)
-
at java.io.ObjectInputStream.readObject(Unknown Source)
-
at sun.rmi.server.UnicastRef.unmarshalValue(Unknown Source)
-
… 5 more
-
*/
2. 如果没有定义PersonService的远程方法抛出RemoteException会导致运行时java.rmi.server.ExportException。
-
/*
-
PersonService的远程方法缺少throws RemoteException语句时的异常
-
java.rmi.server.ExportException: remote object implements illegal remote interface; nested exception is:
-
java.lang.IllegalArgumentException: illegal remote method encountered: public abstract java.util.List com.liu.models.PersonService.getPersonList(int)
-
at sun.rmi.server.UnicastServerRef.exportObject(Unknown Source)
-
at java.rmi.server.UnicastRemoteObject.exportObject(Unknown Source)
-
at java.rmi.server.UnicastRemoteObject.exportObject(Unknown Source)
-
at java.rmi.server.UnicastRemoteObject.<init>(Unknown Source)
-
at java.rmi.server.UnicastRemoteObject.<init>(Unknown Source)
-
at com.liu.models.PersionServiceImpl.<init>(PersionServiceImpl.java:11)
-
at com.liu.server.Server.main(Server.java:14)
-
Caused by: java.lang.IllegalArgumentException: illegal remote method encountered: public abstract java.util.List com.liu.models.PersonService.getPersonList(int)
-
at sun.rmi.server.Util.checkMethod(Unknown Source)
-
at sun.rmi.server.Util.getRemoteInterfaces(Unknown Source)
-
at sun.rmi.server.Util.getRemoteInterfaces(Unknown Source)
-
at sun.rmi.server.Util.createProxy(Unknown Source)
-
… 7 more
-
*/
总结
- 定义远程接口;
- 实现远程接口实现类;
- 定义Client和Server;
- 使用rmic为远程接口生成根(Stub)和干(Skeleton)文件;
- 使用rmiregistry程序开启服务器RMI注册表;
- 运行Server,运行Client。