天天看点

理解设计模式之代理模式

1. 代理模式的作用:为其他对象提供一种代理以控制对这个对象的访问。

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

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

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

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

3. 每一个代理实例都有一个与之关联的invocation handler,当调用了代理实例中的方法,流程就会转到与该代理实例关联的invocation handler中的invoke()方法。

4. 动态代理只是一个代理,它不会执行任何实质性的工作,在生成它的实例时你必须提供一个handler,由他接管实际的工作。

5. 一个代理类有以下特性:

a) 代理类是public、final的,不是抽象的

b) 代理类继承自java.lang.reflect.Proxy类

6. Java中的动态代理对比静态代理,相当于代理角色的有两个类:一个是Proxy,另一个是InvocationHandler,也就是说如果按照代理模式来划分角色的话,这两个类都属于代理角色。

7. Java中的动态代理的图示:

理解设计模式之代理模式

8. Java负责创建真实代理类和对象,我们只需提供在方法调用发生时知道做什么的handler。

9. 不管代理被调用的是何种方法,handler被调用的一定是invoke()方法。

10. 关于Java中动态代理的理解:

首先,我们需要理解静态代理中的几个角色:抽象角色,代理角色,真实角色,要理解每个角色的作用。然后,关于dynamic Proxy,我们首先意识到:动态动态,这个动态意味着什么?答:这个意味着我们的代理类是程序在运行期间生成的,以前,我们定义一个类,都是我们手动填写,例如:

class Person{

String name;

int age;

//生成set和get方法

};但是现在,我们要转变这个固式思维,这个代理类是Java帮助我们在运行时自动生成的,也就是说在我们的代码执行之前,这个代理类是不存在的。那么问题又来了,这个类的结构是怎样的?(比如说有哪些方法)当然这个是需要我们程序员去提供这种规范的,这个规范实际上就是多个接口,我们的代理类需要去实现这些接口中的方法,如何实现我们不用管,我们只需要提供这些接口。再回过头想一想静态代理中的几个角色,其实抽象角色就是一个接口,那么我们在动态代理中同样需要这样一个角色。另外,下面我们看一下这几个类:

1) Proxy类

newProxyInstance()方法:

参数1:ClassLoader,类加载器,一个类要想被使用,需要通过类加载器去加载它,即每个类都需要被类加载器加载之后JVM才认识它,比如我们平时使用的java.lang.String类就是通过bootstrap类加载器加载的,而我们用户自定义类是通过Application类加载器加载,后面的第13点提到,这个方法所起的作用,所以那个动态代理类也不例外,也需要类加载器去加载它。

参数2:Class[]数组,也就是动态代理需要实现的接口的Class对象数组。注意:在这里必须是接口所对应的Class对象数组,类和抽象类都不可以。

参数3:invocationHandler。介绍到这个参数时,我们可以回到上面的语境,动态代理,说到底还只是个代理,它并不去做实质性的工作,那么这个做实质性工作的角色谁来扮演呢?换句话说,也就是在哪里去访问我们的真实对象。咱们可以去查InvocationHandler这个接口的API,读一下这个接口的说明,大致的意思是说:

理解设计模式之代理模式

InvocationHandler是一个需要被代理实例的调用句柄实现(implements)的接口。每一个代理实例都有一个与之关联的调用句柄。当调用了代理实例中的方法(无论哪个方法,只要是调用了代理实例中的方法),流程就会转到与该代理实例关联的invocation handler中的invoke()方法。上面这个标注为红色字体的这句话,个人认为是理解整个动态代理的核心,一定要切记。读到这,我们也就明白了,实质性工作是在invoke()方法中去进行的。好,下面我们来看看invoke()方法中参数表示什么含义:

第一个参数:代理实例,通常我们用不上,看Java编程思想上的一句话:

理解设计模式之代理模式

第二个参数:是个Method对象,通过前面的反射的学习知道,Method对象实际上代表一个方法,那么它代表哪个方法呢?前面提到,无论调用代理实例的哪个方法,程序流程都会转到这个invoke方法。虽然我们不知道当前调用哪个方法,但是有一点可以确定,那就是:在某一个时刻,这个invoke方法肯定只会处理一个实例方法,那么这个Method对象肯定只会对应一个方法,也就是说当前是因为执行哪个方法而使流程跳转到invoke方法,那么这个Method对象就代表那个方法。

第三个参数:参数,方法的参数,哪个方法的参数?分析和上面的Method对象一模一样。

在invoke方法中可以对方法的调用进行相关访问的控制。

11. 在提到代理模式的三个角色时,对代理角色的描述是:提供与真实角色相同的接口以便在任何时刻都能代替真实对象。换句话说真实对象中有的方法,代理对象就应该也有。那么为什么在Java动态代理中InvocationHandler类没有重写所有的真实角色的方法呢?因为InvocationHandler并不是Proxy,而只是代理将调用真实对象的工作分配给了它。这就好比你在软件公司,你老板拉来了项目,分析了用户需求之后,就把编写代码的任务分配给了你,难道说因为你写了代码你就变成了老板了,客户就会把所有资金交到你的手上,显然不是这样的。同理,InvocationHandler并不是真正意义上的代理,也就不需要去重写所有的真实对象的方法了。

12. 动态代理的步骤:

a) 创建一个实现接口InvocationHandler的类,它必须实现invoke方法。

b) 创建被代理的类和接口(抽象角色和真实角色,真实角色可以省略)

c) 通过newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)创建一个代理

d) 通过代理调用方法。

13. Proxy.newInstance()方法做了两件事情:第一件,动态地创建了一个代理类;第二件,生成了一个该代理类的对象返回回来。

###2018.11.1添加示例代码

14. 示例代码:

a) 声明抽象主题角色:

public interface Subject {

    String doSomething(String name);
}
           

b) 定义真实角色:

public class RealSubject implements Subject {
    @Override
    public String doSomething(String name) {
        return "hello " + name;
    }
}
           

c) 实现InvocationHandler:

public class MyInvocationHandler implements InvocationHandler {

    private RealSubject foo;

    public MyInvocationHandler(RealSubject foo) {
        this.foo = foo;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before invoke...");
        Object retObj = method.invoke(foo, args);
        System.out.println("after invoke...");
        return retObj;
    }
}
           

d) Client测试类:

public class Client {

    public static void main(String[] args) {

        RealSubject foo = new RealSubject();
        InvocationHandler invocationHandler = new MyInvocationHandler(foo);
        Subject subject = (Subject) Proxy.newProxyInstance(RealSubject.class.getClassLoader(), new Class[]{Subject.class}, invocationHandler);

        String result = subject.doSomething("david");
        System.out.println(result);
    }
}
           

###2019.4.18日 补充

今天在使用动态代理的时候,抛出了一个异常:

Caused by: java.lang.IllegalArgumentException: android.content.res.ResourcesImpl is not an interface

这也就意味着,Proxy.newProxyInstance()方法的第二个参数中,必须是接口类型的Class对象数组。换句话说,被动态代理类必须是实现了某个接口。举个例子,在本文前面的那个例子中,如果被代理的RealSubject类没有实现接口:Subject,那么动态代理在此处无法生效。那么为什么需要这样去限制的呢?

所谓的动态代理,实际上就是在运行期间根据被代理接口动态生成一个代理类,这个代理类必须是继承自Proxy类。同时,由于Java是单继承的,那么我们只能通过与被代理类实现共同的接口从而实现代理,自然而然也就意味着:被代理类必须实现某个接口!