先给出一个最常用的反射例子:
Class.forName("com.mysql.jdbc.Driver"); //注册数据库驱动
这里是使用JDBC前使用反射注册mysql驱动。
之前只是对 Java反射略有耳闻,但实际上并不清楚为什么需要使用反射以及何时需要使用反射,而Spring内部使用了大量的反射机制,那么如果我们想深入学习Spring,那么首先必须对 Java的反射机制有所了解。因此,这篇文章主要入门地阐述 反射是什么,以及反射如何使用。
一、反射是什么
一般情况下,我们使用某个类时必定知道它是什么类,是用来做什么的。于是我们直接对这个类进行实例化,之后使用这个类对象进行操作。
Apple apple = new Apple(); //直接初始化,「正射」
apple.setPrice(4);
在没有用到反射相关的类的时候,我们都是在做正射,就如上面所示。
而反射则是一开始并不知道我要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。
这时候,我们使用 JDK 提供的反射 API 进行反射调用:
Class clz = Class.forName("com.chenshuyi.reflect.Apple");
Method method = clz.getMethod("setPrice", int.class);
Constructor constructor = clz.getConstructor();
Object object = constructor.newInstance();
method.invoke(object, 4);
上面两段代码的执行结果,其实是完全一样的。但是其思路完全不一样,第一段代码在未运行时就已经确定了要运行的类(Apple),而第二段代码则是在运行时通过字符串值才得知要运行的类(com.chenshuyi.reflect.Apple)。
所以说什么是反射?
反射就是 当在运行状态中才知道要操作的类是什么时,可以使用反射在运行时获取类的完整构造,并调用对应的方法。
反射的定义
Java反射机制是在运行状态中,对于任意一个类,都能知道这个类的属性和方法。对于任意一个对象,都能调用它的任意一个方法和属性。这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
反射提供的功能
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时调用任意一个对象的方法
简单案例
public class Apple {
private String kind;
public String getKind() {
return kind;
}
public void setKind(String kind) {
this.kind = kind;
}
public static void main(String[] args) throws Exception {
// 正射
Apple apple = new Apple();
apple.setKind("red");
System.out.println(apple.getKind());
// 反射
Class clz = Class.forName("reflection.Apple");
Constructor appleConstructor = clz.getConstructor();
Object appleObj = appleConstructor.newInstance();
Method setKindMethod = clz.getMethod("setKind", String.class);
setKindMethod.invoke(appleObj, "red");
Method getKindMethod = clz.getMethod("getKind");
System.out.println(getKindMethod.invoke(appleObj));
}
}
上面通过正射和反射两种方法进行设值和获取值,结果均相同。
接下来对上面反射的步骤进行解析:
- 首先获取Class对象实例
Class clz = Class.forName("reflection.Apple"); // 输入类的全路径名
- 根据Class对象实例获取Constructor对象
Constructor appleConstructor = clz.getConstructor(); // 默认获取无参构造
- 通过Constructor对象的newInstance方法获取反射对象的实例
Object appleObj = appleConstructor.newInstance();
- 使用Class对象的getMethod方法获取Method对象
Method setKindMethod = clz.getMethod("setKind", String.class); // 方法名,参数类型
- 使用Method对象的invoke方法调用目标方法,需传入反射对象实例以及方法参数
setKindMethod.invoke(appleObj, "red"); // 反射实例,方法参数
以上是使用反射的基础步骤,但如果要进一步学习反射,还需要对反射的常用API有更多的了解。
关于Class类
- 要想解剖一个类,必须先要获取到该类的字节码文件对象(class)。通过Class类中的方法来获取class中的各种信息。
- Class对象在类加载的时候创建,并存储在堆中,一个类只能有一个Class对象
-
用于获取与类相关的各种信息,提供了获取类信息的相关方法
- 关于类加载的过程和机制会单独编写一篇文章来叙述(待完成)
二、反射常用API
在 JDK 中,反射相关的 API 可以分为下面几个方面:获取反射的 Class 对象、通过反射创建类对象、通过反射获取类属性方法及构造器。
获取Class对象
第一种:使用 Class.forName 静态方法。
Class clz = Class.forName("reflection.Apple"); // 类全路径名(String)
传入类型为字符串,适用于不知道要使用哪个类的情况
第二种:使用使用 .class方法
Class clz = Apple.class; // 直接输入类名
类名前可以加上包名,默认本包,适用于知道具体要使用哪个类的情况
第三种:使用类对象的 getClass方法
Apple apple = new Apple();
Class clz = apple.getClass(); // 使用实例对象获取Class对象
通过Class获取类的信息
1、获取类的所有public变量
Method[] method = clz.getMethods();
Field[] field = clz.getFields();
2、获取所有的构造方法
Constructor[] constructors = clz.getConstructors();
3、获取所有成员变量包括private
// 获取所有方法,包括private
Method[] declaredMethods = clz.getDeclaredMethods();
// 获取所有属性,包括private
Field[] declaredFields = clz.getDeclaredFields();
也可以传入变量来获取指定方法
通过反射获取对象实例
创建反射类实例对象主要有两种方式,第一种是直接调用Class对象的newInstance() 方法,默认调用无参构造方法。第二种调用Constructor 对象的 newInstance() 方法,可选指定构造方法。
第一种:通过Class对象的newInstance()方法
Class clz = Apple.class;
Apple apple = (Apple)clz.newInstance();
第二种:通过Constructor对象的newInstance()方法
Class clz = Apple.class;
Constructor appleConstructor = clz.getConstructor();// 可传入参数调用指定构造
Apple appleObj = (Apple) appleConstructor.newInstance();
获取对象实例后,就可以操作对象,但是在使用反射的大多数情况下,我们无法知道反射的对象类型,因此不能直接调用对象的方法,要使用invoke来配合使用。
问题:为什么newInstance后不直接调用对象实例的方法?
因为不确定 反射出来的对象类型是什么,所以不知道有什么方法。如果明确知道对象的类型,那么当然可以强制转换后直接调用对象方法。
所以我们要使用method对象的invoke来调用对象方法:
Method setKindMethod = clz.getMethod("setKind", String.class);
setKindMethod.invoke(appleObj, "red");// 对象实例,方法参数
在程序执行之前,程序不知道具体的类名和方法名,只能通过配置文件反射获取,Spring中就大量采用了反射机制。
参考: