- 反射:是框架的设计灵魂。那框架又是什么呢? 框架:是一个半成品软件。可以在框架的基础上进行软件开发,这样可以简化代码。
- 回到反射上面,反射:将类的各个组成部分封装成其他对象,这就是反射机制。这么说还是不懂反射是什么,别急,耐心看下去。
1.先了解下java代码在计算机中需要经历三个阶段:
第一个阶段:Source源代码阶段。通俗点来说这个阶段就是平时我们写的java代码 Demo1.java,经过javac编译后生成一个字节码文件 Demo1.class。字节码文件里面包含成员变量,成员方法,构造函数。这两个文件都只是存放在硬盘上,还没有进内存。
第三个阶段:Runtime运行时阶段。第一个阶段写的文件怎么去使用呢,这就需要在第三阶段new Demo1() 生成一个对象。但对象是在内存中的,第一个阶段生成的文件都没进内存,那第三个阶段怎么去使用呢。这里就需要第二个阶段,把字节码文件加载进内存。
第二个阶段:Class类对象阶段。通过类加载器(ClassLoader)把字节码文件Demo1.class加载进内存。那在内存中怎么去描述这个字节码文件呢?其实,在java中万物皆对象;在第二个阶段中就是通过Class这个类对象去描述字节码文件的。Class类对象里面有三个比较重要的东西,成员变量,成员方法和构造方法;把这三部分都封装为对象,成员变量封装为Field对象,构造方法封装为Constructor对象,成员方法封装为Method对象。因为成员变量可能不止一个,所以一般都用数组Field[ ] fields来描述成员变量,其他两部分也是用数组的形式。后面就是通过这个Class类对象来创建真正的对象(比如new Demo1()),这就是从第二阶段到第三阶段。
再看下图,对这三个阶段的理解就清晰多了。
2.获取Class对象:
- 我们平时写的代码,也就是第一阶段,没有加载进内存,只是存储在硬盘上。那么这个时候怎么获取Class对象呢?可以使用 Class.forName(“全类名”);在第二阶段时,已经加载到内存中了,可以通过 类名.class 获取Class对象;在第三阶段时,因为已经有了对象,所以可以使用 对象.getClass() 方法来获取Class对象
获取Class对象的方式:
1. Class.forName("全类名"):将字节码文件加载进内存,返回Class对象。(全类名:包名+类名)
- 多用于配置文件,将全类名定义在配置文件中。读取文件,加载类。
- 方法实际上也是调用的CLassLoader来实现的
2. 类名.class:通过类名的属性class获取
- 多用于参数的传递,有时候传参需要传一个Class对象
3. 对象.getClass():getClass方法在Object中定义着
- 多用于对象获取字节码的方式
ps:同一个字节码文件(*.class)在一次程序运行中,只会被加载一次,无论通过哪一种方式获取的Class对象都是同一个。
3.Class对象功能:
获取功能:
- 获取成员变量们
Field[ ] getFields( ):获取所有public修饰的成员变量
Field getField(String name): 获取指定名字的成员变量
Field[ ] getDeclaredFields() :获取所有的成员变量,不考虑修饰符
Field getDeclaredField(String name)
- 获取构造方法们
Constructor<?>[] getConstructors()
Constructor<T> getConstructor(类<?>... parameterTypes)
Constructor<?>[] getDeclaredConstructors()
Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)
- 获取成员方法们
Method[] getMethods()
Method getMethod(String name, 类<?>... parameterTypes)
Method[] getDeclaredMethods()
Method getDeclaredMethod(String name, 类<?>... parameterTypes)
4.获取类名
String getName()
使用:
- 获取成员变量
Object get(Object obj) :获取值
void set(Object obj, Object value) : 设置值
setAccessible(true) : 暴力反射,忽略访问权限修饰符的安全检查
- 获取构造方法
创建对象
T newInstance(Object... initargs)
如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法
- 获取成员方法
执行方法:
Object invoke(Object obj, Object... args)
获取方法名
Method.getName()
4.反射的体现,为什么要用反射?
看到这里会觉得反射很麻烦,没有体现出它的价值。接下来看个案例,就会一目了然了。这个案例就是写一个类,在不改变该类任何代码的前提下,可以帮助我们创建任意类的对象,并且执行其中任意方法。
步骤:
1. 将需要创建的对象的全类名和需要执行的方法定义在配置文件
2. 在程序中加载读取配置文件
3. 使用反射技术来加载类文件进入内存
4. 创建对象
5. 执行方法
Student类:
@NoArgsConstructor
public class Student {
public void sleep(){
System.out.println("睡觉");
}
}
pro.properties配置文件:
className=com.lyp.domain.Student
methodName=sleep
ReflectTest类:
public class ReflectTest {
public static void main(String[] args) throws Exception {
// 前提:不能修改该类的代码
// Student s = new Student();
// s.sleep();
// 1.加载配置文件
// 1.1 创建Properties对象
Properties pro = new Properties();
// 1.2 加载配置文件,转化为一个集合
// 1.2.1 获取class目录下的配置文件
ClassLoader classLoader = ReflectTest.class.getClassLoader();//通过类加载器将字节码文件加载进内存
InputStream is = classLoader.getResourceAsStream("pro.properties");
pro.load(is);
// 2.获取配置文件中定义的数据
String className = pro.getProperty("className");
String methodName = pro.getProperty("methodName");
// 3.加载该类进内存
Class cls = Class.forName(className);
// 4.创建对象
Object o = cls.newInstance();
// 5.获取方法对象
Method method = cls.getMethod(methodName);
// 6.执行方法
method.invoke(o);
}
}
运行结果:
将需要生成的对象的信息放在配置文件中,这样我们不需要去修改ReflectTest的代码,只需要修改配置文件中className,methodName即可;如果不使用配置文件,就需要在ReflectTest类中写死,Student s = new Student(); s.sleep();那如果想要生成别的对象每次都需要去找到ReflectTest类,修改Student为别的类。使用反射后,增加了程序的灵活性,避免将代码写死,降低了耦合性。在框架的设计思想里用到的就是反射。