java 反射

29 篇文章 0 订阅
26 篇文章 0 订阅

先验知识

类加载器是JVM执行类加载机制的前提。
ClassLoader的作用:

  • ClassLoader是Java的核心组件,所有的Class都是由ClassLoader进行加载的,ClassLoader负责通过各种方式将Class信息的二进制数据流读入JVM内部,转换为一个与目标类对应的 java.lang.Class对象 实例。
    在这里插入图片描述

概述

在程序运行时动态获取信息以及动态调用对象的方法的功能称为java语言的反射机制。

反射拥有下面的功能:

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法
  • 在运行时调用任意一个对象的方法

(如果属性是 private,正常情况下是不允许外界操作属性值,这里可以用 Field 类的 setAccessible(true) 方法,暂时打开操作的权限)

Class 类

java.lang.Class 类十分特殊,用来表示java汇总类型(class、interface、enum、annotation)本身(通过先验知识我们可以知道所有 class 类都是有类加载器获得的)。

  • Class类的对象包含了某个被加载类的结构,一个被加载的类对应一个Class对象。
  • 当一个类被加载,或当类加载器的defineClass()被JVM调用,JVM便会自动产生一个Class对象。

反射的由来

我们可以使用 class属性中的模型方法来利用 类名加载类信息,加载完类信息之后,在堆内存汇总,就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。

  • 我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射
  • JDK 中的 Class 和 java.lang.reflect 一起对反射提供了支持。

java.lang.reflect 包中最常用的几个类的关系如下:
在这里插入图片描述
其中最主要的三个类 FieldMethodConstructor 分别用于描述类的域、方法和构造器(它们有一个共同的父类 AccessibleObject,它提供了访问控制检查的功能。)

  • Field :描述类的域(属性),可以使用 get() 和 set() 方法读取和修改 Field 对象关联的字段;
  • Method :描述类的方法,可以使用 invoke() 方法调用与 Method 对象关联的方法;
  • Constructor :描述类的构造器,可以用 Constructor 创建新的对象。

通过反射获取实例

通过反射获取实例大致可以分为两个步骤:首先获得 class 对象然后利用 class 对象创建具体类实例。
为了方便测试,下列创建了 Person 和employee两个类结构:

public class Person {
    public String name; // 姓名 公有
    protected String age;   // 年龄 保护
    private String hobby;   // 爱好   私有

    public Person(String name, String age, String hobby) {
        this.name = name;
        this.age = age;
        this.hobby = hobby;
    }
    public String getHobby() {
        return hobby;
    }
}

public class Employee extends Person {
    public static Integer totalNum = 0; // 员工数
    public int empNo;   // 员工编号 公有
    protected String position;  // 职位 保护
    private int salary; // 工资   私有

    public void sayHello() {
        System.out.println(String.format("Hello, 我是 %s, 今年 %s 岁, 爱好是%s, 我目前的工作是%s, 月入%s元\n", name, age, getHobby(), position, salary));
    }
    private void work() {
        System.out.println(String.format("My name is %s, 工作中勿扰.", name));
    }
    public Employee(String name, String age, String hobby, int empNo, String position, int salary) {
        super(name, age, hobby);
        this.empNo = empNo;
        this.position = position;
        this.salary = salary;
        Employee.totalNum++;
    }
}

获得class对象

获取 Class 对象的方式有三种:

  1. 使用 Class 类的 forName 静态方法;
  2. 直接获取某一个对象的 class;
  3. 调用某个对象的 getClass() 方法
public class ClassTest {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        Class c1 = Class.forName("class com.reflect.Employee");   // 第1种,forName 方式获取Class对象
        Class c2 = Employee.class;      // 第2种,直接通过类获取Class对象
        Employee employee = new Employee("小明", "18", "写代码", 1, "Java攻城狮", 100000);
        Class c3 = employee.getClass();    // 第3种,通过调用对象的getClass()方法获取Class对象

        if (c1 == c2 && c1 == c3) {     // 可以通过 == 比较Class对象是否为同一个对象
            System.out.println("c1、c2、c3 为同一个对象");
            System.out.println(c1);     // class reflect.Employee
        }
    }
}

输出结果如下:

