Bootstrap

Java 反射原理

Java 反射原理

一、概述

Java 的反射机制是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法;并且对于任意一个对象,都能够调用它的任意一个方法;这种动态获取信息以及动态调用对象方法的功能成为Java语言的反射机制。对之前写到反射机制补充。

二、正射

有反射就有对应到正射,当需要使用到某一个类的时候,先了解这个类到作用。然后实例化这个类,接着用实例化好的对象进行操作,这就是正射。

		User u= new  User();
		
		u.setAge(20);
		u.setName("java");

三、反射

反射就是,一开始并不知道要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。

       Class<?> clazz = null;
        //获取Class对象的引用
        clazz = Class.forName("com.example.javabase.User");

        //第一种方法,实例化默认构造方法,User必须无参构造函数,否则将抛异常
        User user = (User) clazz.newInstance();
        user.setAge(20);
        user.setName("java");
        System.out.println(user);

两段代码执行效果一样,但是实现的过程还是有很大的差别的:

  • 第一段代码在未运行前就已经知道了要运行的类是 User;
  • 第二段代码则是到整个程序运行的时候,从字符串 “com.example.javabase.User”,才知道要操作的类是 User。

所以反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。

四、Class类的对象

Java运行时识别对象和类的信息主要有两种方式:

  • 一种是传统的RTTI(Run-Time Type Identification)运行时类型识别,其作用是在运行时识别一个对象的类型和类的信息,它假定我们在编译期已知道了所有类型。
  • 另一种是反射机制,它允许我们在运行时发现和使用类的信息。

每个类都有一个Class对象,每当编译一个新类就产生一个Class对象(保存在一个同名的.class文件中)。比如创建一个User类,那么JVM就会创建一个User对应Class类的Class对象,该Class对象保存了User类相关的类型信息

获取反射中的Class对象有三种方法:

  • Class.forName 静态方法
  • 类的.class 方法
  • 实例对象的 getClass() 方法

反射创建类对象主要有两种方式:

  • Class的newInstance()
  • Constructor的newInstance()

反射除了创造对象,还可以获取方法,成员变量,构造器,这些都是基本的使用可以参考之前写到反射机制

五、反射实现原理

  • 第一步:首先调用了 java.lang.Class 的静态方法,获取类信息
    • 主要是先获取 ClassLoader, 然后调用 native方法,获取信息。

class类信息获取到之后开始实例化,有两种(一:无参构造函数,二:有参构造函数)

  • 第二步(无参构造函数): 调用 newInstance() 的实现方式

    • 权限检测,如果不通过直接抛出异常;
    • 查找无参构造器,并将其缓存起来;
    • 调用具体方法的无参构造方法,生成实例并返回
  • 第二步(有参构造函数):获取所有的构造器主要步骤

    • 先尝试从缓存中获取
    • 如果缓存没有,则从jvm中重新获取,并存入缓存,缓存使用软引用进行保存,保证内存可用
    • jvm获取 — getConstructor0() 为获取匹配的构造方器
      • 先获取所有的constructors, 然后通过进行参数类型比较
      • 找到匹配后,通过 ReflectionFactory copy一份constructor返回
      • 否则抛出 NoSuchMethodException;

方法调用:

  • 第一步,先获取 Method
    • 获取所有方法列表(获取所有构造器的方法很相似,都是先从缓存中获取方法,如果没有,则从jvm中获取)
    • 根据方法名称和方法列表,选出符合要求的方法
    • 如果没有找到相应方法,抛出异常,否则返回对应方法
  • 第二步,根据方法名和参数类型过滤指定方法返回(最优匹配或者精准匹配)
  • 第三步,调用 method.invoke() 方法

跟踪底层源码发现Method的invoke方法,是由本地方法invoke0决定的。

六、反射的问题

  • 性能问题

    • java反射的性能并不好,原因主要是编译器没法对反射相关的代码做优化。
    • 解决方案:
      • 1、通过setAccessible(true)关闭JDK的安全检查来提升反射速度。
      • 2、多次创建一个类的实例时,使用缓存。
      • 3、ReflectASM工具类,通过字节码生成的方式加快反射速度。
  • 安全问题

    • 单例模式的设计过程中,会强调将构造器设计为私有,因为这样可以防止从外部构造对象。但是反射可以获取类中的域、方法、构造器,修改访问权限。所以这样并不一定是安全的。
    • 解决方案:
      • 1、在构造方法中检查单例对象,如果已创建则抛出异常。

      • 2、枚举式实现单例。

  • 反射优点

    • 能够运行时动态获取类的实例,提高灵活性。

    • 与动态编译结合。

当然一般到业务场景以及如果一个程序对安全性有强制要求的场景,最好不要使用反射。

;