反射简介

什么是反射

反射(Reflection)机制可以在运行时访问 Java 对象的构造方法、属性、方法。相关的类都在 java.lang.reflect 包下。重要的类有:

  • Class
  • Member
  • Constructor
  • Field
  • Method
  • Array
  • Modifier

反射的应用场景

  • 开发通用框架:比如 Spring,为了保证框架的通用性,它们可能需要根据配置文件去加载不同的类,调用不同的方法,这个时候就需要用到反射、
  • 动态代理:在切面编程(AOP)中,需要拦截特定的方法通常通过动态代理实现,这就会用到反射技术。
  • 注解:注解本身只是起到标记作用,它需要利用反射机制,根据注解标志去调用注解解释器执行行为。
  • Java Hook:利用反射和动态代理机制可以实现 Java 层次的 Hook,具体参考:Java基础:基于反射和动态代理的Hook

反射的缺点

  • 性能开销大:具体有哪些性能开销可以参考:反射的性能开销都在那
  • 不安全:反射调用的时候可以忽略权限检查,可能会导致安全问题
  • 内部曝光:反射可以调用私有方法或者字段,所以反射的使用可能会导致意想不到的副作用。

反射的原理

了解反射的原理就需要去了解 Java 虚拟机的类加载机制,这一部分可以参考 深入理解 Java 虚拟机 第三版 中的第七章。

Java 虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型,这个过程被称为虚拟机的类加载机制。

类加载的过程:

  • 加载
  • 验证
  • 准备
  • 解析
  • 初始化

加载(不是类加载)过程中,虚拟机需要完成:
1.通过一个类的全限定名来获取定义此类的二进制字节流
2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
3.在 Java 堆内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口

正常情况下,Java 对象的创建是通过 new 关键字,创建的原理可以参考 深入理解 Java 虚拟机 第三版 中的 2.3.1 部分。

反射创建对象和正常的对象创建原理一样,都是利用 Java 虚拟机通过类加载生成的 java.lang.Class 对象实现的。

使用反射

这里给出下面的例子中的相关类:

1.需要用到的抽象类:People、接口:PeopleProtocol

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public abstract class People {
public static final String ID = "People";

public String name = "";
public int age = 0;

abstract boolean isSingle();

public void show() {
System.out.println("name = " + name + " age = " + age);
}

public int getAge() {
return age;
}
}

public interface PeopleProtocol {
void eat();
void chat();
}

2.实现类 Man

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class Man extends People implements PeopleProtocol {
public static final String ID = "123";

public String name = "man";
private int id = 123;

public Man() {

}

public Man(String name) {
this.name = name;
}

public Man(String name, int age) {
this.name = name;
this.age = age;
}

private Man(int age) {
this.age = age;
}

@Override
boolean isSingle() {
return true;
}

@Override
public void show() {
System.out.println("My name is " + name);
}

private String getPrefixName(String prefix) {
return prefix + "name";
}

@Override
public void eat() {
System.out.println("I can eat everything");
}

@Override
public void chat() {
System.out.println("I can chat with everyone");
}
}

Class

获取 Class 的方式有三种:

  1. 通过全限定名获取
  2. 通过类获取
  3. 通过对象获取
1
2
3
4
5
6
7
8
9
10
11
12
1.通过全限定名获取
try {
Class<?> clazz = Class.forName("com.kotlin.reflection.Man");
} catch (ClassNotFoundException e) {
// nop
}
// 2.通过类获取
Class<Man> clazz = Man.class;

// 3. 通过对象获取(实际上是继承于 Object)
Man man = new Man();
Class<? extends Man> clazz = man.getClass();

类型判断

Java 中类型判断也有两种方式:

  1. instanceof 关键字
  2. Class 实例的 instance 方法
1
2
3
4
5
6
7
8
// 1. instanceof 关键字
if (man instanceof People) {
System.out.println("man is people");
}
// 2. Class 实例的 instance 方法
if (clazz.isInstance(man)) {
System.out.println("man is Man");
}

创建实例

Java 中利用反射创建对象的方式也有两种:

  1. Class 实例的 newInstance 方法
  2. Constructor 实例的 newInstance 方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 1. Class 实例的 newInstance
try {
StringBuilder temp = StringBuilder.class.newInstance();
temp.append("temp is StringBuilder");
System.out.println(temp);
} catch (InstantiationException e) {
// nop
} catch (IllegalAccessException e) {
// nop
}

// 2. Constructor 实例的 newInstance
try {
Constructor<StringBuilder> constructor = StringBuilder.class.getConstructor(String.class);
StringBuilder temp = constructor.newInstance("temp is StringBuilder");
System.out.println(temp);
} catch (NoSuchMethodException e) {
// nop
} catch (IllegalAccessException e) {
// nop
} catch (InstantiationException e) {
// nop
} catch (InvocationTargetException e) {
// nop
}

// 运行结果
temp is StringBuilder
temp is StringBuilder

Member

Member: 用来表示类中的一个成员:构造函数、属性、方法,ConstructorFieldMethod 都是它的子类。

这里先构建出下文需要用到的实例:

1
2
Man man = new Man();
Class<? extends Man> clazz = man.getClass();

Constructor

获取 Class 实例的 Constructor 有四种方式:

  1. getConstructor:返回类的特定 public 构造方法,参数为方法参数对应 Class 的实例
  2. getDeclaredConstructor:返回类的特定构造方法,参数为方法参数对应 Class 的实例
  3. getConstructors:返回类的所有 public 构造方法
  4. getDeclaredConstructors:返回类的所有构造方法

getConstructor 在上面创建实例中已经使用过了,getDeclaredConstructor 和其类似。所以这里主要看看 getConstructorsgetDeclaredConstructors 的使用:

1
2
3
4
5
6
7
8
9
// getConstructors 的例子
Constructor<?>[] constructors = clazz.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println("name = " + constructor.getName() + " ParameterType = " + Arrays.toString(constructor.getParameterTypes()));
}
// 运行结果
name = com.kotlin.reflection.Man ParameterType = [class java.lang.String, int]
name = com.kotlin.reflection.Man ParameterType = [class java.lang.String]
name = com.kotlin.reflection.Man ParameterType = []
1
2
3
4
5
6
7
8
9
10
// getDeclaredConstructors 的例子
constructors = clazz.getDeclaredConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println("name = " + constructor.getName() + " ParameterType = " + Arrays.toString(constructor.getParameterTypes()));
}
// 运行结果
name = com.kotlin.reflection.Man ParameterType = [int]
name = com.kotlin.reflection.Man ParameterType = [class java.lang.String, int]
name = com.kotlin.reflection.Man ParameterType = [class java.lang.String]
name = com.kotlin.reflection.Man ParameterType = []

Field

获取 Class 实例的 Field 有四种方式:

  1. getField:返回类的特定 public 属性,参数是属性名
  2. getDeclaredField:返回类的特定属性,参数是属性名,但是无法得到继承的属性
  3. getFields:返回类的所有 public 属性
  4. getDeclaredFields:返回类的所有属性,但是无法得到继承的属性

如果想获取 Man 实例的 name 属性,可以使用 getField 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
try {
Field field = clazz.getField("name");
System.out.println("field 的 type = " + field.getType());
field.set(man, "modify name");
String name = (String) field.get(man);
System.out.println("修改后的值 = " + name);
} catch (NoSuchFieldException | IllegalAccessException e) {
// nop
}

// 运行结果
field 的 type = class java.lang.String
修改后的值 = modify name

如何设置/获取 Field 实例对应的属性值呢?
1.设置值:调用 Field 实例的 set 方法,第一个参数是修改的对象,第二个参数是想设置的值
2.获取值:调用 Fieid 实例的 get 方法,需要传入修改的对象作为参数

如果修改的是静态属性,就不需要传入修改的对象,直接传入 null 即可

如果想获取 Man 实例的 id 属性,就需要使用 getDeclaredField 方法:

