百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程网 > 正文

Java反射之Method的invoke方法实现

yuyutoo 2024-11-12 14:57 10 浏览 0 评论

使用reflect(反射)包下面的Field和Method类获得类的属性和方法,并对属性和方法进行操作。

在框架中经常会会用到method.invoke()方法,用来执行某个的对象的目标方法。以前写代码用到反射时,总是获取先获取Method,然后传入对应的Class实例对象执行方法。然而前段时间研究invoke方法时,发现invoke方法居然包含多态的特性,这是以前没有考虑过的一个问题。那么Method.invoke()方法的执行过程是怎么实现的?它的多态又是如何实现的呢?

本文将从java和JVM的源码实现深入探讨invoke方法的实现过程。

首先给出invoke方法多态特性的演示代码:

public class MethodInvoke {	public static void main(String[] args) throws Exception {		Method animalMethod = Animal.class.getDeclaredMethod("print");		Method catMethod = Cat.class.getDeclaredMethod("print");			Animal animal = new Animal();		Cat cat = new Cat();		animalMethod.invoke(cat);		animalMethod.invoke(animal);				catMethod.invoke(cat);		catMethod.invoke(animal);	}} class Animal {	public void print() {		System.out.println("Animal.print()");	}} class Cat extends Animal {	@Override	public void print() {		System.out.println("Cat.print()");	}}

代码中,Cat类覆盖了父类Animal的print()方法, 然后通过反射分别获取print()的Method对象。最后分别用Cat和Animal的实例对象去执行print()方法。其中animalMethod.invoke(animal)和catMethod.invoke(cat),示例对象的真实类型和Method的声明Classs是相同的,按照预期打印结果;animalMethod.invoke(cat)中,由于Cat是Animal的子类,按照多态的特性,子类调用父类的的方法,方法执行时会动态链接到子类的实现方法上。因此,这里会调用Cat.print()方法;而catMethod.invoke(animal)中,传入的参数类型Animal是父类,却期望调用子类Cat的方法,因此这一次会抛出异常。代码打印结果为:

Cat.print()Animal.print()Cat.print()Exception in thread "main" java.lang.IllegalArgumentException: object is not an instance of declaring class	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)	at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)	at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)	at java.lang.reflect.Method.invoke(Unknown Source)	at com.wy.invoke.MethodInvoke.main(MethodInvoke.java:17)

接下来,我们来看看invoke()方法的实现过程。

