00.反射-基本入门

a group of palm trees against a blue sky

概念

反射具备在运行时分析类以及执行类中方法的能力,很多框架都使用了反射,通过反射可以获取任意一个类的所有属性和方法,可以调用这些方法和属性。

应用场景

一、Spring、Spring Boot、Mybatis等框架中都使用了反射机制;

Spring为了保持通用性,通过配置文件加载不同的对象,调用不同的方法;

二、 动态代理

在面向切面编程中,需要拦截特定的方法,就会选择动态代理的方式,而动态代理的底层技术就是反射。

同时,在上述的框架中,也大量使用了动态代理,而动态代理依赖反射机制;

以下为JDK实现动态代理的示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class DebugInvocationHandler implements InvocationHandler {
/**
* 代理类中的真实对象
*/
private final Object target;

public DebugInvocationHandler(Object target) {
this.target = target;
}


public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
System.out.println("before method " + method.getName());
Object result = method.invoke(target, args);
System.out.println("after method " + method.getName());
return result;
}
}

三、Java的注解也用到了反射。

为什么你使用 Spring 的时候 ,一个@Component注解就声明了一个类为 Spring Bean 呢?为什么你通过一个 @Value注解就读取到配置文件中的值呢?究竟是怎么起作用的呢?

这些都是因为你可以基于反射分析类,然后获取到类/属性/方法/方法的参数上的注解。你获取到注解之后,就可以做进一步的处理.

反射的优缺点

优点:代码灵活,为各种框架提供开箱即用的功能;

缺点:

破坏封装:由于反射允许访问私有字段和私有方法,所以可能会破坏封装而导致安全问题,除此之外,反射可以忽略Java泛型参数的安全检查(泛型参数检查发生在编译期)。

性能开销:由于反射涉及到动态解析,因此无法执行Java虚拟机优化。

反射的基本使用方法

image-20230713175042543

第一步,获取反射类的Class对象:

1
Class clazz = Class.forName("全限定包名+类名");

第二步,通过Class对象获取构造方法Constructor对象:

1
Object object = constructor.newInstance();

第三步,通过Constructor对象初始化反射对象

1
Object object = constructor.newInstance();

第四步,获取要调用的方法的Method对象

1
2
Method setNameMethod = clazz.getMethod("setName", String.class);
Method getNameMethod = clazz.getMethod("getName");

第五步,执行invoke方法:

1
2
setNameMethod.invoke(object, "沉默王二");
getNameMethod.invoke(object)

反射原理

引入前提:熟悉《JVM类加载机制》

使用反射的前提是得到该反射类的Class对象,每一个类,不过它最终生成了多少个对象,这些对象只会对应一个Class对象(注意,同一个Class对象),这个Class对象是JVM生成的,包含了整个类的结构信息。

换句话说,java.lang.Class是所有反射API的入口,而方法的反射调用的是由Method对象的invoke方法完成的,源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException {
// 如果方法不允许被覆盖,进行权限检查
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
// 检查调用者是否具有访问权限
checkAccess(caller, clazz, obj, modifiers);
}
}
// 获取方法访问器(从 volatile 变量中读取)
MethodAccessor ma = methodAccessor;
if (ma == null) {
// 如果访问器为空,尝试获取方法访问器
ma = acquireMethodAccessor();
}
// 使用方法访问器调用方法,并返回结果
return ma.invoke(obj, args);
}

上述的两个if语句是用来进行权限检查的。invoke方法实际上委派给MethodAccessor接口执行:

img

而MethodAccessor接口有三个实现类,其中MethodAccessorImpl是抽象类,另外两个具体的实现类继承这个抽象类:

img

  • NativeMethodAccessorImpl:通过本地方法来实现反射调用;
  • DelegatingMethodAccessorImpl:通过委派模式来实现反射调用;

执行到invoke()方法之后,可以看到第一次反射调用会生成一个委派实现DelegatingMethodAccessorImpl,传递一个本地实现NativeMethodAccessorImpl。img

也就是说,invoke() 方法在执行的时候,会先调用 DelegatingMethodAccessorImpl,然后调用 NativeMethodAccessorImpl,最后再调用实际的方法。

为什么不直接调用本地方法NativeMethodAccessorImpl实现反射呢?

回答:

之所以采用委派实现,是为了能够在本地实现和动态实现之间切换。动态实现是另外一种反射调用机制,它是通过生成字节码的形式来实现的。如果反射调用的次数比较多,动态实现的效率就会更高,因为本地实现需要经过 Java 到 C/C++ 再到 Java 之间的切换过程,而动态实现不需要;但如果反射调用的次数比较少,反而本地实现更快一些。

临界点为15次,可以通过-Dsun.reflect.inflationThreshold设置。

反射的常用API

获取反射类的Class对象

1. 知道具体类的情况下:

1
Class<Person> personClass = Person.class;

但是一般情况下,是不知道具体类的,基本通过遍历包下面的类来获取Class对象,通过此方式获取Class对象并不会初始化。这时候并没有通过反射进行实例化对象,应该获取类的Constructor,然后newInstance初始化对象)。

2. 通过Class.forName()传入类的全路径:

1
Class<?> aClass = Class.forName("com.example.basejava.reflect.Person");

3. 通过对象实例instance.getClass()获取:

1
2
Person person = new Person();
Class<? extends Person> personClass1 = person.getClass();

4. 通过类加载器传入类路径获取

1
Class<?> personClass2 = ClassLoader.getSystemClassLoader().loadClass("com.example.basejava.reflect.Person");

注意:通过类加载器获取 Class 对象不会进行初始化,意味着不进行包括初始化等一系列步骤,静态代码块静态对象不会得到执行。

创建反射类的对象

两种方式:

  • 用Class对象的newInstance()方法。
  • 用构造方法Constructor对象的newInsta()方法。

示例:

1
2
3
4
5
6
Class c1 = Writer.class;
Writer writer = (Writer) c1.newInstance();

Class c2 = Class.forName("com.itwanger.s39.Writer");
Constructor constructor = c2.getConstructor();
Object object = constructor.newInstance();

获取构造方法

Class对象提供了以下方法来获取构造方法对象:

  • getConstructor():返回反射类的特定 public 构造方法,可以传递参数,参数为构造方法参数对应 Class 对象;缺省的时候返回默认构造方法。
  • getDeclaredConstructor():返回反射类的特定构造方法,不限定于 public 的。
  • getConstructors():返回类的所有 public 构造方法。
  • getDeclaredConstructors():返回类的所有构造方法,不限定于 public 的。

示例:

1
2
3
4
5
6
7
Class<?> personClass2 = ClassLoader.getSystemClassLoader().loadClass("com.example.basejava.reflect.Person");
Constructor constructor = personClass2.getConstructor();

Constructor[] constructors1 = String.class.getDeclaredConstructors();
for (Constructor c : constructors1) {
System.out.println(c);
}

获取成员变量

获取Field对象即可:

1
2
Method setNameMethod = clazz.getMethod("setName", String.class);
Method getNameMethod = clazz.getMethod("getName");

获取成员方法

获取Method方法即可:

1
2
Method[] methods1 = System.class.getDeclaredMethods();
Method[] methods2 = System.class.getMethods();

反射Demo

创建一个基本的Person类:

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package com.example.basejava.reflect;

import lombok.Data;

import java.util.Date;

/**
* @Author yamon
* @Date 2023-07-13 14:46
* @Description
* @Version 1.0
*/
public class Person {
private String id;
private String name;
private Integer age;
private String gender;
private Date birthDate;

private void privateMethod(){
System.out.println("这是private方法");
}

public Person(String id, String name, Integer age, String gender, Date birthDate) {
this.id = id;
this.name = name;
this.age = age;
this.gender = gender;
this.birthDate = birthDate;
}

public Person() {
}

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getName() {
System.out.println("执行getName方法");
return name;
}

public void setName(String name) {
System.out.println("执行set的方法,name属性值为:" + name);
this.name = name;
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}

public String getGender() {
return gender;
}

public void setGender(String gender) {
this.gender = gender;
}

public Date getBirthDate() {
return birthDate;
}

public void setBirthDate(Date birthDate) {
this.birthDate = birthDate;
}
}

测试:

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
package com.example.basejava.reflect;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
* @Author yamon
* @Date 2023-07-13 14:47
* @Description
* @Version 1.0
*/
public class Main {
public static void main(String[] args) {
// 获取反射对象
// Class<Person> personClass = Person.class;
Person person = new Person();
try {
// Class<?> aClass = Class.forName("com.example.basejava.reflect.Person");
// Person person = new Person();
// Class<? extends Person> personClass1 = person.getClass();
// 通过类加载器获取Class对象
Class<?> personClass2 = ClassLoader.getSystemClassLoader().loadClass("com.example.basejava.reflect.Person");
// 执行getName方法
Method getName = personClass2.getMethod("getName");
getName.invoke(personClass2.newInstance());
// 执行setName方法
Method setName = personClass2.getMethod("setName", String.class);
// 执行invoke方法必须先实例化,实例化方式有两种,第一种通过Class对象的newInstance;
setName.invoke(personClass2.newInstance(), "chenym");
// 第二种,自己new一个对象;
setName.invoke(person, "通过new实例后的setName:chenym");
// 执行private方法,无参的私有方法
Method privateMethod = personClass2.getDeclaredMethod("privateMethod");
privateMethod.setAccessible(true);
privateMethod.invoke(personClass2.newInstance());
} catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException | ClassNotFoundException |
InstantiationException e) {
throw new RuntimeException(e);
}
}
}

注:getDeclaredMethod:获取当前类的所有声明的方法,包括public、protected和private修饰的方法。需要注意的是,这些方法一定是在当前类中声明的,从父类中继承的不算,实现接口的方法由于有声明所以包括在内。

getMethod:获取当前类和父类的所有public的方法。这里的父类,指的是继承层次中的所有父类。比如说,A继承B,B继承C,那么B和C都属于A的父类。

执行结果:

1
2
3
4
执行getName方法
执行set的方法,name属性值为:chenym
执行set的方法,name属性值为:通过new实例后的setName:chenym
这是private方法

参考

Java 反射机制详解

getDeclaredMethod、getMethod

Java 反射详解:动态创建实例、调用方法和访问字段

强烈推荐!!!深入理解Java反射和动态代理