23_反射
反射引入
- 可以通过外部文件配置,在不修改源码的情况下来控制程序,也符合设计模式的ocp原则(开闭原则:不修改源码,扩容功能)
反射机制
- 反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息(比如成员变量,构造器,成员方法等),并能操作对象的属性及方法。反射在设计模式和框架底层都会用到
- 加载完类之后,在堆中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了类的完整结构信息,通过这个对象得到类的结构。
反射相关的主要类:
- java.lang.Class:代表一个类,Class对象表示某个类加载后堆中的对象
- java.lang.reflect.Method:代表类的方法,Method对象表示某个类的方法
- java.lang.reflect.Field:代表类的成员变量,Field对象表示某个类的成员变量
- java.lang.reflect.Constructor:代表类的构造方法,Constructor对象标识某个类的构造方法
反射的优点和缺点
- 优点:可以动态的创建和使用对象,是框架底层核心,使用灵活
- 缺点:使用反射基本是解释执行,对执行速度有影响
反射调用优化-关闭访问检查
- Method和Field、Constructor对象都有setAccessible()方法
- setAccessible作用是启动和禁用访问安全检查的开关
setAccessible(true)
参数值为true,表示取消访问检查,提高反射效率。
Class类
1.Class也是类,因此也继承Object类
2.Class类对象不是new出来的,而是系统创建的
3.对于某个类的Class类对象,只加载一次,只有一个
4.每个类的实例都知道自己对应的Class类对象
5.Class类对象可以得到对应类的完整结构,通过一系列API
6.Class类对象是存放在堆中的
7.类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据
获取Class类对象
- 已知一个类的全类名,且该类在类路径下,可以通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException
应用场景:通过 配置文件 读取类全路径,加载类
Class cls = Class.forName(全类名);
- 已知具体的类,通过类的class获取,该方式 最安全可靠,性能最高
应用场景:用于参数传递
Class cls = 类名.class;
- 已知某个类的实例,调用该实例的getClass()方法获取Class对象
应用场景:通过创建好的对象获取Class对象
Class cls = 对象.getClass();
- 通过类加载器来获得类的Class类对象
//1.先得到类加载器
ClassLoader classLoader = 类对象.getClass().getClassLoader();
//2.通过类加载器来得到Class对象
Class cls = classLoader.loadClass(全类名);
- 基本数据类型(byte,short,int,long,char,float,double,boolean)的Class类对象
基本数据类型.class
- 基本数据类型对应的包装类,可以通过 .TYPE 得到Class对象,得到的Class对象 和 基本数据类型 的Class对象一样
包装类.TYPE
哪些类型有Class对象
- 类
- interface 接口
- 数组
- enum 枚举
- annotation 注解
- 基本数据类型
- void
类加载
- 反射机制是Java 实现动态语言的关键,也就是通过反射实现类动态加载
静态加载:编译时加载相关的类,如果没有 则报错,依赖性太强
动态加载:运行时加载需要的类,如果运行时不用该类,该类不存在也不会报错,降低了依赖性 - 类加载时机
- 静态加载
- 当创建对象时(new )
- 子类被加载时
- 调用类的静态成员时
- 动态加载
- 通过反射
类加载流程图
加载阶段(Loading)
JVM在该阶段的主要目的是将字节码从不同的数据源(可能是class文件、jar包、网络)转化为二进制字节流加载到内存中,并生成一个代表该类的 java.lang.Class 对象
连接阶段(Linking)
1. 验证
- 为了确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全
- 包括:文件格式验证(是否以魔数 oxcafebabe 开头)、元数据验证、字节码验证和符号引用验证
- 可以考虑使用 -Xverify:none 参数来关闭大部分的类验证措施,缩短虚拟机类加载的时间
2. 准备
- JVM会在该阶段对静态变量,分配内存并默认初始化(数据类型的默认初始值,0, 0.0, null等),这些变量所使用的内存都将在方法区中进行分配
class A {
public int n1 = 10;//n1 是实例属性,在准备阶段不会分配内存
public static int n2 = 20;//n2 是静态变量,分配内存 并默认初始化 0
public static final int n3 = 30;//n3 是常量,一旦赋值就不变 n3 = 30
}
3. 解析
- 虚拟机将常量池内的 符号引用 替换为 直接引用 的过程
初始化阶段(initialization)
- 初始化阶段才是真正开始执行类中定义的Java程序代码,此阶段是执行 < clinit > 方法的过程
- < clinit > 方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量的赋值操作和静态代码块中的语句,并进行合并
calss A {
static {
System.out.println("静态代码块");
n = 20;
}
public static int n = 10;//静态变量 赋值
}
/* 初始化阶段将A类中的静态变量赋值 和 静态代码块语句 进行合并
静态变量 和 静态代码块 按顺序执行,合并后可以看作
clinit() {
System.out.println("静态代码块执行");
n = 10;
}
*/
- 虚拟机 会保证一个类的< clinit >方法在多线程环境中被正确 加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的< clinit > 方法,即只会加载一次,只有一个Class类对象
synchronized (getClassLoadingLock(name)) {
...
}
反射暴破
- 暴破(暴力破解) setAccessible(true) 后,可以通过反射使用 private 的成员
操作构造方法创建类对象
- Class类相关的方法
Class对象.getConstructor(参数的class对象)
:根据参数列表,只能获得相应的 public 的构造对象
Class对象.getDeclaredConstructor(参数的class对象)
:根据参数列表,获得相应的不限访问修饰符的构造对象 - Constructor类相关的方法
构造对象.setAccessible(true)
:暴破后,可以使用private的构造器 创建类对象
构造对象.newInstance(参数列表)
:调用构造器创建对象,private修饰的构造器需要先暴破,其他修饰的直接创建
/*
class User {
private int age;
private String name;
User() { // constructor1
}
public User(int age) { // constructor2
this.age = age;
}
private User(String name, int age) { // constructor3
this.age = age;
this.name = name;
}
}
*/
Class<?> cls = Class.forName("com.wiz.reflection.User");
Constructor<?> constructor1 = cls.getDeclaredConstructor();
Constructor<?> constructor2 = cls.getConstructor(int.class);
Constructor<?> constructor3 = cls.getDeclaredConstructor(String.class, int.class);
Object o1 = constructor1.newInstance();
Object o2 = constructor2.newInstance(10);
constructor3.setAccessible(true);
Object o3 = constructor3.newInstance("lyh", 11);
操作方法
- Class类相关方法
Class对象.getMethod(方法名)
:根据方法名,只能获得相应的public方法对象
Class对象.getDeclaredMethod(方法名)
:根据方法名,获得相应的不限访问修饰符的方法对象
Class对象.getMethods()
:获得 所有的public方法对象 数组
Class对象.getDeclaredMethods()
:获得 所有的不限访问修饰符的方法对象 数组 - Method类相关方法
Method对象.setAccessible(true)
:暴破后,可以操作 private的方法
Method对象.invoke(类的对象, 对应方法的参数)
:调用类对象的方法,如果是静态方法 类的对象可以是null
操作属性
- Class类相关方法
Class对象.getField(属性名)
:根据属性名,只能获得相应的public属性对象
Class对象.getDeclaredField(属性名)
:根据属性名,获得相应的不限访问修饰符的属性对象
Class对象.getFields()
:获得 所有的public属性对象 数组
Class对象.getDeclaredFields()
:获得 所有的不限访问修饰符的属性对象 数组 - Field类相关方法
Field对象.setAccessible(true)
:暴破后,可以操作 private的属性
Field对象.set(类的对象, 属性值)
:修改属性值,如果是静态属性 类的对象可以是null
Field对象.get(类的对象)
:获得属性值,如果是静态属性 类的对象可以是null