"talk is cheap, show me the code"。接下来将从实际应用的角度,说明如何使用反射。关于反射相关的概念,可以参考Java反射概述博文。 使用反射的基本步骤如下:
(1) 获取类型的Class对象;
(2) 基于Class对象获取类型元数据:Constructor类型、Field类型、Method类型;
(3) 基于Constructor类型、Field类型、Method类型访问成员。
获取Class对象
Class类是由class关键字修饰的一个特殊的类。Class实例可以看成是Object及其子类的元数据引用。一个Java类均对应一个Class实例。该Class实例引用可以从getClass方法获取。使用Class对象可以获取对应类型的元数据(metadata)信息,如构造器、字段、方法等。
获取Class对象的方法有很多种。这里介绍下常用的几种方法:
(1) 使用Class类的forName静态方法;
(2) 使用Ojbect根类的class静态字段;
(3) 使用类型实例的getClass()方法;
(4) 使用ClassLoader实例的loadClass方法。
在介绍获取Class对象的方法前,先预定义测试类,方便后面统一使用。
// package io.github.courage007.reflect;
@Getter
@Setter
public class Apple {
private String color;
private String size;
public Apple() {
this.color = "red";
this.size = "medium";
}
public Apple(String color, String size) {
this.color = color;
this.size = size;
}
public void changeByTime() {
this.color = "gray";
this.size = "small";
}
private void changeColor() {
this.color = "gray";
}
}
使用Class类的forName静态方法
Class类提供forName静态方法用于获取Class实例。相关源码片段如下:
public final class Class<T> implements java.io.Serializable,
GenericDeclaration,
Type,
AnnotatedElement {
// ...
@CallerSensitive
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
@CallerSensitive
public static Class<?> forName(String name, boolean initialize,
ClassLoader loader)
throws ClassNotFoundException
{
Class<?> caller = null;
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
// Reflective call to get caller class is only needed if a security manager
// is present. Avoid the overhead of making this call otherwise.
caller = Reflection.getCallerClass();
if (loader == null) {
ClassLoader ccl = ClassLoader.getClassLoader(caller);
if (ccl != null) {
sm.checkPermission(
SecurityConstants.GET_CLASSLOADER_PERMISSION);
}
}
}
return forName0(name, initialize, loader, caller);
}
// since java 9
@CallerSensitive
public static Class<?> forName(Module module, String name) {
Objects.requireNonNull(module);
Objects.requireNonNull(name);
ClassLoader cl;
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
Class<?> caller = Reflection.getCallerClass();
if (caller != null && caller.getModule() != module) {
// if caller is null, Class.forName is the last java frame on the stack.
// java.base has all permissions
sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION);
}
PrivilegedAction<ClassLoader> pa = module::getClassLoader;
cl = AccessController.doPrivileged(pa);
} else {
cl = module.getClassLoader();
}
if (cl != null) {
return cl.loadClass(module, name);
} else {
return BootLoader.loadClass(module, name);
}
}
/** Called after security check for system loader access checks have been made. */
private static native Class<?> forName0(String name, boolean initialize,
ClassLoader loader,
Class<?> caller)
throws ClassNotFoundException;
}
可以看到,forName静态方法有三个重载方法。其使用示例如下:
public void getClassRefByForName() {
try {
Class clazz1 = Class.forName("io.github.courage007.reflect.Apple");
// Returns the class of the caller of the method calling this method
// 获取调用当前方法的方法的调用者
Class<?> caller = Reflection.getCallerClass();
Class clazz = Class.forName("io.github.courage007.reflect.Apple", false, caller.getClassLoader());
} catch (ClassNotFoundException ex) {
throw new RuntimeException("get class failed");
}
// java 9新增基于Module获取Class实例,这里不再讨论,有兴趣的同学可以自行学习
}
基于类的全路径名获取 Class 对象的方式,其优势是不需要事先引入依赖,适用于运行时调用的场景。需要说明的是,由于代码里硬编码了类的全路径名,不能很好的适应类的全路径名变化场景,生产上可以这部分数据放置在文件或数据库等存储介质中。
使用Ojbect根类的静态class字段
Ojbect根类提供静态class字段,可以直接获取Class实例。示例代码如下:
public void getClassRefByStaticField() {
Class clazz = Apple.class;
}
使用这种方法,需要引入类对应的包。该方式主要应用于调用方。
使用类型实例的getClass()方法
Ojbect根类提供getClass方法,用于获取实例的Class实例。关键代码如下:
public class Object {
// ...
/**
* Returns the runtime class of this {@code Object}. The returned
* {@code Class} object is the object that is locked by {@code
* static synchronized} methods of the represented class.
*/
@HotSpotIntrinsicCandidate
public final native Class<?> getClass();
}
所以,可以使用getClass方法获取Class实例。示例代码如下:
public void getClassRefByGetClassMethod() {
Apple apple = new Apple();
Class clazz = apple.getClass();
}
需要说明的是,使用这种方法,需要引入实例所属类的包。这种方式的调用多出现于调用方。对于自身来说,因为已经有了类的实例,无需再通过Class实例去构造实例并访问字段或方法。
使用ClassLoader实例的loadClass方法
ClassLoader实例提供loadClass方法来实现指定类的全路径名来获取Class实例。示例代码如下:
public void getClassRefByClassLoader() {
try {
ClassLoader classLoader = this.getClass().getClassLoader();
Class clazz = classLoader.loadClass("io.github.courage007.reflect.Apple");
System.out.println(clazz.toString());
} catch (ClassNotFoundException ex) {
throw new RuntimeException("get class failed");
}
}
基于ClassLoader获取Class的方式与基于Class类的forName静态方法获取Class的方式一样,都是基于类的全路径名获取 Class 对象。其优缺点不再赘述。
基于反射构造实例
获取Class对象后,就可基于Class对象实现构造方法、字段、方法的调用。这里介绍如何基于Class对象调用构造方法以实现实例创建。
按照访问类型、参数个数,可将构造函数分为如下四类:公有无参构造函数、公有带参构造函数、私有无参构造函数、私有带参构造函数。
公有无参构造函数
Class类提供newInstance方法,用于创建类的实例。但是,该方法会返回空实例,在Java 9之后已经弃用,推荐先基于Class获取Constructor实例,然后基于Constructor的newInstance方法去创建类型实例。需要说明的是Constructor的newInstance方法也可创建公有带参构造函数,示例代码如下:
public void getInstanceWithoutParam() {
try {
Class clazz = Class.forName("io.github.courage007.reflect.Apple");
Apple apple = (Apple) clazz.getConstructor(null).newInstance(null);
apple.getColor();
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException ex) {
throw new RuntimeException("get class constructor failed");
}
}
已弃用的Class类的newInstance方法代码片段如下:
public final class Class<T> implements java.io.Serializable,
GenericDeclaration,
Type,
AnnotatedElement {
// ...
/**
* 创建基于Class实例的类的实例。
*
* @deprecated 这个方法会传递空实例,已被弃用。推荐使用
* java.lang.reflect.Constructor的newInstance(java.lang.Object...)方法
*/
@CallerSensitive
@Deprecated(since="9")
public T newInstance() throws InstantiationException, IllegalAccessException {
// ...
try {
// 创建无参
return tmpConstructor.newInstance((Object[])null);
} catch (InvocationTargetException e) {
Unsafe.getUnsafe().throwException(e.getTargetException());
// Not reached
return null;
}
}
}
公有带参构造函数
Constructor的newInstance方法支持创建公有带参构造函数,示例代码如下:
public void getInstanceWithParam() {
try {
Class clazz = Class.forName("io.github.courage007.reflect.Apple");
Apple apple = (Apple) clazz.getConstructor(String.class, String.class).newInstance("green", "small");
apple.getColor();
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException ex) {
throw new RuntimeException("get class constructor failed");
}
}
私有构造函数
Class实例的getConstructor方法只能获取包含父类的某个public的构造方法,对于某个非public访问权限的构造方法,则需使用
getDeclaredConstructor方法。需要说明的是,在使用非public的Constructor时,必须先执行setAccessible(true)方法,设置允许访问。
经过对公有构造函数调用可以发现,基于Constructor调用公有无参构造函数和公有待参构造函数,其差异性仅体现在传参。私有无参构造函数和私有带参构造函数有类似处理机制。这里仅以私有带参构造函数为例,私有无参构造函数处理类似。示例代码如下:
public void getInstanceWithPrivateConstructor() {
try {
Class clazz = Class.forName("io.github.courage007.reflect.Apple");
Constructor currentConstructor = clazz.getDeclaredConstructor(String.class);
currentConstructor.setAccessible(true);
Apple apple = (Apple)currentConstructor.newInstance("yellow");
apple.getColor();
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException ex) {
throw new RuntimeException("get class constructor failed");
}
}
基于反射获取字段
与基于反射构造实例先通过Class对象获取Constructor对象,然后再基于Constructor对象调用构造函数类似,基于反射获取字段先通过Class对象获取Field对象,然后再基于Field对象访问字段。Field提供一系列方法,用于操作字段,常用的方法有:
getName():返回字段名称
getType():返回字段类型,也是一个Class实例,如String.class
getModifiers():返回字段的修饰符,它是一个int,不同的bit表示不同的访问权限
setAccessible():设置变量为public
set():设置字段值
get():获取字段值
这里以访问私有字段为例,介绍下如何基于反射访问字段。示例代码如下:
public void getFieldWithPrivatePermission() {
try {
Class clazz = Class.forName("io.github.courage007.reflect.Apple");
// 获取private字段"grade":
Field colorField = clazz.getDeclaredField("color");
colorField.setAccessible(true);
// 获取字段名
colorField.getName();
// 获取字段类型
colorField.getType();
// 获取字段的访问权限
colorField.getModifiers();
Apple apple = (Apple) clazz.getConstructor(String.class, String.class).newInstance("green", "small");
apple.getColor();
// 设置字段
colorField.set(apple, "red");
// 获取字段值
colorField.get(apple);
} catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException | InstantiationException | InvocationTargetException | NoSuchMethodException ex) {
throw new RuntimeException("get class field failed");
}
}
基于反射获取方法
与基于反射构造实例和基于反射获取字段类似,基于反射获取方法先通过Class对象获取Method对象,然后再基于Method对象访问方法。Method提供一系列方法,用于访问方法,常用的方法有:
getName():返回方法名称
getReturnType():返回方法返回值类型,也是一个Class实例,例如:String.class
getParameterTypes():返回方法的参数类型,是一个Class数组,例如:{String.class, int.class}
getModifiers():返回方法的修饰符,它是一个int,不同的bit表示不同的含义
setAccessible():设置private函数为public属性
invoke(object,new Object[]{}) 调用执行方法
这里以访问私有方法为例,介绍下如何基于反射调用方法。示例代码如下:
public void invokeMethodWithPrivatePermission() {
try {
Class clazz = Class.forName("io.github.courage007.reflect.Apple");
Method changeColorMethod = clazz.getDeclaredMethod("changeColor");
changeColorMethod.setAccessible(true);
// 获取方法名
changeColorMethod.getName();
// 获取参数类型 Class[] 数组
changeColorMethod.getParameterTypes();
// 返回方法返回值类型 Class 实例
changeColorMethod.getReturnType();
// 获取方法的访问权限
changeColorMethod.getModifiers();
Apple apple = (Apple) clazz.getConstructor(String.class, String.class).newInstance("green", "small");
// 调用方法
changeColorMethod.invoke(apple, null);
} catch (ClassNotFoundException | IllegalAccessException | InstantiationException | InvocationTargetException | NoSuchMethodException ex) {
throw new RuntimeException("get class method failed");
}
}
基于反射获取父类成员
通过Class实例的getXxx类型方法可以获取包含父类的某个public的构造方法、字段、方法。对于非public构造方法、字段、方法,可以先通过获取getSuperclass获取父类对应的Class对象,然后通过Class对象访问非public构造方法、字段、方法。这里不再给出示例,有兴趣的同学,可以自行学习。
总结
Java提供Class类型、Constructor类型、Field类型、Method类型,帮助实现运行时访问类型实例上的成员。在获取成员时,根据成员的访问权限、声明位置,需要选用不同的方法,具体可以分为两类:
getXxx 获取包含父类的某个public的构造方法、字段、方法。
getDeclaredXxx 获取当前类的包含private访问权限的所有构造方法、字段、方法。
参考
https://www.anquanke.com/post/id/245458 Java安全之反射