代理就是帮你做事情的对象,为啥要委托它帮你做?因为做这件事太复杂,有一些你不需要了解的细节,所以将它委托给一个专门的对象来处理。
就好比现实生活中的签证代理,各国办签证的需要的材料和流程不尽相同,有一些及其复杂。所以委托给了解这些细节的签证代理帮我们处理(毕竟还有一大推bug等着我们)。
本地 & 远程的概念
java 中,远程和本地划分的标准是:“它们是否运行在同一个内存堆中”。
- 一台计算机上的应用通过网络调用另一台计算机应用的方法叫做远程调用,因为两个应用程序运行在不同计算机的内存堆中。
- Android 系统中,每个应用运行在各自的进程中,每个进程有独立的虚拟机,所以它们运行在同一台计算机内存的不同堆中,跨进程的调用也称为远程调用。
代理分三种:
1.远程代理,帮助我们控制访问远程对象:
远程代理可以作为另一个JVM上对象的本地代表。调用代理的方法,会被代理利用网络转发到远程执行,并且结果会通过网络返回给代理,再由代理将结果转给客户。
2.虚拟代理,帮助我们控制访问创建开销大的资源
虚拟代理作为创建开销大的对象的代表,经常会直到我们真正需要一个对象的时候才创建它。当对象在创建前和创建中时,由虚拟代理地来扮演对象的替身。对象创建后,代理就会将请求直接委托给对象。
3.保护代理,基于权限控制对资源的访问。
本篇讲一讲远程代理:
代理做的事情就是控制和管理访问。
所谓的代理,就是代表某个真实的对象。其实幕后是代理利用网络和一个远程的真正对象沟通。这个代理假装它是真正的对象,但是其实一切的动作是代理对象利用网络和真正的对象沟通。
代理之所以需要控制访问,是因为我们的客户不知道如何和远程对象沟通。从某个方面来看,远程代理控制访问,可以帮我们处理网络上的细节。
远程代理
所谓“远程代理”就好比“远程对象的本地代表”。
所谓“远程对象”就是一种对象,活在不同的java虚拟机(JVM)堆中。更一般的说法是在不同的地址空间运行的远程对象。
所谓本地代表,就是一种可以由本地方法调用的对象,其行为会转发到远程对象中。
所以当客户对象调用代理对象,就像是在做远程方法调用,其实只是调用本地堆中的”代理“对象上的方法,再由代理处理所有网络通信的低层细节。Java已经有内置远程调用的功能了,可以帮助我们实现远程代理。
变量只能引用和当前代码语句在同一堆空间中的对象。
JAVA RMI
RMI提供了客户辅助对象(Stub)和服务辅助对象(Skeleton),为客户辅助对象创建和服务对象相同的方法。RMI的好处在于你不必亲自写任何网络或I/O代码。客户程序调用远程方法(即真正的服务所在)就和在运行在客户自己的本地JVM上对对象进行正常方法调用一样。
下面开始写服务端代码:
第一步:制作远程接口
远程接口定义出可以让客户远程调用的方法。客户将用它作为服务的类类型。Stub 和实际的服务都实现此接口。
1、Remote是一个“记号”接口,所以Remote不具有方法。对于RMI来说,Remote接口具有特别的意义,所以我们必须遵守规则
2、声明所有的方法都会抛出RemoteException,客户使用远程接口调用服务。换句话说,客户会调用实现远程接口的Stub上的方法,而Stub底层用到了网络和I/O,所以各种坏事情都可能会发生。客户必须认识到此风险,通过处理或声明远程异常来解决。如果接口的方法声明了异常,任何在接口类型的引用上调用方法的代码也必须处理或声明异常。
3、确定变量和返回值是属于原语(primitive)类型或者可序列化(Serializable)类型,关键字transient:告诉JVM不要序列化这个字段。远程方法的变量和返回值,必须属于原语类型或Serializable类型。远程方法的变量必须被打包并通过网络运送,这要靠序列化来完成。如果你使用原语类型、字符串和许多API中内定的类型(包括数组和集合),都不会有问题。如果你传送自己定义的类,就必须保证你的类实现了Serializable接口
import java.rmi.Remote;
import java.rmi.RemoteException;
/**
* 定义远程接口
*/
public interface MyRemote extends Remote
{
public String sayHello() throws RemoteException;
}
第二步:制作远程实现
这是做实际工作的类,为远程接口中定义的远程方法提供了真正的实现。这就是客户真正想要调用的方法的对象
(1)实现远程接口,也就是客户将要调用的方法的接口。
(2)扩展UnicastRemoteObject
为了要成为远程服务对象,你的对象需要某些“远程的”功能。最简单的方式是扩展java.rmi.server.UnicastRemoteObject,让 超类帮你做这些工作。
(3)设计一个不带变量的构造器,并声明RemoteException
超类UnicastRemoteObject会带来一个小问题:它的构造器会抛出RemoteException。唯一的解决方法就是为你的远程实现声 明一个构造器,然后抛出RemoteException。当类被实例化的时候,超类的构造器总是会被调用。如果超类的构造器抛出异 常,那么你只能声明子类的构造器也抛出异常。
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
/**
* 实现远程接口,为客户端提供真正的服务
*/
public class MyRemoteImpl extends UnicastRemoteObject implements MyRemote
{
protected MyRemoteImpl() throws RemoteException
{
}
@Override
public String sayHello()
{
return "Server say helloeeee";
}
}
第三步:利用rmic产生的stub和 skeleton
这就是客户和服务的辅助类。你不需要自己创建这些类,也不用理会它们的代码是什么。当你运行rmic工具时,这都会自动创建。rmic工具是JDK内的一个工具,用来为一个服务类产生stub和skeleton。命名习惯是在远程实现的名字后面加上_Stub或_Skel。rmic有一些选项可以调整,包括要不要产生skeleton、查看源代码,甚至使用IIOP作为协议。
使用rmic 的方式:将类产生在当前目录下,请注意,rmic必须看到你的实现类,所以你可能会从你的远程实现所在的目录执行rmic。
cd到放置远程实现的目录,然后运行rmic MyRemoteImpl,也可以用package的完整路径,后面是类名,注意不带后缀.class。
第四步:启动RMI registry
rmiregistry就像是电话簿,客户可以从中查到代理的位置,也就是客户的stub helper对象。
开启一个终端,启动rmiregistry。先确保启动目录可以访问到我们的类。最简单的方法就是从"classes"目录启动。
第五步:开始远程服务
你必须让服务对象开始运行。实例化一个服务的实例,并将这个服务注册到RMI registry,注册之后,这个服务就可以供客户调用了。
我们从Test中的main()方法启动的,main方法会先实例化一个服务对象,然后到RMI registry中注册
import java.rmi.Naming;
public class Test
{
public static void main(String[] args)
{
try
{
// 创建远程服务对象
MyRemote service = new MyRemoteImpl();
// 绑定远程服务对象到 rmiregistry
Naming.bind("RemoteServer",service);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
下面是客户端代码:
第一步:将刚才创建的远程接口复制过来
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface MyRemote extends Remote
{
public String sayHello() throws RemoteException;
}
第二部:编写客户端,访问服务端代码
import java.rmi.Naming;
public class MyReomteClient
{
public static void main(String[] args)
{
new MyReomteClient().go();
}
public void go()
{
try {
MyRemote service = (MyRemote) Naming.lookup("rmi://127.0.0.1/RemoteServer");
String s = service.sayHello();
System.out.println(s);
} catch (Exception e) {
e.printStackTrace();
}
}
}
第三步、将刚才服务端生成的stu复制到class文件夹下
第四步、运行客户端,就可以开始访问了。