动态代理的一个小实验【深入理解动态代理的原理】

动态代理的一个小实验【有助于理解动态代理的原理】

接口

package com.pig.spring.aop.proxy;

public interface Vehicle {
    public void run();
}

接口实现类

package com.pig.spring.aop.proxy;

public class Car implements Vehicle {
    @Override
    public void run() {
        System.out.println("汽车在公路上行驶");
    }
}

单独写的一个类 VehicleInvocationHandler

该类实现了 InvocationHandler 接口,相当于VehicleInvocationHandler 就是 InvocationHandler 的子类,故在调用Proxy.newInstance() 可以传入该类的对象。

package com.pig.spring.aop.proxy;

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

public class VehicleInvocationHandler<T> implements InvocationHandler {
    /**
     * 传入的目标对象
     */
    private Vehicle target;

    public VehicleInvocationHandler(T t) {
        this.target = (Vehicle) t;
    }

    public VehicleInvocationHandler() {
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在调用被代理的方法前,进行某些处理
        System.out.println("交通工具启动了");
        // 通过反射调用被代理的方法 ==> invoke() 传入目标对象(即被代理的对象),args 为参数列表
        Object invokeResult = method.invoke(target, args);
        // 在调用被代理的方法后,进行某些处理
        System.out.println("交通工具停止了");
        return invokeResult;
    }
}

测试类

@Test
public void testVehicleInvocationHandler() throws Throwable {
    VehicleInvocationHandler<Car> invocationHandler = new VehicleInvocationHandler<>(new Car());
    Vehicle                        proxyInstance     = (Vehicle)Proxy.newProxyInstance(VehicleTest.class.getClassLoader(),
            new Class[]{Vehicle.class}, invocationHandler);
    proxyInstance.run();
 	System.out.println("************************************************");
    invocationHandler.invoke(proxyInstance, Car.class.getDeclaredMethod("run"), null);
}

输出结果

image-20221117100934532

思考

为什么会输出两次同样的结果呢?

这说明:

proxyInstance.run();invocationHandler.invoke(proxyInstance, Car.class.getDeclaredMethod("run"), null); 的输出是完全一样的。

也就是说,proxyInstance 这个代理对象调用run() 方法,最终还是会调用invocationHandler.invoke 这个方法,而invoke() 这个方法的内容,正是我们实现了InvocationHandler ,重写的invoke() 方法呀!!


再进一步思考

为什么proxyInstance 调用run() 方法,最终会执行到invocationHandler.invoke()那里去了呢?

探索

网上有很多通过生成代理类的.class 文件的方法。这里介绍一种最简单的

  1. 编辑运行的设置

    image-20221117104242627

  2. 将VM options 的值改为 -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true

    image-20221117104258548

  3. 编译运行后,在项目根目录下会生成$Proxy0 文件

    image-20221117104531317

代理类经过IDEA的反编译后,如下

public final class P r o x y 0 e x t e n d s P r o x y i m p l e m e n t s S m a r t A n i m a l = = > 说 明 这 个 代 理 类 的 名 字 是 ‘ Proxy0 extends Proxy implements SmartAnimal ==> 说明这个代理类的名字是` Proxy0extendsProxyimplementsSmartAnimal==>Proxy0,注意,前面的$ 不是内部类哦!!!代理类名字前面就是加了一个$! P r o x y 0 ‘ 继 承 了 P r o x y 类 , 实 现 了 V e h i c l e 接 口 ! ! ! ! 所 以 我 们 执 行 ‘ p r o x y I n s t a n c e . r u n ( ) ‘ 的 时 候 , 会 动 态 绑 定 到 ‘ Proxy0` 继承了 Proxy类,实现了 Vehicle 接口!!!! 所以我们执行`proxyInstance.run()` 的时候,会动态绑定到` Proxy0ProxyVehicleproxyInstance.run()Proxy0` 的run() 方法!!!这就是动态代理的精髓所在!!

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

package com.sun.proxy;

import com.pig.spring.aop.proxy.Vehicle;
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 Vehicle {
    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 run() throws  {
        try {
            // 重点在这!!!!!!!!!!!!!!!!!
            // m3 = Class.forName("com.pig.spring.aop.proxy.Vehicle").getMethod("run");
            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("com.pig.spring.aop.proxy.Vehicle").getMethod("run");
            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());
        }
    }
}

梳理一下逻辑
/* 我们通过下面获取到的 proxyInstance ,它的运行类型其实是$Proxy0!! 只不过我们在这里用Vehicle 这个接口类型来接受罢了,但是这样不利于分析底层原理*/
Vehicle  proxyInstance = (Vehicle)Proxy.newProxyInstance(VehicleTest.class.getClassLoader(),
            new Class[]{Vehicle.class}, invocationHandler);