1
2
3
4
5
6
7
8
9
10
try {
Field field = clazz.getDeclaredField("id");
field.setAccessible(true);
System.out.println(field.get(man));
} catch (NoSuchFieldException | IllegalAccessException e) {
// nop
}

// 运行结果
123

如果修改的是私有属性,需要使用 setAccessible(true) 避免访问无法访问

接下里看看 getFieldsgetDeclaredFields 的使用:

1
2
3
4
5
6
7
8
9
10
11
12
// getFields 的例子
Field[] fields = clazz.getFields();
for (Field field : fields) {
System.out.println("name = " + field.getName() + " type = " + field.getType());
}

// 运行结果
name = ID type = class java.lang.String
name = name type = class java.lang.String
name = ID type = class java.lang.String
name = name type = class java.lang.String
name = age type = int
1
2
3
4
5
6
7
8
9
10
// getDeclaredFields 的例子
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
System.out.println("name = " + field.getName() + " type = " + field.getType());
}

// 运行结果
name = ID type = class java.lang.String
name = name type = class java.lang.String
name = id type = int

Method

获取 Class 实例的 Method 有四种方式:

  1. getMethod:返回类或者接口的特定 public 方法,第一个参数是方法名,后面的参数是方法参数对应的 Class 对象
  2. getDeclaredMethod:返回类或者接口的特定方法,第一个参数是方法名,后面的参数是方法参数对应的 Class 对象,但是无法得到继承的方法
  3. getMethods:返回类或者接口的所有 public 方法
  4. getDeclaredMethods:返回类或者接口的所有方法,但是无法得到继承的方法

如果想获取 Man 实例的 eat 方法,可以使用 getMethod 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
try {
Method method = clazz.getMethod("eat");
method.invoke(man);
} catch (NoSuchMethodException e) {
// nop
} catch (IllegalAccessException e) {
// nop
} catch (InvocationTargetException e) {
// nop
}

// 运行结果
I can eat everything

如何调用 Method 实例对应的方法呢?
调用 Method 实例的 invoke 方法,第一个参数需要传入调用的对象,后面的参数是待传入参数的值

如果修改的是静态方法,就不需要传入修改的对象,直接传入 null 即可

如果想获取 Man 实例的 getPrefixName 方法,就需要使用 getDeclaredMethod 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
try {
Method method = clazz.getDeclaredMethod("getPrefixName", String.class);
method.setAccessible(true);
String newName = (String) method.invoke(man, "temp");
System.out.println("newName = " + newName);
} catch (NoSuchMethodException e) {
// nop
} catch (IllegalAccessException e) {
// nop
} catch (InvocationTargetException e) {
// nop
}

// 运行结果
newName = tempname

和修改私有属性一样,修改私有方法也需要调用 setAccessible(true) 避免无法访问

接下来看看 getMethodsgetDeclaredMethods 的使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// getMethods 的例子
Method[] methods = clazz.getMethods();
for (Method method : methods) {
System.out.println("name = " + method.getName() + " parameter type = " + Arrays.toString(method.getParameterTypes()) + " return type = " + method.getReturnType());
}

// 运行结果
name = eat parameter type = [] return type = void
name = show parameter type = [] return type = void
name = chat parameter type = [] return type = void
name = getAge parameter type = [] return type = int
name = wait parameter type = [long] return type = void
name = wait parameter type = [long, int] return type = void
name = wait parameter type = [] return type = void
name = equals parameter type = [class java.lang.Object] return type = boolean
name = toString parameter type = [] return type = class java.lang.String
name = hashCode parameter type = [] return type = int
name = getClass parameter type = [] return type = class java.lang.Class
name = notify parameter type = [] return type = void
name = notifyAll parameter type = [] return type = void
1
2
3
4
5
6
7
8
9
10
11
12
// getDeclaredMethods 的例子
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
System.out.println("name = " + method.getName() + " parameter type = " + Arrays.toString(method.getParameterTypes()) + " return type = " + method.getReturnType());
}