c1、c2、c3 为同一个对象
class com.reflect.Employee
  • 其实在类进行第一次加载时,会在就会把类加载器和对应的class对象存入堆中,后面所有的取对象操作,其实都是直接返回对里面的这个对象。
  • 当然,这个也可以通过热替换的原理,让JVM重新加载类文件,进而得到新对象。

通过反射来创建实例

通过反射来生成对象主要有两种方式

  • 使用 Class 对象的 newInstance() 方法来创建Class对象对应类的实例
  • 先通过 Class 对象获取指定的 Constructor对象,再调用Constructor对象的newInstance()方法来创建实例
public class NewInstanceTest {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        Class c = Date.class;
        Date date1 = (Date) c.newInstance();    // 第1种方式:使用Class对象的newInstance()方法来创建Class对象对应类的实例
        System.out.println(date1);      // Wed Dec 19 22:57:16 CST 2018

        long timestamp =date1.getTime();
        Constructor constructor = c.getConstructor(long.class); 
        Date date2 = (Date)constructor.newInstance(timestamp);  // 第2种方式:先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例
        System.out.println(date2);  // Wed Dec 19 22:57:16 CST 2018
    }
}

class.newInstance的原理其实就是调用了对象中的无参数构造器进行构造,如果对象中没有无参数构造器,那么就会抛出错误,具体源码如下

源码解析

进入class类文件,可以发现class强依赖于 java.lang.reflet 换句话说,class对象中的很多静态方法其实就是调用了 java.lang.refletc 。所以反射需要 Class类+java.lang.reflet
在这里插入图片描述

获取类实例 Class.forName(“xxx”)

Class.forName("xxx")java.lang.Class 的静态方法之一,该方法通过class文件获得一个具体的类信息,返回一个类对象。

  • 该方法的主要思想就是利用类名获取 ClassLoader, 然后调用 native 方法,获取信息,加载类获得类对象。
 @CallerSensitive
    public static Class<?> forName(String className)
                throws ClassNotFoundException {
        // 先通过反射,获取调用进来的类信息,从而获取当前的 classLoader
        Class<?> caller = Reflection.getCallerClass();
        // 调用native方法进行获取class信息
        return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
    }
获取具体类的实例对象: Class.newInstance

newInstance() 其实相当于是调用了类的无参构造函数,当然,为了调用这个无惨函数,它做了下面几个步骤:

  • 权限检测:判断系统是否具有读写内存中类对象的权限,如果不通过直接抛出异常;
  • 查找无参构造器,并将其缓存起来;
  • 调用具体方法的无参构造方法,生成实例并返回;
// 首先肯定是 Class.newInstance
    @CallerSensitive
    public T newInstance()
        throws InstantiationException, IllegalAccessException
    {
        if (System.getSecurityManager() != null) {
            checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), false);
        }
 
        // NOTE: the following code may not be strictly correct under
        // the current Java memory model.
 
        // Constructor lookup
        // newInstance() 其实相当于调用类的无参构造函数,所以,首先要找到其无参构造器
        if (cachedConstructor == null) {
            if (this == Class.class) {
                // 不允许调用 Class 的 newInstance() 方法
                throw new IllegalAccessException(
                    "Can not call newInstance() on the Class for java.lang.Class"
                );
            }
            try {
                // 获取无参构造器
                Class<?>[] empty = {};
                final Constructor<T> c = getConstructor0(empty, Member.DECLARED);
				//当访问 Field、Method 和 Constructor 的时候Java会执行访问检查,如果访问者没有权限将抛出SecurityException,
				//譬如访问者是无法访问private修饰的域的。通过设置 setAccessible(true) 可以取消Java的执行访问检查
				//这样访问者就获得了指定 Field、Method 或 Constructor 访问权限  
             java.security.AccessController.doPrivileged(
                    new java.security.PrivilegedAction<Void>() {
                        public Void run() {
                                c.setAccessible(true);
                                return null;
                            }
                        });
                cachedConstructor = c;
            } catch (NoSuchMethodException e) {
                throw (InstantiationException)
                    new InstantiationException(getName()).initCause(e);
            }
        }
        Constructor<T> tmpConstructor = cachedConstructor;
        // Security check (same as in java.lang.reflect.Constructor)
        int modifiers = tmpConstructor.getModifiers();
        if (!Reflection.quickCheckMemberAccess(this, modifiers)) {
            Class<?> caller = Reflection.getCallerClass();
            if (newInstanceCallerCache != caller) {
                Reflection.ensureMemberAccess(caller, this, null, modifiers);
                newInstanceCallerCache = caller;
            }
        }
        // Run constructor
        try {
            // 调用无参构造器
            return tmpConstructor.newInstance((Object[])null);
        } catch (InvocationTargetException e) {
            Unsafe.getUnsafe().throwException(e.getTargetException());
            // Not reached
            return null;
        }
    }
  • 其中 getConstructor0(empty, Member.DECLARED); 表示获得指定参数的构造器,而这里我们传入的 empty,也就是说,我们想要的是一个 空参数的构造器。具体源码如下:

    private Constructor<T> getConstructor0(Class<?>[] parameterTypes,
                                            int which) throws NoSuchMethodException
        {
            // 获取所有构造器:JVM中会有所有构造器的缓存,如果没有JVM会马上进行加载
            Constructor<T>[] constructors = privateGetDeclaredConstructors((which == Member.PUBLIC));
            for (Constructor<T> constructor : constructors) {
                if (arrayContentsEq(parameterTypes,
                                    constructor.getParameterTypes())) {
                    return getReflectionFactory().copyConstructor(constructor);
                }
            }
            throw new NoSuchMethodException(getName() + ".<init>" + argumentTypesToString(parameterTypes));
        }
    
    

    代码简单,就是一个一个遍历,找到参数匹配的构造器。找到匹配后,通过 ReflectionFactory copy一份constructor返回。

