概述

代理模式是一种应用很广泛的结构型设计模式,而且变化很多。在代理模式中引入了一个新的代理对象,代理对象可以在客户端对象和目标对象之间起到中介作用,去掉客户不能看到的内容和服务或者增添客户需要的额外服务。

定义

代理模式(Proxy Pattern):给某一个对象提供一个代理,并由代理对象控制对原对象的引用。代理模式是一种对象结构型模式。

结构图

结构图

代理模式的结构比较简单,其核心是代理类,为了让客户端能够一致地对待真实对象和代理对象,在代理模式中引入抽象模式,结构如上图

由结构图可以看出

  1. Subject(抽象主题角色):它声明了真实主题和代理主题的共同接口,使得在任何使用真实主题的地方都可以使用代理主题,客户端通常需要针对抽象主题角色进行编程

  2. Proxy(代理主题角色):代理主题角色内部包含了对真实主题的引用,从而可以在任何时候操作真实主题对象;在代理主题角色中提供了一个与真实主题角色相同的接口,以便在任何时候都可以代替真实主题对象;代理主题角色还可以控制对真实主题的使用,负责在需要的时候创建和删除真实主题对象,并对真实主题对象的使用加以约束。通常,在代理主题角色中,客户端在调用所引用的真实主题操作之前或者之后还需要执行其他操作,而不仅仅是单纯调用真实主题对象中的操作。

  3. RealSubject(真实主题角色):它定义了代理角色所代表的真实对象,在真实主题角色中实现了真实的业务操作,客户端可以通过代理主题角色间接调用真实主题角色中定义的操作。

典型代码

最简单的代理类实现代码如下:

	class Proxy implements Subject{
		
		private RealSubject realSubject = new RealSubject();	//维持一个对真实主题对象的引用

		public void preRequest(){
			....
		}

		public void request(){
			preRequest();
			realSubject.request();			//调用真实主题对象的方法
			postRequest();
		}

		public void postRequest(){
			....
		}
	}

代理模式种类

在实际开发中,代理类的实现要复杂很多。代理模式根据其目的和实现方式不同可分成很多种类,其中常用的几种代理模式如下:

  1. 远程代理(Remote Proxy):为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以在同一台主机中,也可以在另一台主机中。远程代理又称为大使(Ambassador)

  2. 虚拟代码(Virtual Proxy):如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实的对象只在被需要时才会被真正创建

  3. 保护代理(Protect Proxy):控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限

  4. 缓冲代理(Cache Proxy):为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果

  5. 智能引用代理(Smart Reference Proxy):当一个对象被引用时,提供一些额外操作,例如将对象被调用的次数记录下俩等

扩展

代理模式和装饰模式在实现时有些类似,但是代理模式主要是给真实主题类增加一些全新的职责,例如权限控制,缓冲处理,智能引用,远程访问等,这些职责与原有职责不属于同一问题域;而装饰模式是通过装饰类为具体构件类增加一些相关的职责,是对原有职责的扩展,这些职责属于同一问题域。代理模式和装饰模式的目的也不相同,前者是控制对对象的访问,而后者是为对象动态地增加功能

实例:商务信息的身份验证和日志记录

使用代理模式设计商务信息查询系统的身份验证和日志记录

结构图

结构图

业务类AccessValidator用于验证用户身份,它提供方法validate()来实现身份验证;业务类Logger用于记录用户查询日志,它提供方法log()来保存日志;RealSearcher充当真实主题角色,实现查询功能,它提供方法doSearch()来查询信息;ProxySearcher充当代理主题角色,它是查询代理,维持了对RealSearcher对象,AccessValidator对象和Logger对象的引用;Searcher充当抽象主题角色,声明了doSearch()方法。

代码实现

抽象查询类:抽象主题类

	public interface Searcher {
		public String doSearch(String userId, String keyword);
	}

身份验证类:业务类

	public class AccessValidator {
		//模拟实现登录验证
		public boolean validate(String userId) {
			System.out.println("在数据库中验证用户"+ userId + "是否是合法用户?");
			if(userId.equalsIgnoreCase("杨过")) {
				System.out.println(userId + "登录成功!");
				return true;
			}else {
				System.out.println(userId + "登录失败!");
				return false;
			}
		}
	}

日志记录类:业务员类

	public class Logger {
		//模拟实现日志记录
		public void log(String userId) {
			System.out.println("更新数据库,用户"+ userId + "查询次数加1!");
		}
	}

具体查询类:真实主题类

	public class RealSearcher implements Searcher {
		//模拟查询商务信息
		public String doSearch(String userId, String keyword) {
			System.out.println("用户" + userId + "使用关键词" + keyword + "查询商务信息!");
			return "返回具体内容";
		}
	
	}

