远程代理
远程代理好比”远程对象的本地代表”。远程对象是一种对象,活在JVM堆中,本地代表是一种可以由本地方法调用的对象,其行为会转发到远程对象中。
客户对象调用本地堆中的代理对象上的方法,再由代理处理所有网络通信的低层细节。当调用完毕,结果值也通过网络从远程送回我们的客户。可以利用RMI实现远程调用。
远程方法
远程方法包括四个对象:客户对象、客户辅助对象(代理)、服务辅助对象、服务对象。客户对象以为他调用的是远程服务上的方法,因为客户辅助对象乔装成服务对象,假装自己有客户所要调用的方法。客户辅助对象会联系服务器,传送方法调用信息,然后等待服务器的返回。在服务器端,服务辅助对象从客户辅助对象中接收请求,将调用的信息解包,然后调用真正服务对象上的真正方法,所以,对于服务对象来说,调用是本地的,来自服务辅助对象,而不是远程客户。服务辅助对象从服务中得到返回值,将它打包,然后运回到客户辅助对象,客户辅助对象对信息解包,最后将返回值交给客户对象。
服务对象的方法才是真正做事的方法。
RMI
RMI提供了客户辅助对象和服务辅助对象,为客户辅助对象创建和服务对象相同的方法,RMI的好处在于你不必亲自写任何网络或I/O代码。客户程序调用远程方法就和在运行在客户自己的本地JVM上对对象进行正常方法调用一样。
RMI将客户辅助对象称为桩(stub),服务辅助对象称为骨架(skeleton)。
制作远程服务的步骤:
- 制作远程接口:远程接口定义出可以让客户远程调用的方法,客户将用它作为服务的类类型,Stub和实际的服务都实现此接口;
- 制作远程的实现:这是实际工作的类,为远程接口中定义的远程方法提供了真正的实现,这就是客户真正想要调用的对象;
- 利用rmic产生的stub和skeleton:这是客户和服务的辅助类,由命名行”rmic 类名”实现;
- 启动RMI registry:客户可以从中查到代理的位置,通过命令行”rmiregistry”开启;
- 开始远程服务:运行服务对象,然后运行客户对象即可调用远程方法。
下面是一个例子:
远程接口:
public interface MyRemote extends Remote {
public String sayHello()throws RemoteException;
}
远程服务类:
public class MyRemoteImpl extends UnicastRemoteObject implements MyRemote {
public MyRemoteImpl()throws RemoteException{}
@Override
public String sayHello() {
System.out.println("Hello");
return "Server says, 'Hey'";
}
public static void main(String[] args) {
try {
MyRemote service = new MyRemoteImpl();
Naming.rebind("RemoteHello",service);
}catch (Exception e){
e.printStackTrace();
}
}
}
客户类:
public class MyRemoteClient {
public static void main(String[] args) {
new MyRemoteClient().go();
}
public void go(){
try {
MyRemote service = (MyRemote) Naming.lookup( "rmi://127.0.0.1/RemoteHello");
String s = service.sayHello();
}catch (Exception e){
e.printStackTrace();
}
}
}
依次执行rmi命令行即可运行。
虚拟代理
虚拟代理作为创建开销大的对象的代表,虚拟代理经常直到我们真正需要一个对象的时候才创建他,当对象在创建前和创建中时,由虚拟代理来扮演对象的替身,对象创建后,代理就会将请求直接委托给对象。
利用虚拟代理完成一个CD封面浏览器:
在网上加载图片的时候,往往需要一段时间,在等待的时候,应该显示一些东西,一旦图像加载完毕,刚才显示的东西应该消失,显示图像。采用虚拟代,可以在加载未完成时显示文字,加载完成后,把职责委托给真正的Icon,如果用户请求新的图像,继续创建新的代理。下面是一个图像代理的实现:
public class ImageProxy implements Icon {
ImageIcon imageIcon;
URL imageURL;
Thread retrievalThread;
boolean retrieving = false;
public ImageProxy(URL url){
imageURL = url;
}
public int getIconWidth(){
if(imageIcon != null){
return imageIcon.getIconWidth();
}else {
return 800;
}
}
public int getIconHeight(){
if(imageIcon != null){
return imageIcon.getIconHeight();
}else {
return 600;
}
}
public void paintIcon(final Component c,Graphics g,int x, int y){
if(imageIcon != null){
imageIcon.paintIcon(c,g,x,y);
}
else {
g.drawString("Loading CD cover, please wait...", x+300, y+190);
if(!retrieving){
retrieving = true;
retrievalThread = new Thread(new Runnable() {
@Override
public void run() {
try{
imageIcon = new ImageIcon(imageURL,"CD Cover");
c.repaint();
}catch (Exception e){
e.printStackTrace();
}
}
});
retrievalThread.start();
}
}
}
}
保护代理
保护代理基于权限控制对资源的访问。
动态代理
Java的reflect包中有自己的代理支持,利用这个包可以动态地创建一个代理类,实现一个或多个接口,并将方法的调用转发到指定的类,这个技术称为动态代理。
动态代理需要提供一个InvocationHandler,该类实现invoke方法调用需要的方法,然后在Proxy类中选择调用InvocationHandler。通过动态代理可以选择是否调用方法,实现对象的保护访问。
一个保护代理的例子
保护代理是一种根据访问权限决定客户可否访问对象的代理。比方说,对一个对象来说,雇员可以调用其中一些方法,经理还可以调用更多的方法,他有更多权限。下面来看一个例子。
在一个约会服务系统中,有一个PersonBean存放一个人的所有信息,对个人来说,他可以设置自己的性趣,不可以设置是否喜欢自己,而其他人可以设置是否喜欢他,不可以设置他的性趣等个人信息。
这是PersonBean:
public class PersonBeanImpl implements PersonBean {
String name;
String gender;
String interests;
int rating;
int ratingCount = 0;
public PersonBeanImpl(String name, String gender, String interests, int rating, int ratingCount){
this.name = name;
this.gender = gender;
this.interests = interests;
this.rating = rating;
this.ratingCount = ratingCount;
}
@Override
public String getName() {
return name;
}
@Override
public String getGender() {
return gender;
}
@Override
public String getInterests() {
return interests;
}
@Override
public int getHotOrNotRating() {
if(ratingCount == 0)
return 0;
return (rating/ratingCount);
}
@Override
public void setName(String name) {
this.name = name;
}
@Override
public void setGender(String gender) {
this.gender = gender;
}
@Override
public void setHotOrNotRating(int rating) {
this.rating+= rating;
ratingCount++;
}
@Override
public void setInterests(String interests) {
this.interests = interests;
}
}
需要创建两个InvocationHandler,一个是自己,一个是其他人,实现各自代理的行为,这是一个自己InvocationHandler的例子:
public class OwnerInvocationHandler implements InvocationHandler {
PersonBean person;
public OwnerInvocationHandler(PersonBean person){
this.person = person;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException{
try {
if(method.getName().startsWith("get"))
return method.invoke(person, args);
else if(method.getName().equals("setHotOrNotRating"))
throw new IllegalAccessException();
else if(method.getName().startsWith("set"))
return method.invoke(person, args);
}catch (InvocationTargetException e){
e.printStackTrace();
}
return null;
}
}
这是代理的代码:
public class PersonProxy extends Proxy {
protected PersonProxy(InvocationHandler h) {
super(h);
}
public static PersonBean getOwnerProxy(PersonBean person){
return (PersonBean) Proxy.newProxyInstance(person.getClass().getClassLoader(),person.getClass().getInterfaces(),new OwnerInvocationHandler(person));
}
public static PersonBean getNonOwnerProxy(PersonBean person){
return (PersonBean) Proxy.newProxyInstance(person.getClass().getClassLoader(),
person.getClass().getInterfaces(),new NonOwnerInvocationHandler(person));
}
}
测试代码如下:
public class MatchMakingTest {
public static void main(String[] args) {
MatchMakingTest test = new MatchMakingTest();
test.drive();
}
public void drive(){
PersonBean joe = new PersonBeanImpl("Joe","male","play game",0,0);
PersonBean ownerProxy = PersonProxy.getOwnerProxy(joe);
System.out.println("Name is "+ownerProxy.getName());
ownerProxy.setInterests("bowling, Go");
System.out.println("Interests set from owner proxy");
try {
ownerProxy.setHotOrNotRating(10);
}catch (Exception e){
System.out.println("Can't set rating from owner proxy");
}
System.out.println("Rating is "+ownerProxy.getHotOrNotRating());
PersonBean nonOwnerProxy = PersonProxy.getNonOwnerProxy(joe);
System.out.println("Name is "+nonOwnerProxy.getName());
try {
nonOwnerProxy.setInterests("watch TV");
}catch (Exception e){
System.out.println("Can't set interests from non owner proxy");
}
nonOwnerProxy.setHotOrNotRating(3);
System.out.println("Rating set from non owner proxy");
System.out.println("Rating is "+nonOwnerProxy.getHotOrNotRating());
}
}
从结果可以看出已经分开权限了,保护代理类似于经纪人,起到中间调节者的作用。
总结
代理模式的正式定义:
代理模式为另一个对象提供一个替身或占位符以控制对这个对象的访问。
使用代理模式创建代表对象,让代表控制某对象的访问。
其它的代理模式
- 防火墙代理:控制网络资源的访问,保护主题免于坏客户的侵害;
- 智能引用代理:当主题被引用时,进行额外的动作;
- 缓存代理:为开销大的运算结果提供暂时存储,他也允许多个客户共享结果,以减少计算或网络延迟;
- 同步代理:在多线程的情况下为主题提供安全的访问;
- 复杂隐藏代理:用来隐藏一个类的复杂集合的复杂度,并进行访问控制,有时也称为外观代理;
- 写入时复制代理:用来控制对象的复制,方法是延迟对象的复制,直到客户真的需要为止,这是虚拟代理的变体。