1 反射的概念
在运行状态中,对于任意一个类,都能够获取到这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性(包括私有的方法和属性),这种动态获取的信息以及动态调用对象的方法的功能就称为java语言的反射机制。通俗点讲,通过反射,该类对我们来说是完全透明的,想要获取任何东西都可以。
想要使用反射机制的话,就必须要获得这个类的字节码文件对象(.class),进而获得类里面想要得到的信息。
获取字节码文件对象的三种方式
//1 通过Class类中的静态方法forName,直接获取到一个类的字节码文件对象,此时该类还是源文件阶段,并没有变为字节码文件。
Class clazz1 = Class.forName("全类名(含包)") //例 com.qidian.Test Test为com包下qidian包下的Test类
clazz1.newInstance();//创建实例,使用前提,存在一个无参构造
//2 当类被加载成.class文件时,此时Person类变成了.class,在获取该字节码文件对象,也就是获取自己, 该类处于字节码阶段。
Class clazz2 = Person.class;
//3 通过类的实例获取该类的字节码文件对象,该类处于创建对象阶段
People p1 = new People();
Class clazz3 = p.getClass();
2 反射的功能
反射机制允许程序在运行时取得任何一个已知名称的class的内部信息,包括其modifiers(修饰符),fields(属性),methods(方法)等,并可于运行时改变fields内容或调用methods。
那么我们便可以更灵活的编写代码,代码可以在运行时装配,无需在组件之间进行源代码链接,降低代码的耦合度;还有动态代理的实现等等;但是需要注意的是反射使用不当会造成很高的资源消耗!
3 反射的用途
很多人都认为反射在实际的 Java 开发应用中并不广泛,其实不然。当我们在使用 IDE(如 Eclipse,IDEA)时,当我们输入一个对象或类并想调用它的属性或方法时,一按alt+/,编译器就会自动列出它的属性或方法,这里就会用到反射。
反射最重要的用途就是开发各种通用框架。很多框架(比如 Spring)都是配置化的(比如通过 XML 文件配置 Bean),为了保证框架的通用性,它们可能需要根据配置文件加载不同的对象或类,调用不同的方法,这个时候就必须用到反射,运行时动态加载需要加载的对象。
Servlet 执行过程
1. 服务器根据客户端的访问路径,获得 Servlet 的访问路径;
2. 服务器拿着这个路径去 web.xml 中进行匹配,这个过程需要对 web.xml 解析;
3. 匹配成功后,会找到相应的<servlet-class>标签,其值为 Servlet 的类名,可以通过反射获得该 Servlet 的 Class 对象;
4. 然后调用 Class 对象的 newInstance()方法,实例化一个 Servlet 对象,注意一定要为 Servlet 提供一个无参的构造方法;
5. 利用反射获得 service()方法的 Method 对象,然后调用 service()方法,即执行 method.invoke(Servlet实例,参数的 Class 对象),这样就实现了 service()方法的执行。
4 反射的缺点
1. 性能开销 :反射涉及了动态类型的解析,所以 JVM 无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。我们应该避免在经常被执行的代码或对性能要求很高的程序中使用反射。
2. 安全限制 :使用反射技术要求程序必须在一个没有安全限制的环境中运行。如果一个程序必须在有安全限制的环境中运行,如 Applet,那么这就是个问题了。
3. 内部暴露 :由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用,这可能导致代码功能失调并破坏可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。
5 反射的使用 Class类得API详解
public String getName():获得类的完整名字。
public Field[] getFields():获得类的public类型的属性。
public Field[] getDeclaredFields():获得类的所有属性。包括private 声明的和继承类
public Method getMethod(String name, Class[] parameterTypes):获得类的特定方法,name参数指定方法的名字,parameterTypes 参数指定方法的参数类型。
public Method[] getMethods():返回某个类的所有公用(public)方法包括其继承类的公用方法,当然也包括它所实现接口的方法。
public Method[] getDeclaredMethods():表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。当然也包括它所实现接口的方法。
public Constructor<?>[] getConstructors():获得类的public类型的构造方法。
public Constructor<T> getConstructor(Class[] parameterTypes):获得类的特定构造方法,parameterTypes 参数指定构造方法的参数类型。
public T newInstance():通过类的不带参数的构造方法创建这个类的一个对象。
public void setAccessible(true); 对于私有的成员,设置后可强制访问。
测试数据例:
package test4;
public class People{
private String id;
public String name;
public People() {
System.out.println("无参构造函数");
}
public People(String text) {
super();
System.out.println("有参构造函数:" + text);
}
public void show() {
System.out.println("无参方法");
}
public String show2(String text) {
System.out.println("有参方法:" + text);
return text;
}
public static String show3(String text){
System.out.println("有参静态方法:"+text);
}
}
a) 反射类的完整名字
Class<?> clazz = Class.forName("test4.People"); //获取类完整名字
System.out.println(clazz.getName());//test.People
b) 反射类的字段
Class<?> clazz = Class.forName("test4.People");
//获得类中public的字段
Field[] fields = clazz.getFields();
for(Field field:fields) {
System.out.println(field.getName());//输出name
}
//获得所有字段
Field[] declaredFields = clazz.getDeclaredFields();
for(Field field:declaredFields) {
System.out.println(field.getName());//输出id和name
}
//如果私有的字段使用getField方法获取将会抛出NoSuchFieldException异常
Field field = clazz.getField("name");
Field field2 = clazz.getDeclaredField("id");
System.out.println(field.getName()); //name
System.out.println(field2.getName()); //id
System.out.println(field); //public java.lang.String test4.People.name
c) 反射类的方法
People people = new People();//实例化People类,输出无参构造方法
Class<?> clazz = Class.forName("test4.People");
//1 获取无参show方法
Method method = clazz.getMethod("show", null);
//第一个参数:哪个对象想要执行show方法
//第二个参数:方法的参数,没有即为null
method.invoke(people,null);//执行show方法
//2 获取有参数show2方法
Method method2 = clazz.getMethod("show2", String.class);
method2.invoke(people,"测试");//执行show2方法
//3 获取有参静态show3方法
Method method3 = clazz.getMethod("show3", String.class);
method3.invoke(null,"测试");//执行show3方法
//4 获取类的所有公用(public)方法包括其继承类的公用方法,当然也包括它所实现接口的方法。
// 所有类继承于Object类,所以此时会输出Object类的public方法
Method[] methods = clazz.getMethods();
for(Method method4:methods) {
System.out.println(method4);
}
System.out.println();
//5 获取类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。当然也包括它所实现接口的方法。
Method[] methods2 = clazz.getDeclaredMethods();
for(Method method5:methods2) {
System.out.println(method5);
}
输出:
d) 反射类的构造函数
Class<?> clazz = Class.forName("test4.People");
//1 通过无参构造器得到该对象
Constructor<?> c = clazz.getConstructor(null);
People people = (People) c.newInstance(null);
//2 通过有参构造器得到该对象
Constructor<?> c2 = clazz.getConstructor(String.class);
People people2 = (People) c2.newInstance("参数");
输出:
e) 对于私有成员,强制访问
public class Main {
public static void main(String[] args) throws Exception {
Class<?> clazz = People.class;
People p = new People();
p.setId("1");
p.setName("2");
// 获得类中public的字段
Field[] declaredFields = clazz.getDeclaredFields();
for (Field field : declaredFields) {
field.setAccessible(true);//如果此句注释掉,将会抛出异常,私有的成员不能访问
System.out.println(field.getName() + " " + field.get(p));
}
}
}
设置强制访问,正常运行。
未设置,无法访问,抛出异常