// 运行结果
name = eat parameter type = [] return type = void
name = getPrefixName parameter type = [class java.lang.String] return type = class java.lang.String
name = isSingle parameter type = [] return type = boolean
name = show parameter type = [] return type = void
name = chat parameter type = [] return type = void

Array

数组 Array 在 Java 中比较特殊,下面看下如何在 Java 中通过反射创建一个数组:

1
2
3
4
5
6
7
Object array = Array.newInstance(clazz, 5);
Array.set(array, 0, man);
Man index0 = (Man) Array.get(array, 0);
System.out.println(index0);

// 运行结果
com.kotlin.reflection.Man@2e5c649

其它

java.lang.reflect 包下还有很多其它的类,这里再介绍一个 Modifier
Java 针对类、变量、方法 有很多修饰符,例如public、private、static、final、synchronized、abstract等。 Modifier 中就记录了这些修饰符,下面看看具体的例子:

1
2
3
4
5
6
7
8
9
10
11
12
try {
Field field = clazz.getDeclaredField("id");
int modifier = field.getModifiers();
System.out.println("modifier = " + modifier);
System.out.println("modifier = " + Modifier.toString(modifier));
} catch (NoSuchFieldException e) {
// nop
}

// 运行结果
modifier = 2 // 0010
modifier = private

从上面的运行结果来看,是否有这些修饰符是通过一个 int 类型变量存储,这里巧用了位运算达成目的,下面是解析的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public static String toString(int mod) {
StringJoiner sj = new StringJoiner(" ");
if ((mod & 1) != 0) {
sj.add("public");
}

if ((mod & 4) != 0) {
sj.add("protected");
}

if ((mod & 2) != 0) {
sj.add("private");
}

if ((mod & 1024) != 0) {
sj.add("abstract");
}

if ((mod & 8) != 0) {
sj.add("static");
}

if ((mod & 16) != 0) {
sj.add("final");
}

if ((mod & 128) != 0) {
sj.add("transient");
}

if ((mod & 64) != 0) {
sj.add("volatile");
}

if ((mod & 32) != 0) {
sj.add("synchronized");
}

if ((mod & 256) != 0) {
sj.add("native");
}

if ((mod & 2048) != 0) {
sj.add("strictfp");
}

if ((mod & 512) != 0) {
sj.add("interface");
}

return sj.toString();
}

动态代理

静态代理

说到动态代理之前就需要先说一下静态代理,这样方便引出动态代理,下面是一个静态代理的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public interface Subject {
void request();
}

// 真实类
public class RealSubject implements Subject {
@Override
public void request() {
// nop
}
}

// 代理类
public class ProxySubject implements Subject {
private Subject real;

@Override
public void request() {
if (real == null) {
real = new RealSubject();
}
// 执行这个方法之前做一些事情
real.request();
// 执行这个方法之后做一些事情
}
}

简单来说,静态代理就是创建一个和真实类具有相同接口的代理类。如果需要调用真实类的相关方法,直接调用代理类即可。

从实现方式来看,可以看出静态代理的弊端:

  • 有多个类需要实现代理的时候,如果选择一个代理类来实现所有的接口,那么会造成这个代理类过大;如果选择多个代理类,那么会造成产生过多的代理类
  • 当真实类的接口修改时,需要同时修改真实类和代理类,不易维护

动态代理

基于静态代理的问题,从而引出了动态代理:让代理类在运行时动态的生成。

动态代理分为两类:

  1. JDK 动态代理:通过实现接口的方式
  2. CGLIB 动态代理:通过继承类的方式

由于 CGLIB 动态代理需要引入相关的依赖,所以这里主要说一下 JDK 动态代理。 JDK 动态代理主要涉及两个类:

  1. InvocationHandler
  2. Proxy

下面将上面提到的静态代理例子修改成动态代理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
interface Subject {
void request();
}

class RealSubject implements Subject {
@Override
public void request() {
System.out.println("RealSubject request");
}
}

