📚 系列文章:这是Java注解深度学习系列的第一篇,专注于基础概念的讲解

📖 目录


🎯 什么是注解?

形象比喻

想象你在整理书籍,需要给每本书贴上不同颜色的标签:

  • 🔴 红色标签:重要的书,需要经常翻阅
  • 🟡 黄色标签:参考书,偶尔查阅
  • 🟢 绿色标签:已读完的书,可以借给朋友

Java注解就像这些标签,它们:

  • 不改变书的内容(不改变代码逻辑)
  • 告诉你如何处理这本书(告诉编译器、框架如何处理代码)
  • 提供额外信息(元数据)

技术定义

注解(Annotation)是Java 5.0引入的一种元数据机制,用于:

  • 为代码元素提供额外信息
  • 不直接影响程序的执行逻辑
  • 可以在编译期、类加载期或运行期被读取和处理
// 最简单的注解示例
@Override  // 这就是一个注解!
public String toString() {
    return "Hello Annotation";
}

🔬 注解的本质

从字节码角度理解

让我们看看注解到底是什么:

// 源代码
public class User {
    @Deprecated
    public void oldMethod() {
        System.out.println("这是一个过时的方法");
    }
}

编译后,注解信息会被存储在class文件中:

// 简化的字节码表示
class User {
    @Deprecated  // 注解信息保存在这里
    public void oldMethod() { ... }
}

注解的实现原理

  1. 编译时:编译器将注解信息写入class文件
  2. 运行时:JVM通过反射机制读取注解信息
  3. 框架处理:Spring等框架基于注解信息进行相应处理
// 运行时读取注解的示例
Method method = User.class.getMethod("oldMethod");
if (method.isAnnotationPresent(Deprecated.class)) {
    System.out.println("这个方法被标记为过时");
}

📝 注解的语法规则

基本语法

@注解名称
@注解名称(参数)
@注解名称(参数1 =1, 参数2 =2)

使用位置

注解可以应用在多种代码元素上:

@Entity  // 类级别注解
public class User {
    
    @Id  // 字段级别注解
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "user_name", length = 50)  // 字段注解(带参数)
    private String name;
    
    @Override  // 方法级别注解
    @Transactional(readOnly = true)  // 方法注解(带参数)
    public String toString() {
        return "User{" + "name='" + name + '\'' + '}';
    }
    
    public void setName(@NotNull String name) {  // 参数级别注解
        this.name = name;
    }
}

注解的参数类型

public @interface MyAnnotation {
    String value();                    // 字符串参数
    int count() default 1;             // 整数参数(带默认值)
    Class<?> type() default Object.class;  // 类参数
    String[] tags() default {};       // 数组参数
    RetentionPolicy policy() default RetentionPolicy.RUNTIME;  // 枚举参数
}

// 使用示例
@MyAnnotation(
    value = "示例",
    count = 5,
    type = String.class,
    tags = {"tag1", "tag2"},
    policy = RetentionPolicy.RUNTIME
)
public class Example { }

🏷️ 注解的分类

1. 按来源分类

内置注解(JDK提供)
@Override      // 重写方法检查
@Deprecated    // 标记过时
@SuppressWarnings("unused")  // 抑制警告
@FunctionalInterface  // 函数式接口标记(Java 8+)
第三方注解(框架提供)
// Spring框架
@Component, @Service, @Repository, @Controller
@Autowired, @Value, @Transactional

// Lombok框架  
@Data, @Getter, @Setter, @Builder

// MyBatis框架
@Mapper, @Select, @Insert, @Update, @Delete
自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyCustomAnnotation {
    String value();
}

2. 按作用时机分类

编译期注解
  • 在编译时发挥作用
  • 例:@Override@Deprecated
class Parent {
    public void method() { }
}

class Child extends Parent {
    @Override  // 编译器检查是否正确重写
    public void method() { }  // 编译时验证
}
运行期注解
  • 在程序运行时通过反射处理
  • 例:Spring的@Autowired@Transactional
@Service  // 运行时Spring容器处理
public class UserService {
    
    @Autowired  // 运行时依赖注入
    private UserRepository userRepository;
}

⏰ 注解的生命周期

保留策略(RetentionPolicy)

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.SOURCE)   // 源码期:编译后丢弃
public @interface SourceAnnotation { }

@Retention(RetentionPolicy.CLASS)    // 字节码期:运行时不可见(默认)
public @interface ClassAnnotation { }

@Retention(RetentionPolicy.RUNTIME)  // 运行期:运行时可通过反射获取
public @interface RuntimeAnnotation { }

生命周期对比

阶段 SOURCE CLASS RUNTIME
源码中
字节码中
运行时
典型应用 Lombok 字节码增强 Spring框架

实际示例

// SOURCE示例:Lombok的@Data在编译后就消失了
@Data  // 编译时生成代码,运行时注解消失
public class User {
    private String name;
    // 编译后会有getName()、setName()等方法
}

// RUNTIME示例:Spring的注解需要运行时处理
@Service  // 运行时Spring会扫描并创建Bean
public class UserService {
    // Spring在运行时通过反射处理这个注解
}

🔧 内置注解详解

@Override - 重写方法验证

class Animal {
    public void makeSound() {
        System.out.println("动物发出声音");
    }
    
    public void eat(String food) {
        System.out.println("动物吃" + food);
    }
}

class Dog extends Animal {
    
    @Override
    public void makeSound() {  // ✅ 正确重写
        System.out.println("汪汪汪");
    }
    
    // @Override
    // public void makSound() {  // ❌ 编译错误:方法名拼写错误
    //     System.out.println("汪汪汪");
    // }
    
