jdk动态代理invoke方法自动运行原因

jdk动态代理invoke方法自动运行原因invoke方法介绍想要知道invoke方法为什么会自动调用我们先要来了解一下这个方法publicinterfaceInvocationHandler{publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)首先该方法来自于接口InvocationHandler,该接口中仅有一个invoke方法,该…

大家好,又见面了,我是你们的朋友全栈君。

invoke 方法介绍

想要知道 invoke方法为什么会自动调用我们先要来了解一下这个方法
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)

首先 该方法来自于接口InvocationHandler ,该接口中仅有一个invoke方法 ,该方法中有三个参数
* @param   proxy the proxy instance that the method was invoked 
这是接口源码注释中对第一个参数proxy的解释:方法被调用的代理实例   我们可以认为它就是一个代理实例

但是代理又是什么呢?这时候可能有小伙伴蒙了。
代理实例其实是代理类本身的一个实例,下面是我自己写的一个动态代理小案例

这是一个接口,动态代理必须有一个接口的存在
public interface PorxyInte { 
   
    public void test();
}
这是一个实现了接口的实现类,写的有些简便,主要看起来清晰
public class ProImp implements PorxyInte { 
   
    @Override
    public void test() { 
   
        System.out.println("test");
    }
}

这是测试类 通过了Proxy.newProxyInstance方法(具体作用等下说)返回了一个代理实例

  ProImp proImp = new ProImp();//创建了实例类对象
        PorxyInte porxyInte =(PorxyInte) Proxy.newProxyInstance(ProImp.class.getClassLoader(), ProImp.class.getInterfaces(), new InvocationHandler() { 
   
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
   
                System.out.println("..."+proxy.getClass().getName());//输出proxy对象的字节码文件名
                Method[] declaredMethods = this.getClass().getDeclaredMethods();
                for (Method declaredMethod : declaredMethods) { 
   
                    System.out.println(declaredMethod.getName());
                }

                method.invoke(proImp,null);
                return null;
            }
        });
        System.out.println(porxyInte.getClass().getName());//输出实现类的字节码文件名
        porxyInte.test();
com.sun.proxy.$Proxy0 // 实现类的字节码文件名
...com.sun.proxy.$Proxy0// 参数proxy的字节码文件名
在这边我们发现我自己定义的实现类和参数proxy的字节码文件名是一致的

而根据前面对参数proxy的注释定义,我们不难得出$Proxy0就是一个代理类

那么为什么我调用了.newProxyInstance方法会生成一个代理对象呢?

newProxyInstance方法介绍

这是package java.lang.reflect.proxy下的一个静态方法

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

我们需要注意的是它的三个参数

首先先介绍一下
第一个参数ClassLoader loader:类的加载器,传入我们自定义类的加载器

第二个参数Class<?>[] interfaces 注意很重要 这个参数是传入一个接口数组

第三个参数 h:类型是InvocationHandler,传入InvocationHandler接口的子类

在newProxyInstance方法中 所做的几件事情

!!重头戏来了

第一步:

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

调用了getProxyClass0方法, 该方法 需要传入两个参数 一个是类加载器,一个是接口数组
在方法getProxyClass0 中 会创建出一个类$Proxy0 ,并且创建出这个内部类的引用返回
我通过生出的内部类文件,反编译出源码可以看下:

