最近一周,因为对spring框架原理的一些疑惑,我去学习了一下Java的反射原理,在这之中收获不小,也从中了解到了一些关于Java虚拟机运行原理。
在了解Java反射的具体使用方法之前,我觉得有必要去了解一下类的加载机制。
在Java程序运行开始的时候,JVM会通过虚拟机中的ClassLoader将Java的类加载到虚拟机中,并且,不同的类是通过不同放入ClassLoder进行加载操作的,具体如下:
Bootstrap ClassLoader 启动类加载器,最核心的类加载器,装载最核心的Java类库,如resource.jar、rt.jar、charsets.jar等等
Extension ClassLoader 扩展类加载器,加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext目录下所有的jar包
App ClassLoader 系统类加载器,负责加载应用程序下的jar包和class文件
Other ClassLoader 其他类加载器
它们四者是父子层级关系(不是继承关系),从上到下一起递减,上面的ClassLoader可通过下面的ClassLoader的getParents()获得。同时,在加载类的时候,都会看一下它上面的层次是否已经加载,若已经加载则此ClassLoader不再加载,这样可以防止被程序侵入(防止别人替换JVM已经加载的类)。
而像我们的反射机制,就是在App ClassLoader层次上进行的类似于反编译的操作。
反射机制可以打破类的封装性,让程序可以在类已经被加载之后,还可以进行对类的属性进行读取与赋值以及方法的调用等等操作。
反射的实现,最重要的一步,就是获取到Class类的对象,它有以下三种获取方式:
Class clazz = obj.getClass();
Class clazz = Object.class;
Class clazz = Class.forName(“Object”);
然后就可以根据Class的对象,获取所有的属性:
Field[] fields = clazz.getDeclaredFields();
或者获取所有的方法:
Method[] methods = clazz.getDeclaredMethods();
对属性可以操作它的属性值,也是直接进行赋值操作即可:
field.set(s,“carlos”);
同样的,对方法可以进行方法的调用:
method.invoke(s, “heaven”);
但是,当方法或者是属性是私有变量的时候,就需要加一步操作来打破它们的封装性:
field.setAccessible(true);
method.setAccessible(true);
除此之外,还可以进行类的实例化(以多参构造函数为例):
Constructor con = clazz.getConstructor(String.class,int.class,String.class);
Student s2 = con.newInstance(“aa”,2,“nj”);
System.out.println(s2);
如此,可以实现反射的基本功能。像在spring框架中使用的依赖注入,原理就是从xml中读取到类的名称,然后通过反射进行类的实例化,并且同时使用单例、共工厂等设计模式。