一、概念
Java反射是Java被视为动态(或准动态)语言的一个关键性质。指在程序运行状态中,可以构造任意一个类,可以调用获取任意一个类的方法、变量、构造器等。这种动态获取信息以及动态调用对象方法的功能称为 java 语言的反射机制。反射被视为动态语言的关键,框架设计的灵魂。《百度百科》
以上的解释不理解没关系,下面来结合图分析程序执行的流程:
首先javac编译源代码之后,通过类加载器进内存生成字节码文件对象。一个类的字节码文件大概可以拆分为:包信息、成员变量、构造方法、成员方法等等,个个组成部分映射成一个个对象。图中就是类的正常加载过程,反射的原理就在于如何去操作这些对象
Java代码在计算机中大致分为三个阶段:Source、Class、Runtime
Person person = new Person(); 执行!
JVM 启动后,javac 会把 Person.java 编译成一个 Person.class 文件,通过 ClassLoader 加载进内存的方法区中,类对象是用来描述字节码文件对象的,每个类只有一个class对象且只会加载一次,作为方法区类的数据结构的接口。然后创建Person的类实例到堆(Heap)内存中。注意:Jvm 在创建对象前,会先检查类是否加载,寻找类对应的class对象,加载完成之后,会给对象分配内存。
了解大致过程之后,发现Person是new出来写死给Jvm执行的,假设 当我们的程序在运行时,需要动态的加载一些类,这些类可能运行之前不确定是否需要加载到Jvm中,而是在运行时根据需要才去加载 。这就需要用到反射机制。
举个栗子:目前主流开发所用的 spring 框架,在配置各种各样的 bean 时,是以配置文件的形式配置的,spring 容器会根据配置的信息去动态加载类。 Spring的工作原理就是让一个对象的创建不用new就可以自动的生产,在运行时与xml Spring的配置文件来动态的创建对象和调用对象,而不需要通过代码来关联。
相关类
类名 | 释义 |
---|---|
Class | 类对象,在运行时java类数据结构的接口 |
Field | 类属性(成员变量) |
Method | 类方法 |
Constructor | 类构造方法 |
二、Class类
Class对象的三种获取方式
public class Reflection {
/**
* 1.Source阶段,通过将class字节码文件加载进内存,返回 class 对象
* 2.class对象阶段,通过类名的属性 class 来获取
* 3.Runtime阶段,通过Object.getClass()方法来获取
*
* @param args anything
* @throws ClassNotFoundException
*/
public static void main(String[] args) throws ClassNotFoundException {
// 1.多用于配置文件,将类名配置在文件中,读取文件加载类
Class aClass = Class.forName("com.angst.student.Reflection");
// 2\. 一般用于参数传递,当做参数使用
Class aClass1 = Reflection.class;
// 3\. 多用于对象获取字节码
Class aClass2 = new Reflection().getClass();
}
}
Class
方法 | 释义 |
---|---|
getClassLoader() | 获得类的加载器 |
getClasses() | 返回一个数组,数组中包含该类中所有公共类和接口类的对象 |
getDeclaredClasses() | 返回一个数组,数组中包含该类中所有类和接口类的对象 |
getName() | 获得类的完整路径名字 |
getPackage() | 获得类的包 |
getSimpleName() | 获得类的名字 |
getSuperclass() | 获得当前类继承的父类的名字 |
getInterfaces() | 获得当前类实现的类或是接口 |
asSubclass() | 把传递的类的对象转换成代表其子类的对象 |
newInstance() | 创建一个空参的对象(无参构造使用) |
/**
* 通过类完整路径获取 Class 对象
* @param packagePath 包路径
* @return class
* @throws ClassNotFoundException
*/
public static Class> getClass(String packagePath) throws ClassNotFoundException {
return Class.forName(packagePath);
}
/**
* 通过包名+类名返回 class 对象
* @param packagePath 包路径
* @param obj 类名
* @return class
* @throws ClassNotFoundException
*/
public static Class> getClass(String packagePath, String obj) throws ClassNotFoundException {
if (obj.indexOf(".") == 0) { obj = packagePath + obj; }
return getClass(obj);
}
Constructor
方法 | 释义 |
---|---|
getConstructor() | 获得该类中与参数类型匹配的public构造方法 |
getConstructors() | 获得该类的所有public构造方法 |
getDeclaredConstructor() | 获得该类中与参数类型匹配的构造方法 |
getDeclaredConstructors() | 获得该类所有构造方法 |
newInstance() | 根据传递的参数创建类的对象(new Object) |
/**
* 获取构造器
* @param clazz 类对象
* @param parameterTypes 参数类型
* @return Constructor
* @throws NoSuchMethodException
*/
public static Constructor getConstructor(Class clazz, Class... parameterTypes) throws NoSuchMethodException {
return clazz.getDeclaredConstructor(parameterTypes);
}
Field
方法 | 释义 |
---|---|
getField() | 获得某个public属性对象 |
getFields() | 获得所有public属性对象 |
getDeclaredField() | 获得某个属性对象(包括私有) |
getDeclaredFields() | 获得所有属性对象(包括私有) |
setAccessible() | 设置为true,忽略访问权限修复符,操作 private 类属性(暴力反射不安全) |
/**
* 设置某个字段的值
* @param clazz 类对象
* @param fieldName 属性字段名称
* @param value 设置值
* @throws Exception
*/
public static void setField(Class
Method
方法 | 释义 |
---|---|
getMethod() | 获得该类某个 Public 方法 |
getMethods() | 获得该类所有 Public 方法 |
getDeclaredMethod() | 获得该类某个方法(包括私有) |
getDeclaredMethods() | 获得该类所有方法(包括私有) |
invoke() | 根据传递的object对象及参数调用该对象的方法 |
* 获取所有方法列表
* @param clazz 类对象
* @return
*/
public static List getMethods(Class clazz) {
List list = new ArrayList<>();
for (Method declaredMethod : clazz.getDeclaredMethods()) {
declaredMethod.setAccessible(true);
list.add(declaredMethod);
}
return list;
}
/**
* 指定方法方法调用
* @param clazz 类对象
* @param methodName 调用的方法名称
* @param value 方法入参
* @param type 入参类型
* @throws Exception
*/
public static void invokeMethod(Class clazz, String methodName, String value, Class... type) throws Exception {
Object obj = clazz.newInstance();
Method method = clazz.getDeclaredMethod(methodName, type);
method.invoke(obj, value);
}
Other
方法 | 释义 |
---|---|
isAnnotation() | 如果是注解类型则返回true |
isAnnotationPresent() | 如果是指定类型注解类型则返回true |
isAnonymousClass() | 如果是匿名类则返回true |
isArray() | 如果是一个数组类则返回true |
isEnum() | 如果是枚举类则返回true |
isInstance() | 如果obj是该类的实例则返回true |
isInterface() | 如果是接口类则返回true |
isLocalClass() | 如果是局部类则返回true |
isMemberClass() | 如果是内部类则返回true |
案例
从上面的一些方法示例来看,反射的操作过程比较麻烦,还不如使用new对象来的简单直接。更别说反射是框架设计的灵魂这一说法,并没有体现出本身的任何价值。下面会通过简单的案例演示,可以初步了解到反射的魅力所在。
需求:在不改变该类任何代码前提下,任意创建类对象,并且执行其中任意方法。通过配置文件来实现反射机制。
步骤:
1.将需要创建的对象的类路径和需要执行的方法定义在配置文件中
2.在程序中加载读取配置文件,并通过反射机制将类加载进内存
3.创建对象,执行指定方法
配置文件定义
#pro.properties
className=org.example.basic.Person
methodName=eat
配置类
package org.example.basic;
public class Person {
public void eat() {
System.out.println("anything...");
}
}
反射类
package org.example.basic;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Properties;
public class Reflection {
private static String className;
private static String methodName;
/**
* 加载配置文件
*/
static {
Properties properties = new Properties();
try {
properties.load(Reflection.class.getClassLoader().getResourceAsStream("pro.properties"));
className = properties.getProperty("className");
methodName = properties.getProperty("methodName");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 加载类进内存,创建对象,执行指定方法
* @throws Exception
*/
public static void invokeMethod() throws Exception {
Class> aClass = Class.forName(className); // 类加载进内存,返回类对象
Object obj = aClass.newInstance(); // 创建对象
Method method = aClass.getMethod(methodName); // 获取指定方法
method.invoke(obj); // 执行方法调用
}
public static void main(String[] args) throws Exception {
Reflection.invokeMethod(); // 方法调用输出:anything...
}
}
Epilogue
以上就是通过动态加载配置文件的类和方法,在不改变该类任何代码前提下,可以实现任意创建类对象,并且执行其中任意方法。可以达到一个解耦的目的,提升程序的扩展性。
反思?
通过以上的反射相关类及方法的介绍和案例的使用,已经初步了解的Java反射机制的强大之处,那么如何结合反射应用到实际工作中或者编写一个简单“测试框架
”?