「这是我参与2022首次更文挑战的第6天,活动详情查看:2022首次更文挑战」
01-简介
使用 Java 中的反射技术,允许以编程方式访问已加载类的域、方法和构造器等,所以可以检查、修改类、接口、域和方法的运行时属性。
Oracle 官方对反射的解释是:
Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions.
The API accommodates applications that need access to either the public members of a target object (based on its runtime class) or the members declared by a given class. It also allows programs to suppress default reflective access control.
01.1-反射的应用场景
反射在 Java 应用开发中被广泛的使用,特别是在开发各种各样的通用框架时,例如 Spring 。而且,就算在 Java 内置的机制中,也有一些是通过反射实现的,例如 SPI 机制。
02-反射的基本运用(关键 API )
02.1-Class 对象
获得 Class 对象的方式有三种:
java.lang.Class#forName,使用 Class 类的静态方法。java.lang.Object#getClass,所有类都默认继承 Object 类,因此每个对象都有继承自 Object 的方法 getClass。Object.class,直接通过某个类的 .class 属性,获得该类的 Class 对象。
方法2. 和方法3. 有什么区别呢?
-
方法2. 返回对象的Runtime Type
对基本类型来讲
int number = 7; number.getClass(); // 编译时异常:int cannot be rereferenced对无法实例化的类或接口,自然无法使用这种语法
-
方法3. 返回的是类或接口的Static Type
对基本类型来讲
Class clazz = int.class; clazz.isPrimitive();Interfaces, abstract classes, or classes that can't be instantiated
Class interfaceType = SomeInterface.class; Modifier.isInterface(interfaceType.getModifiers()); // is true
02.2-Field 对象
获取类的成员变量方法:
Class#getField,返回某个特定的域对象。Class#getDeclaredFields,返回声明的域对象组成的数组,包括public, protected, default (package) access和private域,但不包括继承来的域。
02.3-Method 对象
获取类的成员函数方法:
-
Class#getMethod,返回特定的”public member method”。 -
Class#getDeclaredMethod,返回特定的”declared method”。 -
Class#getMethods,返回所有的public方法对象组成的数组,包括当前类或接口声明的,以及从超类或超接口继承的。 -
Class#getDeclaredMethods,返回声明的方法对象组成的数组,包括public, protected, default (package) access和private方法,但不包括继承来的方法。与 getMethods 不同的是,可以返回非 public 方法。例如,
Object.class.getDeclaredMethods()vsObject.class.getMethods():java.lang.Class.getDeclaredMethods: finalize # protected wait wait wait equals toString hashCode getClass clone # protected notify notifyAll registerNatives # private ---------------------------------- java.lang.Class.getMethods: wait wait wait equals toString hashCode getClass notify notifyAll
获得 Method 对象后,如何执行该方法呢?可通过Method#invoke方法。
try {
// 创建一个对象
InnerClass innerClass = InnerClass.class.newInstance();
// 获得 sayHi 方法
Method sayHi = InnerClass.class.getMethod("sayHi");
// 调用 sayHi 方法,因是成员函数,需要传入对象
sayHi.invoke(innerClass);
} catch (Exception ignored) { }
static class InnerClass {
public void sayHi() {
System.out.println("InnerClass say hi~");
}
}
02.4-Constructor 对象
获取类的构造器的方法:
Class#getConstructor,返回某个特定的构造器对象。Class#getConstructors,返回所有的public构造器对象组成的数组。
02.5-通过反射创建对象
通过反射创建对象有以几种方式:
-
Class#newInstance,获得 Class 对象后,调用其 newInstance 方法,可获得该类的对象。// Java 9 中,该方法被废弃了 Object obj = Object.class.newInstance(); -
Constructor#newInstance,获得 Class 对象后,再获得指定的构造器,然后调用构造器的 newInstance 方法。// Object 类中并未声明构造器,但 Java 中默认存在一个无参构造器 Object obj = Object.class.getConstructors()[0].newInstance();
如何判断一个实例是否属于某个类呢?有三种方法:
instanceofClass#isInstanceClass#isAssignableFrom
String str = new String("test string");
boolean way1 = str instanceof String;
boolean way2 = String.class.isInstance(str);
// String.class 是否是 str 所属类或其超类、接口
boolean way3 = String.class.isAssignableFrom(str.getClass());
如何通过反射机制创建一个数组对象呢?
通过反射创建数组,需要借助到java.lang.reflect.Array类:
Object array = Array.newInstance(String.class, 25);
Array.set(array, 0, "a");
Array.set(array, 1, "b");
Array.set(array, 2, "c");
Array.set(array, 3, "d");
System.out.println(Array.get(array, 3));
02.6-其他
除了上述提到方法外,Class 对象还有一系列其他的方法用于获取诸如类名、类修饰符、类所属包、类的超类和接口等信息。
Class#getSimpleName,返回类的simple name,即声明时指定的类名,例如Goat。Class#getName,返回类的全量名,例如com.baeldung.reflection.Goat。Class#getCanonicalName,返回类的canonical name或null。Class#getModifiers,返回一个int值,表示类的修饰符,[java.lang.reflect.Modifier](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/reflect/Modifier.html)提供了方法检测修饰符标志。Class#getPackage,返回一个[Package](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Package.html)对象。Class#getSuperclass,返回直接继承超类的Class对象。Class#getInterfaces,返回直接实现的所有超接口的Class对象的数组。
03-Class.forName
Class.forName(),会尝试 locate, load and link 类或接口(通过全量名指定),且当 ClassLoader 无法载入类或接口时,会抛出ClassNotFoundException异常,因此可以用来测试某个指定的类或接口是否存在。
但该方法存在一个副作用,即若非特别指定 ClassLoader,Class.forName接口会调用类的static initializer(静态初始化器)。例如:
public class InitializingClass {
static {
if (true) { //enable throwing of an exception in a static initialization block
throw new RuntimeException();
}
}
}
Class.forName("path.to.InitializingClass"); // ExceptionInInitializerError 异常
如何避免上述副作用:
Class.forName("self.samson.reflection.demo.InitializingClass", false, getClass().getClassLoader());
原理,从Class.forName(String)的接口文档中可知:
Class.forName("path.to.InitializingClass");
// 等价于
Class.forName("path.to.InitializingClass", true, currentLoader);
第二个参数为true时类会被初始化。
04-java.lang.reflect.InvocationTargetException
The reflection layer wraps the actual exception thrown by the method with the InvocationTargetException.
wrap 的目的是,区分异常发生在通过反射层调用目标方法过程中,还是发生在目标方法执行过程中。
通过以下方式,可获得原始的异常信息:
try {
...
} catch (InvocationTargetException ie) {
ie.getCause().getClass(); // 获取原始异常的运行时类信息
}
05-常用库
The Reflections library
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.9.11</version>
</dependency>
Guava’s Reflection Utilities
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1.1-jre</version>
</dependency>
Spring Test Context framework
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.2.RELEASE</version>
<scope>test</scope>
</dependency>
06-应用场景
在运行时查找某个包中所有的类。JVM使用 ClassLoader 加载类,将 JRE 与文件系统解耦。
InputStream inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream(packageName.replaceAll("[.]", "/"));
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
Set<Class> classes = reader.lines()
.filter(line -> line.endsWith(".class")) // 找到所有以.class结尾的类文件
.map(line -> getClass(packageName, line)) // 加载类
.collect(Collectors.toSet());
public Class getClass(String packageName, String classFileName) {
try {
// 根据类的全量名加载类对象
return Class.forName(String.format("%s.%s",
packageName,
classFileName.substring(0, classFileName.lastIndexOf('.'))));
} catch (ClassNotFoundException cnfe) {
cnfe.printStackTrace();
}
return null;
}
借助其他库,实现上述功能:
-
Reflections reflections = new Reflections(packageName, new SubTypesScanner(false)); reflections.getSubTypesOf(Object.class) .stream() .collect(Collectors.toSet()); -
Guava Library
ClassPath.from(ClassLoader.getSystemClassLoader()) .getAllClasses() .stream() .filter(clazz -> clazz.getPackageName() .equalsIgnoreCase(packageName)) .map(clazz -> clazz.load()) .collect(Collectors.toSet());
往期文章:
Spring MVC 「3」从DispatchServlet开始,一个请求的处理流程
Spring MVC 「2」WebApplicationInitializer的工作原理
参考文章:
[2] Finding All Classes in a Java Package
[3] Invoking a Private Method in Java
[4] The Difference Between a.getClass() and A.class
[5] Method Parameter Reflection in Java
[6] 关于反射调用方法的一个log (反射底层原理分析)
[7] Java反射原理简析