代理查询类:代理主题类

	public class ProxySearcher implements Searcher {
	
		//维持一个对真实主题的引用
		private RealSearcher searcher = new RealSearcher();
		
		private AccessValidator validator;
		
		private Logger logger;
		
		@Override
		public String doSearch(String userId, String keyword) {
			//如果身份验证成功,则执行查询
			if(validate(userId)) {
				//调用真实主题对象的查询方法
				String result = searcher.doSearch(userId, keyword); 
				//记录查询日志
				this.log(userId);
				//返回查询结果
				return result;
			}else {
				return null;
			}
		}

		//创建访问验证对象并调用其validate()方法实现身份验证
		public boolean validate(String userId) {
			validator = new AccessValidator();
			return validator.validate(userId);
		}
		
		//创建日志记录对象并调用log()方法实现日志记录
		public void log(String userId) {
			logger = new Logger();
			logger.log(userId);
		}
	
	}

工具类XMLUtil:用来读取配置文件来反射生成对象

	public class XMLUtil {
		public static Object getBean() {
			try {
				//创建文档对象
				DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
				DocumentBuilder builder = dFactory.newDocumentBuilder();
				Document doc;
				doc = builder.parse(new File("config.xml"));
				
				//获取包含类名的文本节点
				NodeList nl = doc.getElementsByTagName("className");
				Node classNode = nl.item(0).getFirstChild();
				String cName = classNode.getNodeName();
				
				//通过类名生成实例对象并将其返回
				Class c = Class.forName(cName);
				Object obj = c.newInstance();
				return obj;
			}catch (Exception e) {
				e.printStackTrace();
				return null;
			}
		}
	}

配置文件存储了代理主题类的类名

	<?xml version="1.0">
	<config>
		<className>ProxySearcher</className>
	</config>

客户端测试

	public class Client {
	
		public static void main(String[] args) {
			//针对抽象编程,客户端无需分辨真实主题类和代理类
			Searcher searcher;
			searcher = (Searcher)XMLUtil.getBean();
			String result = searcher.doSearch("杨过", "小龙女");
		}
	}

本实例是保护代理和智能引用代理的应用实例,在代理类ProxySearcher中实现对真实主题类的权限控制和引用计数,如果需要在访问真实主题时增加新的访问控制机制和新功能,只需增加一个新的代理类,再修改配置文件,在客户端代码中使用新增代理类即可,源代码无须修改,符合开闭原则

java动态代理

通常情况下,每一个代理类编译之后都会生成一个class文件,代理类所实现的接口和所代理的方法都被固定,这种代理被称之为静态代理(Static Proxy),还有一种机制能够让系统在运行时动态创建代理类,叫做动态代理(Dynamic Proxy),动态代理是一种较为高级的代理模式,它在事务管理,AOP(Aspect-Oriented Programming,面向方面编程)等领域都发挥了重要的作用。

Java语言实现动态代理时需要用到位于java.lang.reflect包中的一些类,如下

1. Proxy类

Proxy类提供给了用于创建动态代理类和实例对象的方法,它是所创建的动态代理类的父类,最常用的方法如下:

(1) public static Class<?> getProxyClass(ClassLoader loader, Class<?>...interfaces).该方法用于返回一个Class类型的代理类,在参数中需要提供类加载器并需要指定代理的接口数组(与真实主题类的接口列表一致)

(2) public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 该方法用于返回一个动态创建的代理类的实例,方法中的第1个参数loader表示代理类的类加载器,第2个参数interfaces表示代理类所实现的接口列表(与真实主题类的接口列表一致),第3个参数h表示所指派的调用处理程序类

2. InvocationHandler接口

InvocationHandler接口是代理处理程序类的实现接口,该接口作为代理实例的调用处理者的公共父类,每一个代理类的实例都可以提供一个相关的具体调用处理者(InvocationHandler接口的子类)。该接口中声明了如下方法:

public Object invoke(Object proxy, Method method, Object[] args). 该方法用于处理对代理类实例的方法调用并返回相应的结果,当一个代理实例中的业务方法被调用时将自动调用该方法。invoke()方法包含3个参数,其中,第1个参数proxy表示代理类的实例,第2个参数method表示需要代理的方法,第3个参数args表示代理方法的参数数组

动态代理类需要在运行时指定所代理真实主题类的接口,客户端在调用动态代理对象的方法时,调用请求会将请求自动转发给InvocationHandler对象的invoke()方法,由invoke()方法来实现对请求的统一处理

例子

使用动态代理进行设计与实现调用日志,用于记录数据访问层DAO每一个方法被调用的时间和调用的结果

代码实现

抽象UserDAO:抽象主题角色

	public interface AbstractUserDAO {
		public Boolean findUserById(String userId);
	}

抽象DocumentDAO:抽象主题角色

	public interface AbstractDocumentDAO {
		public Boolean deleteDocumentById(String documentId);
	}

具体UserDAO类:真实角色

	public class UserDAO implements AbstractUserDAO {
	
		public Boolean findUserById(String userId) {
			if(userId.equalsIgnoreCase("张无忌")) {
				System.out.println("查询ID为"+ userId + "的用户信息成功");
				return true;
			}else {
				System.out.println("查询ID为"+ userId + "的用户信息成功");
				return false;
			}
		}
	
	}

