java 远程调用对象方法

原文:https://blog.csdn.net/teaandnoodle/article/details/52277935

RMI基本概念

RMI(Remote Method Invocation,远程方法调用)是从java1.1开始实现的,它大大增强了Java开发分布式应用的能力。RMI对接口有着强烈的依赖,在需要创建一个远程对象的时候,我们通过传递一个接口来隐藏基层的实施细节,所以客户得到远程对象的一个句柄时,它们真正得到的是接口句柄,然后本地代码通过接口操作远程对象。通过RMI编写程序可以非常方便的实现分布式Java应用程序。

创建一个远程接口时,必须遵守下列规则:

  1. 远程接口必须为public属性(不能是“包访问”),否则一旦Client试图装载一个实现了远程接口的远程对象,就会得到一个错误;
  2. 远程接口必须扩展(extends)接口java.rmi.Remote;
  3. 除了应用程序本身可能抛出的Exception外,远程接口中的每个方法还必须在自己的throws从句中声明抛出java.rmi.RemoteException(否则运行Server时会抛出java.rmi.server.ExportException);
  4. 作为参数或返回值传递的一个远程对象必须声明为远程接口,不可声明为实现类。

RMI开发步骤

  1. 首先创建远程接口并声明远程方法,需要继承自java.rmi.Remote(Client和Server需要共享这个接口);
  2. 创建远程接口的实现类,这个类必须继承自java.rmi.server.UnicastRemoteObject(只有Server需要这个实现类,Client可以没有);
  3. 编写Server,绑定端口,注册对象;
  4. 编写Client,监听端口并查找对象。

实例

编写基本的,要被传输的bean对象(Person),这个对象可以是基本数据类型或者实现了java.io.Serializable的自定义类型。

[java]  view plain  copy
  1. package com.liu.models;  
  2.   
  3. public class Person implements java.io.Serializable {  
  4.     private static final long serialVersionUID = 1L;  
  5.   
  6.     private int id;  
  7.     private String name;  
  8.     private int age;  
  9.   
  10.     public int getId() {  
  11.         return id;  
  12.     }  
  13.   
  14.     public void setId(int id) {  
  15.         this.id = id;  
  16.     }  
  17.   
  18.     public String getName() {  
  19.         return name;  
  20.     }  
  21.   
  22.     public void setName(String name) {  
  23.         this.name = name;  
  24.     }  
  25.   
  26.     public int getAge() {  
  27.         return age;  
  28.     }  
  29.   
  30.     public void setAge(int age) {  
  31.         this.age = age;  
  32.     }  
  33.   
  34.     public String toString() {  
  35.         return "id:" + getId() + " age:" + getAge() + " name:" + getName();  
  36.     }  
  37. }  

创建远程接口PersonService,这个接口为Server和Client共有的,必须继承自Remote,同时它的所有方法都必须抛出java.rmi.RemoteException。

[java]  view plain  copy
  1. package com.liu.models;  
  2.   
  3. import java.rmi.Remote;  
  4. import java.rmi.RemoteException;  
  5. import java.util.List;  
  6.   
  7. public interface PersonService extends Remote {  
  8.     public List getPersonList(int n) throws RemoteException;  
  9. }  

创建远程接口的实现类PersionServiceImpl,需要继承自UnicastRemoteObject,这个类只需要Server拥有即可。

[java]  view plain  copy
  1. package com.liu.models;  
  2.   
  3. import java.rmi.RemoteException;  
  4. import java.rmi.server.UnicastRemoteObject;  
  5. import java.util.List;  
  6.   
  7. public class PersonServiceImpl extends UnicastRemoteObject implements PersonService{  
  8.     private static final long serialVersionUID = 1L;  
  9.       
  10.     public PersonServiceImpl() throws RemoteException {  
  11.         super();  
  12.     }  
  13.     @Override  
  14.     public List getPersonList(int n) throws RemoteException {  
  15.         System.out.println("Get Persons");  
  16.         return Utils.createPersonList(n);  
  17.     }  
  18. }  

