反射:框架设计的灵魂
框架是半成品的软件,在框架的基础上进行软件开发,可以简化编码。比如原来需要写的是10000行代码,用上框架之后可能就只要1000行,剩下的9000行,框架给写完了。在学习了框架之后,对框架的使用理解更为深刻。
反射:将类的各个组成部分封装为其他对象,这就是反射机制。
java代码 在计算机中会经历三个阶段
1.Source源代码阶段:写一个xxx.java代码,经过javac命令进行编译,生成xxx.class文件。此时,称为source源代码阶段,在硬盘中。
2.Class类对象阶段:类加载器(ClassLoader)将xxx.class(字节码文件)从硬盘加载到内存,此时,在内存中一个字节码文件对应一个有Class类对象。(Class类是用来描述字节码文件的共同的特征和行为)但是,一个类可能会有很多成员变量、构造方法、成员方法。故该Class类对象用Fileld[]数组来代表成员变量,Constructor[]来代表构造方法数组,Method[]来代表成员方法数组。将来,可以通过class类对象在第三阶段来创建真正的对象。
3.Runtime运行时阶段:就是new xxx()。此时根据class类对象在内存中创建出对象。
此时,再来看反射就是在第一阶段和第二阶段之间。将class字节码文件中的成员变量、构造函数、成员方法封装成为Fileld[]对象、Constructor[]对象、Method[]对象,将类的各个组成部分封装为其他对象,这就是反射机制。
好处:1.在程序的运行中,可以操作这些对象。比如说,你在idea写了String a = “aaa”;(idea是一直在运行的)当你写a.的时候,就会出现很多String的方法,当定义了一个字符串,会把字符串字节码文件加载到内存,他的Class对象已经将所以方法抽取封装成为Method[]方法了,所以才会有这个提示。
2.可以解耦,提高程序的可扩展性。
获取Class对象的方式:
1.Class.forName(“全类名”):将字节码文件加载进内存,返回Class对象
*由于输入是字符串,多用于配置文件,将类名定义在配置文件中。读取文件,加载类。
2.类名.class:通过类名的属性class获取
*多用于参数的传递
3.对象.getClass():getClass()方法在Object类中定义着。
*多用于对象的获取字符码的方式
*结论:
经过测试,同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个。
class对象方法
获取的功能:
1.获取成员变量们
* Field[] getFields() :获取所有public修饰的成员变量
* Field getField(String name) 获取指定名称的 public修饰的成员变量
* Field[] getDeclaredFields() 获取所有的成员变量,不考虑修饰符
* Field getDeclaredField(String name)
- Field:成员变量
- 操作:
-
设置值
- void set(Object obj, Object value)
-
获取值
- get(Object obj)
-
忽略访问权限修饰符的安全检查
- setAccessible(true):暴力反射
结果如下:
获取了Person类中的成员变量。
虽然可以获取到private的变量,但是,不能直接进行访问。
会报错IllegalAccessException非法获取错误,只有经过暴力反射才可以进行读取数据。
2.获取构造方法们
- setAccessible(true):暴力反射
-
- 操作:
public static void main(String[] args) throws Exception {
//先获得字节码文件
final Class aClass = Class.forName("cn.sainai.Person");
final Constructor constructor = aClass.getConstructor();
final Constructor constructor1 = aClass.getConstructor(String.class, String.class, int.class);
System.out.println(constructor1);
}
因为构造器具有参数,所以,可以用String.class,String.class,int.class来进行表示。
public static void main(String[] args) throws Exception {
//先获得字节码文件
final Class aClass = Class.forName("cn.sainai.Person");
final Constructor constructor = aClass.getConstructor();
final Constructor constructor1 = aClass.getConstructor(String.class, String.class, int.class);
//用获得的Constructor来创建对象。
final Object obj = constructor1.newInstance("张三", "nan", 22);
System.out.println(obj);
}
结果如图所示:
3.获取成员方法们
* Method[] getMethods()
* Method getMethod(String name, 类<?>… parameterTypes)
* Method[] getDeclaredMethods()
* Method getDeclaredMethod(String name, 类<?>... parameterTypes)
* *执行方法:
* Object invoke(Object obj, Object... args)
* 获取方法名称:
* String getName:获取方法名
代码
public static void main(String[] args) throws Exception {
//先获得字节码文件
final Class aClass = Class.forName("cn.sainai.Person");
final Method eat = aClass.getMethod("eat");
//第一个参数传的是方法名,第二次传的是参数类型加.class
final Method eat2 = aClass.getMethod("eat", String.class);
final Person person = new Person();
eat.invoke(person);
eat2.invoke(person,"food");
}
结果如下
4.获取类名
* String getName()
注意:
在反射面前不管是Files ,还是Constructor,还是Methods都有setAccessible(true)这个方法。所以,对于反射来说没有私有还是共有这个概念。
案例:
需求:写一个“框架”,不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法
实现:用配置文件和反射进行实现。
对于一个框架来说里面的对象是不能够修改的,先说明以前框架存在的问题。
package cn.test;
import cn.sainai.Person;
import cn.sainai.Student;
/**
* @Author:luguobao Date: 2020/5/919:57
* Description:这代表的是一个框架类
*/
public class ReflectTest {
public static void main(String[] args) {
/*Person p = new Person();
p.eat();*/
Student student = new Student();
student.study();
}
}
第一次我创建了一个person对象,调用了他的方法。第二次我创建了一个student对象。此时,我再调用它的方法的时候,我需要将原来的person对象进行注释掉,对于一个框架来说的话,很显然是失败的。因为框架代码是不能改变的。是直接用的。
现在用反射来实现这个框架
- 步骤:
1. 将需要创建的对象的全类名和需要执行的方法定义在配置文件中
2. 在程序中加载读取配置文件
3. 使用反射技术来加载类文件进内存
4. 创建对象
5. 执行方法
第一步:在src路径下定义一个配置文件,pro.properties后缀以properties结尾的就是配置文件。
代码如下:
package cn.test;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Properties;
/**
* @Author:luguobao Date: 2020/5/919:57
* Description:这代表的是一个框架类
*/
public class ReflectTest {
public static void main(String[] args) throws Exception {
//1.加载配置文件
//1.1创建Properties对象,作用把以properties结尾的方法以load的方法读取到内存中。
Properties pro = new Properties();
//1.2加载配置文件,转换为一个集合
//1.2.1获取class目录下的配置文件
//1.2.2ReflectTest.class代表的是ReflectTest的字节码文件,getClassLoader是类加载器。
final ClassLoader classLoader = ReflectTest.class.getClassLoader();
//类加载器既然可以找到类路径下的class文件,即src下的文件,即也可以找到配置文件。
//直接找到pro.properties所对应的字节流
final InputStream resourceAsStream = classLoader.getResourceAsStream("pro.properties");
//配置文件加载完全
pro.load(resourceAsStream);
//2.获取配置文件的定义的数据
String className = pro.getProperty("className");
String methodName = pro.getProperty("methodName");
//3.此时用上反射了,加载该类进内存
final Class cls = Class.forName(className);
//4.创建对象
final Object obj = cls.newInstance();
//5.获取方法,假设方法都是无参的
final Method method = cls.getMethod(methodName);
//6.执行方法
method.invoke(obj);
}
}
此时,改变配置文件的配置就可以创建不同对象和执行不同方法了。
总结
反射就是将类加载到内存中。通过反射和输入流相关机制可以通过配置文件就创建对象和执行方法。