转自:
http://blog.csdn.net/elliotann/archive/2009/03/04/3957589.aspx
Java语言允许通过程序化的方式间接的对Class操作,Class文件由类加载器加载后,在JVM中将形成一份描述Class的对象,通过该对象可以获知Class的结构信息,如构造函数,属性,方法等。并分别通过Java实例对这些信息进行描述,Java允许用户通过这个Class相关的描述对象来间接调用类的功能。这就是为程序化方式操作Class文件提供了一个途径。
我们通过一个简单的例子来看看Java的反射机制。
创建一个类Car里面有三个属性,一个默认构造,一个带参构造,一个方法。
package car;
public class Car
{
//汽车价钱
private Double price;
//汽车的颜色
private String color;
//汽车名字
private String name;
//默认构造方法
public Car(){}
//参数化构造方法
Car(String name,Double price,String color)
{
this.name=name;
this.price=price;
this.color=color;
}
public void SetPrice(Double price)
{
this.price=price;
}
public Double getPrice()
{
return this.price;
}
public void SetColor(String color)
{
this.color=color;
}
public String getColor()
{
return this.color;
}
public void SetName(String name)
{
this.name=name;
}
public String getName()
{
return this.name;
}
//汽车行为
public void Run()
{
System.out.println(name+"汽车的颜色为"+color+"她的价钱是"+price);
}
}
下面我们通过反射机制间接的操作目标类
package car;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Reflect
{
public Reflect(){}
public Car ReflectCar()throws Throwable
{
Car car=null;
//通过类加载器获得Car类对象
ClassLoader carLoader=Thread.currentThread().getContextClassLoader();
Class clazz=carLoader.loadClass("car.Car");
//获得类的默认构造器对象并通过它实例化Car
Constructor cons=clazz.getDeclaredConstructor((Class[])null);
car=(Car)cons.newInstance();
//通过方法名与参数类型获得对象中的方法
Method SetName=clazz.getMethod("SetName",String.class);
//反射调用实例中的方法
SetName.invoke(car,"宝马");
Method SetColor=clazz.getMethod("SetColor",String.class);
SetColor.invoke(car,"红色");
Method SetPrice=clazz.getMethod("SetPrice",Double.class);
SetPrice.invoke(car,new Double(100));
return car;
}
}
package car;
public class CarMain
{
public CarMain(){}
public static void main(String arges[])
{
try
{
Reflect ref=new Reflect();
Car car=ref.ReflectCar();
System.out.println(car.getName());
System.out.println(car.getPrice());
System.out.println(car.getColor());
}
catch(Throwable x)
{
x.printStackTrace();
}
}
}
这说明我们完全可以通过编程的方式操作Class所提供的各项功能,这和直接通过构造函数和方法调用类功能的效果是一致的。
我们用到了几个反射类,分别是ClassLoader,Class,Constructor和Medthod,通过这些反射类的协作就可以间接调用目标类Class的各项功能了。
我们获取当前线程的ClassLoader,然后通过指定“car.Car”的全限定类名装载该Class的类反射实例。然后我们通过clazz获取目标类的构造函数对象cons,通过构造函数对象的newInstrance()方法实例化Car对象,效果等同于new Car();然后我们又通过clazz的getMethod(String mehtodName,Class paramClass) 获取属性的Setter方法对象,第一个参数是目标实例的方法名,第二个参数是方法入参的对象类型。获取方法反射对象后,即可通过invoke(Object obj,Object param)方法调用目标类的方法,第一个参数十操作的目标类对象实例,第二个参数是目标方法入参。
现在我们想:如果方法初始化信息通过配置文件提供,我们就可以通过Java语言的反射功能编写通过的代码对类似于Car的类进行实例化操作了。
类加载器ClassLoader
类加载器就是寻找类或接口字节码文件进行解析并构造JVM内部对象表示的组件,在Java中,类加载器把一个类装入JVM中,要经过以下几个步骤:
1.装载,查找和导入Class文件。
2.连接,执行校验,准备和解析步骤,其中解析步骤是可以选择的。
校验:检查载入Class文件数据的正确性。
准备:给类的静态变量分配内存空间。
解析:将符号引用转换成直接引用。
3.初始化:对类的静态变量,静态代码块执行初始化工作。
类装载工作由ClassLoader及其子类负责,ClassLoader是一个重要的Java运行时系统组件,他负责运行时查找和装入Class字节文件。JVM在运行时会生成三个ClassLoader,根装载器,ExtClassLoader(扩展类装载器)和AppClassLoader(系统类装载器)其中根装载器我们在Java中看不到它,它负责装载JRE的核心类库,ExtClassLoader负责装载JRE扩展目录ext中的JAR类包,AppClassLoader负责装载Classpath路径下的类包。这三个类装载器之间存在父子层级关系,根装载器是ExtClassLoader的父装载器,ExtClassLoader是AppClassLoader的父装载器。默认情况下使用AppClassLoader装载应用程序的类。
JVM装载类时使用“全盘负责委托机制”,“全盘负责”是指当一个ClassLoader装载一个类时,除非显示地使用另一个ClassLoader,该类所依赖及引用的类也由这个ClassLoader载入,“委托机制”是指委托父类装载器寻找目标类,只有在找不到的情况下才从自己的类路径中查找并装载目标类,这一点是从安全角度考虑的。试想如果有人编写一个恶意的java.lang.String并装载到JVM中将会引起什么可怕的后果,但是由于有了全盘负责制,java.lang.String永远都是由根装载器来装载的。
ClassLoader重要方法:
在Java中,ClassLoader是一个抽象类,位于Java.lang包中,下面对该类的一些重要接口方法进行介绍:
Class loaderClass(String name): name参数指定类装载器需要装载类的名字,必须使用全限定类型(包名+类名)该方法有一个重载方法loaderClass(String name,boolean resolve)其中resolve参数告诉类装载器是否需要解析该类,在初始化之前,应考虑进行类解析工作,但并不是所有的类都需要解析,如果JVM只需要知道该类是否存在或找出该类的超类,那么就不需要进行解析。
Class defineClass(String name,byte[] bt,int off,int len): 将类文件的字节数组转换成JVM内部的java.lang.Class对象,字节数组可以从本地文件系统,远程网络获取,name为字节数组对象的全限定名。
Class findSystemClass(String name):从本地文件系统载入Class文件,如果本地文件系统不存在该Class文件,将抛出ClassNotFoundException异常。该方法是JVM默认的装载机制。
Class findLoadedClass(String name)调用该方法来查看ClassLoader是否已装入某个类,如果已装入那么返回java.lang.Class对象,否则返回null,如果强行装载已存在的类,将会抛出链接错误。
ClassLoader getParent()获取类装载器的父装载器,除根装载器之外,所有的类装载器都有仅有一个父类装载器。除了JVM默认的三个ClassLoader以外,第三方可以编写自己的类装载器,以显现一些特殊的需求,类文件被装载并解析后,在JVM将拥有一个对应的java.lang.Class类描述对象。该类的实例都拥有指向这个类描述对象的引用,而类描述对象又拥有指向关联ClassLoader的引用。
每一个类在JVM中都拥有一个对应的java.lang.Class对象,他提供了类结构信息的描述,数组,枚举,以及基本的Java类型,甚至void都拥有对应Class对象,Class没有public的构造方法,Class对象是在装载类时由JVM通过调用类装载器中的defineClass()方法自动创造的。
Java的反射机制:通过以上的介绍我们可以看到Class反射对象描述类语义结构,可以Class对象中获取构造函数,成员变量,方法类等类元素的反射对象,并以编程的方式通过这些反射对象对目标类对象进行操作。这些反射类对象在java.reflect包中定义。下面介绍主要的三个反射类:
Constructor:类构造函数反射类,通过Class.getConstructors()方法可以获得类的所有构造函数反射对象数组。在JDK5.0以上的版本我们可以使用getConstructors(Class[] paramerTypes)获取拥有特定入参的构造函数反射对象。Constructor的一个主要方法是newInstance(Object[] initargs)通过该方法我们可以创建一个对象类的实例,相当于使用new关键字。
Method:类方法的反射类,通过Class.getDeclaredMethods()方法可以获取类的所有方法放射对象数组Method[],在JDK5.0以上的版本我们可以使用getDeclaredMethods(String name,Class parameterTypes)获取特定签名方法,name为方法名,Class为方法入参列表,
Method最主要的方法是invoke(Object obj,Object[] arges)obj为操作目标对象,arges为方法入参。
Method 还有很多用于获取类方法更多信息的方法。
Class getRetunType():获取方法的返回值类型。
Class getParameterTypes():获取方法的入参类型数组。等等...更多方法参见API。
Field:类的成员变量的反射类,通过Class#getDeclaredFields()方法可以获取类的成员变量反射对象数组,通过getDeclaredFields(String name)则可以获取某个特定名称的成员变量反射对象。Field类主要的方法是:set(Object obj, Object value) obj表示操作的目
标对象,通过value为目标对象的成员变量设置值,如果成员变量为基础类型,用户可以使用Field类中提供的带类型名的值设置方法。如
setBoolean(Object obj,Boolean value).
在JDK5.0以上还为包及注解提供了AnnotatedElement反射类,总之:Java的反射体系保证了可以通过程序化的方式访问目标类中的所有元素,对于private或protected的成员变量和方法,只要是JVM的安全机制允许,也可以通过反射机制进行调用。
我们只需要用setAccessible(Boolean bool)方法进行设置就可以取消访问修饰符的限制。
true表示取消Java语言访问限制,flase表示不取消访问限制。如果在访问private,protected成员变量和方法时没有设置是否取消限制则会抛出IllegalAccessException.如果JVM的安全管理器设置相应的安全机制,调用该方法将抛出SecurityException。