public final class $Proxy0 extends Proxy
	implements PorxyInte
{ 
   

	private static Method m1;
	private static Method m3;
	private static Method m2;
	private static Method m0;
    // 看到这个构造方法, 调用了父类Proxy构造方法,将我们得invocationhandler实例传值过去
    //那这个构造方法触发的时机就是在 
    // proxy得代理方法中 return cons.newInstance(new Object[]{h}); 通过反射来执行代理类得构造方法,
    //从而将invocationhandler实例从代理类赋值到父类,
    //也就是为什么在下面test方法中在super.h 这个不会产生空指针得重要原因
	public $Proxy0(InvocationHandler invocationhandler)
	{ 
   
		super(invocationhandler);
	}

	public final boolean equals(Object obj)
	{ 
   
		try
		{ 
   
			return ((Boolean)super.h.invoke(this, m1, new Object[] { 
   
				obj
			})).booleanValue();
		}
		catch (Error ) { 
    }
		catch (Throwable throwable)
		{ 
   
			throw new UndeclaredThrowableException(throwable);
		}
	}

	public final void test()
	{ 
   
		try
		{ 
   
			super.h.invoke(this, m3, null);
			return;
		}
		catch (Error ) { 
    }
		catch (Throwable throwable)
		{ 
   
			throw new UndeclaredThrowableException(throwable);
		}
	}

	public final String toString()
	{ 
   
		try
		{ 
   
			return (String)super.h.invoke(this, m2, null);
		}
		catch (Error ) { 
    }
		catch (Throwable throwable)
		{ 
   
			throw new UndeclaredThrowableException(throwable);
		}
	}

	public final int hashCode()
	{ 
   
		try
		{ 
   
			return ((Integer)super.h.invoke(this, m0, null)).intValue();
		}
		catch (Error ) { 
    }
		catch (Throwable throwable)
		{ 
   
			throw new UndeclaredThrowableException(throwable);
		}
	}

	static 
	{ 
   
		try
		{ 
   
			m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { 
   
				Class.forName("java.lang.Object")
			});
			m3 = Class.forName("cn.itcast.web.Test.PorxyInte").getMethod("test", new Class[0]);
			m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
			m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
		}
		catch (NoSuchMethodException nosuchmethodexception)
		{ 
   
			throw new NoSuchMethodError(nosuchmethodexception.getMessage());
		}
		catch (ClassNotFoundException classnotfoundexception)
		{ 
   
			throw new NoClassDefFoundError(classnotfoundexception.getMessage());
		}
	}
}
注意:我们可以发现这个生成的类继承了proxy 并且实现了我自己定义的那个接口
这是因为在调用getProxyClass0 传入的接口数组,他会将这个数组遍历,并且实现


这是他实现的test方法:


public final void test()
	{ 
   
		try
		{ 
   
			super.h.invoke(this, m3, null);
			return;
		}
		catch (Error ) { 
    }
		catch (Throwable throwable)
		{ 
   
			throw new UndeclaredThrowableException(throwable);
		}
	}
  	 super不难理解  是proxy类对象
  	 h :但是h是什么我相信小伙伴们可能有点懵,这里留下个悬念,我们先把newProxyInstance方法聊完

	刚刚说过newProxyInstance第一步返回了$proxy引用对象

	第二步:
   //通过反射创建出构造器对象,并且传入constructorParams 类型参数
     private static final Class<?>[] constructorParams =
        { 
    InvocationHandler.class };
        
  final Constructor<?> cons = cl.getConstructor(constructorParams);//这是$proxy0的构造方法内部通过super(constructorParams) 创建父类对象
  

  

  

  //细心的小伙伴可能发现了,我们刚刚在$proxy中的h,在Proxy中进行了赋值
  //其实这个h就是我们在调用newProxyInstance方法是传进来的第三个参数
 
   final InvocationHandler ih = h;
最后一步:   return cons.newInstance(new Object[]{h});
通过反射 返回$proxy的实例

这个就是我们在掉用newProxyInstance方法所做的事情

那么先回到我们之前的疑问?invoke方法为什么会自动运行?

我相信现在小伙伴们也能理解
总体流程: 我在测试类中通过返回的$proxy引用调用test方法

 porxyInte.test();

这时候会去调用$proxy方法中的test方法

	public final void test()
	{ 
   
		try
		{ 
   
			super.h.invoke(this, m3, null);
			return;
		}
		catch (Error ) { 
    }
		catch (Throwable throwable)
		{ 
   
			throw new UndeclaredThrowableException(throwable);
		}
	}

在test方法中
super代表父类Proxy,h代表父类中的变量,也就是我们传进来的InvocationHandler接口实例
然后又调用了实例中的invoke方法,这个时候是不是就一目了然,这就是为什么我们调用test方法,而 InvocationHandler中的invoke方法会自动运行的原因,这是因为在代理类中的test方法内容重新定义了

