关于Java注解
一、什么是注解(Annotation)?
- 注解是一种 元数据(metadata),用于为代码提供额外信息。
- 它 不直接影响程序逻辑,但可被编译器、开发工具或运行时环境读取并处理。
- 本质上,注解是 接口(interface),继承自
java.lang.annotation.Annotation。
public @interface MyAnnotation {
String value() default "default";
}
✅ 注解不是注释!注释(// 或 /* */)会被编译器忽略;注解会被保留并可被程序读取。
二、Java 内置的三大核心注解(来自 java.lang)
| 注解 | 作用 |
|---|---|
@Override |
标记方法是重写父类/接口方法,编译器会校验 |
@Deprecated |
标记已过时,使用时会警告 |
@SuppressWarnings |
抑制编译器警告(如 "unchecked"、"rawtypes") |
示例:
@SuppressWarnings("unchecked")
List list = new ArrayList();
三、元注解(Meta-Annotations)
元注解是 “注解的注解”,用于定义自定义注解的行为。Java 提供了 5 个标准元注解:
1. @Retention —— 保留策略(生命周期)
决定注解在什么阶段可用:
| 策略 | 含义 | 能否通过反射获取? |
|---|---|---|
RetentionPolicy.SOURCE |
仅在源码中存在,编译后丢弃(如 @Override) |
❌ |
RetentionPolicy.CLASS |
编译到 .class 文件,但 JVM 运行时不加载(默认值) | ❌ |
RetentionPolicy.RUNTIME |
保留在运行时,可通过反射读取 | ✅(最常用) |
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRuntimeAnnotation {}
2. @Target —— 使用位置
限制注解能用在哪些程序元素上:
@Target({ElementType.METHOD, ElementType.FIELD})
public @interface MyFieldOrMethodAnnotation {}
常见 ElementType:
TYPE:类、接口、枚举METHOD:方法FIELD:字段PARAMETER:参数CONSTRUCTOR:构造器LOCAL_VARIABLE:局部变量PACKAGE:包
3. @Documented
表示该注解应包含在 JavaDoc 中。
4. @Inherited
允许子类 自动继承 父类的注解(仅对类注解有效)。
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface ParentAnno {}
@ParentAnno
class Parent {}
class Child extends Parent {} // Child 也“拥有” @ParentAnno
5. @Repeatable(Java 8+)
允许在同一位置重复使用同一注解。
@Repeatable(Authorizations.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface Authorization {
String role();
}
@Retention(RetentionPolicy.RUNTIME)
public @interface Authorizations {
Authorization[] value();
}
// 使用
@Authorization(role = "admin")
@Authorization(role = "user")
public void doSomething() {}
四、如何定义自定义注解?
语法:使用 @interface 关键字。
基本规则:
- 方法不能有参数、不能抛异常、不能是泛型。
- 返回类型只能是:基本类型、String、Class、枚举、注解,或其数组。
- 可以有默认值(
default)。
示例:一个权限控制注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequireRole {
String[] value(); // 必填
boolean logAccess() default true; // 可选,默认 true
}
使用:
@RequireRole(value = {"admin", "manager"}, logAccess = false)
public void deleteUser(String id) {
// ...
}
五、如何读取注解?(反射)
只有 @Retention(RUNTIME) 的注解才能通过反射获取。
示例:读取方法上的注解
Method method = MyClass.class.getMethod("deleteUser", String.class);
if (method.isAnnotationPresent(RequireRole.class)) {
RequireRole anno = method.getAnnotation(RequireRole.class);
System.out.println(Arrays.toString(anno.value())); // [admin, manager]
System.out.println(anno.logAccess()); // false
}
读取类/字段/参数注解类似:
Class.getAnnotation(Class)Field.getAnnotation(Class)Parameter.getAnnotation(Class)
六、注解的典型应用场景
| 场景 | 框架示例 | 说明 |
|---|---|---|
| 依赖注入 | Spring (@Autowired, @Component) |
容器根据注解自动装配 Bean |
| Web 路由 | Spring MVC (@RequestMapping) |
将 HTTP 请求映射到方法 |
| 数据库映射 | JPA/Hibernate (@Entity, @Column) |
对象与表字段映射 |
| 参数校验 | Bean Validation (@NotNull, @Min) |
配合 @Valid 自动校验 |
| AOP 切面 | Spring (@Aspect, @Around) |
声明切面逻辑 |
| 测试 | JUnit (@Test, @BeforeEach) |
标记测试方法 |
七、进阶:编译时注解处理(APT)
除了运行时反射,还可以在 编译期 处理注解,生成代码(如 Lombok、Dagger、ButterKnife)。
- 使用 javax.annotation.processing.Processor
- 需要配合
META-INF/services/javax.annotation.processing.Processor注册 - 不影响运行时性能(因为代码在编译时就生成好了)
⚠️ 这部分较复杂,初学可先掌握运行时反射方式。
八、常见误区 & 注意事项
- 注解 ≠ 接口实现
注解本身没有行为,必须由外部(框架/你自己)解析并执行逻辑。 - 默认值很重要
如果不设default,使用时必须显式赋值。 - 数组 vs 单值
如果只传一个值,可直接写:@MyAnno("hello")(前提是方法名为value()) - 注解不能继承普通类
所有注解都隐式继承java.lang.annotation.Annotation,不能再继承其他类。
九、小练习(巩固理解)
尝试自己实现一个简单的日志注解:
@Retention(RUNTIME)
@Target(METHOD)
public @interface LogExecution {
String value() default "";
}
然后写一个工具方法,通过代理或 AOP(或简单反射)在调用方法前打印日志。
如果你正在学习 Spring,那么注解是它的核心!掌握好注解,你就掌握了现代 Java 开发的“声明式编程”思想。
浙公网安备 33010602011771号