具体DocumentDAO类:真实角色

	public class DocumentDAO implements AbstractDocumentDAO {
	
		public Boolean deleteDocumentById(String documentId) {
			if(documentId.equalsIgnoreCase("D001")) {
				System.out.println("删除ID为" + documentId + "的文档信息成功!");
				return true;
			}else{
				System.out.println("删除ID为" + documentId + "的文档信息失败!");
				return false;
			}
		}
	
	}

自定义请求处理程序类

	public class DAOLogHandler implements InvocationHandler {
	
		private Calendar calendar;
		private Object object;
		
		public DAOLogHandler() {
			// TODO Auto-generated constructor stub
		}
		
		//自定义有参构造函数,用于注入一个需要提供代理的真实主题对象
		public DAOLogHandler(Object object) {
			this.object = object;
		}
		
		//实现invoke()方法,调用在真实主题类中定义的方法
		public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
			beforeInvoke();
			Object result = method.invoke(obj, args);	//转发调用
			afterInoke();
			return result;
		}
		
		//记录方法调用时间
		public void beforeInvoke() {
			calendar = new GregorianCalendar();
			int hour = calendar.get(Calendar.HOUR_OF_DAY);
			int minute = calendar.get(Calendar.MINUTE);
			int second = calendar.get(Calendar.SECOND);
			String time = hour + ":" + minute + ":" + second;
			System.out.println("调用时间:"+time);
		}
		
		public void afterInoke() {
			System.out.println("方法调用结束!");
		}
	}

客户端实现

	public class Client {
		public static void main(String[] args) {
			InvocationHandler handler = null;
			
			AbstractUserDAO userDAO = new UserDAO();
			handler = new DAOLogHandler(userDAO);
			AbstractUserDAO proxy = null;
			//动态创建代理对象,用于代理一个AbstractUserDAO类型的真实主题对象
			proxy = (AbstractUserDAO) Proxy.newProxyInstance(
						AbstractUserDAO.class.getClassLoader(), 
						new Class[]{AbstractUserDAO.class},
						handler);
			
			//调用代理对象的业务方法
			proxy.findUserById("张无忌"); 	
			
			System.out.println("----------------------");
			
			AbstractDocumentDAO docDAO = new DocumentDAO();
			handler = new DAOLogHandler(docDAO);
			AbstractDocumentDAO proxy_new = null;
			//动态创建代理对象,用于代理一个AbstractDocumentDAO类型的真实主题对象
			proxy_new = (AbstractDocumentDAO) Proxy.newProxyInstance(
					AbstractDocumentDAO.class.getClassLoader(),
					new Class[] {AbstractDocumentDAO.class},
					handler);
			//调用代理对象的业务方法
			proxy_new.deleteDocumentById("D002");
		}
	}

注:JDK中提供的动态代理只能代理一个或者多个接口,如果需要动态代理具体类或抽象类,可以使用CGLibe(Code Generation Library)等工具。CGLib是一个功能较为强大,性能和质量较好的代码生成包,许多AOP框架中得到很广泛应用。

总结

代理模式是常用的结构型设计模式之一,它为对象的间接访问提供了一个解决方案,可以对对象的间接访问提供一个解决方案,可以对对象的访问进行控制。代理模式类型较多,其中远程代理,虚拟代理,保护代理等在软件开发中应用非常广泛。在JavaRMI,EJB,Web Service,Spring AOP等技术和框架中都使用了代理模式

优点

  1. 代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度,满足迪米特法则

  2. 客户端可以针对抽象主题角色进行编程,增加和更换代理类无须修改源代码,符合开闭原则,系统具有较好的灵活性和可扩展性

  3. 远程代理为位于两个不同地址空间对象的访问提供了一种实现机制,可以将一些消耗资源较多的对象和操作移至性能更好的计算机上,提供系统的整体运行效率

  4. 虚拟代理通过一个消耗资源较少的对象来代表一个消耗资源较多的对象,可以在一定程度上节省系统的运行开销

  5. 保护代理可以控制对一个对象的访问权限,为不同用户提供不同级别的使用权限

缺点

  1. 由于客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢,例如保护代理

  2. 实现代理模式需要额外的工作,有些代理模式的实现非常复杂,例如远程代理

适用场景

代理模式的类型较多,不同类型的代理模式有不同的优缺点,它们应用于不同的场合

  1. 当客户端对象需要访问远程主机中的对象时,可以使用远程代理

  2. 当需要用一个消耗资源较少的对象来代表一个消耗资源较多的对象,从而降低系统开销,缩短运行时间时,可以使用虚拟代理,例如一个对象需要很长时间才能完成加载时。

  3. 当需要控制对一个对象的访问,为不同用户提供不同级别的访问权限时,可以使用保护代理

  4. 当需要为某一个被频繁访问的操作结果提供一个临时存储空间,以供多个客户端共享访问这些结果时,可以使用缓冲代理,通过缓冲代理,系统无须在客户端每一次访问时都重新执行操作,只需直接从临时缓冲区获取操作结果即可。

  5. 当需要为一个对象的访问(引用)提供一些额外的操作时,可以使用智能引用代理

上次更新: 9/22/2020, 12:20:37 AM