代理模式学习笔记

1. 引入

我们以买房子来举例,小明和对象小花相处已经 3 年了,却仍然没有步入婚姻的殿堂。亲爱的小花说家里要求小明给她买套房子才肯结婚,因为觉得这样子小花以后的生活比较有保障,至少有个住的地方,同时也想考察一下小明的经济实力行不行。

小明很无奈,但是必须得先买房。小明来到了省城买房,刚下车就有卖房中介挤过来,一个劲儿地介绍:想要多大平方的房子?我能帮你找到最合适的房子,我还会帮你搞定银行贷款,不用你来回跑。小明想啊,“靠人不如靠己”,我不能直接去房地产公司买吗?就没有理会这些中介的话。

经过一番周折,小明发现房地产公司大多不负责直接卖房子,而是交给中介处理;还有流程确实很复杂:办理买房手续,资格,以及贷款申请,资格审查。这些如果都是自己去做,不知道效率有多低。看来,“出门还得靠朋友”。好吧,小明只好去和中介周旋了。

其实,上面的例子,就是一个典型的代理模式在实际生活中的应用。在代理模式中,上面的角色也都有对应的表示。

2. 定义

上面说了那么多,那么什么是代理模式呢?

代理模式,为其它对象提供一种代理以控制对这个对象的访问。

下面我们通过上面的例子,来引出代理模式中的角色。

中介在代理模式中是 Proxy 类,他有卖房子的功能;

房地产公司在代理模式中是RealSubject 类,他当然有卖房子的功能;

在程序中,两个类有共同的功能,那么我们就会把这个共同的功能抽取出一个接口来。这里中介和房地产公司的共同功能是卖房子,现在我们抽取一个卖房子的接口,即Subject 接口。

中介和房地产公司之间的关系:中介必须要知道房地产公司,在程序中用关联关系来表示。

中介卖的仍然是房地产公司的房子,真正卖房子的是房地产公司,所以我们说房地产公司是中介所代表的真实实体,也就是说 RealSubjectProxy 所代表的真实实体。

对于小明来说,只要跟中介打交道就行,不用再去跟房地产公司打交道了。

通过类图,来说明一下:

  • Subject 接口,是抽象角色,是 RealSubjectProxy 的共同接口;
  • RealSubject 类,是真实角色,它实现了抽象角色接口。真实角色实现了真正的业务逻辑;
  • Proxy 类,是代理角色,它同样实现了抽象角色接口。它是真实角色的代理,它保存了真实角色的引用。客户调用 Proxy 类所实现的抽象角色接口,而在这个实现方法里面,就调用真实角色的抽象方法实现。

3. 静态代理

3.1 代码演示

把上面场景用代码实现以下,就是典型的静态代理。

ASellHouseInterface 对应于抽象角色Subject 类:

/**
 * A 卖房接口
 */
public interface ASellHouseInterface {
    void sellAHouse(float size);
}

ARealEstateCompany 对应于真实角色RealSubject

/**
 * A 房地产公司
 */
public class ARealEstateCompany implements ASellHouseInterface {
    @Override
    public void sellAHouse(float size) {
        System.out.println("这是一套面积为" + size + "平方的房子。");
    }
}

AMediation 对应于代理角色Proxy

/**
 * A 中介
 */
public class AMediation implements ASellHouseInterface {
    /**
     * 持有的真实角色对象引用
     */
    private ARealEstateCompany company;

    public AMediation(ARealEstateCompany company) {
        this.company = company;
    }
    /*前置处理器*/
    private void doSomethingBefore() {
        System.out.println("帮您分析买房需求,找到最适合您的房子。");
    }
    /*后置处理器*/
    private void doSomethingAfter() {
        System.out.println("帮您搞定繁琐的贷款审批,让您购房无忧!");
    }

    @Override
    public void sellAHouse(float size) {
        doSomethingBefore();
        company.sellAHouse(size);
        doSomethingAfter();
    }
}

测试代理如下:

