java源码分析-反射Constructor类

java源码分析-反射Constructor类

1.是什么?

​ Constructor是java反射时用于表示构造函数的抽象,它包含一个类的构造函数的相关信息。java中一切都是对象,那么每一个构造函数也是一个对象,把这写构造函数抽象出来,就是Constructor类。

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

public final class Constructor<T> extends Executable 

Executable:它是Method和Constructor的公共基类。它继承了AccessibleObject类,实现了Member和GenericDeclaration接口。继承AccessibleObject类意味着Executable(方法和构造方法)可以通过isAccessible()方法来获取是否要进行访问控制权限的校验,还可以通过setAccessible()方法来设置是否要进行访问控制权限的校验。

2.如何获取

​ 有以下三种方式可以获取到Constructor类。

2.1获取所有的构造方法类

​ 可以通过Class类的getDeclaredConstructors方法来获取某个类的所有定义的构造方法。

//获取所有构造器
public Constructor<?>[] getDeclaredConstructors() throws SecurityException

2.2获取公共构造方法类

​ 可以通过Class类的getConstructors方法来获取某个类的所有定义的公共构造方法,即访问修饰符为public。

//获取公共构造器
public Constructor<?>[] getConstructors() throws SecurityException

2.3根据参数列表获取构造方法类

​ 可以根据传入的传输列表类型获取指定的构造方法类。

//根据参数类型获取构造器
public Constructor<T> getConstructor(Class<?>... parameterTypes)
        throws NoSuchMethodException, SecurityException

测试:

定义一个类,含有多个构造方法

package test.java.lang.reflect;

import java.util.Date;

/**
 * @author sj
 * @title: ConstructorDemo1
 * @description: TODO
 * @date 2021/1/15 14:27
 */
public class ConstructorDemo {

    private Integer id;

    private String name;

    private ConstructorDemo() {
    }

    private ConstructorDemo(Integer id) {
        this.id = id;
    }

    public ConstructorDemo(String name) {
        this.name = name;
    }

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

写一个测试类:

package test.java.lang.reflect;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.Arrays;

/**
 * @author sj
 * @title: ConstructorDemo
 * @description: 学习反射Constructor类源码
 * @date 2021/1/1417:02
 */
public class ConstructorTest {