创建Server端代码,绑定特定端口,并注册远程接口的实现类。

[java]  view plain  copy
  1. package com.liu.server;  
  2.   
  3. import java.rmi.registry.LocateRegistry;  
  4.   
  5. import javax.naming.Context;  
  6. import javax.naming.InitialContext;  
  7.   
  8. import com.liu.models.PersonServiceImpl;  
  9. import com.liu.models.PersonService;  
  10.   
  11. public class Server {  
  12.     public static void main(String[] args) {  
  13.         try {  
  14.             PersonService personService = new PersonServiceImpl();  
  15.             LocateRegistry.createRegistry(6600);  
  16.             //这个方法也可以实现绑定  
  17.             //Naming.rebind("rmi://127.0.0.1:6600/PersonService", personService);  
  18.             Context namingContext = new InitialContext();  
  19.             namingContext.rebind("rmi://127.0.0.1:6600/PersonService", personService);  
  20.             System.out.println("Service Start!");  
  21.         } catch (Exception e) {  
  22.             e.printStackTrace();  
  23.         }  
  24.     }  
  25. }  

创建Client端代码,获取远程接口对应的远程实现类,并通过远程接口操作这个远程接口。

[java]  view plain  copy
  1. package com.liu.client;  
  2.   
  3. import java.rmi.Naming;  
  4. import java.util.List;  
  5.   
  6. import com.liu.models.Person;  
  7. import com.liu.models.PersonService;;  
  8.   
  9. public class Client {  
  10.     public static void main(String[] args){  
  11.         try{  
  12.             //远程对象调用的端口和注册类  
  13.             PersonService personService=(PersonService)Naming.lookup("rmi://127.0.0.1:6600/PersonService");  
  14.             List personList = personService.getPersonList(5);  
  15.             for(Person person : personList){  
  16.                 System.out.println(person);  
  17.             }  
  18.         }catch(Exception ex){  
  19.             ex.printStackTrace();  
  20.         }  
  21.     }  
  22. }  

这里还为实现类添加了一个辅助类用来随机生成Person对象。

[java]  view plain  copy
  1. package com.liu.models;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.List;  
  5. import java.util.Random;  
  6.   
  7. import com.liu.models.Person;  
  8.   
  9. public class Utils {  
  10.     public static final Random rand = new Random(47);  
  11.     public static final String names[] = {"aaa","bbb","ccc","ddd","eee","fff","ggg"};  
  12.       
  13.     public static List createPersonList(int n){  
  14.         List list = new ArrayList<>();  
  15.         for(int i = 0; i < n;i++){  
  16.             Person p = new Person();  
  17.             p.setId(i);  
  18.             p.setAge(rand.nextInt(100));  
  19.             p.setName(names[rand.nextInt(names.length)]);  
  20.               
  21.             list.add(p);  
  22.         }  
  23.         return list;  
  24.     }  
  25. }  

如何运行

命令行中运行的方法

首先创建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下如何运行

创建三个项目,结构如图:

java 远程调用对象方法_第1张图片

此外,需要在ModelsServer和ModelsClient项目中添加项目models的引用。

java 远程调用对象方法_第2张图片

最后可以先运行Server,然后再运行Client就会看到和命令行下类似的结果了。

两种可能的错误

