代理模式
给某一个对象提供一个代理,并由代理对象控制对原对象的引用,是一种对象创建型模式。
结构图
角色说明
(1)Subject(抽象主题角色)它声明了真实主题和代理主题的共同接口,使得在任何使用真实主题的地方都可以使用代理主题,客户端往往需要针对此类编程。
(2)Proxy(代理主题角色)代理主题角色包含了对真实主题的引用,可以在任何时候操作真实的主题,提供一个与真实主题角色相同的接口(request),此外可以控制对真实主题的使用,负责在需要的时候创建,或删除真实主题对象,对真实主题加以约束。通常,在代理主角色中,客户端在调用所引用的真实主题操作之前或之后还需要添加其他的操作,不仅仅是单纯的调用真实主题的操作。
(3)RequestSubject(真实主题类)它定义了代理角色所代表的真实对象,在真实主题角色实现了真实的业务操作,客户端可以通过代理主题角色间接调用真实主题角色中定义的操作。
代理模式的结构比较简单,但是在真实的使用和实现过程中,要复杂很多,特别是代理类的设计和实现。
package com.learn.designmode.mode.Proxy;
/**
* 代理
*/
public class Proxy implements Subject {
// 维持对一个真实主题的引用
private RealSubject realSubject;
public void preRequest(){
}
@Override
public void request() {
postRequest();
realSubject.request();
postRequest();
}
public void postRequest(){
}
}
package com.learn.designmode.mode.Proxy;
/**
* 真实主题角色
*/
public class RealSubject implements Subject {
@Override
public void request() {
}
}
package com.learn.designmode.mode.Proxy;
/**
* 客户端针对此编程
*/
public interface Subject {
void request();
}
完整的解决方案
package com.learn.designmode.mode.Proxy.demo;
/**
* 抽象查询类:抽象主题
*/
public interface Searcher {
public String doSearch(String userId,String keyword);
}
class AccessValidator{
public boolean valiate(String userId){
System.out.println("在数据库中验证用户 " + userId +"是否为合法用户");
if (userId.equalsIgnoreCase("杨过")){
System.out.println("登录成功");
return true;
}else {
System.out.println("登录失败");
return false;
}
}
}
/**
* 日志记录类:业务类
*/
class Logger{
public void log(String userId){
System.out.println("更新数据库,用户" + userId + "查询次数加1");
}
}
class RealSercher implements Searcher{
@Override
public String doSearch(String userId, String keyword) {
System.out.println("用户 " + userId + "使用关键词" + keyword + "查询商务信息");
return "返回具体内容";
}
}
package com.learn.designmode.mode.Proxy.demo;
import com.learn.designmode.mode.factory.chart.utils.XMLUtil;
import org.xml.sax.SAXException;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
/**
* 代理类
*/
public class ProxySearcher implements Searcher{
private AccessValidator accessValidator = new AccessValidator();
private Logger logger = new Logger();
private Searcher searcher = new RealSercher();
@Override
public String doSearch(String userId, String keyword) {
if (accessValidator.valiate(userId)){
String result = searcher.doSearch(userId,keyword);
this.logger.log(userId);
return result;
}else {
return null;
}
}
public static void main(String[] args) throws SAXException, IllegalAccessException, IOException, InstantiationException, ParserConfigurationException, ClassNotFoundException {
Searcher searcher = (Searcher) XMLUtil.getBean("proxy");
searcher.doSearch("杨过","玉女心经");
}
}
常用的几种代理模式
- 远程代理(Remote Proxy) 为一个位于不同地址空间的对象提供一个本地代理对象,这个不同的地址空间可以是在同一台主机中,也可以在另外一台主机中。使得客户端程序可以访问远程主机,远程主机可能具有更好的计算性能和处理速度,快速响应客户端的请求。还可以将网络的细节化隐藏起来,客户端完全可以认为被代理的业务是局域的而不是远程的,而远程代理对象承担了大部分的网络通信工作。
Java语言中,可以通过RMI(远程方法调用机制)来实现远程代理,它能够实现一个Java虚拟机中的对象调用另外一个Java虚拟机中对象的方法。在RMI中,客户端可以通过一个桩(Stub)对象与远程主机上的业务对象进行通信,由于桩对象和远程业务对象接口的一致,因此对于客户端而言,操作远程对象和本地桩对象没有任何区别,桩对象就是远程业务对象的代理对象
RMI的实现过程中,远程主机端有一个Skeleton(骨架)对象来负责与(Stub)对象通信,基本步骤如下
(1)客户端发起请求,将请求转到RMI客户端的Stub类
(2)Stub类将请求的接口、方法、参数等信息进行序列化
(3)将序列后的流使用Socket传输至服务器端
(4)服务器端接收到流后将其转发到相应Skeleton类。
(5)Skeleton类将请求信息反序列化后调用实际的业务处理类。
(6)业务处理类处理完毕后,将结果返回给Skeleton类
(7)Skeleton将结果序列化,再次通过Socket将流传送给客户端的Stub
(8)Stub在接收到流后进行反序列化,将反序列化得到的Object返回给客户端调用者。
-
虚拟代理
如果创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才被真正创建。采用虚拟代理扮演真实对象的替身。
以下两种情况考虑虚拟代理
(1)一个对象需要较长的时间加载,此时可以用一个加载时间相对较短的代理来代表真实对象。可以结合多线程的技术,一个线程用于显示代理对象,其他线程用来加载真实对象。这种虚拟代理模式可以应用在程序启动的时候,由于创建代理对象在时间和处理复杂度上要少于创建真实的对象,因此当程序启动时,可以用代理对象替代真实对象的初始化,大大加速系统的启动时间。当需要使用真实对象时,在通过代理对象来引用,而此时真实对象可能已经成功加载完毕,可以缩短用户的等待时间。
(2)当一个对象的加载十分耗费系统资源的时候。可以让那些占用大量内存和处理起来非常复杂的对象推迟到使用他们的时候才创建。为了节省内存,在第一次引用真实对象时再创建对象,并且该对象可悲重用,在以后每次访问时需要检测对象是否被创建。(时间换空间)
无论以上哪种情况,系统都用一个“虚假”的对象代表真实对象,通过代理对象来间接引用真实对象。提供系统的性能。 -
Java动态代理(AOP实现的机制)
package com.learn.designmode.mode.Proxy.dynamicsproxy;
/** 抽象主题角色
* @author Administrator
*/
public interface AbstractUserDao {
public Boolean findUserById(String userId);
}
/** 抽象主题角色
* @author Administrator
*/
interface AbstractDocumentDao{
public Boolean deleteDocumentById(String id);
}
/** 具体主题角色
* @author Administrator
*/
class UserDao implements AbstractUserDao{
@Override
public Boolean findUserById(String userId) {
System.out.println("在数据库中验证用户 " + userId +"是否为合法用户");
if (userId.equalsIgnoreCase("张无忌")){
System.out.println("查询ID为" + userId + "的用户信息成功");
return true;
}else {
System.out.println("查询ID为" + userId + "的用户信息失败");
return false;
}
}
}
/** 具体主题角色
* @author Administrator
*/
class DocumentDao implements AbstractDocumentDao{
@Override
public Boolean deleteDocumentById(String id) {
if (id.equalsIgnoreCase("D001")){
System.out.println("删除ID为" + id + "的用户信息成功");
return true;
}else {
System.out.println("删除ID为" + id + "的用户信息失败");
return false;
}
}
}
package com.learn.designmode.mode.Proxy.dynamicsproxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Calendar;
import java.util.GregorianCalendar;
/**
* 继承动态代理类
*/
public class DaoLogHandler implements InvocationHandler {
public Calendar calendar;
// 这里会在构造方法里边注入真实对象
private Object object;
public DaoLogHandler(Object object){
this.object = object;
}
// 实现invoke 方法
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
beforeInvoke();
// 调取真实对象的方法
Object o1 = method.invoke(object,objects);
afterInvoke();
return o1;
}
// 记录调用时间
public void beforeInvoke(){
calendar = new GregorianCalendar();
int hour = calendar.get(Calendar.HOUR);
int minute = calendar.get(Calendar.MINUTE);
int second = calendar.get(Calendar.SECOND);
String time = hour + ":" + minute + ":" + second;
System.out.println("调用时间" + time);
}
public void afterInvoke(){
System.out.println("方法调用结束");
}
}
package com.learn.designmode.mode.proxy.dynamicsproxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Client {
public static void main(String[] args) {
UserDao userDao = new UserDao();
InvocationHandler invocationHandler = new DaoLogHandler(userDao);
AbstractUserDao proxy = null;
// 通过Proxy类的静态方法newProxyInstance返回一个接口的代理实例。针对不同的代理类,传入相应的代理程序控制器InvocationHandler。
proxy = (AbstractUserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader(),userDao.getClass().getInterfaces(),invocationHandler);
proxy.findUserById("张无忌");
System.out.println("---------------------------------------");
AbstractDocumentDao abstractDocumentDao = new DocumentDao();
InvocationHandler invocationHandler1 = new DaoLogHandler(abstractDocumentDao);
AbstractDocumentDao proxy1 = null;
// 同上
proxy1 = (AbstractDocumentDao) Proxy.newProxyInstance(abstractDocumentDao.getClass().getClassLoader(),abstractDocumentDao.getClass().getInterfaces(),invocationHandler1);
proxy1.deleteDocumentById("D002");
}
}
动态代理底层实现
动态代理具体步骤:
- 通过实现 InvocationHandler 接口创建自己的调用处理器;
- 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
- 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
- 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。
总结
优点
(1)代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度,满足迪米特法则
(2)客户端可以针对抽象主题类进行编程,增加和更换代理类无须修改源代码,符合开闭原则,系统具有较好的灵活性和可扩展性。
(3)远程代理为位于不同地址空间对象的访问提供了一种实现机制,可以将一些资源消耗较多的对象和操作移植到性能更好的计算机上。
(4)虚拟代理通过一个消耗资源较少的对象来代表一个资源消耗较多的对象,在一定程度上节省了系统的开销。
(5)保护代理可以控制一个对象的访问,为不同的用户提供不同级别的使用权限。
缺点
(1)由于客户端增加了代理对象,难免会造成速度慢的缺点。
(2)实现代理模式需要额外的工作,有些代理模式实现非常复杂,例如远程代理。
适用场景
(1)当客户端需要访问远程主机的对象。
(2)当需要一个消耗资源较少替代一个资源消耗较多的对象时。
(3)当需要控制一个对象的访问,为不同的用户提供不同的访问权限时
(4)当需要一个对象的引用提供一些额外的操作时,智能代理模式
(5)当需要一个被频繁访问的操作结果提供一个临时存储空间,以多个客户端共享这些结果时,使用缓冲代理,从临时缓冲区操作结果。