public class HouseBuyer {
    public static void main(String[] args) {
        // 1, 静态代理模式
        ARealEstateCompany aRealEstateCompany = new ARealEstateCompany();
        AMediation aMediation = new AMediation(aRealEstateCompany);
        aMediation.sellAHouse(150f);
    }
}
/*
打印结果:
帮您分析买房需求,找到最适合您的房子。
这是一套面积为150.0平方的房子。
帮您搞定繁琐的贷款审批,让您购房无忧!
 */

到这里,我们总结一下静态代理的步骤

  1. 定义真实角色和代理角色的共同接口,即抽象角色;
  2. 真实角色实现抽象角色的接口方法;
  3. 代理角色实现抽象角色的接口方法:在方法实现中,调用真实角色来完成业务逻辑。

3.2 不足之处

但是,静态代理虽然很好地描述了上面的例子,但是它仍有些不足之处。
我们继续上面的例子来说明:小花知道亲爱的小明在看房子了,小花当然很开心,但是小花不想揹负太大的经济压力,所以小花重点关注了房价这个指标,她也找了相应的房地产公司以及卖房中介。
相应的类如下:
BSellHouseInterface 对应于抽象角色Subject 类:

/**
 * B 卖房接口
 */
public interface BSellHouseInterface {
    void sellBHouse(float price);
}

BRealEstateCompany 对应于真实角色RealSubject

/**
 * B 房地产公司
 */
public class BRealEstateCompany implements BSellHouseInterface {
    @Override
    public void sellBHouse(float price) {
        System.out.println("这是一套价值为" + price + "万的房子。");
    }
}

BMediation 对应于代理角色Proxy

/**
 * B 中介
 */
public class BMediation implements BSellHouseInterface {
    // 真实的对象
    private BRealEstateCompany company;

    public BMediation(BRealEstateCompany company) {
        this.company = company;
    }

    /*前置处理器*/
    private void doSomethingBefore() {
        System.out.println("帮您分析买房需求,找到最适合您的房子。");
    }
    /*后置处理器*/
    private void doSomethingAfter() {
        System.out.println("帮您搞定繁琐的贷款审批,让您购房无忧!");
    }
    @Override
    public void sellBHouse(float price) {
        doSomethingBefore();
        company.sellBHouse(price);
        doSomethingAfter();
    }
}

测试代码如下:

public class HouseBuyer {
    public static void main(String[] args) {
        // 1, 静态代理模式
        // 小明买房
        ARealEstateCompany aRealEstateCompany = new ARealEstateCompany();
        AMediation aMediation = new AMediation(aRealEstateCompany);
        aMediation.sellAHouse(150f);
        // 小花买房
        BRealEstateCompany bRealEstateCompany = new BRealEstateCompany();
        BMediation bMediation = new BMediation(bRealEstateCompany);
        bMediation.sellBHouse(30);
    }
}
/*
打印结果:
帮您分析买房需求,找到最适合您的房子。
这是一套面积为150.0平方的房子。
帮您搞定繁琐的贷款审批,让您购房无忧!
帮您分析买房需求,找到最适合您的房子。
这是一套价值为30.0万的房子。
帮您搞定繁琐的贷款审批,让您购房无忧!
 */

有同学看到这里,会想:这和小明找中介买房的代码很相似,可以使用一套代码来实现,一个中介可以对应于多个开发商嘛。这样可以实现一些代码的复用。因为如果都是一对一(一个中介对应于一个房地产公司),那么会出现很多代理对象,代码量变大,可维护性差的问题。

代码可以这样写:

ABMediation 是代理角色,它对应于 2 家房地产公司:ARealEstateCompanyBRealEstateCompany

/**
 * A & B 中介
 */
public class ABMediation implements ASellHouseInterface, BSellHouseInterface {
    // 真实的对象
    private ARealEstateCompany aCompany;
    private BRealEstateCompany bCompany;

    public ABMediation(ARealEstateCompany aCompany, BRealEstateCompany bCompany) {
        this.aCompany = aCompany;
        this.bCompany = bCompany;
    }

    /*前置处理器*/
    private void doSomethingBefore() {
        System.out.println("帮您分析买房需求,找到最适合您的房子。");
    }

    /*后置处理器*/
    private void doSomethingAfter() {
        System.out.println("帮您搞定繁琐的贷款审批,让您购房无忧!");
    }

    @Override
    public void sellAHouse(float size) {
        doSomethingBefore();
        aCompany.sellAHouse(size);
        doSomethingAfter();
    }

    @Override
    public void sellBHouse(float price) {
        doSomethingBefore();
        bCompany.sellBHouse(price);
        doSomethingAfter();
    }
}

下面是测试代码:

public class HouseBuyer {
    public static void main(String[] args) {
        // 1, 静态代理模式
        // 小明买房,一个中介对应一个房地产公司(一对一)
        ARealEstateCompany aRealEstateCompany = new ARealEstateCompany();
        AMediation aMediation = new AMediation(aRealEstateCompany);
        aMediation.sellAHouse(150f);
        // 小花买房,一个中介对应一个房地产公司(一对一)
        BRealEstateCompany bRealEstateCompany = new BRealEstateCompany();
        BMediation bMediation = new BMediation(bRealEstateCompany);
        bMediation.sellBHouse(30);
        // 小明,小花买房,一个中介对应多个房地产公司(一对多)
        ABMediation abMediation = new ABMediation(aRealEstateCompany, bRealEstateCompany);
        abMediation.sellAHouse(180f);
        abMediation.sellBHouse(40);
    }
}
/*
打印结果:
帮您分析买房需求,找到最适合您的房子。
这是一套面积为150.0平方的房子。
帮您搞定繁琐的贷款审批,让您购房无忧!
帮您分析买房需求,找到最适合您的房子。
这是一套价值为30.0万的房子。
帮您搞定繁琐的贷款审批,让您购房无忧!
帮您分析买房需求,找到最适合您的房子。
这是一套面积为180.0平方的房子。
帮您搞定繁琐的贷款审批,让您购房无忧!
帮您分析买房需求,找到最适合您的房子。
这是一套价值为40.0万的房子。
帮您搞定繁琐的贷款审批,让您购房无忧!
 */

上面的方案,确实可以减少代理对象,但是却违法了开闭原则—对于扩展开发(Open for extension),对于修改关闭(Closed for modification)。

因为如果我们需要增加一家房地产公司 C,就不得不去修改 ABMediation 类;如果小明和小花的需求改变了,比如要买地段好的房子,那么就需要修改接口,这样也不得不修改 ABMediation 类。

而动态代理可以解决这些问题。

4. 动态代理

JDK 支持动态代理,需要使用java.lang.reflect 包下的 Proxy 类和 InvocationHandler 接口。

从这里开始往下写,我花了一天的工夫查看文档,在想怎么写,因为我本想采取一步一步引入的方式来介绍 JDK 中的动态代理,但是发现一天结束了我还是一头雾水,不知道从哪里开始说起(其实是我对动态代理的原理不理解)。是啊,对于 Java 了解浅薄的我,却异想天开地打算从零推出 Java 设计者们设计精巧的动态代理实现,这多么不契合实际。

我想了想,JDK 中的动态代理本身就是一种固定化的实现,我何不先照着 JDK 预定的方式实现动态代理,再去理解其中的设计呢?我希望自己能够接近 Java 设计者的思想。

所以,我打算这样分析:先直接照着文档实现动态代理;然后再进行源码方面的分析;最后,用自己的语言把动态代理的过程描述一下。

4.1 代码演示

首先,定义MediationCompany 类,它是 InvocationHandler 接口的实现:

public class MediationCompany implements InvocationHandler {
    // 真实的对象
    private Object realEstateCompany;