/* 如果写成下面这样,我想你会理解得更彻底!但是,之所以用接口类型来接收proxyInstance,我个人得看法是:
第一,$Proxy0 是在运行时底层生成得一个临时得代理类,我们在编译阶段没办法获取到,或者说我们编写代码的时候,根本就不存在这个类。另外,它有可能不叫$Proxy0,而是叫其他名字,比如$Proxy7...;
第二,由于动态绑定机制的存在,即运行时动态绑定到运行类型,又$Proxy0 实现了Vehicle接口,所以我用Vehicle 接收代理对象,是可以的,并且运行时动态绑定到$Proxy0 的run() 方法,妙哉!
第三,多态 + 反射 + 动态绑定  妙哉妙哉*/
$Proxy0  proxyInstance = ($Proxy0)Proxy.newProxyInstance(VehicleTest.class.getClassLoader(),
            new Class[]{Vehicle.class}, invocationHandler);

  1. 看看 Proxy.newProxyInstance(classLoader, interfaces, invocationHandler) 这个方法到底发生了什么?

    解读:我们传入 三个参数:classLoader, interfaces, invocationHandler,分别进行了相应的处理

    final Class<?>[] intfs = interfaces.clone();

    Class<?> cl = getProxyClass0(loader, intfs);

    final InvocationHandler ih = h;

    Proxy 类里面定义了 protected InvocationHandler h; 这个属性,其实之后我们写的InvocationHandler 的实现子类VehicleInvocationHandler 会传给这个属性

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

下面这张图是debug newInstance() 的结果,我们可以看到,Proxy类的 InvocationHandler h 这个属性值正是我们的VehicleInvocationHandler

image-20221117112721133

这是newProxyInstance() 方法的源码

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
{
    Objects.requireNonNull(h);
    // !!!!!!!!!!!!!!!!!!!!!!!!!!!!
    final Class<?>[] intfs = interfaces.clone();
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }

    /*
     * Look up or generate the designated proxy class.
     */
    // !!!!!!!!!!!!!!!!!!!!!!!!!!!!
    Class<?> cl = getProxyClass0(loader, intfs);

    /*
     * Invoke its constructor with the designated invocation handler.
     */
    try {
        if (sm != null) {
            checkNewProxyPermission(Reflection.getCallerClass(), cl);
        }

        final Constructor<?> cons = cl.getConstructor(constructorParams);
        // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        final InvocationHandler ih = h;
        if (!Modifier.isPublic(cl.getModifiers())) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    cons.setAccessible(true);
                    return null;
                }
            });
        }
        return cons.newInstance(new Object[]{h});
    } catch (IllegalAccessException|InstantiationException e) {
        throw new InternalError(e.toString(), e);
    } catch (InvocationTargetException e) {
        Throwable t = e.getCause();
        if (t instanceof RuntimeException) {
            throw (RuntimeException) t;
        } else {
            throw new InternalError(t.toString(), t);
        }
    } catch (NoSuchMethodException e) {
        throw new InternalError(e.toString(), e);
    }
}

摘取$Proxy0 关键部分进行分析

  1. 可以看出,m3 就是我们通过反射获得的接口方法的Method 对象
	private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;		

m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("com.pig.spring.aop.proxy.Vehicle").getMethod("run");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
  1. super.h.invoke(this, m3, (Object[])null); 方法解读

     	// 重点在这!!!!!!!!!!!!!!!!!!!!!!!
        public final void run() throws  {
            try {
                // 重点在这!!!!!!!!!!!!!!!!!
                // m3 = Class.forName("com.pig.spring.aop.proxy.Vehicle").getMethod("run");
                super.h.invoke(this, m3, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
    • super 指的是Proxy类,因为代理对象$Proxy0 继承了 Proxy 类
    • super.h 的h 正好是Proxy 类定义的 protected InvocationHandler h 这个属性,此时,这个h 传入的是我们的VehicleInvocationHandler
    • invoke() 方法,就是我们VehicleInvocationHandler 中重写的invoke() 方法!@Override public Object invoke(Object proxy, Method method, Object[] args)
    • 传入的参数 this,正是$Proxy 代理对象本身!
    • 传入的 method,此处是m3,正好是接口方法的Method 对象,即要被代理的方法对象
    • 传入的args,是接口方法的参数

    到此,为什么@Override public Object invoke(Object proxy, Method method, Object[] args) ,为什么这个invoke() 方法的第一个参数传入的是代理对象$Proxy,应该懂了吧!!

    到此,我们也解答了:为什么proxyInstance 调用run() 方法,最终会执行到invocationHandler.invoke()那里去这个疑惑。底层使用了多态,动态代理,反射等技术!

总结一下

@Test
public void testVehicleInvocationHandler() throws Throwable {
    VehicleInvocationHandler<Car> invocationHandler = new VehicleInvocationHandler<>(new Car());
    Vehicle                        proxyInstance     = (Vehicle)Proxy.newProxyInstance(VehicleTest.class.getClassLoader(),
            new Class[]{Vehicle.class}, invocationHandler);
    proxyInstance.run();
 	System.out.println("************************************************");
    invocationHandler.invoke(proxyInstance, Car.class.getDeclaredMethod("run"), null);
}

proxyInstance.run()最终是执行到了 super.h.invoke(this, m3, (Object[])null)这个方法!而super.h.invoke(this, m3, (Object[])null) 执行的就是我们重写的invoke() 方法:invocationHandler.invoke(proxyInstance, Car.class.getDeclaredMethod("run"), null); 所以输出的结果都一样。

因为我们懂了动态代理的原理,所以我们敢直接这么用invocationHandler.invoke(proxyInstance, Car.class.getDeclaredMethod("run"), null) ,并且正确输出。这只是一个实验而已,实际开发中,用proxyInstance.run() 就行了!我们这里只不过是将底层封装的方法,提上来直接调用而已啦!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值