设计模式--超详细 手动实现 jdk动态代理 原理(4)

在前几篇文章中,我们通过指出静态代理的不足及弊端,引申出了动态代理,然后一步步的对代码进行优化。那么在jdk中的动态代理究竟是什么样子呢?本篇我们就来使用jdk的动态代理,并在使用的过程中比较一下和我们实现的动态代理有哪些区别。

首先我们要自定义对方法的增强方式,即实现jdk提供的InvocationHandler接口:

package cn.rain.design.proxy.demo4;

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

public class LogHandler implements InvocationHandler {

    private Object target;

    // 创建实例时要拿到代理对象
    public LogHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("logger is start...");
        // result即方法调用的返回值
        Object result = method.invoke(target);
        System.out.println("logger is end...");
        // 返回方法调用的结果,因为我们知道move方法返回值是void,因此这里的result肯定是null
        return result;
    }
}

第三个参数是方法入参,在前边的实现中为了简单起见,我们为被代理对象Movable编写的方法中入参和返回值均为空。在后边的分析中我不对这个细节做过多说明,有兴趣的同学可以按照我们前边的思路将带参数的形式实现一遍,这样可以加深印象。

第二个参数method是我们在生成的代理对象$Proxy.java中传过来的method对象,如图:

设计模式--超详细 手动实现 jdk动态代理 原理(4)_第1张图片

第一个参数Object proxy是什么呢?

通过命名大家应该也猜到了,这个参数其实就是代理对象$Proxy,它是用户获取到代理对象后,当调用代理对象的方法时,将代理对象自身传递给了InvocationHandler。听起来有点抽象,我们试着用代码还原一下,对以下两个地方进行修改:
设计模式--超详细 手动实现 jdk动态代理 原理(4)_第2张图片

设计模式--超详细 手动实现 jdk动态代理 原理(4)_第3张图片

修改后,我们再次生成$Proxy.java就是这个样子:

设计模式--超详细 手动实现 jdk动态代理 原理(4)_第4张图片

当我们通过MyProxy.newProxyInstance方法获取到代理对象后(也就是$Proxy的实例),调用代理对象的方法时,就会通过h.invoke(this, m)将自身的实例传递给用户。

那么将代理对象proxy传递给invocationHandler有什么用呢?很遗憾,我翻了很多资料也没有找到让我信服的答案,网上有很多文章说是可以在实现InvocationHandler.invoke()的时候将它return回去,以便可以对代理对象反复调用。这明显是没有意义的,因为通过上边的代码我们可以知道,其实传递给InvocationHandler的代理对象proxy和MyProxy.newProxyInstance()获取到的代理对象是一样的,所以根本没有必要返回来。我个人的理解它可能是设计上的缺陷,如果不是的话那它必然是提供给用户在实现InvocationHandler.invoke时候使用,但是具体怎么用,我实在是没有找到很好的例子。我也翻了下jdk中对于proxy的用法,如图:

它除了做前置判断外也没有什么具体的使用,如果大家有研究过它的话,欢迎留言交流。

好了说回正题,我们实现完jdk的InvocationHandler后,再来看一下jdk用于生成代理对象实例的java.lang.reflect.Proxy类:

设计模式--超详细 手动实现 jdk动态代理 原理(4)_第5张图片

第一个参数是生成代理对象所用到的类加载器,我们直接写在代码里了;第二个参数传递的是一个Class数组,这很好理解,因为被代理对象可能实现了多个接口,因此代理对象也要实现多个接口去代理所有接口中的方法。

另外需要重点说明的是,这里要传递的必须是被代理对象所实现的接口(如果实现了多个接口可以只传部分接口,我们传入了哪些接口,生成的代理对象就会实现哪些接口),但是如果被代理对象没有实现接口而是继承了某个类,那么它是不能被jdk的动态代理所代理的。也就是说,jdk动态代理只能代理接口,不能代理类。

为什么jdk的动态代理只能代理接口呢,说这个问题之前我们要简单了解一下jdk是怎么生成代理对象的,以及它生成的代理对象和我们的有哪些区别。jdk没有像我们实现的那样去拼接$Proxy.java的字符串,它跳过了这一步,用byte[ ]直接将$Proxy.class字节码文件写到了磁盘上,然后进行编译、创建实例,用完之后再将字节码文件从磁盘上删除。我们可以通过一些手段获取到这个字节码文件(参考 https://blog.csdn.net/qq_34436819/article/details/101310221),对该字节码反编译后:

package com.sun.proxy;

import cn.rain.design.proxy.demo1.model.Movable;
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 Movable {
    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 move() throws  {
        try {
            return super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    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("cn.rain.design.proxy.demo1.model.Movable").getMethod("move");
            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());
        }
    }
}

列举jdk的代理对象和我们代理对象的区别:

  1. jdk除了不仅代理了目标方法,也代理了Object中的equals、hashCode、toString,因此我们使用代理对象调用这3个方法时,也会进行方法的增强。
  2. 我们获取method对象是在对应方法的内部通过反射获取,而jdk是通过静态代码块获取的,这样做的目的我想更多是因为性能上的考虑,毕竟每个代理对象都代理了equals、hashCode、toString,这样可以将这部分内存共享。
  3. 我们的invocationHandler变量直接定义在了代理对象$Proxy中,用户传递过来我们直接将其赋值给$Proxy中的变量h;而jdk将变量定义在了java.lang.reflect.Proxy中,让生成的代理对象$Proxy继承Proxy,并将用户传递的invocationHandler赋值给父类,代理对象$Proxy使用的invocationHandler变量h是继承自父类的。

而jdk的动态代理只能代理接口不能代理类正是由于代理对象继承了Proxy,由于Java不允许多继承,因此它无法再去继承一个类。

那么jdk为什么要这么设计呢?像我们实现的那样不继承MyProxy不也是可以的吗?我想jdk这么做的原因至少有以下两点:

  1. 在上一篇文章中我们说过,其实动态代理要做的事情无非就是在原始方法的基础上进行一些增强。如果说允许代理类的话,那么父类中的字段也会被代理对象继承过来,而这些字段是实际上是没有使用的,对内存空间是一个浪费。
  2. 更致命的是类中有很多的限定符,比如被final限定的方法会导致代理对象无法重写该方法从而无法进行代理;被final修饰的类或者类中的构造器被private修饰会导致该类无法被代理对象继承。

至此,我们比较了jdk动态代理和我们的动态代理的所有区别,最后我们使用jdk的Proxy生成Movable的代理对象:

package cn.rain.design.proxy.demo4;

import cn.rain.design.proxy.demo1.model.Car;
import cn.rain.design.proxy.demo1.model.Movable;

import java.lang.reflect.Proxy;

public class Main {
    public static void main(String[] args) {
        // 创建需要被代理的对象
        Car car = new Car();
        // 创建InvocationHandler实例
        LogHandler h = new LogHandler(car);
        // 获取代理对象
        Movable m = (Movable) Proxy.newProxyInstance(car.getClass().getClassLoader(), car.getClass().getInterfaces(), h);
        // 通过代理对象调用move方法进行方法增强
        m.move();
    }
}

运行结果:

logger is start...
Car moving....
logger is end...

本系列完。

你可能感兴趣的:(java,设计模式)