代理模式

1. 什么是设计模式?

  • 设计模式:一些通用的解决固定问题的方式

2. 为什么需要代理模式?

可以在不修改别代理对象代码的基础上,通过扩展代理类,进行一些功能的附加与增强。

3. 什么是代理模式

代理模式作为23种经典设计模式之一,其比较官方的定义为“为其他对象提供一种代理以控制对这个对象的访问”,简单点说就是,之前A类自己做一件事,在使用代理之后,A类不直接去做,而是由A类的代理类B来去做。代理类其实是在之前类的基础上做了一层封装。java中有静态代理、JDK动态代理、CGLib动态代理的方式。静态代理指的是代理类是在编译期就存在的,相反动态代理则是在程序运行期动态生成的。

概念:

  1. 真实对象:被代理的对象
  2. 代理对象:增强真实对象
  3. 代理模式:代理对象代理真实对象,达到增强真实对象功能的目的

4. 实现方式

1. 静态代理

有一个类文件描述代理模式

1. 实现步骤:

  1. 代理对象与真实对象实现相同的接口
  2. 代理对象中创建真实对象
  3. 使用真实对象调用方法
  4. 增强方法

2. 具体实例

接口:

package site.qujq.staticProxy;

/**
 * 代理类和被代理的接口
 * Created by qjq on 2020/2/23 10:51
 */
public interface SaleComputer {
    public String sale(double money);
    public void show();
}

被代理类(联想电脑厂商):

package site.qujq.staticProxy;

import site.qujq.proxy.SaleComputer;

/**
 * 联想电脑厂商
 * Created by qjq on 2020/2/23 10:53
 */
public class Lenovo implements SaleComputer {
    @Override
    public String sale(double money) {
        System.out.println("卖出一台电脑,价值:"+money);
        return "联想电脑";
    }

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

代理类(经销商):

package site.qujq.staticProxy;


/**
 * 代理 / 经销商
 * Created by qjq on 2020/2/23 10:55
 */
public class StaticProxy implements SaleComputer {
    // 真实对象 / 被代理对象
    private Lenovo factory = new Lenovo();
    @Override
    public String sale(double money) {
        money = money*0.8;//经销商从联想买电脑的价格
        String product = factory.sale(8000);//得到的产品
        return product;
    }

    @Override
    public void show() {
        factory.show();//展示的产品
        System.out.println("小新pro13");
        System.out.println("ThinkPad");
        System.out.println("....");
    }
}

在不使用代理的情况下测试:

package site.qujq.staticProxy;

/**
 * Created by qjq on 2020/2/23 13:33
 */
public class Test {
    public static void main(String[] args) {
        Lenovo factory = new Lenovo();
        factory.sale(8000);//相当于工厂直销
        System.out.println("---------------");
        factory.show();
    }
}

结果是:

卖出一台电脑,价值:8000.0
---------------
展示电脑

使用代理的情况下进行测试:

package site.qujq.staticProxy;

/**
 * 使用静态代理测试
 * Created by qjq on 2020/2/23 13:17
 */
public class StaticProxyTest {
    public static void main(String[] args) {
        StaticProxy staticProxy = new StaticProxy();
        String product = staticProxy.sale(10000);
        System.out.println("得到的产品:"+product);
        System.out.println("-------------");
        staticProxy.show();

    }
}

结果是:

卖出一台电脑,价值:8000.0
得到的产品:联想电脑
-------------
展示电脑
小新pro13
ThinkPad
....

对比,使用静态代理和不使用静态代理,可以发现使用了代理之后,可以在被代理方法的执行前或后加入别的代码,实现诸如权限及日志的操作。

但,静态代理也存在一定的问题,如果被代理方法很多,就要为每个方法进行代理,增加了代码维护的成本。有没有其他的方式可以减少代码的维护,那就是动态代理。

2. JDK动态代理

在内存中形成代理类

  • 特点:字节码随用随创建,随用随加载
  • 作用:不修改源码的基础上对方法增强
  • 动态代理分类:
    • 基于接口的动态代理(本节)
    • 基于子类的动态代理(下一小节)

1. 实现步骤:

  1. 代理对象和真实对象实现相同的接口
  2. 代理对象 = Proxy.newProxyInstance();
  3. 使用代理对象调用方法。
  4. 增强方法

2. 增强方式:

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

3. 具体实现

接口:(与静态代理一样)

package site.qujq.staticProxy;

/**
 * 代理类和被代理的接口
 * Created by qjq on 2020/2/23 10:51
 */
public interface SaleComputer {
    public String sale(double money);
    public void show();
}

被代理对象(联想电脑厂商):

package site.qujq.proxy;

/**
 * Created by qjq on 2020/2/23 10:53
 */
public class Lenovo implements SaleComputer {
    @Override
    public String sale(double money) {
        System.out.println("卖出一台电脑,价值:"+money);
        return "联想电脑";
    }

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

代理对象(经销商):

package site.qujq.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * Created by qjq on 2020/2/23 10:55
 */
public class ProxyTest {
    public static void main(String[] args) {
        //1. 创建真实对象
        Lenovo company = new Lenovo();
        //2. 创建动态代理
        /*
        三个参数:
            1. 类加载器:真实对象.getClass().getClassLoader()
            2. 接口数组:真实对象.getClass().getInterfaces()
            3. 处理器:new InvocationHandler(),处理和被代理对象的方法,即方法增强的地方
        */
        SaleComputer proxy_lenovo = (SaleComputer) Proxy.newProxyInstance(company.getClass().getClassLoader(), company.getClass().getInterfaces(),
                new InvocationHandler() {
            /*代理逻辑编写的方法,代理对象调用的所有方法都会触发该方法的执行
            参数:
                1. proxy:代理对象(一般不用)
                2. method:代理对象调用的方法,被封装为的对象
                3. args:代理对象调用的方法时,传入的实际参数
            */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object obj =null;
                System.out.println("这里是代理。。。。");
                if ("sale".equals(method.getName())){
                    //增强参数
                    Double money = (Double) args[0];//用户买电脑付的钱
                    money = money*0.8;//给电脑厂商的钱
                    System.out.println("你被增强啦");
                    //使用compay对象执行此方法,返回值就是真实对象方法的返回值
                    obj = method.invoke(company, money);
                    //增强返回值
                    obj = obj+"_鼠标垫";
                }else {
                    obj = method.invoke(company, args);
                }
                return obj;
            }
        });

        String computer = proxy_lenovo.sale(8000);//用户付钱
        System.out.println(computer);//用户得到的产品
        System.out.println("---------------------");
        proxy_lenovo.show();//展示商品
    }
}

不使用代理的测试和上一节不使用静态代理的测试一样,不再叙述。
测试结果:

这里是代理。。。。
你被增强啦
卖出一台电脑,价值:6400.0
联想电脑_鼠标垫
---------------------
这里是代理。。。。
展示电脑
小新pro13
ThinkPad
....

从上面可以看出代理类是由Proxy这个类通过newProxyInstance方法动态生成的,生成对象后使用“实例调用方法”的方式进行方法调用,那么代理类的被代理类的关系只有在执行这行代码的时候才会生成,因此成为动态代理。

JDK的动态代理也存在不足,即被代理类必须要有实现的接口,如没有接口则无法使用动态代理(从newProxyInstance方法的第二个参数可得知,必须传入被代理类的实现接口),那么需要使用CGLib动态代理。

3. CGLib动态代理

CGLib代理是功能最为强大的一种代理方式,因为其不仅解决了静态代理需要创建多个代理类的问题,还解决了JDK代理需要被代理对象实现某个接口的问题。对于需要代理的类,如果能为其创建一个子类,并且在子类中编写相关的代理逻辑,因为“子类 instanceof 父类”,因而在进行调用时直接调用子类对象的实例,也可以达到代理的效果。CGLib代理的原理实际上是动态生成被代理类的子类字节码,由于其字节码都是按照jvm编译后的class文件的规范编写的,因而其可以被jvm正常加载并运行。这也就是CGLib代理为什么不需要为每个被代理类编写代理逻辑的原因。这里需要注意的是,根据CGLib实现原理,由于其是通过创建子类字节码的形式来实现代理的,如果被代理类的方法被声明final类型,那么CGLib代理是无法正常工作的,因为final类型方法不能被重写。

1)细节

  • 基于子类的动态代理:

    • 涉及的类:Enhancer
    • 提供者:第三方cglib库
  • 如何创建代理对象:

    • 使用Enhancer类中的create方法
  • 创建代理对象的要求:

  • 被代理类不能是最终类

  • create方法的参数:

    • Class:字节码

      它是用于指定被代理对象的字节码。

    • Callback:用于提供增强的代码

      它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。此接口的实现类都是谁用谁写。我们一般写的都是该接口的子接口实现类:MethodInterceptor

2)具体实现

被代理对象(电脑厂商):此时不需要实现接口

package site.qujq.cglibProxy;


/**
 * 电脑生产商
 * Created by qjq on 2020/2/23 12:53
 */
public class Lenovo {
    public String sale(double money) {
        System.out.println("卖出一台电脑,价值:"+money);
        return "联想电脑";
    }

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

代理对象:

package site.qujq.cglibProxy;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * CGLib动态代理
 * Created by qjq on 2020/2/24 9:10
 */
public class CglibProxy {
    public static void main(String[] args) {
        final Lenovo factory = new Lenovo();
        Lenovo proxy_CGLib = (Lenovo) Enhancer.create(factory.getClass(), new MethodInterceptor() {
            /**
             * 执行被代理对象的任何方法都会经过该方法
             *
             * @param proxy
             * @param method
             * @param args        以上三个参数和基于接口的动态代理中invoke方法的参数是一样的
             * @param methodProxy :当前执行方法的代理对象
             * @return 调用方法的返回值
             * @throws Throwable
             */
            @Override
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                Object obj = null;
                System.out.println("这里是CGLib动态代理。。。。");
                if ("sale".equals(method.getName())) {//当是sale方法时
                    //增强参数
                    Double money = (Double) args[0];//用户买电脑付的钱
                    money = money * 0.8;//给电脑厂商的钱
                    System.out.println("你被增强啦");
                    //使用factory对象执行此方法,返回值就是真实对象方法的返回值
                    obj = method.invoke(factory, money);
                    //增强返回值
                    obj = obj + "_鼠标垫";
                } else {//因为这里只有两个方法,所以使用else,也就是指show方法
                    obj = method.invoke(factory, args);//这里执行打印,有无返回值一样
                    System.out.println("小新pro13");
                    System.out.println("ThinkPad");
                    System.out.println("....");
                }
                return obj;
            }
        });
        //测试
        String computer = proxy_CGLib.sale(8000);//用户付钱
        System.out.println(computer);//用户得到的产品
        System.out.println("---------------------");
        proxy_CGLib.show();//展示商品
    }
}

结果是:

这里是CGLib动态代理。。。。
你被增强啦
卖出一台电脑,价值:6400.0
联想电脑_鼠标垫
---------------------
这里是CGLib动态代理。。。。
展示电脑
小新pro13
ThinkPad
....

5.总结

对静态代理、JDK动态代理、CGLib动态代理做一个总结,静态代理的维护成本比较高,有一个被代理类就需要创建一个代理类,而且需要实现相同的接口。动态代理模式和CGLib动态代理的区别是JDK动态代理需要被代理类实现接口,而CGLib则是生成被代理类的子类,要求被代理类不能是final的,因为final类无法被继承。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,001评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,210评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,874评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,001评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,022评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,005评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,929评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,742评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,193评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,427评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,583评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,305评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,911评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,564评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,731评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,581评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,478评论 2 352

推荐阅读更多精彩内容