    public void setRealEstateCompany(Object realEstateCompany) {
        this.realEstateCompany = realEstateCompany;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        doSomethingBefore();
        Object result = method.invoke(realEstateCompany, args);
        doSomethingAfter();
        return result;
    }

    /*前置处理器*/
    private void doSomethingBefore() {
        System.out.println("帮您分析买房需求,找到最适合您的房子。");
    }

    /*后置处理器*/
    private void doSomethingAfter() {
        System.out.println("帮您搞定繁琐的贷款审批,让您购房无忧!");
    }
}

MediationCompany 实现了 InvocationHandler 接口中的 invoke 方法,并且持有了真实对象的引用。在实现的 invoke 方法中,获取到 Medthod 对象,通过反射调用了真实对象上的对应的方法,并返回了调用后的结果。

这里需要说明一下,持有的真实对象的引用是 Object 类型的,这有什么好处呢?好处是,如果现在要传给 MediationCompany 另外一个真实对象(实现自不同的接口),就不需要更改 MediationCompany 的类结构了。

其次,看一下客户端的代码:

public class HouseBuyer {
    public static void main(String[] args) {
        // 2, 动态代理模式
        MediationCompany mediationCompany = new MediationCompany();
        // 小明买房
        ARealEstateCompany aRealEstateCompany = new ARealEstateCompany();
        mediationCompany.setRealEstateCompany(aRealEstateCompany);
        ASellHouseInterface consultant1 = (ASellHouseInterface) Proxy.newProxyInstance(
                aRealEstateCompany.getClass().getClassLoader(),
                new Class[] {ASellHouseInterface.class},
                mediationCompany);
        consultant1.sellAHouse(100f);
        // 小花买房
        BRealEstateCompany bRealEstateCompany = new BRealEstateCompany();
        mediationCompany.setRealEstateCompany(bRealEstateCompany);
        BSellHouseInterface consultant2 = (BSellHouseInterface) Proxy.newProxyInstance(
                bRealEstateCompany.getClass().getClassLoader(),
                new Class[] {BSellHouseInterface.class},
                mediationCompany);
        consultant2.sellBHouse(20);
    }
}

客户端需要创建动态代理的实例,这需要借助 Proxy 类的静态方法 newProxyInstance

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

参数一:ClassLoader loader,需要一个类加载器,通常从已经被加载的对象中获取其类加载器,传递给它就行;
参数二:Class<?>[] interfaces,就是这个代理类要实现的接口列表(这里需要注意:是接口,而不是类或者抽象类);
参数三:InvocationHandler h,这是 InvocationHandler 的一个实现。因为上面定义了 MedicationCompany 实现了 InvocationHandler 接口,所以我们传递一个 MediationCompany 对象即可。

注意到 newProxyInstance 方法的返回值是一个 Object 类型,所以我们需要把它的返回值强制类型转换为对应的公共接口,如 ASellHouseInterfaceBSellHouseInterface。这样才能调用对应的接口方法。

我们运行一下上面的代码,打印结果如下:

帮您分析买房需求,找到最适合您的房子。
这是一套面积为100.0平方的房子。
帮您搞定繁琐的贷款审批,让您购房无忧!
帮您分析买房需求,找到最适合您的房子。
这是一套价值为20.0万的房子。
帮您搞定繁琐的贷款审批,让您购房无忧!

可以看到,动态代理的代码完全实现了静态代理的功能。

4.2 几个疑问

但是,我们可不能到此为止。

我们心中还有疑问:

  1. 为什么在客户端调用动态代理对象的方法 consultant1.sellAHouse(100f); ,却是在 MediationCompany 类实现的 invoke 方法输出了结果?
  2. 上面明明创建了动态代理类:ASellHouseInterface consultant1BSellHouseInterface consultant2,但是为什么不能在 out 目录下找到它们对应的 .class 文件?

关于问题1,我们在上面的代码中添加一下日志:

public class MediationCompany implements InvocationHandler {
    // 真实的对象
    private Object realEstateCompany;

