说到反射,大家可能并不陌生。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
2
3
4
5
6
7
8
9
import org.springframework.stereotype.Component;

@Component // 标记为Spring管理的bean
public class MyService {
public void performAction() {
System.out.println("Action performed!");
}
}

然后你有一个需要MyService的类:

1
2
3
4
5
6
7
8
9
10
11
12
13
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Application {
@Autowired
private MyService myService; // 自动注入MyService

public void start() {
myService.performAction(); // 使用注入的MyService对象
}
}

在这个例子中:

@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访问类型的元信息(如ClassMethod等)。 通过Go的reflect包来访问类型和值的元信息。
Java依赖JVM在运行时维护的类型信息。 Go依赖编译时生成的运行时类型信息。
主要通过Class类和java.lang.reflect包进行反射操作。 主要通过reflect.Typereflect.Value进行反射操作。

比如 Go 里的结构体标签(tag):

1
2
3
type User struct {
Name string `json:"name"`
}

当我们用 json.Marshal() 把结构体转成 JSON 时,encoding/json 包会通过反射读取 tag 信息,然后决定用哪个字段名。

很多 Go 框架(如 Gin、GORM)也大量使用反射来实现自动映射、自动注入等功能。

5. 总结

反射其实没那么神秘——它就是在运行时,把“看似未知”的东西变成已知的,把“不可见”的类型和值变成可见并可操作的。它的实现依赖于语言编译器和运行时提供的元信息和接口。