代理模式--结构型

代理模式--结构型 

JDK的动态代理深入解析(Proxy,InvocationHandler)(转)

Spring AOP 实现原理

 

  代理模式是常用的Java 设计模式,它的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。 

按照代理类的创建时期,代理类可分为两种。 
  静态代理类: 
    由程序员创建或由特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。 
  动态代理类:在程序运行时,运用反射机制动态创建而成。

一、静态代理

代理模式涉及的角色:
1:抽象主题角色.声明了代理主题和真实主题的公共接口,使任何需要真实主题的地方都能用代理主题代替.

2:代理主题角色.含有真实主题的引用,从而可以在任何时候操作真实主题,代理主题功过提供和真实主题相同的接口,使它可以随时代替真实主题.代理主题通过持有真实主题的引用,不但可以控制真实主题的创建或删除,可以在真实主题被调用前进行拦截,或在调用后进行某些操作.

3:真实代理对象.定义了代理角色所代表的具体对象.

下面是代理模式的实现类图:            

                               

 结构

Uml图:

代理模式也叫做委托模式,它是一项基本设计技巧。许多其他的模式,如状态模式、策略模式、访问者模式本质上是在更特殊的场合采用了委托模式,而且在日常的应用中,代理模式可以提供非常好的访问控制。在一些著名开源软件中也经常见到它的身影,如Struts2的Form元素映射就采用了代理模式(准确地说是动态代理模式)。我们先看一下类图中的三个角色的定义:
● Subject抽象主题角色
抽象主题类可以是抽象类也可以是接口,是一个最普通的业务类型定义,无特殊要求。
● RealSubject具体主题角色
也叫做被委托角色、被代理角色。它才是冤大头,是业务逻辑的具体执行者。
● Proxy代理主题角色
也叫做委托类、代理类。它负责对真实角色的应用,把所有抽象主题类定义的方法限制委托给真实主题角色实现,并且在真实主题角色处理完毕前后做预处理和善后处理工作。

示例:

代理,指的就是一个角色代表另一个角色采取行动,就象生活中,一个红酒厂商,是不会直接把红酒零售客户的,都是通过代理来完成他的销售业务的.而客户,也不用为了喝红酒而到处找工厂,他只要找到厂商在当地的代理就行了,具体红酒工厂在那里,客户不用关心,代理会帮他处理。根据上图的关系,我们可以用客户买红酒来模拟代理模式的实现, 红酒代理商和红酒厂商都有销售红酒的只能,我们可以为他们定义一个共同的抽象主题角色

package com.dxz.pattern.proxy;

/**
 * 抽象主题角色,定义了真实角色和代理角色的公共接口
 */
public interface SellInterface {
    public Object sell();
}

接着,我们定义真实主题角色(这里就是红酒工厂),它必须实现了SellInterface接口的.

package com.dxz.pattern.proxy;

/**
 * 真实主题角色,这里指红酒工厂角色,它实现了SellInterface接口
 */
public class RedWineFactory implements SellInterface {
    public Object sell() {
        System.out.println("真实主题角色RedWineFactory 被调用了");
        return new Object();
    }
}

 

下面是代理主题角色(这里指红酒代理商),同样,代理主题也必须实现SellInterface接口.

package com.dxz.pattern.proxy;

/**
 * 代理主题角色,这里指红酒代理商.它除了也要实现了sellInterface接口外,还持有红酒厂商RedWineFactory
 * 对象的引用,从而使它能在调用真实主题前后做一些必要处理.
 */

public class RedWineProxy implements SellInterface {
    
    // 持有一个RedWineFactory对象的引用
    private RedWineFactory redWineFactory;
    
    public RedWineProxy(RedWineFactory redWineFactory) {
        this.redWineFactory = redWineFactory;
    }
    
    // 销售总量
    private static int sell_count = 0;

    public Object sell() {
        if (checkUser()) {// 在通过代理主题角色,我们可以在真实主题角色被调用前做一些诸如权限判断的事情
            Object obj = redWineFactory.sell();
            sell_count++;// 同样,在调用后我们也可以执行一些额外的动作.
            return obj;
        } else {
            throw new RuntimeException();
        }
    }

    protected boolean checkUser() {
        // do something
        return true;
    }

    // 接下来看看调用代理对象的代码:
    public static void main(String[] args) {
        SellInterface sell = new RedWineProxy(new RedWineFactory());
        sell.sell();
    }
}

从类图我们可以看出,客户端本来可以直接和目标对象打交道,代理中间加了一个间接层,他们实现的功能是一样的,也没有改变参数。相信大家对上面的类图和代码很熟悉,跟我们平时看别人的博文一样,没有任何区别,下面我们看一下静态代理的优缺点。

优缺点

优点:

1、直观感受,静态代理是实实在在的存在的,我们自己写的。

2、在编译期加入,提前就指定好了谁调用谁,效率高。

缺点

同样,它的优点也成了它致命的缺点。

1、静态代理很麻烦,需要大量的代理类

     当我们有多个目标对象需要代理时,我就需要建立多个代理类,改变原有的代码,改的多了就很有可能出问题,必须要重新测试。

2、重复的代码会出现在各个角落里,违背了一个原则:重复不是好味道,我们应该杜绝一次次的重复。

3、在编译期加入,系统的灵活性差

       我们可以看到代理类的每个方法中,都有记录日志,执行成功或失败的代码,每个方法都重复了一遍,如果我们需要修改的话,并没有比不用静态代理时减少修改的地方,只是不用修改目标类。动态代理很好的为我们解决了这个问题,下面我们看一下动态代理。

 

 

 

二、java对代理模式的支持 ---动态代理
上面的代理,我们强迫代理类RedWineProxy实现了抽象接口SellInterface.这导致我们的代理类无法通用于其他接口,所以不得不为每一个接口实现一个代理类.幸好,java为代理模式提供了支持.
java主要是通过Proxy类和InvocationHandler接口来给实现对代理模式的支持的.


下面用java的代理机制来实现上面的例子

package com.dxz.pattern.proxy;

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

import java.lang.reflect.Proxy;

/**
 * 代理类一定要实现了InvocationHandler接口
 */
public class ProxyHandler implements InvocationHandler {

    private Object proxy_obj;
    ProxyHandler(Object obj) {
        this.proxy_obj = obj;
    }

    public static Object factory(Object obj) {
        Class cls = obj.getClass();
        // 通过Proxy类的newProxyInstance方法来返回代理对象
        return Proxy.newProxyInstance(cls.getClassLoader(),
                cls.getInterfaces(), new ProxyHandler(obj));
    }

    /**
     * 实现InvocationHandler接口的invoke
     */
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        System.out.println("函数调用前被拦截了: " + method);
        if (args != null) {
            // 打印参数列表
            System.out.println("方法有 " + args.length + " 个参数");
            for (int i = 0; i < args.length; i++) {
                System.out.println(args[i]);
            }
        }

        // 利用反射机制动态调用原对象的方法
        Object mo = method.invoke(proxy_obj, args);
        System.out.println("函数调用后进行处理 : " + method);
        return mo;
    }
}

调用方法:

package com.dxz.pattern.proxy;

public class Client {

    // 测试代码
    public static void main(String agr[]) {
        SellInterface si = (SellInterface) ProxyHandler.factory(new RedWineFactory());
        si.sell();
    }

}

执行结果:

函数调用前被拦截了: public abstract java.lang.Object com.dxz.pattern.proxy.SellInterface.sell()
真实主题角色RedWineFactory 被调用了
函数调用后进行处理 : public abstract java.lang.Object com.dxz.pattern.proxy.SellInterface.sell()

通过上面的代码可以看出,代理主题ProxyHandler类并没有实现我们定义的SellInterface接口,而是实现了java的InvocationHandler接口,这样就把代理主题角色和我们的业务代码分离开来,使代理对象能通用于其他接口。

动态代理优缺点

优点:

1、一个动态代理类更加简单了,可以解决创建多个静态代理的麻烦,避免不断的重复多余的代码

2、调用目标代码时,会在方法“运行时”动态的加入,决定你是什么类型,才调谁,灵活

缺点:

1、系统灵活了,但是相比而言,效率降低了,比静态代理慢一点

2、动态代理比静态代理在代码的可读性上差了一点,不太容易理解

3、JDK动态代理只能对实现了接口的类进行代理

静态代理和动态代理的本质区别

1)、静态代理只能通过手动完成代理操作,如果被代理类增加了新的方法,代理类需要同步增加,违背开闭原则

2)、动态代理采用在运行时动态生成代码的方式,取消了对被代理类的扩展限制,遵循开闭原则

3)、若动态代理要对目标类的增强逻辑进行扩展,结合策略模式,只需要新增策略类便可完成,无需修改代理类的代码。

总体来说,代理模式的好处:

1)能将代理对象和真正被调用目标对象分离

2)在一定程度上降低了系统的耦合性,扩展性好

3)可以起到保护目标对象的作用

4)可以增强目标对象的功能

三、InvocationHandler

详细见:JDK的动态代理深入解析(Proxy,InvocationHandler)(转)

 

四、代理模式分类:

代理模式根据其目的和实现方式不同可分为很多种类,其中常用的几种代理模式简要说明如下:

远程代理(Remote Proxy)

给一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中,远程代理又称为大使(Ambassador)。

虚拟代理(Virtual Proxy)

如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。

保护代理(Protect Proxy)

控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。

缓冲代理(Cache Proxy)

为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。

智能引用代理(Smart Reference Proxy)

当一个对象被引用时,提供一些额外的操作,例如将对象被调用的次数记录下来等。

 

五、与其他相关模式

1)适配器模式Adapter :适配器Adapter 为它所适配的对象提供了一个不同的接口。相反,代理提供了与它的实体相同的接口。然而,用于访问保护的代理可能会拒绝执行实体会执行的操作,因此,它的接口实际上可能只是实体接口的一个子集。

2) 装饰器模式Decorator:尽管Decorator的实现部分与代理相似,但Decorator的目的不一样。Decorator为对象添加一个或多个功能,而代理则控制对对象的访问。

总结

代理模式在很多情况下都非常有用,特别是你想强行控制一个对象的时候,比如:延迟加载,监视状态变更的方法等等

1、“增加一层间接层”是软件系统中对许多负责问题的一种常见解决方法。在面向对象系统中,直接使用某些对象会带来很多问题,作为间接层的proxy对象便是解决这一问题的常用手段。

2、具体proxy设计模式的实现方法、实现粒度都相差很大,有些可能对单个对象作细粒度的控制,有些可能对组件模块提供抽象代理层,在架构层次对对象作proxy。

3、proxy并不一定要求保持接口的一致性,只要能够实现间接控制,有时候损及一些透明性是可以接受的。

代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。


代理模式的优缺点

代理模式的优点

  •  职责清晰

    真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件事务,附带的结果就是编程简洁清晰。

  •  高扩展性

    具体主题角色是随时都会发生变化的,只要它实现了接口,甭管它如何变化,都逃不脱如来佛的手掌(接口),那我们的代理类完全就可以在不做任何修改的情况下使用。

  •  智能化

    这在我们以上的讲解中还没有体现出来,不过在我们以下的动态代理章节中你就会看到代理的智能化有兴趣的读者也可以看看Struts是如何把表单元素映射到对象上的。

 

代理模式的缺点

  • 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
  • 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
posted on 2012-05-25 09:24  duanxz  阅读(690)  评论(1编辑  收藏  举报