1、概念
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
换句话说,Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields赋值、或调用其methods。
2、示例
public class Car {
private String brand;
private String color;
private int maxSpeed;
public Car(){System.out.println("init 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);
}
public void setBrand(String brand) {
this.brand = brand;
}
public void setColor(String color) {
this.color = color;
}
public void setMaxSpeed(int maxSpeed) {
this.maxSpeed = maxSpeed;
}
}
测试
public class ReflectTest {
public static Car initByDefaultConst() throws Throwable{
//①通过类加载器获取Car类对象
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Class<?> clazz = loader.loadClass("com.java.reflect.Car");
//②获取类的默认构造器对象并通过它实例化Car
Constructor<?> constructor = clazz.getDeclaredConstructor((Class[])null);
Car car = (Car) constructor.newInstance();
//③通过反射方法设置属性
Method setBrand = clazz.getMethod("setBrand", String.class);
setBrand.invoke(car, "volvo XC90");
Method setColor = clazz.getMethod("setColor", String.class);
setColor.invoke(car, "黑色");
Method setMaxSpeed = clazz.getMethod("setMaxSpeed", int.class);
setMaxSpeed.invoke(car, 199);
return car;
}
public static void main(String[] args) throws Throwable {
Car car = initByDefaultConst();
car.introduce();
}
}
结果
init car!!
brand:volvo XC90;color:黑色;maxSpeed:199
3、分析
以上结果与直接通过Car的构造方法赋值效果上没有差别,构造方法的方式是直接调用,反射是间接调用。
反射常用的几个反射类包括:ClassLoader、Class、Construcor和Method,通过这些类就可以间接调用目标Class了。 在①处获取当前线程的ClassLoader,然后通过指定全限定类名"com.java.reflect.Car"装载Car类对应的反射实例。在②处通过Car的反射类对象获取Car的构造方法对象constructor ,通过构造方法对象的newInstance()方法实例化Car对象,其效果等同于new Car()。在③处通过Car的反射类对象的getMethod(String methodName,Class paramClass)获取属性的Setter()方法对象,其中第一个参数是Class的方法名,第二个参数是方法的入参对象类型。获取反射对象后,通过invoke(Object obj,Object param)方法调用目标类的方法,第一个参数是操作的目标类对象实例,第二个参数是目标方法的入参。
4、反射机制
Class反射对象描述类语义结构,可以从Class对象中获取构造函数、成员变量、方法类等类元素的反射对象,并以编程的方式通过这些反射对象对目标类对象进行操作。这些反射对象类在java.reflect包中定义。以下介绍3个主要反射类。统一使用以下代码获取的Class对象为例:
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Class<?> clazz = loader.loadClass("全限定类名");
Constructor: 类的构造器反射类
- clazz.getDeclaredConstructor();接收Class类型的可变参数,以便获取对应的有参构造器,返回Constructor对象,使用该返回对象调用newInstance();方法创建反射类的实例对象,相当于new关键字。
Method: 类方法的反射类
- clazz.getDeclaredMethods();获取所有在类中声明的方法,包括private和static方法,但不包括继承自父类(未重写)的方法,返回Method数组。
- clazz.getMethods();获取所有方法,包括继承自父类的方法。
- clazz.getDeclaredMethod(String name, Class<?>... parameterTypes);获取指定方法,仅限在本类中声明的方法。第一个参数:方法名,传入String类型,第二个可变参数,传入需要获取方法的参数列表的参数类类型(不是类名),如:
Method setBrand = clazz.getDeclaredMethod("setBrand", String.class);
- clazz.getMethod(String name, Class<?>... parameterTypes);获取指定方法,包括继承自父类的方法,参数与getDeclaredMethod();相同。
- Method常用方法:
① Class[] getParameterTypes(); 获取方法的入参类型数组
② Class getReturnTypes(); 获取方法的返回值类型
③ Class[] getExceptionTypes(); 获取方法的异常类型数组
④ Annotation[][] getParameterAnnotation(); 获取方法的注解信息
Field:类的成员变量的反射类
- clazz.getDeclaredFields();获取类的成员变量反射对象数组,仅限本类声明的成员变量;
- clazz.getDeclaredField(String name);获取类的某个特定名称的成员变量反射对象,仅限本类声明的成员变量;
- clazz.getFields();获取类的成员变量反射对象数组,包括继承自父类的成员变量;
- clazz.getField(String name);获取类的某个特定名称的成员变量反射对象,包括继承自父类的成员变量。
Field常用方法:
set(Object obj, Object value);参数obj表示操作的目标对象,即获取的Field对象,参数value 表示要给这个Field赋的值
其他
通过Class还可以获取内部类信息,
Java还提供了Package、AnnotateElement反射类。使用频率较低,自行补充。
5、类加载器ClassLoader
1、工作机制
类加载器就是寻找类的字节码文件并构造出类在JVM内部表示对象的组件。在Java中,类加载器把一个类装入JVM中需要经过以下几个步骤:
- (1)装载:查找和导入Class文件
- (2)链接:执行校验、准备和解析步骤,其中解析步骤是可以选择的
1 校验:检查载入Class文件数据的正确性
2 准备:给类的静态变量分配存储空间
3 解析:将符号引用转为直接引用 - (3)初始化:对类的静态变量、静态代码块执行初始化操作
类装载工作由ClassLoader及其子类负责,ClassLoader是一个重要的Java运行时系统组件,它负责运行时查找和装入Class字节码文件,JVM在运行时会产生三个ClassLoader:根装载器、ExtClassLoader(扩展类装载器)和AppClassLoader(应用类装载器)。其中根装载器不是ClassLoader的子类,它使用C++编写,因而在Java中看不到它,它负责装载rt.jar、charsets.jar等JRE的核心类库。ExtClassLoader和AppClassLoader都是ClassLoader的子类。
这3个类装载器之间存在父子层级关系,即根装载器是ExtClassLoader的父装载器,ExtClassLoader是AppClassLoader的父装载器。在默认情况下,使用AppClassLoader装载应用程序的类。
2、JVM全盘负责委托机制
- 全盘负责:当一个ClassLoader装载一个类时,除非显示地使用另一个ClassLoader,该类所依赖及引用的类有这个ClassLoader负责加载。
- 委托机制:先委托父装载器寻找目标类,只有在找不到的情况下才从自己的类路径中查找并载入目标类。
3、ClassLoader的重要方法
Java中ClassLoader是一个抽象类,位于java.lang包中。
除了JVM默认的3个ClassLoader外,用户可以编写自己的第三方类装载器,以实现一些特殊的需求。
- Class loadClass(String name): name参数指定类装载器需要装载类的名字,必须为全限定类名。 该方法有一个重载的方法,loadClass(String name,boolean resolve),resolve参数告诉类装载器是否需要解析该类。 在初始化类之前,应该考虑进行类解析的工作,但并不是所有的类都需要解析。如JVM只需要知道该类是否存在或找出该类的超类,那么就不需要进行解析。
- Class defineClass(String name,byte[] b,int off,int len):把类文件的字节数组转换成JVM内部的java.lang.Class对象。字节数组可以从本地文件系统、远程网络获取,参数name为字节数组对应的全限定类名。
- Class findSystemClass(String name): 从本地文件系统载入Class文件。如果本地文件系统不存在该Class文件,则抛出ClassNotFoundException异常。该方法是JVM默认使用的装载机制。
- Class findLoadedClass(String name): 通过调用该方法来查看ClassLoader是否已载入某个类。如果已载入,那么返回该类的Class对象;否则返回null。如果强行装载已存在的类,那么将会抛出链接错误。
- ClassLoader getParent(): 获取类装载器的父装载器。除根装载器外,所有的类装载器都有且仅有一个父装载器。ExtClassLoader的父装载器是根装载器,因为根装载器是C++语言写的,所以无法获得,返回null。
4、说明
类文件被装载并解析后,在JVM内将拥有一个对应的java.lang.Class类描述对象,该类的实例都拥有指向这个类描述对象的引用,而类描述对象又拥有指向关联ClassLoader的引用,如下图:
ClassLoader.png
每个类在JVM中都拥有一个对应的java.lang.Class对象,它提供了类结构信息的描述。数组、枚举、注解及基本类型(int、double等),甚至void都有对应的Class对象。Class没有public的构造方法。Class对象是在装载类时由JVM通过类装载器中的defineClass()方法自动构造的。
参考自《精通Spring4.x企业应用开发实战》
网友评论