大白话代理模式

代理模式是非常重要的设计模式,十分常见。本文旨在理解代理模式入门,后面到了Spring框架会再深入。



一.写在前

开发中,经常会遇到增强某个对象的功能。

比如Web的Filter过滤器组件,在过滤敏感词汇的场景下,用户在发送带有敏感词汇的请求时,需要更换掉词汇。而request对象中没有设置请求参数的API,这时候就需要过滤器对request对象进行增强,过滤掉词汇,并返回一个新request对象。

该案例在另一篇博文中: Filter案例:登录验证、敏感词汇过滤.

而如何增强对象的功能?程序员前辈们已经帮我们思考、总结好了一系列套路,我们直接学习使用即可。这些套路就称为:设计模式,一些通用的解决固定问题的方式。当然它包含了解决许多场景下的问题,不局限于增强对象的功能。

软件设计模式(Design pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。一共有23种设计模式。

两种设计模式都可以来完成对象的功能增强:

1.装饰器模式

意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。

2.代理模式

在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。

意图:为其他对象提供一种代理以控制对这个对象的访问。

代理模式相对于装饰器模式来说,更加灵活一些,本文主要来理解代理模式。

二.代理模式

先举个例子:
在这里插入图片描述
假如西安有用户想买一台联想电脑,由于联想公司在北京生产电脑和卖电脑,所以该用户需要前往北京联想公司进行购买电脑,这种卖电脑的能力就比较弱,局限于北京了。

现在西安有了联想电脑的代理商,代理商也可以售卖联想电脑,用户直接就可以在西安购买到联想电脑了。但是代理商真正具备卖电脑的功能吗?显然不是,代理商也得从北京的联想公司进货再进行售卖。

这里的代理商就相当于是联想公司的一个代理对象,用户调用对象的时候就只需要调用代理对象,而代理对象就会具体去调用真正的对象的方法或功能了。这就是代理模式,在卖电脑例子,可以比较容易体会到代理增强了联想公司卖电脑的能力,用户也不需要跨地域购买电脑、节约路费、省事省力。


需要理解的概念:

  1. 真实对象:被代理的对象,比如例子里的联想公司;
  2. 代理对象:代理真实对象的对象,比如西安代理商;
  3. 代理模式:代理对象代理真实对象,达到增强真实对象功能的目的;

代理模式实现方式有:

  • 静态代理
  • 动态代理

1.静态代理

静态代理:有一个类,使用一个类文件描述代理模式。

1.1 实现步骤

静态代理的实现就比较简单了,步骤

  1. 创建一个接口,然后创建真实类实现该接口并且实现该接口中的抽象方法。
  2. 再创建一个代理类,同时使其也实现这个接口。
  3. 在代理类中持有一个被代理对象,即真实对象的引用,而后在代理类方法中调用该对象的方法。

静态代理的实现:以上述的卖联想电脑为例

接口:

SaleComputer.java

/**
 * 共同实现的接口
 */
public interface SaleComputer {

    /**
     * 卖电脑方法
     * @param money
     * @return
     */
    public String sale(double money);

    /**
     * show方法
     */
    public void show();
}

被代理类:

Lenovo.java

/**
 * 真实类
 */
public class Lenovo implements SaleComputer {
    @Override
    public String sale(double money) {

        System.out.println("花了"+money+"元买了一台联想电脑...");
        return "联想电脑";
    }

    @Override
    public void show() {
        System.out.println("展示电脑....");
    }
}

代理类:

StaticProxy.java

public class StaticProxy implements SaleComputer {

    //1.被代理对象的引用
    private SaleComputer saleComputer = new Lenovo();

    @Override
    public String sale(double money) {
        //简单的功能增强
        System.out.println("Before invoke...." );
        //2.在代理类方法中调用真实对象的方法。
        String str = saleComputer.sale(money);
        System.out.println("After invoke......");
        return str;
    }

    @Override
    public void show() {
        //2.在代理类方法中调用真实对象的方法。
        saleComputer.show();
    }
}

代理类调用:

被代理类被传递给了代理类Lenovo,代理类在执行具体方法时通过所持用的被代理类对象完成调用。

/**
 * 静态代理测试类
 */
public class Test {
    public static void main(String[] args) {
        StaticProxy staticProxy = new StaticProxy();
        staticProxy.sale(8000);
        staticProxy.show();
    }
}

运行程序,控制台打印:

在这里插入图片描述
使用静态代理很容易就完成了对一个类的代理操作。但是静态代理的缺点也暴露了出来:由于代理只能为一个类服务,如果需要代理的类很多,那么就需要编写大量的代理类,比较繁琐。

一般很少使用静态代理,重点理解下面的动态代理。

2.动态代理

动态代理:在内存中动态形成代理类,在开发中使用的更多。利用反射机制在运行时创建代理类。

2.1 实现步骤

步骤:

  1. 代理对象真实对象实现相同的接口,兄弟关系。
  2. 代理对象 = Proxy.newProxyInstance();,Proxy是JDK提供的类。
  3. 使用代理对象调用方法。
  4. 增强方法

生成代理对象是用的Proxy类的静态方方法newProxyInstance,我们了解一下这个方法:

  • Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h);,方法返回是代理对象
  • 三个参数值写法比较固定:
    • 1.类加载器:真实对象.getClass().getClassLoader(),代理对象代理的真实对象

    • 2.接口数组:真实对象.getClass().getInterfaces(),代理的真实对象实现的接口

    • 3.处理器:new InvocationHandler(),核心业务逻辑的处理

      • 真正完成代理逻辑的是处理器中的invoke方法,我们重写该方法实现对象的功能增强。

      • /*
            代理逻辑编写的方法:代理对象调用的所有方法都会触发该方法执行
                参数:
                    1. proxy:就是代理对象,很少使用这个对象
                    2. method:代理对象调用的方法,方法被封装成对象了
                    3. args:代理对象调用的方法时,传递的实际参数列表
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {}
        

动态代理的基本实现:以上述的卖联想电脑为例

在这里插入图片描述

共同实现的接口:SaleComputer.java

/**
 * 共同实现的接口
 */
