远程代理:最经典的代理模式之一,远程代理负责与远程JVM通信,以实现本地调用者与远程被调用者之间的正常交互
有些事情必须得依靠代理来完成,比如要调用另一台机器上的一个方法,我们可能就不得不用代理
远程代理的内部机制是这样的:
解释一下,Stub是“桩”也有人称之为“存根”,代表客服服务对象,也就是所谓的代理,代表了Server对象;
Skeleton是“骨架”,是服务的扶助对象。Stub和Skeleton负责通信,类似于用Socket编写的聊天程序
##步骤:
1、制作远程接口
- 扩展java.rmi.Remote
表示此接口用来支持远程调用 - 声明所有的方法都会抛出RemoteException
因为每次远程调用都是有风险的,所以客户在实现方法的时候要处理异常。 - 确定变量和返回值是属于原语类型或者可序列化类型
也就是说远程方法的变量或者返回值需要io传送,必须是原语或者Serializable类型
packageProxyPattern;
import java.rmi.RemoteException;
/**
* 定义服务接口(扩展自java.rmi.Remote接口)
* @author ayqy
*/
publicinterfaceServiceextends java.rmi.Remote{
/* 1.方法返回类型必须是可序列化的Serializable
* 2.每一个方法都要声明异常throws RemoteException(因为是RMI方式)
* */
/**
* @return 完整的问候语句
* @throws RemoteException
*/
publicString greet(String name)throwsRemoteException;
}
2、制作远程实现(也就是所谓的服务器端的代码)
- 实现远程接口
服务端和客户端都要实现这个接口,保证客户调用正确方法 - 扩展UnicastRemoteObject
要保证你的类具有远程功能就继承该类,让超类帮你做这些工作 - 设计一个不带变量的构造器,并声明RemoteException
超类的问题就是构造器会抛出异常,所以子类需要声明构造器也抛出异常。 - 用RMI Registry注册此服务
为你的服务命名,好让客户在注册表中按照此名字找到,在绑定服务对象的时候,RMI会把服务换成stub,stub放在register中。
packageProxyPattern;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
/**
* 实现远程服务(扩展自UnicastRemoteObject并实现自定义远程接口)
* @author ayqy
*/
publicclassMyServiceextendsUnicastRemoteObjectimplementsService{
/**
* 用来校验程序版本(接收端在反序列化是会验证UID,不符则引发异常)
*/
privatestaticfinallong serialVersionUID =1L;
/**
* 空的构造方法,只是为了声明异常(默认的构造方法不会声明异常)
* @throws RemoteException
*/
protectedMyService()throwsRemoteException{
}
@Override
publicString greet(String name)throwsRemoteException{
return"Hey, "+ name;
}
}
服务端有了服务还不够,我们需要一个Server帮助我们启动RMI注册服务,并注册远程对象,供客户端调用:
packageProxyPattern;
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
/**
* 实现服务器类,负责开启服务并注册服务对象
* @author ayqy
*/
publicclassServer{
publicstaticvoid main(String[] args){
try{
//启动RMI注册服务,指定端口为1099 (1099为默认端口)
LocateRegistry.createRegistry(1099);
//创建服务对象
MyService service =newMyService();
//把service注册到RMI注册服务器上,命名为MyService
Naming.rebind("MyService", service);
}catch(RemoteException e){
// TODO Auto-generated catch block
e.printStackTrace();
}catch(MalformedURLException e){
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
3、产生Stub和Skeleton
- 在远程实现类(不是远程接口)上执行rmic
rmic是JDK的工具,主要就是来产生stub和skel两个类的,注意此时这两个类都会以_stub和_skel后缀出现,但stub是需要到客户端,所以等会我们看一下这个类怎么传送到客户端。
直接在CMD中执行
%rmic MyService
4、执行remiregistry
- 开启一个终端,启动rmiregistry
也就是启动注册表,但你要保证可以访问你的类,所以在classes目录下启动比较好
%rmiregistry
5、启动服务
开启一个终端,启动服务
从main中启动,实例化了服务对象并在RMI register中注册
%java Server
6、制作客户端测试
客户端只有拿到了这个stub以后就可以操纵这个服务了
- 客户到RMI registry中寻找
//如果要从另一台启动了RMI注册服务的机器上查找MyService对象,修改IP地址即可
Service service =(Service)Naming.lookup("//127.0.0.1:1099/MyService");
- RMI register返回stub对象, stub再返回的时候回被反序列化的,但是首先保证你要有stub类的,这个是因为你实现了同一个接口,所以rmic会自动在客户端产生stub类的。
- 客户调用stub的方法,就像stub就是真正的服务对象一样
packageProxyPattern;
/* 参考资料:
* 1.JAVA RMI怎么用
* http://blog.csdn.net/afterrain/article/details/1819659
* 2.RMI内部原理
* http://www.cnblogs.com/yin-jingyu/archive/2012/06/14/2549361.html
* */
import java.rmi.Naming;
/**
* 实现客户类
* @author ayqy
*/
publicclassClient{
/**
* 查找远程对象并调用远程方法
*/
publicstaticvoid main(String[] argv)
{
try
{
//如果要从另一台启动了RMI注册服务的机器上查找MyService对象,修改IP地址即可
Service service =(Service)Naming.lookup("//127.0.0.1:1099/MyService");
//调用远程方法
System.out.println(service.greet("SmileStone"));
}
catch(Exception e)
{
System.out.println("Client exception: "+ e);
}
}
}
总结
拦截并控制方法调用(这也是代理模式最大的特点,最典型的,防火墙代理。。) 远程对象的存在对客户是透明的(客户完全把Stub代理对象当做远程对象了,虽然客户有点好奇为什么可能会出现异常。。) 远程代理隐藏了通信细节
当我们需要调用另一台机器(JVM)上指定对象的方法时,使用远程代理是一个不错的选择。。