1. 如果没有把Person定义为Serializable会导致出现java.rmi.UnmarshalException异常;
[java]  view plain  copy
  1. /* 
  2. Person没有实现java.io.Serializable导致的异常 
  3. java.rmi.UnmarshalException: error unmarshalling return; nested exception is:  
  4.     java.io.WriteAbortedException: writing aborted; java.io.NotSerializableException: com.liu.models.Person 
  5.     at sun.rmi.server.UnicastRef.invoke(Unknown Source) 
  6.     at java.rmi.server.RemoteObjectInvocationHandler.invokeRemoteMethod(Unknown Source) 
  7.     at java.rmi.server.RemoteObjectInvocationHandler.invoke(Unknown Source) 
  8.     at com.sun.proxy.$Proxy0.getPersonList(Unknown Source) 
  9.     at com.liu.client.Client.main(Client.java:14) 
  10. Caused by: java.io.WriteAbortedException: writing aborted; java.io.NotSerializableException: com.liu.models.Person 
  11.     at java.io.ObjectInputStream.readObject0(Unknown Source) 
  12.     at java.io.ObjectInputStream.readObject(Unknown Source) 
  13.     at java.util.ArrayList.readObject(Unknown Source) 
  14.     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
  15.     at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) 
  16.     at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) 
  17.     at java.lang.reflect.Method.invoke(Unknown Source) 
  18.     at java.io.ObjectStreamClass.invokeReadObject(Unknown Source) 
  19.     at java.io.ObjectInputStream.readSerialData(Unknown Source) 
  20.     at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source) 
  21.     at java.io.ObjectInputStream.readObject0(Unknown Source) 
  22.     at java.io.ObjectInputStream.readObject(Unknown Source) 
  23.     at sun.rmi.server.UnicastRef.unmarshalValue(Unknown Source) 
  24.     ... 5 more 
  25. */  
2. 如果没有定义PersonService的远程方法抛出RemoteException会导致运行时java.rmi.server.ExportException。
[java]  view plain  copy
  1. /* 
  2. PersonService的远程方法缺少throws RemoteException语句时的异常 
  3. java.rmi.server.ExportException: remote object implements illegal remote interface; nested exception is:  
  4.     java.lang.IllegalArgumentException: illegal remote method encountered: public abstract java.util.List com.liu.models.PersonService.getPersonList(int) 
  5.     at sun.rmi.server.UnicastServerRef.exportObject(Unknown Source) 
  6.     at java.rmi.server.UnicastRemoteObject.exportObject(Unknown Source) 
  7.     at java.rmi.server.UnicastRemoteObject.exportObject(Unknown Source) 
  8.     at java.rmi.server.UnicastRemoteObject.(Unknown Source) 
  9.     at java.rmi.server.UnicastRemoteObject.(Unknown Source) 
  10.     at com.liu.models.PersionServiceImpl.(PersionServiceImpl.java:11) 
  11.     at com.liu.server.Server.main(Server.java:14) 
  12. Caused by: java.lang.IllegalArgumentException: illegal remote method encountered: public abstract java.util.List com.liu.models.PersonService.getPersonList(int) 
  13.     at sun.rmi.server.Util.checkMethod(Unknown Source) 
  14.     at sun.rmi.server.Util.getRemoteInterfaces(Unknown Source) 
  15.     at sun.rmi.server.Util.getRemoteInterfaces(Unknown Source) 
  16.     at sun.rmi.server.Util.createProxy(Unknown Source) 
  17.     ... 7 more 
  18. */  

总结

上面的方式是实现RMI最简单的方式,在网上会有一些说法是按照以下步骤进行的:
  1. 定义远程接口;
  2. 实现远程接口实现类;
  3. 定义Client和Server;
  4. 使用rmic为远程接口生成根(Stub)和干(Skeleton)文件;
  5. 使用rmiregistry程序开启服务器RMI注册表;
  6. 运行Server,运行Client。
以上方式需要额外生成根(Stub)和干(Skeleton)文件这种方式是过时的,现在已经不需要这两个文件了。另外,无论如何运行RMI都需要开启rmiregistry程序,但是开启它有两种方式:直接命令行中运行rmiregistry;在程序中使用LocateRegistry.createRegistry(port)进行开启(也就是上面程序使用的方式)。

代码下载: JavaRMI示例程序

参考资料:
学习笔记:JAVA RMI远程方法调用简单实例
RMI实例(二)(无需dos运行rmic和rmiregistry)
《Thinking in Java》第三版网络编程相关内容

你可能感兴趣的:(RMI)