    public void setRealEstateCompany(Object realEstateCompany) {
        this.realEstateCompany = realEstateCompany;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("invoke(): proxy = " + proxy.getClass() + ", method = " + method + ", args = " + Arrays.toString(args));
        doSomethingBefore();
        Object result = method.invoke(realEstateCompany, args);
        doSomethingAfter();
        return result;
    }

    /*前置处理器*/
    private void doSomethingBefore() {
        System.out.println("帮您分析买房需求,找到最适合您的房子。");
    }

    /*后置处理器*/
    private void doSomethingAfter() {
        System.out.println("帮您搞定繁琐的贷款审批,让您购房无忧!");
    }
}
public class HouseBuyer {
    public static void main(String[] args) {
        // 2, 动态代理模式
        MediationCompany mediationCompany = new MediationCompany();
        // 小明买房
        ARealEstateCompany aRealEstateCompany = new ARealEstateCompany();
        mediationCompany.setRealEstateCompany(aRealEstateCompany);
        ASellHouseInterface consultant1 = (ASellHouseInterface) Proxy.newProxyInstance(
                aRealEstateCompany.getClass().getClassLoader(),
                new Class[] {ASellHouseInterface.class},
                mediationCompany);
        System.out.println("consultant1 = " + consultant1.getClass());
        consultant1.sellAHouse(100f);
        // 小花买房
        BRealEstateCompany bRealEstateCompany = new BRealEstateCompany();
        mediationCompany.setRealEstateCompany(bRealEstateCompany);
        BSellHouseInterface consultant2 = (BSellHouseInterface) Proxy.newProxyInstance(
                bRealEstateCompany.getClass().getClassLoader(),
                new Class[] {BSellHouseInterface.class},
                mediationCompany);
        System.out.println("consultant2 = " + consultant2.getClass());
        consultant2.sellBHouse(20);
    }
}

运行一下,查看打印结果:

consultant1 = class com.sun.proxy.$Proxy0
invoke(): proxy = class com.sun.proxy.$Proxy0, method = public abstract void com.java.advanced.features.proxy.ASellHouseInterface.sellAHouse(float), args = [100.0]
帮您分析买房需求,找到最适合您的房子。
这是一套面积为100.0平方的房子。
帮您搞定繁琐的贷款审批,让您购房无忧!
consultant2 = class com.sun.proxy.$Proxy1
invoke(): proxy = class com.sun.proxy.$Proxy1, method = public abstract void com.java.advanced.features.proxy.BSellHouseInterface.sellBHouse(float), args = [20.0]
帮您分析买房需求,找到最适合您的房子。
这是一套价值为20.0万的房子。
帮您搞定繁琐的贷款审批,让您购房无忧!

从上面的日志,我们可以知道在客户端动态代理实例 constultant1 调用了 consultant1.sellAHouse(100f);,在 MediationCompany 的 invoke 方法中就接收到了 method = public abstract void com.java.advanced.features.proxy.ASellHouseInterface.sellAHouse(float), args = [100.0],这两者的信息是一模一样的。这说明客户端的动态代理实例调用 sellAHouse 方法,和 MediationCompnayinvoke 方法必然存在某种机制,把客户端的动态代理实例的方法调用转发给了 MediationCompany(一个InvocationHandler 接口的实现类) 的 invoke 方法来处理。不过,这只是初步的观点,我们还需要查看源码来验证。

注意到,好事的我还打印了两个动态代理实例对应的 Class 信息,以及 invoke 方法的第一个参数 Object proxy 对应的 Class 信息。截取出来如下:

consultant1 = class com.sun.proxy.$Proxy0
invoke(): proxy = class com.sun.proxy.$Proxy0
consultant2 = class com.sun.proxy.$Proxy1
invoke(): proxy = class com.sun.proxy.$Proxy1

可以看到,动态代理类被 invoke 方法给接收了。另外,也是很重要的一点,不知道你看出来没有,动态代理类的名字怎么这么怪?它的路径竟然不和程序中的包名一样,名字里还有一个 $ 符号。