需要注意的点:
第一点:
很多刚接触代理的小萌新包括博主我 在刚开始的时候都一直代理类当做是我们的自定义实现类对象
代理类不是我们定义的类,而是Proxy创建的$proxy类
第二点:
invoke方法中的第一个参数Proxy,这边注意Proxy在invoke方法被赋值为this,this是谁呢?
他就是调用test方法的对象也就是我们的代理实例

	return (String)super.h.invoke(this, m2, null);

到此为止,为什么动态代理invoke方法会自动运行的原因了
以上所有理解:都是博主自己的理解,如果有错误的地方,还请大家能一一指出,谢谢!!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/133191.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • linux加静态路由命令,LINUX添加静态路由「建议收藏」

    linux加静态路由命令,LINUX添加静态路由「建议收藏」LINUX添加静态路由建议,先用命令添加,测试生效未有其他影响后,添加到配置文件中。以本次127前置添加静态路由为例用户需要添加路由如下,命令格式为windows添加格式。route-a-p10.113.70.0mask255.255.255.010.113.65.44route-a-p10.113.70.240mask255.255.255.24010.113.6…

    2022年10月4日
    7
  • 透彻掌握Spring整合MyBatis的原理彻底征服面试官,建议收藏

    透彻掌握Spring整合MyBatis的原理彻底征服面试官,建议收藏  在MyBatis篇内容的最后我们来给大家详细介绍下Spring是如何整合MyBatis的。让大家彻底掌握MyBatis的底层设计原理及实现。MyBatis整合Spring原理  把MyBatis集成到Spring里面,是为了进一步简化MyBatis的使用,所以只是对MyBatis做了一些封装,并没有替换MyBatis的核心对象。也就是说:MyBatisjar包中的SqlSessionFactory、SqlSession、MapperProxy这些类都会用到。mybatis-spring.jar里

    2022年5月30日
    39
  • Ubuntu16.04 apache2配置虚拟主机[通俗易懂]

    Ubuntu16.04 apache2配置虚拟主机[通俗易懂]1.首先在“/etc/hosts”文件中加入当前主机的IP地址和需要设置的虚拟主机名: 如:192.168.0.143www.moyaping2.com 2.在“/etc/apache2/sites-available”目录下有“000-default.conf” 将000-default.conf复制一份叫做redis.conf 3进入redis.conf 修改Se

    2026年1月16日
    3
  • 计算机网络网络适配器的作用是什么原因,网络适配器是什么东西?网络适配器主要功能…

    计算机网络网络适配器的作用是什么原因,网络适配器是什么东西?网络适配器主要功能…网络适配器就是俗称的网卡,网卡是工作在链路层的网络组件,是局域网中连接计算机和传输介质的接口,简单来说就是,网卡有问题网络就有问题。网卡是工作在链路层的网络组件,是连接计算机和传输介质的接口。但是很多朋友还是不知道网络适配器是什么。下面window小编就来具体说说网络适配器是什么适配器是一个接口转换器,适配器可以是一个独立的硬件接口设备也可以是信息接口。网络适配器就是一种信息接口,用来接受或发送网…

    2022年5月23日
    67
  • 免费版(个人家庭免费使用)xshell7 和 xftp7 下载

    免费版(个人家庭免费使用)xshell7 和 xftp7 下载xshell6、xftp6个人免费版:百度云下载地址:https://pan.baidu.com/s/19mTPpYgXo65u9SCI1IINPQ密码:9wr0安装完毕,启动时会有弹出框,关闭即可缺点:一个xshell中shell窗口个数最多四个,有限制,可以下载下面xmanager5套件,使用不受限制xmanager5[包含xshell,xftp5]:xman…

    2022年10月12日
    3
  • Binary Tree Postorder Traversal — LeetCode

    Binary Tree Postorder Traversal — LeetCode原题链接: http://oj.leetcode.com/problems/binary-tree-postorder-traversal/ 跟BinaryTreeInorderTraversal以及BinaryTreePreorderTraversal一样,二叉树的后序遍历我们还是介绍三种方法,第一种是递归,第二种是迭代方法,第三种是用线索二叉树。递归还是那么简单,算法

    2025年6月28日
    3

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号