反射机制的概述
想象一个应用场景,如果一个应用程序已经可以独立运行了,但是我们想要在它的基础上增加一些额外功能,所以就要用到接口。软件先提前写好接口Inter,然后我们只要用一个类Demo去实现这个接口,再在软件中运行Inter in = new Demo();就可以了。但是如果软件不是我们写的,我们不能修改源代码在其中new对象,就没有办法了。
所以我们换一种思考方式,我们可以使用一个配置文件,然后应用程序在运行的时候去读取配置文件中的信息,在我们用类Demo实现Inter接口后,我们把类名Demo写进这个配置文件中。当程序用流读取配置文件后,就根据类名Demo去寻找对应的class文件。如果找到class文件就加载文件并获取文件中的内容,获取到之后就可以对文件中的方法、字段等进行调用。
如果想要对指定名称的字节码文件进行加载,获取其中的内容并调用,就用到了反射。
举例说明:
如果我们前面所说的应用程序就是Tomcat,它提供了处理请求和应答的方式。因为具体的处理动作不同,所以对外提供了接口Servlet。我们可以实现Servlet接口,自己定义具体请求和应答的处理方式。我们实现Servlet接口的类就是MyServlet类。Tomcat提供了一个配置文件web.xml,我们只需要把我们的类名Myservlet写到这个配置文件中去就行了。Tomcat会自己去读取配置文件中的指定名称。然后根据指定名称找到class文件(最终反射的就是class文件),然后就把这个文件加载进程序的。其实这些动作不是我们做的,是Tomcat自己做的。
反射机制的原理
反射机制原理
一般的类是我们根据具体对象的共性内容向上抽取而来的,而我们写完源程序之后编译得到的字节码文件就是class文件,class文件中也包含类名,字段,方法名等内容。Java把这些字节码文件的共性内容也向上抽取得到了一个对class文件进行描述的类就是java.lang包下的Class类。
我们用这个类就能获取到字节码文件中的内容,也就是说如果想对一个类文件进行解剖,只要获取到该类的字节码文件对象即可。
JAVA反射机制是在运行状态中,对于任意一个类(class文件),都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。动态获取类中的信息,就是java反射。
获取字节码文件对象
想要对字节码文件进行解剖,必须要有字节码文件对象,如何获取字节码文件对象呢?
获取字节码文件对象有三种方法:
1.Object类中的getClass()方法
public static void getClassObject_1() {
//这种方法必须要明确具体的类并创建对象
Person p1 = new Person();
Class clazz1 = p1.getClass();
Person p2 = new Person();
Class clazz2 = p2.getClass();
System.out.println(clazz1==clazz2);
}
输出结果:
true
2.通过静态属性.class
public static void getClassObject_2() {
//需要用到类中的静态成员.class
Class clazz1 = Person.class;
Class clazz2 = Person.class;
System.out.println(clazz1 == clazz2);
}
输出结果:
true
3.通过Class类中的forName方法
public static void getClassObject_3() throws ClassNotFoundException {
String name = "cn.hellobottle.day28.Person";
Class clazz = Class.forName(name);
System.out.println(clazz);
}
输出结果:
class cn.hellobottle.day28.Person
构造函数的反射
如果是以前我们要创建一个类的对象
cn.hellobottle.bean.Person p = new cn.hellobottle.bean.Person();
运行程序,就会在classpath路径内寻找名为cn.hellobottle.bean.Person的字节码文件并将其加载进内存,在堆内存中开辟空间。
但是现在我们只有一个类名,怎么创建类的实例对象?
通过空参数的构造函数创建对象
public static void creatNewObject_1(){
//类名
String name = "cn.hellobottle.bean.Person";
//通过类名获取其字节码文件对象
Class clazz = Class.forName(name);
//创建此Class对象所表示的类的一个新实例对象
Object obj = clazz.newInstance();
}
输出结果:
空参数构造函数run
查看API文档说明
newInstance():创建此 Class 对象所表示的类的一个新实例。如同用一个带有一个空参数列表的 new 表达式实例化该类。如果该类尚未初始化,则初始化这个类。
其实就是用该方法调用class文件中的空参数构造函数。
这种方法和上面直接new对象的方法都执行了空参数的构造函数。
通过非空参数构造函数创建对象
如果想调用非空参数构造函数创建对象,就是要通过指定的构造函数进行对象的初始化,所以应该先获取到该构造函数。既然我们之前已经拿到了该类的字节码文件对象,所以就可以通过对象调用Class类的方法来完成。
getConstructor(Class< ?>…paramterTypes):能拿到指定的公共构造函数
getDeclaredConstructor(Class< ?>… parameterTypes):能拿到指定的所有构造函数
public static void creatNewObject_2(){
String name = "cn.hellobottle.bean.Person";
Class clazz = Class.forName(name);
Constructor con = clazz.getConstructor(String.class, int.class);
Object obj = con.newInstance("张三",16);
}
字段的反射
public公有字段
如果想获取公有字段,就要使用方法
getField(String name):返回一个指定的公有Field对象
getFields():返回所有的公有Field对象的数组
在获取到Field对象后如果想对其进行操作,就需要使用Field类中的set和get方法。
Class clazz = Class.forName("cn.hellobottle.bean.Person");
Field f1 = clazz.getField("name");//只能获取公有的,但可以是从父类继承来的
Object obj = clazz.newInstance();
f1.set(obj,"张三");
Object o = f1.get(obj);
System.out.println(o)
输出结果:
张三
private私有字段
如果想要操作私有字段,会遇到两个问题,一个是如何获取私有字段,一个是如何set和get私有字段
获取就使用
getDeclaredField(String name):获取一个指定的公有或私有Field对象
getDeclaredFields():获取所有的公有或私有Field对象的数组
如果想要set和get私有字段,查看API文档,Field类有一个父类AccessibleObject,它是类Field、Method和Constructor的基类。它提供了将反射的对象标记为在使用时取消默认 Java 语言访问控制检查的能力。
也就是说它提供了方法可以对私有字段取消检查权限,暴力访问。这个方法就是setAccessible(boolean flag)
Class clazz = Class.forName("cn.hellobottle.bean.Person");
Field f2 = clazz.getDeclaredField("age");//可以获取全部的,但只能是本类
Object obj = clazz.newInstance();
f2.setAccessible(true);
f2.set(obj, 25);
Object o = f2.get(obj);
System.out.println(o);
输出结果:
25
一般函数的反射
我们通过字节码文件对象获取构造函数只要确定参数列表就可以了,因为都是重载形式。而获取一般函数的时候不但要确定参数列表还要确定方法名。
获取到指定函数之后,如果想要调用函数,就要用到Method类中的方法
invoke(Object obj, Object… args):obj - 从中调用底层方法的对象,args - 用于方法调用的参数
带参数的一般函数
Class clazz = Class.forName("cn.hellobottle.bean.Person");
Method method = clazz.getDeclaredMethod("paramMethod", String.class, int.class);
Object obj = clazz.newInstance();
method.invoke(obj, "张三", 23);
输出结果:
paramMethod run.....张三:23
私有方法
Class clazz = Class.forName("cn.hellobottle.bean.Person");
Method method = clazz.getDeclaredMethod("privateMethod", null);
Object obj = clazz.newInstance();
method.setAccessible(true);
method.invoke(obj, null);
输出结果:
method run