获取类信息的相关API

  • String getName() : 获取这个Class的类名
  • Constructor[] getDeclaredConstructors(): 返回这个类的所有构造器的对象数组,包含保护和私有的构造器;相近的方法 - getConstructors():则返回这个类的所有公有构造器的对象数组,不包含保护和私有的构造器
  • Method[] getDeclaredMethods(): 返回这个类或接口的所有方法,包括保护和私有的方法,不包括超类的方法;相近的方法 getMethods() 则返回这个类及其超类的公有方法的对象数组,不含保护和私有的方法
  • Field[] getDeclaredFields(): 返回这个类的所有域的对象数组,包括保护域和私有域,不包括超类的域;还有一个相近的API getFields(),返回这个类及其超类的公有域的对象数组,不含保护域和私有域
  • int getModifiers(): 返回一个用于描述Field、Method和Constructor的修饰符的整形数值,该数值代表的含义可通过Modifier这个类分析
    Modifier 类 它提供了有关 Field、Method和Constructor等的访问修饰符的信息,主要的方法有:
    • toString(int modifiers) 返回整形数值modifiers代表的修饰符的字符串;
      -isAbstract 是否被abstract修饰;
    • isVolatile 是否被volatile修饰;
    • isPrivate是否为private;
    • isProtected是否为protected;
    • isPublic是否为public;
    • isStatic是否为static修饰;等等,见名知义

例子:获得指定类的所有类信息