我们或许打算在工程的编译器输出目录下找有没有 com.sun.proxy.$Proxy0.class的存在,因为我们知道:Java 源文件(.java 文件)需要经过 javac 命令编译成 Java 字节码(.class 文件);Java 字节码(.class 文件)经过 java 命令开始执行。那么,我们有理由相信一定在某个地方存放着 com.sun.proxy.$Proxy0.class,不然不可能有对应的动态代理对象 consultant1。不过,在 out 目录下有 MediationCompany.class,但是根本没有 com.sun.proxy.$Proxy0.class,看下图:

4.3 查看源码

到这里,如果不去查看源码,还有什么办法解开我们的疑问呢?同时,我们也是带着我们的疑问去查看源码,这样也更加有目标。
我们的问题目前有两个:
1,动态代理实例(它是通过 Proxy.newProxyInstance 方法创建的)上的方法调用如何转发给了 InvocationHandlerinvoke 方法?
2,动态代理类编译好的 .class 文件在什么地方存放?

这里查看的是 JDK1.8 的源码

Prxoy.newProxyInstance() 方法

Proxy 类的 newProxyInstance() 方法开始:

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        // 省略校验代码部分
        /*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, intfs);
        /*
         * Invoke its constructor with the designated invocation handler.
         */
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            // 省略了非关键的代码部分
            return cons.newInstance(new Object[]{h});
  			// 省略了异常捕获的部分
    }

这个方法接收了一个 ClassLoader 对象,一个代理要实现的接口列表,一个 InvocationHandler 实现类对象。
看一下 Class<?> cl = getProxyClass0(loader, intfs); 这行代码的注释:Look up or generate the designated proxy class(查找或生成指定的代理类),从里我们可以推断,这个方法就是用来获取代理类的。这个方法需要两个参数:一个 ClassLoader 对象,一个 InvocationHandler 实现类对象。
接着看 final Constructor<?> cons = cl.getConstructor(constructorParams); 这行代码,这是获取 Class<?> cl 的带有参数的 Constructor 对象,其中 constructorParams 是:

  /** parameter types of a proxy class constructor */
    private static final Class<?>[] constructorParams =
        { InvocationHandler.class };

最后看 return cons.newInstance(new Object[]{h}); 这行代码就是创建动态代理的实例了,通过反射调用动态代理类的带有 InvocationHandler 类型参数的构造方法而创建的。

ProxygetProxyClass0() 方法

    private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        // If the proxy class defined by the given loader implementing
        // the given interfaces exists, this will simply return the cached copy;
        // otherwise, it will create the proxy class via the ProxyClassFactory
        return proxyClassCache.get(loader, interfaces);
    }

自然,应该看 return proxyClassCache.get(loader, interfaces); 这行代码,同样地,不要视注释而不见。我们把注释翻译一下:如果由给定的加载器定义的且实现了给定的接口的代理类存在,这里就会直接返回缓存的副本;否则,这里将通过 ProxyClassFactory 创建代理类。

划一下重点啊:创建代理类要靠 ProxyClassFactory

ProxyClassFactoryProxy 类的静态内部类。

ProxyClassFactory 类的 Class<?> apply(ClassLoader loader, Class<?>[] interfaces) 方法

		// prefix for all proxy class names
        private static final String proxyClassNamePrefix = "$Proxy";

        // next number to use for generation of unique proxy class names
        private static final AtomicLong nextUniqueNumber = new AtomicLong();
 public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

            Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
            // 省略对 interfaces 进行校验的 for 循环

            String proxyPkg = null;     // package to define proxy class in
            int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
			// 省略处理 非 public 接口的 for 循环
            if (proxyPkg == null) {
                // if no non-public proxy interfaces, use com.sun.proxy package
                // 这里 ReflectUtil.PROXY_PACKAGE 的值是"com.sun.proxy"
                proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
            }

            /*
             * Choose a name for the proxy class to generate.
             */
            long num = nextUniqueNumber.getAndIncrement();
            // 拼接处代理类的名字:"com.sun.proxy" + "$Proxy"+ long 型数字编号
            String proxyName = proxyPkg + proxyClassNamePrefix + num;

            /*
             * Generate the specified proxy class.
             * 生成指定的代理类
             */
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
        }

