RMI使得程序员能够在分布式系统软件开发中使用所有的面向对象编程的功能,如对象、类、继承的使用。
JavaRMI扩展了Java的对象模型,对象由服务器管理,客户端通过远程方法调用来调用它们的方法。在RMI中,客户调用一个对象方法的请求以消息的形式传送到管理该对象的服务器,通过在服务器端执行对象的方法来完成该调用,并将处理的结果通过另一个消息返回给客户。
创建远程接口
//定义一个远程接口,必须继承Rmote接口
//一个学生类的接口,其中的方法用于返回学生信息
//其中需要远程调用的方法必须抛出RemoteException异常
//远程接口:每个远程对象都有一个远程接口,由该接口指定哪些方法可以被远程调用
public interface IStudent extends Remote{
/**
* 远程接口的方法必须抛出RemoteException
*/
public String StudentInfo() throws RemoteException;
}
/**
1. 远程接口
2. 学生信息列表接口,用于注册学生信息,显示学生信息
*/
public interface IStudentList extends Remote {
/**
* 注册新的学生信息
*/
public IStudent regionNewStudent(String name, char sex, int age) throws RemoteException;
/**
* 打印出所有学生信息
*/
public void printStudentInfos() throws RemoteException;
/**
* 返回学生信息列表
*/
public List<IStudent> getStudents() throws RemoteException;
}
注意:远程接口必须在客户端和服务端中均定义,且所在包名相同,否则会报ClassNotFoundException异常。
远程接口实现
public class Student extends UnicastRemoteObject implements IStudent {
private String name;
private char sex;
private int age;
/**
* 因为UnicastRemoteObject的构造方法抛出了RemoteException异常,
* 因此这里默认的构造方法必须写,必须声明抛出RemoteException异常
* @throws RemoteException
*/
protected Student(String n,char s,int a) throws RemoteException {
name = n;
sex = s;
age = a;
}
/**
* 实现远程接口的方法
* @throws RemoteException
*/
@Override
public String StudentInfo() throws RemoteException {
String str = "Name:"+name+"\tSex:"+sex+"\tAge:"+age;
return str;
}
}
public class StudentList extends UnicastRemoteObject implements IStudentList {
private List<IStudent> list;
protected StudentList() throws RemoteException {
list = new ArrayList<>();
}
@Override
public IStudent regionNewStudent(String name, char sex, int age) throws RemoteException {
list.add(new Student(name,sex,age));
return list.get(list.size()-1);
}
@Override
public void printStudentInfos() throws RemoteException {
for(IStudent stu : list)
System.out.println(stu.StudentInfo());
}
@Override
public List<IStudent> getStudents() throws RemoteException {
return list;
}
}
创建服务器程序
/**
* 创建RMI注册表,启动RMI服务,并将远程对象组注册到RMI注册表中
***********************************************************
* 1.服务器程序包含初始化部分,如Java中的main方法,
* 1.1 初始化部分负责创建并初始化至少一个驻留在服务器上的伺服器
* 1.2 初始化部分也可以用一个绑定程序注册它的一些伺服器
* 2.分布式系统中的绑定程序是一个单独的服务,维护一张表,表中包含从文本名字到远程对象引用的映射
* 2.1 服务器用该表来按名字注册远程对象
* 2.2 客户端用名字来查找这些远程对象
***********************************************************
*/
public class Server {
public static void main(String[] args) {
/** From java doc
* Applets typically run in a container that already has a security manager, so there is generally no need for applets to set a security manager.
* If you have a standalone application, you might need to set a SecurityManager in order to enable class downloading.
* This can be done by adding the following to your code.
* (It needs to be executed before RMI can download code from remote hosts, so it most likely needs to appear in the main method of your application.)
*/
if(System.getSecurityManager() == null){
System.setSecurityManager(new SecurityManager());
}
try {
//创建一个远程对象 **伺服器**,传递给客户端,让客户端可以添加新的对象
StudentList stuList = new StudentList();
// 本地主机上的远程对象注册表Registry的实例,必不可少(Java默认端口是1099)缺少注册表创建,则无法绑定对象到远程注册表上
//在端口8088创建注册表,在下一步,将创建的实例绑定到注册表
Registry registry = LocateRegistry.createRegistry(8088);
System.out.println("》》》》INFO:注册表创建成功");
//把远程对象注册到RMI注册服务器上,并命名为stuList
//绑定的URL标准格式为:rmi://host:port/name(其中协议名可以省略)
registry.bind("StudentList",stuList);
System.out.println("》》》》INFO:远程对象绑定成功");
} catch (RemoteException e) {
e.printStackTrace();
} catch (AlreadyBoundException e) {
e.printStackTrace();
}
}
}
创建客户端程序
客户端程序可以在其他任何一台主机上,只要它能够访问服务端程序的套接字。注意客户端和服务器只要在两个进程中运行,就属于远程调用,所以我建了两个工程,分别运行服务器和客户端程序。
必须实现安全管理器SecurityManager,否则无法从远程下载相应的类,这样还必须创建自己的策略文件,否则会抛出AccessControlException异常。
策略文件myPolice.police,最简单的就是赋予所有权限,并使用java -Djava.security.policy=myPolicy.policy:
grant{
permission java.security.AllPermission;
};
同样,服务器端也需要设置。
在客户端,首先从指定的IP地址和端口号获取远程服务器的注册表,从注册表中获取需要的远程对象引用,然后执行相应的操作,这里是注册两个学生的信息,然后显示。
public class Client {
public static void main(String[] args) {
if (System.getSecurityManager() == null)
System.setSecurityManager(new SecurityManager());
try {
//获取远程服务器的注册表
Registry registry = LocateRegistry.getRegistry("localhost", 8088);
//在RMI服务注册表中查找名称为RHello的对象,并调用其上的方法
IStudentList studentList = (IStudentList) registry.lookup("StudentList");
//调用远程对象
studentList.regionNewStudent("Tom",'m',25);
studentList.regionNewStudent("Jack",'w',28);
//在服务端显示信息,说明方法的实际调用是在服务端执行的
studentList.printStudentInfos();
//获取所有学生信息并显示
List<IStudent> students = studentList.getStudents();
for(IStudent stu : students)
System.out.println(stu.StudentInfo());
Remote sList = registry.lookup("StudentList");
System.out.println(sList.getClass());
} catch (RemoteException e) {
System.out.println("RemoteException");
e.printStackTrace();
} catch (NotBoundException e) {
System.out.println("NotBoundException");
e.printStackTrace();
}
}
}
运行结果
首先运行服务端程序,显示绑定成功:
接着运行客户端程序,在客户端注册两个学生的信息:
同时,服务器端也会显示学生信息: