设计模式之代理模式

什么是代理模式?

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

为什么要用代理?

打个比喻,明星为什么需要经纪人来代理他呢?因为明星的专职是唱歌或演戏,如果把除此以外的其他事情比如演出费用谈判等等都揽在身上,他会累死。这就是体现一个思想:专业分工,用面向对象术语说:就是职责分明。
所以,代理类一般是做些除原始类核心功能以外的其他功能,比如权限 事务等等都要专门的代理来实现。

例举一些具体情况: 

  1. 如果那个对象是一个是很大的图片,需要花费很长时间才能显示出来,那么当这个图片包含在文档中时,使用编辑器或浏览器打开这个文档,打开文档必须很迅速,不能等待大图片处理完成,这时需要做个图片Proxy来代替真正的图片。
  2. 如果那个对象在Internet的某个远端服务器上,直接操作这个对象因为网络速度原因可能比较慢,那我们可以先用Proxy来代替那个对象。
  3. 当我们的代码每个类代表一个主要功能,而不是将所有功能混在一个类中,那么代码无疑清晰有条理的,易于维护,比如我要修改权限,就不必打开原始类代码,直接修改权限代理类就可以了。就很少发生修改一个BUG,带来一个新BUG的烦恼问题。

总之原则是,对于开销很大的对象,只有在使用它时才创建,这个原则可以为我们节省很多宝贵的Java内存。

怎么使用代理?

代理模式一般涉及到的角色有:

  1. 抽象角色:声明真实对象和代理对象的共同接口;
  2. 代理角色:代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。
  3. 真实角色:代理角色所代表的真实对象,是我们最终要引用的对象。

下面我们以一个小例子来体验一下简单的代理:

抽象角色:Moveable.java

 

public interface Moveable {
    void move(); 
}

 

真实角色:Tank.java

public class Tank implements Moveable{

    public void move() {
        System.out.println("TanK moving........");
        
    }

}

代理角色:TanktimeProxy.java

public class TanktimeProxy implements Moveable {

    private Moveable t;

    public TanktimeProxy(Moveable t) {
        super();
        this.t = t;
    }

    public void move() {
        long time1 = System.currentTimeMillis();
        System.out.println("time1=" + time1);
        t.move();
        long time2 = System.currentTimeMillis();
        System.out.println("time2=" + time2);
        System.out.println("运行时间为:" + (time2 - time1));

    }

}

在main方法中运行:

public class test {

    public static void main(String[] args) {

        Tank t = new Tank();
        Moveable move = new TanktimeProxy(t);
         
        move.move();

    }

}

运行结果:

从以上代码中可以看出TanktimeProxy实现了对Tank的move()方法运行时间的计算,而TanktimeProxy,Tank都实现了Moveable接口,通过调用TanktimeProxy的move()方法我们可以实现对Tank的move()方法的运行时间的计算,而不用在Tank的move()方法中作任何实现,这就是代理。如果要在Tank的move()方法前后加其他方法,即可在TanktimeProxy的move()方法中的t.move()前后增加相应代码即可。

另外,如果要按照上述的方法使用代理模式,那么真实角色必须是事先已经存在的,并将其作为代理对象的内部属性。但是实际使用时,一个真实角色必须对应一个代理角色,如果大量使用会导致类的急剧膨胀;此外,如果事先并不知道真实角色,该如何使用代理呢?解决这一问题最好的做法是可以通过一个代理类完成全部的代理功能,那么此时就必须使用动态代理完成。

 

那么动态代理又是怎么回事呢?

与静态代理类对照的是动态代理类,动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java 反射机制可以生成任意类型的动态代理类。java.lang.reflect 包中的Proxy类和InvocationHandler 接口提供了生成动态代理类的能力。

动态代理分为两种:

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

JDK动态代理:

JDK动态代理中包含一个类和一个接口: 
InvocationHandler接口: 
public interface InvocationHandler { 
  public Object invoke(Object proxy,Method method,Object[] args) throws Throwable; 

参数说明: 
Object proxy:指被代理的对象。 
Method method:要调用的方法 
Object[] args:方法调用时所需要的参数 

可以将InvocationHandler接口的子类想象成一个代理的最终操作类,替换掉ProxySubject。

Proxy类: 
Proxy类是专门完成代理的操作类,可以通过此类为一个或多个接口动态地生成实现类,此类提供了如下的操作方法: 
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 
                               throws IllegalArgumentException 

参数说明: 
ClassLoader loader:类加载器 
Class<?>[] interfaces:得到全部的接口 
InvocationHandler h:得到InvocationHandler接口的子类实例

Ps:类加载器 
在Proxy类中的newProxyInstance()方法中需要一个ClassLoader类的实例,ClassLoader实际上对应的是类加载器,在Java中主要有一下三种类加载器; 
Booststrap ClassLoader:此加载器采用C++编写,一般开发中是看不到的; 
Extendsion ClassLoader:用来进行扩展类的加载,一般对应的是jrelibext目录中的类; 
AppClassLoader:(默认)加载classpath指定的类,是最常使用的是一种加载器。

下面仍然用一个小例子来体验一下:

 

 BookFacade.java

public interface BookFacade {
    public void addBook();

}

BookFacadeImpl.java

public class BookFacadeImpl implements BookFacade {

    public void addBook() {
        System.out.println("增加图书方法。。。");

    }

}

BookFacadeProxy.java

public class BookFacadeProxy implements InvocationHandler {
    private Object target;

    /**
     * 绑定委托对象并返回一个代理类
     * 
     * @param target
     * @return
     */
    public Object bind(Object target) {
        this.target = target;
        // 取得代理对象
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(), this); // 要绑定接口(这是一个缺陷,cglib弥补了这一缺陷)
    }

    public Object invoke(Object proxy, Method method, Object[] arg)
            throws Throwable {
        Object result = null;
        System.out.println("事物开始");
        // 执行方法
        result = method.invoke(target, arg);
        System.out.println("事物结束");
        return result;
    }

}

test.java

public class test {

    public static void main(String[] args) {

        BookFacadeProxy proxy = new BookFacadeProxy();
        BookFacade bookProxy = (BookFacade) proxy.bind(new BookFacadeImpl());
        bookProxy.addBook();

    }

}

运行结果:

但是,JDK的动态代理依靠接口实现,如果有些类并没有实现接口,则不能使用JDK代理,这就要使用cglib动态代理了。

Cglib动态代理

JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理,cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。(在这里,我们暂时不讨论……)

 本文参考并使用了http://www.cnblogs.com/jqyp/archive/2010/08/20/1805041.html的描述及代码。

原文地址:https://www.cnblogs.com/scetopcsa/p/3834562.html