--- 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 annotations, RoundEnvironment roundEnv) { // 处理注解 for (TypeElement annotation : annotations) { Set 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); } ``` ---