什么是代理模式?
- 当无法直接访问某个对象或者访问某个对象有一些困难时,可以通过代理对象来间接访问,为了保证客户端使用的透明性,所访问的真实对象与代理对象需要实现相同的接口。这就是代理模式。
- 例如: 用户执行了某一简单的注册操作,当然在客户端代码中不仅仅只有注册这一功能的方法,还有其他的方法,比如说打印日志:
XXX于几月几日注册
,当然这些日志是不会返回给用户看到,这些是给开发人员看的,但是用户看到的注册功能和开发人员看到的日志功能要实现同一个接口,这时候就需要一个代理对象来松耦合,真实对象处理业务代码,代理对象处理非业务代码。 - 定义: 给某一个对象提供一个代理或者占位符,并由代理对象来控制对原对象的访问。它去掉客户不能看到的内容和服务或者增加客户需要的额外的新服务。
代理模式的结构
- 代理模式的核心结构就是代理类,为了使客户端 可以一致性的对待真实对象和代理对象,引入了抽象层
- 真实类和代理类都实现了抽象接口,并且在代理类中维持一个真实类的引用
①在任何可以使用真实类的地方,都可以使用代理类。
②代理类还可以控制真实类的使用,因为代理类中有真实类的引用。
③实现了松耦合:真实类处理业务代码,代理类处理非业务代码。
代理模式的实现
需求
- 在一个商务查询的基础上加身份验证和打印日志的功能。当然,用户是看不到任何改变的,还是使用之前的用户查询功能。
实现代码
AccessValidator
身份验证类(业务类),提供validate()
方法实现身份验证。
package com.daq.proxy;
/*
* AccessValidator身份验证类(业务类),提供validate()方法实现身份验证。
*/
public class AccessValidator {
//模拟实现登录验证
public boolean validate(String userId) {
if(userId.equals("代澳旗")) {
System.out.println("欢迎"+userId+"回来!!");
return true;
}else {
System.out.println("请重新登录!");
return false;
}
}
}
Logger
日志记录类,提供log()
方法来保存日志
package com.daq.proxy;
/*
* Logger:日志记录类,提供log()方法来保存日志
*/
public class Logger {
private int num=0;
public void log(String userId) {
num++;
System.out.println(userId+"第"+num+"次查询");
}
}
Searcher
:抽象查询接口,充当抽象类,声明doSeacrh()
方法
package com.daq.proxy;
public interface Searcher {
public String doSearcher(String userId,String keyword) ;
}
RealSearcher
:具体查询类,充当真实类,只实现查询功能。
package com.daq.proxy;
/*
* RealSearcher:具体查询类,充当真实类,只实现查询功能。
*/
public class RealSearcher implements Searcher{
@Override
public String doSearcher(String userId, String keyword) {
System.out.println(userId+"使用"+keyword+"关键词查询!");
return "返回的详细信息";
}
}
ProxySearcher
代理查询类,维持了RealSearcher对象
,AccessValidator对象
,Logger对象
的引用。
package com.daq.proxy;
/*
* ProxySearcher:代理查询类,
* 维持了RealSearcher对象
* AccessValidator对象
* Logger对象的引用。
*/
public class ProxySearcher implements Searcher{
//维持了RealSearcher对象的引用。可以控制真实对象
private RealSearcher realcher=new RealSearcher();
//维持了AccessValidator对象,Logger对象的引用。
//如果真实对象挂掉了,代理对象可以顶。
private AccessValidator validator;
private Logger logger;
@Override
public String doSearch(String userId, String keyword) {
//身份验证成功,执行查询
if(this.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);
}
}
- 配置
config.xml
,在配置文件中存储代理主题类的全类名
<?xml version="1.0" encoding="UTF-8"?>
<config>
<className>com.daq.proxy.ProxySearcher</className>
</config>
XMLUtil
:工具类,提供getBean()
方法,用于从XML配置文件中提取该具体类的名字,并返回一个实例对象
package com.daq.proxy;
import java.io.File;
import java.io.IOException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
public class XMLUtil {
// 该方法用于从XML配置文件中提取该具体类的名字,并返回一个实例对象
public static Object getBean() {
try {
// 创建DOM文档对象
DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dFactory.newDocumentBuilder();
Document doc;
doc = builder.parse(new File("src//designpatterns//Proxy//conf.xml"));
//获取包含类名的文本结点
NodeList nl=doc.getElementsByTagName("className");
Node classNode=nl.item(0).getFirstChild();
String cName=classNode.getNodeValue();
//通过类名生成实例对象并返回
Class c= Class.forName(cName);
Object obj = c.newInstance();
return obj;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
- 客户端测试
package com.daq.proxy;
public class Client {
public static void main(String[] args) {
Searcher searcher;
searcher=(Searcher)XMLUtil.getBean();
String result=searcher.doSearch("代澳旗", "https://daqwt.top");
}
}
总结
- 代理类和真实类都是事先存在的,接口和代理方法也明确指定了,每个代理类编译之后都会生成class文件,这叫做
静态代理(Static Proxy)
新增功能也会增加类的个数,不利于管理,也会增加系统开销。 - 以上就是静态代理的实现思想了:在代理类中实现对真实类的权限控制,如果想新增功能,只需要增加一个相应的代理类,在修改配置文件,不用修改源码,但是这种静态代理的方式,实现起来,还是比较困难的。Java的动态代理,就可以很好地解决这个问题,在Spring框架的AOP中得以运用,而且整合封装之后使用更加简洁。
Java动态代理
- 可以让系统在运行时根据实际需要来动态创建代理类,让同一个代理类能够代理多个不同的真实类,并且可以代理不同的方法,动态代理比较高级,在事务管理,AOP,都有着很大的作用。具体的动态代理实现方法,在后续学习AOP的时候去写。
- 通过动态代理可以实现对多个真实主题类的统一代理和集中控制
- JDK中的动态代理只能代理一个或者多个接口,如果要动态代理某些具体类或者是抽象类,可以使用
GGLib
等工具,它是一款代码生成包。
远程代理
- 它使客户端程序可以访问到远程主机上的对象,远程业务在本地主机有一个代理对象,该代理对象负责对远程业务对象的访问和网络通信,客户端无需关心是谁来实现具体业务,只需要按照服务接口所定义的方式直接与本地主机中的代理对象交互即可。在Java中通过
RMI机制
实现远程代理。(就是Java虚拟机中的对象调用另一个java虚拟机中的对象)
虚拟代理
- 对于一些占用系统资源比较多的或者加载时间长的对象,可以提供虚拟代理,在真实对象创建之前虚拟代理扮演真实对象的替身,而当真实对象创建之后虚拟代理将用户的请求转发给真实对象。
- 以下两种情况下使用虚拟代理:
①由于各种原因,对象的创建时间比较长,在实现是结合多线程,一个对象用于显示代理对象,其他线程用于加载真实对象。在程序启动的时候,用代理对象代替真实对象初始化,加速系统的启动时间,这种方式可以缩短用户等待的时间。
②当一个对象加载非常消耗资源的时候,虚拟代理可以让那些占用大量内存或处理起来比较麻烦的对象推迟到使用他们的时候在创建。访问对象时要进行存在性检测,这可能需要消耗系统时间,但是节省了内存空间,以时间换空间。
各模式的优缺点
-
优点: 解耦,可扩展,灵活。
-
远程代理: 为两个不同地址空间的对象提供一种实现机制,可以将一些资源消耗比较多的对象和操作移动至性能更好的计算机上,提高系统的整体运行效率。
-
虚拟代理: 通过一个消耗资源比较少的对象来替代一个消耗资源比较多的对象,可以在一定程度上节约系统运行开销。
-
缓冲代理: 为某一个操作的结果提供临时的缓存存储空间,以便在后续使用中能够共享这个结果,优化系统性能,缩短执行时间。
-
保护代理: 控制一个对象的访问权限,为不同的用户提供不同的使用权限。
-
缺点:
①由于在客户端和真实类之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度慢,例如保护代理。
②代理的实现比较复杂。