《HeadFirst设计模式》第十一章-3代理模式-保护代理

1.声明

设计模式中的设计思想、图片和部分代码参考自《Head First设计模式》作者Eric Freeman & Elisabeth Freeman & Kathy Siezza & Bert Bates。

在这里我只是对这本书进行学习阅读,并向大家分享一些心得体会。

2.保护代理简介

Java在java.lang.reflect包中有自己的代理支持,利用这个包你可以在运行时动态地创建一个代理,实现一个或多个接口,并将方法地调用转发到我们所指定地类。因为实际地代理类是在运行时创建的,我们称这个Java技术为:动态代理。

我们要利用Java地动态代理创建我们下一个代理实现(保护代理)。但在这之前,先让我们看一下类图,了解一下动态代理是怎么一回事。就和真实世界中大多数的事物一样,它和代理模式的传统意义有一点出入。

《HeadFirst设计模式》第十一章-3代理模式-保护代理_第1张图片

因为Proxy是Java自动帮我们创建的,所以我们需要想办法来告诉Proxy类,我需要做什么。我们不能像以前一样把代码放在Proxy类在,因为Proxy不是我们直接实现的。既然代码不能放在Proxy中,那么应该放在那里?代码需要放在InvocationHandler中。InvocationHandler的工作是响应代理的任何调用。我们可以把InvocationHandler想成是代理受到方法调用后,请求做实际工作的对象。

3.保护代理的使用 

3.1需求

某约会网站需要让我们帮他们制作约会后的评分系统。可以为约会对象打分。

3.2初步代码

玩家信息超类-PersonBean:

//用户信息接口
public interface PersonBean {
 
	//名称
	String getName();
	//性别
	String getGender();
	//爱好
	String getInterests();
	//平均评分
	int getHotOrNotRating();
 
    void setName(String name);
    void setGender(String gender);
    void setInterests(String interests);
    void setHotOrNotRating(int rating); 
 
}

玩家信息实现类-PersonBeanImpl:

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++;
	}
}

3.3缺陷

这个评分看似解决了为我们的需求,但是有一个缺陷。就是被评价者可以随意修改自己的HotOrNotRating值,甚至是修改其他人的性别、名字,这样很不合理。所以我们需要为PersonBean施加保护。

所以这时候就需要用保护代理,什么是保护代理?这是一种根据访问权限决定客户可否访问对象的代理。比方说如果某公司有个项目后台管理系统。那么这个系统只允许普通员工有查权限,而经理可以有部分模块的写权限和所有模块的读权限,而CEO则拥有所有模块的读写权限。

同理在这个约会评分系统中,我们希望被评价者拥有对自己信息中“名称”、“性别”、“爱好”,拥有读写权限,但是对自己的HotOrNotRating值只有读权限。同时我们希望评价者,对他人的信息中的HotOrNotRating值拥有读写权限,但是对他人的“名称”、“性别”、“爱好”三个信息只有读权限。

下面用娱乐圈的一张图来描述一下保护代理:

《HeadFirst设计模式》第十一章-3代理模式-保护代理_第2张图片

 3.4使用保护代理的代码

首先我们要明确一个需求的核心,就是:被评价者不可以改变自己的HotOrNotRating值,也不可以改变他人的“名称”、“性别”、“爱好”三个信息。

要解决这个问题,我们必须创建两个代理:一个是访问自己的PersonBean对象,另一个是访问他人的PersonBean对象。这样代理就可以分情况的对PersonBean进行保护。也就是说我们需要创建两个InvocationHandler

创建这种代理,我们必须使用Java API的动态代理。Java会帮助我们创建两个代理,我们只需要提供handler来处理代理转来的方法。

3.4.1步骤

步骤1:

创建两个InvocationHandlerInvocationHandler实现了代理行为,正如我们所看到的,Java负责创建真实代理类和对象,我们只需要提供在方法调用发生时知道做什么的handler。

步骤2:

书写代码产生代理类,并实例化它。

步骤3:

利用适当的代理包装任何PersonBean对象,当我们需要使用PersonBean对象时,使用方无非就是:被评价者(称为拥有者),或者是评价者(称为非拥有者),无论是哪一方,我们都为PersonBean创建适合的代理。