defineClass0() 是一个本地方法,用来生成 Class 对象。我们无法查看 defineClass0() 的实现。但是,前面一行代码:

byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);

是生成一个字节数组,这就是对应的.class 文件。

这里可以回答第 2 个问题:上面明明创建了动态代理类:ASellHouseInterface consultant1BSellHouseInterface consultant2,但是为什么不能在 out 目录下找到它们对应的 .class 文件?
因为程序中默认并没有生成对应的 .class 文件在磁盘上,而是在内存中直接使用,用于创建动态代理类。

我们可以把这个字节数组写到本地来查看一下。

获取生成的$Proxy.class文件

新建一个ProxyUtils类:

public class ProxyUtils {
	/**
	* 参数一:String proxyName,动态代理类的名字;
	* 参数二:Class<?>[] interfaces,代理类要实现的接口列表
	*/
    public static void generateClassFile(String proxyName, Class<?>[] interfaces) {
        /*
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);*/
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces);
        // 获取当前的class文件输出目录
        String path = ProxyUtils.class.getResource(".").getPath();
        System.out.println(path);
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(path + proxyName + ".class");
            fos.write(proxyClassFile);
            fos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

HouseBuyer 类的 main 方法中添加代码:

 ProxyUtils.generateClassFile(consultant1.getClass().getSimpleName(),
                new Class[]{ASellHouseInterface.class});
        ProxyUtils.generateClassFile(consultant2.getClass().getSimpleName(),
                new Class[]{BSellHouseInterface.class});
        for (Method method : aRealEstateCompany.getClass().getMethods()) {
            System.out.println(method.getName());
        }

运行代码,打印结果如下:

/out/production/Java_01_AdvancedFeatures/com/java/advanced/features/proxy/dynamic/
/out/production/Java_01_AdvancedFeatures/com/java/advanced/features/proxy/dynamic/

在对应的目录下,可以看到生成了两个文件 $Proxy0.class$Proxy1.class

打开 $Proxy0.class,它就是 consultant1 这个动态代理类对应的字节码文件:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import com.java.advanced.features.proxy.ASellHouseInterface;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements ASellHouseInterface {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void sellAHouse(float var1) throws  {
        try {
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.java.advanced.features.proxy.ASellHouseInterface").getMethod("sellAHouse", Float.TYPE);
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

网上有人通过在 main 方法开始处,加入:

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

这就打开了 ProxyGeneratorsaveGeneratedFiles 标记为 true。
运行后,在 src 的同级目录下,会出现一个 com.sun.proxy 包,这里就是动态生成的代理类存放的地方。

分析生成的$Proxy0.class文件

看一下 $Proxy0 类:
首先,看类声明,它继承了 Proxy 类,并实现了 ASellHouseInterface 接口。这里解释了为什么在通过 Proxy.newProxyInstance() 方法获取动态实例后,进行类型强制转换为 ASellHouseInterface 。有同学觉得应该转型为 ARealEstateCompany 的,可以看到明显是不对的。

它有一个带有 InvocationHandler 参数的构造方法,这就解释了 Prxoy.newProxyInstance() 方法中对动态代理类获取有 InvocationHandler 参数的 Constructor 对象,并调用其 newInstance() 方法(需要传递一个 InvocationHandler 对象)获取动态代理实例。在构造方法内部,调用了父类 Proxy 的带有 InvocationHandler 参数的构造方法,完成了对父类 Proxy 类的 protected InvocationHandler h; 赋值操作。

再往下看成员方法,有一个 sellAHouse(float val1) 的方法:

    public final void sellAHouse(float var1) throws  {
        try {
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

这个方法就是我们通过 consultant1.sellAHouse(100f); 调用的。再说明一下:consultant1 是一个动态代理对象,对应的字节码文件就是 $Proxy0.class。方法体内部,super.h.invoke(this, m3, new Object[]{var1}); 调用了 h 变量上的 invoke() 方法。h 是什么呢?它是超类 Proxy 中的一个字段:

    /**
     * the invocation handler for this proxy instance.
     * @serial
     */
    protected InvocationHandler h;

而这个 InvocationHandler 对象就是实现了 InvocationHandler 接口的一个实现类对象,在程序中就是 MediationCompany 对象。我们是在 Proxy.newProxyInstance() 方法中传入的 MediationCompany 对象。

那么,super.h.invoke(this, m3, new Object[]{var1}); 这行代码就是调用了 MediationCompany 类中的 invoke() 方法。第一个参数是 this,即动态代理类$Proxy0 的实例,第二个参数是 m3,是一个 Method 对象,它是在静态代码块中早已赋值:

m3 = Class.forName("com.java.advanced.features.proxy.ASellHouseInterface").getMethod("sellAHouse", Float.TYPE);

m3 就是 sellAHouse 方法对应的 Method 实例。第三个参数是new Object[]{var1} 是参数列表。
到这里,可以回答第 1 个问题:为什么在客户端调用动态代理对象的方法 consultant1.sellAHouse(100f);,却是在 MediationCompany 类实现的 invoke 方法输出了结果?
在客户端通过 Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 方法创建了一个动态代理实例 consultant1,即 $Proxy0类的实例。而在 newProxyInstance 中传入的第三个参数:InvocationHandler 对象(即 MediationCompany 对象),在通过反射调用 $Proxy0 类的带 InvocationHandler 参数 构造方法时,交给 $Proxy0 对象持有。在调用 consultant1.sellAHouse(100f); 时,就是调用了 $Proxy0 中的 sellAHouse() 方法。在方法体内,调用了 $Proxy0 持有的 InvocationHandler 对象的 invoke 方法,当然就是调用 MediationCompany 中的 invoke 方法。

4.4 描述动态代理

Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 方法中做了两件事情:第一,通过前两个参数生成动态代理类;第二,反射创建动态代理实例并把 InvocationHandler h 交给动态代理实例。
调用动态代理实例的方法,在方法内部,把业务处理交给自己持有的 InvocationHandler hinvoke 方法来处理。

动态代理的步骤:

  1. 定义代理角色和真实角色的公共接口;
  2. 真实角色实现公共接口中的方法;
  3. 定义一个实现了 InvocationHandler 的调用处理程序,它持有真实角色的引用;
  4. 在运行时通过 Proxy 类的 newProxyInstance 方法动态生成动态代理对象;
  5. 动态代理实例调用公共接口中的方法,转发给第 3 步中的调用处理程序处理。
  6. 在调用处理程序的 invoke 方法中,调用真实角色的业务逻辑。

这里是动态代理的类图:
在这里插入图片描述

5. 静态代理 vs 动态代理

优点 缺点
动态代理 1,代理类在程序运行时动态生成,减少了手动写代理类的麻烦;2,可以在原始类和接口都未知的情况下,就确定代理类的代理行为。 1,抽象角色只能是接口,不能是类或者抽象类;2,通过反射生成代理类并反射调用真实对象的方法,效率较低。
静态代理 1,抽象角色可以是接口,类,抽象类;2,实现方式较为简单,没有效率问题。 1,违反了开闭原则:接口改变,都不得不修改静态代理的代码;2,需要手动编写代理类,有些麻烦。

6. 应用

对于静态代理,可以查看Android-如何优雅的处理重复点击
对于动态代理,可以查看 从动态代理角度看Retrofit,这才是Retrofit的精髓!

代码地址在https://github.com/jhwsx/Java_01_AdvancedFeatures/tree/master/src/com/java/advanced/features/proxy

参考

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