Java反射机制

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。


你可能感兴趣的:(Java反射机制)