《HeadFirst设计模式》第十一章-3代理模式-保护代理_第3张图片

3.4.2步骤实现

步骤1:创建InvocationHandler

我们需要书写两个InvocationHandler(调用处理器),其中一个给拥有者使用,另一个给非拥有者使用。

InvocationHandler的工作原理:当代理的方法被调用时,代理就会把这个调用转发给InvocationHandler,但是这并不是通过调用InvocationHandler的相应方法做到的。那么是如何做到的?我们可以先看下InvocationHandler的API文档:

《HeadFirst设计模式》第十一章-3代理模式-保护代理_第4张图片

整个InvocationHandler接口中,只有invoke()一个方法,不管代理被调用的是何种方法,InvocationHandler中被调用都是invoke方法(注:invoke方法中有个参数为method可用于区分代理中的不同方法)。

工作原理:

《HeadFirst设计模式》第十一章-3代理模式-保护代理_第5张图片

当proxy调用invoke()时,InvocationHandler是如何做保护的(或者说是如何做权限控制的) ,通常我们会先检查该方法是否是来自proxy,并基于该方法的名称和变量做决定。

被评价者的InvocationHandler-OwnerInvocationHandler:

public class OwnerInvocationHandler implements InvocationHandler { 
	
	//持有RealSubject的引用(这样底层就可以调用RealSubject的方法)
	PersonBean person;
 
	public OwnerInvocationHandler(PersonBean person) {
		this.person = person;
	}
 
	//proxy的任何代码被调用,就会导致invoke方法被调用
	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")) {
				//不允许被评价者调用setHotOrNotRating方法,会抛出异常
				throw new IllegalAccessException();
			} else if (method.getName().startsWith("set")) {
				return method.invoke(person, args);
			} 
        } catch (InvocationTargetException e) {
        	//捕捉真正主题抛出的异常
            e.printStackTrace();
        } 
		//如果调用的是其他的方法,这里不予理会
		return null;
	}
}

评价者的InvocationHandler-NonOwnerInvocationHandler:

//本类和OwnerInvocationHandler同理
public class NonOwnerInvocationHandler implements InvocationHandler { 
	PersonBean person;
 
	public NonOwnerInvocationHandler(PersonBean person) {
		this.person = person;
	}
 
	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")) {
				return method.invoke(person, args);
			} else if (method.getName().startsWith("set")) {
				//不允许评价者设置被评价者的“名称”、“性别”、“爱好”三个信息
				throw new IllegalAccessException();
			} 
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } 
		return null;
	}
}

步骤2:创建Proxy类并实例化Proxy对象:

需要编写一个以PersonBean为参数,并知道如何为PersonBean对象创建拥有者代理的方法。也就是说我们要创建一个代理,把它的方法调用转发给OwnerInvocationHandler:

《HeadFirst设计模式》第十一章-3代理模式-保护代理_第6张图片

注:

对于方法PersonBean getOwnerProxy(PersonBean person),它的形参和返回类型都是PersonBean,但是形参中的PersonBean是RealSubject(真正的主题,被代理者);返回类型的PersonBean是Proxy(代理者),但是由于被代理者和代理者实现了相同的接口,所以导致方法的形参和返回值相同,但是它们并不是一类对象。

测试代码:

public class MatchMakingTestDrive {
	
	//装载PersonBean的容器
	HashMap datingDB = new HashMap();
 	
	public static void main(String[] args) {
		MatchMakingTestDrive test = new MatchMakingTestDrive();
		test.drive();
	}
 
	public MatchMakingTestDrive() {
		//初始化datingDB
		initializeDatabase();
	}

	public void drive() {
		//取得PersonBean
		PersonBean joe = getPersonFromDatabase("Joe Javabean"); 
		//创建出PersonBean的"被评分者代理"对象
		PersonBean ownerProxy = getOwnerProxy(joe);
		System.out.println("Name is " + ownerProxy.getName());
		//被评分者可以设置“名称”、“性别”、“爱好”三个信息
		ownerProxy.setInterests("bowling, Go");
		System.out.println("Interests set from owner proxy");
		try {
			//被评分者不能设置HotOrNotRating值
			ownerProxy.setHotOrNotRating(10);
		} catch (Exception e) {
			System.out.println("Can't set rating from owner proxy");
		}
		System.out.println("Rating is " + ownerProxy.getHotOrNotRating());

		//创建出PersonBean的"评分者代理"对象
		PersonBean nonOwnerProxy = getNonOwnerProxy(joe);
		System.out.println("Name is " + nonOwnerProxy.getName());
		try {
			//评分者不可以设置“名称”、“性别”、“爱好”三个信息
			nonOwnerProxy.setInterests("bowling, Go");
		} catch (Exception e) {
			System.out.println("Can't set interests from non owner proxy");
		}
		//评分者可以设置HotOrNotRating值
		nonOwnerProxy.setHotOrNotRating(3);
		System.out.println("Rating set from non owner proxy");
		System.out.println("Rating is " + nonOwnerProxy.getHotOrNotRating());
	}

	//返回"被评分者的代理"对象
	PersonBean getOwnerProxy(PersonBean person) {
 		
        return (PersonBean) Proxy.newProxyInstance( 
            	person.getClass().getClassLoader(),
            	person.getClass().getInterfaces(),
                new OwnerInvocationHandler(person));
	}

	//返回"评分者的代理"对象
	PersonBean getNonOwnerProxy(PersonBean person) {
		
        return (PersonBean) Proxy.newProxyInstance(
            	person.getClass().getClassLoader(),
            	person.getClass().getInterfaces(),
                new NonOwnerInvocationHandler(person));
	}

	//根据名称从容器中获得PersonBean
	PersonBean getPersonFromDatabase(String name) {
		return (PersonBean)datingDB.get(name);
	}

	void initializeDatabase() {
		PersonBean joe = new PersonBeanImpl();
		joe.setName("Joe Javabean");
		joe.setInterests("cars, computers, music");
		joe.setHotOrNotRating(7);
		datingDB.put(joe.getName(), joe);

		PersonBean kelly = new PersonBeanImpl();
		kelly.setName("Kelly Klosure");
		kelly.setInterests("ebay, movies, music");
		kelly.setHotOrNotRating(6);
		datingDB.put(kelly.getName(), kelly);
	}
}

输出结果:

Name is Joe Javabean
Interests set from owner proxy
Can't set rating from owner proxy
Rating is 7
Name is Joe Javabean
Can't set interests from non owner proxy
Rating set from non owner proxy
Rating is 5

3.4.3小结

问题的根源是,被评价者和评价者都可以对PersonBean(RealSubject)进行修改,但是为了做到让PersonBean对被评分者的setHotOrNotRating屏蔽,同时做到让PersonBean对评分者的基本信息设置的屏蔽,我们将PersonBean保护起来(权限控制),所以我们需要创建两种代理,这两种代理分别放开了不同的权限,PersonBean的操作者无非就是被评价者和评价者,根据操作者的不同,我们分配给它不同的代理。对于Java的动态代理,Proxy是自动创建的,但是需要我们书写InvocationHandler来做具体的访问权限控制,从而做到对PersonBean保护。

3.5问答

  • 问:动态代理的动态体现?答:动态代理运行时才将它的类创建出来。代码开始执行时,还没有Proxy,他是根据需要从传入的接口集中创建的。
  • 问:InvocationHandler是不是代理?答:它并不是代理,它只是一个帮助代理的类,proxy会将调用转发给他处理。Proxy本身是利用静态的proxy.newInstance()方法在运行时动态地创建的。

4.结语 

至此,历时三个月看完了《HeadFirst设计模式》这本书,但是我觉得这只是一个设计模式的入门,未来还要在工作学习中进行深入学习。本书的常规设计模式章节,有的地方理解还不是很完全、附录部分的设计模式还没有去看。这些知识点需要在日后进行补充和跟进,但是总体来说,已经对设计模式有了初步的认识,感谢《HeadFirst设计模式》这本书的作者们。

你可能感兴趣的:(设计模式)