1.2 Java语言反射机制
1.2.1 简单实例
继续上篇提到的Java语言反射机制,我们先来介绍一下Java在这方面的基础知识。
Java语言允许通过程序化的方式间接对Class的对象实例操作,Class文件由类装载器装载后,在JVM中将形成一份描述Class结构的元信息对象,通过该元信息对象可以获知Class的结构信息:如构造函数、属性和方法等。Java允许用户借由这个Class相关的元信息对象间接调用Class对象的功能,这就为使用程序化方式操作Class对象开辟了途径。
我们从以下的简单例子来理解Java反射机制:
public class Car{
private String brand;
private String color;
private int maxSpeed;
//默认构造函数
public Car(){
}
//带参构造函数
public Car(String brand,String color,int maxSpeed){
this.brand = brand;
this.color = color;
this.maxSpeed = maxSpeed;
}
public void introduce(){
System.out.println("brand:"+brand+",color:"+color+",maxSpeed:"
+maxSpeed);
}
//省略参数的getter/setter方法
...
}
下面进入Java反射机制操作:
public class ReflectTest{
public static Car initByDefaultConst()throws Throwable{
//通过类装载器获取Car类对象
ClassLoader loader =
Thread.currentThread().getContextClassLoader();
Class clazz = loader.loadClass("com.reflect.Car");
//获取类的默认构造函数对象并通过它实例化Car
Constructor cons = clazz.getDeclaredConstructor((Class[])null);
Car car = (Car)cons.newInstance();
//通过反射方法设置属性
Method setBrand = clazz.getMethod("setBrand",String.class);
setBrand.invoke(car,"红旗CA72");
Method setColor = clazz.getMethod("setColor",String.class);
setColor.invoke(car,"黑色");
Method setMaxSpeed = clazz.getMethod("setMaxSpeed",int.class);
setMaxSpeed.invoke(car,200);
return car;
}
public static void main(String[] args){
Car car = intByDefaultConst();
car.introduce();
}
}
这样我们在控制台里将会获得我们在反射机制里设置的信息。对于这一流程的实现:我们主要使用了几个重要的反射类,分别是ClassLoader、Class、Constructor和Method,通过它们间接调用了目标Class的各项功能。具体步骤:
(1)首先获取当前线程的ClassLoader,通过它装载Car类对应的反射实例。
(2)然后通过Car的反射类对象获取Car的构造函数对象cons,再通过它的newInstance()方法实例化Car对象,等同于new Car()。
(3)最后是通过Car的反射类对象的getMethod()方法获取属性的Setter方法对象,两个参数分别是目标Class的方法名和目标方法的入参对象类型,再通过invoke()方法调用目标类方法,两个参数分别是操作的目标类对象实例和目标方法的入参。
1.2.2 类装载器ClassLoader
下面我们在针对上面反射机制里提到的概念进行一下机制的简单介绍:
类装载器工作机制
类装载器就是寻找类的字节码文件并构造出类JVM内部表示的对象组件。
类装载工作由ClassLoader及其子类负责,ClassLoader是一个重要的Java运行时系统组件,它负责在运行时查找和装入Class字节码文件。JVM运行时会产生三个ClassLoader:根装载器、ExtClassLoader(扩展类装载器)和AppClassLoader(系统类装载器)。
其中根装载器不是ClassLoader的子类,它使用C++编写,负责装载在JRE的核心类库。ExtClassLoader和AppClassLoader都是ClassLoader的子类。前者负责装载JRE扩展目录ext中的JAR类包;后者负责装载Classpath路径下的类包。
这三个类装载器之间存在父子层级关系,即根装载器是ExtClassLoader的父装载器,ExtClassLoader是AppClassLoader的父装载器。默认情况下,使用AppClassLoader装载应用程序的类。
这里对于ClassLoader类的方法不做过多讲解,有兴趣的人可以查一下手册。
1.2.3 Java反射机制
Class反射对象描述类语义结构,可以从Class对象中获取构造函数、成员变量、方法类等类元素的反射对象,并以编程的方式通过这些反射对象对目标类对象进行操作。这些反射对象类在java.reflect包中定义,下面是最主要的三个反射类:
Constructor:类的构造函数反射类,通过Class#getConstructors()方法可以获得类的所有构造函数反射对象数组。Constructor的一个主要方法是newInstance(Object[] initargs),通过该方法可以创建一个对象类的实例,相当于new关键字。
Method:类方法的反射类,通过Class#getDeclaredMethods()方法可以获取类的所有方法反射类对象数组Method[]。可以通过getDeclaredMethod(String name,Class… parameterTypes)获取特定签名的方法,name为方法名;Class…为方法入参类型列表。Method最主要的方法是invoke(Object obj,Object[] args),obj表示操作的目标对象;args为方法入参。
Field:类的成员变量的反射类,通过Class#getDeclaredField()方法可以获取类的成员变量反射对象数组,通过Class#getDeclaredField(String name)则可获取某个特定名称的成员变量反射对象。Field类中最主要的方法是set(Object obj,Object value),obj表示操作的目标对象,通过value为目标对象的成员变量设置值。
总之,Java的反射体系保证了可以通过程序化的方式访问目标类中所有的元素,对于private或protected的成员变量和方法,只要JVM的安全机制允许,也可以通过反射进行调用。如下所示:
package com.reflect;
public class PrivateCar{
private String color;
protected void drive(){
System.out.println("drive private car!the color is:"+color);
}
}
public class PrivateCarReflect{
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Class clazz = loader.loadClass("com.reflect.PrivateCar");
PrivateCar pcar = (PrivateCar)clazz.newInstance();
Field colorFld = clazz.getDeclaredField("color");
//取消Java语言访问检查以访问private变量
colorFld.setAccessible(true);
colorFld.set(pcar,"红色");
Method driveMtd = clazz.getDeclaredMethod("drive",(Class[])null);
driveMtd.setAccessible(true);
driveMtd.invoke(pcar,(Object[])null);
}
这样我们就实现了对private或protected成员变量或方法的反射调用了。