反射
需求:通过外部文件配置,在不修改源码的情况下,来控制程序,也符合设计模式 的ocp原则(开闭原则:不修改源码,开扩容功能)
配置文件
classfullpath=com.hspedu.Cat
method=hi
这样做的好处时,当方法改变时,不需要修改源码,直接改配置文件中的method即可。
用反射机制得到一个Class对象,接着通过Class对象new一个实例,接着用Class对象得到Method
public class ReflectionQUestion {
public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
//使用Properties类,可以读写配置文件
Properties properties = new Properties();
properties.load(new FileInputStream("D:/javacode/javaweb/javaweb/reflection/src/main/resources/re.properties"));
String classfullpath = properties.get("classfullpath").toString();
String methodName = properties.get("method").toString();
//加载类,返回Class类型的对象cls
Class cls = Class.forName(classfullpath);
//通过cls得到加载的类com.hspedu.Cat实例对象
Object o = cls.newInstance();
//得到o的运行类型
System.out.print(o.getClass());
//通过cls得到加载的类com.hspedu.Cat的methodName的“hi”对象
//即:在反射中,可以把方法视为对象
Method method = cls.getMethod(methodName);
System.out.println("======================");
method.invoke(o);
}
}
反射机制
反射机制允许程序在执行期借助于ReflectionAPI取得任何类的内部信息(比如成员变量,构造器,成员方法等),并能操作对象的属性及方法。反射在设计模式和框架底层都会用到。
加载完类之后,在堆中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了类的完整结构信息。通过这个对象得到类的结构。这个对象就像一面镜子,通过这个镜子看到类的结构,所以,形象的称之为反射。
🍕java程序在计算机有三个阶段
- 代码阶段/编译阶段
- java文件通过javac编译生成字节码文件
- Class类阶段(加载阶段)
- 加载字节码文件,将字节码文件加载到内存中的堆中去。类加载器完成,体现反射机制。
- Runtime运行阶段
创建对象Cat cat = new Cat();
会导致类的加载,加载Cat.class字节码文件,将字节码文件加载到内存中的堆中去,生成一个Class类对象。类加载完成之后生成对象,Cat对象知道它是属于那个Class对象。因此,通过对象也可以知道它所关联的Class
反射机制可以完成
- 在运行时可以判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时得到任意一个类所具有的成员变量和方法
- 在运行时调用任意一个对象的成员变量和方法
- 生成动态代理
反射相关的类
- java.lang.Class:代表一个类,Class对象表示加载后在堆中的对象
- java.lang.reflect.Method:代表类的方法,Method对象表示某个类的方法
- java.lang.reflect.Filed:代表类的成员变量,Filed对象表示某个类的成员变量
- java.lang.reflectConstructor:代表类的构造方法,Constructor对象表示构造器
反射的调用优化
反射的优点:可以动态的创建和使用对象(也是框架底层的核心),使用灵活,没有反射机制,框架技术就失去底层支撑
缺点:使用反射基本是解释执行,对执行速度有影响
反射调用优化—关闭访问检查(有点作用,但是效果不是特别明显)
- Method、Field、Constructor对象都有setAccessible()方法
- setAccessible()方法的作用是启动和禁用访问安全检查的开关
- 参数值为true,反射的对象在使用时取消访问检查,提高反射效率,参数值为False表示反射的对象执行访问检查
Class类
- Class也是类,因此也继承了Object(从IDEA类图中看出)
- Class类对象不是new出来的,而是系统创建的
- 对于某个类的Class类对象,在内存中只有一份,因为类只加载一次
- 每个类的实例都会记得自己是由那个Class实例所生成的
- 通过Class对象可以完整地得到一个类的完整结构,通过一系列API
- Class对象是存放在堆的
- 类的字节码二进制数据是放在方法区的,有的地方称为元数据(包括方法代码、变量名、方法名、访问权限等)
类class对象是由类加载器(ClassLoader)生成的,通过loadClass( )方法完成类加载,生成某个类对应的Class对象。
package com.hspedu.reflection.question;
import java.lang.reflect.Field;
public class Class02 {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
String classPath = "com.hspedu.Car";
//获取Car类对应的Class对象
//<?>不确定的java类型
Class<?> cls = Class.forName(classPath);
//1、显示cls对象,是哪个类的Class对象 com.hspedu.Car,cls不是真正的Car对象
System.out.println(cls);
//2、输出cls运行类型 java.lang.Class
System.out.println(cls.getClass());
//3、得到包名 com.hspedu
System.out.println(cls.getPackage().getName());
//4、得到全类名
System.out.println(cls.getName());
//5、通过cls创建对象实例
Object car = cls.newInstance();
//6、通过反射获取属性brand
Field brand = cls.getField("brand");
System.out.println(brand.get(car));
//7、通过反射给属性赋值
brand.set(car,"奔驰");
System.out.println(brand.get(car));
//8、遍历所有属性
Field[] fields = cls.getFields();
for(Field field : fields){
System.out.println(field.getName());
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U6T1IqXq-1692240829078)(study/08151.jpg)]
获取Class类对象
- 前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可以会抛出ClassNotFoundException异常
应用场景:多用于配置文件,读取全类路径,加载类
- 前提:若已知具体类,通过类的Class获取,该方式最为可靠,程序性能最高
Class cls2 = Cat.Class
应用场景:多用于参数传递,比如通过反射得到对应的构造器对象。
- 前提:已知某个类的实例,调用该实例的getClass( )方法获取Class对象,实例
Class cls3 = 对象.getClass();
应用场景:通过创建好的对象,获取Class对象
- 其他方式
ClassLoader cl = 对象.getClass().getClassLoader();
Class cl2 = cl.loadClass("类的全类名");
-
基本数据(int 、char、boolean、float、double、byte、long、short)按如下方式得到Class类对象
Class cls = 基本数据类型.class
-
基本数据类型对应的包装类,可以通过.type得到Class类
Class cls = 包装类.TYPE;
- 代码阶段\编译阶段:Class.forName 需要类加载器ClassLoader调用loadClass()方法获取类对象
- Class类阶段(加载阶段)类.Class
- Runtime运行阶段 对象.Class 即由对象可以知道它是属于那个Class对象,Class类型就是它真正加载进来的字节码文件Cat.class的类型,也就是它的运行类型(多态 编译类型与运行类型)
那些类型由Class对象
- 外部类、成员内部类、静态内部类、局部内部类、匿名内部类
- interface:接口
- 数组
- enum:枚举
- annotation:注解
- 基本数据类型
- void返回类型
类加载
类加载是指在Java程序运行期间,将类的字节码文件(.class文件)加载到Java虚拟机(JVM)中,并转换成可执行的Java类的过程。
基本说明:
反射机制是java是实现动态语言的关键,也是通过反射实现类动态加载。
-
静态加载:在编译时就会加载相关的类,依赖性太强。
Cat cat = new Cat();
当没有该类时,编译时会报错。 -
动态加载:运行时加载需要的类,如果运行时不用该类且该类不存在,则不报错,降低了依赖性。比如
Class.forName("person")
编译不报错,但是如果运行时,由于条件分支,没有用到这里也不会报错。只有运行到该代码时才会报错。 -
动态加载可以说是延迟加载,静态加载是及时加载。
类加载时间
- 当创建对象时(new)
- 当子类被加载时,父类也加载
- 调用类中静态成员时
- 通过反射
前三种是静态加载,反射是动态加载。
类加载过程:
java源码(Cat.java) —javac编译—->字节码文件(com.Cat.class)—java运行—>加载Loading、连接Linking、初始化initialization(类加载三个阶段)
类加载后内存布局情况
生成的字节码文件通过类加载器,可以把字节码加载到内存中,并在堆中生成了类的Class对象,以及在方法区中得到了类的字节码的二进制数据。
类加载各阶段完成的任务
🍉加载(Loading)
将类的class文件读入内存,并为之创建一个java.lang.Class对象,此过程由类的加载器完成。JVM在该阶段的主要目的将字节码从不同的数据源(类的字节码可以来源于本地文件系统、网络、数据库等)转化为二进制数据加载到内存中。
🍉连接(Linking)将类的二进制数据合并到JRE中。
- 验证(verification)对文件安全性进行校验。目的是确保Class文件的字节流中包含的信息符号当前虚拟机的要求,包括校验文件格式、元数据的正确性、字节码的正确性等。可以考虑使用
-Xverify:none
参数关闭大部分类的验证措施,缩短虚拟机加载时间。 - 准备(Preparation)为类的静态变量分配内存并设置默认初始值。这些变量使用的内存都将在方法区中进行分配。
class A{
/*
属性也是成员变量
分析:类加载链接阶段 准备属性是如何处理
n1是实例属性,不是静态变量,因此在准备阶段,是不会分配内存
n2是静态变量,分配内存 n2默认初始化是0,而不是20
n3是static final 是常量,它和静态变量不一样,一旦赋值就不会 n3=30
*/
public int n1 = 10;
public static int n2 = 20;
public static final int n3 = 30;
}
- 解析(Resolution)虚拟机将常量池内的符号引用替换为直接引用的过程。符号引用是指在编译时使用的类、方法、字段等的符号名称,而直接引用是指在虚拟机运行时直接指向内存中的对象。
🍉初始化(initialization)JVM负责对类进行初始化,这里主要是指静态成员。
执行类的初始化代码,包括静态变量赋值和静态代码块的执行。类的初始化是类加载过程的最后一步,它会按需触发并保证在多线程环境下的安全性。
- 到了初始阶段,才真正开始执行类中定义的java代码,此阶段是执行clinit()方法的过程。
- clinit方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并进行合并。
- 不创建类的对象,只加载类,构造器不执行
- 如果直接使用类的静态属性,会导致类的加载
- 虚拟机会保证一个类的clinit方法在多线程环境中被正确地枷锁、同步,如果多个线程去同时初始化一个类,那么只会一个线程去执行这个类的clinit方法,其他线程都需要阻塞等待,直到活动线程执行clinit方法完毕。正是因为有这个机制,才能保证某个类在内存中,只有一份Class对象。
反射获取类的结构信息
- java.lang.Class
可以获取全类名、简单类名、所有public修饰的属性、方法(本类以及父类),本类的public修饰的构造器,本类所有的属性、方法、构造器,以及包、父类、接口、注解。
-
java.lang.reflect.Field
-
java.lang.reflect.Method
-
java.lang.reflect.Constructor
package com.hspedu.reflection.question;
import org.junit.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectionUtils {
public static void main(String[] args) {
}
@Test
public void api_02() throws ClassNotFoundException {
Class<?> personClass = Class.forName("com.hspedu.Person");
Field[] fields = personClass.getDeclaredFields();
//getModifiers 以int形式返回修饰符
//默认修饰符0,public 1,private 2,protected 4, static 8,final 16
//返回的是属性类型对应的那个Class类对象
for (Field field : fields) {
System.out.println("本类所有属性:"+field.getName()+"----该属性的修饰符值"+field.getModifiers()+"----该属性的类型"+field.getType());
}
Method[] methods = personClass.getDeclaredMethods();
for (Method method : methods) {
System.out.println("本类所有方法:"+method.getName()+"----该方法的修饰符值"+method.getModifiers()+"----该方法的返回值类型"+method.getReturnType());
//返回参数类型数组
Class<?>[] parameterTypes = method.getParameterTypes();
for(Class<?> parameterType : parameterTypes){
System.out.println("该方法的形参类型"+parameterType);
}
}
Constructor<?>[] declaredConstructors = personClass.getDeclaredConstructors();
for(Constructor<?> declaredConstructor : declaredConstructors){
System.out.println("本类中所有构造器"+declaredConstructor.getName());
Class<?>[] parameterTypes = declaredConstructor.getParameterTypes();
for(Class<?> parameterType : parameterTypes){
System.out.println("该构造器的形参类型"+parameterType);
}
}
}
}
通过反射创建对象
- 调用类中的public修饰的无参构造器
- 调用类中的指定构造器
Class类相关方法
- newInstance:调用类中无参构造器,获取对应类的对象
- getConstructor(Class… clazz):根据参数列表,获取public对应的构造器对象
- getDecalaredConstructor(Class… clazz):根据参数,获取对应的所有构造器对象
Constructor类相关方法
setAccessible:暴破
newInstance(Object … obj):调用构造器
反射暴破机制创建实例
package com.hspedu.reflection.question;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
public class ReflectCreateInstance {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
Class<?> userClass = Class.forName("com.hspedu.reflection.question.User");
//通过public无参构造器创建实例
Object o = userClass.newInstance();//o的运行类型就是User
System.out.println(o);
//通过public有参构造器创建实例
//先得到构造器,再创建实例,并传入实参
Constructor<?> constructor = userClass.getConstructor(String.class);//得到public修饰的构造器对象
Object o1 = constructor.newInstance("涂山璟");
System.out.println("user1 " + o1);
//通过非public有参构造器创建实例
Constructor<?> constructor1 = userClass.getDeclaredConstructor(int.class, String.class);
constructor1.setAccessible(true);//解决构造器私有化问题,即使用反射可以访问private的构造器、方法、属性,
// 暴力破解,反射面前都是纸老虎
Object o2 = constructor1.newInstance(100, "叶十七");
System.out.println("user2 "+o2);
Field age =userClass.getField("age");
age.set(o,66);
System.out.println(o);
System.out.println(age.get(o));
Field desc = userClass.getDeclaredField("desc");
desc.setAccessible(true);
//desc.set(o,"厉害了");
desc.set(null,"厉害了");//因为desc是static属性,因此o也可以写成null
System.out.println("user3 "+o);
//System.out.println(desc.get(o));
System.out.println(desc.get(null));
}
}
class User{
public int age=10;
private String name="啊哈";
private static String desc;
public User() {
}
public User(String name) {
this.name = name;
}
private User(int age, String name) {
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "User [ age=" + age +", name=" + name + ", desc=" + desc + "]";
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LFm50bLL-1692240829081)(study/0816.jpg)]
通过反射访问类中的成员
🌷访问属性
- 根据属性名获取Field对象
Field f = clazz对象.getDeclaredField(属性名);
- 暴破:
f.setAccessible(true);
- 访问
f.set(o,值); f.get(o);
- 如果是静态属性,则set和get中的参数可以写成null,因为静态属性是和类相关的,不是和对象相关的
🌷访问方法
- 根据方法名和参数列表获取Method方法对象:
Method m = clazz.getDeclaredMethod(方法名,XX.Class);
- 获取对象
Object o = clazz.newInstance();
- 暴破:
m.setAccessible(true);
- 访问:
Object returnValue = m.invoke(o,实参列表);
- 注意:如果是静态方法,则invoke的参数o,可以写成null
f = clazz对象.getDeclaredField(属性名); 2. 暴破:
f.setAccessible(true);3. 访问
f.set(o,值); f.get(o);`
4. 如果是静态属性,则set和get中的参数可以写成null,因为静态属性是和类相关的,不是和对象相关的
🌷访问方法
- 根据方法名和参数列表获取Method方法对象:
Method m = clazz.getDeclaredMethod(方法名,XX.Class);
- 获取对象
Object o = clazz.newInstance();
- 暴破:
m.setAccessible(true);
- 访问:
Object returnValue = m.invoke(o,实参列表);
- 注意:如果是静态方法,则invoke的参数o,可以写成null