class Main {
public static void main(String[] args) {
final RealSubject realSubject = new RealSubject();
ClassLoader classLoader = realSubject.getClass().getClassLoader();
Class<?>[] interfaces = realSubject.getClass().getInterfaces();
Subject proxy = (Subject) Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] args) throws Throwable {
// 执行这个方法之前做一些事情
System.out.println("before " + method.getName());
Object result = method.invoke(realSubject, args);
// 执行这个方法之后做一些事情
System.out.println("after " + method.getName());
return result;
}
});
proxy.request();
}
}

Kotlin 中使用

动态代理例子

这一篇的标题是 Kotlin 的反射,但是由于当前的 Kotlin 仍然存在一些问题,所以一般情况下还是使用 Java 的反射。下面给出一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
val classWindowManagerGlobal = Class.forName("android.view.WindowManagerGlobal")
val windowSession = classWindowManagerGlobal.getDeclaredMethod("getWindowSession").invoke(null)
val classWindowSession = windowSession.javaClass
val proxy = Proxy.newProxyInstance(classWindowSession.classLoader, classWindowSession.interfaces) { proxy, method, args ->
val ret = if (args == null) {
method.invoke(windowSession)
} else {
method.invoke(windowSession, *args)
}
if (method.name.startsWith("addToDisplay")) {
try {
onAddToDisplayCalled()
} catch (e: Exception) {
CalidgeLogger.e(TAG, "onAddToDisplayCalled failed", e)
}
}
ret
}
val fieldWindowSession = classWindowManagerGlobal.getDeclaredField("sWindowSession")
fieldWindowSession.isAccessible = true
fieldWindowSession.set(null, proxy)

javaClass 和 class.java

下面看看各种 Class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
val clazz1 = Boolean.javaClass
val clazz2 = Boolean::class
val clazz3 = Boolean::class.java
val clazz4 = java.lang.Boolean::class.java

println("class1 = $clazz1")
println("class2 = $clazz2")
println("class3 = $clazz3")
println("class4 = $clazz4")

// 运行结果
class1 = class kotlin.jvm.internal.BooleanCompanionObject
class2 = boolean (Kotlin reflection is not available)
class3 = boolean
class4 = class java.lang.Boolean

Kotlin 中和 Class 对应的叫 KClass,可以通过 ::class 获取,这里它不是主角。

主角是 .javaClass::class.java,从名字上来看它们的获取结果应该是一致的才对,但结果却告诉我们差别很大。

当你使用 Boolean.javaClass 的时候,AndroidStudio 会出现下面的提示:

1
The resulting type of this 'javaClass' call is Class<Boolean.Companion> and not Class<Boolean>. Please use the more clear '::class.java' syntax to avoid confusion

因此,如无必要,不要使用 .javaClass,尽量使用 ::class.java

装箱、拆箱问题

Java 中存在基本类型的装箱、拆箱问题,所以一般都有基本类型和包装类型,例如:booleanjava.lang.Boolean

Kotlin 中不存在基本类型,只有 Boolean,但是 JVM 会对其优化,一般情况下都和基本类型等价。所以 Boolean::class.java 的结果才会是 boolean,当 Boolean 被包装时,会怎么样呢?可以看看下面的例子:

1
2
3
4
5
6
7
8
val boolean1: Boolean = false
val boolean2: Boolean? = false
println("Boolean 简单类型 Class = ${boolean1::class.java}")
println("Boolean 包装类型 Class = ${boolean2!!::class.java}")

// 运行结果
Boolean 简单类型 Class = boolean
Boolean 包装类型 Class = class java.lang.Boolean

类型判断

1
2
3
4
5
6
fun isBoolType(clazz: Class<*>): Boolean {
// 添加 java.lang.Boolean.TYPE 是为了保险一点,实际上它和 Boolean::class.java 的打印结果一样
return clazz == java.lang.Boolean.TYPE ||
clazz == java.lang.Boolean::class.java ||
clazz == Boolean::class.java
}

参考文章