元数据卡
- 前置知识:第7章(面向对象基础)、第8章(继承与多态)
- 预计时间:70 分钟
- 核心难度:
- 阅读模式: 高度专注
- 完成标志:能读懂
@Override/@Deprecated/@SuppressWarnings;能自定义注解并配合反射读取;能通过反射获取类的方法和字段并动态调用
可选进阶章:注解与反射是框架底层技术,对于编程初学者可跳过。 你只需要知道三个标准注解(
@Override/@Deprecated/@SuppressWarnings)的用法即可。本章分层
- 必读(最多):
@Override/@Deprecated/@SuppressWarnings的使用场景- 选读:注解的定义语法(
@interface)、元注解@Target/@Retention- 深水区:反射 API(
Class.forName、getMethod、invoke)——框架原理的前置本章不会要求你掌握
- 自定义注解 + 反射处理器的完整实现(框架开发者才需要)
- Spring/MyBatis 的注解驱动原理
- 字节码操作库(ASM/Byte Buddy)
你在哪
阿花看你每天都在类上写 @Override 之类的符号,忍不住问:"那个 @ 到底是什么东西?"你张了张嘴,发现自己也说不太清楚。
你在变量村已经是一个熟练的工匠了。你能写类、建接口、搞继承、搞多态——面向对象三板斧滚瓜烂熟。
但有一个问题一直让你不安。
你写了一段代码,加了几个 @Override 注解,IDE 的绿色下划线就没再报错了。可是——这个 @ 符号到底是什么?它是代码吗?它编译到哪里去了?它怎么知道"检查父类"?如果你自己想创建一个类似 @MyAnnotation 的东西,然后写一个工具来读取它——这可能吗?
更狠的问题来了。
老陈师傅的图书馆系统已经做到第 500 个类了。每个类都有一个 saveToDatabase() 方法。某天上面要求:所有类的 saveToDatabase() 方法在调用前必须做日志记录——想一下,你该怎么做?
改 500 个类的 500 个方法?还是写一个工具,自动找到所有带某个标记的方法,然后给它们统一加上行为?
"你知道吗,"老陈师傅呷了一口茶,"Java 世界里有两种你还没真正接触的能力。第一种叫注解——给代码贴上标签。第二种叫反射——程序在运行时审视自己。"
"注解是标签,反射是镜子。两者合起来——元编程,也就是写代码的代码。整个 Spring 框架就是建立在这两样东西之上的。"
你的任务
这章分两半。前半段让你明白三件事:
- 注解不是什么魔法——它就是贴在类、方法、字段上的"标签"
- 标准库给了你三个最常用的标签:
@Override(检查重写)、@Deprecated(标记过时)、@SuppressWarnings(按住编译器别叫) - 你也能自己定义标签
后半段是反射——更深入的"元能力":
- 程序在运行时查看自己的结构——"我是一个什么类?我有哪些方法?"
- 动态调用——"这个方法我知道名字,我能在运行时调用它"
- 反射 + 注解 = 框架的基础(包括 Spring)
等你走完这章,你会发现:注解+反射=Java 世界最强大的模式之一。以前你以为框架是黑魔法,现在你将看到它其实就是几个注解定义 + 几个反射方法。
遭遇战 → 获得技能
第一幕:贴标签的艺术——标准注解
先感受一下最简单的注解——不需要自己写代码定义,Java 已经帮你准备好了。
@Override——"我确认我在覆写父类方法"
class Animal {
public void speak() {
System.out.println("Animal speaks");
}
}
class Dog extends Animal {
@Override
public void speak() { // 如果没有 @Override,这个方法只是"正好同名"
System.out.println("Woof!");
}
@Override
public void bark() { // ❌ 编译错误!Animal 没有 bark() 方法
System.out.println("Bark!");
}
}语言:Java 5+ 预期输出:编译报错——method does not override or implement a method from a supertype发生了什么:@Override 告诉编译器"我准备覆写父类方法"。如果名字拼错了或者签名不对,编译器直接报错——而不是悄无声息地新建一个方法。
为什么需要它? 没有
@Override时,你本想说"覆写"却不小心写成了"重载"(参数类型不同),编译器不会报错,你也不会意识到父类的方法根本没被调用。@Override是你的安全带。
@Deprecated——"这东西老了,别用了"
public class OldLibrary {
@Deprecated
public void oldMethod() {
System.out.println("这个方法已经过时,请用 newMethod() 替代");
}
public void newMethod() {
System.out.println("新方法,强烈推荐");
}
}
// 使用者:
public class Client {
public static void main(String[] args) {
OldLibrary lib = new OldLibrary();
lib.oldMethod(); // 编译时出现警告:OldLibrary.oldMethod() 已过时
lib.newMethod(); // OK,没有警告
}
}语言:Java 5+ 如何看到效果:编译时终端出现 deprecation warning;IDE 里 oldMethod() 会被画上中划线 发生了什么:@Deprecated 告诉使用者"这个元素过时了,考虑使用替代方案"。它不会阻止你使用——但你会收到警告。
Javadoc 版本(更规范的做法——包含替代方案提示):
/**
* 老旧的图书查询方法
*
* @deprecated 从 v2.0 起不再推荐使用。
* 请使用 {@link #searchByISBN(String)} 替代。
*/
@Deprecated
public Book searchBook(String keyword) {
return searchLegacy(keyword);
}
public Book searchByISBN(String isbn) {
return searchEngine(isbn);
}@SuppressWarnings——"编译器,安静"
import java.util.ArrayList;
import java.util.List;
public class SuppressDemo {
@SuppressWarnings("unchecked")
public void processRawList() {
List rawList = new ArrayList(); // 原始类型(raw type)——通常会有警告
rawList.add("hello"); // 传统上会警告 unchecked 操作
rawList.add(42);
List<String> safeList = (List<String>) rawList; // unchecked cast
System.out.println(safeList.get(0)); // hello
}
@SuppressWarnings("all") // 压制所有警告——慎用!
public void ignoreAllWarnings() {
// 这里所有警告都不会出现
}
}语言:Java 5+
常见压制参数:
| 参数值 | 压制什么 |
|---|---|
"unchecked" | 未检查的转换(raw type、unchecked cast) |
"deprecation" | 使用了已过时元素 |
"all" | 所有警告 |
"serial" | 缺少 serialVersionUID(仅对 Serializable 类) |
"fallthrough" | switch 中忘记 break(fall-through) |
使用原则:
@SuppressWarnings就像一个"我知道我在做什么"的声明——你确实应该知道。不要为了消灭一个警告而盲目压制所有警告。
🪟 差异窗口
C++ 中的等同概念:
[[deprecated]](C++14):等价于@Deprecated[[nodiscard]](C++17):告诉编译器返回值不应被丢弃——Java 没有完全等同- C++ 没有
@Override的严谨等价物——C++11 的override关键字也是写在方法签名后cpp// C++11 override class Dog : public Animal { void speak() override final; // override 关键字 };Python 中的等同概念:
- Python 没有强制的
@Override——但 Python 标准库也没有内置的override装饰器。社区实现了第三方库(typing没有Override直到 Python 3.12 才加了typing.override)warnings.warn(DeprecationWarning()):等价于@Deprecated- Python 的
@property、@staticmethod、@classmethod是 Python 的"注解"——但它们是解释器级别的装饰器,和 Java 的注解完全不同的机制
🧗 进阶内容:自定义注解是框架开发者的工具。 普通应用开发者只需要会用标准注解,不需要自己定义。
第二幕:造自己的标签——自定义注解
标准注解只有几个。但你真正需要的是:我能在自己的代码里创建注解,然后写代码来读它们。
先看如何定义一个注解:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 定义一个注解:用于标记需要做日志的方法
@Target(ElementType.METHOD) // 这个注解只能用在方法上
@Retention(RetentionPolicy.RUNTIME) // 注解信息保留到运行时(能被反射读取)
public @interface Loggable {
// 注解的属性(看起来像方法,实际上是配置)
String level() default "INFO";
boolean includeParams() default true;
}语言:Java 5+ 语法拆解:
@interface—— 不是"接口",是"注解类型"的关键字@Target—— 元注解(注解的注解):声明本注解可以贴在什么上面(方法、类、字段……)@Retention—— 元注解:声明注解的生命周期level()—— 注解的属性,使用时可提供值,有默认值
元注解速查表:
| 元注解 | 说明 |
|---|---|
@Target | 注解可以贴在哪儿:METHOD / FIELD / TYPE(类/接口)/ PARAMETER / CONSTRUCTOR / ANNOTATION_TYPE |
@Retention | 注解保留到什么阶段:SOURCE(编译期丢弃,如 @Override)/ CLASS(编译进字节码,运行时不加载)/ RUNTIME(运行时可反射读取) |
@Documented | 注解是否出现在 Javadoc 中 |
@Inherited | 子类是否继承父类的该注解 |
一旦你定义了一个 @Loggable,你就能这样用:
public class OrderService {
@Loggable(level = "DEBUG") // 只指定 level
public void placeOrder(String userId, String itemId) {
System.out.println("下单中...");
}
@Loggable(level = "ERROR", includeParams = false) // 指定两个属性
public void cancelOrder(String orderId) {
System.out.println("取消订单中...");
}
@Loggable // 全部使用默认值(level=INFO)
public void queryOrder(String orderId) {
System.out.println("查询订单中...");
}
}注解属性的数据类型限制:
- 基本类型(
int,long,boolean等) StringClass(如Class<?>)- 枚举(
enum) - 注解类型
- 以上类型的数组
public @interface AuthorInfo {
String name();
int version() default 1;
String[] reviewers() default {};
Class<?> relatedEntity() default Void.class;
}🪟 差异窗口
Python 装饰器 vs Java 注解: Python 的装饰器是可运行的代码——
@app.route("/api")是在导入时被执行并注册路由。而 Java 注解是元数据——它本身不执行任何代码,需要其他代码(通常是反射)来读取它。python# Python 装饰器——它在导入时真正执行 from functools import wraps def loggable(level="INFO"): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): print(f"[{level}] 调用 {func.__name__}") return func(*args, **kwargs) return wrapper return decorator @loggable(level="DEBUG") def place_order(user_id, item_id): print("下单中...")这里的
@loggable("DEBUG")实际执行了一个函数调用——它返回一个包装函数,替换了原来的place_order。这是真正的行为注入,远比 Java 注解"元数据 + 反射"的组合更直接,但也更侵入——你无法轻易获取原函数。Python 没有"元注解"的概念——装饰器就是好用的高阶函数。
🧗 进阶内容:反射是框架原理的基石,但日常开发中极少直接使用。 知道"Java 可以在运行时检视自己的类和方法的元信息"就足够了。
第三幕:照镜子——反射 API
注解的定义和使用只是"贴标签"的那一部分。真正的大招是——在运行时读取这些标签。
这就是反射。Java 提供的让你在运行时查看和操作类结构的能力。
获取 Class 对象的三种方式:
public class ReflectionDemo {
public static void main(String[] args) throws Exception {
// 方式1:通过对象获取(运行时)
String str = "hello";
Class<?> strClass1 = str.getClass();
// 方式2:通过类字面量(编译期确定)
Class<?> strClass2 = String.class;
// 方式3:通过全限定类名(最灵活——可以写死在配置文件里)
Class<?> strClass3 = Class.forName("java.lang.String");
// 这三种方式拿到的是同一个 Class 对象
System.out.println(strClass1 == strClass2); // true
System.out.println(strClass2 == strClass3); // true
// Class 对象能告诉你关于这个类的信息
System.out.println("类名: " + strClass1.getName()); // java.lang.String
System.out.println("简单类名: " + strClass1.getSimpleName()); // String
System.out.println("包名: " + strClass1.getPackage()); // package java.lang
System.out.println("是否是接口: " + strClass1.isInterface()); // false
System.out.println("父类: " + strClass1.getSuperclass()); // class java.lang.Object
}
}语言:Java 预期输出:
true
true
类名: java.lang.String
简单类名: String
包名: package java.lang
是否是接口: false
父类: class java.lang.Object核心理解:
Class<?>是一个描述"类的类"。对于 JVM 加载的每一个类,堆里只有一个对应的Class对象。它就像是这个类的"档案袋"——里面装着类名、包名、构造方法、字段、方法……一切信息。
查看一个类的结构——获取方法和字段:
import java.lang.reflect.Method;
import java.lang.reflect.Field;
import java.util.Arrays;
class Person {
private String name;
public int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
private void secretMethod() {
System.out.println("这是一个私有方法");
}
}
public class ReflectionInspect {
public static void main(String[] args) {
Class<?> clazz = Person.class;
System.out.println("=== 获取所有公有方法(含父类的)===");
Method[] publicMethods = clazz.getMethods();
for (Method m : publicMethods) {
System.out.println(" " + m.getName() + "(" +
Arrays.toString(m.getParameterTypes()) + ")");
}
System.out.println("\n=== 获取所有本类声明的字段(含私有的)===");
Field[] allFields = clazz.getDeclaredFields();
for (Field f : allFields) {
System.out.println(" " + f.getType().getSimpleName() + " " + f.getName());
}
}
}语言:Java 预期输出:
=== 获取所有公有方法(含父类的)===
getName([class [Ljava.lang.Object;])
wait([long, int])
wait([long])
wait([])
hashCode([])
equals([class java.lang.Object])
toString([])
getClass([])
notify([])
notifyAll([])
=== 获取所有本类声明的字段(含私有的)===
String name
int age关键区别:
getMethods()—— 返回所有 public 方法,包含从父类继承的getDeclaredMethods()—— 返回本类声明的所有方法,不管访问修饰符,但不包含继承的getFields()/getDeclaredFields()—— 同理
🪟 差异窗口
Python 的反射: Python 的反射更直接——一切都是对象,属性和方法就是字典里的键:
pythonclass Person: def __init__(self, name, age): self.name = name self.age = age def get_name(self): return self.name p = Person("Alice", 25) # 获取所有实例属性 print(p.__dict__) # {'name': 'Alice', 'age': 25} # 检查是否有某个属性 print(hasattr(p, 'name')) # True print(getattr(p, 'name')) # Alice # 获取类的方法 print(type(p).__dict__) # {'__init__': ..., 'get_name': ..., ...}Python 没有
private的真正限制——__dict__暴露了一切。C++ 的反射: C++ 没有内置反射——不能运行时获取类的字段列表。这是 Java 和 Python 的优势。C++ 需要用 RTTI(
typeid)获取类型信息,但仅限于类型识别,不能枚举字段和方法。
第四幕:动态调用——真正的"元编程"
现在你拿到了一个 Class 对象,知道了类的方法有哪些。下一步更刺激:在不知道类名和方法名的情况下,运行时创建一个对象并调用它的方法。
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class DynamicInvokeDemo {
public static void main(String[] args) throws Exception {
// 情景:我们只知道某个类在配置文件里叫 "java.util.ArrayList"
// 我们要动态创建它的实例,并调用 add() 方法
// 1. 获取 Class 对象
Class<?> clazz = Class.forName("java.util.ArrayList");
// 2. 获取无参构造方法,创建实例
Constructor<?> constructor = clazz.getConstructor();
Object list = constructor.newInstance(); // 等价于 new ArrayList<>()
// 3. 获取 add 方法(类型签名:add(Object))
Method addMethod = clazz.getMethod("add", Object.class);
// 4. 动态调用——传入对象和参数
addMethod.invoke(list, "反射添加的内容");
addMethod.invoke(list, "第二条");
// 5. 获取 size 方法调用
Method sizeMethod = clazz.getMethod("size");
Object sizeResult = sizeMethod.invoke(list); // 返回 Object 类型
System.out.println("列表大小: " + sizeResult); // 2
System.out.println("列表内容: " + list); // [反射添加的内容, 第二条]
}
}语言:Java 预期输出:
列表大小: 2
列表内容: [反射添加的内容, 第二条]关键点:
| 步骤 | 反射 API | 等价常规代码 |
|---|---|---|
| 获取类 | Class.forName("java.util.ArrayList") | — |
| 获取构造方法 | clazz.getConstructor() | new ArrayList() |
| 获取方法 | clazz.getMethod("add", Object.class) | 编译期已知 |
| 调用方法 | addMethod.invoke(list, "内容") | list.add("内容") |
| 私有方法? | clazz.getDeclaredMethod("secret") + method.setAccessible(true) | 无法访问 |
调用私有方法——反射的"越权"能力:
public class PrivateAccessDemo {
public static void main(String[] args) throws Exception {
Person p = new Person("Alice", 25);
Class<?> clazz = p.getClass();
// 获取私有方法
Method secretMethod = clazz.getDeclaredMethod("secretMethod");
// 没有这行会报 IllegalAccessException
secretMethod.setAccessible(true);
// 调用
secretMethod.invoke(p); // 这是一个私有方法
}
}语言:Java 预期输出:
这是一个私有方法
setAccessible(true)的代价:它绕过了 Java 的访问控制——从封装角度看是不安全的。框架(如 Spring、Jackson)会因为"我需要访问所有字段"而使用它,但你的业务代码中应该尽量避免。
第五幕:注解 + 反射 = 框架的基础
现在你把两样东西合在一起——用注解贴标签,用反射读标签做事情。
这是我们之前定义的 @Loggable 注解,配合一个"注解处理器"来使用:
import java.lang.reflect.Method;
public class LoggableProcessor {
// 这个方法"处理"一个对象中所有被 @Loggable 标注的方法
public static void processLoggable(Object obj, String methodName, Object... args) throws Exception {
Class<?> clazz = obj.getClass();
// 遍历所有方法
for (Method method : clazz.getDeclaredMethods()) {
if (method.getName().equals(methodName) && method.isAnnotationPresent(Loggable.class)) {
// 读取注解
Loggable loggable = method.getAnnotation(Loggable.class);
// 获取注解中的属性值
String level = loggable.level();
boolean includeParams = loggable.includeParams();
// 执行日志逻辑
System.out.println("[" + level + "] 开始执行 " + methodName);
if (includeParams && args.length > 0) {
System.out.println(" 参数: " + java.util.Arrays.toString(args));
}
// 调用原方法
method.invoke(obj, args);
System.out.println("[" + level + "] " + methodName + " 执行完毕");
return;
}
}
// 没有注解则直接调用
Method method = clazz.getMethod(methodName,
java.util.stream.Stream.of(args).map(Object::getClass).toArray(Class[]::new));
method.invoke(obj, args);
}
public static void main(String[] args) throws Exception {
OrderService service = new OrderService();
// 通过处理器调用——会自动检查 @Loggable
processLoggable(service, "placeOrder", "user001", "item999");
System.out.println("---");
processLoggable(service, "queryOrder", "order123");
}
}语言:Java 预期输出:
[DEBUG] 开始执行 placeOrder
参数: [user001, item999]
下单中...
[DEBUG] placeOrder 执行完毕
---
[INFO] 开始执行 queryOrder
查询订单中...
[INFO] queryOrder 执行完毕这就是 AOP 的原型:面向切面编程(Aspect-Oriented Programming)。你不需要在每个方法里手动写日志代码——你只贴一个
@Loggable标签,然后一个"拦截器"自动在所有带这个标签的方法前后执行日志逻辑。Spring 的原理:Spring 容器启动时,扫描所有类,检查哪些类/方法上有
@Transactional、@GetMapping之类的注解,然后动态创建代理对象,在调用这些方法时插入事务管理、请求路由等逻辑。
Spring 注解快速入门:
import org.springframework.web.bind.annotation.*;
@RestController // 标注这是一个 API 控制器(REST 指 Web 接口的设计风格,暂不深究)
@RequestMapping("/api/books") // 映射基准 URL 路径
public class BookController {
@Autowired // 让 Spring 自动注入依赖(省去 new)
private BookService bookService;
@GetMapping("/{id}") // 映射 GET 请求 + URL 路径参数
public Book getBook(@PathVariable Long id) {
return bookService.findById(id);
}
@PostMapping // 映射 POST 请求
public Book createBook(@RequestBody Book book) {
return bookService.save(book);
}
@Transactional // 这个方法运行在数据库事务中
public void transferBooks(Long fromId, Long toId) {
// 如果这里抛出异常,数据库操作会自动回滚
}
}语言:Java + Spring 你能看懂的部分:注解是标签。@GetMapping 把 URL 和方法绑定。@Autowired 告诉 Spring "我需要这个对象,你帮我初始化"。它们都是基于"自定义注解 + 反射扫描"实现的——你现在理解了底层原理!
🪟 差异窗口
Python 中 Spring 的等价物(Flask / FastAPI):
pythonfrom flask import Flask, request app = Flask(__name__) @app.route("/api/books/<int:id>", methods=["GET"]) # 装饰器:注册路由 def get_book(id): return book_service.find_by_id(id)Python 的装饰器在导入时就执行了路由注册。而 Java 的注解只是元数据,Spring 启动时通过反射扫描——性能更差但更灵活,因为你可以选择在运行时读或不读注解。
常见陷阱
目标:写一个迷你单元测试框架(测试运行器)。
需求:
- 定义一个
@Test注解(仅能被放在方法上,保留到运行时) - 定义一个
@BeforeEach注解——在运行@Test方法前自动执行 - 写一个
TestRunner类,接收一个测试类,扫描其中的@BeforeEach和@Test方法并执行 - 统计测试通过/失败个数
提示:
// @Test 注解定义
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {}
// 测试示例
public class MyTest {
@BeforeEach
public void setup() { System.out.println("准备测试环境"); }
@Test
public void testAdd() {
System.out.println("测试加法: 1+1=2");
// 如果失败,抛 AssertionError
throw new AssertionError("加法测试失败");
}
@Test
public void testSubtract() {
System.out.println("测试减法: 2-1=1");
}
}预期输出:
准备测试环境
测试加法: 1+1=2
❌ testAdd 失败: 加法测试失败
准备测试环境
测试减法: 2-1=1
✅ testSubtract 通过
结果: 1/2 通过通关挑战
场景:你在一个只能通过网络发送类名的系统上工作——你需要写一个"远程方法调用器",客户端传类名和方法名,服务端动态调用并返回结果。
public class RemoteInvoker {
/**
* 接收远程调用的类全名、静态方法名和参数,动态调用并返回结果
* 要求:
* 1. 支持调用本机任意类的静态方法(通过 Class.forName)
* 2. 方法返回值统一转为 String 返回
* 3. 如果方法不存在,返回 "ERROR: 方法不存在"
* 4. 如果方法抛出异常,返回 "ERROR: " + 异常信息
*/
public static String invoke(String className, String methodName, String... args) {
// 你的代码在这里
return "";
}
// 测试场景
public static void main(String[] args) {
// 调用 Math.max(10, 20)
System.out.println(invoke("java.lang.Math", "max", "10", "20")); // 期望 "20"
// 调用 Math.sqrt(16)
System.out.println(invoke("java.lang.Math", "sqrt", "16")); // 期望 "4.0"
// 调用一个不存在的方法
System.out.println(invoke("java.lang.Math", "nonexistent")); // 期望 "ERROR: 方法不存在"
}
}进阶要求:
- 支持不同类型参数(Integer.parseInt、Double.parseDouble 等)
- 支持非静态方法(需要额外传入构造函数参数)
验收标准
完成本章后,你应该能回答:
- [ ]
@Override的作用是什么?不加它编译会出错吗? - [ ]
@Deprecated和@SuppressWarnings分别在什么场景使用? - [ ]
@Target和@Retention是什么?RetentionPolicy.RUNTIME和RetentionPolicy.SOURCE有什么区别? - [ ] 如何定义一个带属性的自定义注解?属性类型有哪些限制?
- [ ] 获取
Class对象的三种方式分别是什么? - [ ]
getMethods()和getDeclaredMethods()的区别? - [ ] 如何通过反射调用一个私有方法?
- [ ]
setAccessible(true)做了什么?为什么框架需要它? - [ ] 注解 + 反射如何组合使用?能举一个实际的框架例子吗?
- [ ] Spring 的
@Autowired大致原理是什么?
常见卡点
"我定义的注解运行时读不到" → 99% 的原因是忘了加 @Retention(RetentionPolicy.RUNTIME)。默认是 RetentionPolicy.CLASS——编译时在字节码里,但 JVM 加载后不保留。
"Class.forName() 抛 ClassNotFoundException" → 检查全限定类名是否写对了——java.util.ArrayList 不是 java.util.ArrayList(别少拼),并且类必须在 classpath 上。
"getMethod 抛 NoSuchMethodException" → 检查方法名是否拼对,并且参数类型是否匹配。getMethod("add", Object.class) 和 getMethod("add", String.class) 是不同的——后者要求方法签名精确匹配。
"invoke 抛 IllegalAccessException" → 方法不是 public。用 getDeclaredMethod() 获取,然后调用 method.setAccessible(true)。
"用反射调用静态方法时 invoke 的第一个参数传 null" → 正确。method.invoke(null, args...)——因为静态方法不需要实例。
"为什么 Spring 里我的字段加了 @Autowired 还是 null?" → 反射通过 setAccessible(true) 注入了值,但那个对象必须是 Spring 管理的(由 Spring 容器创建)。自己 new 的对象不会被 Spring 处理。
现在不需要理解
- 动态代理(
Proxy.newProxyInstance):AOP 的底层实现之一——但目前先理解"注解+反射读"就够了 - ASM / Byte Buddy / Javassist:字节码操作库,在编译后修改 .class 文件——Spring 用到它们,但你不需要现在深究
AnnotatedElement接口体系:Class、Method、Field都实现了它——理解isAnnotationPresent()就够了MethodHandle和VarHandle(Java 7+/9+):反射的高性能替代方案——比传统反射快,但在你碰到性能瓶颈前不急着学- 编译期注解处理(APT):在 javac 编译时生成代码——Lombok(
@Data)就是通过 APT 实现的。这块偏工具链,代码生成领域 - Spring AOP 的代理机制(CGLIB / JDK 动态代理):知道"Spring 通过反射 + 代理在注解周围插入逻辑"就可以了
旅人笔记
注解 = 代码的标签。贴上去,告诉别人(编译器、框架、工具)"我是谁"。
@Override 不会改变代码行为——它只是安全网,帮你捕获笔误。
@Deprecated 是善意的提醒——"别用我了,有更好的替代"。
@SuppressWarnings 是"我知道我在做什么"的声明——别滥用。
自定义注解 = @interface + @Target + @Retention。
元注解 = 注解的注解。@Target 说"贴哪",@Retention 说"活多久"。
RUNTIME 保留 = 反射可读。SOURCE 保留 = 编译时丢弃(如 @Override)。
反射 = 程序照镜子——看自己是什么类、有什么方法。Class 对象 = 类的档案袋。整个 JVM 里每种类只有一个 Class 对象。
getMethods() = 公有方法(含继承)。getDeclaredMethods() = 所有方法(不含继承)。
Method.invoke(对象, 参数) —— 动态调用。私有的需要 setAccessible(true)。
注解 + 反射 = Java 框架的基石 —— Spring、JPA、JUnit 都建立在这个组合上。
注解是设计时的声明,反射是运行时的发现——两者配合,代码就有了"自我意识"。
框架不再神奇了,对吧?
它就是一个循环:扫描类 → 读注解 → 干活。
一句话总结:元编程 = 让代码在运行时知道自己长什么样、贴了什么标签,并据此做出行为。
→ 下一站预告
注解让你给代码贴标签,反射让你在运行时检查代码——但这些都是"单线程世界"里的能力。
下一个大挑战来了。
你的图书管理系统上线了,运行良好。一天,一百个读者同时登录系统查询书目——系统突然变慢了。不是算法问题,是所有人都想读同一个文件。一个线程在写,另一个在读,数据就乱了。
你以前写的所有代码,都假设同一时间只有一个人在执行。现实世界不是这样的——多个操作在同一时刻发生。数据库连接、网络请求、用户操作——它们就像同时涌进大厅的人群,如果没有秩序,就会踩踏。
下一章:【第14章:并发编程基础】——从线程创建到 synchronized,从 volatile 到线程池,你将掌握让多个人同时做事的正确姿势。