创建RMI程序有4个步骤:

1、定义一个远程接口的接口,该接口中的每一个方法必须声明它将产生一个RemoteException异常。
2、定义一个实现该接口的类
3、创建一个服务,用于发布2中定义的类。
4、创建一个客户端,进行RMI调用。

Student demo

程序demo
1、创建一个Student类。实现Serializable接口,用于信息的传输

  1. public class Student implements Serializable {
  2. private String name;
  3. private int age;
  4. public String getName() {
  5. return name;
  6. }
  7. public void setName(String name) {
  8. this.name = name;
  9. }
  10. public int getAge() {
  11. return age;
  12. }
  13. public void setAge(int age) {
  14. this.age = age;
  15. }
  16. }

2、定义一个接口

  1. public interface IStudentService extends Remote {
  2. List<Student> getStudents() throws RemoteException;
  3. }

3、创建一个实现类

  1. public class StudentServiceImpl extends UnicastRemoteObject implements IStudentService {
  2. protected StudentServiceImpl() throws RemoteException {
  3. }
  4. @Override
  5. public List<Student> getStudents() throws RemoteException {
  6. List<Student> students = new ArrayList<>();
  7. for (int i = 1; i < 5; i++) {
  8. Student s = new Student();
  9. s.setName("Nick" + i);
  10. s.setAge(i + 20);
  11. students.add(s);
  12. }
  13. return students;
  14. }
  15. }

4、启动服务

  1. public class RmiServer {
  2. public static void main(String[] args) {
  3. try {
  4. IStudentService studentService = new StudentServiceImpl();
  5. LocateRegistry.createRegistry(5000);
  6. Naming.rebind("rmi://127.0.0.1:5000/IStudentService", studentService);
  7. System.out.println("服务已经启动");
  8. }catch (Exception e){
  9. e.printStackTrace();
  10. }
  11. }
  12. }

5、客户端程序调用RMI方法

  1. public class RmiClient {
  2. public static void main(String[] args) {
  3. try{
  4. IStudentService studentService = (IStudentService) Naming.lookup("rmi://127.0.0.1:5000/IStudentService");
  5. List<Student> students = studentService.getStudents();
  6. for (Student s: students){
  7. System.out.println("姓名:" + s.getName() + ",年龄:" + s.getAge());
  8. }
  9. }catch (Exception e){
  10. e.printStackTrace();
  11. }
  12. }
  13. }

显示结果:

  1. 姓名:Nick1,年龄:21
  2. 姓名:Nick2,年龄:22
  3. 姓名:Nick3,年龄:23
  4. 姓名:Nick4,年龄:24


WordClock demo

目的:服务器会提供一个WorldClock服务,允许客户端获取指定时区的时间

1、要实现RMI,服务器和客户端必须共享同一个接口。定义一个WorldClock接口。
Java的RMI规定此接口必须派生自java.rmi.Remote,并在每个方法声明抛出RemoteException

  1. public interface WorldClock extends Remote {
  2. LocalDateTime getLocalDateTime(String zoneId) throws RemoteException;
  3. }

2、编写服务器的实现类,因为客户端请求的调用方法getLocalDateTime()最终会通过这个实现类返回结果。

  1. public class WorldClockService implements WorldClock {
  2. @Override
  3. public LocalDateTime getLocalDateTime(String zoneId) throws RemoteException {
  4. return LocalDateTime.now(ZoneId.of(zoneId)).withNano(0);
  5. }
  6. }

3、通过Java RMI提供的一系列底层支持接口,把上面编写的服务以RMI的形式暴露在网络上,客户端才能调用

  1. public class RmiServer {
  2. public static void main(String[] args) throws RemoteException {
  3. System.out.println("create World clock remote service...");
  4. // 实例化一个WorldClock:
  5. WorldClock worldClock = new WorldClockService();
  6. // 将此服务转换为远程服务接口:
  7. WorldClock skeleton = (WorldClock) UnicastRemoteObject.exportObject(worldClock, 0);
  8. // 将RMI服务注册到1099端口:
  9. Registry registry = LocateRegistry.createRegistry(1099);
  10. // 注册此服务,服务名为 WorldClock:
  11. registry.rebind("WorldClock", skeleton);
  12. }
  13. }

上述代码主要目的是通过RMI提供的相关类,将我们自己的WorldClock实例注册到RMI服务上。RMI的默认端口是1099,最后一步注册服务时通过rebind()指定服务名称为”WorldClock”。

4、编写客户端代码。RMI要求服务器和客户端共享同一个接口,因此我们要把WorldClock.java这个接口文件复制到客户端,然后在客户端实现RMI调用:

  1. public class RmiClient {
  2. public static void main(String[] args) throws RemoteException, NotBoundException {
  3. // 连接到服务器localhost,端口1099:
  4. Registry registry = LocateRegistry.getRegistry("localhost", 1099);
  5. // 查找名称为"WorldClock"的服务并强制转型为WorldClock接口:
  6. WorldClock worldClock = (WorldClock) registry.lookup("WorldClock");
  7. // 正常调用接口方法:
  8. LocalDateTime now = worldClock.getLocalDateTime("Asia/Shanghai");
  9. // 打印调用结果:
  10. System.out.println(now);
  11. }
  12. }

先运行服务器,再运行客户端。从运行结果可知,因为客户端只有接口,并没有实现类,因此,客户端获得的接口方法返回值实际上是通过网络从服务器端获取的。整个过程实际上非常简单,对客户端来说,客户端持有的WorldClock接口实际上对应了一个“实现类”,它是由Registry内部动态生成的,并负责把方法调用通过网络传递到服务器端。而服务器端接收网络调用的服务并不是我们自己编写的WorldClockService,而是Registry自动生成的代码。我们把客户端的“实现类”称为stub,而服务器端的网络服务类称为skeleton,它会真正调用服务器端的WorldClockService,获取结果,然后把结果通过网络传递给客户端。整个过程由RMI底层负责实现序列化和反序列化: