咱后端写代码时,总少不了跟@XXX这种 "@符号怪" 打交道 ------ 比如加个@Override就敢改父类方法,写个@RestController接口就跑起来了。但你真的懂这些 "标签" 到底是啥吗?今天咱就把注解扒得明明白白,从基础到 SpringBoot 实战,看完保你再也不慌!
一、先搞懂:注解到底是个啥?------ 代码的 "说明书"
其实注解(Annotation)超简单,本质就是给代码贴的 "标签"------ 就像你给快递贴 "易碎品",给文件标 "加急",注解就是给类、方法、变量贴的 "说明符"。
它不直接影响代码运行,但能告诉编译器 / 框架:"喂,这个代码是干这个的!" 比如@Deprecated贴在方法上,编译器就会喊:"警告!这方法要被淘汰啦!"
二、JDK 自带的 "注解小工具"------ 这些你天天用,可能没细想
JDK 早就给咱准备了一批常用注解,相当于 "现成的标签",咱先盘点几个高频的:
注解 | 作用(人话版) | 场景举例 |
---|---|---|
@Override | 告诉编译器:"我这是重写父类的方法!" | 子类重写toString()方法 |
@Deprecated | 标红警告:"这玩意儿要淘汰了,别用了!" | 老项目里的Date的toLocaleString() |
@SuppressWarnings | 让编译器 "闭嘴":"别报这个警告了,我知道我在干啥" | 用了老 API 不想看黄色警告 |
@FunctionalInterface | 强制检查:"这接口必须是函数式接口(只有一个抽象方法)" | 写 Lambda 表达式时用 |
比如你写个类重写方法,没加@Override也能跑,但加了之后编译器会帮你检查 ------ 要是父类根本没这方法,直接报错!这就是 "标签" 的力量~
三、自己造注解!------ 定义注解的 "语法模板"
光用现成的不够爽,咱也能自己造注解!记住,定义注解的语法像极了 "创建接口",但要加个@前缀,还得配 "元注解"(后面讲)。
先看个最简单的自定义注解模板:
less
// 元注解(给注解加的注解,相当于"标签的标签")
@Retention(RetentionPolicy.RUNTIME) // 控制注解能存活到什么时候
@Target(ElementType.METHOD) // 控制注解能贴在什么地方(这里是方法上)
public @interface MyFirstAnnotation {
// 注解的属性(相当于标签的"填写项")
String value() default "默认值"; // 带默认值的属性
int version() required; // 必须填的属性(没有default)
}
划重点:定义注解用@interface,不是interface!别少写了@~
四、元注解:给 "注解" 贴的标签 ------ 注解的 "管理员"
刚才定义注解时,前面加的@Retention、@Target就是 "元注解"------ 相当于给 "标签" 本身定规矩的 "管理员"。JDK 里就 4 个核心元注解,记牢这 4 个就够了:
- @Retention:控制注解 "活多久"(生命周期)
-
- SOURCE:只在源码里存在,编译成 class 就没了(比如@Override)
-
- CLASS:能进 class 文件,但 JVM 运行时不读(默认值,少用)
-
- RUNTIME:能活到 JVM 运行时,框架常用(比如 Spring 的@Autowired)
- @Target:控制注解能 "贴在哪"(作用范围)
-
- 常用值:TYPE(类 / 接口)、METHOD(方法)、FIELD(变量)、PARAMETER(参数)
-
- 比如@Target({ElementType.METHOD, ElementType.FIELD}),就既能贴方法也能贴变量
- @Inherited:允许子类 "继承" 父类的注解(比如父类贴了@MyAnno,子类也能拿到)
- @Documented:让注解能被javadoc生成到文档里(一般用得少,追求文档完整可以加)
五、注解的 "属性":给标签加 "填写项"------ 让注解更灵活
注解的属性就像标签的 "填空栏",比如你造个@Log注解,想让它支持 "日志级别""描述",就可以加属性:
less
@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 {}; // 日志标签,支持多值
}
用的时候超简单,像填表单一样:
less
public class UserService {
// 用value属性:可以省略"value="
@Log("查询用户")
public User getUserById(Long id) { ... }
// 多属性:按名填写,数组用{}
@Log(level = "WARN", value = "删除用户", tags = {"敏感操作", "用户管理"})
public void deleteUser(Long id) { ... }
}
小技巧:如果属性是数组且只有一个值,连{}都能省,比如tags = "敏感操作"~
六、自定义注解实战:写个 "日志注解" 玩玩 ------ 从定义到使用
光说不练假把式,咱来写个能实际用的@Log注解,配合反射解析它,实现 "调用方法时自动打日志"。
步骤 1:定义日志注解
less
@Retention(RetentionPolicy.RUNTIME) // 必须RUNTIME,反射才能拿到
@Target(ElementType.METHOD) // 只贴在方法上
public @interface Log {
String value() default "执行方法"; // 日志描述
String level() default "INFO"; // 日志级别
}
步骤 2:写个工具类,用反射解析注解
反射是解析注解的核心!因为只有反射能在运行时拿到类 / 方法上的注解信息:
arduino
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);
}
}
}
步骤 3:测试一下!
arduino
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注解,编译后会变成这样(反编译结果):
csharp
public interface Log extends java.lang.annotation.Annotation {
String value();
String level();
}
所以你定义的注解属性,其实是接口的抽象方法;拿到的注解对象,是 JVM 动态生成的接口实现类 ------ 是不是瞬间懂了?
八、注解的 "正确打开方式"------ 在哪用,怎么用?
注解的使用场景主要分两类,咱后端 er 最常用的就是第二种:
- 编译器级别的使用:告诉编译器做检查 / 处理
-
- 比如@Override让编译器校验重写正确性,@SuppressWarnings屏蔽警告
-
- 这类注解一般用@Retention(SOURCE),编译后就没了
- 框架级别的使用:给框架提供配置信息
-
- 比如 Spring 的@Autowired让框架自动注入对象,MyBatis 的@Select指定 SQL
-
- 这类注解必须用@Retention(RUNTIME),框架靠反射解析
九、解析注解的 "核心 API"------ 反射包里的这几个方法要记牢
解析注解全靠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 里的 "注解全家桶"------ 这些你天天用,今天懂原理了
最后咱聊点实战的:SpringBoot 里的注解为啥这么好用?其实都是基于前面讲的 "注解 + 反射" 原理!盘点几个高频注解,看完你就懂了:
1. 启动相关
- @SpringBootApplication:SpringBoot 的 "万能钥匙",其实是三个注解的组合:
-
- @SpringBootConfiguration:标记这是配置类
-
- @ComponentScan:扫描当前包及子包的@Component类(比如@Service、@Controller)
-
- @EnableAutoConfiguration:开启自动配置(比如你加了 mysql 依赖,自动配数据源)
2. 接口开发相关
- @RestController:= @Controller + @ResponseBody,告诉 Spring:"这是接口类,返回 JSON"
- @RequestMapping("/user"):给接口贴 "地址标签",浏览器访问/user就找到这个方法
- @RequestParam("id"):告诉 Spring:"把请求里的 id 参数,传给方法的这个参数"
3. 依赖注入相关
- @Autowired:让 Spring "自动找个对象塞进来",不用自己new
- @Service/@Component:给类贴 "组件标签",告诉 Spring:"把我装进容器里,后面好用"
4. 事务相关
- @Transactional:给方法贴 "事务标签",Spring 会自动帮你管理事务(提交 / 回滚)
比如你写个接口:
less
@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 的 "指令",框架靠反射解析这些注解,才帮你完成了注入、接口映射这些工作!
总结:注解这东西,学会了真的香!
其实注解的核心就三点:
- 是给代码贴的 "标签",靠元注解定规矩
- 自定义注解要配@interface+ 属性,解析靠反射
- SpringBoot 等框架靠注解简化配置,本质是 "注解 + 反射"
看完这篇,下次再写@XXX的时候,别再只当它是 "符号" 了 ------ 想想它的生命周期、作用范围,甚至自己造个注解解决问题,这才是后端 er 的进阶姿势!
最后问一句:你平时用注解踩过什么坑?比如没加@Retention(RUNTIME)导致解析不到?评论区聊聊,咱一起避坑~