代理模式之远程代理

远程代理:最经典的代理模式之一,远程代理负责与远程JVM通信,以实现本地调用者与远程被调用者之间的正常交互

有些事情必须得依靠代理来完成,比如要调用另一台机器上的一个方法,我们可能就不得不用代理
远程代理的内部机制是这样的:

解释一下,Stub是“桩”也有人称之为“存根”,代表客服服务对象,也就是所谓的代理,代表了Server对象;
Skeleton是“骨架”,是服务的扶助对象。Stub和Skeleton负责通信,类似于用Socket编写的聊天程序

##步骤:

1、制作远程接口

  • 扩展java.rmi.Remote
    表示此接口用来支持远程调用
  • 声明所有的方法都会抛出RemoteException
    因为每次远程调用都是有风险的,所以客户在实现方法的时候要处理异常。
  • 确定变量和返回值是属于原语类型或者可序列化类型
    也就是说远程方法的变量或者返回值需要io传送,必须是原语或者Serializable类型
  1. packageProxyPattern;
  2. import java.rmi.RemoteException;
  3. /**
  4. * 定义服务接口(扩展自java.rmi.Remote接口)
  5. * @author ayqy
  6. */
  7. publicinterfaceServiceextends java.rmi.Remote{
  8. /* 1.方法返回类型必须是可序列化的Serializable
  9. * 2.每一个方法都要声明异常throws RemoteException(因为是RMI方式)
  10. * */
  11. /**
  12. * @return 完整的问候语句
  13. * @throws RemoteException
  14. */
  15. publicString greet(String name)throwsRemoteException;
  16. }

2、制作远程实现(也就是所谓的服务器端的代码)

  • 实现远程接口
    服务端和客户端都要实现这个接口,保证客户调用正确方法
  • 扩展UnicastRemoteObject
    要保证你的类具有远程功能就继承该类,让超类帮你做这些工作
  • 设计一个不带变量的构造器,并声明RemoteException
    超类的问题就是构造器会抛出异常,所以子类需要声明构造器也抛出异常。
  • 用RMI Registry注册此服务
    为你的服务命名,好让客户在注册表中按照此名字找到,在绑定服务对象的时候,RMI会把服务换成stub,stub放在register中。
  1. packageProxyPattern;
  2. import java.rmi.RemoteException;
  3. import java.rmi.server.UnicastRemoteObject;
  4. /**
  5. * 实现远程服务(扩展自UnicastRemoteObject并实现自定义远程接口)
  6. * @author ayqy
  7. */
  8. publicclassMyServiceextendsUnicastRemoteObjectimplementsService{
  9. /**
  10. * 用来校验程序版本(接收端在反序列化是会验证UID,不符则引发异常)
  11. */
  12. privatestaticfinallong serialVersionUID =1L;
  13. /**
  14. * 空的构造方法,只是为了声明异常(默认的构造方法不会声明异常)
  15. * @throws RemoteException
  16. */
  17. protectedMyService()throwsRemoteException{
  18. }
  19. @Override
  20. publicString greet(String name)throwsRemoteException{
  21. return"Hey, "+ name;
  22. }
  23. }

服务端有了服务还不够,我们需要一个Server帮助我们启动RMI注册服务,并注册远程对象,供客户端调用:

  1. packageProxyPattern;
  2. import java.net.MalformedURLException;
  3. import java.rmi.Naming;
  4. import java.rmi.RemoteException;
  5. import java.rmi.registry.LocateRegistry;
  6. /**
  7. * 实现服务器类,负责开启服务并注册服务对象
  8. * @author ayqy
  9. */
  10. publicclassServer{
  11. publicstaticvoid main(String[] args){
  12. try{
  13. //启动RMI注册服务,指定端口为1099 (1099为默认端口)
  14. LocateRegistry.createRegistry(1099);
  15. //创建服务对象
  16. MyService service =newMyService();
  17. //把service注册到RMI注册服务器上,命名为MyService
  18. Naming.rebind("MyService", service);
  19. }catch(RemoteException e){
  20. // TODO Auto-generated catch block
  21. e.printStackTrace();
  22. }catch(MalformedURLException e){
  23. // TODO Auto-generated catch block
  24. e.printStackTrace();
  25. }
  26. }
  27. }

3、产生Stub和Skeleton

  • 在远程实现类(不是远程接口)上执行rmic
    rmic是JDK的工具,主要就是来产生stub和skel两个类的,注意此时这两个类都会以_stub和_skel后缀出现,但stub是需要到客户端,所以等会我们看一下这个类怎么传送到客户端。
    直接在CMD中执行
  1. %rmic MyService

4、执行remiregistry

  • 开启一个终端,启动rmiregistry
    也就是启动注册表,但你要保证可以访问你的类,所以在classes目录下启动比较好
  1. %rmiregistry

5、启动服务

开启一个终端,启动服务
从main中启动,实例化了服务对象并在RMI register中注册

  1. %java Server

6、制作客户端测试

客户端只有拿到了这个stub以后就可以操纵这个服务了

  • 客户到RMI registry中寻找
  1. //如果要从另一台启动了RMI注册服务的机器上查找MyService对象,修改IP地址即可
  2. Service service =(Service)Naming.lookup("//127.0.0.1:1099/MyService");
  • RMI register返回stub对象, stub再返回的时候回被反序列化的,但是首先保证你要有stub类的,这个是因为你实现了同一个接口,所以rmic会自动在客户端产生stub类的。
  • 客户调用stub的方法,就像stub就是真正的服务对象一样
  1. packageProxyPattern;
  2. /* 参考资料:
  3. * 1.JAVA RMI怎么用
  4. * http://blog.csdn.net/afterrain/article/details/1819659
  5. * 2.RMI内部原理
  6. * http://www.cnblogs.com/yin-jingyu/archive/2012/06/14/2549361.html
  7. * */
  8. import java.rmi.Naming;
  9. /**
  10. * 实现客户类
  11. * @author ayqy
  12. */
  13. publicclassClient{
  14. /**
  15. * 查找远程对象并调用远程方法
  16. */
  17. publicstaticvoid main(String[] argv)
  18. {
  19. try
  20. {
  21. //如果要从另一台启动了RMI注册服务的机器上查找MyService对象,修改IP地址即可
  22. Service service =(Service)Naming.lookup("//127.0.0.1:1099/MyService");
  23. //调用远程方法
  24. System.out.println(service.greet("SmileStone"));
  25. }
  26. catch(Exception e)
  27. {
  28. System.out.println("Client exception: "+ e);
  29. }
  30. }
  31. }

总结

拦截并控制方法调用(这也是代理模式最大的特点,最典型的,防火墙代理。。) 远程对象的存在对客户是透明的(客户完全把Stub代理对象当做远程对象了,虽然客户有点好奇为什么可能会出现异常。。) 远程代理隐藏了通信细节
当我们需要调用另一台机器(JVM)上指定对象的方法时,使用远程代理是一个不错的选择。。





原文地址:https://www.cnblogs.com/oneNightStand/p/28864710b610d4639970ea5f113779cf.html