public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException{        if (!override) {            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {                Class<?> caller = Reflection.getCallerClass(1);                 checkAccess(caller, clazz, obj, modifiers);            }        }        MethodAccessor ma = methodAccessor;             // read volatile    if (ma == null) {        ma = acquireMethodAccessor();    }    return ma.invoke(obj, args);}

invoke()方法中主要分为两部分:访问控制检查和调用MethodAccessor.invoke()实现方法执行。

首先看一下访问控制检查这一块的逻辑。第一眼看到这里的逻辑的时候,很容易搞不清楚是干嘛的。通俗来讲就是通过方法的修饰符(public/protected/private/package),来判断方法的调用者是否可以访问该方法。这是java的基础内容,不过用代码写出来,一下子不容易想到。访问控制检查分为3步:

  1. 检查override,如果override为true,跳过检查;否则继续;
  2. 快速检查,判断该方法的修饰符modifiers是否为public,如果是跳过检查;否则继续;
  3. 详细检查,通过方法的(protected/private/package)修饰符或方法的声明类(例如子类可以访问父类的protected方法)与调用者caller之间的关系,判断caller是否有权限访问该方法。

override属性是Method的父类AccessibleObject中声明的变量,使得程序可以控制是否跳过访问权限的检查。另外,Method的实例对象中,override属性的初始值设置为false。

public void setAccessible(boolean flag) throws SecurityException {        SecurityManager sm = System.getSecurityManager();        if (sm != null) sm.checkPermission(ACCESS_PERMISSION);        setAccessible0(this, flag);    }     private static void setAccessible0(AccessibleObject obj, boolean flag)        throws SecurityException    {        if (obj instanceof Constructor && flag == true) {            Constructor<?> c = (Constructor<?>)obj;            if (c.getDeclaringClass() == Class.class) {                throw new SecurityException("Can not make a java.lang.Class" +                                        " constructor accessible");        }    }    obj.override = flag;}

多说一句,Field同样继承了AccessibleObject,且Field的override也是初始化为false的,也就是说并没有按照变量的修饰符去初始化不同的值。但是我们在调用Field.set(Object obj, Object value)时,如果该Field是private修饰的,会因没有访问权限而抛出异常,因此必须调用setAccessible(true)。此处非常容易理解为因为变量是public的,所以override就被初始化为true。

invoke()方法中,访问控制检查之后,就是通过MethodAccessor.invoke()调用方法。再来看一下代码:

MethodAccessor ma = methodAccessor;             // read volatileif (ma == null) {    ma = acquireMethodAccessor();}return ma.invoke(obj, args);

这里的逻辑很简单,首先将变量methodAccessor赋值给ma,在方法栈中保存一个可以直接引用的本地变量,如果methodAccessor不存在,调用acquireMethodAccessor()方法创建一个。

    private volatile MethodAccessor methodAccessor;    private Method root;        private MethodAccessor acquireMethodAccessor() {        // First check to see if one has been created yet, and take it        // if so        MethodAccessor tmp = null;        if (root != null) tmp = root.getMethodAccessor();        if (tmp != null) {            methodAccessor = tmp;        } else {            // Otherwise fabricate one and propagate it up to the root            tmp = reflectionFactory.newMethodAccessor(this);            setMethodAccessor(tmp);        }         return tmp;    }     void setMethodAccessor(MethodAccessor accessor) {        methodAccessor = accessor;        // Propagate up        if (root != null) {            root.setMethodAccessor(accessor);        }    }     Method copy() {        Method res = new Method(clazz, name, parameterTypes, returnType,                                exceptionTypes, modifiers, slot, signature,                                annotations, parameterAnnotations, annotationDefault);        res.root = this;        res.methodAccessor = methodAccessor;        return res;    }

综合acquireMethodAccessor(),setMethodAccessor()以及copy()这三个方法,可以看到一个Method实例对象维护了一个root引用。当调用Method.copy()进行方法拷贝时,root指向了被拷贝的对象。那么当一个Method被多次拷贝后,调用一次setMethodAccessor()方法,就会将root引用所指向的Method的methodAccessor变量同样赋值。例如:D -> C -> B -> A,其中X-> Y表示X = Y.copy(), 当C对象调用setMethodAccessor()时,B和A都会传播赋值methodAccessor, 而D的methodAccessor还是null。紧接着,当D需要获取methodAccessor而调用acquireMethodAccessor()时,D获取root的methodAccessor, 那么D将和ABC持有相同的methodAccessor。

虽然Method中,通过维护root引用意图使相同的方法始终保持只有一个methodAccessor实例,但是上述方法仍然无法保证相同的方法只有一个methodAccessor实例。例如通过copy()使ABCD保持关系:D -> C -> B -> A, 当B对象调用setMethodAccessor()时,B和A都会赋值methodAccessor, 而C、D的methodAccessor还是null。这时D调用acquireMethodAccessor()时,D获取root也就是C的methodAccessor,发现为空,然后就新创建了一个。从而出现了相同的方法中出现了两个methodAccessor实例对象的现象。

在Class.getMethod()、Class.getDeclaredMethod()以及Class.getDeclaredMethod(String name, Class<?>... parameterTypes)方法中最终都会调用copy()方法来保障Method使用的安全性。 在比较极端加巧合的情况下,可能会引起类膨胀的问题,这就是接下来要讲到的MethodAccessor的实现机制。

在前面代码中,MethodAccessor的创建是通过反射工厂ReflectionFactory的newMethodAccessor(Method)方法来创建的。

public MethodAccessor newMethodAccessor(Method method) {        checkInitted();     if (noInflation) {        return new MethodAccessorGenerator().            generateMethod(method.getDeclaringClass(),                           method.getName(),                           method.getParameterTypes(),                           method.getReturnType(),                           method.getExceptionTypes(),                           method.getModifiers());    } else {        NativeMethodAccessorImpl acc = new NativeMethodAccessorImpl(method);        DelegatingMethodAccessorImpl res = new DelegatingMethodAccessorImpl(acc);        acc.setParent(res);        return res;    }}

其中, checkInitted()方法检查从配置项中读取配置并设置noInflation、inflationThreshold的值:


熊猫追剧-在线视频网站-海量高清视频免费在线观看

相关推荐

YAML配置文件简介及使用(yaml 配置)

简介YAML是"YAMLAin'taMarkupLanguage"(YAML不是一种标记语言)的缩写。相比JSON格式的方便。...

教你如何解决最常见的58种网络故障排除方法

1.故障现象:网络适配器(网卡)设置与计算机资源有冲突。分析、排除:通过调整网卡资源中的IRQ和I/O值来避开与计算机其它资源的冲突。有些情况还需要通过设置主板的跳线来调整与其它资源的冲突。2.故障现...

一分钟带你了解服务器网卡(服务器网卡怎么用)

今天小编和大家聊一下服务器的网卡。什么是网卡?简单说网卡就是计算机与局域网互连的设备。计算机主要通过网卡接入网络。网卡又称为网络适配器或网络接口卡NIC(NetworkinterfaceCard)...

linux文件之ssh配置文件的含义与作用

ssh远程登录命令是操作系统(包括linux和window系统)下常用的操作命令,可以帮助用户,远程登录服务器系统,查看,操作系统相关信息。linux系统对于ssh命令有专门保存其相关配置的目录和文件...

Cilium 官方文档翻译 - IPAM(二)Kubernetes Host模式

KubernetesHostScopeciliumIPAM的kuberneteshost-scope模式通过选项ipam:kubernetes开启,将集群IP地址分配委托给每个独立的节点,并...

域名劫持跳转,域名劫持跳转的解决办法只需5步

简单来说,域名劫持就是把原本准备访问某网站的用户,在不知不觉中,劫持到仿冒的网站上,例如用户准备访问某家知名品牌的网上商店,黑客就可以通过域名劫持的手段,把其带到假的网上商店,同时收集用户的ID信息和...

Linux基本命令(linux基本命令总结)

...

Linux 磁盘和文件系统管理(linux磁盘管理fdisk)

1检测并确认新硬盘...

windows host文件怎么恢复?局域网访问全靠这些!

windowshost文件怎么恢复?windowshost文件是常用网址域名及其相应IP地址建立一个关联文件,通过这个host文件配置域名和IP的映射关系,以提高域名解析的速度,方便局域网用户使用...

Nginx配置文件详解与优化建议(nginx 配置详解)

1、概述今天来详解一下Nginx的配置文件,以及给出一些配置建议,希望能对大家有所帮助。...

Mac电脑hosts文件锁定,如何修改hosts文件权限

有时候我们需要修改hosts文件,但是网上很多教程都行不通,使用sudo命令也不行。其实有一个很简单的方法。打开终端命令行,使用如下命令即可:sudochflags-hvnoschg/etc/...

windows电脑如何修改hosts文件?(windows 修改hosts文件)

先来简单说下电脑host的作用hosts文件的作用:hosts文件是一个用于储存计算机网络中各节点信息的计算机文件;作用是将一些常用的网址域名与其对应的IP地址建立一个关联“数据库”,当用户在浏览器中...

Vigilante恶意软件行为怪异:修改Hosts文件以阻止受害者访问盗版网站

Sophos刚刚报道了一款名叫Vigilante的恶意软件,但其行为却让许多受害者感到不解。与其它专注于偷密码、搞破坏、或勒索赎金的恶意软件不同,Vigilante会通过修改Hosts文件...

hosts文件无法修改几种现象和解决方法

第一种、hosts文件修改完不是直接保存而是弹出另存为窗口解决:1、右击hosts文件——属性——把“只读”前面勾去掉。第二种、打开hosts文件时提示“你没有权限打开该文件,请向文件的所有者或管理员...

hosts文件位置在哪里,教你hosts文件位置在哪里

Hosts是一个没有扩展名的系统文件,其基本作用就是将一些常用的网址域名与其对应的IP地址建立一个关联"数据库",当用户在浏览器中输入一个需要登录的网址时,系统会首先自动从Hosts文件中寻找对应的I...

取消回复欢迎 发表评论: