设计模式之代理模式

什么是代理模式

“代理”,为一个对象提供一个替身或者占位符以访问这个对象。关系示意图如下:
在这里插入图片描述
当我希望调用被代理者的某个方法时,需要经过代理这个中间环节,它会决定被代理者的哪些方法可以被调用,哪些方法,不能被调用,或者为被代理者加上一些别的行为。

一、远程调用(RMI)

当需要引用一个对象,而这个对象处于另一个虚拟机上,这时就需要使用远程调用,远程调用的思想就是通过RMI通信获取远程对象的信息,在本地虚拟机上创建一个远程对象的代理对象,代替它实现一些方法,反馈一些信息。RMI和Socket原理类似,只是基于不同的通信协议,实现不同的功能,RMI主要用于远程对象的方法调用,使用简单,不需要传输很多信息,只求调用的方便,而Socket通道更多应用于远程大量数据的可靠传输。
回归正题,继续聊RMI,被代理者(服务器)实现远程接口:Remote接口,并实现需要被远程调用的方法,然后注册名称在远程对象上:MyRemote service=new MyRemoteImpl();Naming.rebind(“RemoteHello”,service);
然后,客户端通过MyRemote service=(MyRemote)Naming.lookup(“rmi://127.0.0.1/RemoteHello”);然后就可以通过service对象调用远程对象的方法了。
底层原理及基于字节的数据包通信,以及对象的序列化与反序列化的过程。在这整个过程中需要两个代理,一个是在服务器端的客户端代理,一个是在客户端的服务器代理,这两个代理实现了远程对象的调用,掩盖了对象在远程的事实。

二、避免加载大对象的GUI挂起

原理:当需要加载一张比较大的图片时(从网上获取资源),主线程是阻塞的,这时就会导致GUI图形界面挂起,无法响应任何操作。这个问题的解决思路就是创建一个图片的代理,在加载图片的过程中先在屏幕上显示等待加载的提示符,把加载操作放在线程中执行,加载完之后,代理让出显示的权利,让图片显示在界面上。这部分我通过代码实现了一遍,如下:
图片代理类

public class ImageProxy implements Icon{
    ImageIcon imageIcon;
    URL imageURL;
    boolean retrieving=false;    
    /**
     * 构造方法,传入一个地址
     * @param url
     */
    public ImageProxy(URL url){
    	imageURL=url;
    }    
    /**
     * 画图
     */
	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............", x+300, y+190);
			if(!retrieving){
				retrieving=true;
				new Thread(){
					public void run(){
						try{
							imageIcon=new ImageIcon(imageURL,"CD Cover");
							c.repaint();
						}catch(Exception e){
							e.printStackTrace();
						}
					}
				}.start();
			}
		}		
	}
	/**
	 * 获取宽
	 */
	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 class ImageComponent extends JComponent{
   private Icon icon;
	public ImageComponent(Icon icon) {
		this.icon=icon;
	}   
    public void setIcon(Icon icon){
    	this.icon=icon;
    }	
	public void paintComponent(Graphics g){
		super.paintComponent(g);
		int w=icon.getIconWidth();
		int h=icon.getIconHeight();
		int x=(800-w)/2;
		int y=(600-h)/2;
		icon.paintIcon(this, g, x, y);
	}	
}

测试类

public class ImageProxyTestDrive {
       ImageComponent imageComponent;
       JFrame frame;
       JMenuBar menuBar;
       JMenu menu;
       Hashtable cds=new Hashtable();       
       public static void main(String[] args)throws Exception{
    	   ImageProxyTestDrive testDrive=new ImageProxyTestDrive();
       }       
       public ImageProxyTestDrive()throws Exception{
    	   cds.put("picture", "http://images.amazon.com/images/P/B000003S2K.01.LZZZZZZZ.jpg");
    	   cds.put("picture1", "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1548705461289&di=c8bce9258b61574c9dcf0f7949659470&imgtype=0&src=http%3A%2F%2Fimg15.3lian.com%2F2015%2Ff2%2F57%2Fd%2F93.jpg");
    	   cds.put("picture2", "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1548705461832&di=c16d6bd46460b06baee886395b318dec&imgtype=0&src=http%3A%2F%2Fpic33.photophoto.cn%2F20141101%2F0034034426605241_b.jpg");
    	   cds.put("picture3", "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1548705461833&di=04e28a8c36ff98458218c43b95b15f11&imgtype=0&src=http%3A%2F%2Fpic32.photophoto.cn%2F20140915%2F0034034424700073_b.jpg");
    	   cds.put("picture4", "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1548705461833&di=c0d4b36f00832fe7e25f059299417054&imgtype=0&src=http%3A%2F%2Fpic35.photophoto.cn%2F20150402%2F0011024033183316_b.jpg");    	   
    	   URL url=new URL((String)cds.get("picture1"));
    	   frame=new JFrame("picture Viewer");
    	   menuBar=new JMenuBar();
    	   menu=new JMenu("Favourite picture");
    	   menuBar.add(menu);
    	   frame.setJMenuBar(menuBar);
    	   Iterator<String> iterator=cds.keySet().iterator();
    	   while(iterator.hasNext()){
    		   JMenuItem jMenuItem=new JMenuItem(iterator.next());
    		   menu.add(jMenuItem);
    		   jMenuItem.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
                      imageComponent.setIcon(new ImageProxy(getCDUrl(e.getActionCommand())));
                      frame.repaint();
				}
			});
    	   }
    	   Icon icon=new ImageProxy(url);//图标对象
    	   imageComponent=new ImageComponent(icon);
    	   frame.getContentPane().add(imageComponent);
    	   frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    	   frame.setLocationRelativeTo(null);
    	   frame.setSize(800, 600);
    	   frame.setVisible(true);
       }       
       /**
        * 获取URL的方法
        * @param name
        * @return
        */
       private URL getCDUrl(String name){
    	   try{
    		   return new URL((String)cds.get(name));
    	   }catch(MalformedURLException e){
    		   e.printStackTrace();
    		   return null;
    	   }
       }       
}

在这里插入图片描述
在这里插入图片描述
以上代码实现了图片显示代理,这个思想网页加载时的代理显示中也经常看见,把阻塞的方法放在后台执行,确保主线程的通畅,避免的GUI的挂起。

三、访问控制代理

概念:这个代理可以理解为明星的经纪人,想和明星做些什么事情首先需要通过经纪人这个环节,经纪人决定了我们对于明星的访问权。对象访问代理既可以为对象的方法在执行的时候加上一些新的内容,也可以选择性地决定对象的哪些方法是可以调用的,哪些方法是不可以调用的。这种代理思想一般分为静态代理和动态代理两种,“静态”表示在Java程序执行前就以及创建了代理对象,即对象的代理内容是确定了的,“动态”是指程序运行期间才创建代理对象,采用的是Java反射的思想。下面我们来介绍这两种代理。
静态代理:静态代理很容易理解,代理者持有被代理者的引用,二者实现相同的接口,外部只能直接调用代理者的方法,在方法中,代理者决定了是否调用被代理者的方法,或者在调用的同时是否添加一些新的内容。示意图就如同文章开篇给出的那张图。
动态代理:动态代理使用到了反射机制,在程序运行中创建代理对象。和静态代理不同的是它需要实现一个InvocationHandler接口,这个接口的对象是一个调用处理器,它只有一个方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
代理对象对任何方法的调用都实际上需要通过这个方法,接下来我们分析一下这个调用过程:
实现一个买房代理的例子
写一个接口,那就是买房的接口,代理者和被代理者都是它的实例:

public interface SearchHouse {
	public abstract void buyHouse();
}

被代理者实现这个接口:

public class Master implements SearchHouse{
	public void buyHouse() {
		System.out.println("买了一套房子");		
	}
}

实现一个调用处理器,先贴出代码,稍后再详细解释:

public class DymanicProxy implements InvocationHandler{    
	private SearchHouse searchHouse;
	public DymanicProxy(SearchHouse searchHouse){
		this.searchHouse=searchHouse;
	}	
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("找房,看房");
		Object result=method.invoke(searchHouse, args);//两个参数,一个是实际调用方法的对象,一个是参数
		System.out.println("装修房子");
		return result;
	}
}

测试代码:

	public static void main(String[] args){
		try{
		SearchHouse master=new Master();//买房的被代理对象
		SearchHouse searchProxy=(SearchHouse)Proxy.newProxyInstance(SearchHouse.class.getClassLoader(),
				new Class[]{SearchHouse.class}, new DymanicProxy(master));//通过类加载器,类接口,处理器对象得到代理对象
		searchProxy.buyHouse();//代理对象来执行买房操作
		}catch(Exception e){
			e.getMessage();
		}
	}

输出了:
在这里插入图片描述
分析一个这个过程:先new一个买房者(被代理者)的实例,然后创建一个代理者的实例,之前我们提到过,代理者实在程序运行中才被创建的,所以它并没有一个单独的实现类,Proxy类是代理类,可以通过它的静态方法获取实例。代理者和被代理者是联系起来的,代理者需要持有被代理者的类加载器和类接口(这里可以得到类的全部信息),另外代理者还需要被赋予一些特殊的职责,那么它还需要持有调用处理器的实例。这样就可以得到一个代理的实例了。然后调用代理对象的方法:代理对象得到了要调用的方法,就会通过来加载机制获得方法对象,然后实际调用invoke()方法,把方法对象,参数等传入其中,接下来执行代理的特殊代码外,再执行method的invoke方法(注意这里的参数是被代理者实例和参数,处理器是持有被调用者实例的),就可以实现多态代理的效果了。
动态代理的优点:可以在程序运行时灵活调用不同的方法,可以实现多个调用处理器,调用代码和具体代理实现解耦,代码的可维护性和扩展性大大提高。
代理模式和适配器模式的区别:代理模式和适配器都在访问中隔了一个对象,这个对象对于方法的调用做了一些中间的处理,中间环节持有被代理/适配者的引用,二者有相似之处。不同的地方是,代理模式的代理者和被代理者实现了相同的接口,且目的是访问的控制;在适配器模式中,目的是转换接口,二者实现的是不同的接口。

一些别的代理

1、防火墙代理:控制网络资源的访问,保护主题免于“坏客户的侵害”。
2、智能引用代理:当主题被引用时,进行额外动作,比如记录访问次数。
3、缓存代理:为开销大的运算结果提供暂时的存储;也允许多个客户共享结果,以减少计算或者网络延迟。
4、同步代理:在多线程下为主体提供安全访问。
5、复杂隐藏代理:也称外观代理,进行访问控制,用来隐藏一个类的复杂集合的复杂度。
6、写入时复制代理:用来控制对象的复制,方法是延迟对象的复制,直到客户真正需要为止,这是虚拟代理的变体。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值