1 类加载
在使用反射的时候,或许我们经常会遇到类初始化、类加载这些字眼,如果没搞明白它们是什么,我们是不能很好的在恰当的时机使用反射的。所以我们要先搞懂什么是类、类加载、类初始化。具体可以查看另一篇文章 JVM 字节码文件与类加载。
2 反射机制
了解了类加载的机制,我们就可以开始了解反射。
2.1 反射机制的定义
java反射是java被视为动态(或准动态)语言的一个关键性质。反射机制允许程序在运行时(不是编译时)透过 Reflection APIs
取得任何一个已知名称的class内部信息,包括 modifiers
(权限修饰符如 public
、static
等)、superclass
(例如 Object
)、实现的 interface
s(如 Cloneable
)、fields
(属性)、methods
(方法),并可以在运行时改变 field
和使用 methods
。
根据反射的定义,反射最主要的关键在于两点:动态、运行时,前提是获取已知名称的class。
2.2 为什么要反射?
为什么要使用反射?因为我们需要使用的类没有被加载过,或者不能访问,而我们需要访问到类的信息。
结合上面的类加载机制和反射的定义,具体说明是:
我们需要访问的类(比如 Test
)还没有被执行到,为了能够访问到它的信息,所以通过反射的方式将 Test
类的 Test.class
字节码文件提前的在类加载器加载到JVM中,生成 Test
类对应的Class对象,通过访问这个Class对象我就能访问到 Test
的具体类信息了。
3 使用反射
在实际开发中,使用反射这项技术无非就是访问类的方法、成员等,在使用操作步骤上大概可以总结为以下几点:
-
获取Class
-
通过Class获取obj
-
通过obj反射获取对象的成员、方法等
3.1 获取Class
以下是三种获取Class的方式:
// ClassLoader将类装载入内存,但不进行类的初始化
// 即类完成了加载阶段被加载进JVM内存创建了Class
Class clazz = Test.class;
// ClassLoader将类装载入内存,并进行类的初始化
// 即类完成了加载阶段并加载进JVM内存创建了Class,并且完成了类初始化阶段
Class clazz = Class.forName("com.example.Test");
// 返回类对象运行时真正所指的对象、所属类型的Class对象
Class clazz = new Test().getClass();
这三种加载方式有什么区别?在说明之前,提供一个测试用例用来验证三种方式:
public class Test {
static {
System.out.println("static block initialized....");
}
{
System.out.println("non static block initialized...");
}
public Test() {
System.out.println("constructor initialized...");
}
}
3.1.1 xxx.class
Class<Test> clazz = Test.class;
System.out.println(clazz);
运行结果:
class com.example.Test
可以发现,使用字面量 Test.class
可以获取到Class对象的类型信息,但没有执行静态代码块相关的代码。
对应到类的生命周期,说明它已经完成了类加载阶段创建了Class对象,所以才能访问到具体类型。而没有执行静态代码块相关的代码,说明类没有完成初始化阶段。
使用这种方式获取Class有它的局限性:
必须要能访问到具体类型的Class调用 xxx.class
,如果想要反射一些第三方库或源码内部的类是做不到的。
3.1.2 Class.forName()
我们看下 Class.forName()
的源码:
@CallerSensitive
public static Class<?> forName(String className)
throws ClassNotFoundException {
return forName(className, true, VMStack.getCallingClassLoader());
}
/**
* @Param initialize if {@code true} the class will be initialized.
*/
@CallerSensitive
public static Class<?> forName(String name, boolean initialize,
ClassLoader loader)
throws ClassNotFoundException
{
if (loader == null) {
loader = BootClassLoader.getInstance();
}
Class<?> result;
try {
// native获取Class
result = classForName(name, initialize, loader);
} catch (ClassNotFoundException e) {
Throwable cause = e.getCause();
if (cause instanceof LinkageError) {
throw (LinkageError) cause;
}
throw e;
}
return result;
}
@FastNative
static native Class<?> classForName(String className, boolean shouldInitialize,
ClassLoader classLoader) throws ClassNotFoundException;
我们调用 Class.forName()
是一个重载方法,其中有一个参数 initialized
,这个参数代表是否对加载的类进行初始化,设置为true时会类进行初始化,代表会执行类中的静态代码块以及对静态变量的赋值等操作。我们验证一下。
try {
Class<?> clazz = Class.forName("com.example.Test");
System.out.println(clazz);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
运行结果:
static block initialized....
class com.example.Test
可以发现,类的静态代码块被执行了,但是构造函数没有被调用,但也说明完成了类加载和初始化阶段。
3.1.3 obj.getClass()
Test test = new Test();
System.out.println(test.getClass());
运行结果:
static block initialized....
non static block initialized...
constructor initialized...
class com.example.Test
可以发现,类的静态代码块、非静态代码块、构造函数都已经被调用,类已经完成加载阶段、初始化阶段,可以直接使用。
3.2 通过Class获取obj
同样的还是分成两种方式获取。
3.2.1 xxx.class获取obj
Class<Test> clazz = Test.class;
Object obj = clazz.newInstance();
或
Constructor<?> constructor = clazz.getDeclaredConstructor(); // 可传入构造参数类型
Object obj = constructor.newInstance(); // 可传入构造函数参数
使用 clazz.newInstance()
有局限性:
需要提供无参构造方法并且是公开可访问的。
3.2.2 Class.forName()获取obj
Class<?> clazz = Class.forName("com.example.Test");
Constructor<?> constructor = clazz.getDeclaredConstructor(); // 可传入构造参数类型
return constructor.newInstance(); // 可传入构造函数参数
3.3 成员、方法、构造访问方式和限制
3.3.1 Field成员访问方式和限制
方法 | 本class | super class |
---|---|---|
getField | public | public |
getDeclaredField | public protected private | no |
getFields | public | public |
getDeclaredFields | public protected private | no |
getField
只能获取对象中public的修饰符的属性,并且能获取父类Class的public属性;getDeclaredField
能获取对象中各种修饰符的属性,但无法获取父类的任何属性。
获取父类属性,可以通过class对象提供的 getSuperClass
方法获取到父类对象,然后再通过 getDeclaredField
获取属性:
Class clazz = Class.forName("com.example.Reflect");
Class superClazz = clazz.getSuperClass();
Field field = sueprclazz.getDeclaredField("field");
3.3.2 Method方法访问和限制
方法 | 本class | super class |
---|---|---|
getMethod | public | public |
getDeclaredMethod | public protected private | no |
getMethods | public | public |
getDeclaredMethods | public protected private | no |
3.3.3 构造方法访问和限制
方法 | 本class | super class |
---|---|---|
getConstructor | public | no |
getDeclaredConstructor | public protected private | no |
getConstructors | public | no |
getDeclaredConstructors | public protected private | no |
需要注意的是,构造方法无法调用父类的任何构造方法。
3.4 通过obj反射获取对象的成员、方法等
下面的代码中会出现 setAccessible(true)
,在安全管理中会使用checkPermission方法来检查权限,而 setAccessible(true)
并不是将方法的权限改为public,而是取消java的权限控制检查。
3.4.1 xxx.class获取对象的成员、方法等
Class<Test> clazz = Test.class;
Object obj = clazz.newInstance();
// 访问非静态成员变量
Field instanceField = clazz.getDeclaredField("instanceField");
// instanceField.setAccessible(true); // 如果成员是私有的,在访问前修改为可访问
instanceField.get(obj);
// 访问静态成员变量
Field staticField = clazz.getDeclaredField("staticField");
// staticField.setAccessible(true); // 如果成员是私有的,在访问前修改为可访问
staticField.get(obj);
// 访问非静态方法
Method invokeMethod = clazz.getDeclaredMethod("invokeMethod");
// invokeMethod.setAccessible(true); // 如果方法是私有的,在访问前修改为可访问
invokeMethod.invoke(obj);
// 访问有参数的非静态成员方法
Method invokeMethodWithParam = clazz.getDeclaredMethod("invokeMethodWithParam", String.class);
// invokeMethodWithParam.setAccessible(true); // 如果方法是私有的,在访问前修改为可访问
invokeMethodWithParam.invoke(obj, "my param");
// 访问静态方法
Method invokeStaticMethod = clazz.getDeclaredMethod("invokeStaticMethod");
// invokeStaticMethod.setAccessible(true); // 如果方法是私有的,在访问前修改为可访问
invokeStaticMethod.invoke(obj);
// 访问属性中的修饰符
// getModifiers()返回的是一个int类型的返回值,代表类、成员变量、方法的修饰符
String fieldModifier = Modifier.toString(instanceField.getModifiers());
3.4.2 Class.forName()获取对象的成员、方法等
Class<?> clazz = Class.forName("com.example.Test");
Constructor<?> constructor = clazz.getDeclaredConstructor();
Object obj = constructor.newInstance();
// 访问非静态成员变量
Field instanceField = clazz.getDeclaredField("instanceField");
// instanceField.setAccessible(true); // 如果成员是私有的,在访问前修改为可访问
instanceField.get(obj);
// 访问静态成员变量
Field staticField = clazz.getDeclaredField("staticField");
// staticField.setAccessible(true); // 如果成员是私有的,在访问前修改为可访问
staticField.get(obj);
// 访问非静态方法
Method invokeMethod = clazz.getDeclaredMethod("invokeMethod");
// invokeMethod.setAccessible(true); // 如果方法是私有的,在访问前修改为可访问
invokeMethod.invoke(obj);
// 访问有参数的非静态成员方法
Method invokeMethodWithParam = clazz.getDeclaredMethod("invokeMethodWithParam", String.class);
// invokeMethodWithParam.setAccessible(true); // 如果方法是私有的,在访问前修改为可访问
invokeMethodWithParam.invoke(obj, "my param");
// 访问静态方法
Method invokeStaticMethod = clazz.getDeclaredMethod("invokeStaticMethod");
// invokeStaticMethod.setAccessible(true); // 如果方法是私有的,在访问前修改为可访问
invokeStaticMethod.invoke(obj);
// 访问属性中的修饰符
// getModifiers()返回的是一个int类型的返回值,代表类、成员变量、方法的修饰符
String fieldModifier = Modifier.toString(instanceField.getModifiers());