代理模式: 为另一个对象提供一个替身或者占位符以控制对这个对象的访问。
使用代理模式创建代表,让代表对象控制某对象的访问,被代理的对象可以是远程的对象、创建开销大的对象或需要安全控制的对象。
RMI 全称Remote Method Invocation,远程方法调用 ,rmiregistry含义:远程方法调用注册。
RMI提供了客户辅助对象和服务辅助对象,给客户辅助对象创建的方法和服务对象的方法相同。
RMI好处是不需要自己写网络或I/O代码。客户端调用远程方法如同在本地JVM调用自己的方法一样。 并提供所有运行时的基础设施,包括如使用Naming找到服务lookup service、rebind、bind服务,这个服务用来寻找和访问远程对象。
下图是RMI的结构图:
注意:stub表示桩 ,skeleton表示骨架,新版的java不需要显示的skeleton.
方法调用的过程:远程服务接口定义的方法是:doSomething(),
客户端是如何获取到stub对象?
客户端从Registry远程服务注册表找到Naming.lookup(“rmi://地址/远程绑定的服务名”)获取该服务名的代理客户辅助对象 stub,然后可以通过代理对象调用远程服务的方法。
制作远程服务的步骤:
步骤一: 制作服务端的远程接口 ,MyRemote.java
步骤二: 实现服务端的远程接口 , MyRemoteImpl.java
实现的过程需要注意以下:
1. 类应该继承UnicastRemoteObject,使之成为远程服务对象
public class MyRemoteImpl extends UnicastRemoteObject implements MyRemote
2. 默认构造器需要抛出 RemoteException异常 因为父类构造会抛,子类构造也要抛
3. 在实现接口方法时,如果涉及到的数据类型不是 primitive或可序列化Serializable的,需要在自定义的数据类型类实现Iimplements Serializable 接口
因为基本上是用于网络I/O等,必须可序列化传输。
步骤三: 利用rmic产生stub和skeleton (java新版不需要此操作,可以跳过,猜测应该内置可生成),执行:rmic MyRemoteImpl.java
这就是结构图上的客户和服务辅助类,是由工具产生的。生成: MyRemoteImpl_Stub.class MyRemoteImpl_Skel.class
步骤四: 启动 RMI registry服务
打开终端执行 :rmiregistry命令;即在服务端启动RMI服务,用来下一步的注册。
步骤五: 开启远程服务
这个开启肯定是在main(),至于这个main()的类,可以是在远程服务实现类,也可以是自定义的另一个类。
在main()方法中,我们可以用RMI Registry注册实现的远程服务 MyRemoteImpl。
// 用RMI Registry注册远程实现接口的服务,这样客户端就可以通过名称查询注册表获取客户端辅助对象stub,并可以调用远程服务的方法
try{
MyRemote service = new MyRemoteImpl();
//LocateRegistry.createRegistry(1099); // 指定运行rmiregistry时的端口
//Naming.bind("rmi://localhost:1099/RemoteHello", service);
Naming.rebind("RemoteHello", service);
}catch(Exception e){
System.out.println("Trouble: " + e);
}
-----------------------------------------------------------------------------------------------------------
客户端的实现:
客户端要获取stub对象:
//MyRemote service = (MyRemote) Naming.lookup("rmi://localhost:1099/RemoteHello");
MyRemote service = (MyRemote) Naming.lookup("rmi://localhost/RemoteHello");
然后就可以使用该service调用远程的方法了。
----------------------------------------------------------------------------------------------------------------------
实现代码案例:
第一部分:服务端的整体实现
1. 服务端用于传输给客户端的自定义数据类型User
import java.io.Serializable;
// 远程传输一般数据结构类型,需要序列化,涉及到网络IO等
public class User implements Serializable{
public String toString() {
return "User [name=" + name + ", age=" + age + "]";
}
private String name;
private int age;
public User(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
}
2. 服务端的自定义接口 MyRemote,注意需要实现Remote
import java.rmi.Remote;
import java.rmi.RemoteException;
//第一步. 制作远程接口(定义了可让客户远程调用的方法)
public interface MyRemote extends Remote{
public int addResult(int a, int b) throws RemoteException;
public String getString() throws RemoteException;
public User getUser(int i) throws RemoteException;
}
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
// 第二步. 实现远程接口,扩展UnicastRemoteObject使之成为远程服务对象
public class MyRemoteImpl extends UnicastRemoteObject implements MyRemote {
private static final long serialVersionUID = -4451128798823167932L;
// 由于父类构造会抛出异常,因此子类构造需要抛出异常
protected MyRemoteImpl() throws RemoteException {
super();
// TODO Auto-generated constructor stub
}
public int addResult(int a, int b) throws RemoteException {
// TODO Auto-generated method stub
return a+b;
}
public String getString() throws RemoteException {
// TODO Auto-generated method stub
return "Server says, Hey";
}
public User getUser(int i) throws RemoteException {
// TODO Auto-generated method stub
return new User("MM",18);
}
}
// 第三步. 在远程实现类执行rmic,产生STub和Skeleton: rmic MyRemoyeImpl
// 第四步. 开启一个终端,执行rmiregistry
// 第五步. 启动另一终端,启动服务。(可能是远程实现类的main()方法,也可能是另一个独立的启动类)
4. 启动服务端主程序main()入口类 ServerMain
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;
public class ServerMain {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
new ServerMain().runService();
}
public void runService(){
// 用RMI Registry注册服务,在主程序中运行远程服务
try{
System.out.println("ServerMain start");
MyRemote service = new MyRemoteImpl();
//LocateRegistry.createRegistry(1099); // 指定运行rmiregistry时的端口
//Naming.bind("rmi://localhost:1099/RemoteHello", service);
Naming.rebind("RemoteHello", service);
System.out.println("ServerMain: end ");
}catch(Exception e){
System.out.println("Trouble: " + e);
}
}
}
第二部分: 客户端的实现
import java.rmi.Naming;
public class MyRemoteClient {
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println(" MyRemoteClient main start.. " );
new MyRemoteClient().runClient();
}
public void runClient(){
System.out.println(" MyRemoteClient runClient start.. " );
try{
//MyRemote service = (MyRemote) Naming.lookup("rmi://localhost:1099/RemoteHello");
MyRemote service = (MyRemote) Naming.lookup("rmi://localhost/RemoteHello");
String string = service.getString();
int result = service.addResult(10, 12);
User user = service.getUser(0);
System.out.println(" receive from service: string :"+string +" add result:"+result+" user:"+user);
}catch(Exception e){
System.out.println(" MyRemoteClient .. " +e);
}
}
}
---------------
在Eclipse允许结果:
先执行服务端:ServerMain
ServerMain start
ServerMain: end
执行客户端: MyRemoteClient
MyRemoteClient main start..
MyRemoteClient runClient start..
receive from service: string :Server says, Hey add result:22 user:User [name=MM, age=18]
------------------
最后引用一下:https://www.cnblogs.com/dongguacai/p/5617698.html
的RMI示意图及说明:
RMI注册表:通过JNDI发布RMI服务
1、要访问服务器上的一个远程对象时,客户端必须先得到一个本地的存根对象,也就是客户端机器上的代理对象。那么问题来了,如何才能得到这个存根呢?
2、为此,JDK提供了自举注册服务(bootstrap registry service),服务器程序应该使用自举注册服务来注册至少一个远程对象。
3、而要注册一个远程对象,需要一个RMI URL和一个对实现对象的引用。
4、RMI 的URL以rmi:开头,后接域名或IP地址(host),紧接着是端口号(port),最后是服务名(service)。
如:rmi://regserver.mycompany.cmo:99/central_warehouse
如果我们是在本地发布RMI服务,那么host就是“localhost”,此外RMI默认的端口号是“1099”,当然我们也可以自行设置,只要不与其他端口重复即可。 service实际上是基于同一个host与port下唯一的服务名。
1、借助JNDI这个所谓的命名与目录服务,我们成功地发布并调用了RMI服务。实际上,JNDI就是一个注册表,服务端将服务对象放入到注册表中,客户端从注册表中获取服务对象。
2、在服务端我们发布了RMI服务,并在JNDI中进行了注册,此时就在服务端创建了一个Skeleton(骨架),当客户端第一次成功连接JNDI并获取远程服务对象后,立马在本地创建了一个Stub(存根)。
3、远程通信实际是通过Skeleton与Stub来完成的,数据是基于TCP/IP协议,在“传输层”上发送的。
4、毋庸置疑,理论上RMI一定比WebService要快,毕竟WebService是基于http协议的,而http所携带的数据是通过“应用层”来传输的。传输层较应用层更为底层,越底层越快。
RMI的局限性
1、只能实现JAVA系统之间的调用,而WebService可以实现跨语言实现系统之间的调用。
2、RMI使用了JAVA默认的序列化方式,对于性能要求比较高的系统,可能需要其他的序列化方案来解决。
3、RMI服务在运行时难免会存在故障,例如,如果RMI服务无法连接了,就会导致客户端无法响应的现象
部分引用: https://www.cnblogs.com/dongguacai/p/5617698.html