Java源码分析——java.lang.reflect反射包解析(一) AccessibleObject、ReflectionFactory、Filed、Method、Constructor类

版权声明:博主GitHub地址https://github.com/suyeq欢迎大家前来交流学习 https://blog.csdn.net/hackersuye/article/details/83479588

    Java的反射机制一直是被人称赞的,它的定义是:程序在运行中时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。简单的来说就是可以通过Java的反射机制知道自己想知道的类的一切信息。

    在Java的反射机制中,类中的三个组成部分便是其重点,也就是FiledMethodConstructor类。它们分别负责类的属性、类的方法与类的构造方法。关于它们之间的关系,如下图所示,方便读者的理解:
在这里插入图片描述

    从UML途中可以看出,Filed、Method、Constructor类都继承着同一个父类,AccessibleObject类,而Method与Constructor类的直接父类Executable类实现了GenericDeclaration接口,表明Method与Constructor类可以实现泛型,即类型变量,切记Filed类没有直接实现类型变量接口,但是可以使用泛型,泛型不等于类型变量。下面来分别讲解它们的实用以及源码。

AccessibleObject类

    AccessibleObject类作为三者的共同父类,起着检查访问权限,实现注解相关方法的作用,其中重点是权限的更改与判定,在AccessibleObject类开头便用静态常量定义了一个权限属性:

 static final private java.security.Permission ACCESS_PERMISSION =
        new ReflectPermission("suppressAccessChecks");

    这个权限表明访问者可以能够访问类中的字段和调用方法。注意,这不仅包括public,而且还包括 protected 和 private 字段和方法。而该类提供了几个关键的方法来用这个属性修改对应方法及属性的权限,下面列出常见的一种:

 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("Cannot make a java.lang.Class" +
                                            " constructor accessible");
            }
        }
        obj.override = flag;
    }

    从代码中可以看出,setAccessible方法获取了java的安全管理器,通过安全管理器来更改AccessibleObject对象的权限,但是setAccessible0方法中看出,AccessibleObject对象是构造方法且该类的Class类对象就是Class类本身时,会抛出异常,也就是不能更改权限。而override属性为true时,表明这个成员不需要进行访问权限检查。展示更改权限的示例代码:

class KT{
    private KT(){
        System.out.println("啦啦啦啦");
    }
}
public  class Test {
    public static void main(String args[]) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        Class test=Class.forName("test.KT");
        Constructor []constructor=test.getDeclaredConstructors();
        //constructor[0].setAccessible(true);
        System.out.println(constructor[0].newInstance());
    }
}

    运行结果为:java.lang.IllegalAccessException: Class test.Test can not access a member of class 。 也就是没有该成员的访问权限,将注释去掉再次运行,结果为打印出:啦啦啦啦 test.KT@677327b6。 可见确实更改了访问权限。权限更改用起来挺舒服的,但是这个权限的更改带来一个直接的弊端就是会导致类中保密信息的泄露以及破坏类的封装性,所以不到迫不得已不要使用更改权限这一个方法。

ReflectionFactory类

    为什么要说这个类呢?如其名所示,是一个反射的工厂类,既然是工厂类那么它就担任着类的实例化的操作,但是这个工厂类只是深度克隆下面要讲的三个类:Filed类、Method类、Constructor类。并不直接创建,只是复制一份。其中有着三个很重要的方法:

public FieldAccessor newFieldAccessor(Field var1, boolean var2)public MethodAccessor newMethodAccessor(Method var1)public ConstructorAccessor newConstructorAccessor(Constructor<?> var1)

    这三个方法分别对应着Filed类、Method类、Constructor类的访问权限,也就是说,这三个方法返回的对象都是用来判定三者的修饰符权限的,也就是实现了当方法是私有方法时,外部不能访问它的功能的,原理是当外部要访问这个私有方法时,会创建一个方法的MethodAccessor,并在里面进行相关的判断,判断如果是访问的权限那么允许访问者访问,否则拒绝抛出异常,其它两者也是一样的,而且不仅仅会判断修饰符的权限也会判断其属性的类型,方法的类型等。

Filed类

    Filed类是类的属性类,直接继承自AccessibleObject类,还实现了Member接口,它负责管理一切类的属性。怎么获取Filed实例呢?是通过Class类对象的getDeclaredField方法,该方法的两种重载格式定义如下:

public Field[] getDeclaredFields() throws SecurityException {
        checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true);
        return copyFields(privateGetDeclaredFields(false));
    }
    
public Field getDeclaredField(String name)
        throws NoSuchFieldException, SecurityException {
        checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true);
        Field field = searchFields(privateGetDeclaredFields(false), name);
        if (field == null) {
            throw new NoSuchFieldException(name);
        }
        return field;
    }

    第一种重载格式是获取类中所有的属性,而第二种则是获取指定属性名的Filed对象。两种重载方法里都先检查一遍是否有权限查找,再调用privateGetDeclaredFields方法取找对应的Filed。以第二种重载格式来讲解如何获得一个Filed对象,分析代码可知,getDeclaredField方法调用了searchFields方法,来看searchFields方法的源码:

 private static Field searchFields(Field[] fields, String name) {
        String internedName = name.intern();
        for (int i = 0; i < fields.length; i++) {
            if (fields[i].getName() == internedName) {
                return getReflectionFactory().copyField(fields[i]);
            }
        }
        return null;
    }

    searchFields方法是在已知Filed数组的情况下,找到对应匹配的Filed对象,并调用ReflectionFactory类的复制方法深度克隆一个Filed对象。而已知Filed数组的获取是有privateGetDeclaredFields方法来执行的,它的定义如下:

