"talk is cheap, show me the code"。接下来将从实际应用的角度,说明如何使用反射。关于反射相关的概念,可以参考Java反射概述博文。 使用反射的基本步骤如下:
(1) 获取类型的Class对象;
(2) 基于Class对象获取类型元数据:Constructor类型、Field类型、Method类型;
(3) 基于Constructor类型、Field类型、Method类型访问成员。
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 Classimplements 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 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");
// 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 {
// ...
@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公有带参构造函数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()); // 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安全之反射