public class ReflectionTest {
    public static void main(String[] args) throws ClassNotFoundException {
        String name;
        if (args.length > 0) {
            name = args[0];
        } else {
            Scanner in = new Scanner(System.in);
            System.out.println("输入一个类名(e.g. java.util.Date):"); // reflect.Employee
            name = in.next();
        }
        try {
            Class cl = Class.forName(name);
            Class superCl = cl.getSuperclass();
            String modifiers = Modifier.toString(cl.getModifiers());
            if (modifiers.length() > 0) {
                System.out.print(modifiers + " ");
            }
            System.out.print("class " + name);
            if (superCl != null && superCl != Object.class) {
                System.out.print(" extends " + superCl.getName());
            }
            System.out.println("\n{");

            printConstructors(cl); // 打印构造方法
            System.out.println();
            printMethods(cl);   // 打印方法
            System.out.println();
            printFields(cl);    // 打印属性
            System.out.println("}");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        System.exit(0);
    }

    /**
     * 打印Class对象的所有构造方法
     */
    public static void printConstructors(Class cl) {
        Constructor[] constructors = cl.getDeclaredConstructors();

        for (Constructor c : constructors) {
            String name = c.getName();
            System.out.print("  ");
            String modifiers = Modifier.toString(c.getModifiers());
            if (modifiers.length() > 0) {
                System.out.print(modifiers + " ");
            }
            System.out.print(name + "(");
            // 打印构造参数
            Class[] paramTypes = c.getParameterTypes();
            for (int i = 0; i < paramTypes.length; i++) {
                if (i > 0) {
                    System.out.print(", ");
                }
                System.out.print(paramTypes[i].getName());
            }
            System.out.println(");");
        }
    }

    /**
     * 打印Class的所有方法
     */
    public static void printMethods(Class cl) {
        Method[] methods = cl.getDeclaredMethods();
        //Method[] methods = cl.getMethods();
        for (Method m : methods) {
            Class retType = m.getReturnType();  // 返回类型
            System.out.print("  ");
            String modifiers = Modifier.toString(m.getModifiers());
            if (modifiers.length() > 0) {
                System.out.print(modifiers + " ");
            }
            System.out.print(retType.getName() + " " + m.getName() + "(");
            Class[] paramTypes = m.getParameterTypes();
            for (int i = 0; i < paramTypes.length; i++) {
                if (i > 0) {
                    System.out.print(", ");
                }
                System.out.print(paramTypes[i].getName());
            }
            System.out.println(");");
        }
    }

    /**
     * 打印Class的所有属性
     */
    public static void printFields(Class cl) {
        Field[] fields = cl.getDeclaredFields();
        for (Field f: fields) {
            Class type = f.getType();
            System.out.print("  ");
            String modifiers = Modifier.toString(f.getModifiers());
            if (modifiers.length()> 0) {
                System.out.print(modifiers + " ");
            }
            System.out.println(type.getName() + " " + f.getName() + ";");
        }
    }
}

运行时查看对象数据域的实际内容相关API

相当于debug断点时查看对象的各个属性内容

  • Class<?> getComponentType() 返回数组类里组件类型的 Class,如果不是数组类则返回null
  • boolean isArray() 返回这个类是否为数组,同类型的API还有 isAnnotation、isAsciiDigit、isEnum、isInstance、isInterface、isLocalClass、isPrimitive 等
  • int Array.getLength(obj) 返回数组对象obj的长度
  • Object Array.get(obj, i) 获取数组对象下标为i的元素
  • boolean isPrimitive() 返回这个类是否为8种基本类型之一,即是否为boolean, byte, char, short, int, long, float, 和double 等原始类型
  • Field getField(String name) 获取指定名称的域对象
  • AccessibleObject.setAccessible(fields, true) 当访问 Field、Method 和 Constructor 的时候Java会执行访问检查,如果访问者没有权限将抛出SecurityException,譬如访问者是无法访问private修饰的域的。通过设置 setAccessible(true) 可以取消Java的执行访问检查,这样访问者就获得了指定 Field、Method 或 Constructor 访问权限
  • Class<?> Field.getType() 返回一个Class 对象,它标识了此 Field 对象所表示字段的声明类型
  • Object Field.get(Object obj) 获取obj对象上当前域对象表示的属性的实际值,获取到的是一个Object对象,实际使用中还需要转换成实际的类型,或者可以通过 getByte()、getChar、getInt() 等直接获取具体类型的值
  • void Field.set(Object obj, Object value) 设置obj对象上当前域表示的属性的实际值

例子:运行时查看对象数据域的实际内容

public class ObjectAnalyzer {
    private ArrayList<Object> visited = new ArrayList<>();

