程序员最近都爱上了这个网站  程序员们快来瞅瞅吧!  it98k网:it98k.com

本站消息

站长简介/公众号

  出租广告位,需要合作请联系站长


+关注
已关注

分类  

暂无分类

标签  

暂无标签

日期归档  

2023-06(1)

java源码分析-反射Method类

发布于2021-06-12 14:04     阅读(808)     评论(0)     点赞(16)     收藏(1)


java源码分析-反射Method类

1.是什么

​ Method类提供有关类或接口上单个方法的信息和访问权限。Method反映的方法可以是类上的实力方法或者是静态方法也可以是接口上的抽象方法。

public final class Method extends Executable

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yy6fQ1Lu-1611389562488)(C:\Users\viruser.v-desktop\AppData\Roaming\Typora\typora-user-images\image-20210119185214550.png)]

通过类图,可以看到Executable类是Mehod的父类。实际上它是Method和Constructor的公共基类。

2.如果获取

有以下集中方法可以获取到Method类:

2.1获取所有的方法

通过Class类的getDeclaredMethods方法可以获取某个类所定义的所有方法。

public Method[] getDeclaredMethods() throws SecurityException

2.2获取公共方法

通过Class的getMethods方法可以获取到所有public修饰的公共方法。

public Method[] getMethods() throws SecurityException		

2.3获取指定方法

通过Class类的getMethod方法,传入方法名称和参数列表类型,可以获取到某个类的指定方法。

public Method getMethod(String name, Class<?>... parameterTypes)
        throws NoSuchMethodException, SecurityException

测试:

创建一个实体类:

package test.java.lang.reflect;

/**
 * @author sj
 * @title: MethodDemo
 * @description: TODO
 * @date 2021/1/1820:30
 */
public class MethodDemo {

    private Integer id;

    private String name;

    public MethodDemo() {
    }

    public MethodDemo(Integer id, String name) {
        this.id = id;
        this.name = name;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public static void test(String str){
        System.out.println(str);
    }

    private static String getStr(){
        return "hello getStr!";
    }

}

创建测试类:

package test.java.lang.reflect;

import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * @author sj
 * @title: MethodTest
 * @description: TODO
 * @date 2021/1/1820:32
 */
public class MethodTest {

    public static void main(String[] args) {
        try {
            Class clazz = Class.forName("test.java.lang.reflect.MethodDemo");
            //获取所有
            Method[] methods =  clazz.getDeclaredMethods();
            System.out.println(Arrays.toString(methods));
            //获取public方法
            Method[] methods1 = clazz.getMethods();
            System.out.println(Arrays.toString(methods1));
            //根据方法名称和参数列表类型获取Method
            Method setNameMethod = clazz.getMethod("setName", String.class);
            System.out.println(setNameMethod);
        } catch (ClassNotFoundException | NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}

结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fwa7i5oV-1611389562490)(C:\Users\viruser.v-desktop\AppData\Roaming\Typora\typora-user-images\image-20210119190614777.png)]

3.常用方法

3.1getParameterTypes()

​ 用于获取该方法的所有参数列表类型。

//获取所有参数列表类型
public Class<?>[] getParameterTypes()

测试:

//根据方法名称和参数列表类型获取Method
Method setNameMethod = clazz.getMethod("setName", String.class);
//获取参数列表类型
Class<?>[] parameterTypes = setNameMethod.getParameterTypes();
System.out.println(Arrays.toString(parameterTypes));

结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0r3KdChW-1611389562492)(C:\Users\viruser.v-desktop\AppData\Roaming\Typora\typora-user-images\image-20210119191655610.png)]

3.2

4.重点解析

invock方法

public Object invoke(Object obj, Object... args)
        throws IllegalAccessException, IllegalArgumentException,
           InvocationTargetException

该方法是调用Method对象底层方法,相当于调用了声明类的实际方法。如:

Method setNameMethod = clazz.getMethod("setName", String.class);
MethodDemo methodDemo = new MethodDemo();
setNameMethod.invoke(methodDemo, "张三");
System.out.println(methodDemo.getName());

结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qExJyS7f-1611389562494)(C:\Users\viruser.v-desktop\AppData\Roaming\Typora\typora-user-images\image-20210119210123518.png)]

上面我们可以看到,当我们通过反射调用method.invoke()方法时,其实就类似与调用MethodDemo类本身的setName()方法。那它底层到底是怎么实现的呢?我们看一下源码:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2CUk71Ms-1611389562496)(C:\Users\viruser.v-desktop\AppData\Roaming\Typora\typora-user-images\image-20210119210355580.png)]

通过源码我们大致将invoke方法 的执行分为三步:

(1)校验权限;(这里就不细看了,其实就是对访问权限进行校验)

(2)获取方法访问器MethodAccessor;

(3)通过MethodAccessor执行invoke方法。

MethodAccessor方法访问器

在对第2)步进行解析之前,我们先看一下MethodAccessor是什么?

public interface MethodAccessor {
    Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException;
}