    @Override
    public void eat(String food) {  // ✅ 正确重写
        System.out.println("狗狗吃" + food);
    }
    
    // @Override
    // public void eat(int amount) {  // ❌ 编译错误:参数类型不匹配
    //     System.out.println("吃了" + amount + "份食物");
    // }
}

@Override的好处

  • 防止方法名拼写错误
  • 确保方法签名正确
  • 代码更安全可靠

@Deprecated - 过时标记

public class Calculator {
    
    @Deprecated  // 标记为过时方法
    public int add(int a, int b) {
        return a + b;
    }
    
    // 推荐使用的新方法
    public long addLong(long a, long b) {
        return a + b;
    }
}

// 使用效果
public class Main {
    public static void main(String[] args) {
        Calculator calc = new Calculator();
        
        // IDE会显示删除线和警告
        int result = calc.add(1, 2);  // 过时方法调用警告
        
        long result2 = calc.addLong(1L, 2L);  // 推荐方法
    }
}

@SuppressWarnings - 抑制警告

public class WarningExample {
    
    @SuppressWarnings("unused")  // 抑制"未使用变量"警告
    public void method1() {
        String unusedVariable = "这个变量没有被使用";
        // 正常情况下IDE会警告,但@SuppressWarnings会抑制
    }
    
    @SuppressWarnings({"unchecked", "rawtypes"})  // 抑制多种警告
    public void method2() {
        List list = new ArrayList();  // 原始类型警告被抑制
        list.add("item");
    }
    
    @SuppressWarnings("all")  // 抑制所有警告(不推荐)
    public void method3() {
        // 所有警告都会被抑制
    }
}

常见警告类型

  • "unused" - 未使用的变量
  • "unchecked" - 未检查的类型转换
  • "rawtypes" - 使用原始类型
  • "deprecation" - 使用过时的API
  • "all" - 所有警告

🛠️ 自定义注解入门

定义注解的基本步骤

import java.lang.annotation.*;

// 1. 使用@interface定义注解
@Retention(RetentionPolicy.RUNTIME)  // 运行时保留
@Target(ElementType.METHOD)          // 只能用在方法上
@Documented                          // 包含在JavaDoc中
public @interface LogExecutionTime {
    
    // 2. 定义注解参数
    String value() default "";           // 默认参数
    boolean enable() default true;       // 是否启用
    int timeout() default 5000;         // 超时时间(毫秒)
}

元注解详解

// @Retention:注解的保留策略
@Retention(RetentionPolicy.RUNTIME)   // 运行时保留
@Retention(RetentionPolicy.CLASS)     // 编译时保留(默认)
@Retention(RetentionPolicy.SOURCE)    // 源码时保留

// @Target:注解的使用位置
@Target(ElementType.TYPE)             // 类、接口、枚举
@Target(ElementType.METHOD)           // 方法
@Target(ElementType.FIELD)            // 字段
@Target(ElementType.PARAMETER)        // 方法参数
@Target(ElementType.CONSTRUCTOR)      // 构造函数
@Target({ElementType.METHOD, ElementType.FIELD})  // 多个位置

// @Documented:是否包含在JavaDoc中
@Documented
public @interface MyAnnotation { }

// @Inherited:是否可被子类继承
@Inherited
public @interface InheritedAnnotation { }

使用自定义注解

public class UserService {
    
    @LogExecutionTime("用户查询")  // 使用默认参数
    public User findUser(Long id) {
        // 查询用户逻辑
        return new User();
    }
    
    @LogExecutionTime(
        value = "用户创建",
        enable = true,
        timeout = 10000
    )  // 使用自定义参数
    public void createUser(User user) {
        // 创建用户逻辑
    }
}

通过反射处理注解

import java.lang.reflect.Method;

public class AnnotationProcessor {
    
    public static void processAnnotations(Class<?> clazz) {
        // 获取类的所有方法
        Method[] methods = clazz.getDeclaredMethods();
        
        for (Method method : methods) {
            // 检查方法是否有LogExecutionTime注解
            if (method.isAnnotationPresent(LogExecutionTime.class)) {
                
                // 获取注解实例
                LogExecutionTime annotation = method.getAnnotation(LogExecutionTime.class);
                
                // 读取注解参数
                String methodName = annotation.value();
                boolean enabled = annotation.enable();
                int timeout = annotation.timeout();
                
                System.out.println("方法: " + method.getName());
                System.out.println("描述: " + methodName);
                System.out.println("启用: " + enabled);
                System.out.println("超时: " + timeout + "ms");
                System.out.println("---");
            }
        }
    }
    
    public static void main(String[] args) {
        processAnnotations(UserService.class);
    }
}

📚 总结

关键要点

  1. 注解是元数据:为代码提供额外信息,不改变执行逻辑
  2. 三个生命周期:SOURCE(源码期)、CLASS(字节码期)、RUNTIME(运行期)
  3. 四种应用场景:编译检查、运行处理、代码生成、配置简化
  4. 内置注解要掌握@Override@Deprecated@SuppressWarnings
  5. 自定义注解:使用@interface定义,配合元注解使用

学习路径

  1. 掌握基础概念(本篇)
  2. 📖 学习四大作用(下一篇:Java注解四大作用详解)
  3. 🚀 Spring注解实战(第三篇:Spring注解实战指南)

实践建议

  • 多看开源项目中的注解使用
  • 动手编写简单的自定义注解
  • 理解注解背后的反射机制
  • 结合具体框架学习注解应用

🔗 下一篇预告Java注解四大作用详解 - 深入理解编译时检查、运行时处理、代码生成和配置简化四大核心功能


Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