java设计模式之代理模式

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

 

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

抽象角色:声明真实对象和代理对象的共同接口;

代理角色:代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。

真实角色:代理角色所代表的真实对象,是我们最终要引用的对象。


1. 相关概念

1.1 代理

在某些情况下,我们不希望或是不能直接访问对象 A,而是通过访问一个中介对象 B,由 B 去访问 A 达成目的,这种方式我们就称为代理。
这里对象 A 所属类我们称为委托类,也称为被代理类,对象 B 所属类称为代理类。

代理优点有:

  • 隐藏委托类的实现
  • 解耦,不改变委托类代码情况下做一些额外处理,比如添加初始判断及其他公共操作

根据程序运行前代理类是否已经存在,可以将代理分为静态代理和动态代理。

1.2 静态代理

代理类在程序运行前已经存在的代理方式称为静态代理。
通过上面解释可以知道,由开发人员编写或是编译器生成代理类的方式都属于静态代理,如下是简单的静态代理实例:

class ClassA {
    public void operateMethod1() {};

    public void operateMethod2() {};

    public void operateMethod3() {};
}

public class ClassB {
    private ClassA a;

    public ClassB(ClassA a) {
        this.a = a;
    }

    public void operateMethod1() {
        a.operateMethod1();
    };

    public void operateMethod2() {
        a.operateMethod2();
    };

    // not export operateMethod3()
}

上面ClassA是委托类,ClassB是代理类,ClassB中的函数都是直接调用ClassA相应函数,并且隐藏了ClassoperateMethod3()函数。

静态代理中代理类和委托类也常常继承同一父类或实现同一接口

1.3 动态代理

         代理类在程序运行前不存在、运行时由程序动态生成的代理方式称为动态代理。

Java 提供了动态代理的实现方式,可以在运行时刻动态生成代理类。这种代理方式的一大好处是可以方便对代理类的函数做统一或特殊处理,如记录所有函数执行时间、所有函数执行前添加验证判断、对某个特殊函数进行特殊操作,而不用像静态代理方式那样需要修改每个函数。

2. 动态代理实例

实现动态代理包括三步:

(1). 新建委托类;
(2). 实现InvocationHandler接口,这是负责连接代理类和委托类的中间类必须实现的接口;
(3). 通过Proxy类新建代理类对象。

下面通过实例具体介绍,假如现在我们想统计某个类所有函数的执行时间,传统的方式是在类的每个函数前打点统计,动态代理方式如下:



2.1 新建委托类

public interface Operate {

    public void operateMethod1();

    public void operateMethod2();

    public void operateMethod3();
}

public class OperateImpl implements Operate {

    @Override
    public void operateMethod1() {
        System.out.println("Invoke operateMethod1");
        sleep(110);
    }

    @Override
    public void operateMethod2() {
        System.out.println("Invoke operateMethod2");
        sleep(120);
    }

    @Override
    public void operateMethod3() {
        System.out.println("Invoke operateMethod3");
        sleep(130);
    }