我们可以看到MethodAccessor是一个接口,其中只有一个invoke方法。而它的实现类如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o7z3dOEe-1611389562496)(C:\Users\viruser.v-desktop\AppData\Roaming\Typora\typora-user-images\image-20210119212042430.png)]

我们发现它有三个实现类(实际是两个具体的实现类):

MethodAccessorImpl:是一个抽象类。

NativeMethodAccessorImpl:底层调用本地方法来实现invoke0,如下图。

DelegatingMethodAccessorImpl:代理类。其实也调用了NativeMethodAccessorImpl类来实现invoke方法(numInvocations < 15 ,后面会解释)。

获取方法访问器MethodAccessor

下面我们来看一下是怎么获取MethodAccessor的。

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;
    }

源码中root是每一个Method对象都包含一个root对象,而这个root对象里就持有一个MethodAccessor对象,我们获取到一个Method对象就相当于获取到一个root对象的镜像,所有该类Method都共享root里面的MethodAccessor对象。

所以,这里首先会判断root中是否含有MethodAccessor,如果有,就直接使用,若没有则通过ReflectionFactory工厂创建一个MethodAccessor。

public MethodAccessor newMethodAccessor(Method var1) {
        checkInitted();//1)校验是否初始化
        if (noInflation && !ReflectUtil.isVMAnonymousClass(var1.getDeclaringClass())) {//2)根据条件生成对应的MethodAccessor实现类
            return (new MethodAccessorGenerator()).generateMethod(var1.getDeclaringClass(), var1.getName(), var1.getParameterTypes(), var1.getReturnType(), var1.getExceptionTypes(), var1.getModifiers());
        } else {
            NativeMethodAccessorImpl var2 = new NativeMethodAccessorImpl(var1);
            DelegatingMethodAccessorImpl var3 = new DelegatingMethodAccessorImpl(var2);
            var2.setParent(var3);
            return var3;
        }
    }

看到newMethodAccessor()方法的源码主要分为两部分:

1)校验是否初始化

private static void checkInitted() {
        if (!initted) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    if (System.out == null) {
                        return null;
                    } else {
                        String var1 = System.getProperty("sun.reflect.noInflation");
                        if (var1 != null && var1.equals("true")) {
                            ReflectionFactory.noInflation = true;
                        }

                        var1 = System.getProperty("sun.reflect.inflationThreshold");
                        if (var1 != null) {
                            try {
                                ReflectionFactory.inflationThreshold = Integer.parseInt(var1);
                            } catch (NumberFormatException var3) {
                                throw new RuntimeException("Unable to parse property sun.reflect.inflationThreshold", var3);
                            }
                        }

                        ReflectionFactory.initted = true;
                        return null;
                    }
                }
            });
        }
    }

通过上面我们看到这里的初始化主要是校验ReflectionFactory.inflationThreshold的值是否被初始化和ReflectionFactory.noInflation是否被设置为true。

而这两个值都是从系统配置文件中获取到的。如果没有获取到也会有一个默认值:

private static int inflationThreshold = 15;

那么这两个值到底是干什么的呢?我们这里暂时埋个伏笔,后面再来解释。

我们继续回到newMethodAccessor()方法的2)部分。

2)根据条件生成对应的MethodAccessor实现类

​ 首先会判断noInflation是否为true,并且不是匿名内部类。如果满足条件则调用new MethodAccessorGenerator().generateMethod方法返回MethodAccessor。

​ 否则就会返回DelegatingMethodAccessorImpl实现类,实际是NativeMethodAccessorImpl的代理。

再来看一下new MethodAccessorGenerator().generateMethod做了些什么:

...
public MethodAccessor generateMethod(Class<?> var1, String var2, Class<?>[] var3, Class<?> var4, Class<?>[] var5, int var6) {
	return (MethodAccessor)this.generate(var1, var2, var3, var4, var5, var6, false, false, (Class)null);
}

...
private MagicAccessorImpl generate(final Class<?> var1, String var2, Class<?>[] var3, Class<?> var4, Class<?>[] var5, int var6, boolean var7, boolean var8, Class<?> var9) {
        ByteVector var10 = ByteVectorFactory.create();
        this.asm = new ClassFileAssembler(var10);
        this.declaringClass = var1;
        this.parameterTypes = var3;
        this.returnType = var4;
        this.modifiers = var6;
        this.isConstructor = var7;
        this.forSerialization = var8;
        this.asm.emitMagicAndVersion();
        short var11 = 42;
      
    ...//代码有点唱,这里就省略了,有兴趣可以去看源码

        if (this.asm.cpi() != var11) {
            throw new InternalError("Adjust this code (cpi = " + this.asm.cpi() + ", numCPEntries = " + var11 + ")");
        } else {
            this.asm.emitShort((short)1);
            this.asm.emitShort(this.thisClass);
            this.asm.emitShort(this.superClass);
            this.asm.emitShort((short)0);
            this.asm.emitShort((short)0);
            this.asm.emitShort((short)2);
            this.emitConstructor();
            this.emitInvoke();
            this.asm.emitShort((short)0);
            var10.trim();
            final byte[] var17 = var10.getData();
            return (MagicAccessorImpl)AccessController.doPrivileged(new PrivilegedAction<MagicAccessorImpl>() {
                public MagicAccessorImpl run() {
                    try {
                        return (MagicAccessorImpl)ClassDefiner.defineClass(var13, var17, 0, var17.length, var1.getClassLoader()).newInstance();	//这里是重点,构造一个MagicAccessorImpl实例对象
                    } catch (IllegalAccessException | InstantiationException var2) {
                        throw new InternalError(var2);
                    }
                }
            });
        }
    }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8p6q1VSf-1611389562497)(C:\Users\viruser.v-desktop\AppData\Roaming\Typora\typora-user-images\image-20210120210500522.png)]

