设计模式(三)------ 代理模式(静态代理模式、动态代理模式、cgLib代理模式)

 

一、什么是代理?

现实生活中有哪些例子?比如:买房子找中介、打官司找律师等,都是代理模式,都不是你自己直接去干这件事,自己只负责付钱或者出庭,其它前期准备工作及收尾工作交个代理去完成。

代理模式包含三个角色:

  1. 目标对象
  2. 代理对象
  3. 用户

二、代理的好处

编程中有个思想:即开闭原则,对修改关闭,对扩展开放。

代理模式通过代理对象访问目标对象,可以在不修改目标对象的基础上,给目标对象扩展额外的功能操作。

举个现实生活中的例子,例如当你要租房的时候,就可以去找中介帮你完成房源筛选、租房协议办理等事情,而你自己只需要负责给钱签协议既可以了,其余时间该干啥就干啥(你不用改变),给你省下很多时间精力,这就是代理的好处。

在代码世界代理也是一样的作用,我们想增强某个类的功能,不需要直接修改这个类,利用代理模式可以在不改变这个类的基础上 增强这个类的功能。具体的,代理模式有以下应用场景:

  • 远程代理 :为两个不同地址空间对象的访问提供了一种实现机制,可以将消耗资源较多的对象和操作移至性能更好的计算机上,提高系统的整体运行效率。
  • 虚拟代理:通过一个消耗资源较少的对象来代表一个消耗资源较多的对象,可以在一定程度上节省系统的运行开销。
  • 缓冲代理:为某一个操作的结果提供临时缓存存储空间,以便在后续使用中共享这些结果,优化系统性能,缩短执行时间。
  • 保护代理:可以控制对一个对象的访问权限,为不同用户提供不同级别的使用权限。
  • 智能引用:要为一个对象的访问(引用)提供一些额外的操作时可以使用。

三、静态代理

什么是静态代理?

举个例子:王富贵的丈母娘告诉他想娶自己女儿就要买房,否则别想娶自己的小棉袄,王富贵一下就慌了,丈母娘突然来这一手自己一点准备都没有,房子在哪买、买什么样的、每个地区房价怎样、交通、教育、医疗、保值等一点头绪都没有,于是王富贵儿准备去找房产中介张狗蛋帮忙,让中介替自己完成以上繁琐费时的找房任务,而自己坐等有满意的房子直接交钱签字就行了。

静态代理就是目标类和代理类的关系在编译时已经确定,例如现在王富贵已经委托张狗蛋来代理自己去找房子,事先已经确定。

1、定义代表买房的接口

public interface BuyHouse {
    // 付钱
    void pay ();
}

2、目标类/被代理类:王富贵

public class FuGuiWang implements BuyHouse{

    @Override
    public void pay() {
        System.out.println("我是王富贵,张狗蛋找的房子我很满意,我要付钱了。");
    }
}

3、代理类:张狗蛋

public class GouDanZhangProxy implements BuyHouse{
    // 持有被代理对象(目标对象)
    private FuGuiWang fuGuiWang;
    public GouDanZhangProxy(FuGuiWang fuGuiWang) {
        this.fuGuiWang = fuGuiWang;
    }

    @Override
    public void pay() {
        System.out.println("中介找房源!");
        fuGuiWang.pay();
        System.out.println("中介处理后续事宜...");
    }
}

4、测试类与结果

public class TestStatic {
    public static void main(String[] args) {
        FuGuiWang fuGuiWang = new FuGuiWang();
        GouDanZhangProxy gouDanZhangProxy = new GouDanZhangProxy(fuGuiWang);
        gouDanZhangProxy.pay();
    }
}
中介找房源!
我是王富贵,张狗蛋找的房子我很满意,我要付钱了。
中介处理后续事宜...

上面的实例可以看出,可以在不改变FuGuiWang目标类的基础上,为它增加“找房源!”和“处理后续事宜...”的功能,这就是代理的作用。

但仔细观察上面代码,可以看到静态代理中目标类和代理类要实现同一个接口,这会带来一个缺陷:试想下,若王富贵的同事赵铁柱也要买房,采用静态代理的话就需要再增加两个类:一个是赵铁柱类,一个是赵铁柱的代理类,这样会使得代码变得臃肿,重用性很差。动态代理可以避免这样的问题。

四、动态代理

静态代理是目标对象和代理对象的关系是一开始就确定的,而动态代理中,目标对象和代理对象的关系是动态确定的。

动态代理的实现有两种方式:

  1. JDK动态代理
  2. Cglib动态代理

先看JDK动态代理实例:

1、定义代表买房的接口

public interface BuyHouse {
    // 付钱
    void pay ();
}

2、目标类/被代理类:王富贵

public class FuGuiWang implements BuyHouse{

    @Override
    public void pay() {
        System.out.println("我是王富贵,张狗蛋找的房子我很满意,我要付钱了。");
    }
}

3、增强类:要增加的功能

public class ExtraWork {
    public static void findHouse(){
        System.out.println("找房源!");
    }

    public static void other(){
        System.out.println("处理后续事宜...");
    }
}

4、合并所有功能,按流程串起来

public class PackageWork implements InvocationHandler {
    // 持有被代理对象
    private Object target;
    public void setTarget(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        ExtraWork.findHouse();
        method.invoke(target, args);
        ExtraWork.other();
        return null;
    }
}

5、获取代理对象

public class ProxyFactory {
    public static Object getProxy(Object target){
        PackageWork packageWork = new PackageWork();
        packageWork.setTarget(target);
        Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), packageWork);
        return proxy;
    }
}

 6、测试与结果

public class TestDynamic {
    public static void main(String[] args) {
        FuGuiWang fuGuiWang = new FuGuiWang();
        BuyHouse proxy = (BuyHouse) ProxyFactory.getProxy(fuGuiWang);
        proxy.pay();
    }
}
找房源!
我是王富贵,张狗蛋找的房子我很满意,我要付钱了。
处理后续事宜...

上面的JDK动态代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能使用动态代理,因此这也算是一种缺陷,若想代理没有实现接口的类,就需要使用Cglib实现.

五、 Cglib动态代理

上面的静态代理和动态代理模式有个相同点就是都要求目标对象是实现一个接口的对象,然而并不是任何对象都会实现一个接口,也存在没有实现任何的接口的对象,这时就可以使用继承目标类以目标对象子类的方式实现代理,这种方法就叫做:Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。

使用Cglib动态代理需要导入下面两个包:

目标类: 

public class FuGuiWang {
    public void pay() {
        System.out.println("我是王富贵,张狗蛋找的房子我很满意,我要付钱了。");
    }
}

代理类工厂:

public class CglibProxyFactory implements MethodInterceptor {
    // 持有被代理对象(目标对象)
    private Object fuGuiWang;
    public CglibProxyFactory(FuGuiWang fuGuiWang) {
        this.fuGuiWang = fuGuiWang;
    }

    // 为目标对象创建代理对象
    public Object getProxyInstance(){
        //1.工具类
        Enhancer en = new Enhancer();
        //2.设置父类
        en.setSuperclass(fuGuiWang.getClass());
        //3.设置回调函数
        en.setCallback(this);
        //4.创建子类(代理对象)
        return en.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("找房源!");
        // 反射执行目标对象的方法
        Object result = method.invoke(fuGuiWang, args);
        System.out.println("处理后续事宜...");
        return result;
    }
}
找房源!
我是王富贵,张狗蛋找的房子我很满意,我要付钱了。
处理后续事宜...

这里目标类没有实现任何接口,它的代理类实际是自己的子类,由这个代理子类完成任务。

CGLIB原理:动态生成一个要代理类的子类,子类重写要代理的类的所有不是final的方法(因此cglib不能代理final方法)。在子类中实现了MethodInterceptor接口,可以拦截所有父类(目标类)方法的调用,在调用方法时还可以添加好切面,完成完整的功能,它比使用java反射的JDK动态代理要快。

六、Spring的AOP中的动态代理

动态代理完成了在不修改目标类的条件下,增强目标的功能,这点和Spring中的AOP思想是贴合的,那到底用的是哪种代理呢?下面是AOP的部分源码:

可以看出,在Spring的AOP编程中两种动态代理都用了:如果加入容器的目标对象有实现接口,用JDK代理;如果目标对象没有实现接口,用Cglib代理。

                                                                                                       

                                                                                        --------------希望世界和平,王富贵能搞定丈母娘,抱得美人归!

发布了92 篇原创文章 · 获赞 3 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/weixin_41231928/article/details/104538077
今日推荐