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、枚举式实现单例。
-
-
反射优点
-
能够运行时动态获取类的实例,提高灵活性。
-
与动态编译结合。
-
当然一般到业务场景以及如果一个程序对安全性有强制要求的场景,最好不要使用反射。