"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对象的方法前,先预定义测试类,方便后面统一使用。
@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) {
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);
}
@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) {
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);
}
}
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");
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");
}
}
基于类的全路径名获取 Class 对象的方式,其优势是不需要事先引入依赖,适用于运行时调用的场景。需要说明的是,由于代码里硬编码了类的全路径名,不能很好的适应类的全路径名变化场景,生产上可以这部分数据放置在文件或数据库等存储介质中。
使用Ojbect根类的静态class字段
Ojbect根类提供静态class字段,可以直接获取Class实例。示例代码如下:
public void getClassRefByStaticField() {
Class clazz = Apple.class;
}
使用这种方法,需要引入类对应的包。该方式主要应用于调用方。
使用类型实例的getClass()方法
Ojbect根类提供getClass方法,用于获取实例的Class实例。关键代码如下:
public class Object {
@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 {
@CallerSensitive
@Deprecated(since="9")
public T newInstance() throws InstantiationException, IllegalAccessException {
try {
return tmpConstructor.newInstance((Object[])null);
} catch (InvocationTargetException e) {
Unsafe.getUnsafe().throwException(e.getTargetException());
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");
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();
changeColorMethod.getParameterTypes();
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安全之反射
|