---
title: "13-注解原理"
created: 2025-11-26
tags:
- 博客
aliases:
- 注解原理
---
# 注解原理
> 注解(Annotation)是 Java 提供的一种元数据机制,本质上是给代码打“标签”,让程序、编译器或框架在特定阶段“看懂这个标签”,并执行特定的处理逻辑。它是对 Java 语言的一种语义扩展,虽然注解本身不具有行为,但框架或工具能根据这些标记做出“增强反应”。
>
> 最开始注解只是用来做一些编译检查,比如 `@Override` 能帮助我们判断方法是否真的重写成功。但随着框架的发展,注解逐渐演变为整个 Java 生态中“配置驱动”和“约定优于配置”的核心工具。比如 Spring 中的 `@Component`、`@Autowired`、`@Transactional`,几乎一切行为都依赖注解驱动。
>
> 注解之所以强大,是因为它配合反射机制,就能在运行时读取注解信息,从而完成类的识别、依赖注入、AOP 增强等操作——这背后就是所谓的“注解驱动机制”。
> 注解的本质,其实就是“元数据 + 配置思想 + 反射解析”,它让我们能通过一行声明,驱动整个框架行为,极大地提升了开发效率和代码可维护性。
## 第一部分:问题的提出
### 一、传统方式的局限
想象一个场景:
```java
// 传统方式:配置信息分散在代码各处
public class UserService {
public void save(User user) {
// 需要手动检查权限
if (!hasPermission()) {
throw new SecurityException("无权限");
}
// 需要手动开启事务
beginTransaction();
try {
// 业务逻辑
userRepository.save(user);
// 需要手动提交事务
commitTransaction();
} catch (Exception e) {
// 需要手动回滚事务
rollbackTransaction();
throw e;
}
}
}
// 配置文件(XML)
```
这种方式有什么问题?
1. **配置分散** - 配置信息在XML和代码中混乱
2. **难以维护** - 修改配置需要改动多个地方
3. **代码冗长** - 重复的权限检查、事务管理代码
4. **不够直观** - 无法直观看出方法的特殊处理
### 二、理想的解决方案
我们希望能这样写:
```java
// 理想方式:配置信息直接写在代码上
@Service
public class UserService {
@Transactional
@RequirePermission("ADMIN")
public void save(User user) {
// 只写业务逻辑,权限检查和事务管理由框架自动处理
userRepository.save(user);
}
}
// 框架自动识别注解,完成权限检查、事务管理等
```
**这就是注解要解决的问题:用简洁的标签替代复杂的配置,让框架自动处理。**
---
## 第二部分:注解的基础概念
### 三、什么是注解?
#### 3.1 注解的定义
**注解(Annotation)是Java提供的一种元数据机制,本质上是给代码打"标签"。**
```
┌──────────────────────────────────────────┐
│ 注解的本质 │
├──────────────────────────────────────────┤
│ │
│ 注解 = 标签 + 元数据 + 处理机制 │
│ │
│ 1. 标签 │
│ └─ 给代码元素(类、方法、字段)打标记 │
│ │
│ 2. 元数据 │
│ └─ 描述代码的数据,不是代码本身 │
│ │
│ 3. 处理机制 │
│ ├─ 编译期处理(APT) │
│ └─ 运行期处理(反射) │
│ │
└──────────────────────────────────────────┘
```
#### 3.2 注解的特点
1. **注解本身没有行为** - 注解只是标签,不执行任何逻辑
2. **需要处理器来解析** - 编译器或框架通过反射读取注解
3. **提供元信息** - 注解为代码提供额外的信息
4. **不影响业务逻辑** - 注解是辅助性的,不改变代码的执行逻辑
#### 3.3 注解的工作原理
```
┌─────────────────────────────────────────────────┐
│ 注解的工作流程 │
├─────────────────────────────────────────────────┤
│ │
│ 第1步:定义注解 │
│ ├─ 使用@interface定义注解 │
│ └─ 使用元注解配置注解的属性 │
│ │
│ 第2步:使用注解 │
│ ├─ 在类、方法、字段上添加注解 │
│ └─ 注解信息被保存在.class文件中 │
│ │
│ 第3步:处理注解 │
│ ├─ 编译期:APT处理器读取注解,生成代码 │
│ └─ 运行期:反射读取注解,执行相应逻辑 │
│ │
│ 第4步:执行增强逻辑 │
│ ├─ 权限检查、事务管理等 │
│ └─ 框架自动织入增强逻辑 │
│ │
└─────────────────────────────────────────────────┘
```
---
## 第三部分:注解的底层原理
### 四、注解的本质:特殊的接口
#### 4.1 注解是接口
```typescript
// 注解的本质是一个继承了Annotation的特殊接口
@interface MyAnnotation {
String value();
}
// 等价于
interface MyAnnotation extends java.lang.annotation.Annotation {
String value();
}
```
#### 4.2 注解的动态代理实现
```java
// 当我们通过反射获取注解时,返回的是动态代理对象
public class AnnotationProxyDemo {
public static void main(String[] args) {
// 获取注解
MyAnnotation annotation = MyClass.class.getAnnotation(MyAnnotation.class);
// annotation实际上是一个动态代理对象
System.out.println(annotation.getClass().getName());
// 输出:com.sun.proxy.$Proxy1
// 调用注解的方法时,会转发到AnnotationInvocationHandler
String value = annotation.value();
}
}
// 底层实现:JVM生成的动态代理类
public class $Proxy1 implements MyAnnotation {
private AnnotationInvocationHandler handler;
public String value() {
// 调用handler的invoke方法
return handler.invoke(this, valueMethod, null);
}
}
// AnnotationInvocationHandler的invoke方法
public class AnnotationInvocationHandler implements InvocationHandler {
private Map memberValues; // 存储注解的属性值
public Object invoke(Object proxy, Method method, Object[] args) {
// 从memberValues中获取对应的值
return memberValues.get(method.getName());
}
}
```
#### 4.3 注解信息的来源
```
┌──────────────────────────────────────────┐
│ 注解信息的存储和获取 │
├──────────────────────────────────────────┤
│ │
│ 源代码阶段 │
│ ├─ @MyAnnotation("value") │
│ └─ 注解信息写在源代码中 │
│ │
│ 编译阶段 │
│ ├─ 编译器读取注解信息 │
│ └─ 将注解信息存储在.class文件中 │
│ │
│ 运行阶段 │
│ ├─ JVM加载.class文件 │
│ ├─ 注解信息存储在常量池中 │
│ └─ 通过反射获取注解时,JVM创建代理对象 │
│ │
│ 获取注解值 │
│ ├─ 代理对象调用方法 │
│ ├─ AnnotationInvocationHandler拦截 │
│ └─ 从常量池中获取值返回 │
│ │
└──────────────────────────────────────────┘
```
---
## 第四部分:元注解
### 五、元注解:注解的注解
#### 5.1 什么是元注解?
**元注解是用来定义其他注解的注解。**
Java提供了4个元注解,用来配置自定义注解的属性:
```
┌──────────────────────────────────────────┐
│ 四大元注解 │
├──────────────────────────────────────────┤
│ │
│ 1. @Retention │
│ └─ 控制注解的生命周期 │
│ │
│ 2. @Target │
│ └─ 指定注解可以应用的位置 │
│ │
│ 3. @Documented │
│ └─ 控制是否在JavaDoc中显示 │
│ │
│ 4. @Inherited │
│ └─ 控制是否被子类继承 │
│ │
└──────────────────────────────────────────┘
```
#### 5.2 @Retention:注解的生命周期
```typescript
// @Retention控制注解在什么阶段有效
@Retention(RetentionPolicy.SOURCE)
public @interface SourceAnnotation {
// 只在源代码中存在
// 编译时被丢弃
// 用途:编译期检查(@Override、@SuppressWarnings)
}
@Retention(RetentionPolicy.CLASS)
public @interface ClassAnnotation {
// 编译进.class文件
// 但运行时不可见
// 用途:字节码处理工具
// 这是默认值
}
@Retention(RetentionPolicy.RUNTIME)
public @interface RuntimeAnnotation {
// 编译后保留
// 运行时也可见
// 可以通过反射读取
// 用途:Spring、Hibernate等框架
}
```
**三个阶段的对比:**
| 阶段 | SOURCE | CLASS | RUNTIME |
| --- | --- | --- | --- |
| 源代码 | ✅ 存在 | ✅ 存在 | ✅ 存在 |
| .class文件 | ❌ 丢弃 | ✅ 保留 | ✅ 保留 |
| 运行时 | ❌ 不可见 | ❌ 不可见 | ✅ 可见 |
| 反射读取 | ❌ 不能 | ❌ 不能 | ✅ 可以 |
#### 5.3 @Target:注解的位置
```typescript
// @Target指定注解可以应用的位置
@Target(ElementType.TYPE)
public @interface ClassAnnotation {
// 只能用在类、接口、枚举上
}
@Target(ElementType.METHOD)
public @interface MethodAnnotation {
// 只能用在方法上
}
@Target(ElementType.FIELD)
public @interface FieldAnnotation {
// 只能用在字段上
}
@Target(ElementType.PARAMETER)
public @interface ParameterAnnotation {
// 只能用在参数上
}
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface MultiTargetAnnotation {
// 可以用在类和方法上
}
@Target(ElementType.ANNOTATION_TYPE)
public @interface MetaAnnotation {
// 只能用来注解其他注解(元注解)
}
```
**所有可用的位置:**
| ElementType | 说明 |
| --- | --- |
| TYPE | 类、接口、枚举 |
| FIELD | 字段 |
| METHOD | 方法 |
| PARAMETER | 参数 |
| CONSTRUCTOR | 构造函数 |
| LOCAL\_VARIABLE | 局部变量 |
| ANNOTATION\_TYPE | 注解 |
| PACKAGE | 包 |
| TYPE\_PARAMETER | 类型参数(Java 8+) |
| TYPE\_USE | 类型使用(Java 8+) |
#### 5.4 @Documented:JavaDoc支持
```java
// @Documented表示注解是否出现在JavaDoc中
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ApiDoc {
String value();
}
// 使用@Documented的注解会出现在JavaDoc中
public class MyClass {
/**
* 这是一个方法
* @ApiDoc("获取用户信息")
*/
public void getUser() {}
}
```
#### 5.5 @Inherited:继承性
```java
// @Inherited表示注解是否被子类继承
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ParentAnnotation {
}
@ParentAnnotation
public class Parent {
}
// 子类自动继承父类的@ParentAnnotation
public class Child extends Parent {
}
// 验证
public class InheritedDemo {
public static void main(String[] args) {
// 子类继承了父类的注解
System.out.println(Child.class.isAnnotationPresent(ParentAnnotation.class));
// 输出:true
}
}
```
---
## 第五部分:自定义注解
### 六、定义自定义注解
#### 6.1 注解的定义规则
```typescript
// 1. 使用@interface定义注解
public @interface MyAnnotation {
// 2. 注解成员只能是public或默认访问权限
// 3. 注解成员只能是基本类型、String、Enum、Class、Annotation及其数组
// 4. 可以使用default指定默认值
// 5. 如果没有成员,就是标记注解
}
```
#### 6.2 完整示例:水果注解
```java
// 1. 定义水果名称注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitName {
String value() default "";
}
// 2. 定义水果颜色注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitColor {
// 定义颜色枚举
enum Color { BLUE, RED, GREEN }
// 定义颜色属性
Color value() default Color.GREEN;
}
// 3. 定义水果供应商注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitProvider {
// 供应商编号
int id() default -1;
// 供应商名称
String name() default "";
// 供应商地址
String address() default "";
}
// 4. 使用注解
public class Apple {
@FruitName("Apple")
private String appleName;
@FruitColor(FruitColor.Color.RED)
private String appleColor;
@FruitProvider(
id = 1,
name = "陕西红富士集团",
address = "陕西省西安市延安路89号"
)
private String appleProvider;
}
```
#### 6.3 注解成员的规则
```typescript
public @interface AnnotationRules {
// ✅ 正确的成员类型
// 基本类型
int intValue() default 0;
boolean boolValue() default false;
// String类型
String stringValue() default "";
// Enum类型
ElementType enumValue() default ElementType.TYPE;
// Class类型
Class> classValue() default Object.class;
// 注解类型
Deprecated annotationValue() default @Deprecated;
// 数组类型
int[] intArray() default {};
String[] stringArray() default {};
// ❌ 错误的成员类型
// Object objectValue; // Object不支持
// List listValue; // 泛型不支持
// Date dateValue; // 自定义类不支持
}
```
---
## 第六部分:注解的处理
### 七、编译期处理:APT(注解处理器)
#### 7.1 什么是APT?
**APT(Annotation Processing Tool)是Java编译器提供的一种机制,允许在编译期处理注解,生成新的代码或文件。**
```
┌──────────────────────────────────────────┐
│ APT的工作流程 │
├──────────────────────────────────────────┤
│ │
│ 第1步:编译器扫描源代码 │
│ └─ 找到所有的注解 │
│ │
│ 第2步:调用注解处理器 │
│ └─ 编译器调用实现了Processor的处理器 │
│ │
│ 第3步:处理注解 │
│ └─ 处理器读取注解信息,生成代码 │
│ │
│ 第4步:编译新生成的代码 │
│ └─ 编译器编译生成的代码 │
│ │
│ 第5步:重复处理 │
│ └─ 如果有新的注解,重复步骤2-4 │
│ │
└──────────────────────────────────────────┘
```
#### 7.2 实现APT处理器
```java
// 实现Processor接口
public class MyAnnotationProcessor extends AbstractProcessor {
@Override
public Set getSupportedAnnotationTypes() {
// 指定支持的注解
return Collections.singleton(MyAnnotation.class.getName());
}
@Override
public SourceVersion getSupportedSourceVersion() {
// 指定支持的Java版本
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 处理注解
for (TypeElement annotation : annotations) {
Set extends Element> elements = roundEnv.getElementsAnnotatedWith(annotation);
for (Element element : elements) {
// 获取注解信息
MyAnnotation myAnnotation = element.getAnnotation(MyAnnotation.class);
// 生成代码
generateCode(element, myAnnotation);
}
}
return true;
}
private void generateCode(Element element, MyAnnotation annotation) {
// 使用Filer生成新的源文件
// 使用Messager输出编译信息
}
}
```
#### 7.3 APT的实际应用
```java
// Lombok使用APT在编译期生成代码
@Data
public class User {
private String name;
private int age;
}
// 编译后自动生成:
// - getter/setter方法
// - toString()方法
// - equals()和hashCode()方法
// - 构造函数
// 其他APT应用:
// - MapStruct:自动生成对象映射代码
// - Dagger2:自动生成依赖注入代码
// - AutoValue:自动生成值对象代码
```
### 八、运行期处理:反射
#### 8.1 通过反射读取注解
```java
// 运行期通过反射读取注解信息
public class AnnotationReflectionDemo {
public static void main(String[] args) {
// 获取类上的注解
FruitName classAnnotation = Apple.class.getAnnotation(FruitName.class);
// 获取字段上的注解
Field[] fields = Apple.class.getDeclaredFields();
for (Field field : fields) {
// 检查字段是否有某个注解
if (field.isAnnotationPresent(FruitName.class)) {
FruitName annotation = field.getAnnotation(FruitName.class);
System.out.println("字段:" + field.getName());
System.out.println("注解值:" + annotation.value());
}
if (field.isAnnotationPresent(FruitColor.class)) {
FruitColor annotation = field.getAnnotation(FruitColor.class);
System.out.println("颜色:" + annotation.value());
}
}
// 获取方法上的注解
Method[] methods = Apple.class.getDeclaredMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(Deprecated.class)) {
System.out.println("过时方法:" + method.getName());
}
}
}
}
```
#### 8.2 注解处理器的实现
```java
// 注解处理器:读取注解并执行相应逻辑
public class FruitInfoUtil {
public static void getFruitInfo(Class> clazz) {
// 获取所有字段
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
// 处理@FruitName注解
if (field.isAnnotationPresent(FruitName.class)) {
FruitName annotation = field.getAnnotation(FruitName.class);
System.out.println("水果名称:" + annotation.value());
}
// 处理@FruitColor注解
if (field.isAnnotationPresent(FruitColor.class)) {
FruitColor annotation = field.getAnnotation(FruitColor.class);
System.out.println("水果颜色:" + annotation.value());
}
// 处理@FruitProvider注解
if (field.isAnnotationPresent(FruitProvider.class)) {
FruitProvider annotation = field.getAnnotation(FruitProvider.class);
System.out.println("供应商编号:" + annotation.id());
System.out.println("供应商名称:" + annotation.name());
System.out.println("供应商地址:" + annotation.address());
}
}
}
}
// 使用
public class FruitRun {
public static void main(String[] args) {
FruitInfoUtil.getFruitInfo(Apple.class);
}
}
// 输出:
// 水果名称:Apple
// 水果颜色:RED
// 供应商编号:1
// 供应商名称:陕西红富士集团
// 供应商地址:陕西省西安市延安路89号
```
---
## 第七部分:注解的实际应用
### 九、Spring中的注解应用
#### 9.1 组件扫描注解
```java
// @Component及其衍生注解
@Component
public class UserService {
// Spring自动扫描并创建Bean
}
@Service
public class UserServiceImpl implements UserService {
// @Service是@Component的特殊化
}
@Repository
public class UserRepository {
// @Repository是@Component的特殊化
}
@Controller
public class UserController {
// @Controller是@Component的特殊化
}
// Spring通过反射扫描注解,自动创建Bean
```
#### 9.2 依赖注入注解
```java
// @Autowired注解实现依赖注入
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
// Spring通过反射读取@Autowired注解
// 自动创建UserRepository实例并注入
}
// Spring的处理流程:
// 1. 扫描@Service注解,创建UserService Bean
// 2. 扫描@Autowired注解
// 3. 根据类型创建UserRepository实例
// 4. 通过反射将实例注入到userRepository字段
```
#### 9.3 AOP注解
```java
// @Aspect和@Transactional注解实现AOP
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void beforeMethod(JoinPoint joinPoint) {
System.out.println("方法调用前");
}
}
@Service
public class UserService {
@Transactional
public void save(User user) {
// Spring通过反射读取@Transactional注解
// 自动为方法添加事务管理
}
}
// Spring的处理流程:
// 1. 扫描@Transactional注解
// 2. 为该方法创建动态代理
// 3. 在代理中添加事务管理逻辑
```
#### 9.4 请求映射注解
```java
// @RequestMapping等注解实现URL映射
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping("/{id}")
public User getUser(@PathVariable int id) {
// Spring通过反射读取@GetMapping注解
// 自动将GET /api/users/{id}请求映射到此方法
}
@PostMapping
public void saveUser(@RequestBody User user) {
// Spring通过反射读取@PostMapping注解
// 自动将POST /api/users请求映射到此方法
}
}
```
### 十、JPA中的注解应用
```java
// JPA使用注解进行对象-关系映射
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
@Column(name = "user_name", nullable = false)
private String name;
@Column(name = "user_age")
private int age;
@ManyToOne
@JoinColumn(name = "department_id")
private Department department;
}
// Hibernate通过反射读取注解
// 自动生成SQL语句进行数据库操作
```
### 十一、自定义注解的实际应用
```java
// 自定义权限检查注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequirePermission {
String value();
}
// 使用注解
@Service
public class UserService {
@RequirePermission("ADMIN")
public void deleteUser(int id) {
// 删除用户
}
}
// 实现权限检查的AOP切面
@Aspect
@Component
public class PermissionAspect {
@Before("@annotation(requirePermission)")
public void checkPermission(JoinPoint joinPoint, RequirePermission requirePermission) {
// 通过反射读取@RequirePermission注解
String permission = requirePermission.value();
// 检查当前用户是否有该权限
if (!hasPermission(permission)) {
throw new SecurityException("无权限");
}
}
private boolean hasPermission(String permission) {
// 权限检查逻辑
return true;
}
}
```
---
## 第八部分:注解的性能与最佳实践
### 十二、注解的性能考虑
#### 12.1 反射的性能开销
```java
// 反射读取注解有性能开销
public class AnnotationPerformanceDemo {
public static void main(String[] args) {
// 直接调用
long directStart = System.nanoTime();
for (int i = 0; i < 1_000_000; i++) {
checkPermission("ADMIN");
}
long directTime = System.nanoTime() - directStart;
// 通过反射读取注解
Method method = UserService.class.getMethod("deleteUser", int.class);
long reflectStart = System.nanoTime();
for (int i = 0; i < 1_000_000; i++) {
RequirePermission annotation = method.getAnnotation(RequirePermission.class);
checkPermission(annotation.value());
}
long reflectTime = System.nanoTime() - reflectStart;
System.out.println("直接调用:" + directTime);
System.out.println("反射读取:" + reflectTime);
}
}
```
#### 12.2 性能优化建议
```java
// 1. 缓存注解信息
public class AnnotationCache {
private static final Map cache = new ConcurrentHashMap<>();
public static RequirePermission getAnnotation(Method method) {
return cache.computeIfAbsent(method, m -> m.getAnnotation(RequirePermission.class));
}
}
// 2. 避免在热点代码中频繁读取注解
@Aspect
@Component
public class OptimizedPermissionAspect {
private final Map permissionCache = new ConcurrentHashMap<>();
@Before("@annotation(requirePermission)")
public void checkPermission(JoinPoint joinPoint, RequirePermission requirePermission) {
String permission = requirePermission.value();
// 缓存权限检查结果
if (!permissionCache.containsKey(permission)) {
permissionCache.put(permission, checkPermissionInternal(permission));
}
}
}
// 3. 使用编译期注解处理而不是运行期反射
// 编译期处理:Lombok、MapStruct等
// 优势:代码生成在编译期完成,运行期无反射开销
@Data // Lombok在编译期生成getter/setter
public class User {
private String name;
private int age;
}
// 编译后自动生成getter/setter,运行期无反射开销
// 4. 避免在循环中反复获取注解
// ❌ 不好的做法
for (int i = 0; i < 1000; i++) {
Method method = clazz.getMethod("save", User.class); // 每次都获取
SaveAnnotation annotation = method.getAnnotation(SaveAnnotation.class);
}
// ✅ 好的做法
Method method = clazz.getMethod("save", User.class);
SaveAnnotation annotation = method.getAnnotation(SaveAnnotation.class);
for (int i = 0; i < 1000; i++) {
// 使用缓存的注解信息
processWithAnnotation(annotation);
}
```
### 十三、注解的最佳实践
#### 13.1 推荐做法
```java
// 1. 使用元注解正确配置自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequirePermission {
String value();
}
// 2. 为注解提供默认值
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Timeout {
long value() default 5000; // 默认5秒超时
}
// 3. 使用有意义的注解名称
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheResult {
// 清晰表达注解的用途
}
// 4. 在注解上添加JavaDoc
/**
* 权限检查注解
*
* 用法:
* @RequirePermission("ADMIN")
* public void deleteUser(int id) { }
*
* @author xxx
* @since 1.0
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequirePermission {
String value();
}
// 5. 缓存注解信息以提升性能
public class AnnotationHelper {
private static final Map annotationCache = new ConcurrentHashMap<>();
public static T getAnnotation(Method method, Class annotationType) {
String key = method.toString() + ":" + annotationType.getName();
return (T) annotationCache.computeIfAbsent(key, k -> method.getAnnotation(annotationType));
}
}
// 6. 优先使用框架提供的注解而不是自定义
// ✅ 使用Spring的@Transactional
@Transactional
public void save(User user) { }
// ❌ 避免自定义事务注解
@MyTransactional
public void save(User user) { }
// 7. 为注解提供清晰的处理逻辑
@Aspect
@Component
public class AnnotationProcessor {
@Before("@annotation(myAnnotation)")
public void processAnnotation(JoinPoint joinPoint, MyAnnotation myAnnotation) {
// 清晰的处理逻辑
String value = myAnnotation.value();
System.out.println("处理注解:" + value);
}
}
```
#### 13.2 避免做法
```java
// ❌ 不要定义过多的注解参数
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BadAnnotation {
String param1();
String param2();
String param3();
String param4();
String param5();
// 参数过多,使用复杂
}
// ✅ 应该简化注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface GoodAnnotation {
String value();
}
// ❌ 不要在注解中放置复杂的逻辑
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BadLogic {
// ❌ 注解不能包含方法实现
// void process() { }
}
// ✅ 注解只定义元数据,逻辑在处理器中
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface GoodLogic {
String value();
}
// ❌ 不要频繁在热点代码中读取注解
public void hotMethod() {
for (int i = 0; i < 1_000_000; i++) {
Method method = getMethod();
Annotation annotation = method.getAnnotation(MyAnnotation.class); // 频繁反射
process(annotation);
}
}
// ✅ 应该缓存注解信息
private static final Annotation CACHED_ANNOTATION = getMethod().getAnnotation(MyAnnotation.class);
public void hotMethod() {
for (int i = 0; i < 1_000_000; i++) {
process(CACHED_ANNOTATION); // 使用缓存
}
}
// ❌ 不要滥用注解
@Deprecated
@Override
@SuppressWarnings("all")
@FunctionalInterface
public void method() {
// 注解过多,反而降低可读性
}
// ✅ 只使用必要的注解
@Override
public void method() {
// 清晰简洁
}
// ❌ 不要在注解中使用复杂的类型
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BadType {
// ❌ 不支持泛型
// List values();
// ❌ 不支持自定义类
// User user();
}
// ✅ 只使用支持的类型
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface GoodType {
String[] values() default {};
Class> type() default Object.class;
}
```
---
## 第九部分:注解与反射、动态代理的关系
### 十四、三者的关系
```
┌──────────────────────────────────────────┐
│ 注解、反射、动态代理的关系 │
├──────────────────────────────────────────┤
│ │
│ 注解(Annotation) │
│ ├─ 本质:元数据标签 │
│ ├─ 作用:标记代码元素 │
│ └─ 特点:本身无行为 │
│ │
│ 反射(Reflection) │
│ ├─ 本质:运行时获取类信息 │
│ ├─ 作用:读取注解信息 │
│ └─ 特点:是注解处理的基础 │
│ │
│ 动态代理(Dynamic Proxy) │
│ ├─ 本质:运行时生成代理类 │
│ ├─ 作用:拦截方法调用,织入增强逻辑 │
│ └─ 特点:实现注解驱动的AOP │
│ │
│ 三者的协作 │
│ ├─ 注解标记需要增强的方法 │
│ ├─ 反射读取注解信息 │
│ ├─ 动态代理创建代理对象 │
│ └─ 代理对象织入增强逻辑 │
│ │
└──────────────────────────────────────────┘
```
### 十五、完整的注解驱动示例
```java
// 1. 定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Cacheable {
String value() default "";
long timeout() default 3600; // 秒
}
// 2. 定义业务类
@Service
public class UserService {
@Cacheable("user")
public User getUserById(int id) {
System.out.println("从数据库查询用户:" + id);
return new User(id, "Tom");
}
}
// 3. 实现AOP切面(使用反射读取注解)
@Aspect
@Component
public class CacheAspect {
private final Map cache = new ConcurrentHashMap<>();
@Around("@annotation(cacheable)")
public Object cacheMethod(ProceedingJoinPoint joinPoint, Cacheable cacheable) throws Throwable {
// 生成缓存key
String key = generateKey(joinPoint, cacheable);
// 检查缓存
CacheEntry entry = cache.get(key);
if (entry != null && !entry.isExpired()) {
System.out.println("从缓存返回:" + key);
return entry.getValue();
}
// 执行原方法
Object result = joinPoint.proceed();
// 存入缓存
cache.put(key, new CacheEntry(result, cacheable.timeout()));
return result;
}
private String generateKey(ProceedingJoinPoint joinPoint, Cacheable cacheable) {
return cacheable.value() + ":" + Arrays.toString(joinPoint.getArgs());
}
static class CacheEntry {
private final Object value;
private final long expireTime;
public CacheEntry(Object value, long timeout) {
this.value = value;
this.expireTime = System.currentTimeMillis() + timeout * 1000;
}
public boolean isExpired() {
return System.currentTimeMillis() > expireTime;
}
public Object getValue() {
return value;
}
}
}
// 4. 使用
public class AnnotationDemo {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = context.getBean(UserService.class);
// 第一次调用:从数据库查询
User user1 = userService.getUserById(1);
// 第二次调用:从缓存返回
User user2 = userService.getUserById(1);
}
}
// 输出:
// 从数据库查询用户:1
// 从缓存返回:user:1
```
---
## 第十部分:注解的演进与现代应用
### 十六、Java 8+ 的注解新特性
#### 16.1 重复注解
```java
// Java 8引入重复注解,允许在同一元素上多次使用同一注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Schedule {
String cron();
}
// 定义容器注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Schedules {
Schedule[] value();
}
// 使用重复注解
@Schedule(cron = "0 0 * * * ?")
@Schedule(cron = "0 30 * * * ?")
public void task() {
System.out.println("执行定时任务");
}
// 读取重复注解
Method method = MyClass.class.getMethod("task");
Schedule[] schedules = method.getAnnotationsByType(Schedule.class);
for (Schedule schedule : schedules) {
System.out.println("Cron表达式:" + schedule.cron());
}
```
#### 16.2 类型注解
```java
// Java 8引入类型注解,可以在任何使用类型的地方使用注解
@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.RUNTIME)
public @interface NonNull {
}
// 使用类型注解
public class TypeAnnotationDemo {
// 注解方法返回类型
public @NonNull String getName() {
return "Tom";
}
// 注解参数类型
public void setName(@NonNull String name) {
}
// 注解泛型类型
List<@NonNull String> names = new ArrayList<>();
// 注解数组类型
@NonNull String[] array = new String[10];
}
```
### 十七、现代框架中的注解应用
#### 17.1 Spring Boot的注解
```java
// Spring Boot大量使用注解简化配置
@SpringBootApplication // 组合注解
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public User getUser(@PathVariable int id) {
return userService.getUserById(id);
}
@PostMapping
public void saveUser(@RequestBody User user) {
userService.save(user);
}
}
```
#### 17.2 微服务框架中的注解
```java
// Feign使用注解定义RPC接口
@FeignClient("user-service")
public interface UserServiceClient {
@GetMapping("/api/users/{id}")
User getUserById(@PathVariable int id);
@PostMapping("/api/users")
void saveUser(@RequestBody User user);
}
// Dubbo使用注解定义服务
@DubboService
public class UserServiceImpl implements UserService {
@Override
public User getUserById(int id) {
return new User(id, "Tom");
}
}
@DubboReference
private UserService userService;
```
#### 17.3 数据验证注解
```java
// JSR-303/JSR-380提供的数据验证注解
public class User {
@NotNull(message = "用户名不能为空")
private String name;
@Min(value = 0, message = "年龄不能为负数")
@Max(value = 150, message = "年龄不能超过150岁")
private int age;
@Email(message = "邮箱格式不正确")
private String email;
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
}
// 在Controller中使用
@PostMapping("/users")
public void saveUser(@Valid @RequestBody User user) {
// Spring自动验证User对象
// 如果验证失败,返回400错误
}
```
---
## 第十一部分:总结
### 十八、注解的本质思考
**注解是Java语言提供的一种元数据机制,通过标签 + 反射 + 处理器的组合,实现了"配置驱动"和"约定优于配置"的编程范式。**
```
┌──────────────────────────────────────────┐
│ 注解的核心价值 │
├──────────────────────────────────────────┤
│ │
│ 1. 简化配置 │
│ ├─ 从XML配置到注解配置 │
│ ├─ 代码更简洁 │
│ └─ 配置更直观 │
│ │
│ 2. 提升开发效率 │
│ ├─ 减少重复代码 │
│ ├─ 自动化处理 │
│ └─ 框架自动织入增强逻辑 │
│ │
│ 3. 增强可维护性 │
│ ├─ 配置和代码在一起 │
│ ├─ 易于理解和修改 │
│ └─ 减少配置文件 │
│ │
│ 4. 支持框架创新 │
│ ├─ 实现AOP │
│ ├─ 实现依赖注入 │
│ ├─ 实现ORM │
│ └─ 实现RPC │
│ │
└──────────────────────────────────────────┘
```
### 十九、注解的生命周期总结
```
┌──────────────────────────────────────────┐
│ 注解的完整生命周期 │
├──────────────────────────────────────────┤
│ │
│ 编写阶段 │
│ ├─ 定义注解(@interface) │
│ ├─ 配置元注解(@Target、@Retention等) │
│ └─ 在代码上使用注解 │
│ │
│ 编译阶段 │
│ ├─ 编译器扫描注解 │
│ ├─ APT处理器处理注解(如果有) │
│ ├─ 生成.class文件 │
│ └─ 注解信息存储在.class文件中 │
│ │
│ 运行阶段 │
│ ├─ JVM加载.class文件 │
│ ├─ 注解信息存储在常量池中 │
│ ├─ 框架通过反射读取注解 │
│ ├─ 根据注解信息执行相应逻辑 │
│ └─ 织入增强逻辑 │
│ │
└──────────────────────────────────────────┘
```
### 二十、注解的三个层次
```
第1层:基础机制
├─ 注解定义(@interface)
├─ 元注解(@Target、@Retention等)
└─ 注解成员(基本类型、String、Enum等)
第2层:处理机制
├─ 编译期处理(APT)
├─ 运行期处理(反射)
└─ 动态代理织入
第3层:应用框架
├─ Spring(@Component、@Autowired等)
├─ JPA(@Entity、@Column等)
├─ Feign(@FeignClient等)
└─ 自定义注解
```
---
## 二十一、关键要点回顾
### 21.1 注解的核心概念
| 概念 | 说明 | 用途 |
| --- | --- | --- |
| **@interface** | 定义注解 | 创建自定义注解 |
| **@Target** | 指定注解位置 | 限制注解使用范围 |
| **@Retention** | 指定注解生命周期 | 控制注解何时有效 |
| **@Documented** | JavaDoc支持 | 注解出现在文档中 |
| **@Inherited** | 继承性 | 子类继承父类注解 |
| **APT** | 注解处理器 | 编译期处理注解 |
| **反射** | 运行期读取 | 读取注解信息 |
### 21.2 注解的三个生命周期
| 阶段 | SOURCE | CLASS | RUNTIME |
| --- | --- | --- | --- |
| 源代码 | ✅ | ✅ | ✅ |
| .class文件 | ❌ | ✅ | ✅ |
| 运行时 | ❌ | ❌ | ✅ |
| 反射读取 | ❌ | ❌ | ✅ |
### 21.3 注解成员的支持类型
```
✅ 支持的类型:
├─ 基本类型(int、boolean等)
├─ String
├─ Enum
├─ Class>
├─ Annotation
└─ 以上类型的数组
❌ 不支持的类型:
├─ Object
├─ 泛型
├─ 自定义类
└─ List、Map等集合
```
---
## 二十二、记忆口诀
```
注解是标签,元数据来标记
@interface定义,元注解来配置
@Target限位置,@Retention控生命
SOURCE编译丢,CLASS文件存,RUNTIME反射见
APT编译期,反射运行期,动态代理织入
Spring用注解,配置变简单
@Component扫描,@Autowired注入
@Transactional事务,@RequestMapping路由
注解本身无行为,处理器来执行
反射读取注解,代理织入逻辑
框架驱动注解,用户无感知
缓存注解信息,性能要优化
避免频繁反射,热点代码谨慎
编译期优于运行,代码生成最高效
```
---
## 二十三、相关链接
- 01-Class对象与反射机制 - 注解处理的基础
- [[7-Blog/后端与微服务/14-动态代理|动态代理]] - 注解驱动AOP的实现
- Spring AOP(面向切面编程) - 注解在AOP中的应用
- [[7-Blog/后端与微服务/15-Spring IOC(控制反转)|Spring IOC(控制反转)]] - 注解在依赖注入中的应用
---
## 二十四、常见问题解答
### Q1: 注解和注释有什么区别?
**答:**
- **注释(Comment)** - 给程序员看的说明文字,编译时被丢弃
- **注解(Annotation)** - 给编译器或框架看的元数据,可以在运行时保留
### Q2: 为什么注解成员不能是Object类型?
**答:**
- 注解成员的值必须是编译期常量
- Object类型无法在编译期确定
- 只支持基本类型、String、Enum、Class等编译期可确定的类型
### Q3: @Retention(RetentionPolicy.CLASS)有什么用?
**答:**
- 注解保留在.class文件中
- 但运行时不可见(无法通过反射读取)
- 用于字节码处理工具(如字节码增强框架)
### Q4: 如何自定义注解处理器?
**答:**
- 实现`AbstractProcessor`接口
- 重写`process()`方法
- 使用`@SupportedAnnotationTypes`指定处理的注解
- 在编译时自动调用
### Q5: 注解能否继承?
**答:**
- 默认不继承
- 使用`@Inherited`元注解可以使子类继承父类的注解
- 只对类级别的注解有效,方法和字段上的注解不继承
### Q6: 如何读取注解的所有信息?
**答:**
```java
// 获取类上的所有注解
Annotation[] annotations = clazz.getAnnotations();
// 获取特定注解
MyAnnotation annotation = clazz.getAnnotation(MyAnnotation.class);
// 检查是否有某个注解
boolean hasAnnotation = clazz.isAnnotationPresent(MyAnnotation.class);
// 获取注解的所有属性值
Method[] methods = MyAnnotation.class.getDeclaredMethods();
for (Method method : methods) {
Object value = method.invoke(annotation);
}
```
---