    private static void sleep(long millSeconds) {
        try {
            Thread.sleep(millSeconds);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
Operate是一个接口,定了了一些函数,我们要统计这些函数的执行时间。
OperateImpl是委托类,实现Operate接口。每个函数简单输出字符串,并等待一段时间。
动态代理要求委托类必须实现了某个接口,比如这里委托类OperateImpl实现了Operate

2.2. 实现 InvocationHandler 接口

public class TimingInvocationHandler implements InvocationHandler {

    private Object target;

    public TimingInvocationHandler() {}

    public TimingInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long start = System.currentTimeMillis();
        Object obj = method.invoke(target, args);
        System.out.println(method.getName() + " cost time is:" + (System.currentTimeMillis() - start));
        return obj;
    }
}

函数需要去实现,参数:
proxy表示下面2.3 通过 Proxy.newProxyInstance() 生成的代理类对象
method表示代理对象被调用的函数。
args表示代理对象被调用的函数的参数。

调用代理对象的每个函数实际最终都是调用了InvocationHandlerinvoke函数。这里我们在invoke实现中添加了开始结束计时,其中还调用了委托类对象target的相应函数,这样便完成了统计执行时间的需求。
invoke函数中我们也可以通过对method做一些判断,从而对某些函数特殊处理。


2.3. 通过 Proxy 类静态函数生成代理对象

public class Main {
    public static void main(String[] args) {
        // create proxy instance
        TimingInvocationHandler timingInvocationHandler = new TimingInvocationHandler(new OperateImpl());
        Operate operate = (Operate)(Proxy.newProxyInstance(Operate.class.getClassLoader(), new Class[] {Operate.class},
                timingInvocationHandler));

        // call method of proxy instance
        operate.operateMethod1();
        System.out.println();
        operate.operateMethod2();
        System.out.println();
        operate.operateMethod3();
    }
}

这里我们先将委托类对象new OperateImpl()作为TimingInvocationHandler构造函数入参创建timingInvocationHandler对象;
然后通过Proxy.newProxyInstance(…)函数新建了一个代理对象,实际代理类就是在这时候动态生成的。我们调用该代理对象的函数就会调用到timingInvocationHandlerinvoke函数(是不是有点类似静态代理),而invoke函数实现中调用委托类对象new OperateImpl()相应的 method(是不是有点类似静态代理)。

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

loader表示类加载器
interfaces表示委托类的接口,生成代理类时需要实现这些接口
hInvocationHandler实现类对象,负责连接代理类和委托类的中间类

我们可以这样理解,如上的动态代理实现实际是双层的静态代理,开发者提供了委托类 B,程序动态生成了代理类 A。开发者还需要提供一个实现了InvocationHandler的子类 C,子类 C 连接代理类 A 和委托类 B,它是代理类 A 的委托类,委托类 B 的代理类。用户直接调用代理类 A 的对象,A 将调用转发给委托类 C,委托类 C 再将调用转发给它的委托类 B。

3. 动态代理原理

实际上面最后一段已经说清了动态代理的真正原理。我们来仔细分析下

3.1 生成的动态代理类代码

下面是上面示例程序运行时自动生成的动态代理类代码,如何得到这些生成的代码请见ProxyUtils,查看 class 文件可使用 jd-gui


// 动态代理的具体实现

2、代码实现

[java] view plain copy
  1. /** 
  2.  * 示例(二):代理模式 --动态代理 
  3.  *  
  4.  * 以添加用户为例 
  5.  */  
  6. class User {  
  7.     private String username;  
  8.     private String password;  
  9.   
  10.     public User() {  
  11.     }  
  12.   
  13.     public User(String username, String password) {  
  14.         this.username = username;  
  15.         this.password = password;  
  16.     }  
  17.   
  18.     public String getUsername() {  
  19.         return username;  
  20.     }  
  21.   
  22.     public void setUsername(String username) {  
  23.         this.username = username;  
  24.     }  
  25.   
  26.     public String getPassword() {  
  27.         return password;  
  28.     }  
  29.   
  30.     public void setPassword(String password) {  
  31.         this.password = password;  
  32.     }  
  33.   
  34.     @Override  
  35.     public String toString() {  
  36.         return "User [username=" + username + ", password=" + password + "]";  
  37.     }  
  38. }  
  39.   
  40. /** 
  41.  * 目标接口 
  42.  */  
  43. interface IUserDao {  
  44.     public void add(User user);  
  45. }  
  46.   
  47. class UserDaoImpl implements IUserDao {  
  48.     @Override  
  49.     public void add(User user) {  
  50.         System.out.println("add a user successfully...");  
  51.     }  
  52. }  
  53.   
  54. /** 
  55.  * 日志类 --> 待织入的Log类 
  56.  */  
  57. class LogEmbed implements InvocationHandler {  
  58.     private IUserDao target;  
  59.   
  60.     /** 
  61.      * 对target进行封装 
  62.      */  
  63.     public IUserDao getTarget() {  
  64.         return target;  
  65.     }  
  66.   
  67.     public void setTarget(IUserDao target) {  
  68.         this.target = target;  
  69.     }  
  70.   
  71.     private void beforeMethod() {  
  72.         System.out.println("add start...");  
  73.     }  
  74.   
  75.     private void afterMethod() {  
  76.         System.out.println("add end...");  
  77.     }  
  78.   
  79.     /** 
  80.      * 这里用到了反射 
  81.      *  
  82.      * proxy 代理对象 
  83.      *  
  84.      * method 目标方法 
  85.      *  
  86.      * args 目标方法里面参数列表 
  87.      */  
  88.     @Override  
  89.     public Object invoke(Object proxy, Method method, Object[] args)  
  90.             throws Throwable {  
  91.         beforeMethod();  
  92.         // 回调目标对象的方法  
  93.         method.invoke(target, args);  
  94.         System.out.println("LogEmbed --invoke-> method = " + method.getName());  
  95.         afterMethod();  
  96.         return null;  
  97.     }  
  98. }  
  99.   
  100. /** 
  101.  * 客户端测试类 
  102.  *  
  103.  * @author Leo 
  104.  */  
  105. public class Test {  
  106.     public static void main(String[] args) {  
  107.         IUserDao userDao = new UserDaoImpl();  
  108.         LogEmbed log = new LogEmbed();  
  109.         log.setTarget(userDao);  
  110.         /** 
  111.          * 根据实现的接口产生代理 
  112.          */  
  113.         IUserDao userDaoProxy = (IUserDao) Proxy.newProxyInstance(userDao  
  114.                 .getClass().getClassLoader(), userDao.getClass()  
  115.                 .getInterfaces(), log);  
  116.         /** 
  117.          * 注意:这里在调用IUserDao接口里的add方法时, 
  118.          * 代理对象会帮我们调用实现了InvocationHandler接口的LogEmbed类的invoke方法。 
  119.          *  
  120.          * 这样做,是不是有点像Spring里面的拦截器呢? 
  121.          */  
  122.         userDaoProxy.add(new User("张三""123"));  
  123.     }  
  124. }  




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章