设计模式——代理模式你真的了解了吗

代理模式

引入:

代理模式是常用的结构性模式之一,当无法直接访问某个对象或访问某个对象困难时,我们可以通过代理对象间接访问,为了保证客户端使用的透明性,我们所被代理对象与代理对象将会实现一个共同的接口。

根据代理模式的使用目的不同,代理模式又可以分为保护代理、远程代理、虚拟代理、缓冲代理等,需求不一样应用场景不同。

在本章节可以学习到代理模式的定义和结构,并且理解代理模式的多种类型的作用以及实现原理

一、代理模式的简述

为什么需要"代理"呢,因为在面向对象编程时,被代理对象可能任务太多了,此时就需要一个助手去帮助他完成一系列事情。

例如:在电子商务的这个背景下,就衍生出一个代购的概念,也就是找人来帮忙购买用品,肯定需要向代购人支付一定的费用。

可能"本人(被代理对象)"事情比较繁忙,这种事让"代理"对象去实现,就减少了"本人"的工作量。

代理模式的定义:

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

Proxy Pattern : Provide a surrogate or placeholder for another object to control access to it

简单来说:代理对象起到的是一个中介作用,去掉客户不能看到的内容和服务,或者添加一些新的功能。

二、代理模式的结构

代理模式的结构比较简单,代理模式包含三个角色:

  • Subject(抽象主题角色) 声明真实对象和代理对象的共同接口,保证在任意地方使用真实对象的地方都可以使用代理对象。
  • Proxy(代理主题角色) 包含对真实对象的引用,从而可以在任意情况下操作真实对象,在真实对象的基础上还可以增加一些新的功能,不仅仅当真实对象的调用者。
  • RealSubject(真实主题角色) 它定义了代理对象所代表的真实对象,在真实对象中实现了真实的业务操作,客户端可以通过代理对象间接调用真实对象中定义的操作。

三、代理模式的实现

提前了解相关概念:

  • 透明性:代理对象和真实对象都实现了共同的接口,因此在测试中完全不必关注调用的究竟是代理对象还是真实对象。无论是直接调用真实对象还是间接对象都可以。在这里代理对象具有"透明性",就像一幅画之间放置了一块玻璃,仍然可以看见画的内容,即使在Main类和Printer之间加入一个PrinterProxy类,也不会有问题
  • 代理与委托 : 代理对象只能代理他能解决的问题。当遇到他不能解决的问题时,还是会"转交"给真实对象去解决。这里的转交就是委托。从PrinterProxy类的print方法中调用real.print方法正好体现出这种委托

现在我们看一段实例:

使用Proxy模式(静态代理)

案例:带名字的打印机,将文字信息显示在控制台上即可

名字 说明
Printer 带名字的打印机类
Printable Printer 和 PrinterProxy共同接口
PrinterProxy 带名字的打印机的类(代理类)
Main 测试程序的类
public interface Printable {
    
    
    void setPrinterName(String name); //设置名字
    String getPrinterName(); //获取名字
    void print(String name); //打印信息
}
/*
    被代理类
 */
public class Printer implements Printable{
    
    
    private String name;

    public Printer() {
    
    
        System.out.println("正在生成实例...");
    }

    public Printer(String name) {
    
    
        this.name = name;
        heavyJob("生成Printer的实例对象 (" + name + " ) ");
    }

    @Override
    public void setPrinterName(String name) {
    
    
        this.name = name;
    }

    @Override
    public String getPrinterName() {
    
    
        return name;
    }

    @Override
    public void print(String name) {
    
    
        System.out.println(name);
    }

