设计模式 代理模式-java RMI远程代理

本文详细介绍了如何使用代理模式结合Java RMI(远程方法调用)来控制对远程对象的访问。代理模式用于创建代表,以控制对某个对象的访问,而RMI则简化了远程方法调用的过程,使得客户端调用远程方法就像调用本地方法一样。文章通过具体的步骤指导如何创建远程服务接口、实现服务端和客户端,包括生成stub和注册远程服务,并提供了实现代码案例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


代理模式: 为另一个对象提供一个替身或者占位符以控制对这个对象的访问。

使用代理模式创建代表,让代表对象控制某对象的访问,被代理的对象可以是远程的对象、创建开销大的对象或需要安全控制的对象。

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;
}


3. 服务端的实现类 MyRemoteImpl

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值