这句话是面试中的金句:“反射是框架的灵魂。”
1. 什么是反射?(核心定义)
一句话总结: 反射机制允许程序在运行状态 (Runtime) 中,对于任意一个类,都能够知道它有哪些属性和方法;对于任意一个对象,都能够调用它的任意方法和属性。
- 动态性: 这种动态获取信息、动态调用对象功能的能力就是反射。
- 本质: 也就是在运行时解剖
.class文件对象。

2. 反射能做什么?(主要功能)
通过反射 API,我们可以做到平时用 new 做不到的事情:
- 运行时获取类信息: 比如类名、包名、父类、接口。
- 动态创建对象: 即使编译时不知道类名,也能在运行时通过字符串(类全名)创建对象。
- 动态调用方法: 包括调用私有方法。
- 动态修改属性: 包括修改私有字段的值。
3. 反射的应用场景 (面试必问)
面试官常问:“你平时写代码哪里用到了反射?” 如果是写业务代码,可能很少直接用,但你用的所有框架底层都在用。
- 场景一:JDBC 加载驱动 (经典案例)
- 代码:
Class.forName("com.mysql.cj.jdbc.Driver"); - 原理: 根据字符串动态加载数据库驱动类。这样如果想换数据库(比如换成 Oracle),只需要修改配置文件中的字符串,而不需要修改 Java 代码。
- 代码:
- 场景二:Spring 框架 (IOC/DI)
- 原理: Spring 通过 XML 或注解配置 Bean。
- 流程: Spring 读取配置文件 → 获取类名字符串 → 通过反射 (
Class.forName) 加载类 → 通过反射 (newInstance) 实例化对象 → 通过反射 (setXxx) 注入属性。 - 没有反射,Spring 就无法实现“依赖注入”和“控制反转”。
- 场景三:AOP (动态代理)
- Java 的动态代理(
Proxy.newProxyInstance)底层也是基于反射实现的。
- Java 的动态代理(
4. 核心 API 类 (简单了解)
Class类: 反射的入口,代表类的实体。Field类: 代表类的成员变量(可暴力破解setAccessible(true))。Method类: 代表类的方法(核心方法invoke())。Constructor类: 代表类的构造方法。
总结表格
| 特性 | 说明 |
|---|---|
| 一句话定义 | 运行时动态获取类信息和调用对象方法的能力 |
| 核心价值 | 解耦、动态性 (让代码更加灵活) |
| 最大缺点 | 性能开销大 (涉及动态解析)、破坏封装性 (可访问 private) |
| 主要应用 | Spring (IOC/AOP)、MyBatis、JDBC 驱动加载、JUnit 测试框架 |
在面试中,注解通常和反射一起考,面试官主要想问:“注解到底是怎么起作用的?它只是一个注释吗?”
1. 注解的本质是什么?(原理核心)
一句话总结: 注解本质上是一个 继承了 java.lang.annotation.Annotation 接口的特殊接口。
- 反编译证据: 如果你把注解类(
@interface)反编译,会发现它其实就是public interface MyAnnotation extends Annotation。 - 实现原理: Java 运行时 (Runtime) 生成的注解对象,其实是一个 JDK 动态代理类。
- 当你通过反射获取注解对象时(如
getAnnotation),JVM 给你的其实是一个代理对象。 - 当你调用注解里的方法(如
value())时,其实是调用了AnnotationInvocationHandler的invoke方法,它会从一个 Map (memberValues) 中取出对应的值。
- 当你通过反射获取注解对象时(如
2. 注解的生命周期 (@Retention)
这是面试中最基础的考点。Java 通过元注解 @Retention 定义了注解能活多久:
- 源码级 (SOURCE): 只存在于
.java源文件中,编译成.class后就没了。- 例子:
@Override,@SuppressWarnings。给编译器看的,看完就扔。
- 例子:
- 类文件级 (CLASS - 默认): 保留在
.class文件中,但 JVM 加载时会被忽略,运行时无法获取。- 例子: Lombok 的注解(在编译期修改字节码)。
- 运行时 (RUNTIME): 一直保留到运行时,可以通过反射获取。
- 例子: Spring 的
@Controller,@Autowired。这是我们在开发框架中最常用的。
- 例子: Spring 的
3. 注解是如何被解析的?(工作流程)
注解本身只是一个“标签”,不起任何逻辑作用。它必须搭配 反射 或 编译器插件 来使用。
- 编译器解析 (针对 SOURCE/CLASS): 比如 Lombok,利用
JSR 269插件化注解处理 API,在编译期扫描注解并修改抽象语法树 (AST),自动生成 getter/setter 代码。 - 运行时解析 (针对 RUNTIME): 比如 Spring。
- JVM 启动,加载类文件。
- 读取
.class文件中的属性表(Attribute Table),里面存储了RuntimeVisibleAnnotations。 - 框架通过反射 API (如
class.getAnnotation()) 读取这些信息。 - 根据注解的配置执行相应的逻辑(比如看到
@Service就把这个类实例化并放入容器)。
总结表格
| 维度 | 说明 |
|---|---|
| 本质 | 特殊接口 (extends Annotation) |
| 底层实现 | JDK 动态代理 |
| 核心依赖 | 反射机制 (运行时解析) 或 编译插件 (编译时解析) |
| 最常用类型 | RUNTIME (只有这种才能被 Spring 等框架在运行时读取) |
4. Java注解的作用域
通俗理解:注解的“粘贴规则”
我们在现实生活中贴标签是有规则的:
- “易碎品”的标签,通常贴在箱子上(相当于贴在字段 Field 上)。
- “推/拉”的标签,通常贴在门上(相当于贴在方法 Method 上)。
- “总经理室”的标签,通常贴在房间上(相当于贴在类 Type 上)。
Java 的
@Target就是制定这个“粘贴规则”的管理员。 如果你定义了一个注解,却没写@Target,那么这个注解就像个“万能贴”,想贴哪就贴哪(类、方法、变量都能贴)。但通常为了规范,我们会限制它只能贴在特定的地方。
常见的“位置” (ElementType)
在代码中,这个规则是由 ElementType 枚举定义的。
1. ElementType.TYPE —— 贴在“大门”上
- 位置: 类、接口、枚举 (Enum)。
- 通俗理解: 这是最高级别的标签,说明这个标签是管整个“房间”的。
- 例子:
@Controller(Spring),@Table(数据库表名)。 Java@Controller // <--- 贴在类上 (TYPE) public class UserController { ... }
2. ElementType.METHOD —— 贴在“动作”上
- 位置: 方法。
- 通俗理解: 说明这个标签是管具体的某个行为的。
- 例子:
@GetMapping,@Override,@Test。 Java@GetMapping("/login") // <--- 贴在方法上 (METHOD) public void login() { ... }
3. ElementType.FIELD —— 贴在“私人物品”上
- 位置: 成员变量 (字段)。
- 通俗理解: 说明这个标签是管具体的数据的。
- 例子:
@Autowired(注入),@Value(配置值),@Id(主键)。 Java@Autowired // <--- 贴在变量上 (FIELD) private UserService userService;
4. ElementType.PARAMETER —— 贴在“参数”上
- 位置: 方法的入参。
- 通俗理解: 说明这个标签是管别人递进来的东西的。
- 例子:
@RequestParam,@PathVariable。 Java// vvvvvvvvvvvvvvv 贴在参数上 (PARAMETER) public void login(@RequestParam String name) { ... }
总结
当你看到面试题问 “注解的作用域” 时,如果他指的是 @Target,那他在问你:
“请告诉我,Java 允许把标签贴在哪些地方?”
一句话回答:
“注解的作用域由 @Target 决定。最常用的是 TYPE(类)、METHOD(方法)和 FIELD(字段)。这就像是规定了便利贴只能贴在门上、桌子上,还是杯子上,防止乱贴导致程序逻辑混乱。”