设计模式笔记——代理模式(Proxy Pattern)

一、引言

在软件开发过程中,有些对象有时候会由于网络或其他的障碍,以至于不能够或者不能直接访问到这些对象,如果直接访问对象给系统带来不必要的复杂性,这时候可以在客户端和目标对象之间增加一层中间层,让代理对象代替目标对象,然后客户端只需要访问代理对象,由代理对象去帮我们去请求目标对象并返回结果给客户端,这样的一个解决思路就是今天要介绍的代理模式。

二、代理模式的详细介绍

代理模式按照使用目的可以分为以下几种:

  • 远程(Remote)代理:为一个位于不同的地址空间的对象提供一个局域代表对象。这个不同的地址空间可以是本电脑中,也可以在另一台电脑中。最典型的例子就是——客户端调用Web服务或WCF服务。
  • 虚拟(Virtual)代理:根据需要创建一个资源消耗较大的对象,使得对象只在需要时才会被真正创建。
  • Copy-on-Write代理:虚拟代理的一种,把复制(或者叫克隆)拖延到只有在客户端需要时,才真正采取行动。
  • 保护(Protect or Access)代理:控制一个对象的访问,可以给不同的用户提供不同级别的使用权限。
  • 防火墙(Firewall)代理:保护目标不让恶意用户接近。
  • 智能引用(Smart Reference)代理:当一个对象被引用时,提供一些额外的操作,比如将对此对象调用的次数记录下来等。
  • Cache代理:为开销大的运算结果提供暂时存储,它允许多个客户共享结果,以减少计算或网络延迟。

在哦上面所有种类的代理模式中,虚拟代理、远程代理、智能引用代理和保护代理较为常见的代理模式。下面让我们具体看看代理模式的具体定义。

2.1 定义

代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

2.2 代理模式实现

看完代理模式的描述之后,下面以一个生活中的例子来解释下代理模式,在现实生活中,如果有同事出国或者朋友出国的情况下,我们经常会拖这位朋友帮忙带一些电子产品或化妆品等东西,这个场景中,出国的朋友就是一个代理,他(她)是他(她)朋友的一个代理,由于他朋友不能去国外买东西,他却可以,所以朋友们都托他帮忙带一些东西的。下面就以这个场景来实现下代理模式,具体代码如下:

复制代码

  // 客户端调用
    class Client
    {
        static void Main(string[] args)
        {
            // 创建一个代理对象并发出请求
            Person proxy = new Friend();
            proxy.BuyProduct();
            Console.Read();
        }
    }

    // 抽象主题角色
    public abstract class Person
    {
        public abstract void BuyProduct();
    }

    //真实主题角色
    public class RealBuyPerson : Person
    {
        public override void BuyProduct()
        {
            Console.WriteLine("帮我买一个IPhone和一台苹果电脑");
        }
    }

    // 代理角色
    public class Friend:Person
    {
        // 引用真实主题实例
        RealBuyPerson realSubject;

        public override void BuyProduct()
        {
            Console.WriteLine("通过代理类访问真实实体对象的方法");
            if (realSubject == null)
            {
                realSubject = new RealBuyPerson();
            }

            this.PreBuyProduct();
            // 调用真实主题方法
            realSubject.BuyProduct();
            this.PostBuyProduct();
        }

        // 代理角色执行的一些操作
        public void PreBuyProduct()
        {
            // 可能不知一个朋友叫这位朋友带东西,首先这位出国的朋友要对每一位朋友要带的东西列一个清单等
            Console.WriteLine("我怕弄糊涂了,需要列一张清单,张三:要带相机,李四:要带Iphone...........");
        }
        
        // 买完东西之后,代理角色需要针对每位朋友需要的对买来的东西进行分类
        public void PostBuyProduct()
        {
            Console.WriteLine("终于买完了,现在要对东西分一下,相机是张三的;Iphone是李四的..........");
        }
    }

复制代码

在上面的代码中都有相应的注释,这里也不多解释了。

2.3 代理模式的类图结构

看完代理模式的实现之后,下面就以上面的例子来分析下代理模式的类图结构。具体的类图如下所示:

在上面类图中,代理模式所涉及的角色有三个:

抽象主题角色(Person):声明了真实主题和代理主题的公共接口,这样一来在使用真实主题的任何地方都可以使用代理主题。

代理主题角色(Friend):代理主题角色内部含有对真实主题的引用,从而可以操作真实主题对象;代理主题角色负责在需要的时候创建真实主题对象;代理角色通常在将客户端调用传递到真实主题之前或之后,都要执行一些其他的操作,而不是单纯地将调用传递给真实主题对象。例如这里的PreBuyProduct和PostBuyProduct方法就是代理主题角色所执行的其他操作。

真实主题角色(RealBuyPerson):定义了代理角色所代表的真是对象。

附:在实际开发过程中,我们在客户端添加服务引用的时候,在客户程序中会添加一些额外的类,在客户端生成的类扮演着代理主题角色,我们客户端也是直接调用这些代理角色来访问远程服务提供的操作。这个是远程代理的一个典型例子。

三、代理模式的优缺点

全面分析完代理模式之后,让我们看看这个模式的优缺点:

优点:

  1. 代理模式能够将调用用于真正被调用的对象隔离,在一定程度上降低了系统的耦合度;
  2. 代理对象在客户端和目标对象之间起到一个中介的作用,这样可以起到对目标对象的保护。代理对象可以在对目标对象发出请求之前进行一个额外的操作,例如权限检查等。

缺点:

  1.  由于在客户端和真实主题之间增加了一个代理对象,所以会造成请求的处理速度变慢
  2. 实现代理类也需要额外的工作,从而增加了系统的实现复杂度。

五、总结

到这里,代理模式的介绍就结束了,代理模式提供了对目标对象访问的代理。并且到这里,结构型模式的介绍也结束了,结构型模式包括:适配器模式桥接模式装饰者模式组合模式外观模式享元模式和代理模式,下面开始介绍行为型模式的第一个模式:模板方法模式。

java

java代理知识:

静态代理是指预先确定了代理与被代理者的关系,

动态代理是代理与被代理人的关系是动态确定的

代理模式的关键点是:代理对象与目标对象.代理对象是对目标对象的扩展,并会调用目标对象

静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类

动态代理不需要实现接口,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型),也叫jdk代理

Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展.

在java的动态代理机制中,有两个重要的类或接口,一个是InvocationHandler接口、另一个则是 Proxy类,这个类和接口是实现我们动态代理所必须用到的。
JDK实现代理只需要使用Proxy.newProxyInstance方法,但是该方法需要接收三个参数,完整的写法是:

static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )

注意该方法是在Proxy类中是静态方法,且接收的三个参数依次为:

  • ClassLoader loader,:指定当前目标对象使用类加载器,获取加载器的方法是固定的
  • Class<?>[] interfaces,:目标对象实现的接口的类型,使用泛型方式确认类型
  • InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入

Cglib子类代理实现方法:
1.需要引入cglib的jar文件,但是Spring的核心包中已经包括了Cglib功能,所以直接引入pring-core-3.2.5.jar即可.
2.引入功能包后,就可以在内存中动态构建子类
3.代理的类不能为final,否则报错
4.目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.

cgLib的动态代理原理
CGLIB原理:动态生成一个要代理类的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快。

CGLIB底层:使用字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。

CGLIB缺点:对于final方法,无法进行代理。

动态代理在AOP中的应用
什么是AOP? 维基百科上如是说:

定义:In computing, aspect-oriented programming (AOP) is a programming paradigm that aims to increase modularity by allowing the separation of cross-cutting concerns. 
AOP是一种编程范式,其目标是通过隔离切面耦合来增加程序的模块化。

首先声明,AOP是OOP的补充,其地位及其重要性远不及OOP,总体来说OOP面向名词领域而AOP面向动词领域,例如对一个人的设计肯定是使用OOP,例如这个人有手,脚,眼睛瞪属性。而对这个人上厕所这个动作就会涉及到AOP,例如上厕所前的先确定一下拿没拿手纸等。要理解AOP就首先要理解什么是切面耦合(cross-cutting concerns)。例如有这样一个需求,要求为一个程序中所有方法名称以test开头的方法打印一句log,这个行为就是一个典型的cross-cutting场景。首先这个打印log和业务毫无关系,然后其处于分散在整个程序当中的各个模块,如果按照我们原始的方法开发,一旦后期需求变动将是及其繁琐的。所以我们就需要将这个切面耦合封装隔离,不要将其混入业务代码当中。

例如在王二狗的案子中,我们希望在案子起诉后打印一句成功的log,如果不使用代理的话,我们是需要将log写在相应的业务逻辑里面的,例如王二狗诉讼类SecondDogWang里面的submit()方法中。使用了动态代理后,我们只需要在InvocationHandler 里面的invoke()方法中写就可以了,不会侵入业务代码当中,在以后的维护过程中对业务毫无影响,这是我们希望看到的。

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (method.getName().equals("submit"))
           System.out.println("案件提交成功,证据如下:"+ Arrays.asList(args));
    Object result=method.invoke(target,args);
    return result;
}


 
输出结果为:

案件提交成功,证据如下:[工资流水在此]
老板欠薪跑路,证据如下:工资流水在此
铁证如山,马旭还牛翠花血汗钱

所以AOP主要可以用于:日志记录,性能统计,安全控制,事务处理,异常处理等场景下。

 java 代码实现:

静态代理是指预先确定了代理与被代理者的关系,例如王二狗的代理律师方文镜是在开庭前就确定的了。那映射到编程领域的话,就是指代理类与被代理类的依赖关系在编译期间就确定了。下面就是王二狗劳动仲裁的代码实现:

首先定义一个代表诉讼的接口

public interface ILawSuit {
    void submit(String proof);//提起诉讼
    void defend();//法庭辩护
}
王二狗诉讼类型,实现ILawSuit接口

public class SecondDogWang implements ILawSuit {
    @Override
    public void submit(String proof) {
        System.out.println(String.format("老板欠薪跑路,证据如下:%s",proof));
    }

    @Override
    public void defend() {
        System.out.println(String.format("铁证如山,%s还钱","马旭"));
    }
}
代理律师诉讼类,实现ILawSuit接口

public class ProxyLawyer implements ILawSuit {

    ILawSuit plaintiff;//持有要代理的那个对象
    public ProxyLawyer(ILawSuit plaintiff) {
        this.plaintiff=plaintiff;
    }

    @Override
    public void submit(String proof) {
        plaintiff.submit(proof);
    }

    @Override
    public void defend() {
        plaintiff.defend();
    }
}
产生代理对象的静态代理工厂类

public class ProxyFactory {
    public static ILawSuit getProxy(){
        return new ProxyLawyer(new SecondDogWang());
    }
}

这样就基本构建了静态代理关系了,然后在客户端就可以使用代理对象来进行操作了。

    public static void main(String[] args) {
        ProxyFactory.getProxy().submit("工资流水在此");
        ProxyFactory.getProxy().defend();
    }


输出结果如下:

老板欠薪跑路,证据如下:工资流水在此
铁证如山,马旭还钱

动态代理:

牛翠花动态指定代理律师是如何实现的。 
1.构建一个牛翠花诉讼类

public class CuiHuaNiu implements ILawSuit {
    @Override
    public void submit(String proof) {
        System.out.println(String.format("老板欠薪跑路,证据如下:%s",proof));
    }
    @Override
    public void defend() {
        System.out.println(String.format("铁证如山,%s还牛翠花血汗钱","马旭"));
    }
}

2.构建一个动态代理类

public class DynProxyLawyer implements InvocationHandler {
    private Object target;//被代理的对象
    public DynProxyLawyer(Object obj){
        this.target=obj;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("案件进展:"+method.getName());
        Object result=method.invoke(target,args);
        return result;
    }
}

12
3.修改静态工厂方法

public class ProxyFactory {
    ...

    public static Object getDynProxy(Object target) {
        InvocationHandler handler = new DynProxyLawyer(target);
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler);
    }

4.客户端使用

    public static void main(String[] args) {
        ILawSuit proxy= (ILawSuit) ProxyFactory.getDynProxy(new CuiHuaNiu());
        proxy.submit("工资流水在此");
        proxy.defend();
    }

输出结果为:

cgLib的动态代理实现
由于JDK只能针对实现了接口的类做动态代理,而不能对没有实现接口的类做动态代理,所以cgLib横空出世!CGLib(Code Generation Library)是一个强大、高性能的Code生成类库,它可以在程序运行期间动态扩展类或接口,它的底层是使用java字节码操作框架ASM实现。

1 引入cgLib 库 
cglib-nodep-3.2.6.jar:使用nodep包不需要关联asm的jar包,jar包内部包含asm的类.

2 定义业务类,被代理的类没有实现任何接口

public class Frank {
   public void submit(String proof) {
       System.out.println(String.format("老板欠薪跑路,证据如下:%s",proof));
   }
   public void defend() {
       System.out.println(String.format("铁证如山,%s还Frank血汗钱","马旭"));
   }
}

3 定义拦截器,在调用目标方法时,CGLib会回调MethodInterceptor接口方法拦截,来实现你自己的代理逻辑,类似于JDK中的InvocationHandler接口。

public class cgLibDynProxyLawyer implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] params, MethodProxy methodProxy) throws Throwable {
        if (method.getName().equals("submit"))
            System.out.println("案件提交成功,证据如下:"+ Arrays.asList(params));
        Object result = methodProxy.invokeSuper(o, params);
        return result;
    }
}

4定义动态代理工厂,生成动态代理

public class ProxyFactory {
    public static Object getGcLibDynProxy(Object target){
        Enhancer enhancer=new Enhancer();
        enhancer.setSuperclass(target.getClass());
        enhancer.setCallback(new cgLibDynProxyLawyer());
        Object targetProxy= enhancer.create();
        return targetProxy;
    }
}

5客户端调用

  public static void main(String[] args) {
        Frank cProxy= (Frank) ProxyFactory.getGcLibDynProxy(new Frank());
        cProxy.submit("工资流水在此");
        cProxy.defend();
    }


输出结果:

案件提交成功,证据如下:[工资流水在此]
老板欠薪跑路,证据如下:工资流水在此
铁证如山,马旭还Frank血汗钱

个人java总结:

 静态代理实现 要继承同一父类或实现同一接口。

动态代理 代理类 实现InvocationHandler事件处理器的invoke方法,使用Proxy.newProxyInstance调用

cgLib 实现MethodInterceptor的intercept方法,Enhancer的setSuperclass设置父类,setCallback方法设置事件处理,然后create创建

 参考文献:

http://www.cnblogs.com/zhili/p/ProxyPattern.html

https://blog.csdn.net/ShuSheng0007/article/details/80864854

https://www.cnblogs.com/cenyu/p/6289209.html

  

猜你喜欢

转载自blog.csdn.net/qq_25744257/article/details/84964321