这句话是面试中的金句:“反射是框架的灵魂。”

1. 什么是反射?(核心定义)

一句话总结: 反射机制允许程序在运行状态 (Runtime) 中,对于任意一个类,都能够知道它有哪些属性和方法;对于任意一个对象,都能够调用它的任意方法和属性。

  • 动态性: 这种动态获取信息、动态调用对象功能的能力就是反射。
  • 本质: 也就是在运行时解剖 .class 文件对象。

2. 反射能做什么?(主要功能)

通过反射 API,我们可以做到平时用 new 做不到的事情:

  1. 运行时获取类信息: 比如类名、包名、父类、接口。
  2. 动态创建对象: 即使编译时不知道类名,也能在运行时通过字符串(类全名)创建对象。
  3. 动态调用方法: 包括调用私有方法。
  4. 动态修改属性: 包括修改私有字段的值。

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)底层也是基于反射实现的。

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())时,其实是调用了 AnnotationInvocationHandlerinvoke 方法,它会从一个 Map (memberValues) 中取出对应的值。

2. 注解的生命周期 (@Retention)

这是面试中最基础的考点。Java 通过元注解 @Retention 定义了注解能活多久:

  1. 源码级 (SOURCE): 只存在于 .java 源文件中,编译成 .class 后就没了。
    • 例子: @Override, @SuppressWarnings。给编译器看的,看完就扔。
  2. 类文件级 (CLASS - 默认): 保留在 .class 文件中,但 JVM 加载时会被忽略,运行时无法获取。
    • 例子: Lombok 的注解(在编译期修改字节码)。
  3. 运行时 (RUNTIME): 一直保留到运行时,可以通过反射获取
    • 例子: Spring 的 @Controller, @Autowired。这是我们在开发框架中最常用的。

3. 注解是如何被解析的?(工作流程)

注解本身只是一个“标签”,不起任何逻辑作用。它必须搭配 反射编译器插件 来使用。

  • 编译器解析 (针对 SOURCE/CLASS): 比如 Lombok,利用 JSR 269 插件化注解处理 API,在编译期扫描注解并修改抽象语法树 (AST),自动生成 getter/setter 代码。
  • 运行时解析 (针对 RUNTIME): 比如 Spring。
    1. JVM 启动,加载类文件。
    2. 读取 .class 文件中的属性表(Attribute Table),里面存储了 RuntimeVisibleAnnotations
    3. 框架通过反射 API (如 class.getAnnotation()) 读取这些信息。
    4. 根据注解的配置执行相应的逻辑(比如看到 @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(字段)。这就像是规定了便利贴只能贴在门上、桌子上,还是杯子上,防止乱贴导致程序逻辑混乱。”