public interface SaleComputer {

    /**
     * 卖电脑方法
     * @param money
     * @return
     */
    public String sale(double money);

    /**
     * show方法
     */
    public void show();
}

真实对象类:Lenovo.java

/**
 * 真实类
 */
public class Lenovo implements SaleComputer {
    @Override
    public String sale(double money) {
        System.out.println("花了"+money+"元买了一台联想电脑...");
        return "联想电脑";
    }

    @Override
    public void show() {
        System.out.println("展示电脑....");
    }
}

代理测试类:ProxyTest.java

不使用代理模式,直接调用真实对象的方法的情况:

public class ProxyTest {
    public static void main(String[] args) {
        //1.创建真实对象
        Lenovo lenovo = new Lenovo();
        //2.获取代理对象,动态代理真实对象

        //3.调用对象方法
        String computer = lenovo.sale(8000);
        System.out.println(computer);
        lenovo.show();
    }
}

运行程序,控制台打印:

在这里插入图片描述
使用动态代理,使用代理对象来调用方法的情况,现在并未进行对象功能的增强,只是动态代理的基本实现,后面再进行增强:

public class ProxyTest {
    public static void main(String[] args) {
        //1.创建真实对象
        Lenovo lenovo = new Lenovo();
        //2.获取代理对象,动态代理增强真实对象
        SaleComputer Proxy_lenovo = (SaleComputer)Proxy.newProxyInstance(lenovo.getClass().getClassLoader(), lenovo.getClass().getInterfaces(), new InvocationHandler() {
            /*
                代理逻辑编写的方法:代理对象调用的所有方法都会触发该方法执行
                    参数:
                        1. proxy:代理对象,就是Proxy_lenovo,很少使用这个对象
                        2. method:代理对象调用的方法,方法被封装成对象了
                        3. args:代理对象调用的方法时,传递的实际参数列表
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //验证invoke方法执行了
                System.out.println("该方法执行了....");
                //打印调用对象的方法名称
                System.out.println(method.getName());
                //代理对象调用的方法时,传递的实际参数
                System.out.println(args[0]);

                //返回值是代理对象调用方法后,接收的返回值
                return null;
            }
        });

        //3.调用对象方法
        String computer = Proxy_lenovo.sale(8000);
        System.out.println(computer);
    }
}

运行程序,控制台打印:

在这里插入图片描述
通过上面控制台打印,可以发现,虽然代理对象调用了sale()方法,但并没有执行真实对象的sale()方法内容。

也证实前面例子中【代理商本质上不具备卖电脑的功能,需要去联想公司进货】。所以还是需要使用真实对象调用方法。

在哪里调用?其实就是在invoke()方法中,这个方法的参数有method对象method对象可以执行invoke()方法,这里从反射机制来理解。谁执行method对象真实对象执行method,并且传递代理对象调用方法的参数。

真正完成代理的情况:

public class ProxyTest {
    public static void main(String[] args) {
        //1.创建真实对象
        Lenovo lenovo = new Lenovo();
        //2.获取代理对象,动态代理增强真实对象
        SaleComputer Proxy_lenovo = (SaleComputer) Proxy.newProxyInstance(lenovo.getClass().getClassLoader(), lenovo.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                /*System.out.println("invoke方法执行了....");
                System.out.println(method.getName());
                System.out.println(args[0]);*/


                /*
                    使用真实对象调用该方法,方法参数传递:
                    1.lenovo:真实对象来执行方法
                    2.args:代理对象调用方法的参数列表
                */
                Object obj = method.invoke(lenovo, args);
                //返回值是代理对象调用方法后,接收的返回值
                return obj;
            }
        });

