强者的自我修养免安装绿色中文版
4.29G · 2025-11-02
其实注解(Annotation)超简单,本质就是给代码贴的 “标签”—— 就像你给快递贴 “易碎品”,给文件标 “加急”,注解就是给类、方法、变量贴的 “说明符”。
它不直接影响代码运行,但能告诉编译器 / 框架:“喂,这个代码是干这个的!” 比如@Deprecated贴在方法上,编译器就会喊:“警告!这方法要被淘汰啦!”
JDK 早就给咱准备了一批常用注解,相当于 “现成的标签”,咱先盘点几个高频的:
| 注解 | 作用(人话版) | 场景举例 |
|---|---|---|
| @Override | 告诉编译器:“我这是重写父类的方法!” | 子类重写toString()方法 |
| @Deprecated | 标红警告:“这玩意儿要淘汰了,别用了!” | 老项目里的Date的toLocaleString() |
| @SuppressWarnings | 让编译器 “闭嘴”:“别报这个警告了,我知道我在干啥” | 用了老 API 不想看黄色警告 |
| @FunctionalInterface | 强制检查:“这接口必须是函数式接口(只有一个抽象方法)” | 写 Lambda 表达式时用 |
比如你写个类重写方法,没加@Override也能跑,但加了之后编译器会帮你检查 —— 要是父类根本没这方法,直接报错!这就是 “标签” 的力量~
光用现成的不够爽,咱也能自己造注解!记住,定义注解的语法像极了 “创建接口”,但要加个@前缀,还得配 “元注解”(后面讲)。
先看个最简单的自定义注解模板:
// 元注解(给注解加的注解,相当于“标签的标签”)
@Retention(RetentionPolicy.RUNTIME) // 控制注解能存活到什么时候
@Target(ElementType.METHOD) // 控制注解能贴在什么地方(这里是方法上)
public @interface MyFirstAnnotation {
// 注解的属性(相当于标签的“填写项”)
String value() default "默认值"; // 带默认值的属性
int version() required; // 必须填的属性(没有default)
}
划重点:定义注解用@interface,不是interface!别少写了@~
刚才定义注解时,前面加的@Retention、@Target就是 “元注解”—— 相当于给 “标签” 本身定规矩的 “管理员”。JDK 里就 4 个核心元注解,记牢这 4 个就够了:
注解的属性就像标签的 “填空栏”,比如你造个@Log注解,想让它支持 “日志级别”“描述”,就可以加属性:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Log {
// 1. 普通属性:类型 + 名称(),可加default给默认值
String level() default "INFO"; // 日志级别,默认INFO
// 2. value属性:特殊!使用时可以省略“value=”
String value() default "无描述"; // 日志描述
// 3. 数组属性:用{}表示多个值
String[] tags() default {}; // 日志标签,支持多值
}
public class UserService {
// 用value属性:可以省略“value=”
@Log("查询用户")
public User getUserById(Long id) { ... }
// 多属性:按名填写,数组用{}
@Log(level = "WARN", value = "删除用户", tags = {"敏感操作", "用户管理"})
public void deleteUser(Long id) { ... }
}
小技巧:如果属性是数组且只有一个值,连{}都能省,比如tags = "敏感操作"~
光说不练假把式,咱来写个能实际用的@Log注解,配合反射解析它,实现 “调用方法时自动打日志”。
@Retention(RetentionPolicy.RUNTIME) // 必须RUNTIME,反射才能拿到
@Target(ElementType.METHOD) // 只贴在方法上
public @interface Log {
String value() default "执行方法"; // 日志描述
String level() default "INFO"; // 日志级别
}
反射是解析注解的核心!因为只有反射能在运行时拿到类 / 方法上的注解信息:
public class LogUtils {
// 传入方法,解析它的@Log注解并打日志
public static void printLog(Method method) {
// 1. 判断方法上有没有@Log注解
if (method.isAnnotationPresent(Log.class)) {
// 2. 拿到注解对象
Log logAnnotation = method.getAnnotation(Log.class);
// 3. 获取注解的属性值,打日志
String level = logAnnotation.level();
String desc = logAnnotation.value();
System.out.printf("[%s] %s:%s%n", level, method.getName(), desc);
}
}
}
public class TestLog {
@Log(value = "查询用户列表", level = "DEBUG")
public void getUsers() { /* 模拟查用户 */ }
public static void main(String[] args) throws NoSuchMethodException {
// 拿到getUsers方法对象
Method method = TestLog.class.getMethod("getUsers");
// 解析注解并打日志
LogUtils.printLog(method);
// 输出结果:[DEBUG] getUsers:查询用户列表
}
}
看到没?这就是自定义注解的魅力 —— 以后想给方法加统一功能(比如日志、权限校验),用注解 + 反射就搞定!
前面说注解像标签,但你知道它的本质吗?其实注解编译后会变成接口,而且默认继承java.lang.annotation.Annotation!
比如刚才的@Log注解,编译后会变成这样(反编译结果):
public interface Log extends java.lang.annotation.Annotation {
String value();
String level();
}
所以你定义的注解属性,其实是接口的抽象方法;拿到的注解对象,是 JVM 动态生成的接口实现类 —— 是不是瞬间懂了?
注解的使用场景主要分两类,咱后端 er 最常用的就是第二种:
解析注解全靠java.lang.reflect包,关键就这几个方法,记下来够用了:
| 反射对象 | 核心方法 | 作用 |
|---|---|---|
| Class/Method/Field | isAnnotationPresent(Class<? extends Annotation> annoClass) | 判断是否有指定注解 |
| Class/Method/Field | getAnnotation(Class annoClass) | 拿到指定注解的对象 |
| Class/Method/Field | getAnnotations() | 拿到所有注解(包括继承的) |
| Annotation | annotationType() | 拿到注解的类型(比如 Log.class) |
最后咱聊点实战的:SpringBoot 里的注解为啥这么好用?其实都是基于前面讲的 “注解 + 反射” 原理!盘点几个高频注解,看完你就懂了:
比如你写个接口:
@RestController // 标记这是接口类
@RequestMapping("/user") // 接口前缀
public class UserController {
@Autowired // 自动注入UserService
private UserService userService;
@GetMapping("/{id}") // 接口地址:/user/123
public User getUser(@PathVariable("id") Long id) { // 拿路径里的id参数
return userService.getUserById(id);
}
}
现在再看这段代码,是不是瞬间明白:每个注解都是给 SpringBoot 的 “指令”,框架靠反射解析这些注解,才帮你完成了注入、接口映射这些工作!
其实注解的核心就三点:
看完这篇,下次再写@XXX的时候,别再只当它是 “符号” 了 —— 想想它的生命周期、作用范围,甚至自己造个注解解决问题,这才是后端 er 的进阶姿势!
最后问一句:你平时用注解踩过什么坑?比如没加@Retention(RUNTIME)导致解析不到?评论区聊聊,咱一起避坑~