13.代理模式

1.什么是代理模式?

因为某些原因,客户端不想或不能直接访问一个对象,此时可以通过一个成为“代理”的第三者来实现间接访问。

代理模式是一种对象结构型模式,代理对象在客户端对象目标对象之间起到中介作用,它去掉客户端不能看到的内容或者添加客户需要的额外的新服务。


代理模式:给某一个对象提供一个代理或者占位符,并由代理对象来控制对原对象的访问

 

简单理解,这个代理,就是相当于日常中常见的代购,起到了一样的作用。

 

2.代理模式的结构

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

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

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

 

3.代理模式的实现

(1)抽象主题

/**
 * 抽象主题
 */
public abstract class Subject {
    public abstract void request();
}

(2)真实主题

/**
 * 真实主题
 */
public class RealSubject extends Subject {
    @Override
    public void request() {
        //业务方法的具体实现代码
    }
}

(3)代理

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

    public void preRequest(){
        //...
    }

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

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

 

4.代理的种类

(1)远程代理:为一个位于不同地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以在一台主机中,也可以在另一台主机中,远程代理又称为大使。(多少有点π币内味了。。。)

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

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

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

(5)智能引用代理:当一个对象被引用时提供一些额外的操作

 

5.代理模式实例——收费查询系统添加身份验证和日志功能

(1)身份验证类(业务类)

/**
 * 身份验证类(业务类)
 */
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;
        }
    }
}

(2)日志记录类(业务类)

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

(3)抽象查询类,充当抽象主题角色

/**
 * 抽象查询类,充当抽象主题角色
 */
public interface Searcher {
    public String doSearch(String userId,String keyword);
}

(4)具体查询类,充当真实主题角色

/**
 * 具体查询类,充当真实主题角色
 */
public class RealSearch implements Searcher {

    @Override
    public String doSearch(String userId, String keyword) {
        System.out.println("用户'"+userId+"'使用关键词'"+keyword+"'查询商务信息");
        return "返回具体内容";
    }
}

(5)代理查询类,充当代理主题角色

/**
 * 代理查询类,充当代理主题角色
 */
public class ProxySearch implements Searcher {
    private RealSearch search = new RealSearch();//维持一个真实主题的引用
    private AccessValidator validator;
    private Logger logger;

    @Override
    public String doSearch(String userId, String keyword) {
        //如果身份验证成功,则执行查询
        if(validate(userId)){
            String result = search.doSearch(userId,keyword);
            this.log(userId);
            return result;
        }else{
            return null;
        }
    }

    //创建访问验证对象
    public boolean validate(String userId){
        validator = new AccessValidator();
        return validator.validate(userId);
    }

    //创建日志记录对象
    public void log(String userId){
        logger = new Logger();
        logger.log(userId);
    }


}

(6)config

<?xml version="1.0" encoding="UTF-8" ?>
<config>
    <className>controller.proxyModulePay.ProxySearch</className>
</config>

(7)工具类

package controller.proxyModulePay;


import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;

public class XMLUtil {
    /**
     * 从xml配置文件中提取具体类的类名,并返回一个实例对象
     */
    public static Object getBean(){
        try {
            //创建DOM文档对象
            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = documentBuilderFactory.newDocumentBuilder();
            Document document = builder.parse(new File("src//controller//proxyModulePay//config.xml"));

            //获取包含类名的文本节点
            NodeList nl = document.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;
        }

    }
}

(8)客户端

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

(9)输出结果及路径

 

6.远程代理

客户端不直接访问远程主机中的业务对象,远程业务对象在本地主机有一个代理对象,该代理对象负责对远程业务对象的访问和网络通信,它对于客户端对象是透明的。

在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在接收到流后进行反序列化,将反序列哈后得到的java对象返回客户端调用者

 

7.虚拟代理

通常两种情况会使用虚拟代理:

(1)由于对象复杂或网络原因导致一个对象需要较长的加载时间,此时可以用一个加载时间较短的代理对象来代替真实对象。通常实现可以通过多线程技术,一个线程用于显示代理对象,其他线程用于加载真实对象。

这种虚拟代理模式可以应用在程序启动的时候,由于创建代理对象在时间和处理复杂度上要少于创建真实对象,因此可以大大加速系统启动时间。当需要使用真实对象时通过代理对象引用,此时真实对象已经加载成功,大大缩短用户的等待时间

(2)当加载一个对象十分消耗系统资源的时候

 

8.java动态代理

前面我们所写的代理模式,代理类和真实主题事前都已存在,是静态代理。当我们需要为不同的真实主题类提供代理类或者代理一个真实主题类的不同方法,就需要增加新的代理类了,这显然不好。

动态代理可以让系统在运行时根据实际需要来动态创建代理类,让同一个代理类能够代理多个不同的真实主题类而且可以代理不同的方法,多用于事务管理,AOP等。(重要!

java实现动态代理需要用到Proxy类和InvocationHandler接口:

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

        Proxy类的常见方法

        (1)public static Class<?> getProxyClass(ClassLoader loader,Class<?>... interfaces):该方法返回一个Class类型的代理类。

        (2)public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h):该方法返回一个动态创建的实例。

        InvocationHandler接口代理处理程序类的实现接口,该接口作为代理实例的调用处理者的公共父类,每一个代理类的实例都可以提供一个相关的具体调用处理者。

public Object invoke(Object proxy,Method method,Object[] args):该方法用于处理对代理类实例的方法调用并返回相应的结果

        

    9.java动态代理实例——OA系统DAO层增加方法调用日志

(1)抽象用户DAO类,抽象主题角色

/**
 * 抽象用户DAO类,抽象主题角色
 */
public interface AbstractUserDAO {
    public boolean findUserById(String userId);
}

(2)抽象文档DAO类,抽象主题角色

/**
 * 抽象文档DAO类,抽象主题角色
 */
public interface AbstractDocumentDAO {
    public boolean deleteDocumentById(String documentId);
}

(3)用户DAO,具体主题角色

/**
 * 用户DAO,具体主题角色
 */
public class UserDAO implements AbstractUserDAO {
    @Override
    public boolean findUserById(String userId) {
        if(userId.equalsIgnoreCase("张无忌")){
            System.out.println("查询ID为"+userId+"的用户信息成功");
            return true;
        }else {
            System.out.println("查询ID为"+userId+"的用户信息失败");
            return false;
        }
    }
}

(4)文档DAO类,具体主题角色

/**
 * 文档DAO类,具体主题角色
 */
public class DocumentDAO implements AbstractDocumentDAO {
    @Override
    public boolean deleteDocumentById(String documentId) {
        if(documentId.equalsIgnoreCase("D001")){
            System.out.println("删除ID为"+documentId+"的文档信息成功");
            return true;
        }else {
            System.out.println("删除ID为"+documentId+"的文档信息失败");
            return false;
        }
    }
}

(5)自定义请求处理程序类

/**
 * 自定义请求处理程序类
 */
public class DAOLogHandler implements InvocationHandler {
    private Calendar calendar;
    private Object object;

    public DAOLogHandler(){

    }

    //自定义构造函数,用于注入一个需要提供代理的真实主题对象
    public DAOLogHandler(Object object){
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        beforeInvoke();
        Object result = method.invoke(object,args);
        afterInvoke();
        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 afterInvoke(){
        System.out.println("方法结束");
    }
}

(6)客户端


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("张无忌");//调用代理对象的业务方法

        AbstractDocumentDAO documentDAO = new DocumentDAO();
        handler = new DAOLogHandler(documentDAO);
        AbstractDocumentDAO proxy_new = null;

        //动态创建代理对象,用于代理一个AbstractDocumentDAO类型的真实主题对象
        proxy_new = (AbstractDocumentDAO)Proxy.newProxyInstance(AbstractDocumentDAO.class.getClassLoader(),new Class[]{AbstractDocumentDAO.class},handler);
        proxy_new.deleteDocumentById("D002");//调用代理对象的业务方法
    }
}

(7)路径及结果

 

10.代理模式优缺点

优:

(1)协调调用者和被调用者,降低耦合

(2)客户端针对抽象主题角色编程

(3)远程代理为不同地址的对象提供实现机制

(4)虚拟代理可以节省开销

(5)缓冲代理提供临时缓存空间

(6)保护代理提供权限

 

缺:

(1)多了一层代理,处理速度可能会变慢

(2)系统较为复杂

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鹏哥哥啊Aaaa

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值