    public String toString(Object obj) {
        if (obj == null) {
            return "null";
        }
        if (visited.contains(obj)) {    // 如果该对象已经处理过,则不再处理
            return "...";
        }
        visited.add(obj);

        Class cl = obj.getClass(); // 获取Class对象
        if (cl == String.class) {   // 如果是String类型则直接转为String
            return (String) obj;
        }
        if (cl.isArray()) {        // 如果是数组
            String r = cl.getComponentType() + "[]{\n";     // 数组的元素的类型
            for (int i = 0; i < Array.getLength(obj); i++) {
                if (i > 0) {   // 不是数组的第一个元素加逗号和换行,显示更加美观
                    r += ",\n";
                }
                r += "\t";
                Object val = Array.get(obj, i);
                if (cl.getComponentType().isPrimitive()) { // Class为8种基本类型的时候为 true,直接输出
                    r += val;
                } else {
                    r += toString(val); // 不是8中基本类型时,说明是类,递归调用toString
                }
            }
            return r + "\n}";
        }
        // 既不是String,也不是数组时,输出该对象的类型和属性值
        String r = cl.getName();
        do {
            r += "[";
            Field[] fields = cl.getDeclaredFields();    // 获取该类自己定义的所有域,包括私有的,不包括父类的
            AccessibleObject.setAccessible(fields, true); // 访问私有的属性,需要打开这个设置,否则会报非法访问异常
            for (Field f : fields) {
                if (!Modifier.isStatic(f.getModifiers())) { // 通过 Modifier 可获取该域的修饰符,这里判断是否为 static
                    if (!r.endsWith("[")) {
                        r += ",";
                    }
                    r += f.getName() + "=";     // 域名称
                    try {
                        Class t = f.getType();  // 域(属性)的类型
                        Object val = f.get(obj);   // 获取obj对象上该域的实际值
                        if (t.isPrimitive()) {     // 如果类型为8种基本类型,则直接输出
                            r += val;
                        } else {
                            r += toString(val);     // 不是8种基本类型,递归调用toString
                        }
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
            r += "]";
            cl = cl.getSuperclass(); // 继续打印超类的类信息
        } while (cl != null);
        return r;
    }
}

测试代码:

public class ObjectAnalyzerTest {
    public static void main(String[] args) {
        int size = 4;
        ArrayList<Integer> squares = new ArrayList<>(size);
        for (int i = 0; i < size; i++) {
            squares.add(i * i);
        }
        ObjectAnalyzer objectAnalyzer = new ObjectAnalyzer(); // 创建一个上面定义的分析类ObjectAnalyzer的对象
        System.out.println(objectAnalyzer.toString(squares)); // 分析ArrayList<Integer>对象的实际值

        Employee employee = new Employee("小明", "18", "爱好写代码", 1, "Java攻城狮", 100); // 分析自定义类Employee的对象的实际值
        System.out.println(objectAnalyzer.toString(employee));
    }
}

运行结果:

java.util.ArrayList[elementData=class java.lang.Object[]{
    java.lang.Integer[value=0][][],
    java.lang.Integer[value=1][][],
    java.lang.Integer[value=4][][],
    java.lang.Integer[value=9][][]
},size=4][modCount=4][][]
reflect.Employee[empNo=1,position=Java攻城狮,salary=100][name=小明,age=18,hobby=爱好写代码][]

其中

  • ArrayList<Integer>打印了类名和5个元素的类型和值,- Employee 打印了类名,自己定义的3个基本类型的属性的实际值,和父类Person的3个基本类型的属性的实际值

需要注意的是,position,age 是 protected 保护域,salary,hobby 是 private 私有域,Java的安全机制只允许查看任意对象有哪些域,但是不允许读取它们的值

  • 程序中是通过 AccessibleObject.setAccessible(fields, true) 将域设置为了可访问,取消了Java的执行访问检查,因此可以访问,如果不加会报异常 IllegalAccessException

调用任意方法相关的API

处理了查看类的信息以及实例的各种信息外,我们还可以使用反射来调用类中的任意方法。Method 调用方法是通过 Method 类的 invoke 方法

  • Method getMethod(String name, Class<?>... parameterTypes): 获取指定的 Method
    • 参数 name 为要获取的方法名
    • parameterTypes 为指定方法的参数的 Class,
    • 由于可能存在多个同名的重载方法,所以只有提供正确的 parameterTypes 才能准确的获取到指定的 Method
  • Object invoke(Object obj, Object... args) 执行方法
    • 第一个参数执行该方法的对象,如果是static修饰的类方法,则传null即可;
    • 后面是传给该方法执行的具体的参数值

例子:调用任意方法

public class MethodTableTest {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Employee employee = new Employee("小明", "18", "写代码", 1, "Java攻城狮", 100000);
        Method sayHello = employee.getClass().getMethod("sayHello");
        System.out.println(sayHello);   // 打印 sayHello 的方法信息
        sayHello.invoke(employee);      // 让 employee 执行 sayHello 方法

        double x = 3.0;
        Method square = MethodTableTest.class.getMethod("square", double.class);  // 获取 MethodTableTest 的square方法
        double y1 = (double) square.invoke(null, x);    // 调用类方法 square 求平方,方法参数 x 
        System.out.printf("square    %-10.4f -> %10.4f%n", x, y1);

        Method sqrt = Math.class.getMethod("sqrt", double.class);   // 获取 Math 的 sqrt 方法
        double y2 = (double) sqrt.invoke(null, x);  // 调用类方法 sqrt 求根,方法参数 x 
        System.out.printf("sqrt      %-10.4f -> %10.4f%n", x, y2);
    }

    // static静态方法 计算乘方
    public static double square(double x) {
        return x * x;
    }
}

执行结果:

public void com.reflect.Employee.sayHello()
Hello, 我是 小明, 今年 18, 爱好是写代码, 我目前的工作是Java攻城狮, 月入100000元

square    3.0000     ->     9.0000
sqrt      3.0000     ->     1.7321

noinflation机制

其实,Java 的反射调用机制还设立了另一种动态生成字节码的实现(下称动态实现)来直接使用 invoke 指令来调用目标方法。之所以采用委派实现,是为了能够在本地实现和动态实现中切换。

  • 动态实现和本地实现相比,运行效率更高。这是因为动态实现无需经过 Java 到 C++ 再到 Java 的切换,单由于生成字节码十分耗时,仅调用一次的话,反而是本地实现要更快
  • 实际中,许多反射调用仅会执行一次,Java 虚拟机设置了一个阈值 15,当某个反射调用的调用次数在 15 之下时,采用本地实现;当达到 15 时,便开始动态生成字节码,并将委派实现的委派对象切换至动态实现,这个过程叫 inflation。
  • 反射调用的 inflation 机制是可以通过参数(-Dsun.reflect.noinflation = true) 来关闭的。这样一来,在反射调用一开始便会直接生成动态实现,而不会使用委派实现或者本地实现。
  • 也可以通过-Dsun.reflect.inflationThreshold -阈值) 来设定开始动态调用的阈值。

例子

JVM: -XX:+TraceClassLoading

        public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {

            Employee employee = new Employee("小明", "18", "写代码", 1, "Java攻城狮", 100000);
            Method sayHello = employee.getClass().getMethod("sayHello");
            System.out.println(sayHello);   // 打印 sayHello 的方法信息

            for (int i = 0; i < 30; i++) {
                sayHello.invoke(employee);      // 让 employee 执行 sayHello 方法
                long end = System.currentTimeMillis();

            }

        }

输出结果:

...各种加载类的信息
Hello, 我是 小明, 今年 18, 爱好是写代码, 我目前的工作是Java攻城狮, 月入100000...一共14条上面的输出
[Loaded sun.reflect.ClassFileConstants from C:\Program Files\Java\jdk1.8.0_221\jre\lib\rt.jar]
[Loaded sun.reflect.AccessorGenerator from C:\Program Files\Java\jdk1.8.0_221\jre\lib\rt.jar]
[Loaded sun.reflect.MethodAccessorGenerator from C:\Program Files\Java\jdk1.8.0_221\jre\lib\rt.jar]
[Loaded sun.reflect.ByteVectorFactory from C:\Program Files\Java\jdk1.8.0_221\jre\lib\rt.jar]
[Loaded sun.reflect.ByteVector from C:\Program Files\Java\jdk1.8.0_221\jre\lib\rt.jar]
[Loaded sun.reflect.ByteVectorImpl from C:\Program Files\Java\jdk1.8.0_221\jre\lib\rt.jar]
[Loaded sun.reflect.ClassFileAssembler from C:\Program Files\Java\jdk1.8.0_221\jre\lib\rt.jar]
[Loaded sun.reflect.UTF8 from C:\Program Files\Java\jdk1.8.0_221\jre\lib\rt.jar]
[Loaded sun.reflect.Label from C:\Program Files\Java\jdk1.8.0_221\jre\lib\rt.jar]
[Loaded sun.reflect.Label$PatchInfo from C:\Program Files\Java\jdk1.8.0_221\jre\lib\rt.jar]
[Loaded sun.reflect.MethodAccessorGenerator$1 from C:\Program Files\Java\jdk1.8.0_221\jre\lib\rt.jar]
[Loaded sun.reflect.ClassDefiner from C:\Program Files\Java\jdk1.8.0_221\jre\lib\rt.jar]
[Loaded sun.reflect.ClassDefiner$1 from C:\Program Files\Java\jdk1.8.0_221\jre\lib\rt.jar]
[Loaded sun.reflect.GeneratedMethodAccessor1 from __JVM_DefineClass__]
Hello, 我是 小明, 今年 18, 爱好是写代码, 我目前的工作是Java攻城狮, 月入100000....

可以看到,在第16次反射调用时似乎有什么东西被触发了,导致JVM新加载了一堆类,其中就包括 [Loaded sun.reflect.GeneratedMethodAccessor1 from __JVM_DefineClass__]这么一行。而这个 GeneratedMethodAccessor1 就会动态生成一个java类放到虚拟堆中,方便调用。

invoke 源码解析

