Java反射机制介绍
- 文档概述
Java反射是Java被视为动态(或准动态)语言的一个关键性质,Java反射机制容许程序在运行时加载、探知、使用编译期间完全未知的classes。换言之,Java可以加载一个运行时才得知名称的class,获得其完整结构。
在工作过程中,常会听到反射这个概念,在平常的代码开发中也有看到和使用到,只是对它没有一个较深入的了解,这次重新理解学习了一下反射机制,结合公司产品中的Hotweb框架,加深理解,本文为学习过程中的总结。
- 术语解释
静态加载类(编译时加载类):大多数情况下都是使用这种形式。比如我们定义了一个类A,实例化采用A a = new A()接着就可以通过a对象调用相关方法或属性,这就是静态加载类的过程。
动态加载类(运行时加载类):所谓动态加载类,只需要通过Class c = Class.forName("类的全名")即可获得类类型,然后通过调用A a = c.newInstance()方法即可实例化这个类。
本质的区别在于静态加载的类的源程序在编译时期加载(必须存在),而动态加载的类在编译时期可以缺席(源程序不必存在)。
反射机制:在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
- 反射功能
反射机制主要提供了以下功能:
- 在运行时判断任意一个对象所属的类;
- 在运行时构造任意一个类的对象;
- 在运行时判断任意一个类所具有的成员变量和方法;
- 在运行时调用任意一个对象的方法;
- 生成动态代理。
在JDK中,主要通过以下类实现java反射机制,这些类都位于java.lang.reflect包中。
- Class:代表一个类
- Filed:代表类的成员变量或者说成员属性
- Method:代表类的方法
- Constructor:代表类的构造方法
- Array:提供了动态创建数组,以及访问数组元素的静态方法
首先,需要定义测试使用的类
-
- 获取类的Class对象
Class 类的实例,表示正在运行的 Java 应用程序中的类和接口。获取类的Class对象有多种方式:
方式 | 方法 |
调用getClass | Boolean var1 = true; Class<?> classType2 = var1.getClass(); System.out.println(classType2); 输出:class java.lang.Boolean |
运用.class 语法 | Class<?> classType4 = Boolean.class; System.out.println(classType4); 输出:class java.lang.Boolean |
运用static method Class.forName() | Class<?> classType5 = Class.forName("java.lang.Boolean"); System.out.println(classType5); 输出:class java.lang.Boolean |
-
- 获取类的Fields
可以通过反射机制得到某个类的某个属性,然后改变对应于这个类的某个实例的该属性值。JAVA 的Class<T>类提供了几个方法获取类的属性。
方法 | 说明 |
public FieldgetField(String name) | 返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段 |
public Field[] getFields() | 返回一个包含某些 Field 对象的数组,这些对象反映此 Class 对象所表示的类或接口的所有可访问公共字段 |
public FieldgetDeclaredField(Stringname) | 返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段 |
public Field[] getDeclaredFields() | 返回 Field 对象的一个数组,这些对象反映此 Class 对象所表示的类或接口所声明的所有字段 |
使用样例如下图所示:
可见getFields和getDeclaredFields区别:
getFields返回的是申明为public的属性,包括父类中定义,
getDeclaredFields返回的是指定类定义的所有定义的属性,不包括父类的。
-
- 获取类的Method
通过反射机制得到某个类的某个方法,然后调用对应于这个类的某个实例的该方法
Class<T>类提供了几个方法获取类的方法。
方法 | 说明 |
public MethodgetMethod(String name,Class<?>... parameterTypes) | 返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法 |
public Method[] getMethods() | 返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共 member 方法 |
public MethodgetDeclaredMethod(Stringname,Class<?>... parameterTypes) | 返回一个 Method 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法 |
public Method[] getDeclaredMethods() | 返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法 |
使用样例如下图所示:
-
- 获取类的Constructor
通过反射机制得到某个类的构造器,然后调用该构造器创建该类的一个实例
Class<T>类提供了几个方法获取类的构造器。
方法 | 说明 |
public Constructor<T> getConstructor(Class<?>... parameterTypes) | 返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法 |
public Constructor<?>[] getConstructors() | 返回一个包含某些 Constructor 对象的数组,这些对象反映此 Class 对象所表示的类的所有公共构造方法 |
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) | 返回一个 Constructor 对象,该对象反映此 Class 对象所表示的类或接口的指定构造方法 |
public Constructor<?>[] getDeclaredConstructors() | 返回 Constructor 对象的一个数组,这些对象反映此 Class 对象表示的类声明的所有构造方法。它们是公共、保护、默认(包)访问和私有构造方法 |
使用样例如下图所示:
-
- 新建类的实例
通过反射机制创建新类的实例,有几种方法可以创建
方法 | 说明 |
调用类的Class对象的newInstance方法,该方法会调用对象的默认构造器,如果没有默认构造器,会调用失败 | Class<?> classType = User.class; Object inst = classType.newInstance(); System.out.println(inst); 输出: Person:Default Constructor User:Default Constructor com.aeai.reflect.test.User@1f6ba0f |
调用默认Constructor对象的newInstance方法 | Constructor<?> constructor1 = classType.getConstructor(); Object inst = constructor1.newInstance(); System.out.println(inst); 输出: Person:Default Constructor User:Default Constructor com.aeai.reflect.test.User@f47bf5 |
调用带参数Constructor对象的newInstance方法 | Constructor<?> constructor2 = classType.getDeclaredConstructor(int.class, String.class); Object inst = constructor2.newInstance(20, "小明"); System.out.println(inst); 输出: Person:Default Constructor User:Constructor with parameters com.aeai.reflect.test.User@1cd0888 |
-
- 调用类的函数
通过反射获取类Method对象,调用Field的Invoke方法调用函数。
Class<?> classType = User.class; Object inst = classType.newInstance(); //对象 Method logMethod = classType.getDeclaredMethod("Log", String.class); //方法 logMethod.invoke(inst, "test");//调用User对象的Log方法(实例inst 的logMethod方法) |
调用后结果如下图所示
上面失败是由于没有权限调用private函数,这里需要设置Accessible为true;
-
- 设置/获取类的属性值
通过反射获取类的Field对象,调用Field方法设置或获取值
Class<?> classType = User.class; Object inst = classType.newInstance(); Field intField = classType.getField("ageExtend");//User字段 intField.setInt(inst, 30); //设置User对象的ageExtend属性值 int value = intField.getInt(inst); //获取User的ageExtend属性(获取inst对象的intField) System.out.println(value); |
调用后结果如下图:
-
- 实例化对象并调用其方法
Person中有两个方法,sayHi和sayHello
通过已定义的类字符串变量,实例化该类,创建对象,然后根据传入的方法参数,调用Person中的方法
String className = "com.aeai.reflect.test.Person"; Object obj = Class.forName(className).newInstance();//实例化Person类,创建对象 Method method1 = obj.getClass().getMethod("sayHi", String.class); Method method2 = obj.getClass().getMethod("sayHello", String.class); System.out.println(method1.invoke(obj, "小李")); System.out.println(method2.invoke(obj, "小王")); |
调用后结果如下图:
不管是类名还是方法名,都可以定义在配置文件中,然后读取实例化、方法调用,通常Java web框架都是如此,数通畅联Hotweb MVC框架也有类似机制,具体参加5.4节。
- 应用实例
- ArrayList中存放对象
在泛型为Integer的ArrayList中存放一个String类型的对象。
-
- 修改数组的信息
修改数组的信息实例,如下图:
-
- 修改数组的大小
修改数组的大小实例,如下图:
-
- Hotweb框架经典样例
以数通畅联基础Hotweb MVC框架为例说明反射机制
首先,在DispatchServlet转发请求至Handler时,需要实例化handler对象。
其中,在HandlerParser类中,使用handlerId通过配置文件HandlerModule.xml读取到对应Handler并实例化。
然后,通过instantiateHandler方法,添加handler的属性,
最后,在实例化了handler对象后,通过传入名为actionType的变量参数调用handler中对应名称的方法。
在获取方法名的时候,默认是prepareDisplay
最后,通过反射得到的Method对象,调用Field的Invoke方法调用指定的函数。