设计模式(11)代理模式(The Proxy Pattern)- 2 虚拟代理和动态代理

本文介绍了代理模式的基本概念,探讨了远程代理、虚拟代理和保护代理的应用场景,并通过代码示例展示了虚拟代理和保护代理的具体实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

代理模式定义

为另一个对象提供一个代理或者占位符,来控制对他的访问。

几个控制访问的方法:

  • 一个远程代理控制对远程对象的访问

  • 虚拟代理控制对创建成本较高的资源的访问

  • 保护代理控制对于权限资源的访问

远程代理

见:
设计模式(11)代理模式The Proxy Pattern - 1 - 远程代理rmi

虚拟代理

image-20200428173745083

虚拟代理用来标识一个需要昂贵代价去创建的对象。虚拟代理通常来推迟这个对象的创建,直到这个对象被需要时再创建。在被代理对象创建完成之前,虚拟代理扮演者被代理者(RealSubject)的角色, 创建完成之后,虚拟代理把请求转交给被代理对象。

例如:播放在线音乐,当音乐封面没有被下载的时候,虚拟代理充当封面的角色(可以使一串字符串“正在下载封面”,或者其他的某个默认图片), 一旦封面下载完成,虚拟代理就把所有调用的方法委托给实际的图片。

// Head Fist Design Pattern P456
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() {
                public void run() {
                    try {
                    	imageIcon = new ImageIcon(imageURL, “CD Cover”);
                    	c.repaint();
                    } catch (Exception e) {
                    	e.printStackTrace();
                    }
                 }
            });
        	retrievalThread.start();
        	}
        }
    }
}

简而言之

两个类:ImageIcon 、ImageProxy

一个接口:Icon

  • 这两个类都实现了Icon的接口。所以外部调用只是用Icon就行。0–

  • ImageProxy内部存在一个ImageIcon类,准确的说ImageProxy负责管理ImageIcon。创建时ImageProxy时新建线程负责下载ImageIcon。没下载完成之前,都是用ImageProx的参数,下载完成后,image非空,就使用image了

保护代理/动态代理

例:有一个PersonImpl,实现了PersonBean接口。包括了一些个人的信息,如姓名、性别、爱好等。

所有的get方法,可以直接调用。

我们对于PersonBean的方法访问有两组不同的权限

  • 能够调用setHotOrNotRating()的不能调用其他的setXXX方法

  • 能够调用setXXX方法的不能调用setHotOrNotRating()方法

public class PersonBeanImpl implements PersonBean {
	String name;
	String gender;
	String interests;
	int rating;
	int ratingCount = 0;

	public String getName() {
		return name;
	}

	public String getGender() {
		return gender;
	}

	public String getInterests() {
		return interests;
	}

	public int getHotOrNotRating() {
		if (ratingCount == 0)
			return 0;
		return (rating / ratingCount);
	}

	public void setName(String name) {
		this.name = name;
	}

	public void setGender(String gender) {
		this.gender = gender;
	}

	public void setInterests(String interests) {
		this.interests = interests;
	}
	
    // 只有其他人可以调用
	public void setHotOrNotRating(int rating) {
		this.rating += rating;
		ratingCount++;
	}
}

接口AND实现类:

  • PersonBean

    • PersonImpl:最终要被操作的实体类
  • InvocationHaandler(Java.lang.reflect.*)

    • OwnerInvocationHandler:某个实例的拥有者的方法调用管理器,决定某个方法是否有资格被该用户调用
    • NonOwnerInvocationHandler:某个实例的非拥有者调用管理器。决定某些方法时候有资格调用。
  • 无接口

    • MatchMakingTestDrive:测试调用类。在此处动态创建代理。

正常的调用流程:

NonOwnerInvocationHandler和OwnerInvocationHandler相似,所以接下来全部以OwnerInvocationHandler为例

以下代码均来自《Head First Design Patterns》Chapter11

  1. MatchMakingTestDrive类:获取代理的实例:

    PersonBean getOwnerProxy(PersonBean person) {
    	return (PersonBean) Proxy.newProxyInstance( //通过代理创建一个PersonBean实例
    		person.getClass().getClassLoader(), 
    		person.getClass().getInterfaces(),
    		new OwnerInvocationHandler(person)); // 传入我们写的调用处理。这个决定某些函数能否被调用成功。
    }
    
  2. 调用上面的函数,我们可以得到一个PersonBean的实例。但是,我们得到的这个实例不包含PersonBean的全部方法,只有OwnerInvocationHandler允许我们调用的方法。调用不被允许的方法会报错。

  3. 具体OwnerInvocationHandler是如何控制我们对PersonBean方法的使用呢?

    public class OwnerInvocationHandler implements InvocationHandler {
        PersonBean person;
        /**
        * 构造的时候要传入用户
        */
        public OwnerInvocationHandler(PersonBean person) {
        	this.person = person;
        }
        
        /**
        * invoke接口内部决定什么方法可以被调用
        */
        public Object invoke(Object proxy, Method method, Object[] args)
            throws IllegalAccessException {
            try {
                // get方法全部被允许
                if (method.getName().startsWith(“get”)) {
                	return method.invoke(person, args);
                // set方法部分允许
                } 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;
        }
    }
    

    如果是NonOwnerInvocationHandler,那么就允许setHotOrNotRating,而不允许其他的setXXX函数。

总结来说,在调用层,通过动态创建代理,即调用getOwnerProxy()或者getNonOwnerProxy()函数来得到一个"特殊的"PersonBean实例。调用这两个函数虽然得到的都是PersonBean实例,但是他们对函数的访问权限是不同的。这样我们就可以根据实际场合来决定调用哪个函数获取动态代理,从而获得对实例不同的修改权限。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值