        //3.再调用代理对象方法
        String computer = Proxy_lenovo.sale(8000);
        System.out.println(computer);
        Proxy_lenovo.show();
    }
}

运行程序,控制台打印:

在这里插入图片描述
现在可以发现代理对象调用方法和真实对象调用方法,业务逻辑都是一样的了,这里就完成代理的第一步。下面就要实现对真实对象的功能增强了,这也是代理对象的主要目的。

了解了动态代理的实现,我们来进行对象功能的增强。

2.2 增强的方式有三种

对象功能的增强,也可以说是对方法的增强,从方法的三个方面下手:

  1. 增强方法参数列表
  2. 增强方法返回值类型
  3. 增强方法体执行逻辑

还是以卖电脑的例子,有这样一个场景,现在联想公司的西安代理商,想要进行促销、回馈用户,原价8000元的电脑,现在打8折,现在用户只需要花6400元就能购买,还送一个鼠标垫。并且为购买电脑的用户提供专车接收和免费送货上门。

福利有点夸张,下面就用增强的三种方式来实现上述场景:

ProxyTest.java

/**
 * 代理对象测试类
 */
public class ProxyTest {

    public static void main(String[] args) {
        //1.创建真实对象
        Lenovo lenovo = new Lenovo();
        
        //2.获取代理对象,动态代理增强真实对象
        //强制转化为接口类型,两个对象实现了同一接口
        SaleComputer Proxy_lenovo = (SaleComputer) Proxy.newProxyInstance(lenovo.getClass().getClassLoader(), lenovo.getClass().getInterfaces(), new InvocationHandler() {

            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //判断是否是sale方法,有参数的方法才能增强参数
                if(method.getName().equals("sale")){
                    //1.增强参数
                    double money = (double) args[0];
                    money = money / 0.80;

                    //3.1 增强方法体逻辑
                    System.out.println("专车接送....");
                    //使用真实对象调用该方法
                    String obj = (String) method.invoke(lenovo, money);
                    //3.2 增强方法体逻辑
                    System.out.println("免费送货上门...");

                    //2.增强返回值
                    return obj+"_鼠标垫";
                }else{
                    //其它方法原样执行
                    Object obj = method.invoke(lenovo, args);
                    return obj;
                }
            }
        });

        //3.调用代理对象方法
        String computer = Proxy_lenovo.sale(6400);
        System.out.println(computer);
        //其它方法..
        Proxy_lenovo.show();
    }
}

运行程序,控制台打印:

在这里插入图片描述
就实现了对真实对象的sale()方法简单增强。

看到这里,相信小伙伴对动态代理有了比较好理解。


欢迎点赞评论,指出不足,笔者由衷感谢哦!~

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值