通过看该方法源码我们看见最终就是通过调用newInstance()方法来生成一个MagicAccessorImpl对象,而这个对象就包含了我们一开始需要带调用的方法invoke()的实现。换句话来说就是相当于构建了一个专门含有invoke()方法的对象。为什么要这么做呢。

其实,MethodAccessor实现有两个版本,一个是Java实现的,就是上面说的MagicAccessorImpl,另一个是通过本地方法native(c/c++语言)实现的。

Java实现的版本在初始化时需要较多时间,类加载那一套急需要时间又需要内存,但长久来说性能较好;

native版本正好相反,启动时相对较快,但运行时间长了之后速度就比不过Java版了。这是HotSpot的优化方式带来的性能特性,同时也是许多虚拟机的共同点:跨越native边界也就是从java程序跨越到c/c++程序会对优化有阻碍作用,它就像个黑箱一样让虚拟机难以分析也将其内联,于是运行时间长了之后反而是托管版本的代码更快些。

为了权衡两个版本的性能,Sun的JDK使用了“inflation”的技巧:让Java方法在被反射调用时,开头若干次使用native版,等反射调用次数超过阈值时则生成一个专用的MethodAccessor实现类,生成其中的invoke()方法的字节码,以后对该Java方法的反射调用就会使用Java版。

Sun的JDK是从1.4系开始采用这种优化的。

PS.可以在启动命令里加上-Dsun.reflect.noInflation=true,就会RefactionFactory的noInflation属性就变成true了,这样不用等到15调用后,程序一开始就会用java版的MethodAccessor了。

看到这里我们是不是想起我们前面埋得伏笔了。对的,前面说的ReflectionFactory.inflationThreshold和ReflectionFactory.noInflation就是系统中的变量,ReflectionFactory.noInflation的作用就像一个启动器,如果它为true,那么在进行invoke方法是底层就会直接使用java版本的MethodAccessor对象;否则,就会在返回代表本地方法的NativeMethodAccessorImpl实现类。

ReflectionFactory.inflationThreshold就相当与一个阈值,当超过这个阈值时,也会使用java版本的MethodAccessor对象;

通过MethodAccessor执行invoke方法

下面我们来看invoke方法最后一部分,调用MethodAccessor执行invoke方法。

public interface MethodAccessor {
    Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException;
}

上面说过MethodAccessor是一个接口,具体的实现在不同的实现类中。我们看一下NativeMethodAccessorImpl类:

 public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
        if (++this.numInvocations > ReflectionFactory.inflationThreshold() && !ReflectUtil.isVMAnonymousClass(this.method.getDeclaringClass())) {
            MethodAccessorImpl var3 = (MethodAccessorImpl)(new MethodAccessorGenerator()).generateMethod(this.method.getDeclaringClass(), this.method.getName(), this.method.getParameterTypes(), this.method.getReturnType(), this.method.getExceptionTypes(), this.method.getModifiers());
            this.parent.setDelegate(var3);
        }

        return invoke0(this.method, var1, var2);
    }

private static native Object invoke0(Method var0, Object var1, Object[] var2);

再次看到了noInflation机制,这里是判断numInvocations的值是否大于15默认,如果没有,那么就直接调用native本地方法invoke0来最终执行调用。

如果noInflation超过15,就会像上面说的也会实现一个java版本的MethodAccessor实现,最终调用其java方法来执行调用。

5.总结

1)Method类是是用来封装java类中方法的相关信息,是对java所有方法的抽象;

2)可以通过Class对象获取到Method对象;

3)Method对象提供了很多方法来获取某个类的某个方法相关信息。

4)Method.invoke()方法底层本身不是Method类去实现,而是委托给了MethodAccessor及其实现类来实现;同时采用了noInflation机制,在默认情况下前十五次都是调用本地方法invoke0来执行,十五次之后就会创建一个java对象来执行方法。

原文链接:https://blog.csdn.net/m0_37258694/article/details/113055952



所属网站分类: 技术文章 > 博客

作者:小泽圈儿郎

链接:http://www.javaheidong.com/blog/article/222451/a8932a629727c8966d70/

来源:java黑洞网

任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任

16 0
收藏该文
已收藏

评论内容:(最多支持255个字符)