一文把反射说得明明白白
说到反射,大家可能并不陌生。Java有这个概念,Go语言也有。但无论是官网还是各路教程,往往说得玄乎又抽象,让人看完还是一脸问号。今天我们就用最通俗的语言,把它说清楚。
先以 Java反射 举个例子。
1. 先搞明白 Java 运行时
Java 写好的代码要先编译成 .class
字节码文件,再由 Java 虚拟机(JVM)翻译成二进制机器码运行。JVM 可以根据不同操作系统适配成对应的机器码,这就是 Java 那句经典口号——“一次编写,到处运行”。
所谓 运行时,就是 Java 代码编译成 .class
后,在 JVM 里运行的那个状态。
2. 那反射跟运行时有什么关系?
反射的核心功能是:在运行时获取类的信息(方法名、字段、类型等),甚至调用它们,而不需要在代码里提前写死。
为什么能做到?
因为在编译 .java
文件成 .class
的时候,类的结构(方法名、参数类型、字段类型等)会被完整写进字节码文件里。JVM 不仅保存了这些信息,还提供了读取它们的 API。反射就是利用这些 API,在程序运行中获取到类型和值。
这就是反射的本质——运行时动态探测和操作类型信息。所以我们通过反射就不需要提前写死声明,他在运行时自己探测。
3. 框架为什么爱用反射?
知道了他的本质,我们再看它的应用场景,
举个 Java 框架的例子,比如 Spring Boot。
在我们自己写代码时,new
一个对象是显式写出来的:
1 | MyService s = new MyService(); |
而 Spring Boot 可以不用你手动写,就直接实例化一个对象并注入到类里。这背后就是反射——框架在运行时扫描类,通过反射找到可用的构造方法,然后动态 new
出对象。举个栗子:
假设你有如下的类:
1 | import org.springframework.stereotype.Component; |
然后你有一个需要MyService的类:
1 | import org.springframework.beans.factory.annotation.Autowired; |
在这个例子中:
@Component标记了MyService和Application类,Spring Boot会自动将它们注册为bean。
@Autowired标记在Application类的myService字段上,Spring会通过反射自动注入MyService的实例。
因此你并不需要显式地使用new MyService()来创建对象,Spring会在幕后通过反射来完成这一过程。
其中@Component和@Autowired就是 Java 里的注解。 通常它写在类、方法、字段前面,形如
@Something
,相当于一个标签,用来告诉框架“这里有特殊处理”。框架在启动时会扫描项目的所有类,通过反射获取它们的注解信息,然后按照注解规则去做事。
注解有两类: 1、标记类、方法、字段为bean,使得它们可以被Spring容器管理,如@Component ;2、注入其他bean到类的字段、方法或构造器中,帮助实现依赖注入,达到解耦目的,如@Autowired
说白了,框架的本质就是一堆通用功能的集合。用盖房子打比方:
- 以前我们写业务代码,得从零打地基、搬砖盖墙(搭建底层架构)
- 有了框架,就像开发商先帮你把毛坯房造好,你直接装修(写业务逻辑)就行
而反射,就是框架实现“自动适配你写的代码”的关键工具。
4. Go 语言里的反射
Go 的反射原理和 Java 非常像:
Java反射 | Go反射 |
---|---|
通过JVM的API访问类型的元信息(如Class 、Method 等)。 |
通过Go的reflect 包来访问类型和值的元信息。 |
Java依赖JVM在运行时维护的类型信息。 | Go依赖编译时生成的运行时类型信息。 |
主要通过Class 类和java.lang.reflect 包进行反射操作。 |
主要通过reflect.Type 和reflect.Value 进行反射操作。 |
比如 Go 里的结构体标签(tag):
1 | type User struct { |
当我们用 json.Marshal()
把结构体转成 JSON 时,encoding/json
包会通过反射读取 tag
信息,然后决定用哪个字段名。
很多 Go 框架(如 Gin、GORM)也大量使用反射来实现自动映射、自动注入等功能。
5. 总结
反射其实没那么神秘——它就是在运行时,把“看似未知”的东西变成已知的,把“不可见”的类型和值变成可见并可操作的。它的实现依赖于语言编译器和运行时提供的元信息和接口。