“JAVA反射机制详解”

什么是反射?

反射是与正射相对应的,正射就是在使用某个类之前就已经知道了这个类的类型等等,可以直接使用new关键字调用构造方法进行初始化,之后就可以正常使用这个类的的对象了
Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类中的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java 语言的反射机制。

反射的特点

  1. 运行时类信息访问:反射允许程序在运行时获取类的完结构信息,包括类名、包名、父类、实现的接口、构造函数、方法和字段等等
  2. 动态对象创建:可以使用反射API动态地创建对象实例,即使在编译时不知道具体的类名。这是通过Class类的newInstance()方法或Constructor对象的newnstance()方法实现的。
  3. 动态方法调用:可以在运行时动态地调用对象的方法,包括私有方法。这通过Method类的invoke()方法
    实现,允许你传入对象实例和参数值来执行方法。
    4.访问和修改字段值:反射还允许程序在运行时访问和修改对象的字段值,即使是私有的。这是通过Field类的qet()和set()方法完成的

反射的缺点

破坏封装:由于反射允许访问私有字段和私有方法,所以可能会破坏封装而导致安全问题
性能开销:由于反射涉及到动态解析,因此无法执行Java 虚拟机优化,再加上反射的写法的确要复杂得多,所以性能要比“正射”差很多,在一些性能敏感的程序中应该避免使用反射。

反射的应用场景

  1. 通用框架中加载配置文件:类似spring、springboot等,为了保持通用性,通过不同的配置文件来加载不同的对象,比如数据库驱动等等
  2. 动态代理:在面向切面编程中,需要拦截特定的方法,就会选择动态代理的方式,动态代理的底层实现就是利用了反射
  3. 注解:注解本身不会直接影响程序的执行,但可以通过反射机制在运行时获取和处理这些注解

反射的基本使用

在 Java 中,Class 对象是一种特殊的对象,它代表了程序中的类和接口。Java 中的每个类型(包括类、接口、数组以及基础类型)在 JVM 中都有一个唯一的 Class 对象与之对应。这个 Class 对象被创建的时机是在 JVM 加载类时,由 JVM 自动完成。Class 对象中包含了与类相关的很多信息,如类的名称、类的父类、类实现的接口、类的构造方法、类的方法、类的字段等等。这些信息通常被称为元数据(metadata)。

获取类的Class对象

共有三种方法:

  1. 通过类的全名获取Class对象
    1
    Class clazz = Class.forName("com.xxx");
  2. 通过 Class 对象获取构造方法 Constructor 对象
    1
    Constructor constructor = clazz.getConstructor();
  3. 通过 Constructor 对象初始化反射类对象
    1
    Object object = constructor.newInstance();
  4. 获取要调用的方法的 Method 对象
    1
    2
    Method setNameMethod = clazz.getMethod("setName", String.class);
    Method getNameMethod = clazz.getMethod("getName");
  5. 通过 invoke() 方法执行
    1
    2
    setNameMethod.invoke(object, "zhangsan");
    getNameMethod.invoke(object)

反射底层机制

使用反射,必须要获得反射类的Class对象,每一个类只有一个Class对象,无论这个类生成了多少个对象。这个Class对象是由JAVA虚拟机生成的,由它来获取这个类的所有结构信息
也就是说,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);
}

invoke() 方法实际上是委派给 MethodAccessor 接口来完成的

MethodAccessor 接口有三个实现类,其中的 MethodAccessorImpl 是一个抽象类,另外两个具体的实现类 NativeMethodAccessorImplDelegatingMethodAccessorImpl 继承了这个抽象类。
NativeMethodAccessorImpl:通过本地方法来实现反射调用;
DelegatingMethodAccessorImpl:通过委派模式来实现反射调用;

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

为什么不直接调用本地实现呢?

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

反射常用的API

Class.forName(),参数为反射类的完全限定名。

类名 + .class,只适合在编译前就知道操作的 Class。

获取构造器、字段、方法等

getConstructor():返回反射类的特定 public 构造方法,可以传递参数,参数为构造方法参数对应 Class 对象;缺省的时候返回默认构造方法。
getDeclaredConstructor():返回反射类的特定构造方法,不限定于 public 的。
getConstructors():返回类的所有 public 构造方法。
getDeclaredConstructors():返回类的所有构造方法,不限定于 public 的。
获取字段和方法都是类似,换成Field或者Method就行

注意,如果反射访问私有字段和(构造)方法的话,需要使用 Constructor/Field/Method.setAccessible(true) 来绕开 Java 语言的访问限制

注:参考javabetterxiaolincoding,侵删

作者

Zhangmingyu

发布于

2024-10-14

更新于

2025-06-04

许可协议

You need to set install_url to use ShareThis. Please set it in _config.yml.
You forgot to set the business or currency_code for Paypal. Please set it in _config.yml.

评论

You forgot to set the shortname for Disqus. Please set it in _config.yml.