private Field[] privateGetDeclaredFields(boolean publicOnly) {
       ......    
        res = Reflection.filterFields(this, getDeclaredFields0(publicOnly));    
       ......      
    }
    
private native Field[]       getDeclaredFields0(boolean publicOnly);

    在privateGetDeclaredFields方法里你会发现其实调用了native方法来查询所有的关于对应类的属性,publicOnlytrue时,只查询public的属性,否则查询所有的。Method类与Constructor类的获取与Filed类的获取原理一样,故下文不再重复,要注意的是,它们三者的获取都是深度克隆来的并不是直接获取。

    Filed类的重要的方法如下,也就是获取对应属性的值:

public Object get(Object obj)
        throws IllegalArgumentException, IllegalAccessException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, obj, modifiers);
            }
        }
        return getFieldAccessor(obj).get(obj);
    }

    get方法是获取一个实例话的对象其中的属性的值的,在方法里先判断权限,后调用FieldAccessor属性访问者来获判断是否权限合格,然后通过Filed对象判断其类型,再调用相关的函数返回传进来的属性的值,上述是getFieldAccessor方法的实现过程。对象示例代码如下:

class KT{
    private   int filed=9;
}
public  class Test {
    public static void main(String args[]) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        Class test=Class.forName("test.KT");
        KT kt=(KT)test.newInstance();
        Field field=test.getDeclaredField("filed");
        field.setAccessible(true);
        System.out.println("原来的值");
        System.out.println(field.get(kt));
        field.set(kt,10);
        System.out.println("更改后的值");
        System.out.println(field.get(kt));
    }
}

在这里插入图片描述

Executable类

    Executable类是一个抽象类,直接继承AccessibleObject类,并实现Membet与GenericDeclaration接口,主要作用是实现类型变量的相关操作以及展示方法的信息。主要的方法如下:

String sharedToString(int modifierMask,
                          boolean isDefault,
                          Class<?>[] parameterTypes,
                          Class<?>[] exceptionTypes);
                        
String sharedToGenericString(int modifierMask, boolean isDefault);

    这两个方法都会被Method类以及Constructor类展示自身的所有信息以及类型变量的信息的。

Method类

    Method类继承自Executable抽象类,负责管理所有类的方法,其主要的方法如下:

 public Object invoke(Object obj, Object... args)
        throws IllegalAccessException, IllegalArgumentException,
           InvocationTargetException
    {
    //访问权限检查
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, obj, modifiers);
            }
        }
        //同步读
        MethodAccessor ma = methodAccessor;             // read volatile
        if (ma == null) {
            ma = acquireMethodAccessor();
        }
        //调用MethodAccessor的invoke方法
        return ma.invoke(obj, args);
    }

    与Filed类一样,先判断是否取消了权限的检查,再通过MethodAccessor接口来判断权限,后调用其invoke方法。当你点开MethodAccessor时,你会发现它是个接口:

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

    而MethodAccessor接口的实现需要到NativeMethodAccessorImpl类中去查看,其中实现的invoke方法底层调用的就是native本地方法实现的:

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

    这个方法主要作用是用来反向调用方法的,也就是通过反射的方法来调用对应类中的方法,示例代码如下:

class KT{
    public String print(String name){
        return name;
    }
}
public  class Test {
    public static void main(String args[]) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        Class test=Class.forName("test.KT");
        KT kt=(KT)test.newInstance();
        Method method=test.getDeclaredMethod("print", String.class);
        System.out.println(method.invoke(kt,"lalalal"));
    }
}
//输出为:lalalal

Constructor类

    Constructor类直接继承自Executable类,负责管理所有的构造方法,其主要方法如下:

public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
       //需要权限检查
        if (!override) {
        //快速的检查一遍权限
            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对象
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
			//从反射工厂里获取ConstructorAccessor对象
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }

    当你查看ConstructorAccessor时,也会发现是个接口,其具体实现是NativeConstructorAccessorImpl类,其中实现的newInstance方法就调用了native方法来创建实例对象:

private static native Object newInstance0(Constructor<?> var0, Object[] var1) throws InstantiationException, IllegalArgumentException, InvocationTargetException;

其示例代码如下:

class KT{
    public  String name;
    public KT(String name){
        this.name=name;

    }
}
public  class Test {
    public static void main(String args[]) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        Class test=Class.forName("test.KT");
        Constructor constructor=test.getDeclaredConstructor(String.class);
        KT kt=(KT)constructor.newInstance("hahahh");
        System.out.println(kt.name);
    }
}
//打印出:hahahh

    从结果可以看出,类被成功的用Constructor的newInstance方法给实例化了。

    虽然反射很强大,调用很灵活,但是不要轻易的去使用反射去创建实例,调用方法,因为反射虽然优点很多,但是缺点也很明显,它最大的缺点就是耗时多,它比平常的普通实例化耗费的时间多,因为平常的调用是已经实例好的,直接调用即可,而反射的的调用则是需要创建复制体,然后通过jvm查找对应的属性、方法或者构造方法的。

猜你喜欢

转载自blog.csdn.net/hackersuye/article/details/83479588