    private void heavyJob(String msg){
    
    
        System.out.println(msg);
        for (int i = 0; i < 5; i++){
    
    
            try {
    
    
                //每一次工作 休息一秒
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
        System.out.println("结束...");
    }
}
/*
    代理类
 */
public class PrinterProxy implements Printable{
    
    
    private String name ; //名字
    private Printer real; //被代理对象  真实对象

    public PrinterProxy() {
    
    
    }

    public PrinterProxy(String name) {
    
    
        this.name = name;
    }

    @Override
    public synchronized void setPrinterName(String name) {
    
     //设置名字
        this.name = name;
        if (real != null){
    
    
            this.setPrinterName(name); //同时设置真实对象名字
        }
    }

    @Override
    public String getPrinterName() {
    
    
        return name;
    }

    @Override
    public void print(String name) {
    
    
        realize();
        real.print(name);
    }

    private synchronized void realize(){
    
    
        if (real == null){
    
    
            real = new Printer(name);
        }
    }
}
public class Main {
    
    
    public static void main(String[] args) {
    
    
        Printable p = new PrinterProxy("惠普打印机");
        System.out.println("现在的名字是 : " + p.getPrinterName());

        p.setPrinterName("不知名的打印机");
        System.out.println("现在的名字是 : " + p.getPrinterName());

        p.print("Hello World"); //调用该方法时 才生成Printer类实例 并调用带参构造器初始化对象
    }

详解PrinterProxy类

是一个代理类,实现Printable接口。

name字段中保存了打印机的名字,而real字段保存的是 “被代理人”

不论setPrinterName和getPrinterName方法被调用多少次,都不会生成Printer对象。只有当真正需要真实对象,才会生成Printer实例。

realize方法很简单 当real字段为空时 会使用new Printer来生成Printer类实例。反之

最重要的是,PrinterProxy知道Printer类的存在,Printer并不知晓PrinterProxy的存在。也就是说Printer类不知道自己是被直接调用还是间接调用(PrinterProxy调用)。

四、JDK代理方式

在上述,传统的代理模式中通过客户端通过代理类调用封装好的方法print()的这种方法。如果按这种方法使用代理模式,很显然,代理类和真实主题类都应该是事先存在,代理类的接口和所代理的方法都已明确指定。每一个代理类在经过编译生成一个字节码文件,代理类所实现的接口和方法都是固定的,这样的代理方式称为静态代理

现在我们将介绍动态代理——JDK方式
动态代理:

根据实际需求来动态的创建代理类,同一个代理类能够代理多个真实主题类,并且可以代理不同的方法。尤其注意的是在SpringAOP中就采用了这种方式来实现。

介绍JDK当中相关的类和接口:

1、Proxy类
这个类提供了创建动态代理类和实例对象的方法,它是创建动态代理类的父类,常用的方法有:
a、public static Class<?> getProxyClass(ClassLoader loader,Class<?>… interfaces):该方法用于返回一个Class类型的代理类
在参数中需要指定代理的接口数组
b、public static Object newProxyInstance(ClassLoader loader,Class<?>[]… interfaces,InvocationHandler h):该方法用于返回一个动态创建的代理类实例,方法中第一个参数表示代理类的类加载器,第二个参数interfaces表示代理类所实现的接口列表,第三个参数表示调用处理的程序类

2、InvocationHandler接口
这个接口表示的是处理程序类的实现接口,该接口作为代理对象的调用处理的共父类,每一个代理类的实例都可以提供一个相关的具体调用处理者(该接口的子类)。该接口的声明了如下方法:
public Object invoke(Object proxy,Method moethod,Object[] args)

该方法用于处理对代理类实例的方法调用并返回相应的结果,当一个代理实例中的业务方法被调用时 自动调用该方法。invoke方法包含三个参数,其中第一个参数proxy表示代理类的实例,第二个参数mehtod表示需要代理的方法,第三个参数args表示代理方法的参数数组

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

实例:

类(接口) 描述
UserDao 抽象主题角色
DocumentDao 抽象主题角色
UserDaoImpl 具体主题角色
DocumentDaoImpl 具体主题角色
DaoLogHandler 自定义请求处理程序类
Client 客户端测试类
public interface UserDao {
    
    
    Boolean findUserById(String id);
}
/*
    具体主题角色
 */
public class UserDaoImpl implements UserDao{
    
    
    @Override
    public Boolean findUserById(String id) {
    
    
        if (id.equalsIgnoreCase("张三")){
    
    
            System.out.println("查询ID为:" + id);
            return true;
        }else {
    
    
            System.out.println("查询失败");
            return false;
        }
    }
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Calendar;
import java.util.GregorianCalendar;

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

    public DaoLogHandler() {
    
    
    }

    //有参构造 注入一个需要提供代理的真实主题对象
    public DaoLogHandler(Object object){
    
    
        this.object = object;
    }

    //实现invoke方法 调用在真实主题类中定义的方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
        beforeInvoke();
        Object obj = method.invoke(object, args); //转发调用
        afterInvoke();
        return obj;
    }

    //记录方法调用事件
    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("调用结束");
    }
}
/*
    文档接口 抽象主题类
 */
public interface DocumentDao {
    
    
    Boolean deleteDocumentById(String id);
}
/*
    具体文档类 具体主题角色
 */
public class DocumentDaoImpl implements DocumentDao{
    
    
    @Override
    public Boolean deleteDocumentById(String id) {
    
    
        if (id.equalsIgnoreCase("001")){
    
    
            System.out.println("删除ID是: " + id);
            return true;
        }else {
    
    
            System.out.println("删除失败");
            return false;
        }
    }
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Client {
    
    
    public static void main(String[] args) {
    
    
        InvocationHandler handler = null;
        UserDao dao = new UserDaoImpl();
        handler = new DaoLogHandler(dao);
        UserDao proxy = null;

        //动态创建对象 代理UserDao类型的真实对象
        proxy = (UserDao) Proxy.newProxyInstance
                (UserDao.class.getClassLoader(),new Class[] {
    
    UserDao.class},handler);
        proxy.findUserById("张三");
        System.out.println("------------------------");

        DocumentDao documentDao =  new DocumentDaoImpl();
        handler = new DaoLogHandler(documentDao);
        DocumentDao proxy_new = null;

        proxy_new = (DocumentDao) Proxy.newProxyInstance(
                DocumentDao.class.getClassLoader(),new Class[] {
    
    DocumentDao.class},handler);
        proxy_new.deleteDocumentById("1"); //调用代理对象的业务方法

    }
}

五、小结

通过动态代理可以实现对多个真实主题类的统一代理和控制。

JDK中提供的动态代理只能代理一个或多个接口,如果需要动态代理具体类或抽象类,我们可以使用CGLib(Code Generation Library)工具。这是一个功能强大、性能和质量比较好的代码生成包。

面向接口的时候我们选择JDK的方式实现动态代理

面向类选择CGLib的方式。

猜你喜欢

转载自blog.csdn.net/ChengXuTeng/article/details/124044584