    public static void main(String[] args) {
        try {
            Class clazz = Class.forName("test.java.lang.reflect.ConstructorDemo");
            Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
            System.out.println(Arrays.toString(declaredConstructors));
            Constructor[] constructors = clazz.getConstructors();
            System.out.println(Arrays.toString(constructors));
            Constructor constructor1 = clazz.getConstructor(Integer.class, String.class);
            System.out.println(constructor1);
            Constructor constructor2 = clazz.getConstructor(Integer.class);
            System.out.println(constructor2);
        } catch (ClassNotFoundException | NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}

结果:

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

​ 前三个都能正常打印处结果获取到指定的信息。

​ 但是第四个 Constructor constructor2 = clazz.getConstructor(Integer.class);却报错了。原因是因为参数列表是Integer的构造函数是private私有的,所以无法通过getConstructor(Integer.class)方法获取到。具体可以参考《java反射源码分析-Class》这篇文章。

3.能干什么

​ 下面我们了解一下构造函数Constructor类能做什么。

3.1getParameterTypes()

​ 获取该构造器的参数列表类型。

public Class<?>[] getParameterTypes()

测试:

Class clazz = Class.forName("test.java.lang.reflect.ConstructorDemo");
Constructor constructor1 = clazz.getConstructor(Integer.class, String.class);
//获取构造器的参数列表类型
Class[] parameterTypes = constructor1.getParameterTypes();
System.out.println(Arrays.toString(parameterTypes));

结果:

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

3.2getParameters()

获取构造器的参数列表

public Parameter[] getParameters() 

测试:

Parameter[] parameters = constructor1.getParameters();
System.out.println(Arrays.toString(parameters));

结果:

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

3.3getDeclaringClass()

获取该构造器所属类类型

public Class<T> getDeclaringClass()

测试:

Class declaringClass = constructor1.getDeclaringClass();
System.out.println(declaringClass);

结果:

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

3.4getDeclaredAnnotations()

获取构造器上标注的注解

public Annotation[] getDeclaredAnnotations()

测试:我们首先在ConstructorDemo(Integer bath, String name) 构造函数上随意加上一个注解,如@Deprecated,然后测试:

Annotation[] declaredAnnotations = constructor1.getDeclaredAnnotations();
System.out.println(Arrays.toString(declaredAnnotations));

得到结果如下:

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

3.5getExceptionTypes()

获取构造上的异常类型

public Class<?>[] getExceptionTypes()

测试:首先在ConstructorDemo(Integer bath, String name) 构造函数上加上异常Exception,然后测试:

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

Class[] exceptionTypes = constructor1.getExceptionTypes();
System.out.println(Arrays.toString(exceptionTypes));

结果:

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

3.6getModifiers()

获取构造器的标识语言描述符的整数值

public int getModifiers()

测试:

int modifiers = constructor1.getModifiers();
System.out.println(modifiers);

结果:

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

3.7isVarArgs()

判断构造器的参数是否为可变参数

public boolean isVarArgs() 

测试:

boolean varArgs = constructor1.isVarArgs();
System.out.println(varArgs);

结果:

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

4.重点解析

newInstance方法详解

public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException

该方法是用来生成一个构造器的声明类的对象实例。如:

ConstructorDemo constructorDemo1 = (ConstructorDemo) constructor1.newInstance(1, "zhangsan");
System.out.println(constructorDemo1.toString());

结果:

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

那么它是如何根据生成的呢?我们去看一下源码:

@CallerSensitive
public T newInstance(Object ... initargs)
    throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{   //通过参数列表,构造该构造器对应的类的实例对象
    if (!override) {	//该标识表示是否覆盖语言级别的访问检查权限,初始false,可以通过通过constructor1.setAccessible(true)设置为true
        if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
            Class<?> caller = Reflection.getCallerClass();  //获取调用类
            checkAccess(caller, clazz, null, modifiers);//校验权限
        }
    }
    if ((clazz.getModifiers() & Modifier.ENUM) != 0)    //修饰符是否为枚举类型
        throw new IllegalArgumentException("Cannot reflectively create enum objects");
    ConstructorAccessor ca = constructorAccessor;   // read volatile
    if (ca == null) {
        ca = acquireConstructorAccessor();  //获取构造器的访问器(声明类)
    }
    @SuppressWarnings("unchecked")
    T inst = (T) ca.newInstance(initargs);//通过构造函数声明类的newInstance来实例化对象
    return inst;
}

总结上面的源码分为以下及部分:

1)校验权限(这里我们就不讨论了,见之前文章)

2)判断是否为枚举类

3)获取构造器的声明类ConstructorAccessor

4)通过ConstructorAccessor创建实例

获取构造器的声明类ConstructorAccessor

private ConstructorAccessor acquireConstructorAccessor() {
        // First check to see if one has been created yet, and take it
        // if so.
        ConstructorAccessor tmp = null;
        if (root != null) tmp = root.getConstructorAccessor();//判断是否已经有ConstructorAccessor,root时用于共享构造器访问器
        if (tmp != null) {
            constructorAccessor = tmp;
        } else {
            // Otherwise fabricate one and propagate it up to the root
            tmp = reflectionFactory.newConstructorAccessor(this);
            setConstructorAccessor(tmp);
        }

        return tmp;
    }

上述代码主要是获取构造函数的声明类ConstructorAccessor:

​ 1)首先判断是否已经创建类ConstructorAccessor,如果创建了就返回;

​ 2)如果没有创建,则通过反射工厂ReflectionFactory的newConstructorAccessor(this)方法创建ConstructorAccessor。

通过ReflectionFactor创建ConstructorAccessor

在查看之前我们先来了解一下ConstructorAccessor是个什么?[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gDhUyuiC-1611389273411)(C:\Users\viruser.v-desktop\AppData\Roaming\Typora\typora-user-images\image-20210118170000573.png)]

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

如图,ConstructorAccessor是一个接口,里面只有一个方法newInstance,用来创建实例。它有如下实现类,这些实现类及其作用如下:

BootstrapConstructorAccessorImpl:作用是调用底层的c获取c++方法,来创建对象实例;

InstantiationExceptionConstructorAccessorImpl:异常实现类,用于接收在创建实例时抛出的各种异常。

SerializationConstructorAccessorImpl:作用是通过读取字节码的方式来创建对象实例;

NativeConstructorAccessorImpl:通过调用本地接口的方式来创建对象实例。

DelegatingConstructorAccessorImpl:它是一个代理类,也是NativeConstructorAccessorImpl的父类。也就是最终还是调用NativeConstructorAccessorImpl来实现创建对象实例。

现在我们再看源码:

public ConstructorAccessor newConstructorAccessor(Constructor<?> var1) {
        checkInitted();//1
        Class var2 = var1.getDeclaringClass();
        if (Modifier.isAbstract(var2.getModifiers())) {//2
            return new InstantiationExceptionConstructorAccessorImpl((String)null);
        } else if (var2 == Class.class) {//3
            return new InstantiationExceptionConstructorAccessorImpl("Can not instantiate java.lang.Class");
        } else if (Reflection.isSubclassOf(var2, ConstructorAccessorImpl.class)) {//4
            return new BootstrapConstructorAccessorImpl(var1);
        } else if (noInflation && !ReflectUtil.isVMAnonymousClass(var1.getDeclaringClass())) {//5
            return (new MethodAccessorGenerator()).generateConstructor(var1.getDeclaringClass(), var1.getParameterTypes(), var1.getExceptionTypes(), var1.getModifiers());
        } else {//6
            NativeConstructorAccessorImpl var3 = new NativeConstructorAccessorImpl(var1);
            DelegatingConstructorAccessorImpl var4 = new DelegatingConstructorAccessorImpl(var3);
            var3.setParent(var4);
            return var4;
        }
    }

​ 1)反射工厂(ReflectionFactory)检查初始化状态;

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");//从系统变量中获取noInflation的值
                        if (var1 != null && var1.equals("true")) {
                            ReflectionFactory.noInflation = true;
                        }

                        var1 = System.getProperty("sun.reflect.inflationThreshold");//从系统变量中获取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;
                    }
                }
            });
        }
    }

​ 这里主要时设置noInflation=true,和ReflectionFactory.inflationThreshold的值,默认是15。

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

PS:

​	通过查阅相关资料,inflationThreshold是一个阈值,主要是用来计数通过本地接口执行程序的次数(JNI)。当它被设置为0时,会强制虚拟机通过字节码的方式访问。

那么这个值在这里起什么作用呢?

​	这个阈值其实是表示,当我们不修改inflationThreshold值时,会先进行15次的JNI存取器,即访问本机接口。15次之后才会使用java字节码存取器。

​ 2)判断构造器的Class类型是否是abstract,如果是构建InstantiationExceptionConstructorAccessorImpl实现类返回;

​ 3)判断构造器的Class类型是否是Class类型本身,如果是构建InstantiationExceptionConstructorAccessorImpl异常实现类返回,因为Class不能直接实例化,它是有jvm创建的;

​ 4)判断构造器的Class类型是否继承ConstructorAccessorImpl的子类型,如果是构建BootstrapConstructorAccessorImpl实现类,返回;

​ 5)如果 noInflation 为 true 并且 Class 实例不是匿名的,需要调用 MethodAccessorGenerator.generateConstructor()创建 ConstructorAccessor,通过读取二进制文件,将 Class 实例加载进来,然后根据一些条件获取到想要的 Constructor。

​ 6)否则创建NativeConstructorAccessorImpl。

根据ConstructorAccessor创建实例

​ 根据上一步得到的ConstructorAccessor实现类,调用newInstance方法,来创建具体的实例。我们用NativeConstructorAccessorImpl来看一下其实现:

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

​ 它会判断numInvocations是否>15,也就是当我们使用JNI的方式 >15次后,会使用代理类DelegatingConstructorAccessorImpl。

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

否则使用本地方法newInstance0来创建实例。

5.总结

​ 上面零零散散讲了一些内容,下面我们总结一下Constructor类。

1)Constructor提供了对一个类的构造器/构造函数的信息进行访问;

2)通过Class类的三种方法可以拿到Constructor对象;

3)Constructor提供了各种方法来获取与构造函数相关的数据;

4)Constructor.newInstance()方法可以获取指定构造函数的声明类的实力对象。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值