  • 先检查 AccessibleObject 的 override 属性是否为true。
    AccessibleObject是Method,Field,Constructor的父类,override属性默认为false,可调用setAccessible方法改变。

  • 如果设置为true,则表示可以忽略访问权限的限制,直接调用。如果不是ture,则要进行访问权限检测。

    权限检测:Reflection.quickCheckMemberAccess 方法先检查是不是 public 的,如果不是再用Reflection.getCallerClass() 方法获 得到调用这个方法的 Class , 然后做是否有权限访问的校验,校验之后缓存一次,以便下次如果还是这个类来调用就不用去做校验了,直接用上次的结果。(一种简单的缓冲机制,只适用于一个类的重复调用,当来新的校验,会被覆盖掉)。

  • 调用MethodAccessor的invoke方法
    每个Method对象包含一个root对象,root对象里持有一个MethodAccessor对象。我们获得的Method独享相当于一个root对象的镜像,所有这类Method共享root里的MethodAccessor对象,(这个对象由ReflectionFactory方法 生成, ReflectionFactory对象在Method类中是static final的由native方法实例化)。 ReflectionFactory生成MethodAccessor:

    • 如果noInflation的属性为true,则直接返回MethodAccessorGenerator 创建的一个MethodAccessor
    • 否则返回 DelegatingMethodAccessorImpl,并将他与一个NativeMethodAccessorImpl互相引用 。即 DelegatingMethodAccessorImpl 执行 invoke 方法的时候又委托给 NativeMethodAccessorImpl 了。
  • NativeMethodAccessorImpl: 里面主要就是判断当前调用次数,如果少于15,则用native调用。如果超过15则重新利用 ReflectionFactory 生成MethodAccessor

实际上:

  • MethodAccessor的实例 是由 ReflectionFactory 产生的。里面其实是由两种 MethodAccessor的实现方式,一个是Java实现的,另一个是native code实现的。
    • Java实现的版本在初始化时需要较多时间,但长久来说性能较好;
    • native版本正好相反,启动时相对较快,但运行时间长了之后速度就比不过Java版了。

这是HotSpot的优化方式带来的性能特性,同时也是许多虚拟机的共同点==:跨越native边界会对优化有阻碍作用,它就像个黑箱一样让虚拟机难以分析也将其内联,于是运行时间长了之后反而是托管版本的代码更快些。==

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

    Sun的JDK是从1.4系开始采用这种优化的。
    反射的优点:
    可扩展性 :应用程序可以利用全限定名创建可扩展对象的实例,来使用来自外部的用户自定义类。

反射的优缺点

优点:多种应用场景

  • 类浏览器和可视化开发环境 :一个类浏览器需要可以枚举类的成员。可视化开发环境(如 IDE)可以从利用反射中可用的类型信息中受益,以帮助程序员编写正确的代码。
  • 调试器和测试工具 : 调试器需要能够检查一个类里的私有成员。测试工具可以利用反射来自动地调用类里定义的可被发现的 API 定义,以确保一组测试中有较高的代码覆盖率。

反射的缺点:
尽管反射非常强大,但也不能滥用。如果一个功能可以不用反射完成,那么最好就不用。在我们使用反射技术时,下面几条内容应该牢记于心。

  • 性能开销 :反射涉及了动态类型的解析,所以 JVM 无法对这些代码进行优化。 因此,反射操作的效率要比那些非反射操作低得多。我们应该避免在经常被执行的代码或对性能要求很高的程序中使用反射。

  • 安全限制 :使用反射技术要求程序必须在一个没有安全限制的环境中运行。如果一个程序必须在有安全限制的环境中运行,如 Applet,那么这就是个问题了。

  • 内部暴露 :由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用,这可能导致代码功能失调并破坏可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值